@enterprisestandard/core 0.0.15-beta.20260407.1 → 0.0.16

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/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { version } from "../package.json";
2
2
  import { StandardSchemaV1 as StandardSchemaV18 } from "@standard-schema/spec";
3
- import { StandardSchemaV1 as StandardSchemaV15 } from "@standard-schema/spec";
3
+ import { StandardSchemaV1 as StandardSchemaV17 } from "@standard-schema/spec";
4
4
  /**
5
5
  * Minimal logger interface compatible with common patterns (console, pino, winston, etc.)
6
6
  */
@@ -641,37 +641,8 @@ interface GroupStore<TExtended = Record<string, never>> {
641
641
  */
642
642
  removeMember(groupId: string, memberId: string): Promise<void>;
643
643
  }
644
- /**
645
- * Base user with simple, developer-friendly attributes.
646
- * Extended by User (SSO) and EnterpriseUser (SCIM).
647
- */
648
- interface BaseUser {
649
- /**
650
- * Unique identifier for the user
651
- */
652
- id?: string;
653
- /**
654
- * REQUIRED. Unique identifier for login
655
- */
656
- userName: string;
657
- /**
658
- * REQUIRED. Simple display name
659
- */
660
- name: string;
661
- /**
662
- * REQUIRED. Primary email address
663
- */
664
- email: string;
665
- /**
666
- * URL to user's avatar/profile picture
667
- */
668
- avatar?: string;
669
- /**
670
- * User type (e.g., "employee", "contractor", "customer").
671
- * Shared by SSO/CIAM and enterprise/SCIM user representations.
672
- */
673
- userType?: string;
674
- }
644
+ import { StandardSchemaV1 as StandardSchemaV16 } from "@standard-schema/spec";
645
+ import { StandardSchemaV1 as StandardSchemaV13 } from "@standard-schema/spec";
675
646
  import { StandardSchemaV1 as StandardSchemaV12 } from "@standard-schema/spec";
676
647
  /**
677
648
  * OIDC Code Flow Callback URL Parameters
@@ -758,175 +729,360 @@ interface IdTokenClaims {
758
729
  */
759
730
  declare function idTokenClaimsSchema(vendor: string): StandardSchemaV12<Record<string, unknown>, IdTokenClaims>;
760
731
  /**
761
- * Primary user type for SSO/OIDC applications.
762
- * Extends BaseUser with SSO-specific data.
732
+ * Session management for tracking user sessions and enabling backchannel logout.
733
+ *
734
+ * Session stores are optional - the package works with JWT cookies alone.
735
+ * Sessions are only required for backchannel logout functionality.
736
+ *
737
+ * ## Session Validation Strategies
738
+ *
739
+ * When using a session store, you can configure when sessions are validated:
740
+ *
741
+ * ### 'always' (default)
742
+ * Validates session on every authenticated request.
743
+ * - **Security**: Maximum - immediate session revocation
744
+ * - **Performance**: InMemory ~0.00005ms, Redis ~1-2ms per request
745
+ * - **Backchannel Logout**: Takes effect immediately
746
+ * - **Use when**: Security is paramount, using InMemory or Redis backend
747
+ *
748
+ * ### 'refresh-only'
749
+ * Validates session only during token refresh (typically every 5-15 minutes).
750
+ * - **Security**: Good - 5-15 minute revocation window
751
+ * - **Performance**: 99% reduction in session lookups
752
+ * - **Backchannel Logout**: Takes effect within token TTL (5-15 min)
753
+ * - **Use when**: Performance is critical AND delayed revocation is acceptable
754
+ * - **WARNING**: Compromised sessions remain valid until next refresh
755
+ *
756
+ * ### 'disabled'
757
+ * Never validates sessions against the store.
758
+ * - **Security**: None - backchannel logout doesn't work
759
+ * - **Performance**: No overhead
760
+ * - **Use when**: Cookie-only mode without session store
761
+ * - **WARNING**: Do not use with sessionStore configured
762
+ *
763
+ * ## Performance Characteristics
764
+ *
765
+ * | Backend | Lookup Time | Impact on Request | Recommendation |
766
+ * |--------------|-------------|-------------------|------------------------|
767
+ * | InMemory | <0.00005ms | Negligible | Use 'always' |
768
+ * | Redis | 1-2ms | 2-4% increase | Use 'always' or test |
769
+ * | Database | 5-20ms | 10-40% increase | Use Redis cache layer |
770
+ *
771
+ * ## Example Usage
772
+ *
773
+ * ```typescript
774
+ * import { sso, InMemorySessionStore } from '@enterprisestandard/server';
775
+ *
776
+ * // Maximum security (default)
777
+ * const secure = sso({
778
+ * // ... other config
779
+ * sessionStore: new InMemorySessionStore(),
780
+ * session_validation: 'always', // Immediate revocation
781
+ * });
782
+ *
783
+ * // High performance
784
+ * const fast = sso({
785
+ * // ... other config
786
+ * sessionStore: new InMemorySessionStore(),
787
+ * session_validation: {
788
+ * strategy: 'refresh-only' // 5-15 min revocation delay
789
+ * }
790
+ * });
791
+ * ```
763
792
  */
764
- interface User2 extends BaseUser {
765
- /**
766
- * SSO/OIDC authentication data
767
- */
768
- sso: {
769
- /**
770
- * ID Token claims from the identity provider
771
- */
772
- profile: IdTokenClaims;
773
- /**
774
- * Tenant/organization information
775
- */
776
- tenant: {
777
- id: string;
778
- name: string;
779
- };
780
- /**
781
- * OAuth scopes granted
782
- */
783
- scope?: string;
784
- /**
785
- * Token type (typically "Bearer")
786
- */
787
- tokenType: string;
788
- /**
789
- * Session state from the identity provider
790
- */
791
- sessionState?: string;
792
- /**
793
- * Token expiration time
794
- */
795
- expires: Date;
796
- };
797
- }
798
793
  /**
799
- * Stored user data with required id and tracking metadata.
800
- *
801
- * Extends the SSO User type with:
802
- * - Required `id` (the `sub` claim from the IdP)
803
- * - Timestamps for tracking when users were first seen and last updated
804
- * - Optional custom extended data
794
+ * Core session data tracked for each authenticated user session.
805
795
  *
806
- * @template TExtended - Type-safe custom data that consumers can add to users
796
+ * @template TExtended - Type-safe custom data that consumers can add to sessions
807
797
  */
