@enterprisestandard/core 0.0.15-beta.20260420.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,6 +729,191 @@ interface IdTokenClaims {
758
729
  */
759
730
  declare function idTokenClaimsSchema(vendor: string): StandardSchemaV12<Record<string, unknown>, IdTokenClaims>;
760
731
  /**
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
+ * ```
792
+ */
793
+ /**
794
+ * Core session data tracked for each authenticated user session.
795
+ *
796
+ * @template TExtended - Type-safe custom data that consumers can add to sessions
797
+ */
798
+ type Session<TExtended = object> = {
799
+ /**
800
+ * Session ID from the Identity Provider (from `sid` claim in ID token).
801
+ * This is the unique identifier for the session.
802
+ */
803
+ sid: string;
804
+ /**
805
+ * Subject identifier (user ID) from the Identity Provider.
806
+ * From the `sub` claim in the ID token.
807
+ */
808
+ sub: string;
809
+ /**
810
+ * Timestamp when the session was created.
811
+ */
812
+ createdAt: Date;
813
+ /**
814
+ * Timestamp of the last activity in this session.
815
+ * Can be updated to track session activity.
816
+ */
817
+ lastActivityAt: Date;
818
+ /**
819
+ * Allow consumers to add runtime data to sessions.
820
+ */
821
+ [key: string]: unknown;
822
+ } & TExtended;
823
+ /**
824
+ * Abstract interface for session storage backends.
825
+ *
826
+ * Consumers can implement this interface to use different storage backends:
827
+ * - Redis
828
+ * - Database (PostgreSQL, MySQL, etc.)
829
+ * - Distributed cache
830
+ * - Custom solutions
831
+ *
832
+ * @template TExtended - Type-safe custom data that consumers can add to sessions
833
+ *
834
+ * @example
835
+ * ```typescript
836
+ * // Custom session data
837
+ * type MySessionData = {
838
+ * ipAddress: string;
839
+ * userAgent: string;
840
+ * };
841
+ *
842
+ * // Implement custom store
843
+ * class RedisSessionStore implements SessionStore<MySessionData> {
844
+ * async create(session: Session<MySessionData>): Promise<void> {
845
+ * await redis.set(`session:${session.sid}`, JSON.stringify(session));
846
+ * }
847
+ * // ... other methods
848
+ * }
849
+ * ```
850
+ */
851
+ interface SessionStore<TExtended = object> {
852
+ /**
853
+ * Create a new session in the store.
854
+ *
855
+ * @param session - The session data to store
856
+ * @throws Error if session with same sid already exists
857
+ */
858
+ create(session: Session<TExtended>): Promise<void>;
859
+ /**
860
+ * Retrieve a session by its IdP session ID (sid).
861
+ *
862
+ * @param sid - The session.sid from the Identity Provider
863
+ * @returns The session if found, null otherwise
864
+ */
865
+ get(sid: string): Promise<Session<TExtended> | null>;
866
+ /**
867
+ * Update an existing session with partial data.
868
+ *
869
+ * Commonly used to update lastActivityAt or add custom fields.
870
+ *
871
+ * @param sid - The session.sid to update
872
+ * @param data - Partial session data to merge
873
+ * @throws Error if session not found
874
+ */
875
+ update(sid: string, data: Partial<Session<TExtended>>): Promise<void>;
876
+ /**
877
+ * Delete a session by its IdP session ID (sid).
878
+ *
879
+ * Used for both normal logout and backchannel logout flows.
880
+ *
881
+ * @param sid - The session.sid to delete
882
+ */
883
+ delete(sid: string): Promise<void>;
884
+ }
885
+ /**
886
+ * Base user with simple, developer-friendly attributes.
887
+ * Extended by User (SSO) and EnterpriseUser (SCIM).
888
+ */
889
+ interface BaseUser {
890
+ /**
891
+ * Unique identifier for the user
892
+ */
893
+ id?: string;
894
+ /**
895
+ * REQUIRED. Unique identifier for login
896
+ */
897
+ userName: string;
898
+ /**
899
+ * REQUIRED. Simple display name
900
+ */
901
+ name: string;
902
+ /**
903
+ * REQUIRED. Primary email address
904
+ */
905
+ email: string;
906
+ /**
907
+ * URL to user's avatar/profile picture
908
+ */
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
+ /**
761
917
  * Primary user type for SSO/OIDC applications.
762
918
  * Extends BaseUser with SSO-specific data.
763
919
  */
@@ -795,229 +951,111 @@ interface User2 extends BaseUser {
795
951
  expires: Date;
796
952
  };
797
953
  }
798
- /**
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
805
- *
806
- * @template TExtended - Type-safe custom data that consumers can add to users
807
- */
808
- type StoredUser<TExtended = object> = User2 & {
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";
809
972
  /**
810
- * Required unique identifier (the `sub` claim from the IdP).
811
- * This is the primary key for user storage.
973
+ * Optional cookie name for tracking the active session across tenants.
974
+ * Defaults to 'es.active_session' when using the helper utilities.
812
975
  */
813
- id: string;
814
- /**
815
- * Optional Enterprise Standard tenant identifier for tenant-aware apps.
816
- * Built-in user stores can use this when registering HRD mappings.
817
- */
818
- tenantId?: string;
819
- /**
820
- * Optional external identifier from the provisioning client.
821
- * Commonly set by IAM inbound SCIM provisioning flows.
822
- */
823
- externalId?: string;
824
- /**
825
- * Optional SCIM display name distinct from the simple `name` field.
826
- * Commonly set by IAM inbound SCIM provisioning flows.
827
- */
828
- displayName?: string;
829
- /**
830
- * Optional structured SCIM name.
831
- * Commonly set by IAM inbound SCIM provisioning flows.
832
- */
833
- scimName?: Name;
834
- /**
835
- * Optional SCIM email collection.
836
- * The simple `email` field still stores the primary email for quick lookup.
837
- */
838
- emails?: Email[];
839
- /**
840
- * Optional SCIM nickname.
841
- * Commonly set by IAM inbound SCIM provisioning flows.
842
- */
843
- nickName?: string;
844
- /**
845
- * Optional SCIM account state.
846
- * Commonly set by IAM inbound SCIM provisioning flows.
847
- */
848
- active?: boolean;
849
- /**
850
- * Optional SCIM job title.
851
- * Commonly set by IAM inbound SCIM provisioning flows.
852
- */
853
- title?: string;
854
- /**
855
- * Optional SCIM preferred language.
856
- * Commonly set by IAM inbound SCIM provisioning flows.
857
- */
858
- preferredLanguage?: string;
859
- /**
860
- * Optional SCIM locale.
861
- * Commonly set by IAM inbound SCIM provisioning flows.
862
- */
863
- locale?: string;
864
- /**
865
- * Optional SCIM timezone.
866
- * Commonly set by IAM inbound SCIM provisioning flows.
867
- */
868
- timezone?: string;
869
- /**
870
- * Optional SCIM phone numbers.
871
- * Commonly set by IAM inbound SCIM provisioning flows.
872
- */
873
- phoneNumbers?: PhoneNumber[];
874
- /**
875
- * Optional SCIM instant messaging addresses.
876
- * Commonly set by IAM inbound SCIM provisioning flows.
877
- */
878
- ims?: Array<{
879
- value: string;
880
- display?: string;
881
- type?: string;
882
- primary?: boolean;
883
- }>;
884
- /**
885
- * Optional SCIM photos.
886
- * Commonly set by IAM inbound SCIM provisioning flows.
887
- */
888
- photos?: Photo[];
889
- /**
890
- * Optional SCIM addresses.
891
- * Commonly set by IAM inbound SCIM provisioning flows.
892
- */
893
- addresses?: Address[];
894
- /**
895
- * Optional SCIM roles.
896
- * Commonly set by IAM inbound SCIM provisioning flows.
897
- */
898
- roles?: Role[];
899
- /**
900
- * Optional SCIM groups.
901
- * Commonly set by IAM inbound SCIM provisioning flows.
902
- */
903
- groups?: Group[];
904
- /**
905
- * Optional SCIM entitlements.
906
- * Commonly set by IAM inbound SCIM provisioning flows.
907
- */
908
- entitlements?: Array<{
909
- value: string;
910
- display?: string;
911
- type?: string;
912
- primary?: boolean;
913
- }>;
914
- /**
915
- * Optional SCIM X.509 certificates.
916
- * Commonly set by IAM inbound SCIM provisioning flows.
917
- */
918
- x509Certificates?: X509Certificate[];
919
- /**
920
- * Optional SCIM enterprise extension.
921
- * Mirrors `urn:ietf:params:scim:schemas:extension:enterprise:2.0:User`.
922
- * Commonly set by IAM inbound SCIM provisioning flows.
923
- */
924
- scimEnterprise?: EnterpriseExtension;
925
- /**
926
- * Optional pass-through SCIM schema extensions keyed by full URN.
927
- * Use this when your validator accepts custom top-level SCIM extensions and
928
- * you want them to round-trip through a `UserStore` implementation.
929
- */
930
- scimSchemaExtensions?: Record<string, unknown>;
931
- /**
932
- * Timestamp when the user was first stored.
933
- */
934
- createdAt: Date;
935
- /**
936
- * Timestamp when the user was last updated (e.g., on re-login).
937
- */
938
- updatedAt: Date;
939
- } & TExtended;
940
- /**
941
- * Abstract interface for user storage backends.
942
- *
943
- * Consumers can implement this interface to use different storage backends:
944
- * - In-memory (for development/testing)
945
- * - Redis (for production with fast lookups)
946
- * - Database (PostgreSQL, MySQL, etc.)
947
- *
948
- * @template TExtended - Type-safe custom data that consumers can add to users
949
- *
950
- * @example
951
- * ```typescript
952
- * // Custom user data
953
- * type MyUserData = {
954
- * department: string;
955
- * roles: string[];
956
- * };
957
- *
958
- * // Implement custom store
959
- * class RedisUserStore implements UserStore<MyUserData> {
960
- * async get(sub: string): Promise<StoredUser<MyUserData> | null> {
961
- * const data = await redis.get(`user:${sub}`);
962
- * return data ? JSON.parse(data) : null;
963
- * }
964
- * // ... other methods
965
- * }
966
- * ```
967
- */
968
- interface UserStore<TExtended = object> {
969
- /**
970
- * Retrieve a user by their subject identifier (sub).
971
- *
972
- * @param sub - The user's unique identifier from the IdP
973
- * @returns The user if found, null otherwise
974
- */
975
- get(sub: string): Promise<StoredUser<TExtended> | null>;
976
- /**
977
- * Retrieve a user by their email address.
978
- *
979
- * @param email - The user's email address
980
- * @returns The user if found, null otherwise
981
- */
982
- getByEmail(email: string): Promise<StoredUser<TExtended> | null>;
983
- /**
984
- * Retrieve a user by their username.
985
- *
986
- * @param userName - The user's username
987
- * @returns The user if found, null otherwise
988
- */
989
- getByUserName(userName: string): Promise<StoredUser<TExtended> | null>;
990
- /**
991
- * Create or update a user in the store.
992
- *
993
- * If a user with the same `id` (sub) exists, it will be updated.
994
- * Otherwise, a new user will be created.
995
- *
996
- * @param user - The user data to store
997
- */
998
- upsert(user: StoredUser<TExtended>): Promise<void>;
976
+ activeSessionCookieName?: string;
977
+ endSessionEndpoint?: string;
978
+ revocationEndpoint?: string;
979
+ sessionStore?: SessionStore<TSessionData>;
999
980
  /**
1000
- * Delete a user by their subject identifier (sub).
1001
- *
1002
- * @param sub - The user's unique identifier to delete
981
+ * Optional user store for persisting user profiles from SSO authentication.
982
+ * When configured, users are automatically stored/updated on each login.
1003
983
  */
1004
- delete(sub: string): Promise<void>;
984
+ userStore?: UserStore<TUserData>;
1005
985
  /**
1006
- * List users in the store with optional pagination and sort.
1007
- *
1008
- * @param options - Optional start (0-based), limit (page size), and sort
1009
- * @returns ListResult with total, count, items, size, page, pages
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
1010
991
  */
1011
- list(options?: UserListOptions): Promise<ListResult<StoredUser<TExtended>>>;
1012
- }
1013
- import { StandardSchemaV1 as StandardSchemaV14 } from "@standard-schema/spec";
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";
1014
1052
  type ChangeListener = () => void;
1015
1053
  type ReactiveHandle = {
1016
1054
  beforeChange?(listener: ChangeListener): () => void;
1017
1055
  afterChange?(listener: ChangeListener): () => void;
1018
1056
  isAvailable?(): boolean;
1019
1057
  };
1020
- import { StandardSchemaV1 as StandardSchemaV13 } from "@standard-schema/spec";
1058
+ import { StandardSchemaV1 as StandardSchemaV14 } from "@standard-schema/spec";
1021
1059
  /**
1022
1060
  * JWT Assertion Claims for OAuth2 JWT Bearer Grant (RFC 7523) and OAuth2 Access Tokens
1023
1061
  * @see https://datatracker.ietf.org/doc/html/rfc7523
@@ -1064,7 +1102,7 @@ interface JWTAssertionClaims {
1064
1102
  * @param vendor - The name of the vendor creating this schema
1065
1103
  * @returns A StandardSchemaV1 instance for JWT Assertion Claims validation
1066
1104
  */
1067
- declare function jwtAssertionClaimsSchema(vendor: string): StandardSchemaV13<Record<string, unknown>, JWTAssertionClaims>;
1105
+ declare function jwtAssertionClaimsSchema(vendor: string): StandardSchemaV14<Record<string, unknown>, JWTAssertionClaims>;
1068
1106
  /**
1069
1107
  * Workload Token Response from OAuth2 token endpoint
1070
1108
  * @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
@@ -1100,7 +1138,7 @@ interface WorkloadTokenResponse {
1100
1138
  * @param vendor - The name of the vendor creating this schema
1101
1139
  * @returns A StandardSchemaV1 instance for Workload Token Response validation
1102
1140
  */
1103
- declare function workloadTokenResponseSchema(vendor: string): StandardSchemaV13<Record<string, unknown>, WorkloadTokenResponse>;
1141
+ declare function workloadTokenResponseSchema(vendor: string): StandardSchemaV14<Record<string, unknown>, WorkloadTokenResponse>;
1104
1142
  /**
1105
1143
  * Token Validation Result
1106
1144
  */
@@ -1347,8 +1385,8 @@ type WorkloadConfigBase = {
1347
1385
  validators?: WorkloadValidators;
1348
1386
  };
1349
1387
  type WorkloadValidators = {
1350
- jwtAssertionClaims: StandardSchemaV14<unknown, JWTAssertionClaims>;
1351
- tokenResponse: StandardSchemaV14<unknown, WorkloadTokenResponse>;
1388
+ jwtAssertionClaims: StandardSchemaV15<unknown, JWTAssertionClaims>;
1389
+ tokenResponse: StandardSchemaV15<unknown, WorkloadTokenResponse>;
1352
1390
  };
1353
1391
  /**
1354
1392
  * JWT Bearer Grant (RFC 7523) Configuration
@@ -1528,951 +1566,545 @@ type Workload = WorkloadConfig & ReactiveHandle & {
1528
1566
  };
1529
1567
  type WorkloadClient = Pick<Workload, "getToken" | "refreshToken" | "generateJWTAssertion" | "revokeToken" | "beforeChange" | "afterChange" | "isAvailable">;
1530
1568
  /**
1531
- * SCIM Error response structure
1532
- */
1533
- interface ScimError {
1534
- schemas: string[];
1535
- status: string;
1536
- scimType?: string;
1537
- detail?: string;
1538
- }
1539
- /**
1540
- * SCIM List Response for bulk operations
1541
- */
1542
- interface ScimListResponse<T> {
1543
- schemas: string[];
1544
- totalResults: number;
1545
- startIndex?: number;
1546
- itemsPerPage?: number;
1547
- Resources: T[];
1548
- }
1549
- /**
1550
- * Result of a SCIM operation
1551
- */
1552
- interface ScimResult<T> {
1553
- success: boolean;
1554
- data?: T;
1555
- error?: ScimError;
1556
- status: number;
1557
- }
1558
- /**
1559
- * Handler configuration for IAM
1569
+ * Magic link data stored in the store.
1570
+ *
1571
+ * @template TExtended - Type-safe custom data that consumers can add to magic links
1560
1572
  */
1561
- interface IAMHandlerConfig {
1573
+ type MagicLink<TExtended = object> = {
1562
1574
  /**
1563
- * Base path for the SCIM Users endpoints (e.g., '/api/iam/Users')
1575
+ * The magic link token (unique identifier)
1564
1576
  */
1565
- usersUrl?: string;
1577
+ token: string;
1566
1578
  /**
1567
- * Base path for the SCIM Groups endpoints (e.g., '/api/iam/Groups')
1579
+ * User information associated with this magic link
1568
1580
  */
1569
- groupsUrl?: string;
1581
+ user: BaseUser;
1570
1582
  /**
1571
- * Handler overrides for SCIM discovery endpoints (e.g., '/api/iam/ServiceProviderConfig')
1583
+ * Timestamp when the magic link was created
1572
1584
  */
1573
- discovery?: IAMDiscoveryHandlerConfig;
1574
- }
1585
+ createdAt: Date;
1586
+ /**
1587
+ * Timestamp when the magic link expires
1588
+ */
1589
+ expiresAt: Date;
1590
+ /**
1591
+ * Allow consumers to add runtime data to magic links
1592
+ */
1593
+ [key: string]: unknown;
1594
+ } & TExtended;
1575
1595
  /**
1576
- * IAM configuration
1596
+ * Abstract interface for magic link storage backends.
1577
1597
  *
1578
- * - If `url` is provided, groups_outbound is enabled (app calls external IAM)
1579
- * - If `groupStore` is provided, groups_inbound is enabled (external IAM calls app)
1580
- * - If `userStore` is provided, users_inbound is enabled (external IAM calls app)
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
+ * ```
1581
1630
  */
1582
- type IAMConfig = {
1631
+ interface MagicLinkStore<TExtended = object> {
1583
1632
  /**
1584
- * Base URL of the external SCIM endpoint (e.g., https://sailpoint.example.com/scim/v2)
1585
- * If provided, enables outbound SCIM operations (app -> external IAM)
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
1586
1639
  */
1587
- url?: string;
1640
+ create(token: string, user: BaseUser, expiresAt: Date): Promise<void>;
1588
1641
  /**
1589
- * Store for inbound user provisioning from external IAM providers.
1590
- * When configured, the app can receive user CRUD operations via SCIM.
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
1591
1646
  */
1592
- userStore?: UserStore;
1647
+ get(token: string): Promise<MagicLink<TExtended> | null>;
1593
1648
  /**
1594
- * Optional inbound user mapping hooks for SCIM provisioning.
1595
- * Use these when the default StoredUser <-> SCIM mapping is not a direct fit
1596
- * for your application's database model.
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
1597
1654
  */
1598
- inboundUsers?: IAMInboundUsersConfig;
1655
+ delete(token: string): Promise<void>;
1656
+ }
1657
+ type Secret<T> = {
1658
+ data: T;
1659
+ metadata: MetaData;
1660
+ };
1661
+ type MetaData = {
1662
+ path: string;
1663
+ version: number;
1664
+ created: Date;
1665
+ };
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;
1672
+ };
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>;
1599
1681
  /**
1600
- * Store for inbound group provisioning from external IAM providers.
1601
- * When configured, enables groups_inbound (external IAM -> app).
1682
+ * Write a secret at the given path.
1683
+ * Implementations may throw for read-only or unsupported secret sources.
1602
1684
  */
1603
- groupStore?: GroupStore;
1685
+ putSecret: (path: string, value: Record<string, unknown>, options?: SecretsOperationOptions) => Promise<void>;
1604
1686
  /**
1605
- * Optional handler defaults. These are merged with per-call overrides in
1606
- * `iam.handler`, with per-call values taking precedence.
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.
1607
1691
  */
1608
- usersUrl?: string;
1609
- groupsUrl?: string;
1610
- discovery?: IAMDiscoveryConfig;
1611
- };
1612
- type IAMValidators = {
1613
- user: StandardSchemaV15<unknown, User>;
1614
- group: StandardSchemaV15<unknown, GroupResource>;
1615
- };
1616
- interface IAMInboundUserContext {
1692
+ subscribe<T>(path: string, onChange: (fullSecret: Secret<T>) => void): () => void;
1617
1693
  /**
1618
- * Current stored user when replacing or patching an existing resource.
1694
+ * Deletes a secret at the given path.
1619
1695
  */
1620
- existing?: StoredUser;
1696
+ deleteSecret(path: string, options?: SecretsOperationOptions): Promise<void>;
1621
1697
  /**
1622
- * Operation mode for the inbound mapper.
1698
+ * Lists child paths under the given base path.
1623
1699
  */
1624
- mode: "create" | "replace" | "patch";
1625
- }
1626
- interface IAMInboundUsersConfig {
1700
+ listPaths(path: string, options?: SecretsOperationOptions): Promise<string[]>;
1627
1701
  /**
1628
- * Replace the default validated SCIM -> StoredUser mapper.
1702
+ * Returns true when a secret path exists.
1629
1703
  */
1630
- mapValidatedScimToStoredUser?: (validated: User, context: IAMInboundUserContext) => StoredUser | Promise<StoredUser>;
1704
+ exists(path: string, options?: SecretsOperationOptions): Promise<boolean>;
1631
1705
  /**
1632
- * Replace the default StoredUser -> SCIM response mapper.
1706
+ * Requests secret rotation for the given path.
1633
1707
  */
1634
- mapStoredUserToScim?: (stored: StoredUser) => User | Promise<User>;
1635
- }
1636
- interface ScimAuthenticationScheme {
1637
- type: string;
1638
- name: string;
1639
- description?: string;
1640
- specUri?: string;
1641
- documentationUri?: string;
1642
- primary?: boolean;
1643
- }
1644
- interface ScimServiceProviderConfig {
1645
- schemas: string[];
1646
- documentationUri?: string;
1647
- patch: {
1648
- supported: boolean;
1649
- };
1650
- bulk: {
1651
- supported: boolean;
1652
- maxOperations?: number;
1653
- maxPayloadSize?: number;
1654
- };
1655
- filter: {
1656
- supported: boolean;
1657
- maxResults?: number;
1658
- };
1659
- changePassword: {
1660
- supported: boolean;
1661
- };
1662
- sort: {
1663
- supported: boolean;
1664
- };
1665
- etag: {
1666
- supported: boolean;
1667
- };
1668
- authenticationSchemes?: ScimAuthenticationScheme[];
1669
- meta?: {
1670
- resourceType?: string;
1671
- location?: string;
1672
- };
1673
- }
1674
- interface ScimResourceTypeSchemaExtension {
1675
- schema: string;
1676
- required: boolean;
1677
- }
1678
- interface ScimResourceType {
1679
- schemas: string[];
1680
- id: string;
1681
- name: string;
1682
- description?: string;
1683
- endpoint: string;
1684
- schema: string;
1685
- schemaExtensions?: ScimResourceTypeSchemaExtension[];
1686
- meta?: {
1687
- resourceType?: string;
1688
- location?: string;
1689
- };
1690
- }
1691
- interface ScimSchemaAttributeDefinition {
1692
- name: string;
1693
- type: "string" | "boolean" | "complex" | "reference" | "dateTime";
1694
- multiValued: boolean;
1695
- description?: string;
1696
- required?: boolean;
1697
- caseExact?: boolean;
1698
- mutability?: "readOnly" | "readWrite" | "immutable" | "writeOnly";
1699
- returned?: "always" | "never" | "default" | "request";
1700
- uniqueness?: "none" | "server" | "global";
1701
- referenceTypes?: string[];
1702
- subAttributes?: ScimSchemaAttributeDefinition[];
1703
- }
1704
- interface ScimSchemaDefinition {
1705
- schemas: string[];
1706
- id: string;
1707
- name: string;
1708
- description?: string;
1709
- attributes: ScimSchemaAttributeDefinition[];
1710
- meta?: {
1711
- resourceType?: string;
1712
- location?: string;
1713
- };
1714
- }
1715
- interface IAMDiscoveryContext {
1716
- request: Request;
1717
- basePath: string;
1718
- usersUrl: string;
1719
- groupsUrl: string;
1720
- supportsUsers: boolean;
1721
- supportsGroups: boolean;
1722
- }
1723
- interface IAMDiscoveryConfig {
1724
- /**
1725
- * Public IAM base path used for SCIM discovery. When omitted, the SDK derives
1726
- * it from `usersUrl` or `groupsUrl`.
1727
- */
1728
- basePath?: string;
1729
- /**
1730
- * Optional documentation URI advertised in ServiceProviderConfig.
1731
- */
1732
- documentationUri?: string;
1708
+ requestRotate(path: string, request?: SecretLifecycleRequest, options?: SecretsOperationOptions): Promise<void>;
1733
1709
  /**
1734
- * Override the default ServiceProviderConfig response.
1710
+ * Requests secret revocation for the given path.
1735
1711
  */
1736
- buildServiceProviderConfig?: (context: IAMDiscoveryContext, defaults: ScimServiceProviderConfig) => ScimServiceProviderConfig | Promise<ScimServiceProviderConfig>;
1712
+ requestRevoke(path: string, request?: SecretLifecycleRequest, options?: SecretsOperationOptions): Promise<void>;
1737
1713
  /**
1738
- * Override the default ResourceTypes response.
1714
+ * Reads metadata for the given path.
1739
1715
  */
1740
- buildResourceTypes?: (context: IAMDiscoveryContext, defaults: ScimResourceType[]) => ScimResourceType[] | Promise<ScimResourceType[]>;
1716
+ getMetadata(path: string, options?: SecretsOperationOptions): Promise<Record<string, unknown>>;
1717
+ };
1718
+ type SecretsValidators = {
1741
1719
  /**
1742
- * Override the default Schemas response.
1720
+ * Optional hook to validate merged source configs before they are resolved.
1721
+ * Throw from this callback to reject invalid secrets source configuration.
1743
1722
  */
1744
- buildSchemas?: (context: IAMDiscoveryContext, defaults: ScimSchemaDefinition[]) => ScimSchemaDefinition[] | Promise<ScimSchemaDefinition[]>;
1745
- }
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
+ };
1746
1761
  /**
1747
- * Options for creating a user
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.
1748
1764
  */
