125 lines
3.4 KiB
TypeScript
125 lines
3.4 KiB
TypeScript
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'
|
|
|
|
const tokensRouter = express.Router()
|
|
|
|
const loginRateLimiter = RateLimit({
|
|
windowMs: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS,
|
|
max: CONFIG.RATES_LIMIT.LOGIN.MAX
|
|
})
|
|
|
|
tokensRouter.post('/token',
|
|
loginRateLimiter,
|
|
asyncMiddleware(handleToken)
|
|
)
|
|
|
|
tokensRouter.post('/revoke-token',
|
|
authenticate,
|
|
asyncMiddleware(handleTokenRevocation)
|
|
)
|
|
|
|
tokensRouter.get('/scoped-tokens',
|
|
authenticate,
|
|
getScopedTokens
|
|
)
|
|
|
|
tokensRouter.post('/scoped-tokens',
|
|
authenticate,
|
|
asyncMiddleware(renewScopedTokens)
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export {
|
|
tokensRouter
|
|
}
|
|
// ---------------------------------------------------------------------------
|
|
|
|
async function handleToken (req: express.Request, res: express.Response, next: express.NextFunction) {
|
|
const grantType = req.body.grant_type
|
|
|
|
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, { req, explicitLogout: true })
|
|
|
|
return res.json(result)
|
|
}
|
|
|
|
function getScopedTokens (req: express.Request, res: express.Response) {
|
|
const user = res.locals.oauth.token.user
|
|
|
|
return res.json({
|
|
feedToken: user.feedToken
|
|
} as ScopedToken)
|
|
}
|
|
|
|
async function renewScopedTokens (req: express.Request, res: express.Response) {
|
|
const user = res.locals.oauth.token.user
|
|
|
|
user.feedToken = uuidv4()
|
|
await user.save()
|
|
|
|
return res.json({
|
|
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)
|
|
}
|