diff options
author | Chocobozzz <me@florianbigard.com> | 2022-10-07 11:06:28 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2022-10-07 11:06:28 +0200 |
commit | d12b40fb96d56786a96c06a621f3d8e0a0d24f4a (patch) | |
tree | 7047fa5cd7e778eb377c897eccb539c52b2e59bc /client/src/app/core | |
parent | 56f47830758ff8e92abcfcc5f35d474ab12fe215 (diff) | |
download | PeerTube-d12b40fb96d56786a96c06a621f3d8e0a0d24f4a.tar.gz PeerTube-d12b40fb96d56786a96c06a621f3d8e0a0d24f4a.tar.zst PeerTube-d12b40fb96d56786a96c06a621f3d8e0a0d24f4a.zip |
Implement two factor in client
Diffstat (limited to 'client/src/app/core')
-rw-r--r-- | client/src/app/core/auth/auth.service.ts | 23 | ||||
-rw-r--r-- | client/src/app/core/confirm/confirm.service.ts | 47 | ||||
-rw-r--r-- | client/src/app/core/rest/rest-extractor.service.ts | 6 | ||||
-rw-r--r-- | client/src/app/core/users/user.model.ts | 4 |
4 files changed, 64 insertions, 16 deletions
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 @@ | |||
1 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' | 1 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' |
2 | import { Observable, ReplaySubject, Subject, throwError as observableThrowError } from 'rxjs' | 2 | import { Observable, ReplaySubject, Subject, throwError as observableThrowError } from 'rxjs' |
3 | import { catchError, map, mergeMap, share, tap } from 'rxjs/operators' | 3 | import { catchError, map, mergeMap, share, tap } from 'rxjs/operators' |
4 | import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http' | 4 | import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http' |
5 | import { Injectable } from '@angular/core' | 5 | import { Injectable } from '@angular/core' |
6 | import { Router } from '@angular/router' | 6 | import { Router } from '@angular/router' |
7 | import { Notifier } from '@app/core/notification/notifier.service' | 7 | import { Notifier } from '@app/core/notification/notifier.service' |
@@ -141,7 +141,14 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular | |||
141 | return !!this.getAccessToken() | 141 | return !!this.getAccessToken() |
142 | } | 142 | } |
143 | 143 | ||
144 | login (username: string, password: string, token?: string) { | 144 | login (options: { |
145 | username: string | ||
146 | password: string | ||
147 | otpToken?: string | ||
148 | token?: string | ||
149 | }) { | ||
150 | const { username, password, token, otpToken } = options | ||
151 | |||
145 | // Form url encoded | 152 | // Form url encoded |
146 | const body = { | 153 | const body = { |
147 | client_id: this.clientId, | 154 | client_id: this.clientId, |
@@ -155,7 +162,9 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular | |||
155 | 162 | ||
156 | if (token) Object.assign(body, { externalAuthToken: token }) | 163 | if (token) Object.assign(body, { externalAuthToken: token }) |
157 | 164 | ||
158 | const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded') | 165 | let headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded') |
166 | if (otpToken) headers = headers.set('x-peertube-otp', otpToken) | ||
167 | |||
159 | return this.http.post<UserLogin>(AuthService.BASE_TOKEN_URL, objectToUrlEncoded(body), { headers }) | 168 | return this.http.post<UserLogin>(AuthService.BASE_TOKEN_URL, objectToUrlEncoded(body), { headers }) |
160 | .pipe( | 169 | .pipe( |
161 | map(res => Object.assign(res, { username })), | 170 | map(res => Object.assign(res, { username })), |
@@ -245,6 +254,14 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular | |||
245 | }) | 254 | }) |
246 | } | 255 | } |
247 | 256 | ||
257 | isOTPMissingError (err: HttpErrorResponse) { | ||
258 | if (err.status !== HttpStatusCode.UNAUTHORIZED_401) return false | ||
259 | |||
260 | if (err.headers.get('x-peertube-otp') !== 'required; app') return false | ||
261 | |||
262 | return true | ||
263 | } | ||
264 | |||
248 | private mergeUserInformation (obj: UserLoginWithUsername): Observable<UserLoginWithUserInformation> { | 265 | private mergeUserInformation (obj: UserLoginWithUsername): Observable<UserLoginWithUserInformation> { |
249 | // User is not loaded yet, set manually auth header | 266 | // User is not loaded yet, set manually auth header |
250 | const headers = new HttpHeaders().set('Authorization', `${obj.token_type} ${obj.access_token}`) | 267 | 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 @@ | |||
1 | import { firstValueFrom, Subject } from 'rxjs' | 1 | import { firstValueFrom, map, Observable, Subject } from 'rxjs' |
2 | import { Injectable } from '@angular/core' | 2 | import { Injectable } from '@angular/core' |
3 | 3 | ||
4 | type ConfirmOptions = { | 4 | type ConfirmOptions = { |
5 | title: string | 5 | title: string |
6 | message: string | 6 | message: string |
7 | inputLabel?: string | 7 | } & ( |
8 | expectedInputValue?: string | 8 | { |
9 | confirmButtonText?: string | 9 | type: 'confirm' |
10 | } | 10 | confirmButtonText?: string |
11 | } | | ||
12 | { | ||
13 | type: 'confirm-password' | ||
14 | confirmButtonText?: string | ||
15 | } | | ||
16 | { | ||
17 | type: 'confirm-expected-input' | ||
18 | inputLabel?: string | ||
19 | expectedInputValue?: string | ||
20 | confirmButtonText?: string | ||
21 | } | ||
22 | ) | ||
11 | 23 | ||
12 | @Injectable() | 24 | @Injectable() |
13 | export class ConfirmService { | 25 | export class ConfirmService { |
14 | showConfirm = new Subject<ConfirmOptions>() | 26 | showConfirm = new Subject<ConfirmOptions>() |
15 | confirmResponse = new Subject<boolean>() | 27 | confirmResponse = new Subject<{ confirmed: boolean, value?: string }>() |
16 | 28 | ||
17 | confirm (message: string, title = '', confirmButtonText?: string) { | 29 | confirm (message: string, title = '', confirmButtonText?: string) { |
18 | this.showConfirm.next({ title, message, confirmButtonText }) | 30 | this.showConfirm.next({ type: 'confirm', title, message, confirmButtonText }) |
31 | |||
32 | return firstValueFrom(this.extractConfirmed(this.confirmResponse.asObservable())) | ||
33 | } | ||
19 | 34 | ||
20 | return firstValueFrom(this.confirmResponse.asObservable()) | 35 | confirmWithPassword (message: string, title = '', confirmButtonText?: string) { |
36 | this.showConfirm.next({ type: 'confirm-password', title, message, confirmButtonText }) | ||
37 | |||
38 | const obs = this.confirmResponse.asObservable() | ||
39 | .pipe(map(({ confirmed, value }) => ({ confirmed, password: value }))) | ||
40 | |||
41 | return firstValueFrom(obs) | ||
21 | } | 42 | } |
22 | 43 | ||
23 | confirmWithInput (message: string, inputLabel: string, expectedInputValue: string, title = '', confirmButtonText?: string) { | 44 | confirmWithExpectedInput (message: string, inputLabel: string, expectedInputValue: string, title = '', confirmButtonText?: string) { |
24 | this.showConfirm.next({ title, message, inputLabel, expectedInputValue, confirmButtonText }) | 45 | this.showConfirm.next({ type: 'confirm-expected-input', title, message, inputLabel, expectedInputValue, confirmButtonText }) |
46 | |||
47 | return firstValueFrom(this.extractConfirmed(this.confirmResponse.asObservable())) | ||
48 | } | ||
25 | 49 | ||
26 | return firstValueFrom(this.confirmResponse.asObservable()) | 50 | private extractConfirmed (obs: Observable<{ confirmed: boolean }>) { |
51 | return obs.pipe(map(({ confirmed }) => confirmed)) | ||
27 | } | 52 | } |
28 | } | 53 | } |
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' | |||
4 | import { DateFormat, dateToHuman } from '@app/helpers' | 4 | import { DateFormat, dateToHuman } from '@app/helpers' |
5 | import { logger } from '@root-helpers/logger' | 5 | import { logger } from '@root-helpers/logger' |
6 | import { HttpStatusCode, ResultList } from '@shared/models' | 6 | import { HttpStatusCode, ResultList } from '@shared/models' |
7 | import { HttpHeaderResponse } from '@angular/common/http' | ||
7 | 8 | ||
8 | @Injectable() | 9 | @Injectable() |
9 | export class RestExtractor { | 10 | export class RestExtractor { |
@@ -54,10 +55,11 @@ export class RestExtractor { | |||
54 | handleError (err: any) { | 55 | handleError (err: any) { |
55 | const errorMessage = this.buildErrorMessage(err) | 56 | const errorMessage = this.buildErrorMessage(err) |
56 | 57 | ||
57 | const errorObj: { message: string, status: string, body: string } = { | 58 | const errorObj: { message: string, status: string, body: string, headers: HttpHeaderResponse } = { |
58 | message: errorMessage, | 59 | message: errorMessage, |
59 | status: undefined, | 60 | status: undefined, |
60 | body: undefined | 61 | body: undefined, |
62 | headers: err.headers | ||
61 | } | 63 | } |
62 | 64 | ||
63 | if (err.status) { | 65 | 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 { | |||
66 | 66 | ||
67 | lastLoginDate: Date | null | 67 | lastLoginDate: Date | null |
68 | 68 | ||
69 | twoFactorEnabled: boolean | ||
70 | |||
69 | createdAt: Date | 71 | createdAt: Date |
70 | 72 | ||
71 | constructor (hash: Partial<UserServerModel>) { | 73 | constructor (hash: Partial<UserServerModel>) { |
@@ -108,6 +110,8 @@ export class User implements UserServerModel { | |||
108 | 110 | ||
109 | this.notificationSettings = hash.notificationSettings | 111 | this.notificationSettings = hash.notificationSettings |
110 | 112 | ||
113 | this.twoFactorEnabled = hash.twoFactorEnabled | ||
114 | |||
111 | this.createdAt = hash.createdAt | 115 | this.createdAt = hash.createdAt |
112 | 116 | ||
113 | this.pluginAuth = hash.pluginAuth | 117 | this.pluginAuth = hash.pluginAuth |