import { JWSHeaderParameters, FlattenedJWSInput, JWTPayload, JWK, RemoteJWKSetOptions } from 'jose'; import { R as ReplayStore } from '../jti-replayed-BS2cgKFP.js'; export { J as JtiReplayed, S as S2SAudienceMismatch, a as S2SIssuerMismatch, b as S2SReplayStoreUnavailable, c as S2STokenExpired, d as S2STokenInvalid, e as S2SVerifyError } from '../jti-replayed-BS2cgKFP.js'; import { S3Client } from '@aws-sdk/client-s3'; /** * SigV4-signed JWKS resolver. Pulls a private `jwks.json` from S3 via * the AWS SDK (NOT anonymous HTTPS) and exposes a `jose`-compatible * `GetKeyFn`. * * Lifted 1:1 from provisioning's `server/lib/auth/sigv4-s3-jwks.ts`. */ /** * jose-compatible dynamic-key resolver. Matches the shape `jwtVerify` * accepts (`createLocalJWKSet`'s return type) — the resolver returns the * WebCrypto `CryptoKey` jose hands to the verifier. */ type GetKeyFn = (protectedHeader: JWSHeaderParameters, token?: FlattenedJWSInput) => Promise; type SigV4S3RemoteJWKSetOptions = { region: string; bucket: string; key: string; cacheMaxAgeMs?: number; cooldownDurationMs?: number; s3Client?: S3Client; /** * Explicit S3 endpoint override. When set — or when the standard * `AWS_ENDPOINT_URL_S3` (service-specific) or `AWS_ENDPOINT_URL` (global) env * var is set — the S3 client targets this endpoint with path-style addressing, * required to reach LocalStack / MinIO / any non-AWS or path-style S3 (req * a50c8d74; global-var fallback per drift b02d1d9f). Precedence mirrors the AWS * SDK: this option > `AWS_ENDPOINT_URL_S3` > `AWS_ENDPOINT_URL`. * * When NONE of those is set, the client uses the AWS-prod default (no * endpoint, virtual-host addressing) — standard AWS deploys are UNCHANGED. */ s3Endpoint?: string; /** * Force path-style S3 addressing (`https://endpoint/bucket/key` instead * of `https://bucket.endpoint/key`). Defaults to `true` whenever an * endpoint is resolved (LocalStack/MinIO require it). Set to `false` to * opt out even with a custom endpoint. Ignored on the AWS-prod default * path (no endpoint). */ forcePathStyle?: boolean; }; /** * Build the S3 client the resolver uses, honoring the endpoint override * (option > `AWS_ENDPOINT_URL_S3` env) + path-style addressing (req a50c8d74). * * On the AWS-prod default path (neither option nor env set) the client is * constructed with NO `endpoint` and NO `forcePathStyle` — virtual-host * addressing, unchanged from upstream (back-compat). Exported for tests so the * prod-default branch is asserted on the ACTUAL factory logic (not a hand-rolled * client). Mirrors the Go `buildSigV4S3Client` seam. */ declare function buildSigV4S3Client(opts: Pick): S3Client; declare function createSigV4S3JWKSet(opts: SigV4S3RemoteJWKSetOptions): GetKeyFn; /** * Wire the JWKS source. Call once at boot, before the first * `verifyS2SToken` invocation. Typically this is `createSigV4S3JWKSet(...)`. * * The resolver is stored process-globally, so it is shared across ALL * `@nodii/grpc-auth` subpath entrypoints (root / `/server` / `/verify`) — * the import path can no longer silently split it. */ declare function setJwksResolver(resolver: GetKeyFn): void; type VerifiedS2SToken = { issuer: string; subject: string; /** * RFC 7519 § 4.1.3 — `aud` is either a single string or an array of * strings. Per polyglot parity (drift `0707f386`), the TS surface * normalizes to ALWAYS `string[]` to match the Go SDK's `[]string` * shape (and the Python SDK's `list[str]`). A scalar `aud` claim is * lifted to a 1-element array during verify. Callers that need the * raw JWT shape can read `raw.aud`. */ audience: string[]; scopes: string[]; serviceId: string; serviceName: string; instanceId?: string; issuedAt: number; expiresAt: number; /** * `jti` claim (RFC 7519 § 4.1.7) — JWT id, unique per-issued-token. * Always populated when the verifier sees a string `jti` claim; the * claim is REQUIRED whenever `replayStore` is set in `VerifyOptions` * (per `01-communication-doctrine § 6` + auth-004 acceptance gate). */ jti?: string; raw: JWTPayload; }; type VerifyOptions = { expectedAudience: string; expectedIssuer: string; maxClockSkewSeconds?: number; /** * Optional jti-replay store. When provided, the verifier: * 1. requires a non-empty string `jti` claim (otherwise throws * `S2STokenInvalid("missing_jti")`) * 2. calls `replayStore.reserve(jti, ttlMs)` AFTER all other claim * checks pass * 3. throws `JtiReplayed` if `reserve` returns `false` * * Per R1 the lib ships NO in-memory default. `configureGrpcAuth({... * replayStore: ...})` is the canonical wiring point; passing a store * directly to `verifyS2SToken` is supported for tests + advanced * single-call overrides. */ replayStore?: ReplayStore; /** * TTL (seconds) for the replay-store entry. When `undefined`, the * verifier computes `expiresAt - now()` and uses that (clamped to a * minimum of 1s) — so the replay entry self-expires the moment the * token does. */ replayTtlSeconds?: number; /** * Clock injection (epoch seconds). Defaults to `Math.floor(Date.now()/1000)`. * Used by replay-TTL computation; parity with the Python + Go ports. */ nowFn?: () => number; }; declare function verifyS2SToken(token: string, opts: VerifyOptions): Promise; /** * Pluggable JWKS provider factory (drift `81f3f606`, wave item #21). * * The verifier (`verifyS2SToken` / `configureGrpcAuth({ jwksResolver })`) * has always taken a `GetKeyFn` — but the only ergonomic ways to BUILD one * were `createSigV4S3JWKSet` (private-S3) and reaching directly into jose's * `createLocalJWKSet` / `createRemoteJWKSet`. That left every downstream * service writing its own JWKS plumbing, and meant the canonical * `/.well-known/jwks.json` URL was effectively hardcoded per consumer with * no first-class test seam — so auth-transport e2e tests degraded to * `test.skipIf(!hasS2SInfra())` everywhere. * * `createJwksProvider(...)` is the single canonical constructor. It returns * a `GetKeyFn` the verifier consumes unchanged. The discriminated-union * input picks the source: * * - `{ staticJwks }` — TEST mode. Verify against an in-memory JWKS via * `jose.createLocalJWKSet`; NO network. This is the seam that lets a * test mint a JWT with a local private key and verify it offline (the * D203 § "Test pattern" addendum — real jose + real JWKS shape, NOT a * fabricated bypass). * - `{ remoteUrl }` — PROD (canonical, default). `jose.createRemoteJWKSet` * over the HTTPS `/.well-known/jwks.json` endpoint — the exact D203 * "Verify (canonical impl)" primitive. Prod default behaviour is * UNCHANGED: a consumer that wires the remote URL gets byte-identical * semantics to the pre-existing direct `createRemoteJWKSet` usage. * - `{ sigV4S3 }` — the existing private-S3-bucket resolver * (`createSigV4S3JWKSet`), surfaced through the same factory so callers * have ONE import for all three sources. * * Prod stays the prod default: nothing here changes how a service that * passes a remote-URL (or SigV4-S3) provider behaves. The factory only * ADDS the static-JWKS test path + a uniform construction surface. */ /** RFC 7517 JWK Set object — what a `/.well-known/jwks.json` body deserializes to. */ type JwksObject = { keys: JWK[]; }; /** * Static-JWKS provider input — TEST mode. The verifier resolves keys from * this in-memory set; no network call is ever made. `kid` matching is * delegated to jose's `createLocalJWKSet` (rotation-aware: the set may * carry multiple keys; jose picks by the token's `kid` header, per D203). */ type StaticJwksProviderOptions = { staticJwks: JwksObject; remoteUrl?: never; sigV4S3?: never; }; /** * Remote-URL provider input — PROD (canonical, default). Wraps * `jose.createRemoteJWKSet` over the HTTPS `/.well-known/jwks.json` * endpoint per D203. `remoteOptions` is passed straight through to jose * (cooldown / cache / timeout tuning); omit it for jose's defaults. */ type RemoteJwksProviderOptions = { remoteUrl: string | URL; remoteOptions?: RemoteJWKSetOptions; staticJwks?: never; sigV4S3?: never; }; /** * SigV4-S3 provider input — the existing private-S3-bucket resolver, * surfaced through the unified factory. Identical to calling * `createSigV4S3JWKSet(opts)` directly. */ type SigV4S3JwksProviderOptions = { sigV4S3: SigV4S3RemoteJWKSetOptions; staticJwks?: never; remoteUrl?: never; }; /** * `createJwksProvider` input — exactly one of `staticJwks` / `remoteUrl` / * `sigV4S3`. The discriminated union makes "pass two sources" a compile * error so the source is always unambiguous. */ type JwksProviderOptions = StaticJwksProviderOptions | RemoteJwksProviderOptions | SigV4S3JwksProviderOptions; /** * Build a `GetKeyFn` from a static JWKS, a remote `/.well-known/jwks.json` * URL, or the SigV4-S3 source. Pass the result to * `configureGrpcAuth({ jwksResolver })` or `verifyS2SToken(..., resolver)`. * * @example Prod (canonical, default — D203): * ```ts * const jwks = createJwksProvider({ * remoteUrl: `${NODII_AUTH_BASE_URL}/.well-known/jwks.json`, * }); * configureGrpcAuth({ jwksResolver: jwks, ... }); * ``` * * @example Test (in-memory, no network — sign locally, verify offline): * ```ts * const jwks = createJwksProvider({ staticJwks: { keys: [publicJwk] } }); * await verifyS2SToken(token, { ... }, ); // resolver wired via configure * ``` */ declare function createJwksProvider(opts: JwksProviderOptions): GetKeyFn; /** * Env-aware JWKS provider selector (capability `createJwksProviderFromEnv`, * request `91428c9a`; tracks drift `b02d1d9f`). * * Operator principle (2026-06-30): the S2S verifier must ALWAYS run — there is * no `NODE_ENV=test` / `skipAuth` bypass on the regression stack. Today the * JWKS-source selection (`S2S_AUTH_JWKS_URL ? remoteUrl : sigV4S3`) is * copy-pasted per service; that divergence — and the friction of a hardcoded * `createSigV4S3JWKSet` that can't reach a local/non-AWS stack — is exactly * what tempts a bypass. This factory pulls that selection into the lib ONCE so * every service makes a single call and none hand-rolls (or skips) auth. * * Fixed precedence (highest first): * 1. `opts.staticJwks` — UNIT TESTS ONLY. An explicit, PROGRAMMATIC in-memory * JWKS so a test can mint a JWT with a local key and verify it offline. * Deliberately NOT selectable from any env var: an env-driven static * source would be precisely the auth-bypass vector the doctrine forbids * (ops could pin a known key and skip real verification). It is opt-in * from code, never from deployment config. * 2. `S2S_AUTH_JWKS_URL` — remote HTTP (`jose.createRemoteJWKSet`) for * non-AWS / local stacks pointing at a published `/.well-known/jwks.json`. * 3. `S2S_AUTH_JWKS_S3_{BUCKET,REGION}` (+ optional `S2S_AUTH_JWKS_S3_KEY`, * default `.well-known/jwks.json`) — SigV4-S3 (`createSigV4S3JWKSet`), the * PROD default. That resolver already honors `AWS_ENDPOINT_URL_S3` / * `AWS_ENDPOINT_URL` + path-style (drift `b02d1d9f`), so the SAME env-built * provider reaches LocalStack/MinIO in the regression with NO bypass. * * When NOTHING is configured — or the S3 group is only partially set — the * factory THROWS. A missing JWKS source is a deploy misconfiguration that must * fail loud at boot, never degrade into a silent "skip auth". */ /** Default S3 object key when `S2S_AUTH_JWKS_S3_KEY` is unset — the canonical well-known path. */ declare const DEFAULT_JWKS_S3_KEY = ".well-known/jwks.json"; /** Env var names the selector reads. Exported so consumers / tests reference the canonical keys. */ declare const JWKS_ENV_VARS: { readonly remoteUrl: "S2S_AUTH_JWKS_URL"; readonly s3Bucket: "S2S_AUTH_JWKS_S3_BUCKET"; readonly s3Key: "S2S_AUTH_JWKS_S3_KEY"; readonly s3Region: "S2S_AUTH_JWKS_S3_REGION"; }; type JwksFromEnvOptions = { /** * Env source. Defaults to `process.env`. Pass an explicit map in tests to * avoid mutating the real process environment. */ env?: Record; /** * Explicit static JWKS — UNIT TESTS ONLY. Takes precedence over every env * var and builds an offline in-memory resolver (no network). NEVER wire this * from production config; it exists so a unit test can verify a * locally-minted JWT without standing up S3/HTTP. See the precedence note * above for why it is intentionally not env-selectable. */ staticJwks?: JwksObject; /** Passed straight through to the remote-URL source (jose cooldown / cache / timeout tuning). */ remoteOptions?: RemoteJWKSetOptions; }; /** * Build the canonical `GetKeyFn` for the S2S verifier from the environment, * with the fixed precedence documented above. The result is wired exactly like * any other provider: `configureGrpcAuth({ jwksResolver })` or * `verifyS2SToken(token, opts)` after `setJwksResolver(...)`. * * @throws if no JWKS source is configured, or the SigV4-S3 group is partial — * a deploy misconfig must fail loud, never silently skip auth. * * @example Prod (SigV4-S3, the default): * ```ts * // env: S2S_AUTH_JWKS_S3_BUCKET=nodii-s2s-auth S2S_AUTH_JWKS_S3_REGION=ap-south-1 * configureGrpcAuth({ jwksResolver: createJwksProviderFromEnv(), ... }); * ``` * @example Local / non-AWS stack (remote HTTP): * ```ts * // env: S2S_AUTH_JWKS_URL=http://auth:8080/.well-known/jwks.json * configureGrpcAuth({ jwksResolver: createJwksProviderFromEnv(), ... }); * ``` */ declare function createJwksProviderFromEnv(opts?: JwksFromEnvOptions): GetKeyFn; export { DEFAULT_JWKS_S3_KEY, type GetKeyFn, JWKS_ENV_VARS, type JwksFromEnvOptions, type JwksObject, type JwksProviderOptions, type RemoteJwksProviderOptions, type SigV4S3JwksProviderOptions, type SigV4S3RemoteJWKSetOptions, type StaticJwksProviderOptions, type VerifiedS2SToken, type VerifyOptions, buildSigV4S3Client, createJwksProvider, createJwksProviderFromEnv, createSigV4S3JWKSet, setJwksResolver, verifyS2SToken };