mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2025-10-04 10:19:35 +02:00
Refactor auth flow
Reimplement some node-oauth2-server methods to remove hacky code needed by our external login workflow
This commit is contained in:
parent
cae2df6bdc
commit
f43db2f46e
24 changed files with 487 additions and 255 deletions
|
@ -2,8 +2,10 @@ import * as express from 'express'
|
|||
import * as RateLimit from 'express-rate-limit'
|
||||
import { tokensRouter } from '@server/controllers/api/users/token'
|
||||
import { Hooks } from '@server/lib/plugins/hooks'
|
||||
import { OAuthTokenModel } from '@server/models/oauth/oauth-token'
|
||||
import { MUser, MUserAccountDefault } from '@server/types/models'
|
||||
import { UserCreate, UserRight, UserRole, UserUpdate } from '../../../../shared'
|
||||
import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
|
||||
import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
|
||||
import { UserRegister } from '../../../../shared/models/users/user-register.model'
|
||||
import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
|
||||
|
@ -14,7 +16,6 @@ import { WEBSERVER } from '../../../initializers/constants'
|
|||
import { sequelizeTypescript } from '../../../initializers/database'
|
||||
import { Emailer } from '../../../lib/emailer'
|
||||
import { Notifier } from '../../../lib/notifier'
|
||||
import { deleteUserToken } from '../../../lib/oauth-model'
|
||||
import { Redis } from '../../../lib/redis'
|
||||
import { createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user'
|
||||
import {
|
||||
|
@ -52,7 +53,6 @@ import { myVideosHistoryRouter } from './my-history'
|
|||
import { myNotificationsRouter } from './my-notifications'
|
||||
import { mySubscriptionsRouter } from './my-subscriptions'
|
||||
import { myVideoPlaylistsRouter } from './my-video-playlists'
|
||||
import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
|
||||
|
||||
const auditLogger = auditLoggerFactory('users')
|
||||
|
||||
|
@ -335,7 +335,7 @@ async function updateUser (req: express.Request, res: express.Response) {
|
|||
const user = await userToUpdate.save()
|
||||
|
||||
// Destroy user token to refresh rights
|
||||
if (roleChanged || body.password !== undefined) await deleteUserToken(userToUpdate.id)
|
||||
if (roleChanged || body.password !== undefined) await OAuthTokenModel.deleteUserToken(userToUpdate.id)
|
||||
|
||||
auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView)
|
||||
|
||||
|
@ -395,7 +395,7 @@ async function changeUserBlock (res: express.Response, user: MUserAccountDefault
|
|||
user.blockedReason = reason || null
|
||||
|
||||
await sequelizeTypescript.transaction(async t => {
|
||||
await deleteUserToken(user.id, t)
|
||||
await OAuthTokenModel.deleteUserToken(user.id, t)
|
||||
|
||||
await user.save({ transaction: t })
|
||||
})
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import { handleLogin, handleTokenRevocation } from '@server/lib/auth'
|
||||
import * as RateLimit from 'express-rate-limit'
|
||||
import { CONFIG } from '@server/initializers/config'
|
||||
import * as express from 'express'
|
||||
import * as RateLimit from 'express-rate-limit'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { logger } from '@server/helpers/logger'
|
||||
import { CONFIG } from '@server/initializers/config'
|
||||
import { getAuthNameFromRefreshGrant, getBypassFromExternalAuth, getBypassFromPasswordGrant } from '@server/lib/auth/external-auth'
|
||||
import { handleOAuthToken } from '@server/lib/auth/oauth'
|
||||
import { BypassLogin, revokeToken } from '@server/lib/auth/oauth-model'
|
||||
import { Hooks } from '@server/lib/plugins/hooks'
|
||||
import { asyncMiddleware, authenticate } from '@server/middlewares'
|
||||
import { ScopedToken } from '@shared/models/users/user-scoped-token'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
const tokensRouter = express.Router()
|
||||
|
||||
|
@ -16,8 +19,7 @@ const loginRateLimiter = RateLimit({
|
|||
|
||||
tokensRouter.post('/token',
|
||||
loginRateLimiter,
|
||||
handleLogin,
|
||||
tokenSuccess
|
||||
asyncMiddleware(handleToken)
|
||||
)
|
||||
|
||||
tokensRouter.post('/revoke-token',
|
||||
|
@ -42,10 +44,53 @@ export {
|
|||
}
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function tokenSuccess (req: express.Request) {
|
||||
const username = req.body.username
|
||||
async function handleToken (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const grantType = req.body.grant_type
|
||||
|
||||
Hooks.runAction('action:api.user.oauth2-got-token', { username, ip: req.ip })
|
||||
try {
|
||||
const bypassLogin = await buildByPassLogin(req, grantType)
|
||||
|
||||
const refreshTokenAuthName = grantType === 'refresh_token'
|
||||
? await getAuthNameFromRefreshGrant(req.body.refresh_token)
|
||||
: undefined
|
||||
|
||||
const options = {
|
||||
refreshTokenAuthName,
|
||||
bypassLogin
|
||||
}
|
||||
|
||||
const token = await handleOAuthToken(req, options)
|
||||
|
||||
res.set('Cache-Control', 'no-store')
|
||||
res.set('Pragma', 'no-cache')
|
||||
|
||||
Hooks.runAction('action:api.user.oauth2-got-token', { username: token.user.username, ip: req.ip })
|
||||
|
||||
return res.json({
|
||||
token_type: 'Bearer',
|
||||
|
||||
access_token: token.accessToken,
|
||||
refresh_token: token.refreshToken,
|
||||
|
||||
expires_in: token.accessTokenExpiresIn,
|
||||
refresh_token_expires_in: token.refreshTokenExpiresIn
|
||||
})
|
||||
} catch (err) {
|
||||
logger.warn('Login error', { err })
|
||||
|
||||
return res.status(err.code || 400).json({
|
||||
code: err.name,
|
||||
error: err.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function handleTokenRevocation (req: express.Request, res: express.Response) {
|
||||
const token = res.locals.oauth.token
|
||||
|
||||
const result = await revokeToken(token, true)
|
||||
|
||||
return res.json(result)
|
||||
}
|
||||
|
||||
function getScopedTokens (req: express.Request, res: express.Response) {
|
||||
|
@ -66,3 +111,14 @@ async function renewScopedTokens (req: express.Request, res: express.Response) {
|
|||
feedToken: user.feedToken
|
||||
} as ScopedToken)
|
||||
}
|
||||
|
||||
async function buildByPassLogin (req: express.Request, grantType: string): Promise<BypassLogin> {
|
||||
if (grantType !== 'password') return undefined
|
||||
|
||||
if (req.body.externalAuthToken) {
|
||||
// Consistency with the getBypassFromPasswordGrant promise
|
||||
return getBypassFromExternalAuth(req.body.username, req.body.externalAuthToken)
|
||||
}
|
||||
|
||||
return getBypassFromPasswordGrant(req.body.username, req.body.password)
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import * as express from 'express'
|
||||
import { PLUGIN_GLOBAL_CSS_PATH } from '../initializers/constants'
|
||||
import { join } from 'path'
|
||||
import { PluginManager, RegisteredPlugin } from '../lib/plugins/plugin-manager'
|
||||
import { getPluginValidator, pluginStaticDirectoryValidator, getExternalAuthValidator } from '../middlewares/validators/plugins'
|
||||
import { serveThemeCSSValidator } from '../middlewares/validators/themes'
|
||||
import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes'
|
||||
import { logger } from '@server/helpers/logger'
|
||||
import { optionalAuthenticate } from '@server/middlewares/auth'
|
||||
import { getCompleteLocale, is18nLocale } from '../../shared/core-utils/i18n'
|
||||
import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes'
|
||||
import { PluginType } from '../../shared/models/plugins/plugin.type'
|
||||
import { isTestInstance } from '../helpers/core-utils'
|
||||
import { logger } from '@server/helpers/logger'
|
||||
import { optionalAuthenticate } from '@server/middlewares/oauth'
|
||||
import { PLUGIN_GLOBAL_CSS_PATH } from '../initializers/constants'
|
||||
import { PluginManager, RegisteredPlugin } from '../lib/plugins/plugin-manager'
|
||||
import { getExternalAuthValidator, getPluginValidator, pluginStaticDirectoryValidator } from '../middlewares/validators/plugins'
|
||||
import { serveThemeCSSValidator } from '../middlewares/validators/themes'
|
||||
|
||||
const sendFileOptions = {
|
||||
maxAge: '30 days',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue