From: Chocobozzz Date: Fri, 24 Apr 2020 09:33:01 +0000 (+0200) Subject: Add ability for auth plugins to hook tokens validity X-Git-Tag: v2.2.0-rc.1~115 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=e307e4fce39853d445d086f92b8c556c363ee15d;p=oweals%2Fpeertube.git Add ability for auth plugins to hook tokens validity --- diff --git a/server/controllers/api/users/token.ts b/server/controllers/api/users/token.ts index 9694f9e5e..f4be228f6 100644 --- a/server/controllers/api/users/token.ts +++ b/server/controllers/api/users/token.ts @@ -20,8 +20,7 @@ tokensRouter.post('/token', tokensRouter.post('/revoke-token', authenticate, - asyncMiddleware(handleTokenRevocation), - tokenSuccess + asyncMiddleware(handleTokenRevocation) ) // --------------------------------------------------------------------------- diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index 2d49e6869..aeb8fde01 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts @@ -7,8 +7,7 @@ import { signJsonLDObject } from './peertube-crypto' import { pageToStartAndCount } from './core-utils' import { URL } from 'url' import { MActor, MVideoAccountLight } from '../typings/models' - -export type ContextType = 'All' | 'View' | 'Announce' | 'CacheFile' +import { ContextType } from '@shared/models/activitypub/context' function getContextData (type: ContextType) { const context: any[] = [ diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts index 0635c7b66..e521cabbc 100644 --- a/server/lib/activitypub/send/send-create.ts +++ b/server/lib/activitypub/send/send-create.ts @@ -15,8 +15,8 @@ import { MVideoRedundancyFileVideo, MVideoRedundancyStreamingPlaylistVideo } from '../../../typings/models' -import { ContextType } from '@server/helpers/activitypub' import { getServerActor } from '@server/models/application/application' +import { ContextType } from '@shared/models/activitypub/context' async function sendCreateVideo (video: MVideoAP, t: Transaction) { if (!video.hasPrivacyForFederation()) return undefined diff --git a/server/lib/activitypub/send/utils.ts b/server/lib/activitypub/send/utils.ts index 0dfcc51be..44a8926e5 100644 --- a/server/lib/activitypub/send/utils.ts +++ b/server/lib/activitypub/send/utils.ts @@ -7,8 +7,8 @@ import { JobQueue } from '../../job-queue' import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience' import { afterCommitIfTransaction } from '../../../helpers/database-utils' import { MActor, MActorId, MActorLight, MActorWithInboxes, MVideoAccountLight, MVideoId, MVideoImmutable } from '../../../typings/models' -import { ContextType } from '@server/helpers/activitypub' import { getServerActor } from '@server/models/application/application' +import { ContextType } from '@shared/models/activitypub/context' async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { byActor: MActorLight diff --git a/server/lib/auth.ts b/server/lib/auth.ts index 3495571db..c2a6fcaff 100644 --- a/server/lib/auth.ts +++ b/server/lib/auth.ts @@ -6,6 +6,7 @@ import { RegisterServerAuthPassOptions } from '@shared/models/plugins/register-s import { logger } from '@server/helpers/logger' import { UserRole } from '@shared/models' import { revokeToken } from '@server/lib/oauth-model' +import { OAuthTokenModel } from '@server/models/oauth/oauth-token' const oAuthServer = new OAuthServer({ useErrorHandler: true, @@ -20,6 +21,74 @@ function onExternalAuthPlugin (npmName: string, username: string, email: 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) + + return forwardTokenReq(req, res, next) +} + +async function handleTokenRevocation (req: express.Request, res: express.Response) { + const token = res.locals.oauth.token + + res.locals.explicitLogout = true + await revokeToken(token) + + // FIXME: uncomment when https://github.com/oauthjs/node-oauth2-server/pull/289 is released + // oAuthServer.revoke(req, res, err => { + // if (err) { + // logger.warn('Error in revoke token handler.', { err }) + // + // return res.status(err.status) + // .json({ + // error: err.message, + // code: err.name + // }) + // .end() + // } + // }) + + return res.sendStatus(200) +} + +// --------------------------------------------------------------------------- + +export { + oAuthServer, + handleIdAndPassLogin, + onExternalAuthPlugin, + 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() + } + + return next() + }) +} + +async function proxifyRefreshGrant (req: express.Request, res: express.Response) { + const refreshToken = req.body.refresh_token + if (!refreshToken) return + + const tokenModel = await OAuthTokenModel.loadByRefreshToken(refreshToken) + if (tokenModel?.authName) res.locals.refreshTokenAuthName = tokenModel.authName +} + +async function proxifyPasswordGrant (req: express.Request, res: express.Response) { const plugins = PluginManager.Instance.getIdAndPassAuths() const pluginAuths: { npmName?: string, registerAuthOptions: RegisterServerAuthPassOptions }[] = [] @@ -76,64 +145,7 @@ async function handleIdAndPassLogin (req: express.Request, res: express.Response } } - break + return } } - - return localLogin(req, res, next) -} - -async function handleTokenRevocation (req: express.Request, res: express.Response) { - const token = res.locals.oauth.token - - PluginManager.Instance.onLogout(token.User.pluginAuth, token.authName) - - await revokeToken(token) - .catch(err => { - logger.error('Cannot revoke token.', err) - }) - - // FIXME: uncomment when https://github.com/oauthjs/node-oauth2-server/pull/289 is released - // oAuthServer.revoke(req, res, err => { - // if (err) { - // logger.warn('Error in revoke token handler.', { err }) - // - // return res.status(err.status) - // .json({ - // error: err.message, - // code: err.name - // }) - // .end() - // } - // }) - - return res.sendStatus(200) -} - -// --------------------------------------------------------------------------- - -export { - oAuthServer, - handleIdAndPassLogin, - onExternalAuthPlugin, - handleTokenRevocation -} - -// --------------------------------------------------------------------------- - -function localLogin (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() - } - - return next() - }) } diff --git a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts index 437ea06fc..bcb49a731 100644 --- a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts +++ b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts @@ -1,9 +1,10 @@ -import { buildSignedActivity, ContextType } from '../../../../helpers/activitypub' +import { buildSignedActivity } from '../../../../helpers/activitypub' import { ActorModel } from '../../../../models/activitypub/actor' import { ACTIVITY_PUB, HTTP_SIGNATURE } from '../../../../initializers/constants' import { MActor } from '../../../../typings/models' import { getServerActor } from '@server/models/application/application' import { buildDigest } from '@server/helpers/peertube-crypto' +import { ContextType } from '@shared/models/activitypub/context' type Payload = { body: any, contextType?: ContextType, signatureActorId?: number } diff --git a/server/lib/oauth-model.ts b/server/lib/oauth-model.ts index 7a6ed63be..6eb0e4473 100644 --- a/server/lib/oauth-model.ts +++ b/server/lib/oauth-model.ts @@ -1,4 +1,3 @@ -import * as Bluebird from 'bluebird' import * as express from 'express' import { AccessDeniedError } from 'oauth2-server' import { logger } from '../helpers/logger' @@ -47,22 +46,33 @@ function clearCacheByToken (token: string) { } } -function getAccessToken (bearerToken: string) { +async function getAccessToken (bearerToken: string) { logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') - if (!bearerToken) return Bluebird.resolve(undefined) + if (!bearerToken) return undefined - if (accessTokenCache.has(bearerToken)) return Bluebird.resolve(accessTokenCache.get(bearerToken)) + let tokenModel: MOAuthTokenUser - return OAuthTokenModel.getByTokenAndPopulateUser(bearerToken) - .then(tokenModel => { - if (tokenModel) { - accessTokenCache.set(bearerToken, tokenModel) - userHavingToken.set(tokenModel.userId, tokenModel.accessToken) - } + if (accessTokenCache.has(bearerToken)) { + tokenModel = accessTokenCache.get(bearerToken) + } else { + tokenModel = await OAuthTokenModel.getByTokenAndPopulateUser(bearerToken) - return tokenModel - }) + if (tokenModel) { + accessTokenCache.set(bearerToken, tokenModel) + userHavingToken.set(tokenModel.userId, tokenModel.accessToken) + } + } + + if (!tokenModel) return undefined + + if (tokenModel.User.pluginAuth) { + const valid = await PluginManager.Instance.isTokenValid(tokenModel, 'access') + + if (valid !== true) return undefined + } + + return tokenModel } function getClient (clientId: string, clientSecret: string) { @@ -71,14 +81,27 @@ function getClient (clientId: string, clientSecret: string) { return OAuthClientModel.getByIdAndSecret(clientId, clientSecret) } -function getRefreshToken (refreshToken: string) { +async function getRefreshToken (refreshToken: string) { logger.debug('Getting RefreshToken (refreshToken: ' + refreshToken + ').') - return OAuthTokenModel.getByRefreshTokenAndPopulateClient(refreshToken) + const tokenInfo = await OAuthTokenModel.getByRefreshTokenAndPopulateClient(refreshToken) + if (!tokenInfo) return undefined + + const tokenModel = tokenInfo.token + + if (tokenModel.User.pluginAuth) { + const valid = await PluginManager.Instance.isTokenValid(tokenModel, 'refresh') + + if (valid !== true) return undefined + } + + return tokenInfo } async function getUser (usernameOrEmail: string, password: string) { const res: express.Response = this.request.res + + // Special treatment coming from a plugin if (res.locals.bypassLogin && res.locals.bypassLogin.bypass === true) { const obj = res.locals.bypassLogin logger.info('Bypassing oauth login by plugin %s.', obj.pluginName) @@ -110,7 +133,7 @@ async function getUser (usernameOrEmail: string, password: string) { return user } -async function revokeToken (tokenInfo: TokenInfo) { +async function revokeToken (tokenInfo: { refreshToken: string }) { const res: express.Response = this.request.res const token = await OAuthTokenModel.getByRefreshTokenAndPopulateUser(tokenInfo.refreshToken) @@ -133,9 +156,12 @@ async function revokeToken (tokenInfo: TokenInfo) { async function saveToken (token: TokenInfo, client: OAuthClientModel, user: UserModel) { const res: express.Response = this.request.res - const authName = res.locals.bypassLogin?.bypass === true - ? res.locals.bypassLogin.authName - : null + let authName: string = null + if (res.locals.bypassLogin?.bypass === true) { + authName = res.locals.bypassLogin.authName + } else if (res.locals.refreshTokenAuthName) { + authName = res.locals.refreshTokenAuthName + } logger.debug('Saving token ' + token.accessToken + ' for client ' + client.id + ' and user ' + user.id + '.') diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts index 9d646b689..c64ca60aa 100644 --- a/server/lib/plugins/plugin-manager.ts +++ b/server/lib/plugins/plugin-manager.ts @@ -21,6 +21,7 @@ import { ClientHtml } from '../client-html' import { PluginTranslation } from '../../../shared/models/plugins/plugin-translation.model' import { RegisterHelpersStore } from './register-helpers-store' import { RegisterServerHookOptions } from '@shared/models/plugins/register-server-hook.model' +import { MOAuthTokenUser } from '@server/typings/models' export interface RegisteredPlugin { npmName: string @@ -133,13 +134,11 @@ export class PluginManager implements ServerHook { } onLogout (npmName: string, authName: string) { - const plugin = this.getRegisteredPluginOrTheme(npmName) - if (!plugin || plugin.type !== PluginType.PLUGIN) return + const auth = this.getAuth(npmName, authName) - const auth = plugin.registerHelpersStore.getIdAndPassAuths() - .find(a => a.authName === authName) + if (auth?.onLogout) { + logger.info('Running onLogout function from auth %s of plugin %s', authName, npmName) - if (auth.onLogout) { try { auth.onLogout() } catch (err) { @@ -148,6 +147,28 @@ export class PluginManager implements ServerHook { } } + async isTokenValid (token: MOAuthTokenUser, type: 'access' | 'refresh') { + const auth = this.getAuth(token.User.pluginAuth, token.authName) + if (!auth) return true + + if (auth.hookTokenValidity) { + try { + const { valid } = await auth.hookTokenValidity({ token, type }) + + if (valid === false) { + logger.info('Rejecting %s token validity from auth %s of plugin %s', type, token.authName, token.User.pluginAuth) + } + + return valid + } catch (err) { + logger.warn('Cannot run check token validity from auth %s of plugin %s.', token.authName, token.User.pluginAuth, { err }) + return true + } + } + + return true + } + // ###################### Hooks ###################### async runHook (hookName: ServerHookName, result?: T, params?: any): Promise { @@ -453,6 +474,14 @@ export class PluginManager implements ServerHook { return join(CONFIG.STORAGE.PLUGINS_DIR, 'node_modules', npmName) } + private getAuth (npmName: string, authName: string) { + const plugin = this.getRegisteredPluginOrTheme(npmName) + if (!plugin || plugin.type !== PluginType.PLUGIN) return null + + return plugin.registerHelpersStore.getIdAndPassAuths() + .find(a => a.authName === authName) + } + // ###################### Private getters ###################### private getRegisteredPluginsOrThemes (type: PluginType) { diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts index e73c4be7d..3541b6103 100644 --- a/server/models/oauth/oauth-token.ts +++ b/server/models/oauth/oauth-token.ts @@ -30,6 +30,7 @@ export type OAuthTokenInfo = { user: { id: number } + token: MOAuthTokenUser } enum ScopeNames { @@ -136,33 +137,43 @@ export class OAuthTokenModel extends Model { return clearCacheByToken(token.accessToken) } + static loadByRefreshToken (refreshToken: string) { + const query = { + where: { refreshToken } + } + + return OAuthTokenModel.findOne(query) + } + static getByRefreshTokenAndPopulateClient (refreshToken: string) { const query = { where: { - refreshToken: refreshToken + refreshToken }, include: [ OAuthClientModel ] } - return OAuthTokenModel.findOne(query) - .then(token => { - if (!token) return null - - return { - refreshToken: token.refreshToken, - refreshTokenExpiresAt: token.refreshTokenExpiresAt, - client: { - id: token.oAuthClientId - }, - user: { - id: token.userId - } - } as OAuthTokenInfo - }) - .catch(err => { - logger.error('getRefreshToken error.', { err }) - throw err - }) + return OAuthTokenModel.scope(ScopeNames.WITH_USER) + .findOne(query) + .then(token => { + if (!token) return null + + return { + refreshToken: token.refreshToken, + refreshTokenExpiresAt: token.refreshTokenExpiresAt, + client: { + id: token.oAuthClientId + }, + user: { + id: token.userId + }, + token + } as OAuthTokenInfo + }) + .catch(err => { + logger.error('getRefreshToken error.', { err }) + throw err + }) } static getByTokenAndPopulateUser (bearerToken: string): Bluebird { @@ -184,14 +195,14 @@ export class OAuthTokenModel extends Model { static getByRefreshTokenAndPopulateUser (refreshToken: string): Bluebird { const query = { where: { - refreshToken: refreshToken + refreshToken } } return OAuthTokenModel.scope(ScopeNames.WITH_USER) .findOne(query) .then(token => { - if (!token) return new OAuthTokenModel() + if (!token) return undefined return Object.assign(token, { user: token.User }) }) diff --git a/server/tests/fixtures/peertube-plugin-test-id-pass-auth-two/main.js b/server/tests/fixtures/peertube-plugin-test-id-pass-auth-two/main.js index c0e560019..ceab7b60d 100644 --- a/server/tests/fixtures/peertube-plugin-test-id-pass-auth-two/main.js +++ b/server/tests/fixtures/peertube-plugin-test-id-pass-auth-two/main.js @@ -11,6 +11,24 @@ async function register ({ getWeight: () => 30, + hookTokenValidity: (options) => { + if (options.type === 'refresh') { + return { valid: false } + } + + if (options.type === 'access') { + const token = options.token + const now = new Date() + now.setTime(now.getTime() - 5000) + + const createdAt = new Date(token.createdAt) + + return { valid: createdAt.getTime() >= now.getTime() } + } + + return { valid: true } + }, + login (body) { if (body.id === 'laguna' && body.password === 'laguna password') { return Promise.resolve({ diff --git a/server/tests/plugins/id-and-pass-auth.ts b/server/tests/plugins/id-and-pass-auth.ts index 45fa7856c..0268d35a0 100644 --- a/server/tests/plugins/id-and-pass-auth.ts +++ b/server/tests/plugins/id-and-pass-auth.ts @@ -10,14 +10,21 @@ import { setAccessTokensToServers, uninstallPlugin, updateMyUser, - userLogin + userLogin, + wait, + login, refreshToken } from '../../../shared/extra-utils' import { User, UserRole } from '@shared/models' import { expect } from 'chai' describe('Test id and pass auth plugins', function () { let server: ServerInfo - let crashToken: string + + let crashAccessToken: string + let crashRefreshToken: string + + let lagunaAccessToken: string + let lagunaRefreshToken: string before(async function () { this.timeout(30000) @@ -50,36 +57,64 @@ describe('Test id and pass auth plugins', function () { }) it('Should login Crash, create the user and use the token', async function () { - crashToken = await userLogin(server, { username: 'crash', password: 'crash password' }) + { + const res = await login(server.url, server.client, { username: 'crash', password: 'crash password' }) + crashAccessToken = res.body.access_token + crashRefreshToken = res.body.refresh_token + } - const res = await getMyUserInformation(server.url, crashToken) + { + const res = await getMyUserInformation(server.url, crashAccessToken) - const body: User = res.body - expect(body.username).to.equal('crash') - expect(body.account.displayName).to.equal('Crash Bandicoot') - expect(body.role).to.equal(UserRole.MODERATOR) + const body: User = res.body + expect(body.username).to.equal('crash') + expect(body.account.displayName).to.equal('Crash Bandicoot') + expect(body.role).to.equal(UserRole.MODERATOR) + } }) it('Should login the first Laguna, create the user and use the token', async function () { - const accessToken = await userLogin(server, { username: 'laguna', password: 'laguna password' }) + { + const res = await login(server.url, server.client, { username: 'laguna', password: 'laguna password' }) + lagunaAccessToken = res.body.access_token + lagunaRefreshToken = res.body.refresh_token + } - const res = await getMyUserInformation(server.url, accessToken) + { + const res = await getMyUserInformation(server.url, lagunaAccessToken) - const body: User = res.body - expect(body.username).to.equal('laguna') - expect(body.account.displayName).to.equal('laguna') - expect(body.role).to.equal(UserRole.USER) + const body: User = res.body + expect(body.username).to.equal('laguna') + expect(body.account.displayName).to.equal('laguna') + expect(body.role).to.equal(UserRole.USER) + } + }) + + it('Should refresh crash token, but not laguna token', async function () { + { + const resRefresh = await refreshToken(server, crashRefreshToken) + crashAccessToken = resRefresh.body.access_token + crashRefreshToken = resRefresh.body.refresh_token + + const res = await getMyUserInformation(server.url, crashAccessToken) + const user: User = res.body + expect(user.username).to.equal('crash') + } + + { + await refreshToken(server, lagunaRefreshToken, 400) + } }) it('Should update Crash profile', async function () { await updateMyUser({ url: server.url, - accessToken: crashToken, + accessToken: crashAccessToken, displayName: 'Beautiful Crash', description: 'Mutant eastern barred bandicoot' }) - const res = await getMyUserInformation(server.url, crashToken) + const res = await getMyUserInformation(server.url, crashAccessToken) const body: User = res.body expect(body.account.displayName).to.equal('Beautiful Crash') @@ -87,19 +122,19 @@ describe('Test id and pass auth plugins', function () { }) it('Should logout Crash', async function () { - await logout(server.url, crashToken) + await logout(server.url, crashAccessToken) }) it('Should have logged out Crash', async function () { - await getMyUserInformation(server.url, crashToken, 401) - await waitUntilLog(server, 'On logout for auth 1 - 2') + + await getMyUserInformation(server.url, crashAccessToken, 401) }) it('Should login Crash and keep the old existing profile', async function () { - crashToken = await userLogin(server, { username: 'crash', password: 'crash password' }) + crashAccessToken = await userLogin(server, { username: 'crash', password: 'crash password' }) - const res = await getMyUserInformation(server.url, crashToken) + const res = await getMyUserInformation(server.url, crashAccessToken) const body: User = res.body expect(body.username).to.equal('crash') @@ -108,6 +143,14 @@ describe('Test id and pass auth plugins', function () { expect(body.role).to.equal(UserRole.MODERATOR) }) + it('Should correctly auth token of laguna', async function () { + this.timeout(10000) + + await wait(5000) + + await getMyUserInformation(server.url, lagunaAccessToken, 401) + }) + it('Should uninstall the plugin one and do not login existing Crash', async function () { await uninstallPlugin({ url: server.url, diff --git a/server/typings/express.ts b/server/typings/express.ts index 2d12a486a..e6e120403 100644 --- a/server/typings/express.ts +++ b/server/typings/express.ts @@ -46,6 +46,8 @@ declare module 'express' { } } + refreshTokenAuthName?: string + explicitLogout: boolean videoAll?: MVideoFullLight diff --git a/shared/extra-utils/users/login.ts b/shared/extra-utils/users/login.ts index 2d68337a6..b12b51b8c 100644 --- a/shared/extra-utils/users/login.ts +++ b/shared/extra-utils/users/login.ts @@ -43,6 +43,24 @@ async function serverLogin (server: Server) { return res.body.access_token as string } +function refreshToken (server: ServerInfo, refreshToken: string, expectedStatus = 200) { + const path = '/api/v1/users/token' + + const body = { + client_id: server.client.id, + client_secret: server.client.secret, + refresh_token: refreshToken, + response_type: 'code', + grant_type: 'refresh_token' + } + + return request(server.url) + .post(path) + .type('form') + .send(body) + .expect(expectedStatus) +} + async function userLogin (server: Server, user: User, expectedStatus = 200) { const res = await login(server.url, server.client, user, expectedStatus) @@ -83,6 +101,7 @@ export { login, logout, serverLogin, + refreshToken, userLogin, getAccessToken, setAccessTokensToServers, diff --git a/shared/models/activitypub/context.ts b/shared/models/activitypub/context.ts new file mode 100644 index 000000000..bd795a2fd --- /dev/null +++ b/shared/models/activitypub/context.ts @@ -0,0 +1 @@ +export type ContextType = 'All' | 'View' | 'Announce' | 'CacheFile' diff --git a/shared/models/plugins/register-server-auth.model.ts b/shared/models/plugins/register-server-auth.model.ts index dc46dcbc8..403a49994 100644 --- a/shared/models/plugins/register-server-auth.model.ts +++ b/shared/models/plugins/register-server-auth.model.ts @@ -1,4 +1,5 @@ import { UserRole } from '@shared/models' +import { MOAuthToken } from '@server/typings/models' export type RegisterServerAuthOptions = RegisterServerAuthPassOptions | RegisterServerAuthExternalOptions @@ -6,11 +7,16 @@ export interface RegisterServerAuthPassOptions { // Authentication name (a plugin can register multiple auth strategies) authName: string - onLogout?: Function + // 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 }> + // Used by PeerTube to login a user // Returns null if the login failed, or { username, email } on success login(body: { diff --git a/shared/models/server/job.model.ts b/shared/models/server/job.model.ts index 694361276..57d61c480 100644 --- a/shared/models/server/job.model.ts +++ b/shared/models/server/job.model.ts @@ -1,6 +1,6 @@ -import { ContextType } from '@server/helpers/activitypub' import { SendEmailOptions } from './emailer.model' import { VideoResolution } from '@shared/models' +import { ContextType } from '../activitypub/context' export type JobState = 'active' | 'completed' | 'failed' | 'waiting' | 'delayed'