1749
- interface CreateUserOptions {
1750
- /**
1751
- * External identifier for the user
1752
- */
1753
- externalId?: string;
1754
- }
1765
+ type FrameworkSecretsSourceConfig = Partial<SecretsSourceConfig>;
1755
1766
  /**
1756
- * Options for creating a group
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.
1757
1769
  */
1758
- interface CreateGroupOptions {
1759
- /**
1760
- * External identifier for the group
1761
- */
1762
- externalId?: string;
1763
- /**
1764
- * Initial members to add to the group
1765
- */
1766
- members?: GroupMember[];
1767
- }
1770
+ type FrameworkSecretsModuleConfig = Record<string, FrameworkSecretsSourceConfig>;
1768
1771
  /**
1769
- * Handler configuration for groups_inbound
1772
+ * TODO: Let's see if we can do some clean inference and remove this!!!
1770
1773
  */
1771
- interface GroupsInboundHandlerConfig {
1772
- /**
1773
- * Base path for the SCIM Groups endpoints (e.g., '/api/iam/Groups')
1774
- */
1775
- basePath?: string;
1776
- }
1774
+ type SecretsSourceMap = Record<string, SecretsSource>;
1775
+ type SecretsSourceConfig = DevSecretsConfig | GcpSecretsConfig | VaultSecretsConfig | AwsSecretsConfig | AzureSecretsConfig;
1777
1776
  /**
1778
- * Handler configuration for users_inbound
1777
+ * Raw module config keyed by source name.
1778
+ * The secrets module resolves this into a runtime SecretsSourceMap.
1779
1779
  */
