ingenium 0.0.1 → 0.0.2

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.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Buffer as Buffer$1 } from 'node:buffer';
2
2
  import * as node_http from 'node:http';
3
- import { IncomingHttpHeaders, Server as Server$1 } from 'node:http';
3
+ import { IncomingHttpHeaders, IncomingMessage, Server as Server$1 } from 'node:http';
4
4
  import { Readable } from 'node:stream';
5
5
  import { KeyObject } from 'node:crypto';
6
6
  import { WebSocket as WebSocket$1 } from 'ws';
@@ -2202,7 +2202,9 @@ type CorsOriginFn = (origin: string, ctx: IngeniumContext) => boolean | string |
2202
2202
  /**
2203
2203
  * Spec for the `origin` option.
2204
2204
  *
2205
- * - `boolean` — `true` reflects any request `Origin`; `false` disables CORS.
2205
+ * - `boolean` — `true` reflects any request `Origin` (but never the literal
2206
+ * `"null"`, and rejected at construction when `credentials: true`); `false`
2207
+ * disables CORS.
2206
2208
  * - `'*'` — wildcard: `Access-Control-Allow-Origin: *`.
2207
2209
  * - any other `string` — exact match against the request's `Origin`.
2208
2210
  * - `string[]` — allowlist; matched exactly.
@@ -2234,8 +2236,9 @@ interface CorsOptions {
2234
2236
  exposedHeaders?: string[];
2235
2237
  /**
2236
2238
  * If `true`, sets `Access-Control-Allow-Credentials: true`.
2237
- * Incompatible with `origin: '*'` — throws at construction time.
2238
- * Default: `false`.
2239
+ * Incompatible with `origin: '*'` and `origin: true` both throw at
2240
+ * construction time, because reflecting credentials to an unrestricted set of
2241
+ * origins lets any site read authenticated responses. Default: `false`.
2239
2242
  */
2240
2243
  credentials?: boolean;
2241
2244
  /**
@@ -2498,7 +2501,12 @@ interface CsrfCookieOptions {
2498
2501
  domain?: string;
2499
2502
  /** SameSite policy. Default `'lax'`. */
2500
2503
  sameSite?: 'lax' | 'strict' | 'none';
2501
- /** Mark cookie Secure. Default `false`; set `true` behind TLS. */
2504
+ /**
2505
+ * Mark cookie Secure. **Default `true`** so the CSRF cookie is never sent
2506
+ * over plaintext HTTP where a network attacker could read it and forge the
2507
+ * double-submit header. Override to `false` only for local HTTP development;
2508
+ * doing so emits a dev-mode warning.
2509
+ */
2502
2510
  secure?: boolean;
2503
2511
  /**
2504
2512
  * Mark cookie HttpOnly. **Default `false`** — clients must read the cookie
@@ -2521,6 +2529,22 @@ interface CsrfOptions {
2521
2529
  storage?: CsrfStorage;
2522
2530
  /** Cookie options when `storage === 'cookie'`. */
2523
2531
  cookie?: CsrfCookieOptions;
2532
+ /**
2533
+ * Bind a cookie-mode token to a per-session/per-user identifier (e.g. a
2534
+ * session id or authenticated user id derived from `ctx`).
2535
+ *
2536
+ * Without binding, a signed double-submit token is only proven to have been
2537
+ * minted by *this server* — any token the server ever issued is globally
2538
+ * valid for any user, so a leaked or shared token defeats the protection.
2539
+ * When this returns a stable value, the token is HMAC'd over
2540
+ * `raw + '.' + binding` and the binding is re-verified on unsafe requests,
2541
+ * so a token minted for one session cannot be replayed against another.
2542
+ *
2543
+ * Returning `undefined` (e.g. before login) falls back to plain
2544
+ * double-submit and emits a dev-mode warning. Has no effect in session
2545
+ * storage mode, where the session id already authenticates the binding.
2546
+ */
2547
+ sessionBinding?: (ctx: IngeniumContext) => string | undefined;
2524
2548
  /** Methods that bypass validation. Default `['GET', 'HEAD', 'OPTIONS', 'TRACE']`. */
2525
2549
  ignoreMethods?: readonly string[];
2526
2550
  /**
@@ -2773,6 +2797,8 @@ type JwtVerifyError = {
2773
2797
  error: 'bad_signature';
2774
2798
  } | {
2775
2799
  error: 'expired';
2800
+ } | {
2801
+ error: 'missing_exp';
2776
2802
  } | {
2777
2803
  error: 'not_yet_valid';
2778
2804
  } | {
@@ -2832,6 +2858,17 @@ interface JwtOptions<T = Record<string, unknown>> {
2832
2858
  maxAgeSeconds?: number;
2833
2859
  /** Leeway for `nbf` / `exp` checks, in seconds. Default `5`. */
2834
2860
  clockSkewSeconds?: number;
