Add external login buttons
authorChocobozzz <me@florianbigard.com>
Wed, 29 Apr 2020 08:42:35 +0000 (10:42 +0200)
committerChocobozzz <chocobozzz@cpy.re>
Mon, 4 May 2020 14:21:39 +0000 (16:21 +0200)
client/src/app/login/login.component.html
client/src/app/login/login.component.scss
client/src/app/login/login.component.ts
client/src/sass/include/_variables.scss
server/controllers/api/config.ts
server/lib/plugins/plugin-manager.ts
shared/models/server/server-config.model.ts

index 3e53e58540103d698aacf9b96e670f54031afaca..b0639d8cae0a5412ae40d7e1a702a6d3dc1c4893 100644 (file)
       <span *ngIf="error === 'User email is not verified.'"> <a i18n routerLink="/verify-account/ask-send-email">Request new verification email.</a></span>
     </div>
 
-    <form role="form" (ngSubmit)="login()" [formGroup]="form">
-      <div class="form-group">
-        <div>
-          <label i18n for="username">User</label>
-          <input
-            type="text" id="username" i18n-placeholder placeholder="Username or email address" required tabindex="1"
-            formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" #emailInput
-          >
-          <a i18n *ngIf="signupAllowed === true" routerLink="/signup" class="create-an-account">
-            or create an account
-          </a>
+    <div class="login-form-and-externals">
+
+      <form role="form" (ngSubmit)="login()" [formGroup]="form">
+        <div class="form-group">
+          <div>
+            <label i18n for="username">User</label>
+            <input
+              type="text" id="username" i18n-placeholder placeholder="Username or email address" required tabindex="1"
+              formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" #usernameInput
+            >
+            <a i18n *ngIf="signupAllowed === true" routerLink="/signup" class="create-an-account">
+              or create an account
+            </a>
+          </div>
+
+          <div *ngIf="formErrors.username" class="form-error">
+            {{ formErrors.username }}
+          </div>
         </div>
 
-        <div *ngIf="formErrors.username" class="form-error">
-          {{ formErrors.username }}
+        <div class="form-group">
+          <label i18n for="password">Password</label>
+          <div>
+            <input
+              type="password" name="password" id="password" i18n-placeholder placeholder="Password" required tabindex="2" autocomplete="current-password"
+              formControlName="password" class="form-control" [ngClass]="{ 'input-error': formErrors['password'] }"
+            >
+            <a i18n-title class="forgot-password-button" (click)="openForgotPasswordModal()" title="Click here to reset your password">I forgot my password</a>
+          </div>
+          <div *ngIf="formErrors.password" class="form-error">
+            {{ formErrors.password }}
+          </div>
         </div>
-      </div>
 
-      <div class="form-group">
-        <label i18n for="password">Password</label>
-        <div>
-          <input
-            type="password" name="password" id="password" i18n-placeholder placeholder="Password" required tabindex="2" autocomplete="current-password"
-            formControlName="password" class="form-control" [ngClass]="{ 'input-error': formErrors['password'] }"
-          >
-          <a i18n-title class="forgot-password-button" (click)="openForgotPasswordModal()" title="Click here to reset your password">I forgot my password</a>
-        </div>
-        <div *ngIf="formErrors.password" class="form-error">
-          {{ formErrors.password }}
+        <input type="submit" i18n-value value="Login" [disabled]="!form.valid">
+      </form>
+
+      <div class="external-login-blocks" *ngIf="getExternalLogins().length !== 0">
+        <div class="block-title" i18n>Or sign in with</div>
+
+        <div class="external-login-block">
+          <a *ngFor="let auth of getExternalLogins()" [href]="getAuthHref(auth)" role="button">
+            {{ auth.authDisplayName }}
+          </a>
         </div>
       </div>
+    </div>
 
-      <input type="submit" i18n-value value="Login" [disabled]="!form.valid">
-    </form>
   </ng-container>
 </div>
 
index 8ac231475ecc52c1ab015c18e04eb72a981752cd..ccc98c12af69e58dae21b89f8f6fcf83ec1df334 100644 (file)
@@ -21,9 +21,49 @@ input[type=submit] {
   color: var(--mainForegroundColor);
   cursor: pointer;
   transition: opacity cubic-bezier(0.39, 0.575, 0.565, 1);
-  
+
   &:hover {
     text-decoration: none !important;
     opacity: .7 !important;
   }
 }
+
+.login-form-and-externals {
+  display: flex;
+  flex-wrap: wrap;
+  font-size: 15px;
+
+  form {
+    margin: 0 50px 20px 0;
+  }
+
+  .external-login-blocks {
+    padding: 0 10px 10px 10px;
+    min-width: 200px;
+
+    .block-title {
+      font-weight: $font-semibold;
+    }
+
+    .external-login-block {
+      cursor: pointer;
+      border: 1px solid #d1d7e0;
+      border-radius: 5px;
+      margin: 10px 10px 0 0;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      min-height: 35px;
+      min-width: 100px;
+
+      &:hover {
+        background-color: rgba(209, 215, 224, 0.5)
+      }
+
+      a {
+        @include disable-default-a-behaviour;
+        color: var(--mainForegroundColor);
+      }
+    }
+  }
+}
index 9c8f5c52ed493df353c3ec77559d981e8ff6cd97..5db8d3dbb84ddb22f19978585ece05744885b6ca 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'
+import { Component, ElementRef, OnInit, ViewChild, AfterViewInit } from '@angular/core'
 import { Notifier, RedirectService } from '@app/core'
 import { UserService } from '@app/shared'
 import { AuthService } from '../core'
