@cboxdk/id-js 0.15.1 → 0.18.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.
Files changed (41) hide show
  1. package/README.md +455 -4
  2. package/dist/chunk-2E25IPBA.js +155 -0
  3. package/dist/chunk-2E25IPBA.js.map +1 -0
  4. package/dist/{chunk-3V6OKRWF.js → chunk-CXGKOJRJ.js} +374 -90
  5. package/dist/chunk-CXGKOJRJ.js.map +1 -0
  6. package/dist/chunk-QQW63PGU.js +25 -0
  7. package/dist/chunk-QQW63PGU.js.map +1 -0
  8. package/dist/chunk-SEUD4TE4.js +61 -0
  9. package/dist/chunk-SEUD4TE4.js.map +1 -0
  10. package/dist/{client-CecDIu_N.d.cts → client-BSZASrMo.d.ts} +109 -136
  11. package/dist/{client-CecDIu_N.d.ts → client-BxUHm5hX.d.cts} +109 -136
  12. package/dist/errors-D61xljZE.d.cts +87 -0
  13. package/dist/errors-D61xljZE.d.ts +87 -0
  14. package/dist/index.cjs +440 -35
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +40 -90
  17. package/dist/index.d.ts +40 -90
  18. package/dist/index.js +64 -25
  19. package/dist/index.js.map +1 -1
  20. package/dist/management.cjs +4372 -0
  21. package/dist/management.cjs.map +1 -0
  22. package/dist/management.d.cts +10923 -0
  23. package/dist/management.d.ts +10923 -0
  24. package/dist/management.js +4335 -0
  25. package/dist/management.js.map +1 -0
  26. package/dist/nextjs.cjs +517 -42
  27. package/dist/nextjs.cjs.map +1 -1
  28. package/dist/nextjs.d.cts +29 -8
  29. package/dist/nextjs.d.ts +29 -8
  30. package/dist/nextjs.js +42 -12
  31. package/dist/nextjs.js.map +1 -1
  32. package/dist/server.cjs +220 -0
  33. package/dist/server.cjs.map +1 -0
  34. package/dist/server.d.cts +105 -0
  35. package/dist/server.d.ts +105 -0
  36. package/dist/server.js +5 -0
  37. package/dist/server.js.map +1 -0
  38. package/dist/types-BYZldock.d.cts +316 -0
  39. package/dist/types-BYZldock.d.ts +316 -0
  40. package/package.json +14 -2
  41. package/dist/chunk-3V6OKRWF.js.map +0 -1
package/dist/index.d.cts CHANGED
@@ -1,4 +1,6 @@
1
- export { A as AuthorizationRequest, C as CallbackParams, a as CboxIdClient, b as CboxIdConfig, c as CboxUser, D as DiscoveryDocument, R as RefreshedTokens, S as StoreSecretInput, d as StoredAuthState, T as TokenResponse, e as TokenTypeHint, V as VaultClient, f as VaultLease, g as VaultSecretRef, h as VerifyWebhookOptions, v as verifyWebhook } from './client-CecDIu_N.cjs';
1
+ export { A as AuthenticationError, C as CboxIdError, a as ConfigurationError, F as FrontendApiError, I as InvalidStateError } from './errors-D61xljZE.cjs';
2
+ export { a as CallbackParams, C as CboxIdClient, S as StoreSecretInput, b as StoredAuthState, V as VaultClient, c as VaultLease, d as VaultSecretRef, e as VerifyStandardWebhookOptions, f as VerifyWebhookOptions, W as WebhookHeaders, v as verifyStandardWebhook, g as verifyWebhook } from './client-BxUHm5hX.cjs';
3
+ export { c as AuthorizationPrompt, b as AuthorizationRequest, A as AuthorizationRequestOptions, d as CboxActiveOrganization, e as CboxActor, a as CboxIdConfig, f as CboxOrganization, C as CboxUser, g as ClaimSource, D as DeviceAuthorization, h as DiscoveryDocument, O as OrganizationRole, R as RefreshedTokens, i as TokenResponse, T as TokenTypeHint, j as actor, k as hasPermission, l as hasRole, m as isSupportSession, o as organization, p as permissions, r as roles, s as sessionId } from './types-BYZldock.cjs';
2
4
 
