Handle external login errors
authorChocobozzz <me@florianbigard.com>
Thu, 30 Apr 2020 13:03:09 +0000 (15:03 +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
server/lib/auth.ts
server/lib/client-html.ts
server/lib/plugins/register-helpers-store.ts
server/models/server/plugin.ts

index b0639d8cae0a5412ae40d7e1a702a6d3dc1c4893..a935c86c37f62050a2d4f63c40b9420abf9a546c 100644 (file)
@@ -3,7 +3,11 @@
     Login
   </div>
 
-  <ng-container *ngIf="!isAuthenticatedWithExternalAuth">
+  <div class="alert alert-danger" i18n *ngIf="externalAuthError">
+    Sorry but there was an issue with the external login process. Please <a routerLink="/about">contact an administrator</a>.
+  </div>
+
+  <ng-container *ngIf="!externalAuthError && !isAuthenticatedWithExternalAuth">
     <div class="alert alert-info" *ngIf="signupAllowed === false" role="alert">
       <h6 class="alert-heading" i18n>
         If you are looking for an account…
@@ -63,8 +67,8 @@
       <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">
+        <div>
+          <a class="external-login-block" *ngFor="let auth of getExternalLogins()" [href]="getAuthHref(auth)" role="button">
             {{ auth.authDisplayName }}
           </a>
         </div>
index ccc98c12af69e58dae21b89f8f6fcf83ec1df334..db9f78f7c40130116fc17c412e21bc349c4a7822 100644 (file)
@@ -38,7 +38,6 @@ input[type=submit] {
   }
 
   .external-login-blocks {
-    padding: 0 10px 10px 10px;
     min-width: 200px;
 
     .block-title {
@@ -46,9 +45,12 @@ input[type=submit] {
     }
 
     .external-login-block {
+      @include disable-default-a-behaviour;
+
       cursor: pointer;
       border: 1px solid #d1d7e0;
       border-radius: 5px;
+      color: var(--mainForegroundColor);
       margin: 10px 10px 0 0;
       display: flex;
       justify-content: center;
@@ -59,11 +61,6 @@ input[type=submit] {
       &:hover {
         background-color: rgba(209, 215, 224, 0.5)
       }
-
-      a {
-        @include disable-default-a-behaviour;
-        color: var(--mainForegroundColor);
-      }
     }
   }
 }
index 5db8d3dbb84ddb22f19978585ece05744885b6ca..5d935cb49d7235734c0fa6126666d631abb59873 100644 (file)
@@ -23,7 +23,9 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni
 
   error: string = null
   forgotPasswordEmail = ''
+
   isAuthenticatedWithExternalAuth = false
+  externalAuthError = false
   externalLogins: string[] = []
 
   private openedForgotPasswordModal: NgbModalRef
@@ -61,6 +63,11 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni
       return
     }
 
+    if (snapshot.queryParams.externalAuthError) {
+      this.externalAuthError = true
+      return
+    }
+
     this.buildForm({
       username: this.loginValidatorsService.LOGIN_USERNAME,
       password: this.loginValidatorsService.LOGIN_PASSWORD
index 1fa896f6ef6ec9324f3b497c1ea1f82ed7cb3f77..7c1dd1139b4ba9cb00a553f678d345ed7ecce485 100644 (file)
@@ -83,10 +83,13 @@ async function onExternalUserAuthenticated (options: {
     return
   }
 
-  if (!isAuthResultValid(npmName, authName, authResult)) return
-
   const { res } = authResult
 
+  if (!isAuthResultValid(npmName, authName, authResult)) {
+    res.redirect('/login?externalAuthError=true')
+    return
+  }
+
   logger.info('Generating auth bypass token for %s in auth %s of plugin %s.', authResult.username, authName, npmName)
 
   const bypassToken = await generateRandomString(32)
@@ -238,24 +241,27 @@ function proxifyExternalAuthBypass (req: express.Request, res: express.Response)
 
 function isAuthResultValid (npmName: string, authName: string, result: RegisterServerAuthenticatedResult) {
   if (!isUserUsernameValid(result.username)) {
-    logger.error('Auth method %s of plugin %s did not provide a valid username.', authName, npmName, { result })
+    logger.error('Auth method %s of plugin %s did not provide a valid username.', authName, npmName, { username: result.username })
     return false
   }
 
   if (!result.email) {
-    logger.error('Auth method %s of plugin %s did not provide a valid email.', authName, npmName, { result })
+    logger.error('Auth method %s of plugin %s did not provide a valid email.', authName, npmName, { email: result.email })
     return false
   }
 
   // role is optional
   if (result.role && !isUserRoleValid(result.role)) {
-    logger.error('Auth method %s of plugin %s did not provide a valid role.', authName, npmName, { result })
+    logger.error('Auth method %s of plugin %s did not provide a valid role.', authName, npmName, { role: result.role })
     return false
   }
 
   // display name is optional
   if (result.displayName && !isUserDisplayNameValid(result.displayName)) {
-    logger.error('Auth method %s of plugin %s did not provide a valid display name.', authName, npmName, { result })
+    logger.error(
+      'Auth method %s of plugin %s did not provide a valid display name.',
+      authName, npmName, { displayName: result.displayName }
+    )
     return false
   }
 
index 572bd03bd3c8a007f924203ece10656bab40c8db..4a4b0d12f9799b1d21455a0cafb16e925e625504 100644 (file)
@@ -119,7 +119,7 @@ export class ClientHtml {
       // Save locale in cookies
       res.cookie('clientLanguage', lang, {
         secure: WEBSERVER.SCHEME === 'https',
-        sameSite: true,
+        sameSite: 'none',
         maxAge: 1000 * 3600 * 24 * 90 // 3 months
       })
 
index a3ec7ef6a0eb521964df925aa439d0f41d4ebb4b..e337b1cb09853a4ce48c3cbb11a28f21691cb97f 100644 (file)
@@ -230,9 +230,9 @@ export class RegisterHelpersStore {
 
   private buildSettingsManager (): PluginSettingsManager {
     return {
-      getSetting: (name: string) => PluginModel.getSetting(this.plugin.name, this.plugin.type, name),
+      getSetting: (name: string) => PluginModel.getSetting(this.plugin.name, this.plugin.type, name, this.settings),
 
-      getSettings: (names: string[]) => PluginModel.getSettings(this.plugin.name, this.plugin.type, names),
+      getSettings: (names: string[]) => PluginModel.getSettings(this.plugin.name, this.plugin.type, names, this.settings),
 
       setSetting: (name: string, value: string) => PluginModel.setSetting(this.plugin.name, this.plugin.type, name, value),
 
index 83c873c5bad8dabc86c476e0c868a87f835e874e..3f88ac26d5b160ad79442fec107992cc2ff1e45b 100644 (file)
@@ -1,5 +1,10 @@
+import * as Bluebird from 'bluebird'
+import { FindAndCountOptions, json } from 'sequelize'
 import { AllowNull, Column, CreatedAt, DataType, DefaultScope, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
-import { getSort, throwIfNotValid } from '../utils'
+import { MPlugin, MPluginFormattable } from '@server/typings/models'
+import { PeerTubePlugin } from '../../../shared/models/plugins/peertube-plugin.model'
+import { PluginType } from '../../../shared/models/plugins/plugin.type'
+import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model'
 import {
   isPluginDescriptionValid,
   isPluginHomepage,
@@ -7,12 +12,7 @@ import {
   isPluginTypeValid,
   isPluginVersionValid
 } from '../../helpers/custom-validators/plugins'
-import { PluginType } from '../../../shared/models/plugins/plugin.type'
-import { PeerTubePlugin } from '../../../shared/models/plugins/peertube-plugin.model'
-import { FindAndCountOptions, json } from 'sequelize'
-import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model'
-import * as Bluebird from 'bluebird'
-import { MPlugin, MPluginFormattable } from '@server/typings/models'
+import { getSort, throwIfNotValid } from '../utils'
 
 @DefaultScope(() => ({
   attributes: {
@@ -112,7 +112,7 @@ export class PluginModel extends Model<PluginModel> {
     return PluginModel.findOne(query)
   }
 
-  static getSetting (pluginName: string, pluginType: PluginType, settingName: string) {
+  static getSetting (pluginName: string, pluginType: PluginType, settingName: string, registeredSettings: RegisterServerSettingOptions[]) {
     const query = {
       attributes: [ 'settings' ],
       where: {
@@ -123,13 +123,23 @@ export class PluginModel extends Model<PluginModel> {
 
     return PluginModel.findOne(query)
       .then(p => {
-        if (!p || !p.settings) return undefined
+        if (!p || p.settings === undefined) {
+          const registered = registeredSettings.find(s => s.name === settingName)
+          if (!registered || registered.default === undefined) return undefined
+
+          return registered.default
+        }
 
         return p.settings[settingName]
       })
   }
 
-  static getSettings (pluginName: string, pluginType: PluginType, settingNames: string[]) {
+  static getSettings (
+    pluginName: string,
+    pluginType: PluginType,
+    settingNames: string[],
+    registeredSettings: RegisterServerSettingOptions[]
+  ) {
     const query = {
       attributes: [ 'settings' ],
       where: {
@@ -140,13 +150,17 @@ export class PluginModel extends Model<PluginModel> {
 
     return PluginModel.findOne(query)
       .then(p => {
-        if (!p || !p.settings) return {}
+        const result: { [settingName: string ]: string | boolean } = {}
 
-        const result: { [settingName: string ]: string } = {}
+        for (const name of settingNames) {
+          if (!p || p.settings[name] === undefined) {
+            const registered = registeredSettings.find(s => s.name === name)
 
-        for (const key of Object.keys(p.settings)) {
-          if (settingNames.includes(key)) {
-            result[key] = p.settings[key]
+            if (registered?.default !== undefined) {
+              result[name] = registered.default
+            }
+          } else {
+            result[name] = p.settings[name]
           }
         }