auth.provider API
    Preparing search index...

    Interface ComponentMap

    ComponentMap — the typed DI graph for v0.5.0 manifest authoring.

    The base interface is intentionally empty. Slot declarations are added by other files in @o3co/auth-provider-core (and by downstream packages such as @o3co/auth-provider-redis) via TypeScript declaration merging:

    declare module "@o3co/auth-provider-core" {
      interface ComponentMap {
        readonly mySlot: MyType;
      }
    }
    

    Per A2-α §6.1 the v0.5.0 baseline slot set is added incrementally during Phases 3–8 of the v0.5.0 redesign. This empty base is the foundation.

    Per the cross-spec X1 amendment (documented in v0.5.0 redesign specs A3 §5.5 line 391 and A4 §5.6 of this repository's design history): the v0.5.0 ComponentMap does NOT contain the legacy userSessionStore: UserSessionStoreBase nor refreshTokenStore: RefreshTokenStoreBase slots. Phase 5 (A1) and later phases declaration-merge their replacement slots without those legacy names appearing.

    Consumer-side augmentation MUST namespace consumer-specific keys (e.g. acme.cacheClient) to avoid colliding with o3co-claimed slot names.

    v0.5.0 in-tree slot inventory (declaration-merged into this interface from elsewhere in the package; grep declare module "@o3co/auth-provider-core" for the authoritative list):

    • config: AppConfig — declared in boot/types.mts per A2-β §6.2
    • pathResolver: PathResolver — declared in boot/types.mts per A2-β §6.2
    • (Phase 5 onward adds storage / federation / session slots)
    interface ComponentMap {
        accessTokenDenylist?: AccessTokenDenylist;
        auditSink?: AuditSink;
        challengeCeremony?: ChallengeCeremony;
        challengeStore?: ChallengeStore;
        clientRepository: ClientRepository;
        codeRepository: CodeRepository;
        config: {
            accessTokenDenylist?: { adapter?: "memory" | "redis" };
            audit?: { sink: { type: string; [key: string]: unknown } };
            cors: { allowedOrigins: string[] };
            deployment?: { mode?: "single" | "multi" };
            endpoints: { login: { url: string } };
            federations: Record<
                string,
                { enabled: boolean; type?: string; [key: string]: unknown },
            >;
            http: {
                port: number;
                readinessTimeoutMs: number;
                trustProxy: number | boolean | string[];
            };
            logging: {
                level: | "trace"
                | "debug"
                | "info"
                | "warn"
                | "error"
                | "fatal"
                | "silent";
            };
            memoryRateLimiter?: {
                defaultLimit?: { limit: number; windowSeconds: number };
                limits?: Record<string, { limit: number; windowSeconds: number }>;
                maxBuckets?: number;
            };
            oauth: {
                accessToken: { expiresIn: number };
                authorize?: Record<string, never>;
                code?: { adapter?: "memory" | "redis" };
                grants: { [key: string]: unknown };
                jwt: {
                    issuer: string;
                    jwksCacheMaxAge?: number;
                    jwksPath?: string;
                    legacyTypAccept?: boolean;
                    signingKey: {
                        local?:
                            | {
                                algorithm: "HS256";
                                kid: string;
                                previousSecrets?: { expiresAt: string; kid: string; secret: string }[];
                                secret?: string;
                            }
                            | {
                                algorithm: "RS256"
                                | "ES256"
                                | "EdDSA";
                                kid: string;
                                previousKeys?: {
                                    expiresAt: string;
                                    kid: string;
                                    publicKey?: string;
                                    publicKeyPath?: string;
                                }[];
                                privateKey?: string;
                                privateKeyPath?: string;
                                publicKey?: string;
                                publicKeyPath?: string;
                                [key: string]: unknown;
                            };
                        provider: string;
                        [key: string]: unknown;
                    };
                };
                nonce?: { maxLength: number };
                oidcMode: "oidc-required" | "dual";
                refreshToken: {
                    expiresIn: number;
                    legacyRtPolicy: "reject";
                    unknownFamilyPolicy: "accept" | "reject";
                };
                requireEmailVerified?: boolean;
                resourceIndicator?: { enabled: boolean };
                revocation?: { accessToken: "denylist" | "unsupported" };
                tokenBinding?: {
                    "dispatch-policy": "intent-explicit" | "strict-mutual-exclusion";
                };
                tokenExchange?: { maxActorChainDepth: number };
            };
            rateLimit: {
                failMode: "open"
                | "closed";
                login: { limit: number; windowMs: number };
            };
            rateLimiter?: { adapter?: "memory"
            | "redis" };
            redisAccessTokenDenylist?: { keyPrefix?: string };
            redisCodeRepository?: { defaultExpiresIn?: number; keyPrefix?: string };
            redisRefreshTokenFamilyStore?: {
                casRetryLimit?: number;
                keyPrefix?: string;
            };
            redisSessionStores?: { keyPrefix?: string };
            refreshTokenFamilyStore?: { redis?: { password?: string; url: string } };
            repositories: {
                client: { type: string; [key: string]: unknown };
                code: { type: string; [key: string]: unknown };
                user: { type: string; [key: string]: unknown };
            };
            session: {
                csrf?: { trustedOrigins: string[]; ttlSeconds: number };
                domain: string | null;
                maxAge: number;
                name: string;
                sameSite: "lax" | "none" | "strict";
                secret: string;
                secure: boolean;
                storage: {
                    redis?: { password?: string; url: string };
                    type: string;
                    [key: string]: unknown;
                };
            };
            userSessionStores?: { adapter?: "memory"
            | "redis" };
        };
        federationProviders?: ReadonlyMap<string, unknown>;
        federationTokenStore?: FederationTokenStore;
        grantHandlerResolver?: GrantHandlerResolver;
        grantPolicy?: GrantPolicyHook;
        keyStore: KeyStore;
        lifecycleRegistrar: LifecycleRegistrar;
        logger?: Logger;
        pathResolver: PathResolver;
        rateLimiter?: RateLimiter;
        readinessRegistrar: ReadinessRegistrar;
        refreshTokenFamilyRevocation?: RefreshTokenFamilyRevocation;
        refreshTokenFamilyRotation?: RefreshTokenFamilyRotation;
        refreshTokenFamilyStore?: RefreshTokenFamilyStore;
        replaySeenSet?: ReplaySeenSet;
        sessionFamilyIndex?: SessionFamilyIndex;
        sessionFederationIndex?: SessionFederationIndex;
        sessionRPRegistry?: SessionRPRegistry;
        subjectRevocation?: SubjectRevocation;
        subjectSessionIndex?: SubjectSessionIndex;
        tokenExchangeValidatorResolver?: TokenExchangeValidatorResolver;
        userRepository: UserRepository;
        userSessionStore?: UserSessionStore;
        webauthnCredentialStore?: WebAuthnCredentialStore;
    }
    Index
    accessTokenDenylist?: AccessTokenDenylist
    auditSink?: AuditSink
    challengeCeremony?: ChallengeCeremony
    challengeStore?: ChallengeStore
    clientRepository: ClientRepository
    codeRepository: CodeRepository
    config: {
        accessTokenDenylist?: { adapter?: "memory" | "redis" };
        audit?: { sink: { type: string; [key: string]: unknown } };
        cors: { allowedOrigins: string[] };
        deployment?: { mode?: "single" | "multi" };
        endpoints: { login: { url: string } };
        federations: Record<
            string,
            { enabled: boolean; type?: string; [key: string]: unknown },
        >;
        http: {
            port: number;
            readinessTimeoutMs: number;
            trustProxy: number | boolean | string[];
        };
        logging: {
            level: | "trace"
            | "debug"
            | "info"
            | "warn"
            | "error"
            | "fatal"
            | "silent";
        };
        memoryRateLimiter?: {
            defaultLimit?: { limit: number; windowSeconds: number };
            limits?: Record<string, { limit: number; windowSeconds: number }>;
            maxBuckets?: number;
        };
        oauth: {
            accessToken: { expiresIn: number };
            authorize?: Record<string, never>;
            code?: { adapter?: "memory" | "redis" };
            grants: { [key: string]: unknown };
            jwt: {
                issuer: string;
                jwksCacheMaxAge?: number;
                jwksPath?: string;
                legacyTypAccept?: boolean;
                signingKey: {
                    local?:
                        | {
                            algorithm: "HS256";
                            kid: string;
                            previousSecrets?: { expiresAt: string; kid: string; secret: string }[];
                            secret?: string;
                        }
                        | {
                            algorithm: "RS256"
                            | "ES256"
                            | "EdDSA";
                            kid: string;
                            previousKeys?: {
                                expiresAt: string;
                                kid: string;
                                publicKey?: string;
                                publicKeyPath?: string;
                            }[];
                            privateKey?: string;
                            privateKeyPath?: string;
                            publicKey?: string;
                            publicKeyPath?: string;
                            [key: string]: unknown;
                        };
                    provider: string;
                    [key: string]: unknown;
                };
            };
            nonce?: { maxLength: number };
            oidcMode: "oidc-required" | "dual";
            refreshToken: {
                expiresIn: number;
                legacyRtPolicy: "reject";
                unknownFamilyPolicy: "accept" | "reject";
            };
            requireEmailVerified?: boolean;
            resourceIndicator?: { enabled: boolean };
            revocation?: { accessToken: "denylist" | "unsupported" };
            tokenBinding?: {
                "dispatch-policy": "intent-explicit" | "strict-mutual-exclusion";
            };
            tokenExchange?: { maxActorChainDepth: number };
        };
        rateLimit: {
            failMode: "open"
            | "closed";
            login: { limit: number; windowMs: number };
        };
        rateLimiter?: { adapter?: "memory"
        | "redis" };
        redisAccessTokenDenylist?: { keyPrefix?: string };
        redisCodeRepository?: { defaultExpiresIn?: number; keyPrefix?: string };
        redisRefreshTokenFamilyStore?: {
            casRetryLimit?: number;
            keyPrefix?: string;
        };
        redisSessionStores?: { keyPrefix?: string };
        refreshTokenFamilyStore?: { redis?: { password?: string; url: string } };
        repositories: {
            client: { type: string; [key: string]: unknown };
            code: { type: string; [key: string]: unknown };
            user: { type: string; [key: string]: unknown };
        };
        session: {
            csrf?: { trustedOrigins: string[]; ttlSeconds: number };
            domain: string | null;
            maxAge: number;
            name: string;
            sameSite: "lax" | "none" | "strict";
            secret: string;
            secure: boolean;
            storage: {
                redis?: { password?: string; url: string };
                type: string;
                [key: string]: unknown;
            };
        };
        userSessionStores?: { adapter?: "memory"
        | "redis" };
    }

    Type Declaration

    • OptionalaccessTokenDenylist?: { adapter?: "memory" | "redis" }
    • Optionalaudit?: { sink: { type: string; [key: string]: unknown } }
    • cors: { allowedOrigins: string[] }
    • Optionaldeployment?: { mode?: "single" | "multi" }
    • endpoints: { login: { url: string } }
    • federations: Record<string, { enabled: boolean; type?: string; [key: string]: unknown }>
    • http: {
          port: number;
          readinessTimeoutMs: number;
          trustProxy: number | boolean | string[];
      }
    • logging: { level: "trace" | "debug" | "info" | "warn" | "error" | "fatal" | "silent" }
    • OptionalmemoryRateLimiter?: {
          defaultLimit?: { limit: number; windowSeconds: number };
          limits?: Record<string, { limit: number; windowSeconds: number }>;
          maxBuckets?: number;
      }
    • oauth: {
          accessToken: { expiresIn: number };
          authorize?: Record<string, never>;
          code?: { adapter?: "memory" | "redis" };
          grants: { [key: string]: unknown };
          jwt: {
              issuer: string;
              jwksCacheMaxAge?: number;
              jwksPath?: string;
              legacyTypAccept?: boolean;
              signingKey: {
                  local?:
                      | {
                          algorithm: "HS256";
                          kid: string;
                          previousSecrets?: { expiresAt: string; kid: string; secret: string }[];
                          secret?: string;
                      }
                      | {
                          algorithm: "RS256"
                          | "ES256"
                          | "EdDSA";
                          kid: string;
                          previousKeys?: {
                              expiresAt: string;
                              kid: string;
                              publicKey?: string;
                              publicKeyPath?: string;
                          }[];
                          privateKey?: string;
                          privateKeyPath?: string;
                          publicKey?: string;
                          publicKeyPath?: string;
                          [key: string]: unknown;
                      };
                  provider: string;
                  [key: string]: unknown;
              };
          };
          nonce?: { maxLength: number };
          oidcMode: "oidc-required" | "dual";
          refreshToken: {
              expiresIn: number;
              legacyRtPolicy: "reject";
              unknownFamilyPolicy: "accept" | "reject";
          };
          requireEmailVerified?: boolean;
          resourceIndicator?: { enabled: boolean };
          revocation?: { accessToken: "denylist" | "unsupported" };
          tokenBinding?: {
              "dispatch-policy": "intent-explicit" | "strict-mutual-exclusion";
          };
          tokenExchange?: { maxActorChainDepth: number };
      }
    • rateLimit: { failMode: "open" | "closed"; login: { limit: number; windowMs: number } }

      Rate-limit config for SESSION routes (e.g. /session/login bruteforce protection). Uses windowMs (milliseconds) for historical reasons — the section was shaped by express-rate-limit, which packages/session/src/routes/Session.mts consumed until #270.

      Since #270 /session/login runs on the shared rateLimiter component instead, keyed login:ip:<ip>, so the guard is one bucket set across replicas rather than one per process. These values stay the single source of truth: both bundled limiter adapters seed their own limits.login from them (resolveLoginLimitSpec), converting to the whole seconds a RateLimitSpec takes.

      IH-18 — config split: This section ONLY governs session-route rate limiting. OAuth endpoint rate limiting (/token, /authorize) is provided via the optional rateLimiter component slot; the built-in module config lives under memoryRateLimiter.* / redisRateLimiter.* and uses windowSeconds (seconds) per RateLimitSpec in packages/core/src/ratelimit/types.mts. Two independent systems, different keys, different units.

    • OptionalrateLimiter?: { adapter?: "memory" | "redis" }
    • OptionalredisAccessTokenDenylist?: { keyPrefix?: string }
    • OptionalredisCodeRepository?: { defaultExpiresIn?: number; keyPrefix?: string }
    • OptionalredisRefreshTokenFamilyStore?: { casRetryLimit?: number; keyPrefix?: string }
    • OptionalredisSessionStores?: { keyPrefix?: string }
    • OptionalrefreshTokenFamilyStore?: { redis?: { password?: string; url: string } }
    • repositories: {
          client: { type: string; [key: string]: unknown };
          code: { type: string; [key: string]: unknown };
          user: { type: string; [key: string]: unknown };
      }
    • session: {
          csrf?: { trustedOrigins: string[]; ttlSeconds: number };
          domain: string | null;
          maxAge: number;
          name: string;
          sameSite: "lax" | "none" | "strict";
          secret: string;
          secure: boolean;
          storage: {
              redis?: { password?: string; url: string };
              type: string;
              [key: string]: unknown;
          };
      }
      • Optionalcsrf?: { trustedOrigins: string[]; ttlSeconds: number }

        #272 — CSRF policy for the state-changing session routes.

        .optional() on purpose: a deployment inheriting reference.conf always has it, and every value has a code-side default, so a hand-built config (tests, embedders composing their own object) is not forced to restate a section it has no opinion about.

        trustedOrigins is NOT cors.allowedOrigins. "May this origin read my responses" and "may this origin make me change state" are two questions, and #272 was filed because one list was answering both. Deployments whose login UI is served from a different origin than the provider list those origins here — explicitly.

      • domain: string | null
      • maxAge: number
      • name: string
      • sameSite: "lax" | "none" | "strict"
      • secret: string
      • secure: boolean
      • storage: {
            redis?: { password?: string; url: string };
            type: string;
            [key: string]: unknown;
        }
    • OptionaluserSessionStores?: { adapter?: "memory" | "redis" }
    federationProviders?: ReadonlyMap<string, unknown>
    federationTokenStore?: FederationTokenStore
    grantHandlerResolver?: GrantHandlerResolver
    grantPolicy?: GrantPolicyHook
    keyStore: KeyStore
    lifecycleRegistrar: LifecycleRegistrar

    Boot-planner-owned lifecycle registrar (D-5). Pre-seeded as a bootstrap component before any module factory runs. Modules that create disposable sub-resources (Redis clients, interval timers) declare optional: ["lifecycleRegistrar"] and forward the value into createAdapterFactory(kind, { lifecycle: deps.lifecycleRegistrar }) so each builder receives the registrar via BuilderContext.lifecycle.

    This slot is NOT consumer-overridable — bootstrap-component-collision fires if a consumer passes it via bootstrapComponents / overrideComponents.

    logger?: Logger
    pathResolver: PathResolver
    rateLimiter?: RateLimiter
    readinessRegistrar: ReadinessRegistrar

    Boot-planner-owned readiness registrar. Pre-seeded alongside lifecycleRegistrar and subject to the same rules: modules that open a connection declare optional: ["readinessRegistrar"] and forward the value into createAdapterFactory(kind, { readiness: deps.readinessRegistrar }) so builders can register a probe for the resource they hold.

    Also NOT consumer-overridable — a second registrar would collect probes the planner never reads, and /readyz would report ready while the dependency it was meant to watch is down.

    refreshTokenFamilyRevocation?: RefreshTokenFamilyRevocation
    refreshTokenFamilyRotation?: RefreshTokenFamilyRotation
    refreshTokenFamilyStore?: RefreshTokenFamilyStore
    replaySeenSet?: ReplaySeenSet
    sessionFamilyIndex?: SessionFamilyIndex
    sessionFederationIndex?: SessionFederationIndex
    sessionRPRegistry?: SessionRPRegistry
    subjectRevocation?: SubjectRevocation
    subjectSessionIndex?: SubjectSessionIndex
    tokenExchangeValidatorResolver?: TokenExchangeValidatorResolver
    userRepository: UserRepository
    userSessionStore?: UserSessionStore
    webauthnCredentialStore?: WebAuthnCredentialStore

    Optional WebAuthn credential store. Present when the webauthn package is wired.