3
5
  /**
4
6
  * The handler on YOUR side of a legacy migration.
@@ -281,92 +283,6 @@ declare class CboxIdFrontend {
281
283
  private attempt;
282
284
  }
283
285
 
284
- /**
285
- * Base class for every error this SDK throws, so callers can `catch (e) { if (e
286
- * instanceof CboxIdError) ... }` in one branch.
287
- */
288
- declare class CboxIdError extends Error {
289
- constructor(message: string);
290
- }
291
- /** The client is misconfigured (a required option is missing or empty). */
292
- declare class ConfigurationError extends CboxIdError {
293
- }
294
- /**
295
- * The login `state` did not match what was stored — the callback is forged or
296
- * stale. Treat as a fresh start, not an error to surface to the user.
297
- */
298
- declare class InvalidStateError extends CboxIdError {
299
- }
300
- /**
301
- * Login could not be completed: the provider returned an error, the callback was
302
- * missing a code, or a token / id_token failed verification.
303
- *
304
- * `error` carries the RFC 6749 §5.2 code the authorization server sent, when it sent one.
305
- * It used to be discarded at every back-channel boundary, leaving a single message string
306
- * for outcomes that need opposite responses: `invalid_grant` on a refresh means the
307
- * session is over and the person has to sign in again, while `temporarily_unavailable`
308
- * means try the same token in a moment. A caller reduced to matching on prose either
309
- * retries what can never succeed, or signs out somebody who did not need to be.
310
- */
311
- declare class AuthenticationError extends CboxIdError {
312
- /** RFC 6749 §5.2 error code, when the server sent a parseable one. */
313
- readonly error?: string | undefined;
314
- /** The server's `error_description`, verbatim. Not end-user copy. */
315
- readonly errorDescription?: string | undefined;
316
- /** HTTP status, for the cases where the body says nothing useful. */
317
- readonly status?: number | undefined;
318
- /**
319
- * Seconds to wait, off the `Retry-After` header — set only on a 429.
320
- *
321
- * The distinction it carries is the one a caller most needs and could least infer:
322
- * a 429 is the ONLY back-channel failure where the same request will succeed
323
- * unchanged if you simply wait. Every other one needs a different request, or a new
324
- * sign-in. Without this the SDK reported "token refresh failed" and a caller with a
325
- * retry loop hammered a limiter that was already telling it exactly how long to stop.
326
- */
327
- readonly retryAfter?: number | undefined;
328
- constructor(message: string,
329
- /** RFC 6749 §5.2 error code, when the server sent a parseable one. */
330
- error?: string | undefined,
331
- /** The server's `error_description`, verbatim. Not end-user copy. */
332
- errorDescription?: string | undefined,
333
- /** HTTP status, for the cases where the body says nothing useful. */
334
- status?: number | undefined,
335
- /**
336
- * Seconds to wait, off the `Retry-After` header — set only on a 429.
337
- *
338
- * The distinction it carries is the one a caller most needs and could least infer:
339
- * a 429 is the ONLY back-channel failure where the same request will succeed
340
- * unchanged if you simply wait. Every other one needs a different request, or a new
341
- * sign-in. Without this the SDK reported "token refresh failed" and a caller with a
342
- * retry loop hammered a limiter that was already telling it exactly how long to stop.
343
- */
344
- retryAfter?: number | undefined);
345
- /** Whether waiting `retryAfter` seconds and repeating the same request is worth it. */
346
- get isRateLimited(): boolean;
347
- }
348
- /**
349
- * The Frontend API refused, or could not be reached.
350
- *
351
- * Carries a `code` because the four ways this goes wrong need four different responses
352
- * from the caller, and telling them apart from a message string is the kind of thing
353
- * that works until somebody rewords the message:
354
- *
355
- * - `origin_not_allowed` — the overwhelmingly likely cause of a refusal, and a
356
- * configuration problem the developer fixes once in the console.
357
- * - `rate_limited` — back off; `retryAfter` says for how long, when the server said.
358
- * - `unavailable` — the network, or a 5xx. Transient by assumption; already retried.
359
- * - `malformed` — a 200 whose body is not the document we expect, which means something
360
- * between the page and us is rewriting responses (a proxy, an extension, a captive
361
- * portal). Worth distinguishing, because retrying it never helps.
362
- */
363
- declare class FrontendApiError extends CboxIdError {
364
- readonly code: 'origin_not_allowed' | 'rate_limited' | 'unavailable' | 'malformed';
365
- readonly status?: number | undefined;
366
- readonly retryAfter?: number | undefined;
367
- constructor(message: string, code: 'origin_not_allowed' | 'rate_limited' | 'unavailable' | 'malformed', status?: number | undefined, retryAfter?: number | undefined);
368
- }
369
-
370
286
  /**
371
287
  * PKCE (RFC 7636) and the random values the login flow needs, built on Web Crypto
372
288
  * (`globalThis.crypto`) so the same code runs on Node, edge runtimes and browsers.
@@ -385,6 +301,13 @@ declare function challenge(verifier: string): Promise<string>;
385
301
  interface PermissionDefinition {
386
302
  key: string;
387
303
  description?: string;
304
+ /**
305
+ * Whether an organization's own administrators may hand this permission out in a custom
306
+ * role they build themselves (self-serve). Defaults to `false`: a permission is internal
307
+ * unless you opt it in, so a key added in a hurry never becomes something every customer
308
+ * can grant. Sent as `tenant_assignable: true`.
309
+ */
310
+ tenantAssignable?: boolean;
388
311
  }
