]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - client/src/app/+login/login.component.ts
Fix terms/code of conduct link toggle
[github/Chocobozzz/PeerTube.git] / client / src / app / +login / login.component.ts
index cbc51ee216132fba8e82a6e65647fd394867b7dd..e486df61dd4bac37bb3b9573de2b5446be97718f 100644 (file)
@@ -1,12 +1,15 @@
 import { environment } from 'src/environments/environment'
 import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'
-import { ActivatedRoute } from '@angular/router'
-import { AuthService, Notifier, RedirectService, UserService } from '@app/core'
+import { ActivatedRoute, Router } from '@angular/router'
+import { AuthService, Notifier, RedirectService, SessionStorageService, UserService } from '@app/core'
 import { HooksService } from '@app/core/plugins/hooks.service'
-import { FormReactive, FormValidatorService, LoginValidatorsService } from '@app/shared/shared-forms'
-import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
-import { I18n } from '@ngx-translate/i18n-polyfill'
-import { RegisteredExternalAuthConfig, ServerConfig } from '@shared/models'
+import { LOGIN_PASSWORD_VALIDATOR, LOGIN_USERNAME_VALIDATOR } from '@app/shared/form-validators/login-validators'
+import { USER_OTP_TOKEN_VALIDATOR } from '@app/shared/form-validators/user-validators'
+import { FormReactive, FormReactiveService, InputTextComponent } from '@app/shared/shared-forms'
+import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance'
+import { NgbAccordionDirective, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
+import { getExternalAuthHref } from '@shared/core-utils'
+import { RegisteredExternalAuthConfig, ServerConfig, ServerErrorCode } from '@shared/models'
 
 @Component({
   selector: 'my-login',
@@ -15,9 +18,13 @@ import { RegisteredExternalAuthConfig, ServerConfig } from '@shared/models'
 })
 
 export class LoginComponent extends FormReactive implements OnInit, AfterViewInit {
-  @ViewChild('usernameInput', { static: false }) usernameInput: ElementRef
+  private static SESSION_STORAGE_REDIRECT_URL_KEY = 'login-previous-url'
+
   @ViewChild('forgotPasswordModal', { static: true }) forgotPasswordModal: ElementRef
+  @ViewChild('otpTokenInput') otpTokenInput: InputTextComponent
+  @ViewChild('instanceAboutAccordion') instanceAboutAccordion: InstanceAboutAccordionComponent
 
+  accordion: NgbAccordionDirective
   error: string = null
   forgotPasswordEmail = ''
 
@@ -25,20 +32,30 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni
   externalAuthError = false
   externalLogins: string[] = []
 
+  instanceInformationPanels = {
+    terms: true,
+    administrators: false,
+    features: false,
+    moderation: false,
+    codeOfConduct: false
+  }
+
+  otpStep = false
+
   private openedForgotPasswordModal: NgbModalRef
   private serverConfig: ServerConfig
 
   constructor (
-    protected formValidatorService: FormValidatorService,
+    protected formReactiveService: FormReactiveService,
     private route: ActivatedRoute,
     private modalService: NgbModal,
-    private loginValidatorsService: LoginValidatorsService,
     private authService: AuthService,
     private userService: UserService,
     private redirectService: RedirectService,
     private notifier: Notifier,
     private hooks: HooksService,
-    private i18n: I18n
+    private storage: SessionStorageService,
+    private router: Router
   ) {
     super()
   }
@@ -47,6 +64,19 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni
     return this.serverConfig.signup.allowed === true
   }
 
+  get instanceName () {
+    return this.serverConfig.instance.name
+  }
+
+  onTermsClick (event: Event, instanceInformation: HTMLElement) {
+    event.preventDefault()
+
+    if (this.instanceAboutAccordion) {
+      this.instanceAboutAccordion.expandTerms()
+      instanceInformation.scrollIntoView({ behavior: 'smooth' })
+    }
+  }
+
   isEmailDisabled () {
     return this.serverConfig.email.enabled === false
   }
@@ -54,6 +84,16 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni
   ngOnInit () {
     const snapshot = this.route.snapshot
 
+    // Avoid undefined errors when accessing form error properties
+    this.buildForm({
+      'username': LOGIN_USERNAME_VALIDATOR,
+      'password': LOGIN_PASSWORD_VALIDATOR,
+      'otp-token': {
+        VALIDATORS: [], // Will be set dynamically
+        MESSAGES: USER_OTP_TOKEN_VALIDATOR.MESSAGES
+      }
+    })
+
     this.serverConfig = snapshot.data.serverConfig
 
     if (snapshot.queryParams.externalAuthToken) {
@@ -66,17 +106,13 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni
       return
     }
 
-    this.buildForm({
-      username: this.loginValidatorsService.LOGIN_USERNAME,
-      password: this.loginValidatorsService.LOGIN_PASSWORD
-    })
+    const previousUrl = this.redirectService.getPreviousUrl()
+    if (previousUrl && previousUrl !== '/') {
+      this.storage.setItem(LoginComponent.SESSION_STORAGE_REDIRECT_URL_KEY, previousUrl)
+    }
   }
 
   ngAfterViewInit () {
-    if (this.usernameInput) {
-      this.usernameInput.nativeElement.focus()
-    }
-
     this.hooks.runAction('action:login.init', 'login')
   }
 
@@ -85,36 +121,42 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni
   }
 
   getAuthHref (auth: RegisteredExternalAuthConfig) {
-    return environment.apiUrl + `/plugins/${auth.name}/${auth.version}/auth/${auth.authName}`
+    return getExternalAuthHref(environment.apiUrl, auth)
   }
 
   login () {
     this.error = null
 
-    const { username, password } = this.form.value
+    const options = {
+      username: this.form.value['username'],
+      password: this.form.value['password'],
+      otpToken: this.form.value['otp-token']
+    }
 
-    this.authService.login(username, password)
-      .subscribe(
-        () => this.redirectService.redirectToPreviousRoute(),
+    this.authService.login(options)
+      .pipe()
+      .subscribe({
+        next: () => this.redirectService.redirectToPreviousRoute(),
 
-        err => this.handleError(err)
-      )
+        error: err => {
+          this.handleError(err)
+        }
+      })
   }
 
   askResetPassword () {
     this.userService.askResetPassword(this.forgotPasswordEmail)
-      .subscribe(
-        () => {
-          const message = this.i18n(
-            'An email with the reset password instructions will be sent to {{email}}. The link will expire within 1 hour.',
-            { email: this.forgotPasswordEmail }
-          )
+      .subscribe({
+        next: () => {
+          const message = $localize`An email with the reset password instructions will be sent to ${this.forgotPasswordEmail}.
+The link will expire within 1 hour.`
+
           this.notifier.success(message)
           this.hideForgotPasswordModal()
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   openForgotPasswordModal () {
@@ -125,23 +167,68 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni
     this.openedForgotPasswordModal.close()
   }
 
+  onInstanceAboutAccordionInit (instanceAboutAccordion: InstanceAboutAccordionComponent) {
+    this.accordion = instanceAboutAccordion.accordion
+  }
+
+  hasUsernameUppercase () {
+    return this.form.value['username'].match(/[A-Z]/)
+  }
+
   private loadExternalAuthToken (username: string, token: string) {
     this.isAuthenticatedWithExternalAuth = true
 
-    this.authService.login(username, null, token)
-    .subscribe(
-      () => this.redirectService.redirectToPreviousRoute(),
+    this.authService.login({ username, password: null, token })
+      .subscribe({
+        next: () => {
+          const redirectUrl = this.storage.getItem(LoginComponent.SESSION_STORAGE_REDIRECT_URL_KEY)
+          if (redirectUrl) {
+            this.storage.removeItem(LoginComponent.SESSION_STORAGE_REDIRECT_URL_KEY)
+            return this.router.navigateByUrl(redirectUrl)
+          }
 
-      err => {
-        this.handleError(err)
-        this.isAuthenticatedWithExternalAuth = false
-      }
-    )
+          this.redirectService.redirectToLatestSessionRoute()
+        },
+
+        error: err => {
+          this.handleError(err)
+          this.isAuthenticatedWithExternalAuth = false
+        }
+      })
   }
 
   private handleError (err: any) {
-    if (err.message.indexOf('credentials are invalid') !== -1) this.error = this.i18n('Incorrect username or password.')
-    else if (err.message.indexOf('blocked') !== -1) this.error = this.i18n('You account is blocked.')
-    else this.error = err.message
+    if (this.authService.isOTPMissingError(err)) {
+      this.otpStep = true
+
+      setTimeout(() => {
+        this.form.get('otp-token').setValidators(USER_OTP_TOKEN_VALIDATOR.VALIDATORS)
+        this.otpTokenInput.focus()
+      })
+
+      return
+    }
+
+    if (err.message.includes('credentials are invalid')) {
+      this.error = $localize`Incorrect username or password.`
+      return
+    }
+
+    if (err.message.includes('blocked')) {
+      this.error = $localize`Your account is blocked.`
+      return
+    }
+
+    if (err.body?.code === ServerErrorCode.ACCOUNT_WAITING_FOR_APPROVAL) {
+      this.error = $localize`This account is awaiting approval by moderators.`
+      return
+    }
+
+    if (err.body?.code === ServerErrorCode.ACCOUNT_APPROVAL_REJECTED) {
+      this.error = $localize`Registration approval has been rejected for this account.`
+      return
+    }
+
+    this.error = err.message
   }
 }