oauth2-provider/src/controller/tokens/authorizationCode.ts

155 lines
4.0 KiB
TypeScript

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<OAuth2TokenResponse> {
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;
}