389
312
  /**
390
313
  * A role your app declares. `permissions` lists the permission keys the role
@@ -395,6 +318,33 @@ interface RoleDefinition {
395
318
  name: string;
396
319
  description?: string;
397
320
  permissions: string[];
321
+ /**
322
+ * Whether an organization's administrators may assign this role to their members.
323
+ * Defaults to `true`.
324
+ *
325
+ * Set `false` for a STAFF role — your own support or operations people, held
326
+ * environment-wide across every customer (typically the role that grants
327
+ * `support:impersonate`). Cbox ID never lists or accepts a staff role on the
328
+ * organization plane; only an environment administrator can grant it. Sent as
329
+ * `tenant_assignable: false`.
330
+ */
331
+ tenantAssignable?: boolean;
332
+ }
333
+ /** A permission as it is sent to Cbox ID (snake_case, the manifest's wire format). */
334
+ interface ManifestPermission {
335
+ key: string;
336
+ description?: string;
337
+ /** Present only for a self-serve permission. See {@link PermissionDefinition.tenantAssignable}. */
338
+ tenant_assignable?: true;
339
+ }
340
+ /** A role as it is sent to Cbox ID (snake_case, the manifest's wire format). */
341
+ interface ManifestRole {
342
+ key: string;
343
+ name: string;
344
+ description?: string;
345
+ permissions: string[];
346
+ /** Present only for a staff role. See {@link RoleDefinition.tenantAssignable}. */
347
+ tenant_assignable?: false;
398
348
  }
399
349
  /**
400
350
  * The roles + permissions an app declares in code. Either list may be omitted.
@@ -432,8 +382,8 @@ interface LegacyLoginDeclaration {
432
382
  */
433
383
  interface AuthzManifest {
434
384
  version: string;
435
- permissions: PermissionDefinition[];
436
- roles: RoleDefinition[];
385
+ permissions: ManifestPermission[];
386
+ roles: ManifestRole[];
437
387
  legacy_login?: LegacyLoginDeclaration;
438
388
  }
439
389
  /** What {@link publishManifest} needs to authenticate the push. */
@@ -492,4 +442,4 @@ declare function buildManifest(declaration: AuthzDeclaration): Promise<AuthzMani
492
442
  */
493
443
  declare function publishManifest(config: ManifestPublisherConfig, declaration: AuthzDeclaration): Promise<ManifestSyncSummary>;
494
444
 