2861
+ /**
2862
+ * Require a numeric `exp` claim. Default `true`.
2863
+ *
2864
+ * Why default-on: a token that omits `exp` (or carries a non-numeric one)
2865
+ * would otherwise verify forever — a stolen token never stops working. We
2866
+ * refuse those by default and only relax it for callers who deliberately
2867
+ * issue non-expiring tokens (set this to `false`). Either way a present
2868
+ * `exp`/`nbf`/`iat` that is not a finite number is treated as malformed, not
2869
+ * silently skipped.
2870
+ */
2871
+ requireExp?: boolean;
2835
2872
  /**
2836
2873
  * If `true` (default), missing tokens raise `IngeniumUnauthorizedError`.
2837
2874
  * If `false`, missing tokens just call `next()` with no `ctx.jwt`.
@@ -2861,6 +2898,17 @@ interface JwtOptions<T = Record<string, unknown>> {
2861
2898
  _payload?: T;
2862
2899
  }
2863
2900
 
2901
+ /**
2902
+ * 500 — the configured algorithm allowlist and the supplied key material are
2903
+ * from different families (an HMAC alg paired with an asymmetric PEM / public
2904
+ * key, or vice versa). Allowing this is the canonical algorithm-confusion
2905
+ * footgun: an attacker forges `HS256` tokens using the server's *public* key
2906
+ * as the HMAC secret. We refuse the configuration at construction time so the
2907
+ * mistake surfaces at boot, not as a silent auth bypass.
2908
+ */
2909
+ declare class IngeniumJwtKeyAlgMismatchError extends IngeniumError {
2910
+ constructor(message: string);
2911
+ }
2864
2912
  /**
2865
2913
  * Bearer-token JWT verification middleware.
2866
2914
  *
@@ -3496,6 +3544,11 @@ interface VerifyOptions {
3496
3544
  issuer?: string | readonly string[];
3497
3545
  maxAgeSeconds?: number;
3498
3546
  clockSkewSeconds?: number;
3547
+ /**
3548
+ * Require a finite numeric `exp`. Default `true` — a token without an
3549
+ * expiry would otherwise verify forever (see middleware `requireExp`).
3550
+ */
3551
+ requireExp?: boolean;
3499
3552
  /** Override "now" for deterministic tests. Returns seconds since epoch. */
3500
3553
  nowSeconds?: () => number;
3501
3554
  }
@@ -3658,7 +3711,20 @@ interface SessionCookieOptions {
3658
3711
  httpOnly?: boolean;
3659
3712
  /** Cookie `SameSite` attribute. @default 'lax' */
3660
3713
  sameSite?: 'lax' | 'strict' | 'none';
3661
- /** Cookie `Secure` attribute. @default false */
3714
+ /**
3715
+ * Cookie `Secure` attribute.
3716
+ *
3717
+ * When left undefined, {@link sessionMiddleware} resolves it safely: ON in
3718
+ * production (`NODE_ENV==='production'`) so the cookie cannot ride plaintext
3719
+ * HTTP, OFF in dev so `http://localhost` keeps working. Set `false`
3720
+ * explicitly to opt out in production (emits a dev warning), or `true` to
3721
+ * force it on in every environment.
3722
+ *
3723
+ * Note: the raw {@link serializeCookie} helper still treats `undefined` as
3724
+ * off — only the middleware applies the environment-aware default.
3725
+ *
3726
+ * @default undefined (production → Secure, dev → not Secure)
3727
+ */
3662
3728
  secure?: boolean;
3663
3729
  }
3664
3730
  /** Options accepted by {@link sessionMiddleware}. */
