From d12b40fb96d56786a96c06a621f3d8e0a0d24f4a Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 7 Oct 2022 11:06:28 +0200 Subject: Implement two factor in client --- client/src/app/core/auth/auth.service.ts | 23 +++++++++-- client/src/app/core/confirm/confirm.service.ts | 47 +++++++++++++++++----- client/src/app/core/rest/rest-extractor.service.ts | 6 ++- client/src/app/core/users/user.model.ts | 4 ++ 4 files changed, 64 insertions(+), 16 deletions(-) (limited to 'client/src/app/core') diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts index ca46866f5..7f4fae4aa 100644 --- a/client/src/app/core/auth/auth.service.ts +++ b/client/src/app/core/auth/auth.service.ts @@ -1,7 +1,7 @@ import { Hotkey, HotkeysService } from 'angular2-hotkeys' import { Observable, ReplaySubject, Subject, throwError as observableThrowError } from 'rxjs' import { catchError, map, mergeMap, share, tap } from 'rxjs/operators' -import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http' +import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http' import { Injectable } from '@angular/core' import { Router } from '@angular/router' import { Notifier } from '@app/core/notification/notifier.service' @@ -141,7 +141,14 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular return !!this.getAccessToken() } - login (username: string, password: string, token?: string) { + login (options: { + username: string + password: string + otpToken?: string + token?: string + }) { + const { username, password, token, otpToken } = options + // Form url encoded const body = { client_id: this.clientId, @@ -155,7 +162,9 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular if (token) Object.assign(body, { externalAuthToken: token }) - const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded') + let headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded') + if (otpToken) headers = headers.set('x-peertube-otp', otpToken) + return this.http.post(AuthService.BASE_TOKEN_URL, objectToUrlEncoded(body), { headers }) .pipe( map(res => Object.assign(res, { username })), @@ -245,6 +254,14 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular }) } + isOTPMissingError (err: HttpErrorResponse) { + if (err.status !== HttpStatusCode.UNAUTHORIZED_401) return false + + if (err.headers.get('x-peertube-otp') !== 'required; app') return false + + return true + } + private mergeUserInformation (obj: UserLoginWithUsername): Observable { // User is not loaded yet, set manually auth header const headers = new HttpHeaders().set('Authorization', `${obj.token_type} ${obj.access_token}`) diff --git a/client/src/app/core/confirm/confirm.service.ts b/client/src/app/core/confirm/confirm.service.ts index 338b8762c..89a25f0a5 100644 --- a/client/src/app/core/confirm/confirm.service.ts +++ b/client/src/app/core/confirm/confirm.service.ts @@ -1,28 +1,53 @@ -import { firstValueFrom, Subject } from 'rxjs' +import { firstValueFrom, map, Observable, Subject } from 'rxjs' import { Injectable } from '@angular/core' type ConfirmOptions = { title: string message: string - inputLabel?: string - expectedInputValue?: string - confirmButtonText?: string -} +} & ( + { + type: 'confirm' + confirmButtonText?: string + } | + { + type: 'confirm-password' + confirmButtonText?: string + } | + { + type: 'confirm-expected-input' + inputLabel?: string + expectedInputValue?: string + confirmButtonText?: string + } +) @Injectable() export class ConfirmService { showConfirm = new Subject() - confirmResponse = new Subject() + confirmResponse = new Subject<{ confirmed: boolean, value?: string }>() confirm (message: string, title = '', confirmButtonText?: string) { - this.showConfirm.next({ title, message, confirmButtonText }) + this.showConfirm.next({ type: 'confirm', title, message, confirmButtonText }) + + return firstValueFrom(this.extractConfirmed(this.confirmResponse.asObservable())) + } - return firstValueFrom(this.confirmResponse.asObservable()) + confirmWithPassword (message: string, title = '', confirmButtonText?: string) { + this.showConfirm.next({ type: 'confirm-password', title, message, confirmButtonText }) + + const obs = this.confirmResponse.asObservable() + .pipe(map(({ confirmed, value }) => ({ confirmed, password: value }))) + + return firstValueFrom(obs) } - confirmWithInput (message: string, inputLabel: string, expectedInputValue: string, title = '', confirmButtonText?: string) { - this.showConfirm.next({ title, message, inputLabel, expectedInputValue, confirmButtonText }) + confirmWithExpectedInput (message: string, inputLabel: string, expectedInputValue: string, title = '', confirmButtonText?: string) { + this.showConfirm.next({ type: 'confirm-expected-input', title, message, inputLabel, expectedInputValue, confirmButtonText }) + + return firstValueFrom(this.extractConfirmed(this.confirmResponse.asObservable())) + } - return firstValueFrom(this.confirmResponse.asObservable()) + private extractConfirmed (obs: Observable<{ confirmed: boolean }>) { + return obs.pipe(map(({ confirmed }) => confirmed)) } } diff --git a/client/src/app/core/rest/rest-extractor.service.ts b/client/src/app/core/rest/rest-extractor.service.ts index 7eec2eca6..57dd9ae26 100644 --- a/client/src/app/core/rest/rest-extractor.service.ts +++ b/client/src/app/core/rest/rest-extractor.service.ts @@ -4,6 +4,7 @@ import { Router } from '@angular/router' import { DateFormat, dateToHuman } from '@app/helpers' import { logger } from '@root-helpers/logger' import { HttpStatusCode, ResultList } from '@shared/models' +import { HttpHeaderResponse } from '@angular/common/http' @Injectable() export class RestExtractor { @@ -54,10 +55,11 @@ export class RestExtractor { handleError (err: any) { const errorMessage = this.buildErrorMessage(err) - const errorObj: { message: string, status: string, body: string } = { + const errorObj: { message: string, status: string, body: string, headers: HttpHeaderResponse } = { message: errorMessage, status: undefined, - body: undefined + body: undefined, + headers: err.headers } if (err.status) { diff --git a/client/src/app/core/users/user.model.ts b/client/src/app/core/users/user.model.ts index 6ba30e4b8..8385a4012 100644 --- a/client/src/app/core/users/user.model.ts +++ b/client/src/app/core/users/user.model.ts @@ -66,6 +66,8 @@ export class User implements UserServerModel { lastLoginDate: Date | null + twoFactorEnabled: boolean + createdAt: Date constructor (hash: Partial) { @@ -108,6 +110,8 @@ export class User implements UserServerModel { this.notificationSettings = hash.notificationSettings + this.twoFactorEnabled = hash.twoFactorEnabled + this.createdAt = hash.createdAt this.pluginAuth = hash.pluginAuth -- cgit v1.2.3