495
- export { type Appearance, AuthenticationError, type AuthzDeclaration, type AuthzManifest, CboxIdError, CboxIdFrontend, ConfigurationError, FrontendApiError, type FrontendClientOptions, type FrontendConfig, type FrontendSession, InvalidStateError, type LegacyUser, type LegacyVerifierOptions, type ManifestPublisherConfig, type ManifestSyncSummary, type PasskeyOptions, type PermissionDefinition, type RoleDefinition, type SignInResult, type SocialProvider, buildManifest, challenge, createLegacyVerifier, createVerifier, defineAuthz, publishManifest, randomToken };
445
+ export { type Appearance, type AuthzDeclaration, type AuthzManifest, CboxIdFrontend, type FrontendClientOptions, type FrontendConfig, type FrontendSession, type LegacyUser, type LegacyVerifierOptions, type ManifestPermission, type ManifestPublisherConfig, type ManifestRole, type ManifestSyncSummary, type PasskeyOptions, type PermissionDefinition, type RoleDefinition, type SignInResult, type SocialProvider, buildManifest, challenge, createLegacyVerifier, createVerifier, defineAuthz, publishManifest, randomToken };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- export { A as AuthorizationRequest, C as CallbackParams, a as CboxIdClient, b as CboxIdConfig, c as CboxUser, D as DiscoveryDocument, R as RefreshedTokens, S as StoreSecretInput, d as StoredAuthState, T as TokenResponse, e as TokenTypeHint, V as VaultClient, f as VaultLease, g as VaultSecretRef, h as VerifyWebhookOptions, v as verifyWebhook } from './client-CecDIu_N.js';
1
+ export { A as AuthenticationError, C as CboxIdError, a as ConfigurationError, F as FrontendApiError, I as InvalidStateError } from './errors-D61xljZE.js';
2
+ export { a as CallbackParams, C as CboxIdClient, S as StoreSecretInput, b as StoredAuthState, V as VaultClient, c as VaultLease, d as VaultSecretRef, e as VerifyStandardWebhookOptions, f as VerifyWebhookOptions, W as WebhookHeaders, v as verifyStandardWebhook, g as verifyWebhook } from './client-BSZASrMo.js';
3
+ export { c as AuthorizationPrompt, b as AuthorizationRequest, A as AuthorizationRequestOptions, d as CboxActiveOrganization, e as CboxActor, a as CboxIdConfig, f as CboxOrganization, C as CboxUser, g as ClaimSource, D as DeviceAuthorization, h as DiscoveryDocument, O as OrganizationRole, R as RefreshedTokens, i as TokenResponse, T as TokenTypeHint, j as actor, k as hasPermission, l as hasRole, m as isSupportSession, o as organization, p as permissions, r as roles, s as sessionId } from './types-BYZldock.js';
2
4
 