@@ -3762,8 +3828,10 @@ interface SessionStore {
3762
3828
  * - HMAC-SHA-256 over the session id, base64url-encoded; verified with
3763
3829
  * `timingSafeEqual`.
3764
3830
  * - 144-bit (18-byte) random ids.
3765
- * - Defaults: `HttpOnly`, `SameSite=Lax`, `Path=/`. Set `secure: true`
3766
- * behind TLS to enable `Secure`.
3831
+ * - Defaults: `HttpOnly`, `SameSite=Lax`, `Path=/`. `Secure` defaults ON in
3832
+ * production (`NODE_ENV==='production'`) and OFF in dev so http://localhost
3833
+ * keeps working; set `cookie.secure: false` to force it off in production
3834
+ * (a dev warning fires), or `true` to force it on everywhere.
3767
3835
  * - Tampered or unknown cookies silently issue a fresh session — never an
3768
3836
  * error response, since this is an attacker-influenced surface.
3769
3837
  */
@@ -3810,12 +3878,40 @@ type WebSocket = WebSocket$1;
3810
3878
  * are not meaningful for WS handlers (the upgrade has already happened).
3811
3879
  */
3812
3880
  type WebSocketHandler = (socket: WebSocket$1, ctx: IngeniumContext) => void | Promise<void>;
3881
+ /**
3882
+ * Custom Origin verifier. Receives the request's `Origin` header (or
3883
+ * `undefined` when absent, e.g. a non-browser client) plus the raw upgrade
3884
+ * request. Return `true` to allow the upgrade, `false` to reject it with a
3885
+ * `403` handshake.
3886
+ */
3887
+ type WebSocketOriginVerifier = (origin: string | undefined, req: IncomingMessage) => boolean;
3888
+ /**
3889
+ * Origin allowlist policy for a WebSocket upgrade. WS handlers run OUTSIDE the
3890
+ * normal middleware pipeline, so the only built-in defense against
3891
+ * Cross-Site WebSocket Hijacking (CSWSH) is this option — browsers attach the
3892
+ * victim's cookies to cross-origin upgrades, so without an Origin check any
3893
+ * external page can open an authenticated socket.
3894
+ *
3895
+ * - `true` — same-origin only: the `Origin` host must equal the request `Host`.
3896
+ * - `string` / `string[]` — exact `Origin` allowlist (compared verbatim).
3897
+ * - function — custom predicate (see {@link WebSocketOriginVerifier}).
3898
+ *
3899
+ * Omitting the option preserves backward compatibility (no enforcement) but
3900
+ * emits a one-time dev warning, because the safe default for a browser-facing
3901
+ * socket is to restrict origins.
3902
+ */
3903
+ type WebSocketOriginOption = boolean | string | string[] | WebSocketOriginVerifier;
3813
3904
  /** Per-handler options forwarded to `WebSocketServer({ noServer: true, ... })`. */
3814
3905
  interface WebSocketHandlerOptions {
3815
3906
  /** Max payload size (bytes) for incoming frames. */
3816
3907
  maxPayload?: number;
3817
3908
  /** Enable permessage-deflate. Defaults to false (matches `ws` default). */
3818
3909
  perMessageDeflate?: boolean;
3910
+ /**
3911
+ * Origin allowlist for the upgrade handshake — the built-in CSWSH defense.
3912
+ * See {@link WebSocketOriginOption} for semantics and why it matters.
3913
+ */
3914
+ origin?: WebSocketOriginOption;
3819
3915
  }
3820
3916
  /** Bag passed to integrators (advanced). */
3821
3917
  interface WsIntegrator {
@@ -4259,4 +4355,4 @@ declare const ingenium: IngeniumFactory & {
4259
4355
  openapiHandler: typeof openapiHandler;
4260
4356
  };
4261
4357
 
4262
- export { type ApiKeyLogger, type ApiKeyOptions, type ApiKeyValidator, type CachedResponse, type CloseOptions, type ComposedHandler, type CookieGetOptions, type CookieSetOptions, type CorsOptions, type CorsOrigin, type CorsOriginFn, type CronHandler, type CronMatch, type CronOptions, CronRegistry, type CsrfCookieOptions, type CsrfOptions, type CsrfStorage, type CsrfValueReader, type Decorator, DecoratorRegistry, type EagerDecorator, type EnableWebSocketsOptions, type ExtractParams, type FailedJob, type FormatHandlers, type FormattableCtx, type ForwardedInfo, type GenerateOpenApiOptions, HTTP_METHODS, type HeaderBag, type Hooks, HooksRegistry, Http2Adapter, type Http2AdapterOptions, Http2cAdapter, type HttpMethod, IdempotencyMemoryStore, type IdempotencyOptions, type IdempotencyStore, IngeniumApp, type IngeniumAppOptions, IngeniumBadRequestError, IngeniumBody, IngeniumContext, IngeniumContextPool, type IngeniumCookies, IngeniumCronJob, IngeniumCsrfError, IngeniumError, type IngeniumErrorHandler, IngeniumHaltError, type IngeniumHandler, IngeniumHeaderInjectionError, IngeniumMethodNotAllowedError, type IngeniumMiddleware, IngeniumNotFoundError, IngeniumPayloadTooLargeError, type IngeniumPlugin, type IngeniumQuery, IngeniumQueue, IngeniumTimeoutError, IngeniumUnauthorizedError, IngeniumUnserializableError, IngeniumValidationError, type InjectRequest, type InjectResponse, type JobHandle, type JsonEtagCtx, type JsonEtagOptions, type JwtAlgorithm, type JwtHeader, type JwtKey, type JwtLogger, type JwtOptions, type JwtSecret, type JwtSecretResolver, type JwtTokenReader, type JwtVerified, type LazyDecorator, type ListeningServer, type MatchMiss, type MatchResult, MemoryQueueStore, type MultipartFile, type MultipartOptions, type MultipartResult, type NegotiableCtx, NodeAdapter, type OnComposeHook, type OnErrorHook, type OnRequestHook, type OnResponseHook, type OnRouteHook, type Components as OpenApiComponents, type Info as OpenApiInfo, type Response as OpenApiResponse, type Schema as OpenApiSchema, type SecurityRequirement as OpenApiSecurityRequirement, type SecurityScheme as OpenApiSecurityScheme, type Server as OpenApiServer, type OpenApiSpec, type Tag as OpenApiTag, type Operation, type Parameter, type ParseSchema, type ParsedAccept, type PathItem, type PluginTarget, type ProblemDetails, type ProblemDetailsOptions, type QueueOptions, QueueRegistry, type QueueStore, type QueueWorker, MemoryStore$1 as RateLimitMemoryStore, type RateLimitOptions, type RateLimitStore, type RegisteredQueue, type RegistrationEvent, type RequestBody, type ResponseBody, type RetryPolicy, RouteBuilder, type RouteDescriptor, type RouteOptions, Router, RouterTrie, type SafeJsonStringifyOptions, type SafeParseSchema, ScopedApp, type Session, type SessionCookieOptions, MemoryStore as SessionMemoryStore, type SessionOptions, type SessionStore, type ShutdownOptions, type SseEvent, type SseStream, type StandardFailureResult, type StandardIssue, type StandardPathSegment, type StandardResult, type StandardSchemaV1, type StandardSchemaV1Props, type StandardSuccessResult, type StaticOptions, type Transport, type TransportHooks, TrieNode, type TrustProxy, type WebSocket, type WebSocketHandler, type WebSocketHandlerOptions, type WsIntegrator, WsNodeAdapter, type WsRegistrar, _resetDefaultApp, accepts, acceptsCharsets, acceptsEncodings, acceptsLanguages, after, apiKeyMiddleware, before, clearJwksCache, compose, composeWithHandler, computeEtag, corsMiddleware as cors_, createWebSocketRegistrar, csrfMiddleware, ingenium as default, defaultApp, del as delete, enableWebSockets, expandShorthand, fetchJwks, formatResponse, generateOpenApi, get, gracefulShutdown, head, idempotencyMiddleware, ingenium, isFresh, isStandardSchema, jwtMiddleware, listen, nextFireFrom, onError, openapiHandler, options, parseAcceptHeader, parseCronSpec, patch, peerHasWs, post, problemDetailsMiddleware, put, rateLimit, resolveForwarded, respondJsonWithEtag, safeJsonStringify, selectBest, sessionMiddleware, sortByPreference, sse, startKeepAlive, staticMiddleware as static_, toProblemDetails, use, verifyJwt };
4358
+ export { type ApiKeyLogger, type ApiKeyOptions, type ApiKeyValidator, type CachedResponse, type CloseOptions, type ComposedHandler, type CookieGetOptions, type CookieSetOptions, type CorsOptions, type CorsOrigin, type CorsOriginFn, type CronHandler, type CronMatch, type CronOptions, CronRegistry, type CsrfCookieOptions, type CsrfOptions, type CsrfStorage, type CsrfValueReader, type Decorator, DecoratorRegistry, type EagerDecorator, type EnableWebSocketsOptions, type ExtractParams, type FailedJob, type FormatHandlers, type FormattableCtx, type ForwardedInfo, type GenerateOpenApiOptions, HTTP_METHODS, type HeaderBag, type Hooks, HooksRegistry, Http2Adapter, type Http2AdapterOptions, Http2cAdapter, type HttpMethod, IdempotencyMemoryStore, type IdempotencyOptions, type IdempotencyStore, IngeniumApp, type IngeniumAppOptions, IngeniumBadRequestError, IngeniumBody, IngeniumContext, IngeniumContextPool, type IngeniumCookies, IngeniumCronJob, IngeniumCsrfError, IngeniumError, type IngeniumErrorHandler, IngeniumHaltError, type IngeniumHandler, IngeniumHeaderInjectionError, IngeniumJwtKeyAlgMismatchError, IngeniumMethodNotAllowedError, type IngeniumMiddleware, IngeniumNotFoundError, IngeniumPayloadTooLargeError, type IngeniumPlugin, type IngeniumQuery, IngeniumQueue, IngeniumTimeoutError, IngeniumUnauthorizedError, IngeniumUnserializableError, IngeniumValidationError, type InjectRequest, type InjectResponse, type JobHandle, type JsonEtagCtx, type JsonEtagOptions, type JwtAlgorithm, type JwtHeader, type JwtKey, type JwtLogger, type JwtOptions, type JwtSecret, type JwtSecretResolver, type JwtTokenReader, type JwtVerified, type LazyDecorator, type ListeningServer, type MatchMiss, type MatchResult, MemoryQueueStore, type MultipartFile, type MultipartOptions, type MultipartResult, type NegotiableCtx, NodeAdapter, type OnComposeHook, type OnErrorHook, type OnRequestHook, type OnResponseHook, type OnRouteHook, type Components as OpenApiComponents, type Info as OpenApiInfo, type Response as OpenApiResponse, type Schema as OpenApiSchema, type SecurityRequirement as OpenApiSecurityRequirement, type SecurityScheme as OpenApiSecurityScheme, type Server as OpenApiServer, type OpenApiSpec, type Tag as OpenApiTag, type Operation, type Parameter, type ParseSchema, type ParsedAccept, type PathItem, type PluginTarget, type ProblemDetails, type ProblemDetailsOptions, type QueueOptions, QueueRegistry, type QueueStore, type QueueWorker, MemoryStore$1 as RateLimitMemoryStore, type RateLimitOptions, type RateLimitStore, type RegisteredQueue, type RegistrationEvent, type RequestBody, type ResponseBody, type RetryPolicy, RouteBuilder, type RouteDescriptor, type RouteOptions, Router, RouterTrie, type SafeJsonStringifyOptions, type SafeParseSchema, ScopedApp, type Session, type SessionCookieOptions, MemoryStore as SessionMemoryStore, type SessionOptions, type SessionStore, type ShutdownOptions, type SseEvent, type SseStream, type StandardFailureResult, type StandardIssue, type StandardPathSegment, type StandardResult, type StandardSchemaV1, type StandardSchemaV1Props, type StandardSuccessResult, type StaticOptions, type Transport, type TransportHooks, TrieNode, type TrustProxy, type WebSocket, type WebSocketHandler, type WebSocketHandlerOptions, type WsIntegrator, WsNodeAdapter, type WsRegistrar, _resetDefaultApp, accepts, acceptsCharsets, acceptsEncodings, acceptsLanguages, after, apiKeyMiddleware, before, clearJwksCache, compose, composeWithHandler, computeEtag, corsMiddleware as cors_, createWebSocketRegistrar, csrfMiddleware, ingenium as default, defaultApp, del as delete, enableWebSockets, expandShorthand, fetchJwks, formatResponse, generateOpenApi, get, gracefulShutdown, head, idempotencyMiddleware, ingenium, isFresh, isStandardSchema, jwtMiddleware, listen, nextFireFrom, onError, openapiHandler, options, parseAcceptHeader, parseCronSpec, patch, peerHasWs, post, problemDetailsMiddleware, put, rateLimit, resolveForwarded, respondJsonWithEtag, safeJsonStringify, selectBest, sessionMiddleware, sortByPreference, sse, startKeepAlive, staticMiddleware as static_, toProblemDetails, use, verifyJwt };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Buffer as Buffer$1 } from 'node:buffer';
2
2
  import * as node_http from 'node:http';
3
- import { IncomingHttpHeaders, Server as Server$1 } from 'node:http';
3
+ import { IncomingHttpHeaders, IncomingMessage, Server as Server$1 } from 'node:http';
4
4
  import { Readable } from 'node:stream';
5
5
  import { KeyObject } from 'node:crypto';
6
6
  import { WebSocket as WebSocket$1 } from 'ws';
@@ -2202,7 +2202,9 @@ type CorsOriginFn = (origin: string, ctx: IngeniumContext) => boolean | string |
2202
2202
  /**
2203
2203
  * Spec for the `origin` option.
2204
2204
  *
2205
- * - `boolean` — `true` reflects any request `Origin`; `false` disables CORS.
2205
+ * - `boolean` — `true` reflects any request `Origin` (but never the literal
2206
+ * `"null"`, and rejected at construction when `credentials: true`); `false`
2207
+ * disables CORS.
2206
2208
  * - `'*'` — wildcard: `Access-Control-Allow-Origin: *`.
2207
2209
  * - any other `string` — exact match against the request's `Origin`.
2208
2210
  * - `string[]` — allowlist; matched exactly.
@@ -2234,8 +2236,9 @@ interface CorsOptions {
2234
2236
  exposedHeaders?: string[];
2235
2237
  /**
2236
2238
  * If `true`, sets `Access-Control-Allow-Credentials: true`.
2237
- * Incompatible with `origin: '*'` — throws at construction time.
2238
- * Default: `false`.
2239
+ * Incompatible with `origin: '*'` and `origin: true` both throw at
2240
+ * construction time, because reflecting credentials to an unrestricted set of
2241
+ * origins lets any site read authenticated responses. Default: `false`.
2239
2242
  */
2240
2243
  credentials?: boolean;
2241
2244
  /**
@@ -2498,7 +2501,12 @@ interface CsrfCookieOptions {
2498
2501
  domain?: string;
2499
2502
  /** SameSite policy. Default `'lax'`. */
2500
2503
  sameSite?: 'lax' | 'strict' | 'none';
2501
- /** Mark cookie Secure. Default `false`; set `true` behind TLS. */
2504
+ /**
2505
+ * Mark cookie Secure. **Default `true`** so the CSRF cookie is never sent
2506
+ * over plaintext HTTP where a network attacker could read it and forge the
2507
+ * double-submit header. Override to `false` only for local HTTP development;
2508
+ * doing so emits a dev-mode warning.
2509
+ */
2502
2510
  secure?: boolean;
2503
2511
  /**
2504
2512
  * Mark cookie HttpOnly. **Default `false`** — clients must read the cookie
@@ -2521,6 +2529,22 @@ interface CsrfOptions {
2521
2529
  storage?: CsrfStorage;
2522
2530
  /** Cookie options when `storage === 'cookie'`. */
2523
2531
  cookie?: CsrfCookieOptions;
2532
+ /**
2533
+ * Bind a cookie-mode token to a per-session/per-user identifier (e.g. a
2534
+ * session id or authenticated user id derived from `ctx`).
2535
+ *
2536
+ * Without binding, a signed double-submit token is only proven to have been
2537
+ * minted by *this server* — any token the server ever issued is globally
2538
+ * valid for any user, so a leaked or shared token defeats the protection.
2539
+ * When this returns a stable value, the token is HMAC'd over
2540
+ * `raw + '.' + binding` and the binding is re-verified on unsafe requests,
2541
+ * so a token minted for one session cannot be replayed against another.
2542
+ *
2543
+ * Returning `undefined` (e.g. before login) falls back to plain
2544
+ * double-submit and emits a dev-mode warning. Has no effect in session
2545
+ * storage mode, where the session id already authenticates the binding.
2546
+ */
2547
+ sessionBinding?: (ctx: IngeniumContext) => string | undefined;
2524
2548
  /** Methods that bypass validation. Default `['GET', 'HEAD', 'OPTIONS', 'TRACE']`. */
2525
2549
  ignoreMethods?: readonly string[];
2526
2550
  /**
@@ -2773,6 +2797,8 @@ type JwtVerifyError = {
2773
2797
  error: 'bad_signature';
2774
2798
  } | {
2775
2799
  error: 'expired';
2800
+ } | {
2801
+ error: 'missing_exp';
2776
2802
  } | {
2777
2803
  error: 'not_yet_valid';
2778
2804
  } | {
@@ -2832,6 +2858,17 @@ interface JwtOptions<T = Record<string, unknown>> {
2832
2858
  maxAgeSeconds?: number;
2833
2859
  /** Leeway for `nbf` / `exp` checks, in seconds. Default `5`. */
2834
2860
  clockSkewSeconds?: number;
2861
+ /**
2862
+ * Require a numeric `exp` claim. Default `true`.
2863
+ *
2864
+ * Why default-on: a token that omits `exp` (or carries a non-numeric one)
2865
+ * would otherwise verify forever — a stolen token never stops working. We
2866
+ * refuse those by default and only relax it for callers who deliberately
2867
+ * issue non-expiring tokens (set this to `false`). Either way a present
2868
+ * `exp`/`nbf`/`iat` that is not a finite number is treated as malformed, not
2869
+ * silently skipped.
2870
+ */
2871
+ requireExp?: boolean;
2835
2872
  /**
2836
2873
  * If `true` (default), missing tokens raise `IngeniumUnauthorizedError`.
2837
2874
  * If `false`, missing tokens just call `next()` with no `ctx.jwt`.
@@ -2861,6 +2898,17 @@ interface JwtOptions<T = Record<string, unknown>> {
2861
2898
  _payload?: T;
2862
2899
  }
2863
2900
 
2901
+ /**
2902
+ * 500 — the configured algorithm allowlist and the supplied key material are
2903
+ * from different families (an HMAC alg paired with an asymmetric PEM / public
2904
+ * key, or vice versa). Allowing this is the canonical algorithm-confusion
2905
+ * footgun: an attacker forges `HS256` tokens using the server's *public* key
2906
+ * as the HMAC secret. We refuse the configuration at construction time so the
2907
+ * mistake surfaces at boot, not as a silent auth bypass.
2908
+ */
2909
+ declare class IngeniumJwtKeyAlgMismatchError extends IngeniumError {
2910
+ constructor(message: string);
2911
+ }
2864
2912
  /**
2865
2913
  * Bearer-token JWT verification middleware.
2866
2914
  *
@@ -3496,6 +3544,11 @@ interface VerifyOptions {
3496
3544
  issuer?: string | readonly string[];
3497
3545
  maxAgeSeconds?: number;
3498
3546
  clockSkewSeconds?: number;
3547
+ /**
3548
+ * Require a finite numeric `exp`. Default `true` — a token without an
3549
+ * expiry would otherwise verify forever (see middleware `requireExp`).
3550
+ */
3551
+ requireExp?: boolean;
3499
3552
  /** Override "now" for deterministic tests. Returns seconds since epoch. */
3500
3553
  nowSeconds?: () => number;
3501
3554
  }
