UNPKG

oidc-provider

Version:

OAuth 2.0 Authorization Server implementation for Node.js with OpenID Connect

203 lines (165 loc) 6.2 kB
import { InvalidTarget } from './errors.js'; import instance from './weak_cache.js'; import filterClaims from './filter_claims.js'; import combinedScope from './combined_scope.js'; import getCtxAccountClaims from './account_claims.js'; async function tokenHandler(ctx) { const { accountId } = ctx.oidc.session; const token = new ctx.oidc.provider.AccessToken({ accountId, client: ctx.oidc.client, grantId: ctx.oidc.session.grantIdFor(ctx.oidc.client.clientId), gty: 'implicit', sessionUid: ctx.oidc.session.uid, sid: ctx.oidc.session.sidFor(ctx.oidc.client.clientId), }); const { expiresWithSession, features: { resourceIndicators }, } = instance(ctx.oidc.provider).configuration; let { resource } = ctx.oidc.params; if (Array.isArray(resource)) { resource = await resourceIndicators.defaultResource(ctx, ctx.oidc.client, resource); } if (Array.isArray(resource)) { throw new InvalidTarget('only a single resource indicator value must be requested/resolved during Access Token Request'); } const { grant } = ctx.oidc; if (resource) { const resourceServer = ctx.oidc.resourceServers[resource]; if (!resourceServer) throw new InvalidTarget(); token.resourceServer = resourceServer; token.scope = grant.getResourceScopeFiltered(resource, ctx.oidc.requestParamScopes); } else { token.claims = ctx.oidc.claims; token.scope = grant.getOIDCScopeFiltered(ctx.oidc.requestParamOIDCScopes); } if (!token.resourceServer || token.resourceServer.accessTokenFormat === 'opaque') { if (await expiresWithSession(ctx, token)) { token.expiresWithSession = true; } else { ctx.oidc.session.authorizationFor(ctx.oidc.client.clientId).persistsLogout = true; } } ctx.oidc.entity('AccessToken', token); const result = { access_token: await token.save(), expires_in: token.expiration, token_type: token.tokenType, scope: token.scope, }; return result; } async function codeHandler(ctx) { const { expiresWithSession, features: { richAuthorizationRequests, }, } = instance(ctx.oidc.provider).configuration; const { grant } = ctx.oidc; const scopeSet = combinedScope(grant, ctx.oidc.requestParamScopes, ctx.oidc.resourceServers); const code = new ctx.oidc.provider.AuthorizationCode({ accountId: ctx.oidc.session.accountId, acr: ctx.oidc.acr, amr: ctx.oidc.amr, authTime: ctx.oidc.session.authTime(), claims: ctx.oidc.claims, client: ctx.oidc.client, codeChallenge: ctx.oidc.params.code_challenge, codeChallengeMethod: ctx.oidc.params.code_challenge_method, grantId: ctx.oidc.session.grantIdFor(ctx.oidc.client.clientId), nonce: ctx.oidc.params.nonce, redirectUri: ctx.oidc.params.redirect_uri, resource: Object.keys(ctx.oidc.resourceServers), scope: [...scopeSet].join(' '), sessionUid: ctx.oidc.session.uid, dpopJkt: ctx.oidc.params.dpop_jkt, }); if (ctx.oidc.entities.PushedAuthorizationRequest?.attestationJkt) { code.attestationJkt = ctx.oidc.entities.PushedAuthorizationRequest.attestationJkt; } if (richAuthorizationRequests.enabled) { code.rar = await richAuthorizationRequests.rarForAuthorizationCode(ctx); } if (Object.keys(code.claims).length === 0) { delete code.claims; } // eslint-disable-next-line default-case switch (code.resource.length) { case 0: delete code.resource; break; case 1: [code.resource] = code.resource; break; } if (await expiresWithSession(ctx, code)) { code.expiresWithSession = true; } else { ctx.oidc.session.authorizationFor(ctx.oidc.client.clientId).persistsLogout = true; } if (ctx.oidc.client.includeSid() || (ctx.oidc.claims.id_token && 'sid' in ctx.oidc.claims.id_token)) { code.sid = ctx.oidc.session.sidFor(ctx.oidc.client.clientId); } ctx.oidc.entity('AuthorizationCode', code); return { code: await code.save() }; } async function idTokenHandler(ctx) { const claims = filterClaims(ctx.oidc.claims, 'id_token', ctx.oidc.grant); const rejected = ctx.oidc.grant.getRejectedOIDCClaims(); const scope = ctx.oidc.grant.getOIDCScopeFiltered(ctx.oidc.requestParamScopes); const idToken = new ctx.oidc.provider.IdToken({ ...await getCtxAccountClaims(ctx, 'id_token', scope, claims, rejected), acr: ctx.oidc.acr, amr: ctx.oidc.amr, auth_time: ctx.oidc.session.authTime(), }, { ctx }); const { conformIdTokenClaims, features: { userinfo }, } = instance(ctx.oidc.provider).configuration; if (conformIdTokenClaims && userinfo.enabled && ctx.oidc.params.response_type !== 'id_token' && !ctx.oidc.params.resource) { idToken.scope = 'openid'; } else { idToken.scope = scope; } idToken.mask = claims; idToken.rejected = rejected; idToken.set('nonce', ctx.oidc.params.nonce); if (ctx.oidc.client.includeSid() || (ctx.oidc.claims.id_token && 'sid' in ctx.oidc.claims.id_token)) { idToken.set('sid', ctx.oidc.session.sidFor(ctx.oidc.client.clientId)); } return { id_token: idToken }; } /* * Resolves each requested response type to a single response object. If one of the hybrid * response types is used an appropriate _hash is also pushed on to the id_token. */ export default async function processResponseTypes(ctx) { const responses = ctx.oidc.params.response_type.split(' '); const response = Object.assign({}, ...await Promise.all(responses.map((responseType) => { switch (responseType) { case 'code': return codeHandler(ctx); case 'token': return tokenHandler(ctx); case 'id_token': return idTokenHandler(ctx); default: return {}; } }))); if ('id_token' in response) { if ('access_token' in response) { response.id_token.set('at_hash', response.access_token); } if ('code' in response) { response.id_token.set('c_hash', response.code); } if (ctx.oidc.params.state && ctx.oidc.isFapi('1.0 Final')) { response.id_token.set('s_hash', ctx.oidc.params.state); } response.id_token = await response.id_token.issue({ use: 'idtoken' }); } return response; }