From f43db2f46ee50bacb402a6ef42d768694c2bc9a8 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 12 Mar 2021 15:20:46 +0100 Subject: Refactor auth flow Reimplement some node-oauth2-server methods to remove hacky code needed by our external login workflow --- server/controllers/api/users/index.ts | 8 ++-- server/controllers/api/users/token.ts | 72 +++++++++++++++++++++++++++++++---- 2 files changed, 68 insertions(+), 12 deletions(-) (limited to 'server/controllers/api') diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts index 3be1d55ae..e2b1ea7cd 100644 --- a/server/controllers/api/users/index.ts +++ b/server/controllers/api/users/index.ts @@ -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 }) }) diff --git a/server/controllers/api/users/token.ts b/server/controllers/api/users/token.ts index 821429358..3eae28b34 100644 --- a/server/controllers/api/users/token.ts +++ b/server/controllers/api/users/token.ts @@ -1,11 +1,14 @@ -import { handleLogin, handleTokenRevocation } from '@server/lib/auth' +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 * as express from 'express' +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 + + 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', - Hooks.runAction('action:api.user.oauth2-got-token', { username, ip: req.ip }) + 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 { + 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) +} -- cgit v1.2.3