forked from panva/node-oidc-provider
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathauthorization_code.js
131 lines (106 loc) · 3.88 KB
/
authorization_code.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
const { get } = require('lodash');
const assert = require('assert');
const base64url = require('base64url');
const crypto = require('crypto');
const { InvalidGrantError } = require('../../helpers/errors');
const presence = require('../../helpers/validate_presence');
const instance = require('../../helpers/weak_cache');
module.exports.handler = function getAuthorizationCodeHandler(provider) {
const { features: { pkce, alwaysIssueRefresh }, audiences } = instance(provider).configuration();
return async function authorizationCodeResponse(ctx, next) {
presence(ctx, ['code', 'redirect_uri']);
const code = await provider.AuthorizationCode.find(ctx.oidc.params.code, {
ignoreExpiration: true,
});
if (!code) {
ctx.throw(new InvalidGrantError('authorization code not found'));
}
if (code.isExpired) {
ctx.throw(new InvalidGrantError('authorization code is expired'));
}
// PKCE check
if (pkce && (ctx.oidc.params.code_verifier || code.codeChallenge)) {
try {
let expected = ctx.oidc.params.code_verifier;
assert(expected);
if (code.codeChallengeMethod === 'S256') {
expected = base64url(crypto.createHash('sha256').update(expected).digest());
}
assert.equal(code.codeChallenge, expected);
} catch (err) {
ctx.throw(new InvalidGrantError('PKCE verification failed'));
}
}
try {
if (code.consumed) {
ctx.throw(new InvalidGrantError('authorization code already consumed'));
}
await code.consume();
} catch (err) {
await code.destroy();
throw err;
}
if (code.clientId !== ctx.oidc.client.clientId) {
ctx.throw(new InvalidGrantError('authorization code client mismatch'));
}
if (code.redirectUri !== ctx.oidc.params.redirect_uri) {
ctx.throw(new InvalidGrantError('authorization code redirect_uri mismatch'));
}
const account = await provider.Account.findById(ctx, code.accountId, code);
if (!account) {
ctx.throw(new InvalidGrantError('authorization code invalid (referenced account not found)'));
}
const { AccessToken, IdToken, RefreshToken } = provider;
const at = new AccessToken({
accountId: account.accountId,
claims: code.claims,
clientId: ctx.oidc.client.clientId,
grantId: code.grantId,
scope: code.scope,
sid: code.sid,
});
const accessToken = await at.save();
const { expiresIn } = AccessToken;
let refreshToken;
const grantPresent = ctx.oidc.client.grantTypes.includes('refresh_token');
if (grantPresent && (alwaysIssueRefresh || code.scope.split(' ').includes('offline_access'))) {
const rt = new RefreshToken({
accountId: account.accountId,
acr: code.acr,
amr: code.amr,
authTime: code.authTime,
claims: code.claims,
clientId: ctx.oidc.client.clientId,
grantId: code.grantId,
nonce: code.nonce,
scope: code.scope,
sid: code.sid,
});
refreshToken = await rt.save();
}
const token = new IdToken(Object.assign({}, await account.claims('id_token', code.scope), {
acr: code.acr,
amr: code.amr,
auth_time: code.authTime,
}), ctx.oidc.client.sectorIdentifier);
token.scope = code.scope;
token.mask = get(code.claims, 'id_token', {});
token.set('nonce', code.nonce);
token.set('at_hash', accessToken);
token.set('rt_hash', refreshToken);
token.set('sid', code.sid);
const idToken = await token.sign(ctx.oidc.client, {
audiences: await audiences(ctx, code.accountId, code),
});
ctx.body = {
access_token: accessToken,
expires_in: expiresIn,
id_token: idToken,
refresh_token: refreshToken,
scope: code.scope,
token_type: 'Bearer',
};
await next();
};
};
module.exports.parameters = ['code', 'redirect_uri', 'code_verifier'];