1 import express from 'express'
8 UnauthorizedClientError,
9 UnsupportedGrantTypeError
10 } from 'oauth2-server'
11 import { randomBytesPromise } from '@server/helpers/core-utils'
12 import { MOAuthClient } from '@server/types/models'
13 import { sha1 } from '@shared/extra-utils'
14 import { OAUTH_LIFETIME } from '../../initializers/constants'
15 import { BypassLogin, getClient, getRefreshToken, getUser, revokeToken, saveToken } from './oauth-model'
19 * Reimplement some functions of OAuth2Server to inject external auth methods
23 const oAuthServer = new (require('oauth2-server'))({
24 accessTokenLifetime: OAUTH_LIFETIME.ACCESS_TOKEN,
25 refreshTokenLifetime: OAUTH_LIFETIME.REFRESH_TOKEN,
27 // See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications
28 model: require('./oauth-model')
31 // ---------------------------------------------------------------------------
33 async function handleOAuthToken (req: express.Request, options: { refreshTokenAuthName?: string, bypassLogin?: BypassLogin }) {
34 const request = new Request(req)
35 const { refreshTokenAuthName, bypassLogin } = options
37 if (request.method !== 'POST') {
38 throw new InvalidRequestError('Invalid request: method must be POST')
41 if (!request.is([ 'application/x-www-form-urlencoded' ])) {
42 throw new InvalidRequestError('Invalid request: content must be application/x-www-form-urlencoded')
45 const clientId = request.body.client_id
46 const clientSecret = request.body.client_secret
48 if (!clientId || !clientSecret) {
49 throw new InvalidClientError('Invalid client: cannot retrieve client credentials')
52 const client = await getClient(clientId, clientSecret)
54 throw new InvalidClientError('Invalid client: client is invalid')
57 const grantType = request.body.grant_type
59 throw new InvalidRequestError('Missing parameter: `grant_type`')
62 if (![ 'password', 'refresh_token' ].includes(grantType)) {
63 throw new UnsupportedGrantTypeError('Unsupported grant type: `grant_type` is invalid')
66 if (!client.grants.includes(grantType)) {
67 throw new UnauthorizedClientError('Unauthorized client: `grant_type` is invalid')
70 if (grantType === 'password') {
71 return handlePasswordGrant({
78 return handleRefreshGrant({
85 function handleOAuthAuthenticate (
87 res: express.Response,
88 authenticateInQuery = false
90 const options = authenticateInQuery
91 ? { allowBearerTokensInQueryString: true }
94 return oAuthServer.authenticate(new Request(req), new Response(res), options)
99 handleOAuthAuthenticate
102 // ---------------------------------------------------------------------------
104 async function handlePasswordGrant (options: {
107 bypassLogin?: BypassLogin
109 const { request, client, bypassLogin } = options
111 if (!request.body.username) {
112 throw new InvalidRequestError('Missing parameter: `username`')
115 if (!bypassLogin && !request.body.password) {
116 throw new InvalidRequestError('Missing parameter: `password`')
119 const user = await getUser(request.body.username, request.body.password, bypassLogin)
120 if (!user) throw new InvalidGrantError('Invalid grant: user credentials are invalid')
122 const token = await buildToken()
124 return saveToken(token, client, user, { bypassLogin })
127 async function handleRefreshGrant (options: {
130 refreshTokenAuthName: string
132 const { request, client, refreshTokenAuthName } = options
134 if (!request.body.refresh_token) {
135 throw new InvalidRequestError('Missing parameter: `refresh_token`')
138 const refreshToken = await getRefreshToken(request.body.refresh_token)
141 throw new InvalidGrantError('Invalid grant: refresh token is invalid')
144 if (refreshToken.client.id !== client.id) {
145 throw new InvalidGrantError('Invalid grant: refresh token is invalid')
148 if (refreshToken.refreshTokenExpiresAt && refreshToken.refreshTokenExpiresAt < new Date()) {
149 throw new InvalidGrantError('Invalid grant: refresh token has expired')
152 await revokeToken({ refreshToken: refreshToken.refreshToken })
154 const token = await buildToken()
156 return saveToken(token, client, refreshToken.user, { refreshTokenAuthName })
159 function generateRandomToken () {
160 return randomBytesPromise(256)
161 .then(buffer => sha1(buffer))
164 function getTokenExpiresAt (type: 'access' | 'refresh') {
165 const lifetime = type === 'access'
166 ? OAUTH_LIFETIME.ACCESS_TOKEN
167 : OAUTH_LIFETIME.REFRESH_TOKEN
169 return new Date(Date.now() + lifetime * 1000)
172 async function buildToken () {
173 const [ accessToken, refreshToken ] = await Promise.all([ generateRandomToken(), generateRandomToken() ])
178 accessTokenExpiresAt: getTokenExpiresAt('access'),
179 refreshTokenExpiresAt: getTokenExpiresAt('refresh')