@cboxdk/id-js 0.6.0 → 0.7.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 +7 -1
- package/dist/{chunk-Y6BPPCM4.js → chunk-7HLUTJIN.js} +79 -9
- package/dist/chunk-7HLUTJIN.js.map +1 -0
- package/dist/{client-ksHj7ooh.d.cts → client-DkZSRmnO.d.cts} +50 -3
- package/dist/{client-ksHj7ooh.d.ts → client-DkZSRmnO.d.ts} +50 -3
- package/dist/index.cjs +77 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/nextjs.cjs +90 -11
- package/dist/nextjs.cjs.map +1 -1
- package/dist/nextjs.d.cts +15 -4
- package/dist/nextjs.d.ts +15 -4
- package/dist/nextjs.js +14 -5
- package/dist/nextjs.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-Y6BPPCM4.js.map +0 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ hosted-identity product needs:
|
|
|
12
12
|
- **Hosted profile management** — send a signed-in user to the instance's own account
|
|
13
13
|
page (password, MFA, passkeys, sessions) and back to your app.
|
|
14
14
|
- **Back-channel calls** — machine (client-credentials) tokens, UserInfo, RFC 7662
|
|
15
|
-
introspection.
|
|
15
|
+
introspection, RFC 7009 revocation.
|
|
16
16
|
- **Webhook / action verification** — confirm an inbound `X-Cbox-Signature`.
|
|
17
17
|
|
|
18
18
|
Runs on Node, edge runtimes and the browser (built on Web Crypto and `fetch`), with a
|
|
@@ -96,8 +96,14 @@ const user = await client.authenticate({
|
|
|
96
96
|
const token = await client.machineToken({ scopes: ['reports.read'] }); // as your app
|
|
97
97
|
const claims = await client.userinfo(user.accessToken); // as a user
|
|
98
98
|
const introspection = await client.introspect(someToken); // RFC 7662
|
|
99
|
+
await client.revoke(user.refreshToken!, 'refresh_token'); // RFC 7009
|
|
99
100
|
```
|
|
100
101
|
|
|
102
|
+
Revoking a refresh token drops the whole token family — that's what "sign out
|
|
103
|
+
everywhere" needs. `machineToken`, `introspect` and `revoke` authenticate as the
|
|
104
|
+
client, so they require a `clientSecret`; `userinfo` authenticates with the user's
|
|
105
|
+
own access token and does not.
|
|
106
|
+
|
|
101
107
|
## Verify webhooks
|
|
102
108
|
|
|
103
109
|
```ts
|
|
@@ -275,7 +275,15 @@ var CboxIdClient = class {
|
|
|
275
275
|
params.set("max_age", String(options.maxAge));
|
|
276
276
|
}
|
|
277
277
|
const endpoint = await this.discovery.endpoint("authorization_endpoint");
|
|
278
|
-
return {
|
|
278
|
+
return {
|
|
279
|
+
url: `${endpoint}?${params.toString()}`,
|
|
280
|
+
state,
|
|
281
|
+
codeVerifier,
|
|
282
|
+
nonce,
|
|
283
|
+
// Carried through so authenticate() can hold the instance to it. Only present
|
|
284
|
+
// when requested (exactOptionalPropertyTypes).
|
|
285
|
+
...typeof options.maxAge === "number" ? { maxAge: options.maxAge } : {}
|
|
286
|
+
};
|
|
279
287
|
}
|
|
280
288
|
/**
|
|
281
289
|
* Complete login on your callback route: verify state, exchange the code with the
|
|
@@ -303,7 +311,11 @@ var CboxIdClient = class {
|
|
|
303
311
|
}
|
|
304
312
|
let claims = {};
|
|
305
313
|
if (tokens.id_token) {
|
|
306
|
-
claims = await this.verifyIdToken(tokens.id_token, stored.nonce);
|
|
314
|
+
claims = await this.verifyIdToken(tokens.id_token, stored.nonce, stored.maxAge);
|
|
315
|
+
} else if (typeof stored.maxAge === "number") {
|
|
316
|
+
throw new AuthenticationError(
|
|
317
|
+
"A max_age was requested but no id_token was returned, so the authentication age could not be verified."
|
|
318
|
+
);
|
|
307
319
|
}
|
|
308
320
|
claims = { ...claims, ...await this.userinfo(tokens.access_token) };
|
|
309
321
|
const sub = claims["sub"];
|
|
@@ -369,13 +381,30 @@ var CboxIdClient = class {
|
|
|
369
381
|
const base = `${this.config.issuer.replace(/\/$/, "")}${this.accountPath()}`;
|
|
370
382
|
return returnTo ? `${base}?${new URLSearchParams({ return_to: returnTo }).toString()}` : base;
|
|
371
383
|
}
|
|
372
|
-
/**
|
|
373
|
-
|
|
384
|
+
/**
|
|
385
|
+
* The RP-initiated logout URL, or null when the instance advertises none.
|
|
386
|
+
*
|
|
387
|
+
* `client_id` is always sent, even without a `returnTo`: the OP validates
|
|
388
|
+
* `post_logout_redirect_uri` against the registered allow-list of THAT client
|
|
389
|
+
* (OIDC RP-Initiated Logout 1.0 §2). With no way to name the RP the OP cannot
|
|
390
|
+
* check the list, so it silently drops the return URL and strands the user on a
|
|
391
|
+
* bare "you are signed out" page. `idTokenHint` — the user's `id_token`, if you
|
|
392
|
+
* still hold it — is the spec's other way to identify the RP, and additionally
|
|
393
|
+
* tells the OP which subject is logging out.
|
|
394
|
+
*/
|
|
395
|
+
async logoutUrl(returnTo, idTokenHint) {
|
|
374
396
|
const endpoint = await this.discovery.optionalEndpoint("end_session_endpoint");
|
|
375
397
|
if (!endpoint) {
|
|
376
398
|
return null;
|
|
377
399
|
}
|
|
378
|
-
|
|
400
|
+
const params = new URLSearchParams({ client_id: this.config.clientId });
|
|
401
|
+
if (returnTo) {
|
|
402
|
+
params.set("post_logout_redirect_uri", returnTo);
|
|
403
|
+
}
|
|
404
|
+
if (idTokenHint) {
|
|
405
|
+
params.set("id_token_hint", idTokenHint);
|
|
406
|
+
}
|
|
407
|
+
return `${endpoint}?${params.toString()}`;
|
|
379
408
|
}
|
|
380
409
|
/**
|
|
381
410
|
* A machine (client-credentials) access token for calling Cbox ID APIs as your
|
|
@@ -434,6 +463,28 @@ var CboxIdClient = class {
|
|
|
434
463
|
}
|
|
435
464
|
return await response.json();
|
|
436
465
|
}
|
|
466
|
+
/**
|
|
467
|
+
* RFC 7009 token revocation (confidential-client auth). Revokes an access or
|
|
468
|
+
* refresh token; revoking a refresh token also drops the whole token family, so
|
|
469
|
+
* this is what a real "sign out everywhere" does. Requires a secret.
|
|
470
|
+
*
|
|
471
|
+
* Per RFC 7009 the server answers 200 for an unknown or already-revoked token, so
|
|
472
|
+
* a successful call means "the token is not valid any more", not "it existed".
|
|
473
|
+
*
|
|
474
|
+
* @param tokenTypeHint which store to search first — only a hint, never required.
|
|
475
|
+
*/
|
|
476
|
+
async revoke(token, tokenTypeHint) {
|
|
477
|
+
const endpoint = await this.discovery.endpoint("revocation_endpoint");
|
|
478
|
+
const basic = btoa(`${this.config.clientId}:${this.requireSecret()}`);
|
|
479
|
+
const body = new URLSearchParams({ token });
|
|
480
|
+
if (tokenTypeHint) {
|
|
481
|
+
body.set("token_type_hint", tokenTypeHint);
|
|
482
|
+
}
|
|
483
|
+
const response = await this.post(endpoint, body, { authorization: `Basic ${basic}` });
|
|
484
|
+
if (!response.ok) {
|
|
485
|
+
throw new AuthenticationError("Revocation request failed.");
|
|
486
|
+
}
|
|
487
|
+
}
|
|
437
488
|
/** Verify a Cbox ID webhook / inline-action signature. See {@link verifyWebhook}. */
|
|
438
489
|
verifyWebhook(options) {
|
|
439
490
|
return verifyWebhook(options);
|
|
@@ -463,7 +514,7 @@ var CboxIdClient = class {
|
|
|
463
514
|
}
|
|
464
515
|
return await response.json();
|
|
465
516
|
}
|
|
466
|
-
async verifyIdToken(idToken, nonce) {
|
|
517
|
+
async verifyIdToken(idToken, nonce, maxAge) {
|
|
467
518
|
const jwksUri = await this.discovery.endpoint("jwks_uri");
|
|
468
519
|
let resolver = this.jwksByUri.get(jwksUri);
|
|
469
520
|
if (!resolver) {
|
|
@@ -474,7 +525,11 @@ var CboxIdClient = class {
|
|
|
474
525
|
try {
|
|
475
526
|
({ payload } = await jwtVerify(idToken, resolver, {
|
|
476
527
|
issuer: this.config.issuer,
|
|
477
|
-
audience: this.config.clientId
|
|
528
|
+
audience: this.config.clientId,
|
|
529
|
+
// Pin the accepted signature algorithms explicitly. `jose` already refuses
|
|
530
|
+
// `alg: none` and key-type confusion, so this is belt-and-braces — but it
|
|
531
|
+
// states the contract in code rather than relying on a library default.
|
|
532
|
+
algorithms: ["RS256", "ES256"]
|
|
478
533
|
}));
|
|
479
534
|
} catch (error) {
|
|
480
535
|
throw new AuthenticationError(`The id_token could not be verified: ${String(error)}`);
|
|
@@ -482,6 +537,21 @@ var CboxIdClient = class {
|
|
|
482
537
|
if (nonce && payload["nonce"] !== nonce) {
|
|
483
538
|
throw new AuthenticationError("The id_token nonce did not match \u2014 possible replay.");
|
|
484
539
|
}
|
|
540
|
+
if (typeof maxAge === "number") {
|
|
541
|
+
const authTime = payload["auth_time"];
|
|
542
|
+
if (typeof authTime !== "number") {
|
|
543
|
+
throw new AuthenticationError(
|
|
544
|
+
"A max_age was requested but the id_token carried no auth_time, so the authentication age could not be verified."
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
const tolerance = this.config.authTimeToleranceSeconds ?? 60;
|
|
548
|
+
const age = Math.floor(Date.now() / 1e3) - authTime;
|
|
549
|
+
if (age > maxAge + tolerance) {
|
|
550
|
+
throw new AuthenticationError(
|
|
551
|
+
`The authentication is ${age}s old but max_age required ${maxAge}s \u2014 the user did not re-authenticate.`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
485
555
|
return payload;
|
|
486
556
|
}
|
|
487
557
|
scopes() {
|
|
@@ -545,5 +615,5 @@ function parseOrganizations(claim) {
|
|
|
545
615
|
}
|
|
546
616
|
|
|
547
617
|
export { AuthenticationError, CboxIdClient, CboxIdError, ConfigurationError, Discovery, InvalidStateError, VaultClient, challenge, createVerifier, randomToken, verifyWebhook };
|
|
548
|
-
//# sourceMappingURL=chunk-
|
|
549
|
-
//# sourceMappingURL=chunk-
|
|
618
|
+
//# sourceMappingURL=chunk-7HLUTJIN.js.map
|
|
619
|
+
//# sourceMappingURL=chunk-7HLUTJIN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/pkce.ts","../src/vault.ts","../src/webhook.ts","../src/discovery.ts","../src/client.ts"],"names":["key"],"mappings":"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","file":"chunk-7HLUTJIN.js","sourcesContent":["/**\n * Base class for every error this SDK throws, so callers can `catch (e) { if (e\n * instanceof CboxIdError) ... }` in one branch.\n */\nexport class CboxIdError extends Error {\n constructor(message: string) {\n super(message);\n this.name = new.target.name;\n }\n}\n\n/** The client is misconfigured (a required option is missing or empty). */\nexport class ConfigurationError extends CboxIdError {}\n\n/**\n * The login `state` did not match what was stored — the callback is forged or\n * stale. Treat as a fresh start, not an error to surface to the user.\n */\nexport class InvalidStateError extends CboxIdError {}\n\n/**\n * Login could not be completed: the provider returned an error, the callback was\n * missing a code, or a token / id_token failed verification.\n */\nexport class AuthenticationError extends CboxIdError {}\n","/**\n * PKCE (RFC 7636) and the random values the login flow needs, built on Web Crypto\n * (`globalThis.crypto`) so the same code runs on Node, edge runtimes and browsers.\n */\n\nfunction base64url(bytes: Uint8Array): string {\n let binary = '';\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/** A URL-safe random token of `bytes` bytes (default 32), for state and nonce. */\nexport function randomToken(bytes = 32): string {\n return base64url(crypto.getRandomValues(new Uint8Array(bytes)));\n}\n\n/** A high-entropy PKCE code verifier. */\nexport function createVerifier(): string {\n return randomToken(32);\n}\n\n/** The S256 code challenge for a verifier. */\nexport async function challenge(verifier: string): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));\n return base64url(new Uint8Array(digest));\n}\n","import { AuthenticationError } from './errors.js';\n\n/** A stored vault secret's metadata (never its plaintext). */\nexport interface VaultSecretRef {\n id: string;\n name: string;\n provider: string;\n ownerType: string | null;\n ownerId: string | null;\n expiresAt: string | null;\n revoked: boolean;\n}\n\n/** A brokered credential — the plaintext, for immediate use. */\nexport interface VaultLease {\n secretId: string;\n provider: string;\n secret: string;\n expiresAt: string;\n}\n\nexport interface StoreSecretInput {\n name: string;\n provider: string;\n secret: string;\n ownerType?: 'organization' | 'user';\n ownerId?: string;\n expiresAt?: string;\n}\n\n/**\n * Client for a Cbox ID instance's Token Vault API. Bind it to an access token\n * (obtain one with {@link CboxIdClient.machineToken}, scoped `vault.manage` for\n * provisioning or `vault.lease` for redeeming). All calls are server-to-server.\n */\nexport class VaultClient {\n private readonly base: string;\n\n constructor(\n issuer: string,\n private readonly token: string,\n private readonly timeoutMs = 10_000,\n ) {\n this.base = `${issuer.replace(/\\/$/, '')}/api/v1/vault`;\n }\n\n /** Ingest a downstream credential, sealed at rest (scope `vault.manage`). */\n store(input: StoreSecretInput): Promise<VaultSecretRef> {\n return this.json<VaultSecretRef>('POST', '/secrets', {\n name: input.name,\n provider: input.provider,\n secret: input.secret,\n owner_type: input.ownerType,\n owner_id: input.ownerId,\n expires_at: input.expiresAt,\n });\n }\n\n /** Rotate a secret's value, keeping its id and grants (scope `vault.manage`). */\n rotate(secretId: string, secret: string): Promise<VaultSecretRef> {\n return this.json<VaultSecretRef>('POST', `/secrets/${encodeURIComponent(secretId)}/rotate`, { secret });\n }\n\n /** Revoke a secret permanently (scope `vault.manage`). */\n async revoke(secretId: string): Promise<void> {\n await this.request('DELETE', `/secrets/${encodeURIComponent(secretId)}`);\n }\n\n /** Authorize an agent client to lease a secret (scope `vault.manage`). */\n grant(\n secretId: string,\n clientId: string,\n maxTtlSeconds?: number,\n ): Promise<{ secretId: string; clientId: string; maxTtlSeconds: number | null }> {\n return this.json('POST', `/secrets/${encodeURIComponent(secretId)}/grants`, {\n client_id: clientId,\n max_ttl_seconds: maxTtlSeconds,\n }).then((raw) => {\n const r = raw as { secret_id: string; client_id: string; max_ttl_seconds: number | null };\n return { secretId: r.secret_id, clientId: r.client_id, maxTtlSeconds: r.max_ttl_seconds };\n });\n }\n\n /** Revoke an agent's authorization (scope `vault.manage`). */\n async revokeGrant(secretId: string, clientId: string): Promise<void> {\n await this.request(\n 'DELETE',\n `/secrets/${encodeURIComponent(secretId)}/grants/${encodeURIComponent(clientId)}`,\n );\n }\n\n /**\n * Redeem a leased credential for immediate use (scope `vault.lease`). The caller\n * is identified by the token's client; a lease with no live grant is refused.\n */\n lease(secretId: string, purpose: string): Promise<VaultLease> {\n return this.json('POST', `/secrets/${encodeURIComponent(secretId)}/lease`, { purpose }).then((raw) => {\n const r = raw as { secret_id: string; provider: string; secret: string; expires_at: string };\n return { secretId: r.secret_id, provider: r.provider, secret: r.secret, expiresAt: r.expires_at };\n });\n }\n\n private async json<T>(method: string, path: string, body: Record<string, unknown>): Promise<T> {\n const response = await this.request(method, path, body);\n return (await response.json()) as T;\n }\n\n private async request(method: string, path: string, body?: Record<string, unknown>): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n const init: RequestInit = {\n method,\n headers: {\n authorization: `Bearer ${this.token}`,\n ...(body ? { 'content-type': 'application/json' } : {}),\n },\n signal: controller.signal,\n };\n if (body) {\n init.body = JSON.stringify(body);\n }\n const response = await fetch(`${this.base}${path}`, init);\n if (!response.ok) {\n throw new AuthenticationError(`Vault ${method} ${path} failed with status ${response.status}.`);\n }\n return response;\n } finally {\n clearTimeout(timer);\n }\n }\n}\n","/**\n * Verify a Cbox ID webhook / inline-action signature. The header is\n * `X-Cbox-Signature: t={unix},v1={hex hmac}` — an HMAC-SHA256 over\n * `\"{timestamp}.{raw body}\"`, valid within a freshness window. Built on Web Crypto\n * so it runs on Node, edge and the browser.\n */\n\n/** Constant-time compare of two hex strings. */\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false;\n }\n let mismatch = 0;\n for (let i = 0; i < a.length; i++) {\n mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);\n }\n return mismatch === 0;\n}\n\nfunction toHex(bytes: ArrayBuffer): string {\n return Array.from(new Uint8Array(bytes))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\nexport interface VerifyWebhookOptions {\n /** The RAW request body — the exact bytes received, not a re-encoded copy. */\n payload: string;\n /** The value of the `X-Cbox-Signature` header. */\n signatureHeader: string | null | undefined;\n /** The signing secret issued when the webhook/action endpoint was registered. */\n secret: string;\n /** Freshness window in seconds. Defaults to 300 (5 minutes). */\n toleranceSeconds?: number;\n /** Injectable clock (unix seconds), for tests. Defaults to `Date.now()`. */\n now?: number;\n}\n\n/** Returns `true` only when the signature is present, fresh and valid. Never throws. */\nexport async function verifyWebhook(options: VerifyWebhookOptions): Promise<boolean> {\n const { payload, signatureHeader, secret } = options;\n const toleranceSeconds = options.toleranceSeconds ?? 300;\n const now = options.now ?? Math.floor(Date.now() / 1000);\n\n if (!signatureHeader) {\n return false;\n }\n\n const parts: Record<string, string> = {};\n for (const segment of signatureHeader.split(',')) {\n const [key, value] = segment.trim().split('=', 2);\n if (key) {\n parts[key] = value ?? '';\n }\n }\n\n const timestamp = parts['t'] ?? '';\n const signature = parts['v1'] ?? '';\n\n if (timestamp === '' || signature === '' || !/^\\d+$/.test(timestamp)) {\n return false;\n }\n\n if (Math.abs(now - Number(timestamp)) > toleranceSeconds) {\n return false;\n }\n\n const key = await crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n );\n const expected = toHex(\n await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(`${timestamp}.${payload}`)),\n );\n\n return timingSafeEqual(expected, signature);\n}\n","import { AuthenticationError, ConfigurationError } from './errors.js';\nimport type { DiscoveryDocument } from './types.js';\n\n/**\n * Fetches and caches an instance's OIDC discovery document. One instance of this\n * lives per {@link CboxIdClient}; the document is refetched only after the TTL.\n */\nexport class Discovery {\n private cached: { doc: DiscoveryDocument; expiresAt: number } | null = null;\n\n constructor(\n private readonly issuer: string,\n private readonly timeoutMs: number,\n private readonly cacheTtlMs: number,\n private readonly now: () => number = Date.now,\n ) {}\n\n async document(): Promise<DiscoveryDocument> {\n if (this.cached && this.cached.expiresAt > this.now()) {\n return this.cached.doc;\n }\n\n const url = `${this.issuer.replace(/\\/$/, '')}/.well-known/openid-configuration`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let doc: DiscoveryDocument;\n try {\n const response = await fetch(url, { signal: controller.signal });\n if (!response.ok) {\n throw new AuthenticationError(`Discovery request failed with status ${response.status}.`);\n }\n doc = (await response.json()) as DiscoveryDocument;\n } catch (error) {\n if (error instanceof AuthenticationError) {\n throw error;\n }\n throw new AuthenticationError(`Discovery request failed: ${String(error)}`);\n } finally {\n clearTimeout(timer);\n }\n\n if (typeof doc.issuer !== 'string' || doc.issuer === '') {\n throw new AuthenticationError('The discovery document was missing an issuer.');\n }\n\n this.cached = { doc, expiresAt: this.now() + this.cacheTtlMs };\n return doc;\n }\n\n /** A required endpoint from the discovery document. */\n async endpoint(name: keyof DiscoveryDocument): Promise<string> {\n const value = (await this.document())[name];\n if (typeof value !== 'string' || value === '') {\n throw new ConfigurationError(`The instance does not advertise a ${name}.`);\n }\n return value;\n }\n\n /** An optional endpoint, or null when the instance advertises none. */\n async optionalEndpoint(name: keyof DiscoveryDocument): Promise<string | null> {\n const value = (await this.document())[name];\n return typeof value === 'string' && value !== '' ? value : null;\n }\n}\n","import { createRemoteJWKSet, jwtVerify, type JWTPayload } from 'jose';\nimport { Discovery } from './discovery.js';\nimport { AuthenticationError, ConfigurationError, InvalidStateError } from './errors.js';\nimport { challenge, createVerifier, randomToken } from './pkce.js';\nimport type {\n AuthorizationRequest,\n CboxIdConfig,\n CboxOrganization,\n CboxUser,\n RefreshedTokens,\n TokenResponse,\n TokenTypeHint,\n} from './types.js';\nimport { VaultClient } from './vault.js';\nimport { verifyWebhook, type VerifyWebhookOptions } from './webhook.js';\n\ntype JwksResolver = ReturnType<typeof createRemoteJWKSet>;\n\n/** Query parameters as they arrive on your callback route. */\nexport interface CallbackParams {\n code?: string | null;\n state?: string | null;\n error?: string | null;\n error_description?: string | null;\n}\n\n/** The values you persisted from {@link CboxIdClient.createAuthorizationRequest}. */\nexport interface StoredAuthState {\n state: string;\n codeVerifier: string;\n nonce: string;\n /** The `maxAge` this login demanded, if any. See {@link AuthorizationRequest.maxAge}. */\n maxAge?: number;\n}\n\n/**\n * Turnkey Cbox ID client for JavaScript/TypeScript. It speaks standard OpenID\n * Connect against a Cbox ID instance — so integrating is a redirect and a callback,\n * not a rewrite — and adds the conveniences a hosted-identity product needs: a\n * redirect to the instance's hosted profile page, and back-channel helpers (machine\n * tokens, userinfo, introspection, webhook verification).\n *\n * Login is hardened by default: PKCE (S256), a CSRF state check, a nonce, and full\n * id_token signature + issuer + audience verification against the instance's JWKS\n * (via `jose`).\n *\n * The class is framework-agnostic: {@link createAuthorizationRequest} hands you the\n * `state`, `codeVerifier` and `nonce` to persist however you like, and\n * {@link authenticate} takes them back. For Next.js, the `@cboxdk/id-js/nextjs`\n * entry wires this to cookies for you.\n */\nexport class CboxIdClient {\n private readonly discovery: Discovery;\n private readonly jwksByUri = new Map<string, JwksResolver>();\n\n constructor(private readonly config: CboxIdConfig) {\n if (!config.issuer) {\n throw new ConfigurationError('Cbox ID config `issuer` is required.');\n }\n if (!config.clientId) {\n throw new ConfigurationError('Cbox ID config `clientId` is required.');\n }\n if (!config.redirectUri) {\n throw new ConfigurationError('Cbox ID config `redirectUri` is required.');\n }\n this.discovery = new Discovery(\n config.issuer,\n config.timeoutMs ?? 10_000,\n config.cacheTtlMs ?? 3_600_000,\n );\n }\n\n /**\n * Begin login. Returns the authorize URL plus the `state`, `codeVerifier` and\n * `nonce` — persist those (signed, httpOnly cookies are ideal) and hand them back\n * to {@link authenticate} on the callback.\n */\n async createAuthorizationRequest(options: {\n scopes?: string[];\n redirectUri?: string;\n state?: string;\n prompt?: string;\n loginHint?: string;\n maxAge?: number;\n } = {}): Promise<AuthorizationRequest> {\n const codeVerifier = createVerifier();\n const state = options.state ?? randomToken(16);\n const nonce = randomToken(16);\n\n const params = new URLSearchParams({\n response_type: 'code',\n client_id: this.config.clientId,\n redirect_uri: options.redirectUri ?? this.config.redirectUri,\n scope: (options.scopes ?? this.scopes()).join(' '),\n state,\n nonce,\n code_challenge: await challenge(codeVerifier),\n code_challenge_method: 'S256',\n });\n if (options.prompt) {\n params.set('prompt', options.prompt);\n }\n if (options.loginHint) {\n params.set('login_hint', options.loginHint);\n }\n if (typeof options.maxAge === 'number') {\n params.set('max_age', String(options.maxAge));\n }\n\n const endpoint = await this.discovery.endpoint('authorization_endpoint');\n return {\n url: `${endpoint}?${params.toString()}`,\n state,\n codeVerifier,\n nonce,\n // Carried through so authenticate() can hold the instance to it. Only present\n // when requested (exactOptionalPropertyTypes).\n ...(typeof options.maxAge === 'number' ? { maxAge: options.maxAge } : {}),\n };\n }\n\n /**\n * Complete login on your callback route: verify state, exchange the code with the\n * PKCE verifier, verify the id_token, and return the authenticated user.\n *\n * @throws InvalidStateError when state does not match (forged/stale request)\n * @throws AuthenticationError on any other failure\n */\n async authenticate(input: {\n params: CallbackParams;\n stored: StoredAuthState;\n redirectUri?: string;\n }): Promise<CboxUser> {\n const { params, stored } = input;\n\n if (!params.state || !stored.state || !timingSafeEqualString(params.state, stored.state)) {\n throw new InvalidStateError('The login state did not match — the request may be forged or stale.');\n }\n\n if (params.error) {\n throw new AuthenticationError(\n `Cbox ID returned an error: ${params.error}${params.error_description ? ` (${params.error_description})` : ''}`,\n );\n }\n\n if (!params.code) {\n throw new AuthenticationError('The callback was missing an authorization code.');\n }\n\n const tokens = await this.exchange(params.code, stored.codeVerifier, input.redirectUri);\n\n if (!tokens.access_token) {\n throw new AuthenticationError('No access token was returned.');\n }\n\n let claims: Record<string, unknown> = {};\n if (tokens.id_token) {\n claims = await this.verifyIdToken(tokens.id_token, stored.nonce, stored.maxAge);\n } else if (typeof stored.maxAge === 'number') {\n // No id_token means no auth_time, and therefore no evidence the demanded\n // re-authentication happened. Accepting that silently is the whole defect.\n throw new AuthenticationError(\n 'A max_age was requested but no id_token was returned, so the authentication age could not be verified.',\n );\n }\n // Enrich with userinfo (email/name/org a minimal id_token may omit).\n claims = { ...claims, ...(await this.userinfo(tokens.access_token)) };\n\n const sub = claims['sub'];\n if (typeof sub !== 'string' || sub === '') {\n throw new AuthenticationError('The verified token carried no subject.');\n }\n\n const organizations = parseOrganizations(claims['organizations']);\n\n return {\n id: sub,\n email: typeof claims['email'] === 'string' ? claims['email'] : null,\n name: typeof claims['name'] === 'string' ? claims['name'] : null,\n organizationId: typeof claims['org'] === 'string' ? claims['org'] : null,\n // Only present when the instance emitted the claim (exactOptionalPropertyTypes).\n ...(organizations ? { organizations } : {}),\n claims,\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token ?? null,\n idToken: tokens.id_token ?? null,\n expiresIn: typeof tokens.expires_in === 'number' ? tokens.expires_in : 0,\n };\n }\n\n /**\n * Exchange a refresh token for a fresh access token (OAuth 2.0 `refresh_token`\n * grant). Cbox ID rotates refresh tokens and detects reuse, so ALWAYS persist the\n * returned `refreshToken` and discard the one you passed — presenting a rotated\n * token again revokes the entire token family.\n *\n * @throws AuthenticationError when the refresh token is invalid, expired, or already rotated.\n */\n async refresh(refreshToken: string): Promise<RefreshedTokens> {\n const body = new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: this.config.clientId,\n refresh_token: refreshToken,\n });\n if (this.config.clientSecret) {\n body.set('client_secret', this.config.clientSecret);\n }\n\n const response = await this.post(await this.discovery.endpoint('token_endpoint'), body);\n if (!response.ok) {\n throw new AuthenticationError(`Token refresh failed: ${await response.text()}`);\n }\n\n const tokens = (await response.json()) as TokenResponse;\n if (!tokens.access_token) {\n throw new AuthenticationError('The refresh response carried no access token.');\n }\n\n return {\n accessToken: tokens.access_token,\n // Keep the presented token when the server does not rotate (OAuth 2.0 §6);\n // callers persist this value, so it must never come back null.\n refreshToken: tokens.refresh_token ?? refreshToken,\n idToken: tokens.id_token ?? null,\n expiresIn: typeof tokens.expires_in === 'number' ? tokens.expires_in : 0,\n scope: typeof tokens.scope === 'string' ? tokens.scope : null,\n };\n }\n\n /**\n * The URL of the instance's hosted account/profile page (self-service password,\n * MFA, passkeys, sessions). A signed-in user is authenticated there by their Cbox\n * ID session; `returnTo` is passed so the page can link back to your app.\n */\n profileUrl(returnTo?: string): string {\n const base = `${this.config.issuer.replace(/\\/$/, '')}${this.accountPath()}`;\n return returnTo ? `${base}?${new URLSearchParams({ return_to: returnTo }).toString()}` : base;\n }\n\n /**\n * The RP-initiated logout URL, or null when the instance advertises none.\n *\n * `client_id` is always sent, even without a `returnTo`: the OP validates\n * `post_logout_redirect_uri` against the registered allow-list of THAT client\n * (OIDC RP-Initiated Logout 1.0 §2). With no way to name the RP the OP cannot\n * check the list, so it silently drops the return URL and strands the user on a\n * bare \"you are signed out\" page. `idTokenHint` — the user's `id_token`, if you\n * still hold it — is the spec's other way to identify the RP, and additionally\n * tells the OP which subject is logging out.\n */\n async logoutUrl(returnTo?: string, idTokenHint?: string): Promise<string | null> {\n const endpoint = await this.discovery.optionalEndpoint('end_session_endpoint');\n if (!endpoint) {\n return null;\n }\n const params = new URLSearchParams({ client_id: this.config.clientId });\n if (returnTo) {\n params.set('post_logout_redirect_uri', returnTo);\n }\n if (idTokenHint) {\n params.set('id_token_hint', idTokenHint);\n }\n return `${endpoint}?${params.toString()}`;\n }\n\n /**\n * A machine (client-credentials) access token for calling Cbox ID APIs as your\n * app, not on a user's behalf. Requires a client secret.\n */\n async machineToken(options: { scopes?: string[]; resource?: string } = {}): Promise<string> {\n const body = new URLSearchParams({\n grant_type: 'client_credentials',\n client_id: this.config.clientId,\n client_secret: this.requireSecret(),\n });\n if (options.scopes && options.scopes.length > 0) {\n body.set('scope', options.scopes.join(' '));\n }\n if (options.resource) {\n body.set('resource', options.resource);\n }\n\n const response = await this.post(await this.discovery.endpoint('token_endpoint'), body);\n if (!response.ok) {\n throw new AuthenticationError(`Machine token request failed: ${await response.text()}`);\n }\n const json = (await response.json()) as { access_token?: unknown };\n if (typeof json.access_token !== 'string') {\n throw new AuthenticationError('The token response had no access_token.');\n }\n return json.access_token;\n }\n\n /** The OIDC userinfo claims for an access token. */\n async userinfo(accessToken: string): Promise<Record<string, unknown>> {\n const endpoint = await this.discovery.optionalEndpoint('userinfo_endpoint');\n if (!endpoint) {\n return {};\n }\n const response = await this.fetchWithTimeout(endpoint, {\n headers: { authorization: `Bearer ${accessToken}` },\n });\n if (!response.ok) {\n throw new AuthenticationError('Userinfo request failed.');\n }\n return (await response.json()) as Record<string, unknown>;\n }\n\n /**\n * RFC 7662 token introspection (confidential-client auth). Returns the raw\n * response; `active` tells you if the token is currently valid. Requires a secret.\n */\n async introspect(token: string): Promise<Record<string, unknown>> {\n const endpoint = await this.discovery.endpoint('introspection_endpoint');\n const basic = btoa(`${this.config.clientId}:${this.requireSecret()}`);\n const response = await this.post(\n endpoint,\n new URLSearchParams({ token }),\n { authorization: `Basic ${basic}` },\n );\n if (!response.ok) {\n throw new AuthenticationError('Introspection request failed.');\n }\n return (await response.json()) as Record<string, unknown>;\n }\n\n /**\n * RFC 7009 token revocation (confidential-client auth). Revokes an access or\n * refresh token; revoking a refresh token also drops the whole token family, so\n * this is what a real \"sign out everywhere\" does. Requires a secret.\n *\n * Per RFC 7009 the server answers 200 for an unknown or already-revoked token, so\n * a successful call means \"the token is not valid any more\", not \"it existed\".\n *\n * @param tokenTypeHint which store to search first — only a hint, never required.\n */\n async revoke(token: string, tokenTypeHint?: TokenTypeHint): Promise<void> {\n const endpoint = await this.discovery.endpoint('revocation_endpoint');\n const basic = btoa(`${this.config.clientId}:${this.requireSecret()}`);\n const body = new URLSearchParams({ token });\n if (tokenTypeHint) {\n body.set('token_type_hint', tokenTypeHint);\n }\n const response = await this.post(endpoint, body, { authorization: `Basic ${basic}` });\n if (!response.ok) {\n throw new AuthenticationError('Revocation request failed.');\n }\n }\n\n /** Verify a Cbox ID webhook / inline-action signature. See {@link verifyWebhook}. */\n verifyWebhook(options: VerifyWebhookOptions): Promise<boolean> {\n return verifyWebhook(options);\n }\n\n /**\n * A Token Vault client bound to an access token. Obtain the token with\n * {@link machineToken} (scope `vault.manage` to provision/grant, `vault.lease` to\n * redeem), then call `store` / `grant` / `lease` etc.\n */\n vault(accessToken: string): VaultClient {\n return new VaultClient(this.config.issuer, accessToken, this.config.timeoutMs ?? 10_000);\n }\n\n private async exchange(code: string, verifier: string, redirectUri?: string): Promise<TokenResponse> {\n const body = new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n redirect_uri: redirectUri ?? this.config.redirectUri,\n client_id: this.config.clientId,\n code_verifier: verifier,\n });\n if (this.config.clientSecret) {\n body.set('client_secret', this.config.clientSecret);\n }\n\n const response = await this.post(await this.discovery.endpoint('token_endpoint'), body);\n if (!response.ok) {\n throw new AuthenticationError(`Token exchange failed: ${await response.text()}`);\n }\n return (await response.json()) as TokenResponse;\n }\n\n private async verifyIdToken(\n idToken: string,\n nonce: string,\n maxAge?: number,\n ): Promise<Record<string, unknown>> {\n const jwksUri = await this.discovery.endpoint('jwks_uri');\n let resolver = this.jwksByUri.get(jwksUri);\n if (!resolver) {\n resolver = createRemoteJWKSet(new URL(jwksUri));\n this.jwksByUri.set(jwksUri, resolver);\n }\n\n let payload: JWTPayload;\n try {\n ({ payload } = await jwtVerify(idToken, resolver, {\n issuer: this.config.issuer,\n audience: this.config.clientId,\n // Pin the accepted signature algorithms explicitly. `jose` already refuses\n // `alg: none` and key-type confusion, so this is belt-and-braces — but it\n // states the contract in code rather than relying on a library default.\n algorithms: ['RS256', 'ES256'],\n }));\n } catch (error) {\n throw new AuthenticationError(`The id_token could not be verified: ${String(error)}`);\n }\n\n if (nonce && payload['nonce'] !== nonce) {\n throw new AuthenticationError('The id_token nonce did not match — possible replay.');\n }\n\n // OIDC Core §3.1.3.7 step 12: when max_age was requested, `auth_time` is REQUIRED\n // and the relying party MUST check it. `max_age` is the control you reach for\n // before a payment or an admin grant, and the whole point is that the person\n // authenticated JUST NOW — so a login({maxAge}) whose result is never checked is a\n // step-up in name only: a day-old session comes back carrying its original\n // auth_time and nothing anywhere says so.\n if (typeof maxAge === 'number') {\n const authTime = payload['auth_time'];\n\n if (typeof authTime !== 'number') {\n throw new AuthenticationError(\n 'A max_age was requested but the id_token carried no auth_time, so the authentication age could not be verified.',\n );\n }\n\n const tolerance = this.config.authTimeToleranceSeconds ?? 60;\n const age = Math.floor(Date.now() / 1000) - authTime;\n\n if (age > maxAge + tolerance) {\n throw new AuthenticationError(\n `The authentication is ${age}s old but max_age required ${maxAge}s — the user did not re-authenticate.`,\n );\n }\n }\n\n return payload as Record<string, unknown>;\n }\n\n private scopes(): string[] {\n const scopes = this.config.scopes;\n return scopes && scopes.length > 0 ? scopes : ['openid', 'profile', 'email'];\n }\n\n private accountPath(): string {\n const path = this.config.accountPath;\n return path && path !== '' ? `/${path.replace(/^\\//, '')}` : '/settings';\n }\n\n private requireSecret(): string {\n if (!this.config.clientSecret) {\n throw new ConfigurationError('This call requires a `clientSecret`, but none is configured.');\n }\n return this.config.clientSecret;\n }\n\n private post(url: string, body: URLSearchParams, headers: Record<string, string> = {}): Promise<Response> {\n return this.fetchWithTimeout(url, {\n method: 'POST',\n headers: { 'content-type': 'application/x-www-form-urlencoded', ...headers },\n body,\n });\n }\n\n private async fetchWithTimeout(url: string, init: RequestInit): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.config.timeoutMs ?? 10_000);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } finally {\n clearTimeout(timer);\n }\n }\n}\n\n/** Constant-time string compare, so state validation doesn't leak via timing. */\nfunction timingSafeEqualString(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false;\n }\n let mismatch = 0;\n for (let i = 0; i < a.length; i++) {\n mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);\n }\n return mismatch === 0;\n}\n\n/**\n * Parse an `organizations` claim into typed orgs, ignoring anything malformed. The\n * claim is optional and instance-specific; absent or non-array → undefined, so\n * callers can tell \"no claim\" from \"empty\".\n */\nfunction parseOrganizations(claim: unknown): CboxOrganization[] | undefined {\n if (!Array.isArray(claim)) {\n return undefined;\n }\n const orgs: CboxOrganization[] = [];\n for (const entry of claim) {\n if (typeof entry !== 'object' || entry === null) {\n continue;\n }\n const record = entry as Record<string, unknown>;\n const id = record['id'];\n const name = record['name'];\n if (typeof id === 'string' && id !== '' && typeof name === 'string' && name !== '') {\n orgs.push({ id, name, role: typeof record['role'] === 'string' ? record['role'] : null });\n }\n }\n return orgs;\n}\n"]}
|
|
@@ -27,6 +27,18 @@ interface CboxIdConfig {
|
|
|
27
27
|
timeoutMs?: number;
|
|
28
28
|
/** How long (ms) the discovery document is cached. Defaults to `3600000` (1h). */
|
|
29
29
|
cacheTtlMs?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Leeway (seconds) when checking the id_token's `auth_time` against the `maxAge` you
|
|
32
|
+
* requested at login. Defaults to `60`.
|
|
33
|
+
*
|
|
34
|
+
* Some slack is required, not optional: `maxAge` bounds the age of the
|
|
35
|
+
* AUTHENTICATION, but `auth_time` is read after a browser redirect and a token
|
|
36
|
+
* exchange, so even a just-completed re-authentication is already several seconds
|
|
37
|
+
* old — and the RP's clock and the instance's need not agree. Sixty seconds absorbs
|
|
38
|
+
* that while still catching what this check exists for: a session hours or days old
|
|
39
|
+
* being passed off as fresh.
|
|
40
|
+
*/
|
|
41
|
+
authTimeToleranceSeconds?: number;
|
|
30
42
|
}
|
|
31
43
|
/** The OIDC discovery document fields this SDK uses. */
|
|
32
44
|
interface DiscoveryDocument {
|
|
@@ -36,8 +48,14 @@ interface DiscoveryDocument {
|
|
|
36
48
|
jwks_uri: string;
|
|
37
49
|
userinfo_endpoint?: string;
|
|
38
50
|
introspection_endpoint?: string;
|
|
51
|
+
revocation_endpoint?: string;
|
|
39
52
|
end_session_endpoint?: string;
|
|
40
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* RFC 7009 `token_type_hint`: which token store the server should search first.
|
|
56
|
+
* Only a hint — the server must still find the token if the hint is wrong.
|
|
57
|
+
*/
|
|
58
|
+
type TokenTypeHint = 'access_token' | 'refresh_token';
|
|
41
59
|
/**
|
|
42
60
|
* The values {@link CboxIdClient.createAuthorizationRequest} returns. Persist
|
|
43
61
|
* `state`, `codeVerifier` and `nonce` (e.g. in signed, httpOnly cookies) and hand
|
|
@@ -49,6 +67,12 @@ interface AuthorizationRequest {
|
|
|
49
67
|
state: string;
|
|
50
68
|
codeVerifier: string;
|
|
51
69
|
nonce: string;
|
|
70
|
+
/**
|
|
71
|
+
* Echoed back when you passed `maxAge`. Persist it with the rest and hand it to
|
|
72
|
+
* {@link CboxIdClient.authenticate}, which then verifies the id_token's `auth_time`
|
|
73
|
+
* against it — a step-up nobody checks is not a step-up.
|
|
74
|
+
*/
|
|
75
|
+
maxAge?: number;
|
|
52
76
|
}
|
|
53
77
|
/** The raw token-endpoint response. */
|
|
54
78
|
interface TokenResponse {
|
|
@@ -193,6 +217,8 @@ interface StoredAuthState {
|
|
|
193
217
|
state: string;
|
|
194
218
|
codeVerifier: string;
|
|
195
219
|
nonce: string;
|
|
220
|
+
/** The `maxAge` this login demanded, if any. See {@link AuthorizationRequest.maxAge}. */
|
|
221
|
+
maxAge?: number;
|
|
196
222
|
}
|
|
197
223
|
/**
|
|
198
224
|
* Turnkey Cbox ID client for JavaScript/TypeScript. It speaks standard OpenID
|
|
@@ -255,8 +281,18 @@ declare class CboxIdClient {
|
|
|
255
281
|
* ID session; `returnTo` is passed so the page can link back to your app.
|
|
256
282
|
*/
|
|
257
283
|
profileUrl(returnTo?: string): string;
|
|
258
|
-
/**
|
|
259
|
-
|
|
284
|
+
/**
|
|
285
|
+
* The RP-initiated logout URL, or null when the instance advertises none.
|
|
286
|
+
*
|
|
287
|
+
* `client_id` is always sent, even without a `returnTo`: the OP validates
|
|
288
|
+
* `post_logout_redirect_uri` against the registered allow-list of THAT client
|
|
289
|
+
* (OIDC RP-Initiated Logout 1.0 §2). With no way to name the RP the OP cannot
|
|
290
|
+
* check the list, so it silently drops the return URL and strands the user on a
|
|
291
|
+
* bare "you are signed out" page. `idTokenHint` — the user's `id_token`, if you
|
|
292
|
+
* still hold it — is the spec's other way to identify the RP, and additionally
|
|
293
|
+
* tells the OP which subject is logging out.
|
|
294
|
+
*/
|
|
295
|
+
logoutUrl(returnTo?: string, idTokenHint?: string): Promise<string | null>;
|
|
260
296
|
/**
|
|
261
297
|
* A machine (client-credentials) access token for calling Cbox ID APIs as your
|
|
262
298
|
* app, not on a user's behalf. Requires a client secret.
|
|
@@ -272,6 +308,17 @@ declare class CboxIdClient {
|
|
|
272
308
|
* response; `active` tells you if the token is currently valid. Requires a secret.
|
|
273
309
|
*/
|
|
274
310
|
introspect(token: string): Promise<Record<string, unknown>>;
|
|
311
|
+
/**
|
|
312
|
+
* RFC 7009 token revocation (confidential-client auth). Revokes an access or
|
|
313
|
+
* refresh token; revoking a refresh token also drops the whole token family, so
|
|
314
|
+
* this is what a real "sign out everywhere" does. Requires a secret.
|
|
315
|
+
*
|
|
316
|
+
* Per RFC 7009 the server answers 200 for an unknown or already-revoked token, so
|
|
317
|
+
* a successful call means "the token is not valid any more", not "it existed".
|
|
318
|
+
*
|
|
319
|
+
* @param tokenTypeHint which store to search first — only a hint, never required.
|
|
320
|
+
*/
|
|
321
|
+
revoke(token: string, tokenTypeHint?: TokenTypeHint): Promise<void>;
|
|
275
322
|
/** Verify a Cbox ID webhook / inline-action signature. See {@link verifyWebhook}. */
|
|
276
323
|
verifyWebhook(options: VerifyWebhookOptions): Promise<boolean>;
|
|
277
324
|
/**
|
|
@@ -289,4 +336,4 @@ declare class CboxIdClient {
|
|
|
289
336
|
private fetchWithTimeout;
|
|
290
337
|
}
|
|
291
338
|
|
|
292
|
-
export { type AuthorizationRequest as A, type CallbackParams as C, type DiscoveryDocument as D, type RefreshedTokens as R, type StoreSecretInput as S, type TokenResponse as T, VaultClient as V, CboxIdClient as a, type CboxIdConfig as b, type CboxUser as c, type StoredAuthState as d, type
|
|
339
|
+
export { type AuthorizationRequest as A, type CallbackParams as C, type DiscoveryDocument as D, type RefreshedTokens as R, type StoreSecretInput as S, type TokenResponse as T, VaultClient as V, CboxIdClient as a, type CboxIdConfig as b, type CboxUser as c, type StoredAuthState as d, type TokenTypeHint as e, type VaultLease as f, type VaultSecretRef as g, type VerifyWebhookOptions as h, verifyWebhook as v };
|
|
@@ -27,6 +27,18 @@ interface CboxIdConfig {
|
|
|
27
27
|
timeoutMs?: number;
|
|
28
28
|
/** How long (ms) the discovery document is cached. Defaults to `3600000` (1h). */
|
|
29
29
|
cacheTtlMs?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Leeway (seconds) when checking the id_token's `auth_time` against the `maxAge` you
|
|
32
|
+
* requested at login. Defaults to `60`.
|
|
33
|
+
*
|
|
34
|
+
* Some slack is required, not optional: `maxAge` bounds the age of the
|
|
35
|
+
* AUTHENTICATION, but `auth_time` is read after a browser redirect and a token
|
|
36
|
+
* exchange, so even a just-completed re-authentication is already several seconds
|
|
37
|
+
* old — and the RP's clock and the instance's need not agree. Sixty seconds absorbs
|
|
38
|
+
* that while still catching what this check exists for: a session hours or days old
|
|
39
|
+
* being passed off as fresh.
|
|
40
|
+
*/
|
|
41
|
+
authTimeToleranceSeconds?: number;
|
|
30
42
|
}
|
|
31
43
|
/** The OIDC discovery document fields this SDK uses. */
|
|
32
44
|
interface DiscoveryDocument {
|
|
@@ -36,8 +48,14 @@ interface DiscoveryDocument {
|
|
|
36
48
|
jwks_uri: string;
|
|
37
49
|
userinfo_endpoint?: string;
|
|
38
50
|
introspection_endpoint?: string;
|
|
51
|
+
revocation_endpoint?: string;
|
|
39
52
|
end_session_endpoint?: string;
|
|
40
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* RFC 7009 `token_type_hint`: which token store the server should search first.
|
|
56
|
+
* Only a hint — the server must still find the token if the hint is wrong.
|
|
57
|
+
*/
|
|
58
|
+
type TokenTypeHint = 'access_token' | 'refresh_token';
|
|
41
59
|
/**
|
|
42
60
|
* The values {@link CboxIdClient.createAuthorizationRequest} returns. Persist
|
|
43
61
|
* `state`, `codeVerifier` and `nonce` (e.g. in signed, httpOnly cookies) and hand
|
|
@@ -49,6 +67,12 @@ interface AuthorizationRequest {
|
|
|
49
67
|
state: string;
|
|
50
68
|
codeVerifier: string;
|
|
51
69
|
nonce: string;
|
|
70
|
+
/**
|
|
71
|
+
* Echoed back when you passed `maxAge`. Persist it with the rest and hand it to
|
|
72
|
+
* {@link CboxIdClient.authenticate}, which then verifies the id_token's `auth_time`
|
|
73
|
+
* against it — a step-up nobody checks is not a step-up.
|
|
74
|
+
*/
|
|
75
|
+
maxAge?: number;
|
|
52
76
|
}
|
|
53
77
|
/** The raw token-endpoint response. */
|
|
54
78
|
interface TokenResponse {
|
|
@@ -193,6 +217,8 @@ interface StoredAuthState {
|
|
|
193
217
|
state: string;
|
|
194
218
|
codeVerifier: string;
|
|
195
219
|
nonce: string;
|
|
220
|
+
/** The `maxAge` this login demanded, if any. See {@link AuthorizationRequest.maxAge}. */
|
|
221
|
+
maxAge?: number;
|
|
196
222
|
}
|
|
197
223
|
/**
|
|
198
224
|
* Turnkey Cbox ID client for JavaScript/TypeScript. It speaks standard OpenID
|
|
@@ -255,8 +281,18 @@ declare class CboxIdClient {
|
|
|
255
281
|
* ID session; `returnTo` is passed so the page can link back to your app.
|
|
256
282
|
*/
|
|
257
283
|
profileUrl(returnTo?: string): string;
|
|
258
|
-
/**
|
|
259
|
-
|
|
284
|
+
/**
|
|
285
|
+
* The RP-initiated logout URL, or null when the instance advertises none.
|
|
286
|
+
*
|
|
287
|
+
* `client_id` is always sent, even without a `returnTo`: the OP validates
|
|
288
|
+
* `post_logout_redirect_uri` against the registered allow-list of THAT client
|
|
289
|
+
* (OIDC RP-Initiated Logout 1.0 §2). With no way to name the RP the OP cannot
|
|
290
|
+
* check the list, so it silently drops the return URL and strands the user on a
|
|
291
|
+
* bare "you are signed out" page. `idTokenHint` — the user's `id_token`, if you
|
|
292
|
+
* still hold it — is the spec's other way to identify the RP, and additionally
|
|
293
|
+
* tells the OP which subject is logging out.
|
|
294
|
+
*/
|
|
295
|
+
logoutUrl(returnTo?: string, idTokenHint?: string): Promise<string | null>;
|
|
260
296
|
/**
|
|
261
297
|
* A machine (client-credentials) access token for calling Cbox ID APIs as your
|
|
262
298
|
* app, not on a user's behalf. Requires a client secret.
|
|
@@ -272,6 +308,17 @@ declare class CboxIdClient {
|
|
|
272
308
|
* response; `active` tells you if the token is currently valid. Requires a secret.
|
|
273
309
|
*/
|
|
274
310
|
introspect(token: string): Promise<Record<string, unknown>>;
|
|
311
|
+
/**
|
|
312
|
+
* RFC 7009 token revocation (confidential-client auth). Revokes an access or
|
|
313
|
+
* refresh token; revoking a refresh token also drops the whole token family, so
|
|
314
|
+
* this is what a real "sign out everywhere" does. Requires a secret.
|
|
315
|
+
*
|
|
316
|
+
* Per RFC 7009 the server answers 200 for an unknown or already-revoked token, so
|
|
317
|
+
* a successful call means "the token is not valid any more", not "it existed".
|
|
318
|
+
*
|
|
319
|
+
* @param tokenTypeHint which store to search first — only a hint, never required.
|
|
320
|
+
*/
|
|
321
|
+
revoke(token: string, tokenTypeHint?: TokenTypeHint): Promise<void>;
|
|
275
322
|
/** Verify a Cbox ID webhook / inline-action signature. See {@link verifyWebhook}. */
|
|
276
323
|
verifyWebhook(options: VerifyWebhookOptions): Promise<boolean>;
|
|
277
324
|
/**
|
|
@@ -289,4 +336,4 @@ declare class CboxIdClient {
|
|
|
289
336
|
private fetchWithTimeout;
|
|
290
337
|
}
|
|
291
338
|
|
|
292
|
-
export { type AuthorizationRequest as A, type CallbackParams as C, type DiscoveryDocument as D, type RefreshedTokens as R, type StoreSecretInput as S, type TokenResponse as T, VaultClient as V, CboxIdClient as a, type CboxIdConfig as b, type CboxUser as c, type StoredAuthState as d, type
|
|
339
|
+
export { type AuthorizationRequest as A, type CallbackParams as C, type DiscoveryDocument as D, type RefreshedTokens as R, type StoreSecretInput as S, type TokenResponse as T, VaultClient as V, CboxIdClient as a, type CboxIdConfig as b, type CboxUser as c, type StoredAuthState as d, type TokenTypeHint as e, type VaultLease as f, type VaultSecretRef as g, type VerifyWebhookOptions as h, verifyWebhook as v };
|
package/dist/index.cjs
CHANGED
|
@@ -279,7 +279,15 @@ var CboxIdClient = class {
|
|
|
279
279
|
params.set("max_age", String(options.maxAge));
|
|
280
280
|
}
|
|
281
281
|
const endpoint = await this.discovery.endpoint("authorization_endpoint");
|
|
282
|
-
return {
|
|
282
|
+
return {
|
|
283
|
+
url: `${endpoint}?${params.toString()}`,
|
|
284
|
+
state,
|
|
285
|
+
codeVerifier,
|
|
286
|
+
nonce,
|
|
287
|
+
// Carried through so authenticate() can hold the instance to it. Only present
|
|
288
|
+
// when requested (exactOptionalPropertyTypes).
|
|
289
|
+
...typeof options.maxAge === "number" ? { maxAge: options.maxAge } : {}
|
|
290
|
+
};
|
|
283
291
|
}
|
|
284
292
|
/**
|
|
285
293
|
* Complete login on your callback route: verify state, exchange the code with the
|
|
@@ -307,7 +315,11 @@ var CboxIdClient = class {
|
|
|
307
315
|
}
|
|
308
316
|
let claims = {};
|
|
309
317
|
if (tokens.id_token) {
|
|
310
|
-
claims = await this.verifyIdToken(tokens.id_token, stored.nonce);
|
|
318
|
+
claims = await this.verifyIdToken(tokens.id_token, stored.nonce, stored.maxAge);
|
|
319
|
+
} else if (typeof stored.maxAge === "number") {
|
|
320
|
+
throw new AuthenticationError(
|
|
321
|
+
"A max_age was requested but no id_token was returned, so the authentication age could not be verified."
|
|
322
|
+
);
|
|
311
323
|
}
|
|
312
324
|
claims = { ...claims, ...await this.userinfo(tokens.access_token) };
|
|
313
325
|
const sub = claims["sub"];
|
|
@@ -373,13 +385,30 @@ var CboxIdClient = class {
|
|
|
373
385
|
const base = `${this.config.issuer.replace(/\/$/, "")}${this.accountPath()}`;
|
|
374
386
|
return returnTo ? `${base}?${new URLSearchParams({ return_to: returnTo }).toString()}` : base;
|
|
375
387
|
}
|
|
376
|
-
/**
|
|
377
|
-
|
|
388
|
+
/**
|
|
389
|
+
* The RP-initiated logout URL, or null when the instance advertises none.
|
|
390
|
+
*
|
|
391
|
+
* `client_id` is always sent, even without a `returnTo`: the OP validates
|
|
392
|
+
* `post_logout_redirect_uri` against the registered allow-list of THAT client
|
|
393
|
+
* (OIDC RP-Initiated Logout 1.0 §2). With no way to name the RP the OP cannot
|
|
394
|
+
* check the list, so it silently drops the return URL and strands the user on a
|
|
395
|
+
* bare "you are signed out" page. `idTokenHint` — the user's `id_token`, if you
|
|
396
|
+
* still hold it — is the spec's other way to identify the RP, and additionally
|
|
397
|
+
* tells the OP which subject is logging out.
|
|
398
|
+
*/
|
|
399
|
+
async logoutUrl(returnTo, idTokenHint) {
|
|
378
400
|
const endpoint = await this.discovery.optionalEndpoint("end_session_endpoint");
|
|
379
401
|
if (!endpoint) {
|
|
380
402
|
return null;
|
|
381
403
|
}
|
|
382
|
-
|
|
404
|
+
const params = new URLSearchParams({ client_id: this.config.clientId });
|
|
405
|
+
if (returnTo) {
|
|
406
|
+
params.set("post_logout_redirect_uri", returnTo);
|
|
407
|
+
}
|
|
408
|
+
if (idTokenHint) {
|
|
409
|
+
params.set("id_token_hint", idTokenHint);
|
|
410
|
+
}
|
|
411
|
+
return `${endpoint}?${params.toString()}`;
|
|
383
412
|
}
|
|
384
413
|
/**
|
|
385
414
|
* A machine (client-credentials) access token for calling Cbox ID APIs as your
|
|
@@ -438,6 +467,28 @@ var CboxIdClient = class {
|
|
|
438
467
|
}
|
|
439
468
|
return await response.json();
|
|
440
469
|
}
|
|
470
|
+
/**
|
|
471
|
+
* RFC 7009 token revocation (confidential-client auth). Revokes an access or
|
|
472
|
+
* refresh token; revoking a refresh token also drops the whole token family, so
|
|
473
|
+
* this is what a real "sign out everywhere" does. Requires a secret.
|
|
474
|
+
*
|
|
475
|
+
* Per RFC 7009 the server answers 200 for an unknown or already-revoked token, so
|
|
476
|
+
* a successful call means "the token is not valid any more", not "it existed".
|
|
477
|
+
*
|
|
478
|
+
* @param tokenTypeHint which store to search first — only a hint, never required.
|
|
479
|
+
*/
|
|
480
|
+
async revoke(token, tokenTypeHint) {
|
|
481
|
+
const endpoint = await this.discovery.endpoint("revocation_endpoint");
|
|
482
|
+
const basic = btoa(`${this.config.clientId}:${this.requireSecret()}`);
|
|
483
|
+
const body = new URLSearchParams({ token });
|
|
484
|
+
if (tokenTypeHint) {
|
|
485
|
+
body.set("token_type_hint", tokenTypeHint);
|
|
486
|
+
}
|
|
487
|
+
const response = await this.post(endpoint, body, { authorization: `Basic ${basic}` });
|
|
488
|
+
if (!response.ok) {
|
|
489
|
+
throw new AuthenticationError("Revocation request failed.");
|
|
490
|
+
}
|
|
491
|
+
}
|
|
441
492
|
/** Verify a Cbox ID webhook / inline-action signature. See {@link verifyWebhook}. */
|
|
442
493
|
verifyWebhook(options) {
|
|
443
494
|
return verifyWebhook(options);
|
|
@@ -467,7 +518,7 @@ var CboxIdClient = class {
|
|
|
467
518
|
}
|
|
468
519
|
return await response.json();
|
|
469
520
|
}
|
|
470
|
-
async verifyIdToken(idToken, nonce) {
|
|
521
|
+
async verifyIdToken(idToken, nonce, maxAge) {
|
|
471
522
|
const jwksUri = await this.discovery.endpoint("jwks_uri");
|
|
472
523
|
let resolver = this.jwksByUri.get(jwksUri);
|
|
473
524
|
if (!resolver) {
|
|
@@ -478,7 +529,11 @@ var CboxIdClient = class {
|
|
|
478
529
|
try {
|
|
479
530
|
({ payload } = await jose.jwtVerify(idToken, resolver, {
|
|
480
531
|
issuer: this.config.issuer,
|
|
481
|
-
audience: this.config.clientId
|
|
532
|
+
audience: this.config.clientId,
|
|
533
|
+
// Pin the accepted signature algorithms explicitly. `jose` already refuses
|
|
534
|
+
// `alg: none` and key-type confusion, so this is belt-and-braces — but it
|
|
535
|
+
// states the contract in code rather than relying on a library default.
|
|
536
|
+
algorithms: ["RS256", "ES256"]
|
|
482
537
|
}));
|
|
483
538
|
} catch (error) {
|
|
484
539
|
throw new AuthenticationError(`The id_token could not be verified: ${String(error)}`);
|
|
@@ -486,6 +541,21 @@ var CboxIdClient = class {
|
|
|
486
541
|
if (nonce && payload["nonce"] !== nonce) {
|
|
487
542
|
throw new AuthenticationError("The id_token nonce did not match \u2014 possible replay.");
|
|
488
543
|
}
|
|
544
|
+
if (typeof maxAge === "number") {
|
|
545
|
+
const authTime = payload["auth_time"];
|
|
546
|
+
if (typeof authTime !== "number") {
|
|
547
|
+
throw new AuthenticationError(
|
|
548
|
+
"A max_age was requested but the id_token carried no auth_time, so the authentication age could not be verified."
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
const tolerance = this.config.authTimeToleranceSeconds ?? 60;
|
|
552
|
+
const age = Math.floor(Date.now() / 1e3) - authTime;
|
|
553
|
+
if (age > maxAge + tolerance) {
|
|
554
|
+
throw new AuthenticationError(
|
|
555
|
+
`The authentication is ${age}s old but max_age required ${maxAge}s \u2014 the user did not re-authenticate.`
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
489
559
|
return payload;
|
|
490
560
|
}
|
|
491
561
|
scopes() {
|