@@ -3658,7 +3711,20 @@ interface SessionCookieOptions {
3658
3711
  httpOnly?: boolean;
3659
3712
  /** Cookie `SameSite` attribute. @default 'lax' */
3660
3713
  sameSite?: 'lax' | 'strict' | 'none';
3661
- /** Cookie `Secure` attribute. @default false */
3714
+ /**
3715
+ * Cookie `Secure` attribute.
3716
+ *
3717
+ * When left undefined, {@link sessionMiddleware} resolves it safely: ON in
3718
+ * production (`NODE_ENV==='production'`) so the cookie cannot ride plaintext
3719
+ * HTTP, OFF in dev so `http://localhost` keeps working. Set `false`
3720
+ * explicitly to opt out in production (emits a dev warning), or `true` to
3721
+ * force it on in every environment.
3722
+ *
3723
+ * Note: the raw {@link serializeCookie} helper still treats `undefined` as
3724
+ * off — only the middleware applies the environment-aware default.
3725
+ *
3726
+ * @default undefined (production → Secure, dev → not Secure)
3727
+ */
3662
3728
  secure?: boolean;
3663
3729
  }
3664
3730
  /** Options accepted by {@link sessionMiddleware}. */
@@ -3762,8 +3828,10 @@ interface SessionStore {
3762
3828
  * - HMAC-SHA-256 over the session id, base64url-encoded; verified with
3763
3829
  * `timingSafeEqual`.
3764
3830
  * - 144-bit (18-byte) random ids.
3765
- * - Defaults: `HttpOnly`, `SameSite=Lax`, `Path=/`. Set `secure: true`
3766
- * behind TLS to enable `Secure`.
3831
+ * - Defaults: `HttpOnly`, `SameSite=Lax`, `Path=/`. `Secure` defaults ON in
3832
+ * production (`NODE_ENV==='production'`) and OFF in dev so http://localhost
3833
+ * keeps working; set `cookie.secure: false` to force it off in production
3834
+ * (a dev warning fires), or `true` to force it on everywhere.
3767
3835
  * - Tampered or unknown cookies silently issue a fresh session — never an
3768
3836
  * error response, since this is an attacker-influenced surface.
3769
3837
  */
@@ -3810,12 +3878,40 @@ type WebSocket = WebSocket$1;
3810
3878
  * are not meaningful for WS handlers (the upgrade has already happened).
3811
3879
  */
3812
3880
  type WebSocketHandler = (socket: WebSocket$1, ctx: IngeniumContext) => void | Promise<void>;
3881
+ /**
3882
+ * Custom Origin verifier. Receives the request's `Origin` header (or
3883
+ * `undefined` when absent, e.g. a non-browser client) plus the raw upgrade
3884
+ * request. Return `true` to allow the upgrade, `false` to reject it with a
3885
+ * `403` handshake.
3886
+ */
3887
+ type WebSocketOriginVerifier = (origin: string | undefined, req: IncomingMessage) => boolean;
3888
+ /**
3889
+ * Origin allowlist policy for a WebSocket upgrade. WS handlers run OUTSIDE the
3890
+ * normal middleware pipeline, so the only built-in defense against
3891
+ * Cross-Site WebSocket Hijacking (CSWSH) is this option — browsers attach the
3892
+ * victim's cookies to cross-origin upgrades, so without an Origin check any
3893
+ * external page can open an authenticated socket.
3894
+ *
3895
+ * - `true` — same-origin only: the `Origin` host must equal the request `Host`.
3896
+ * - `string` / `string[]` — exact `Origin` allowlist (compared verbatim).
3897
+ * - function — custom predicate (see {@link WebSocketOriginVerifier}).
3898
+ *
3899
+ * Omitting the option preserves backward compatibility (no enforcement) but
3900
+ * emits a one-time dev warning, because the safe default for a browser-facing
3901
+ * socket is to restrict origins.
3902
+ */
3903
+ type WebSocketOriginOption = boolean | string | string[] | WebSocketOriginVerifier;
3813
3904
  /** Per-handler options forwarded to `WebSocketServer({ noServer: true, ... })`. */
3814
3905
  interface WebSocketHandlerOptions {
3815
3906
  /** Max payload size (bytes) for incoming frames. */
3816
3907
  maxPayload?: number;
3817
3908
  /** Enable permessage-deflate. Defaults to false (matches `ws` default). */
3818
3909
  perMessageDeflate?: boolean;
3910
+ /**
3911
+ * Origin allowlist for the upgrade handshake — the built-in CSWSH defense.
3912
+ * See {@link WebSocketOriginOption} for semantics and why it matters.
3913
+ */
3914
+ origin?: WebSocketOriginOption;
3819
3915
  }
3820
3916
  /** Bag passed to integrators (advanced). */
3821
3917
  interface WsIntegrator {
@@ -4259,4 +4355,4 @@ declare const ingenium: IngeniumFactory & {
4259
4355
  openapiHandler: typeof openapiHandler;
4260
4356
  };
4261
4357
 
4262
- export { type ApiKeyLogger, type ApiKeyOptions, type ApiKeyValidator, type CachedResponse, type CloseOptions, type ComposedHandler, type CookieGetOptions, type CookieSetOptions, type CorsOptions, type CorsOrigin, type CorsOriginFn, type CronHandler, type CronMatch, type CronOptions, CronRegistry, type CsrfCookieOptions, type CsrfOptions, type CsrfStorage, type CsrfValueReader, type Decorator, DecoratorRegistry, type EagerDecorator, type EnableWebSocketsOptions, type ExtractParams, type FailedJob, type FormatHandlers, type FormattableCtx, type ForwardedInfo, type GenerateOpenApiOptions, HTTP_METHODS, type HeaderBag, type Hooks, HooksRegistry, Http2Adapter, type Http2AdapterOptions, Http2cAdapter, type HttpMethod, IdempotencyMemoryStore, type IdempotencyOptions, type IdempotencyStore, IngeniumApp, type IngeniumAppOptions, IngeniumBadRequestError, IngeniumBody, IngeniumContext, IngeniumContextPool, type IngeniumCookies, IngeniumCronJob, IngeniumCsrfError, IngeniumError, type IngeniumErrorHandler, IngeniumHaltError, type IngeniumHandler, IngeniumHeaderInjectionError, IngeniumMethodNotAllowedError, type IngeniumMiddleware, IngeniumNotFoundError, IngeniumPayloadTooLargeError, type IngeniumPlugin, type IngeniumQuery, IngeniumQueue, IngeniumTimeoutError, IngeniumUnauthorizedError, IngeniumUnserializableError, IngeniumValidationError, type InjectRequest, type InjectResponse, type JobHandle, type JsonEtagCtx, type JsonEtagOptions, type JwtAlgorithm, type JwtHeader, type JwtKey, type JwtLogger, type JwtOptions, type JwtSecret, type JwtSecretResolver, type JwtTokenReader, type JwtVerified, type LazyDecorator, type ListeningServer, type MatchMiss, type MatchResult, MemoryQueueStore, type MultipartFile, type MultipartOptions, type MultipartResult, type NegotiableCtx, NodeAdapter, type OnComposeHook, type OnErrorHook, type OnRequestHook, type OnResponseHook, type OnRouteHook, type Components as OpenApiComponents, type Info as OpenApiInfo, type Response as OpenApiResponse, type Schema as OpenApiSchema, type SecurityRequirement as OpenApiSecurityRequirement, type SecurityScheme as OpenApiSecurityScheme, type Server as OpenApiServer, type OpenApiSpec, type Tag as OpenApiTag, type Operation, type Parameter, type ParseSchema, type ParsedAccept, type PathItem, type PluginTarget, type ProblemDetails, type ProblemDetailsOptions, type QueueOptions, QueueRegistry, type QueueStore, type QueueWorker, MemoryStore$1 as RateLimitMemoryStore, type RateLimitOptions, type RateLimitStore, type RegisteredQueue, type RegistrationEvent, type RequestBody, type ResponseBody, type RetryPolicy, RouteBuilder, type RouteDescriptor, type RouteOptions, Router, RouterTrie, type SafeJsonStringifyOptions, type SafeParseSchema, ScopedApp, type Session, type SessionCookieOptions, MemoryStore as SessionMemoryStore, type SessionOptions, type SessionStore, type ShutdownOptions, type SseEvent, type SseStream, type StandardFailureResult, type StandardIssue, type StandardPathSegment, type StandardResult, type StandardSchemaV1, type StandardSchemaV1Props, type StandardSuccessResult, type StaticOptions, type Transport, type TransportHooks, TrieNode, type TrustProxy, type WebSocket, type WebSocketHandler, type WebSocketHandlerOptions, type WsIntegrator, WsNodeAdapter, type WsRegistrar, _resetDefaultApp, accepts, acceptsCharsets, acceptsEncodings, acceptsLanguages, after, apiKeyMiddleware, before, clearJwksCache, compose, composeWithHandler, computeEtag, corsMiddleware as cors_, createWebSocketRegistrar, csrfMiddleware, ingenium as default, defaultApp, del as delete, enableWebSockets, expandShorthand, fetchJwks, formatResponse, generateOpenApi, get, gracefulShutdown, head, idempotencyMiddleware, ingenium, isFresh, isStandardSchema, jwtMiddleware, listen, nextFireFrom, onError, openapiHandler, options, parseAcceptHeader, parseCronSpec, patch, peerHasWs, post, problemDetailsMiddleware, put, rateLimit, resolveForwarded, respondJsonWithEtag, safeJsonStringify, selectBest, sessionMiddleware, sortByPreference, sse, startKeepAlive, staticMiddleware as static_, toProblemDetails, use, verifyJwt };
4358
+ export { type ApiKeyLogger, type ApiKeyOptions, type ApiKeyValidator, type CachedResponse, type CloseOptions, type ComposedHandler, type CookieGetOptions, type CookieSetOptions, type CorsOptions, type CorsOrigin, type CorsOriginFn, type CronHandler, type CronMatch, type CronOptions, CronRegistry, type CsrfCookieOptions, type CsrfOptions, type CsrfStorage, type CsrfValueReader, type Decorator, DecoratorRegistry, type EagerDecorator, type EnableWebSocketsOptions, type ExtractParams, type FailedJob, type FormatHandlers, type FormattableCtx, type ForwardedInfo, type GenerateOpenApiOptions, HTTP_METHODS, type HeaderBag, type Hooks, HooksRegistry, Http2Adapter, type Http2AdapterOptions, Http2cAdapter, type HttpMethod, IdempotencyMemoryStore, type IdempotencyOptions, type IdempotencyStore, IngeniumApp, type IngeniumAppOptions, IngeniumBadRequestError, IngeniumBody, IngeniumContext, IngeniumContextPool, type IngeniumCookies, IngeniumCronJob, IngeniumCsrfError, IngeniumError, type IngeniumErrorHandler, IngeniumHaltError, type IngeniumHandler, IngeniumHeaderInjectionError, IngeniumJwtKeyAlgMismatchError, IngeniumMethodNotAllowedError, type IngeniumMiddleware, IngeniumNotFoundError, IngeniumPayloadTooLargeError, type IngeniumPlugin, type IngeniumQuery, IngeniumQueue, IngeniumTimeoutError, IngeniumUnauthorizedError, IngeniumUnserializableError, IngeniumValidationError, type InjectRequest, type InjectResponse, type JobHandle, type JsonEtagCtx, type JsonEtagOptions, type JwtAlgorithm, type JwtHeader, type JwtKey, type JwtLogger, type JwtOptions, type JwtSecret, type JwtSecretResolver, type JwtTokenReader, type JwtVerified, type LazyDecorator, type ListeningServer, type MatchMiss, type MatchResult, MemoryQueueStore, type MultipartFile, type MultipartOptions, type MultipartResult, type NegotiableCtx, NodeAdapter, type OnComposeHook, type OnErrorHook, type OnRequestHook, type OnResponseHook, type OnRouteHook, type Components as OpenApiComponents, type Info as OpenApiInfo, type Response as OpenApiResponse, type Schema as OpenApiSchema, type SecurityRequirement as OpenApiSecurityRequirement, type SecurityScheme as OpenApiSecurityScheme, type Server as OpenApiServer, type OpenApiSpec, type Tag as OpenApiTag, type Operation, type Parameter, type ParseSchema, type ParsedAccept, type PathItem, type PluginTarget, type ProblemDetails, type ProblemDetailsOptions, type QueueOptions, QueueRegistry, type QueueStore, type QueueWorker, MemoryStore$1 as RateLimitMemoryStore, type RateLimitOptions, type RateLimitStore, type RegisteredQueue, type RegistrationEvent, type RequestBody, type ResponseBody, type RetryPolicy, RouteBuilder, type RouteDescriptor, type RouteOptions, Router, RouterTrie, type SafeJsonStringifyOptions, type SafeParseSchema, ScopedApp, type Session, type SessionCookieOptions, MemoryStore as SessionMemoryStore, type SessionOptions, type SessionStore, type ShutdownOptions, type SseEvent, type SseStream, type StandardFailureResult, type StandardIssue, type StandardPathSegment, type StandardResult, type StandardSchemaV1, type StandardSchemaV1Props, type StandardSuccessResult, type StaticOptions, type Transport, type TransportHooks, TrieNode, type TrustProxy, type WebSocket, type WebSocketHandler, type WebSocketHandlerOptions, type WsIntegrator, WsNodeAdapter, type WsRegistrar, _resetDefaultApp, accepts, acceptsCharsets, acceptsEncodings, acceptsLanguages, after, apiKeyMiddleware, before, clearJwksCache, compose, composeWithHandler, computeEtag, corsMiddleware as cors_, createWebSocketRegistrar, csrfMiddleware, ingenium as default, defaultApp, del as delete, enableWebSockets, expandShorthand, fetchJwks, formatResponse, generateOpenApi, get, gracefulShutdown, head, idempotencyMiddleware, ingenium, isFresh, isStandardSchema, jwtMiddleware, listen, nextFireFrom, onError, openapiHandler, options, parseAcceptHeader, parseCronSpec, patch, peerHasWs, post, problemDetailsMiddleware, put, rateLimit, resolveForwarded, respondJsonWithEtag, safeJsonStringify, selectBest, sessionMiddleware, sortByPreference, sse, startKeepAlive, staticMiddleware as static_, toProblemDetails, use, verifyJwt };