3
5
  /**
4
6
  * The handler on YOUR side of a legacy migration.
@@ -281,92 +283,6 @@ declare class CboxIdFrontend {
281
283
  private attempt;
282
284
  }
283
285
 
284
- /**
285
- * Base class for every error this SDK throws, so callers can `catch (e) { if (e
286
- * instanceof CboxIdError) ... }` in one branch.
287
- */
288
- declare class CboxIdError extends Error {
289
- constructor(message: string);
290
- }
291
- /** The client is misconfigured (a required option is missing or empty). */
292
- declare class ConfigurationError extends CboxIdError {
293
- }
294
- /**
295
- * The login `state` did not match what was stored — the callback is forged or
296
- * stale. Treat as a fresh start, not an error to surface to the user.
297
- */
298
- declare class InvalidStateError extends CboxIdError {
299
- }
300
- /**
301
- * Login could not be completed: the provider returned an error, the callback was
302
- * missing a code, or a token / id_token failed verification.
303
- *
304
- * `error` carries the RFC 6749 §5.2 code the authorization server sent, when it sent one.
305
- * It used to be discarded at every back-channel boundary, leaving a single message string
306
- * for outcomes that need opposite responses: `invalid_grant` on a refresh means the
307
- * session is over and the person has to sign in again, while `temporarily_unavailable`
308
- * means try the same token in a moment. A caller reduced to matching on prose either
309
- * retries what can never succeed, or signs out somebody who did not need to be.
310
- */
311
- declare class AuthenticationError extends CboxIdError {
312
- /** RFC 6749 §5.2 error code, when the server sent a parseable one. */
313
- readonly error?: string | undefined;
314
- /** The server's `error_description`, verbatim. Not end-user copy. */
315
- readonly errorDescription?: string | undefined;
316
- /** HTTP status, for the cases where the body says nothing useful. */
317
- readonly status?: number | undefined;
318
- /**
319
- * Seconds to wait, off the `Retry-After` header — set only on a 429.
320
- *
321
- * The distinction it carries is the one a caller most needs and could least infer:
322
- * a 429 is the ONLY back-channel failure where the same request will succeed
323
- * unchanged if you simply wait. Every other one needs a different request, or a new
324
- * sign-in. Without this the SDK reported "token refresh failed" and a caller with a
325
- * retry loop hammered a limiter that was already telling it exactly how long to stop.
326
- */
327
- readonly retryAfter?: number | undefined;
328
- constructor(message: string,
329
- /** RFC 6749 §5.2 error code, when the server sent a parseable one. */
330
- error?: string | undefined,
331
- /** The server's `error_description`, verbatim. Not end-user copy. */
332
- errorDescription?: string | undefined,
333
- /** HTTP status, for the cases where the body says nothing useful. */
334
- status?: number | undefined,
335
- /**
336
- * Seconds to wait, off the `Retry-After` header — set only on a 429.
337
- *
338
- * The distinction it carries is the one a caller most needs and could least infer:
339
- * a 429 is the ONLY back-channel failure where the same request will succeed
340
- * unchanged if you simply wait. Every other one needs a different request, or a new
341
- * sign-in. Without this the SDK reported "token refresh failed" and a caller with a
342
- * retry loop hammered a limiter that was already telling it exactly how long to stop.
343
- */
344
- retryAfter?: number | undefined);
345
- /** Whether waiting `retryAfter` seconds and repeating the same request is worth it. */
346
- get isRateLimited(): boolean;
347
- }
348
- /**
349
- * The Frontend API refused, or could not be reached.
350
- *
351
- * Carries a `code` because the four ways this goes wrong need four different responses
352
- * from the caller, and telling them apart from a message string is the kind of thing
353
- * that works until somebody rewords the message:
354
- *
355
- * - `origin_not_allowed` — the overwhelmingly likely cause of a refusal, and a
356
- * configuration problem the developer fixes once in the console.
357
- * - `rate_limited` — back off; `retryAfter` says for how long, when the server said.
358
- * - `unavailable` — the network, or a 5xx. Transient by assumption; already retried.
359
- * - `malformed` — a 200 whose body is not the document we expect, which means something
360
- * between the page and us is rewriting responses (a proxy, an extension, a captive
361
- * portal). Worth distinguishing, because retrying it never helps.
362
- */
363
- declare class FrontendApiError extends CboxIdError {
364
- readonly code: 'origin_not_allowed' | 'rate_limited' | 'unavailable' | 'malformed';
365
- readonly status?: number | undefined;
366
- readonly retryAfter?: number | undefined;
367
- constructor(message: string, code: 'origin_not_allowed' | 'rate_limited' | 'unavailable' | 'malformed', status?: number | undefined, retryAfter?: number | undefined);
368
- }
369
-
370
286
  /**
371
287
  * PKCE (RFC 7636) and the random values the login flow needs, built on Web Crypto
372
288
  * (`globalThis.crypto`) so the same code runs on Node, edge runtimes and browsers.
@@ -385,6 +301,13 @@ declare function challenge(verifier: string): Promise<string>;
385
301
  interface PermissionDefinition {
386
302
  key: string;
387
303
  description?: string;
304
+ /**
305
+ * Whether an organization's own administrators may hand this permission out in a custom
306
+ * role they build themselves (self-serve). Defaults to `false`: a permission is internal
307
+ * unless you opt it in, so a key added in a hurry never becomes something every customer
308
+ * can grant. Sent as `tenant_assignable: true`.
309
+ */
310
+ tenantAssignable?: boolean;
388
311
  }