808
- type StoredUser<TExtended = object> = User2 & {
798
+ type Session<TExtended = object> = {
809
799
  /**
810
- * Required unique identifier (the `sub` claim from the IdP).
811
- * This is the primary key for user storage.
800
+ * Session ID from the Identity Provider (from `sid` claim in ID token).
801
+ * This is the unique identifier for the session.
812
802
  */
813
- id: string;
803
+ sid: string;
814
804
  /**
815
- * Optional Enterprise Standard tenant identifier for tenant-aware apps.
816
- * Built-in user stores can use this when registering HRD mappings.
805
+ * Subject identifier (user ID) from the Identity Provider.
806
+ * From the `sub` claim in the ID token.
817
807
  */
818
- tenantId?: string;
808
+ sub: string;
819
809
  /**
820
- * Timestamp when the user was first stored.
810
+ * Timestamp when the session was created.
821
811
  */
822
812
  createdAt: Date;
823
813
  /**
824
- * Timestamp when the user was last updated (e.g., on re-login).
814
+ * Timestamp of the last activity in this session.
815
+ * Can be updated to track session activity.
825
816
  */
826
- updatedAt: Date;
817
+ lastActivityAt: Date;
818
+ /**
819
+ * Allow consumers to add runtime data to sessions.
820
+ */
821
+ [key: string]: unknown;
827
822
  } & TExtended;
828
823
  /**
829
- * Abstract interface for user storage backends.
824
+ * Abstract interface for session storage backends.
830
825
  *
831
826
  * Consumers can implement this interface to use different storage backends:
832
- * - In-memory (for development/testing)
833
- * - Redis (for production with fast lookups)
827
+ * - Redis
834
828
  * - Database (PostgreSQL, MySQL, etc.)
829
+ * - Distributed cache
830
+ * - Custom solutions
835
831
  *
836
- * @template TExtended - Type-safe custom data that consumers can add to users
832
+ * @template TExtended - Type-safe custom data that consumers can add to sessions
837
833
  *
838
834
  * @example
839
835
  * ```typescript
840
- * // Custom user data
841
- * type MyUserData = {
842
- * department: string;
843
- * roles: string[];
836
+ * // Custom session data
837
+ * type MySessionData = {
838
+ * ipAddress: string;
839
+ * userAgent: string;
844
840
  * };
845
841
  *
846
842
  * // Implement custom store
847
- * class RedisUserStore implements UserStore<MyUserData> {
848
- * async get(sub: string): Promise<StoredUser<MyUserData> | null> {
849
- * const data = await redis.get(`user:${sub}`);
850
- * return data ? JSON.parse(data) : null;
843
+ * class RedisSessionStore implements SessionStore<MySessionData> {
844
+ * async create(session: Session<MySessionData>): Promise<void> {
845
+ * await redis.set(`session:${session.sid}`, JSON.stringify(session));
851
846
  * }
852
847
  * // ... other methods
853
848
  * }
854
849
  * ```
855
850
  */
856
- interface UserStore<TExtended = object> {
857
- /**
858
- * Retrieve a user by their subject identifier (sub).
859
- *
860
- * @param sub - The user's unique identifier from the IdP
861
- * @returns The user if found, null otherwise
862
- */
863
- get(sub: string): Promise<StoredUser<TExtended> | null>;
851
+ interface SessionStore<TExtended = object> {
864
852
  /**
865
- * Retrieve a user by their email address.
853
+ * Create a new session in the store.
866
854
  *
867
- * @param email - The user's email address
868
- * @returns The user if found, null otherwise
855
+ * @param session - The session data to store
856
+ * @throws Error if session with same sid already exists
869
857
  */
870
- getByEmail(email: string): Promise<StoredUser<TExtended> | null>;
858
+ create(session: Session<TExtended>): Promise<void>;
871
859
  /**
872
- * Retrieve a user by their username.
860
+ * Retrieve a session by its IdP session ID (sid).
873
861
  *
874
- * @param userName - The user's username
875
- * @returns The user if found, null otherwise
862
+ * @param sid - The session.sid from the Identity Provider
863
+ * @returns The session if found, null otherwise
876
864
  */
877
- getByUserName(userName: string): Promise<StoredUser<TExtended> | null>;
865
+ get(sid: string): Promise<Session<TExtended> | null>;
878
866
  /**
879
- * Create or update a user in the store.
867
+ * Update an existing session with partial data.
880
868
  *
881
- * If a user with the same `id` (sub) exists, it will be updated.
882
- * Otherwise, a new user will be created.
869
+ * Commonly used to update lastActivityAt or add custom fields.
883
870
  *
884
- * @param user - The user data to store
871
+ * @param sid - The session.sid to update
872
+ * @param data - Partial session data to merge
873
+ * @throws Error if session not found
885
874
  */
886
- upsert(user: StoredUser<TExtended>): Promise<void>;
875
+ update(sid: string, data: Partial<Session<TExtended>>): Promise<void>;
887
876
  /**
888
- * Delete a user by their subject identifier (sub).
877
+ * Delete a session by its IdP session ID (sid).
889
878
  *
890
- * @param sub - The user's unique identifier to delete
891
- */
892
- delete(sub: string): Promise<void>;
893
- /**
894
- * List users in the store with optional pagination and sort.
879
+ * Used for both normal logout and backchannel logout flows.
895
880
  *
896
- * @param options - Optional start (0-based), limit (page size), and sort
897
- * @returns ListResult with total, count, items, size, page, pages
881
+ * @param sid - The session.sid to delete
898
882
  */
899
- list(options?: UserListOptions): Promise<ListResult<StoredUser<TExtended>>>;
883
+ delete(sid: string): Promise<void>;
900
884
  }
901
- import { StandardSchemaV1 as StandardSchemaV14 } from "@standard-schema/spec";
902
- import { StandardSchemaV1 as StandardSchemaV13 } from "@standard-schema/spec";
903
885
  /**
904
- * JWT Assertion Claims for OAuth2 JWT Bearer Grant (RFC 7523) and OAuth2 Access Tokens
905
- * @see https://datatracker.ietf.org/doc/html/rfc7523
906
- * @see https://datatracker.ietf.org/doc/html/rfc9068
886
+ * Base user with simple, developer-friendly attributes.
887
+ * Extended by User (SSO) and EnterpriseUser (SCIM).
907
888
  */
908
- interface JWTAssertionClaims {
889
+ interface BaseUser {
909
890
  /**
910
- * REQUIRED. Issuer - the workload identity (e.g., SPIFFE ID) or authorization server
891
+ * Unique identifier for the user
911
892
  */
912
- iss: string;
893
+ id?: string;
913
894
  /**
914
- * REQUIRED. Subject - the workload identity or service account
895
+ * REQUIRED. Unique identifier for login
915
896
  */
916
- sub: string;
897
+ userName: string;
917
898
  /**
918
- * OPTIONAL. Audience - may be a string or array of strings
919
- * Note: Required for JWT assertions, but may be absent in OAuth2 access tokens
899
+ * REQUIRED. Simple display name
920
900
  */
921
- aud?: string | string[];
901
+ name: string;
922
902
  /**
923
- * REQUIRED. Expiration time (Unix timestamp)
903
+ * REQUIRED. Primary email address
924
904
  */
925
- exp: number;
905
+ email: string;
926
906
  /**
927
- * REQUIRED. Issued at time (Unix timestamp)
907
+ * URL to user's avatar/profile picture
928
908
  */
929
- iat: number;
909
+ avatar?: string;
910
+ /**
911
+ * User type (e.g., "employee", "contractor", "customer").
912
+ * Shared by SSO/CIAM and enterprise/SCIM user representations.
913
+ */
914
+ userType?: string;
915
+ }
916
+ /**
917
+ * Primary user type for SSO/OIDC applications.
918
+ * Extends BaseUser with SSO-specific data.
919
+ */
920
+ interface User2 extends BaseUser {
921
+ /**
922
+ * SSO/OIDC authentication data
923
+ */
924
+ sso: {
925
+ /**
926
+ * ID Token claims from the identity provider
927
+ */
928
+ profile: IdTokenClaims;
929
+ /**
930
+ * Tenant/organization information
931
+ */
932
+ tenant: {
933
+ id: string;
934
+ name: string;
935
+ };
936
+ /**
937
+ * OAuth scopes granted
938
+ */
939
+ scope?: string;
940
+ /**
941
+ * Token type (typically "Bearer")
942
+ */
943
+ tokenType: string;
944
+ /**
945
+ * Session state from the identity provider
946
+ */
947
+ sessionState?: string;
948
+ /**
949
+ * Token expiration time
950
+ */
951
+ expires: Date;
952
+ };
953
+ }
954
+ type SSOConfig<
955
+ TSessionData = Record<string, never>,
956
+ TUserData = Record<string, never>
957
+ > = {
958
+ authority?: string;
959
+ tokenUrl?: string;
960
+ authorizationUrl?: string;
961
+ clientId?: string;
962
+ clientSecret?: string;
963
+ redirectUri?: string;
964
+ responseType?: "code";
965
+ scope?: string;
966
+ silentRedirectUri?: string;
967
+ jwksUri?: string;
968
+ cookiesPrefix?: string;
969
+ cookiesPath?: string;
970
+ cookiesSecure?: boolean;
971
+ cookiesSameSite?: "Strict" | "Lax";
972
+ /**
973
+ * Optional cookie name for tracking the active session across tenants.
974
+ * Defaults to 'es.active_session' when using the helper utilities.
975
+ */
976
+ activeSessionCookieName?: string;
977
+ endSessionEndpoint?: string;
978
+ revocationEndpoint?: string;
979
+ sessionStore?: SessionStore<TSessionData>;
980
+ /**
981
+ * Optional user store for persisting user profiles from SSO authentication.
982
+ * When configured, users are automatically stored/updated on each login.
983
+ */
984
+ userStore?: UserStore<TUserData>;
985
+ /**
986
+ * Enable Just-In-Time (JIT) user provisioning.
987
+ * When enabled, new users are automatically created in the userStore on their first login.
988
+ * When disabled (default), only existing users in the userStore are updated on login.
989
+ * Requires userStore to be configured.
990
+ * @default false
991
+ */
992
+ enableJitUserProvisioning?: boolean;
993
+ validators?: SSOValidators;
994
+ } & SSOHandlerConfig;
995
+ type StateCookie = {
996
+ state: string;
997
+ codeVerifier: string;
998
+ landingUrl: string;
999
+ errorUrl?: string;
1000
+ };
1001
+ type CookieOptions = {
1002
+ cookieName?: string;
1003
+ path?: string;
1004
+ maxAge?: number;
1005
+ secure?: boolean;
1006
+ sameSite?: "Strict" | "Lax";
1007
+ };
1008
+ declare function getActiveSession(request: Request, options?: CookieOptions): string | undefined;
1009
+ declare function setActiveSession(clientId: string, options?: CookieOptions): string;
1010
+ declare function clearActiveSession(options?: CookieOptions): string;
1011
+ declare function listSsoClientIdsFromCookies(requestOrCookieHeader: Request | string | null | undefined, options?: {
1012
+ cookiePrefix?: string;
1013
+ }): string[];
1014
+ declare function findTenantFromStateParam(cookieHeader: string | null | undefined, stateParam: string): {
1015
+ clientId: string;
1016
+ stateCookie: StateCookie;
1017
+ } | undefined;
1018
+ type LoginConfig = {
1019
+ landingUrl: string;
1020
+ errorUrl?: string;
1021
+ };
1022
+ type SSOHandlerConfig = {
1023
+ loginUrl?: string;
1024
+ userUrl?: string;
1025
+ errorUrl?: string;
1026
+ landingUrl?: string;
1027
+ tokenUrl?: string;
1028
+ refreshUrl?: string;
1029
+ jwksUrl?: string;
1030
+ logoutUrl?: string;
1031
+ logoutBackChannelUrl?: string;
1032
+ };
1033
+ type SSOValidators = {
1034
+ callbackParams: StandardSchemaV13<unknown, OidcCallbackParams>;
1035
+ idTokenClaims: StandardSchemaV13<unknown, IdTokenClaims>;
1036
+ tokenResponse: StandardSchemaV13<unknown, TokenResponse>;
1037
+ };
1038
+ type SSO<
1039
+ TSessionData = Record<string, never>,
1040
+ TUserData = Record<string, never>
1041
+ > = SSOConfig<TSessionData, TUserData> & {
1042
+ getUser: (request: Request) => Promise<User2 | undefined>;
1043
+ getRequiredUser: (request: Request) => Promise<User2>;
1044
+ getJwt: (request: Request) => Promise<string | undefined>;
1045
+ initiateLogin: (config: LoginConfig, requestUrl?: string) => Promise<Response>;
1046
+ logout: (request: Request, config?: LoginConfig) => Promise<Response>;
1047
+ logoutBackChannel2: (request: Request) => Promise<Response>;
1048
+ callbackHandler: (request: Request) => Promise<Response>;
1049
+ handler: (request: Request) => Promise<Response>;
1050
+ };
1051
+ import { StandardSchemaV1 as StandardSchemaV15 } from "@standard-schema/spec";
1052
+ type ChangeListener = () => void;
1053
+ type ReactiveHandle = {
1054
+ beforeChange?(listener: ChangeListener): () => void;
1055
+ afterChange?(listener: ChangeListener): () => void;
1056
+ isAvailable?(): boolean;
1057
+ };
1058
+ import { StandardSchemaV1 as StandardSchemaV14 } from "@standard-schema/spec";
1059
+ /**
1060
+ * JWT Assertion Claims for OAuth2 JWT Bearer Grant (RFC 7523) and OAuth2 Access Tokens
1061
+ * @see https://datatracker.ietf.org/doc/html/rfc7523
1062
+ * @see https://datatracker.ietf.org/doc/html/rfc9068
1063
+ */
1064
+ interface JWTAssertionClaims {
1065
+ /**
1066
+ * REQUIRED. Issuer - the workload identity (e.g., SPIFFE ID) or authorization server
1067
+ */
1068
+ iss: string;
1069
+ /**
1070
+ * REQUIRED. Subject - the workload identity or service account
1071
+ */
1072
+ sub: string;
1073
+ /**
1074
+ * OPTIONAL. Audience - may be a string or array of strings
1075
+ * Note: Required for JWT assertions, but may be absent in OAuth2 access tokens
1076
+ */
1077
+ aud?: string | string[];
1078
+ /**
1079
+ * REQUIRED. Expiration time (Unix timestamp)
1080
+ */
1081
+ exp: number;
1082
+ /**
1083
+ * REQUIRED. Issued at time (Unix timestamp)
1084
+ */
1085
+ iat: number;
930
1086
  /**
931
1087
  * OPTIONAL. JWT ID - unique identifier for this token
932
1088
  * Note: Required for JWT assertions, optional for access tokens
@@ -946,7 +1102,7 @@ interface JWTAssertionClaims {
946
1102
  * @param vendor - The name of the vendor creating this schema
947
1103
  * @returns A StandardSchemaV1 instance for JWT Assertion Claims validation
948
1104
  */
949
- declare function jwtAssertionClaimsSchema(vendor: string): StandardSchemaV13<Record<string, unknown>, JWTAssertionClaims>;
1105
+ declare function jwtAssertionClaimsSchema(vendor: string): StandardSchemaV14<Record<string, unknown>, JWTAssertionClaims>;
950
1106
  /**
951
1107
  * Workload Token Response from OAuth2 token endpoint
952
1108
  * @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
@@ -982,7 +1138,7 @@ interface WorkloadTokenResponse {
982
1138
  * @param vendor - The name of the vendor creating this schema
983
1139
  * @returns A StandardSchemaV1 instance for Workload Token Response validation
984
1140
  */
985
- declare function workloadTokenResponseSchema(vendor: string): StandardSchemaV13<Record<string, unknown>, WorkloadTokenResponse>;
1141
+ declare function workloadTokenResponseSchema(vendor: string): StandardSchemaV14<Record<string, unknown>, WorkloadTokenResponse>;
986
1142
  /**
987
1143
  * Token Validation Result
988
1144
  */
@@ -1229,8 +1385,8 @@ type WorkloadConfigBase = {
1229
1385
  validators?: WorkloadValidators;
1230
1386
  };
1231
1387
  type WorkloadValidators = {
1232
- jwtAssertionClaims: StandardSchemaV14<unknown, JWTAssertionClaims>;
1233
- tokenResponse: StandardSchemaV14<unknown, WorkloadTokenResponse>;
1388
+ jwtAssertionClaims: StandardSchemaV15<unknown, JWTAssertionClaims>;
1389
+ tokenResponse: StandardSchemaV15<unknown, WorkloadTokenResponse>;
1234
1390
  };
1235
1391
  /**
1236
1392
  * JWT Bearer Grant (RFC 7523) Configuration
@@ -1393,7 +1549,7 @@ type WorkloadIdentity = {
1393
1549
  /**
1394
1550
  * Workload Identity Authentication Interface
1395
1551
  */
1396
- type Workload = WorkloadConfig & {
1552
+ type Workload = WorkloadConfig & ReactiveHandle & {
1397
1553
  /**
1398
1554
  * Returns a token for this workload configuration.
1399
1555
  * The optional argument overrides the configured default scope.
@@ -1408,1159 +1564,1279 @@ type Workload = WorkloadConfig & {
1408
1564
  /** Framework-agnostic request handler for the Workload module (token, validate, jwks, refresh). */
1409
1565
  handler: (request: Request) => Promise<Response>;
1410
1566
  };
1411
- type WorkloadClient = Pick<Workload, "getToken" | "refreshToken" | "generateJWTAssertion" | "revokeToken">;
1412
- /**
1413
- * SCIM Error response structure
1414
- */
1415
- interface ScimError {
1416
- schemas: string[];
1417
- status: string;
1418
- scimType?: string;
1419
- detail?: string;
1420
- }
1421
- /**
1422
- * SCIM List Response for bulk operations
1423
- */
1424
- interface ScimListResponse<T> {
1425
- schemas: string[];
1426
- totalResults: number;
1427
- startIndex?: number;
1428
- itemsPerPage?: number;
1429
- Resources: T[];
1430
- }
1431
- /**
1432
- * Result of a SCIM operation
1433
- */
1434
- interface ScimResult<T> {
1435
- success: boolean;
1436
- data?: T;
1437
- error?: ScimError;
1438
- status: number;
1439
- }
1440
- /**
1441
- * Handler configuration for IAM
1442
- */
1443
- interface IAMHandlerConfig {
1444
- /**
1445
- * Base path for the SCIM Users endpoints (e.g., '/api/iam/Users')
1446
- */
1447
- usersUrl?: string;
1448
- /**
1449
- * Base path for the SCIM Groups endpoints (e.g., '/api/iam/Groups')
1450
- */
1451
- groupsUrl?: string;
1452
- }
1567
+ type WorkloadClient = Pick<Workload, "getToken" | "refreshToken" | "generateJWTAssertion" | "revokeToken" | "beforeChange" | "afterChange" | "isAvailable">;
1453
1568
  /**
1454
- * IAM configuration
1569
+ * Magic link data stored in the store.
1455
1570
  *
1456
- * - If `url` is provided, groups_outbound is enabled (app calls external IAM)
1457
- * - If `groupStore` is provided, groups_inbound is enabled (external IAM calls app)
1458
- * - If `userStore` is provided, users_inbound is enabled (external IAM calls app)
1571
+ * @template TExtended - Type-safe custom data that consumers can add to magic links
1459
1572
  */
1460
- type IAMConfig = {
1573
+ type MagicLink<TExtended = object> = {
1461
1574
  /**
1462
- * Base URL of the external SCIM endpoint (e.g., https://sailpoint.example.com/scim/v2)
1463
- * If provided, enables outbound SCIM operations (app -> external IAM)
1575
+ * The magic link token (unique identifier)
1464
1576
  */
1465
- url?: string;
1577
+ token: string;
1466
1578
  /**
1467
- * Store for inbound user provisioning from external IAM providers.
1468
- * When configured, the app can receive user CRUD operations via SCIM.
1579
+ * User information associated with this magic link
1469
1580
  */
1470
- userStore?: UserStore;
1581
+ user: BaseUser;
1471
1582
  /**
1472
- * Store for inbound group provisioning from external IAM providers.
1473
- * When configured, enables groups_inbound (external IAM -> app).
1583
+ * Timestamp when the magic link was created
1474
1584
  */
1475
- groupStore?: GroupStore;
1585
+ createdAt: Date;
1476
1586
  /**
1477
- * Optional handler defaults. These are merged with per-call overrides in
1478
- * `iam.handler`, with per-call values taking precedence.
1587
+ * Timestamp when the magic link expires
1479
1588
  */
1480
- usersUrl?: string;
1481
- groupsUrl?: string;
1482
- };
1483
- type IAMValidators = {
1484
- user: StandardSchemaV15<unknown, User>;
1485
- group: StandardSchemaV15<unknown, GroupResource>;
1486
- };
1487
- /**
1488
- * Options for creating a user
1489
- */
1490
- interface CreateUserOptions {
1589
+ expiresAt: Date;
1491
1590
  /**
1492
- * External identifier for the user
1591
+ * Allow consumers to add runtime data to magic links
1493
1592
  */
1494
- externalId?: string;
1495
- }
1593
+ [key: string]: unknown;
1594
+ } & TExtended;
1496
1595
  /**
1497
- * Options for creating a group
1498
- */
1499
- interface CreateGroupOptions {
1500
- /**
1501
- * External identifier for the group
1502
- */
1503
- externalId?: string;
1504
- /**
1505
- * Initial members to add to the group
1506
- */
1507
- members?: GroupMember[];
1508
- }
1509
- /**
1510
- * Handler configuration for groups_inbound
1596
+ * Abstract interface for magic link storage backends.
1597
+ *
1598
+ * Consumers can implement this interface to use different storage backends:
1599
+ * - In-memory (for development/testing)
1600
+ * - Redis (for production with fast lookups and automatic expiration)
1601
+ * - Database (PostgreSQL, MySQL, etc.)
1602
+ *
1603
+ * @template TExtended - Type-safe custom data that consumers can add to magic links
1604
+ *
1605
+ * @example
1606
+ * ```typescript
1607
+ * // Custom magic link data
1608
+ * type MyMagicLinkData = {
1609
+ * source: string;
1610
+ * metadata: Record<string, unknown>;
1611
+ * };
1612
+ *
1613
+ * // Implement custom store
1614
+ * class RedisMagicLinkStore implements MagicLinkStore<MyMagicLinkData> {
1615
+ * async create(token: string, user: BaseUser, expiresAt: Date): Promise<void> {
1616
+ * const magicLink: MagicLink<MyMagicLinkData> = {
1617
+ * token,
1618
+ * user,
1619
+ * createdAt: new Date(),
1620
+ * expiresAt,
1621
+ * source: 'api',
1622
+ * metadata: {},
1623
+ * };
1624
+ * const ttl = Math.floor((expiresAt.getTime() - Date.now()) / 1000);
1625
+ * await redis.setex(`magic-link:${token}`, ttl, JSON.stringify(magicLink));
1626
+ * }
1627
+ * // ... other methods
1628
+ * }
1629
+ * ```
1511
1630
  */
1512
- interface GroupsInboundHandlerConfig {
1631
+ interface MagicLinkStore<TExtended = object> {
1513
1632
  /**
1514
- * Base path for the SCIM Groups endpoints (e.g., '/api/iam/Groups')
1633
+ * Create a new magic link in the store.
1634
+ *
1635
+ * @param token - The magic link token (unique identifier)
1636
+ * @param user - The user information to associate with this magic link
1637
+ * @param expiresAt - When the magic link expires
1638
+ * @throws Error if magic link with same token already exists
1515
1639
  */
1516
- basePath?: string;
1517
- }
1518
- /**
1519
- * Handler configuration for users_inbound
1520
- */
1521
- interface UsersInboundHandlerConfig {
1640
+ create(token: string, user: BaseUser, expiresAt: Date): Promise<void>;
1522
1641
  /**
1523
- * Base path for the SCIM Users endpoints (e.g., '/api/iam/Users')
1642
+ * Retrieve a magic link by its token.
1643
+ *
1644
+ * @param token - The magic link token
1645
+ * @returns The magic link if found and not expired, null otherwise
1524
1646
  */
1525
- basePath?: string;
1526
- }
1527
- /**
1528
- * Groups Outbound extension - for creating groups in external IAM providers.
1529
- * Enabled when `url` is configured in IAMConfig.
1530
- */
1531
- type IAMGroupsOutbound = {
1647
+ get(token: string): Promise<MagicLink<TExtended> | null>;
1532
1648
  /**
1533
- * Create a new group in the external IAM provider
1534
- * @param displayName - The display name for the group
1535
- * @param options - Optional configuration for the group creation
1536
- * @returns The created group resource from the provider
1649
+ * Delete a magic link by its token.
1650
+ *
1651
+ * Used after a magic link has been consumed (one-time use).
1652
+ *
1653
+ * @param token - The magic link token to delete
1537
1654
  */
1538
- createGroup: (displayName: string, options?: CreateGroupOptions) => Promise<ScimResult<GroupResource>>;
1655
+ delete(token: string): Promise<void>;
1656
+ }
1657
+ type Secret<T> = {
1658
+ data: T;
1659
+ metadata: MetaData;
1539
1660
  };
1540
- /**
1541
- * Groups Inbound extension - for receiving group provisioning from external IAM providers.
1542
- * Enabled when `groupStore` is configured in IAMConfig.
1543
- */
1544
- type IAMGroupsInbound = {
1545
- /**
1546
- * Handle inbound SCIM requests for group management.
1547
- * Routes: GET/POST /Groups, GET/PUT/PATCH/DELETE /Groups/:id
1548
- */
1549
- handler: (request: Request, config?: GroupsInboundHandlerConfig) => Promise<Response>;
1661
+ type MetaData = {
1662
+ path: string;
1663
+ version: number;
1664
+ created: Date;
1550
1665
  };
1551
- /**
1552
- * Users Inbound extension - for receiving user provisioning from external IAM providers.
1553
- * Enabled when `userStore` is configured in IAMConfig.
1554
- */
1555
- type IAMUsersInbound = {
1556
- /**
1557
- * Handle inbound SCIM requests for user management.
1558
- * Routes: GET/POST /Users, GET/PUT/PATCH/DELETE /Users/:id
1559
- */
1560
- handler: (request: Request, config?: UsersInboundHandlerConfig) => Promise<Response>;
1666
+ type SecretsSourceType = "vault" | "azure" | "aws" | "gcp" | "dev";
1667
+ type SecretRequestSeverity = "low" | "medium" | "high" | "critical";
1668
+ type SecretLifecycleRequest = {
1669
+ reason?: string;
1670
+ severity?: SecretRequestSeverity;
1671
+ incidentRef?: string;
1561
1672
  };
1562
- /**
1563
- * Core IAM service interface.
1564
- *
1565
- * - Core functions are user-related (outbound to external IAM)
1566
- * - `groups_outbound` is available when `url` is configured
1567
- * - `groups_inbound` is available when `groupStore` is configured
1568
- * - `users_inbound` is available when `userStore` is configured
1569
- */
1570
- type IAM = IAMConfig & {
1571
- /**
1572
- * Create a new user/account in the external IAM provider
1573
- * Only available when `url` is configured.
1574
- */
1575
- createUser?: (user: User, options?: CreateUserOptions) => Promise<ScimResult<User>>;
1673
+ type SecretsOperationOptions = {
1674
+ /** Optional timeout in milliseconds for this secrets operation. */
1675
+ timeout?: number;
1676
+ };
1677
+ type SecretsSource = ReactiveHandle & {
1678
+ type: SecretsSourceType;
1679
+ getFullSecret: <T>(path: string, options?: SecretsOperationOptions) => Promise<Secret<T>>;
1680
+ getSecret: <T>(path: string, options?: SecretsOperationOptions) => Promise<T>;
1576
1681
  /**
1577
- * Get the configured external SCIM base URL
1682
+ * Write a secret at the given path.
1683
+ * Implementations may throw for read-only or unsupported secret sources.
1578
1684
  */
1579
- getBaseUrl: () => string | undefined;
1685
+ putSecret: (path: string, value: Record<string, unknown>, options?: SecretsOperationOptions) => Promise<void>;
1580
1686
  /**
1581
- * Groups Outbound extension - create groups in external IAM provider.
1582
- * Available when `url` is configured in IAMConfig.
1687
+ * When set, the secret source supports change detection and will call onChange when the secret changes.
1688
+ * Implementations must only invoke onChange when the secret version has changed from the last time
1689
+ * that subscription was notified (same version must not trigger a second call).
1690
+ * @returns a unsubscribe/cleanup function.
1583
1691
  */
1584
- groups_outbound?: IAMGroupsOutbound;
1692
+ subscribe<T>(path: string, onChange: (fullSecret: Secret<T>) => void): () => void;
1585
1693
  /**
1586
- * Groups Inbound extension - receive group provisioning from external IAM.
1587
- * Available when `groupStore` is configured in IAMConfig.
1694
+ * Deletes a secret at the given path.
1588
1695
  */
1589
- groups_inbound?: IAMGroupsInbound;
1696
+ deleteSecret(path: string, options?: SecretsOperationOptions): Promise<void>;
1590
1697
  /**
1591
- * Users Inbound extension - receive user provisioning from external IAM.
1592
- * Available when `userStore` is configured in IAMConfig.
1698
+ * Lists child paths under the given base path.
1593
1699
  */
1594
- users_inbound?: IAMUsersInbound;
1700
+ listPaths(path: string, options?: SecretsOperationOptions): Promise<string[]>;
1595
1701
  /**
1596
- * Framework-agnostic request handler for the IAM module.
1597
- * Routes to users_inbound or groups_inbound based on the request path.
1702
+ * Returns true when a secret path exists.
1598
1703
  */
1599
- handler: (request: Request, config?: IAMHandlerConfig) => Promise<Response>;
1600
- };
1601
- import { StandardSchemaV1 as StandardSchemaV16 } from "@standard-schema/spec";
1602
- /**
1603
- * Session management for tracking user sessions and enabling backchannel logout.
1604
- *
1605
- * Session stores are optional - the package works with JWT cookies alone.
1606
- * Sessions are only required for backchannel logout functionality.
1607
- *
1608
- * ## Session Validation Strategies
1609
- *
1610
- * When using a session store, you can configure when sessions are validated:
1611
- *
1612
- * ### 'always' (default)
1613
- * Validates session on every authenticated request.
1614
- * - **Security**: Maximum - immediate session revocation
1615
- * - **Performance**: InMemory ~0.00005ms, Redis ~1-2ms per request
1616
- * - **Backchannel Logout**: Takes effect immediately
1617
- * - **Use when**: Security is paramount, using InMemory or Redis backend
1618
- *
1619
- * ### 'refresh-only'
1620
- * Validates session only during token refresh (typically every 5-15 minutes).
1621
- * - **Security**: Good - 5-15 minute revocation window
1622
- * - **Performance**: 99% reduction in session lookups
1623
- * - **Backchannel Logout**: Takes effect within token TTL (5-15 min)
1624
- * - **Use when**: Performance is critical AND delayed revocation is acceptable
1625
- * - **WARNING**: Compromised sessions remain valid until next refresh
1626
- *
1627
- * ### 'disabled'
1628
- * Never validates sessions against the store.
1629
- * - **Security**: None - backchannel logout doesn't work
1630
- * - **Performance**: No overhead
1631
- * - **Use when**: Cookie-only mode without session store
1632
- * - **WARNING**: Do not use with sessionStore configured
1633
- *
1634
- * ## Performance Characteristics
1635
- *
1636
- * | Backend | Lookup Time | Impact on Request | Recommendation |
1637
- * |--------------|-------------|-------------------|------------------------|
1638
- * | InMemory | <0.00005ms | Negligible | Use 'always' |
1639
- * | Redis | 1-2ms | 2-4% increase | Use 'always' or test |
1640
- * | Database | 5-20ms | 10-40% increase | Use Redis cache layer |
1641
- *
1642
- * ## Example Usage
1643
- *
1644
- * ```typescript
1645
- * import { sso, InMemorySessionStore } from '@enterprisestandard/server';
1646
- *
1647
- * // Maximum security (default)
1648
- * const secure = sso({
1649
- * // ... other config
1650
- * sessionStore: new InMemorySessionStore(),
1651
- * session_validation: 'always', // Immediate revocation
1652
- * });
1653
- *
1654
- * // High performance
1655
- * const fast = sso({
1656
- * // ... other config
1657
- * sessionStore: new InMemorySessionStore(),
1658
- * session_validation: {
1659
- * strategy: 'refresh-only' // 5-15 min revocation delay
1660
- * }
1661
- * });
1662
- * ```
1663
- */
1664
- /**
1665
- * Core session data tracked for each authenticated user session.
1666
- *
1667
- * @template TExtended - Type-safe custom data that consumers can add to sessions
1668
- */
1669
- type Session<TExtended = object> = {
1704
+ exists(path: string, options?: SecretsOperationOptions): Promise<boolean>;
1670
1705
  /**
1671
- * Session ID from the Identity Provider (from `sid` claim in ID token).
1672
- * This is the unique identifier for the session.
1706
+ * Requests secret rotation for the given path.
1673
1707
  */
1674
- sid: string;
1708
+ requestRotate(path: string, request?: SecretLifecycleRequest, options?: SecretsOperationOptions): Promise<void>;
1675
1709
  /**
1676
- * Subject identifier (user ID) from the Identity Provider.
1677
- * From the `sub` claim in the ID token.
1710
+ * Requests secret revocation for the given path.
1678
1711
  */
1679
- sub: string;
1712
+ requestRevoke(path: string, request?: SecretLifecycleRequest, options?: SecretsOperationOptions): Promise<void>;
1680
1713
  /**
1681
- * Timestamp when the session was created.
1714
+ * Reads metadata for the given path.
1682
1715
  */
1683
- createdAt: Date;
1716
+ getMetadata(path: string, options?: SecretsOperationOptions): Promise<Record<string, unknown>>;
1717
+ };
1718
+ type SecretsValidators = {
1684
1719
  /**
1685
- * Timestamp of the last activity in this session.
1686
- * Can be updated to track session activity.
1720
+ * Optional hook to validate merged source configs before they are resolved.
1721
+ * Throw from this callback to reject invalid secrets source configuration.
1687
1722
  */
1688
- lastActivityAt: Date;
1723
+ validateSourceConfig?(sourceName: string, config: SecretsSourceConfig): void;
1724
+ };
1725
+ type Secrets = ReactiveHandle & {
1726
+ /** Named secrets sources client configurations from RemoteConfig. */
1727
+ config: SecretsSourceMap;
1728
+ /** Returns configured secrets source names/keys. */
1729
+ listSecretsSources(): string[];
1730
+ /** Gets a named secrets source client. Throws when missing. */
1731
+ getSecretsSource(sourceName: string): SecretsSource;
1732
+ /** Reads a secret from a named secrets source client. */
1733
+ getSecret<T>(sourceName: string, path: string, options?: SecretsOperationOptions): Promise<T>;
1734
+ /** Reads full secret data + metadata from a named secrets source client. */
1735
+ getFullSecret<T>(sourceName: string, path: string, options?: SecretsOperationOptions): Promise<Secret<T>>;
1736
+ /** Writes a secret to a named secrets source client. */
1737
+ putSecret(sourceName: string, path: string, value: Record<string, unknown>, options?: SecretsOperationOptions): Promise<void>;
1738
+ /** Deletes a secret from a named secrets source client. */
1739
+ deleteSecret(sourceName: string, path: string, options?: SecretsOperationOptions): Promise<void>;
1740
+ /** Lists child paths under a base path for a named secrets source client. */
1741
+ listPaths(sourceName: string, path: string, options?: SecretsOperationOptions): Promise<string[]>;
1742
+ /** Returns true when a path exists for a named secrets source client. */
1743
+ exists(sourceName: string, path: string, options?: SecretsOperationOptions): Promise<boolean>;
1744
+ /** Requests rotation for a secret path in a named secrets source client. */
1745
+ requestRotate(sourceName: string, path: string, request?: SecretLifecycleRequest, options?: SecretsOperationOptions): Promise<void>;
1746
+ /** Requests revocation for a secret path in a named secrets source client. */
1747
+ requestRevoke(sourceName: string, path: string, request?: SecretLifecycleRequest, options?: SecretsOperationOptions): Promise<void>;
1748
+ /** Reads metadata for a secret path in a named secrets source client. */
1749
+ getMetadata(sourceName: string, path: string, options?: SecretsOperationOptions): Promise<Record<string, unknown>>;
1750
+ /** Subscribes to secret changes on a named secrets source client. */
1751
+ subscribe<T>(sourceName: string, path: string, onChange: (fullSecret: Secret<T>) => void): () => void;
1752
+ /** Returns true when request matches any configured LFV delivery path. */
1753
+ isLfvDeliveryRequest?(request: Request): boolean;
1754
+ /** Returns true when request matches any configured LFV events path. */
1755
+ isLfvEventsRequest?(request: Request): boolean;
1756
+ /** Handles LFV delivery callbacks for configured LFV sources. */
1757
+ handleLfvDelivery?(request: Request): Promise<Response>;
1758
+ /** Handles LFV events callbacks for configured LFV sources. */
1759
+ handleLfvEvents?(request: Request): Promise<Response>;
1760
+ };
1761
+ /**
1762
+ * Partial secrets source config used in framework/app code to declare expected source names.
1763
+ * ConfigSource-backed values may still provide the actual source details at runtime.
1764
+ */
1765
+ type FrameworkSecretsSourceConfig = Partial<SecretsSourceConfig>;
1766
+ /**
1767
+ * Framework-level named secrets source declarations keyed by source name.
1768
+ * Values may be partial or empty when the app only wants to declare expected names/types.
1769
+ */
1770
+ type FrameworkSecretsModuleConfig = Record<string, FrameworkSecretsSourceConfig>;
1771
+ /**
1772
+ * TODO: Let's see if we can do some clean inference and remove this!!!
1773
+ */
1774
+ type SecretsSourceMap = Record<string, SecretsSource>;
1775
+ type SecretsSourceConfig = DevSecretsConfig | GcpSecretsConfig | VaultSecretsConfig | AwsSecretsConfig | AzureSecretsConfig;
1776
+ /**
1777
+ * Raw module config keyed by source name.
1778
+ * The secrets module resolves this into a runtime SecretsSourceMap.
1779
+ */
1780
+ type SecretsModuleConfig = Record<string, SecretsSourceConfig>;
1781
+ type DevSecretsConfig = {
1782
+ type: "dev";
1783
+ ioniteUrl: string;
1784
+ };
1785
+ type GcpSecretsConfig = {
1786
+ type: "gcp";
1787
+ };
1788
+ type VaultLfvSecretsConfig = {
1789
+ /** LFV server base URL for OTP/action endpoints. */
1790
+ lfvServerUrl?: string;
1791
+ /** LFV client id used for OTP issuance. */
1792
+ clientId?: string;
1793
+ /** Signature value for X-LFV-Signature header. */
1794
+ signature?: string;
1795
+ deliveryEndpoint: string;
1689
1796
  /**
1690
- * Allow consumers to add runtime data to sessions.
1797
+ * Optional LFV signature verification key.
1798
+ * When omitted, LFV callbacks are accepted without signature verification.
1691
1799
  */
1692
- [key: string]: unknown;
1693
- } & TExtended;
1800
+ verifyPublicKey?: string;
1801
+ eventsEndpoint: string;
1802
+ path?: string;
1803
+ /**
1804
+ * Timeout in milliseconds when waiting for a LFV delivery payload after a read request.
1805
+ * After this duration, an error is thrown.
1806
+ */
1807
+ deliveryTimeout?: number;
1808
+ /** Retry interval in milliseconds between LFV transport retry attempts. */
1809
+ retryInterval?: number;
1810
+ /** Warning interval in milliseconds for LFV retry logs. Set to 0 to disable warnings. */
1811
+ warnInterval?: number;
1812
+ /**
1813
+ * Optional logger for request/response tracing. Use `debugLogger` from `@enterprisestandard/core`
1814
+ * to get debug output with request_id for LFV operations.
1815
+ */
1816
+ log?: Logger;
1817
+ };
1694
1818
  /**
1695
- * Abstract interface for session storage backends.
1696
- *
1697
- * Consumers can implement this interface to use different storage backends:
1698
- * - Redis
1699
- * - Database (PostgreSQL, MySQL, etc.)
1700
- * - Distributed cache
1701
- * - Custom solutions
1702
- *
1703
- * @template TExtended - Type-safe custom data that consumers can add to sessions
1704
- *
1705
- * @example
1706
- * ```typescript
1707
- * // Custom session data
1708
- * type MySessionData = {
1709
- * ipAddress: string;
1710
- * userAgent: string;
1711
- * };
1712
- *
1713
- * // Implement custom store
1714
- * class RedisSessionStore implements SessionStore<MySessionData> {
1715
- * async create(session: Session<MySessionData>): Promise<void> {
1716
- * await redis.set(`session:${session.sid}`, JSON.stringify(session));
1717
- * }
1718
- * // ... other methods
1719
- * }
1720
- * ```
1819
+ * Runtime-ready LFV source config.
1820
+ * Input config can be partially declared/merged, but LFV operations require these fields.
1721
1821
  */
1722
- interface SessionStore<TExtended = object> {
1822
+ type ResolvedVaultLfvSecretsConfig = Omit<VaultLfvSecretsConfig, "lfvServerUrl" | "clientId" | "path"> & {
1823
+ lfvServerUrl: string;
1824
+ clientId: string;
1825
+ path: string;
1826
+ };
1827
+ type VaultWebSocketAuthHeader = "X-Vault-Token" | "Authorization";
1828
+ type VaultWebSocketSecretsConfig = {
1829
+ /** Websocket URL for vault command execution and live secret subscriptions. */
1830
+ url?: string;
1831
+ /** Token used during websocket connect/auth. */
1832
+ token?: string;
1833
+ /** Header name used to send the websocket token. Defaults to X-Vault-Token. */
1834
+ header?: VaultWebSocketAuthHeader;
1835
+ };
1836
+ type VaultSecretsConfig = {
1837
+ type: "vault";
1838
+ url?: string;
1839
+ token?: string;
1840
+ /** Optional LFV transport capability for reads/lifecycle operations. */
1841
+ lfv?: VaultLfvSecretsConfig;
1842
+ /** Optional websocket capability for vault commands and live subscriptions. */
1843
+ websocket?: VaultWebSocketSecretsConfig;
1723
1844
  /**
1724
- * Create a new session in the store.
1725
- *
1726
- * @param session - The session data to store
1727
- * @throws Error if session with same sid already exists
1845
+ * MINIMUM: 600_000 milliseconds (10 minutes). Polls the path every ttl milliseconds and calls onConfig when config changes.
1728
1846
  */
1729
- create(session: Session<TExtended>): Promise<void>;
1847
+ ttl?: number;
1848
+ };
1849
+ type AwsSecretsConfig = {
1850
+ type: "aws";
1851
+ webhookUrl: string;
1852
+ };
1853
+ type AwsAuthMethod = "client_secret" | "workload_identity" | "managed_identity";
1854
+ type AzureSecretsConfig = {
1855
+ type: "azure";
1730
1856
  /**
1731
- * Retrieve a session by its IdP session ID (sid).
1857
+ * How to authenticate to Azure to fetch Key Vault access tokens.
1732
1858
  *
1733
- * @param sid - The session.sid from the Identity Provider
1734
- * @returns The session if found, null otherwise
1735
- */
1736
- get(sid: string): Promise<Session<TExtended> | null>;
1737
- /**
1738
- * Update an existing session with partial data.
1859
+ * - client_secret: Entra app client secret (client credentials)
1860
+ * - workload_identity: AKS Workload Identity (federated token file)
1861
+ * - managed_identity: Azure Managed Identity via IMDS
1739
1862
  *
1740
- * Commonly used to update lastActivityAt or add custom fields.
1863
+ * If omitted, we auto-detect:
1864
+ * - workload_identity if a federated token file is present
1865
+ * - else client_secret if a clientSecret is present
1866
+ * - else managed_identity
1741
1867
  *
1742
- * @param sid - The session.sid to update
1743
- * @param data - Partial session data to merge
1744
- * @throws Error if session not found
1868
+ * Env: ES_AZURE_AUTH_METHOD
1745
1869
  */
1746
- update(sid: string, data: Partial<Session<TExtended>>): Promise<void>;
1870
+ authMethod?: AwsAuthMethod;
1747
1871
  /**
1748
- * Delete a session by its IdP session ID (sid).
1749
- *
1750
- * Used for both normal logout and backchannel logout flows.
1751
- *
1752
- * @param sid - The session.sid to delete
1872
+ * Azure Entra tenant id (GUID), used for OAuth2 token exchange.
1873
+ * Env: ES_AZURE_TENANT_ID
1874
+ */
1875
+ tenantId?: string;
1876
+ /**
1877
+ * Azure app registration client id (GUID).
1878
+ * Env: ES_AZURE_CLIENT_ID
1753
1879
  */
1754
- delete(sid: string): Promise<void>;
1755
- }
1756
- type SSOConfig<
1757
- TSessionData = Record<string, never>,
1758
- TUserData = Record<string, never>
1759
- > = {
1760
- authority?: string;
1761
- tokenUrl?: string;
1762
- authorizationUrl?: string;
1763
1880
  clientId?: string;
1881
+ /**
1882
+ * Azure app registration client secret.
1883
+ * Env: ES_AZURE_CLIENT_SECRET
1884
+ */
1764
1885
  clientSecret?: string;
1765
- redirectUri?: string;
1766
- responseType?: "code";
1767
- scope?: string;
1768
- silentRedirectUri?: string;
1769
- jwksUri?: string;
1770
- cookiesPrefix?: string;
1771
- cookiesPath?: string;
1772
- cookiesSecure?: boolean;
1773
- cookiesSameSite?: "Strict" | "Lax";
1774
1886
  /**
1775
- * Optional cookie name for tracking the active session across tenants.
1776
- * Defaults to 'es.active_session' when using the helper utilities.
1887
+ * Path to the projected federated token file (AKS Workload Identity).
1888
+ * Env: ES_AZURE_FEDERATED_TOKEN_FILE or AZURE_FEDERATED_TOKEN_FILE
1777
1889
  */
1778
- activeSessionCookieName?: string;
1779
- endSessionEndpoint?: string;
1780
- revocationEndpoint?: string;
1781
- sessionStore?: SessionStore<TSessionData>;
1890
+ federatedTokenFile?: string;
1782
1891
  /**
1783
- * Optional user store for persisting user profiles from SSO authentication.
1784
- * When configured, users are automatically stored/updated on each login.
1892
+ * Managed Identity client id (user-assigned), optional.
1893
+ * Env: ES_AZURE_MANAGED_IDENTITY_CLIENT_ID
1785
1894
  */
1786
- userStore?: UserStore<TUserData>;
1895
+ managedIdentityClientId?: string;
1787
1896
  /**
1788
- * Enable Just-In-Time (JIT) user provisioning.
1789
- * When enabled, new users are automatically created in the userStore on their first login.
1790
- * When disabled (default), only existing users in the userStore are updated on login.
1791
- * Requires userStore to be configured.
1792
- * @default false
1897
+ * IMDS API version (managed identity).
1898
+ * Env: ES_AZURE_IMDS_API_VERSION
1899
+ * Default: 2018-02-01
1793
1900
  */
1794
- enableJitUserProvisioning?: boolean;
1795
- validators?: SSOValidators;
1796
- } & SSOHandlerConfig;
1797
- type StateCookie = {
1798
- state: string;
1799
- codeVerifier: string;
1800
- landingUrl: string;
1801
- errorUrl?: string;
1802
- };
1803
- type CookieOptions = {
1804
- cookieName?: string;
1805
- path?: string;
1806
- maxAge?: number;
1807
- secure?: boolean;
1808
- sameSite?: "Strict" | "Lax";
1809
- };
1810
- declare function getActiveSession(request: Request, options?: CookieOptions): string | undefined;
1811
- declare function setActiveSession(clientId: string, options?: CookieOptions): string;
1812
- declare function clearActiveSession(options?: CookieOptions): string;
1813
- declare function listSsoClientIdsFromCookies(requestOrCookieHeader: Request | string | null | undefined, options?: {
1814
- cookiePrefix?: string;
1815
- }): string[];
1816
- declare function findTenantFromStateParam(cookieHeader: string | null | undefined, stateParam: string): {
1817
- clientId: string;
1818
- stateCookie: StateCookie;
1819
- } | undefined;
1820
- type LoginConfig = {
1821
- landingUrl: string;
1822
- errorUrl?: string;
1901
+ imdsApiVersion?: string;
1902
+ /**
1903
+ * Key Vault URL, e.g. https://myvault.vault.azure.net
1904
+ * Env: ES_AZURE_KEY_VAULT_URL
1905
+ */
1906
+ vaultUrl?: string;
1907
+ /**
1908
+ * Alternative to vaultUrl. If provided, vaultUrl is derived as:
1909
+ * https://${vaultName}.vault.azure.net
1910
+ * Env: ES_AZURE_KEY_VAULT_NAME
1911
+ */
1912
+ vaultName?: string;
1913
+ /**
1914
+ * Key Vault API version.
1915
+ * Env: ES_AZURE_KEY_VAULT_API_VERSION
1916
+ * Default: 7.4
1917
+ */
1918
+ apiVersion?: string;
1919
+ /**
1920
+ * Maps vault "path" to a Key Vault secret name.
1921
+ * Default: replaces `/` with `--` and trims leading/trailing `/`.
1922
+ */
1923
+ secretNameTransform?: (path: string) => string;
1924
+ /**
1925
+ * Optional prefix added to all computed secret names.
1926
+ * Useful to namespace secrets by app/environment.
1927
+ */
1928
+ secretNamePrefix?: string;
1929
+ /**
1930
+ * OAuth2 scope; default is Key Vault resource scope.
1931
+ * Default: https://vault.azure.net/.default
1932
+ */
1933
+ scope?: string;
1934
+ /**
1935
+ * When set, the vault implements subscribe: poll this path every ttl seconds and call onConfig when config changes.
1936
+ */
1937
+ ttl?: number;
1823
1938
  };
1824
- type SSOHandlerConfig = {
1825
- loginUrl?: string;
1826
- userUrl?: string;
1827
- errorUrl?: string;
1828
- landingUrl?: string;
1829
- tokenUrl?: string;
1830
- refreshUrl?: string;
1831
- jwksUrl?: string;
1832
- logoutUrl?: string;
1833
- logoutBackChannelUrl?: string;
1939
+ type ConfigSourceType = "vault" | "azure" | "aws" | "gcp";
1940
+ type ESValidators = {
1941
+ sso: SSOValidators;
1942
+ iam: IAMValidators;
1943
+ workload: WorkloadValidators;
1944
+ ciam: CIAMValidators;
1945
+ secrets?: SecretsValidators;
1834
1946
  };
1835
- type SSOValidators = {
1836
- callbackParams: StandardSchemaV16<unknown, OidcCallbackParams>;
1837
- idTokenClaims: StandardSchemaV16<unknown, IdTokenClaims>;
1838
- tokenResponse: StandardSchemaV16<unknown, TokenResponse>;
1947
+ type ApplicationValidators<TTenantValidators extends TenantValidators = TenantValidators> = ESValidators & {
1948
+ tenant: TTenantValidators;
1839
1949
  };
1840
- type SSO<
1841
- TSessionData = Record<string, never>,
1842
- TUserData = Record<string, never>
1843
- > = SSOConfig<TSessionData, TUserData> & {
1844
- getUser: (request: Request) => Promise<User2 | undefined>;
1845
- getRequiredUser: (request: Request) => Promise<User2>;
1846
- getJwt: (request: Request) => Promise<string | undefined>;
1847
- initiateLogin: (config: LoginConfig, requestUrl?: string) => Promise<Response>;
1848
- logout: (request: Request, config?: LoginConfig) => Promise<Response>;
1849
- logoutBackChannel2: (request: Request) => Promise<Response>;
1850
- callbackHandler: (request: Request) => Promise<Response>;
1851
- handler: (request: Request) => Promise<Response>;
1950
+ /**
1951
+ * Configuration supplied by the framework/application when creating an Enterprise Standard instance.
1952
+ * Merged with RemoteConfig from the ConfigSource (framework config wins). Pass as the second
1953
+ * argument to enterpriseStandard(source, config).
1954
+ * Set a module to `null` to explicitly disable it; then the corresponding property on the
1955
+ * EnterpriseStandard instance is typed as `never`. Omit a module to allow it to be supplied
1956
+ * from ConfigSource / adaptive (typed as the module type, non-optional).
1957
+ */
1958
+ type FrameworkConfig = {
1959
+ /** Optional `Logger` implementation (e.g. `consoleLogger`); exposed on the instance as `log`. */
1960
+ log?: Logger;
1961
+ sso?: SSOConfig | null;
1962
+ iam?: IAMConfig | null;
1963
+ workload?: FrameworkWorkloadConfig | null;
1964
+ secrets?: FrameworkSecretsModuleConfig | null;
1965
+ ciam?: CIAMConfig | null;
1966
+ validators: ESValidators;
1852
1967
  };
1853
- import { StandardSchemaV1 as StandardSchemaV17 } from "@standard-schema/spec";
1854
- type Secret<T> = {
1855
- data: T;
1856
- metadata: MetaData;
1968
+ /**
1969
+ * Final configuration after merging ConfigSource (RemoteConfig) and FrameworkConfig.
1970
+ * Same shape as FrameworkConfig; each module (SSO, IAM, etc.) resolves its config from
1971
+ * both sources at runtime. Use this type when referring to the effective/merged config.
1972
+ */
1973
+ type ESConfig = FrameworkConfig;
1974
+ /**
1975
+ * Remote config read from a config source (vault, lfv, etc.).
1976
+ */
1977
+ type RemoteConfig = {
1978
+ /** Optional app/tenant identifier for this ESA (e.g. from vault path or config). */
1979
+ tenantId?: string;
1980
+ sso?: SSOConfig;
1981
+ iam?: IAMConfig;
1982
+ /**
1983
+ * Workload: single config, or incoming/outgoing (server vs client roles), or flat map of named clients.
1984
+ * Preferred: { incoming: { jwksUri, issuer }, outgoing: { TNT_*: { clientId, ... } } }.
1985
+ * Legacy: { default: { jwksUri, issuer }, TNT_*: { ... } } (flat map).
1986
+ * TODO: Let's see if we can do some clean inference here!!!
1987
+ */
1988
+ workload?: WorkloadConfig | WorkloadConfigMap | WorkloadIncomingOutgoing;
1989
+ /** Optional named secrets-source configs available to this ESA instance. */
1990
+ secrets?: SecretsModuleConfig;
1991
+ ciam?: CIAMConfig;
1857
1992
  };
1858
- type MetaData = {
1993
+ /**
1994
+ * Stores supplied by the framework/application when creating an Enterprise Standard instance.
1995
+ */
1996
+ type FrameworkStores = {
1997
+ sessionStore?: SessionStore<unknown>;
1998
+ userStore?: UserStore<unknown>;
1999
+ groupStore?: GroupStore<unknown>;
2000
+ magicLinkStore?: MagicLinkStore<unknown>;
2001
+ workloadTokenStore?: WorkloadTokenStore;
2002
+ };
2003
+ type ModifiableFrameworkConfig = FrameworkConfig & {
2004
+ setStores(stores: FrameworkStores): void;
2005
+ };
2006
+ /** Return type from the beforeChange hook passed to enterpriseStandard(). */
2007
+ type ESConfigChangeResult = {
2008
+ config?: RemoteConfig;
2009
+ frameworkConfig?: FrameworkConfig;
2010
+ };
2011
+ /** beforeChange callback invoked on every config application (initial load and updates). */
2012
+ type ESConfigChangeCallback = (config: RemoteConfig, frameworkConfig: ModifiableFrameworkConfig, oldConfig: RemoteConfig | undefined) => ESConfigChangeResult | void;
2013
+ type ConfigSource = {
2014
+ load(): Promise<RemoteConfig>;
2015
+ /**
2016
+ * Called when the config changes.
2017
+ * @param onConfig - The callback to call when the config changes.
2018
+ * @return an unsubscribe/cleanup function.
2019
+ */
2020
+ subscribe(onConfig: (config: RemoteConfig) => void): undefined | (() => void);
2021
+ /**
2022
+ * Default secret client for the config source itself.
2023
+ * For vault-backed sources this is the vault used to read RemoteConfig.
2024
+ */
2025
+ secret: SecretsSource;
2026
+ /**
2027
+ * Optional. If not set by the creator, the framework may set this before calling load/subscribe
2028
+ * so the source can use the same logger as the Enterprise Standard instance (`log`).
2029
+ */
2030
+ log?: Logger;
2031
+ /**
2032
+ * Optional. If not set by the creator, the framework may set this before calling load/subscribe
2033
+ * so the source can use the same validators.
2034
+ */
2035
+ validators?: ESValidators;
2036
+ };
2037
+ type EnvironmentType = "POC" | "DEV" | "QA" | "PROD";
2038
+ type TenantStatus = "pending" | "processing" | "completed" | "failed" | "action_required";
2039
+ type UpsertTenantRequestBase = {
2040
+ tenantId: string;
2041
+ companyId: string;
2042
+ companyName: string;
2043
+ environmentType: EnvironmentType;
2044
+ email?: string;
2045
+ webhookUrl?: string;
2046
+ callbackUrl?: string;
2047
+ configSource: TenantSecretsConfig;
2048
+ };
2049
+ type UpsertTenantRequest<TExtended extends object = object> = UpsertTenantRequestBase & TExtended;
2050
+ type UpsertTenantResponse = {
2051
+ tenantUrl?: string;
2052
+ status: Exclude<TenantStatus, "action_required">;
2053
+ error?: string;
2054
+ refs?: RefUrls[];
2055
+ } | {
2056
+ status: "action_required";
2057
+ actionUrl: string;
2058
+ requestToken: string;
2059
+ expiresAt: string;
2060
+ refs?: RefUrls[];
2061
+ };
2062
+ type CreateTenantRequest = UpsertTenantRequest;
2063
+ type CreateTenantResponse = UpsertTenantResponse;
2064
+ /**
2065
+ * The audience of the reference URL.
2066
+ * - 'human' for human-readable documentation such as user guides, documentation, etc.
2067
+ * - 'ai' for AI-generated reference such as llms.txt, AI-optimized markdown, etc.
2068
+ * - 'spec' for specifications and code-gen ready docs such as OpenAPI, GraphQL, etc.
2069
+ */
2070
+ type Audience = "human" | "ai" | "spec";
2071
+ interface RefUrls {
2072
+ audience: Audience;
2073
+ url: string;
2074
+ description: string;
2075
+ createdAt: Date;
2076
+ updatedAt: Date;
2077
+ }
2078
+ interface TenantWebhookPayload {
2079
+ tenantId: string;
2080
+ companyId: string;
2081
+ status: TenantStatus;
2082
+ tenantUrl?: string;
2083
+ actionUrl?: string;
2084
+ requestToken?: string;
2085
+ expiresAt?: string;
2086
+ error?: string;
2087
+ }
2088
+ declare class TenantRequestError extends Error {
2089
+ constructor(message: string, options?: ErrorOptions);
2090
+ }
2091
+ declare class MultipleTenantsForUserError extends Error {
2092
+ readonly userId: string;
2093
+ readonly tenantIds: string[];
2094
+ constructor(userId: string, tenantIds: string[], options?: ErrorOptions);
2095
+ }
2096
+ type TenantValidators<
2097
+ TUpsertTenantRequest extends UpsertTenantRequest = UpsertTenantRequest,
2098
+ TUpsertTenantResponse extends UpsertTenantResponse = UpsertTenantResponse
2099
+ > = {
2100
+ upsertTenantRequest: StandardSchemaV16<unknown, TUpsertTenantRequest>;
2101
+ upsertTenantResponse?: StandardSchemaV16<unknown, TUpsertTenantResponse>;
2102
+ createTenantRequest?: StandardSchemaV16<unknown, TUpsertTenantRequest>;
2103
+ createTenantResponse?: StandardSchemaV16<unknown, TUpsertTenantResponse>;
2104
+ };
2105
+ /**
2106
+ * Env-like tenant config variables used to build a ConfigSource at runtime.
2107
+ * These mirror the ES_* variables read by envConfig().
2108
+ */
2109
+ type TenantConfigEnv = {
2110
+ ES_CONFIG_TYPE?: ConfigSourceType;
2111
+ ES_VAULT_URL?: string;
2112
+ ES_VAULT_TOKEN?: string;
2113
+ ES_VAULT_PATH?: string;
2114
+ ES_VAULT_TTL?: string;
2115
+ ES_VAULT_LFV_SERVER_URL?: string;
2116
+ ES_VAULT_LFV_CLIENT_ID?: string;
2117
+ ES_VAULT_LFV_SIGNATURE?: string;
2118
+ ES_VAULT_LFV_DELIVERY_ENDPOINT?: string;
2119
+ ES_VAULT_LFV_VERIFY_PUBLIC_KEY?: string;
2120
+ ES_VAULT_LFV_EVENTS_ENDPOINT?: string;
2121
+ ES_VAULT_LFV_DELIVERY_TIMEOUT?: string;
2122
+ ES_VAULT_LFV_RETRY_INTERVAL?: string;
2123
+ ES_VAULT_LFV_WARN_INTERVAL?: string;
2124
+ ES_VAULT_WEBSOCKET_URL?: string;
2125
+ ES_VAULT_WEBSOCKET_TOKEN?: string;
2126
+ ES_VAULT_WEBSOCKET_HEADER?: "X-Vault-Token" | "Authorization";
2127
+ ES_AZURE_API_VERSION?: string;
2128
+ ES_AZURE_SCOPE?: string;
2129
+ ES_AZURE_SECRET_NAME_PREFIX?: string;
2130
+ ES_AZURE_AUTH_METHOD?: AwsAuthMethod;
2131
+ ES_AZURE_TENANT_ID?: string;
2132
+ ES_AZURE_CLIENT_ID?: string;
2133
+ ES_AZURE_CLIENT_SECRET?: string;
2134
+ ES_AZURE_FEDERATED_TOKEN_FILE?: string;
2135
+ ES_AZURE_MANAGED_IDENTITY_CLIENT_ID?: string;
2136
+ ES_AZURE_IMDS_API_VERSION?: string;
2137
+ ES_AZURE_VAULT_URL?: string;
2138
+ ES_AZURE_VAULT_NAME?: string;
2139
+ ES_AZURE_TTL?: string;
2140
+ ES_AWS_WEBHOOK_URL?: string;
2141
+ ES_AWS_TTL?: string;
2142
+ ES_GCP_TTL?: string;
2143
+ };
2144
+ type TenantSecretsConfig = (VaultSecretsConfig & {
2145
+ path: string;
2146
+ retryInterval?: number;
2147
+ }) | (AwsSecretsConfig & {
2148
+ ttl?: number;
2149
+ }) | AzureSecretsConfig | (GcpSecretsConfig & {
2150
+ ttl?: number;
2151
+ });
2152
+ type TenantStoredConfigLocator = {
2153
+ /** Indicates that the tenant config descriptor is stored securely outside the tenant record. */
2154
+ type: "stored";
2155
+ /** Root secure source type used to fetch the stored tenant config descriptor. */
2156
+ sourceType: "vault";
2157
+ /** Path to the stored tenant config descriptor. */
1859
2158
  path: string;
1860
- version: number;
1861
- created: Date;
1862
2159
  };
1863
- type SecretsSourceType = "vault" | "azure" | "aws" | "gcp" | "dev";
1864
- type SecretRequestSeverity = "low" | "medium" | "high" | "critical";
1865
- type SecretLifecycleRequest = {
1866
- reason?: string;
1867
- severity?: SecretRequestSeverity;
1868
- incidentRef?: string;
2160
+ type TenantRemoteConfigLocator = {
2161
+ /** Indicates that the tenant RemoteConfig already exists at this secure source path. */
2162
+ type: "remoteConfig";
2163
+ /** Secure source type used to load the RemoteConfig document directly. */
2164
+ sourceType: "vault";
2165
+ /** Path to the tenant RemoteConfig document. */
2166
+ path: string;
1869
2167
  };
1870
- type SecretsOperationOptions = {
1871
- /** Optional timeout in milliseconds for this secrets operation. */
1872
- timeout?: number;
2168
+ type TenantConfigLocator = TenantStoredConfigLocator | TenantRemoteConfigLocator;
2169
+ type TenantConfigSourceInput = TenantConfigLocator | ConfigSource;
2170
+ type TenantBaseRecord = {
2171
+ tenantId: string;
2172
+ companyId: string;
2173
+ companyName: string;
2174
+ environmentType: EnvironmentType;
2175
+ email?: string;
2176
+ webhookUrl?: string;
2177
+ callbackUrl?: string;
2178
+ tenantUrl?: string;
2179
+ status: TenantStatus;
2180
+ error?: string;
2181
+ actionUrl?: string;
2182
+ requestToken?: string;
2183
+ expiresAt?: string;
2184
+ createdAt: Date;
2185
+ updatedAt: Date;
2186
+ /** Persisted tenant config metadata, or a runtime ConfigSource for internal-only tenants. */
2187
+ configSource: TenantConfigSourceInput;
2188
+ /** Runtime helper that returns a ConfigSource for this tenant. */
2189
+ config?: (source?: SecretsSource) => ConfigSource;
2190
+ };
2191
+ type TenantBaseConstraint = Omit<TenantBaseRecord, "configSource"> & {
2192
+ configSource?: TenantConfigSourceInput;
2193
+ };
2194
+ type TenantRecordBase = TenantBaseConstraint;
2195
+ type StoredTenant<TTenant extends TenantRecordBase = TenantRecordBase> = TTenant;
2196
+ type StoredTenantRecord<TTenant extends TenantRecordBase = TenantRecordBase> = Omit<StoredTenant<TTenant>, "config">;
2197
+ type TenantEsFactory<TTenant extends TenantRecordBase = TenantRecordBase> = (tenant: StoredTenant<TTenant>) => EnterpriseStandard;
2198
+ type TenantConfigStoreRequest<
2199
+ TTenant extends TenantRecordBase = TenantRecordBase,
2200
+ TRequest extends UpsertTenantRequest = UpsertTenantRequest
2201
+ > = {
2202
+ es: EnterpriseStandard;
2203
+ tenantId: string;
2204
+ request: TRequest;
2205
+ configData: TenantSecretsConfig;
2206
+ existingTenant: StoredTenant<TTenant> | undefined;
1873
2207
  };
1874
- type SecretsSource = {
1875
- type: SecretsSourceType;
1876
- getFullSecret: <T>(path: string, options?: SecretsOperationOptions) => Promise<Secret<T>>;
1877
- getSecret: <T>(path: string, options?: SecretsOperationOptions) => Promise<T>;
2208
+ type TenantStoreWithESOptions<TTenant extends TenantRecordBase = TenantRecordBase> = {
2209
+ /**
2210
+ * TTL for cached per-tenant EnterpriseStandard instances, in milliseconds.
2211
+ * Default is forever; set to 0 to recreate ES on every getEs() call.
2212
+ */
2213
+ ttl?: number;
2214
+ /**
2215
+ * Optional factory used to create an ES instance for a tenant.
2216
+ * If omitted, getEs() throws.
2217
+ */
2218
+ createEs?: TenantEsFactory<TTenant>;
2219
+ };
2220
+ type TenantUserRegistration = {
2221
+ registerUserTenantId(userId: string, tenantId: string | null | undefined): void | Promise<void>;
2222
+ registerUserToTenant?(userId: string, tenantId: string): void | Promise<void>;
2223
+ };
2224
+ declare abstract class TenantStore<TTenant extends TenantRecordBase = TenantRecordBase> implements TenantUserRegistration {
2225
+ storeConfig?(config: TenantConfigStoreRequest<TTenant>): Promise<TenantConfigSourceInput>;
2226
+ abstract get(tenantId: string): Promise<StoredTenant<TTenant> | undefined>;
2227
+ abstract list(options?: TenantListOptions): Promise<ListResult<StoredTenant<TTenant>>>;
2228
+ abstract upsert(tenant: StoredTenant<TTenant>): Promise<StoredTenant<TTenant>>;
2229
+ abstract delete(tenantId: string): Promise<number>;
2230
+ abstract registerUserTenantId(userId: string, tenantId: string | null | undefined): void | Promise<void>;
2231
+ registerUserToTenant?(userId: string, tenantId: string): void | Promise<void>;
2232
+ abstract findTenantsByUser(user: User2): Promise<StoredTenant<TTenant>[]>;
2233
+ findTenantByUser(user: User2): Promise<StoredTenant<TTenant> | undefined>;
2234
+ }
2235
+ type TenantManagerStore<TTenant extends TenantRecordBase = TenantRecordBase> = Pick<TenantStoreWithES<TTenant>, "get" | "list" | "upsert" | "delete" | "getEs" | "findTenantByUser" | "findTenantsByUser"> & {
2236
+ storeConfig?: TenantStoreWithES<TTenant>["storeConfig"];
2237
+ };
2238
+ type InMemoryTenantStoreOptions<TTenant extends TenantRecordBase = TenantRecordBase> = TenantStoreWithESOptions<TTenant>;
2239
+ type TenantStoreWithRequiredEsOptions<TTenant extends TenantRecordBase = TenantRecordBase> = Omit<TenantStoreWithESOptions<TTenant>, "createEs"> & {
2240
+ createEs: TenantEsFactory<TTenant>;
2241
+ };
2242
+ type SingleTenantStoreOptions<TTenant extends TenantRecordBase = TenantRecordBase> = TenantStoreWithRequiredEsOptions<TTenant>;
2243
+ type MultiTenantStoreOptions<TTenant extends TenantRecordBase = TenantRecordBase> = TenantStoreWithRequiredEsOptions<TTenant>;
2244
+ declare abstract class TenantStoreWithEsCache<TTenant extends TenantRecordBase = TenantRecordBase> extends TenantStore<TTenant> {
2245
+ readonly ttl: number;
2246
+ private readonly createEs?;
2247
+ private readonly tenantEsMap;
2248
+ constructor(options: TenantStoreWithESOptions<TTenant>);
2249
+ registerUserTenantId(userId: string, tenantId: string | null | undefined): Promise<void>;
2250
+ registerUserToTenant(_userId: string, _tenantId: string): Promise<void>;
2251
+ protected prepareTenantForCreateEs(tenant: StoredTenant<TTenant>): StoredTenant<TTenant>;
2252
+ protected invalidateTenantEsCache(tenantId: string): void;
2253
+ getEs(tenantId: string): Promise<EnterpriseStandard | undefined>;
2254
+ getCachedTenantIds(): string[];
2255
+ }
2256
+ declare abstract class SingleTenantStore<TTenant extends TenantRecordBase = TenantRecordBase> extends TenantStoreWithEsCache<TTenant> {
2257
+ abstract findTenantByUser(user: User2): Promise<StoredTenant<TTenant> | undefined>;
2258
+ findTenantsByUser(user: User2): Promise<StoredTenant<TTenant>[]>;
2259
+ }
2260
+ declare abstract class MultiTenantStore<TTenant extends TenantRecordBase = TenantRecordBase> extends TenantStoreWithEsCache<TTenant> {}
2261
+ type TenantStoreWithES<TTenant extends TenantRecordBase = TenantRecordBase> = TenantStoreWithEsCache<TTenant>;
2262
+ type InMemorySingleTenantStoreOptions<TTenant extends TenantRecordBase = TenantRecordBase> = InMemoryTenantStoreOptions<TTenant>;
2263
+ type InMemoryMultiTenantStoreOptions<TTenant extends TenantRecordBase = TenantRecordBase> = InMemoryTenantStoreOptions<TTenant>;
2264
+ declare class InMemorySingleTenantStore<TTenant extends TenantRecordBase = TenantRecordBase> extends SingleTenantStore<TTenant> {
2265
+ private readonly store;
2266
+ constructor(options?: InMemorySingleTenantStoreOptions<TTenant>);
2267
+ get(tenantId: string): Promise<StoredTenant<TTenant> | undefined>;
2268
+ list(options?: TenantListOptions): Promise<ListResult<StoredTenant<TTenant>>>;
2269
+ upsert(tenant: StoredTenant<TTenant>): Promise<StoredTenant<TTenant>>;
2270
+ delete(tenantId: string): Promise<number>;
2271
+ registerUserTenantId(userId: string, tenantId: string | null | undefined): Promise<void>;
2272
+ findTenantByUser(user: User2): Promise<StoredTenant<TTenant> | undefined>;
2273
+ }
2274
+ declare class InMemoryMultiTenantStore<TTenant extends TenantRecordBase = TenantRecordBase> extends MultiTenantStore<TTenant> {
2275
+ private readonly store;
2276
+ constructor(options?: InMemoryMultiTenantStoreOptions<TTenant>);
2277
+ get(tenantId: string): Promise<StoredTenant<TTenant> | undefined>;
2278
+ list(options?: TenantListOptions): Promise<ListResult<StoredTenant<TTenant>>>;
2279
+ upsert(tenant: StoredTenant<TTenant>): Promise<StoredTenant<TTenant>>;
2280
+ delete(tenantId: string): Promise<number>;
2281
+ registerUserTenantId(userId: string, tenantId: string | null | undefined): Promise<void>;
2282
+ findTenantsByUser(user: User2): Promise<StoredTenant<TTenant>[]>;
2283
+ }
2284
+ declare function sendTenantWebhook(webhookUrl: string, payload: TenantWebhookPayload, log: Logger): Promise<void>;
2285
+ /**
2286
+ * Stored user data with required id and tracking metadata.
2287
+ *
2288
+ * Extends the SSO User type with:
2289
+ * - Required `id` (the `sub` claim from the IdP)
2290
+ * - Timestamps for tracking when users were first seen and last updated
2291
+ * - Optional custom extended data
2292
+ *
2293
+ * @template TExtended - Type-safe custom data that consumers can add to users
2294
+ */
2295
+ type StoredUser<TExtended = object> = Omit<User2, "sso"> & {
2296
+ /**
2297
+ * Required unique identifier (the `sub` claim from the IdP).
2298
+ * This is the primary key for user storage.
2299
+ */
2300
+ id?: string;
2301
+ /**
2302
+ * Optional Enterprise Standard tenant identifier for tenant-aware apps.
2303
+ * Built-in user stores can use this when registering HRD mappings.
2304
+ */
2305
+ tenantId?: string;
2306
+ /**
2307
+ * Optional external identifier from the provisioning client.
2308
+ * Commonly set by IAM inbound SCIM provisioning flows.
2309
+ */
2310
+ externalId?: string;
2311
+ /**
2312
+ * Optional SCIM display name distinct from the simple `name` field.
2313
+ * Commonly set by IAM inbound SCIM provisioning flows.
2314
+ */
2315
+ displayName?: string;
2316
+ /**
2317
+ * Optional structured SCIM name.
2318
+ * Commonly set by IAM inbound SCIM provisioning flows.
2319
+ */
2320
+ scimName?: Name;
1878
2321
  /**
1879
- * Write a secret at the given path.
1880
- * Implementations may throw for read-only or unsupported secret sources.
2322
+ * Optional SCIM email collection.
2323
+ * The simple `email` field still stores the primary email for convenience.
1881
2324
  */
1882
- putSecret: (path: string, value: Record<string, unknown>, options?: SecretsOperationOptions) => Promise<void>;
2325
+ emails?: Email[];
1883
2326
  /**
1884
- * When set, the secret source supports change detection and will call onChange when the secret changes.
1885
- * Implementations must only invoke onChange when the secret version has changed from the last time
1886
- * that subscription was notified (same version must not trigger a second call).
1887
- * @returns a unsubscribe/cleanup function.
2327
+ * Optional SCIM nickname.
2328
+ * Commonly set by IAM inbound SCIM provisioning flows.
1888
2329
  */
1889
- subscribe<T>(path: string, onChange: (fullSecret: Secret<T>) => void): () => void;
2330
+ nickName?: string;
1890
2331
  /**
1891
- * Deletes a secret at the given path.
2332
+ * Optional SCIM account state.
2333
+ * Commonly set by IAM inbound SCIM provisioning flows.
1892
2334
  */
1893
- deleteSecret(path: string, options?: SecretsOperationOptions): Promise<void>;
2335
+ active?: boolean;
1894
2336
  /**
1895
- * Lists child paths under the given base path.
2337
+ * Optional SCIM job title.
2338
+ * Commonly set by IAM inbound SCIM provisioning flows.
1896
2339
  */
1897
- listPaths(path: string, options?: SecretsOperationOptions): Promise<string[]>;
2340
+ title?: string;
1898
2341
  /**
1899
- * Returns true when a secret path exists.
2342
+ * Optional SCIM preferred language.
2343
+ * Commonly set by IAM inbound SCIM provisioning flows.
1900
2344
  */
1901
- exists(path: string, options?: SecretsOperationOptions): Promise<boolean>;
2345
+ preferredLanguage?: string;
1902
2346
  /**
1903
- * Requests secret rotation for the given path.
2347
+ * Optional SCIM locale.
2348
+ * Commonly set by IAM inbound SCIM provisioning flows.
1904
2349
  */
1905
- requestRotate(path: string, request?: SecretLifecycleRequest, options?: SecretsOperationOptions): Promise<void>;
2350
+ locale?: string;
1906
2351
  /**
1907
- * Requests secret revocation for the given path.
2352
+ * Optional SCIM timezone.
2353
+ * Commonly set by IAM inbound SCIM provisioning flows.
1908
2354
  */
1909
- requestRevoke(path: string, request?: SecretLifecycleRequest, options?: SecretsOperationOptions): Promise<void>;
2355
+ timezone?: string;
1910
2356
  /**
1911
- * Reads metadata for the given path.
2357
+ * Optional SCIM phone numbers.
2358
+ * Commonly set by IAM inbound SCIM provisioning flows.
1912
2359
  */
1913
- getMetadata(path: string, options?: SecretsOperationOptions): Promise<Record<string, unknown>>;
1914
- };
1915
- type SecretsValidators = {
2360
+ phoneNumbers?: PhoneNumber[];
1916
2361
  /**
1917
- * Optional hook to validate merged source configs before they are resolved.
1918
- * Throw from this callback to reject invalid secrets source configuration.
2362
+ * Optional SCIM instant messaging addresses.
2363
+ * Commonly set by IAM inbound SCIM provisioning flows.
1919
2364
  */
1920
- validateSourceConfig?(sourceName: string, config: SecretsSourceConfig): void;
1921
- };
1922
- type Secrets = {
1923
- /** Named secrets sources client configurations from RemoteConfig. */
1924
- config: SecretsSourceMap;
1925
- /** Returns configured secrets source names/keys. */
1926
- listSecretsSources(): string[];
1927
- /** Gets a named secrets source client. Throws when missing. */
1928
- getSecretsSource(sourceName: string): SecretsSource;
1929
- /** Reads a secret from a named secrets source client. */
1930
- getSecret<T>(sourceName: string, path: string, options?: SecretsOperationOptions): Promise<T>;
1931
- /** Reads full secret data + metadata from a named secrets source client. */
1932
- getFullSecret<T>(sourceName: string, path: string, options?: SecretsOperationOptions): Promise<Secret<T>>;
1933
- /** Writes a secret to a named secrets source client. */
1934
- putSecret(sourceName: string, path: string, value: Record<string, unknown>, options?: SecretsOperationOptions): Promise<void>;
1935
- /** Deletes a secret from a named secrets source client. */
1936
- deleteSecret(sourceName: string, path: string, options?: SecretsOperationOptions): Promise<void>;
1937
- /** Lists child paths under a base path for a named secrets source client. */
1938
- listPaths(sourceName: string, path: string, options?: SecretsOperationOptions): Promise<string[]>;
1939
- /** Returns true when a path exists for a named secrets source client. */
1940
- exists(sourceName: string, path: string, options?: SecretsOperationOptions): Promise<boolean>;
1941
- /** Requests rotation for a secret path in a named secrets source client. */
1942
- requestRotate(sourceName: string, path: string, request?: SecretLifecycleRequest, options?: SecretsOperationOptions): Promise<void>;
1943
- /** Requests revocation for a secret path in a named secrets source client. */
1944
- requestRevoke(sourceName: string, path: string, request?: SecretLifecycleRequest, options?: SecretsOperationOptions): Promise<void>;
1945
- /** Reads metadata for a secret path in a named secrets source client. */
1946
- getMetadata(sourceName: string, path: string, options?: SecretsOperationOptions): Promise<Record<string, unknown>>;
1947
- /** Subscribes to secret changes on a named secrets source client. */
1948
- subscribe<T>(sourceName: string, path: string, onChange: (fullSecret: Secret<T>) => void): () => void;
1949
- /** Returns true when request matches any configured LFV delivery path. */
1950
- isLfvDeliveryRequest?(request: Request): boolean;
1951
- /** Returns true when request matches any configured LFV events path. */
1952
- isLfvEventsRequest?(request: Request): boolean;
1953
- /** Handles LFV delivery callbacks for configured LFV sources. */
1954
- handleLfvDelivery?(request: Request): Promise<Response>;
1955
- /** Handles LFV events callbacks for configured LFV sources. */
1956
- handleLfvEvents?(request: Request): Promise<Response>;
1957
- };
1958
- /**
1959
- * Partial secrets source config used in framework/app code to declare expected source names.
1960
- * ConfigSource-backed values may still provide the actual source details at runtime.
1961
- */
1962
- type FrameworkSecretsSourceConfig = Partial<SecretsSourceConfig>;
1963
- /**
1964
- * Framework-level named secrets source declarations keyed by source name.
1965
- * Values may be partial or empty when the app only wants to declare expected names/types.
1966
- */
1967
- type FrameworkSecretsModuleConfig = Record<string, FrameworkSecretsSourceConfig>;
1968
- /**
1969
- * TODO: Let's see if we can do some clean inference and remove this!!!
1970
- */
1971
- type SecretsSourceMap = Record<string, SecretsSource>;
1972
- type SecretsSourceConfig = DevSecretsConfig | GcpSecretsConfig | VaultSecretsConfig | AwsSecretsConfig | AzureSecretsConfig;
1973
- /**
1974
- * Raw module config keyed by source name.
1975
- * The secrets module resolves this into a runtime SecretsSourceMap.
1976
- */
1977
- type SecretsModuleConfig = Record<string, SecretsSourceConfig>;
1978
- type DevSecretsConfig = {
1979
- type: "dev";
1980
- ioniteUrl: string;
1981
- };
1982
- type GcpSecretsConfig = {
1983
- type: "gcp";
1984
- };
1985
- type VaultLfvSecretsConfig = {
1986
- /** LFV server base URL for OTP/action endpoints. */
1987
- lfvServerUrl?: string;
1988
- /** LFV client id used for OTP issuance. */
1989
- clientId?: string;
1990
- /** Signature value for X-LFV-Signature header. */
1991
- signature?: string;
1992
- deliveryEndpoint: string;
2365
+ ims?: Array<{
2366
+ value: string;
2367
+ display?: string;
2368
+ type?: string;
2369
+ primary?: boolean;
2370
+ }>;
1993
2371
  /**
1994
- * Optional LFV signature verification key.
1995
- * When omitted, LFV callbacks are accepted without signature verification.
2372
+ * Optional SCIM photos.
2373
+ * Commonly set by IAM inbound SCIM provisioning flows.
1996
2374
  */
1997
- verifyPublicKey?: string;
1998
- eventsEndpoint: string;
1999
- path?: string;
2375
+ photos?: Photo[];
2000
2376
  /**
2001
- * Timeout in milliseconds when waiting for a LFV delivery payload after a read request.
2002
- * After this duration, an error is thrown.
2377
+ * Optional SCIM addresses.
2378
+ * Commonly set by IAM inbound SCIM provisioning flows.
2003
2379
  */
2004
- deliveryTimeout?: number;
2005
- /** Retry interval in milliseconds between LFV transport retry attempts. */
2006
- retryInterval?: number;
2007
- /** Warning interval in milliseconds for LFV retry logs. Set to 0 to disable warnings. */
2008
- warnInterval?: number;
2380
+ addresses?: Address[];
2009
2381
  /**
2010
- * Optional logger for request/response tracing. Use `debugLogger` from `@enterprisestandard/core`
2011
- * to get debug output with request_id for LFV operations.
2382
+ * Optional SCIM roles.
2383
+ * Commonly set by IAM inbound SCIM provisioning flows.
2384
+ */
2385
+ roles?: Role[];
2386
+ /**
2387
+ * Optional SCIM groups.
2388
+ * Commonly set by IAM inbound SCIM provisioning flows.
2012
2389
  */
2013
- logger?: Logger;
2390
+ groups?: Group[];
2391
+ /**
2392
+ * Optional SCIM entitlements.
2393
+ * Commonly set by IAM inbound SCIM provisioning flows.
2394
+ */
2395
+ entitlements?: Array<{
2396
+ value: string;
2397
+ display?: string;
2398
+ type?: string;
2399
+ primary?: boolean;
2400
+ }>;
2401
+ /**
2402
+ * Optional SCIM X.509 certificates.
2403
+ * Commonly set by IAM inbound SCIM provisioning flows.
2404
+ */
2405
+ x509Certificates?: X509Certificate[];
2406
+ /**
2407
+ * Optional SCIM enterprise extension.
2408
+ * Mirrors `urn:ietf:params:scim:schemas:extension:enterprise:2.0:User`.
2409
+ * Commonly set by IAM inbound SCIM provisioning flows.
2410
+ */
2411
+ scimEnterprise?: EnterpriseExtension;
2412
+ /**
2413
+ * Optional pass-through SCIM schema extensions keyed by full URN.
2414
+ * Use this when your validator accepts custom top-level SCIM extensions and
2415
+ * you want them to round-trip through a `UserStore` implementation.
2416
+ */
2417
+ scimSchemaExtensions?: Record<string, unknown>;
2418
+ /**
2419
+ * Timestamp when the user was first stored.
2420
+ */
2421
+ createdAt?: Date;
2422
+ /**
2423
+ * Timestamp when the user was last updated (e.g., on re-login).
2424
+ */
2425
+ updatedAt?: Date;
2426
+ /**
2427
+ * Optional SSO envelope for stores that persist full auth profile data.
2428
+ * Simple app stores MAY omit this field.
2429
+ */
2430
+ sso?: User2["sso"];
2431
+ } & TExtended;
2432
+ type UserStoreOptions = {
2433
+ tenantId: string;
2434
+ tenants: TenantStore;
2014
2435
  };
2015
2436
  /**
2016
- * Runtime-ready LFV source config.
2017
- * Input config can be partially declared/merged, but LFV operations require these fields.
2437
+ * Abstract interface for user storage backends.
2438
+ *
2439
+ * Consumers can implement this interface to use different storage backends:
2440
+ * - In-memory (for development/testing)
2441
+ * - Redis (for production with fast lookups)
2442
+ * - Database (PostgreSQL, MySQL, etc.)
2443
+ *
2444
+ * @template TExtended - Type-safe custom data that consumers can add to users
2445
+ *
2446
+ * @example
2447
+ * ```typescript
2448
+ * // Custom user data
2449
+ * type MyUserData = {
2450
+ * department: string;
2451
+ * roles: string[];
2452
+ * };
2453
+ *
2454
+ * // Implement custom store
2455
+ * class RedisUserStore implements UserStore<MyUserData> {
2456
+ * async get(sub: string): Promise<StoredUser<MyUserData> | null> {
2457
+ * const data = await redis.get(`user:${sub}`);
2458
+ * return data ? JSON.parse(data) : null;
2459
+ * }
2460
+ * // ... other methods
2461
+ * }
2462
+ * ```
2018
2463
  */
2019
- type ResolvedVaultLfvSecretsConfig = Omit<VaultLfvSecretsConfig, "lfvServerUrl" | "clientId" | "path"> & {
2020
- lfvServerUrl: string;
2021
- clientId: string;
2022
- path: string;
2023
- };
2024
- type VaultWebSocketAuthHeader = "X-Vault-Token" | "Authorization";
2025
- type VaultWebSocketSecretsConfig = {
2026
- /** Websocket URL for vault command execution and live secret subscriptions. */
2027
- url?: string;
2028
- /** Token used during websocket connect/auth. */
2029
- token?: string;
2030
- /** Header name used to send the websocket token. Defaults to X-Vault-Token. */
2031
- header?: VaultWebSocketAuthHeader;
2032
- };
2033
- type VaultSecretsConfig = {
2034
- type: "vault";
2035
- url?: string;
2036
- token?: string;
2037
- /** Optional LFV transport capability for reads/lifecycle operations. */
2038
- lfv?: VaultLfvSecretsConfig;
2039
- /** Optional websocket capability for vault commands and live subscriptions. */
2040
- websocket?: VaultWebSocketSecretsConfig;
2464
+ interface UserStore<TExtended = object> {
2041
2465
  /**
2042
- * MINIMUM: 600_000 milliseconds (10 minutes). Polls the path every ttl milliseconds and calls onConfig when config changes.
2466
+ * Retrieve a user by their subject identifier (sub).
2467
+ *
2468
+ * This is the canonical lookup used by SDK flows whenever possible.
2469
+ * Other lookup methods (userName) are secondary convenience indexes.
2470
+ *
2471
+ * @param sub - The user's unique identifier from the IdP
2472
+ * @returns The user if found, undefined otherwise
2043
2473
  */
2044
- ttl?: number;
2045
- };
2046
- type AwsSecretsConfig = {
2047
- type: "aws";
2048
- webhookUrl: string;
2049
- };
2050
- type AwsAuthMethod = "client_secret" | "workload_identity" | "managed_identity";
2051
- type AzureSecretsConfig = {
2052
- type: "azure";
2474
+ get(sub: string): Promise<StoredUser<TExtended> | undefined>;
2053
2475
  /**
2054
- * How to authenticate to Azure to fetch Key Vault access tokens.
2476
+ * Retrieve a user by their username.
2055
2477
  *
2056
- * - client_secret: Entra app client secret (client credentials)
2057
- * - workload_identity: AKS Workload Identity (federated token file)
2058
- * - managed_identity: Azure Managed Identity via IMDS
2478
+ * @param userName - The user's username
2479
+ * @returns The user if found, undefined otherwise
2480
+ */
2481
+ getByUserName(userName: string): Promise<StoredUser<TExtended> | undefined>;
2482
+ /**
2483
+ * Create or update a user in the store.
2059
2484
  *
2060
- * If omitted, we auto-detect:
2061
- * - workload_identity if a federated token file is present
2062
- * - else client_secret if a clientSecret is present
2063
- * - else managed_identity
2485
+ * If a user with the same `id` (sub) exists, it will be updated.
2486
+ * Otherwise, a new user will be created.
2064
2487
  *
2065
- * Env: ES_AZURE_AUTH_METHOD
2488
+ * @param user - The user data to store
2066
2489
  */
2067
- authMethod?: AwsAuthMethod;
2490
+ upsert(user: StoredUser<TExtended>): Promise<StoredUser<TExtended>>;
2068
2491
  /**
2069
- * Azure Entra tenant id (GUID), used for OAuth2 token exchange.
2070
- * Env: ES_AZURE_TENANT_ID
2492
+ * Delete a user by their subject identifier (sub).
2493
+ *
2494
+ * @param sub - The user's unique identifier to delete
2071
2495
  */
2072
- tenantId?: string;
2496
+ delete(sub: string): Promise<number>;
2497
+ /**
2498
+ * List users in the store with optional pagination and sort.
2499
+ *
2500
+ * @param options - Optional start (0-based), limit (page size), and sort
2501
+ * @returns ListResult with total, count, items, size, page, pages
2502
+ */
2503
+ list(options?: UserListOptions): Promise<ListResult<StoredUser<TExtended>>>;
2504
+ }
2505
+ /**
2506
+ * SCIM Error response structure
2507
+ */
2508
+ interface ScimError {
2509
+ schemas: string[];
2510
+ status: string;
2511
+ scimType?: string;
2512
+ detail?: string;
2513
+ }
2514
+ /**
2515
+ * SCIM List Response for bulk operations
2516
+ */
2517
+ interface ScimListResponse<T> {
2518
+ schemas: string[];
2519
+ totalResults: number;
2520
+ startIndex?: number;
2521
+ itemsPerPage?: number;
2522
+ Resources: T[];
2523
+ }
2524
+ /**
2525
+ * Result of a SCIM operation
2526
+ */
2527
+ interface ScimResult<T> {
2528
+ success: boolean;
2529
+ data?: T;
2530
+ error?: ScimError;
2531
+ status: number;
2532
+ }
2533
+ /**
2534
+ * Handler configuration for IAM
2535
+ */
2536
+ interface IAMHandlerConfig {
2073
2537
  /**
2074
- * Azure app registration client id (GUID).
2075
- * Env: ES_AZURE_CLIENT_ID
2538
+ * Base path for the SCIM Users endpoints (e.g., '/api/iam/Users')
2076
2539
  */
2077
- clientId?: string;
2540
+ usersUrl?: string;
2078
2541
  /**
2079
- * Azure app registration client secret.
2080
- * Env: ES_AZURE_CLIENT_SECRET
2542
+ * Base path for the SCIM Groups endpoints (e.g., '/api/iam/Groups')
2081
2543
  */
2082
- clientSecret?: string;
2544
+ groupsUrl?: string;
2083
2545
  /**
2084
- * Path to the projected federated token file (AKS Workload Identity).
2085
- * Env: ES_AZURE_FEDERATED_TOKEN_FILE or AZURE_FEDERATED_TOKEN_FILE
2546
+ * Handler overrides for SCIM discovery endpoints (e.g., '/api/iam/ServiceProviderConfig')
2086
2547
  */
2087
- federatedTokenFile?: string;
2548
+ discovery?: IAMDiscoveryHandlerConfig;
2549
+ }
2550
+ /**
2551
+ * IAM configuration
2552
+ *
2553
+ * - If `url` is provided, groups_outbound is enabled (app calls external IAM)
2554
+ * - If `groupStore` is provided, groups_inbound is enabled (external IAM calls app)
2555
+ * - If `userStore` is provided, users_inbound is enabled (external IAM calls app)
2556
+ */
2557
+ type IAMConfig = {
2088
2558
  /**
2089
- * Managed Identity client id (user-assigned), optional.
2090
- * Env: ES_AZURE_MANAGED_IDENTITY_CLIENT_ID
2559
+ * Base URL of the external SCIM endpoint (e.g., https://sailpoint.example.com/scim/v2)
2560
+ * If provided, enables outbound SCIM operations (app -> external IAM)
2091
2561
  */
2092
- managedIdentityClientId?: string;
2562
+ url?: string;
2093
2563
  /**
2094
- * IMDS API version (managed identity).
2095
- * Env: ES_AZURE_IMDS_API_VERSION
2096
- * Default: 2018-02-01
2564
+ * Store for inbound user provisioning from external IAM providers.
2565
+ * When configured, the app can receive user CRUD operations via SCIM.
2097
2566
  */
2098
- imdsApiVersion?: string;
2567
+ userStore?: UserStore;
2099
2568
  /**
2100
- * Key Vault URL, e.g. https://myvault.vault.azure.net
2101
- * Env: ES_AZURE_KEY_VAULT_URL
2569
+ * Optional inbound user mapping hooks for SCIM provisioning.
2570
+ * Use these when the default StoredUser <-> SCIM mapping is not a direct fit
2571
+ * for your application's database model.
2102
2572
  */
2103
- vaultUrl?: string;
2573
+ inboundUsers?: IAMInboundUsersConfig;
2104
2574
  /**
2105
- * Alternative to vaultUrl. If provided, vaultUrl is derived as:
2106
- * https://${vaultName}.vault.azure.net
2107
- * Env: ES_AZURE_KEY_VAULT_NAME
2575
+ * Store for inbound group provisioning from external IAM providers.
2576
+ * When configured, enables groups_inbound (external IAM -> app).
2108
2577
  */
2109
- vaultName?: string;
2578
+ groupStore?: GroupStore;
2110
2579
  /**
2111
- * Key Vault API version.
2112
- * Env: ES_AZURE_KEY_VAULT_API_VERSION
2113
- * Default: 7.4
2580
+ * Optional handler defaults. These are merged with per-call overrides in
2581
+ * `iam.handler`, with per-call values taking precedence.
2114
2582
  */
2115
- apiVersion?: string;
2583
+ usersUrl?: string;
2584
+ groupsUrl?: string;
2585
+ discovery?: IAMDiscoveryConfig;
2586
+ };
2587
+ type IAMValidators = {
2588
+ user: StandardSchemaV17<unknown, User>;
2589
+ group: StandardSchemaV17<unknown, GroupResource>;
2590
+ };
2591
+ interface IAMInboundUserContext {
2116
2592
  /**
2117
- * Maps vault "path" to a Key Vault secret name.
2118
- * Default: replaces `/` with `--` and trims leading/trailing `/`.
2593
+ * Current stored user when replacing or patching an existing resource.
2119
2594
  */
2120
- secretNameTransform?: (path: string) => string;
2595
+ existing?: StoredUser;
2121
2596
  /**
2122
- * Optional prefix added to all computed secret names.
2123
- * Useful to namespace secrets by app/environment.
2597
+ * Operation mode for the inbound mapper.
2124
2598
  */
2125
- secretNamePrefix?: string;
2599
+ mode: "create" | "replace" | "patch";
2600
+ }
2601
+ interface IAMInboundUsersConfig {
2126
2602
  /**
2127
- * OAuth2 scope; default is Key Vault resource scope.
2128
- * Default: https://vault.azure.net/.default
2603
+ * Replace the default validated SCIM -> StoredUser mapper.
2129
2604
  */
2130
- scope?: string;
2605
+ mapValidatedScimToStoredUser?: (validated: User, context: IAMInboundUserContext) => StoredUser | Promise<StoredUser>;
2131
2606
  /**
2132
- * When set, the vault implements subscribe: poll this path every ttl seconds and call onConfig when config changes.
2607
+ * Replace the default StoredUser -> SCIM response mapper.
2133
2608
  */
2134
- ttl?: number;
2135
- };
2136
- type EnvironmentType = "POC" | "DEV" | "QA" | "PROD";
2137
- type TenantStatus = "pending" | "processing" | "completed" | "failed" | "action_required";
2138
- interface UpsertTenantRequest {
2139
- tenantId: string;
2140
- companyId: string;
2141
- companyName: string;
2142
- environmentType: EnvironmentType;
2143
- email?: string;
2144
- webhookUrl?: string;
2145
- callbackUrl?: string;
2146
- configSource: TenantSecretsConfig;
2147
- }
2148
- type UpsertTenantResponse = {
2149
- tenantUrl?: string;
2150
- status: Exclude<TenantStatus, "action_required">;
2151
- error?: string;
2152
- refs?: RefUrls[];
2153
- } | {
2154
- status: "action_required";
2155
- actionUrl: string;
2156
- requestToken: string;
2157
- expiresAt: string;
2158
- refs?: RefUrls[];
2159
- };
2160
- type CreateTenantRequest = UpsertTenantRequest;
2161
- type CreateTenantResponse = UpsertTenantResponse;
2162
- /**
2163
- * The audience of the reference URL.
2164
- * - 'human' for human-readable documentation such as user guides, documentation, etc.
2165
- * - 'ai' for AI-generated reference such as llms.txt, AI-optimized markdown, etc.
2166
- * - 'spec' for specifications and code-gen ready docs such as OpenAPI, GraphQL, etc.
2167
- */
2168
- type Audience = "human" | "ai" | "spec";
2169
- interface RefUrls {
2170
- audience: Audience;
2171
- url: string;
2172
- description: string;
2173
- createdAt: Date;
2174
- updatedAt: Date;
2609
+ mapStoredUserToScim?: (stored: StoredUser) => User | Promise<User>;
2175
2610
  }
2176
- interface TenantWebhookPayload {
2177
- tenantId: string;
2178
- companyId: string;
2179
- status: TenantStatus;
2180
- tenantUrl?: string;
2181
- actionUrl?: string;
2182
- requestToken?: string;
2183
- expiresAt?: string;
2184
- error?: string;
2611
+ interface ScimAuthenticationScheme {
2612
+ type: string;
2613
+ name: string;
2614
+ description?: string;
2615
+ specUri?: string;
2616
+ documentationUri?: string;
2617
+ primary?: boolean;
2185
2618
  }
2186
- declare class TenantRequestError extends Error {
2187
- constructor(message: string, options?: ErrorOptions);
2619
+ interface ScimServiceProviderConfig {
2620
+ schemas: string[];
2621
+ documentationUri?: string;
2622
+ patch: {
2623
+ supported: boolean;
2624
+ };
2625
+ bulk: {
2626
+ supported: boolean;
2627
+ maxOperations?: number;
2628
+ maxPayloadSize?: number;
2629
+ };
2630
+ filter: {
2631
+ supported: boolean;
2632
+ maxResults?: number;
2633
+ };
2634
+ changePassword: {
2635
+ supported: boolean;
2636
+ };
2637
+ sort: {
2638
+ supported: boolean;
2639
+ };
2640
+ etag: {
2641
+ supported: boolean;
2642
+ };
2643
+ authenticationSchemes?: ScimAuthenticationScheme[];
2644
+ meta?: {
2645
+ resourceType?: string;
2646
+ location?: string;
2647
+ };
2188
2648
  }
2189
- declare class MultipleTenantsForUserError extends Error {
2190
- readonly userId: string;
2191
- readonly tenantIds: string[];
2192
- constructor(userId: string, tenantIds: string[], options?: ErrorOptions);
2649
+ interface ScimResourceTypeSchemaExtension {
2650
+ schema: string;
2651
+ required: boolean;
2193
2652
  }
2194
- type UserMode = "singleTenantOnly" | "multipleTenantsPerUser";
2195
- type TenantValidators = {
2196
- upsertTenantRequest: StandardSchemaV17<unknown, UpsertTenantRequest>;
2197
- upsertTenantResponse?: StandardSchemaV17<unknown, UpsertTenantResponse>;
2198
- createTenantRequest?: StandardSchemaV17<unknown, UpsertTenantRequest>;
2199
- createTenantResponse?: StandardSchemaV17<unknown, UpsertTenantResponse>;
2200
- };
2201
- /**
2202
- * Env-like tenant config variables used to build a ConfigSource at runtime.
2203
- * These mirror the ES_* variables read by envConfig().
2204
- */
2205
- type TenantConfigEnv = {
2206
- ES_CONFIG_TYPE?: ConfigSourceType;
2207
- ES_VAULT_URL?: string;
2208
- ES_VAULT_TOKEN?: string;
2209
- ES_VAULT_PATH?: string;
2210
- ES_VAULT_TTL?: string;
2211
- ES_VAULT_LFV_SERVER_URL?: string;
2212
- ES_VAULT_LFV_CLIENT_ID?: string;
2213
- ES_VAULT_LFV_SIGNATURE?: string;
2214
- ES_VAULT_LFV_DELIVERY_ENDPOINT?: string;
2215
- ES_VAULT_LFV_VERIFY_PUBLIC_KEY?: string;
2216
- ES_VAULT_LFV_EVENTS_ENDPOINT?: string;
2217
- ES_VAULT_LFV_DELIVERY_TIMEOUT?: string;
2218
- ES_VAULT_LFV_RETRY_INTERVAL?: string;
2219
- ES_VAULT_LFV_WARN_INTERVAL?: string;
2220
- ES_VAULT_WEBSOCKET_URL?: string;
2221
- ES_VAULT_WEBSOCKET_TOKEN?: string;
2222
- ES_VAULT_WEBSOCKET_HEADER?: "X-Vault-Token" | "Authorization";
2223
- ES_AZURE_API_VERSION?: string;
2224
- ES_AZURE_SCOPE?: string;
2225
- ES_AZURE_SECRET_NAME_PREFIX?: string;
2226
- ES_AZURE_AUTH_METHOD?: AwsAuthMethod;
2227
- ES_AZURE_TENANT_ID?: string;
2228
- ES_AZURE_CLIENT_ID?: string;
2229
- ES_AZURE_CLIENT_SECRET?: string;
2230
- ES_AZURE_FEDERATED_TOKEN_FILE?: string;
2231
- ES_AZURE_MANAGED_IDENTITY_CLIENT_ID?: string;
2232
- ES_AZURE_IMDS_API_VERSION?: string;
2233
- ES_AZURE_VAULT_URL?: string;
2234
- ES_AZURE_VAULT_NAME?: string;
2235
- ES_AZURE_TTL?: string;
2236
- ES_AWS_WEBHOOK_URL?: string;
2237
- ES_AWS_TTL?: string;
2238
- ES_GCP_TTL?: string;
2239
- };
2240
- type TenantSecretsConfig = (VaultSecretsConfig & {
2241
- path: string;
2242
- retryInterval?: number;
2243
- }) | (AwsSecretsConfig & {
2244
- ttl?: number;
2245
- }) | AzureSecretsConfig | (GcpSecretsConfig & {
2246
- ttl?: number;
2247
- });
2248
- type TenantStoredConfigLocator = {
2249
- /** Indicates that the tenant config descriptor is stored securely outside the tenant record. */
2250
- type: "stored";
2251
- /** Root secure source type used to fetch the stored tenant config descriptor. */
2252
- sourceType: "vault";
2253
- /** Path to the stored tenant config descriptor. */
2254
- path: string;
2255
- };
2256
- type TenantRemoteConfigLocator = {
2257
- /** Indicates that the tenant RemoteConfig already exists at this secure source path. */
2258
- type: "remoteConfig";
2259
- /** Secure source type used to load the RemoteConfig document directly. */
2260
- sourceType: "vault";
2261
- /** Path to the tenant RemoteConfig document. */
2262
- path: string;
2263
- };
2264
- type TenantConfigLocator = TenantStoredConfigLocator | TenantRemoteConfigLocator;
2265
- type TenantConfigSourceInput = TenantConfigLocator | ConfigSource;
2266
- type TenantBaseRecord = {
2267
- tenantId: string;
2268
- companyId: string;
2269
- companyName: string;
2270
- environmentType: EnvironmentType;
2271
- email?: string;
2272
- webhookUrl?: string;
2273
- callbackUrl?: string;
2274
- tenantUrl?: string;
2275
- status: TenantStatus;
2276
- error?: string;
2277
- actionUrl?: string;
2278
- requestToken?: string;
2279
- expiresAt?: string;
2280
- createdAt: Date;
2281
- updatedAt: Date;
2282
- /** Persisted tenant config metadata, or a runtime ConfigSource for internal-only tenants. */
2283
- configSource: TenantConfigSourceInput;
2284
- /** Runtime helper that returns a ConfigSource for this tenant. */
2285
- config: (source?: SecretsSource) => ConfigSource;
2286
- };
2287
- type StoredTenant<TExtended extends object = Record<string, never>> = TenantBaseRecord & TExtended;
2288
- type StoredTenantRecord<TExtended extends object = Record<string, never>> = Omit<StoredTenant<TExtended>, "config">;
2289
- type TenantEsFactory<TExtended extends object = Record<string, never>> = (tenant: StoredTenant<TExtended>) => EnterpriseStandard;
2290
- type TenantConfigStoreRequest<TExtended extends object = Record<string, never>> = {
2291
- es: EnterpriseStandard;
2292
- tenantId: string;
2293
- request: UpsertTenantRequest;
2294
- configData: TenantSecretsConfig;
2295
- existingTenant: StoredTenant<TExtended> | null;
2296
- };
2297
- type TenantStoreWithESOptions<TExtended extends object = Record<string, never>> = {
2653
+ interface ScimResourceType {
2654
+ schemas: string[];
2655
+ id: string;
2656
+ name: string;
2657
+ description?: string;
2658
+ endpoint: string;
2659
+ schema: string;
2660
+ schemaExtensions?: ScimResourceTypeSchemaExtension[];
2661
+ meta?: {
2662
+ resourceType?: string;
2663
+ location?: string;
2664
+ };
2665
+ }
2666
+ interface ScimSchemaAttributeDefinition {
2667
+ name: string;
2668
+ type: "string" | "boolean" | "complex" | "reference" | "dateTime";
2669
+ multiValued: boolean;
2670
+ description?: string;
2671
+ required?: boolean;
2672
+ caseExact?: boolean;
2673
+ mutability?: "readOnly" | "readWrite" | "immutable" | "writeOnly";
2674
+ returned?: "always" | "never" | "default" | "request";
2675
+ uniqueness?: "none" | "server" | "global";
2676
+ referenceTypes?: string[];
2677
+ subAttributes?: ScimSchemaAttributeDefinition[];
2678
+ }
2679
+ interface ScimSchemaDefinition {
2680
+ schemas: string[];
2681
+ id: string;
2682
+ name: string;
2683
+ description?: string;
2684
+ attributes: ScimSchemaAttributeDefinition[];
2685
+ meta?: {
2686
+ resourceType?: string;
2687
+ location?: string;
2688
+ };
2689
+ }
2690
+ interface IAMDiscoveryContext {
2691
+ request: Request;
2692
+ basePath: string;
2693
+ usersUrl: string;
2694
+ groupsUrl: string;
2695
+ supportsUsers: boolean;
2696
+ supportsGroups: boolean;
2697
+ }
2698
+ interface IAMDiscoveryConfig {
2298
2699
  /**
2299
- * TTL for cached per-tenant EnterpriseStandard instances, in milliseconds.
2300
- * Default is forever; set to 0 to recreate ES on every getEs() call.
2700
+ * Public IAM base path used for SCIM discovery. When omitted, the SDK derives
2701
+ * it from `usersUrl` or `groupsUrl`.
2301
2702
  */
2302
- ttl?: number;
2703
+ basePath?: string;
2303
2704
  /**
2304
- * Optional factory used to create an ES instance for a tenant.
2305
- * If omitted, getEs() throws.
2705
+ * Optional documentation URI advertised in ServiceProviderConfig.
2306
2706
  */
2307
- createEs?: TenantEsFactory<TExtended>;
2308
- };
2309
- type TenantMetadataRecord = Omit<TenantBaseRecord, "tenantId" | "config" | "configSource" | "status" | "createdAt" | "updatedAt">;
2310
- type TenantExtendedUpsertFields<TExtended extends object> = string extends keyof TExtended ? unknown : Partial<TExtended>;
2311
- type TenantStoreUpsertRecord<TExtended extends object = Record<string, never>> = Pick<TenantBaseRecord, "tenantId" | "configSource"> & Partial<TenantMetadataRecord> & Partial<Pick<TenantBaseRecord, "status" | "createdAt" | "updatedAt">> & TenantExtendedUpsertFields<TExtended>;
2312
- type TenantUserRegistration<TMode extends UserMode = UserMode> = {
2313
- userMode: TMode;
2314
- registerUserTenantId?(userId: string, tenantId: string | null | undefined): void | Promise<void>;
2315
- };
2316
- type TenantStoreBase<
2317
- TMode extends UserMode = "singleTenantOnly",
2318
- TExtended extends object = Record<string, never>
2319
- > = TenantUserRegistration<TMode> & {
2320
- storeConfig(config: TenantConfigStoreRequest<TExtended>): Promise<TenantConfigSourceInput>;
2321
- get(tenantId: string): Promise<StoredTenant<TExtended> | null>;
2322
- list(options?: TenantListOptions): Promise<ListResult<StoredTenant<TExtended>>>;
2323
- upsert(tenant: TenantStoreUpsertRecord<TExtended>): Promise<StoredTenant<TExtended>>;
2324
- delete(tenantId: string): Promise<void>;
2325
- };
2326
- type TenantLookupMethods<
2327
- TMode extends UserMode,
2328
- TExtended extends object
2329
- > = TMode extends "singleTenantOnly" ? {
2330
- findTenantByUserId(userId: string): Promise<StoredTenant<TExtended> | null>;
2331
- findTenantsByUserId?: never;
2332
- } : {
2333
- findTenantByUserId?: never;
2334
- findTenantsByUserId(userId: string): Promise<StoredTenant<TExtended>[]>;
2335
- };
2336
- type TenantStore<
2337
- TMode extends UserMode = "singleTenantOnly",
2338
- TExtended extends object = Record<string, never>
2339
- > = TenantStoreBase<TMode, TExtended> & TenantLookupMethods<TMode, TExtended>;
2340
- type TenantStoreWithES<
2341
- TMode extends UserMode = "singleTenantOnly",
2342
- TExtended extends object = Record<string, never>
2343
- > = TenantStore<TMode, TExtended> & {
2344
- ttl: number;
2345
- getEs(tenantId: string): Promise<EnterpriseStandard | null>;
2346
- getCachedTenantIds(): string[];
2347
- };
2348
- type InMemoryTenantStoreOptions<
2349
- TMode extends UserMode = "singleTenantOnly",
2350
- TExtended extends object = Record<string, never>
2351
- > = TenantStoreWithESOptions<TExtended> & {
2352
- userMode: TMode;
2353
- };
2354
- declare class InMemoryTenantStore<
2355
- TMode extends UserMode = "singleTenantOnly",
2356
- TExtended extends object = Record<string, never>
2357
- > {
2358
- private tenants;
2359
- private tenantEsMap;
2360
- private userTenantIds;
2361
- readonly ttl: number;
2362
- readonly userMode: TMode;
2363
- private readonly createEs?;
2364
- readonly findTenantByUserId: TMode extends "singleTenantOnly" ? (userId: string) => Promise<StoredTenant<TExtended> | null> : never;
2365
- readonly findTenantsByUserId: TMode extends "multipleTenantsPerUser" ? (userId: string) => Promise<StoredTenant<TExtended>[]> : never;
2366
- constructor(options: InMemoryTenantStoreOptions<TMode, TExtended>);
2367
- get(tenantId: string): Promise<StoredTenant<TExtended> | null>;
2368
- list(options?: TenantListOptions): Promise<ListResult<StoredTenant<TExtended>>>;
2369
- upsert(tenant: TenantStoreUpsertRecord<TExtended>): Promise<StoredTenant<TExtended>>;
2370
- delete(tenantId: string): Promise<void>;
2371
- getEs(tenantId: string): Promise<EnterpriseStandard>;
2372
- getCachedTenantIds(): string[];
2373
- registerUserTenantId(userId: string, tenantId: string | null | undefined): Promise<void>;
2374
- private findSingleTenantByUserId;
2375
- private findMultipleTenantsByUserId;
2376
- private resolveTenantsByUserId;
2377
- }
2378
- declare function sendTenantWebhook(webhookUrl: string, payload: TenantWebhookPayload, log: Logger): Promise<void>;
2379
- /**
2380
- * Magic link data stored in the store.
2381
- *
2382
- * @template TExtended - Type-safe custom data that consumers can add to magic links
2383
- */
2384
- type MagicLink<TExtended = object> = {
2707
+ documentationUri?: string;
2385
2708
  /**
2386
- * The magic link token (unique identifier)
2709
+ * Override the default ServiceProviderConfig response.
2387
2710
  */
2388
- token: string;
2711
+ buildServiceProviderConfig?: (context: IAMDiscoveryContext, defaults: ScimServiceProviderConfig) => ScimServiceProviderConfig | Promise<ScimServiceProviderConfig>;
2389
2712
  /**
2390
- * User information associated with this magic link
2713
+ * Override the default ResourceTypes response.
2391
2714
  */
2392
- user: BaseUser;
2715
+ buildResourceTypes?: (context: IAMDiscoveryContext, defaults: ScimResourceType[]) => ScimResourceType[] | Promise<ScimResourceType[]>;
2393
2716
  /**
2394
- * Timestamp when the magic link was created
2717
+ * Override the default Schemas response.
2395
2718
  */
2396
- createdAt: Date;
2719
+ buildSchemas?: (context: IAMDiscoveryContext, defaults: ScimSchemaDefinition[]) => ScimSchemaDefinition[] | Promise<ScimSchemaDefinition[]>;
2720
+ }
2721
+ /**
2722
+ * Options for creating a user
2723
+ */
2724
+ interface CreateUserOptions {
2397
2725
  /**
2398
- * Timestamp when the magic link expires
2726
+ * External identifier for the user
2399
2727
  */
2400
- expiresAt: Date;
2728
+ externalId?: string;
2729
+ }
2730
+ /**
2731
+ * Options for creating a group
2732
+ */
2733
+ interface CreateGroupOptions {
2401
2734
  /**
2402
- * Allow consumers to add runtime data to magic links
2735
+ * External identifier for the group
2403
2736
  */
2404
- [key: string]: unknown;
2405
- } & TExtended;
2737
+ externalId?: string;
2738
+ /**
2739
+ * Initial members to add to the group
2740
+ */
2741
+ members?: GroupMember[];
2742
+ }
2406
2743
  /**
2407
- * Abstract interface for magic link storage backends.
2408
- *
2409
- * Consumers can implement this interface to use different storage backends:
2410
- * - In-memory (for development/testing)
2411
- * - Redis (for production with fast lookups and automatic expiration)
2412
- * - Database (PostgreSQL, MySQL, etc.)
2413
- *
2414
- * @template TExtended - Type-safe custom data that consumers can add to magic links
2415
- *
2416
- * @example
2417
- * ```typescript
2418
- * // Custom magic link data
2419
- * type MyMagicLinkData = {
2420
- * source: string;
2421
- * metadata: Record<string, unknown>;
2422
- * };
2423
- *
2424
- * // Implement custom store
2425
- * class RedisMagicLinkStore implements MagicLinkStore<MyMagicLinkData> {
2426
- * async create(token: string, user: BaseUser, expiresAt: Date): Promise<void> {
2427
- * const magicLink: MagicLink<MyMagicLinkData> = {
2428
- * token,
2429
- * user,
2430
- * createdAt: new Date(),
2431
- * expiresAt,
2432
- * source: 'api',
2433
- * metadata: {},
2434
- * };
2435
- * const ttl = Math.floor((expiresAt.getTime() - Date.now()) / 1000);
2436
- * await redis.setex(`magic-link:${token}`, ttl, JSON.stringify(magicLink));
2437
- * }
2438
- * // ... other methods
2439
- * }
2440
- * ```
2744
+ * Handler configuration for groups_inbound
2441
2745
  */
2442
- interface MagicLinkStore<TExtended = object> {
2746
+ interface GroupsInboundHandlerConfig {
2443
2747
  /**
2444
- * Create a new magic link in the store.
2445
- *
2446
- * @param token - The magic link token (unique identifier)
2447
- * @param user - The user information to associate with this magic link
2448
- * @param expiresAt - When the magic link expires
2449
- * @throws Error if magic link with same token already exists
2748
+ * Base path for the SCIM Groups endpoints (e.g., '/api/iam/Groups')
2450
2749
  */
2451
- create(token: string, user: BaseUser, expiresAt: Date): Promise<void>;
2750
+ basePath?: string;
2751
+ }
2752
+ /**
2753
+ * Handler configuration for users_inbound
2754
+ */
2755
+ interface UsersInboundHandlerConfig {
2452
2756
  /**
2453
- * Retrieve a magic link by its token.
2454
- *
2455
- * @param token - The magic link token
2456
- * @returns The magic link if found and not expired, null otherwise
2757
+ * Base path for the SCIM Users endpoints (e.g., '/api/iam/Users')
2457
2758
  */
2458
- get(token: string): Promise<MagicLink<TExtended> | null>;
2759
+ basePath?: string;
2760
+ }
2761
+ interface IAMDiscoveryHandlerConfig {
2459
2762
  /**
2460
- * Delete a magic link by its token.
2461
- *
2462
- * Used after a magic link has been consumed (one-time use).
2463
- *
2464
- * @param token - The magic link token to delete
2763
+ * Base path for the IAM discovery endpoints (e.g., '/api/iam')
2465
2764
  */
2466
- delete(token: string): Promise<void>;
2765
+ basePath?: string;
2467
2766
  }
2468
- type ConfigSourceType = "vault" | "azure" | "aws" | "gcp";
2469
- type ESValidators = {
2470
- sso: SSOValidators;
2471
- iam: IAMValidators;
2472
- workload: WorkloadValidators;
2473
- ciam: CIAMValidators;
2474
- secrets?: SecretsValidators;
2475
- };
2476
- type ApplicationValidators = ESValidators & {
2477
- tenant: TenantValidators;
2478
- };
2479
2767
  /**
2480
- * Configuration supplied by the framework/application when creating an Enterprise Standard instance.
2481
- * Merged with RemoteConfig from the ConfigSource (framework config wins). Pass as the second
2482
- * argument to enterpriseStandard(source, config).
2483
- * Set a module to `null` to explicitly disable it; then the corresponding property on the
2484
- * EnterpriseStandard instance is typed as `never`. Omit a module to allow it to be supplied
2485
- * from ConfigSource / adaptive (typed as the module type, non-optional).
2768
+ * Groups Outbound extension - for creating groups in external IAM providers.
2769
+ * Enabled when `url` is configured in IAMConfig.
2486
2770
  */
2487
- type FrameworkConfig = {
2488
- logger?: Logger;
2489
- sso?: SSOConfig | null;
2490
- iam?: IAMConfig | null;
2491
- workload?: FrameworkWorkloadConfig | null;
2492
- secrets?: FrameworkSecretsModuleConfig | null;
2493
- ciam?: CIAMConfig | null;
2494
- validators: ESValidators;
2771
+ type IAMGroupsOutbound = {
2772
+ /**
2773
+ * Create a new group in the external IAM provider
2774
+ * @param displayName - The display name for the group
2775
+ * @param options - Optional configuration for the group creation
2776
+ * @returns The created group resource from the provider
2777
+ */
2778
+ createGroup: (displayName: string, options?: CreateGroupOptions) => Promise<ScimResult<GroupResource>>;
2495
2779
  };
2496
2780
  /**
2497
- * Final configuration after merging ConfigSource (RemoteConfig) and FrameworkConfig.
2498
- * Same shape as FrameworkConfig; each module (SSO, IAM, etc.) resolves its config from
2499
- * both sources at runtime. Use this type when referring to the effective/merged config.
2781
+ * Groups Inbound extension - for receiving group provisioning from external IAM providers.
2782
+ * Enabled when `groupStore` is configured in IAMConfig.
2500
2783
  */
2501
- type ESConfig = FrameworkConfig;
2784
+ type IAMGroupsInbound = {
2785
+ /**
2786
+ * Handle inbound SCIM requests for group management.
2787
+ * Routes: GET/POST /Groups, GET/PUT/PATCH/DELETE /Groups/:id
2788
+ */
2789
+ handler: (request: Request, config?: GroupsInboundHandlerConfig) => Promise<Response>;
2790
+ };
2502
2791
  /**
2503
- * Remote config read from a config source (vault, lfv, etc.).
2792
+ * Users Inbound extension - for receiving user provisioning from external IAM providers.
2793
+ * Enabled when `userStore` is configured in IAMConfig.
2504
2794
  */
2505
- type RemoteConfig = {
2506
- /** Optional app/tenant identifier for this ESA (e.g. from vault path or config). */
2507
- tenantId?: string;
2508
- sso?: SSOConfig;
2509
- iam?: IAMConfig;
2795
+ type IAMUsersInbound = {
2510
2796
  /**
2511
- * Workload: single config, or incoming/outgoing (server vs client roles), or flat map of named clients.
2512
- * Preferred: { incoming: { jwksUri, issuer }, outgoing: { TNT_*: { clientId, ... } } }.
2513
- * Legacy: { default: { jwksUri, issuer }, TNT_*: { ... } } (flat map).
2514
- * TODO: Let's see if we can do some clean inference here!!!
2797
+ * Handle inbound SCIM requests for user management.
2798
+ * Routes: GET/POST /Users, GET/PUT/PATCH/DELETE /Users/:id
2515
2799
  */
2516
- workload?: WorkloadConfig | WorkloadConfigMap | WorkloadIncomingOutgoing;
2517
- /** Optional named secrets-source configs available to this ESA instance. */
2518
- secrets?: SecretsModuleConfig;
2519
- ciam?: CIAMConfig;
2800
+ handler: (request: Request, config?: UsersInboundHandlerConfig) => Promise<Response>;
2520
2801
  };
2521
2802
  /**
2522
- * Stores supplied by the framework/application when creating an Enterprise Standard instance.
2803
+ * Core IAM service interface.
2804
+ *
2805
+ * - Core functions are user-related (outbound to external IAM)
2806
+ * - `groups_outbound` is available when `url` is configured
2807
+ * - `groups_inbound` is available when `groupStore` is configured
2808
+ * - `users_inbound` is available when `userStore` is configured
2523
2809
  */
2524
- type FrameworkStores = {
2525
- sessionStore?: SessionStore<unknown>;
2526
- userStore?: UserStore<unknown>;
2527
- groupStore?: GroupStore<unknown>;
2528
- magicLinkStore?: MagicLinkStore<unknown>;
2529
- workloadTokenStore?: WorkloadTokenStore;
2530
- };
2531
- type ModifiableFrameworkConfig = FrameworkConfig & {
2532
- setStores(stores: FrameworkStores): void;
2533
- };
2534
- /** Return type from the beforeChange hook passed to enterpriseStandard(). */
2535
- type ESConfigChangeResult = {
2536
- config?: RemoteConfig;
2537
- frameworkConfig?: FrameworkConfig;
2538
- };
2539
- /** beforeChange callback invoked on every config application (initial load and updates). */
2540
- type ESConfigChangeCallback = (config: RemoteConfig, frameworkConfig: ModifiableFrameworkConfig, oldConfig: RemoteConfig | undefined) => ESConfigChangeResult | void;
2541
- type ConfigSource = {
2542
- load(): Promise<RemoteConfig>;
2810
+ type IAM = IAMConfig & {
2543
2811
  /**
2544
- * Called when the config changes.
2545
- * @param onConfig - The callback to call when the config changes.
2546
- * @return an unsubscribe/cleanup function.
2812
+ * Create a new user/account in the external IAM provider
2813
+ * Only available when `url` is configured.
2547
2814
  */
2548
- subscribe(onConfig: (config: RemoteConfig) => void): undefined | (() => void);
2815
+ createUser?: (user: User, options?: CreateUserOptions) => Promise<ScimResult<User>>;
2549
2816
  /**
2550
- * Default secret client for the config source itself.
2551
- * For vault-backed sources this is the vault used to read RemoteConfig.
2817
+ * Get the configured external SCIM base URL
2552
2818
  */
2553
- secret: SecretsSource;
2819
+ getBaseUrl: () => string | undefined;
2554
2820
  /**
2555
- * Optional. If not set by the creator, the framework may set this before calling load/subscribe
2556
- * so the source can use the same logger.
2821
+ * Groups Outbound extension - create groups in external IAM provider.
2822
+ * Available when `url` is configured in IAMConfig.
2557
2823
  */
2558
- logger?: Logger;
2824
+ groups_outbound?: IAMGroupsOutbound;
2559
2825
  /**
2560
- * Optional. If not set by the creator, the framework may set this before calling load/subscribe
2561
- * so the source can use the same validators.
2826
+ * Groups Inbound extension - receive group provisioning from external IAM.
2827
+ * Available when `groupStore` is configured in IAMConfig.
2562
2828
  */
2563
- validators?: ESValidators;
2829
+ groups_inbound?: IAMGroupsInbound;
2830
+ /**
2831
+ * Users Inbound extension - receive user provisioning from external IAM.
2832
+ * Available when `userStore` is configured in IAMConfig.
2833
+ */
2834
+ users_inbound?: IAMUsersInbound;
2835
+ /**
2836
+ * Framework-agnostic request handler for the IAM module.
2837
+ * Routes to discovery, users_inbound, or groups_inbound based on the request path.
2838
+ */
2839
+ handler: (request: Request, config?: IAMHandlerConfig) => Promise<Response>;
2564
2840
  };
2565
2841
  /**
2566
2842
  * Serializes a FrameworkConfig (or ESConfig) to a JSON-serializable object.
@@ -2599,7 +2875,8 @@ type WorkloadModuleFromConfig<C extends FrameworkConfig> = Exclude<C["workload"]
2599
2875
  type EnterpriseStandardFromConfig<C extends FrameworkConfig = FrameworkConfig> = EnterpriseStandardStrict<C>;
2600
2876
  /** Base shape shared by all EnterpriseStandard variants (modules optional for backward compatibility). */
2601
2877
  type EnterpriseStandardBase = {
2602
- logger?: Logger;
2878
+ /** Effective framework logger for this instance (from framework `log` or `defaultLogger`). */
2879
+ log: Logger;
2603
2880
  /** App/tenant identifier when provided by ConfigSource (e.g. vault). */
2604
2881
  tenantId?: string;
2605
2882
  /** Most recent remote config applied to this instance (from ConfigSource, after beforeChange if any). */
@@ -2627,7 +2904,7 @@ type EnterpriseStandardBase = {
2627
2904
  };
2628
2905
  /** Config-driven module types: null in config → never; otherwise module type (non-optional). */
2629
2906
  type EnterpriseStandardStrict<C extends FrameworkConfig> = {
2630
- logger?: Logger;
2907
+ log: Logger;
2631
2908
  tenantId?: string;
2632
2909
  config?: RemoteConfig;
2633
2910
  secret: SecretsSource;
@@ -3213,6 +3490,12 @@ declare function mergeConfig<T extends Record<string, unknown>>(fromVault: T | u
3213
3490
  declare function stripJsonComments(content: string): string;
3214
3491
  declare function parseJsonc<T>(content: string): T;
3215
3492
  /**
3493
+ * Deep equality for JSON-like values used in config snapshots.
3494
+ * Treats object key order as irrelevant and treats missing and `undefined`
3495
+ * object properties as equal by ignoring `undefined` keys on both sides.
3496
+ */
3497
+ declare function deepEqualPlain(a: unknown, b: unknown): boolean;
3498
+ /**
3216
3499
  * Waits for a HTTP service to be ready by polling its URL.
3217
3500
  * Connection errors (e.g. connection refused) are treated as "not ready" and retried.
3218
3501
  * @param url - The URL to poll.
@@ -3222,4 +3505,4 @@ declare function parseJsonc<T>(content: string): T;
3222
3505
  * @returns A promise that resolves when the service is ready.
3223
3506
  */
3224
3507
  declare function waitOn(url: string, test?: (resp: Response) => boolean | Promise<boolean>, pingInterval?: number, warnInterval?: number, timeout?: number): Promise<void>;
3225
- export { workloadTokenResponseSchema, withValidate, waitOn, version, validationFailureResponse, userSchema, tokenResponseSchema, stripJsonComments, silentLogger, setActiveSession, serializeESConfig, sendTenantWebhook, parseJsonc, oidcCallbackSchema, normalizeTenantRoutingStrategy, normalizeTenantPathNamespace, must, mergeConfig, matchTenantPath, listSsoClientIdsFromCookies, list, jwtAssertionClaimsSchema, infoLogger, idTokenClaimsSchema, groupResourceSchema, getActiveSession, findTenantFromStateParam, defaultLogger, decodeUser, debugLogger, consoleLogger, clearActiveSession, claimsToUser, buildTenantPath, X509Certificate, WorkloadValidators, WorkloadTokenStore, WorkloadTokenResponse, WorkloadIncomingOutgoing, WorkloadIdentity, WorkloadConfigMap, WorkloadConfig, WorkloadClient, Workload, VaultWebSocketSecretsConfig, VaultWebSocketAuthHeader, VaultSecretsConfig, VaultLfvSecretsConfig, ValidateResult, UsersInboundHandlerConfig, UserStore, UserSortOptions, UserSortField, UserMode, UserListOptions, User2 as User, UpsertTenantResponse, UpsertTenantRequest, TokenValidationResult, TokenResponse, TenantWebhookPayload, TenantValidators, TenantUserRegistration, TenantStoredConfigLocator, TenantStoreWithESOptions, TenantStoreWithES, TenantStoreUpsertRecord, TenantStore, TenantStatus, TenantSortOptions, TenantSortField, TenantSecretsConfig, TenantRoutingStrategy, TenantRequestError, TenantRemoteConfigLocator, TenantPathRoutingStrategy, TenantPathNamespace, TenantPathMatch, TenantListOptions, TenantJwtRoutingStrategy, TenantEsFactory, TenantDirectoryTenant, TenantDirectoryResponse, TenantDirectoryAccount, TenantConfigStoreRequest, TenantConfigSourceInput, TenantConfigLocator, TenantConfigEnv, StoredUser, StoredTenantRecord, StoredTenant, StoredGroup, StateCookie, StandardSchemaWithValidate, SortDirection, SessionStore, Session, ServerOnlyWorkloadConfig, SecretsValidators, SecretsSourceType, SecretsSourceMap, SecretsSourceConfig, SecretsSource, SecretsOperationOptions, SecretsModuleConfig, Secrets, SecretRequestSeverity, SecretLifecycleRequest, Secret, User as ScimUser, ScimResult, ScimListResponse, ScimError, SSOValidators, SSOHandlerConfig, SSOConfig, SSOAppValidators, SSOAppRegistry, SSO, Role, ResolvedVaultLfvSecretsConfig, RemoteConfig, RegisterSSOAppResult, RegisterSSOAppPayload, RegisterSSOAppError, RegisterIAMAppResult, RegisterIAMAppPayload, RegisterIAMAppError, Photo, PhoneNumber, OidcCallbackParams, Name, MultipleTenantsForUserError, ModifiableFrameworkConfig, MetaData, MagicLinkStore, MagicLink, LoginConfig, Logger, ListResult, LfvOtpResponse, LfvOtpRequest, LfvErrorResponse, LfvErrorCode, LfvActionRequestBase, LfvActionName, LfvActionAcceptedResponse, JwtBearerWorkloadConfig, JWTAssertionClaims, InMemoryTenantStoreOptions, InMemoryTenantStore, IdTokenClaims, IAMValidators, IAMUsersInbound, IAMHandlerConfig, IAMGroupsOutbound, IAMGroupsInbound, IAMConfig, IAMAppValidators, IAMAppRole, IAMAppRegistry, IAM, GroupsInboundHandlerConfig, GroupStore, GroupSortOptions, GroupSortField, GroupResource, GroupMember, GroupListOptions, Group, GcpSecretsConfig, FrameworkWorkloadIncomingOutgoing, FrameworkWorkloadConfig, FrameworkStores, FrameworkSecretsSourceConfig, FrameworkSecretsModuleConfig, FrameworkConfig, EnvironmentType, EnterpriseUser, EnterpriseStandardFromConfig, EnterpriseStandardBase, EnterpriseStandard, EnterpriseExtension, Email, ESValidators, ESRoutingOptions, ESRouteModule, ESRouteFilterResult, ESResolvedRoute, ESModuleFromConfig, ESConfigChangeResult, ESConfigChangeOptions, ESConfigChangeCallback, ESConfig, DevSecretsConfig, DEFAULT_TENANT_UI_NAMESPACE, DEFAULT_TENANT_API_NAMESPACE, CreateUserOptions, CreateTenantResponse, CreateTenantRequest, CreateGroupOptions, ConfigSourceType, ConfigSource, ClientCredentialsWorkloadConfig, CachedWorkloadToken, CIAMValidators, CIAMConfigFromCode, CIAMConfig, CIAM, BaseUser, AzureSecretsConfig, AwsSecretsConfig, AwsAuthMethod, ApplicationValidators, Address };
3508
+ export { workloadTokenResponseSchema, withValidate, waitOn, version, validationFailureResponse, userSchema, tokenResponseSchema, stripJsonComments, silentLogger, setActiveSession, serializeESConfig, sendTenantWebhook, parseJsonc, oidcCallbackSchema, normalizeTenantRoutingStrategy, normalizeTenantPathNamespace, must, mergeConfig, matchTenantPath, listSsoClientIdsFromCookies, list, jwtAssertionClaimsSchema, infoLogger, idTokenClaimsSchema, groupResourceSchema, getActiveSession, findTenantFromStateParam, defaultLogger, deepEqualPlain, decodeUser, debugLogger, consoleLogger, clearActiveSession, claimsToUser, buildTenantPath, X509Certificate, WorkloadValidators, WorkloadTokenStore, WorkloadTokenResponse, WorkloadIncomingOutgoing, WorkloadIdentity, WorkloadConfigMap, WorkloadConfig, WorkloadClient, Workload, VaultWebSocketSecretsConfig, VaultWebSocketAuthHeader, VaultSecretsConfig, VaultLfvSecretsConfig, ValidateResult, UsersInboundHandlerConfig, UserStoreOptions, UserStore, UserSortOptions, UserSortField, UserListOptions, User2 as User, UpsertTenantResponse, UpsertTenantRequest, TokenValidationResult, TokenResponse, TenantWebhookPayload, TenantValidators, TenantUserRegistration, TenantStoredConfigLocator, TenantStoreWithEsCache, TenantStoreWithESOptions, TenantStoreWithES, TenantStore, TenantStatus, TenantSortOptions, TenantSortField, TenantSecretsConfig, TenantRoutingStrategy, TenantRequestError, TenantRemoteConfigLocator, TenantPathRoutingStrategy, TenantPathNamespace, TenantPathMatch, TenantManagerStore, TenantListOptions, TenantJwtRoutingStrategy, TenantEsFactory, TenantDirectoryTenant, TenantDirectoryResponse, TenantDirectoryAccount, TenantConfigStoreRequest, TenantConfigSourceInput, TenantConfigLocator, TenantConfigEnv, StoredUser, StoredTenantRecord, StoredTenant, StoredGroup, StateCookie, StandardSchemaWithValidate, SortDirection, SingleTenantStoreOptions, SingleTenantStore, SessionStore, Session, ServerOnlyWorkloadConfig, SecretsValidators, SecretsSourceType, SecretsSourceMap, SecretsSourceConfig, SecretsSource, SecretsOperationOptions, SecretsModuleConfig, Secrets, SecretRequestSeverity, SecretLifecycleRequest, Secret, User as ScimUser, ScimServiceProviderConfig, ScimSchemaDefinition, ScimSchemaAttributeDefinition, ScimResult, ScimResourceTypeSchemaExtension, ScimResourceType, ScimListResponse, ScimError, ScimAuthenticationScheme, SSOValidators, SSOHandlerConfig, SSOConfig, SSOAppValidators, SSOAppRegistry, SSO, Role, ResolvedVaultLfvSecretsConfig, RemoteConfig, RegisterSSOAppResult, RegisterSSOAppPayload, RegisterSSOAppError, RegisterIAMAppResult, RegisterIAMAppPayload, RegisterIAMAppError, ReactiveHandle, Photo, PhoneNumber, OidcCallbackParams, Name, MultipleTenantsForUserError, MultiTenantStoreOptions, MultiTenantStore, ModifiableFrameworkConfig, MetaData, MagicLinkStore, MagicLink, LoginConfig, Logger, ListResult, LfvOtpResponse, LfvOtpRequest, LfvErrorResponse, LfvErrorCode, LfvActionRequestBase, LfvActionName, LfvActionAcceptedResponse, JwtBearerWorkloadConfig, JWTAssertionClaims, InMemoryTenantStoreOptions, InMemorySingleTenantStoreOptions, InMemorySingleTenantStore, InMemoryMultiTenantStoreOptions, InMemoryMultiTenantStore, IdTokenClaims, IAMValidators, IAMUsersInbound, IAMInboundUsersConfig, IAMInboundUserContext, IAMHandlerConfig, IAMGroupsOutbound, IAMGroupsInbound, IAMDiscoveryHandlerConfig, IAMDiscoveryContext, IAMDiscoveryConfig, IAMConfig, IAMAppValidators, IAMAppRole, IAMAppRegistry, IAM, GroupsInboundHandlerConfig, GroupStore, GroupSortOptions, GroupSortField, GroupResource, GroupMember, GroupListOptions, Group, GcpSecretsConfig, FrameworkWorkloadIncomingOutgoing, FrameworkWorkloadConfig, FrameworkStores, FrameworkSecretsSourceConfig, FrameworkSecretsModuleConfig, FrameworkConfig, EnvironmentType, EnterpriseUser, EnterpriseStandardFromConfig, EnterpriseStandardBase, EnterpriseStandard, EnterpriseExtension, Email, ESValidators, ESRoutingOptions, ESRouteModule, ESRouteFilterResult, ESResolvedRoute, ESModuleFromConfig, ESConfigChangeResult, ESConfigChangeOptions, ESConfigChangeCallback, ESConfig, DevSecretsConfig, DEFAULT_TENANT_UI_NAMESPACE, DEFAULT_TENANT_API_NAMESPACE, CreateUserOptions, CreateTenantResponse, CreateTenantRequest, CreateGroupOptions, ConfigSourceType, ConfigSource, ClientCredentialsWorkloadConfig, ChangeListener, CachedWorkloadToken, CIAMValidators, CIAMConfigFromCode, CIAMConfig, CIAM, BaseUser, AzureSecretsConfig, AwsSecretsConfig, AwsAuthMethod, ApplicationValidators, Address };