@tachybase/plugin-auth-oidc 0.23.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/.turbo/turbo-build.log +12 -0
  2. package/README.md +11 -0
  3. package/README.zh-CN.md +38 -0
  4. package/client.d.ts +2 -0
  5. package/client.js +1 -0
  6. package/dist/client/OIDCButton.d.ts +9 -0
  7. package/dist/client/Options.d.ts +2 -0
  8. package/dist/client/index.d.ts +5 -0
  9. package/dist/client/index.js +3 -0
  10. package/dist/client/locale/index.d.ts +3 -0
  11. package/dist/constants.d.ts +3 -0
  12. package/dist/constants.js +34 -0
  13. package/dist/externalVersion.js +14 -0
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.js +39 -0
  16. package/dist/locale/en-US.json +40 -0
  17. package/dist/locale/es-ES.json +25 -0
  18. package/dist/locale/fr-FR.json +21 -0
  19. package/dist/locale/ko_KR.json +28 -0
  20. package/dist/locale/pt-BR.json +21 -0
  21. package/dist/locale/zh-CN.json +28 -0
  22. package/dist/node_modules/nanoid/.devcontainer.json +23 -0
  23. package/dist/node_modules/nanoid/LICENSE +20 -0
  24. package/dist/node_modules/nanoid/async/index.browser.cjs +69 -0
  25. package/dist/node_modules/nanoid/async/index.browser.js +69 -0
  26. package/dist/node_modules/nanoid/async/index.cjs +71 -0
  27. package/dist/node_modules/nanoid/async/index.d.ts +56 -0
  28. package/dist/node_modules/nanoid/async/index.js +71 -0
  29. package/dist/node_modules/nanoid/async/index.native.js +57 -0
  30. package/dist/node_modules/nanoid/async/package.json +12 -0
  31. package/dist/node_modules/nanoid/bin/nanoid.cjs +55 -0
  32. package/dist/node_modules/nanoid/index.browser.cjs +72 -0
  33. package/dist/node_modules/nanoid/index.browser.js +72 -0
  34. package/dist/node_modules/nanoid/index.cjs +1 -0
  35. package/dist/node_modules/nanoid/index.d.cts +91 -0
  36. package/dist/node_modules/nanoid/index.d.ts +91 -0
  37. package/dist/node_modules/nanoid/index.js +85 -0
  38. package/dist/node_modules/nanoid/nanoid.js +1 -0
  39. package/dist/node_modules/nanoid/non-secure/index.cjs +34 -0
  40. package/dist/node_modules/nanoid/non-secure/index.d.ts +33 -0
  41. package/dist/node_modules/nanoid/non-secure/index.js +34 -0
  42. package/dist/node_modules/nanoid/non-secure/package.json +6 -0
  43. package/dist/node_modules/nanoid/package.json +1 -0
  44. package/dist/node_modules/nanoid/url-alphabet/index.cjs +7 -0
  45. package/dist/node_modules/nanoid/url-alphabet/index.js +7 -0
  46. package/dist/node_modules/nanoid/url-alphabet/package.json +6 -0
  47. package/dist/node_modules/openid-client/lib/client.js +1884 -0
  48. package/dist/node_modules/openid-client/lib/device_flow_handle.js +125 -0
  49. package/dist/node_modules/openid-client/lib/errors.js +55 -0
  50. package/dist/node_modules/openid-client/lib/helpers/assert.js +24 -0
  51. package/dist/node_modules/openid-client/lib/helpers/base64url.js +13 -0
  52. package/dist/node_modules/openid-client/lib/helpers/client.js +208 -0
  53. package/dist/node_modules/openid-client/lib/helpers/consts.js +7 -0
  54. package/dist/node_modules/openid-client/lib/helpers/decode_jwt.js +27 -0
  55. package/dist/node_modules/openid-client/lib/helpers/deep_clone.js +1 -0
  56. package/dist/node_modules/openid-client/lib/helpers/defaults.js +27 -0
  57. package/dist/node_modules/openid-client/lib/helpers/generators.js +14 -0
  58. package/dist/node_modules/openid-client/lib/helpers/is_key_object.js +4 -0
  59. package/dist/node_modules/openid-client/lib/helpers/is_plain_object.js +1 -0
  60. package/dist/node_modules/openid-client/lib/helpers/issuer.js +111 -0
  61. package/dist/node_modules/openid-client/lib/helpers/keystore.js +298 -0
  62. package/dist/node_modules/openid-client/lib/helpers/merge.js +24 -0
  63. package/dist/node_modules/openid-client/lib/helpers/pick.js +9 -0
  64. package/dist/node_modules/openid-client/lib/helpers/process_response.js +71 -0
  65. package/dist/node_modules/openid-client/lib/helpers/request.js +200 -0
  66. package/dist/node_modules/openid-client/lib/helpers/unix_timestamp.js +1 -0
  67. package/dist/node_modules/openid-client/lib/helpers/weak_cache.js +1 -0
  68. package/dist/node_modules/openid-client/lib/helpers/webfinger_normalize.js +71 -0
  69. package/dist/node_modules/openid-client/lib/helpers/www_authenticate_parser.js +14 -0
  70. package/dist/node_modules/openid-client/lib/index.js +1 -0
  71. package/dist/node_modules/openid-client/lib/issuer.js +192 -0
  72. package/dist/node_modules/openid-client/lib/issuer_registry.js +3 -0
  73. package/dist/node_modules/openid-client/lib/passport_strategy.js +205 -0
  74. package/dist/node_modules/openid-client/lib/token_set.js +35 -0
  75. package/dist/node_modules/openid-client/package.json +1 -0
  76. package/dist/node_modules/openid-client/types/index.d.ts +623 -0
  77. package/dist/server/actions/getAuthUrl.d.ts +2 -0
  78. package/dist/server/actions/getAuthUrl.js +47 -0
  79. package/dist/server/actions/redirect.d.ts +2 -0
  80. package/dist/server/actions/redirect.js +55 -0
  81. package/dist/server/index.d.ts +1 -0
  82. package/dist/server/index.js +33 -0
  83. package/dist/server/migrations/20231007124508-update-autosignup.d.ts +6 -0
  84. package/dist/server/migrations/20231007124508-update-autosignup.js +52 -0
  85. package/dist/server/oidc-auth.d.ts +15 -0
  86. package/dist/server/oidc-auth.js +154 -0
  87. package/dist/server/plugin.d.ts +11 -0
  88. package/dist/server/plugin.js +83 -0
  89. package/dist/swagger/index.d.ts +143 -0
  90. package/dist/swagger/index.js +178 -0
  91. package/package.json +37 -0
  92. package/server.d.ts +2 -0
  93. package/server.js +1 -0