1780
- interface UsersInboundHandlerConfig {
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;
1781
1796
  /**
1782
- * Base path for the SCIM Users endpoints (e.g., '/api/iam/Users')
1797
+ * Optional LFV signature verification key.
1798
+ * When omitted, LFV callbacks are accepted without signature verification.
1783
1799
  */
1784
- basePath?: string;
1785
- }
1786
- interface IAMDiscoveryHandlerConfig {
1800
+ verifyPublicKey?: string;
1801
+ eventsEndpoint: string;
1802
+ path?: string;
1787
1803
  /**
1788
- * Base path for the IAM discovery endpoints (e.g., '/api/iam')
1804
+ * Timeout in milliseconds when waiting for a LFV delivery payload after a read request.
1805
+ * After this duration, an error is thrown.
1789
1806
  */
1790
- basePath?: string;
1791
- }
1792
- /**
1793
- * Groups Outbound extension - for creating groups in external IAM providers.
1794
- * Enabled when `url` is configured in IAMConfig.
1795
- */
1796
- type IAMGroupsOutbound = {
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;
1797
1812
  /**
1798
- * Create a new group in the external IAM provider
1799
- * @param displayName - The display name for the group
1800
- * @param options - Optional configuration for the group creation
1801
- * @returns The created group resource from the provider
1813
+ * Optional logger for request/response tracing. Use `debugLogger` from `@enterprisestandard/core`
1814
+ * to get debug output with request_id for LFV operations.
1802
1815
  */
1803
- createGroup: (displayName: string, options?: CreateGroupOptions) => Promise<ScimResult<GroupResource>>;
1816
+ log?: Logger;
1804
1817
  };
1805
1818
  /**
1806
- * Groups Inbound extension - for receiving group provisioning from external IAM providers.
1807
- * Enabled when `groupStore` is configured in IAMConfig.
1819
+ * Runtime-ready LFV source config.
1820
+ * Input config can be partially declared/merged, but LFV operations require these fields.
1808
1821
  */
