Begin support for external auths
This commit is contained in:
parent
98813e69bc
commit
4a8d113b9b
|
@ -83,6 +83,7 @@
|
||||||
"@typescript-eslint/consistent-type-definitions": "off",
|
"@typescript-eslint/consistent-type-definitions": "off",
|
||||||
"@typescript-eslint/no-misused-promises": "off",
|
"@typescript-eslint/no-misused-promises": "off",
|
||||||
"@typescript-eslint/no-namespace": "off",
|
"@typescript-eslint/no-namespace": "off",
|
||||||
|
"@typescript-eslint/no-empty-interface": "off",
|
||||||
"@typescript-eslint/no-extraneous-class": "off",
|
"@typescript-eslint/no-extraneous-class": "off",
|
||||||
// bugged but useful
|
// bugged but useful
|
||||||
"@typescript-eslint/restrict-plus-operands": "off"
|
"@typescript-eslint/restrict-plus-operands": "off"
|
||||||
|
|
|
@ -145,7 +145,7 @@ export class AuthService {
|
||||||
return !!this.getAccessToken()
|
return !!this.getAccessToken()
|
||||||
}
|
}
|
||||||
|
|
||||||
login (username: string, password: string) {
|
login (username: string, password: string, token?: string) {
|
||||||
// Form url encoded
|
// Form url encoded
|
||||||
const body = {
|
const body = {
|
||||||
client_id: this.clientId,
|
client_id: this.clientId,
|
||||||
|
@ -157,6 +157,8 @@ export class AuthService {
|
||||||
password
|
password
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (token) Object.assign(body, { externalAuthToken: token })
|
||||||
|
|
||||||
const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')
|
const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
return this.http.post<UserLogin>(AuthService.BASE_TOKEN_URL, objectToUrlEncoded(body), { headers })
|
return this.http.post<UserLogin>(AuthService.BASE_TOKEN_URL, objectToUrlEncoded(body), { headers })
|
||||||
.pipe(
|
.pipe(
|
||||||
|
|
|
@ -54,7 +54,9 @@ export class ServerService {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugin: {
|
plugin: {
|
||||||
registered: []
|
registered: [],
|
||||||
|
registeredExternalAuths: [],
|
||||||
|
registeredIdAndPassAuths: []
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
registered: [],
|
registered: [],
|
||||||
|
|
|
@ -3,59 +3,61 @@
|
||||||
Login
|
Login
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="alert alert-info" *ngIf="signupAllowed === false" role="alert">
|
<ng-container *ngIf="!isAuthenticatedWithExternalAuth">
|
||||||
<h6 class="alert-heading" i18n>
|
<div class="alert alert-info" *ngIf="signupAllowed === false" role="alert">
|
||||||
If you are looking for an account…
|
<h6 class="alert-heading" i18n>
|
||||||
</h6>
|
If you are looking for an account…
|
||||||
|
</h6>
|
||||||
|
|
||||||
<div i18n>
|
<div i18n>
|
||||||
Currently this instance doesn't allow for user registration, but you can find an instance
|
Currently this instance doesn't allow for user registration, but you can find an instance
|
||||||
that gives you the possibility to sign up for an account and upload your videos there.
|
that gives you the possibility to sign up for an account and upload your videos there.
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
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>.
|
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>.
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="error" class="alert alert-danger">{{ error }}
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<div *ngIf="formErrors.username" class="form-error">
|
|
||||||
{{ formErrors.username }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div *ngIf="error" class="alert alert-danger">{{ error }}
|
||||||
<label i18n for="password">Password</label>
|
<span *ngIf="error === 'User email is not verified.'"> <a i18n routerLink="/verify-account/ask-send-email">Request new verification email.</a></span>
|
||||||
<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>
|
||||||
|
|
||||||
<input type="submit" i18n-value value="Login" [disabled]="!form.valid">
|
<form role="form" (ngSubmit)="login()" [formGroup]="form">
|
||||||
</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>
|
||||||
|
|
||||||
|
<div *ngIf="formErrors.username" class="form-error">
|
||||||
|
{{ formErrors.username }}
|
||||||
|
</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 }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="submit" i18n-value value="Login" [disabled]="!form.valid">
|
||||||
|
</form>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #forgotPasswordModal>
|
<ng-template #forgotPasswordModal>
|
||||||
|
|
|
@ -22,6 +22,7 @@ export class LoginComponent extends FormReactive implements OnInit {
|
||||||
|
|
||||||
error: string = null
|
error: string = null
|
||||||
forgotPasswordEmail = ''
|
forgotPasswordEmail = ''
|
||||||
|
isAuthenticatedWithExternalAuth = false
|
||||||
|
|
||||||
private openedForgotPasswordModal: NgbModalRef
|
private openedForgotPasswordModal: NgbModalRef
|
||||||
private serverConfig: ServerConfig
|
private serverConfig: ServerConfig
|
||||||
|
@ -49,7 +50,14 @@ export class LoginComponent extends FormReactive implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.serverConfig = this.route.snapshot.data.serverConfig
|
const snapshot = this.route.snapshot
|
||||||
|
|
||||||
|
this.serverConfig = snapshot.data.serverConfig
|
||||||
|
|
||||||
|
if (snapshot.queryParams.externalAuthToken) {
|
||||||
|
this.loadExternalAuthToken(snapshot.queryParams.username, snapshot.queryParams.externalAuthToken)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.buildForm({
|
this.buildForm({
|
||||||
username: this.loginValidatorsService.LOGIN_USERNAME,
|
username: this.loginValidatorsService.LOGIN_USERNAME,
|
||||||
|
@ -68,11 +76,7 @@ export class LoginComponent extends FormReactive implements OnInit {
|
||||||
.subscribe(
|
.subscribe(
|
||||||
() => this.redirectService.redirectToPreviousRoute(),
|
() => this.redirectService.redirectToPreviousRoute(),
|
||||||
|
|
||||||
err => {
|
err => this.handleError(err)
|
||||||
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
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,4 +103,24 @@ export class LoginComponent extends FormReactive implements OnInit {
|
||||||
hideForgotPasswordModal () {
|
hideForgotPasswordModal () {
|
||||||
this.openedForgotPasswordModal.close()
|
this.openedForgotPasswordModal.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private loadExternalAuthToken (username: string, token: string) {
|
||||||
|
this.isAuthenticatedWithExternalAuth = true
|
||||||
|
|
||||||
|
this.authService.login(username, null, token)
|
||||||
|
.subscribe(
|
||||||
|
() => this.redirectService.redirectToPreviousRoute(),
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
|
import { Hooks } from '@server/lib/plugins/hooks'
|
||||||
import * as express from 'express'
|
import * as express from 'express'
|
||||||
|
import { remove, writeJSON } from 'fs-extra'
|
||||||
import { snakeCase } from 'lodash'
|
import { snakeCase } from 'lodash'
|
||||||
import { ServerConfig, UserRight } from '../../../shared'
|
import validator from 'validator'
|
||||||
|
import { RegisteredExternalAuthConfig, RegisteredIdAndPassAuthConfig, ServerConfig, UserRight } from '../../../shared'
|
||||||
import { About } from '../../../shared/models/server/about.model'
|
import { About } from '../../../shared/models/server/about.model'
|
||||||
import { CustomConfig } from '../../../shared/models/server/custom-config.model'
|
import { CustomConfig } from '../../../shared/models/server/custom-config.model'
|
||||||
import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
|
|
||||||
import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION } from '../../initializers/constants'
|
|
||||||
import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
|
|
||||||
import { customConfigUpdateValidator } from '../../middlewares/validators/config'
|
|
||||||
import { ClientHtml } from '../../lib/client-html'
|
|
||||||
import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger'
|
import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger'
|
||||||
import { remove, writeJSON } from 'fs-extra'
|
|
||||||
import { getServerCommit } from '../../helpers/utils'
|
|
||||||
import validator from 'validator'
|
|
||||||
import { objectConverter } from '../../helpers/core-utils'
|
import { objectConverter } from '../../helpers/core-utils'
|
||||||
|
import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
|
||||||
|
import { getServerCommit } from '../../helpers/utils'
|
||||||
import { CONFIG, isEmailEnabled, reloadConfig } from '../../initializers/config'
|
import { CONFIG, isEmailEnabled, reloadConfig } from '../../initializers/config'
|
||||||
|
import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION } from '../../initializers/constants'
|
||||||
|
import { ClientHtml } from '../../lib/client-html'
|
||||||
import { PluginManager } from '../../lib/plugins/plugin-manager'
|
import { PluginManager } from '../../lib/plugins/plugin-manager'
|
||||||
import { getThemeOrDefault } from '../../lib/plugins/theme-utils'
|
import { getThemeOrDefault } from '../../lib/plugins/theme-utils'
|
||||||
import { Hooks } from '@server/lib/plugins/hooks'
|
import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
|
||||||
|
import { customConfigUpdateValidator } from '../../middlewares/validators/config'
|
||||||
|
|
||||||
const configRouter = express.Router()
|
const configRouter = express.Router()
|
||||||
|
|
||||||
|
@ -79,7 +79,9 @@ async function getConfig (req: express.Request, res: express.Response) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugin: {
|
plugin: {
|
||||||
registered: getRegisteredPlugins()
|
registered: getRegisteredPlugins(),
|
||||||
|
registeredExternalAuths: getExternalAuthsPlugins(),
|
||||||
|
registeredIdAndPassAuths: getIdAndPassAuthPlugins()
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
registered: getRegisteredThemes(),
|
registered: getRegisteredThemes(),
|
||||||
|
@ -269,6 +271,38 @@ function getRegisteredPlugins () {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getIdAndPassAuthPlugins () {
|
||||||
|
const result: RegisteredIdAndPassAuthConfig[] = []
|
||||||
|
|
||||||
|
for (const p of PluginManager.Instance.getIdAndPassAuths()) {
|
||||||
|
for (const auth of p.idAndPassAuths) {
|
||||||
|
result.push({
|
||||||
|
npmName: p.npmName,
|
||||||
|
authName: auth.authName,
|
||||||
|
weight: auth.getWeight()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExternalAuthsPlugins () {
|
||||||
|
const result: RegisteredExternalAuthConfig[] = []
|
||||||
|
|
||||||
|
for (const p of PluginManager.Instance.getExternalAuths()) {
|
||||||
|
for (const auth of p.externalAuths) {
|
||||||
|
result.push({
|
||||||
|
npmName: p.npmName,
|
||||||
|
authName: auth.authName,
|
||||||
|
authDisplayName: auth.authDisplayName
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -2,11 +2,12 @@ import * as express from 'express'
|
||||||
import { PLUGIN_GLOBAL_CSS_PATH } from '../initializers/constants'
|
import { PLUGIN_GLOBAL_CSS_PATH } from '../initializers/constants'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { PluginManager, RegisteredPlugin } from '../lib/plugins/plugin-manager'
|
import { PluginManager, RegisteredPlugin } from '../lib/plugins/plugin-manager'
|
||||||
import { getPluginValidator, pluginStaticDirectoryValidator } from '../middlewares/validators/plugins'
|
import { getPluginValidator, pluginStaticDirectoryValidator, getExternalAuthValidator } from '../middlewares/validators/plugins'
|
||||||
import { serveThemeCSSValidator } from '../middlewares/validators/themes'
|
import { serveThemeCSSValidator } from '../middlewares/validators/themes'
|
||||||
import { PluginType } from '../../shared/models/plugins/plugin.type'
|
import { PluginType } from '../../shared/models/plugins/plugin.type'
|
||||||
import { isTestInstance } from '../helpers/core-utils'
|
import { isTestInstance } from '../helpers/core-utils'
|
||||||
import { getCompleteLocale, is18nLocale } from '../../shared/models/i18n'
|
import { getCompleteLocale, is18nLocale } from '../../shared/models/i18n'
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
|
||||||
const sendFileOptions = {
|
const sendFileOptions = {
|
||||||
maxAge: '30 days',
|
maxAge: '30 days',
|
||||||
|
@ -23,6 +24,12 @@ pluginsRouter.get('/plugins/translations/:locale.json',
|
||||||
getPluginTranslations
|
getPluginTranslations
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pluginsRouter.get('/plugins/:pluginName/:pluginVersion/auth/:authName',
|
||||||
|
getPluginValidator(PluginType.PLUGIN),
|
||||||
|
getExternalAuthValidator,
|
||||||
|
handleAuthInPlugin
|
||||||
|
)
|
||||||
|
|
||||||
pluginsRouter.get('/plugins/:pluginName/:pluginVersion/static/:staticEndpoint(*)',
|
pluginsRouter.get('/plugins/:pluginName/:pluginVersion/static/:staticEndpoint(*)',
|
||||||
getPluginValidator(PluginType.PLUGIN),
|
getPluginValidator(PluginType.PLUGIN),
|
||||||
pluginStaticDirectoryValidator,
|
pluginStaticDirectoryValidator,
|
||||||
|
@ -134,3 +141,14 @@ function serveThemeCSSDirectory (req: express.Request, res: express.Response) {
|
||||||
|
|
||||||
return res.sendFile(join(plugin.path, staticEndpoint), sendFileOptions)
|
return res.sendFile(join(plugin.path, staticEndpoint), sendFileOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleAuthInPlugin (req: express.Request, res: express.Response) {
|
||||||
|
const authOptions = res.locals.externalAuth
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.debug('Forwarding auth plugin request in %s of plugin %s.', authOptions.authName, res.locals.registeredPlugin.npmName)
|
||||||
|
authOptions.onAuthRequest(req, res)
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Forward request error in auth %s of plugin %s.', authOptions.authName, res.locals.registeredPlugin.npmName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
import * as express from 'express'
|
import { isUserDisplayNameValid, isUserRoleValid, isUserUsernameValid } from '@server/helpers/custom-validators/users'
|
||||||
import { OAUTH_LIFETIME } from '@server/initializers/constants'
|
|
||||||
import * as OAuthServer from 'express-oauth-server'
|
|
||||||
import { PluginManager } from '@server/lib/plugins/plugin-manager'
|
|
||||||
import { RegisterServerAuthPassOptions } from '@shared/models/plugins/register-server-auth.model'
|
|
||||||
import { logger } from '@server/helpers/logger'
|
import { logger } from '@server/helpers/logger'
|
||||||
import { UserRole } from '@shared/models'
|
import { generateRandomString } from '@server/helpers/utils'
|
||||||
|
import { OAUTH_LIFETIME, WEBSERVER } from '@server/initializers/constants'
|
||||||
import { revokeToken } from '@server/lib/oauth-model'
|
import { revokeToken } from '@server/lib/oauth-model'
|
||||||
|
import { PluginManager } from '@server/lib/plugins/plugin-manager'
|
||||||
import { OAuthTokenModel } from '@server/models/oauth/oauth-token'
|
import { OAuthTokenModel } from '@server/models/oauth/oauth-token'
|
||||||
import { isUserUsernameValid, isUserRoleValid, isUserDisplayNameValid } from '@server/helpers/custom-validators/users'
|
import { UserRole } from '@shared/models'
|
||||||
|
import {
|
||||||
|
RegisterServerAuthenticatedResult,
|
||||||
|
RegisterServerAuthPassOptions,
|
||||||
|
RegisterServerExternalAuthenticatedResult
|
||||||
|
} from '@shared/models/plugins/register-server-auth.model'
|
||||||
|
import * as express from 'express'
|
||||||
|
import * as OAuthServer from 'express-oauth-server'
|
||||||
|
|
||||||
const oAuthServer = new OAuthServer({
|
const oAuthServer = new OAuthServer({
|
||||||
useErrorHandler: true,
|
useErrorHandler: true,
|
||||||
|
@ -17,15 +22,28 @@ const oAuthServer = new OAuthServer({
|
||||||
model: require('./oauth-model')
|
model: require('./oauth-model')
|
||||||
})
|
})
|
||||||
|
|
||||||
function onExternalAuthPlugin (npmName: string, username: string, email: string) {
|
// Token is the key, expiration date is the value
|
||||||
|
const authBypassTokens = new Map<string, {
|
||||||
}
|
expires: Date
|
||||||
|
user: {
|
||||||
|
username: string
|
||||||
|
email: string
|
||||||
|
displayName: string
|
||||||
|
role: UserRole
|
||||||
|
}
|
||||||
|
authName: string
|
||||||
|
npmName: string
|
||||||
|
}>()
|
||||||
|
|
||||||
async function handleIdAndPassLogin (req: express.Request, res: express.Response, next: express.NextFunction) {
|
async function handleIdAndPassLogin (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
const grantType = req.body.grant_type
|
const grantType = req.body.grant_type
|
||||||
|
|
||||||
if (grantType === 'password') await proxifyPasswordGrant(req, res)
|
if (grantType === 'password') {
|
||||||
else if (grantType === 'refresh_token') await proxifyRefreshGrant(req, res)
|
if (req.body.externalAuthToken) proxifyExternalAuthBypass(req, res)
|
||||||
|
else await proxifyPasswordGrant(req, res)
|
||||||
|
} else if (grantType === 'refresh_token') {
|
||||||
|
await proxifyRefreshGrant(req, res)
|
||||||
|
}
|
||||||
|
|
||||||
return forwardTokenReq(req, res, next)
|
return forwardTokenReq(req, res, next)
|
||||||
}
|
}
|
||||||
|
@ -53,31 +71,60 @@ async function handleTokenRevocation (req: express.Request, res: express.Respons
|
||||||
return res.sendStatus(200)
|
return res.sendStatus(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
async function onExternalUserAuthenticated (options: {
|
||||||
|
npmName: string
|
||||||
|
authName: string
|
||||||
|
authResult: RegisterServerExternalAuthenticatedResult
|
||||||
|
}) {
|
||||||
|
const { npmName, authName, authResult } = options
|
||||||
|
|
||||||
export {
|
if (!authResult.req || !authResult.res) {
|
||||||
oAuthServer,
|
logger.error('Cannot authenticate external user for auth %s of plugin %s: no req or res are provided.', authName, npmName)
|
||||||
handleIdAndPassLogin,
|
return
|
||||||
onExternalAuthPlugin,
|
}
|
||||||
handleTokenRevocation
|
|
||||||
|
if (!isAuthResultValid(npmName, authName, authResult)) return
|
||||||
|
|
||||||
|
const { res } = authResult
|
||||||
|
|
||||||
|
logger.info('Generating auth bypass token for %s in auth %s of plugin %s.', authResult.username, authName, npmName)
|
||||||
|
|
||||||
|
const bypassToken = await generateRandomString(32)
|
||||||
|
const tokenLifetime = 1000 * 60 * 5 // 5 minutes
|
||||||
|
|
||||||
|
const expires = new Date()
|
||||||
|
expires.setTime(expires.getTime() + tokenLifetime)
|
||||||
|
|
||||||
|
const user = buildUserResult(authResult)
|
||||||
|
authBypassTokens.set(bypassToken, {
|
||||||
|
expires,
|
||||||
|
user,
|
||||||
|
npmName,
|
||||||
|
authName
|
||||||
|
})
|
||||||
|
|
||||||
|
res.redirect(`/login?externalAuthToken=${bypassToken}&username=${user.username}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function forwardTokenReq (req: express.Request, res: express.Response, next: express.NextFunction) {
|
export { oAuthServer, handleIdAndPassLogin, onExternalUserAuthenticated, handleTokenRevocation }
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function forwardTokenReq (req: express.Request, res: express.Response, next?: express.NextFunction) {
|
||||||
return oAuthServer.token()(req, res, err => {
|
return oAuthServer.token()(req, res, err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.warn('Login error.', { err })
|
logger.warn('Login error.', { err })
|
||||||
|
|
||||||
return res.status(err.status)
|
return res.status(err.status)
|
||||||
.json({
|
.json({
|
||||||
error: err.message,
|
error: err.message,
|
||||||
code: err.name
|
code: err.name
|
||||||
})
|
})
|
||||||
.end()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return next()
|
if (next) return next()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,50 +178,96 @@ async function proxifyPasswordGrant (req: express.Request, res: express.Response
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const loginResult = await authOptions.login(loginOptions)
|
const loginResult = await authOptions.login(loginOptions)
|
||||||
if (loginResult) {
|
|
||||||
logger.info(
|
|
||||||
'Login success with auth method %s of plugin %s for %s.',
|
|
||||||
authName, npmName, loginOptions.id
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!isUserUsernameValid(loginResult.username)) {
|
if (!loginResult) continue
|
||||||
logger.error('Auth method %s of plugin %s did not provide a valid username.', authName, npmName, { loginResult })
|
if (!isAuthResultValid(pluginAuth.npmName, authOptions.authName, loginResult)) continue
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!loginResult.email) {
|
logger.info(
|
||||||
logger.error('Auth method %s of plugin %s did not provide a valid email.', authName, npmName, { loginResult })
|
'Login success with auth method %s of plugin %s for %s.',
|
||||||
continue
|
authName, npmName, loginOptions.id
|
||||||
}
|
)
|
||||||
|
|
||||||
// role is optional
|
res.locals.bypassLogin = {
|
||||||
if (loginResult.role && !isUserRoleValid(loginResult.role)) {
|
bypass: true,
|
||||||
logger.error('Auth method %s of plugin %s did not provide a valid role.', authName, npmName, { loginResult })
|
pluginName: pluginAuth.npmName,
|
||||||
continue
|
authName: authOptions.authName,
|
||||||
}
|
user: buildUserResult(loginResult)
|
||||||
|
|
||||||
// display name is optional
|
|
||||||
if (loginResult.displayName && !isUserDisplayNameValid(loginResult.displayName)) {
|
|
||||||
logger.error('Auth method %s of plugin %s did not provide a valid display name.', authName, npmName, { loginResult })
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
res.locals.bypassLogin = {
|
|
||||||
bypass: true,
|
|
||||||
pluginName: pluginAuth.npmName,
|
|
||||||
authName: authOptions.authName,
|
|
||||||
user: {
|
|
||||||
username: loginResult.username,
|
|
||||||
email: loginResult.email,
|
|
||||||
role: loginResult.role || UserRole.USER,
|
|
||||||
displayName: loginResult.displayName || loginResult.username
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('Error in auth method %s of plugin %s', authOptions.authName, pluginAuth.npmName, { err })
|
logger.error('Error in auth method %s of plugin %s', authOptions.authName, pluginAuth.npmName, { err })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function proxifyExternalAuthBypass (req: express.Request, res: express.Response) {
|
||||||
|
const obj = authBypassTokens.get(req.body.externalAuthToken)
|
||||||
|
if (!obj) {
|
||||||
|
logger.error('Cannot authenticate user with unknown bypass token')
|
||||||
|
return res.sendStatus(400)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { expires, user, authName, npmName } = obj
|
||||||
|
|
||||||
|
const now = new Date()
|
||||||
|
if (now.getTime() > expires.getTime()) {
|
||||||
|
logger.error('Cannot authenticate user with an expired bypass token')
|
||||||
|
return res.sendStatus(400)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.username !== req.body.username) {
|
||||||
|
logger.error('Cannot authenticate user %s with invalid username %s.', req.body.username)
|
||||||
|
return res.sendStatus(400)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bypass oauth library validation
|
||||||
|
req.body.password = 'fake'
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
'Auth success with external auth method %s of plugin %s for %s.',
|
||||||
|
authName, npmName, user.email
|
||||||
|
)
|
||||||
|
|
||||||
|
res.locals.bypassLogin = {
|
||||||
|
bypass: true,
|
||||||
|
pluginName: npmName,
|
||||||
|
authName: authName,
|
||||||
|
user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 })
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.email) {
|
||||||
|
logger.error('Auth method %s of plugin %s did not provide a valid email.', authName, npmName, { result })
|
||||||
|
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 })
|
||||||
|
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 })
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildUserResult (pluginResult: RegisterServerAuthenticatedResult) {
|
||||||
|
return {
|
||||||
|
username: pluginResult.username,
|
||||||
|
email: pluginResult.email,
|
||||||
|
role: pluginResult.role || UserRole.USER,
|
||||||
|
displayName: pluginResult.displayName || pluginResult.username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -98,7 +98,7 @@ async function getRefreshToken (refreshToken: string) {
|
||||||
return tokenInfo
|
return tokenInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getUser (usernameOrEmail: string, password: string) {
|
async function getUser (usernameOrEmail?: string, password?: string) {
|
||||||
const res: express.Response = this.request.res
|
const res: express.Response = this.request.res
|
||||||
|
|
||||||
// Special treatment coming from a plugin
|
// Special treatment coming from a plugin
|
||||||
|
|
|
@ -1,31 +1,21 @@
|
||||||
import { PluginSettingsManager } from '@shared/models/plugins/plugin-settings-manager.model'
|
|
||||||
import { PluginModel } from '@server/models/server/plugin'
|
|
||||||
import { PluginStorageManager } from '@shared/models/plugins/plugin-storage-manager.model'
|
|
||||||
import { PluginVideoLanguageManager } from '@shared/models/plugins/plugin-video-language-manager.model'
|
|
||||||
import {
|
|
||||||
VIDEO_CATEGORIES,
|
|
||||||
VIDEO_LANGUAGES,
|
|
||||||
VIDEO_LICENCES,
|
|
||||||
VIDEO_PLAYLIST_PRIVACIES,
|
|
||||||
VIDEO_PRIVACIES
|
|
||||||
} from '@server/initializers/constants'
|
|
||||||
import { PluginVideoLicenceManager } from '@shared/models/plugins/plugin-video-licence-manager.model'
|
|
||||||
import { PluginVideoCategoryManager } from '@shared/models/plugins/plugin-video-category-manager.model'
|
|
||||||
import { RegisterServerOptions } from '@server/typings/plugins'
|
|
||||||
import { buildPluginHelpers } from './plugin-helpers'
|
|
||||||
import { logger } from '@server/helpers/logger'
|
import { logger } from '@server/helpers/logger'
|
||||||
import { RegisterServerHookOptions } from '@shared/models/plugins/register-server-hook.model'
|
import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PLAYLIST_PRIVACIES, VIDEO_PRIVACIES } from '@server/initializers/constants'
|
||||||
import { serverHookObject } from '@shared/models/plugins/server-hook.model'
|
import { onExternalUserAuthenticated } from '@server/lib/auth'
|
||||||
import { RegisterServerSettingOptions } from '@shared/models/plugins/register-server-setting.model'
|
import { PluginModel } from '@server/models/server/plugin'
|
||||||
import * as express from 'express'
|
import { RegisterServerOptions } from '@server/typings/plugins'
|
||||||
import { PluginVideoPrivacyManager } from '@shared/models/plugins/plugin-video-privacy-manager.model'
|
|
||||||
import { PluginPlaylistPrivacyManager } from '@shared/models/plugins/plugin-playlist-privacy-manager.model'
|
import { PluginPlaylistPrivacyManager } from '@shared/models/plugins/plugin-playlist-privacy-manager.model'
|
||||||
import {
|
import { PluginSettingsManager } from '@shared/models/plugins/plugin-settings-manager.model'
|
||||||
RegisterServerAuthExternalOptions,
|
import { PluginStorageManager } from '@shared/models/plugins/plugin-storage-manager.model'
|
||||||
RegisterServerAuthExternalResult,
|
import { PluginVideoCategoryManager } from '@shared/models/plugins/plugin-video-category-manager.model'
|
||||||
RegisterServerAuthPassOptions
|
import { PluginVideoLanguageManager } from '@shared/models/plugins/plugin-video-language-manager.model'
|
||||||
} from '@shared/models/plugins/register-server-auth.model'
|
import { PluginVideoLicenceManager } from '@shared/models/plugins/plugin-video-licence-manager.model'
|
||||||
import { onExternalAuthPlugin } from '@server/lib/auth'
|
import { PluginVideoPrivacyManager } from '@shared/models/plugins/plugin-video-privacy-manager.model'
|
||||||
|
import { RegisterServerAuthExternalOptions, RegisterServerAuthExternalResult, RegisterServerAuthPassOptions, RegisterServerExternalAuthenticatedResult } from '@shared/models/plugins/register-server-auth.model'
|
||||||
|
import { RegisterServerHookOptions } from '@shared/models/plugins/register-server-hook.model'
|
||||||
|
import { RegisterServerSettingOptions } from '@shared/models/plugins/register-server-setting.model'
|
||||||
|
import { serverHookObject } from '@shared/models/plugins/server-hook.model'
|
||||||
|
import * as express from 'express'
|
||||||
|
import { buildPluginHelpers } from './plugin-helpers'
|
||||||
|
|
||||||
type AlterableVideoConstant = 'language' | 'licence' | 'category' | 'privacy' | 'playlistPrivacy'
|
type AlterableVideoConstant = 'language' | 'licence' | 'category' | 'privacy' | 'playlistPrivacy'
|
||||||
type VideoConstant = { [key in number | string]: string }
|
type VideoConstant = { [key in number | string]: string }
|
||||||
|
@ -187,8 +177,14 @@ export class RegisterHelpersStore {
|
||||||
this.externalAuths.push(options)
|
this.externalAuths.push(options)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
onAuth (options: { username: string, email: string }): void {
|
userAuthenticated (result: RegisterServerExternalAuthenticatedResult): void {
|
||||||
onExternalAuthPlugin(self.npmName, options.username, options.email)
|
onExternalUserAuthenticated({
|
||||||
|
npmName: self.npmName,
|
||||||
|
authName: options.authName,
|
||||||
|
authResult: result
|
||||||
|
}).catch(err => {
|
||||||
|
logger.error('Cannot execute onExternalUserAuthenticated.', { npmName: self.npmName, authName: options.authName, err })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} as RegisterServerAuthExternalResult
|
} as RegisterServerAuthExternalResult
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { logger } from '../../helpers/logger'
|
||||||
import { areValidationErrors } from './utils'
|
import { areValidationErrors } from './utils'
|
||||||
import { isNpmPluginNameValid, isPluginNameValid, isPluginTypeValid, isPluginVersionValid } from '../../helpers/custom-validators/plugins'
|
import { isNpmPluginNameValid, isPluginNameValid, isPluginTypeValid, isPluginVersionValid } from '../../helpers/custom-validators/plugins'
|
||||||
import { PluginManager } from '../../lib/plugins/plugin-manager'
|
import { PluginManager } from '../../lib/plugins/plugin-manager'
|
||||||
import { isBooleanValid, isSafePath, toBooleanOrNull } from '../../helpers/custom-validators/misc'
|
import { isBooleanValid, isSafePath, toBooleanOrNull, exists } from '../../helpers/custom-validators/misc'
|
||||||
import { PluginModel } from '../../models/server/plugin'
|
import { PluginModel } from '../../models/server/plugin'
|
||||||
import { InstallOrUpdatePlugin } from '../../../shared/models/plugins/install-plugin.model'
|
import { InstallOrUpdatePlugin } from '../../../shared/models/plugins/install-plugin.model'
|
||||||
import { PluginType } from '../../../shared/models/plugins/plugin.type'
|
import { PluginType } from '../../../shared/models/plugins/plugin.type'
|
||||||
|
@ -40,6 +40,26 @@ const getPluginValidator = (pluginType: PluginType, withVersion = true) => {
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getExternalAuthValidator = [
|
||||||
|
param('authName').custom(exists).withMessage('Should have a valid auth name'),
|
||||||
|
|
||||||
|
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
|
logger.debug('Checking getExternalAuthValidator parameters', { parameters: req.params })
|
||||||
|
|
||||||
|
if (areValidationErrors(req, res)) return
|
||||||
|
|
||||||
|
const plugin = res.locals.registeredPlugin
|
||||||
|
if (!plugin.registerHelpersStore) return res.sendStatus(404)
|
||||||
|
|
||||||
|
const externalAuth = plugin.registerHelpersStore.getExternalAuths().find(a => a.authName === req.params.authName)
|
||||||
|
if (!externalAuth) return res.sendStatus(404)
|
||||||
|
|
||||||
|
res.locals.externalAuth = externalAuth
|
||||||
|
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
const pluginStaticDirectoryValidator = [
|
const pluginStaticDirectoryValidator = [
|
||||||
param('staticEndpoint').custom(isSafePath).withMessage('Should have a valid static endpoint'),
|
param('staticEndpoint').custom(isSafePath).withMessage('Should have a valid static endpoint'),
|
||||||
|
|
||||||
|
@ -175,5 +195,6 @@ export {
|
||||||
listAvailablePluginsValidator,
|
listAvailablePluginsValidator,
|
||||||
existingPluginValidator,
|
existingPluginValidator,
|
||||||
installOrUpdatePluginValidator,
|
installOrUpdatePluginValidator,
|
||||||
listPluginsValidator
|
listPluginsValidator,
|
||||||
|
getExternalAuthValidator
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { MPlugin, MServer } from '@server/typings/models/server'
|
||||||
import { MServerBlocklist } from './models/server/server-blocklist'
|
import { MServerBlocklist } from './models/server/server-blocklist'
|
||||||
import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token'
|
import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token'
|
||||||
import { UserRole } from '@shared/models'
|
import { UserRole } from '@shared/models'
|
||||||
|
import { RegisterServerAuthExternalOptions } from '@shared/models/plugins/register-server-auth.model'
|
||||||
|
|
||||||
declare module 'express' {
|
declare module 'express' {
|
||||||
interface Response {
|
interface Response {
|
||||||
|
@ -115,6 +116,8 @@ declare module 'express' {
|
||||||
|
|
||||||
registeredPlugin?: RegisteredPlugin
|
registeredPlugin?: RegisteredPlugin
|
||||||
|
|
||||||
|
externalAuth?: RegisterServerAuthExternalOptions
|
||||||
|
|
||||||
plugin?: MPlugin
|
plugin?: MPlugin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,52 @@
|
||||||
import { UserRole } from '@shared/models'
|
import { UserRole } from '@shared/models'
|
||||||
import { MOAuthToken } from '@server/typings/models'
|
import { MOAuthToken } from '@server/typings/models'
|
||||||
|
import * as express from 'express'
|
||||||
|
|
||||||
export type RegisterServerAuthOptions = RegisterServerAuthPassOptions | RegisterServerAuthExternalOptions
|
export type RegisterServerAuthOptions = RegisterServerAuthPassOptions | RegisterServerAuthExternalOptions
|
||||||
|
|
||||||
export interface RegisterServerAuthPassOptions {
|
export interface RegisterServerAuthenticatedResult {
|
||||||
|
username: string
|
||||||
|
email: string
|
||||||
|
role?: UserRole
|
||||||
|
displayName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RegisterServerExternalAuthenticatedResult extends RegisterServerAuthenticatedResult {
|
||||||
|
req: express.Request
|
||||||
|
res: express.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RegisterServerAuthBase {
|
||||||
// Authentication name (a plugin can register multiple auth strategies)
|
// Authentication name (a plugin can register multiple auth strategies)
|
||||||
authName: string
|
authName: string
|
||||||
|
|
||||||
// Called by PeerTube when a user from your plugin logged out
|
// Called by PeerTube when a user from your plugin logged out
|
||||||
onLogout?(): void
|
onLogout?(): void
|
||||||
|
|
||||||
// Weight of this authentication so PeerTube tries the auth methods in DESC weight order
|
|
||||||
getWeight(): number
|
|
||||||
|
|
||||||
// Your plugin can hook PeerTube access/refresh token validity
|
// Your plugin can hook PeerTube access/refresh token validity
|
||||||
// So you can control for your plugin the user session lifetime
|
// So you can control for your plugin the user session lifetime
|
||||||
hookTokenValidity?(options: { token: MOAuthToken, type: 'access' | 'refresh' }): Promise<{ valid: boolean }>
|
hookTokenValidity?(options: { token: MOAuthToken, type: 'access' | 'refresh' }): Promise<{ valid: boolean }>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RegisterServerAuthPassOptions extends RegisterServerAuthBase {
|
||||||
|
// Weight of this authentication so PeerTube tries the auth methods in DESC weight order
|
||||||
|
getWeight(): number
|
||||||
|
|
||||||
// Used by PeerTube to login a user
|
// Used by PeerTube to login a user
|
||||||
// Returns null if the login failed, or { username, email } on success
|
// Returns null if the login failed, or { username, email } on success
|
||||||
login(body: {
|
login(body: {
|
||||||
id: string
|
id: string
|
||||||
password: string
|
password: string
|
||||||
}): Promise<{
|
}): Promise<RegisterServerAuthenticatedResult | null>
|
||||||
username: string
|
|
||||||
email: string
|
|
||||||
role?: UserRole
|
|
||||||
displayName?: string
|
|
||||||
} | null>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RegisterServerAuthExternalOptions {
|
export interface RegisterServerAuthExternalOptions extends RegisterServerAuthBase {
|
||||||
// Authentication name (a plugin can register multiple auth strategies)
|
// Will be displayed in a block next to the login form
|
||||||
authName: string
|
authDisplayName: string
|
||||||
|
|
||||||
onLogout?: Function
|
onAuthRequest: (req: express.Request, res: express.Response) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RegisterServerAuthExternalResult {
|
export interface RegisterServerAuthExternalResult {
|
||||||
onAuth (options: { username: string, email: string }): void
|
userAuthenticated (options: RegisterServerExternalAuthenticatedResult): void
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ export interface RegisterServerSettingOptions {
|
||||||
private: boolean
|
private: boolean
|
||||||
|
|
||||||
// Default setting value
|
// Default setting value
|
||||||
default?: string
|
default?: string | boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RegisteredServerSettings {
|
export interface RegisteredServerSettings {
|
||||||
|
|
|
@ -12,6 +12,18 @@ export interface ServerConfigTheme extends ServerConfigPlugin {
|
||||||
css: string[]
|
css: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RegisteredExternalAuthConfig {
|
||||||
|
npmName: string
|
||||||
|
authName: string
|
||||||
|
authDisplayName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RegisteredIdAndPassAuthConfig {
|
||||||
|
npmName: string
|
||||||
|
authName: string
|
||||||
|
weight: number
|
||||||
|
}
|
||||||
|
|
||||||
export interface ServerConfig {
|
export interface ServerConfig {
|
||||||
serverVersion: string
|
serverVersion: string
|
||||||
serverCommit?: string
|
serverCommit?: string
|
||||||
|
@ -37,6 +49,10 @@ export interface ServerConfig {
|
||||||
|
|
||||||
plugin: {
|
plugin: {
|
||||||
registered: ServerConfigPlugin[]
|
registered: ServerConfigPlugin[]
|
||||||
|
|
||||||
|
registeredExternalAuths: RegisteredExternalAuthConfig[]
|
||||||
|
|
||||||
|
registeredIdAndPassAuths: RegisteredIdAndPassAuthConfig[]
|
||||||
}
|
}
|
||||||
|
|
||||||
theme: {
|
theme: {
|
||||||
|
|
Loading…
Reference in New Issue