@@ -0,0 +1,125 @@
1
+ const { inspect } = require('util');
2
+
3
+ const { RPError, OPError } = require('./errors');
4
+ const now = require('./helpers/unix_timestamp');
5
+
6
+ class DeviceFlowHandle {
7
+ #aborted;
8
+ #client;
9
+ #clientAssertionPayload;
10
+ #DPoP;
11
+ #exchangeBody;
12
+ #expires_at;
13
+ #interval;
14
+ #maxAge;
15
+ #response;
16
+ constructor({ client, exchangeBody, clientAssertionPayload, response, maxAge, DPoP }) {
17
+ ['verification_uri', 'user_code', 'device_code'].forEach((prop) => {
18
+ if (typeof response[prop] !== 'string' || !response[prop]) {
19
+ throw new RPError(
20
+ `expected ${prop} string to be returned by Device Authorization Response, got %j`,
21
+ response[prop],
22
+ );
23
+ }
24
+ });
25
+
26
+ if (!Number.isSafeInteger(response.expires_in)) {
27
+ throw new RPError(
28
+ 'expected expires_in number to be returned by Device Authorization Response, got %j',
29
+ response.expires_in,
30
+ );
31
+ }
32
+
33
+ this.#expires_at = now() + response.expires_in;
34
+ this.#client = client;
35
+ this.#DPoP = DPoP;
36
+ this.#maxAge = maxAge;
37
+ this.#exchangeBody = exchangeBody;
38
+ this.#clientAssertionPayload = clientAssertionPayload;
39
+ this.#response = response;
40
+ this.#interval = response.interval * 1000 || 5000;
41
+ }
42
+
43
+ abort() {
44
+ this.#aborted = true;
45
+ }
46
+
47
+ async poll({ signal } = {}) {
48
+ if ((signal && signal.aborted) || this.#aborted) {
49
+ throw new RPError('polling aborted');
50
+ }
51
+
52
+ if (this.expired()) {
53
+ throw new RPError(
54
+ 'the device code %j has expired and the device authorization session has concluded',
55
+ this.device_code,
56
+ );
57
+ }
58
+
59
+ await new Promise((resolve) => setTimeout(resolve, this.#interval));
60
+
61
+ let tokenset;
62
+ try {
63
+ tokenset = await this.#client.grant(
64
+ {
65
+ ...this.#exchangeBody,
66
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
67
+ device_code: this.device_code,
68
+ },
69
+ { clientAssertionPayload: this.#clientAssertionPayload, DPoP: this.#DPoP },
70
+ );
71
+ } catch (err) {
72
+ switch (err instanceof OPError && err.error) {
73
+ case 'slow_down':
74
+ this.#interval += 5000;
75
+ case 'authorization_pending':
76
+ return this.poll({ signal });
77
+ default:
78
+ throw err;
79
+ }
80
+ }
81
+
82
+ if ('id_token' in tokenset) {
83
+ await this.#client.decryptIdToken(tokenset);
84
+ await this.#client.validateIdToken(tokenset, undefined, 'token', this.#maxAge);
85
+ }
86
+
87
+ return tokenset;
88
+ }
89
+
90
+ get device_code() {
91
+ return this.#response.device_code;
92
+ }
93
+
94
+ get user_code() {
95
+ return this.#response.user_code;
96
+ }
97
+
98
+ get verification_uri() {
99
+ return this.#response.verification_uri;
100
+ }
101
+
102
+ get verification_uri_complete() {
103
+ return this.#response.verification_uri_complete;
104
+ }
105
+
106
+ get expires_in() {
107
+ return Math.max.apply(null, [this.#expires_at - now(), 0]);
108
+ }
109
+
110
+ expired() {
111
+ return this.expires_in === 0;
112
+ }
113
+
114
+ /* istanbul ignore next */
115
+ [inspect.custom]() {
116
+ return `${this.constructor.name} ${inspect(this.#response, {
117
+ depth: Infinity,
118
+ colors: process.stdout.isTTY,
119
+ compact: false,
120
+ sorted: true,
121
+ })}`;
122
+ }
123
+ }
124
+
125
+ module.exports = DeviceFlowHandle;
@@ -0,0 +1,55 @@
1
+ const { format } = require('util');
2
+
3
+ class OPError extends Error {
4
+ constructor({ error_description, error, error_uri, session_state, state, scope }, response) {
5
+ super(!error_description ? error : `${error} (${error_description})`);
6
+
7
+ Object.assign(
8
+ this,
9
+ { error },
10
+ error_description && { error_description },
11
+ error_uri && { error_uri },
12
+ state && { state },
13
+ scope && { scope },
14
+ session_state && { session_state },
15
+ );
16
+
17
+ if (response) {
18
+ Object.defineProperty(this, 'response', {
19
+ value: response,
20
+ });
21
+ }
22
+
23
+ this.name = this.constructor.name;
24
+ Error.captureStackTrace(this, this.constructor);
25
+ }
26
+ }
27
+
28
+ class RPError extends Error {
29
+ constructor(...args) {
30
+ if (typeof args[0] === 'string') {
31
+ super(format(...args));
32
+ } else {
33
+ const { message, printf, response, ...rest } = args[0];
34
+ if (printf) {
35
+ super(format(...printf));
36
+ } else {
37
+ super(message);
38
+ }
39
+ Object.assign(this, rest);
40
+ if (response) {
41
+ Object.defineProperty(this, 'response', {
42
+ value: response,
43
+ });
44
+ }
45
+ }
46
+
47
+ this.name = this.constructor.name;
48
+ Error.captureStackTrace(this, this.constructor);
49
+ }
50
+ }
51
+
52
+ module.exports = {
53
+ OPError,
54
+ RPError,
55
+ };
@@ -0,0 +1,24 @@
1
+ function assertSigningAlgValuesSupport(endpoint, issuer, properties) {
2
+ if (!issuer[`${endpoint}_endpoint`]) return;
3
+
4
+ const eam = `${endpoint}_endpoint_auth_method`;
5
+ const easa = `${endpoint}_endpoint_auth_signing_alg`;
6
+ const easavs = `${endpoint}_endpoint_auth_signing_alg_values_supported`;
7
+
8
+ if (properties[eam] && properties[eam].endsWith('_jwt') && !properties[easa] && !issuer[easavs]) {
9
+ throw new TypeError(
10
+ `${easavs} must be configured on the issuer if ${easa} is not defined on a client`,
11
+ );
12
+ }
13
+ }
14
+
15
+ function assertIssuerConfiguration(issuer, endpoint) {
16
+ if (!issuer[endpoint]) {
17
+ throw new TypeError(`${endpoint} must be configured on the issuer`);
18
+ }
19
+ }
20
+
21
+ module.exports = {
22
+ assertSigningAlgValuesSupport,
23
+ assertIssuerConfiguration,
24
+ };
@@ -0,0 +1,13 @@
1
+ let encode;
2
+ if (Buffer.isEncoding('base64url')) {
3
+ encode = (input, encoding = 'utf8') => Buffer.from(input, encoding).toString('base64url');
4
+ } else {
5
+ const fromBase64 = (base64) => base64.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
6
+ encode = (input, encoding = 'utf8') =>
7
+ fromBase64(Buffer.from(input, encoding).toString('base64'));
8
+ }
9
+
10
+ const decode = (input) => Buffer.from(input, 'base64');
11
+
12
+ module.exports.decode = decode;
13
+ module.exports.encode = encode;
@@ -0,0 +1,208 @@
1
+ const jose = require('jose');
2
+
3
+ const { RPError } = require('../errors');
4
+
5
+ const { assertIssuerConfiguration } = require('./assert');
6
+ const { random } = require('./generators');
7
+ const now = require('./unix_timestamp');
8
+ const request = require('./request');
9
+ const { keystores } = require('./weak_cache');
10
+ const merge = require('./merge');
11
+
12
+ // TODO: in v6.x additionally encode the `- _ . ! ~ * ' ( )` characters
13
+ // https://github.com/panva/node-openid-client/commit/5a2ea80ef5e59ec0c03dbd97d82f551e24a9d348
14
+ const formUrlEncode = (value) => encodeURIComponent(value).replace(/%20/g, '+');
15
+
16
+ async function clientAssertion(endpoint, payload) {
17
+ let alg = this[`${endpoint}_endpoint_auth_signing_alg`];
18
+ if (!alg) {
19
+ assertIssuerConfiguration(
20
+ this.issuer,
21
+ `${endpoint}_endpoint_auth_signing_alg_values_supported`,
22
+ );
23
+ }
24
+
25
+ if (this[`${endpoint}_endpoint_auth_method`] === 'client_secret_jwt') {
26
+ if (!alg) {
27
+ const supported = this.issuer[`${endpoint}_endpoint_auth_signing_alg_values_supported`];
28
+ alg =
29
+ Array.isArray(supported) && supported.find((signAlg) => /^HS(?:256|384|512)/.test(signAlg));
30
+ }
31
+
32
+ if (!alg) {
33
+ throw new RPError(
34
+ `failed to determine a JWS Algorithm to use for ${
35
+ this[`${endpoint}_endpoint_auth_method`]
36
+ } Client Assertion`,
37
+ );
38
+ }
39
+
40
+ return new jose.CompactSign(Buffer.from(JSON.stringify(payload)))
41
+ .setProtectedHeader({ alg })
42
+ .sign(this.secretForAlg(alg));
43
+ }
44
+
45
+ const keystore = await keystores.get(this);
46
+
47
+ if (!keystore) {
48
+ throw new TypeError('no client jwks provided for signing a client assertion with');
49
+ }
50
+
51
+ if (!alg) {
52
+ const supported = this.issuer[`${endpoint}_endpoint_auth_signing_alg_values_supported`];
53
+ alg =
54
+ Array.isArray(supported) &&
55
+ supported.find((signAlg) => keystore.get({ alg: signAlg, use: 'sig' }));
56
+ }
57
+
58
+ if (!alg) {
59
+ throw new RPError(
60
+ `failed to determine a JWS Algorithm to use for ${
61
+ this[`${endpoint}_endpoint_auth_method`]
62
+ } Client Assertion`,
63
+ );
64
+ }
65
+
66
+ const key = keystore.get({ alg, use: 'sig' });
67
+ if (!key) {
68
+ throw new RPError(
69
+ `no key found in client jwks to sign a client assertion with using alg ${alg}`,
70
+ );
71
+ }
72
+
73
+ return new jose.CompactSign(Buffer.from(JSON.stringify(payload)))
74
+ .setProtectedHeader({ alg, kid: key.jwk && key.jwk.kid })
75
+ .sign(await key.keyObject(alg));
76
+ }
77
+
78
+ async function authFor(endpoint, { clientAssertionPayload } = {}) {
79
+ const authMethod = this[`${endpoint}_endpoint_auth_method`];
80
+ switch (authMethod) {
81
+ case 'self_signed_tls_client_auth':
82
+ case 'tls_client_auth':
83
+ case 'none':
84
+ return { form: { client_id: this.client_id } };
85
+ case 'client_secret_post':
86
+ if (typeof this.client_secret !== 'string') {
87
+ throw new TypeError(
88
+ 'client_secret_post client authentication method requires a client_secret',
89
+ );
90
+ }
91
+ return { form: { client_id: this.client_id, client_secret: this.client_secret } };
92
+ case 'private_key_jwt':
93
+ case 'client_secret_jwt': {
94
+ const timestamp = now();
95
+
96
+ const assertion = await clientAssertion.call(this, endpoint, {
97
+ iat: timestamp,
98
+ exp: timestamp + 60,
99
+ jti: random(),
100
+ iss: this.client_id,
101
+ sub: this.client_id,
102
+ aud: this.issuer.issuer,
103
+ ...clientAssertionPayload,
104
+ });
105
+
106
+ return {
107
+ form: {
108
+ client_id: this.client_id,
109
+ client_assertion: assertion,
110
+ client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
111
+ },
112
+ };
113
+ }
114
+ case 'client_secret_basic': {
115
+ // This is correct behaviour, see https://tools.ietf.org/html/rfc6749#section-2.3.1 and the
116
+ // related appendix. (also https://github.com/panva/node-openid-client/pull/91)
117
+ // > The client identifier is encoded using the
118
+ // > "application/x-www-form-urlencoded" encoding algorithm per
119
+ // > Appendix B, and the encoded value is used as the username; the client
120
+ // > password is encoded using the same algorithm and used as the
121
+ // > password.
122
+ if (typeof this.client_secret !== 'string') {
123
+ throw new TypeError(
124
+ 'client_secret_basic client authentication method requires a client_secret',
125
+ );
126
+ }
127
+ const encoded = `${formUrlEncode(this.client_id)}:${formUrlEncode(this.client_secret)}`;
128
+ const value = Buffer.from(encoded).toString('base64');
129
+ return { headers: { Authorization: `Basic ${value}` } };
130
+ }
131
+ default: {
132
+ throw new TypeError(`missing, or unsupported, ${endpoint}_endpoint_auth_method`);
133
+ }
134
+ }
135
+ }
136
+
137
+ function resolveResponseType() {
138
+ const { length, 0: value } = this.response_types;
139
+
140
+ if (length === 1) {
141
+ return value;
142
+ }
143
+
144
+ return undefined;
145
+ }
146
+
147
+ function resolveRedirectUri() {
148
+ const { length, 0: value } = this.redirect_uris || [];
149
+
150
+ if (length === 1) {
151
+ return value;
152
+ }
153
+
154
+ return undefined;
155
+ }
156
+
157
+ async function authenticatedPost(
158
+ endpoint,
159
+ opts,
160
+ { clientAssertionPayload, endpointAuthMethod = endpoint, DPoP } = {},
161
+ ) {
162
+ const auth = await authFor.call(this, endpointAuthMethod, { clientAssertionPayload });
163
+ const requestOpts = merge(opts, auth);
164
+
165
+ const mTLS =
166
+ this[`${endpointAuthMethod}_endpoint_auth_method`].includes('tls_client_auth') ||
167
+ (endpoint === 'token' && this.tls_client_certificate_bound_access_tokens);
168
+
169
+ let targetUrl;
170
+ if (mTLS && this.issuer.mtls_endpoint_aliases) {
171
+ targetUrl = this.issuer.mtls_endpoint_aliases[`${endpoint}_endpoint`];
172
+ }
173
+
174
+ targetUrl = targetUrl || this.issuer[`${endpoint}_endpoint`];
175
+
176
+ if ('form' in requestOpts) {
177
+ for (const [key, value] of Object.entries(requestOpts.form)) {
178
+ if (typeof value === 'undefined') {
179
+ delete requestOpts.form[key];
180
+ }
181
+ }
182
+ }
183
+
184
+ return request.call(
185
+ this,
186
+ {
187
+ ...requestOpts,
188
+ method: 'POST',
189
+ url: targetUrl,
190
+ headers: {
191
+ ...(endpoint !== 'revocation'
192
+ ? {
193
+ Accept: 'application/json',
194
+ }
195
+ : undefined),
196
+ ...requestOpts.headers,
197
+ },
198
+ },
199
+ { mTLS, DPoP },
200
+ );
201
+ }
202
+
203
+ module.exports = {
204
+ resolveResponseType,
205
+ resolveRedirectUri,
206
+ authFor,
207
+ authenticatedPost,
208
+ };
@@ -0,0 +1,7 @@
1
+ const HTTP_OPTIONS = Symbol();
2
+ const CLOCK_TOLERANCE = Symbol();
3
+
4
+ module.exports = {
5
+ CLOCK_TOLERANCE,
6
+ HTTP_OPTIONS,
7
+ };
@@ -0,0 +1,27 @@
1
+ const base64url = require('./base64url');
2
+
3
+ module.exports = (token) => {
4
+ if (typeof token !== 'string' || !token) {
5
+ throw new TypeError('JWT must be a string');
6
+ }
7
+
8
+ const { 0: header, 1: payload, 2: signature, length } = token.split('.');
9
+
10
+ if (length === 5) {
11
+ throw new TypeError('encrypted JWTs cannot be decoded');
12
+ }
13
+
14
+ if (length !== 3) {
15
+ throw new Error('JWTs must have three components');
16
+ }
17
+
18
+ try {
19
+ return {
20
+ header: JSON.parse(base64url.decode(header)),
21
+ payload: JSON.parse(base64url.decode(payload)),
22
+ signature,
23
+ };
24
+ } catch (err) {
25
+ throw new Error('JWT is malformed');
26
+ }
27
+ };
@@ -0,0 +1 @@
1
+ module.exports = globalThis.structuredClone || ((obj) => JSON.parse(JSON.stringify(obj)));
@@ -0,0 +1,27 @@
1
+ const isPlainObject = require('./is_plain_object');
2
+
3
+ function defaults(deep, target, ...sources) {
4
+ for (const source of sources) {
5
+ if (!isPlainObject(source)) {
6
+ continue;
7
+ }
8
+ for (const [key, value] of Object.entries(source)) {
9
+ /* istanbul ignore if */
10
+ if (key === '__proto__' || key === 'constructor') {
11
+ continue;
12
+ }
13
+ if (typeof target[key] === 'undefined' && typeof value !== 'undefined') {
14
+ target[key] = value;
15
+ }
16
+
17
+ if (deep && isPlainObject(target[key]) && isPlainObject(value)) {
18
+ defaults(true, target[key], value);
19
+ }
20
+ }
21
+ }
22
+
23
+ return target;
24
+ }
25
+
26
+ module.exports = defaults.bind(undefined, false);
27
+ module.exports.deep = defaults.bind(undefined, true);
@@ -0,0 +1,14 @@
1
+ const { createHash, randomBytes } = require('crypto');
2
+
3
+ const base64url = require('./base64url');
4
+
5
+ const random = (bytes = 32) => base64url.encode(randomBytes(bytes));
6
+
7
+ module.exports = {
8
+ random,
9
+ state: random,
10
+ nonce: random,
11
+ codeVerifier: random,
12
+ codeChallenge: (codeVerifier) =>
13
+ base64url.encode(createHash('sha256').update(codeVerifier).digest()),
14
+ };
@@ -0,0 +1,4 @@
1
+ const util = require('util');
2
+ const crypto = require('crypto');
3
+
4
+ module.exports = util.types.isKeyObject || ((obj) => obj && obj instanceof crypto.KeyObject);
@@ -0,0 +1 @@
1
+ module.exports = (a) => !!a && a.constructor === Object;
@@ -0,0 +1,111 @@
1
+ const objectHash = require('object-hash');
2
+ const LRU = require('lru-cache');
3
+
4
+ const { RPError } = require('../errors');
5
+
6
+ const { assertIssuerConfiguration } = require('./assert');
7
+ const KeyStore = require('./keystore');
8
+ const { keystores } = require('./weak_cache');
9
+ const processResponse = require('./process_response');
10
+ const request = require('./request');
11
+
12
+ const inFlight = new WeakMap();
13
+ const caches = new WeakMap();
14
+ const lrus = (ctx) => {
15
+ if (!caches.has(ctx)) {
16
+ caches.set(ctx, new LRU({ max: 100 }));
17
+ }
18
+ return caches.get(ctx);
19
+ };
20
+
21
+ async function getKeyStore(reload = false) {
22
+ assertIssuerConfiguration(this, 'jwks_uri');
23
+
24
+ const keystore = keystores.get(this);
25
+ const cache = lrus(this);
26
+
27
+ if (reload || !keystore) {
28
+ if (inFlight.has(this)) {
29
+ return inFlight.get(this);
30
+ }
31
+ cache.reset();
32
+ inFlight.set(
33
+ this,
34
+ (async () => {
35
+ const response = await request
36
+ .call(this, {
37
+ method: 'GET',
38
+ responseType: 'json',
39
+ url: this.jwks_uri,
40
+ headers: {
41
+ Accept: 'application/json, application/jwk-set+json',
42
+ },
43
+ })
44
+ .finally(() => {
45
+ inFlight.delete(this);
46
+ });
47
+ const jwks = processResponse(response);
48
+
49
+ const joseKeyStore = KeyStore.fromJWKS(jwks, { onlyPublic: true });
50
+ cache.set('throttle', true, 60 * 1000);
51
+ keystores.set(this, joseKeyStore);
52
+
53
+ return joseKeyStore;
54
+ })(),
55
+ );
56
+
57
+ return inFlight.get(this);
58
+ }
59
+
60
+ return keystore;
61
+ }
62
+
63
+ async function queryKeyStore({ kid, kty, alg, use }, { allowMulti = false } = {}) {
64
+ const cache = lrus(this);
65
+
66
+ const def = {
67
+ kid,
68
+ kty,
69
+ alg,
70
+ use,
71
+ };
72
+
73
+ const defHash = objectHash(def, {
74
+ algorithm: 'sha256',
75
+ ignoreUnknown: true,
76
+ unorderedArrays: true,
77
+ unorderedSets: true,
78
+ respectType: false,
79
+ });
80
+
81
+ // refresh keystore on every unknown key but also only upto once every minute
82
+ const freshJwksUri = cache.get(defHash) || cache.get('throttle');
83
+
84
+ const keystore = await getKeyStore.call(this, !freshJwksUri);
85
+ const keys = keystore.all(def);
86
+
87
+ delete def.use;
88
+ if (keys.length === 0) {
89
+ throw new RPError({
90
+ printf: ["no valid key found in issuer's jwks_uri for key parameters %j", def],
91
+ jwks: keystore,
92
+ });
93
+ }
94
+
95
+ if (!allowMulti && keys.length > 1 && !kid) {
96
+ throw new RPError({
97
+ printf: [
98
+ "multiple matching keys found in issuer's jwks_uri for key parameters %j, kid must be provided in this case",
99
+ def,
100
+ ],
101
+ jwks: keystore,
102
+ });
103
+ }
104
+
105
+ cache.set(defHash, true);
106
+
107
+ return keys;
108
+ }
109
+
110
+ module.exports.queryKeyStore = queryKeyStore;
111
+ module.exports.keystore = getKeyStore;