aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/+my-account
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/+my-account')
-rw-r--r--client/src/app/+my-account/my-account-routing.module.ts11
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts4
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts4
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts2
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-settings.component.html10
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-two-factor/index.ts3
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-two-factor/my-account-two-factor-button.component.html12
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-two-factor/my-account-two-factor-button.component.ts49
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-two-factor/my-account-two-factor.component.html54
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-two-factor/my-account-two-factor.component.scss16
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-two-factor/my-account-two-factor.component.ts105
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-two-factor/two-factor.service.ts52
-rw-r--r--client/src/app/+my-account/my-account.module.ts14
13 files changed, 330 insertions, 6 deletions
diff --git a/client/src/app/+my-account/my-account-routing.module.ts b/client/src/app/+my-account/my-account-routing.module.ts
index ef39c1a36..b39b1f6b4 100644
--- a/client/src/app/+my-account/my-account-routing.module.ts
+++ b/client/src/app/+my-account/my-account-routing.module.ts
@@ -7,6 +7,7 @@ import { MyAccountBlocklistComponent } from './my-account-blocklist/my-account-b
7import { MyAccountServerBlocklistComponent } from './my-account-blocklist/my-account-server-blocklist.component' 7import { MyAccountServerBlocklistComponent } from './my-account-blocklist/my-account-server-blocklist.component'
8import { MyAccountNotificationsComponent } from './my-account-notifications/my-account-notifications.component' 8import { MyAccountNotificationsComponent } from './my-account-notifications/my-account-notifications.component'
9import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component' 9import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component'
10import { MyAccountTwoFactorComponent } from './my-account-settings/my-account-two-factor'
10import { MyAccountComponent } from './my-account.component' 11import { MyAccountComponent } from './my-account.component'
11 12
12const myAccountRoutes: Routes = [ 13const myAccountRoutes: Routes = [
@@ -31,6 +32,16 @@ const myAccountRoutes: Routes = [
31 }, 32 },
32 33
33 { 34 {
35 path: 'two-factor-auth',
36 component: MyAccountTwoFactorComponent,
37 data: {
38 meta: {
39 title: $localize`Two factor authentication`
40 }
41 }
42 },
43
44 {
34 path: 'video-channels', 45 path: 'video-channels',
35 redirectTo: '/my-library/video-channels', 46 redirectTo: '/my-library/video-channels',
36 pathMatch: 'full' 47 pathMatch: 'full'
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts
index 9b87daa40..9e6b8e21d 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts
+++ b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts
@@ -4,7 +4,7 @@ import { Component, OnInit } from '@angular/core'
4import { AuthService, ServerService, UserService } from '@app/core' 4import { AuthService, ServerService, UserService } from '@app/core'
5import { USER_EMAIL_VALIDATOR, USER_PASSWORD_VALIDATOR } from '@app/shared/form-validators/user-validators' 5import { USER_EMAIL_VALIDATOR, USER_PASSWORD_VALIDATOR } from '@app/shared/form-validators/user-validators'
6import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 6import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
7import { User } from '@shared/models' 7import { HttpStatusCode, User } from '@shared/models'
8 8
9@Component({ 9@Component({
10 selector: 'my-account-change-email', 10 selector: 'my-account-change-email',
@@ -57,7 +57,7 @@ export class MyAccountChangeEmailComponent extends FormReactive implements OnIni
57 }, 57 },
58 58
59 error: err => { 59 error: err => {
60 if (err.status === 401) { 60 if (err.status === HttpStatusCode.UNAUTHORIZED_401) {
61 this.error = $localize`You current password is invalid.` 61 this.error = $localize`You current password is invalid.`
62 return 62 return
63 } 63 }
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts
index 47e54dc23..dd405de33 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts
+++ b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts
@@ -7,7 +7,7 @@ import {
7 USER_PASSWORD_VALIDATOR 7 USER_PASSWORD_VALIDATOR
8} from '@app/shared/form-validators/user-validators' 8} from '@app/shared/form-validators/user-validators'
9import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 9import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
10import { User } from '@shared/models' 10import { HttpStatusCode, User } from '@shared/models'
11 11
12@Component({ 12@Component({
13 selector: 'my-account-change-password', 13 selector: 'my-account-change-password',
@@ -57,7 +57,7 @@ export class MyAccountChangePasswordComponent extends FormReactive implements On
57 }, 57 },
58 58
59 error: err => { 59 error: err => {
60 if (err.status === 401) { 60 if (err.status === HttpStatusCode.UNAUTHORIZED_401) {
61 this.error = $localize`You current password is invalid.` 61 this.error = $localize`You current password is invalid.`
62 return 62 return
63 } 63 }
diff --git a/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts b/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts
index 2bae3499e..9619623ee 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts
+++ b/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts
@@ -18,7 +18,7 @@ export class MyAccountDangerZoneComponent {
18 ) { } 18 ) { }
19 19
20 async deleteMe () { 20 async deleteMe () {
21 const res = await this.confirmService.confirmWithInput( 21 const res = await this.confirmService.confirmWithExpectedInput(
22 $localize`Are you sure you want to delete your account?` + 22 $localize`Are you sure you want to delete your account?` +
23 '<br /><br />' + 23 '<br /><br />' +
24 // eslint-disable-next-line max-len 24 // eslint-disable-next-line max-len
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html
index 42a8d0856..666205de6 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html
+++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html
@@ -62,6 +62,16 @@
62 </div> 62 </div>
63</div> 63</div>
64 64
65<div class="row mt-5" *ngIf="user.pluginAuth === null"> <!-- two factor auth grid -->
66 <div class="col-12 col-lg-4 col-xl-3">
67 <h2 i18n class="account-title">Two-factor authentication</h2>
68 </div>
69
70 <div class="col-12 col-lg-8 col-xl-9">
71 <my-account-two-factor-button [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-two-factor-button>
72 </div>
73</div>
74
65<div class="row mt-5" *ngIf="user.pluginAuth === null"> <!-- email grid --> 75<div class="row mt-5" *ngIf="user.pluginAuth === null"> <!-- email grid -->
66 <div class="col-12 col-lg-4 col-xl-3"> 76 <div class="col-12 col-lg-4 col-xl-3">
67 <h2 i18n class="account-title">EMAIL</h2> 77 <h2 i18n class="account-title">EMAIL</h2>
diff --git a/client/src/app/+my-account/my-account-settings/my-account-two-factor/index.ts b/client/src/app/+my-account/my-account-settings/my-account-two-factor/index.ts
new file mode 100644
index 000000000..ef83009a5
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-two-factor/index.ts
@@ -0,0 +1,3 @@
1export * from './my-account-two-factor-button.component'
2export * from './my-account-two-factor.component'
3export * from './two-factor.service'
diff --git a/client/src/app/+my-account/my-account-settings/my-account-two-factor/my-account-two-factor-button.component.html b/client/src/app/+my-account/my-account-settings/my-account-two-factor/my-account-two-factor-button.component.html
new file mode 100644
index 000000000..2fcfffbf3
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-two-factor/my-account-two-factor-button.component.html
@@ -0,0 +1,12 @@
1<div class="two-factor">
2 <ng-container *ngIf="!twoFactorEnabled">
3 <p i18n>Two factor authentication adds an additional layer of security to your account by requiring a numeric code from another device (most commonly mobile phones) when you log in.</p>
4
5 <my-button [routerLink]="[ '/my-account/two-factor-auth' ]" className="orange-button-link" i18n>Enable two-factor authentication</my-button>
6 </ng-container>
7
8 <ng-container *ngIf="twoFactorEnabled">
9 <my-button className="orange-button" (click)="disableTwoFactor()" i18n>Disable two-factor authentication</my-button>
10 </ng-container>
11
12</div>
diff --git a/client/src/app/+my-account/my-account-settings/my-account-two-factor/my-account-two-factor-button.component.ts b/client/src/app/+my-account/my-account-settings/my-account-two-factor/my-account-two-factor-button.component.ts
new file mode 100644
index 000000000..03b00e933
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-two-factor/my-account-two-factor-button.component.ts
@@ -0,0 +1,49 @@
1import { Subject } from 'rxjs'
2import { Component, Input, OnInit } from '@angular/core'
3import { AuthService, ConfirmService, Notifier, User } from '@app/core'
4import { TwoFactorService } from './two-factor.service'
5
6@Component({
7 selector: 'my-account-two-factor-button',
8 templateUrl: './my-account-two-factor-button.component.html'
9})
10export class MyAccountTwoFactorButtonComponent implements OnInit {
11 @Input() user: User = null
12 @Input() userInformationLoaded: Subject<any>
13
14 twoFactorEnabled = false
15
16 constructor (
17 private notifier: Notifier,
18 private twoFactorService: TwoFactorService,
19 private confirmService: ConfirmService,
20 private auth: AuthService
21 ) {
22 }
23
24 ngOnInit () {
25 this.userInformationLoaded.subscribe(() => {
26 this.twoFactorEnabled = this.user.twoFactorEnabled
27 })
28 }
29
30 async disableTwoFactor () {
31 const message = $localize`Are you sure you want to disable two factor authentication of your account?`
32
33 const { confirmed, password } = await this.confirmService.confirmWithPassword(message, $localize`Disable two factor`)
34 if (confirmed === false) return
35
36 this.twoFactorService.disableTwoFactor({ userId: this.user.id, currentPassword: password })
37 .subscribe({
38 next: () => {
39 this.twoFactorEnabled = false
40
41 this.auth.refreshUserInformation()
42
43 this.notifier.success($localize`Two factor authentication disabled`)
44 },
45
46 error: err => this.notifier.error(err.message)
47 })
48 }
49}
diff --git a/client/src/app/+my-account/my-account-settings/my-account-two-factor/my-account-two-factor.component.html b/client/src/app/+my-account/my-account-settings/my-account-two-factor/my-account-two-factor.component.html
new file mode 100644
index 000000000..16c344e3b
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-two-factor/my-account-two-factor.component.html
@@ -0,0 +1,54 @@
1<h1>
2 <my-global-icon iconName="cog" aria-hidden="true"></my-global-icon>
3 <ng-container i18n>Two factor authentication</ng-container>
4</h1>
5
6<div i18n *ngIf="twoFactorAlreadyEnabled === true" class="root already-enabled">
7 Two factor authentication is already enabled.
8</div>
9
10<div class="root" *ngIf="twoFactorAlreadyEnabled === false">
11 <ng-container *ngIf="step === 'request'">
12 <form role="form" (ngSubmit)="requestTwoFactor()" [formGroup]="formPassword">
13
14 <label i18n for="current-password">Your password</label>
15 <div class="form-group-description" i18n>Confirm your password to enable two factor authentication</div>
16
17 <my-input-text
18 formControlName="current-password" inputId="current-password" i18n-placeholder placeholder="Current password"
19 [formError]="formErrorsPassword['current-password']" autocomplete="current-password"
20 ></my-input-text>
21
22 <input class="peertube-button orange-button mt-3" type="submit" i18n-value value="Confirm" [disabled]="!formPassword.valid">
23 </form>
24 </ng-container>
25
26 <ng-container *ngIf="step === 'confirm'">
27
28 <p i18n>
29 Scan this QR code into a TOTP app on your phone. This app will generate tokens that you will have to enter when logging in.
30 </p>
31
32 <qrcode [qrdata]="twoFactorURI" [width]="256" level="Q"></qrcode>
33
34 <div i18n>
35 If you can't scan the QR code and need to enter it manually, here is the plain-text secret:
36 </div>
37
38 <div class="secret-plain-text">{{ twoFactorSecret }}</div>
39
40 <form class="mt-3" role="form" (ngSubmit)="confirmTwoFactor()" [formGroup]="formOTP">
41
42 <label i18n for="otp-token">Two-factor code</label>
43 <div class="form-group-description" i18n>Enter the code generated by your authenticator app to confirm</div>
44
45 <my-input-text
46 [show]="true" formControlName="otp-token" inputId="otp-token"
47 [formError]="formErrorsOTP['otp-token']" autocomplete="otp-token"
48 ></my-input-text>
49
50 <input class="peertube-button orange-button mt-3" type="submit" i18n-value value="Confirm" [disabled]="!formOTP.valid">
51 </form>
52 </ng-container>
53
54</div>
diff --git a/client/src/app/+my-account/my-account-settings/my-account-two-factor/my-account-two-factor.component.scss b/client/src/app/+my-account/my-account-settings/my-account-two-factor/my-account-two-factor.component.scss
new file mode 100644
index 000000000..cee016bb8
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-two-factor/my-account-two-factor.component.scss
@@ -0,0 +1,16 @@
1@use '_variables' as *;
2@use '_mixins' as *;
3
4.root {
5 max-width: 600px;
6}
7
8.secret-plain-text {
9 font-family: monospace;
10 font-size: 0.9rem;
11}
12
13qrcode {
14 display: inline-block;
15 margin: auto;
16}
diff --git a/client/src/app/+my-account/my-account-settings/my-account-two-factor/my-account-two-factor.component.ts b/client/src/app/+my-account/my-account-settings/my-account-two-factor/my-account-two-factor.component.ts
new file mode 100644
index 000000000..e4d4188f7
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-two-factor/my-account-two-factor.component.ts
@@ -0,0 +1,105 @@
1import { Component, OnInit } from '@angular/core'
2import { FormGroup } from '@angular/forms'
3import { Router } from '@angular/router'
4import { AuthService, Notifier, User } from '@app/core'
5import { USER_EXISTING_PASSWORD_VALIDATOR, USER_OTP_TOKEN_VALIDATOR } from '@app/shared/form-validators/user-validators'
6import { FormReactiveService } from '@app/shared/shared-forms'
7import { TwoFactorService } from './two-factor.service'
8
9@Component({
10 selector: 'my-account-two-factor',
11 templateUrl: './my-account-two-factor.component.html',
12 styleUrls: [ './my-account-two-factor.component.scss' ]
13})
14export class MyAccountTwoFactorComponent implements OnInit {
15 twoFactorAlreadyEnabled: boolean
16
17 step: 'request' | 'confirm' | 'confirmed' = 'request'
18
19 twoFactorSecret: string
20 twoFactorURI: string
21
22 inPasswordStep = true
23
24 formPassword: FormGroup
25 formErrorsPassword: any
26
27 formOTP: FormGroup
28 formErrorsOTP: any
29
30 private user: User
31 private requestToken: string
32
33 constructor (
34 private notifier: Notifier,
35 private twoFactorService: TwoFactorService,
36 private formReactiveService: FormReactiveService,
37 private auth: AuthService,
38 private router: Router
39 ) {
40 }
41
42 ngOnInit () {
43 this.buildPasswordForm()
44 this.buildOTPForm()
45
46 this.auth.userInformationLoaded.subscribe(() => {
47 this.user = this.auth.getUser()
48
49 this.twoFactorAlreadyEnabled = this.user.twoFactorEnabled
50 })
51 }
52
53 requestTwoFactor () {
54 this.twoFactorService.requestTwoFactor({
55 userId: this.user.id,
56 currentPassword: this.formPassword.value['current-password']
57 }).subscribe({
58 next: ({ otpRequest }) => {
59 this.requestToken = otpRequest.requestToken
60 this.twoFactorURI = otpRequest.uri
61 this.twoFactorSecret = otpRequest.secret.replace(/(.{4})/g, '$1 ').trim()
62
63 this.step = 'confirm'
64 },
65
66 error: err => this.notifier.error(err.message)
67 })
68 }
69
70 confirmTwoFactor () {
71 this.twoFactorService.confirmTwoFactorRequest({
72 userId: this.user.id,
73 requestToken: this.requestToken,
74 otpToken: this.formOTP.value['otp-token']
75 }).subscribe({
76 next: () => {
77 this.notifier.success($localize`Two factor authentication has been enabled.`)
78
79 this.auth.refreshUserInformation()
80
81 this.router.navigateByUrl('/my-account/settings')
82 },
83
84 error: err => this.notifier.error(err.message)
85 })
86 }
87
88 private buildPasswordForm () {
89 const { form, formErrors } = this.formReactiveService.buildForm({
90 'current-password': USER_EXISTING_PASSWORD_VALIDATOR
91 })
92
93 this.formPassword = form
94 this.formErrorsPassword = formErrors
95 }
96
97 private buildOTPForm () {
98 const { form, formErrors } = this.formReactiveService.buildForm({
99 'otp-token': USER_OTP_TOKEN_VALIDATOR
100 })
101
102 this.formOTP = form
103 this.formErrorsOTP = formErrors
104 }
105}
diff --git a/client/src/app/+my-account/my-account-settings/my-account-two-factor/two-factor.service.ts b/client/src/app/+my-account/my-account-settings/my-account-two-factor/two-factor.service.ts
new file mode 100644
index 000000000..c0e5ac492
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-two-factor/two-factor.service.ts
@@ -0,0 +1,52 @@
1import { catchError } from 'rxjs/operators'
2import { HttpClient } from '@angular/common/http'
3import { Injectable } from '@angular/core'
4import { RestExtractor, UserService } from '@app/core'
5import { TwoFactorEnableResult } from '@shared/models'
6
7@Injectable()
8export class TwoFactorService {
9 constructor (
10 private authHttp: HttpClient,
11 private restExtractor: RestExtractor
12 ) { }
13
14 // ---------------------------------------------------------------------------
15
16 requestTwoFactor (options: {
17 userId: number
18 currentPassword: string
19 }) {
20 const { userId, currentPassword } = options
21
22 const url = UserService.BASE_USERS_URL + userId + '/two-factor/request'
23
24 return this.authHttp.post<TwoFactorEnableResult>(url, { currentPassword })
25 .pipe(catchError(err => this.restExtractor.handleError(err)))
26 }
27
28 confirmTwoFactorRequest (options: {
29 userId: number
30 requestToken: string
31 otpToken: string
32 }) {
33 const { userId, requestToken, otpToken } = options
34
35 const url = UserService.BASE_USERS_URL + userId + '/two-factor/confirm-request'
36
37 return this.authHttp.post(url, { requestToken, otpToken })
38 .pipe(catchError(err => this.restExtractor.handleError(err)))
39 }
40
41 disableTwoFactor (options: {
42 userId: number
43 currentPassword: string
44 }) {
45 const { userId, currentPassword } = options
46
47 const url = UserService.BASE_USERS_URL + userId + '/two-factor/disable'
48
49 return this.authHttp.post(url, { currentPassword })
50 .pipe(catchError(err => this.restExtractor.handleError(err)))
51 }
52}
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts
index 4081e4f01..f5beaa4db 100644
--- a/client/src/app/+my-account/my-account.module.ts
+++ b/client/src/app/+my-account/my-account.module.ts
@@ -1,3 +1,4 @@
1import { QRCodeModule } from 'angularx-qrcode'
1import { AutoCompleteModule } from 'primeng/autocomplete' 2import { AutoCompleteModule } from 'primeng/autocomplete'
2import { TableModule } from 'primeng/table' 3import { TableModule } from 'primeng/table'
3import { DragDropModule } from '@angular/cdk/drag-drop' 4import { DragDropModule } from '@angular/cdk/drag-drop'
@@ -23,12 +24,18 @@ import { MyAccountDangerZoneComponent } from './my-account-settings/my-account-d
23import { MyAccountNotificationPreferencesComponent } from './my-account-settings/my-account-notification-preferences' 24import { MyAccountNotificationPreferencesComponent } from './my-account-settings/my-account-notification-preferences'
24import { MyAccountProfileComponent } from './my-account-settings/my-account-profile/my-account-profile.component' 25import { MyAccountProfileComponent } from './my-account-settings/my-account-profile/my-account-profile.component'
25import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component' 26import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component'
27import {
28 MyAccountTwoFactorButtonComponent,
29 MyAccountTwoFactorComponent,
30 TwoFactorService
31} from './my-account-settings/my-account-two-factor'
26import { MyAccountComponent } from './my-account.component' 32import { MyAccountComponent } from './my-account.component'
27 33
28@NgModule({ 34@NgModule({
29 imports: [ 35 imports: [
30 MyAccountRoutingModule, 36 MyAccountRoutingModule,
31 37
38 QRCodeModule,
32 AutoCompleteModule, 39 AutoCompleteModule,
33 TableModule, 40 TableModule,
34 DragDropModule, 41 DragDropModule,
@@ -52,6 +59,9 @@ import { MyAccountComponent } from './my-account.component'
52 MyAccountChangeEmailComponent, 59 MyAccountChangeEmailComponent,
53 MyAccountApplicationsComponent, 60 MyAccountApplicationsComponent,
54 61
62 MyAccountTwoFactorButtonComponent,
63 MyAccountTwoFactorComponent,
64
55 MyAccountDangerZoneComponent, 65 MyAccountDangerZoneComponent,
56 MyAccountBlocklistComponent, 66 MyAccountBlocklistComponent,
57 MyAccountAbusesListComponent, 67 MyAccountAbusesListComponent,
@@ -64,7 +74,9 @@ import { MyAccountComponent } from './my-account.component'
64 MyAccountComponent 74 MyAccountComponent
65 ], 75 ],
66 76
67 providers: [] 77 providers: [
78 TwoFactorService
79 ]
68}) 80})
69export class MyAccountModule { 81export class MyAccountModule {
70} 82}