aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/+login
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2022-10-07 11:06:28 +0200
committerChocobozzz <me@florianbigard.com>2022-10-07 11:06:28 +0200
commitd12b40fb96d56786a96c06a621f3d8e0a0d24f4a (patch)
tree7047fa5cd7e778eb377c897eccb539c52b2e59bc /client/src/app/+login
parent56f47830758ff8e92abcfcc5f35d474ab12fe215 (diff)
downloadPeerTube-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.html46
-rw-r--r--client/src/app/+login/login.component.ts38
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'
4import { AuthService, Notifier, RedirectService, SessionStorageService, UserService } from '@app/core' 4import { AuthService, Notifier, RedirectService, SessionStorageService, UserService } from '@app/core'
5import { HooksService } from '@app/core/plugins/hooks.service' 5import { HooksService } from '@app/core/plugins/hooks.service'
6import { LOGIN_PASSWORD_VALIDATOR, LOGIN_USERNAME_VALIDATOR } from '@app/shared/form-validators/login-validators' 6import { LOGIN_PASSWORD_VALIDATOR, LOGIN_USERNAME_VALIDATOR } from '@app/shared/form-validators/login-validators'
7import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 7import { USER_OTP_TOKEN_VALIDATOR } from '@app/shared/form-validators/user-validators'
8import { FormReactive, FormValidatorService, InputTextComponent } from '@app/shared/shared-forms'
8import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance' 9import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance'
9import { NgbAccordion, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' 10import { NgbAccordion, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
10import { PluginsManager } from '@root-helpers/plugins-manager' 11import { 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