@meistrari/auth-core 1.34.0 → 1.36.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 CHANGED
@@ -17,6 +17,8 @@ A TypeScript/JavaScript SDK for interacting with the Auth API service.
17
17
  - **Application Authentication**
18
18
  - OAuth PKCE application flows
19
19
  - OAuth Device Authorization Grant endpoints
20
+ - OAuth Client Credentials Grant for service accounts
21
+ - Organization-scoped application token caching, renewal, and retry handling
20
22
  - Application token refresh, logout, and organization switching
21
23
  - **API Keys**
22
24
  - User-scoped API key CRUD
@@ -57,6 +59,10 @@ const tokens = await authClient.application.completeAuthorizationFlow(code, 'pkc
57
59
  console.log('Application user:', tokens.user.email)
58
60
  ```
59
61
 
62
+ ## Guides
63
+
64
+ - [Client Credentials Grant](https://github.com/meistrari/auth-api/blob/main/packages/core/docs/CLIENT_CREDENTIALS.md) — server-to-server authentication using the recommended `ApplicationTokenStorage` helper
65
+
60
66
  ## API Reference
61
67
 
62
68
  See it on [Pantry](https://pantry.tela.tools/ingredients/auth-core/api)
package/dist/index.d.mts CHANGED
@@ -14,11 +14,11 @@ declare const AALBrand: unique symbol;
14
14
  declare const ACRBrand: unique symbol;
15
15
  declare const AuthMethodBrand: unique symbol;
16
16
  declare const AMRs: {
17
- readonly Pwd: ("pwd" & z.core.$brand<typeof AMRBrand>) | ("otp" & z.core.$brand<typeof AMRBrand>) | ("oauth" & z.core.$brand<typeof AMRBrand>) | ("sso" & z.core.$brand<typeof AMRBrand>) | ("saml" & z.core.$brand<typeof AMRBrand>);
18
- readonly Otp: ("pwd" & z.core.$brand<typeof AMRBrand>) | ("otp" & z.core.$brand<typeof AMRBrand>) | ("oauth" & z.core.$brand<typeof AMRBrand>) | ("sso" & z.core.$brand<typeof AMRBrand>) | ("saml" & z.core.$brand<typeof AMRBrand>);
19
- readonly Oauth: ("pwd" & z.core.$brand<typeof AMRBrand>) | ("otp" & z.core.$brand<typeof AMRBrand>) | ("oauth" & z.core.$brand<typeof AMRBrand>) | ("sso" & z.core.$brand<typeof AMRBrand>) | ("saml" & z.core.$brand<typeof AMRBrand>);
20
- readonly Sso: ("pwd" & z.core.$brand<typeof AMRBrand>) | ("otp" & z.core.$brand<typeof AMRBrand>) | ("oauth" & z.core.$brand<typeof AMRBrand>) | ("sso" & z.core.$brand<typeof AMRBrand>) | ("saml" & z.core.$brand<typeof AMRBrand>);
21
- readonly Saml: ("pwd" & z.core.$brand<typeof AMRBrand>) | ("otp" & z.core.$brand<typeof AMRBrand>) | ("oauth" & z.core.$brand<typeof AMRBrand>) | ("sso" & z.core.$brand<typeof AMRBrand>) | ("saml" & z.core.$brand<typeof AMRBrand>);
17
+ readonly Pwd: ("sso" & z.core.$brand<typeof AMRBrand>) | ("saml" & z.core.$brand<typeof AMRBrand>) | ("otp" & z.core.$brand<typeof AMRBrand>) | ("pwd" & z.core.$brand<typeof AMRBrand>) | ("oauth" & z.core.$brand<typeof AMRBrand>);
18
+ readonly Otp: ("sso" & z.core.$brand<typeof AMRBrand>) | ("saml" & z.core.$brand<typeof AMRBrand>) | ("otp" & z.core.$brand<typeof AMRBrand>) | ("pwd" & z.core.$brand<typeof AMRBrand>) | ("oauth" & z.core.$brand<typeof AMRBrand>);
19
+ readonly Oauth: ("sso" & z.core.$brand<typeof AMRBrand>) | ("saml" & z.core.$brand<typeof AMRBrand>) | ("otp" & z.core.$brand<typeof AMRBrand>) | ("pwd" & z.core.$brand<typeof AMRBrand>) | ("oauth" & z.core.$brand<typeof AMRBrand>);
20
+ readonly Sso: ("sso" & z.core.$brand<typeof AMRBrand>) | ("saml" & z.core.$brand<typeof AMRBrand>) | ("otp" & z.core.$brand<typeof AMRBrand>) | ("pwd" & z.core.$brand<typeof AMRBrand>) | ("oauth" & z.core.$brand<typeof AMRBrand>);
21
+ readonly Saml: ("sso" & z.core.$brand<typeof AMRBrand>) | ("saml" & z.core.$brand<typeof AMRBrand>) | ("otp" & z.core.$brand<typeof AMRBrand>) | ("pwd" & z.core.$brand<typeof AMRBrand>) | ("oauth" & z.core.$brand<typeof AMRBrand>);
22
22
  };
23
23
  declare const AALs: {
24
24
  readonly Aal1: ("aal1" & z.core.$brand<typeof AALBrand>) | ("aal2" & z.core.$brand<typeof AALBrand>) | ("aal3" & z.core.$brand<typeof AALBrand>);
@@ -29,11 +29,11 @@ declare const ACRs: {
29
29
  readonly Unspecified: "urn:tela:auth:unspecified" & z.core.$brand<typeof ACRBrand>;
30
30
  };
31
31
  declare const AuthMethods: {
32
- readonly Password: ("oauth" & z.core.$brand<typeof AuthMethodBrand>) | ("saml" & z.core.$brand<typeof AuthMethodBrand>) | ("password" & z.core.$brand<typeof AuthMethodBrand>) | ("email_otp" & z.core.$brand<typeof AuthMethodBrand>) | ("unknown" & z.core.$brand<typeof AuthMethodBrand>);
33
- readonly EmailOTP: ("oauth" & z.core.$brand<typeof AuthMethodBrand>) | ("saml" & z.core.$brand<typeof AuthMethodBrand>) | ("password" & z.core.$brand<typeof AuthMethodBrand>) | ("email_otp" & z.core.$brand<typeof AuthMethodBrand>) | ("unknown" & z.core.$brand<typeof AuthMethodBrand>);
34
- readonly OAuth: ("oauth" & z.core.$brand<typeof AuthMethodBrand>) | ("saml" & z.core.$brand<typeof AuthMethodBrand>) | ("password" & z.core.$brand<typeof AuthMethodBrand>) | ("email_otp" & z.core.$brand<typeof AuthMethodBrand>) | ("unknown" & z.core.$brand<typeof AuthMethodBrand>);
35
- readonly SAML: ("oauth" & z.core.$brand<typeof AuthMethodBrand>) | ("saml" & z.core.$brand<typeof AuthMethodBrand>) | ("password" & z.core.$brand<typeof AuthMethodBrand>) | ("email_otp" & z.core.$brand<typeof AuthMethodBrand>) | ("unknown" & z.core.$brand<typeof AuthMethodBrand>);
36
- readonly Unknown: ("oauth" & z.core.$brand<typeof AuthMethodBrand>) | ("saml" & z.core.$brand<typeof AuthMethodBrand>) | ("password" & z.core.$brand<typeof AuthMethodBrand>) | ("email_otp" & z.core.$brand<typeof AuthMethodBrand>) | ("unknown" & z.core.$brand<typeof AuthMethodBrand>);
32
+ readonly Password: ("unknown" & z.core.$brand<typeof AuthMethodBrand>) | ("password" & z.core.$brand<typeof AuthMethodBrand>) | ("saml" & z.core.$brand<typeof AuthMethodBrand>) | ("oauth" & z.core.$brand<typeof AuthMethodBrand>) | ("email_otp" & z.core.$brand<typeof AuthMethodBrand>);
33
+ readonly EmailOTP: ("unknown" & z.core.$brand<typeof AuthMethodBrand>) | ("password" & z.core.$brand<typeof AuthMethodBrand>) | ("saml" & z.core.$brand<typeof AuthMethodBrand>) | ("oauth" & z.core.$brand<typeof AuthMethodBrand>) | ("email_otp" & z.core.$brand<typeof AuthMethodBrand>);
34
+ readonly OAuth: ("unknown" & z.core.$brand<typeof AuthMethodBrand>) | ("password" & z.core.$brand<typeof AuthMethodBrand>) | ("saml" & z.core.$brand<typeof AuthMethodBrand>) | ("oauth" & z.core.$brand<typeof AuthMethodBrand>) | ("email_otp" & z.core.$brand<typeof AuthMethodBrand>);
35
+ readonly SAML: ("unknown" & z.core.$brand<typeof AuthMethodBrand>) | ("password" & z.core.$brand<typeof AuthMethodBrand>) | ("saml" & z.core.$brand<typeof AuthMethodBrand>) | ("oauth" & z.core.$brand<typeof AuthMethodBrand>) | ("email_otp" & z.core.$brand<typeof AuthMethodBrand>);
36
+ readonly Unknown: ("unknown" & z.core.$brand<typeof AuthMethodBrand>) | ("password" & z.core.$brand<typeof AuthMethodBrand>) | ("saml" & z.core.$brand<typeof AuthMethodBrand>) | ("oauth" & z.core.$brand<typeof AuthMethodBrand>) | ("email_otp" & z.core.$brand<typeof AuthMethodBrand>);
37
37
  };
38
38
  declare const AMR: z.core.$ZodBranded<z.ZodEnum<{
39
39
  readonly Pwd: "pwd";
@@ -370,6 +370,30 @@ type JWTPayloadData = {
370
370
  declare const JWTPayload: z$1.ZodType<JWTPayloadData>;
371
371
  type JWTPayload = JWTPayload$1 & JWTPayloadData;
372
372
 
373
+ /**
374
+ * Checks if a JWT token has expired or will expire within a specified time window.
375
+ *
376
+ * @param token - The JWT token string to check
377
+ * @param expiresInMs - Optional time window in milliseconds. If provided, returns `true` if the token will expire within this time window
378
+ * @returns `true` if the token is expired (or will expire within the specified window), `false` otherwise
379
+ *
380
+ * @example
381
+ * ```typescript
382
+ * const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...'
383
+ *
384
+ * // Check if token is already expired
385
+ * if (isTokenExpired(token)) {
386
+ * console.log('Token has expired')
387
+ * }
388
+ *
389
+ * // Check if token will expire in the next 5 minutes
390
+ * if (isTokenExpired(token, 5 * 60 * 1000)) {
391
+ * console.log('Token will expire in the next 5 minutes')
392
+ * }
393
+ * ```
394
+ */
395
+ declare function isTokenExpired(token: string, expiresInMs?: number): boolean;
396
+
373
397
  declare const OAuthStepUpProvider: z.ZodEnum<{
374
398
  google: "google";
375
399
  microsoft: "microsoft";
@@ -631,6 +655,24 @@ type CompleteAuthorizationFlowResponse = {
631
655
  /** Assurance context embedded in the issued access token. */
632
656
  assurance: JWTPayload['assurance'];
633
657
  };
658
+ /**
659
+ * Credentials used to authenticate an application service account.
660
+ */
661
+ type ClientCredentialsGrant = {
662
+ /** The application identifier. */
663
+ clientId: string;
664
+ /** A valid secret issued for the application. */
665
+ clientSecret: string;
666
+ /** The organization to scope the access token to. */
667
+ organizationId: string;
668
+ };
669
+ /**
670
+ * Response returned after a successful client credentials exchange.
671
+ *
672
+ * Client credentials issue only a short-lived access token, so this response
673
+ * intentionally does not include a refresh token.
674
+ */
675
+ type ClientCredentialsTokenResponse = Omit<CompleteAuthorizationFlowResponse, 'refreshToken'>;
634
676
  /**
635
677
  * Response returned by the "who am I" endpoint.
636
678
  *
@@ -815,6 +857,7 @@ declare function isSessionAssuranceSufficient(sessionAssurance: JWTPayload['assu
815
857
  * Provides functionality for:
816
858
  * - OAuth authorization code flow (PKCE)
817
859
  * - Device authorization flow (RFC 8628)
860
+ * - Client credentials flow for service accounts
818
861
  * - Token management (refresh, exchange)
819
862
  * - Organization switching
820
863
  */
@@ -905,6 +948,16 @@ declare class ApplicationService {
905
948
  * @throws {ApplicationSessionAssuranceError} Assurance policy changed or is no longer met — start a new flow
906
949
  */
907
950
  exchangeDeviceCodeForTokens(deviceCode: string): Promise<CompleteAuthorizationFlowResponse>;
951
+ /**
952
+ * Exchanges application client credentials for a service-account access token.
953
+ *
954
+ * The returned token is scoped to the requested organization and cannot be
955
+ * refreshed. Exchange the credentials again when a new token is needed.
956
+ *
957
+ * @param credentials - Application credentials and target organization
958
+ * @returns A short-lived access token with service-account and organization details
959
+ */
960
+ exchangeClientCredentialsForToken(credentials: ClientCredentialsGrant): Promise<ClientCredentialsTokenResponse>;
908
961
  /**
909
962
  * Completes an authorization flow for a specific application.
910
963
  *
@@ -4482,6 +4535,17 @@ declare function createAPIClient(apiUrl: string, fetchOptions?: BetterFetchOptio
4482
4535
  data: CompleteAuthorizationFlowResponse;
4483
4536
  error: null;
4484
4537
  }>;
4538
+ exchangeClientCredentialsForToken: (credentials: ClientCredentialsGrant) => Promise<{
4539
+ data: ClientCredentialsTokenResponse;
4540
+ error: null;
4541
+ } | {
4542
+ data: null;
4543
+ error: {
4544
+ message?: string | undefined;
4545
+ status: number;
4546
+ statusText: string;
4547
+ };
4548
+ }>;
4485
4549
  completeAuthorizationFlow: (code: string, codeVerifier: string) => Promise<{
4486
4550
  data: null;
4487
4551
  error: {
@@ -5767,6 +5831,39 @@ type CreateAdminApplicationEntitlementOptions = {
5767
5831
  /** Organization that should be entitled to the application. */
5768
5832
  organizationId: string;
5769
5833
  };
5834
+ /**
5835
+ * Options accepted when creating a client secret for an application.
5836
+ */
5837
+ type CreateAdminApplicationSecretOptions = {
5838
+ /** Optional display name for the secret. */
5839
+ name?: string;
5840
+ };
5841
+ /**
5842
+ * Client secret metadata returned by application management endpoints.
5843
+ */
5844
+ type AdminApplicationSecret = {
5845
+ id: string;
5846
+ applicationId: string;
5847
+ name: string | null;
5848
+ /** First characters of the secret, kept for later identification. */
5849
+ start: string;
5850
+ createdAt: string;
5851
+ lastUsedAt: string | null;
5852
+ };
5853
+ /**
5854
+ * Service-account identity backing client-credentials tokens for an application.
5855
+ */
5856
+ type AdminApplicationIdentity = {
5857
+ id: string;
5858
+ email: string;
5859
+ };
5860
+ /**
5861
+ * Newly-created client secret. The plaintext secret is returned only once.
5862
+ */
5863
+ type CreatedAdminApplicationSecret = AdminApplicationSecret & {
5864
+ secret: string;
5865
+ serviceAccountUser: AdminApplicationIdentity;
5866
+ };
5770
5867
  /**
5771
5868
  * Options accepted when fetching a single organization as a global admin.
5772
5869
  */
@@ -6079,6 +6176,8 @@ type AdminUserAccessApplication = {
6079
6176
  imageUrl: string | null;
6080
6177
  /** Effective role from entitlement rule evaluation. Null when the first matching rule denies access or no rule matches. */
6081
6178
  role: string | null;
6179
+ /** User-facing name from the application role catalog. Null when no role matches. */
6180
+ roleDisplayName: string | null;
6082
6181
  };
6083
6182
  /**
6084
6183
  * Team row in an admin user access report.
@@ -6135,6 +6234,17 @@ type AdminApplicationEntitlement = {
6135
6234
  * Entitlement record returned by entitlement mutation endpoints.
6136
6235
  */
6137
6236
  type AdminApplicationEntitlementRecord = Omit<AdminApplicationEntitlement, 'organization'>;
6237
+ /** Application-scoped role definition used by entitlement rules. */
6238
+ type AdminApplicationRole = {
6239
+ applicationId: string;
6240
+ name: string;
6241
+ displayName: string;
6242
+ description: string;
6243
+ };
6244
+ /** Options accepted when creating an application role. */
6245
+ type CreateAdminApplicationRoleOptions = Omit<AdminApplicationRole, 'applicationId'>;
6246
+ /** Options accepted when updating an application role. */
6247
+ type UpdateAdminApplicationRoleOptions = CreateAdminApplicationRoleOptions;
6138
6248
  /** Kind of matcher an entitlement rule applies. */
6139
6249
  type AdminEntitlementRuleType = 'email_equals' | 'email_domain_equals' | 'organization_member' | 'user_id_equals';
6140
6250
  /**
@@ -6145,7 +6255,7 @@ type AdminEntitlementRuleType = 'email_equals' | 'email_domain_equals' | 'organi
6145
6255
  type AdminEntitlementRule = {
6146
6256
  type: AdminEntitlementRuleType;
6147
6257
  value: string;
6148
- role?: string | null;
6258
+ role: string | null;
6149
6259
  position: number;
6150
6260
  };
6151
6261
  /** An entitlement together with its ordered access rules. */
@@ -6180,6 +6290,8 @@ type AdminApplication = {
6180
6290
  redirectUris: string[];
6181
6291
  homeUrl: string | null;
6182
6292
  config: ApplicationConfig | null;
6293
+ serviceAccountUserId: string | null;
6294
+ serviceAccountUser?: AdminApplicationIdentity | null;
6183
6295
  createdBy: string;
6184
6296
  updatedBy: string;
6185
6297
  createdAt: string;
@@ -6339,6 +6451,14 @@ declare class AdminService {
6339
6451
  * @returns The updated application
6340
6452
  */
6341
6453
  updateApplication(applicationId: string, options: CreateAdminApplicationOptions): Promise<AdminApplicationRecord>;
6454
+ /** Lists the role catalog for an application. */
6455
+ listApplicationRoles(applicationId: string): Promise<AdminApplicationRole[]>;
6456
+ /** Creates an application-scoped role definition. */
6457
+ createApplicationRole(applicationId: string, options: CreateAdminApplicationRoleOptions): Promise<AdminApplicationRole>;
6458
+ /** Updates an application-scoped role definition. */
6459
+ updateApplicationRole(applicationId: string, currentName: string, options: UpdateAdminApplicationRoleOptions): Promise<AdminApplicationRole>;
6460
+ /** Deletes an application-scoped role definition. */
6461
+ deleteApplicationRole(applicationId: string, name: string): Promise<AdminApplicationRole>;
6342
6462
  /**
6343
6463
  * Enables an organization for an application.
6344
6464
  *
@@ -6347,6 +6467,36 @@ declare class AdminService {
6347
6467
  * @returns The created entitlement
6348
6468
  */
6349
6469
  createApplicationEntitlement(applicationId: string, options: CreateAdminApplicationEntitlementOptions): Promise<AdminApplicationEntitlementRecord>;
6470
+ /**
6471
+ * Lists client secrets for an application.
6472
+ *
6473
+ * @param applicationId - ID of the application to inspect
6474
+ * @returns Secret metadata without plaintext values
6475
+ */
6476
+ listApplicationSecrets(applicationId: string): Promise<AdminApplicationSecret[]>;
6477
+ /**
6478
+ * Creates a client secret for an application.
6479
+ *
6480
+ * @param applicationId - ID of the application to update
6481
+ * @param options - Optional secret display name
6482
+ * @returns The created secret, including the plaintext value shown only once
6483
+ */
6484
+ createApplicationSecret(applicationId: string, options?: CreateAdminApplicationSecretOptions): Promise<CreatedAdminApplicationSecret>;
6485
+ /**
6486
+ * Deletes one client secret for an application.
6487
+ *
6488
+ * @param applicationId - ID of the application to update
6489
+ * @param secretId - ID of the secret to delete
6490
+ * @returns The deleted secret metadata
6491
+ */
6492
+ deleteApplicationSecret(applicationId: string, secretId: string): Promise<AdminApplicationSecret>;
6493
+ /**
6494
+ * Retires an application's service-account identity.
6495
+ *
6496
+ * @param applicationId - ID of the application to update
6497
+ * @returns The updated application
6498
+ */
6499
+ retireApplicationServiceAccount(applicationId: string): Promise<AdminApplicationRecord>;
6350
6500
  /**
6351
6501
  * Lists all organizations, optionally with member and application previews
6352
6502
  * and totals when a preview size is requested.
@@ -6355,15 +6505,15 @@ declare class AdminService {
6355
6505
  * @returns The paginated organization listing
6356
6506
  */
6357
6507
  listOrganizations(options?: ListAdminOrganizationsOptions): Promise<{
6358
- data: ListAdminOrganizationsResult;
6359
- error: null;
6360
- } | {
6361
6508
  data: null;
6362
6509
  error: {
6363
6510
  message?: string | undefined;
6364
6511
  status: number;
6365
6512
  statusText: string;
6366
6513
  };
6514
+ } | {
6515
+ data: ListAdminOrganizationsResult;
6516
+ error: null;
6367
6517
  }>;
6368
6518
  /**
6369
6519
  * Lists users as a global admin, optionally with organization previews
@@ -7457,32 +7607,59 @@ declare class AuthClient {
7457
7607
  constructor(apiUrl: string, fetchOptions?: BetterFetchOption);
7458
7608
  }
7459
7609
 
7460
- type DecodedJWTPayload = Omit<JWTPayload, 'assurance'> & {
7461
- assurance?: JWTPayloadAssurance;
7610
+ /** Configuration for an in-memory application token cache. */
7611
+ type ApplicationTokenStorageOptions = {
7612
+ /** Base URL of the Auth API. */
7613
+ authApiUrl: string;
7614
+ /** Application identifier used for client-credentials exchanges. */
7615
+ clientId: string;
7616
+ /** Application secret used for client-credentials exchanges. */
7617
+ clientSecret: string;
7618
+ /** Full-jitter upper bound for the first retry delay. Defaults to 100 milliseconds. */
7619
+ initialBackoffMs?: number;
7620
+ /** Maximum number of exchange attempts, including the initial request. Defaults to 3. */
7621
+ maxAttempts?: number;
7622
+ /** Maximum number of organization tokens retained in memory. Defaults to 300. */
7623
+ maxOrganizations?: number;
7624
+ };
7625
+ /** Per-call retry overrides for {@link ApplicationTokenStorage.getToken}. */
7626
+ type GetTokenOptions = {
7627
+ /** Full-jitter upper bound for the first retry delay. */
7628
+ initialBackoffMs?: number;
7629
+ /** Maximum number of exchange attempts, including the initial request. */
7630
+ maxAttempts?: number;
7462
7631
  };
7463
7632
  /**
7464
- * Checks if a JWT token has expired or will expire within a specified time window.
7633
+ * Caches organization-scoped application tokens in memory.
7465
7634
  *
7466
- * @param token - The JWT token string to check
7467
- * @param expiresInMs - Optional time window in milliseconds. If provided, returns `true` if the token will expire within this time window
7468
- * @returns `true` if the token is expired (or will expire within the specified window), `false` otherwise
7469
- *
7470
- * @example
7471
- * ```typescript
7472
- * const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...'
7473
- *
7474
- * // Check if token is already expired
7475
- * if (isTokenExpired(token)) {
7476
- * console.log('Token has expired')
7477
- * }
7478
- *
7479
- * // Check if token will expire in the next 5 minutes
7480
- * if (isTokenExpired(token, 5 * 60 * 1000)) {
7481
- * console.log('Token will expire in the next 5 minutes')
7482
- * }
7483
- * ```
7635
+ * Tokens are reissued one minute before expiry. Cache hits promote their
7636
+ * organization to the most-recently-used position, and insertions beyond the
7637
+ * configured limit evict the least recently used organization. Transient
7638
+ * exchange failures use full-jitter exponential backoff.
7484
7639
  */
7485
- declare function isTokenExpired(token: string, expiresInMs?: number): boolean;
7640
+ declare class ApplicationTokenStorage {
7641
+ private readonly auth;
7642
+ private readonly clientId;
7643
+ private readonly clientSecret;
7644
+ private readonly initialBackoffMs;
7645
+ private readonly inFlight;
7646
+ private readonly maxAttempts;
7647
+ private readonly maxOrganizations;
7648
+ private readonly tokens;
7649
+ /** Creates an application token cache for one client-credentials identity. */
7650
+ constructor({ authApiUrl, clientId, clientSecret, initialBackoffMs, maxAttempts, maxOrganizations, }: ApplicationTokenStorageOptions);
7651
+ /** Returns a fresh access token scoped to the requested organization. */
7652
+ getToken(organizationId: string, { initialBackoffMs, maxAttempts, }?: GetTokenOptions): Promise<string>;
7653
+ private exchangeTokenWithRetry;
7654
+ /** Performs the client-credentials exchange for one organization. */
7655
+ protected exchangeToken(organizationId: string): Promise<string>;
7656
+ /** Waits before the next exchange attempt. */
7657
+ protected wait(delayMs: number): Promise<void>;
7658
+ }
7659
+
7660
+ type DecodedJWTPayload = Omit<JWTPayload, 'assurance'> & {
7661
+ assurance?: JWTPayloadAssurance;
7662
+ };
7486
7663
  /**
7487
7664
  * Validates a JWT token by checking its expiration and verifying its signature
7488
7665
  * against the JWKS endpoint.
@@ -7521,5 +7698,5 @@ declare function validateToken(token: string, apiUrl: string): Promise<boolean>;
7521
7698
  */
7522
7699
  declare function extractTokenPayload(token: string): DecodedJWTPayload;
7523
7700
 
7524
- export { AAL, AALBrand, AALs, ACR, ACRBrand, ACRs, AMR, AMRBrand, AMRs, ApiKeyMetadata, ApplicationConfig, ApplicationError, ApplicationSessionAssuranceConfig, ApplicationSessionAssuranceError, AuthClient, AuthMethod, AuthMethodBrand, AuthMethods, AuthorizationFlowError, DEFAULT_APPLICATION_CONFIG, DEFAULT_JWT_ASSURANCE, DEFAULT_SESSION_ASSURANCE_CONFIG, DeviceAccessDeniedError, DeviceAuthorizationPendingError, DeviceAuthorizationSlowDownError, DeviceCodeExpiredError, DeviceTransientServerError, EmailOTPService, EmailRequired, InvalidCallbackURL, InvalidSocialProvider, JWTPayload, JWTPayloadAssurance, JWTPayloadUser, JWTPayloadWorkspace, OAuthBeginStepUpResponse, OAuthCompleteStepUpResponse, OAuthStepUpProvider, RefreshTokenExpiredError, Roles, SENSITIVE_ACTION_ASSURANCE_POLICY, STEP_UP_MAX_AGE_SECONDS, SendOtpResponse, SessionAssurancePolicy, SessionAssuranceRequiredError, SessionAssuranceRequiredPayload, SessionAssuranceResponse, SessionAssuranceService, StepUpOption, UserNotLoggedInError, ac, assuranceFields, canStepUpWithAmr, createAPIClient, extractTokenPayload, getAalRank, invitationAdditionalFields, isAssuranceSufficientForPolicy, isSessionAssuranceSufficient, isTokenExpired, memberAdditionalFields, mergeStepUpAssurance, organizationAdditionalFields, parseSessionAssuranceRequiredPayload, rolesAccessControl, sessionAssurancePluginClient, userAdditionalFields, validateToken };
7525
- export type { APIClient, AcceptInvitationResult, AdminApplication, AdminApplicationEntitlement, AdminApplicationEntitlementRecord, AdminApplicationMember, AdminApplicationOrganization, AdminApplicationRecord, AdminEntitlementRule, AdminEntitlementRuleType, AdminEntitlementWithRules, AdminMember, AdminOrganization, AdminOrganizationApplication, AdminOrganizationMember, AdminOrganizationMemberApplication, AdminOrganizationMemberRecord, AdminOrganizationMemberTeam, AdminOrganizationSummary, AdminOrganizationTeam, AdminOrganizationTeamRecord, AdminUser, AdminUserAccess, AdminUserAccessApplication, AdminUserAccessOrganization, AdminUserAccessTeam, AdminUserInvitation, AdminUserOrganization, ApiKey, ApiKeyWithoutSecret, Application, ApplicationAuthContextResponse, AssuranceFields, BaseOrganization, BeginOAuthStepUpParams, BillingMode, CompleteAuthorizationFlowResponse, CreateAdminApplicationEntitlementOptions, CreateAdminApplicationOptions, CreateAdminOrganizationOptions, CreateAdminOrganizationTeamOptions, CreateApiKeyPayload, CreateTeamPayload, DecodedJWTPayload, DeviceAuthorizationActionResponse, DeviceAuthorizationContextResponse, DeviceAuthorizationResponse, DeviceContextApplication, EmailOTPSignInOptions, FullOrganization, GetAdminOrganizationOptions, GetAdminOrganizationsSummaryOptions, Invitation, InviteAdminOrganizationUsersOptions, InviteAdminOrganizationUsersResult, InviteUserToOrganizationOptions, JWTPayloadData, ListAdminApplicationsOptions, ListAdminApplicationsResult, ListAdminOrganizationMembersOptions, ListAdminOrganizationMembersResult, ListAdminOrganizationTeamsOptions, ListAdminOrganizationTeamsResult, ListAdminOrganizationsOptions, ListAdminOrganizationsResult, ListAdminUsersOptions, ListAdminUsersResult, ListCandidateOrganizationsResponse, ListMembersOptions, Member, ExtendedOrganization as Organization, OrganizationSettings, RemoveAdminMemberOptions, RemoveUserFromOrganizationOptions, ResendAdminInviteOptions, Role, SendVerificationOTPOptions, Session, SessionAssuranceActions, SessionData, SetAdminEntitlementRulesOptions, SetAdminUserApplicationRoleOptions, SetAdminUserTeamOptions, SignInWithEmailAndPasswordOptions, SignInWithSamlOptions, SocialSignInOptions, StartAuthorizationFlowResponse, Strict, Team, TeamMember, UpdateAdminEntitlementOptions, UpdateAdminMemberRoleOptions, UpdateAdminOrganizationOptions, UpdateApiKeyPayload, UpdateMemberRoleOptions, UpdateOrganizationPayload, UpdateTeamPayload, User, UserProfile, WhoAmIInclude, WhoAmIOptions, WhoAmIOrganization, WhoAmIResponse };
7701
+ export { AAL, AALBrand, AALs, ACR, ACRBrand, ACRs, AMR, AMRBrand, AMRs, ApiKeyMetadata, ApplicationConfig, ApplicationError, ApplicationSessionAssuranceConfig, ApplicationSessionAssuranceError, ApplicationTokenStorage, AuthClient, AuthMethod, AuthMethodBrand, AuthMethods, AuthorizationFlowError, DEFAULT_APPLICATION_CONFIG, DEFAULT_JWT_ASSURANCE, DEFAULT_SESSION_ASSURANCE_CONFIG, DeviceAccessDeniedError, DeviceAuthorizationPendingError, DeviceAuthorizationSlowDownError, DeviceCodeExpiredError, DeviceTransientServerError, EmailOTPService, EmailRequired, InvalidCallbackURL, InvalidSocialProvider, JWTPayload, JWTPayloadAssurance, JWTPayloadUser, JWTPayloadWorkspace, OAuthBeginStepUpResponse, OAuthCompleteStepUpResponse, OAuthStepUpProvider, RefreshTokenExpiredError, Roles, SENSITIVE_ACTION_ASSURANCE_POLICY, STEP_UP_MAX_AGE_SECONDS, SendOtpResponse, SessionAssurancePolicy, SessionAssuranceRequiredError, SessionAssuranceRequiredPayload, SessionAssuranceResponse, SessionAssuranceService, StepUpOption, UserNotLoggedInError, ac, assuranceFields, canStepUpWithAmr, createAPIClient, extractTokenPayload, getAalRank, invitationAdditionalFields, isAssuranceSufficientForPolicy, isSessionAssuranceSufficient, isTokenExpired, memberAdditionalFields, mergeStepUpAssurance, organizationAdditionalFields, parseSessionAssuranceRequiredPayload, rolesAccessControl, sessionAssurancePluginClient, userAdditionalFields, validateToken };
7702
+ export type { APIClient, AcceptInvitationResult, AdminApplication, AdminApplicationEntitlement, AdminApplicationEntitlementRecord, AdminApplicationIdentity, AdminApplicationMember, AdminApplicationOrganization, AdminApplicationRecord, AdminApplicationRole, AdminApplicationSecret, AdminEntitlementRule, AdminEntitlementRuleType, AdminEntitlementWithRules, AdminMember, AdminOrganization, AdminOrganizationApplication, AdminOrganizationMember, AdminOrganizationMemberApplication, AdminOrganizationMemberRecord, AdminOrganizationMemberTeam, AdminOrganizationSummary, AdminOrganizationTeam, AdminOrganizationTeamRecord, AdminUser, AdminUserAccess, AdminUserAccessApplication, AdminUserAccessOrganization, AdminUserAccessTeam, AdminUserInvitation, AdminUserOrganization, ApiKey, ApiKeyWithoutSecret, Application, ApplicationAuthContextResponse, ApplicationTokenStorageOptions, AssuranceFields, BaseOrganization, BeginOAuthStepUpParams, BillingMode, ClientCredentialsGrant, ClientCredentialsTokenResponse, CompleteAuthorizationFlowResponse, CreateAdminApplicationEntitlementOptions, CreateAdminApplicationOptions, CreateAdminApplicationRoleOptions, CreateAdminApplicationSecretOptions, CreateAdminOrganizationOptions, CreateAdminOrganizationTeamOptions, CreateApiKeyPayload, CreateTeamPayload, CreatedAdminApplicationSecret, DecodedJWTPayload, DeviceAuthorizationActionResponse, DeviceAuthorizationContextResponse, DeviceAuthorizationResponse, DeviceContextApplication, EmailOTPSignInOptions, FullOrganization, GetAdminOrganizationOptions, GetAdminOrganizationsSummaryOptions, GetTokenOptions, Invitation, InviteAdminOrganizationUsersOptions, InviteAdminOrganizationUsersResult, InviteUserToOrganizationOptions, JWTPayloadData, ListAdminApplicationsOptions, ListAdminApplicationsResult, ListAdminOrganizationMembersOptions, ListAdminOrganizationMembersResult, ListAdminOrganizationTeamsOptions, ListAdminOrganizationTeamsResult, ListAdminOrganizationsOptions, ListAdminOrganizationsResult, ListAdminUsersOptions, ListAdminUsersResult, ListCandidateOrganizationsResponse, ListMembersOptions, Member, ExtendedOrganization as Organization, OrganizationSettings, RemoveAdminMemberOptions, RemoveUserFromOrganizationOptions, ResendAdminInviteOptions, Role, SendVerificationOTPOptions, Session, SessionAssuranceActions, SessionData, SetAdminEntitlementRulesOptions, SetAdminUserApplicationRoleOptions, SetAdminUserTeamOptions, SignInWithEmailAndPasswordOptions, SignInWithSamlOptions, SocialSignInOptions, StartAuthorizationFlowResponse, Strict, Team, TeamMember, UpdateAdminApplicationRoleOptions, UpdateAdminEntitlementOptions, UpdateAdminMemberRoleOptions, UpdateAdminOrganizationOptions, UpdateApiKeyPayload, UpdateMemberRoleOptions, UpdateOrganizationPayload, UpdateTeamPayload, User, UserProfile, WhoAmIInclude, WhoAmIOptions, WhoAmIOrganization, WhoAmIResponse };
package/dist/index.d.ts CHANGED
@@ -14,11 +14,11 @@ declare const AALBrand: unique symbol;
14
14
  declare const ACRBrand: unique symbol;
15
15
  declare const AuthMethodBrand: unique symbol;
16
16
  declare const AMRs: {
17
- readonly Pwd: ("pwd" & z.core.$brand<typeof AMRBrand>) | ("otp" & z.core.$brand<typeof AMRBrand>) | ("oauth" & z.core.$brand<typeof AMRBrand>) | ("sso" & z.core.$brand<typeof AMRBrand>) | ("saml" & z.core.$brand<typeof AMRBrand>);
18
- readonly Otp: ("pwd" & z.core.$brand<typeof AMRBrand>) | ("otp" & z.core.$brand<typeof AMRBrand>) | ("oauth" & z.core.$brand<typeof AMRBrand>) | ("sso" & z.core.$brand<typeof AMRBrand>) | ("saml" & z.core.$brand<typeof AMRBrand>);
19
- readonly Oauth: ("pwd" & z.core.$brand<typeof AMRBrand>) | ("otp" & z.core.$brand<typeof AMRBrand>) | ("oauth" & z.core.$brand<typeof AMRBrand>) | ("sso" & z.core.$brand<typeof AMRBrand>) | ("saml" & z.core.$brand<typeof AMRBrand>);
20
- readonly Sso: ("pwd" & z.core.$brand<typeof AMRBrand>) | ("otp" & z.core.$brand<typeof AMRBrand>) | ("oauth" & z.core.$brand<typeof AMRBrand>) | ("sso" & z.core.$brand<typeof AMRBrand>) | ("saml" & z.core.$brand<typeof AMRBrand>);
21
- readonly Saml: ("pwd" & z.core.$brand<typeof AMRBrand>) | ("otp" & z.core.$brand<typeof AMRBrand>) | ("oauth" & z.core.$brand<typeof AMRBrand>) | ("sso" & z.core.$brand<typeof AMRBrand>) | ("saml" & z.core.$brand<typeof AMRBrand>);
17
+ readonly Pwd: ("sso" & z.core.$brand<typeof AMRBrand>) | ("saml" & z.core.$brand<typeof AMRBrand>) | ("otp" & z.core.$brand<typeof AMRBrand>) | ("pwd" & z.core.$brand<typeof AMRBrand>) | ("oauth" & z.core.$brand<typeof AMRBrand>);
18
+ readonly Otp: ("sso" & z.core.$brand<typeof AMRBrand>) | ("saml" & z.core.$brand<typeof AMRBrand>) | ("otp" & z.core.$brand<typeof AMRBrand>) | ("pwd" & z.core.$brand<typeof AMRBrand>) | ("oauth" & z.core.$brand<typeof AMRBrand>);
19
+ readonly Oauth: ("sso" & z.core.$brand<typeof AMRBrand>) | ("saml" & z.core.$brand<typeof AMRBrand>) | ("otp" & z.core.$brand<typeof AMRBrand>) | ("pwd" & z.core.$brand<typeof AMRBrand>) | ("oauth" & z.core.$brand<typeof AMRBrand>);
20
+ readonly Sso: ("sso" & z.core.$brand<typeof AMRBrand>) | ("saml" & z.core.$brand<typeof AMRBrand>) | ("otp" & z.core.$brand<typeof AMRBrand>) | ("pwd" & z.core.$brand<typeof AMRBrand>) | ("oauth" & z.core.$brand<typeof AMRBrand>);
21
+ readonly Saml: ("sso" & z.core.$brand<typeof AMRBrand>) | ("saml" & z.core.$brand<typeof AMRBrand>) | ("otp" & z.core.$brand<typeof AMRBrand>) | ("pwd" & z.core.$brand<typeof AMRBrand>) | ("oauth" & z.core.$brand<typeof AMRBrand>);
22
22
  };
23
23
  declare const AALs: {
24
24
  readonly Aal1: ("aal1" & z.core.$brand<typeof AALBrand>) | ("aal2" & z.core.$brand<typeof AALBrand>) | ("aal3" & z.core.$brand<typeof AALBrand>);
@@ -29,11 +29,11 @@ declare const ACRs: {
29
29
  readonly Unspecified: "urn:tela:auth:unspecified" & z.core.$brand<typeof ACRBrand>;
30
30
  };
31
31
  declare const AuthMethods: {
32
- readonly Password: ("oauth" & z.core.$brand<typeof AuthMethodBrand>) | ("saml" & z.core.$brand<typeof AuthMethodBrand>) | ("password" & z.core.$brand<typeof AuthMethodBrand>) | ("email_otp" & z.core.$brand<typeof AuthMethodBrand>) | ("unknown" & z.core.$brand<typeof AuthMethodBrand>);
33
- readonly EmailOTP: ("oauth" & z.core.$brand<typeof AuthMethodBrand>) | ("saml" & z.core.$brand<typeof AuthMethodBrand>) | ("password" & z.core.$brand<typeof AuthMethodBrand>) | ("email_otp" & z.core.$brand<typeof AuthMethodBrand>) | ("unknown" & z.core.$brand<typeof AuthMethodBrand>);
34
- readonly OAuth: ("oauth" & z.core.$brand<typeof AuthMethodBrand>) | ("saml" & z.core.$brand<typeof AuthMethodBrand>) | ("password" & z.core.$brand<typeof AuthMethodBrand>) | ("email_otp" & z.core.$brand<typeof AuthMethodBrand>) | ("unknown" & z.core.$brand<typeof AuthMethodBrand>);
35
- readonly SAML: ("oauth" & z.core.$brand<typeof AuthMethodBrand>) | ("saml" & z.core.$brand<typeof AuthMethodBrand>) | ("password" & z.core.$brand<typeof AuthMethodBrand>) | ("email_otp" & z.core.$brand<typeof AuthMethodBrand>) | ("unknown" & z.core.$brand<typeof AuthMethodBrand>);
36
- readonly Unknown: ("oauth" & z.core.$brand<typeof AuthMethodBrand>) | ("saml" & z.core.$brand<typeof AuthMethodBrand>) | ("password" & z.core.$brand<typeof AuthMethodBrand>) | ("email_otp" & z.core.$brand<typeof AuthMethodBrand>) | ("unknown" & z.core.$brand<typeof AuthMethodBrand>);
32
+ readonly Password: ("unknown" & z.core.$brand<typeof AuthMethodBrand>) | ("password" & z.core.$brand<typeof AuthMethodBrand>) | ("saml" & z.core.$brand<typeof AuthMethodBrand>) | ("oauth" & z.core.$brand<typeof AuthMethodBrand>) | ("email_otp" & z.core.$brand<typeof AuthMethodBrand>);
33
+ readonly EmailOTP: ("unknown" & z.core.$brand<typeof AuthMethodBrand>) | ("password" & z.core.$brand<typeof AuthMethodBrand>) | ("saml" & z.core.$brand<typeof AuthMethodBrand>) | ("oauth" & z.core.$brand<typeof AuthMethodBrand>) | ("email_otp" & z.core.$brand<typeof AuthMethodBrand>);
34
+ readonly OAuth: ("unknown" & z.core.$brand<typeof AuthMethodBrand>) | ("password" & z.core.$brand<typeof AuthMethodBrand>) | ("saml" & z.core.$brand<typeof AuthMethodBrand>) | ("oauth" & z.core.$brand<typeof AuthMethodBrand>) | ("email_otp" & z.core.$brand<typeof AuthMethodBrand>);
35
+ readonly SAML: ("unknown" & z.core.$brand<typeof AuthMethodBrand>) | ("password" & z.core.$brand<typeof AuthMethodBrand>) | ("saml" & z.core.$brand<typeof AuthMethodBrand>) | ("oauth" & z.core.$brand<typeof AuthMethodBrand>) | ("email_otp" & z.core.$brand<typeof AuthMethodBrand>);
36
+ readonly Unknown: ("unknown" & z.core.$brand<typeof AuthMethodBrand>) | ("password" & z.core.$brand<typeof AuthMethodBrand>) | ("saml" & z.core.$brand<typeof AuthMethodBrand>) | ("oauth" & z.core.$brand<typeof AuthMethodBrand>) | ("email_otp" & z.core.$brand<typeof AuthMethodBrand>);
37
37
  };
38
38
  declare const AMR: z.core.$ZodBranded<z.ZodEnum<{
39
39
  readonly Pwd: "pwd";
@@ -370,6 +370,30 @@ type JWTPayloadData = {
370
370
  declare const JWTPayload: z$1.ZodType<JWTPayloadData>;
371
371
  type JWTPayload = JWTPayload$1 & JWTPayloadData;
372
372
 
373
+ /**
374
+ * Checks if a JWT token has expired or will expire within a specified time window.
375
+ *
376
+ * @param token - The JWT token string to check
377
+ * @param expiresInMs - Optional time window in milliseconds. If provided, returns `true` if the token will expire within this time window
378
+ * @returns `true` if the token is expired (or will expire within the specified window), `false` otherwise
379
+ *
380
+ * @example
381
+ * ```typescript
382
+ * const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...'
383
+ *
384
+ * // Check if token is already expired
385
+ * if (isTokenExpired(token)) {
386
+ * console.log('Token has expired')
387
+ * }
388
+ *
389
+ * // Check if token will expire in the next 5 minutes
390
+ * if (isTokenExpired(token, 5 * 60 * 1000)) {
391
+ * console.log('Token will expire in the next 5 minutes')
392
+ * }
393
+ * ```
394
+ */
395
+ declare function isTokenExpired(token: string, expiresInMs?: number): boolean;
396
+
373
397
  declare const OAuthStepUpProvider: z.ZodEnum<{
374
398
  google: "google";
375
399
  microsoft: "microsoft";
@@ -631,6 +655,24 @@ type CompleteAuthorizationFlowResponse = {
631
655
  /** Assurance context embedded in the issued access token. */
632
656
  assurance: JWTPayload['assurance'];
633
657
  };
658
+ /**
659
+ * Credentials used to authenticate an application service account.
660
+ */
661
+ type ClientCredentialsGrant = {
662
+ /** The application identifier. */
663
+ clientId: string;
664
+ /** A valid secret issued for the application. */
665
+ clientSecret: string;
666
+ /** The organization to scope the access token to. */
667
+ organizationId: string;
668
+ };
669
+ /**
670
+ * Response returned after a successful client credentials exchange.
671
+ *
672
+ * Client credentials issue only a short-lived access token, so this response
673
+ * intentionally does not include a refresh token.
674
+ */
675
+ type ClientCredentialsTokenResponse = Omit<CompleteAuthorizationFlowResponse, 'refreshToken'>;
634
676
  /**
635
677
  * Response returned by the "who am I" endpoint.
636
678
  *
@@ -815,6 +857,7 @@ declare function isSessionAssuranceSufficient(sessionAssurance: JWTPayload['assu
815
857
  * Provides functionality for:
816
858
  * - OAuth authorization code flow (PKCE)
817
859
  * - Device authorization flow (RFC 8628)
860
+ * - Client credentials flow for service accounts
818
861
  * - Token management (refresh, exchange)
819
862
  * - Organization switching
820
863
  */
@@ -905,6 +948,16 @@ declare class ApplicationService {
905
948
  * @throws {ApplicationSessionAssuranceError} Assurance policy changed or is no longer met — start a new flow
906
949
  */
907
950
  exchangeDeviceCodeForTokens(deviceCode: string): Promise<CompleteAuthorizationFlowResponse>;
951
+ /**
952
+ * Exchanges application client credentials for a service-account access token.
953
+ *
954
+ * The returned token is scoped to the requested organization and cannot be
955
+ * refreshed. Exchange the credentials again when a new token is needed.
956
+ *
957
+ * @param credentials - Application credentials and target organization
958
+ * @returns A short-lived access token with service-account and organization details
959
+ */
960
+ exchangeClientCredentialsForToken(credentials: ClientCredentialsGrant): Promise<ClientCredentialsTokenResponse>;
908
961
  /**
909
962
  * Completes an authorization flow for a specific application.
910
963
  *
@@ -4482,6 +4535,17 @@ declare function createAPIClient(apiUrl: string, fetchOptions?: BetterFetchOptio
4482
4535
  data: CompleteAuthorizationFlowResponse;
4483
4536
  error: null;
4484
4537
  }>;
4538
+ exchangeClientCredentialsForToken: (credentials: ClientCredentialsGrant) => Promise<{
4539
+ data: ClientCredentialsTokenResponse;
4540
+ error: null;
4541
+ } | {
4542
+ data: null;
4543
+ error: {
4544
+ message?: string | undefined;
4545
+ status: number;
4546
+ statusText: string;
4547
+ };
4548
+ }>;
4485
4549
  completeAuthorizationFlow: (code: string, codeVerifier: string) => Promise<{
4486
4550
  data: null;
4487
4551
  error: {
@@ -5767,6 +5831,39 @@ type CreateAdminApplicationEntitlementOptions = {
5767
5831
  /** Organization that should be entitled to the application. */
5768
5832
  organizationId: string;
5769
5833
  };
5834
+ /**
5835
+ * Options accepted when creating a client secret for an application.
5836
+ */
5837
+ type CreateAdminApplicationSecretOptions = {
5838
+ /** Optional display name for the secret. */
5839
+ name?: string;
5840
+ };
5841
+ /**
5842
+ * Client secret metadata returned by application management endpoints.
5843
+ */
5844
+ type AdminApplicationSecret = {
5845
+ id: string;
5846
+ applicationId: string;
5847
+ name: string | null;
5848
+ /** First characters of the secret, kept for later identification. */
5849
+ start: string;
5850
+ createdAt: string;
5851
+ lastUsedAt: string | null;
5852
+ };
5853
+ /**
5854
+ * Service-account identity backing client-credentials tokens for an application.
5855
+ */
5856
+ type AdminApplicationIdentity = {
5857
+ id: string;
5858
+ email: string;
5859
+ };
5860
+ /**
5861
+ * Newly-created client secret. The plaintext secret is returned only once.
5862
+ */
5863
+ type CreatedAdminApplicationSecret = AdminApplicationSecret & {
5864
+ secret: string;
5865
+ serviceAccountUser: AdminApplicationIdentity;
5866
+ };
5770
5867
  /**
5771
5868
  * Options accepted when fetching a single organization as a global admin.
5772
5869
  */
@@ -6079,6 +6176,8 @@ type AdminUserAccessApplication = {
6079
6176
  imageUrl: string | null;
6080
6177
  /** Effective role from entitlement rule evaluation. Null when the first matching rule denies access or no rule matches. */
6081
6178
  role: string | null;
6179
+ /** User-facing name from the application role catalog. Null when no role matches. */
6180
+ roleDisplayName: string | null;
6082
6181
  };
6083
6182
  /**
6084
6183
  * Team row in an admin user access report.
@@ -6135,6 +6234,17 @@ type AdminApplicationEntitlement = {
6135
6234
  * Entitlement record returned by entitlement mutation endpoints.
6136
6235
  */
6137
6236
  type AdminApplicationEntitlementRecord = Omit<AdminApplicationEntitlement, 'organization'>;
6237
+ /** Application-scoped role definition used by entitlement rules. */
6238
+ type AdminApplicationRole = {
6239
+ applicationId: string;
6240
+ name: string;
6241
+ displayName: string;
6242
+ description: string;
6243
+ };
6244
+ /** Options accepted when creating an application role. */
6245
+ type CreateAdminApplicationRoleOptions = Omit<AdminApplicationRole, 'applicationId'>;
6246
+ /** Options accepted when updating an application role. */
6247
+ type UpdateAdminApplicationRoleOptions = CreateAdminApplicationRoleOptions;
6138
6248
  /** Kind of matcher an entitlement rule applies. */
6139
6249
  type AdminEntitlementRuleType = 'email_equals' | 'email_domain_equals' | 'organization_member' | 'user_id_equals';
6140
6250
  /**
@@ -6145,7 +6255,7 @@ type AdminEntitlementRuleType = 'email_equals' | 'email_domain_equals' | 'organi
6145
6255
  type AdminEntitlementRule = {
6146
6256
  type: AdminEntitlementRuleType;
6147
6257
  value: string;
6148
- role?: string | null;
6258
+ role: string | null;
6149
6259
  position: number;
6150
6260
  };
6151
6261
  /** An entitlement together with its ordered access rules. */
@@ -6180,6 +6290,8 @@ type AdminApplication = {
6180
6290
  redirectUris: string[];
6181
6291
  homeUrl: string | null;
6182
6292
  config: ApplicationConfig | null;
6293
+ serviceAccountUserId: string | null;
6294
+ serviceAccountUser?: AdminApplicationIdentity | null;
6183
6295
  createdBy: string;
6184
6296
  updatedBy: string;
6185
6297
  createdAt: string;
@@ -6339,6 +6451,14 @@ declare class AdminService {
6339
6451
  * @returns The updated application
6340
6452
  */
6341
6453
  updateApplication(applicationId: string, options: CreateAdminApplicationOptions): Promise<AdminApplicationRecord>;
6454
+ /** Lists the role catalog for an application. */
6455
+ listApplicationRoles(applicationId: string): Promise<AdminApplicationRole[]>;
6456
+ /** Creates an application-scoped role definition. */
6457
+ createApplicationRole(applicationId: string, options: CreateAdminApplicationRoleOptions): Promise<AdminApplicationRole>;
6458
+ /** Updates an application-scoped role definition. */
6459
+ updateApplicationRole(applicationId: string, currentName: string, options: UpdateAdminApplicationRoleOptions): Promise<AdminApplicationRole>;
6460
+ /** Deletes an application-scoped role definition. */
6461
+ deleteApplicationRole(applicationId: string, name: string): Promise<AdminApplicationRole>;
6342
6462
  /**
6343
6463
  * Enables an organization for an application.
6344
6464
  *
@@ -6347,6 +6467,36 @@ declare class AdminService {
6347
6467
  * @returns The created entitlement
6348
6468
  */
6349
6469
  createApplicationEntitlement(applicationId: string, options: CreateAdminApplicationEntitlementOptions): Promise<AdminApplicationEntitlementRecord>;
6470
+ /**
6471
+ * Lists client secrets for an application.
6472
+ *
6473
+ * @param applicationId - ID of the application to inspect
6474
+ * @returns Secret metadata without plaintext values
6475
+ */
6476
+ listApplicationSecrets(applicationId: string): Promise<AdminApplicationSecret[]>;
6477
+ /**
6478
+ * Creates a client secret for an application.
6479
+ *
6480
+ * @param applicationId - ID of the application to update
6481
+ * @param options - Optional secret display name
6482
+ * @returns The created secret, including the plaintext value shown only once
6483
+ */
6484
+ createApplicationSecret(applicationId: string, options?: CreateAdminApplicationSecretOptions): Promise<CreatedAdminApplicationSecret>;
6485
+ /**
6486
+ * Deletes one client secret for an application.
6487
+ *
6488
+ * @param applicationId - ID of the application to update
6489
+ * @param secretId - ID of the secret to delete
6490
+ * @returns The deleted secret metadata
6491
+ */
6492
+ deleteApplicationSecret(applicationId: string, secretId: string): Promise<AdminApplicationSecret>;
6493
+ /**
6494
+ * Retires an application's service-account identity.
6495
+ *
6496
+ * @param applicationId - ID of the application to update
6497
+ * @returns The updated application
6498
+ */
6499
+ retireApplicationServiceAccount(applicationId: string): Promise<AdminApplicationRecord>;
6350
6500
  /**
6351
6501
  * Lists all organizations, optionally with member and application previews
6352
6502
  * and totals when a preview size is requested.
@@ -6355,15 +6505,15 @@ declare class AdminService {
6355
6505
  * @returns The paginated organization listing
6356
6506
  */
6357
6507
  listOrganizations(options?: ListAdminOrganizationsOptions): Promise<{
6358
- data: ListAdminOrganizationsResult;
6359
- error: null;
6360
- } | {
6361
6508
  data: null;
6362
6509
  error: {
6363
6510
  message?: string | undefined;
6364
6511
  status: number;
6365
6512
  statusText: string;
6366
6513
  };
6514
+ } | {
6515
+ data: ListAdminOrganizationsResult;
6516
+ error: null;
6367
6517
  }>;
6368
6518
  /**
6369
6519
  * Lists users as a global admin, optionally with organization previews
@@ -7457,32 +7607,59 @@ declare class AuthClient {
7457
7607
  constructor(apiUrl: string, fetchOptions?: BetterFetchOption);
7458
7608
  }
7459
7609
 
7460
- type DecodedJWTPayload = Omit<JWTPayload, 'assurance'> & {
7461
- assurance?: JWTPayloadAssurance;
7610
+ /** Configuration for an in-memory application token cache. */
7611
+ type ApplicationTokenStorageOptions = {
7612
+ /** Base URL of the Auth API. */
7613
+ authApiUrl: string;
7614
+ /** Application identifier used for client-credentials exchanges. */
7615
+ clientId: string;
7616
+ /** Application secret used for client-credentials exchanges. */
7617
+ clientSecret: string;
7618
+ /** Full-jitter upper bound for the first retry delay. Defaults to 100 milliseconds. */
7619
+ initialBackoffMs?: number;
7620
+ /** Maximum number of exchange attempts, including the initial request. Defaults to 3. */
7621
+ maxAttempts?: number;
7622
+ /** Maximum number of organization tokens retained in memory. Defaults to 300. */
7623
+ maxOrganizations?: number;
7624
+ };
7625
+ /** Per-call retry overrides for {@link ApplicationTokenStorage.getToken}. */
7626
+ type GetTokenOptions = {
7627
+ /** Full-jitter upper bound for the first retry delay. */
7628
+ initialBackoffMs?: number;
7629
+ /** Maximum number of exchange attempts, including the initial request. */
7630
+ maxAttempts?: number;
7462
7631
  };
7463
7632
  /**
7464
- * Checks if a JWT token has expired or will expire within a specified time window.
7633
+ * Caches organization-scoped application tokens in memory.
7465
7634
  *
7466
- * @param token - The JWT token string to check
7467
- * @param expiresInMs - Optional time window in milliseconds. If provided, returns `true` if the token will expire within this time window
7468
- * @returns `true` if the token is expired (or will expire within the specified window), `false` otherwise
7469
- *
7470
- * @example
7471
- * ```typescript
7472
- * const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...'
7473
- *
7474
- * // Check if token is already expired
7475
- * if (isTokenExpired(token)) {
7476
- * console.log('Token has expired')
7477
- * }
7478
- *
7479
- * // Check if token will expire in the next 5 minutes
7480
- * if (isTokenExpired(token, 5 * 60 * 1000)) {
7481
- * console.log('Token will expire in the next 5 minutes')
7482
- * }
7483
- * ```
7635
+ * Tokens are reissued one minute before expiry. Cache hits promote their
7636
+ * organization to the most-recently-used position, and insertions beyond the
7637
+ * configured limit evict the least recently used organization. Transient
7638
+ * exchange failures use full-jitter exponential backoff.
7484
7639
  */
7485
- declare function isTokenExpired(token: string, expiresInMs?: number): boolean;
7640
+ declare class ApplicationTokenStorage {
7641
+ private readonly auth;
7642
+ private readonly clientId;
7643
+ private readonly clientSecret;
7644
+ private readonly initialBackoffMs;
7645
+ private readonly inFlight;
7646
+ private readonly maxAttempts;
7647
+ private readonly maxOrganizations;
7648
+ private readonly tokens;
7649
+ /** Creates an application token cache for one client-credentials identity. */
7650
+ constructor({ authApiUrl, clientId, clientSecret, initialBackoffMs, maxAttempts, maxOrganizations, }: ApplicationTokenStorageOptions);
7651
+ /** Returns a fresh access token scoped to the requested organization. */
7652
+ getToken(organizationId: string, { initialBackoffMs, maxAttempts, }?: GetTokenOptions): Promise<string>;
7653
+ private exchangeTokenWithRetry;
7654
+ /** Performs the client-credentials exchange for one organization. */
7655
+ protected exchangeToken(organizationId: string): Promise<string>;
7656
+ /** Waits before the next exchange attempt. */
7657
+ protected wait(delayMs: number): Promise<void>;
7658
+ }
7659
+
7660
+ type DecodedJWTPayload = Omit<JWTPayload, 'assurance'> & {
7661
+ assurance?: JWTPayloadAssurance;
7662
+ };
7486
7663
  /**
7487
7664
  * Validates a JWT token by checking its expiration and verifying its signature
7488
7665
  * against the JWKS endpoint.
@@ -7521,5 +7698,5 @@ declare function validateToken(token: string, apiUrl: string): Promise<boolean>;
7521
7698
  */
7522
7699
  declare function extractTokenPayload(token: string): DecodedJWTPayload;
7523
7700
 
7524
- export { AAL, AALBrand, AALs, ACR, ACRBrand, ACRs, AMR, AMRBrand, AMRs, ApiKeyMetadata, ApplicationConfig, ApplicationError, ApplicationSessionAssuranceConfig, ApplicationSessionAssuranceError, AuthClient, AuthMethod, AuthMethodBrand, AuthMethods, AuthorizationFlowError, DEFAULT_APPLICATION_CONFIG, DEFAULT_JWT_ASSURANCE, DEFAULT_SESSION_ASSURANCE_CONFIG, DeviceAccessDeniedError, DeviceAuthorizationPendingError, DeviceAuthorizationSlowDownError, DeviceCodeExpiredError, DeviceTransientServerError, EmailOTPService, EmailRequired, InvalidCallbackURL, InvalidSocialProvider, JWTPayload, JWTPayloadAssurance, JWTPayloadUser, JWTPayloadWorkspace, OAuthBeginStepUpResponse, OAuthCompleteStepUpResponse, OAuthStepUpProvider, RefreshTokenExpiredError, Roles, SENSITIVE_ACTION_ASSURANCE_POLICY, STEP_UP_MAX_AGE_SECONDS, SendOtpResponse, SessionAssurancePolicy, SessionAssuranceRequiredError, SessionAssuranceRequiredPayload, SessionAssuranceResponse, SessionAssuranceService, StepUpOption, UserNotLoggedInError, ac, assuranceFields, canStepUpWithAmr, createAPIClient, extractTokenPayload, getAalRank, invitationAdditionalFields, isAssuranceSufficientForPolicy, isSessionAssuranceSufficient, isTokenExpired, memberAdditionalFields, mergeStepUpAssurance, organizationAdditionalFields, parseSessionAssuranceRequiredPayload, rolesAccessControl, sessionAssurancePluginClient, userAdditionalFields, validateToken };
7525
- export type { APIClient, AcceptInvitationResult, AdminApplication, AdminApplicationEntitlement, AdminApplicationEntitlementRecord, AdminApplicationMember, AdminApplicationOrganization, AdminApplicationRecord, AdminEntitlementRule, AdminEntitlementRuleType, AdminEntitlementWithRules, AdminMember, AdminOrganization, AdminOrganizationApplication, AdminOrganizationMember, AdminOrganizationMemberApplication, AdminOrganizationMemberRecord, AdminOrganizationMemberTeam, AdminOrganizationSummary, AdminOrganizationTeam, AdminOrganizationTeamRecord, AdminUser, AdminUserAccess, AdminUserAccessApplication, AdminUserAccessOrganization, AdminUserAccessTeam, AdminUserInvitation, AdminUserOrganization, ApiKey, ApiKeyWithoutSecret, Application, ApplicationAuthContextResponse, AssuranceFields, BaseOrganization, BeginOAuthStepUpParams, BillingMode, CompleteAuthorizationFlowResponse, CreateAdminApplicationEntitlementOptions, CreateAdminApplicationOptions, CreateAdminOrganizationOptions, CreateAdminOrganizationTeamOptions, CreateApiKeyPayload, CreateTeamPayload, DecodedJWTPayload, DeviceAuthorizationActionResponse, DeviceAuthorizationContextResponse, DeviceAuthorizationResponse, DeviceContextApplication, EmailOTPSignInOptions, FullOrganization, GetAdminOrganizationOptions, GetAdminOrganizationsSummaryOptions, Invitation, InviteAdminOrganizationUsersOptions, InviteAdminOrganizationUsersResult, InviteUserToOrganizationOptions, JWTPayloadData, ListAdminApplicationsOptions, ListAdminApplicationsResult, ListAdminOrganizationMembersOptions, ListAdminOrganizationMembersResult, ListAdminOrganizationTeamsOptions, ListAdminOrganizationTeamsResult, ListAdminOrganizationsOptions, ListAdminOrganizationsResult, ListAdminUsersOptions, ListAdminUsersResult, ListCandidateOrganizationsResponse, ListMembersOptions, Member, ExtendedOrganization as Organization, OrganizationSettings, RemoveAdminMemberOptions, RemoveUserFromOrganizationOptions, ResendAdminInviteOptions, Role, SendVerificationOTPOptions, Session, SessionAssuranceActions, SessionData, SetAdminEntitlementRulesOptions, SetAdminUserApplicationRoleOptions, SetAdminUserTeamOptions, SignInWithEmailAndPasswordOptions, SignInWithSamlOptions, SocialSignInOptions, StartAuthorizationFlowResponse, Strict, Team, TeamMember, UpdateAdminEntitlementOptions, UpdateAdminMemberRoleOptions, UpdateAdminOrganizationOptions, UpdateApiKeyPayload, UpdateMemberRoleOptions, UpdateOrganizationPayload, UpdateTeamPayload, User, UserProfile, WhoAmIInclude, WhoAmIOptions, WhoAmIOrganization, WhoAmIResponse };
7701
+ export { AAL, AALBrand, AALs, ACR, ACRBrand, ACRs, AMR, AMRBrand, AMRs, ApiKeyMetadata, ApplicationConfig, ApplicationError, ApplicationSessionAssuranceConfig, ApplicationSessionAssuranceError, ApplicationTokenStorage, AuthClient, AuthMethod, AuthMethodBrand, AuthMethods, AuthorizationFlowError, DEFAULT_APPLICATION_CONFIG, DEFAULT_JWT_ASSURANCE, DEFAULT_SESSION_ASSURANCE_CONFIG, DeviceAccessDeniedError, DeviceAuthorizationPendingError, DeviceAuthorizationSlowDownError, DeviceCodeExpiredError, DeviceTransientServerError, EmailOTPService, EmailRequired, InvalidCallbackURL, InvalidSocialProvider, JWTPayload, JWTPayloadAssurance, JWTPayloadUser, JWTPayloadWorkspace, OAuthBeginStepUpResponse, OAuthCompleteStepUpResponse, OAuthStepUpProvider, RefreshTokenExpiredError, Roles, SENSITIVE_ACTION_ASSURANCE_POLICY, STEP_UP_MAX_AGE_SECONDS, SendOtpResponse, SessionAssurancePolicy, SessionAssuranceRequiredError, SessionAssuranceRequiredPayload, SessionAssuranceResponse, SessionAssuranceService, StepUpOption, UserNotLoggedInError, ac, assuranceFields, canStepUpWithAmr, createAPIClient, extractTokenPayload, getAalRank, invitationAdditionalFields, isAssuranceSufficientForPolicy, isSessionAssuranceSufficient, isTokenExpired, memberAdditionalFields, mergeStepUpAssurance, organizationAdditionalFields, parseSessionAssuranceRequiredPayload, rolesAccessControl, sessionAssurancePluginClient, userAdditionalFields, validateToken };
7702
+ export type { APIClient, AcceptInvitationResult, AdminApplication, AdminApplicationEntitlement, AdminApplicationEntitlementRecord, AdminApplicationIdentity, AdminApplicationMember, AdminApplicationOrganization, AdminApplicationRecord, AdminApplicationRole, AdminApplicationSecret, AdminEntitlementRule, AdminEntitlementRuleType, AdminEntitlementWithRules, AdminMember, AdminOrganization, AdminOrganizationApplication, AdminOrganizationMember, AdminOrganizationMemberApplication, AdminOrganizationMemberRecord, AdminOrganizationMemberTeam, AdminOrganizationSummary, AdminOrganizationTeam, AdminOrganizationTeamRecord, AdminUser, AdminUserAccess, AdminUserAccessApplication, AdminUserAccessOrganization, AdminUserAccessTeam, AdminUserInvitation, AdminUserOrganization, ApiKey, ApiKeyWithoutSecret, Application, ApplicationAuthContextResponse, ApplicationTokenStorageOptions, AssuranceFields, BaseOrganization, BeginOAuthStepUpParams, BillingMode, ClientCredentialsGrant, ClientCredentialsTokenResponse, CompleteAuthorizationFlowResponse, CreateAdminApplicationEntitlementOptions, CreateAdminApplicationOptions, CreateAdminApplicationRoleOptions, CreateAdminApplicationSecretOptions, CreateAdminOrganizationOptions, CreateAdminOrganizationTeamOptions, CreateApiKeyPayload, CreateTeamPayload, CreatedAdminApplicationSecret, DecodedJWTPayload, DeviceAuthorizationActionResponse, DeviceAuthorizationContextResponse, DeviceAuthorizationResponse, DeviceContextApplication, EmailOTPSignInOptions, FullOrganization, GetAdminOrganizationOptions, GetAdminOrganizationsSummaryOptions, GetTokenOptions, Invitation, InviteAdminOrganizationUsersOptions, InviteAdminOrganizationUsersResult, InviteUserToOrganizationOptions, JWTPayloadData, ListAdminApplicationsOptions, ListAdminApplicationsResult, ListAdminOrganizationMembersOptions, ListAdminOrganizationMembersResult, ListAdminOrganizationTeamsOptions, ListAdminOrganizationTeamsResult, ListAdminOrganizationsOptions, ListAdminOrganizationsResult, ListAdminUsersOptions, ListAdminUsersResult, ListCandidateOrganizationsResponse, ListMembersOptions, Member, ExtendedOrganization as Organization, OrganizationSettings, RemoveAdminMemberOptions, RemoveUserFromOrganizationOptions, ResendAdminInviteOptions, Role, SendVerificationOTPOptions, Session, SessionAssuranceActions, SessionData, SetAdminEntitlementRulesOptions, SetAdminUserApplicationRoleOptions, SetAdminUserTeamOptions, SignInWithEmailAndPasswordOptions, SignInWithSamlOptions, SocialSignInOptions, StartAuthorizationFlowResponse, Strict, Team, TeamMember, UpdateAdminApplicationRoleOptions, UpdateAdminEntitlementOptions, UpdateAdminMemberRoleOptions, UpdateAdminOrganizationOptions, UpdateApiKeyPayload, UpdateMemberRoleOptions, UpdateOrganizationPayload, UpdateTeamPayload, User, UserProfile, WhoAmIInclude, WhoAmIOptions, WhoAmIOrganization, WhoAmIResponse };
package/dist/index.mjs CHANGED
@@ -8,7 +8,20 @@ import { defaultStatements } from 'better-auth/plugins/organization/access';
8
8
  import z, { z as z$1 } from 'zod';
9
9
  export { APIError } from 'better-auth';
10
10
 
11
- const version = "1.34.0";
11
+ function isTokenExpired(token, expiresInMs) {
12
+ const payload = JSON.parse(atob(token.split(".")[1] ?? ""));
13
+ if (!payload.exp)
14
+ return false;
15
+ const nowInSeconds = Date.now() / 1e3;
16
+ const expirationTime = payload.exp;
17
+ if (expiresInMs !== void 0) {
18
+ const futureTimeInSeconds = nowInSeconds + expiresInMs / 1e3;
19
+ return futureTimeInSeconds >= expirationTime;
20
+ }
21
+ return nowInSeconds > expirationTime;
22
+ }
23
+
24
+ const version = "1.36.0";
12
25
 
13
26
  function parsedEnumValues(schema) {
14
27
  return Object.fromEntries(
@@ -187,15 +200,17 @@ const JWTPayload = z$1.object({
187
200
  });
188
201
 
189
202
  const DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
203
+ const CLIENT_CREDENTIALS_GRANT = "client_credentials";
190
204
  function normalizeOrganizationsResponse(response) {
191
205
  if (Array.isArray(response)) {
192
206
  return {
193
207
  data: response
194
208
  };
195
209
  }
210
+ const candidate = response;
196
211
  return {
197
- ...response,
198
- data: Array.isArray(response.data) ? response.data : []
212
+ ...candidate,
213
+ data: Array.isArray(candidate.data) ? candidate.data : []
199
214
  };
200
215
  }
201
216
  function applicationsPluginClient() {
@@ -273,6 +288,17 @@ function applicationsPluginClient() {
273
288
  }
274
289
  });
275
290
  },
291
+ exchangeClientCredentialsForToken: async (credentials) => {
292
+ return await $fetch("/applications/token", {
293
+ method: "POST",
294
+ body: {
295
+ grantType: CLIENT_CREDENTIALS_GRANT,
296
+ clientId: credentials.clientId,
297
+ clientSecret: credentials.clientSecret,
298
+ organizationId: credentials.organizationId
299
+ }
300
+ });
301
+ },
276
302
  completeAuthorizationFlow: async (code, codeVerifier) => {
277
303
  return await $fetch("/applications/token", {
278
304
  method: "POST",
@@ -408,12 +434,23 @@ function createAPIClient(apiUrl, fetchOptions = {}) {
408
434
  });
409
435
  }
410
436
 
411
- async function readErrorMessage(response) {
437
+ class ApplicationRouteError extends Error {
438
+ code;
439
+ details;
440
+ status;
441
+ constructor(response, body) {
442
+ super(body?.error ?? body?.message ?? response.statusText);
443
+ this.name = "ApplicationRouteError";
444
+ this.status = response.status;
445
+ this.code = body?.code;
446
+ this.details = body?.details;
447
+ }
448
+ }
449
+ async function readErrorBody(response) {
412
450
  try {
413
- const body = await response.json();
414
- return body.error ?? body.message ?? response.statusText;
451
+ return await response.json();
415
452
  } catch {
416
- return response.statusText;
453
+ return null;
417
454
  }
418
455
  }
419
456
  class AdminService {
@@ -459,7 +496,7 @@ class AdminService {
459
496
  ...request.body !== void 0 ? { body: JSON.stringify(request.body) } : {}
460
497
  });
461
498
  if (!response.ok)
462
- throw new Error(await readErrorMessage(response));
499
+ throw new ApplicationRouteError(response, await readErrorBody(response));
463
500
  return await response.json();
464
501
  }
465
502
  /**
@@ -506,6 +543,33 @@ class AdminService {
506
543
  body: options
507
544
  });
508
545
  }
546
+ /** Lists the role catalog for an application. */
547
+ async listApplicationRoles(applicationId) {
548
+ return await this.requestApplicationRoute(
549
+ `applications/${encodeURIComponent(applicationId)}/roles`
550
+ );
551
+ }
552
+ /** Creates an application-scoped role definition. */
553
+ async createApplicationRole(applicationId, options) {
554
+ return await this.requestApplicationRoute(
555
+ `applications/${encodeURIComponent(applicationId)}/roles`,
556
+ { method: "POST", body: options }
557
+ );
558
+ }
559
+ /** Updates an application-scoped role definition. */
560
+ async updateApplicationRole(applicationId, currentName, options) {
561
+ return await this.requestApplicationRoute(
562
+ `applications/${encodeURIComponent(applicationId)}/roles/${encodeURIComponent(currentName)}`,
563
+ { method: "PUT", body: options }
564
+ );
565
+ }
566
+ /** Deletes an application-scoped role definition. */
567
+ async deleteApplicationRole(applicationId, name) {
568
+ return await this.requestApplicationRoute(
569
+ `applications/${encodeURIComponent(applicationId)}/roles/${encodeURIComponent(name)}`,
570
+ { method: "DELETE" }
571
+ );
572
+ }
509
573
  /**
510
574
  * Enables an organization for an application.
511
575
  *
@@ -519,6 +583,53 @@ class AdminService {
519
583
  body: options
520
584
  });
521
585
  }
586
+ /**
587
+ * Lists client secrets for an application.
588
+ *
589
+ * @param applicationId - ID of the application to inspect
590
+ * @returns Secret metadata without plaintext values
591
+ */
592
+ async listApplicationSecrets(applicationId) {
593
+ return await this.requestApplicationRoute(`applications/${encodeURIComponent(applicationId)}/secrets`);
594
+ }
595
+ /**
596
+ * Creates a client secret for an application.
597
+ *
598
+ * @param applicationId - ID of the application to update
599
+ * @param options - Optional secret display name
600
+ * @returns The created secret, including the plaintext value shown only once
601
+ */
602
+ async createApplicationSecret(applicationId, options = {}) {
603
+ return await this.requestApplicationRoute(`applications/${encodeURIComponent(applicationId)}/secrets`, {
604
+ method: "POST",
605
+ body: options
606
+ });
607
+ }
608
+ /**
609
+ * Deletes one client secret for an application.
610
+ *
611
+ * @param applicationId - ID of the application to update
612
+ * @param secretId - ID of the secret to delete
613
+ * @returns The deleted secret metadata
614
+ */
615
+ async deleteApplicationSecret(applicationId, secretId) {
616
+ return await this.requestApplicationRoute(
617
+ `applications/${encodeURIComponent(applicationId)}/secrets/${encodeURIComponent(secretId)}`,
618
+ { method: "DELETE" }
619
+ );
620
+ }
621
+ /**
622
+ * Retires an application's service-account identity.
623
+ *
624
+ * @param applicationId - ID of the application to update
625
+ * @returns The updated application
626
+ */
627
+ async retireApplicationServiceAccount(applicationId) {
628
+ return await this.requestApplicationRoute(
629
+ `applications/${encodeURIComponent(applicationId)}/service-account`,
630
+ { method: "DELETE" }
631
+ );
632
+ }
522
633
  /**
523
634
  * Lists all organizations, optionally with member and application previews
524
635
  * and totals when a preview size is requested.
@@ -1332,6 +1443,22 @@ class ApplicationService {
1332
1443
  throwDeviceGrantError(error);
1333
1444
  }
1334
1445
  }
1446
+ /**
1447
+ * Exchanges application client credentials for a service-account access token.
1448
+ *
1449
+ * The returned token is scoped to the requested organization and cannot be
1450
+ * refreshed. Exchange the credentials again when a new token is needed.
1451
+ *
1452
+ * @param credentials - Application credentials and target organization
1453
+ * @returns A short-lived access token with service-account and organization details
1454
+ */
1455
+ async exchangeClientCredentialsForToken(credentials) {
1456
+ const response = await this.client.applications.exchangeClientCredentialsForToken(credentials);
1457
+ if (!response.data) {
1458
+ throw new Error("No data returned from the API", { cause: response.error });
1459
+ }
1460
+ return response.data;
1461
+ }
1335
1462
  /**
1336
1463
  * Completes an authorization flow for a specific application.
1337
1464
  *
@@ -2135,18 +2262,128 @@ class AuthClient {
2135
2262
  }
2136
2263
  }
2137
2264
 
2138
- function isTokenExpired(token, expiresInMs) {
2139
- const payload = JSON.parse(atob(token.split(".")[1] ?? ""));
2140
- if (!payload.exp)
2141
- return false;
2142
- const nowInSeconds = Date.now() / 1e3;
2143
- const expirationTime = payload.exp;
2144
- if (expiresInMs !== void 0) {
2145
- const futureTimeInSeconds = nowInSeconds + expiresInMs / 1e3;
2146
- return futureTimeInSeconds >= expirationTime;
2265
+ const TOKEN_REFRESH_WINDOW_MS = 6e4;
2266
+ const DEFAULT_INITIAL_BACKOFF_MS = 100;
2267
+ const DEFAULT_MAX_ATTEMPTS = 3;
2268
+ const DEFAULT_MAX_ORGANIZATIONS = 300;
2269
+ function validateMaxAttempts(maxAttempts) {
2270
+ if (!Number.isInteger(maxAttempts) || maxAttempts < 1) {
2271
+ throw new RangeError("maxAttempts must be a positive integer");
2147
2272
  }
2148
- return nowInSeconds > expirationTime;
2149
2273
  }
2274
+ function validateInitialBackoff(initialBackoffMs) {
2275
+ if (!Number.isFinite(initialBackoffMs) || initialBackoffMs < 0) {
2276
+ throw new RangeError("initialBackoffMs must be a non-negative finite number");
2277
+ }
2278
+ }
2279
+ function getErrorStatus(error) {
2280
+ const candidates = [error];
2281
+ const visited = /* @__PURE__ */ new Set();
2282
+ while (candidates.length > 0) {
2283
+ const candidate = candidates.shift();
2284
+ if (!candidate || typeof candidate !== "object" || visited.has(candidate))
2285
+ continue;
2286
+ visited.add(candidate);
2287
+ const errorCandidate = candidate;
2288
+ if (typeof errorCandidate.status === "number")
2289
+ return errorCandidate.status;
2290
+ candidates.push(errorCandidate.cause, errorCandidate.error);
2291
+ }
2292
+ return void 0;
2293
+ }
2294
+ function isTransientError(error) {
2295
+ const status = getErrorStatus(error);
2296
+ return status === void 0 || status === 429 || status >= 500 && status < 600;
2297
+ }
2298
+ class ApplicationTokenStorage {
2299
+ auth;
2300
+ clientId;
2301
+ clientSecret;
2302
+ initialBackoffMs;
2303
+ inFlight = /* @__PURE__ */ new Map();
2304
+ maxAttempts;
2305
+ maxOrganizations;
2306
+ tokens = /* @__PURE__ */ new Map();
2307
+ /** Creates an application token cache for one client-credentials identity. */
2308
+ constructor({
2309
+ authApiUrl,
2310
+ clientId,
2311
+ clientSecret,
2312
+ initialBackoffMs = DEFAULT_INITIAL_BACKOFF_MS,
2313
+ maxAttempts = DEFAULT_MAX_ATTEMPTS,
2314
+ maxOrganizations = DEFAULT_MAX_ORGANIZATIONS
2315
+ }) {
2316
+ if (!Number.isInteger(maxOrganizations) || maxOrganizations < 1) {
2317
+ throw new RangeError("maxOrganizations must be a positive integer");
2318
+ }
2319
+ validateMaxAttempts(maxAttempts);
2320
+ validateInitialBackoff(initialBackoffMs);
2321
+ this.auth = new AuthClient(authApiUrl);
2322
+ this.clientId = clientId;
2323
+ this.clientSecret = clientSecret;
2324
+ this.initialBackoffMs = initialBackoffMs;
2325
+ this.maxAttempts = maxAttempts;
2326
+ this.maxOrganizations = maxOrganizations;
2327
+ }
2328
+ /** Returns a fresh access token scoped to the requested organization. */
2329
+ async getToken(organizationId, {
2330
+ initialBackoffMs = this.initialBackoffMs,
2331
+ maxAttempts = this.maxAttempts
2332
+ } = {}) {
2333
+ validateMaxAttempts(maxAttempts);
2334
+ validateInitialBackoff(initialBackoffMs);
2335
+ const cachedToken = this.tokens.get(organizationId);
2336
+ if (cachedToken && !isTokenExpired(cachedToken, TOKEN_REFRESH_WINDOW_MS)) {
2337
+ this.tokens.delete(organizationId);
2338
+ this.tokens.set(organizationId, cachedToken);
2339
+ return cachedToken;
2340
+ }
2341
+ const pendingExchange = this.inFlight.get(organizationId);
2342
+ if (pendingExchange) {
2343
+ return await pendingExchange;
2344
+ }
2345
+ const exchange = this.exchangeTokenWithRetry(organizationId, maxAttempts, initialBackoffMs).then((token) => {
2346
+ this.tokens.delete(organizationId);
2347
+ this.tokens.set(organizationId, token);
2348
+ if (this.tokens.size > this.maxOrganizations) {
2349
+ const leastRecentlyUsedOrganization = this.tokens.keys().next().value;
2350
+ if (leastRecentlyUsedOrganization !== void 0) {
2351
+ this.tokens.delete(leastRecentlyUsedOrganization);
2352
+ }
2353
+ }
2354
+ return token;
2355
+ }).finally(() => this.inFlight.delete(organizationId));
2356
+ this.inFlight.set(organizationId, exchange);
2357
+ return await exchange;
2358
+ }
2359
+ async exchangeTokenWithRetry(organizationId, maxAttempts, initialBackoffMs) {
2360
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
2361
+ try {
2362
+ return await this.exchangeToken(organizationId);
2363
+ } catch (error) {
2364
+ if (attempt === maxAttempts || !isTransientError(error))
2365
+ throw error;
2366
+ const exponentialDelay = initialBackoffMs * 2 ** (attempt - 1);
2367
+ await this.wait(Math.random() * exponentialDelay);
2368
+ }
2369
+ }
2370
+ throw new Error("Client credentials exchange exhausted without a result");
2371
+ }
2372
+ /** Performs the client-credentials exchange for one organization. */
2373
+ async exchangeToken(organizationId) {
2374
+ const { accessToken } = await this.auth.application.exchangeClientCredentialsForToken({
2375
+ clientId: this.clientId,
2376
+ clientSecret: this.clientSecret,
2377
+ organizationId
2378
+ });
2379
+ return accessToken;
2380
+ }
2381
+ /** Waits before the next exchange attempt. */
2382
+ async wait(delayMs) {
2383
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
2384
+ }
2385
+ }
2386
+
2150
2387
  async function validateToken(token, apiUrl) {
2151
2388
  if (isTokenExpired(token)) {
2152
2389
  return false;
@@ -2168,4 +2405,4 @@ function extractTokenPayload(token) {
2168
2405
  return payload;
2169
2406
  }
2170
2407
 
2171
- export { AAL, AALBrand, AALs, ACR, ACRBrand, ACRs, AMR, AMRBrand, AMRs, ApiKeyMetadata, ApplicationConfig, ApplicationError, ApplicationSessionAssuranceConfig, ApplicationSessionAssuranceError, AuthClient, AuthMethod, AuthMethodBrand, AuthMethods, AuthorizationFlowError, DEFAULT_APPLICATION_CONFIG, DEFAULT_JWT_ASSURANCE, DEFAULT_SESSION_ASSURANCE_CONFIG, DeviceAccessDeniedError, DeviceAuthorizationPendingError, DeviceAuthorizationSlowDownError, DeviceCodeExpiredError, DeviceTransientServerError, EmailOTPService, EmailRequired, InvalidCallbackURL, InvalidSocialProvider, JWTPayload, JWTPayloadAssurance, JWTPayloadUser, JWTPayloadWorkspace, OAuthStepUpProvider, RefreshTokenExpiredError, Roles, SENSITIVE_ACTION_ASSURANCE_POLICY, STEP_UP_MAX_AGE_SECONDS, SessionAssurancePolicy, SessionAssuranceRequiredError, SessionAssuranceRequiredPayload, SessionAssuranceService, StepUpOption, UserNotLoggedInError, ac, assuranceFields, canStepUpWithAmr, extractTokenPayload, getAalRank, invitationAdditionalFields, isAssuranceSufficientForPolicy, isSessionAssuranceSufficient, isTokenExpired, memberAdditionalFields, mergeStepUpAssurance, organizationAdditionalFields, parseSessionAssuranceRequiredPayload, rolesAccessControl, sessionAssurancePluginClient, userAdditionalFields, validateToken };
2408
+ export { AAL, AALBrand, AALs, ACR, ACRBrand, ACRs, AMR, AMRBrand, AMRs, ApiKeyMetadata, ApplicationConfig, ApplicationError, ApplicationSessionAssuranceConfig, ApplicationSessionAssuranceError, ApplicationTokenStorage, AuthClient, AuthMethod, AuthMethodBrand, AuthMethods, AuthorizationFlowError, DEFAULT_APPLICATION_CONFIG, DEFAULT_JWT_ASSURANCE, DEFAULT_SESSION_ASSURANCE_CONFIG, DeviceAccessDeniedError, DeviceAuthorizationPendingError, DeviceAuthorizationSlowDownError, DeviceCodeExpiredError, DeviceTransientServerError, EmailOTPService, EmailRequired, InvalidCallbackURL, InvalidSocialProvider, JWTPayload, JWTPayloadAssurance, JWTPayloadUser, JWTPayloadWorkspace, OAuthStepUpProvider, RefreshTokenExpiredError, Roles, SENSITIVE_ACTION_ASSURANCE_POLICY, STEP_UP_MAX_AGE_SECONDS, SessionAssurancePolicy, SessionAssuranceRequiredError, SessionAssuranceRequiredPayload, SessionAssuranceService, StepUpOption, UserNotLoggedInError, ac, assuranceFields, canStepUpWithAmr, extractTokenPayload, getAalRank, invitationAdditionalFields, isAssuranceSufficientForPolicy, isSessionAssuranceSufficient, isTokenExpired, memberAdditionalFields, mergeStepUpAssurance, organizationAdditionalFields, parseSessionAssuranceRequiredPayload, rolesAccessControl, sessionAssurancePluginClient, userAdditionalFields, validateToken };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/auth-core",
3
- "version": "1.34.0",
3
+ "version": "1.36.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {