1 import * as express from 'express'
8 UnauthorizedClientError,
9 UnsupportedGrantTypeError
10 } from 'oauth2-server'
11 import { randomBytesPromise, sha1 } from '@server/helpers/core-utils'
12 import { MOAuthClient } from '@server/types/models'
13 import { OAUTH_LIFETIME } from '../../initializers/constants'
14 import { BypassLogin, getClient, getRefreshToken, getUser, revokeToken, saveToken } from './oauth-model'
18 * Reimplement some functions of OAuth2Server to inject external auth methods
22 const oAuthServer = new (require('oauth2-server'))({
23 accessTokenLifetime: OAUTH_LIFETIME.ACCESS_TOKEN,
24 refreshTokenLifetime: OAUTH_LIFETIME.REFRESH_TOKEN,
26 // See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications
27 model: require('./oauth-model')
30 // ---------------------------------------------------------------------------
32 async function handleOAuthToken (req: express.Request, options: { refreshTokenAuthName?: string, bypassLogin?: BypassLogin }) {
33 const request = new Request(req)
34 const { refreshTokenAuthName, bypassLogin } = options
36 if (request.method !== 'POST') {
37 throw new InvalidRequestError('Invalid request: method must be POST')
40 if (!request.is([ 'application/x-www-form-urlencoded' ])) {
41 throw new InvalidRequestError('Invalid request: content must be application/x-www-form-urlencoded')
44 const clientId = request.body.client_id
45 const clientSecret = request.body.client_secret
47 if (!clientId || !clientSecret) {
48 throw new InvalidClientError('Invalid client: cannot retrieve client credentials')
51 const client = await getClient(clientId, clientSecret)
53 throw new InvalidClientError('Invalid client: client is invalid')
56 const grantType = request.body.grant_type
58 throw new InvalidRequestError('Missing parameter: `grant_type`')
61 if (![ 'password', 'refresh_token' ].includes(grantType)) {
62 throw new UnsupportedGrantTypeError('Unsupported grant type: `grant_type` is invalid')
65 if (!client.grants.includes(grantType)) {
66 throw new UnauthorizedClientError('Unauthorized client: `grant_type` is invalid')
69 if (grantType === 'password') {
70 return handlePasswordGrant({
77 return handleRefreshGrant({
84 async function handleOAuthAuthenticate (
86 res: express.Response,
87 authenticateInQuery = false
89 const options = authenticateInQuery
90 ? { allowBearerTokensInQueryString: true }
93 return oAuthServer.authenticate(new Request(req), new Response(res), options)
98 handleOAuthAuthenticate
101 // ---------------------------------------------------------------------------
103 async function handlePasswordGrant (options: {
106 bypassLogin?: BypassLogin
108 const { request, client, bypassLogin } = options
110 if (!request.body.username) {
111 throw new InvalidRequestError('Missing parameter: `username`')
114 if (!bypassLogin && !request.body.password) {
115 throw new InvalidRequestError('Missing parameter: `password`')
118 const user = await getUser(request.body.username, request.body.password, bypassLogin)
119 if (!user) throw new InvalidGrantError('Invalid grant: user credentials are invalid')
121 const token = await buildToken()
123 return saveToken(token, client, user, { bypassLogin })
126 async function handleRefreshGrant (options: {
129 refreshTokenAuthName: string
131 const { request, client, refreshTokenAuthName } = options
133 if (!request.body.refresh_token) {
134 throw new InvalidRequestError('Missing parameter: `refresh_token`')
137 const refreshToken = await getRefreshToken(request.body.refresh_token)
140 throw new InvalidGrantError('Invalid grant: refresh token is invalid')
143 if (refreshToken.client.id !== client.id) {
144 throw new InvalidGrantError('Invalid grant: refresh token is invalid')
147 if (refreshToken.refreshTokenExpiresAt && refreshToken.refreshTokenExpiresAt < new Date()) {
148 throw new InvalidGrantError('Invalid grant: refresh token has expired')
151 await revokeToken({ refreshToken: refreshToken.refreshToken })
153 const token = await buildToken()
155 return saveToken(token, client, refreshToken.user, { refreshTokenAuthName })
158 function generateRandomToken () {
159 return randomBytesPromise(256)
160 .then(buffer => sha1(buffer))
163 function getTokenExpiresAt (type: 'access' | 'refresh') {
164 const lifetime = type === 'access'
165 ? OAUTH_LIFETIME.ACCESS_TOKEN
166 : OAUTH_LIFETIME.REFRESH_TOKEN
168 return new Date(Date.now() + lifetime * 1000)
171 async function buildToken () {
172 const [ accessToken, refreshToken ] = await Promise.all([ generateRandomToken(), generateRandomToken() ])
177 accessTokenExpiresAt: getTokenExpiresAt('access'),
178 refreshTokenExpiresAt: getTokenExpiresAt('refresh')