"@typescript-eslint/consistent-type-definitions": "off",
"@typescript-eslint/no-misused-promises": "off",
"@typescript-eslint/no-namespace": "off",
+ "@typescript-eslint/no-empty-interface": "off",
"@typescript-eslint/no-extraneous-class": "off",
// bugged but useful
"@typescript-eslint/restrict-plus-operands": "off"
return !!this.getAccessToken()
}
- login (username: string, password: string) {
+ login (username: string, password: string, token?: string) {
// Form url encoded
const body = {
client_id: this.clientId,
password
}
+ if (token) Object.assign(body, { externalAuthToken: token })
+
const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')
return this.http.post<UserLogin>(AuthService.BASE_TOKEN_URL, objectToUrlEncoded(body), { headers })
.pipe(
}
},
plugin: {
- registered: []
+ registered: [],
+ registeredExternalAuths: [],
+ registeredIdAndPassAuths: []
},
theme: {
registered: [],
Login
</div>
- <div class="alert alert-info" *ngIf="signupAllowed === false" role="alert">
- <h6 class="alert-heading" i18n>
- If you are looking for an account…
- </h6>
+ <ng-container *ngIf="!isAuthenticatedWithExternalAuth">
+ <div class="alert alert-info" *ngIf="signupAllowed === false" role="alert">
+ <h6 class="alert-heading" i18n>
+ If you are looking for an account…
+ </h6>
- <div i18n>
- 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.
+ <div i18n>
+ 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.
- <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>.
- </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>
+ 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="formErrors.username" class="form-error">
- {{ formErrors.username }}
- </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>
- <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>
+ <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 *ngIf="formErrors.password" class="form-error">
- {{ formErrors.password }}
+
+ <div class="form-group">
+ <label i18n for="password">Password</label>
+ <div>
+ <input
+ type="password" name="password" id="password" i18n-placeholder placeholder="Password" required tabindex="2" autocomplete="current-password"
+ formControlName="password" class="form-control" [ngClass]="{ 'input-error': formErrors['password'] }"
+ >
+ <a i18n-title class="forgot-password-button" (click)="openForgotPasswordModal()" title="Click here to reset your password">I forgot my password</a>
+ </div>
+ <div *ngIf="formErrors.password" class="form-error">
+ {{ formErrors.password }}
+ </div>
</div>
- </div>
- <input type="submit" i18n-value value="Login" [disabled]="!form.valid">
- </form>
+ <input type="submit" i18n-value value="Login" [disabled]="!form.valid">
+ </form>
+ </ng-container>
</div>
<ng-template #forgotPasswordModal>
error: string = null
forgotPasswordEmail = ''
+ isAuthenticatedWithExternalAuth = false
private openedForgotPasswordModal: NgbModalRef
private serverConfig: ServerConfig
}
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({
username: this.loginValidatorsService.LOGIN_USERNAME,
.subscribe(
() => this.redirectService.redirectToPreviousRoute(),
- 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
- }
+ err => this.handleError(err)
)
}
hideForgotPasswordModal () {
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
+ }
}
+import { Hooks } from '@server/lib/plugins/hooks'
import * as express from 'express'
+import { remove, writeJSON } from 'fs-extra'
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 { 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 { remove, writeJSON } from 'fs-extra'
-import { getServerCommit } from '../../helpers/utils'
-import validator from 'validator'
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 { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION } from '../../initializers/constants'
+import { ClientHtml } from '../../lib/client-html'
import { PluginManager } from '../../lib/plugins/plugin-manager'
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()
}
},
plugin: {
- registered: getRegisteredPlugins()
+ registered: getRegisteredPlugins(),
+ registeredExternalAuths: getExternalAuthsPlugins(),
+ registeredIdAndPassAuths: getIdAndPassAuthPlugins()
},
theme: {
registered: getRegisteredThemes(),
}))
}
+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 {
import { PLUGIN_GLOBAL_CSS_PATH } from '../initializers/constants'
import { join } from 'path'
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 { PluginType } from '../../shared/models/plugins/plugin.type'
import { isTestInstance } from '../helpers/core-utils'
import { getCompleteLocale, is18nLocale } from '../../shared/models/i18n'
+import { logger } from '@server/helpers/logger'
const sendFileOptions = {
maxAge: '30 days',
getPluginTranslations
)
+pluginsRouter.get('/plugins/:pluginName/:pluginVersion/auth/:authName',
+ getPluginValidator(PluginType.PLUGIN),
+ getExternalAuthValidator,
+ handleAuthInPlugin
+)
+
pluginsRouter.get('/plugins/:pluginName/:pluginVersion/static/:staticEndpoint(*)',
getPluginValidator(PluginType.PLUGIN),
pluginStaticDirectoryValidator,
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)
+ }
+}
-import * as express from 'express'
-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 { isUserDisplayNameValid, isUserRoleValid, isUserUsernameValid } from '@server/helpers/custom-validators/users'
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 { PluginManager } from '@server/lib/plugins/plugin-manager'
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({
useErrorHandler: true,
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) {
const grantType = req.body.grant_type
- if (grantType === 'password') await proxifyPasswordGrant(req, res)
- else if (grantType === 'refresh_token') await proxifyRefreshGrant(req, res)
+ if (grantType === 'password') {
+ 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 res.sendStatus(200)
}
-// ---------------------------------------------------------------------------
+async function onExternalUserAuthenticated (options: {
+ npmName: string
+ authName: string
+ authResult: RegisterServerExternalAuthenticatedResult
+}) {
+ const { npmName, authName, authResult } = options
-export {
- oAuthServer,
- handleIdAndPassLogin,
- onExternalAuthPlugin,
- handleTokenRevocation
+ if (!authResult.req || !authResult.res) {
+ logger.error('Cannot authenticate external user for auth %s of plugin %s: no req or res are provided.', authName, npmName)
+ return
+ }
+
+ 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 => {
if (err) {
logger.warn('Login error.', { err })
return res.status(err.status)
- .json({
- error: err.message,
- code: err.name
- })
- .end()
+ .json({
+ error: err.message,
+ code: err.name
+ })
}
- return next()
+ if (next) return next()
})
}
try {
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)) {
- logger.error('Auth method %s of plugin %s did not provide a valid username.', authName, npmName, { loginResult })
- continue
- }
-
- if (!loginResult.email) {
- logger.error('Auth method %s of plugin %s did not provide a valid email.', authName, npmName, { loginResult })
- continue
- }
-
- // role is optional
- if (loginResult.role && !isUserRoleValid(loginResult.role)) {
- logger.error('Auth method %s of plugin %s did not provide a valid role.', authName, npmName, { loginResult })
- continue
- }
-
- // 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
+
+ if (!loginResult) continue
+ if (!isAuthResultValid(pluginAuth.npmName, authOptions.authName, loginResult)) continue
+
+ logger.info(
+ 'Login success with auth method %s of plugin %s for %s.',
+ authName, npmName, loginOptions.id
+ )
+
+ res.locals.bypassLogin = {
+ bypass: true,
+ pluginName: pluginAuth.npmName,
+ authName: authOptions.authName,
+ user: buildUserResult(loginResult)
}
+
+ return
} catch (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
+ }
+}
return tokenInfo
}
-async function getUser (usernameOrEmail: string, password: string) {
+async function getUser (usernameOrEmail?: string, password?: string) {
const res: express.Response = this.request.res
// Special treatment coming from a plugin
-import { PluginSettingsManager } from '@shared/models/plugins/plugin-settings-manager.model'
+import { logger } from '@server/helpers/logger'
+import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PLAYLIST_PRIVACIES, VIDEO_PRIVACIES } from '@server/initializers/constants'
+import { onExternalUserAuthenticated } from '@server/lib/auth'
import { PluginModel } from '@server/models/server/plugin'
+import { RegisterServerOptions } from '@server/typings/plugins'
+import { PluginPlaylistPrivacyManager } from '@shared/models/plugins/plugin-playlist-privacy-manager.model'
+import { PluginSettingsManager } from '@shared/models/plugins/plugin-settings-manager.model'
import { PluginStorageManager } from '@shared/models/plugins/plugin-storage-manager.model'
+import { PluginVideoCategoryManager } from '@shared/models/plugins/plugin-video-category-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 { 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 { serverHookObject } from '@shared/models/plugins/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 { PluginVideoPrivacyManager } from '@shared/models/plugins/plugin-video-privacy-manager.model'
-import { PluginPlaylistPrivacyManager } from '@shared/models/plugins/plugin-playlist-privacy-manager.model'
-import {
- RegisterServerAuthExternalOptions,
- RegisterServerAuthExternalResult,
- RegisterServerAuthPassOptions
-} from '@shared/models/plugins/register-server-auth.model'
-import { onExternalAuthPlugin } from '@server/lib/auth'
+import { buildPluginHelpers } from './plugin-helpers'
type AlterableVideoConstant = 'language' | 'licence' | 'category' | 'privacy' | 'playlistPrivacy'
type VideoConstant = { [key in number | string]: string }
this.externalAuths.push(options)
return {
- onAuth (options: { username: string, email: string }): void {
- onExternalAuthPlugin(self.npmName, options.username, options.email)
+ userAuthenticated (result: RegisterServerExternalAuthenticatedResult): void {
+ 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
}
import { areValidationErrors } from './utils'
import { isNpmPluginNameValid, isPluginNameValid, isPluginTypeValid, isPluginVersionValid } from '../../helpers/custom-validators/plugins'
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 { InstallOrUpdatePlugin } from '../../../shared/models/plugins/install-plugin.model'
import { PluginType } from '../../../shared/models/plugins/plugin.type'
])
}
+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 = [
param('staticEndpoint').custom(isSafePath).withMessage('Should have a valid static endpoint'),
listAvailablePluginsValidator,
existingPluginValidator,
installOrUpdatePluginValidator,
- listPluginsValidator
+ listPluginsValidator,
+ getExternalAuthValidator
}
import { MServerBlocklist } from './models/server/server-blocklist'
import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token'
import { UserRole } from '@shared/models'
+import { RegisterServerAuthExternalOptions } from '@shared/models/plugins/register-server-auth.model'
declare module 'express' {
interface Response {
registeredPlugin?: RegisteredPlugin
+ externalAuth?: RegisterServerAuthExternalOptions
+
plugin?: MPlugin
}
}
import { UserRole } from '@shared/models'
import { MOAuthToken } from '@server/typings/models'
+import * as express from 'express'
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)
authName: string
// Called by PeerTube when a user from your plugin logged out
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
// So you can control for your plugin the user session lifetime
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
// Returns null if the login failed, or { username, email } on success
login(body: {
id: string
password: string
- }): Promise<{
- username: string
- email: string
- role?: UserRole
- displayName?: string
- } | null>
+ }): Promise<RegisterServerAuthenticatedResult | null>
}
-export interface RegisterServerAuthExternalOptions {
- // Authentication name (a plugin can register multiple auth strategies)
- authName: string
+export interface RegisterServerAuthExternalOptions extends RegisterServerAuthBase {
+ // Will be displayed in a block next to the login form
+ authDisplayName: string
- onLogout?: Function
+ onAuthRequest: (req: express.Request, res: express.Response) => void
}
export interface RegisterServerAuthExternalResult {
- onAuth (options: { username: string, email: string }): void
+ userAuthenticated (options: RegisterServerExternalAuthenticatedResult): void
}
private: boolean
// Default setting value
- default?: string
+ default?: string | boolean
}
export interface RegisteredServerSettings {
css: string[]
}
+export interface RegisteredExternalAuthConfig {
+ npmName: string
+ authName: string
+ authDisplayName: string
+}
+
+export interface RegisteredIdAndPassAuthConfig {
+ npmName: string
+ authName: string
+ weight: number
+}
+
export interface ServerConfig {
serverVersion: string
serverCommit?: string
plugin: {
registered: ServerConfigPlugin[]
+
+ registeredExternalAuths: RegisteredExternalAuthConfig[]
+
+ registeredIdAndPassAuths: RegisteredIdAndPassAuthConfig[]
}
theme: {