@cboxdk/id-js 0.15.1 → 0.17.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 +237 -2
- package/dist/chunk-ASOQN47M.js +81 -0
- package/dist/chunk-ASOQN47M.js.map +1 -0
- package/dist/{chunk-3V6OKRWF.js → chunk-G3CD7X4U.js} +321 -90
- package/dist/chunk-G3CD7X4U.js.map +1 -0
- package/dist/chunk-WYTGLCMF.js +154 -0
- package/dist/chunk-WYTGLCMF.js.map +1 -0
- package/dist/{client-CecDIu_N.d.cts → client-CEXddiCR.d.cts} +81 -136
- package/dist/{client-CecDIu_N.d.ts → client-CdwdigzG.d.ts} +81 -136
- package/dist/index.cjs +384 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -4
- package/dist/index.d.ts +39 -4
- package/dist/index.js +63 -25
- package/dist/index.js.map +1 -1
- package/dist/nextjs.cjs +517 -42
- package/dist/nextjs.cjs.map +1 -1
- package/dist/nextjs.d.cts +29 -8
- package/dist/nextjs.d.ts +29 -8
- package/dist/nextjs.js +41 -12
- package/dist/nextjs.js.map +1 -1
- package/dist/server.cjs +220 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +105 -0
- package/dist/server.d.ts +105 -0
- package/dist/server.js +4 -0
- package/dist/server.js.map +1 -0
- package/dist/types-BYZldock.d.cts +316 -0
- package/dist/types-BYZldock.d.ts +316 -0
- package/package.json +6 -1
- package/dist/chunk-3V6OKRWF.js.map +0 -1
|
@@ -1,63 +1,6 @@
|
|
|
1
|
+
import { AuthenticationError, ConfigurationError, assertSecureIssuer, InvalidStateError, oauthError } from './chunk-ASOQN47M.js';
|
|
1
2
|
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
|
2
3
|
|
|
3
|
-
// src/errors.ts
|
|
4
|
-
var CboxIdError = class extends Error {
|
|
5
|
-
constructor(message) {
|
|
6
|
-
super(message);
|
|
7
|
-
this.name = new.target.name;
|
|
8
|
-
}
|
|
9
|
-
};
|
|
10
|
-
var ConfigurationError = class extends CboxIdError {
|
|
11
|
-
};
|
|
12
|
-
var InvalidStateError = class extends CboxIdError {
|
|
13
|
-
};
|
|
14
|
-
var AuthenticationError = class extends CboxIdError {
|
|
15
|
-
constructor(message, error, errorDescription, status, retryAfter) {
|
|
16
|
-
super(message);
|
|
17
|
-
this.error = error;
|
|
18
|
-
this.errorDescription = errorDescription;
|
|
19
|
-
this.status = status;
|
|
20
|
-
this.retryAfter = retryAfter;
|
|
21
|
-
}
|
|
22
|
-
error;
|
|
23
|
-
errorDescription;
|
|
24
|
-
status;
|
|
25
|
-
retryAfter;
|
|
26
|
-
/** Whether waiting `retryAfter` seconds and repeating the same request is worth it. */
|
|
27
|
-
get isRateLimited() {
|
|
28
|
-
return this.status === 429;
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
async function oauthError(response, fallback) {
|
|
32
|
-
const body = await response.text().catch(() => "");
|
|
33
|
-
let code;
|
|
34
|
-
let description;
|
|
35
|
-
try {
|
|
36
|
-
const parsed = JSON.parse(body);
|
|
37
|
-
if (typeof parsed === "object" && parsed !== null) {
|
|
38
|
-
const { error, error_description: errorDescription } = parsed;
|
|
39
|
-
code = typeof error === "string" ? error : void 0;
|
|
40
|
-
description = typeof errorDescription === "string" ? errorDescription : void 0;
|
|
41
|
-
}
|
|
42
|
-
} catch {
|
|
43
|
-
}
|
|
44
|
-
const detail = code ?? `HTTP ${response.status}`;
|
|
45
|
-
const retryAfterHeader = response.headers.get("retry-after");
|
|
46
|
-
const retryAfter = retryAfterHeader !== null && /^\d+$/.test(retryAfterHeader.trim()) ? Number(retryAfterHeader.trim()) : void 0;
|
|
47
|
-
return new AuthenticationError(`${fallback}: ${detail}`, code, description, response.status, retryAfter);
|
|
48
|
-
}
|
|
49
|
-
var FrontendApiError = class extends CboxIdError {
|
|
50
|
-
constructor(message, code, status, retryAfter) {
|
|
51
|
-
super(message);
|
|
52
|
-
this.code = code;
|
|
53
|
-
this.status = status;
|
|
54
|
-
this.retryAfter = retryAfter;
|
|
55
|
-
}
|
|
56
|
-
code;
|
|
57
|
-
status;
|
|
58
|
-
retryAfter;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
4
|
// src/pkce.ts
|
|
62
5
|
function base64url(bytes) {
|
|
63
6
|
let binary = "";
|
|
@@ -77,6 +20,75 @@ async function challenge(verifier) {
|
|
|
77
20
|
return base64url(new Uint8Array(digest));
|
|
78
21
|
}
|
|
79
22
|
|
|
23
|
+
// src/claims.ts
|
|
24
|
+
var ORGANIZATION_ROLES = /* @__PURE__ */ new Set([
|
|
25
|
+
"owner",
|
|
26
|
+
"admin",
|
|
27
|
+
"developer",
|
|
28
|
+
"member",
|
|
29
|
+
"viewer"
|
|
30
|
+
]);
|
|
31
|
+
function organization(source) {
|
|
32
|
+
const claims = claimsOf(source);
|
|
33
|
+
const id = claims["org"];
|
|
34
|
+
if (typeof id !== "string" || id === "") {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const name = claims["org_name"];
|
|
38
|
+
const role = claims["org_role"];
|
|
39
|
+
return {
|
|
40
|
+
id,
|
|
41
|
+
name: typeof name === "string" && name !== "" ? name : null,
|
|
42
|
+
role: typeof role === "string" && ORGANIZATION_ROLES.has(role) ? role : null
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function actor(source) {
|
|
46
|
+
return parseActor(claimsOf(source)["act"], 0);
|
|
47
|
+
}
|
|
48
|
+
function isSupportSession(source) {
|
|
49
|
+
return actor(source) !== null;
|
|
50
|
+
}
|
|
51
|
+
function sessionId(source) {
|
|
52
|
+
const sid = claimsOf(source)["sid"];
|
|
53
|
+
return typeof sid === "string" && sid !== "" ? sid : null;
|
|
54
|
+
}
|
|
55
|
+
function roles(source) {
|
|
56
|
+
return stringList(claimsOf(source)["roles"]);
|
|
57
|
+
}
|
|
58
|
+
function permissions(source) {
|
|
59
|
+
return stringList(claimsOf(source)["permissions"]);
|
|
60
|
+
}
|
|
61
|
+
function hasRole(source, role) {
|
|
62
|
+
return roles(source).includes(role);
|
|
63
|
+
}
|
|
64
|
+
function hasPermission(source, permission) {
|
|
65
|
+
return permissions(source).includes(permission);
|
|
66
|
+
}
|
|
67
|
+
var MAX_ACTOR_DEPTH = 8;
|
|
68
|
+
function parseActor(claim, depth) {
|
|
69
|
+
if (claim === void 0 || claim === null) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (typeof claim !== "object" || Array.isArray(claim)) {
|
|
73
|
+
return { sub: null, actor: null };
|
|
74
|
+
}
|
|
75
|
+
const record = claim;
|
|
76
|
+
const sub = record["sub"];
|
|
77
|
+
return {
|
|
78
|
+
sub: typeof sub === "string" && sub !== "" ? sub : null,
|
|
79
|
+
actor: depth + 1 < MAX_ACTOR_DEPTH ? parseActor(record["act"], depth + 1) : null
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function stringList(claim) {
|
|
83
|
+
return Array.isArray(claim) ? claim.filter((value) => typeof value === "string" && value !== "") : [];
|
|
84
|
+
}
|
|
85
|
+
function claimsOf(source) {
|
|
86
|
+
return isUser(source) ? source.claims : source;
|
|
87
|
+
}
|
|
88
|
+
function isUser(source) {
|
|
89
|
+
return typeof source["claims"] === "object" && source["claims"] !== null && typeof source["accessToken"] === "string";
|
|
90
|
+
}
|
|
91
|
+
|
|
80
92
|
// src/vault.ts
|
|
81
93
|
var VaultClient = class {
|
|
82
94
|
constructor(issuer, token, timeoutMs = 1e4) {
|
|
@@ -276,26 +288,6 @@ function trimSlash(value) {
|
|
|
276
288
|
return value.replace(/\/+$/, "");
|
|
277
289
|
}
|
|
278
290
|
|
|
279
|
-
// src/issuer.ts
|
|
280
|
-
function assertSecureIssuer(issuer) {
|
|
281
|
-
let url;
|
|
282
|
-
try {
|
|
283
|
-
url = new URL(issuer);
|
|
284
|
-
} catch {
|
|
285
|
-
throw new ConfigurationError("Cbox ID config `issuer` is not a valid URL.");
|
|
286
|
-
}
|
|
287
|
-
if (url.protocol === "https:") {
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]" || url.hostname === "::1";
|
|
291
|
-
if (url.protocol === "http:" && loopback) {
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
throw new ConfigurationError(
|
|
295
|
-
`Cbox ID config \`issuer\` must be https (got ${url.protocol}//${url.hostname}).`
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
291
|
// src/client.ts
|
|
300
292
|
var CboxIdClient = class {
|
|
301
293
|
constructor(config) {
|
|
@@ -306,9 +298,6 @@ var CboxIdClient = class {
|
|
|
306
298
|
if (!config.clientId) {
|
|
307
299
|
throw new ConfigurationError("Cbox ID config `clientId` is required.");
|
|
308
300
|
}
|
|
309
|
-
if (!config.redirectUri) {
|
|
310
|
-
throw new ConfigurationError("Cbox ID config `redirectUri` is required.");
|
|
311
|
-
}
|
|
312
301
|
assertSecureIssuer(config.issuer);
|
|
313
302
|
this.discovery = new Discovery(
|
|
314
303
|
config.issuer,
|
|
@@ -325,21 +314,29 @@ var CboxIdClient = class {
|
|
|
325
314
|
* to {@link authenticate} on the callback.
|
|
326
315
|
*/
|
|
327
316
|
async createAuthorizationRequest(options = {}) {
|
|
317
|
+
const prompt = promptValue(options.prompt);
|
|
318
|
+
assertOrganizationOptions(options, prompt);
|
|
328
319
|
const codeVerifier = createVerifier();
|
|
329
320
|
const state = options.state ?? randomToken(16);
|
|
330
321
|
const nonce = randomToken(16);
|
|
322
|
+
const redirectUri = options.redirectUri ?? this.config.redirectUri;
|
|
323
|
+
if (redirectUri === void 0 || redirectUri === "") {
|
|
324
|
+
throw new ConfigurationError(
|
|
325
|
+
"A `redirectUri` is required to start the browser sign-in flow. Set it in the config, or pass it to createAuthorizationRequest."
|
|
326
|
+
);
|
|
327
|
+
}
|
|
331
328
|
const params = new URLSearchParams({
|
|
332
329
|
response_type: "code",
|
|
333
330
|
client_id: this.config.clientId,
|
|
334
|
-
redirect_uri:
|
|
331
|
+
redirect_uri: redirectUri,
|
|
335
332
|
scope: (options.scopes ?? this.scopes()).join(" "),
|
|
336
333
|
state,
|
|
337
334
|
nonce,
|
|
338
335
|
code_challenge: await challenge(codeVerifier),
|
|
339
336
|
code_challenge_method: "S256"
|
|
340
337
|
});
|
|
341
|
-
if (
|
|
342
|
-
params.set("prompt",
|
|
338
|
+
if (prompt.length > 0) {
|
|
339
|
+
params.set("prompt", prompt.join(" "));
|
|
343
340
|
}
|
|
344
341
|
if (options.loginHint) {
|
|
345
342
|
params.set("login_hint", options.loginHint);
|
|
@@ -347,6 +344,12 @@ var CboxIdClient = class {
|
|
|
347
344
|
if (typeof options.maxAge === "number") {
|
|
348
345
|
params.set("max_age", String(options.maxAge));
|
|
349
346
|
}
|
|
347
|
+
if (options.organization !== void 0) {
|
|
348
|
+
params.set("organization", options.organization);
|
|
349
|
+
}
|
|
350
|
+
if (options.organizationHint !== void 0) {
|
|
351
|
+
params.set("organization_hint", options.organizationHint);
|
|
352
|
+
}
|
|
350
353
|
const endpoint = await this.discovery.endpoint("authorization_endpoint");
|
|
351
354
|
return {
|
|
352
355
|
url: `${endpoint}?${params.toString()}`,
|
|
@@ -355,9 +358,27 @@ var CboxIdClient = class {
|
|
|
355
358
|
nonce,
|
|
356
359
|
// Carried through so authenticate() can hold the instance to it. Only present
|
|
357
360
|
// when requested (exactOptionalPropertyTypes).
|
|
358
|
-
...typeof options.maxAge === "number" ? { maxAge: options.maxAge } : {}
|
|
361
|
+
...typeof options.maxAge === "number" ? { maxAge: options.maxAge } : {},
|
|
362
|
+
...options.organization !== void 0 ? { organization: options.organization } : {}
|
|
359
363
|
};
|
|
360
364
|
}
|
|
365
|
+
/**
|
|
366
|
+
* Switch the signed-in person to another organization: a new authorization bound to
|
|
367
|
+
* `organizationId`. Persist and redirect exactly as for
|
|
368
|
+
* {@link createAuthorizationRequest} — it is one, with `organization` set.
|
|
369
|
+
*
|
|
370
|
+
* Cbox ID already holds the person's session, so this is normally a redirect there and
|
|
371
|
+
* straight back with no sign-in form. The tokens that come back carry the new `org`,
|
|
372
|
+
* `org_role`, `roles` and `permissions`; replace your session with them rather than
|
|
373
|
+
* patching the old one, because every one of those can differ between organizations.
|
|
374
|
+
*
|
|
375
|
+
* A person who is not (or is no longer) an active member of that organization comes
|
|
376
|
+
* back with `error=access_denied`, which {@link authenticate} throws as an
|
|
377
|
+
* `AuthenticationError` with `error === 'access_denied'`.
|
|
378
|
+
*/
|
|
379
|
+
switchOrganization(organizationId, options = {}) {
|
|
380
|
+
return this.createAuthorizationRequest({ ...options, organization: organizationId });
|
|
381
|
+
}
|
|
361
382
|
/**
|
|
362
383
|
* Complete login on your callback route: verify state, exchange the code with the
|
|
363
384
|
* PKCE verifier, verify the id_token, and return the authenticated user.
|
|
@@ -372,7 +393,9 @@ var CboxIdClient = class {
|
|
|
372
393
|
}
|
|
373
394
|
if (params.error) {
|
|
374
395
|
throw new AuthenticationError(
|
|
375
|
-
`Cbox ID returned an error: ${params.error}${params.error_description ? ` (${params.error_description})` : ""}
|
|
396
|
+
`Cbox ID returned an error: ${params.error}${params.error_description ? ` (${params.error_description})` : ""}`,
|
|
397
|
+
params.error,
|
|
398
|
+
params.error_description ?? void 0
|
|
376
399
|
);
|
|
377
400
|
}
|
|
378
401
|
if (!params.code) {
|
|
@@ -394,6 +417,24 @@ var CboxIdClient = class {
|
|
|
394
417
|
"A max_age was requested but no id_token was returned, so the authentication age could not be verified."
|
|
395
418
|
);
|
|
396
419
|
}
|
|
420
|
+
const user = await this.identityFrom(tokens, verified);
|
|
421
|
+
if (stored.organization !== void 0 && user.organizationId !== stored.organization) {
|
|
422
|
+
throw new AuthenticationError(
|
|
423
|
+
`The sign-in was bound to organization ${stored.organization}, but the tokens are for ${user.organizationId ?? "no organization"}. The instance may not support organization selection.`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
return user;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Turn a token response into the verified {@link CboxUser} every flow returns.
|
|
430
|
+
*
|
|
431
|
+
* Shared by the browser flow and the device flow rather than written twice: the checks
|
|
432
|
+
* in here — the UserInfo subject matching the id_token's, the merge that lets UserInfo
|
|
433
|
+
* enrich but never overwrite signed claims — are the ones that decide whether an
|
|
434
|
+
* identity is trustworthy. A second copy is a second place for one of them to be
|
|
435
|
+
* quietly missing, and the flow it was missing from would look exactly as correct.
|
|
436
|
+
*/
|
|
437
|
+
async identityFrom(tokens, verified) {
|
|
397
438
|
const profile = await this.userinfo(tokens.access_token);
|
|
398
439
|
const verifiedSub = verified["sub"];
|
|
399
440
|
const profileSub = profile["sub"];
|
|
@@ -411,6 +452,11 @@ var CboxIdClient = class {
|
|
|
411
452
|
email: typeof claims["email"] === "string" ? claims["email"] : null,
|
|
412
453
|
name: typeof claims["name"] === "string" ? claims["name"] : null,
|
|
413
454
|
organizationId: typeof claims["org"] === "string" ? claims["org"] : null,
|
|
455
|
+
organization: organization(claims),
|
|
456
|
+
roles: roles(claims),
|
|
457
|
+
permissions: permissions(claims),
|
|
458
|
+
actor: actor(claims),
|
|
459
|
+
sessionId: sessionId(claims),
|
|
414
460
|
// Only present when the instance emitted the claim (exactOptionalPropertyTypes).
|
|
415
461
|
...organizations ? { organizations } : {},
|
|
416
462
|
claims,
|
|
@@ -420,6 +466,118 @@ var CboxIdClient = class {
|
|
|
420
466
|
expiresIn: typeof tokens.expires_in === "number" ? tokens.expires_in : 0
|
|
421
467
|
};
|
|
422
468
|
}
|
|
469
|
+
/**
|
|
470
|
+
* Start the device authorization grant (RFC 8628) — the flow for a program with no
|
|
471
|
+
* browser of its own: a CLI, a CI job, a container, a TV.
|
|
472
|
+
*
|
|
473
|
+
* Print `userCode` and `verificationUri`, then call
|
|
474
|
+
* {@link CboxIdClient.pollDeviceToken}. If the machine has a desktop you may also open
|
|
475
|
+
* `verificationUriComplete`, which fills the code in — but print the code anyway: the
|
|
476
|
+
* machine running your program is often not the one the person is looking at.
|
|
477
|
+
*
|
|
478
|
+
* The scopes are bounded by what the app is REGISTERED for. A device request naming one
|
|
479
|
+
* outside that ceiling is refused with `invalid_scope` rather than quietly reduced,
|
|
480
|
+
* because no browser is in front of it to notice a smaller grant.
|
|
481
|
+
*
|
|
482
|
+
* @throws ConfigurationError when the instance advertises no device endpoint.
|
|
483
|
+
*/
|
|
484
|
+
async requestDeviceAuthorization(scopes) {
|
|
485
|
+
const endpoint = await this.discovery.optionalEndpoint("device_authorization_endpoint");
|
|
486
|
+
if (endpoint === null) {
|
|
487
|
+
throw new ConfigurationError(
|
|
488
|
+
"This instance does not advertise a device_authorization_endpoint, so it does not support CLI sign-in."
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
const body = new URLSearchParams({
|
|
492
|
+
client_id: this.config.clientId,
|
|
493
|
+
scope: (scopes ?? this.scopes()).join(" ")
|
|
494
|
+
});
|
|
495
|
+
if (this.config.clientSecret) {
|
|
496
|
+
body.set("client_secret", this.config.clientSecret);
|
|
497
|
+
}
|
|
498
|
+
const response = await this.post(endpoint, body);
|
|
499
|
+
if (!response.ok) {
|
|
500
|
+
throw await oauthError(response, "Device authorization request failed");
|
|
501
|
+
}
|
|
502
|
+
const payload = await response.json();
|
|
503
|
+
const deviceCode = payload["device_code"];
|
|
504
|
+
const userCode = payload["user_code"];
|
|
505
|
+
const verificationUri = payload["verification_uri"];
|
|
506
|
+
if (typeof deviceCode !== "string" || typeof userCode !== "string" || typeof verificationUri !== "string") {
|
|
507
|
+
throw new AuthenticationError("The device authorization response was incomplete.");
|
|
508
|
+
}
|
|
509
|
+
return {
|
|
510
|
+
deviceCode,
|
|
511
|
+
userCode,
|
|
512
|
+
verificationUri,
|
|
513
|
+
verificationUriComplete: typeof payload["verification_uri_complete"] === "string" ? payload["verification_uri_complete"] : null,
|
|
514
|
+
expiresIn: typeof payload["expires_in"] === "number" ? payload["expires_in"] : 600,
|
|
515
|
+
// RFC 8628 §3.2: absent means 5 seconds. Polling faster than the server allows is
|
|
516
|
+
// answered with `slow_down`, so the default is the one the server assumes too.
|
|
517
|
+
interval: typeof payload["interval"] === "number" ? payload["interval"] : 5
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Poll until the person approves, and return them.
|
|
522
|
+
*
|
|
523
|
+
* Blocks. It honours the server's `interval`, backs off permanently by five seconds on
|
|
524
|
+
* `slow_down` (RFC 8628 §3.5), and stops on the three answers that are final: they
|
|
525
|
+
* declined, the code expired, or tokens arrived. Pass an `AbortSignal` to give up
|
|
526
|
+
* early — a CLI should let Ctrl-C work.
|
|
527
|
+
*
|
|
528
|
+
* @throws AuthenticationError when the person declines or the code expires.
|
|
529
|
+
*/
|
|
530
|
+
async pollDeviceToken(authorization, options = {}) {
|
|
531
|
+
const endpoint = await this.discovery.endpoint("token_endpoint");
|
|
532
|
+
const deadline = Date.now() + authorization.expiresIn * 1e3;
|
|
533
|
+
let intervalMs = authorization.interval * 1e3;
|
|
534
|
+
for (; ; ) {
|
|
535
|
+
if (options.signal?.aborted === true) {
|
|
536
|
+
throw new AuthenticationError("Device sign-in was cancelled.");
|
|
537
|
+
}
|
|
538
|
+
if (Date.now() > deadline) {
|
|
539
|
+
throw new AuthenticationError(
|
|
540
|
+
"The device code expired before it was approved. Start the sign-in again."
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
await sleep(intervalMs, options.signal);
|
|
544
|
+
const body = new URLSearchParams({
|
|
545
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
546
|
+
device_code: authorization.deviceCode,
|
|
547
|
+
client_id: this.config.clientId
|
|
548
|
+
});
|
|
549
|
+
if (this.config.clientSecret) {
|
|
550
|
+
body.set("client_secret", this.config.clientSecret);
|
|
551
|
+
}
|
|
552
|
+
const response = await this.post(endpoint, body);
|
|
553
|
+
if (response.ok) {
|
|
554
|
+
const tokens = await response.json();
|
|
555
|
+
if (!tokens.access_token) {
|
|
556
|
+
throw new AuthenticationError("The device token response carried no access token.");
|
|
557
|
+
}
|
|
558
|
+
const verified = tokens.id_token ? await this.verifyIdToken(tokens.id_token, "") : {};
|
|
559
|
+
return await this.identityFrom(tokens, verified);
|
|
560
|
+
}
|
|
561
|
+
const payload = await response.json().catch(() => ({}));
|
|
562
|
+
const error = typeof payload["error"] === "string" ? payload["error"] : "invalid_request";
|
|
563
|
+
if (error === "authorization_pending") {
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
if (error === "slow_down") {
|
|
567
|
+
intervalMs += 5e3;
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
if (error === "access_denied") {
|
|
571
|
+
throw new AuthenticationError("Sign-in was declined.");
|
|
572
|
+
}
|
|
573
|
+
if (error === "expired_token") {
|
|
574
|
+
throw new AuthenticationError(
|
|
575
|
+
"The device code expired before it was approved. Start the sign-in again."
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
throw await oauthError(response, "Device sign-in failed");
|
|
579
|
+
}
|
|
580
|
+
}
|
|
423
581
|
/**
|
|
424
582
|
* Exchange a refresh token for a fresh access token (OAuth 2.0 `refresh_token`
|
|
425
583
|
* grant). Cbox ID rotates refresh tokens and detects reuse, so ALWAYS persist the
|
|
@@ -467,6 +625,25 @@ var CboxIdClient = class {
|
|
|
467
625
|
const base = `${this.config.issuer.replace(/\/$/, "")}${this.accountPath()}`;
|
|
468
626
|
return returnTo ? `${base}?${new URLSearchParams({ return_to: returnTo }).toString()}` : base;
|
|
469
627
|
}
|
|
628
|
+
/**
|
|
629
|
+
* The hosted page where a person creates and revokes API keys for your API — Cbox ID's
|
|
630
|
+
* `/account/api-keys`, preselected to this app (or `clientId`). Keys made there are
|
|
631
|
+
* checked with `ApiKeyVerifier` from `@cboxdk/id-js/server`.
|
|
632
|
+
*
|
|
633
|
+
* `returnTo` becomes a link back to your app, honoured only for an origin the app
|
|
634
|
+
* registered. `organization` picks which of the person's organizations the keys act in;
|
|
635
|
+
* omitted, the page uses the one they are in.
|
|
636
|
+
*/
|
|
637
|
+
apiKeysUrl(options = {}) {
|
|
638
|
+
const params = new URLSearchParams({ client_id: options.clientId ?? this.config.clientId });
|
|
639
|
+
if (options.returnTo) {
|
|
640
|
+
params.set("return_to", options.returnTo);
|
|
641
|
+
}
|
|
642
|
+
if (options.organization) {
|
|
643
|
+
params.set("organization", options.organization);
|
|
644
|
+
}
|
|
645
|
+
return `${this.config.issuer.replace(/\/$/, "")}/account/api-keys?${params.toString()}`;
|
|
646
|
+
}
|
|
470
647
|
/**
|
|
471
648
|
* The RP-initiated logout URL, or null when the instance advertises none.
|
|
472
649
|
*
|
|
@@ -606,10 +783,16 @@ var CboxIdClient = class {
|
|
|
606
783
|
return new VaultClient(this.config.issuer, accessToken, this.config.timeoutMs ?? 1e4);
|
|
607
784
|
}
|
|
608
785
|
async exchange(code, verifier, redirectUri) {
|
|
786
|
+
const callback = redirectUri ?? this.config.redirectUri;
|
|
787
|
+
if (callback === void 0 || callback === "") {
|
|
788
|
+
throw new ConfigurationError(
|
|
789
|
+
"A `redirectUri` is required to exchange an authorization code. It must be the same one the authorization request used."
|
|
790
|
+
);
|
|
791
|
+
}
|
|
609
792
|
const body = new URLSearchParams({
|
|
610
793
|
grant_type: "authorization_code",
|
|
611
794
|
code,
|
|
612
|
-
redirect_uri:
|
|
795
|
+
redirect_uri: callback,
|
|
613
796
|
client_id: this.config.clientId,
|
|
614
797
|
code_verifier: verifier
|
|
615
798
|
});
|
|
@@ -693,6 +876,37 @@ var CboxIdClient = class {
|
|
|
693
876
|
}
|
|
694
877
|
}
|
|
695
878
|
};
|
|
879
|
+
function promptValue(prompt) {
|
|
880
|
+
if (prompt === void 0) {
|
|
881
|
+
return [];
|
|
882
|
+
}
|
|
883
|
+
const values = [...new Set(typeof prompt === "string" ? [prompt] : prompt)];
|
|
884
|
+
if (values.includes("none") && values.length > 1) {
|
|
885
|
+
throw new ConfigurationError("`prompt: 'none'` cannot be combined with another prompt value.");
|
|
886
|
+
}
|
|
887
|
+
return values;
|
|
888
|
+
}
|
|
889
|
+
function assertOrganizationOptions(options, prompt) {
|
|
890
|
+
if (options.organization === "") {
|
|
891
|
+
throw new ConfigurationError("`organization` is empty. Omit it to sign in without binding to an organization.");
|
|
892
|
+
}
|
|
893
|
+
if (options.organizationHint === "") {
|
|
894
|
+
throw new ConfigurationError("`organizationHint` is empty. Omit it when you have no organization to suggest.");
|
|
895
|
+
}
|
|
896
|
+
if (options.organization === void 0) {
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
if (prompt.includes("select_organization")) {
|
|
900
|
+
throw new ConfigurationError(
|
|
901
|
+
"`organization` binds the sign-in to one organization, so `prompt: 'select_organization'` has nothing to choose. Use `organizationHint` to preselect an organization in the picker instead."
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
if (prompt.includes("create_organization")) {
|
|
905
|
+
throw new ConfigurationError(
|
|
906
|
+
"`organization` binds the sign-in to an existing organization and `prompt: 'create_organization'` creates a new one. Send one or the other."
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
696
910
|
function timingSafeEqualString(a, b) {
|
|
697
911
|
if (a.length !== b.length) {
|
|
698
912
|
return false;
|
|
@@ -721,7 +935,24 @@ function parseOrganizations(claim) {
|
|
|
721
935
|
}
|
|
722
936
|
return orgs;
|
|
723
937
|
}
|
|
938
|
+
function sleep(ms, signal) {
|
|
939
|
+
return new Promise((resolve, reject) => {
|
|
940
|
+
if (signal?.aborted === true) {
|
|
941
|
+
reject(new AuthenticationError("Device sign-in was cancelled."));
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
const timer = setTimeout(() => {
|
|
945
|
+
signal?.removeEventListener("abort", onAbort);
|
|
946
|
+
resolve();
|
|
947
|
+
}, ms);
|
|
948
|
+
const onAbort = () => {
|
|
949
|
+
clearTimeout(timer);
|
|
950
|
+
reject(new AuthenticationError("Device sign-in was cancelled."));
|
|
951
|
+
};
|
|
952
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
953
|
+
});
|
|
954
|
+
}
|
|
724
955
|
|
|
725
|
-
export {
|
|
726
|
-
//# sourceMappingURL=chunk-
|
|
727
|
-
//# sourceMappingURL=chunk-
|
|
956
|
+
export { CboxIdClient, Discovery, VaultClient, actor, challenge, createVerifier, hasPermission, hasRole, isSupportSession, organization, permissions, randomToken, roles, sessionId, verifyWebhook };
|
|
957
|
+
//# sourceMappingURL=chunk-G3CD7X4U.js.map
|
|
958
|
+
//# sourceMappingURL=chunk-G3CD7X4U.js.map
|