import { DcrClientRegistry, SessionCredentialVault, type AuthorizationStore, type ConsentStore, type DcrRegistryConfig, type FederatedAuthSessionStore, type SecureStoreBackend, type OrchestratedTokenStore as TokenStore, type VerifyResult } from '@frontmcp/auth'; import { FrontMcpAuth, type FrontMcpLogger, type JWK, type ScopeEntry, type ServerRequest } from '../../common'; import { type LocalAuthOptions, type PublicAuthOptions, type RemoteAuthOptions } from '../../common/types/options/auth'; import type ProviderRegistry from '../../provider/provider.registry'; /** * Options type for LocalPrimaryAuth - can be public, orchestrated local, or orchestrated remote */ export type LocalPrimaryAuthOptions = PublicAuthOptions | LocalAuthOptions | RemoteAuthOptions; /** * User information for JWT claims */ export interface UserInfo { sub: string; email?: string; name?: string; picture?: string; roles?: string[]; } /** * Token response from the token endpoint */ export interface TokenResponse { access_token: string; token_type: 'Bearer'; expires_in: number; refresh_token?: string; scope?: string; } /** * Consent and federated login metadata for JWT claims */ export interface ConsentMetadata { selectedToolIds?: string[]; selectedProviderIds?: string[]; skippedProviderIds?: string[]; consentEnabled?: boolean; federatedLoginUsed?: boolean; /** * Progressive/Incremental authorization: the set of app IDs this token grants * access to. ONLY emitted when `incrementalAuth` is enabled for the scope — * its presence turns on app-level gating in `checkToolAuthorization`, so it is * deliberately omitted for non-incremental setups to preserve the historical * allow-all behavior. Embedded as the `authorized_apps` claim by * {@link LocalPrimaryAuth.signAccessToken}. */ authorizedAppIds?: string[]; /** * Custom claims from a local `authenticate` verifier (Checkpoint 3a). Merged * into the access token by {@link LocalPrimaryAuth.signAccessToken} with a * reserved-claim guard so they can never clobber sub/iss/exp/etc. */ customClaims?: Record; } /** * Extended token response from upstream providers (includes id_token) */ export interface UpstreamTokenResponse { access_token: string; token_type: string; expires_in?: number; refresh_token?: string; scope?: string; id_token?: string; } /** * Provider configuration for upstream OAuth providers */ export interface UpstreamProviderConfig { /** Provider ID */ id: string; /** Display name */ name: string; /** Authorization endpoint */ authorizationEndpoint: string; /** Token endpoint */ tokenEndpoint: string; /** User info endpoint (optional) */ userInfoEndpoint?: string; /** JWKS URI for ID token validation (optional) */ jwksUri?: string; /** Client ID */ clientId: string; /** Client secret (for confidential clients) */ clientSecret?: string; /** Default scopes to request */ scopes: string[]; /** Callback URL for this provider */ callbackUrl: string; } export declare class LocalPrimaryAuth extends FrontMcpAuth { private scope; private providers; readonly host: string; readonly port: number; readonly issuer: string; readonly keys: JWK[]; readonly secret: Uint8Array; readonly logger: FrontMcpLogger; private jwks; private cimdService; /** * Token storage backend selected from `options.tokenStorage`. * - `'memory'` / undefined → in-memory stores (default; lost on restart). * - `{ redis }` / `{ sqlite }` → adapter-backed stores that survive restart. * * The three stores below are constructed in-memory synchronously in the * constructor (preserving the exact legacy default behavior). When a * persistent backend is configured, {@link initializeStores} swaps in the * StorageAdapter-backed implementations during async `initialize()`, before * the server signals ready. */ private readonly tokenStorage; /** OAuth authorization-code / pending / refresh-token store. */ private authorizationStoreImpl; /** Federated auth session store for multi-provider flows. */ private federatedSessionStoreImpl; /** Token store for upstream provider tokens. */ private orchestratedTokenStoreImpl; /** Remembered per-(user, client) consent selections (`rememberConsent`). */ private consentStoreImpl; /** * Local-AS Dynamic Client Registration registry (#462). Seeded with any * `dcr.clients` at construction so the authorize/token flows accept those * pre-registered clients without a DCR round-trip, and mutated by * `POST /oauth/register`. Always present (empty when no `dcr` is configured). */ private readonly dcrClientRegistryImpl; /** Storage adapter backing the persistent stores (kept for disposal). */ private storageAdapter?; /** * Per-session encrypted credential vault (Checkpoint 3b). Backs * `this.credentials` in tools and persists `authenticate()` credentials. * Constructed in {@link initialize} once the storage backend is known. */ private credentialVaultImpl?; /** Per-session encrypted credential vault (Checkpoint 3b), if enabled. */ get credentialVault(): SessionCredentialVault | undefined; /** * General session-scoped secure-secret store backend (#470). Backs * `this.secureStore` in tools. Constructed in {@link initialize} from * `auth.secureStore`; defaults to an in-memory, AES-256-GCM-encrypted backing. */ private secureStoreBackendImpl?; /** General secure-store backend (#470), if enabled. */ get secureStoreBackend(): SecureStoreBackend | undefined; /** OAuth authorization-code / pending / refresh-token store. */ get authorizationStore(): AuthorizationStore; /** Federated auth session store for multi-provider flows. */ get federatedSessionStore(): FederatedAuthSessionStore; /** Token store for upstream provider tokens. */ get orchestratedTokenStore(): TokenStore; /** * Remembered per-(user, client) consent selections, backing * `auth.consent.rememberConsent`. Shares the configured token-storage backend * (memory by default; Redis/SQLite when persistent storage is configured). */ get consentStore(): ConsentStore; /** * Local-AS Dynamic Client Registration registry (#462). Consulted by the * register/authorize flows to enforce the declarative `dcr` allowlists and to * look up pre-registered + dynamically-registered clients. */ get dcrClientRegistry(): DcrClientRegistry; /** * Resolved local-AS DCR config. Only `local` mode carries `dcr`; every other * mode returns `undefined`, preserving the historical behavior exactly. */ getDcrConfig(): DcrRegistryConfig | undefined; /** * Whether `POST /oauth/register` and the `registration_endpoint` advertisement * are active. Honors an explicit `dcr.enabled`; when unset, falls back to the * historical guard (enabled in development, disabled in production). */ isDcrEnabled(): boolean; /** Provider configurations (indexed by provider ID) */ private readonly providerConfigs; /** * Remote-mode single upstream provider id (set by {@link registerRemoteProvider}). * * In `mode: 'remote'` FrontMCP federates exactly ONE mandatory upstream IdP. * The authorize flow auto-starts federation against this id (no in-tree login * page, no provider-selection page), and tools read its token via * `this.orchestration.getToken(remoteProviderId)`. Undefined in every other mode. */ private remoteProviderIdImpl?; /** Remote-mode single upstream provider id, or undefined outside remote mode. */ get remoteProviderId(): string | undefined; /** Default access token TTL (1 hour) */ private readonly accessTokenTtlSeconds; /** Default refresh token TTL (30 days) */ private readonly refreshTokenTtlSeconds; constructor(scope: ScopeEntry, providers: ProviderRegistry, options: LocalPrimaryAuthOptions); /** * Derive issuer from options. * * `FRONTMCP_PUBLIC_HOST` overrides only the HOST portion of the boot-time * issuer (see `this.host` in the constructor); the scheme stays `http` and * the port stays `this.port`. To override the scheme and/or port (e.g. * advertise `https://…` with no explicit port behind a TLS proxy), set an * explicit `local.issuer` — that is the supported way to make the boot-time * issuer match what discovery advertises. JWT verification accepts an issuer * array, so tokens minted under a different scheme/port are still tolerated * when running behind a TLS-terminating proxy, but `local.issuer` is the * supported knob for aligning the issuer with discovery. */ private deriveIssuer; /** * Read the `tokenStorage` selection off the auth options. Only * local/remote/orchestrated modes declare it; public mode does not, in which * case we treat it as the in-memory default. */ private readTokenStorage; /** * When a persistent token-storage backend (Redis/SQLite) is configured, build * a shared `StorageAdapter` and swap the three in-memory stores for their * adapter-backed equivalents. For `'memory'` (or unset) this is a no-op, so * the default behavior is preserved exactly. * * The orchestrated-token store keeps using the JWT secret as its encryption * key, so upstream provider tokens stay encrypted at rest in every backend. */ private initializeStores; /** * Build the per-session credential vault (Checkpoint 3b) and register the * `this.credentials` accessor + the `/oauth/connect` add-credential flow. * * Skipped in pure public mode (no authenticated subject / no authenticate() * verifier there). The vault shares the persistent StorageAdapter when one is * configured; otherwise it uses a dedicated in-memory adapter. The HMAC pepper * and resume-link signing key both derive from the server JWT secret * (`this.secret`), so resume URLs are framework-signed with the same trust * root as the access tokens. */ private initializeCredentialVault; /** * Read the `secureStore` selection off the auth options. Only * local/remote/orchestrated modes declare it; public mode does not. */ private readSecureStoreConfig; /** * Build the general session-scoped secure-secret store (#470) and register the * `this.secureStore` accessor. * * Skipped in pure public mode (no authenticated subject / no session-bound * secrets there). The backing comes from `auth.secureStore`: * - unset / `'memory'` → in-memory, AES-256-GCM-encrypted store (default); * - `{ sqlite }` / `{ redis }` → persistent encrypted store. When the chosen * persistent backing matches the configured `tokenStorage`, the SAME * StorageAdapter is reused (one connection); * - `{ backend }` → a custom backing used as-is (e.g. an OS keychain) — the * framework bundles NO native dependency for this path. * * The HKDF pepper for the built-in encrypted backings derives from the server * JWT secret (`this.secret`) unless an explicit `encryption.pepper` is set, so * secrets share the same trust root as the access tokens. */ private initializeSecureStore; /** * Whether the `secureStore` config selects the SAME persistent backing as the * configured `tokenStorage`, so they can share one StorageAdapter. Returns * false for memory/custom/undefined and for mismatched backings (in which case * the secure store gets its own adapter). */ private secureStoreSharesTokenStorage; signAnonymousJwt(): Promise; /** * Cryptographically verify a gateway-issued access token (public/local/remote * "gateway" modes). Gateway tokens — both authenticated access tokens * ({@link signAccessToken}) and anonymous tokens ({@link signAnonymousJwt}) — * are HS256-signed with `this.secret`, so this instance is the sole holder of * the verification key. * * Enforces the signature and lifetime claims (`exp`/`nbf`, checked by `jose` * by default). Issuer equality is intentionally NOT enforced: proxy/tunnel * deployments legitimately present an `iss` that differs from the * request-derived base URL (see {@link deriveIssuer}). Algorithm is pinned to * HS256 to block `alg` confusion (e.g. a forged `alg: none` or asymmetric * header). `expectedIssuer` is accepted for parity/logging only. */ verifyGatewayToken(token: string, expectedIssuer: string): Promise; /** * Sign an access token for an authenticated user */ signAccessToken(user: UserInfo, scopes: string[], audience?: string, consentMetadata?: ConsentMetadata): Promise; /** * Exchange an authorization code for tokens */ exchangeCode(code: string, clientId: string, redirectUri: string, codeVerifier: string, clientSecret?: string): Promise; /** * Refresh an access token using a refresh token */ refreshAccessToken(refreshToken: string, clientId: string, clientSecret?: string): Promise; /** * Create an authorization code for a user (called after login) */ createAuthorizationCode(params: { clientId: string; redirectUri: string; scopes: string[]; codeChallenge: string; userSub: string; userEmail?: string; userName?: string; state?: string; resource?: string; selectedToolIds?: string[]; selectedProviderIds?: string[]; skippedProviderIds?: string[]; consentEnabled?: boolean; federatedLoginUsed?: boolean; pendingAuthId?: string; authorizedAppIds?: string[]; customClaims?: Record; }): Promise; protected initialize(): Promise; fetch(input: RequestInfo | URL, init?: RequestInit): Promise; validate(request: ServerRequest): Promise; private registerAuthFlows; /** * Register an upstream OAuth provider configuration */ registerProvider(config: UpstreamProviderConfig): void; /** * Bridge declarative `auth.providers` (local-mode multi-provider orchestration) * into the upstream-provider registry. Runs once during `initialize()`. * * For each declared provider we map the ergonomic `authorizeUrl`/`tokenUrl` * aliases onto the canonical `authorizationEndpoint`/`tokenEndpoint`, default * `name`/`scopes`, and compute the per-provider callback URL from the issuer * (`${issuer}/oauth/provider/${id}/callback`). No-op when not local mode or * when no providers are declared, so existing configs are unaffected. * * Security: only non-PII provider metadata is read here; client secrets are * kept in the provider config and never logged or exposed to the LLM. */ private registerConfiguredProviders; /** * Remote mode (`mode: 'remote'`): register the flat remote config as the * SINGLE mandatory upstream provider. Runs once during `initialize()`. * * The flat fields (`provider` base URL + `clientId`/`clientSecret`/`scopes`) * and the `providerConfig` endpoint overrides (`authEndpoint`/`tokenEndpoint`/ * `userInfoEndpoint`/`jwksUri`) are mapped onto an {@link UpstreamProviderConfig}. * Endpoints not explicitly overridden are derived from the `provider` base URL * using the standard OIDC paths (`/authorize`, `/token`, `/userinfo`, * `/.well-known/jwks.json`) — the same convention `transparent` mode uses for * discovery. The provider id comes from `providerConfig.id` when set, else it * is derived from the provider hostname (mirroring `deriveProviderId`). * * No-op outside remote mode, so local/public/transparent are unaffected. * * Security: only non-PII provider metadata is read here; the client secret is * kept in the provider config and never logged or exposed to the LLM. */ private registerRemoteProvider; /** * Derive a stable provider id from the upstream `provider` base URL, mirroring * the detection layer's `deriveProviderId`/`urlToProviderId` (hostname with * dots replaced by underscores) so the id is consistent across surfaces. */ private deriveRemoteProviderId; /** * Get provider configuration */ getProviderConfig(providerId: string): UpstreamProviderConfig | undefined; /** * Build OAuth authorize URL for an upstream provider */ buildProviderAuthorizeUrl(providerId: string, params: { state: string; codeChallenge: string; codeChallengeMethod: 'S256'; scopes?: string[]; }): Promise; /** * Exchange authorization code with upstream provider for tokens */ exchangeProviderCode(providerId: string, code: string, codeVerifier?: string): Promise; /** * Get user info from upstream provider */ getProviderUserInfo(providerId: string, accessToken: string, idToken?: string): Promise<{ sub: string; email?: string; name?: string; picture?: string; claims?: Record; }>; /** * Refresh tokens from upstream provider */ refreshProviderToken(providerId: string, refreshToken: string): Promise; } //# sourceMappingURL=instance.local-primary-auth.d.ts.map