@osdk/oauth 1.1.0-beta.9 → 1.1.1
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/CHANGELOG.md +40 -0
- package/build/browser/AuthorizationServer.test.js +7 -0
- package/build/browser/AuthorizationServer.test.js.map +1 -1
- package/build/browser/common.js +4 -3
- package/build/browser/common.js.map +1 -1
- package/build/browser/common.test.js +4 -3
- package/build/browser/common.test.js.map +1 -1
- package/build/browser/createConfidentialOauthClient.js +3 -2
- package/build/browser/createConfidentialOauthClient.js.map +1 -1
- package/build/browser/createPublicOauthClient.js +8 -5
- package/build/browser/createPublicOauthClient.js.map +1 -1
- package/build/browser/publicOauth.test.js +37 -9
- package/build/browser/publicOauth.test.js.map +1 -1
- package/build/browser/utils.js +1 -1
- package/build/browser/utils.js.map +1 -1
- package/build/cjs/index.cjs +1019 -35
- package/build/cjs/index.cjs.map +1 -1
- package/build/esm/AuthorizationServer.test.js +7 -0
- package/build/esm/AuthorizationServer.test.js.map +1 -1
- package/build/esm/common.js +4 -3
- package/build/esm/common.js.map +1 -1
- package/build/esm/common.test.js +4 -3
- package/build/esm/common.test.js.map +1 -1
- package/build/esm/createConfidentialOauthClient.js +3 -2
- package/build/esm/createConfidentialOauthClient.js.map +1 -1
- package/build/esm/createPublicOauthClient.js +8 -5
- package/build/esm/createPublicOauthClient.js.map +1 -1
- package/build/esm/publicOauth.test.js +37 -9
- package/build/esm/publicOauth.test.js.map +1 -1
- package/build/esm/utils.js +1 -1
- package/build/esm/utils.js.map +1 -1
- package/build/types/AuthorizationServer.test.d.ts.map +1 -0
- package/build/types/BaseOauthClient.d.ts +3 -3
- package/build/types/BaseOauthClient.d.ts.map +1 -0
- package/build/types/ConfidentialOauthClient.d.ts.map +1 -0
- package/build/types/PublicOauthClient.d.ts.map +1 -0
- package/build/types/Token.d.ts.map +1 -0
- package/build/types/__tests__/createPublicOauthClient.test.d.ts.map +1 -0
- package/build/types/common.d.ts +15 -12
- package/build/types/common.d.ts.map +1 -0
- package/build/types/common.test.d.ts.map +1 -0
- package/build/types/createConfidentialOauthClient.d.ts.map +1 -0
- package/build/types/createPublicOauthClient.d.ts.map +1 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/publicOauth.test.d.ts.map +1 -0
- package/build/types/throwIfError.d.ts.map +1 -0
- package/build/types/utils.d.ts +2 -1
- package/build/types/utils.d.ts.map +1 -0
- package/package.json +1 -2
package/build/cjs/index.cjs
CHANGED
|
@@ -1,18 +1,942 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var oauth4webapi = require('oauth4webapi');
|
|
4
3
|
var invariant2 = require('tiny-invariant');
|
|
5
4
|
var typescriptEventTarget = require('typescript-event-target');
|
|
6
|
-
var delay = require('delay');
|
|
7
5
|
|
|
8
6
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
7
|
|
|
10
8
|
var invariant2__default = /*#__PURE__*/_interopDefault(invariant2);
|
|
11
|
-
var delay__default = /*#__PURE__*/_interopDefault(delay);
|
|
12
9
|
|
|
13
|
-
//
|
|
10
|
+
// ../../node_modules/.pnpm/oauth4webapi@2.10.4/node_modules/oauth4webapi/build/index.js
|
|
11
|
+
var USER_AGENT;
|
|
12
|
+
if (typeof navigator === "undefined" || !navigator.userAgent?.startsWith?.("Mozilla/5.0 ")) {
|
|
13
|
+
const NAME = "oauth4webapi";
|
|
14
|
+
const VERSION = "v2.10.4";
|
|
15
|
+
USER_AGENT = `${NAME}/${VERSION}`;
|
|
16
|
+
}
|
|
17
|
+
function looseInstanceOf(input, expected) {
|
|
18
|
+
if (input == null) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
return input instanceof expected || Object.getPrototypeOf(input)[Symbol.toStringTag] === expected.prototype[Symbol.toStringTag];
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
var clockSkew = Symbol();
|
|
28
|
+
var clockTolerance = Symbol();
|
|
29
|
+
var customFetch = Symbol();
|
|
30
|
+
var useMtlsAlias = Symbol();
|
|
31
|
+
var encoder = new TextEncoder();
|
|
32
|
+
var decoder = new TextDecoder();
|
|
33
|
+
function buf(input) {
|
|
34
|
+
if (typeof input === "string") {
|
|
35
|
+
return encoder.encode(input);
|
|
36
|
+
}
|
|
37
|
+
return decoder.decode(input);
|
|
38
|
+
}
|
|
39
|
+
var CHUNK_SIZE = 32768;
|
|
40
|
+
function encodeBase64Url(input) {
|
|
41
|
+
if (input instanceof ArrayBuffer) {
|
|
42
|
+
input = new Uint8Array(input);
|
|
43
|
+
}
|
|
44
|
+
const arr = [];
|
|
45
|
+
for (let i = 0; i < input.byteLength; i += CHUNK_SIZE) {
|
|
46
|
+
arr.push(String.fromCharCode.apply(null, input.subarray(i, i + CHUNK_SIZE)));
|
|
47
|
+
}
|
|
48
|
+
return btoa(arr.join("")).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
49
|
+
}
|
|
50
|
+
function decodeBase64Url(input) {
|
|
51
|
+
try {
|
|
52
|
+
const binary = atob(input.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, ""));
|
|
53
|
+
const bytes = new Uint8Array(binary.length);
|
|
54
|
+
for (let i = 0; i < binary.length; i++) {
|
|
55
|
+
bytes[i] = binary.charCodeAt(i);
|
|
56
|
+
}
|
|
57
|
+
return bytes;
|
|
58
|
+
} catch (cause) {
|
|
59
|
+
throw new OPE("The input to be decoded is not correctly encoded.", {
|
|
60
|
+
cause
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function b64u(input) {
|
|
65
|
+
if (typeof input === "string") {
|
|
66
|
+
return decodeBase64Url(input);
|
|
67
|
+
}
|
|
68
|
+
return encodeBase64Url(input);
|
|
69
|
+
}
|
|
70
|
+
var LRU = class {
|
|
71
|
+
constructor(maxSize) {
|
|
72
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
73
|
+
this._cache = /* @__PURE__ */ new Map();
|
|
74
|
+
this.maxSize = maxSize;
|
|
75
|
+
}
|
|
76
|
+
get(key) {
|
|
77
|
+
let v = this.cache.get(key);
|
|
78
|
+
if (v) {
|
|
79
|
+
return v;
|
|
80
|
+
}
|
|
81
|
+
if (v = this._cache.get(key)) {
|
|
82
|
+
this.update(key, v);
|
|
83
|
+
return v;
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
has(key) {
|
|
88
|
+
return this.cache.has(key) || this._cache.has(key);
|
|
89
|
+
}
|
|
90
|
+
set(key, value) {
|
|
91
|
+
if (this.cache.has(key)) {
|
|
92
|
+
this.cache.set(key, value);
|
|
93
|
+
} else {
|
|
94
|
+
this.update(key, value);
|
|
95
|
+
}
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
delete(key) {
|
|
99
|
+
if (this.cache.has(key)) {
|
|
100
|
+
return this.cache.delete(key);
|
|
101
|
+
}
|
|
102
|
+
if (this._cache.has(key)) {
|
|
103
|
+
return this._cache.delete(key);
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
update(key, value) {
|
|
108
|
+
this.cache.set(key, value);
|
|
109
|
+
if (this.cache.size >= this.maxSize) {
|
|
110
|
+
this._cache = this.cache;
|
|
111
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
var UnsupportedOperationError = class extends Error {
|
|
116
|
+
constructor(message) {
|
|
117
|
+
super(message ?? "operation not supported");
|
|
118
|
+
this.name = this.constructor.name;
|
|
119
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
var OperationProcessingError = class extends Error {
|
|
123
|
+
constructor(message, options) {
|
|
124
|
+
super(message, options);
|
|
125
|
+
this.name = this.constructor.name;
|
|
126
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
var OPE = OperationProcessingError;
|
|
130
|
+
var dpopNonces = new LRU(100);
|
|
131
|
+
function isCryptoKey(key) {
|
|
132
|
+
return key instanceof CryptoKey;
|
|
133
|
+
}
|
|
134
|
+
function isPrivateKey(key) {
|
|
135
|
+
return isCryptoKey(key) && key.type === "private";
|
|
136
|
+
}
|
|
137
|
+
function isPublicKey(key) {
|
|
138
|
+
return isCryptoKey(key) && key.type === "public";
|
|
139
|
+
}
|
|
140
|
+
function processDpopNonce(response) {
|
|
141
|
+
try {
|
|
142
|
+
const nonce = response.headers.get("dpop-nonce");
|
|
143
|
+
if (nonce) {
|
|
144
|
+
dpopNonces.set(new URL(response.url).origin, nonce);
|
|
145
|
+
}
|
|
146
|
+
} catch {
|
|
147
|
+
}
|
|
148
|
+
return response;
|
|
149
|
+
}
|
|
150
|
+
function isJsonObject(input) {
|
|
151
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
function prepareHeaders(input) {
|
|
157
|
+
if (looseInstanceOf(input, Headers)) {
|
|
158
|
+
input = Object.fromEntries(input.entries());
|
|
159
|
+
}
|
|
160
|
+
const headers = new Headers(input);
|
|
161
|
+
if (USER_AGENT && !headers.has("user-agent")) {
|
|
162
|
+
headers.set("user-agent", USER_AGENT);
|
|
163
|
+
}
|
|
164
|
+
if (headers.has("authorization")) {
|
|
165
|
+
throw new TypeError('"options.headers" must not include the "authorization" header name');
|
|
166
|
+
}
|
|
167
|
+
if (headers.has("dpop")) {
|
|
168
|
+
throw new TypeError('"options.headers" must not include the "dpop" header name');
|
|
169
|
+
}
|
|
170
|
+
return headers;
|
|
171
|
+
}
|
|
172
|
+
function signal(value) {
|
|
173
|
+
if (typeof value === "function") {
|
|
174
|
+
value = value();
|
|
175
|
+
}
|
|
176
|
+
if (!(value instanceof AbortSignal)) {
|
|
177
|
+
throw new TypeError('"options.signal" must return or be an instance of AbortSignal');
|
|
178
|
+
}
|
|
179
|
+
return value;
|
|
180
|
+
}
|
|
181
|
+
function validateString(input) {
|
|
182
|
+
return typeof input === "string" && input.length !== 0;
|
|
183
|
+
}
|
|
184
|
+
function randomBytes() {
|
|
185
|
+
return b64u(crypto.getRandomValues(new Uint8Array(32)));
|
|
186
|
+
}
|
|
187
|
+
function generateRandomCodeVerifier() {
|
|
188
|
+
return randomBytes();
|
|
189
|
+
}
|
|
190
|
+
function generateRandomState() {
|
|
191
|
+
return randomBytes();
|
|
192
|
+
}
|
|
193
|
+
async function calculatePKCECodeChallenge(codeVerifier) {
|
|
194
|
+
if (!validateString(codeVerifier)) {
|
|
195
|
+
throw new TypeError('"codeVerifier" must be a non-empty string');
|
|
196
|
+
}
|
|
197
|
+
return b64u(await crypto.subtle.digest("SHA-256", buf(codeVerifier)));
|
|
198
|
+
}
|
|
199
|
+
function getKeyAndKid(input) {
|
|
200
|
+
if (input instanceof CryptoKey) {
|
|
201
|
+
return {
|
|
202
|
+
key: input
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (!(input?.key instanceof CryptoKey)) {
|
|
206
|
+
return {};
|
|
207
|
+
}
|
|
208
|
+
if (input.kid !== undefined && !validateString(input.kid)) {
|
|
209
|
+
throw new TypeError('"kid" must be a non-empty string');
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
key: input.key,
|
|
213
|
+
kid: input.kid
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function formUrlEncode(token) {
|
|
217
|
+
return encodeURIComponent(token).replace(/%20/g, "+");
|
|
218
|
+
}
|
|
219
|
+
function clientSecretBasic(clientId, clientSecret) {
|
|
220
|
+
const username = formUrlEncode(clientId);
|
|
221
|
+
const password = formUrlEncode(clientSecret);
|
|
222
|
+
const credentials = btoa(`${username}:${password}`);
|
|
223
|
+
return `Basic ${credentials}`;
|
|
224
|
+
}
|
|
225
|
+
function psAlg(key) {
|
|
226
|
+
switch (key.algorithm.hash.name) {
|
|
227
|
+
case "SHA-256":
|
|
228
|
+
return "PS256";
|
|
229
|
+
case "SHA-384":
|
|
230
|
+
return "PS384";
|
|
231
|
+
case "SHA-512":
|
|
232
|
+
return "PS512";
|
|
233
|
+
default:
|
|
234
|
+
throw new UnsupportedOperationError("unsupported RsaHashedKeyAlgorithm hash name");
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function rsAlg(key) {
|
|
238
|
+
switch (key.algorithm.hash.name) {
|
|
239
|
+
case "SHA-256":
|
|
240
|
+
return "RS256";
|
|
241
|
+
case "SHA-384":
|
|
242
|
+
return "RS384";
|
|
243
|
+
case "SHA-512":
|
|
244
|
+
return "RS512";
|
|
245
|
+
default:
|
|
246
|
+
throw new UnsupportedOperationError("unsupported RsaHashedKeyAlgorithm hash name");
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function esAlg(key) {
|
|
250
|
+
switch (key.algorithm.namedCurve) {
|
|
251
|
+
case "P-256":
|
|
252
|
+
return "ES256";
|
|
253
|
+
case "P-384":
|
|
254
|
+
return "ES384";
|
|
255
|
+
case "P-521":
|
|
256
|
+
return "ES512";
|
|
257
|
+
default:
|
|
258
|
+
throw new UnsupportedOperationError("unsupported EcKeyAlgorithm namedCurve");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function keyToJws(key) {
|
|
262
|
+
switch (key.algorithm.name) {
|
|
263
|
+
case "RSA-PSS":
|
|
264
|
+
return psAlg(key);
|
|
265
|
+
case "RSASSA-PKCS1-v1_5":
|
|
266
|
+
return rsAlg(key);
|
|
267
|
+
case "ECDSA":
|
|
268
|
+
return esAlg(key);
|
|
269
|
+
case "Ed25519":
|
|
270
|
+
case "Ed448":
|
|
271
|
+
return "EdDSA";
|
|
272
|
+
default:
|
|
273
|
+
throw new UnsupportedOperationError("unsupported CryptoKey algorithm name");
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function getClockSkew(client) {
|
|
277
|
+
const skew = client?.[clockSkew];
|
|
278
|
+
return typeof skew === "number" && Number.isFinite(skew) ? skew : 0;
|
|
279
|
+
}
|
|
280
|
+
function getClockTolerance(client) {
|
|
281
|
+
const tolerance = client?.[clockTolerance];
|
|
282
|
+
return typeof tolerance === "number" && Number.isFinite(tolerance) && Math.sign(tolerance) !== -1 ? tolerance : 30;
|
|
283
|
+
}
|
|
284
|
+
function epochTime() {
|
|
285
|
+
return Math.floor(Date.now() / 1e3);
|
|
286
|
+
}
|
|
287
|
+
function clientAssertion(as, client) {
|
|
288
|
+
const now = epochTime() + getClockSkew(client);
|
|
289
|
+
return {
|
|
290
|
+
jti: randomBytes(),
|
|
291
|
+
aud: [as.issuer, as.token_endpoint],
|
|
292
|
+
exp: now + 60,
|
|
293
|
+
iat: now,
|
|
294
|
+
nbf: now,
|
|
295
|
+
iss: client.client_id,
|
|
296
|
+
sub: client.client_id
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
async function privateKeyJwt(as, client, key, kid) {
|
|
300
|
+
return jwt({
|
|
301
|
+
alg: keyToJws(key),
|
|
302
|
+
kid
|
|
303
|
+
}, clientAssertion(as, client), key);
|
|
304
|
+
}
|
|
305
|
+
function assertAs(as) {
|
|
306
|
+
if (typeof as !== "object" || as === null) {
|
|
307
|
+
throw new TypeError('"as" must be an object');
|
|
308
|
+
}
|
|
309
|
+
if (!validateString(as.issuer)) {
|
|
310
|
+
throw new TypeError('"as.issuer" property must be a non-empty string');
|
|
311
|
+
}
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
function assertClient(client) {
|
|
315
|
+
if (typeof client !== "object" || client === null) {
|
|
316
|
+
throw new TypeError('"client" must be an object');
|
|
317
|
+
}
|
|
318
|
+
if (!validateString(client.client_id)) {
|
|
319
|
+
throw new TypeError('"client.client_id" property must be a non-empty string');
|
|
320
|
+
}
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
function assertClientSecret(clientSecret) {
|
|
324
|
+
if (!validateString(clientSecret)) {
|
|
325
|
+
throw new TypeError('"client.client_secret" property must be a non-empty string');
|
|
326
|
+
}
|
|
327
|
+
return clientSecret;
|
|
328
|
+
}
|
|
329
|
+
function assertNoClientPrivateKey(clientAuthMethod, clientPrivateKey) {
|
|
330
|
+
if (clientPrivateKey !== undefined) {
|
|
331
|
+
throw new TypeError(`"options.clientPrivateKey" property must not be provided when ${clientAuthMethod} client authentication method is used.`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function assertNoClientSecret(clientAuthMethod, clientSecret) {
|
|
335
|
+
if (clientSecret !== undefined) {
|
|
336
|
+
throw new TypeError(`"client.client_secret" property must not be provided when ${clientAuthMethod} client authentication method is used.`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
async function clientAuthentication(as, client, body, headers, clientPrivateKey) {
|
|
340
|
+
body.delete("client_secret");
|
|
341
|
+
body.delete("client_assertion_type");
|
|
342
|
+
body.delete("client_assertion");
|
|
343
|
+
switch (client.token_endpoint_auth_method) {
|
|
344
|
+
case undefined:
|
|
345
|
+
case "client_secret_basic": {
|
|
346
|
+
assertNoClientPrivateKey("client_secret_basic", clientPrivateKey);
|
|
347
|
+
headers.set("authorization", clientSecretBasic(client.client_id, assertClientSecret(client.client_secret)));
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
case "client_secret_post": {
|
|
351
|
+
assertNoClientPrivateKey("client_secret_post", clientPrivateKey);
|
|
352
|
+
body.set("client_id", client.client_id);
|
|
353
|
+
body.set("client_secret", assertClientSecret(client.client_secret));
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
case "private_key_jwt": {
|
|
357
|
+
assertNoClientSecret("private_key_jwt", client.client_secret);
|
|
358
|
+
if (clientPrivateKey === undefined) {
|
|
359
|
+
throw new TypeError('"options.clientPrivateKey" must be provided when "client.token_endpoint_auth_method" is "private_key_jwt"');
|
|
360
|
+
}
|
|
361
|
+
const {
|
|
362
|
+
key,
|
|
363
|
+
kid
|
|
364
|
+
} = getKeyAndKid(clientPrivateKey);
|
|
365
|
+
if (!isPrivateKey(key)) {
|
|
366
|
+
throw new TypeError('"options.clientPrivateKey.key" must be a private CryptoKey');
|
|
367
|
+
}
|
|
368
|
+
body.set("client_id", client.client_id);
|
|
369
|
+
body.set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
|
|
370
|
+
body.set("client_assertion", await privateKeyJwt(as, client, key, kid));
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
case "tls_client_auth":
|
|
374
|
+
case "self_signed_tls_client_auth":
|
|
375
|
+
case "none": {
|
|
376
|
+
assertNoClientSecret(client.token_endpoint_auth_method, client.client_secret);
|
|
377
|
+
assertNoClientPrivateKey(client.token_endpoint_auth_method, clientPrivateKey);
|
|
378
|
+
body.set("client_id", client.client_id);
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
default:
|
|
382
|
+
throw new UnsupportedOperationError("unsupported client token_endpoint_auth_method");
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
async function jwt(header, claimsSet, key) {
|
|
386
|
+
if (!key.usages.includes("sign")) {
|
|
387
|
+
throw new TypeError('CryptoKey instances used for signing assertions must include "sign" in their "usages"');
|
|
388
|
+
}
|
|
389
|
+
const input = `${b64u(buf(JSON.stringify(header)))}.${b64u(buf(JSON.stringify(claimsSet)))}`;
|
|
390
|
+
const signature = b64u(await crypto.subtle.sign(keyToSubtle(key), key, buf(input)));
|
|
391
|
+
return `${input}.${signature}`;
|
|
392
|
+
}
|
|
393
|
+
async function dpopProofJwt(headers, options, url, htm, clockSkew2, accessToken) {
|
|
394
|
+
const {
|
|
395
|
+
privateKey,
|
|
396
|
+
publicKey,
|
|
397
|
+
nonce = dpopNonces.get(url.origin)
|
|
398
|
+
} = options;
|
|
399
|
+
if (!isPrivateKey(privateKey)) {
|
|
400
|
+
throw new TypeError('"DPoP.privateKey" must be a private CryptoKey');
|
|
401
|
+
}
|
|
402
|
+
if (!isPublicKey(publicKey)) {
|
|
403
|
+
throw new TypeError('"DPoP.publicKey" must be a public CryptoKey');
|
|
404
|
+
}
|
|
405
|
+
if (nonce !== undefined && !validateString(nonce)) {
|
|
406
|
+
throw new TypeError('"DPoP.nonce" must be a non-empty string or undefined');
|
|
407
|
+
}
|
|
408
|
+
if (!publicKey.extractable) {
|
|
409
|
+
throw new TypeError('"DPoP.publicKey.extractable" must be true');
|
|
410
|
+
}
|
|
411
|
+
const now = epochTime() + clockSkew2;
|
|
412
|
+
const proof = await jwt({
|
|
413
|
+
alg: keyToJws(privateKey),
|
|
414
|
+
typ: "dpop+jwt",
|
|
415
|
+
jwk: await publicJwk(publicKey)
|
|
416
|
+
}, {
|
|
417
|
+
iat: now,
|
|
418
|
+
jti: randomBytes(),
|
|
419
|
+
htm,
|
|
420
|
+
nonce,
|
|
421
|
+
htu: `${url.origin}${url.pathname}`,
|
|
422
|
+
ath: undefined
|
|
423
|
+
}, privateKey);
|
|
424
|
+
headers.set("dpop", proof);
|
|
425
|
+
}
|
|
426
|
+
var jwkCache;
|
|
427
|
+
async function getSetPublicJwkCache(key) {
|
|
428
|
+
const {
|
|
429
|
+
kty,
|
|
430
|
+
e,
|
|
431
|
+
n,
|
|
432
|
+
x,
|
|
433
|
+
y,
|
|
434
|
+
crv
|
|
435
|
+
} = await crypto.subtle.exportKey("jwk", key);
|
|
436
|
+
const jwk = {
|
|
437
|
+
kty,
|
|
438
|
+
e,
|
|
439
|
+
n,
|
|
440
|
+
x,
|
|
441
|
+
y,
|
|
442
|
+
crv
|
|
443
|
+
};
|
|
444
|
+
jwkCache.set(key, jwk);
|
|
445
|
+
return jwk;
|
|
446
|
+
}
|
|
447
|
+
async function publicJwk(key) {
|
|
448
|
+
jwkCache || (jwkCache = /* @__PURE__ */ new WeakMap());
|
|
449
|
+
return jwkCache.get(key) || getSetPublicJwkCache(key);
|
|
450
|
+
}
|
|
451
|
+
function validateEndpoint(value, endpoint, options) {
|
|
452
|
+
if (typeof value !== "string") {
|
|
453
|
+
if (options?.[useMtlsAlias]) {
|
|
454
|
+
throw new TypeError(`"as.mtls_endpoint_aliases.${endpoint}" must be a string`);
|
|
455
|
+
}
|
|
456
|
+
throw new TypeError(`"as.${endpoint}" must be a string`);
|
|
457
|
+
}
|
|
458
|
+
return new URL(value);
|
|
459
|
+
}
|
|
460
|
+
function resolveEndpoint(as, endpoint, options) {
|
|
461
|
+
if (options?.[useMtlsAlias] && as.mtls_endpoint_aliases && endpoint in as.mtls_endpoint_aliases) {
|
|
462
|
+
return validateEndpoint(as.mtls_endpoint_aliases[endpoint], endpoint, options);
|
|
463
|
+
}
|
|
464
|
+
return validateEndpoint(as[endpoint], endpoint);
|
|
465
|
+
}
|
|
466
|
+
function isOAuth2Error(input) {
|
|
467
|
+
const value = input;
|
|
468
|
+
if (typeof value !== "object" || Array.isArray(value) || value === null) {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
return value.error !== undefined;
|
|
472
|
+
}
|
|
473
|
+
async function authenticatedRequest(as, client, method, url, body, headers, options) {
|
|
474
|
+
await clientAuthentication(as, client, body, headers, options?.clientPrivateKey);
|
|
475
|
+
headers.set("content-type", "application/x-www-form-urlencoded;charset=UTF-8");
|
|
476
|
+
return (options?.[customFetch] || fetch)(url.href, {
|
|
477
|
+
body,
|
|
478
|
+
headers: Object.fromEntries(headers.entries()),
|
|
479
|
+
method,
|
|
480
|
+
redirect: "manual",
|
|
481
|
+
signal: options?.signal ? signal(options.signal) : null
|
|
482
|
+
}).then(processDpopNonce);
|
|
483
|
+
}
|
|
484
|
+
async function tokenEndpointRequest(as, client, grantType, parameters, options) {
|
|
485
|
+
const url = resolveEndpoint(as, "token_endpoint", options);
|
|
486
|
+
parameters.set("grant_type", grantType);
|
|
487
|
+
const headers = prepareHeaders(options?.headers);
|
|
488
|
+
headers.set("accept", "application/json");
|
|
489
|
+
if (options?.DPoP !== undefined) {
|
|
490
|
+
await dpopProofJwt(headers, options.DPoP, url, "POST", getClockSkew(client));
|
|
491
|
+
}
|
|
492
|
+
return authenticatedRequest(as, client, "POST", url, parameters, headers, options);
|
|
493
|
+
}
|
|
494
|
+
async function refreshTokenGrantRequest(as, client, refreshToken, options) {
|
|
495
|
+
assertAs(as);
|
|
496
|
+
assertClient(client);
|
|
497
|
+
if (!validateString(refreshToken)) {
|
|
498
|
+
throw new TypeError('"refreshToken" must be a non-empty string');
|
|
499
|
+
}
|
|
500
|
+
const parameters = new URLSearchParams(options?.additionalParameters);
|
|
501
|
+
parameters.set("refresh_token", refreshToken);
|
|
502
|
+
return tokenEndpointRequest(as, client, "refresh_token", parameters, options);
|
|
503
|
+
}
|
|
504
|
+
var idTokenClaims = /* @__PURE__ */ new WeakMap();
|
|
505
|
+
async function processGenericAccessTokenResponse(as, client, response, ignoreIdToken = false, ignoreRefreshToken = false) {
|
|
506
|
+
assertAs(as);
|
|
507
|
+
assertClient(client);
|
|
508
|
+
if (!looseInstanceOf(response, Response)) {
|
|
509
|
+
throw new TypeError('"response" must be an instance of Response');
|
|
510
|
+
}
|
|
511
|
+
if (response.status !== 200) {
|
|
512
|
+
let err;
|
|
513
|
+
if (err = await handleOAuthBodyError(response)) {
|
|
514
|
+
return err;
|
|
515
|
+
}
|
|
516
|
+
throw new OPE('"response" is not a conform Token Endpoint response');
|
|
517
|
+
}
|
|
518
|
+
assertReadableResponse(response);
|
|
519
|
+
let json;
|
|
520
|
+
try {
|
|
521
|
+
json = await response.json();
|
|
522
|
+
} catch (cause) {
|
|
523
|
+
throw new OPE('failed to parse "response" body as JSON', {
|
|
524
|
+
cause
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
if (!isJsonObject(json)) {
|
|
528
|
+
throw new OPE('"response" body must be a top level object');
|
|
529
|
+
}
|
|
530
|
+
if (!validateString(json.access_token)) {
|
|
531
|
+
throw new OPE('"response" body "access_token" property must be a non-empty string');
|
|
532
|
+
}
|
|
533
|
+
if (!validateString(json.token_type)) {
|
|
534
|
+
throw new OPE('"response" body "token_type" property must be a non-empty string');
|
|
535
|
+
}
|
|
536
|
+
json.token_type = json.token_type.toLowerCase();
|
|
537
|
+
if (json.token_type !== "dpop" && json.token_type !== "bearer") {
|
|
538
|
+
throw new UnsupportedOperationError("unsupported `token_type` value");
|
|
539
|
+
}
|
|
540
|
+
if (json.expires_in !== undefined && (typeof json.expires_in !== "number" || json.expires_in <= 0)) {
|
|
541
|
+
throw new OPE('"response" body "expires_in" property must be a positive number');
|
|
542
|
+
}
|
|
543
|
+
if (!ignoreRefreshToken && json.refresh_token !== undefined && !validateString(json.refresh_token)) {
|
|
544
|
+
throw new OPE('"response" body "refresh_token" property must be a non-empty string');
|
|
545
|
+
}
|
|
546
|
+
if (json.scope !== undefined && typeof json.scope !== "string") {
|
|
547
|
+
throw new OPE('"response" body "scope" property must be a string');
|
|
548
|
+
}
|
|
549
|
+
if (!ignoreIdToken) {
|
|
550
|
+
if (json.id_token !== undefined && !validateString(json.id_token)) {
|
|
551
|
+
throw new OPE('"response" body "id_token" property must be a non-empty string');
|
|
552
|
+
}
|
|
553
|
+
if (json.id_token) {
|
|
554
|
+
const {
|
|
555
|
+
claims
|
|
556
|
+
} = await validateJwt(json.id_token, checkSigningAlgorithm.bind(undefined, client.id_token_signed_response_alg, as.id_token_signing_alg_values_supported), noSignatureCheck, getClockSkew(client), getClockTolerance(client)).then(validatePresence.bind(undefined, ["aud", "exp", "iat", "iss", "sub"])).then(validateIssuer.bind(undefined, as.issuer)).then(validateAudience.bind(undefined, client.client_id));
|
|
557
|
+
if (Array.isArray(claims.aud) && claims.aud.length !== 1 && claims.azp !== client.client_id) {
|
|
558
|
+
throw new OPE('unexpected ID Token "azp" (authorized party) claim value');
|
|
559
|
+
}
|
|
560
|
+
if (client.require_auth_time && typeof claims.auth_time !== "number") {
|
|
561
|
+
throw new OPE('unexpected ID Token "auth_time" (authentication time) claim value');
|
|
562
|
+
}
|
|
563
|
+
idTokenClaims.set(json, claims);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return json;
|
|
567
|
+
}
|
|
568
|
+
function validateAudience(expected, result) {
|
|
569
|
+
if (Array.isArray(result.claims.aud)) {
|
|
570
|
+
if (!result.claims.aud.includes(expected)) {
|
|
571
|
+
throw new OPE('unexpected JWT "aud" (audience) claim value');
|
|
572
|
+
}
|
|
573
|
+
} else if (result.claims.aud !== expected) {
|
|
574
|
+
throw new OPE('unexpected JWT "aud" (audience) claim value');
|
|
575
|
+
}
|
|
576
|
+
return result;
|
|
577
|
+
}
|
|
578
|
+
function validateIssuer(expected, result) {
|
|
579
|
+
if (result.claims.iss !== expected) {
|
|
580
|
+
throw new OPE('unexpected JWT "iss" (issuer) claim value');
|
|
581
|
+
}
|
|
582
|
+
return result;
|
|
583
|
+
}
|
|
584
|
+
var branded = /* @__PURE__ */ new WeakSet();
|
|
585
|
+
function brand(searchParams) {
|
|
586
|
+
branded.add(searchParams);
|
|
587
|
+
return searchParams;
|
|
588
|
+
}
|
|
589
|
+
async function authorizationCodeGrantRequest(as, client, callbackParameters, redirectUri, codeVerifier, options) {
|
|
590
|
+
assertAs(as);
|
|
591
|
+
assertClient(client);
|
|
592
|
+
if (!branded.has(callbackParameters)) {
|
|
593
|
+
throw new TypeError('"callbackParameters" must be an instance of URLSearchParams obtained from "validateAuthResponse()", or "validateJwtAuthResponse()');
|
|
594
|
+
}
|
|
595
|
+
if (!validateString(redirectUri)) {
|
|
596
|
+
throw new TypeError('"redirectUri" must be a non-empty string');
|
|
597
|
+
}
|
|
598
|
+
if (!validateString(codeVerifier)) {
|
|
599
|
+
throw new TypeError('"codeVerifier" must be a non-empty string');
|
|
600
|
+
}
|
|
601
|
+
const code = getURLSearchParameter(callbackParameters, "code");
|
|
602
|
+
if (!code) {
|
|
603
|
+
throw new OPE('no authorization code in "callbackParameters"');
|
|
604
|
+
}
|
|
605
|
+
const parameters = new URLSearchParams(options?.additionalParameters);
|
|
606
|
+
parameters.set("redirect_uri", redirectUri);
|
|
607
|
+
parameters.set("code_verifier", codeVerifier);
|
|
608
|
+
parameters.set("code", code);
|
|
609
|
+
return tokenEndpointRequest(as, client, "authorization_code", parameters, options);
|
|
610
|
+
}
|
|
611
|
+
var jwtClaimNames = {
|
|
612
|
+
aud: "audience",
|
|
613
|
+
c_hash: "code hash",
|
|
614
|
+
client_id: "client id",
|
|
615
|
+
exp: "expiration time",
|
|
616
|
+
iat: "issued at",
|
|
617
|
+
iss: "issuer",
|
|
618
|
+
jti: "jwt id",
|
|
619
|
+
nonce: "nonce",
|
|
620
|
+
s_hash: "state hash",
|
|
621
|
+
sub: "subject",
|
|
622
|
+
ath: "access token hash",
|
|
623
|
+
htm: "http method",
|
|
624
|
+
htu: "http uri",
|
|
625
|
+
cnf: "confirmation"
|
|
626
|
+
};
|
|
627
|
+
function validatePresence(required, result) {
|
|
628
|
+
for (const claim of required) {
|
|
629
|
+
if (result.claims[claim] === undefined) {
|
|
630
|
+
throw new OPE(`JWT "${claim}" (${jwtClaimNames[claim]}) claim missing`);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return result;
|
|
634
|
+
}
|
|
635
|
+
async function processAuthorizationCodeOAuth2Response(as, client, response) {
|
|
636
|
+
const result = await processGenericAccessTokenResponse(as, client, response, true);
|
|
637
|
+
if (isOAuth2Error(result)) {
|
|
638
|
+
return result;
|
|
639
|
+
}
|
|
640
|
+
if (result.id_token !== undefined) {
|
|
641
|
+
if (typeof result.id_token === "string" && result.id_token.length) {
|
|
642
|
+
throw new OPE("Unexpected ID Token returned, use processAuthorizationCodeOpenIDResponse() for OpenID Connect callback processing");
|
|
643
|
+
}
|
|
644
|
+
delete result.id_token;
|
|
645
|
+
}
|
|
646
|
+
return result;
|
|
647
|
+
}
|
|
648
|
+
async function clientCredentialsGrantRequest(as, client, parameters, options) {
|
|
649
|
+
assertAs(as);
|
|
650
|
+
assertClient(client);
|
|
651
|
+
return tokenEndpointRequest(as, client, "client_credentials", new URLSearchParams(parameters), options);
|
|
652
|
+
}
|
|
653
|
+
async function processClientCredentialsResponse(as, client, response) {
|
|
654
|
+
const result = await processGenericAccessTokenResponse(as, client, response, true, true);
|
|
655
|
+
if (isOAuth2Error(result)) {
|
|
656
|
+
return result;
|
|
657
|
+
}
|
|
658
|
+
return result;
|
|
659
|
+
}
|
|
660
|
+
async function revocationRequest(as, client, token, options) {
|
|
661
|
+
assertAs(as);
|
|
662
|
+
assertClient(client);
|
|
663
|
+
if (!validateString(token)) {
|
|
664
|
+
throw new TypeError('"token" must be a non-empty string');
|
|
665
|
+
}
|
|
666
|
+
const url = resolveEndpoint(as, "revocation_endpoint", options);
|
|
667
|
+
const body = new URLSearchParams(options?.additionalParameters);
|
|
668
|
+
body.set("token", token);
|
|
669
|
+
const headers = prepareHeaders(options?.headers);
|
|
670
|
+
headers.delete("accept");
|
|
671
|
+
return authenticatedRequest(as, client, "POST", url, body, headers, options);
|
|
672
|
+
}
|
|
673
|
+
async function processRevocationResponse(response) {
|
|
674
|
+
if (!looseInstanceOf(response, Response)) {
|
|
675
|
+
throw new TypeError('"response" must be an instance of Response');
|
|
676
|
+
}
|
|
677
|
+
if (response.status !== 200) {
|
|
678
|
+
let err;
|
|
679
|
+
if (err = await handleOAuthBodyError(response)) {
|
|
680
|
+
return err;
|
|
681
|
+
}
|
|
682
|
+
throw new OPE('"response" is not a conform Revocation Endpoint response');
|
|
683
|
+
}
|
|
684
|
+
return undefined;
|
|
685
|
+
}
|
|
686
|
+
function assertReadableResponse(response) {
|
|
687
|
+
if (response.bodyUsed) {
|
|
688
|
+
throw new TypeError('"response" body has been used already');
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
async function handleOAuthBodyError(response) {
|
|
692
|
+
if (response.status > 399 && response.status < 500) {
|
|
693
|
+
assertReadableResponse(response);
|
|
694
|
+
try {
|
|
695
|
+
const json = await response.json();
|
|
696
|
+
if (isJsonObject(json) && typeof json.error === "string" && json.error.length) {
|
|
697
|
+
if (json.error_description !== void 0 && typeof json.error_description !== "string") {
|
|
698
|
+
delete json.error_description;
|
|
699
|
+
}
|
|
700
|
+
if (json.error_uri !== void 0 && typeof json.error_uri !== "string") {
|
|
701
|
+
delete json.error_uri;
|
|
702
|
+
}
|
|
703
|
+
if (json.algs !== void 0 && typeof json.algs !== "string") {
|
|
704
|
+
delete json.algs;
|
|
705
|
+
}
|
|
706
|
+
if (json.scope !== void 0 && typeof json.scope !== "string") {
|
|
707
|
+
delete json.scope;
|
|
708
|
+
}
|
|
709
|
+
return json;
|
|
710
|
+
}
|
|
711
|
+
} catch {
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
return undefined;
|
|
715
|
+
}
|
|
716
|
+
function checkRsaKeyAlgorithm(algorithm) {
|
|
717
|
+
if (typeof algorithm.modulusLength !== "number" || algorithm.modulusLength < 2048) {
|
|
718
|
+
throw new OPE(`${algorithm.name} modulusLength must be at least 2048 bits`);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
function ecdsaHashName(namedCurve) {
|
|
722
|
+
switch (namedCurve) {
|
|
723
|
+
case "P-256":
|
|
724
|
+
return "SHA-256";
|
|
725
|
+
case "P-384":
|
|
726
|
+
return "SHA-384";
|
|
727
|
+
case "P-521":
|
|
728
|
+
return "SHA-512";
|
|
729
|
+
default:
|
|
730
|
+
throw new UnsupportedOperationError();
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
function keyToSubtle(key) {
|
|
734
|
+
switch (key.algorithm.name) {
|
|
735
|
+
case "ECDSA":
|
|
736
|
+
return {
|
|
737
|
+
name: key.algorithm.name,
|
|
738
|
+
hash: ecdsaHashName(key.algorithm.namedCurve)
|
|
739
|
+
};
|
|
740
|
+
case "RSA-PSS": {
|
|
741
|
+
checkRsaKeyAlgorithm(key.algorithm);
|
|
742
|
+
switch (key.algorithm.hash.name) {
|
|
743
|
+
case "SHA-256":
|
|
744
|
+
case "SHA-384":
|
|
745
|
+
case "SHA-512":
|
|
746
|
+
return {
|
|
747
|
+
name: key.algorithm.name,
|
|
748
|
+
saltLength: parseInt(key.algorithm.hash.name.slice(-3), 10) >> 3
|
|
749
|
+
};
|
|
750
|
+
default:
|
|
751
|
+
throw new UnsupportedOperationError();
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
case "RSASSA-PKCS1-v1_5":
|
|
755
|
+
checkRsaKeyAlgorithm(key.algorithm);
|
|
756
|
+
return key.algorithm.name;
|
|
757
|
+
case "Ed448":
|
|
758
|
+
case "Ed25519":
|
|
759
|
+
return key.algorithm.name;
|
|
760
|
+
}
|
|
761
|
+
throw new UnsupportedOperationError();
|
|
762
|
+
}
|
|
763
|
+
var noSignatureCheck = Symbol();
|
|
764
|
+
async function validateJwt(jws, checkAlg, getKey, clockSkew2, clockTolerance2) {
|
|
765
|
+
const {
|
|
766
|
+
0: protectedHeader,
|
|
767
|
+
1: payload,
|
|
768
|
+
2: encodedSignature,
|
|
769
|
+
length
|
|
770
|
+
} = jws.split(".");
|
|
771
|
+
if (length === 5) {
|
|
772
|
+
throw new UnsupportedOperationError("JWE structure JWTs are not supported");
|
|
773
|
+
}
|
|
774
|
+
if (length !== 3) {
|
|
775
|
+
throw new OPE("Invalid JWT");
|
|
776
|
+
}
|
|
777
|
+
let header;
|
|
778
|
+
try {
|
|
779
|
+
header = JSON.parse(buf(b64u(protectedHeader)));
|
|
780
|
+
} catch (cause) {
|
|
781
|
+
throw new OPE("failed to parse JWT Header body as base64url encoded JSON", {
|
|
782
|
+
cause
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
if (!isJsonObject(header)) {
|
|
786
|
+
throw new OPE("JWT Header must be a top level object");
|
|
787
|
+
}
|
|
788
|
+
checkAlg(header);
|
|
789
|
+
if (header.crit !== undefined) {
|
|
790
|
+
throw new OPE('unexpected JWT "crit" header parameter');
|
|
791
|
+
}
|
|
792
|
+
const signature = b64u(encodedSignature);
|
|
793
|
+
let key;
|
|
794
|
+
if (getKey !== noSignatureCheck) {
|
|
795
|
+
key = await getKey(header);
|
|
796
|
+
const input = `${protectedHeader}.${payload}`;
|
|
797
|
+
const verified = await crypto.subtle.verify(keyToSubtle(key), key, signature, buf(input));
|
|
798
|
+
if (!verified) {
|
|
799
|
+
throw new OPE("JWT signature verification failed");
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
let claims;
|
|
803
|
+
try {
|
|
804
|
+
claims = JSON.parse(buf(b64u(payload)));
|
|
805
|
+
} catch (cause) {
|
|
806
|
+
throw new OPE("failed to parse JWT Payload body as base64url encoded JSON", {
|
|
807
|
+
cause
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
if (!isJsonObject(claims)) {
|
|
811
|
+
throw new OPE("JWT Payload must be a top level object");
|
|
812
|
+
}
|
|
813
|
+
const now = epochTime() + clockSkew2;
|
|
814
|
+
if (claims.exp !== undefined) {
|
|
815
|
+
if (typeof claims.exp !== "number") {
|
|
816
|
+
throw new OPE('unexpected JWT "exp" (expiration time) claim type');
|
|
817
|
+
}
|
|
818
|
+
if (claims.exp <= now - clockTolerance2) {
|
|
819
|
+
throw new OPE('unexpected JWT "exp" (expiration time) claim value, timestamp is <= now()');
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
if (claims.iat !== undefined) {
|
|
823
|
+
if (typeof claims.iat !== "number") {
|
|
824
|
+
throw new OPE('unexpected JWT "iat" (issued at) claim type');
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (claims.iss !== undefined) {
|
|
828
|
+
if (typeof claims.iss !== "string") {
|
|
829
|
+
throw new OPE('unexpected JWT "iss" (issuer) claim type');
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
if (claims.nbf !== undefined) {
|
|
833
|
+
if (typeof claims.nbf !== "number") {
|
|
834
|
+
throw new OPE('unexpected JWT "nbf" (not before) claim type');
|
|
835
|
+
}
|
|
836
|
+
if (claims.nbf > now + clockTolerance2) {
|
|
837
|
+
throw new OPE('unexpected JWT "nbf" (not before) claim value, timestamp is > now()');
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (claims.aud !== undefined) {
|
|
841
|
+
if (typeof claims.aud !== "string" && !Array.isArray(claims.aud)) {
|
|
842
|
+
throw new OPE('unexpected JWT "aud" (audience) claim type');
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return {
|
|
846
|
+
header,
|
|
847
|
+
claims,
|
|
848
|
+
signature,
|
|
849
|
+
key
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
function checkSigningAlgorithm(client, issuer, header) {
|
|
853
|
+
if (client !== undefined) {
|
|
854
|
+
if (header.alg !== client) {
|
|
855
|
+
throw new OPE('unexpected JWT "alg" header parameter');
|
|
856
|
+
}
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
if (Array.isArray(issuer)) {
|
|
860
|
+
if (!issuer.includes(header.alg)) {
|
|
861
|
+
throw new OPE('unexpected JWT "alg" header parameter');
|
|
862
|
+
}
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
if (header.alg !== "RS256") {
|
|
866
|
+
throw new OPE('unexpected JWT "alg" header parameter');
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
function getURLSearchParameter(parameters, name) {
|
|
870
|
+
const {
|
|
871
|
+
0: value,
|
|
872
|
+
length
|
|
873
|
+
} = parameters.getAll(name);
|
|
874
|
+
if (length > 1) {
|
|
875
|
+
throw new OPE(`"${name}" parameter must be provided only once`);
|
|
876
|
+
}
|
|
877
|
+
return value;
|
|
878
|
+
}
|
|
879
|
+
var skipStateCheck = Symbol();
|
|
880
|
+
var expectNoState = Symbol();
|
|
881
|
+
function validateAuthResponse(as, client, parameters, expectedState) {
|
|
882
|
+
assertAs(as);
|
|
883
|
+
assertClient(client);
|
|
884
|
+
if (parameters instanceof URL) {
|
|
885
|
+
parameters = parameters.searchParams;
|
|
886
|
+
}
|
|
887
|
+
if (!(parameters instanceof URLSearchParams)) {
|
|
888
|
+
throw new TypeError('"parameters" must be an instance of URLSearchParams, or URL');
|
|
889
|
+
}
|
|
890
|
+
if (getURLSearchParameter(parameters, "response")) {
|
|
891
|
+
throw new OPE('"parameters" contains a JARM response, use validateJwtAuthResponse() instead of validateAuthResponse()');
|
|
892
|
+
}
|
|
893
|
+
const iss = getURLSearchParameter(parameters, "iss");
|
|
894
|
+
const state = getURLSearchParameter(parameters, "state");
|
|
895
|
+
if (!iss && as.authorization_response_iss_parameter_supported) {
|
|
896
|
+
throw new OPE('response parameter "iss" (issuer) missing');
|
|
897
|
+
}
|
|
898
|
+
if (iss && iss !== as.issuer) {
|
|
899
|
+
throw new OPE('unexpected "iss" (issuer) response parameter value');
|
|
900
|
+
}
|
|
901
|
+
switch (expectedState) {
|
|
902
|
+
case undefined:
|
|
903
|
+
case expectNoState:
|
|
904
|
+
if (state !== undefined) {
|
|
905
|
+
throw new OPE('unexpected "state" response parameter encountered');
|
|
906
|
+
}
|
|
907
|
+
break;
|
|
908
|
+
case skipStateCheck:
|
|
909
|
+
break;
|
|
910
|
+
default:
|
|
911
|
+
if (!validateString(expectedState)) {
|
|
912
|
+
throw new OPE('"expectedState" must be a non-empty string');
|
|
913
|
+
}
|
|
914
|
+
if (state === undefined) {
|
|
915
|
+
throw new OPE('response parameter "state" missing');
|
|
916
|
+
}
|
|
917
|
+
if (state !== expectedState) {
|
|
918
|
+
throw new OPE('unexpected "state" response parameter value');
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
const error = getURLSearchParameter(parameters, "error");
|
|
922
|
+
if (error) {
|
|
923
|
+
return {
|
|
924
|
+
error,
|
|
925
|
+
error_description: getURLSearchParameter(parameters, "error_description"),
|
|
926
|
+
error_uri: getURLSearchParameter(parameters, "error_uri")
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
const id_token = getURLSearchParameter(parameters, "id_token");
|
|
930
|
+
const token = getURLSearchParameter(parameters, "token");
|
|
931
|
+
if (id_token !== undefined || token !== undefined) {
|
|
932
|
+
throw new UnsupportedOperationError("implicit and hybrid flows are not supported");
|
|
933
|
+
}
|
|
934
|
+
return brand(new URLSearchParams(parameters));
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// src/throwIfError.ts
|
|
14
938
|
function throwIfError(result) {
|
|
15
|
-
if (
|
|
939
|
+
if (isOAuth2Error(result)) {
|
|
16
940
|
throw new Error(`Failed to get token: ${result.error}`);
|
|
17
941
|
}
|
|
18
942
|
return result;
|
|
@@ -56,7 +980,7 @@ function readSession(client) {
|
|
|
56
980
|
globalThis.sessionStorage?.getItem(localStorageKey(client)) ?? "{}"
|
|
57
981
|
);
|
|
58
982
|
}
|
|
59
|
-
function common(client, as, _signIn, oauthHttpOptions, refresh, refreshTokenMarker) {
|
|
983
|
+
function common(client, as, _signIn, oauthHttpOptions, refresh, refreshTokenMarker, scopes) {
|
|
60
984
|
let token;
|
|
61
985
|
const eventTarget = new typescriptEventTarget.TypedEventTarget();
|
|
62
986
|
function makeTokenAndSaveRefresh(resp, type) {
|
|
@@ -65,10 +989,11 @@ function common(client, as, _signIn, oauthHttpOptions, refresh, refreshTokenMark
|
|
|
65
989
|
expires_in,
|
|
66
990
|
access_token
|
|
67
991
|
} = resp;
|
|
68
|
-
!(expires_in != null) ? process.env.NODE_ENV !== "production" ? invariant2__default.default(false) : invariant2__default.default(false) :
|
|
992
|
+
!(expires_in != null) ? process.env.NODE_ENV !== "production" ? invariant2__default.default(false) : invariant2__default.default(false) : undefined;
|
|
69
993
|
saveLocal(client, {
|
|
70
994
|
refresh_token,
|
|
71
|
-
refreshTokenMarker
|
|
995
|
+
refreshTokenMarker,
|
|
996
|
+
requestedScopes: scopes
|
|
72
997
|
});
|
|
73
998
|
token = {
|
|
74
999
|
refresh_token,
|
|
@@ -92,11 +1017,11 @@ function common(client, as, _signIn, oauthHttpOptions, refresh, refreshTokenMark
|
|
|
92
1017
|
}
|
|
93
1018
|
}
|
|
94
1019
|
async function signOut() {
|
|
95
|
-
!token ? process.env.NODE_ENV !== "production" ? invariant2__default.default(false, "not signed in") : invariant2__default.default(false) :
|
|
96
|
-
const result = await
|
|
1020
|
+
!token ? process.env.NODE_ENV !== "production" ? invariant2__default.default(false, "not signed in") : invariant2__default.default(false) : undefined;
|
|
1021
|
+
const result = await processRevocationResponse(await revocationRequest(as, client, token.access_token, oauthHttpOptions));
|
|
97
1022
|
rmTimeout();
|
|
98
1023
|
removeLocal(client);
|
|
99
|
-
token =
|
|
1024
|
+
token = undefined;
|
|
100
1025
|
throwIfError(result);
|
|
101
1026
|
eventTarget.dispatchTypedEvent("signOut", new Event("signOut"));
|
|
102
1027
|
}
|
|
@@ -109,14 +1034,14 @@ function common(client, as, _signIn, oauthHttpOptions, refresh, refreshTokenMark
|
|
|
109
1034
|
pendingSignIn = _signIn();
|
|
110
1035
|
return await pendingSignIn;
|
|
111
1036
|
} finally {
|
|
112
|
-
pendingSignIn =
|
|
1037
|
+
pendingSignIn = undefined;
|
|
113
1038
|
}
|
|
114
1039
|
}
|
|
115
1040
|
eventTarget.addEventListener("signIn", restartRefreshTimer);
|
|
116
1041
|
eventTarget.addEventListener("refresh", restartRefreshTimer);
|
|
117
1042
|
function getTokenOrUndefined() {
|
|
118
1043
|
if (!token || Date.now() >= token.expires_at) {
|
|
119
|
-
return
|
|
1044
|
+
return undefined;
|
|
120
1045
|
}
|
|
121
1046
|
return token?.access_token;
|
|
122
1047
|
}
|
|
@@ -140,7 +1065,7 @@ function common(client, as, _signIn, oauthHttpOptions, refresh, refreshTokenMark
|
|
|
140
1065
|
};
|
|
141
1066
|
}
|
|
142
1067
|
function createAuthorizationServer(ctxPath, url) {
|
|
143
|
-
const issuer = `${new URL(ctxPath, url + "/")}`;
|
|
1068
|
+
const issuer = `${new URL(ctxPath, url.endsWith("/") ? url : url + "/")}`;
|
|
144
1069
|
return {
|
|
145
1070
|
token_endpoint: `${issuer}/api/oauth2/token`,
|
|
146
1071
|
authorization_endpoint: `${issuer}/api/oauth2/authorize`,
|
|
@@ -157,23 +1082,79 @@ function createConfidentialOauthClient(client_id, client_secret, url, scopes = [
|
|
|
157
1082
|
};
|
|
158
1083
|
const authServer = createAuthorizationServer(ctxPath, url);
|
|
159
1084
|
const oauthHttpOptions = {
|
|
160
|
-
[
|
|
1085
|
+
[customFetch]: fetchFn
|
|
161
1086
|
};
|
|
1087
|
+
const joinedScopes = scopes.join(" ");
|
|
162
1088
|
const {
|
|
163
1089
|
getToken,
|
|
164
1090
|
makeTokenAndSaveRefresh
|
|
165
|
-
} = common(client, authServer, _signIn, oauthHttpOptions,
|
|
1091
|
+
} = common(client, authServer, _signIn, oauthHttpOptions, undefined, undefined, joinedScopes);
|
|
166
1092
|
async function _signIn() {
|
|
167
|
-
return makeTokenAndSaveRefresh(throwIfError(await
|
|
168
|
-
scope:
|
|
1093
|
+
return makeTokenAndSaveRefresh(throwIfError(await processClientCredentialsResponse(authServer, client, await clientCredentialsGrantRequest(authServer, client, new URLSearchParams({
|
|
1094
|
+
scope: joinedScopes
|
|
169
1095
|
}), oauthHttpOptions))), "signIn");
|
|
170
1096
|
}
|
|
171
1097
|
return getToken;
|
|
172
1098
|
}
|
|
1099
|
+
|
|
1100
|
+
// ../../node_modules/.pnpm/delay@6.0.0/node_modules/delay/index.js
|
|
1101
|
+
var createAbortError = () => {
|
|
1102
|
+
const error = new Error("Delay aborted");
|
|
1103
|
+
error.name = "AbortError";
|
|
1104
|
+
return error;
|
|
1105
|
+
};
|
|
1106
|
+
var clearMethods = /* @__PURE__ */ new WeakMap();
|
|
1107
|
+
function createDelay({
|
|
1108
|
+
clearTimeout: defaultClear,
|
|
1109
|
+
setTimeout: defaultSet
|
|
1110
|
+
} = {}) {
|
|
1111
|
+
return (milliseconds, {
|
|
1112
|
+
value,
|
|
1113
|
+
signal: signal2
|
|
1114
|
+
} = {}) => {
|
|
1115
|
+
if (signal2?.aborted) {
|
|
1116
|
+
return Promise.reject(createAbortError());
|
|
1117
|
+
}
|
|
1118
|
+
let timeoutId;
|
|
1119
|
+
let settle;
|
|
1120
|
+
let rejectFunction;
|
|
1121
|
+
const clear = defaultClear ?? clearTimeout;
|
|
1122
|
+
const signalListener = () => {
|
|
1123
|
+
clear(timeoutId);
|
|
1124
|
+
rejectFunction(createAbortError());
|
|
1125
|
+
};
|
|
1126
|
+
const cleanup = () => {
|
|
1127
|
+
if (signal2) {
|
|
1128
|
+
signal2.removeEventListener("abort", signalListener);
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
const delayPromise = new Promise((resolve, reject) => {
|
|
1132
|
+
settle = () => {
|
|
1133
|
+
cleanup();
|
|
1134
|
+
resolve(value);
|
|
1135
|
+
};
|
|
1136
|
+
rejectFunction = reject;
|
|
1137
|
+
timeoutId = (defaultSet ?? setTimeout)(settle, milliseconds);
|
|
1138
|
+
});
|
|
1139
|
+
if (signal2) {
|
|
1140
|
+
signal2.addEventListener("abort", signalListener, {
|
|
1141
|
+
once: true
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
clearMethods.set(delayPromise, () => {
|
|
1145
|
+
clear(timeoutId);
|
|
1146
|
+
timeoutId = null;
|
|
1147
|
+
settle();
|
|
1148
|
+
});
|
|
1149
|
+
return delayPromise;
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
var delay = createDelay();
|
|
1153
|
+
var delay_default = delay;
|
|
173
1154
|
function processOptionsAndAssignDefaults(url, redirect_uri, useHistory, loginPage, postLoginPage, scopes, fetchFn, ctxPath) {
|
|
174
1155
|
let options = {};
|
|
175
1156
|
if (typeof useHistory === "object") {
|
|
176
|
-
!(!loginPage && !postLoginPage && !scopes && !fetchFn && !ctxPath) ? process.env.NODE_ENV !== "production" ? invariant2__default.default(false, "If useHistory is an object, other options should not be provided") : invariant2__default.default(false) :
|
|
1157
|
+
!(!loginPage && !postLoginPage && !scopes && !fetchFn && !ctxPath) ? process.env.NODE_ENV !== "production" ? invariant2__default.default(false, "If useHistory is an object, other options should not be provided") : invariant2__default.default(false) : undefined;
|
|
177
1158
|
options = useHistory;
|
|
178
1159
|
} else {
|
|
179
1160
|
options = {
|
|
@@ -185,13 +1166,13 @@ function processOptionsAndAssignDefaults(url, redirect_uri, useHistory, loginPag
|
|
|
185
1166
|
ctxPath
|
|
186
1167
|
};
|
|
187
1168
|
}
|
|
188
|
-
!url ? process.env.NODE_ENV !== "production" ? invariant2__default.default(false, "url is required") : invariant2__default.default(false) :
|
|
189
|
-
!redirect_uri ? process.env.NODE_ENV !== "production" ? invariant2__default.default(false, "redirectUrl is required") : invariant2__default.default(false) :
|
|
1169
|
+
!url ? process.env.NODE_ENV !== "production" ? invariant2__default.default(false, "url is required") : invariant2__default.default(false) : undefined;
|
|
1170
|
+
!redirect_uri ? process.env.NODE_ENV !== "production" ? invariant2__default.default(false, "redirectUrl is required") : invariant2__default.default(false) : undefined;
|
|
190
1171
|
return {
|
|
191
1172
|
useHistory: options.useHistory ?? true,
|
|
192
1173
|
loginPage: options.loginPage,
|
|
193
1174
|
postLoginPage: options.postLoginPage || window.location.toString(),
|
|
194
|
-
|
|
1175
|
+
joinedScopes: [...options.scopes ?? ["api:read-data", "api:write-data"]].sort().join(" "),
|
|
195
1176
|
fetchFn: options.fetchFn ?? globalThis.fetch,
|
|
196
1177
|
ctxPath: options.ctxPath ?? "multipass",
|
|
197
1178
|
refreshTokenMarker: options.refreshTokenMarker
|
|
@@ -201,11 +1182,12 @@ function processOptionsAndAssignDefaults(url, redirect_uri, useHistory, loginPag
|
|
|
201
1182
|
// src/createPublicOauthClient.ts
|
|
202
1183
|
function createPublicOauthClient(client_id, url, redirect_uri, useHistory, loginPage, postLoginPage, scopes, fetchFn, ctxPath) {
|
|
203
1184
|
let refreshTokenMarker;
|
|
1185
|
+
let joinedScopes;
|
|
204
1186
|
({
|
|
205
1187
|
useHistory,
|
|
206
1188
|
loginPage,
|
|
207
1189
|
postLoginPage,
|
|
208
|
-
|
|
1190
|
+
joinedScopes,
|
|
209
1191
|
fetchFn,
|
|
210
1192
|
ctxPath,
|
|
211
1193
|
refreshTokenMarker
|
|
@@ -216,31 +1198,33 @@ function createPublicOauthClient(client_id, url, redirect_uri, useHistory, login
|
|
|
216
1198
|
};
|
|
217
1199
|
const authServer = createAuthorizationServer(ctxPath, url);
|
|
218
1200
|
const oauthHttpOptions = {
|
|
219
|
-
[
|
|
1201
|
+
[customFetch]: fetchFn
|
|
220
1202
|
};
|
|
221
1203
|
const {
|
|
222
1204
|
makeTokenAndSaveRefresh,
|
|
223
1205
|
getToken
|
|
224
|
-
} = common(client, authServer, _signIn, oauthHttpOptions, maybeRefresh.bind(globalThis, true), refreshTokenMarker);
|
|
1206
|
+
} = common(client, authServer, _signIn, oauthHttpOptions, maybeRefresh.bind(globalThis, true), refreshTokenMarker, joinedScopes);
|
|
225
1207
|
const go = async (x) => {
|
|
226
1208
|
if (useHistory) {
|
|
227
1209
|
window.history.replaceState({}, "", x);
|
|
228
1210
|
return;
|
|
229
1211
|
} else window.location.assign(x);
|
|
230
|
-
await
|
|
1212
|
+
await delay_default(1e3);
|
|
231
1213
|
throw new Error("Unable to redirect");
|
|
232
1214
|
};
|
|
233
1215
|
async function maybeRefresh(expectRefreshToken) {
|
|
234
1216
|
const {
|
|
235
1217
|
refresh_token,
|
|
236
|
-
refreshTokenMarker: lastRefreshTokenMarker
|
|
1218
|
+
refreshTokenMarker: lastRefreshTokenMarker,
|
|
1219
|
+
requestedScopes: initialRequestedScopes
|
|
237
1220
|
} = readLocal(client);
|
|
238
|
-
|
|
1221
|
+
const areScopesEqual = initialRequestedScopes != null && joinedScopes === initialRequestedScopes;
|
|
1222
|
+
if (!refresh_token || lastRefreshTokenMarker !== refreshTokenMarker || !areScopesEqual) {
|
|
239
1223
|
if (expectRefreshToken) throw new Error("No refresh token found");
|
|
240
1224
|
return;
|
|
241
1225
|
}
|
|
242
1226
|
try {
|
|
243
|
-
const result = makeTokenAndSaveRefresh(throwIfError(await
|
|
1227
|
+
const result = makeTokenAndSaveRefresh(throwIfError(await processAuthorizationCodeOAuth2Response(authServer, client, await refreshTokenGrantRequest(authServer, client, refresh_token, oauthHttpOptions))), "refresh");
|
|
244
1228
|
if (result && window.location.pathname === new URL(redirect_uri).pathname) {
|
|
245
1229
|
const {
|
|
246
1230
|
oldUrl
|
|
@@ -266,7 +1250,7 @@ function createPublicOauthClient(client_id, url, redirect_uri, useHistory, login
|
|
|
266
1250
|
} = readSession(client);
|
|
267
1251
|
if (!codeVerifier) return;
|
|
268
1252
|
try {
|
|
269
|
-
const ret = makeTokenAndSaveRefresh(throwIfError(await
|
|
1253
|
+
const ret = makeTokenAndSaveRefresh(throwIfError(await processAuthorizationCodeOAuth2Response(authServer, client, await authorizationCodeGrantRequest(authServer, client, throwIfError(validateAuthResponse(authServer, client, new URL(window.location.href), state)), redirect_uri, codeVerifier, oauthHttpOptions))), "signIn");
|
|
270
1254
|
void go(oldUrl);
|
|
271
1255
|
return ret;
|
|
272
1256
|
} catch (e) {
|
|
@@ -287,8 +1271,8 @@ function createPublicOauthClient(client_id, url, redirect_uri, useHistory, login
|
|
|
287
1271
|
await go(loginPage);
|
|
288
1272
|
return;
|
|
289
1273
|
}
|
|
290
|
-
const state =
|
|
291
|
-
const codeVerifier =
|
|
1274
|
+
const state = generateRandomState();
|
|
1275
|
+
const codeVerifier = generateRandomCodeVerifier();
|
|
292
1276
|
const oldUrl = readSession(client).oldUrl ?? window.location.toString();
|
|
293
1277
|
saveLocal(client, {});
|
|
294
1278
|
saveSession(client, {
|
|
@@ -301,11 +1285,11 @@ function createPublicOauthClient(client_id, url, redirect_uri, useHistory, login
|
|
|
301
1285
|
response_type: "code",
|
|
302
1286
|
state,
|
|
303
1287
|
redirect_uri,
|
|
304
|
-
code_challenge: await
|
|
1288
|
+
code_challenge: await calculatePKCECodeChallenge(codeVerifier),
|
|
305
1289
|
code_challenge_method: "S256",
|
|
306
|
-
scope:
|
|
1290
|
+
scope: `offline_access ${joinedScopes}`
|
|
307
1291
|
})}`);
|
|
308
|
-
await
|
|
1292
|
+
await delay_default(1e3);
|
|
309
1293
|
throw new Error("Unable to redirect");
|
|
310
1294
|
};
|
|
311
1295
|
async function _signIn() {
|