@@ -8,7 +8,8 @@ import { FormValidatorService } from '@app/shared/forms/form-validators/form-val
 import { LoginValidatorsService } from '@app/shared/forms/form-validators/login-validators.service'
 import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
 import { ActivatedRoute } from '@angular/router'
-import { ServerConfig } from '@shared/models/server/server-config.model'
+import { ServerConfig, RegisteredExternalAuthConfig } from '@shared/models/server/server-config.model'
+import { environment } from 'src/environments/environment'
 
 @Component({
   selector: 'my-login',
@@ -16,13 +17,14 @@ import { ServerConfig } from '@shared/models/server/server-config.model'
   styleUrls: [ './login.component.scss' ]
 })
 
-export class LoginComponent extends FormReactive implements OnInit {
-  @ViewChild('emailInput', { static: true }) input: ElementRef
+export class LoginComponent extends FormReactive implements OnInit, AfterViewInit {
+  @ViewChild('usernameInput', { static: false }) usernameInput: ElementRef
   @ViewChild('forgotPasswordModal', { static: true }) forgotPasswordModal: ElementRef
 
   error: string = null
   forgotPasswordEmail = ''
   isAuthenticatedWithExternalAuth = false
+  externalLogins: string[] = []
 
   private openedForgotPasswordModal: NgbModalRef
   private serverConfig: ServerConfig
@@ -63,8 +65,20 @@ export class LoginComponent extends FormReactive implements OnInit {
       username: this.loginValidatorsService.LOGIN_USERNAME,
       password: this.loginValidatorsService.LOGIN_PASSWORD
     })
+  }
+
+  ngAfterViewInit () {
+    if (this.usernameInput) {
+      this.usernameInput.nativeElement.focus()
+    }
+  }
+
+  getExternalLogins () {
+    return this.serverConfig.plugin.registeredExternalAuths
+  }
 
-    this.input.nativeElement.focus()
+  getAuthHref (auth: RegisteredExternalAuthConfig) {
+    return environment.apiUrl + `/plugins/${auth.name}/${auth.version}/auth/${auth.authName}`
   }
 
   login () {
index 72eb7b61ed84beeb9e14267284a2fd9ef9518e12..46f1e99f7f8bc46ecc03ac409e7fa1baecf1740a 100644 (file)
@@ -53,8 +53,6 @@ $sub-menu-color: #F7F7F7;
 $footer-height: 30px;
 $footer-margin: 30px;
 
-$footer-border-color: $header-border-color;
-
 $separator-border-color: rgba(0, 0, 0, 0.10);
 
 $video-miniature-width: 238px;
index e8941bc732dcb072a19e6ce13239c835866fd471..85f3ad3d942d114c04aaab5c7790f3e5385e7167 100644 (file)
@@ -278,6 +278,8 @@ function getIdAndPassAuthPlugins () {
     for (const auth of p.idAndPassAuths) {
       result.push({
         npmName: p.npmName,
+        name: p.name,
+        version: p.version,
         authName: auth.authName,
         weight: auth.getWeight()
       })
@@ -294,6 +296,8 @@ function getExternalAuthsPlugins () {
     for (const auth of p.externalAuths) {
       result.push({
         npmName: p.npmName,
+        name: p.name,
+        version: p.version,
         authName: auth.authName,
         authDisplayName: auth.authDisplayName
       })
index 38336bcc64eda8a24fd76b4385fdcd8a19bcc842..f7b84b1ff1b7013d5e89651e5107bc69ba8f059d 100644 (file)
@@ -106,14 +106,24 @@ export class PluginManager implements ServerHook {
 
   getIdAndPassAuths () {
     return this.getRegisteredPlugins()
-      .map(p => ({ npmName: p.npmName, idAndPassAuths: p.registerHelpersStore.getIdAndPassAuths() }))
+      .map(p => ({
+        npmName: p.npmName,
+        name: p.name,
+        version: p.version,
+        idAndPassAuths: p.registerHelpersStore.getIdAndPassAuths()
+      }))
       .filter(v => v.idAndPassAuths.length !== 0)
   }
 
   getExternalAuths () {
     return this.getRegisteredPlugins()
-               .map(p => ({ npmName: p.npmName, externalAuths: p.registerHelpersStore.getExternalAuths() }))
-               .filter(v => v.externalAuths.length !== 0)
+      .map(p => ({
+        npmName: p.npmName,
+        name: p.name,
+        version: p.version,
+        externalAuths: p.registerHelpersStore.getExternalAuths()
+      }))
+      .filter(v => v.externalAuths.length !== 0)
   }
 
   getRegisteredSettings (npmName: string) {
index 0ff0792167d65076a22a112545de7ec0dafe9874..a1f9b3b5de0e5384e38b9b88b2e399ce2399579a 100644 (file)
@@ -14,12 +14,16 @@ export interface ServerConfigTheme extends ServerConfigPlugin {
 
 export interface RegisteredExternalAuthConfig {
   npmName: string
+  name: string
+  version: string
   authName: string
   authDisplayName: string
 }
 
 export interface RegisteredIdAndPassAuthConfig {
   npmName: string
+  name: string
+  version: string
   authName: string
   weight: number
 }