aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/shared')
-rw-r--r--client/src/app/shared/form-validators/user-validators.ts9
-rw-r--r--client/src/app/shared/shared-abuse-list/abuse-message-modal.component.ts4
-rw-r--r--client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.ts4
-rw-r--r--client/src/app/shared/shared-forms/form-reactive.service.ts101
-rw-r--r--client/src/app/shared/shared-forms/form-reactive.ts87
-rw-r--r--client/src/app/shared/shared-forms/form-validator.service.ts2
-rw-r--r--client/src/app/shared/shared-forms/index.ts1
-rw-r--r--client/src/app/shared/shared-forms/input-text.component.ts10
-rw-r--r--client/src/app/shared/shared-forms/shared-form.module.ts5
-rw-r--r--client/src/app/shared/shared-main/auth/auth-interceptor.service.ts13
-rw-r--r--client/src/app/shared/shared-moderation/batch-domains-modal.component.ts4
-rw-r--r--client/src/app/shared/shared-moderation/report-modals/account-report.component.ts4
-rw-r--r--client/src/app/shared/shared-moderation/report-modals/comment-report.component.ts4
-rw-r--r--client/src/app/shared/shared-moderation/report-modals/video-report.component.ts4
-rw-r--r--client/src/app/shared/shared-moderation/user-ban-modal.component.ts4
-rw-r--r--client/src/app/shared/shared-moderation/video-block.component.ts4
-rw-r--r--client/src/app/shared/shared-user-settings/user-interface-settings.component.ts4
-rw-r--r--client/src/app/shared/shared-user-settings/user-video-settings.component.ts4
-rw-r--r--client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts4
-rw-r--r--client/src/app/shared/shared-users/index.ts1
-rw-r--r--client/src/app/shared/shared-users/shared-users.module.ts4
-rw-r--r--client/src/app/shared/shared-users/two-factor.service.ts52
-rw-r--r--client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts4
23 files changed, 220 insertions, 113 deletions
diff --git a/client/src/app/shared/form-validators/user-validators.ts b/client/src/app/shared/form-validators/user-validators.ts
index 3262853d8..b93de75ea 100644
--- a/client/src/app/shared/form-validators/user-validators.ts
+++ b/client/src/app/shared/form-validators/user-validators.ts
@@ -61,6 +61,15 @@ export const USER_EXISTING_PASSWORD_VALIDATOR: BuildFormValidator = {
61 } 61 }
62} 62}
63 63
64export const USER_OTP_TOKEN_VALIDATOR: BuildFormValidator = {
65 VALIDATORS: [
66 Validators.required
67 ],
68 MESSAGES: {
69 required: $localize`OTP token is required.`
70 }
71}
72
64export const USER_PASSWORD_VALIDATOR = { 73export const USER_PASSWORD_VALIDATOR = {
65 VALIDATORS: [ 74 VALIDATORS: [
66 Validators.required, 75 Validators.required,
diff --git a/client/src/app/shared/shared-abuse-list/abuse-message-modal.component.ts b/client/src/app/shared/shared-abuse-list/abuse-message-modal.component.ts
index d24a5d58d..12d503f56 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-message-modal.component.ts
+++ b/client/src/app/shared/shared-abuse-list/abuse-message-modal.component.ts
@@ -1,6 +1,6 @@
1import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' 1import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
2import { AuthService, HtmlRendererService, Notifier } from '@app/core' 2import { AuthService, HtmlRendererService, Notifier } from '@app/core'
3import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 3import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
4import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 4import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
5import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' 5import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
6import { logger } from '@root-helpers/logger' 6import { logger } from '@root-helpers/logger'
@@ -29,7 +29,7 @@ export class AbuseMessageModalComponent extends FormReactive implements OnInit {
29 private abuse: UserAbuse 29 private abuse: UserAbuse
30 30
31 constructor ( 31 constructor (
32 protected formValidatorService: FormValidatorService, 32 protected formReactiveService: FormReactiveService,
33 private modalService: NgbModal, 33 private modalService: NgbModal,
34 private htmlRenderer: HtmlRendererService, 34 private htmlRenderer: HtmlRendererService,
35 private auth: AuthService, 35 private auth: AuthService,
diff --git a/client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.ts b/client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.ts
index 2600da8da..4ad807d25 100644
--- a/client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.ts
+++ b/client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.ts
@@ -1,6 +1,6 @@
1import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' 1import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
2import { Notifier } from '@app/core' 2import { Notifier } from '@app/core'
3import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 3import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
4import { AbuseService } from '@app/shared/shared-moderation' 4import { AbuseService } from '@app/shared/shared-moderation'
5import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 5import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
6import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' 6import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
@@ -20,7 +20,7 @@ export class ModerationCommentModalComponent extends FormReactive implements OnI
20 private openedModal: NgbModalRef 20 private openedModal: NgbModalRef
21 21
22 constructor ( 22 constructor (
23 protected formValidatorService: FormValidatorService, 23 protected formReactiveService: FormReactiveService,
24 private modalService: NgbModal, 24 private modalService: NgbModal,
25 private notifier: Notifier, 25 private notifier: Notifier,
26 private abuseService: AbuseService 26 private abuseService: AbuseService
diff --git a/client/src/app/shared/shared-forms/form-reactive.service.ts b/client/src/app/shared/shared-forms/form-reactive.service.ts
new file mode 100644
index 000000000..f1b7e0ef2
--- /dev/null
+++ b/client/src/app/shared/shared-forms/form-reactive.service.ts
@@ -0,0 +1,101 @@
1import { Injectable } from '@angular/core'
2import { AbstractControl, FormGroup } from '@angular/forms'
3import { wait } from '@root-helpers/utils'
4import { BuildFormArgument, BuildFormDefaultValues } from '../form-validators/form-validator.model'
5import { FormValidatorService } from './form-validator.service'
6
7export type FormReactiveErrors = { [ id: string ]: string | FormReactiveErrors }
8export type FormReactiveValidationMessages = {
9 [ id: string ]: { [ name: string ]: string } | FormReactiveValidationMessages
10}
11
12@Injectable()
13export class FormReactiveService {
14
15 constructor (private formValidatorService: FormValidatorService) {
16
17 }
18
19 buildForm (obj: BuildFormArgument, defaultValues: BuildFormDefaultValues = {}) {
20 const { formErrors, validationMessages, form } = this.formValidatorService.buildForm(obj, defaultValues)
21
22 form.statusChanges.subscribe(async () => {
23 // FIXME: remove when https://github.com/angular/angular/issues/41519 is fixed
24 await this.waitPendingCheck(form)
25
26 this.onStatusChanged({ form, formErrors, validationMessages })
27 })
28
29 return { form, formErrors, validationMessages }
30 }
31
32 async waitPendingCheck (form: FormGroup) {
33 if (form.status !== 'PENDING') return
34
35 // FIXME: the following line does not work: https://github.com/angular/angular/issues/41519
36 // return firstValueFrom(form.statusChanges.pipe(filter(status => status !== 'PENDING')))
37 // So we have to fallback to active wait :/
38
39 do {
40 await wait(10)
41 } while (form.status === 'PENDING')
42 }
43
44 markAllAsDirty (controlsArg: { [ key: string ]: AbstractControl }) {
45 const controls = controlsArg
46
47 for (const key of Object.keys(controls)) {
48 const control = controls[key]
49
50 if (control instanceof FormGroup) {
51 this.markAllAsDirty(control.controls)
52 continue
53 }
54
55 control.markAsDirty()
56 }
57 }
58
59 forceCheck (form: FormGroup, formErrors: any, validationMessages: FormReactiveValidationMessages) {
60 this.onStatusChanged({ form, formErrors, validationMessages, onlyDirty: false })
61 }
62
63 private onStatusChanged (options: {
64 form: FormGroup
65 formErrors: FormReactiveErrors
66 validationMessages: FormReactiveValidationMessages
67 onlyDirty?: boolean // default true
68 }) {
69 const { form, formErrors, validationMessages, onlyDirty = true } = options
70
71 for (const field of Object.keys(formErrors)) {
72 if (formErrors[field] && typeof formErrors[field] === 'object') {
73 this.onStatusChanged({
74 form: form.controls[field] as FormGroup,
75 formErrors: formErrors[field] as FormReactiveErrors,
76 validationMessages: validationMessages[field] as FormReactiveValidationMessages,
77 onlyDirty
78 })
79
80 continue
81 }
82
83 // clear previous error message (if any)
84 formErrors[field] = ''
85 const control = form.get(field)
86
87 if (!control || (onlyDirty && !control.dirty) || !control.enabled || !control.errors) continue
88
89 const staticMessages = validationMessages[field]
90 for (const key of Object.keys(control.errors)) {
91 const formErrorValue = control.errors[key]
92
93 // Try to find error message in static validation messages first
94 // Then check if the validator returns a string that is the error
95 if (staticMessages[key]) formErrors[field] += staticMessages[key] + ' '
96 else if (typeof formErrorValue === 'string') formErrors[field] += control.errors[key]
97 else throw new Error('Form error value of ' + field + ' is invalid')
98 }
99 }
100 }
101}
diff --git a/client/src/app/shared/shared-forms/form-reactive.ts b/client/src/app/shared/shared-forms/form-reactive.ts
index a19ffdd82..d1e7be802 100644
--- a/client/src/app/shared/shared-forms/form-reactive.ts
+++ b/client/src/app/shared/shared-forms/form-reactive.ts
@@ -1,16 +1,9 @@
1 1import { FormGroup } from '@angular/forms'
2import { AbstractControl, FormGroup } from '@angular/forms'
3import { wait } from '@root-helpers/utils'
4import { BuildFormArgument, BuildFormDefaultValues } from '../form-validators/form-validator.model' 2import { BuildFormArgument, BuildFormDefaultValues } from '../form-validators/form-validator.model'
5import { FormValidatorService } from './form-validator.service' 3import { FormReactiveService, FormReactiveValidationMessages } from './form-reactive.service'
6
7export type FormReactiveErrors = { [ id: string ]: string | FormReactiveErrors }
8export type FormReactiveValidationMessages = {
9 [ id: string ]: { [ name: string ]: string } | FormReactiveValidationMessages
10}
11 4
12export abstract class FormReactive { 5export abstract class FormReactive {
13 protected abstract formValidatorService: FormValidatorService 6 protected abstract formReactiveService: FormReactiveService
14 protected formChanged = false 7 protected formChanged = false
15 8
16 form: FormGroup 9 form: FormGroup
@@ -18,86 +11,22 @@ export abstract class FormReactive {
18 validationMessages: FormReactiveValidationMessages 11 validationMessages: FormReactiveValidationMessages
19 12
20 buildForm (obj: BuildFormArgument, defaultValues: BuildFormDefaultValues = {}) { 13 buildForm (obj: BuildFormArgument, defaultValues: BuildFormDefaultValues = {}) {
21 const { formErrors, validationMessages, form } = this.formValidatorService.buildForm(obj, defaultValues) 14 const { formErrors, validationMessages, form } = this.formReactiveService.buildForm(obj, defaultValues)
22 15
23 this.form = form 16 this.form = form
24 this.formErrors = formErrors 17 this.formErrors = formErrors
25 this.validationMessages = validationMessages 18 this.validationMessages = validationMessages
26
27 this.form.statusChanges.subscribe(async () => {
28 // FIXME: remove when https://github.com/angular/angular/issues/41519 is fixed
29 await this.waitPendingCheck()
30
31 this.onStatusChanged(this.form, this.formErrors, this.validationMessages)
32 })
33 } 19 }
34 20
35 protected async waitPendingCheck () { 21 protected async waitPendingCheck () {
36 if (this.form.status !== 'PENDING') return 22 return this.formReactiveService.waitPendingCheck(this.form)
37
38 // FIXME: the following line does not work: https://github.com/angular/angular/issues/41519
39 // return firstValueFrom(this.form.statusChanges.pipe(filter(status => status !== 'PENDING')))
40 // So we have to fallback to active wait :/
41
42 do {
43 await wait(10)
44 } while (this.form.status === 'PENDING')
45 } 23 }
46 24
47 protected markAllAsDirty (controlsArg?: { [ key: string ]: AbstractControl }) { 25 protected markAllAsDirty () {
48 const controls = controlsArg || this.form.controls 26 return this.formReactiveService.markAllAsDirty(this.form.controls)
49
50 for (const key of Object.keys(controls)) {
51 const control = controls[key]
52
53 if (control instanceof FormGroup) {
54 this.markAllAsDirty(control.controls)
55 continue
56 }
57
58 control.markAsDirty()
59 }
60 } 27 }
61 28
62 protected forceCheck () { 29 protected forceCheck () {
63 this.onStatusChanged(this.form, this.formErrors, this.validationMessages, false) 30 return this.formReactiveService.forceCheck(this.form, this.formErrors, this.validationMessages)
64 }
65
66 private onStatusChanged (
67 form: FormGroup,
68 formErrors: FormReactiveErrors,
69 validationMessages: FormReactiveValidationMessages,
70 onlyDirty = true
71 ) {
72 for (const field of Object.keys(formErrors)) {
73 if (formErrors[field] && typeof formErrors[field] === 'object') {
74 this.onStatusChanged(
75 form.controls[field] as FormGroup,
76 formErrors[field] as FormReactiveErrors,
77 validationMessages[field] as FormReactiveValidationMessages,
78 onlyDirty
79 )
80 continue
81 }
82
83 // clear previous error message (if any)
84 formErrors[field] = ''
85 const control = form.get(field)
86
87 if (control.dirty) this.formChanged = true
88
89 if (!control || (onlyDirty && !control.dirty) || !control.enabled || !control.errors) continue
90
91 const staticMessages = validationMessages[field]
92 for (const key of Object.keys(control.errors)) {
93 const formErrorValue = control.errors[key]
94
95 // Try to find error message in static validation messages first
96 // Then check if the validator returns a string that is the error
97 if (staticMessages[key]) formErrors[field] += staticMessages[key] + ' '
98 else if (typeof formErrorValue === 'string') formErrors[field] += control.errors[key]
99 else throw new Error('Form error value of ' + field + ' is invalid')
100 }
101 }
102 } 31 }
103} 32}
diff --git a/client/src/app/shared/shared-forms/form-validator.service.ts b/client/src/app/shared/shared-forms/form-validator.service.ts
index f67d5bb33..897008242 100644
--- a/client/src/app/shared/shared-forms/form-validator.service.ts
+++ b/client/src/app/shared/shared-forms/form-validator.service.ts
@@ -1,7 +1,7 @@
1import { Injectable } from '@angular/core' 1import { Injectable } from '@angular/core'
2import { AsyncValidatorFn, FormArray, FormBuilder, FormControl, FormGroup, ValidatorFn } from '@angular/forms' 2import { AsyncValidatorFn, FormArray, FormBuilder, FormControl, FormGroup, ValidatorFn } from '@angular/forms'
3import { BuildFormArgument, BuildFormDefaultValues } from '../form-validators/form-validator.model' 3import { BuildFormArgument, BuildFormDefaultValues } from '../form-validators/form-validator.model'
4import { FormReactiveErrors, FormReactiveValidationMessages } from './form-reactive' 4import { FormReactiveErrors, FormReactiveValidationMessages } from './form-reactive.service'
5 5
6@Injectable() 6@Injectable()
7export class FormValidatorService { 7export class FormValidatorService {
diff --git a/client/src/app/shared/shared-forms/index.ts b/client/src/app/shared/shared-forms/index.ts
index 495785e7b..bff9862f2 100644
--- a/client/src/app/shared/shared-forms/index.ts
+++ b/client/src/app/shared/shared-forms/index.ts
@@ -1,4 +1,5 @@
1export * from './advanced-input-filter.component' 1export * from './advanced-input-filter.component'
2export * from './form-reactive.service'
2export * from './form-reactive' 3export * from './form-reactive'
3export * from './form-validator.service' 4export * from './form-validator.service'
4export * from './form-validator.service' 5export * from './form-validator.service'
diff --git a/client/src/app/shared/shared-forms/input-text.component.ts b/client/src/app/shared/shared-forms/input-text.component.ts
index d667ed663..aa4a1cba8 100644
--- a/client/src/app/shared/shared-forms/input-text.component.ts
+++ b/client/src/app/shared/shared-forms/input-text.component.ts
@@ -1,4 +1,4 @@
1import { Component, forwardRef, Input } from '@angular/core' 1import { Component, ElementRef, forwardRef, Input, ViewChild } from '@angular/core'
2import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' 2import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
3import { Notifier } from '@app/core' 3import { Notifier } from '@app/core'
4 4
@@ -15,6 +15,8 @@ import { Notifier } from '@app/core'
15 ] 15 ]
16}) 16})
17export class InputTextComponent implements ControlValueAccessor { 17export class InputTextComponent implements ControlValueAccessor {
18 @ViewChild('input') inputElement: ElementRef
19
18 @Input() inputId = Math.random().toString(11).slice(2, 8) // id cannot be left empty or undefined 20 @Input() inputId = Math.random().toString(11).slice(2, 8) // id cannot be left empty or undefined
19 @Input() value = '' 21 @Input() value = ''
20 @Input() autocomplete = 'off' 22 @Input() autocomplete = 'off'
@@ -65,4 +67,10 @@ export class InputTextComponent implements ControlValueAccessor {
65 update () { 67 update () {
66 this.propagateChange(this.value) 68 this.propagateChange(this.value)
67 } 69 }
70
71 focus () {
72 const el: HTMLElement = this.inputElement.nativeElement
73
74 el.focus({ preventScroll: true })
75 }
68} 76}
diff --git a/client/src/app/shared/shared-forms/shared-form.module.ts b/client/src/app/shared/shared-forms/shared-form.module.ts
index 81f076db6..628affb56 100644
--- a/client/src/app/shared/shared-forms/shared-form.module.ts
+++ b/client/src/app/shared/shared-forms/shared-form.module.ts
@@ -1,4 +1,3 @@
1
2import { InputMaskModule } from 'primeng/inputmask' 1import { InputMaskModule } from 'primeng/inputmask'
3import { NgModule } from '@angular/core' 2import { NgModule } from '@angular/core'
4import { FormsModule, ReactiveFormsModule } from '@angular/forms' 3import { FormsModule, ReactiveFormsModule } from '@angular/forms'
@@ -7,6 +6,7 @@ import { SharedGlobalIconModule } from '../shared-icons'
7import { SharedMainModule } from '../shared-main/shared-main.module' 6import { SharedMainModule } from '../shared-main/shared-main.module'
8import { AdvancedInputFilterComponent } from './advanced-input-filter.component' 7import { AdvancedInputFilterComponent } from './advanced-input-filter.component'
9import { DynamicFormFieldComponent } from './dynamic-form-field.component' 8import { DynamicFormFieldComponent } from './dynamic-form-field.component'
9import { FormReactiveService } from './form-reactive.service'
10import { FormValidatorService } from './form-validator.service' 10import { FormValidatorService } from './form-validator.service'
11import { InputSwitchComponent } from './input-switch.component' 11import { InputSwitchComponent } from './input-switch.component'
12import { InputTextComponent } from './input-text.component' 12import { InputTextComponent } from './input-text.component'
@@ -96,7 +96,8 @@ import { TimestampInputComponent } from './timestamp-input.component'
96 ], 96 ],
97 97
98 providers: [ 98 providers: [
99 FormValidatorService 99 FormValidatorService,
100 FormReactiveService
100 ] 101 ]
101}) 102})
102export class SharedFormModule { } 103export class SharedFormModule { }
diff --git a/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts b/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts
index e4b74f3ad..93b3a93d6 100644
--- a/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts
+++ b/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts
@@ -27,13 +27,16 @@ export class AuthInterceptor implements HttpInterceptor {
27 .pipe( 27 .pipe(
28 catchError((err: HttpErrorResponse) => { 28 catchError((err: HttpErrorResponse) => {
29 const error = err.error as PeerTubeProblemDocument 29 const error = err.error as PeerTubeProblemDocument
30 const isOTPMissingError = this.authService.isOTPMissingError(err)
30 31
31 if (err.status === HttpStatusCode.UNAUTHORIZED_401 && error && error.code === OAuth2ErrorCode.INVALID_TOKEN) { 32 if (!isOTPMissingError) {
32 return this.handleTokenExpired(req, next) 33 if (err.status === HttpStatusCode.UNAUTHORIZED_401 && error && error.code === OAuth2ErrorCode.INVALID_TOKEN) {
33 } 34 return this.handleTokenExpired(req, next)
35 }
34 36
35 if (err.status === HttpStatusCode.UNAUTHORIZED_401) { 37 if (err.status === HttpStatusCode.UNAUTHORIZED_401) {
36 return this.handleNotAuthenticated(err) 38 return this.handleNotAuthenticated(err)
39 }
37 } 40 }
38 41
39 return observableThrowError(() => err) 42 return observableThrowError(() => err)
diff --git a/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts b/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts
index 20be728f6..ec2fea528 100644
--- a/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts
+++ b/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts
@@ -1,5 +1,5 @@
1import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' 1import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
2import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 2import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
3import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 3import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
4import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' 4import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
5import { splitAndGetNotEmpty, UNIQUE_HOSTS_VALIDATOR } from '../form-validators/host-validators' 5import { splitAndGetNotEmpty, UNIQUE_HOSTS_VALIDATOR } from '../form-validators/host-validators'
@@ -18,7 +18,7 @@ export class BatchDomainsModalComponent extends FormReactive implements OnInit {
18 private openedModal: NgbModalRef 18 private openedModal: NgbModalRef
19 19
20 constructor ( 20 constructor (
21 protected formValidatorService: FormValidatorService, 21 protected formReactiveService: FormReactiveService,
22 private modalService: NgbModal 22 private modalService: NgbModal
23 ) { 23 ) {
24 super() 24 super()
diff --git a/client/src/app/shared/shared-moderation/report-modals/account-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/account-report.component.ts
index 78c9b3382..d587a9709 100644
--- a/client/src/app/shared/shared-moderation/report-modals/account-report.component.ts
+++ b/client/src/app/shared/shared-moderation/report-modals/account-report.component.ts
@@ -2,7 +2,7 @@ import { mapValues, pickBy } from 'lodash-es'
2import { Component, OnInit, ViewChild } from '@angular/core' 2import { Component, OnInit, ViewChild } from '@angular/core'
3import { Notifier } from '@app/core' 3import { Notifier } from '@app/core'
4import { ABUSE_REASON_VALIDATOR } from '@app/shared/form-validators/abuse-validators' 4import { ABUSE_REASON_VALIDATOR } from '@app/shared/form-validators/abuse-validators'
5import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 5import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
6import { Account } from '@app/shared/shared-main' 6import { Account } from '@app/shared/shared-main'
7import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 7import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
8import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' 8import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
@@ -26,7 +26,7 @@ export class AccountReportComponent extends FormReactive implements OnInit {
26 private openedModal: NgbModalRef 26 private openedModal: NgbModalRef
27 27
28 constructor ( 28 constructor (
29 protected formValidatorService: FormValidatorService, 29 protected formReactiveService: FormReactiveService,
30 private modalService: NgbModal, 30 private modalService: NgbModal,
31 private abuseService: AbuseService, 31 private abuseService: AbuseService,
32 private notifier: Notifier 32 private notifier: Notifier
diff --git a/client/src/app/shared/shared-moderation/report-modals/comment-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/comment-report.component.ts
index 7c0907ce4..e35d70c8f 100644
--- a/client/src/app/shared/shared-moderation/report-modals/comment-report.component.ts
+++ b/client/src/app/shared/shared-moderation/report-modals/comment-report.component.ts
@@ -2,7 +2,7 @@ import { mapValues, pickBy } from 'lodash-es'
2import { Component, Input, OnInit, ViewChild } from '@angular/core' 2import { Component, Input, OnInit, ViewChild } from '@angular/core'
3import { Notifier } from '@app/core' 3import { Notifier } from '@app/core'
4import { ABUSE_REASON_VALIDATOR } from '@app/shared/form-validators/abuse-validators' 4import { ABUSE_REASON_VALIDATOR } from '@app/shared/form-validators/abuse-validators'
5import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 5import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
6import { VideoComment } from '@app/shared/shared-video-comment' 6import { VideoComment } from '@app/shared/shared-video-comment'
7import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 7import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
8import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' 8import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
@@ -27,7 +27,7 @@ export class CommentReportComponent extends FormReactive implements OnInit {
27 private openedModal: NgbModalRef 27 private openedModal: NgbModalRef
28 28
29 constructor ( 29 constructor (
30 protected formValidatorService: FormValidatorService, 30 protected formReactiveService: FormReactiveService,
31 private modalService: NgbModal, 31 private modalService: NgbModal,
32 private abuseService: AbuseService, 32 private abuseService: AbuseService,
33 private notifier: Notifier 33 private notifier: Notifier
diff --git a/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts
index 38dd92910..16be8e0a1 100644
--- a/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts
+++ b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts
@@ -3,7 +3,7 @@ import { Component, Input, OnInit, ViewChild } from '@angular/core'
3import { DomSanitizer } from '@angular/platform-browser' 3import { DomSanitizer } from '@angular/platform-browser'
4import { Notifier } from '@app/core' 4import { Notifier } from '@app/core'
5import { ABUSE_REASON_VALIDATOR } from '@app/shared/form-validators/abuse-validators' 5import { ABUSE_REASON_VALIDATOR } from '@app/shared/form-validators/abuse-validators'
6import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 6import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
7import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 7import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
8import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' 8import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
9import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse' 9import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse'
@@ -27,7 +27,7 @@ export class VideoReportComponent extends FormReactive implements OnInit {
27 private openedModal: NgbModalRef 27 private openedModal: NgbModalRef
28 28
29 constructor ( 29 constructor (
30 protected formValidatorService: FormValidatorService, 30 protected formReactiveService: FormReactiveService,
31 private modalService: NgbModal, 31 private modalService: NgbModal,
32 private abuseService: AbuseService, 32 private abuseService: AbuseService,
33 private notifier: Notifier, 33 private notifier: Notifier,
diff --git a/client/src/app/shared/shared-moderation/user-ban-modal.component.ts b/client/src/app/shared/shared-moderation/user-ban-modal.component.ts
index 617408f2a..27dcf043a 100644
--- a/client/src/app/shared/shared-moderation/user-ban-modal.component.ts
+++ b/client/src/app/shared/shared-moderation/user-ban-modal.component.ts
@@ -2,7 +2,7 @@ import { forkJoin } from 'rxjs'
2import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' 2import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
3import { Notifier } from '@app/core' 3import { Notifier } from '@app/core'
4import { prepareIcu } from '@app/helpers' 4import { prepareIcu } from '@app/helpers'
5import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 5import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
6import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 6import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
7import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' 7import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
8import { User } from '@shared/models' 8import { User } from '@shared/models'
@@ -25,7 +25,7 @@ export class UserBanModalComponent extends FormReactive implements OnInit {
25 modalMessage = '' 25 modalMessage = ''
26 26
27 constructor ( 27 constructor (
28 protected formValidatorService: FormValidatorService, 28 protected formReactiveService: FormReactiveService,
29 private modalService: NgbModal, 29 private modalService: NgbModal,
30 private notifier: Notifier, 30 private notifier: Notifier,
31 private userAdminService: UserAdminService, 31 private userAdminService: UserAdminService,
diff --git a/client/src/app/shared/shared-moderation/video-block.component.ts b/client/src/app/shared/shared-moderation/video-block.component.ts
index f8b22a3f6..3ff53443a 100644
--- a/client/src/app/shared/shared-moderation/video-block.component.ts
+++ b/client/src/app/shared/shared-moderation/video-block.component.ts
@@ -1,7 +1,7 @@
1import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' 1import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
2import { Notifier } from '@app/core' 2import { Notifier } from '@app/core'
3import { prepareIcu } from '@app/helpers' 3import { prepareIcu } from '@app/helpers'
4import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 4import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
5import { Video } from '@app/shared/shared-main' 5import { Video } from '@app/shared/shared-main'
6import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 6import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
7import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' 7import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
@@ -25,7 +25,7 @@ export class VideoBlockComponent extends FormReactive implements OnInit {
25 private openedModal: NgbModalRef 25 private openedModal: NgbModalRef
26 26
27 constructor ( 27 constructor (
28 protected formValidatorService: FormValidatorService, 28 protected formReactiveService: FormReactiveService,
29 private modalService: NgbModal, 29 private modalService: NgbModal,
30 private videoBlocklistService: VideoBlockService, 30 private videoBlocklistService: VideoBlockService,
31 private notifier: Notifier 31 private notifier: Notifier
diff --git a/client/src/app/shared/shared-user-settings/user-interface-settings.component.ts b/client/src/app/shared/shared-user-settings/user-interface-settings.component.ts
index 13e2e5424..c2c30d38b 100644
--- a/client/src/app/shared/shared-user-settings/user-interface-settings.component.ts
+++ b/client/src/app/shared/shared-user-settings/user-interface-settings.component.ts
@@ -1,7 +1,7 @@
1import { Subject, Subscription } from 'rxjs' 1import { Subject, Subscription } from 'rxjs'
2import { Component, Input, OnDestroy, OnInit } from '@angular/core' 2import { Component, Input, OnDestroy, OnInit } from '@angular/core'
3import { AuthService, Notifier, ServerService, ThemeService, UserService } from '@app/core' 3import { AuthService, Notifier, ServerService, ThemeService, UserService } from '@app/core'
4import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 4import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
5import { HTMLServerConfig, User, UserUpdateMe } from '@shared/models' 5import { HTMLServerConfig, User, UserUpdateMe } from '@shared/models'
6import { SelectOptionsItem } from 'src/types' 6import { SelectOptionsItem } from 'src/types'
7 7
@@ -22,7 +22,7 @@ export class UserInterfaceSettingsComponent extends FormReactive implements OnIn
22 private serverConfig: HTMLServerConfig 22 private serverConfig: HTMLServerConfig
23 23
24 constructor ( 24 constructor (
25 protected formValidatorService: FormValidatorService, 25 protected formReactiveService: FormReactiveService,
26 private authService: AuthService, 26 private authService: AuthService,
27 private notifier: Notifier, 27 private notifier: Notifier,
28 private userService: UserService, 28 private userService: UserService,
diff --git a/client/src/app/shared/shared-user-settings/user-video-settings.component.ts b/client/src/app/shared/shared-user-settings/user-video-settings.component.ts
index 7d6b69469..af0870f12 100644
--- a/client/src/app/shared/shared-user-settings/user-video-settings.component.ts
+++ b/client/src/app/shared/shared-user-settings/user-video-settings.component.ts
@@ -3,7 +3,7 @@ import { Subject, Subscription } from 'rxjs'
3import { first } from 'rxjs/operators' 3import { first } from 'rxjs/operators'
4import { Component, Input, OnDestroy, OnInit } from '@angular/core' 4import { Component, Input, OnDestroy, OnInit } from '@angular/core'
5import { AuthService, Notifier, ServerService, User, UserService } from '@app/core' 5import { AuthService, Notifier, ServerService, User, UserService } from '@app/core'
6import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 6import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
7import { UserUpdateMe } from '@shared/models' 7import { UserUpdateMe } from '@shared/models'
8import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' 8import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type'
9 9
@@ -22,7 +22,7 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
22 formValuesWatcher: Subscription 22 formValuesWatcher: Subscription
23 23
24 constructor ( 24 constructor (
25 protected formValidatorService: FormValidatorService, 25 protected formReactiveService: FormReactiveService,
26 private authService: AuthService, 26 private authService: AuthService,
27 private notifier: Notifier, 27 private notifier: Notifier,
28 private userService: UserService, 28 private userService: UserService,
diff --git a/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts
index 7bcfdd8aa..61bcd5345 100644
--- a/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts
+++ b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts
@@ -1,6 +1,6 @@
1import { Component, Input, OnInit } from '@angular/core' 1import { Component, Input, OnInit } from '@angular/core'
2import { Notifier } from '@app/core' 2import { Notifier } from '@app/core'
3import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 3import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
4import { logger } from '@root-helpers/logger' 4import { logger } from '@root-helpers/logger'
5import { USER_HANDLE_VALIDATOR } from '../form-validators/user-validators' 5import { USER_HANDLE_VALIDATOR } from '../form-validators/user-validators'
6 6
@@ -15,7 +15,7 @@ export class RemoteSubscribeComponent extends FormReactive implements OnInit {
15 @Input() showHelp = false 15 @Input() showHelp = false
16 16
17 constructor ( 17 constructor (
18 protected formValidatorService: FormValidatorService, 18 protected formReactiveService: FormReactiveService,
19 private notifier: Notifier 19 private notifier: Notifier
20 ) { 20 ) {
21 super() 21 super()
diff --git a/client/src/app/shared/shared-users/index.ts b/client/src/app/shared/shared-users/index.ts
index 8f90f2515..20e60486d 100644
--- a/client/src/app/shared/shared-users/index.ts
+++ b/client/src/app/shared/shared-users/index.ts
@@ -1,4 +1,5 @@
1export * from './user-admin.service' 1export * from './user-admin.service'
2export * from './user-signup.service' 2export * from './user-signup.service'
3export * from './two-factor.service'
3 4
4export * from './shared-users.module' 5export * from './shared-users.module'
diff --git a/client/src/app/shared/shared-users/shared-users.module.ts b/client/src/app/shared/shared-users/shared-users.module.ts
index 2a1dadf20..5a1675dc9 100644
--- a/client/src/app/shared/shared-users/shared-users.module.ts
+++ b/client/src/app/shared/shared-users/shared-users.module.ts
@@ -1,6 +1,7 @@
1 1
2import { NgModule } from '@angular/core' 2import { NgModule } from '@angular/core'
3import { SharedMainModule } from '../shared-main/shared-main.module' 3import { SharedMainModule } from '../shared-main/shared-main.module'
4import { TwoFactorService } from './two-factor.service'
4import { UserAdminService } from './user-admin.service' 5import { UserAdminService } from './user-admin.service'
5import { UserSignupService } from './user-signup.service' 6import { UserSignupService } from './user-signup.service'
6 7
@@ -15,7 +16,8 @@ import { UserSignupService } from './user-signup.service'
15 16
16 providers: [ 17 providers: [
17 UserSignupService, 18 UserSignupService,
18 UserAdminService 19 UserAdminService,
20 TwoFactorService
19 ] 21 ]
20}) 22})
21export class SharedUsersModule { } 23export class SharedUsersModule { }
diff --git a/client/src/app/shared/shared-users/two-factor.service.ts b/client/src/app/shared/shared-users/two-factor.service.ts
new file mode 100644
index 000000000..9ff916f15
--- /dev/null
+++ b/client/src/app/shared/shared-users/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/shared/shared-video-playlist/video-add-to-playlist.component.ts b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts
index e019fdd26..f81de7c6b 100644
--- a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts
+++ b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts
@@ -3,7 +3,7 @@ import { Subject, Subscription } from 'rxjs'
3import { debounceTime, filter } from 'rxjs/operators' 3import { debounceTime, filter } from 'rxjs/operators'
4import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core' 4import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core'
5import { AuthService, DisableForReuseHook, Notifier } from '@app/core' 5import { AuthService, DisableForReuseHook, Notifier } from '@app/core'
6import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 6import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
7import { secondsToTime } from '@shared/core-utils' 7import { secondsToTime } from '@shared/core-utils'
8import { 8import {
9 Video, 9 Video,
@@ -59,7 +59,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
59 private pendingAddId: number 59 private pendingAddId: number
60 60
61 constructor ( 61 constructor (
62 protected formValidatorService: FormValidatorService, 62 protected formReactiveService: FormReactiveService,
63 private authService: AuthService, 63 private authService: AuthService,
64 private notifier: Notifier, 64 private notifier: Notifier,
65 private videoPlaylistService: VideoPlaylistService, 65 private videoPlaylistService: VideoPlaylistService,