@logto/connector-apple 1.0.2 → 1.1.0
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.
- package/README.md +3 -3
- package/lib/constant.d.ts +3 -3
- package/lib/index.js +167 -478
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ You need to enroll [Apple Developer Program](https://developer.apple.com/program
|
|
|
30
30
|
|
|
31
31
|
You can do it via Xcode -> Project settings -> Signing & Capabilities, or visit [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list/bundleId).
|
|
32
32
|
|
|
33
|
-

|
|
33
|
+

|
|
34
34
|
|
|
35
35
|
See the "Enable an App ID" section in [Apple official docs](https://developer.apple.com/documentation/sign_in_with_apple/configuring_your_environment_for_sign_in_with_apple) for more info.
|
|
36
36
|
|
|
@@ -45,13 +45,13 @@ See the "Enable an App ID" section in [Apple official docs](https://developer.ap
|
|
|
45
45
|
|
|
46
46
|
Click the identifier you just created. Check "Sign in with Apple" on the details page and click "Configure".
|
|
47
47
|
|
|
48
|
-

|
|
48
|
+

|
|
49
49
|
|
|
50
50
|
In the opening modal, select the App ID you just enabled Sign in with Apple.
|
|
51
51
|
|
|
52
52
|
Enter the domain of your Logto instance without protocol and port, e.g., `your.logto.domain`; then enter the "Return URL" (i.e., Redirect URI), which is the Logto URL with `/callback/${connector_id}`, e.g., `https://your.logto.domain/callback/apple-universal`. You can get the randomly generated `connector_id` after creating Apple connector in Admin Console.
|
|
53
53
|
|
|
54
|
-

|
|
54
|
+

|
|
55
55
|
|
|
56
56
|
Click "Next" then "Done" to close the modal. Click "Continue" on the top-right corner, then click "Save" to save your configuration.
|
|
57
57
|
|
package/lib/constant.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ConnectorMetadata } from '@logto/connector-kit';
|
|
2
2
|
export declare const issuer = "https://appleid.apple.com";
|
|
3
|
-
export declare const authorizationEndpoint
|
|
4
|
-
export declare const accessTokenEndpoint
|
|
5
|
-
export declare const jwksUri
|
|
3
|
+
export declare const authorizationEndpoint = "https://appleid.apple.com/auth/authorize";
|
|
4
|
+
export declare const accessTokenEndpoint = "https://appleid.apple.com/auth/token";
|
|
5
|
+
export declare const jwksUri = "https://appleid.apple.com/auth/keys";
|
|
6
6
|
export declare const scope = "";
|
|
7
7
|
export declare const defaultMetadata: ConnectorMetadata;
|
|
8
8
|
export declare const defaultTimeout = 5000;
|
package/lib/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { ConnectorPlatform, ConnectorConfigFormItemType, ConnectorType, validateConfig, ConnectorError, ConnectorErrorCodes } from '@logto/connector-kit';
|
|
2
|
-
import * as crypto from 'crypto';
|
|
3
|
-
import {
|
|
2
|
+
import * as crypto from 'node:crypto';
|
|
3
|
+
import crypto__default, { KeyObject, createPrivateKey, createPublicKey, constants, createSecretKey } from 'node:crypto';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import
|
|
6
|
-
import * as
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import * as
|
|
10
|
-
import {
|
|
5
|
+
import * as http from 'node:http';
|
|
6
|
+
import * as https from 'node:https';
|
|
7
|
+
import { once } from 'node:events';
|
|
8
|
+
import { Buffer as Buffer$1 } from 'node:buffer';
|
|
9
|
+
import * as util from 'node:util';
|
|
10
|
+
import { promisify } from 'node:util';
|
|
11
11
|
|
|
12
12
|
// https://github.com/facebook/jest/issues/7547
|
|
13
13
|
const assert = (value, error) => {
|
|
@@ -20,22 +20,22 @@ const assert = (value, error) => {
|
|
|
20
20
|
|
|
21
21
|
const POOL_SIZE_MULTIPLIER = 128;
|
|
22
22
|
let pool, poolOffset;
|
|
23
|
-
|
|
23
|
+
function fillPool(bytes) {
|
|
24
24
|
if (!pool || pool.length < bytes) {
|
|
25
25
|
pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
|
|
26
|
-
|
|
26
|
+
crypto__default.getRandomValues(pool);
|
|
27
27
|
poolOffset = 0;
|
|
28
28
|
} else if (poolOffset + bytes > pool.length) {
|
|
29
|
-
|
|
29
|
+
crypto__default.getRandomValues(pool);
|
|
30
30
|
poolOffset = 0;
|
|
31
31
|
}
|
|
32
32
|
poolOffset += bytes;
|
|
33
|
-
}
|
|
34
|
-
|
|
33
|
+
}
|
|
34
|
+
function random(bytes) {
|
|
35
35
|
fillPool((bytes -= 0));
|
|
36
36
|
return pool.subarray(poolOffset - bytes, poolOffset)
|
|
37
|
-
}
|
|
38
|
-
|
|
37
|
+
}
|
|
38
|
+
function customRandom(alphabet, defaultSize, getRandom) {
|
|
39
39
|
let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1;
|
|
40
40
|
let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length);
|
|
41
41
|
return (size = defaultSize) => {
|
|
@@ -49,9 +49,10 @@ let customRandom = (alphabet, defaultSize, getRandom) => {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
customRandom(alphabet, size, random)
|
|
52
|
+
}
|
|
53
|
+
function customAlphabet(alphabet, size = 21) {
|
|
54
|
+
return customRandom(alphabet, size, random)
|
|
55
|
+
}
|
|
55
56
|
|
|
56
57
|
const lowercaseAlphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
|
|
57
58
|
const alphabet = `${lowercaseAlphabet}ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
|
|
@@ -96,141 +97,109 @@ function normalize(input) {
|
|
|
96
97
|
}
|
|
97
98
|
return encoded;
|
|
98
99
|
}
|
|
99
|
-
|
|
100
|
-
const decode = (input) => Buffer$1.from(normalize(input), 'base64');
|
|
100
|
+
const decode = (input) => new Uint8Array(Buffer$1.from(normalize(input), 'base64'));
|
|
101
101
|
|
|
102
102
|
class JOSEError extends Error {
|
|
103
|
+
static get code() {
|
|
104
|
+
return 'ERR_JOSE_GENERIC';
|
|
105
|
+
}
|
|
106
|
+
code = 'ERR_JOSE_GENERIC';
|
|
103
107
|
constructor(message) {
|
|
104
|
-
var _a;
|
|
105
108
|
super(message);
|
|
106
|
-
this.code = 'ERR_JOSE_GENERIC';
|
|
107
109
|
this.name = this.constructor.name;
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
static get code() {
|
|
111
|
-
return 'ERR_JOSE_GENERIC';
|
|
110
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
112
111
|
}
|
|
113
112
|
}
|
|
114
113
|
class JWTClaimValidationFailed extends JOSEError {
|
|
114
|
+
static get code() {
|
|
115
|
+
return 'ERR_JWT_CLAIM_VALIDATION_FAILED';
|
|
116
|
+
}
|
|
117
|
+
code = 'ERR_JWT_CLAIM_VALIDATION_FAILED';
|
|
118
|
+
claim;
|
|
119
|
+
reason;
|
|
115
120
|
constructor(message, claim = 'unspecified', reason = 'unspecified') {
|
|
116
121
|
super(message);
|
|
117
|
-
this.code = 'ERR_JWT_CLAIM_VALIDATION_FAILED';
|
|
118
122
|
this.claim = claim;
|
|
119
123
|
this.reason = reason;
|
|
120
124
|
}
|
|
121
|
-
static get code() {
|
|
122
|
-
return 'ERR_JWT_CLAIM_VALIDATION_FAILED';
|
|
123
|
-
}
|
|
124
125
|
}
|
|
125
126
|
class JWTExpired extends JOSEError {
|
|
127
|
+
static get code() {
|
|
128
|
+
return 'ERR_JWT_EXPIRED';
|
|
129
|
+
}
|
|
130
|
+
code = 'ERR_JWT_EXPIRED';
|
|
131
|
+
claim;
|
|
132
|
+
reason;
|
|
126
133
|
constructor(message, claim = 'unspecified', reason = 'unspecified') {
|
|
127
134
|
super(message);
|
|
128
|
-
this.code = 'ERR_JWT_EXPIRED';
|
|
129
135
|
this.claim = claim;
|
|
130
136
|
this.reason = reason;
|
|
131
137
|
}
|
|
132
|
-
static get code() {
|
|
133
|
-
return 'ERR_JWT_EXPIRED';
|
|
134
|
-
}
|
|
135
138
|
}
|
|
136
139
|
class JOSEAlgNotAllowed extends JOSEError {
|
|
137
|
-
constructor() {
|
|
138
|
-
super(...arguments);
|
|
139
|
-
this.code = 'ERR_JOSE_ALG_NOT_ALLOWED';
|
|
140
|
-
}
|
|
141
140
|
static get code() {
|
|
142
141
|
return 'ERR_JOSE_ALG_NOT_ALLOWED';
|
|
143
142
|
}
|
|
143
|
+
code = 'ERR_JOSE_ALG_NOT_ALLOWED';
|
|
144
144
|
}
|
|
145
145
|
class JOSENotSupported extends JOSEError {
|
|
146
|
-
constructor() {
|
|
147
|
-
super(...arguments);
|
|
148
|
-
this.code = 'ERR_JOSE_NOT_SUPPORTED';
|
|
149
|
-
}
|
|
150
146
|
static get code() {
|
|
151
147
|
return 'ERR_JOSE_NOT_SUPPORTED';
|
|
152
148
|
}
|
|
149
|
+
code = 'ERR_JOSE_NOT_SUPPORTED';
|
|
153
150
|
}
|
|
154
151
|
class JWSInvalid extends JOSEError {
|
|
155
|
-
constructor() {
|
|
156
|
-
super(...arguments);
|
|
157
|
-
this.code = 'ERR_JWS_INVALID';
|
|
158
|
-
}
|
|
159
152
|
static get code() {
|
|
160
153
|
return 'ERR_JWS_INVALID';
|
|
161
154
|
}
|
|
155
|
+
code = 'ERR_JWS_INVALID';
|
|
162
156
|
}
|
|
163
157
|
class JWTInvalid extends JOSEError {
|
|
164
|
-
constructor() {
|
|
165
|
-
super(...arguments);
|
|
166
|
-
this.code = 'ERR_JWT_INVALID';
|
|
167
|
-
}
|
|
168
158
|
static get code() {
|
|
169
159
|
return 'ERR_JWT_INVALID';
|
|
170
160
|
}
|
|
161
|
+
code = 'ERR_JWT_INVALID';
|
|
171
162
|
}
|
|
172
163
|
class JWKSInvalid extends JOSEError {
|
|
173
|
-
constructor() {
|
|
174
|
-
super(...arguments);
|
|
175
|
-
this.code = 'ERR_JWKS_INVALID';
|
|
176
|
-
}
|
|
177
164
|
static get code() {
|
|
178
165
|
return 'ERR_JWKS_INVALID';
|
|
179
166
|
}
|
|
167
|
+
code = 'ERR_JWKS_INVALID';
|
|
180
168
|
}
|
|
181
169
|
class JWKSNoMatchingKey extends JOSEError {
|
|
182
|
-
constructor() {
|
|
183
|
-
super(...arguments);
|
|
184
|
-
this.code = 'ERR_JWKS_NO_MATCHING_KEY';
|
|
185
|
-
this.message = 'no applicable key found in the JSON Web Key Set';
|
|
186
|
-
}
|
|
187
170
|
static get code() {
|
|
188
171
|
return 'ERR_JWKS_NO_MATCHING_KEY';
|
|
189
172
|
}
|
|
173
|
+
code = 'ERR_JWKS_NO_MATCHING_KEY';
|
|
174
|
+
message = 'no applicable key found in the JSON Web Key Set';
|
|
190
175
|
}
|
|
191
176
|
class JWKSMultipleMatchingKeys extends JOSEError {
|
|
192
|
-
|
|
193
|
-
super(...arguments);
|
|
194
|
-
this.code = 'ERR_JWKS_MULTIPLE_MATCHING_KEYS';
|
|
195
|
-
this.message = 'multiple matching keys found in the JSON Web Key Set';
|
|
196
|
-
}
|
|
177
|
+
[Symbol.asyncIterator];
|
|
197
178
|
static get code() {
|
|
198
179
|
return 'ERR_JWKS_MULTIPLE_MATCHING_KEYS';
|
|
199
180
|
}
|
|
181
|
+
code = 'ERR_JWKS_MULTIPLE_MATCHING_KEYS';
|
|
182
|
+
message = 'multiple matching keys found in the JSON Web Key Set';
|
|
200
183
|
}
|
|
201
184
|
class JWKSTimeout extends JOSEError {
|
|
202
|
-
constructor() {
|
|
203
|
-
super(...arguments);
|
|
204
|
-
this.code = 'ERR_JWKS_TIMEOUT';
|
|
205
|
-
this.message = 'request timed out';
|
|
206
|
-
}
|
|
207
185
|
static get code() {
|
|
208
186
|
return 'ERR_JWKS_TIMEOUT';
|
|
209
187
|
}
|
|
188
|
+
code = 'ERR_JWKS_TIMEOUT';
|
|
189
|
+
message = 'request timed out';
|
|
210
190
|
}
|
|
211
191
|
class JWSSignatureVerificationFailed extends JOSEError {
|
|
212
|
-
constructor() {
|
|
213
|
-
super(...arguments);
|
|
214
|
-
this.code = 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED';
|
|
215
|
-
this.message = 'signature verification failed';
|
|
216
|
-
}
|
|
217
192
|
static get code() {
|
|
218
193
|
return 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED';
|
|
219
194
|
}
|
|
195
|
+
code = 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED';
|
|
196
|
+
message = 'signature verification failed';
|
|
220
197
|
}
|
|
221
198
|
|
|
222
|
-
var isKeyObject = util.types.isKeyObject
|
|
223
|
-
? (obj) => util.types.isKeyObject(obj)
|
|
224
|
-
: (obj) => obj != null && obj instanceof KeyObject;
|
|
199
|
+
var isKeyObject = (obj) => util.types.isKeyObject(obj);
|
|
225
200
|
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
:
|
|
229
|
-
(key) => false;
|
|
230
|
-
|
|
231
|
-
function isCloudflareWorkers() {
|
|
232
|
-
return false;
|
|
233
|
-
}
|
|
201
|
+
const webcrypto = crypto.webcrypto;
|
|
202
|
+
const isCryptoKey = (key) => util.types.isCryptoKey(key);
|
|
234
203
|
|
|
235
204
|
function unusable(name, prop = 'algorithm.name') {
|
|
236
205
|
return new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);
|
|
@@ -304,11 +273,6 @@ function checkSigCryptoKey(key, alg, ...usages) {
|
|
|
304
273
|
throw unusable(`SHA-${expected}`, 'algorithm.hash');
|
|
305
274
|
break;
|
|
306
275
|
}
|
|
307
|
-
case isCloudflareWorkers() : {
|
|
308
|
-
if (!isAlgorithm(key.algorithm, 'NODE-ED25519'))
|
|
309
|
-
throw unusable('NODE-ED25519');
|
|
310
|
-
break;
|
|
311
|
-
}
|
|
312
276
|
case 'EdDSA': {
|
|
313
277
|
if (key.algorithm.name !== 'Ed25519' && key.algorithm.name !== 'Ed448') {
|
|
314
278
|
throw unusable('Ed25519 or Ed448');
|
|
@@ -365,7 +329,7 @@ function withAlg(alg, actual, ...types) {
|
|
|
365
329
|
|
|
366
330
|
var isKeyLike = (key) => isKeyObject(key) || isCryptoKey(key);
|
|
367
331
|
const types = ['KeyObject'];
|
|
368
|
-
if (
|
|
332
|
+
if (globalThis.CryptoKey || webcrypto?.CryptoKey) {
|
|
369
333
|
types.push('CryptoKey');
|
|
370
334
|
}
|
|
371
335
|
|
|
@@ -408,11 +372,6 @@ function isObject(input) {
|
|
|
408
372
|
return Object.getPrototypeOf(input) === proto;
|
|
409
373
|
}
|
|
410
374
|
|
|
411
|
-
const p256 = Buffer$1.from([42, 134, 72, 206, 61, 3, 1, 7]);
|
|
412
|
-
const p384 = Buffer$1.from([43, 129, 4, 0, 34]);
|
|
413
|
-
const p521 = Buffer$1.from([43, 129, 4, 0, 35]);
|
|
414
|
-
const secp256k1 = Buffer$1.from([43, 129, 4, 0, 10]);
|
|
415
|
-
const weakMap$1 = new WeakMap();
|
|
416
375
|
const namedCurveToJOSE = (namedCurve) => {
|
|
417
376
|
switch (namedCurve) {
|
|
418
377
|
case 'prime256v1':
|
|
@@ -428,7 +387,6 @@ const namedCurveToJOSE = (namedCurve) => {
|
|
|
428
387
|
}
|
|
429
388
|
};
|
|
430
389
|
const getNamedCurve = (kee, raw) => {
|
|
431
|
-
var _a;
|
|
432
390
|
let key;
|
|
433
391
|
if (isCryptoKey(kee)) {
|
|
434
392
|
key = KeyObject.from(kee);
|
|
@@ -450,315 +408,38 @@ const getNamedCurve = (kee, raw) => {
|
|
|
450
408
|
case 'x448':
|
|
451
409
|
return `X${key.asymmetricKeyType.slice(1)}`;
|
|
452
410
|
case 'ec': {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
}
|
|
456
|
-
let namedCurve = (_a = key.asymmetricKeyDetails) === null || _a === void 0 ? void 0 : _a.namedCurve;
|
|
457
|
-
if (!namedCurve && key.type === 'private') {
|
|
458
|
-
namedCurve = getNamedCurve(createPublicKey(key), true);
|
|
459
|
-
}
|
|
460
|
-
else if (!namedCurve) {
|
|
461
|
-
const buf = key.export({ format: 'der', type: 'spki' });
|
|
462
|
-
const i = buf[1] < 128 ? 14 : 15;
|
|
463
|
-
const len = buf[i];
|
|
464
|
-
const curveOid = buf.slice(i + 1, i + 1 + len);
|
|
465
|
-
if (curveOid.equals(p256)) {
|
|
466
|
-
namedCurve = 'prime256v1';
|
|
467
|
-
}
|
|
468
|
-
else if (curveOid.equals(p384)) {
|
|
469
|
-
namedCurve = 'secp384r1';
|
|
470
|
-
}
|
|
471
|
-
else if (curveOid.equals(p521)) {
|
|
472
|
-
namedCurve = 'secp521r1';
|
|
473
|
-
}
|
|
474
|
-
else if (curveOid.equals(secp256k1)) {
|
|
475
|
-
namedCurve = 'secp256k1';
|
|
476
|
-
}
|
|
477
|
-
else {
|
|
478
|
-
throw new JOSENotSupported('Unsupported key curve for this operation');
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
if (raw)
|
|
411
|
+
let namedCurve = key.asymmetricKeyDetails.namedCurve;
|
|
412
|
+
if (raw) {
|
|
482
413
|
return namedCurve;
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
return curve;
|
|
414
|
+
}
|
|
415
|
+
return namedCurveToJOSE(namedCurve);
|
|
486
416
|
}
|
|
487
417
|
default:
|
|
488
418
|
throw new TypeError('Invalid asymmetric key type for this operation');
|
|
489
419
|
}
|
|
490
420
|
};
|
|
491
|
-
function setCurve(keyObject, curve) {
|
|
492
|
-
weakMap$1.set(keyObject, curve);
|
|
493
|
-
}
|
|
494
421
|
|
|
495
|
-
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
if ((len & 0x80) === 0) {
|
|
499
|
-
if (index === 0) {
|
|
500
|
-
return len;
|
|
501
|
-
}
|
|
502
|
-
return getLength(buf.subarray(2 + len), index - 1);
|
|
503
|
-
}
|
|
504
|
-
const num = len & 0x7f;
|
|
505
|
-
len = 0;
|
|
506
|
-
for (let i = 0; i < num; i++) {
|
|
507
|
-
len <<= 8;
|
|
508
|
-
const j = buf.readUInt8(2 + i);
|
|
509
|
-
len |= j;
|
|
510
|
-
}
|
|
511
|
-
if (index === 0) {
|
|
512
|
-
return len;
|
|
513
|
-
}
|
|
514
|
-
return getLength(buf.subarray(2 + len), index - 1);
|
|
515
|
-
};
|
|
516
|
-
const getLengthOfSeqIndex = (sequence, index) => {
|
|
517
|
-
const len = sequence.readUInt8(1);
|
|
518
|
-
if ((len & 0x80) === 0) {
|
|
519
|
-
return getLength(sequence.subarray(2), index);
|
|
520
|
-
}
|
|
521
|
-
const num = len & 0x7f;
|
|
522
|
-
return getLength(sequence.subarray(2 + num), index);
|
|
523
|
-
};
|
|
524
|
-
const getModulusLength = (key) => {
|
|
525
|
-
var _a, _b;
|
|
526
|
-
if (weakMap.has(key)) {
|
|
527
|
-
return weakMap.get(key);
|
|
528
|
-
}
|
|
529
|
-
const modulusLength = (_b = (_a = key.asymmetricKeyDetails) === null || _a === void 0 ? void 0 : _a.modulusLength) !== null && _b !== void 0 ? _b : (getLengthOfSeqIndex(key.export({ format: 'der', type: 'pkcs1' }), key.type === 'private' ? 1 : 0) -
|
|
530
|
-
1) <<
|
|
531
|
-
3;
|
|
532
|
-
weakMap.set(key, modulusLength);
|
|
533
|
-
return modulusLength;
|
|
534
|
-
};
|
|
535
|
-
const setModulusLength = (keyObject, modulusLength) => {
|
|
536
|
-
weakMap.set(keyObject, modulusLength);
|
|
537
|
-
};
|
|
538
|
-
var checkModulusLength = (key, alg) => {
|
|
539
|
-
if (getModulusLength(key) < 2048) {
|
|
422
|
+
var checkKeyLength = (key, alg) => {
|
|
423
|
+
const { modulusLength } = key.asymmetricKeyDetails;
|
|
424
|
+
if (typeof modulusLength !== 'number' || modulusLength < 2048) {
|
|
540
425
|
throw new TypeError(`${alg} requires key modulusLength to be 2048 bits or larger`);
|
|
541
426
|
}
|
|
542
427
|
};
|
|
543
428
|
|
|
544
|
-
const tagInteger = 0x02;
|
|
545
|
-
const tagBitStr = 0x03;
|
|
546
|
-
const tagOctStr = 0x04;
|
|
547
|
-
const tagSequence = 0x30;
|
|
548
|
-
const bZero = Buffer$1.from([0x00]);
|
|
549
|
-
const bTagInteger = Buffer$1.from([tagInteger]);
|
|
550
|
-
const bTagBitStr = Buffer$1.from([tagBitStr]);
|
|
551
|
-
const bTagSequence = Buffer$1.from([tagSequence]);
|
|
552
|
-
const bTagOctStr = Buffer$1.from([tagOctStr]);
|
|
553
|
-
const encodeLength = (len) => {
|
|
554
|
-
if (len < 128)
|
|
555
|
-
return Buffer$1.from([len]);
|
|
556
|
-
const buffer = Buffer$1.alloc(5);
|
|
557
|
-
buffer.writeUInt32BE(len, 1);
|
|
558
|
-
let offset = 1;
|
|
559
|
-
while (buffer[offset] === 0)
|
|
560
|
-
offset++;
|
|
561
|
-
buffer[offset - 1] = 0x80 | (5 - offset);
|
|
562
|
-
return buffer.slice(offset - 1);
|
|
563
|
-
};
|
|
564
|
-
const oids = new Map([
|
|
565
|
-
['P-256', Buffer$1.from('06 08 2A 86 48 CE 3D 03 01 07'.replace(/ /g, ''), 'hex')],
|
|
566
|
-
['secp256k1', Buffer$1.from('06 05 2B 81 04 00 0A'.replace(/ /g, ''), 'hex')],
|
|
567
|
-
['P-384', Buffer$1.from('06 05 2B 81 04 00 22'.replace(/ /g, ''), 'hex')],
|
|
568
|
-
['P-521', Buffer$1.from('06 05 2B 81 04 00 23'.replace(/ /g, ''), 'hex')],
|
|
569
|
-
['ecPublicKey', Buffer$1.from('06 07 2A 86 48 CE 3D 02 01'.replace(/ /g, ''), 'hex')],
|
|
570
|
-
['X25519', Buffer$1.from('06 03 2B 65 6E'.replace(/ /g, ''), 'hex')],
|
|
571
|
-
['X448', Buffer$1.from('06 03 2B 65 6F'.replace(/ /g, ''), 'hex')],
|
|
572
|
-
['Ed25519', Buffer$1.from('06 03 2B 65 70'.replace(/ /g, ''), 'hex')],
|
|
573
|
-
['Ed448', Buffer$1.from('06 03 2B 65 71'.replace(/ /g, ''), 'hex')],
|
|
574
|
-
]);
|
|
575
|
-
class DumbAsn1Encoder {
|
|
576
|
-
constructor() {
|
|
577
|
-
this.length = 0;
|
|
578
|
-
this.elements = [];
|
|
579
|
-
}
|
|
580
|
-
oidFor(oid) {
|
|
581
|
-
const bOid = oids.get(oid);
|
|
582
|
-
if (!bOid) {
|
|
583
|
-
throw new JOSENotSupported('Invalid or unsupported OID');
|
|
584
|
-
}
|
|
585
|
-
this.elements.push(bOid);
|
|
586
|
-
this.length += bOid.length;
|
|
587
|
-
}
|
|
588
|
-
zero() {
|
|
589
|
-
this.elements.push(bTagInteger, Buffer$1.from([0x01]), bZero);
|
|
590
|
-
this.length += 3;
|
|
591
|
-
}
|
|
592
|
-
one() {
|
|
593
|
-
this.elements.push(bTagInteger, Buffer$1.from([0x01]), Buffer$1.from([0x01]));
|
|
594
|
-
this.length += 3;
|
|
595
|
-
}
|
|
596
|
-
unsignedInteger(integer) {
|
|
597
|
-
if (integer[0] & 0x80) {
|
|
598
|
-
const len = encodeLength(integer.length + 1);
|
|
599
|
-
this.elements.push(bTagInteger, len, bZero, integer);
|
|
600
|
-
this.length += 2 + len.length + integer.length;
|
|
601
|
-
}
|
|
602
|
-
else {
|
|
603
|
-
let i = 0;
|
|
604
|
-
while (integer[i] === 0 && (integer[i + 1] & 0x80) === 0)
|
|
605
|
-
i++;
|
|
606
|
-
const len = encodeLength(integer.length - i);
|
|
607
|
-
this.elements.push(bTagInteger, encodeLength(integer.length - i), integer.slice(i));
|
|
608
|
-
this.length += 1 + len.length + integer.length - i;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
octStr(octStr) {
|
|
612
|
-
const len = encodeLength(octStr.length);
|
|
613
|
-
this.elements.push(bTagOctStr, encodeLength(octStr.length), octStr);
|
|
614
|
-
this.length += 1 + len.length + octStr.length;
|
|
615
|
-
}
|
|
616
|
-
bitStr(bitS) {
|
|
617
|
-
const len = encodeLength(bitS.length + 1);
|
|
618
|
-
this.elements.push(bTagBitStr, encodeLength(bitS.length + 1), bZero, bitS);
|
|
619
|
-
this.length += 1 + len.length + bitS.length + 1;
|
|
620
|
-
}
|
|
621
|
-
add(seq) {
|
|
622
|
-
this.elements.push(seq);
|
|
623
|
-
this.length += seq.length;
|
|
624
|
-
}
|
|
625
|
-
end(tag = bTagSequence) {
|
|
626
|
-
const len = encodeLength(this.length);
|
|
627
|
-
return Buffer$1.concat([tag, len, ...this.elements], 1 + len.length + this.length);
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
const [major$2, minor$2] = process.version
|
|
632
|
-
.slice(1)
|
|
633
|
-
.split('.')
|
|
634
|
-
.map((str) => parseInt(str, 10));
|
|
635
|
-
const jwkImportSupported = major$2 >= 16 || (major$2 === 15 && minor$2 >= 12);
|
|
636
429
|
const parse = (jwk) => {
|
|
637
|
-
|
|
638
|
-
return jwk.d
|
|
639
|
-
? createPrivateKey({ format: 'jwk', key: jwk })
|
|
640
|
-
: createPublicKey({ format: 'jwk', key: jwk });
|
|
641
|
-
}
|
|
642
|
-
switch (jwk.kty) {
|
|
643
|
-
case 'oct': {
|
|
644
|
-
return createSecretKey(decode(jwk.k));
|
|
645
|
-
}
|
|
646
|
-
case 'RSA': {
|
|
647
|
-
const enc = new DumbAsn1Encoder();
|
|
648
|
-
const isPrivate = jwk.d !== undefined;
|
|
649
|
-
const modulus = Buffer$1.from(jwk.n, 'base64');
|
|
650
|
-
const exponent = Buffer$1.from(jwk.e, 'base64');
|
|
651
|
-
if (isPrivate) {
|
|
652
|
-
enc.zero();
|
|
653
|
-
enc.unsignedInteger(modulus);
|
|
654
|
-
enc.unsignedInteger(exponent);
|
|
655
|
-
enc.unsignedInteger(Buffer$1.from(jwk.d, 'base64'));
|
|
656
|
-
enc.unsignedInteger(Buffer$1.from(jwk.p, 'base64'));
|
|
657
|
-
enc.unsignedInteger(Buffer$1.from(jwk.q, 'base64'));
|
|
658
|
-
enc.unsignedInteger(Buffer$1.from(jwk.dp, 'base64'));
|
|
659
|
-
enc.unsignedInteger(Buffer$1.from(jwk.dq, 'base64'));
|
|
660
|
-
enc.unsignedInteger(Buffer$1.from(jwk.qi, 'base64'));
|
|
661
|
-
}
|
|
662
|
-
else {
|
|
663
|
-
enc.unsignedInteger(modulus);
|
|
664
|
-
enc.unsignedInteger(exponent);
|
|
665
|
-
}
|
|
666
|
-
const der = enc.end();
|
|
667
|
-
const createInput = {
|
|
668
|
-
key: der,
|
|
669
|
-
format: 'der',
|
|
670
|
-
type: 'pkcs1',
|
|
671
|
-
};
|
|
672
|
-
const keyObject = isPrivate ? createPrivateKey(createInput) : createPublicKey(createInput);
|
|
673
|
-
setModulusLength(keyObject, modulus.length << 3);
|
|
674
|
-
return keyObject;
|
|
675
|
-
}
|
|
676
|
-
case 'EC': {
|
|
677
|
-
const enc = new DumbAsn1Encoder();
|
|
678
|
-
const isPrivate = jwk.d !== undefined;
|
|
679
|
-
const pub = Buffer$1.concat([
|
|
680
|
-
Buffer$1.alloc(1, 4),
|
|
681
|
-
Buffer$1.from(jwk.x, 'base64'),
|
|
682
|
-
Buffer$1.from(jwk.y, 'base64'),
|
|
683
|
-
]);
|
|
684
|
-
if (isPrivate) {
|
|
685
|
-
enc.zero();
|
|
686
|
-
const enc$1 = new DumbAsn1Encoder();
|
|
687
|
-
enc$1.oidFor('ecPublicKey');
|
|
688
|
-
enc$1.oidFor(jwk.crv);
|
|
689
|
-
enc.add(enc$1.end());
|
|
690
|
-
const enc$2 = new DumbAsn1Encoder();
|
|
691
|
-
enc$2.one();
|
|
692
|
-
enc$2.octStr(Buffer$1.from(jwk.d, 'base64'));
|
|
693
|
-
const enc$3 = new DumbAsn1Encoder();
|
|
694
|
-
enc$3.bitStr(pub);
|
|
695
|
-
const f2 = enc$3.end(Buffer$1.from([0xa1]));
|
|
696
|
-
enc$2.add(f2);
|
|
697
|
-
const f = enc$2.end();
|
|
698
|
-
const enc$4 = new DumbAsn1Encoder();
|
|
699
|
-
enc$4.add(f);
|
|
700
|
-
const f3 = enc$4.end(Buffer$1.from([0x04]));
|
|
701
|
-
enc.add(f3);
|
|
702
|
-
const der = enc.end();
|
|
703
|
-
const keyObject = createPrivateKey({ key: der, format: 'der', type: 'pkcs8' });
|
|
704
|
-
setCurve(keyObject, jwk.crv);
|
|
705
|
-
return keyObject;
|
|
706
|
-
}
|
|
707
|
-
const enc$1 = new DumbAsn1Encoder();
|
|
708
|
-
enc$1.oidFor('ecPublicKey');
|
|
709
|
-
enc$1.oidFor(jwk.crv);
|
|
710
|
-
enc.add(enc$1.end());
|
|
711
|
-
enc.bitStr(pub);
|
|
712
|
-
const der = enc.end();
|
|
713
|
-
const keyObject = createPublicKey({ key: der, format: 'der', type: 'spki' });
|
|
714
|
-
setCurve(keyObject, jwk.crv);
|
|
715
|
-
return keyObject;
|
|
716
|
-
}
|
|
717
|
-
case 'OKP': {
|
|
718
|
-
const enc = new DumbAsn1Encoder();
|
|
719
|
-
const isPrivate = jwk.d !== undefined;
|
|
720
|
-
if (isPrivate) {
|
|
721
|
-
enc.zero();
|
|
722
|
-
const enc$1 = new DumbAsn1Encoder();
|
|
723
|
-
enc$1.oidFor(jwk.crv);
|
|
724
|
-
enc.add(enc$1.end());
|
|
725
|
-
const enc$2 = new DumbAsn1Encoder();
|
|
726
|
-
enc$2.octStr(Buffer$1.from(jwk.d, 'base64'));
|
|
727
|
-
const f = enc$2.end(Buffer$1.from([0x04]));
|
|
728
|
-
enc.add(f);
|
|
729
|
-
const der = enc.end();
|
|
730
|
-
return createPrivateKey({ key: der, format: 'der', type: 'pkcs8' });
|
|
731
|
-
}
|
|
732
|
-
const enc$1 = new DumbAsn1Encoder();
|
|
733
|
-
enc$1.oidFor(jwk.crv);
|
|
734
|
-
enc.add(enc$1.end());
|
|
735
|
-
enc.bitStr(Buffer$1.from(jwk.x, 'base64'));
|
|
736
|
-
const der = enc.end();
|
|
737
|
-
return createPublicKey({ key: der, format: 'der', type: 'spki' });
|
|
738
|
-
}
|
|
739
|
-
default:
|
|
740
|
-
throw new JOSENotSupported('Invalid or unsupported JWK "kty" (Key Type) Parameter value');
|
|
741
|
-
}
|
|
430
|
+
return (jwk.d ? createPrivateKey : createPublicKey)({ format: 'jwk', key: jwk });
|
|
742
431
|
};
|
|
743
432
|
|
|
744
|
-
async function importJWK(jwk, alg
|
|
745
|
-
var _a;
|
|
433
|
+
async function importJWK(jwk, alg) {
|
|
746
434
|
if (!isObject(jwk)) {
|
|
747
435
|
throw new TypeError('JWK must be an object');
|
|
748
436
|
}
|
|
749
|
-
alg
|
|
750
|
-
if (typeof alg !== 'string' || !alg) {
|
|
751
|
-
throw new TypeError('"alg" argument is required when "jwk.alg" is not present');
|
|
752
|
-
}
|
|
437
|
+
alg ||= jwk.alg;
|
|
753
438
|
switch (jwk.kty) {
|
|
754
439
|
case 'oct':
|
|
755
440
|
if (typeof jwk.k !== 'string' || !jwk.k) {
|
|
756
441
|
throw new TypeError('missing "k" (Key Value) Parameter value');
|
|
757
442
|
}
|
|
758
|
-
octAsKeyObject !== null && octAsKeyObject !== void 0 ? octAsKeyObject : (octAsKeyObject = jwk.ext !== true);
|
|
759
|
-
if (octAsKeyObject) {
|
|
760
|
-
return parse({ ...jwk, alg, ext: (_a = jwk.ext) !== null && _a !== void 0 ? _a : false });
|
|
761
|
-
}
|
|
762
443
|
return decode(jwk.k);
|
|
763
444
|
case 'RSA':
|
|
764
445
|
if (jwk.oth !== undefined) {
|
|
@@ -881,12 +562,6 @@ function dsaDigest(alg) {
|
|
|
881
562
|
}
|
|
882
563
|
}
|
|
883
564
|
|
|
884
|
-
const [major$1, minor$1] = process.version
|
|
885
|
-
.slice(1)
|
|
886
|
-
.split('.')
|
|
887
|
-
.map((str) => parseInt(str, 10));
|
|
888
|
-
const electron = 'electron' in process.versions;
|
|
889
|
-
const rsaPssParams = !electron && (major$1 >= 17 || (major$1 === 16 && minor$1 >= 9));
|
|
890
565
|
const PSS = {
|
|
891
566
|
padding: constants.RSA_PKCS1_PSS_PADDING,
|
|
892
567
|
saltLength: constants.RSA_PSS_SALTLEN_DIGEST,
|
|
@@ -910,11 +585,11 @@ function keyForCrypto(alg, key) {
|
|
|
910
585
|
if (key.asymmetricKeyType !== 'rsa') {
|
|
911
586
|
throw new TypeError('Invalid key for this operation, its asymmetricKeyType must be rsa');
|
|
912
587
|
}
|
|
913
|
-
|
|
588
|
+
checkKeyLength(key, alg);
|
|
914
589
|
return key;
|
|
915
|
-
case
|
|
916
|
-
case
|
|
917
|
-
case
|
|
590
|
+
case 'PS256':
|
|
591
|
+
case 'PS384':
|
|
592
|
+
case 'PS512':
|
|
918
593
|
if (key.asymmetricKeyType === 'rsa-pss') {
|
|
919
594
|
const { hashAlgorithm, mgf1HashAlgorithm, saltLength } = key.asymmetricKeyDetails;
|
|
920
595
|
const length = parseInt(alg.slice(-3), 10);
|
|
@@ -929,15 +604,7 @@ function keyForCrypto(alg, key) {
|
|
|
929
604
|
else if (key.asymmetricKeyType !== 'rsa') {
|
|
930
605
|
throw new TypeError('Invalid key for this operation, its asymmetricKeyType must be rsa or rsa-pss');
|
|
931
606
|
}
|
|
932
|
-
|
|
933
|
-
return { key, ...PSS };
|
|
934
|
-
case !rsaPssParams && 'PS256':
|
|
935
|
-
case !rsaPssParams && 'PS384':
|
|
936
|
-
case !rsaPssParams && 'PS512':
|
|
937
|
-
if (key.asymmetricKeyType !== 'rsa') {
|
|
938
|
-
throw new TypeError('Invalid key for this operation, its asymmetricKeyType must be rsa');
|
|
939
|
-
}
|
|
940
|
-
checkModulusLength(key, alg);
|
|
607
|
+
checkKeyLength(key, alg);
|
|
941
608
|
return { key, ...PSS };
|
|
942
609
|
case 'ES256':
|
|
943
610
|
case 'ES256K':
|
|
@@ -988,13 +655,7 @@ function getSignVerifyKey(alg, key, usage) {
|
|
|
988
655
|
throw new TypeError(invalidKeyInput(key, ...types, 'Uint8Array'));
|
|
989
656
|
}
|
|
990
657
|
|
|
991
|
-
|
|
992
|
-
if (crypto.sign.length > 3) {
|
|
993
|
-
oneShotSign = promisify(crypto.sign);
|
|
994
|
-
}
|
|
995
|
-
else {
|
|
996
|
-
oneShotSign = crypto.sign;
|
|
997
|
-
}
|
|
658
|
+
const oneShotSign = promisify(crypto.sign);
|
|
998
659
|
const sign = async (alg, key, data) => {
|
|
999
660
|
const keyObject = getSignVerifyKey(alg, key, 'sign');
|
|
1000
661
|
if (alg.startsWith('HS')) {
|
|
@@ -1005,18 +666,7 @@ const sign = async (alg, key, data) => {
|
|
|
1005
666
|
return oneShotSign(dsaDigest(alg), data, keyForCrypto(alg, keyObject));
|
|
1006
667
|
};
|
|
1007
668
|
|
|
1008
|
-
const
|
|
1009
|
-
.slice(1)
|
|
1010
|
-
.split('.')
|
|
1011
|
-
.map((str) => parseInt(str, 10));
|
|
1012
|
-
const oneShotCallbackSupported = major >= 16 || (major === 15 && minor >= 13);
|
|
1013
|
-
let oneShotVerify;
|
|
1014
|
-
if (crypto.verify.length > 4 && oneShotCallbackSupported) {
|
|
1015
|
-
oneShotVerify = promisify(crypto.verify);
|
|
1016
|
-
}
|
|
1017
|
-
else {
|
|
1018
|
-
oneShotVerify = crypto.verify;
|
|
1019
|
-
}
|
|
669
|
+
const oneShotVerify = promisify(crypto.verify);
|
|
1020
670
|
const verify = async (alg, key, signature, data) => {
|
|
1021
671
|
const keyObject = getSignVerifyKey(alg, key, 'verify');
|
|
1022
672
|
if (alg.startsWith('HS')) {
|
|
@@ -1040,7 +690,6 @@ const verify = async (alg, key, signature, data) => {
|
|
|
1040
690
|
};
|
|
1041
691
|
|
|
1042
692
|
async function flattenedVerify(jws, key, options) {
|
|
1043
|
-
var _a;
|
|
1044
693
|
if (!isObject(jws)) {
|
|
1045
694
|
throw new JWSInvalid('Flattened JWS must be an object');
|
|
1046
695
|
}
|
|
@@ -1076,7 +725,7 @@ async function flattenedVerify(jws, key, options) {
|
|
|
1076
725
|
...parsedProt,
|
|
1077
726
|
...jws.header,
|
|
1078
727
|
};
|
|
1079
|
-
const extensions = validateCrit(JWSInvalid, new Map([['b64', true]]), options
|
|
728
|
+
const extensions = validateCrit(JWSInvalid, new Map([['b64', true]]), options?.crit, parsedProt, joseHeader);
|
|
1080
729
|
let b64 = true;
|
|
1081
730
|
if (extensions.has('b64')) {
|
|
1082
731
|
b64 = parsedProt.b64;
|
|
@@ -1090,7 +739,7 @@ async function flattenedVerify(jws, key, options) {
|
|
|
1090
739
|
}
|
|
1091
740
|
const algorithms = options && validateAlgorithms('algorithms', options.algorithms);
|
|
1092
741
|
if (algorithms && !algorithms.has(alg)) {
|
|
1093
|
-
throw new JOSEAlgNotAllowed('"alg" (Algorithm) Header Parameter not allowed');
|
|
742
|
+
throw new JOSEAlgNotAllowed('"alg" (Algorithm) Header Parameter value not allowed');
|
|
1094
743
|
}
|
|
1095
744
|
if (b64) {
|
|
1096
745
|
if (typeof jws.payload !== 'string') {
|
|
@@ -1106,15 +755,26 @@ async function flattenedVerify(jws, key, options) {
|
|
|
1106
755
|
resolvedKey = true;
|
|
1107
756
|
}
|
|
1108
757
|
checkKeyType(alg, key, 'verify');
|
|
1109
|
-
const data = concat(encoder.encode(
|
|
1110
|
-
|
|
758
|
+
const data = concat(encoder.encode(jws.protected ?? ''), encoder.encode('.'), typeof jws.payload === 'string' ? encoder.encode(jws.payload) : jws.payload);
|
|
759
|
+
let signature;
|
|
760
|
+
try {
|
|
761
|
+
signature = decode(jws.signature);
|
|
762
|
+
}
|
|
763
|
+
catch {
|
|
764
|
+
throw new JWSInvalid('Failed to base64url decode the signature');
|
|
765
|
+
}
|
|
1111
766
|
const verified = await verify(alg, key, signature, data);
|
|
1112
767
|
if (!verified) {
|
|
1113
768
|
throw new JWSSignatureVerificationFailed();
|
|
1114
769
|
}
|
|
1115
770
|
let payload;
|
|
1116
771
|
if (b64) {
|
|
1117
|
-
|
|
772
|
+
try {
|
|
773
|
+
payload = decode(jws.payload);
|
|
774
|
+
}
|
|
775
|
+
catch {
|
|
776
|
+
throw new JWSInvalid('Failed to base64url decode the payload');
|
|
777
|
+
}
|
|
1118
778
|
}
|
|
1119
779
|
else if (typeof jws.payload === 'string') {
|
|
1120
780
|
payload = encoder.encode(jws.payload);
|
|
@@ -1227,15 +887,26 @@ var jwtPayload = (protectedHeader, encodedPayload, options = {}) => {
|
|
|
1227
887
|
if (!isObject(payload)) {
|
|
1228
888
|
throw new JWTInvalid('JWT Claims Set must be a top-level JSON object');
|
|
1229
889
|
}
|
|
1230
|
-
const { issuer } = options;
|
|
890
|
+
const { requiredClaims = [], issuer, subject, audience, maxTokenAge } = options;
|
|
891
|
+
if (maxTokenAge !== undefined)
|
|
892
|
+
requiredClaims.push('iat');
|
|
893
|
+
if (audience !== undefined)
|
|
894
|
+
requiredClaims.push('aud');
|
|
895
|
+
if (subject !== undefined)
|
|
896
|
+
requiredClaims.push('sub');
|
|
897
|
+
if (issuer !== undefined)
|
|
898
|
+
requiredClaims.push('iss');
|
|
899
|
+
for (const claim of new Set(requiredClaims.reverse())) {
|
|
900
|
+
if (!(claim in payload)) {
|
|
901
|
+
throw new JWTClaimValidationFailed(`missing required "${claim}" claim`, claim, 'missing');
|
|
902
|
+
}
|
|
903
|
+
}
|
|
1231
904
|
if (issuer && !(Array.isArray(issuer) ? issuer : [issuer]).includes(payload.iss)) {
|
|
1232
905
|
throw new JWTClaimValidationFailed('unexpected "iss" claim value', 'iss', 'check_failed');
|
|
1233
906
|
}
|
|
1234
|
-
const { subject } = options;
|
|
1235
907
|
if (subject && payload.sub !== subject) {
|
|
1236
908
|
throw new JWTClaimValidationFailed('unexpected "sub" claim value', 'sub', 'check_failed');
|
|
1237
909
|
}
|
|
1238
|
-
const { audience } = options;
|
|
1239
910
|
if (audience &&
|
|
1240
911
|
!checkAudiencePresence(payload.aud, typeof audience === 'string' ? [audience] : audience)) {
|
|
1241
912
|
throw new JWTClaimValidationFailed('unexpected "aud" claim value', 'aud', 'check_failed');
|
|
@@ -1256,7 +927,7 @@ var jwtPayload = (protectedHeader, encodedPayload, options = {}) => {
|
|
|
1256
927
|
}
|
|
1257
928
|
const { currentDate } = options;
|
|
1258
929
|
const now = epoch(currentDate || new Date());
|
|
1259
|
-
if ((payload.iat !== undefined ||
|
|
930
|
+
if ((payload.iat !== undefined || maxTokenAge) && typeof payload.iat !== 'number') {
|
|
1260
931
|
throw new JWTClaimValidationFailed('"iat" claim must be a number', 'iat', 'invalid');
|
|
1261
932
|
}
|
|
1262
933
|
if (payload.nbf !== undefined) {
|
|
@@ -1275,9 +946,9 @@ var jwtPayload = (protectedHeader, encodedPayload, options = {}) => {
|
|
|
1275
946
|
throw new JWTExpired('"exp" claim timestamp check failed', 'exp', 'check_failed');
|
|
1276
947
|
}
|
|
1277
948
|
}
|
|
1278
|
-
if (
|
|
949
|
+
if (maxTokenAge) {
|
|
1279
950
|
const age = now - payload.iat;
|
|
1280
|
-
const max = typeof
|
|
951
|
+
const max = typeof maxTokenAge === 'number' ? maxTokenAge : secs(maxTokenAge);
|
|
1281
952
|
if (age - tolerance > max) {
|
|
1282
953
|
throw new JWTExpired('"iat" claim timestamp check failed (too far in the past)', 'iat', 'check_failed');
|
|
1283
954
|
}
|
|
@@ -1289,9 +960,8 @@ var jwtPayload = (protectedHeader, encodedPayload, options = {}) => {
|
|
|
1289
960
|
};
|
|
1290
961
|
|
|
1291
962
|
async function jwtVerify(jwt, key, options) {
|
|
1292
|
-
var _a;
|
|
1293
963
|
const verified = await compactVerify(jwt, key, options);
|
|
1294
|
-
if (
|
|
964
|
+
if (verified.protectedHeader.crit?.includes('b64') && verified.protectedHeader.b64 === false) {
|
|
1295
965
|
throw new JWTInvalid('JWTs MUST NOT use unencoded payload');
|
|
1296
966
|
}
|
|
1297
967
|
const payload = jwtPayload(verified.protectedHeader, verified.payload, options);
|
|
@@ -1331,15 +1001,16 @@ function clone(obj) {
|
|
|
1331
1001
|
return JSON.parse(JSON.stringify(obj));
|
|
1332
1002
|
}
|
|
1333
1003
|
class LocalJWKSet {
|
|
1004
|
+
_jwks;
|
|
1005
|
+
_cached = new WeakMap();
|
|
1334
1006
|
constructor(jwks) {
|
|
1335
|
-
this._cached = new WeakMap();
|
|
1336
1007
|
if (!isJWKSLike(jwks)) {
|
|
1337
1008
|
throw new JWKSInvalid('JSON Web Key Set malformed');
|
|
1338
1009
|
}
|
|
1339
1010
|
this._jwks = clone(jwks);
|
|
1340
1011
|
}
|
|
1341
1012
|
async getKey(protectedHeader, token) {
|
|
1342
|
-
const { alg, kid } = { ...protectedHeader, ...token
|
|
1013
|
+
const { alg, kid } = { ...protectedHeader, ...token?.header };
|
|
1343
1014
|
const kty = getKtyFromAlg(alg);
|
|
1344
1015
|
const candidates = this._jwks.keys.filter((jwk) => {
|
|
1345
1016
|
let candidate = kty === jwk.kty;
|
|
@@ -1381,18 +1052,33 @@ class LocalJWKSet {
|
|
|
1381
1052
|
throw new JWKSNoMatchingKey();
|
|
1382
1053
|
}
|
|
1383
1054
|
else if (length !== 1) {
|
|
1384
|
-
|
|
1055
|
+
const error = new JWKSMultipleMatchingKeys();
|
|
1056
|
+
const { _cached } = this;
|
|
1057
|
+
error[Symbol.asyncIterator] = async function* () {
|
|
1058
|
+
for (const jwk of candidates) {
|
|
1059
|
+
try {
|
|
1060
|
+
yield await importWithAlgCache(_cached, jwk, alg);
|
|
1061
|
+
}
|
|
1062
|
+
catch {
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
throw error;
|
|
1385
1068
|
}
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1069
|
+
return importWithAlgCache(this._cached, jwk, alg);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
async function importWithAlgCache(cache, jwk, alg) {
|
|
1073
|
+
const cached = cache.get(jwk) || cache.set(jwk, {}).get(jwk);
|
|
1074
|
+
if (cached[alg] === undefined) {
|
|
1075
|
+
const key = await importJWK({ ...jwk, ext: true }, alg);
|
|
1076
|
+
if (key instanceof Uint8Array || key.type !== 'public') {
|
|
1077
|
+
throw new JWKSInvalid('JSON Web Key Set members must be public keys');
|
|
1393
1078
|
}
|
|
1394
|
-
|
|
1079
|
+
cached[alg] = key;
|
|
1395
1080
|
}
|
|
1081
|
+
return cached[alg];
|
|
1396
1082
|
}
|
|
1397
1083
|
|
|
1398
1084
|
const fetchJwks = async (url, timeout, options) => {
|
|
@@ -1433,7 +1119,19 @@ const fetchJwks = async (url, timeout, options) => {
|
|
|
1433
1119
|
}
|
|
1434
1120
|
};
|
|
1435
1121
|
|
|
1122
|
+
function isCloudflareWorkers() {
|
|
1123
|
+
return (typeof WebSocketPair !== 'undefined' ||
|
|
1124
|
+
(typeof navigator !== 'undefined' && navigator.userAgent === 'Cloudflare-Workers') ||
|
|
1125
|
+
(typeof EdgeRuntime !== 'undefined' && EdgeRuntime === 'vercel'));
|
|
1126
|
+
}
|
|
1436
1127
|
class RemoteJWKSet extends LocalJWKSet {
|
|
1128
|
+
_url;
|
|
1129
|
+
_timeoutDuration;
|
|
1130
|
+
_cooldownDuration;
|
|
1131
|
+
_cacheMaxAge;
|
|
1132
|
+
_jwksTimestamp;
|
|
1133
|
+
_pendingFetch;
|
|
1134
|
+
_options;
|
|
1437
1135
|
constructor(url, options) {
|
|
1438
1136
|
super({ keys: [] });
|
|
1439
1137
|
this._jwks = undefined;
|
|
@@ -1441,12 +1139,12 @@ class RemoteJWKSet extends LocalJWKSet {
|
|
|
1441
1139
|
throw new TypeError('url must be an instance of URL');
|
|
1442
1140
|
}
|
|
1443
1141
|
this._url = new URL(url.href);
|
|
1444
|
-
this._options = { agent: options
|
|
1142
|
+
this._options = { agent: options?.agent, headers: options?.headers };
|
|
1445
1143
|
this._timeoutDuration =
|
|
1446
|
-
typeof
|
|
1144
|
+
typeof options?.timeoutDuration === 'number' ? options?.timeoutDuration : 5000;
|
|
1447
1145
|
this._cooldownDuration =
|
|
1448
|
-
typeof
|
|
1449
|
-
this._cacheMaxAge = typeof
|
|
1146
|
+
typeof options?.cooldownDuration === 'number' ? options?.cooldownDuration : 30000;
|
|
1147
|
+
this._cacheMaxAge = typeof options?.cacheMaxAge === 'number' ? options?.cacheMaxAge : 600000;
|
|
1450
1148
|
}
|
|
1451
1149
|
coolingDown() {
|
|
1452
1150
|
return typeof this._jwksTimestamp === 'number'
|
|
@@ -1477,38 +1175,29 @@ class RemoteJWKSet extends LocalJWKSet {
|
|
|
1477
1175
|
}
|
|
1478
1176
|
async reload() {
|
|
1479
1177
|
if (this._pendingFetch && isCloudflareWorkers()) {
|
|
1480
|
-
|
|
1481
|
-
const isDone = () => {
|
|
1482
|
-
if (this._pendingFetch === undefined) {
|
|
1483
|
-
resolve();
|
|
1484
|
-
}
|
|
1485
|
-
else {
|
|
1486
|
-
setTimeout(isDone, 5);
|
|
1487
|
-
}
|
|
1488
|
-
};
|
|
1489
|
-
isDone();
|
|
1490
|
-
});
|
|
1491
|
-
}
|
|
1492
|
-
if (!this._pendingFetch) {
|
|
1493
|
-
this._pendingFetch = fetchJwks(this._url, this._timeoutDuration, this._options)
|
|
1494
|
-
.then((json) => {
|
|
1495
|
-
if (!isJWKSLike(json)) {
|
|
1496
|
-
throw new JWKSInvalid('JSON Web Key Set malformed');
|
|
1497
|
-
}
|
|
1498
|
-
this._jwks = { keys: json.keys };
|
|
1499
|
-
this._jwksTimestamp = Date.now();
|
|
1500
|
-
this._pendingFetch = undefined;
|
|
1501
|
-
})
|
|
1502
|
-
.catch((err) => {
|
|
1503
|
-
this._pendingFetch = undefined;
|
|
1504
|
-
throw err;
|
|
1505
|
-
});
|
|
1178
|
+
this._pendingFetch = undefined;
|
|
1506
1179
|
}
|
|
1180
|
+
this._pendingFetch ||= fetchJwks(this._url, this._timeoutDuration, this._options)
|
|
1181
|
+
.then((json) => {
|
|
1182
|
+
if (!isJWKSLike(json)) {
|
|
1183
|
+
throw new JWKSInvalid('JSON Web Key Set malformed');
|
|
1184
|
+
}
|
|
1185
|
+
this._jwks = { keys: json.keys };
|
|
1186
|
+
this._jwksTimestamp = Date.now();
|
|
1187
|
+
this._pendingFetch = undefined;
|
|
1188
|
+
})
|
|
1189
|
+
.catch((err) => {
|
|
1190
|
+
this._pendingFetch = undefined;
|
|
1191
|
+
throw err;
|
|
1192
|
+
});
|
|
1507
1193
|
await this._pendingFetch;
|
|
1508
1194
|
}
|
|
1509
1195
|
}
|
|
1510
1196
|
function createRemoteJWKSet(url, options) {
|
|
1511
|
-
|
|
1197
|
+
const set = new RemoteJWKSet(url, options);
|
|
1198
|
+
return async function (protectedHeader, token) {
|
|
1199
|
+
return set.getKey(protectedHeader, token);
|
|
1200
|
+
};
|
|
1512
1201
|
}
|
|
1513
1202
|
|
|
1514
1203
|
// https://appleid.apple.com/.well-known/openid-configuration
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logto/connector-apple",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Apple web connector implementation.",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@logto/connector-kit": "^
|
|
7
|
-
"@logto/shared": "^3.
|
|
8
|
-
"jose": "^
|
|
6
|
+
"@logto/connector-kit": "^2.1.0",
|
|
7
|
+
"@logto/shared": "^3.1.0",
|
|
8
|
+
"jose": "^5.0.0"
|
|
9
9
|
},
|
|
10
10
|
"main": "./lib/index.js",
|
|
11
11
|
"module": "./lib/index.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"logo-dark.svg"
|
|
20
20
|
],
|
|
21
21
|
"engines": {
|
|
22
|
-
"node": "^
|
|
22
|
+
"node": "^20.9.0"
|
|
23
23
|
},
|
|
24
24
|
"eslintConfig": {
|
|
25
25
|
"extends": "@silverhand",
|