diff options
author | Chocobozzz <me@florianbigard.com> | 2020-04-28 14:49:03 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2020-05-04 16:21:39 +0200 |
commit | 4a8d113b9b57d97ff13ad1608798eabca99643e4 (patch) | |
tree | 7c6e1ba86bff31680ba6c8ba4bd903b997592269 | |
parent | 98813e69bccc568eff771cfcaf907ccdd82ce3f1 (diff) | |
download | PeerTube-4a8d113b9b57d97ff13ad1608798eabca99643e4.tar.gz PeerTube-4a8d113b9b57d97ff13ad1608798eabca99643e4.tar.zst PeerTube-4a8d113b9b57d97ff13ad1608798eabca99643e4.zip |
Begin support for external auths
-rw-r--r-- | .eslintrc.json | 1 | ||||
-rw-r--r-- | client/src/app/core/auth/auth.service.ts | 4 | ||||
-rw-r--r-- | client/src/app/core/server/server.service.ts | 4 | ||||
-rw-r--r-- | client/src/app/login/login.component.html | 88 | ||||
-rw-r--r-- | client/src/app/login/login.component.ts | 36 | ||||
-rw-r--r-- | server/controllers/api/config.ts | 56 | ||||
-rw-r--r-- | server/controllers/plugins.ts | 20 | ||||
-rw-r--r-- | server/lib/auth.ts | 225 | ||||
-rw-r--r-- | server/lib/oauth-model.ts | 2 | ||||
-rw-r--r-- | server/lib/plugins/register-helpers-store.ts | 42 | ||||
-rw-r--r-- | server/middlewares/validators/plugins.ts | 25 | ||||
-rw-r--r-- | server/typings/express.ts | 3 | ||||
-rw-r--r-- | shared/models/plugins/register-server-auth.model.ts | 40 | ||||
-rw-r--r-- | shared/models/plugins/register-server-setting.model.ts | 2 | ||||
-rw-r--r-- | shared/models/server/server-config.model.ts | 16 |
15 files changed, 393 insertions, 171 deletions
diff --git a/.eslintrc.json b/.eslintrc.json index 9b578b186..e71be9bc5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json | |||
@@ -83,6 +83,7 @@ | |||
83 | "@typescript-eslint/consistent-type-definitions": "off", | 83 | "@typescript-eslint/consistent-type-definitions": "off", |
84 | "@typescript-eslint/no-misused-promises": "off", | 84 | "@typescript-eslint/no-misused-promises": "off", |
85 | "@typescript-eslint/no-namespace": "off", | 85 | "@typescript-eslint/no-namespace": "off", |
86 | "@typescript-eslint/no-empty-interface": "off", | ||
86 | "@typescript-eslint/no-extraneous-class": "off", | 87 | "@typescript-eslint/no-extraneous-class": "off", |
87 | // bugged but useful | 88 | // bugged but useful |
88 | "@typescript-eslint/restrict-plus-operands": "off" | 89 | "@typescript-eslint/restrict-plus-operands": "off" |
diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts index 9ae008e39..61d755ba0 100644 --- a/client/src/app/core/auth/auth.service.ts +++ b/client/src/app/core/auth/auth.service.ts | |||
@@ -145,7 +145,7 @@ export class AuthService { | |||
145 | return !!this.getAccessToken() | 145 | return !!this.getAccessToken() |
146 | } | 146 | } |
147 | 147 | ||
148 | login (username: string, password: string) { | 148 | login (username: string, password: string, token?: string) { |
149 | // Form url encoded | 149 | // Form url encoded |
150 | const body = { | 150 | const body = { |
151 | client_id: this.clientId, | 151 | client_id: this.clientId, |
@@ -157,6 +157,8 @@ export class AuthService { | |||
157 | password | 157 | password |
158 | } | 158 | } |
159 | 159 | ||
160 | if (token) Object.assign(body, { externalAuthToken: token }) | ||
161 | |||
160 | const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded') | 162 | const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded') |
161 | return this.http.post<UserLogin>(AuthService.BASE_TOKEN_URL, objectToUrlEncoded(body), { headers }) | 163 | return this.http.post<UserLogin>(AuthService.BASE_TOKEN_URL, objectToUrlEncoded(body), { headers }) |
162 | .pipe( | 164 | .pipe( |
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts index da7832b32..eac8f85e4 100644 --- a/client/src/app/core/server/server.service.ts +++ b/client/src/app/core/server/server.service.ts | |||
@@ -54,7 +54,9 @@ export class ServerService { | |||
54 | } | 54 | } |
55 | }, | 55 | }, |
56 | plugin: { | 56 | plugin: { |
57 | registered: [] | 57 | registered: [], |
58 | registeredExternalAuths: [], | ||
59 | registeredIdAndPassAuths: [] | ||
58 | }, | 60 | }, |
59 | theme: { | 61 | theme: { |
60 | registered: [], | 62 | registered: [], |
diff --git a/client/src/app/login/login.component.html b/client/src/app/login/login.component.html index 3a2d4b876..3e53e5854 100644 --- a/client/src/app/login/login.component.html +++ b/client/src/app/login/login.component.html | |||
@@ -3,59 +3,61 @@ | |||
3 | Login | 3 | Login |
4 | </div> | 4 | </div> |
5 | 5 | ||
6 | <div class="alert alert-info" *ngIf="signupAllowed === false" role="alert"> | 6 | <ng-container *ngIf="!isAuthenticatedWithExternalAuth"> |
7 | <h6 class="alert-heading" i18n> | 7 | <div class="alert alert-info" *ngIf="signupAllowed === false" role="alert"> |
8 | If you are looking for an account… | 8 | <h6 class="alert-heading" i18n> |
9 | </h6> | 9 | If you are looking for an account… |
10 | </h6> | ||
10 | 11 | ||
11 | <div i18n> | 12 | <div i18n> |
12 | Currently this instance doesn't allow for user registration, but you can find an instance | 13 | Currently this instance doesn't allow for user registration, but you can find an instance |
13 | that gives you the possibility to sign up for an account and upload your videos there. | 14 | that gives you the possibility to sign up for an account and upload your videos there. |
14 | 15 | ||
15 | <br /> | 16 | <br /> |
16 | 17 | ||
17 | Find yours among multiple instances at <a class="alert-link" href="https://joinpeertube.org/instances" target="_blank" rel="noopener noreferrer">https://joinpeertube.org/instances</a>. | 18 | Find yours among multiple instances at <a class="alert-link" href="https://joinpeertube.org/instances" target="_blank" rel="noopener noreferrer">https://joinpeertube.org/instances</a>. |
18 | </div> | ||
19 | </div> | ||
20 | |||
21 | <div *ngIf="error" class="alert alert-danger">{{ error }} | ||
22 | <span *ngIf="error === 'User email is not verified.'"> <a i18n routerLink="/verify-account/ask-send-email">Request new verification email.</a></span> | ||
23 | </div> | ||
24 | |||
25 | <form role="form" (ngSubmit)="login()" [formGroup]="form"> | ||
26 | <div class="form-group"> | ||
27 | <div> | ||
28 | <label i18n for="username">User</label> | ||
29 | <input | ||
30 | type="text" id="username" i18n-placeholder placeholder="Username or email address" required tabindex="1" | ||
31 | formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" #emailInput | ||
32 | > | ||
33 | <a i18n *ngIf="signupAllowed === true" routerLink="/signup" class="create-an-account"> | ||
34 | or create an account | ||
35 | </a> | ||
36 | </div> | 19 | </div> |
20 | </div> | ||
37 | 21 | ||
38 | <div *ngIf="formErrors.username" class="form-error"> | 22 | <div *ngIf="error" class="alert alert-danger">{{ error }} |
39 | {{ formErrors.username }} | 23 | <span *ngIf="error === 'User email is not verified.'"> <a i18n routerLink="/verify-account/ask-send-email">Request new verification email.</a></span> |
40 | </div> | ||
41 | </div> | 24 | </div> |
42 | 25 | ||
43 | <div class="form-group"> | 26 | <form role="form" (ngSubmit)="login()" [formGroup]="form"> |
44 | <label i18n for="password">Password</label> | 27 | <div class="form-group"> |
45 | <div> | 28 | <div> |
46 | <input | 29 | <label i18n for="username">User</label> |
47 | type="password" name="password" id="password" i18n-placeholder placeholder="Password" required tabindex="2" autocomplete="current-password" | 30 | <input |
48 | formControlName="password" class="form-control" [ngClass]="{ 'input-error': formErrors['password'] }" | 31 | type="text" id="username" i18n-placeholder placeholder="Username or email address" required tabindex="1" |
49 | > | 32 | formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" #emailInput |
50 | <a i18n-title class="forgot-password-button" (click)="openForgotPasswordModal()" title="Click here to reset your password">I forgot my password</a> | 33 | > |
34 | <a i18n *ngIf="signupAllowed === true" routerLink="/signup" class="create-an-account"> | ||
35 | or create an account | ||
36 | </a> | ||
37 | </div> | ||
38 | |||
39 | <div *ngIf="formErrors.username" class="form-error"> | ||
40 | {{ formErrors.username }} | ||
41 | </div> | ||
51 | </div> | 42 | </div> |
52 | <div *ngIf="formErrors.password" class="form-error"> | 43 | |
53 | {{ formErrors.password }} | 44 | <div class="form-group"> |
45 | <label i18n for="password">Password</label> | ||
46 | <div> | ||
47 | <input | ||
48 | type="password" name="password" id="password" i18n-placeholder placeholder="Password" required tabindex="2" autocomplete="current-password" | ||
49 | formControlName="password" class="form-control" [ngClass]="{ 'input-error': formErrors['password'] }" | ||
50 | > | ||
51 | <a i18n-title class="forgot-password-button" (click)="openForgotPasswordModal()" title="Click here to reset your password">I forgot my password</a> | ||
52 | </div> | ||
53 | <div *ngIf="formErrors.password" class="form-error"> | ||
54 | {{ formErrors.password }} | ||
55 | </div> | ||
54 | </div> | 56 | </div> |
55 | </div> | ||
56 | 57 | ||
57 | <input type="submit" i18n-value value="Login" [disabled]="!form.valid"> | 58 | <input type="submit" i18n-value value="Login" [disabled]="!form.valid"> |
58 | </form> | 59 | </form> |
60 | </ng-container> | ||
59 | </div> | 61 | </div> |
60 | 62 | ||
61 | <ng-template #forgotPasswordModal> | 63 | <ng-template #forgotPasswordModal> |
diff --git a/client/src/app/login/login.component.ts b/client/src/app/login/login.component.ts index 580f28822..9c8f5c52e 100644 --- a/client/src/app/login/login.component.ts +++ b/client/src/app/login/login.component.ts | |||
@@ -22,6 +22,7 @@ export class LoginComponent extends FormReactive implements OnInit { | |||
22 | 22 | ||
23 | error: string = null | 23 | error: string = null |
24 | forgotPasswordEmail = '' | 24 | forgotPasswordEmail = '' |
25 | isAuthenticatedWithExternalAuth = false | ||
25 | 26 | ||
26 | private openedForgotPasswordModal: NgbModalRef | 27 | private openedForgotPasswordModal: NgbModalRef |
27 | private serverConfig: ServerConfig | 28 | private serverConfig: ServerConfig |
@@ -49,7 +50,14 @@ export class LoginComponent extends FormReactive implements OnInit { | |||
49 | } | 50 | } |
50 | 51 | ||
51 | ngOnInit () { | 52 | ngOnInit () { |
52 | this.serverConfig = this.route.snapshot.data.serverConfig | 53 | const snapshot = this.route.snapshot |
54 | |||
55 | this.serverConfig = snapshot.data.serverConfig | ||
56 | |||
57 | if (snapshot.queryParams.externalAuthToken) { | ||
58 | this.loadExternalAuthToken(snapshot.queryParams.username, snapshot.queryParams.externalAuthToken) | ||
59 | return | ||
60 | } | ||
53 | 61 | ||
54 | this.buildForm({ | 62 | this.buildForm({ |
55 | username: this.loginValidatorsService.LOGIN_USERNAME, | 63 | username: this.loginValidatorsService.LOGIN_USERNAME, |
@@ -68,11 +76,7 @@ export class LoginComponent extends FormReactive implements OnInit { | |||
68 | .subscribe( | 76 | .subscribe( |
69 | () => this.redirectService.redirectToPreviousRoute(), | 77 | () => this.redirectService.redirectToPreviousRoute(), |
70 | 78 | ||
71 | err => { | 79 | err => this.handleError(err) |
72 | if (err.message.indexOf('credentials are invalid') !== -1) this.error = this.i18n('Incorrect username or password.') | ||
73 | else if (err.message.indexOf('blocked') !== -1) this.error = this.i18n('You account is blocked.') | ||
74 | else this.error = err.message | ||
75 | } | ||
76 | ) | 80 | ) |
77 | } | 81 | } |
78 | 82 | ||
@@ -99,4 +103,24 @@ export class LoginComponent extends FormReactive implements OnInit { | |||
99 | hideForgotPasswordModal () { | 103 | hideForgotPasswordModal () { |
100 | this.openedForgotPasswordModal.close() | 104 | this.openedForgotPasswordModal.close() |
101 | } | 105 | } |
106 | |||
107 | private loadExternalAuthToken (username: string, token: string) { | ||
108 | this.isAuthenticatedWithExternalAuth = true | ||
109 | |||
110 | this.authService.login(username, null, token) | ||
111 | .subscribe( | ||
112 | () => this.redirectService.redirectToPreviousRoute(), | ||
113 | |||
114 | err => { | ||
115 | this.handleError(err) | ||
116 | this.isAuthenticatedWithExternalAuth = false | ||
117 | } | ||
118 | ) | ||
119 | } | ||
120 | |||
121 | private handleError (err: any) { | ||
122 | if (err.message.indexOf('credentials are invalid') !== -1) this.error = this.i18n('Incorrect username or password.') | ||
123 | else if (err.message.indexOf('blocked') !== -1) this.error = this.i18n('You account is blocked.') | ||
124 | else this.error = err.message | ||
125 | } | ||
102 | } | 126 | } |
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index 06fe30371..e8941bc73 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts | |||
@@ -1,22 +1,22 @@ | |||
1 | import { Hooks } from '@server/lib/plugins/hooks' | ||
1 | import * as express from 'express' | 2 | import * as express from 'express' |
3 | import { remove, writeJSON } from 'fs-extra' | ||
2 | import { snakeCase } from 'lodash' | 4 | import { snakeCase } from 'lodash' |
3 | import { ServerConfig, UserRight } from '../../../shared' | 5 | import validator from 'validator' |
6 | import { RegisteredExternalAuthConfig, RegisteredIdAndPassAuthConfig, ServerConfig, UserRight } from '../../../shared' | ||
4 | import { About } from '../../../shared/models/server/about.model' | 7 | import { About } from '../../../shared/models/server/about.model' |
5 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' | 8 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' |
6 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' | ||
7 | import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION } from '../../initializers/constants' | ||
8 | import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares' | ||
9 | import { customConfigUpdateValidator } from '../../middlewares/validators/config' | ||
10 | import { ClientHtml } from '../../lib/client-html' | ||
11 | import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger' | 9 | import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger' |
12 | import { remove, writeJSON } from 'fs-extra' | ||
13 | import { getServerCommit } from '../../helpers/utils' | ||
14 | import validator from 'validator' | ||
15 | import { objectConverter } from '../../helpers/core-utils' | 10 | import { objectConverter } from '../../helpers/core-utils' |
11 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' | ||
12 | import { getServerCommit } from '../../helpers/utils' | ||
16 | import { CONFIG, isEmailEnabled, reloadConfig } from '../../initializers/config' | 13 | import { CONFIG, isEmailEnabled, reloadConfig } from '../../initializers/config' |
14 | import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION } from '../../initializers/constants' | ||
15 | import { ClientHtml } from '../../lib/client-html' | ||
17 | import { PluginManager } from '../../lib/plugins/plugin-manager' | 16 | import { PluginManager } from '../../lib/plugins/plugin-manager' |
18 | import { getThemeOrDefault } from '../../lib/plugins/theme-utils' | 17 | import { getThemeOrDefault } from '../../lib/plugins/theme-utils' |
19 | import { Hooks } from '@server/lib/plugins/hooks' | 18 | import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares' |
19 | import { customConfigUpdateValidator } from '../../middlewares/validators/config' | ||
20 | 20 | ||
21 | const configRouter = express.Router() | 21 | const configRouter = express.Router() |
22 | 22 | ||
@@ -79,7 +79,9 @@ async function getConfig (req: express.Request, res: express.Response) { | |||
79 | } | 79 | } |
80 | }, | 80 | }, |
81 | plugin: { | 81 | plugin: { |
82 | registered: getRegisteredPlugins() | 82 | registered: getRegisteredPlugins(), |
83 | registeredExternalAuths: getExternalAuthsPlugins(), | ||
84 | registeredIdAndPassAuths: getIdAndPassAuthPlugins() | ||
83 | }, | 85 | }, |
84 | theme: { | 86 | theme: { |
85 | registered: getRegisteredThemes(), | 87 | registered: getRegisteredThemes(), |
@@ -269,6 +271,38 @@ function getRegisteredPlugins () { | |||
269 | })) | 271 | })) |
270 | } | 272 | } |
271 | 273 | ||
274 | function getIdAndPassAuthPlugins () { | ||
275 | const result: RegisteredIdAndPassAuthConfig[] = [] | ||
276 | |||
277 | for (const p of PluginManager.Instance.getIdAndPassAuths()) { | ||
278 | for (const auth of p.idAndPassAuths) { | ||
279 | result.push({ | ||
280 | npmName: p.npmName, | ||
281 | authName: auth.authName, | ||
282 | weight: auth.getWeight() | ||
283 | }) | ||
284 | } | ||
285 | } | ||
286 | |||
287 | return result | ||
288 | } | ||
289 | |||
290 | function getExternalAuthsPlugins () { | ||
291 | const result: RegisteredExternalAuthConfig[] = [] | ||
292 | |||
293 | for (const p of PluginManager.Instance.getExternalAuths()) { | ||
294 | for (const auth of p.externalAuths) { | ||
295 | result.push({ | ||
296 | npmName: p.npmName, | ||
297 | authName: auth.authName, | ||
298 | authDisplayName: auth.authDisplayName | ||
299 | }) | ||
300 | } | ||
301 | } | ||
302 | |||
303 | return result | ||
304 | } | ||
305 | |||
272 | // --------------------------------------------------------------------------- | 306 | // --------------------------------------------------------------------------- |
273 | 307 | ||
274 | export { | 308 | export { |
diff --git a/server/controllers/plugins.ts b/server/controllers/plugins.ts index 1fc49b646..f12e1c0f5 100644 --- a/server/controllers/plugins.ts +++ b/server/controllers/plugins.ts | |||
@@ -2,11 +2,12 @@ import * as express from 'express' | |||
2 | import { PLUGIN_GLOBAL_CSS_PATH } from '../initializers/constants' | 2 | import { PLUGIN_GLOBAL_CSS_PATH } from '../initializers/constants' |
3 | import { join } from 'path' | 3 | import { join } from 'path' |
4 | import { PluginManager, RegisteredPlugin } from '../lib/plugins/plugin-manager' | 4 | import { PluginManager, RegisteredPlugin } from '../lib/plugins/plugin-manager' |
5 | import { getPluginValidator, pluginStaticDirectoryValidator } from '../middlewares/validators/plugins' | 5 | import { getPluginValidator, pluginStaticDirectoryValidator, getExternalAuthValidator } from '../middlewares/validators/plugins' |
6 | import { serveThemeCSSValidator } from '../middlewares/validators/themes' | 6 | import { serveThemeCSSValidator } from '../middlewares/validators/themes' |
7 | import { PluginType } from '../../shared/models/plugins/plugin.type' | 7 | import { PluginType } from '../../shared/models/plugins/plugin.type' |
8 | import { isTestInstance } from '../helpers/core-utils' | 8 | import { isTestInstance } from '../helpers/core-utils' |
9 | import { getCompleteLocale, is18nLocale } from '../../shared/models/i18n' | 9 | import { getCompleteLocale, is18nLocale } from '../../shared/models/i18n' |
10 | import { logger } from '@server/helpers/logger' | ||
10 | 11 | ||
11 | const sendFileOptions = { | 12 | const sendFileOptions = { |
12 | maxAge: '30 days', | 13 | maxAge: '30 days', |
@@ -23,6 +24,12 @@ pluginsRouter.get('/plugins/translations/:locale.json', | |||
23 | getPluginTranslations | 24 | getPluginTranslations |
24 | ) | 25 | ) |
25 | 26 | ||
27 | pluginsRouter.get('/plugins/:pluginName/:pluginVersion/auth/:authName', | ||
28 | getPluginValidator(PluginType.PLUGIN), | ||
29 | getExternalAuthValidator, | ||
30 | handleAuthInPlugin | ||
31 | ) | ||
32 | |||
26 | pluginsRouter.get('/plugins/:pluginName/:pluginVersion/static/:staticEndpoint(*)', | 33 | pluginsRouter.get('/plugins/:pluginName/:pluginVersion/static/:staticEndpoint(*)', |
27 | getPluginValidator(PluginType.PLUGIN), | 34 | getPluginValidator(PluginType.PLUGIN), |
28 | pluginStaticDirectoryValidator, | 35 | pluginStaticDirectoryValidator, |
@@ -134,3 +141,14 @@ function serveThemeCSSDirectory (req: express.Request, res: express.Response) { | |||
134 | 141 | ||
135 | return res.sendFile(join(plugin.path, staticEndpoint), sendFileOptions) | 142 | return res.sendFile(join(plugin.path, staticEndpoint), sendFileOptions) |
136 | } | 143 | } |
144 | |||
145 | function handleAuthInPlugin (req: express.Request, res: express.Response) { | ||
146 | const authOptions = res.locals.externalAuth | ||
147 | |||
148 | try { | ||
149 | logger.debug('Forwarding auth plugin request in %s of plugin %s.', authOptions.authName, res.locals.registeredPlugin.npmName) | ||
150 | authOptions.onAuthRequest(req, res) | ||
151 | } catch (err) { | ||
152 | logger.error('Forward request error in auth %s of plugin %s.', authOptions.authName, res.locals.registeredPlugin.npmName) | ||
153 | } | ||
154 | } | ||
diff --git a/server/lib/auth.ts b/server/lib/auth.ts index 5a6dd9dec..eaae5fdf3 100644 --- a/server/lib/auth.ts +++ b/server/lib/auth.ts | |||
@@ -1,13 +1,18 @@ | |||
1 | import * as express from 'express' | 1 | import { isUserDisplayNameValid, isUserRoleValid, isUserUsernameValid } from '@server/helpers/custom-validators/users' |
2 | import { OAUTH_LIFETIME } from '@server/initializers/constants' | ||
3 | import * as OAuthServer from 'express-oauth-server' | ||
4 | import { PluginManager } from '@server/lib/plugins/plugin-manager' | ||
5 | import { RegisterServerAuthPassOptions } from '@shared/models/plugins/register-server-auth.model' | ||
6 | import { logger } from '@server/helpers/logger' | 2 | import { logger } from '@server/helpers/logger' |
7 | import { UserRole } from '@shared/models' | 3 | import { generateRandomString } from '@server/helpers/utils' |
4 | import { OAUTH_LIFETIME, WEBSERVER } from '@server/initializers/constants' | ||
8 | import { revokeToken } from '@server/lib/oauth-model' | 5 | import { revokeToken } from '@server/lib/oauth-model' |
6 | import { PluginManager } from '@server/lib/plugins/plugin-manager' | ||
9 | import { OAuthTokenModel } from '@server/models/oauth/oauth-token' | 7 | import { OAuthTokenModel } from '@server/models/oauth/oauth-token' |
10 | import { isUserUsernameValid, isUserRoleValid, isUserDisplayNameValid } from '@server/helpers/custom-validators/users' | 8 | import { UserRole } from '@shared/models' |
9 | import { | ||
10 | RegisterServerAuthenticatedResult, | ||
11 | RegisterServerAuthPassOptions, | ||
12 | RegisterServerExternalAuthenticatedResult | ||
13 | } from '@shared/models/plugins/register-server-auth.model' | ||
14 | import * as express from 'express' | ||
15 | import * as OAuthServer from 'express-oauth-server' | ||
11 | 16 | ||
12 | const oAuthServer = new OAuthServer({ | 17 | const oAuthServer = new OAuthServer({ |
13 | useErrorHandler: true, | 18 | useErrorHandler: true, |
@@ -17,15 +22,28 @@ const oAuthServer = new OAuthServer({ | |||
17 | model: require('./oauth-model') | 22 | model: require('./oauth-model') |
18 | }) | 23 | }) |
19 | 24 | ||
20 | function onExternalAuthPlugin (npmName: string, username: string, email: string) { | 25 | // Token is the key, expiration date is the value |
21 | 26 | const authBypassTokens = new Map<string, { | |
22 | } | 27 | expires: Date |
28 | user: { | ||
29 | username: string | ||
30 | email: string | ||
31 | displayName: string | ||
32 | role: UserRole | ||
33 | } | ||
34 | authName: string | ||
35 | npmName: string | ||
36 | }>() | ||
23 | 37 | ||
24 | async function handleIdAndPassLogin (req: express.Request, res: express.Response, next: express.NextFunction) { | 38 | async function handleIdAndPassLogin (req: express.Request, res: express.Response, next: express.NextFunction) { |
25 | const grantType = req.body.grant_type | 39 | const grantType = req.body.grant_type |
26 | 40 | ||
27 | if (grantType === 'password') await proxifyPasswordGrant(req, res) | 41 | if (grantType === 'password') { |
28 | else if (grantType === 'refresh_token') await proxifyRefreshGrant(req, res) | 42 | if (req.body.externalAuthToken) proxifyExternalAuthBypass(req, res) |
43 | else await proxifyPasswordGrant(req, res) | ||
44 | } else if (grantType === 'refresh_token') { | ||
45 | await proxifyRefreshGrant(req, res) | ||
46 | } | ||
29 | 47 | ||
30 | return forwardTokenReq(req, res, next) | 48 | return forwardTokenReq(req, res, next) |
31 | } | 49 | } |
@@ -53,31 +71,60 @@ async function handleTokenRevocation (req: express.Request, res: express.Respons | |||
53 | return res.sendStatus(200) | 71 | return res.sendStatus(200) |
54 | } | 72 | } |
55 | 73 | ||
56 | // --------------------------------------------------------------------------- | 74 | async function onExternalUserAuthenticated (options: { |
75 | npmName: string | ||
76 | authName: string | ||
77 | authResult: RegisterServerExternalAuthenticatedResult | ||
78 | }) { | ||
79 | const { npmName, authName, authResult } = options | ||
57 | 80 | ||
58 | export { | 81 | if (!authResult.req || !authResult.res) { |
59 | oAuthServer, | 82 | logger.error('Cannot authenticate external user for auth %s of plugin %s: no req or res are provided.', authName, npmName) |
60 | handleIdAndPassLogin, | 83 | return |
61 | onExternalAuthPlugin, | 84 | } |
62 | handleTokenRevocation | 85 | |
86 | if (!isAuthResultValid(npmName, authName, authResult)) return | ||
87 | |||
88 | const { res } = authResult | ||
89 | |||
90 | logger.info('Generating auth bypass token for %s in auth %s of plugin %s.', authResult.username, authName, npmName) | ||
91 | |||
92 | const bypassToken = await generateRandomString(32) | ||
93 | const tokenLifetime = 1000 * 60 * 5 // 5 minutes | ||
94 | |||
95 | const expires = new Date() | ||
96 | expires.setTime(expires.getTime() + tokenLifetime) | ||
97 | |||
98 | const user = buildUserResult(authResult) | ||
99 | authBypassTokens.set(bypassToken, { | ||
100 | expires, | ||
101 | user, | ||
102 | npmName, | ||
103 | authName | ||
104 | }) | ||
105 | |||
106 | res.redirect(`/login?externalAuthToken=${bypassToken}&username=${user.username}`) | ||
63 | } | 107 | } |
64 | 108 | ||
65 | // --------------------------------------------------------------------------- | 109 | // --------------------------------------------------------------------------- |
66 | 110 | ||
67 | function forwardTokenReq (req: express.Request, res: express.Response, next: express.NextFunction) { | 111 | export { oAuthServer, handleIdAndPassLogin, onExternalUserAuthenticated, handleTokenRevocation } |
112 | |||
113 | // --------------------------------------------------------------------------- | ||
114 | |||
115 | function forwardTokenReq (req: express.Request, res: express.Response, next?: express.NextFunction) { | ||
68 | return oAuthServer.token()(req, res, err => { | 116 | return oAuthServer.token()(req, res, err => { |
69 | if (err) { | 117 | if (err) { |
70 | logger.warn('Login error.', { err }) | 118 | logger.warn('Login error.', { err }) |
71 | 119 | ||
72 | return res.status(err.status) | 120 | return res.status(err.status) |
73 | .json({ | 121 | .json({ |
74 | error: err.message, | 122 | error: err.message, |
75 | code: err.name | 123 | code: err.name |
76 | }) | 124 | }) |
77 | .end() | ||
78 | } | 125 | } |
79 | 126 | ||
80 | return next() | 127 | if (next) return next() |
81 | }) | 128 | }) |
82 | } | 129 | } |
83 | 130 | ||
@@ -131,50 +178,96 @@ async function proxifyPasswordGrant (req: express.Request, res: express.Response | |||
131 | 178 | ||
132 | try { | 179 | try { |
133 | const loginResult = await authOptions.login(loginOptions) | 180 | const loginResult = await authOptions.login(loginOptions) |
134 | if (loginResult) { | 181 | |
135 | logger.info( | 182 | if (!loginResult) continue |
136 | 'Login success with auth method %s of plugin %s for %s.', | 183 | if (!isAuthResultValid(pluginAuth.npmName, authOptions.authName, loginResult)) continue |
137 | authName, npmName, loginOptions.id | 184 | |
138 | ) | 185 | logger.info( |
139 | 186 | 'Login success with auth method %s of plugin %s for %s.', | |
140 | if (!isUserUsernameValid(loginResult.username)) { | 187 | authName, npmName, loginOptions.id |
141 | logger.error('Auth method %s of plugin %s did not provide a valid username.', authName, npmName, { loginResult }) | 188 | ) |
142 | continue | 189 | |
143 | } | 190 | res.locals.bypassLogin = { |
144 | 191 | bypass: true, | |
145 | if (!loginResult.email) { | 192 | pluginName: pluginAuth.npmName, |
146 | logger.error('Auth method %s of plugin %s did not provide a valid email.', authName, npmName, { loginResult }) | 193 | authName: authOptions.authName, |
147 | continue | 194 | user: buildUserResult(loginResult) |
148 | } | ||
149 | |||
150 | // role is optional | ||
151 | if (loginResult.role && !isUserRoleValid(loginResult.role)) { | ||
152 | logger.error('Auth method %s of plugin %s did not provide a valid role.', authName, npmName, { loginResult }) | ||
153 | continue | ||
154 | } | ||
155 | |||
156 | // display name is optional | ||
157 | if (loginResult.displayName && !isUserDisplayNameValid(loginResult.displayName)) { | ||
158 | logger.error('Auth method %s of plugin %s did not provide a valid display name.', authName, npmName, { loginResult }) | ||
159 | continue | ||
160 | } | ||
161 | |||
162 | res.locals.bypassLogin = { | ||
163 | bypass: true, | ||
164 | pluginName: pluginAuth.npmName, | ||
165 | authName: authOptions.authName, | ||
166 | user: { | ||
167 | username: loginResult.username, | ||
168 | email: loginResult.email, | ||
169 | role: loginResult.role || UserRole.USER, | ||
170 | displayName: loginResult.displayName || loginResult.username | ||
171 | } | ||
172 | } | ||
173 | |||
174 | return | ||
175 | } | 195 | } |
196 | |||
197 | return | ||
176 | } catch (err) { | 198 | } catch (err) { |
177 | logger.error('Error in auth method %s of plugin %s', authOptions.authName, pluginAuth.npmName, { err }) | 199 | logger.error('Error in auth method %s of plugin %s', authOptions.authName, pluginAuth.npmName, { err }) |
178 | } | 200 | } |
179 | } | 201 | } |
180 | } | 202 | } |
203 | |||
204 | function proxifyExternalAuthBypass (req: express.Request, res: express.Response) { | ||
205 | const obj = authBypassTokens.get(req.body.externalAuthToken) | ||
206 | if (!obj) { | ||
207 | logger.error('Cannot authenticate user with unknown bypass token') | ||
208 | return res.sendStatus(400) | ||
209 | } | ||
210 | |||
211 | const { expires, user, authName, npmName } = obj | ||
212 | |||
213 | const now = new Date() | ||
214 | if (now.getTime() > expires.getTime()) { | ||
215 | logger.error('Cannot authenticate user with an expired bypass token') | ||
216 | return res.sendStatus(400) | ||
217 | } | ||
218 | |||
219 | if (user.username !== req.body.username) { | ||
220 | logger.error('Cannot authenticate user %s with invalid username %s.', req.body.username) | ||
221 | return res.sendStatus(400) | ||
222 | } | ||
223 | |||
224 | // Bypass oauth library validation | ||
225 | req.body.password = 'fake' | ||
226 | |||
227 | logger.info( | ||
228 | 'Auth success with external auth method %s of plugin %s for %s.', | ||
229 | authName, npmName, user.email | ||
230 | ) | ||
231 | |||
232 | res.locals.bypassLogin = { | ||
233 | bypass: true, | ||
234 | pluginName: npmName, | ||
235 | authName: authName, | ||
236 | user | ||
237 | } | ||
238 | } | ||
239 | |||
240 | function isAuthResultValid (npmName: string, authName: string, result: RegisterServerAuthenticatedResult) { | ||
241 | if (!isUserUsernameValid(result.username)) { | ||
242 | logger.error('Auth method %s of plugin %s did not provide a valid username.', authName, npmName, { result }) | ||
243 | return false | ||
244 | } | ||
245 | |||
246 | if (!result.email) { | ||
247 | logger.error('Auth method %s of plugin %s did not provide a valid email.', authName, npmName, { result }) | ||
248 | return false | ||
249 | } | ||
250 | |||
251 | // role is optional | ||
252 | if (result.role && !isUserRoleValid(result.role)) { | ||
253 | logger.error('Auth method %s of plugin %s did not provide a valid role.', authName, npmName, { result }) | ||
254 | return false | ||
255 | } | ||
256 | |||
257 | // display name is optional | ||
258 | if (result.displayName && !isUserDisplayNameValid(result.displayName)) { | ||
259 | logger.error('Auth method %s of plugin %s did not provide a valid display name.', authName, npmName, { result }) | ||
260 | return false | ||
261 | } | ||
262 | |||
263 | return true | ||
264 | } | ||
265 | |||
266 | function buildUserResult (pluginResult: RegisterServerAuthenticatedResult) { | ||
267 | return { | ||
268 | username: pluginResult.username, | ||
269 | email: pluginResult.email, | ||
270 | role: pluginResult.role || UserRole.USER, | ||
271 | displayName: pluginResult.displayName || pluginResult.username | ||
272 | } | ||
273 | } | ||
diff --git a/server/lib/oauth-model.ts b/server/lib/oauth-model.ts index 6eb0e4473..8b9975bb4 100644 --- a/server/lib/oauth-model.ts +++ b/server/lib/oauth-model.ts | |||
@@ -98,7 +98,7 @@ async function getRefreshToken (refreshToken: string) { | |||
98 | return tokenInfo | 98 | return tokenInfo |
99 | } | 99 | } |
100 | 100 | ||
101 | async function getUser (usernameOrEmail: string, password: string) { | 101 | async function getUser (usernameOrEmail?: string, password?: string) { |
102 | const res: express.Response = this.request.res | 102 | const res: express.Response = this.request.res |
103 | 103 | ||
104 | // Special treatment coming from a plugin | 104 | // Special treatment coming from a plugin |
diff --git a/server/lib/plugins/register-helpers-store.ts b/server/lib/plugins/register-helpers-store.ts index 687974ccf..277f2b687 100644 --- a/server/lib/plugins/register-helpers-store.ts +++ b/server/lib/plugins/register-helpers-store.ts | |||
@@ -1,31 +1,21 @@ | |||
1 | import { PluginSettingsManager } from '@shared/models/plugins/plugin-settings-manager.model' | 1 | import { logger } from '@server/helpers/logger' |
2 | import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PLAYLIST_PRIVACIES, VIDEO_PRIVACIES } from '@server/initializers/constants' | ||
3 | import { onExternalUserAuthenticated } from '@server/lib/auth' | ||
2 | import { PluginModel } from '@server/models/server/plugin' | 4 | import { PluginModel } from '@server/models/server/plugin' |
5 | import { RegisterServerOptions } from '@server/typings/plugins' | ||
6 | import { PluginPlaylistPrivacyManager } from '@shared/models/plugins/plugin-playlist-privacy-manager.model' | ||
7 | import { PluginSettingsManager } from '@shared/models/plugins/plugin-settings-manager.model' | ||
3 | import { PluginStorageManager } from '@shared/models/plugins/plugin-storage-manager.model' | 8 | import { PluginStorageManager } from '@shared/models/plugins/plugin-storage-manager.model' |
9 | import { PluginVideoCategoryManager } from '@shared/models/plugins/plugin-video-category-manager.model' | ||
4 | import { PluginVideoLanguageManager } from '@shared/models/plugins/plugin-video-language-manager.model' | 10 | import { PluginVideoLanguageManager } from '@shared/models/plugins/plugin-video-language-manager.model' |
5 | import { | ||
6 | VIDEO_CATEGORIES, | ||
7 | VIDEO_LANGUAGES, | ||
8 | VIDEO_LICENCES, | ||
9 | VIDEO_PLAYLIST_PRIVACIES, | ||
10 | VIDEO_PRIVACIES | ||
11 | } from '@server/initializers/constants' | ||
12 | import { PluginVideoLicenceManager } from '@shared/models/plugins/plugin-video-licence-manager.model' | 11 | import { PluginVideoLicenceManager } from '@shared/models/plugins/plugin-video-licence-manager.model' |
13 | import { PluginVideoCategoryManager } from '@shared/models/plugins/plugin-video-category-manager.model' | 12 | import { PluginVideoPrivacyManager } from '@shared/models/plugins/plugin-video-privacy-manager.model' |
14 | import { RegisterServerOptions } from '@server/typings/plugins' | 13 | import { RegisterServerAuthExternalOptions, RegisterServerAuthExternalResult, RegisterServerAuthPassOptions, RegisterServerExternalAuthenticatedResult } from '@shared/models/plugins/register-server-auth.model' |
15 | import { buildPluginHelpers } from './plugin-helpers' | ||
16 | import { logger } from '@server/helpers/logger' | ||
17 | import { RegisterServerHookOptions } from '@shared/models/plugins/register-server-hook.model' | 14 | import { RegisterServerHookOptions } from '@shared/models/plugins/register-server-hook.model' |
18 | import { serverHookObject } from '@shared/models/plugins/server-hook.model' | ||
19 | import { RegisterServerSettingOptions } from '@shared/models/plugins/register-server-setting.model' | 15 | import { RegisterServerSettingOptions } from '@shared/models/plugins/register-server-setting.model' |
16 | import { serverHookObject } from '@shared/models/plugins/server-hook.model' | ||
20 | import * as express from 'express' | 17 | import * as express from 'express' |
21 | import { PluginVideoPrivacyManager } from '@shared/models/plugins/plugin-video-privacy-manager.model' | 18 | import { buildPluginHelpers } from './plugin-helpers' |
22 | import { PluginPlaylistPrivacyManager } from '@shared/models/plugins/plugin-playlist-privacy-manager.model' | ||
23 | import { | ||
24 | RegisterServerAuthExternalOptions, | ||
25 | RegisterServerAuthExternalResult, | ||
26 | RegisterServerAuthPassOptions | ||
27 | } from '@shared/models/plugins/register-server-auth.model' | ||
28 | import { onExternalAuthPlugin } from '@server/lib/auth' | ||
29 | 19 | ||
30 | type AlterableVideoConstant = 'language' | 'licence' | 'category' | 'privacy' | 'playlistPrivacy' | 20 | type AlterableVideoConstant = 'language' | 'licence' | 'category' | 'privacy' | 'playlistPrivacy' |
31 | type VideoConstant = { [key in number | string]: string } | 21 | type VideoConstant = { [key in number | string]: string } |
@@ -187,8 +177,14 @@ export class RegisterHelpersStore { | |||
187 | this.externalAuths.push(options) | 177 | this.externalAuths.push(options) |
188 | 178 | ||
189 | return { | 179 | return { |
190 | onAuth (options: { username: string, email: string }): void { | 180 | userAuthenticated (result: RegisterServerExternalAuthenticatedResult): void { |
191 | onExternalAuthPlugin(self.npmName, options.username, options.email) | 181 | onExternalUserAuthenticated({ |
182 | npmName: self.npmName, | ||
183 | authName: options.authName, | ||
184 | authResult: result | ||
185 | }).catch(err => { | ||
186 | logger.error('Cannot execute onExternalUserAuthenticated.', { npmName: self.npmName, authName: options.authName, err }) | ||
187 | }) | ||
192 | } | 188 | } |
193 | } as RegisterServerAuthExternalResult | 189 | } as RegisterServerAuthExternalResult |
194 | } | 190 | } |
diff --git a/server/middlewares/validators/plugins.ts b/server/middlewares/validators/plugins.ts index 65765f473..2cb49ec43 100644 --- a/server/middlewares/validators/plugins.ts +++ b/server/middlewares/validators/plugins.ts | |||
@@ -4,7 +4,7 @@ import { logger } from '../../helpers/logger' | |||
4 | import { areValidationErrors } from './utils' | 4 | import { areValidationErrors } from './utils' |
5 | import { isNpmPluginNameValid, isPluginNameValid, isPluginTypeValid, isPluginVersionValid } from '../../helpers/custom-validators/plugins' | 5 | import { isNpmPluginNameValid, isPluginNameValid, isPluginTypeValid, isPluginVersionValid } from '../../helpers/custom-validators/plugins' |
6 | import { PluginManager } from '../../lib/plugins/plugin-manager' | 6 | import { PluginManager } from '../../lib/plugins/plugin-manager' |
7 | import { isBooleanValid, isSafePath, toBooleanOrNull } from '../../helpers/custom-validators/misc' | 7 | import { isBooleanValid, isSafePath, toBooleanOrNull, exists } from '../../helpers/custom-validators/misc' |
8 | import { PluginModel } from '../../models/server/plugin' | 8 | import { PluginModel } from '../../models/server/plugin' |
9 | import { InstallOrUpdatePlugin } from '../../../shared/models/plugins/install-plugin.model' | 9 | import { InstallOrUpdatePlugin } from '../../../shared/models/plugins/install-plugin.model' |
10 | import { PluginType } from '../../../shared/models/plugins/plugin.type' | 10 | import { PluginType } from '../../../shared/models/plugins/plugin.type' |
@@ -40,6 +40,26 @@ const getPluginValidator = (pluginType: PluginType, withVersion = true) => { | |||
40 | ]) | 40 | ]) |
41 | } | 41 | } |
42 | 42 | ||
43 | const getExternalAuthValidator = [ | ||
44 | param('authName').custom(exists).withMessage('Should have a valid auth name'), | ||
45 | |||
46 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
47 | logger.debug('Checking getExternalAuthValidator parameters', { parameters: req.params }) | ||
48 | |||
49 | if (areValidationErrors(req, res)) return | ||
50 | |||
51 | const plugin = res.locals.registeredPlugin | ||
52 | if (!plugin.registerHelpersStore) return res.sendStatus(404) | ||
53 | |||
54 | const externalAuth = plugin.registerHelpersStore.getExternalAuths().find(a => a.authName === req.params.authName) | ||
55 | if (!externalAuth) return res.sendStatus(404) | ||
56 | |||
57 | res.locals.externalAuth = externalAuth | ||
58 | |||
59 | return next() | ||
60 | } | ||
61 | ] | ||
62 | |||
43 | const pluginStaticDirectoryValidator = [ | 63 | const pluginStaticDirectoryValidator = [ |
44 | param('staticEndpoint').custom(isSafePath).withMessage('Should have a valid static endpoint'), | 64 | param('staticEndpoint').custom(isSafePath).withMessage('Should have a valid static endpoint'), |
45 | 65 | ||
@@ -175,5 +195,6 @@ export { | |||
175 | listAvailablePluginsValidator, | 195 | listAvailablePluginsValidator, |
176 | existingPluginValidator, | 196 | existingPluginValidator, |
177 | installOrUpdatePluginValidator, | 197 | installOrUpdatePluginValidator, |
178 | listPluginsValidator | 198 | listPluginsValidator, |
199 | getExternalAuthValidator | ||
179 | } | 200 | } |
diff --git a/server/typings/express.ts b/server/typings/express.ts index e6e120403..5973496f1 100644 --- a/server/typings/express.ts +++ b/server/typings/express.ts | |||
@@ -29,6 +29,7 @@ import { MPlugin, MServer } from '@server/typings/models/server' | |||
29 | import { MServerBlocklist } from './models/server/server-blocklist' | 29 | import { MServerBlocklist } from './models/server/server-blocklist' |
30 | import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token' | 30 | import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token' |
31 | import { UserRole } from '@shared/models' | 31 | import { UserRole } from '@shared/models' |
32 | import { RegisterServerAuthExternalOptions } from '@shared/models/plugins/register-server-auth.model' | ||
32 | 33 | ||
33 | declare module 'express' { | 34 | declare module 'express' { |
34 | interface Response { | 35 | interface Response { |
@@ -115,6 +116,8 @@ declare module 'express' { | |||
115 | 116 | ||
116 | registeredPlugin?: RegisteredPlugin | 117 | registeredPlugin?: RegisteredPlugin |
117 | 118 | ||
119 | externalAuth?: RegisterServerAuthExternalOptions | ||
120 | |||
118 | plugin?: MPlugin | 121 | plugin?: MPlugin |
119 | } | 122 | } |
120 | } | 123 | } |
diff --git a/shared/models/plugins/register-server-auth.model.ts b/shared/models/plugins/register-server-auth.model.ts index 403a49994..08053f017 100644 --- a/shared/models/plugins/register-server-auth.model.ts +++ b/shared/models/plugins/register-server-auth.model.ts | |||
@@ -1,42 +1,52 @@ | |||
1 | import { UserRole } from '@shared/models' | 1 | import { UserRole } from '@shared/models' |
2 | import { MOAuthToken } from '@server/typings/models' | 2 | import { MOAuthToken } from '@server/typings/models' |
3 | import * as express from 'express' | ||
3 | 4 | ||
4 | export type RegisterServerAuthOptions = RegisterServerAuthPassOptions | RegisterServerAuthExternalOptions | 5 | export type RegisterServerAuthOptions = RegisterServerAuthPassOptions | RegisterServerAuthExternalOptions |
5 | 6 | ||
6 | export interface RegisterServerAuthPassOptions { | 7 | export interface RegisterServerAuthenticatedResult { |
8 | username: string | ||
9 | email: string | ||
10 | role?: UserRole | ||
11 | displayName?: string | ||
12 | } | ||
13 | |||
14 | export interface RegisterServerExternalAuthenticatedResult extends RegisterServerAuthenticatedResult { | ||
15 | req: express.Request | ||
16 | res: express.Response | ||
17 | } | ||
18 | |||
19 | interface RegisterServerAuthBase { | ||
7 | // Authentication name (a plugin can register multiple auth strategies) | 20 | // Authentication name (a plugin can register multiple auth strategies) |
8 | authName: string | 21 | authName: string |
9 | 22 | ||
10 | // Called by PeerTube when a user from your plugin logged out | 23 | // Called by PeerTube when a user from your plugin logged out |
11 | onLogout?(): void | 24 | onLogout?(): void |
12 | 25 | ||
13 | // Weight of this authentication so PeerTube tries the auth methods in DESC weight order | ||
14 | getWeight(): number | ||
15 | |||
16 | // Your plugin can hook PeerTube access/refresh token validity | 26 | // Your plugin can hook PeerTube access/refresh token validity |
17 | // So you can control for your plugin the user session lifetime | 27 | // So you can control for your plugin the user session lifetime |
18 | hookTokenValidity?(options: { token: MOAuthToken, type: 'access' | 'refresh' }): Promise<{ valid: boolean }> | 28 | hookTokenValidity?(options: { token: MOAuthToken, type: 'access' | 'refresh' }): Promise<{ valid: boolean }> |
29 | } | ||
30 | |||
31 | export interface RegisterServerAuthPassOptions extends RegisterServerAuthBase { | ||
32 | // Weight of this authentication so PeerTube tries the auth methods in DESC weight order | ||
33 | getWeight(): number | ||
19 | 34 | ||
20 | // Used by PeerTube to login a user | 35 | // Used by PeerTube to login a user |
21 | // Returns null if the login failed, or { username, email } on success | 36 | // Returns null if the login failed, or { username, email } on success |
22 | login(body: { | 37 | login(body: { |
23 | id: string | 38 | id: string |
24 | password: string | 39 | password: string |
25 | }): Promise<{ | 40 | }): Promise<RegisterServerAuthenticatedResult | null> |
26 | username: string | ||
27 | email: string | ||
28 | role?: UserRole | ||
29 | displayName?: string | ||
30 | } | null> | ||
31 | } | 41 | } |
32 | 42 | ||
33 | export interface RegisterServerAuthExternalOptions { | 43 | export interface RegisterServerAuthExternalOptions extends RegisterServerAuthBase { |
34 | // Authentication name (a plugin can register multiple auth strategies) | 44 | // Will be displayed in a block next to the login form |
35 | authName: string | 45 | authDisplayName: string |
36 | 46 | ||
37 | onLogout?: Function | 47 | onAuthRequest: (req: express.Request, res: express.Response) => void |
38 | } | 48 | } |
39 | 49 | ||
40 | export interface RegisterServerAuthExternalResult { | 50 | export interface RegisterServerAuthExternalResult { |
41 | onAuth (options: { username: string, email: string }): void | 51 | userAuthenticated (options: RegisterServerExternalAuthenticatedResult): void |
42 | } | 52 | } |
diff --git a/shared/models/plugins/register-server-setting.model.ts b/shared/models/plugins/register-server-setting.model.ts index ec175e9ef..920c3480f 100644 --- a/shared/models/plugins/register-server-setting.model.ts +++ b/shared/models/plugins/register-server-setting.model.ts | |||
@@ -9,7 +9,7 @@ export interface RegisterServerSettingOptions { | |||
9 | private: boolean | 9 | private: boolean |
10 | 10 | ||
11 | // Default setting value | 11 | // Default setting value |
12 | default?: string | 12 | default?: string | boolean |
13 | } | 13 | } |
14 | 14 | ||
15 | export interface RegisteredServerSettings { | 15 | export interface RegisteredServerSettings { |
diff --git a/shared/models/server/server-config.model.ts b/shared/models/server/server-config.model.ts index c3976a346..0ff079216 100644 --- a/shared/models/server/server-config.model.ts +++ b/shared/models/server/server-config.model.ts | |||
@@ -12,6 +12,18 @@ export interface ServerConfigTheme extends ServerConfigPlugin { | |||
12 | css: string[] | 12 | css: string[] |
13 | } | 13 | } |
14 | 14 | ||
15 | export interface RegisteredExternalAuthConfig { | ||
16 | npmName: string | ||
17 | authName: string | ||
18 | authDisplayName: string | ||
19 | } | ||
20 | |||
21 | export interface RegisteredIdAndPassAuthConfig { | ||
22 | npmName: string | ||
23 | authName: string | ||
24 | weight: number | ||
25 | } | ||
26 | |||
15 | export interface ServerConfig { | 27 | export interface ServerConfig { |
16 | serverVersion: string | 28 | serverVersion: string |
17 | serverCommit?: string | 29 | serverCommit?: string |
@@ -37,6 +49,10 @@ export interface ServerConfig { | |||
37 | 49 | ||
38 | plugin: { | 50 | plugin: { |
39 | registered: ServerConfigPlugin[] | 51 | registered: ServerConfigPlugin[] |
52 | |||
53 | registeredExternalAuths: RegisteredExternalAuthConfig[] | ||
54 | |||
55 | registeredIdAndPassAuths: RegisteredIdAndPassAuthConfig[] | ||
40 | } | 56 | } |
41 | 57 | ||
42 | theme: { | 58 | theme: { |