389
312
  /**
390
313
  * A role your app declares. `permissions` lists the permission keys the role
@@ -395,6 +318,33 @@ interface RoleDefinition {
395
318
  name: string;
396
319
  description?: string;
397
320
  permissions: string[];
321
+ /**
322
+ * Whether an organization's administrators may assign this role to their members.
323
+ * Defaults to `true`.
324
+ *
325
+ * Set `false` for a STAFF role — your own support or operations people, held
326
+ * environment-wide across every customer (typically the role that grants
327
+ * `support:impersonate`). Cbox ID never lists or accepts a staff role on the
328
+ * organization plane; only an environment administrator can grant it. Sent as
329
+ * `tenant_assignable: false`.
330
+ */
331
+ tenantAssignable?: boolean;
332
+ }
333
+ /** A permission as it is sent to Cbox ID (snake_case, the manifest's wire format). */
334
+ interface ManifestPermission {
335
+ key: string;
336
+ description?: string;
337
+ /** Present only for a self-serve permission. See {@link PermissionDefinition.tenantAssignable}. */
338
+ tenant_assignable?: true;
339
+ }
340
+ /** A role as it is sent to Cbox ID (snake_case, the manifest's wire format). */
341
+ interface ManifestRole {
342
+ key: string;
343
+ name: string;
344
+ description?: string;
345
+ permissions: string[];
346
+ /** Present only for a staff role. See {@link RoleDefinition.tenantAssignable}. */
347
+ tenant_assignable?: false;
398
348
  }
399
349
  /**
400
350
  * The roles + permissions an app declares in code. Either list may be omitted.
@@ -432,8 +382,8 @@ interface LegacyLoginDeclaration {
432
382
  */
433
383
  interface AuthzManifest {
434
384
  version: string;
435
- permissions: PermissionDefinition[];
436
- roles: RoleDefinition[];
385
+ permissions: ManifestPermission[];
386
+ roles: ManifestRole[];
437
387
  legacy_login?: LegacyLoginDeclaration;
438
388
  }
439
389
  /** What {@link publishManifest} needs to authenticate the push. */
@@ -492,4 +442,4 @@ declare function buildManifest(declaration: AuthzDeclaration): Promise<AuthzMani
492
442
  */
493
443
  declare function publishManifest(config: ManifestPublisherConfig, declaration: AuthzDeclaration): Promise<ManifestSyncSummary>;
494
444
 
495
- export { type Appearance, AuthenticationError, type AuthzDeclaration, type AuthzManifest, CboxIdError, CboxIdFrontend, ConfigurationError, FrontendApiError, type FrontendClientOptions, type FrontendConfig, type FrontendSession, InvalidStateError, type LegacyUser, type LegacyVerifierOptions, type ManifestPublisherConfig, type ManifestSyncSummary, type PasskeyOptions, type PermissionDefinition, type RoleDefinition, type SignInResult, type SocialProvider, buildManifest, challenge, createLegacyVerifier, createVerifier, defineAuthz, publishManifest, randomToken };
445
+ export { type Appearance, type AuthzDeclaration, type AuthzManifest, CboxIdFrontend, type FrontendClientOptions, type FrontendConfig, type FrontendSession, type LegacyUser, type LegacyVerifierOptions, type ManifestPermission, type ManifestPublisherConfig, type ManifestRole, type ManifestSyncSummary, type PasskeyOptions, type PermissionDefinition, type RoleDefinition, type SignInResult, type SocialProvider, buildManifest, challenge, createLegacyVerifier, createVerifier, defineAuthz, publishManifest, randomToken };
package/dist/index.js CHANGED
@@ -1,5 +1,8 @@
1
- import { ConfigurationError, assertSecureIssuer, FrontendApiError, Discovery, AuthenticationError } from './chunk-3V6OKRWF.js';
2
- export { AuthenticationError, CboxIdClient, CboxIdError, ConfigurationError, FrontendApiError, InvalidStateError, VaultClient, challenge, createVerifier, randomToken, verifyWebhook } from './chunk-3V6OKRWF.js';
1
+ import { Discovery } from './chunk-CXGKOJRJ.js';
2
+ export { CboxIdClient, VaultClient, actor, challenge, createVerifier, hasPermission, hasRole, isSupportSession, organization, permissions, randomToken, roles, sessionId, verifyStandardWebhook, verifyWebhook } from './chunk-CXGKOJRJ.js';
3
+ import { assertSecureIssuer } from './chunk-QQW63PGU.js';
4
+ import { ConfigurationError, FrontendApiError, AuthenticationError } from './chunk-SEUD4TE4.js';
5
+ export { AuthenticationError, CboxIdError, ConfigurationError, FrontendApiError, InvalidStateError } from './chunk-SEUD4TE4.js';
3
6
 