1809
- type IAMGroupsInbound = {
1810
- /**
1811
- * Handle inbound SCIM requests for group management.
1812
- * Routes: GET/POST /Groups, GET/PUT/PATCH/DELETE /Groups/:id
1813
- */
1814
- handler: (request: Request, config?: GroupsInboundHandlerConfig) => Promise<Response>;
1822
+ type ResolvedVaultLfvSecretsConfig = Omit<VaultLfvSecretsConfig, "lfvServerUrl" | "clientId" | "path"> & {
1823
+ lfvServerUrl: string;
1824
+ clientId: string;
1825
+ path: string;
1815
1826
  };
1816
- /**
1817
- * Users Inbound extension - for receiving user provisioning from external IAM providers.
1818
- * Enabled when `userStore` is configured in IAMConfig.
1819
- */
1820
- type IAMUsersInbound = {
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;
1821
1844
  /**
1822
- * Handle inbound SCIM requests for user management.
1823
- * Routes: GET/POST /Users, GET/PUT/PATCH/DELETE /Users/:id
1845
+ * MINIMUM: 600_000 milliseconds (10 minutes). Polls the path every ttl milliseconds and calls onConfig when config changes.
1824
1846
  */
1825
- handler: (request: Request, config?: UsersInboundHandlerConfig) => Promise<Response>;
1847
+ ttl?: number;
1826
1848
  };
1827
- /**
1828
- * Core IAM service interface.
1829
- *
1830
- * - Core functions are user-related (outbound to external IAM)
1831
- * - `groups_outbound` is available when `url` is configured
1832
- * - `groups_inbound` is available when `groupStore` is configured
1833
- * - `users_inbound` is available when `userStore` is configured
1834
- */
1835
- type IAM = IAMConfig & {
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";
1836
1856
  /**
1837
- * Create a new user/account in the external IAM provider
1838
- * Only available when `url` is configured.
1857
+ * How to authenticate to Azure to fetch Key Vault access tokens.
1858
+ *
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
1862
+ *
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
1867
+ *
1868
+ * Env: ES_AZURE_AUTH_METHOD
1839
1869
  */
1840
- createUser?: (user: User, options?: CreateUserOptions) => Promise<ScimResult<User>>;
1870
+ authMethod?: AwsAuthMethod;
1841
1871
  /**
1842
- * Get the configured external SCIM base URL
1872
+ * Azure Entra tenant id (GUID), used for OAuth2 token exchange.
1873
+ * Env: ES_AZURE_TENANT_ID
1843
1874
  */
1844
- getBaseUrl: () => string | undefined;
1875
+ tenantId?: string;
1845
1876
  /**
1846
- * Groups Outbound extension - create groups in external IAM provider.
1847
- * Available when `url` is configured in IAMConfig.
1877
+ * Azure app registration client id (GUID).
1878
+ * Env: ES_AZURE_CLIENT_ID
1848
1879
  */
1849
- groups_outbound?: IAMGroupsOutbound;
1880
+ clientId?: string;
1850
1881
  /**
1851
- * Groups Inbound extension - receive group provisioning from external IAM.
1852
- * Available when `groupStore` is configured in IAMConfig.
1882
+ * Azure app registration client secret.
1883
+ * Env: ES_AZURE_CLIENT_SECRET
1853
1884
  */
1854
- groups_inbound?: IAMGroupsInbound;
1885
+ clientSecret?: string;
1855
1886
  /**
1856
- * Users Inbound extension - receive user provisioning from external IAM.
1857
- * Available when `userStore` is configured in IAMConfig.
1858
- */
1859
- users_inbound?: IAMUsersInbound;
1860
- /**
1861
- * Framework-agnostic request handler for the IAM module.
1862
- * Routes to discovery, users_inbound, or groups_inbound based on the request path.
1863
- */
1864
- handler: (request: Request, config?: IAMHandlerConfig) => Promise<Response>;
1865
- };
1866
- import { StandardSchemaV1 as StandardSchemaV16 } from "@standard-schema/spec";
1867
- /**
1868
- * Session management for tracking user sessions and enabling backchannel logout.
1869
- *
1870
- * Session stores are optional - the package works with JWT cookies alone.
1871
- * Sessions are only required for backchannel logout functionality.
1872
- *
1873
- * ## Session Validation Strategies
1874
- *
1875
- * When using a session store, you can configure when sessions are validated:
1876
- *
1877
- * ### 'always' (default)
1878
- * Validates session on every authenticated request.
1879
- * - **Security**: Maximum - immediate session revocation
1880
- * - **Performance**: InMemory ~0.00005ms, Redis ~1-2ms per request
1881
- * - **Backchannel Logout**: Takes effect immediately
1882
- * - **Use when**: Security is paramount, using InMemory or Redis backend
1883
- *
1884
- * ### 'refresh-only'
1885
- * Validates session only during token refresh (typically every 5-15 minutes).
1886
- * - **Security**: Good - 5-15 minute revocation window
1887
- * - **Performance**: 99% reduction in session lookups
1888
- * - **Backchannel Logout**: Takes effect within token TTL (5-15 min)
1889
- * - **Use when**: Performance is critical AND delayed revocation is acceptable
1890
- * - **WARNING**: Compromised sessions remain valid until next refresh
1891
- *
1892
- * ### 'disabled'
1893
- * Never validates sessions against the store.
1894
- * - **Security**: None - backchannel logout doesn't work
1895
- * - **Performance**: No overhead
1896
- * - **Use when**: Cookie-only mode without session store
1897
- * - **WARNING**: Do not use with sessionStore configured
1898
- *
1899
- * ## Performance Characteristics
1900
- *
1901
- * | Backend | Lookup Time | Impact on Request | Recommendation |
1902
- * |--------------|-------------|-------------------|------------------------|
1903
- * | InMemory | <0.00005ms | Negligible | Use 'always' |
1904
- * | Redis | 1-2ms | 2-4% increase | Use 'always' or test |
1905
- * | Database | 5-20ms | 10-40% increase | Use Redis cache layer |
1906
- *
1907
- * ## Example Usage
1908
- *
1909
- * ```typescript
1910
- * import { sso, InMemorySessionStore } from '@enterprisestandard/server';
1911
- *
1912
- * // Maximum security (default)
1913
- * const secure = sso({
1914
- * // ... other config
1915
- * sessionStore: new InMemorySessionStore(),
1916
- * session_validation: 'always', // Immediate revocation
1917
- * });
1918
- *
1919
- * // High performance
1920
- * const fast = sso({
1921
- * // ... other config
1922
- * sessionStore: new InMemorySessionStore(),
1923
- * session_validation: {
1924
- * strategy: 'refresh-only' // 5-15 min revocation delay
1925
- * }
1926
- * });
1927
- * ```
1928
- */
1929
- /**
1930
- * Core session data tracked for each authenticated user session.
1931
- *
1932
- * @template TExtended - Type-safe custom data that consumers can add to sessions
1933
- */
1934
- type Session<TExtended = object> = {
1935
- /**
1936
- * Session ID from the Identity Provider (from `sid` claim in ID token).
1937
- * This is the unique identifier for the session.
1887
+ * Path to the projected federated token file (AKS Workload Identity).
1888
+ * Env: ES_AZURE_FEDERATED_TOKEN_FILE or AZURE_FEDERATED_TOKEN_FILE
1938
1889
  */
1939
- sid: string;
1890
+ federatedTokenFile?: string;
1940
1891
  /**
1941
- * Subject identifier (user ID) from the Identity Provider.
1942
- * From the `sub` claim in the ID token.
1892
+ * Managed Identity client id (user-assigned), optional.
1893
+ * Env: ES_AZURE_MANAGED_IDENTITY_CLIENT_ID
1943
1894
  */
1944
- sub: string;
1895
+ managedIdentityClientId?: string;
1945
1896
  /**
1946
- * Timestamp when the session was created.
1897
+ * IMDS API version (managed identity).
1898
+ * Env: ES_AZURE_IMDS_API_VERSION
1899
+ * Default: 2018-02-01
1947
1900
  */
1948
- createdAt: Date;
1901
+ imdsApiVersion?: string;
1949
1902
  /**
1950
- * Timestamp of the last activity in this session.
1951
- * Can be updated to track session activity.
1903
+ * Key Vault URL, e.g. https://myvault.vault.azure.net
1904
+ * Env: ES_AZURE_KEY_VAULT_URL
1952
1905
  */
1953
- lastActivityAt: Date;
1906
+ vaultUrl?: string;
1954
1907
  /**
1955
- * Allow consumers to add runtime data to sessions.
1908
+ * Alternative to vaultUrl. If provided, vaultUrl is derived as:
1909
+ * https://${vaultName}.vault.azure.net
1910
+ * Env: ES_AZURE_KEY_VAULT_NAME
1956
1911
  */
1957
- [key: string]: unknown;
1958
- } & TExtended;
1959
- /**
1960
- * Abstract interface for session storage backends.
1961
- *
1962
- * Consumers can implement this interface to use different storage backends:
1963
- * - Redis
1964
- * - Database (PostgreSQL, MySQL, etc.)
1965
- * - Distributed cache
1966
- * - Custom solutions
1967
- *
1968
- * @template TExtended - Type-safe custom data that consumers can add to sessions
1969
- *
1970
- * @example
1971
- * ```typescript
1972
- * // Custom session data
1973
- * type MySessionData = {
1974
- * ipAddress: string;
1975
- * userAgent: string;
1976
- * };
1977
- *
1978
- * // Implement custom store
1979
- * class RedisSessionStore implements SessionStore<MySessionData> {
1980
- * async create(session: Session<MySessionData>): Promise<void> {
1981
- * await redis.set(`session:${session.sid}`, JSON.stringify(session));
1982
- * }
1983
- * // ... other methods
1984
- * }
1985
- * ```
1986
- */
1987
- interface SessionStore<TExtended = object> {
1912
+ vaultName?: string;
1988
1913
  /**
1989
- * Create a new session in the store.
1990
- *
1991
- * @param session - The session data to store
1992
- * @throws Error if session with same sid already exists
1914
+ * Key Vault API version.
1915
+ * Env: ES_AZURE_KEY_VAULT_API_VERSION
1916
+ * Default: 7.4
1993
1917
  */
1994
- create(session: Session<TExtended>): Promise<void>;
1918
+ apiVersion?: string;
1995
1919
  /**
1996
- * Retrieve a session by its IdP session ID (sid).
1997
- *
1998
- * @param sid - The session.sid from the Identity Provider
1999
- * @returns The session if found, null otherwise
1920
+ * Maps vault "path" to a Key Vault secret name.
1921
+ * Default: replaces `/` with `--` and trims leading/trailing `/`.
2000
1922
  */
2001
- get(sid: string): Promise<Session<TExtended> | null>;
1923
+ secretNameTransform?: (path: string) => string;
2002
1924
  /**
2003
- * Update an existing session with partial data.
2004
- *
2005
- * Commonly used to update lastActivityAt or add custom fields.
2006
- *
2007
- * @param sid - The session.sid to update
2008
- * @param data - Partial session data to merge
2009
- * @throws Error if session not found
1925
+ * Optional prefix added to all computed secret names.
1926
+ * Useful to namespace secrets by app/environment.
2010
1927
  */
2011
- update(sid: string, data: Partial<Session<TExtended>>): Promise<void>;
1928
+ secretNamePrefix?: string;
2012
1929
  /**
2013
- * Delete a session by its IdP session ID (sid).
2014
- *
2015
- * Used for both normal logout and backchannel logout flows.
2016
- *
2017
- * @param sid - The session.sid to delete
1930
+ * OAuth2 scope; default is Key Vault resource scope.
1931
+ * Default: https://vault.azure.net/.default
2018
1932
  */
2019
- delete(sid: string): Promise<void>;
2020
- }
2021
- type SSOConfig<
2022
- TSessionData = Record<string, never>,
2023
- TUserData = Record<string, never>
2024
- > = {
2025
- authority?: string;
2026
- tokenUrl?: string;
2027
- authorizationUrl?: string;
2028
- clientId?: string;
2029
- clientSecret?: string;
2030
- redirectUri?: string;
2031
- responseType?: "code";
2032
1933
  scope?: string;
2033
- silentRedirectUri?: string;
2034
- jwksUri?: string;
2035
- cookiesPrefix?: string;
2036
- cookiesPath?: string;
2037
- cookiesSecure?: boolean;
2038
- cookiesSameSite?: "Strict" | "Lax";
2039
1934
  /**
2040
- * Optional cookie name for tracking the active session across tenants.
2041
- * Defaults to 'es.active_session' when using the helper utilities.
2042
- */
2043
- activeSessionCookieName?: string;
2044
- endSessionEndpoint?: string;
2045
- revocationEndpoint?: string;
2046
- sessionStore?: SessionStore<TSessionData>;
2047
- /**
2048
- * Optional user store for persisting user profiles from SSO authentication.
2049
- * When configured, users are automatically stored/updated on each login.
2050
- */
2051
- userStore?: UserStore<TUserData>;
2052
- /**
2053
- * Enable Just-In-Time (JIT) user provisioning.
2054
- * When enabled, new users are automatically created in the userStore on their first login.
2055
- * When disabled (default), only existing users in the userStore are updated on login.
2056
- * Requires userStore to be configured.
2057
- * @default false
1935
+ * When set, the vault implements subscribe: poll this path every ttl seconds and call onConfig when config changes.
2058
1936
  */
2059
- enableJitUserProvisioning?: boolean;
2060
- validators?: SSOValidators;
2061
- } & SSOHandlerConfig;
2062
- type StateCookie = {
2063
- state: string;
2064
- codeVerifier: string;
2065
- landingUrl: string;
2066
- errorUrl?: string;
1937
+ ttl?: number;
2067
1938
  };
2068
- type CookieOptions = {
2069
- cookieName?: string;
2070
- path?: string;
2071
- maxAge?: number;
2072
- secure?: boolean;
2073
- sameSite?: "Strict" | "Lax";
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;
2074
1946
  };
2075
- declare function getActiveSession(request: Request, options?: CookieOptions): string | undefined;
2076
- declare function setActiveSession(clientId: string, options?: CookieOptions): string;
2077
- declare function clearActiveSession(options?: CookieOptions): string;
2078
- declare function listSsoClientIdsFromCookies(requestOrCookieHeader: Request | string | null | undefined, options?: {
2079
- cookiePrefix?: string;
2080
- }): string[];
2081
- declare function findTenantFromStateParam(cookieHeader: string | null | undefined, stateParam: string): {
2082
- clientId: string;
2083
- stateCookie: StateCookie;
2084
- } | undefined;
2085
- type LoginConfig = {
2086
- landingUrl: string;
2087
- errorUrl?: string;
1947
+ type ApplicationValidators<TTenantValidators extends TenantValidators = TenantValidators> = ESValidators & {
1948
+ tenant: TTenantValidators;
2088
1949
  };
2089
- type SSOHandlerConfig = {
2090
- loginUrl?: string;
2091
- userUrl?: string;
2092
- errorUrl?: string;
2093
- landingUrl?: string;
2094
- tokenUrl?: string;
2095
- refreshUrl?: string;
2096
- jwksUrl?: string;
2097
- logoutUrl?: string;
2098
- logoutBackChannelUrl?: string;
2099
- };
2100
- type SSOValidators = {
2101
- callbackParams: StandardSchemaV16<unknown, OidcCallbackParams>;
2102
- idTokenClaims: StandardSchemaV16<unknown, IdTokenClaims>;
2103
- tokenResponse: StandardSchemaV16<unknown, TokenResponse>;
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;
2104
1967
  };
2105
- type SSO<
2106
- TSessionData = Record<string, never>,
2107
- TUserData = Record<string, never>
2108
- > = SSOConfig<TSessionData, TUserData> & {
2109
- getUser: (request: Request) => Promise<User2 | undefined>;
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;
2110
1982
  /**
2111
- * Read the SSO user from cookies without ever issuing an IdP refresh-token round
2112
- * trip. Returns `undefined` when access cookies are missing, the control cookie
2113
- * reports expiry in the past, or JWT verification fails. Intended for multi-session
2114
- * listing paths where N candidates must not silently fan out N IdP requests.
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!!!
2115
1987
  */
2116
- getUserNoRefresh: (request: Request) => Promise<User2 | undefined>;
2117
- getRequiredUser: (request: Request) => Promise<User2>;
2118
- getJwt: (request: Request) => Promise<string | undefined>;
2119
- initiateLogin: (config: LoginConfig, requestUrl?: string) => Promise<Response>;
2120
- logout: (request: Request, config?: LoginConfig) => Promise<Response>;
2121
- logoutBackChannel2: (request: Request) => Promise<Response>;
2122
- callbackHandler: (request: Request) => Promise<Response>;
2123
- handler: (request: Request) => Promise<Response>;
2124
- };
2125
- import { StandardSchemaV1 as StandardSchemaV17 } from "@standard-schema/spec";
2126
- type Secret<T> = {
2127
- data: T;
2128
- metadata: MetaData;
1988
+ workload?: WorkloadConfig | WorkloadConfigMap | WorkloadIncomingOutgoing;
1989
+ /** Optional named secrets-source configs available to this ESA instance. */
1990
+ secrets?: SecretsModuleConfig;
1991
+ ciam?: CIAMConfig;
2129
1992
  };
2130
- type MetaData = {
2131
- path: string;
2132
- version: number;
2133
- created: Date;
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;
2134
2002
  };
2135
- type SecretsSourceType = "vault" | "azure" | "aws" | "gcp" | "dev";
2136
- type SecretRequestSeverity = "low" | "medium" | "high" | "critical";
2137
- type SecretLifecycleRequest = {
2138
- reason?: string;
2139
- severity?: SecretRequestSeverity;
2140
- incidentRef?: string;
2003
+ type ModifiableFrameworkConfig = FrameworkConfig & {
2004
+ setStores(stores: FrameworkStores): void;
2141
2005
  };
2142
- type SecretsOperationOptions = {
2143
- /** Optional timeout in milliseconds for this secrets operation. */
2144
- timeout?: number;
2006
+ /** Return type from the beforeChange hook passed to enterpriseStandard(). */
2007
+ type ESConfigChangeResult = {
2008
+ config?: RemoteConfig;
2009
+ frameworkConfig?: FrameworkConfig;
2145
2010
  };
2146
- type SecretsSource = ReactiveHandle & {
2147
- type: SecretsSourceType;
2148
- getFullSecret: <T>(path: string, options?: SecretsOperationOptions) => Promise<Secret<T>>;
2149
- getSecret: <T>(path: string, options?: SecretsOperationOptions) => Promise<T>;
2150
- /**
2151
- * Write a secret at the given path.
2152
- * Implementations may throw for read-only or unsupported secret sources.
2153
- */
2154
- putSecret: (path: string, value: Record<string, unknown>, options?: SecretsOperationOptions) => Promise<void>;
2155
- /**
2156
- * When set, the secret source supports change detection and will call onChange when the secret changes.
2157
- * Implementations must only invoke onChange when the secret version has changed from the last time
2158
- * that subscription was notified (same version must not trigger a second call).
2159
- * @returns a unsubscribe/cleanup function.
2160
- */
2161
- subscribe<T>(path: string, onChange: (fullSecret: Secret<T>) => void): () => void;
2162
- /**
2163
- * Deletes a secret at the given path.
2164
- */
2165
- deleteSecret(path: string, options?: SecretsOperationOptions): Promise<void>;
2166
- /**
2167
- * Lists child paths under the given base path.
2168
- */
2169
- listPaths(path: string, options?: SecretsOperationOptions): Promise<string[]>;
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>;
2170
2015
  /**
2171
- * Returns true when a secret path exists.
2016
+ * Called when the config changes.
2017
+ * @param onConfig - The callback to call when the config changes.
2018
+ * @return an unsubscribe/cleanup function.
2172
2019
  */
2173
- exists(path: string, options?: SecretsOperationOptions): Promise<boolean>;
2020
+ subscribe(onConfig: (config: RemoteConfig) => void): undefined | (() => void);
2174
2021
  /**
2175
- * Requests secret rotation for the given path.
2022
+ * Default secret client for the config source itself.
2023
+ * For vault-backed sources this is the vault used to read RemoteConfig.
2176
2024
  */
2177
- requestRotate(path: string, request?: SecretLifecycleRequest, options?: SecretsOperationOptions): Promise<void>;
2025
+ secret: SecretsSource;
2178
2026
  /**
2179
- * Requests secret revocation for the given path.
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`).
2180
2029
  */
2181
- requestRevoke(path: string, request?: SecretLifecycleRequest, options?: SecretsOperationOptions): Promise<void>;
2030
+ log?: Logger;
2182
2031
  /**
2183
- * Reads metadata for the given path.
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.
2184
2034
  */
2185
- getMetadata(path: string, options?: SecretsOperationOptions): Promise<Record<string, unknown>>;
2035
+ validators?: ESValidators;
2186
2036
  };
2187
- type SecretsValidators = {
2188
- /**
2189
- * Optional hook to validate merged source configs before they are resolved.
2190
- * Throw from this callback to reject invalid secrets source configuration.
2191
- */
2192
- validateSourceConfig?(sourceName: string, config: SecretsSourceConfig): void;
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;
2193
2048
  };
2194
- type Secrets = ReactiveHandle & {
2195
- /** Named secrets sources client configurations from RemoteConfig. */
2196
- config: SecretsSourceMap;
2197
- /** Returns configured secrets source names/keys. */
2198
- listSecretsSources(): string[];
2199
- /** Gets a named secrets source client. Throws when missing. */
2200
- getSecretsSource(sourceName: string): SecretsSource;
2201
- /** Reads a secret from a named secrets source client. */
2202
- getSecret<T>(sourceName: string, path: string, options?: SecretsOperationOptions): Promise<T>;
2203
- /** Reads full secret data + metadata from a named secrets source client. */
2204
- getFullSecret<T>(sourceName: string, path: string, options?: SecretsOperationOptions): Promise<Secret<T>>;
2205
- /** Writes a secret to a named secrets source client. */
2206
- putSecret(sourceName: string, path: string, value: Record<string, unknown>, options?: SecretsOperationOptions): Promise<void>;
2207
- /** Deletes a secret from a named secrets source client. */
2208
- deleteSecret(sourceName: string, path: string, options?: SecretsOperationOptions): Promise<void>;
2209
- /** Lists child paths under a base path for a named secrets source client. */
2210
- listPaths(sourceName: string, path: string, options?: SecretsOperationOptions): Promise<string[]>;
2211
- /** Returns true when a path exists for a named secrets source client. */
2212
- exists(sourceName: string, path: string, options?: SecretsOperationOptions): Promise<boolean>;
2213
- /** Requests rotation for a secret path in a named secrets source client. */
2214
- requestRotate(sourceName: string, path: string, request?: SecretLifecycleRequest, options?: SecretsOperationOptions): Promise<void>;
2215
- /** Requests revocation for a secret path in a named secrets source client. */
2216
- requestRevoke(sourceName: string, path: string, request?: SecretLifecycleRequest, options?: SecretsOperationOptions): Promise<void>;
2217
- /** Reads metadata for a secret path in a named secrets source client. */
2218
- getMetadata(sourceName: string, path: string, options?: SecretsOperationOptions): Promise<Record<string, unknown>>;
2219
- /** Subscribes to secret changes on a named secrets source client. */
2220
- subscribe<T>(sourceName: string, path: string, onChange: (fullSecret: Secret<T>) => void): () => void;
2221
- /** Returns true when request matches any configured LFV delivery path. */
2222
- isLfvDeliveryRequest?(request: Request): boolean;
2223
- /** Returns true when request matches any configured LFV events path. */
2224
- isLfvEventsRequest?(request: Request): boolean;
2225
- /** Handles LFV delivery callbacks for configured LFV sources. */
2226
- handleLfvDelivery?(request: Request): Promise<Response>;
2227
- /** Handles LFV events callbacks for configured LFV sources. */
2228
- handleLfvEvents?(request: Request): Promise<Response>;
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[];
2229
2061
  };
2062
+ type CreateTenantRequest = UpsertTenantRequest;
2063
+ type CreateTenantResponse = UpsertTenantResponse;
2230
2064
  /**
2231
- * Partial secrets source config used in framework/app code to declare expected source names.
2232
- * ConfigSource-backed values may still provide the actual source details at runtime.
2233
- */
2234
- type FrameworkSecretsSourceConfig = Partial<SecretsSourceConfig>;
2235
- /**
2236
- * Framework-level named secrets source declarations keyed by source name.
2237
- * Values may be partial or empty when the app only wants to declare expected names/types.
2238
- */
2239
- type FrameworkSecretsModuleConfig = Record<string, FrameworkSecretsSourceConfig>;
2240
- /**
2241
- * TODO: Let's see if we can do some clean inference and remove this!!!
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.
2242
2069
  */
2243
- type SecretsSourceMap = Record<string, SecretsSource>;
2244
- type SecretsSourceConfig = DevSecretsConfig | GcpSecretsConfig | VaultSecretsConfig | AwsSecretsConfig | AzureSecretsConfig;
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
+ };
2245
2105
  /**
2246
- * Raw module config keyed by source name.
2247
- * The secrets module resolves this into a runtime SecretsSourceMap.
2248
- */
2249
- type SecretsModuleConfig = Record<string, SecretsSourceConfig>;
2250
- type DevSecretsConfig = {
2251
- type: "dev";
2252
- ioniteUrl: string;
2253
- };
2254
- type GcpSecretsConfig = {
2255
- type: "gcp";
2256
- };
2257
- type VaultLfvSecretsConfig = {
2258
- /** LFV server base URL for OTP/action endpoints. */
2259
- lfvServerUrl?: string;
2260
- /** LFV client id used for OTP issuance. */
2261
- clientId?: string;
2262
- /** Signature value for X-LFV-Signature header. */
2263
- signature?: string;
2264
- deliveryEndpoint: string;
2265
- /**
2266
- * Optional LFV signature verification key.
2267
- * When omitted, LFV callbacks are accepted without signature verification.
2268
- */
2269
- verifyPublicKey?: string;
2270
- eventsEndpoint: string;
2271
- path?: string;
2272
- /**
2273
- * Timeout in milliseconds when waiting for a LFV delivery payload after a read request.
2274
- * After this duration, an error is thrown.
2275
- */
2276
- deliveryTimeout?: number;
2277
- /** Retry interval in milliseconds between LFV transport retry attempts. */
2278
- retryInterval?: number;
2279
- /** Warning interval in milliseconds for LFV retry logs. Set to 0 to disable warnings. */
2280
- warnInterval?: number;
2281
- /**
2282
- * Optional logger for request/response tracing. Use `debugLogger` from `@enterprisestandard/core`
2283
- * to get debug output with request_id for LFV operations.
2284
- */
2285
- log?: Logger;
2286
- };
2287
- /**
2288
- * Runtime-ready LFV source config.
2289
- * Input config can be partially declared/merged, but LFV operations require these fields.
2290
- */
2291
- type ResolvedVaultLfvSecretsConfig = Omit<VaultLfvSecretsConfig, "lfvServerUrl" | "clientId" | "path"> & {
2292
- lfvServerUrl: string;
2293
- clientId: string;
2294
- path: string;
2295
- };
2296
- type VaultWebSocketAuthHeader = "X-Vault-Token" | "Authorization";
2297
- type VaultWebSocketSecretsConfig = {
2298
- /** Websocket URL for vault command execution and live secret subscriptions. */
2299
- url?: string;
2300
- /** Token used during websocket connect/auth. */
2301
- token?: string;
2302
- /** Header name used to send the websocket token. Defaults to X-Vault-Token. */
2303
- header?: VaultWebSocketAuthHeader;
2304
- };
2305
- type VaultSecretsConfig = {
2306
- type: "vault";
2307
- url?: string;
2308
- token?: string;
2309
- /** Optional LFV transport capability for reads/lifecycle operations. */
2310
- lfv?: VaultLfvSecretsConfig;
2311
- /** Optional websocket capability for vault commands and live subscriptions. */
2312
- websocket?: VaultWebSocketSecretsConfig;
2313
- /**
2314
- * MINIMUM: 600_000 milliseconds (10 minutes). Polls the path every ttl milliseconds and calls onConfig when config changes.
2315
- */
2316
- ttl?: number;
2317
- };
2318
- type AwsSecretsConfig = {
2319
- type: "aws";
2320
- webhookUrl: string;
2321
- };
2322
- type AwsAuthMethod = "client_secret" | "workload_identity" | "managed_identity";
2323
- type AzureSecretsConfig = {
2324
- type: "azure";
2325
- /**
2326
- * How to authenticate to Azure to fetch Key Vault access tokens.
2327
- *
2328
- * - client_secret: Entra app client secret (client credentials)
2329
- * - workload_identity: AKS Workload Identity (federated token file)
2330
- * - managed_identity: Azure Managed Identity via IMDS
2331
- *
2332
- * If omitted, we auto-detect:
2333
- * - workload_identity if a federated token file is present
2334
- * - else client_secret if a clientSecret is present
2335
- * - else managed_identity
2336
- *
2337
- * Env: ES_AZURE_AUTH_METHOD
2338
- */
2339
- authMethod?: AwsAuthMethod;
2340
- /**
2341
- * Azure Entra tenant id (GUID), used for OAuth2 token exchange.
2342
- * Env: ES_AZURE_TENANT_ID
2343
- */
2344
- tenantId?: string;
2345
- /**
2346
- * Azure app registration client id (GUID).
2347
- * Env: ES_AZURE_CLIENT_ID
2348
- */
2349
- clientId?: string;
2350
- /**
2351
- * Azure app registration client secret.
2352
- * Env: ES_AZURE_CLIENT_SECRET
2353
- */
2354
- clientSecret?: string;
2355
- /**
2356
- * Path to the projected federated token file (AKS Workload Identity).
2357
- * Env: ES_AZURE_FEDERATED_TOKEN_FILE or AZURE_FEDERATED_TOKEN_FILE
2358
- */
2359
- federatedTokenFile?: string;
2360
- /**
2361
- * Managed Identity client id (user-assigned), optional.
2362
- * Env: ES_AZURE_MANAGED_IDENTITY_CLIENT_ID
2363
- */
2364
- managedIdentityClientId?: string;
2365
- /**
2366
- * IMDS API version (managed identity).
2367
- * Env: ES_AZURE_IMDS_API_VERSION
2368
- * Default: 2018-02-01
2369
- */
2370
- imdsApiVersion?: string;
2371
- /**
2372
- * Key Vault URL, e.g. https://myvault.vault.azure.net
2373
- * Env: ES_AZURE_KEY_VAULT_URL
2374
- */
2375
- vaultUrl?: string;
2376
- /**
2377
- * Alternative to vaultUrl. If provided, vaultUrl is derived as:
2378
- * https://${vaultName}.vault.azure.net
2379
- * Env: ES_AZURE_KEY_VAULT_NAME
2380
- */
2381
- vaultName?: string;
2382
- /**
2383
- * Key Vault API version.
2384
- * Env: ES_AZURE_KEY_VAULT_API_VERSION
2385
- * Default: 7.4
2386
- */
2387
- apiVersion?: string;
2388
- /**
2389
- * Maps vault "path" to a Key Vault secret name.
2390
- * Default: replaces `/` with `--` and trims leading/trailing `/`.
2391
- */
2392
- secretNameTransform?: (path: string) => string;
2393
- /**
2394
- * Optional prefix added to all computed secret names.
2395
- * Useful to namespace secrets by app/environment.
2396
- */
2397
- secretNamePrefix?: string;
2398
- /**
2399
- * OAuth2 scope; default is Key Vault resource scope.
2400
- * Default: https://vault.azure.net/.default
2401
- */
2402
- scope?: string;
2403
- /**
2404
- * When set, the vault implements subscribe: poll this path every ttl seconds and call onConfig when config changes.
2405
- */
2406
- ttl?: number;
2407
- };
2408
- type EnvironmentType = "POC" | "DEV" | "QA" | "PROD";
2409
- type TenantStatus = "pending" | "processing" | "completed" | "failed" | "action_required";
2410
- interface UpsertTenantRequest {
2411
- tenantId: string;
2412
- companyId: string;
2413
- companyName: string;
2414
- environmentType: EnvironmentType;
2415
- email?: string;
2416
- webhookUrl?: string;
2417
- callbackUrl?: string;
2418
- configSource: TenantSecretsConfig;
2419
- }
2420
- type UpsertTenantResponse = {
2421
- tenantUrl?: string;
2422
- status: Exclude<TenantStatus, "action_required">;
2423
- error?: string;
2424
- refs?: RefUrls[];
2425
- } | {
2426
- status: "action_required";
2427
- actionUrl: string;
2428
- requestToken: string;
2429
- expiresAt: string;
2430
- refs?: RefUrls[];
2431
- };
2432
- type CreateTenantRequest = UpsertTenantRequest;
2433
- type CreateTenantResponse = UpsertTenantResponse;
2434
- /**
2435
- * The audience of the reference URL.
2436
- * - 'human' for human-readable documentation such as user guides, documentation, etc.
2437
- * - 'ai' for AI-generated reference such as llms.txt, AI-optimized markdown, etc.
2438
- * - 'spec' for specifications and code-gen ready docs such as OpenAPI, GraphQL, etc.
2439
- */
2440
- type Audience = "human" | "ai" | "spec";
2441
- interface RefUrls {
2442
- audience: Audience;
2443
- url: string;
2444
- description: string;
2445
- createdAt: Date;
2446
- updatedAt: Date;
2447
- }
2448
- interface TenantWebhookPayload {
2449
- tenantId: string;
2450
- companyId: string;
2451
- status: TenantStatus;
2452
- tenantUrl?: string;
2453
- actionUrl?: string;
2454
- requestToken?: string;
2455
- expiresAt?: string;
2456
- error?: string;
2457
- }
2458
- declare class TenantRequestError extends Error {
2459
- constructor(message: string, options?: ErrorOptions);
2460
- }
2461
- declare class MultipleTenantsForUserError extends Error {
2462
- readonly userId: string;
2463
- readonly tenantIds: string[];
2464
- constructor(userId: string, tenantIds: string[], options?: ErrorOptions);
2465
- }
2466
- type UserMode = "singleTenantOnly" | "multipleTenantsPerUser";
2467
- type TenantValidators = {
2468
- upsertTenantRequest: StandardSchemaV17<unknown, UpsertTenantRequest>;
2469
- upsertTenantResponse?: StandardSchemaV17<unknown, UpsertTenantResponse>;
2470
- createTenantRequest?: StandardSchemaV17<unknown, UpsertTenantRequest>;
2471
- createTenantResponse?: StandardSchemaV17<unknown, UpsertTenantResponse>;
2472
- };
2473
- /**
2474
- * Env-like tenant config variables used to build a ConfigSource at runtime.
2475
- * These mirror the ES_* variables read by envConfig().
2106
+ * Env-like tenant config variables used to build a ConfigSource at runtime.
2107
+ * These mirror the ES_* variables read by envConfig().
2476
2108
  */
2477
2109
  type TenantConfigEnv = {
2478
2110
  ES_CONFIG_TYPE?: ConfigSourceType;
@@ -2554,317 +2186,657 @@ type TenantBaseRecord = {
2554
2186
  /** Persisted tenant config metadata, or a runtime ConfigSource for internal-only tenants. */
2555
2187
  configSource: TenantConfigSourceInput;
2556
2188
  /** Runtime helper that returns a ConfigSource for this tenant. */
2557
- config: (source?: SecretsSource) => ConfigSource;
2558
- };
2559
- type StoredTenant<TExtended extends object = Record<string, never>> = TenantBaseRecord & TExtended;
2560
- type StoredTenantRecord<TExtended extends object = Record<string, never>> = Omit<StoredTenant<TExtended>, "config">;
2561
- type TenantEsFactory<TExtended extends object = Record<string, never>> = (tenant: StoredTenant<TExtended>) => EnterpriseStandard;
2562
- type TenantConfigStoreRequest<TExtended extends object = Record<string, never>> = {
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
+ > = {
2563
2202
  es: EnterpriseStandard;
2564
2203
  tenantId: string;
2565
- request: UpsertTenantRequest;
2204
+ request: TRequest;
2566
2205
  configData: TenantSecretsConfig;
2567
- existingTenant: StoredTenant<TExtended> | null;
2206
+ existingTenant: StoredTenant<TTenant> | undefined;
2207
+ };
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"];
2568
2237
  };
2569
- type TenantStoreWithESOptions<TExtended extends object = Record<string, never>> = {
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;
2321
+ /**
2322
+ * Optional SCIM email collection.
2323
+ * The simple `email` field still stores the primary email for convenience.
2324
+ */
2325
+ emails?: Email[];
2326
+ /**
2327
+ * Optional SCIM nickname.
2328
+ * Commonly set by IAM inbound SCIM provisioning flows.
2329
+ */
2330
+ nickName?: string;
2331
+ /**
2332
+ * Optional SCIM account state.
2333
+ * Commonly set by IAM inbound SCIM provisioning flows.
2334
+ */
2335
+ active?: boolean;
2336
+ /**
2337
+ * Optional SCIM job title.
2338
+ * Commonly set by IAM inbound SCIM provisioning flows.
2339
+ */
2340
+ title?: string;
2341
+ /**
2342
+ * Optional SCIM preferred language.
2343
+ * Commonly set by IAM inbound SCIM provisioning flows.
2344
+ */
2345
+ preferredLanguage?: string;
2346
+ /**
2347
+ * Optional SCIM locale.
2348
+ * Commonly set by IAM inbound SCIM provisioning flows.
2349
+ */
2350
+ locale?: string;
2351
+ /**
2352
+ * Optional SCIM timezone.
2353
+ * Commonly set by IAM inbound SCIM provisioning flows.
2354
+ */
2355
+ timezone?: string;
2356
+ /**
2357
+ * Optional SCIM phone numbers.
2358
+ * Commonly set by IAM inbound SCIM provisioning flows.
2359
+ */
2360
+ phoneNumbers?: PhoneNumber[];
2361
+ /**
2362
+ * Optional SCIM instant messaging addresses.
2363
+ * Commonly set by IAM inbound SCIM provisioning flows.
2364
+ */
2365
+ ims?: Array<{
2366
+ value: string;
2367
+ display?: string;
2368
+ type?: string;
2369
+ primary?: boolean;
2370
+ }>;
2371
+ /**
2372
+ * Optional SCIM photos.
2373
+ * Commonly set by IAM inbound SCIM provisioning flows.
2374
+ */
2375
+ photos?: Photo[];
2376
+ /**
2377
+ * Optional SCIM addresses.
2378
+ * Commonly set by IAM inbound SCIM provisioning flows.
2379
+ */
2380
+ addresses?: Address[];
2381
+ /**
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.
2389
+ */
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;
2435
+ };
2436
+ /**
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
+ * ```
2463
+ */
2464
+ interface UserStore<TExtended = object> {
2465
+ /**
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
2473
+ */
2474
+ get(sub: string): Promise<StoredUser<TExtended> | undefined>;
2475
+ /**
2476
+ * Retrieve a user by their username.
2477
+ *
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.
2484
+ *
2485
+ * If a user with the same `id` (sub) exists, it will be updated.
2486
+ * Otherwise, a new user will be created.
2487
+ *
2488
+ * @param user - The user data to store
2489
+ */
2490
+ upsert(user: StoredUser<TExtended>): Promise<StoredUser<TExtended>>;
2491
+ /**
2492
+ * Delete a user by their subject identifier (sub).
2493
+ *
2494
+ * @param sub - The user's unique identifier to delete
2495
+ */
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 {
2537
+ /**
2538
+ * Base path for the SCIM Users endpoints (e.g., '/api/iam/Users')
2539
+ */
2540
+ usersUrl?: string;
2541
+ /**
2542
+ * Base path for the SCIM Groups endpoints (e.g., '/api/iam/Groups')
2543
+ */
2544
+ groupsUrl?: string;
2545
+ /**
2546
+ * Handler overrides for SCIM discovery endpoints (e.g., '/api/iam/ServiceProviderConfig')
2547
+ */
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 = {
2558
+ /**
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)
2561
+ */
2562
+ url?: string;
2563
+ /**
2564
+ * Store for inbound user provisioning from external IAM providers.
2565
+ * When configured, the app can receive user CRUD operations via SCIM.
2566
+ */
2567
+ userStore?: UserStore;
2568
+ /**
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.
2572
+ */
2573
+ inboundUsers?: IAMInboundUsersConfig;
2574
+ /**
2575
+ * Store for inbound group provisioning from external IAM providers.
2576
+ * When configured, enables groups_inbound (external IAM -> app).
2577
+ */
2578
+ groupStore?: GroupStore;
2579
+ /**
2580
+ * Optional handler defaults. These are merged with per-call overrides in
2581
+ * `iam.handler`, with per-call values taking precedence.
2582
+ */
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 {
2592
+ /**
2593
+ * Current stored user when replacing or patching an existing resource.
2594
+ */
2595
+ existing?: StoredUser;
2596
+ /**
2597
+ * Operation mode for the inbound mapper.
2598
+ */
2599
+ mode: "create" | "replace" | "patch";
2600
+ }
2601
+ interface IAMInboundUsersConfig {
2602
+ /**
2603
+ * Replace the default validated SCIM -> StoredUser mapper.
2604
+ */
2605
+ mapValidatedScimToStoredUser?: (validated: User, context: IAMInboundUserContext) => StoredUser | Promise<StoredUser>;
2606
+ /**
2607
+ * Replace the default StoredUser -> SCIM response mapper.
2608
+ */
2609
+ mapStoredUserToScim?: (stored: StoredUser) => User | Promise<User>;
2610
+ }
2611
+ interface ScimAuthenticationScheme {
2612
+ type: string;
2613
+ name: string;
2614
+ description?: string;
2615
+ specUri?: string;
2616
+ documentationUri?: string;
2617
+ primary?: boolean;
2618
+ }
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
+ };
2648
+ }
2649
+ interface ScimResourceTypeSchemaExtension {
2650
+ schema: string;
2651
+ required: boolean;
2652
+ }
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 {
2699
+ /**
2700
+ * Public IAM base path used for SCIM discovery. When omitted, the SDK derives
2701
+ * it from `usersUrl` or `groupsUrl`.
2702
+ */
2703
+ basePath?: string;
2570
2704
  /**
2571
- * TTL for cached per-tenant EnterpriseStandard instances, in milliseconds.
2572
- * Default is forever; set to 0 to recreate ES on every getEs() call.
2705
+ * Optional documentation URI advertised in ServiceProviderConfig.
2573
2706
  */
2574
- ttl?: number;
2707
+ documentationUri?: string;
2575
2708
  /**
2576
- * Optional factory used to create an ES instance for a tenant.
2577
- * If omitted, getEs() throws.
2709
+ * Override the default ServiceProviderConfig response.
2578
2710
  */
2579
- createEs?: TenantEsFactory<TExtended>;
2580
- };
2581
- type TenantMetadataRecord = Omit<TenantBaseRecord, "tenantId" | "config" | "configSource" | "status" | "createdAt" | "updatedAt">;
2582
- type TenantExtendedUpsertFields<TExtended extends object> = string extends keyof TExtended ? unknown : Partial<TExtended>;
2583
- type TenantStoreUpsertRecord<TExtended extends object = Record<string, never>> = Pick<TenantBaseRecord, "tenantId" | "configSource"> & Partial<TenantMetadataRecord> & Partial<Pick<TenantBaseRecord, "status" | "createdAt" | "updatedAt">> & TenantExtendedUpsertFields<TExtended>;
2584
- type TenantUserRegistration<TMode extends UserMode = UserMode> = {
2585
- userMode: TMode;
2586
- registerUserTenantId?(userId: string, tenantId: string | null | undefined): void | Promise<void>;
2587
- };
2588
- type TenantStoreBase<
2589
- TMode extends UserMode = "singleTenantOnly",
2590
- TExtended extends object = Record<string, never>
2591
- > = TenantUserRegistration<TMode> & {
2592
- storeConfig(config: TenantConfigStoreRequest<TExtended>): Promise<TenantConfigSourceInput>;
2593
- get(tenantId: string): Promise<StoredTenant<TExtended> | null>;
2594
- list(options?: TenantListOptions): Promise<ListResult<StoredTenant<TExtended>>>;
2595
- upsert(tenant: TenantStoreUpsertRecord<TExtended>): Promise<StoredTenant<TExtended>>;
2596
- delete(tenantId: string): Promise<void>;
2597
- };
2598
- type TenantLookupMethods<
2599
- TMode extends UserMode,
2600
- TExtended extends object
2601
- > = TMode extends "singleTenantOnly" ? {
2602
- findTenantByUserId(userId: string): Promise<StoredTenant<TExtended> | null>;
2603
- findTenantsByUserIds?: never;
2604
- } : {
2605
- findTenantByUserId?: never;
2606
- /**
2607
- * Batched HRD lookup. Resolves the tenants associated with each user id and
2608
- * returns a `Map` keyed by the original `userId`. Implementations MUST handle
2609
- * up to {@link MAX_TENANTS_BATCH_USER_IDS} ids per call (matches
2610
- * `MAX_SSO_DISCOVERY_CANDIDATES`); larger inputs are a programming error.
2611
- *
2612
- * **Per-engine guidance for store authors:**
2613
- *
2614
- * - Modern engines (PostgreSQL, MySQL/MariaDB, SQLite, MS SQL Server): a single
2615
- * parameterized `WHERE user_id IN (?, ?, ...)` is fine at this batch size.
2616
- * - PostgreSQL specifically: prefer `WHERE user_id = ANY($1::text[])` to keep
2617
- * one prepared-statement plan in cache.
2618
- * - Oracle (≤19c with the 1000-element `IN`-list cap): 32 is well within range;
2619
- * if you ever raise this cap upstream, chunk by 1000.
2620
- * - IBM DB2 / strict engines with low `IN`-list or 32 KB statement-text limits:
2621
- * safe at 32, but if the cap is raised the implementation MUST chunk or fall
2622
- * back to N+1 lookups internally — never throw and never return partial
2623
- * results silently.
2624
- * - Always parameterize the `IN` value type to match the `user_id` column
2625
- * (text vs numeric) to avoid implicit-conversion full table scans.
2626
- */
2627
- findTenantsByUserIds(userIds: string[]): Promise<Map<string, StoredTenant<TExtended>[]>>;
2628
- };
2629
- /**
2630
- * Hard upper bound on the number of user ids accepted by
2631
- * {@link TenantStore.findTenantsByUserIds} (and the public
2632
- * `getTenantsForUserIds`/`getSSOUsers` helpers). Tied to the SSO discovery cap so
2633
- * a single cookie-first browser request never exceeds this value.
2634
- *
2635
- * Treat this as an upper bound, not a target. See per-engine implementation
2636
- * guidance on `findTenantsByUserIds`.
2637
- */
2638
- declare const MAX_TENANTS_BATCH_USER_IDS = 32;
2639
- type TenantStore<
2640
- TMode extends UserMode = "singleTenantOnly",
2641
- TExtended extends object = Record<string, never>
2642
- > = TenantStoreBase<TMode, TExtended> & TenantLookupMethods<TMode, TExtended>;
2643
- type TenantStoreWithES<
2644
- TMode extends UserMode = "singleTenantOnly",
2645
- TExtended extends object = Record<string, never>
2646
- > = TenantStore<TMode, TExtended> & {
2647
- ttl: number;
2648
- getEs(tenantId: string): Promise<EnterpriseStandard | null>;
2649
- getCachedTenantIds(): string[];
2650
- };
2651
- type InMemoryTenantStoreOptions<
2652
- TMode extends UserMode = "singleTenantOnly",
2653
- TExtended extends object = Record<string, never>
2654
- > = TenantStoreWithESOptions<TExtended> & {
2655
- userMode: TMode;
2656
- };
2657
- declare class InMemoryTenantStore<
2658
- TMode extends UserMode = "singleTenantOnly",
2659
- TExtended extends object = Record<string, never>
2660
- > {
2661
- private tenants;
2662
- private tenantEsMap;
2663
- private userTenantIds;
2664
- readonly ttl: number;
2665
- readonly userMode: TMode;
2666
- private readonly createEs?;
2667
- readonly findTenantByUserId: TMode extends "singleTenantOnly" ? (userId: string) => Promise<StoredTenant<TExtended> | null> : never;
2668
- readonly findTenantsByUserIds: TMode extends "multipleTenantsPerUser" ? (userIds: string[]) => Promise<Map<string, StoredTenant<TExtended>[]>> : never;
2669
- constructor(options: InMemoryTenantStoreOptions<TMode, TExtended>);
2670
- get(tenantId: string): Promise<StoredTenant<TExtended> | null>;
2671
- list(options?: TenantListOptions): Promise<ListResult<StoredTenant<TExtended>>>;
2672
- upsert(tenant: TenantStoreUpsertRecord<TExtended>): Promise<StoredTenant<TExtended>>;
2673
- delete(tenantId: string): Promise<void>;
2674
- getEs(tenantId: string): Promise<EnterpriseStandard>;
2675
- getCachedTenantIds(): string[];
2676
- registerUserTenantId(userId: string, tenantId: string | null | undefined): Promise<void>;
2677
- private findSingleTenantByUserId;
2678
- private findMultipleTenantsByUserIds;
2679
- private resolveTenantsByUserId;
2680
- }
2681
- declare function sendTenantWebhook(webhookUrl: string, payload: TenantWebhookPayload, log: Logger): Promise<void>;
2682
- /**
2683
- * Magic link data stored in the store.
2684
- *
2685
- * @template TExtended - Type-safe custom data that consumers can add to magic links
2686
- */
2687
- type MagicLink<TExtended = object> = {
2711
+ buildServiceProviderConfig?: (context: IAMDiscoveryContext, defaults: ScimServiceProviderConfig) => ScimServiceProviderConfig | Promise<ScimServiceProviderConfig>;
2688
2712
  /**
2689
- * The magic link token (unique identifier)
2713
+ * Override the default ResourceTypes response.
2690
2714
  */
2691
- token: string;
2715
+ buildResourceTypes?: (context: IAMDiscoveryContext, defaults: ScimResourceType[]) => ScimResourceType[] | Promise<ScimResourceType[]>;
2692
2716
  /**
2693
- * User information associated with this magic link
2717
+ * Override the default Schemas response.
2694
2718
  */
2695
- user: BaseUser;
2719
+ buildSchemas?: (context: IAMDiscoveryContext, defaults: ScimSchemaDefinition[]) => ScimSchemaDefinition[] | Promise<ScimSchemaDefinition[]>;
2720
+ }
2721
+ /**
2722
+ * Options for creating a user
2723
+ */
2724
+ interface CreateUserOptions {
2696
2725
  /**
2697
- * Timestamp when the magic link was created
2726
+ * External identifier for the user
2698
2727
  */
2699
- createdAt: Date;
2728
+ externalId?: string;
2729
+ }
2730
+ /**
2731
+ * Options for creating a group
2732
+ */
2733
+ interface CreateGroupOptions {
2700
2734
  /**
2701
- * Timestamp when the magic link expires
2735
+ * External identifier for the group
2702
2736
  */
2703
- expiresAt: Date;
2737
+ externalId?: string;
2704
2738
  /**
2705
- * Allow consumers to add runtime data to magic links
2739
+ * Initial members to add to the group
2706
2740
  */
2707
- [key: string]: unknown;
2708
- } & TExtended;
2741
+ members?: GroupMember[];
2742
+ }
2709
2743
  /**
2710
- * Abstract interface for magic link storage backends.
2711
- *
2712
- * Consumers can implement this interface to use different storage backends:
2713
- * - In-memory (for development/testing)
2714
- * - Redis (for production with fast lookups and automatic expiration)
2715
- * - Database (PostgreSQL, MySQL, etc.)
2716
- *
2717
- * @template TExtended - Type-safe custom data that consumers can add to magic links
2718
- *
2719
- * @example
2720
- * ```typescript
2721
- * // Custom magic link data
2722
- * type MyMagicLinkData = {
2723
- * source: string;
2724
- * metadata: Record<string, unknown>;
2725
- * };
2726
- *
2727
- * // Implement custom store
2728
- * class RedisMagicLinkStore implements MagicLinkStore<MyMagicLinkData> {
2729
- * async create(token: string, user: BaseUser, expiresAt: Date): Promise<void> {
2730
- * const magicLink: MagicLink<MyMagicLinkData> = {
2731
- * token,
2732
- * user,
2733
- * createdAt: new Date(),
2734
- * expiresAt,
2735
- * source: 'api',
2736
- * metadata: {},
2737
- * };
2738
- * const ttl = Math.floor((expiresAt.getTime() - Date.now()) / 1000);
2739
- * await redis.setex(`magic-link:${token}`, ttl, JSON.stringify(magicLink));
2740
- * }
2741
- * // ... other methods
2742
- * }
2743
- * ```
2744
+ * Handler configuration for groups_inbound
2744
2745
  */
2745
- interface MagicLinkStore<TExtended = object> {
2746
+ interface GroupsInboundHandlerConfig {
2746
2747
  /**
2747
- * Create a new magic link in the store.
2748
- *
2749
- * @param token - The magic link token (unique identifier)
2750
- * @param user - The user information to associate with this magic link
2751
- * @param expiresAt - When the magic link expires
2752
- * @throws Error if magic link with same token already exists
2748
+ * Base path for the SCIM Groups endpoints (e.g., '/api/iam/Groups')
2753
2749
  */
2754
- create(token: string, user: BaseUser, expiresAt: Date): Promise<void>;
2750
+ basePath?: string;
2751
+ }
2752
+ /**
2753
+ * Handler configuration for users_inbound
2754
+ */
2755
+ interface UsersInboundHandlerConfig {
2755
2756
  /**
2756
- * Retrieve a magic link by its token.
2757
- *
2758
- * @param token - The magic link token
2759
- * @returns The magic link if found and not expired, null otherwise
2757
+ * Base path for the SCIM Users endpoints (e.g., '/api/iam/Users')
2760
2758
  */
2761
- get(token: string): Promise<MagicLink<TExtended> | null>;
2759
+ basePath?: string;
2760
+ }
2761
+ interface IAMDiscoveryHandlerConfig {
2762
2762
  /**
2763
- * Delete a magic link by its token.
2764
- *
2765
- * Used after a magic link has been consumed (one-time use).
2766
- *
2767
- * @param token - The magic link token to delete
2763
+ * Base path for the IAM discovery endpoints (e.g., '/api/iam')
2768
2764
  */
2769
- delete(token: string): Promise<void>;
2765
+ basePath?: string;
2770
2766
  }
2771
- type ConfigSourceType = "vault" | "azure" | "aws" | "gcp";
2772
- type ESValidators = {
2773
- sso: SSOValidators;
2774
- iam: IAMValidators;
2775
- workload: WorkloadValidators;
2776
- ciam: CIAMValidators;
2777
- secrets?: SecretsValidators;
2778
- };
2779
- type ApplicationValidators = ESValidators & {
2780
- tenant: TenantValidators;
2781
- };
2782
2767
  /**
2783
- * Configuration supplied by the framework/application when creating an Enterprise Standard instance.
2784
- * Merged with RemoteConfig from the ConfigSource (framework config wins). Pass as the second
2785
- * argument to enterpriseStandard(source, config).
2786
- * Set a module to `null` to explicitly disable it; then the corresponding property on the
2787
- * EnterpriseStandard instance is typed as `never`. Omit a module to allow it to be supplied
2788
- * 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.
2789
2770
  */
2790
- type FrameworkConfig = {
2791
- /** Optional `Logger` implementation (e.g. `consoleLogger`); exposed on the instance as `log`. */
2792
- log?: Logger;
2793
- sso?: SSOConfig | null;
2794
- iam?: IAMConfig | null;
2795
- workload?: FrameworkWorkloadConfig | null;
2796
- secrets?: FrameworkSecretsModuleConfig | null;
2797
- ciam?: CIAMConfig | null;
2798
- 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>>;
2799
2779
  };
2800
2780
  /**
2801
- * Final configuration after merging ConfigSource (RemoteConfig) and FrameworkConfig.
2802
- * Same shape as FrameworkConfig; each module (SSO, IAM, etc.) resolves its config from
2803
- * 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.
2804
2783
  */
2805
- 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
+ };
2806
2791
  /**
2807
- * 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.
2808
2794
  */
2809
- type RemoteConfig = {
2810
- /** Optional app/tenant identifier for this ESA (e.g. from vault path or config). */
2811
- tenantId?: string;
2812
- sso?: SSOConfig;
2813
- iam?: IAMConfig;
2795
+ type IAMUsersInbound = {
2814
2796
  /**
2815
- * Workload: single config, or incoming/outgoing (server vs client roles), or flat map of named clients.
2816
- * Preferred: { incoming: { jwksUri, issuer }, outgoing: { TNT_*: { clientId, ... } } }.
2817
- * Legacy: { default: { jwksUri, issuer }, TNT_*: { ... } } (flat map).
2818
- * 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
2819
2799
  */
2820
- workload?: WorkloadConfig | WorkloadConfigMap | WorkloadIncomingOutgoing;
2821
- /** Optional named secrets-source configs available to this ESA instance. */
2822
- secrets?: SecretsModuleConfig;
2823
- ciam?: CIAMConfig;
2800
+ handler: (request: Request, config?: UsersInboundHandlerConfig) => Promise<Response>;
2824
2801
  };
2825
2802
  /**
2826
- * 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
2827
2809
  */
2828
- type FrameworkStores = {
2829
- sessionStore?: SessionStore<unknown>;
2830
- userStore?: UserStore<unknown>;
2831
- groupStore?: GroupStore<unknown>;
2832
- magicLinkStore?: MagicLinkStore<unknown>;
2833
- workloadTokenStore?: WorkloadTokenStore;
2834
- };
2835
- type ModifiableFrameworkConfig = FrameworkConfig & {
2836
- setStores(stores: FrameworkStores): void;
2837
- };
2838
- /** Return type from the beforeChange hook passed to enterpriseStandard(). */
2839
- type ESConfigChangeResult = {
2840
- config?: RemoteConfig;
2841
- frameworkConfig?: FrameworkConfig;
2842
- };
2843
- /** beforeChange callback invoked on every config application (initial load and updates). */
2844
- type ESConfigChangeCallback = (config: RemoteConfig, frameworkConfig: ModifiableFrameworkConfig, oldConfig: RemoteConfig | undefined) => ESConfigChangeResult | void;
2845
- type ConfigSource = {
2846
- load(): Promise<RemoteConfig>;
2810
+ type IAM = IAMConfig & {
2847
2811
  /**
2848
- * Called when the config changes.
2849
- * @param onConfig - The callback to call when the config changes.
2850
- * @return an unsubscribe/cleanup function.
2812
+ * Create a new user/account in the external IAM provider
2813
+ * Only available when `url` is configured.
2851
2814
  */
2852
- subscribe(onConfig: (config: RemoteConfig) => void): undefined | (() => void);
2815
+ createUser?: (user: User, options?: CreateUserOptions) => Promise<ScimResult<User>>;
2853
2816
  /**
2854
- * Default secret client for the config source itself.
2855
- * For vault-backed sources this is the vault used to read RemoteConfig.
2817
+ * Get the configured external SCIM base URL
2856
2818
  */
2857
- secret: SecretsSource;
2819
+ getBaseUrl: () => string | undefined;
2858
2820
  /**
2859
- * Optional. If not set by the creator, the framework may set this before calling load/subscribe
2860
- * so the source can use the same logger as the Enterprise Standard instance (`log`).
2821
+ * Groups Outbound extension - create groups in external IAM provider.
2822
+ * Available when `url` is configured in IAMConfig.
2861
2823
  */
2862
- log?: Logger;
2824
+ groups_outbound?: IAMGroupsOutbound;
2863
2825
  /**
2864
- * Optional. If not set by the creator, the framework may set this before calling load/subscribe
2865
- * 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.
2866
2828
  */
2867
- 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>;
2868
2840
  };
2869
2841
  /**
2870
2842
  * Serializes a FrameworkConfig (or ESConfig) to a JSON-serializable object.
@@ -3533,4 +3505,4 @@ declare function deepEqualPlain(a: unknown, b: unknown): boolean;
3533
3505
  * @returns A promise that resolves when the service is ready.
3534
3506
  */
3535
3507
  declare function waitOn(url: string, test?: (resp: Response) => boolean | Promise<boolean>, pingInterval?: number, warnInterval?: number, timeout?: number): Promise<void>;
3536
- 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, 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, 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, ModifiableFrameworkConfig, MetaData, MagicLinkStore, MagicLink, MAX_TENANTS_BATCH_USER_IDS, LoginConfig, Logger, ListResult, LfvOtpResponse, LfvOtpRequest, LfvErrorResponse, LfvErrorCode, LfvActionRequestBase, LfvActionName, LfvActionAcceptedResponse, JwtBearerWorkloadConfig, JWTAssertionClaims, InMemoryTenantStoreOptions, InMemoryTenantStore, 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 };
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 };