import { InvalidRequest, ServerError, InvalidGrant } from '../../model/error'; import { OAuth2, OAuth2Client, OAuth2Code, OAuth2TokenResponse, } from '../../model/model'; import { createS256, safeCompare } from '../../utils/crypto-tools'; /** * Issue an access token by authorization code * @param oauth2 - OAuth2 instance * @param client - OAuth2 client * @param providedCode - Authorization code * @returns Access token. */ export async function authorizationCode( oauth2: OAuth2, client: OAuth2Client, providedCode: string, codeVerifier?: string, ): Promise { const respObj: OAuth2TokenResponse = { token_type: 'bearer', }; let code: OAuth2Code | null = null; if (!providedCode) { throw new InvalidRequest( 'code is mandatory for authorization_code grant type' ); } try { code = await oauth2.model.code.fetchByCode(providedCode); } catch (err) { oauth2.logger.error(err); throw new ServerError('Failed to call code.fetchByCode function'); } if (code) { if ( oauth2.model.code.getClientId(code) !== oauth2.model.client.getId(client) ) { throw new InvalidGrant('Code was issued by another client'); } if (!oauth2.model.code.checkTTL(code)) { throw new InvalidGrant('Code has already expired'); } } else { throw new InvalidGrant('Code not found'); } oauth2.logger.debug('Code fetched', code); const scope = oauth2.model.code.getScope(code); const cleanScope = oauth2.model.client.transformScope(scope); const userId = oauth2.model.code.getUserId(code); const clientId = oauth2.model.code.getClientId(code); if (oauth2.model.code.getCodeChallenge) { const { challenge, method } = oauth2.model.code.getCodeChallenge(code); if (challenge && method) { if (!codeVerifier) { throw new InvalidGrant('Code verifier is required!'); } if (method === 'plain' && !safeCompare(challenge, codeVerifier)) { throw new InvalidGrant('Invalid code verifier!'); } if (method === 'S256' && !safeCompare(createS256(codeVerifier), challenge)) { throw new InvalidGrant('Invalid code verifier!'); } } oauth2.logger.debug('Code passed PCKE check'); } if (oauth2.model.refreshToken.invalidateOld) { try { await oauth2.model.refreshToken.removeByUserIdClientId(userId, clientId); } catch (err) { oauth2.logger.error(err); throw new ServerError( 'Failed to call refreshToken.removeByUserIdClientId function' ); } oauth2.logger.debug('Refresh token removed'); } if (!oauth2.model.client.checkGrantType(client, 'refresh_token')) { oauth2.logger.debug( 'Client does not allow grant type refresh_token, skip creation' ); } else { try { respObj.refresh_token = await oauth2.model.refreshToken.create( userId, clientId, scope ); } catch (err) { oauth2.logger.error(err); throw new ServerError('Failed to call refreshToken.create function'); } } try { respObj.access_token = await oauth2.model.accessToken.create( userId, clientId, scope, oauth2.model.accessToken.ttl ); } catch (err) { oauth2.logger.error(err); throw new ServerError('Failed to call accessToken.create function'); } respObj.expires_in = oauth2.model.accessToken.ttl; oauth2.logger.debug('Access token saved:', respObj.access_token); try { await oauth2.model.code.removeByCode(providedCode); } catch (err) { oauth2.logger.error(err); throw new ServerError('Failed to call code.removeByCode function'); } if (cleanScope.includes('openid') && oauth2.model.jwt) { const user = await oauth2.model.user.fetchById( oauth2.model.code.getUserId(code) ); try { respObj.id_token = await oauth2.model.jwt.issueIdToken( user, client, cleanScope, code.nonce ); } catch (err) { oauth2.logger.error(err); throw new ServerError('Failed to issue an ID token'); } } return respObj; }