4
7
  // src/legacy.ts
5
8
  function createLegacyVerifier(options) {
@@ -403,22 +406,24 @@ function isFrontendSession(body) {
403
406
 
404
407
  // src/authz.ts
405
408
  function defineAuthz(declaration) {
406
- const permissions = declaration.permissions ?? [];
407
- const roles = declaration.roles ?? [];
408
- assertDeclaration(permissions, roles);
409
+ const permissions2 = declaration.permissions ?? [];
410
+ const roles2 = declaration.roles ?? [];
411
+ assertDeclaration(permissions2, roles2);
409
412
  if (declaration.legacyLogin) {
410
413
  assertLegacyLogin(declaration.legacyLogin);
411
414
  }
412
- return { ...declaration, permissions, roles };
415
+ return { ...declaration, permissions: permissions2, roles: roles2 };
413
416
  }
414
417
  async function buildManifest(declaration) {
415
- const permissions = (declaration.permissions ?? []).map(canonicalPermission);
416
- const roles = (declaration.roles ?? []).map(canonicalRole);
417
- assertDeclaration(permissions, roles);
418
- permissions.sort((a, b) => byteCompare(a.key, b.key));
419
- roles.sort((a, b) => byteCompare(a.key, b.key));
420
- const version = (await sha256Hex(canonicalManifestJson(permissions, roles))).slice(0, 16);
421
- const manifest = { version, permissions, roles };
418
+ const permissions2 = declaration.permissions ?? [];
419
+ const roles2 = declaration.roles ?? [];
420
+ assertDeclaration(permissions2, roles2);
421
+ const version = (await sha256Hex(canonicalManifestJson(permissions2, roles2))).slice(0, 16);
422
+ const manifest = {
423
+ version,
424
+ permissions: [...permissions2].sort((a, b) => byteCompare(a.key, b.key)).map(wirePermission),
425
+ roles: [...roles2].sort((a, b) => byteCompare(a.key, b.key)).map(wireRole)
426
+ };
422
427
  if (declaration.legacyLogin) {
423
428
  assertLegacyLogin(declaration.legacyLogin);
424
429
  manifest.legacy_login = declaration.legacyLogin;
@@ -475,28 +480,33 @@ async function mintToken(config, discovery, timeoutMs) {
475
480
  }
476
481
  return json2.access_token;
477
482
  }
478
- function assertDeclaration(permissions, roles) {
483
+ var KEY_PATTERN = /^[a-z][a-z0-9_-]*(?:[.:][a-z0-9_-]+)*$/;
484
+ function assertDeclaration(permissions2, roles2) {
479
485
  const permissionKeys = /* @__PURE__ */ new Set();
480
- for (const permission of permissions) {
486
+ for (const permission of permissions2) {
481
487
  if (!permission.key) {
482
488
  throw new ConfigurationError("Every permission needs a non-empty `key`.");
483
489
  }
490
+ assertKey(permission.key, "Permission");
484
491
  if (permissionKeys.has(permission.key)) {
485
492
  throw new ConfigurationError(`Permission "${permission.key}" is declared more than once.`);
486
493
  }
494
+ assertFlag(permission.tenantAssignable, `Permission "${permission.key}"`);
487
495
  permissionKeys.add(permission.key);
488
496
  }
489
497
  const roleKeys = /* @__PURE__ */ new Set();
490
- for (const role of roles) {
498
+ for (const role of roles2) {
491
499
  if (!role.key) {
492
500
  throw new ConfigurationError("Every role needs a non-empty `key`.");
493
501
  }
502
+ assertKey(role.key, "Role");
494
503
  if (!role.name) {
495
504
  throw new ConfigurationError(`Role "${role.key}" needs a non-empty \`name\`.`);
496
505
  }
497
506
  if (roleKeys.has(role.key)) {
498
507
  throw new ConfigurationError(`Role "${role.key}" is declared more than once.`);
499
508
  }
509
+ assertFlag(role.tenantAssignable, `Role "${role.key}"`);
500
510
  roleKeys.add(role.key);
501
511
  for (const reference of role.permissions) {
502
512
  if (!permissionKeys.has(reference)) {
@@ -507,25 +517,54 @@ function assertDeclaration(permissions, roles) {
507
517
  }
508
518
  }
509
519
  }
510
- function canonicalPermission(permission) {
511
- return permission.description === void 0 ? { key: permission.key } : { key: permission.key, description: permission.description };
520
+ function assertKey(key, what) {
521
+ if (!KEY_PATTERN.test(key)) {
522
+ throw new ConfigurationError(
523
+ `${what} key "${key}" is not a lowercase \`feature:action\` slug (e.g. \`invoices:create\`).`
524
+ );
525
+ }
526
+ }
527
+ function assertFlag(value, owner) {
528
+ if (value !== void 0 && typeof value !== "boolean") {
529
+ throw new ConfigurationError(`${owner} \`tenantAssignable\` must be true or false.`);
530
+ }
512
531
  }
513
- function canonicalRole(role) {
514
- const permissions = [...role.permissions].sort(byteCompare);
515
- return role.description === void 0 ? { key: role.key, name: role.name, permissions } : { key: role.key, name: role.name, description: role.description, permissions };
532
+ function wirePermission(permission) {
533
+ return {
534
+ key: permission.key,
535
+ ...permission.description === void 0 ? {} : { description: permission.description },
536
+ ...permission.tenantAssignable === true ? { tenant_assignable: true } : {}
537
+ };
516
538
  }
517
- function canonicalManifestJson(permissions, roles) {
539
+ function wireRole(role) {
540
+ return {
541
+ key: role.key,
542
+ name: role.name,
543
+ ...role.description === void 0 ? {} : { description: role.description },
544
+ permissions: uniqueSorted(role.permissions),
545
+ ...role.tenantAssignable === false ? { tenant_assignable: false } : {}
546
+ };
547
+ }
548
+ function canonicalManifestJson(permissions2, roles2) {
518
549
  const canonical = {
519
- permissions: [...permissions].sort((a, b) => byteCompare(a.key, b.key)).map((p) => ({ key: p.key, description: emptyToNull(p.description) })),
520
- roles: [...roles].sort((a, b) => byteCompare(a.key, b.key)).map((r) => ({
550
+ permissions: [...permissions2].sort((a, b) => byteCompare(a.key, b.key)).map((p) => ({
551
+ key: p.key,
552
+ description: emptyToNull(p.description),
553
+ ...p.tenantAssignable === true ? { tenant_assignable: true } : {}
554
+ })),
555
+ roles: [...roles2].sort((a, b) => byteCompare(a.key, b.key)).map((r) => ({
521
556
  key: r.key,
522
557
  name: r.name,
523
558
  description: emptyToNull(r.description),
524
- permissions: [...r.permissions].sort(byteCompare)
559
+ permissions: uniqueSorted(r.permissions),
560
+ ...r.tenantAssignable === false ? { tenant_assignable: false } : {}
525
561
  }))
526
562
  };
527
563
  return escapeLikePhp(JSON.stringify(canonical));
528
564
  }
565
+ function uniqueSorted(values) {
566
+ return [...new Set(values)].sort(byteCompare);
567
+ }
529
568
  function emptyToNull(value) {
530
569
  return value === void 0 || value === "" ? null : value;
531
570
  }