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/+login | |
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/+login')
-rw-r--r-- | client/src/app/+login/login.component.html | 46 | ||||
-rw-r--r-- | client/src/app/+login/login.component.ts | 38 |
2 files changed, 62 insertions, 22 deletions
diff --git a/client/src/app/+login/login.component.html b/client/src/app/+login/login.component.html index f3a2476f9..49b443a20 100644 --- a/client/src/app/+login/login.component.html +++ b/client/src/app/+login/login.component.html | |||
@@ -39,34 +39,48 @@ | |||
39 | <div class="login-form-and-externals"> | 39 | <div class="login-form-and-externals"> |
40 | 40 | ||
41 | <form myPluginSelector pluginSelectorId="login-form" role="form" (ngSubmit)="login()" [formGroup]="form"> | 41 | <form myPluginSelector pluginSelectorId="login-form" role="form" (ngSubmit)="login()" [formGroup]="form"> |
42 | <div class="form-group"> | 42 | <ng-container *ngIf="!otpStep"> |
43 | <div> | 43 | <div class="form-group"> |
44 | <label i18n for="username">Username or email address</label> | 44 | <div> |
45 | <input | 45 | <label i18n for="username">Username or email address</label> |
46 | type="text" id="username" i18n-placeholder placeholder="Example: john@example.com" required tabindex="1" | 46 | <input |
47 | formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" myAutofocus | 47 | type="text" id="username" i18n-placeholder placeholder="Example: john@example.com" required tabindex="1" |
48 | > | 48 | formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" myAutofocus |
49 | > | ||
50 | </div> | ||
51 | |||
52 | <div *ngIf="formErrors.username" class="form-error">{{ formErrors.username }}</div> | ||
53 | |||
54 | <div *ngIf="hasUsernameUppercase()" i18n class="form-warning"> | ||
55 | ⚠️ Most email addresses do not include capital letters. | ||
56 | </div> | ||
49 | </div> | 57 | </div> |
50 | 58 | ||
51 | <div *ngIf="formErrors.username" class="form-error">{{ formErrors.username }}</div> | 59 | <div class="form-group"> |
60 | <label i18n for="password">Password</label> | ||
52 | 61 | ||
53 | <div *ngIf="hasUsernameUppercase()" i18n class="form-warning"> | 62 | <my-input-text |
54 | ⚠️ Most email addresses do not include capital letters. | 63 | formControlName="password" inputId="password" i18n-placeholder placeholder="Password" |
64 | [formError]="formErrors['password']" autocomplete="current-password" [tabindex]="2" | ||
65 | ></my-input-text> | ||
55 | </div> | 66 | </div> |
56 | </div> | 67 | </ng-container> |
68 | |||
69 | <div *ngIf="otpStep" class="form-group"> | ||
70 | <p i18n>Enter the two-factor code generated by your phone app:</p> | ||
57 | 71 | ||
58 | <div class="form-group"> | 72 | <label i18n for="otp-token">Two factor authentication token</label> |
59 | <label i18n for="password">Password</label> | ||
60 | 73 | ||
61 | <my-input-text | 74 | <my-input-text |
62 | formControlName="password" inputId="password" i18n-placeholder placeholder="Password" | 75 | #otpTokenInput |
63 | [formError]="formErrors['password']" autocomplete="current-password" [tabindex]="2" | 76 | [show]="true" formControlName="otp-token" inputId="otp-token" |
77 | [formError]="formErrors['otp-token']" autocomplete="otp-token" | ||
64 | ></my-input-text> | 78 | ></my-input-text> |
65 | </div> | 79 | </div> |
66 | 80 | ||
67 | <input type="submit" class="peertube-button orange-button" i18n-value value="Login" [disabled]="!form.valid"> | 81 | <input type="submit" class="peertube-button orange-button" i18n-value value="Login" [disabled]="!form.valid"> |
68 | 82 | ||
69 | <div class="additional-links"> | 83 | <div *ngIf="!otpStep" class="additional-links"> |
70 | <a i18n role="button" class="link-orange" (click)="openForgotPasswordModal()" i18n-title title="Click here to reset your password">I forgot my password</a> | 84 | <a i18n role="button" class="link-orange" (click)="openForgotPasswordModal()" i18n-title title="Click here to reset your password">I forgot my password</a> |
71 | 85 | ||
72 | <ng-container *ngIf="signupAllowed"> | 86 | <ng-container *ngIf="signupAllowed"> |
diff --git a/client/src/app/+login/login.component.ts b/client/src/app/+login/login.component.ts index 2ed9be16c..9095e43a7 100644 --- a/client/src/app/+login/login.component.ts +++ b/client/src/app/+login/login.component.ts | |||
@@ -4,7 +4,8 @@ import { ActivatedRoute, Router } from '@angular/router' | |||
4 | import { AuthService, Notifier, RedirectService, SessionStorageService, UserService } from '@app/core' | 4 | import { AuthService, Notifier, RedirectService, SessionStorageService, UserService } from '@app/core' |
5 | import { HooksService } from '@app/core/plugins/hooks.service' | 5 | import { HooksService } from '@app/core/plugins/hooks.service' |
6 | import { LOGIN_PASSWORD_VALIDATOR, LOGIN_USERNAME_VALIDATOR } from '@app/shared/form-validators/login-validators' | 6 | import { LOGIN_PASSWORD_VALIDATOR, LOGIN_USERNAME_VALIDATOR } from '@app/shared/form-validators/login-validators' |
7 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 7 | import { USER_OTP_TOKEN_VALIDATOR } from '@app/shared/form-validators/user-validators' |
8 | import { FormReactive, FormValidatorService, InputTextComponent } from '@app/shared/shared-forms' | ||
8 | import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance' | 9 | import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance' |
9 | import { NgbAccordion, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' | 10 | import { NgbAccordion, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' |
10 | import { PluginsManager } from '@root-helpers/plugins-manager' | 11 | import { PluginsManager } from '@root-helpers/plugins-manager' |
@@ -20,6 +21,7 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni | |||
20 | private static SESSION_STORAGE_REDIRECT_URL_KEY = 'login-previous-url' | 21 | private static SESSION_STORAGE_REDIRECT_URL_KEY = 'login-previous-url' |
21 | 22 | ||
22 | @ViewChild('forgotPasswordModal', { static: true }) forgotPasswordModal: ElementRef | 23 | @ViewChild('forgotPasswordModal', { static: true }) forgotPasswordModal: ElementRef |
24 | @ViewChild('otpTokenInput') otpTokenInput: InputTextComponent | ||
23 | 25 | ||
24 | accordion: NgbAccordion | 26 | accordion: NgbAccordion |
25 | error: string = null | 27 | error: string = null |
@@ -37,6 +39,8 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni | |||
37 | codeOfConduct: false | 39 | codeOfConduct: false |
38 | } | 40 | } |
39 | 41 | ||
42 | otpStep = false | ||
43 | |||
40 | private openedForgotPasswordModal: NgbModalRef | 44 | private openedForgotPasswordModal: NgbModalRef |
41 | private serverConfig: ServerConfig | 45 | private serverConfig: ServerConfig |
42 | 46 | ||
@@ -82,7 +86,11 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni | |||
82 | // Avoid undefined errors when accessing form error properties | 86 | // Avoid undefined errors when accessing form error properties |
83 | this.buildForm({ | 87 | this.buildForm({ |
84 | username: LOGIN_USERNAME_VALIDATOR, | 88 | username: LOGIN_USERNAME_VALIDATOR, |
85 | password: LOGIN_PASSWORD_VALIDATOR | 89 | password: LOGIN_PASSWORD_VALIDATOR, |
90 | 'otp-token': { | ||
91 | VALIDATORS: [], // Will be set dynamically | ||
92 | MESSAGES: USER_OTP_TOKEN_VALIDATOR.MESSAGES | ||
93 | } | ||
86 | }) | 94 | }) |
87 | 95 | ||
88 | this.serverConfig = snapshot.data.serverConfig | 96 | this.serverConfig = snapshot.data.serverConfig |
@@ -118,13 +126,20 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni | |||
118 | login () { | 126 | login () { |
119 | this.error = null | 127 | this.error = null |
120 | 128 | ||
121 | const { username, password } = this.form.value | 129 | const options = { |
130 | username: this.form.value['username'], | ||
131 | password: this.form.value['password'], | ||
132 | otpToken: this.form.value['otp-token'] | ||
133 | } | ||
122 | 134 | ||
123 | this.authService.login(username, password) | 135 | this.authService.login(options) |
136 | .pipe() | ||
124 | .subscribe({ | 137 | .subscribe({ |
125 | next: () => this.redirectService.redirectToPreviousRoute(), | 138 | next: () => this.redirectService.redirectToPreviousRoute(), |
126 | 139 | ||
127 | error: err => this.handleError(err) | 140 | error: err => { |
141 | this.handleError(err) | ||
142 | } | ||
128 | }) | 143 | }) |
129 | } | 144 | } |
130 | 145 | ||
@@ -162,7 +177,7 @@ The link will expire within 1 hour.` | |||
162 | private loadExternalAuthToken (username: string, token: string) { | 177 | private loadExternalAuthToken (username: string, token: string) { |
163 | this.isAuthenticatedWithExternalAuth = true | 178 | this.isAuthenticatedWithExternalAuth = true |
164 | 179 | ||
165 | this.authService.login(username, null, token) | 180 | this.authService.login({ username, password: null, token }) |
166 | .subscribe({ | 181 | .subscribe({ |
167 | next: () => { | 182 | next: () => { |
168 | const redirectUrl = this.storage.getItem(LoginComponent.SESSION_STORAGE_REDIRECT_URL_KEY) | 183 | const redirectUrl = this.storage.getItem(LoginComponent.SESSION_STORAGE_REDIRECT_URL_KEY) |
@@ -182,6 +197,17 @@ The link will expire within 1 hour.` | |||
182 | } | 197 | } |
183 | 198 | ||
184 | private handleError (err: any) { | 199 | private handleError (err: any) { |
200 | if (this.authService.isOTPMissingError(err)) { | ||
201 | this.otpStep = true | ||
202 | |||
203 | setTimeout(() => { | ||
204 | this.form.get('otp-token').setValidators(USER_OTP_TOKEN_VALIDATOR.VALIDATORS) | ||
205 | this.otpTokenInput.focus() | ||
206 | }) | ||
207 | |||
208 | return | ||
209 | } | ||
210 | |||
185 | if (err.message.indexOf('credentials are invalid') !== -1) this.error = $localize`Incorrect username or password.` | 211 | if (err.message.indexOf('credentials are invalid') !== -1) this.error = $localize`Incorrect username or password.` |
186 | else if (err.message.indexOf('blocked') !== -1) this.error = $localize`Your account is blocked.` | 212 | else if (err.message.indexOf('blocked') !== -1) this.error = $localize`Your account is blocked.` |
187 | else this.error = err.message | 213 | else this.error = err.message |