@meistrari/auth-core 1.35.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
@@ -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
  *
@@ -4402,15 +4455,15 @@ declare function createAPIClient(apiUrl: string, fetchOptions?: BetterFetchOptio
4402
4455
  } & {
4403
4456
  applications: {
4404
4457
  getApplicationAuthContext: (applicationId: string) => Promise<{
4405
- data: ApplicationAuthContextResponse;
4406
- error: null;
4407
- } | {
4408
4458
  data: null;
4409
4459
  error: {
4410
4460
  message?: string | undefined;
4411
4461
  status: number;
4412
4462
  statusText: string;
4413
4463
  };
4464
+ } | {
4465
+ data: ApplicationAuthContextResponse;
4466
+ error: null;
4414
4467
  }>;
4415
4468
  listCandidateOrganizations: (applicationId: string) => Promise<{
4416
4469
  data: ListCandidateOrganizationsResponse;
@@ -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
  */
@@ -6193,6 +6290,8 @@ type AdminApplication = {
6193
6290
  redirectUris: string[];
6194
6291
  homeUrl: string | null;
6195
6292
  config: ApplicationConfig | null;
6293
+ serviceAccountUserId: string | null;
6294
+ serviceAccountUser?: AdminApplicationIdentity | null;
6196
6295
  createdBy: string;
6197
6296
  updatedBy: string;
6198
6297
  createdAt: string;
@@ -6368,6 +6467,36 @@ declare class AdminService {
6368
6467
  * @returns The created entitlement
6369
6468
  */
6370
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>;
6371
6500
  /**
6372
6501
  * Lists all organizations, optionally with member and application previews
6373
6502
  * and totals when a preview size is requested.
@@ -7478,32 +7607,59 @@ declare class AuthClient {
7478
7607
  constructor(apiUrl: string, fetchOptions?: BetterFetchOption);
7479
7608
  }
7480
7609
 
7481
- type DecodedJWTPayload = Omit<JWTPayload, 'assurance'> & {
7482
- 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;
7483
7631
  };
7484
7632
  /**
7485
- * Checks if a JWT token has expired or will expire within a specified time window.
7486
- *
7487
- * @param token - The JWT token string to check
7488
- * @param expiresInMs - Optional time window in milliseconds. If provided, returns `true` if the token will expire within this time window
7489
- * @returns `true` if the token is expired (or will expire within the specified window), `false` otherwise
7490
- *
7491
- * @example
7492
- * ```typescript
7493
- * const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...'
7494
- *
7495
- * // Check if token is already expired
7496
- * if (isTokenExpired(token)) {
7497
- * console.log('Token has expired')
7498
- * }
7633
+ * Caches organization-scoped application tokens in memory.
7499
7634
  *
7500
- * // Check if token will expire in the next 5 minutes
7501
- * if (isTokenExpired(token, 5 * 60 * 1000)) {
7502
- * console.log('Token will expire in the next 5 minutes')
7503
- * }
7504
- * ```
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.
7505
7639
  */
7506
- 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
+ };
7507
7663
  /**
7508
7664
  * Validates a JWT token by checking its expiration and verifying its signature
7509
7665
  * against the JWKS endpoint.
@@ -7542,5 +7698,5 @@ declare function validateToken(token: string, apiUrl: string): Promise<boolean>;
7542
7698
  */
7543
7699
  declare function extractTokenPayload(token: string): DecodedJWTPayload;
7544
7700
 
7545
- 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 };
7546
- export type { APIClient, AcceptInvitationResult, AdminApplication, AdminApplicationEntitlement, AdminApplicationEntitlementRecord, AdminApplicationMember, AdminApplicationOrganization, AdminApplicationRecord, AdminApplicationRole, 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, CreateAdminApplicationRoleOptions, 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, UpdateAdminApplicationRoleOptions, 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
@@ -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
  *
@@ -4402,15 +4455,15 @@ declare function createAPIClient(apiUrl: string, fetchOptions?: BetterFetchOptio
4402
4455
  } & {
4403
4456
  applications: {
4404
4457
  getApplicationAuthContext: (applicationId: string) => Promise<{
4405
- data: ApplicationAuthContextResponse;
4406
- error: null;
4407
- } | {
4408
4458
  data: null;
4409
4459
  error: {
4410
4460
  message?: string | undefined;
4411
4461
  status: number;
4412
4462
  statusText: string;
4413
4463
  };
4464
+ } | {
4465
+ data: ApplicationAuthContextResponse;
4466
+ error: null;
4414
4467
  }>;
4415
4468
  listCandidateOrganizations: (applicationId: string) => Promise<{
4416
4469
  data: ListCandidateOrganizationsResponse;
@@ -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
  */
@@ -6193,6 +6290,8 @@ type AdminApplication = {
6193
6290
  redirectUris: string[];
6194
6291
  homeUrl: string | null;
6195
6292
  config: ApplicationConfig | null;
6293
+ serviceAccountUserId: string | null;
6294
+ serviceAccountUser?: AdminApplicationIdentity | null;
6196
6295
  createdBy: string;
6197
6296
  updatedBy: string;
6198
6297
  createdAt: string;
@@ -6368,6 +6467,36 @@ declare class AdminService {
6368
6467
  * @returns The created entitlement
6369
6468
  */
6370
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>;
6371
6500
  /**
6372
6501
  * Lists all organizations, optionally with member and application previews
6373
6502
  * and totals when a preview size is requested.
@@ -7478,32 +7607,59 @@ declare class AuthClient {
7478
7607
  constructor(apiUrl: string, fetchOptions?: BetterFetchOption);
7479
7608
  }
7480
7609
 
7481
- type DecodedJWTPayload = Omit<JWTPayload, 'assurance'> & {
7482
- 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;
7483
7631
  };
7484
7632
  /**
7485
- * Checks if a JWT token has expired or will expire within a specified time window.
7486
- *
7487
- * @param token - The JWT token string to check
7488
- * @param expiresInMs - Optional time window in milliseconds. If provided, returns `true` if the token will expire within this time window
7489
- * @returns `true` if the token is expired (or will expire within the specified window), `false` otherwise
7490
- *
7491
- * @example
7492
- * ```typescript
7493
- * const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...'
7494
- *
7495
- * // Check if token is already expired
7496
- * if (isTokenExpired(token)) {
7497
- * console.log('Token has expired')
7498
- * }
7633
+ * Caches organization-scoped application tokens in memory.
7499
7634
  *
7500
- * // Check if token will expire in the next 5 minutes
7501
- * if (isTokenExpired(token, 5 * 60 * 1000)) {
7502
- * console.log('Token will expire in the next 5 minutes')
7503
- * }
7504
- * ```
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.
7505
7639
  */
7506
- 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
+ };
7507
7663
  /**
7508
7664
  * Validates a JWT token by checking its expiration and verifying its signature
7509
7665
  * against the JWKS endpoint.
@@ -7542,5 +7698,5 @@ declare function validateToken(token: string, apiUrl: string): Promise<boolean>;
7542
7698
  */
7543
7699
  declare function extractTokenPayload(token: string): DecodedJWTPayload;
7544
7700
 
7545
- 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 };
7546
- export type { APIClient, AcceptInvitationResult, AdminApplication, AdminApplicationEntitlement, AdminApplicationEntitlementRecord, AdminApplicationMember, AdminApplicationOrganization, AdminApplicationRecord, AdminApplicationRole, 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, CreateAdminApplicationRoleOptions, 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, UpdateAdminApplicationRoleOptions, 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.35.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
  /**
@@ -546,6 +583,53 @@ class AdminService {
546
583
  body: options
547
584
  });
548
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
+ }
549
633
  /**
550
634
  * Lists all organizations, optionally with member and application previews
551
635
  * and totals when a preview size is requested.
@@ -1359,6 +1443,22 @@ class ApplicationService {
1359
1443
  throwDeviceGrantError(error);
1360
1444
  }
1361
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
+ }
1362
1462
  /**
1363
1463
  * Completes an authorization flow for a specific application.
1364
1464
  *
@@ -2162,18 +2262,128 @@ class AuthClient {
2162
2262
  }
2163
2263
  }
2164
2264
 
2165
- function isTokenExpired(token, expiresInMs) {
2166
- const payload = JSON.parse(atob(token.split(".")[1] ?? ""));
2167
- if (!payload.exp)
2168
- return false;
2169
- const nowInSeconds = Date.now() / 1e3;
2170
- const expirationTime = payload.exp;
2171
- if (expiresInMs !== void 0) {
2172
- const futureTimeInSeconds = nowInSeconds + expiresInMs / 1e3;
2173
- 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");
2272
+ }
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));
2174
2384
  }
2175
- return nowInSeconds > expirationTime;
2176
2385
  }
2386
+
2177
2387
  async function validateToken(token, apiUrl) {
2178
2388
  if (isTokenExpired(token)) {
2179
2389
  return false;
@@ -2195,4 +2405,4 @@ function extractTokenPayload(token) {
2195
2405
  return payload;
2196
2406
  }
2197
2407
 
2198
- 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.35.0",
3
+ "version": "1.36.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {