From bc90883f1a5e9c4ecb76ae358734b85be515af7f Mon Sep 17 00:00:00 2001
From: Chocobozzz <me@florianbigard.com>
Date: Thu, 30 Apr 2020 15:03:09 +0200
Subject: [PATCH] Handle external login errors

---
 client/src/app/login/login.component.html    | 10 +++--
 client/src/app/login/login.component.scss    |  9 ++--
 client/src/app/login/login.component.ts      |  7 ++++
 server/lib/auth.ts                           | 18 +++++---
 server/lib/client-html.ts                    |  2 +-
 server/lib/plugins/register-helpers-store.ts |  4 +-
 server/models/server/plugin.ts               | 44 +++++++++++++-------
 7 files changed, 61 insertions(+), 33 deletions(-)

diff --git a/client/src/app/login/login.component.html b/client/src/app/login/login.component.html
index b0639d8ca..a935c86c3 100644
--- a/client/src/app/login/login.component.html
+++ b/client/src/app/login/login.component.html
@@ -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>
diff --git a/client/src/app/login/login.component.scss b/client/src/app/login/login.component.scss
index ccc98c12a..db9f78f7c 100644
--- a/client/src/app/login/login.component.scss
+++ b/client/src/app/login/login.component.scss
@@ -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);
-      }
     }
   }
 }
diff --git a/client/src/app/login/login.component.ts b/client/src/app/login/login.component.ts
index 5db8d3dbb..5d935cb49 100644
--- a/client/src/app/login/login.component.ts
+++ b/client/src/app/login/login.component.ts
@@ -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
diff --git a/server/lib/auth.ts b/server/lib/auth.ts
index 1fa896f6e..7c1dd1139 100644
--- a/server/lib/auth.ts
+++ b/server/lib/auth.ts
@@ -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
   }
 
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts
index 572bd03bd..4a4b0d12f 100644
--- a/server/lib/client-html.ts
+++ b/server/lib/client-html.ts
@@ -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
       })
 
diff --git a/server/lib/plugins/register-helpers-store.ts b/server/lib/plugins/register-helpers-store.ts
index a3ec7ef6a..e337b1cb0 100644
--- a/server/lib/plugins/register-helpers-store.ts
+++ b/server/lib/plugins/register-helpers-store.ts
@@ -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),
 
diff --git a/server/models/server/plugin.ts b/server/models/server/plugin.ts
index 83c873c5b..3f88ac26d 100644
--- a/server/models/server/plugin.ts
+++ b/server/models/server/plugin.ts
@@ -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]
           }
         }