{"version":3,"file":"index.cjs","names":[],"sources":["../../../src/invalidators/redis/index.ts"],"sourcesContent":["import { createHmac, timingSafeEqual } from 'node:crypto'\nimport type { IamEngineTypes } from '../../core/engine/engine.types'\n\n/** Redis invalidator integration types. Type-only namespace - zero bundle cost. */\nexport namespace IamRedisInvalidator {\n  /**\n   * Describes the minimum pub/sub surface needed by the Redis invalidator.\n   *\n   * Both ioredis and node-redis v4+ implement this shape; call sites stay\n   * intentionally narrow to avoid pulling in either as a hard dependency.\n   * Pass two clients - Redis requires a separate connection per subscriber.\n   */\n  export interface IPubSubLike {\n    /**\n     * Publishes the JSON payload on the given channel for every local admin\n     * mutation. Synchronous return is fine; the caller does not await.\n     *\n     * @param channel - Names the channel to publish on.\n     * @param message - Serialised JSON payload to broadcast.\n     * @returns Whatever the underlying client returns; ignored by the invalidator.\n     */\n    publish(channel: string, message: string): unknown\n    /**\n     * Subscribes the handler to incoming messages on the channel. The factory\n     * calls this once with the channel name and a raw-message handler.\n     *\n     * @param channel - Names the channel to subscribe to.\n     * @param handler - Receives each raw message string from the channel.\n     * @returns Void synchronously or a promise the caller may await on startup.\n     */\n    subscribe(channel: string, handler: (message: string) => void): void | Promise<void>\n    /**\n     * Tears down the subscription. Engine calls this on `dispose()`. Optional -\n     * passing a no-op stub is fine if your client manages connection lifecycle\n     * out of band.\n     *\n     * @param channel - Names the channel to detach from.\n     * @returns Void synchronously or a promise.\n     */\n    unsubscribe?(channel: string): void | Promise<void>\n  }\n\n  /** Configures {@link createIamRedisInvalidator}. */\n  export interface IConfig {\n    /** Redis pub/sub adapter implementing {@link IPubSubLike}. */\n    client: IPubSubLike\n    /**\n     * Channel name. Every engine subscribing to the same channel shares an\n     * invalidate broadcast group. Defaults to `'duck-iam:invalidate'`. Use\n     * {@link tenantId} for the common multi-tenant case instead of building\n     * the channel name yourself.\n     */\n    channel?: string\n    /**\n     * CAVEAT-1: convenience helper for multi-tenant deployments. When set,\n     * the effective channel becomes `duck-iam:invalidate:tenant:${tenantId}`\n     * (or `${channel}:tenant:${tenantId}` if `channel` is also given). This\n     * guarantees tenant isolation on a shared Redis instance - tenant A's\n     * revoke cannot wipe tenant B's cache.\n     *\n     * Validates against `/^[A-Za-z0-9_-]{1,64}$/` to keep channel names\n     * pub/sub-safe and prevent injection via attacker-controlled tenant\n     * identifiers.\n     *\n     * @example\n     * ```ts\n     * createIamRedisInvalidator({ client, secret, tenantId: req.tenantSlug })\n     * ```\n     */\n    tenantId?: string\n    /**\n     * Shared HMAC secret. When set, every published envelope is signed with\n     * `HMAC-SHA256(secret, canonicalJSON(payload))` and incoming envelopes\n     * without a verifying signature are dropped (silent - one `console.warn`\n     * per channel on the first rejection). When `null` or omitted (default),\n     * the invalidator falls back to legacy unsigned envelopes and warns once\n     * at construction. Any party with PUBLISH rights to the channel can wipe\n     * caches in that mode; set a secret in production.\n     */\n    secret?: string | null\n    /**\n     * Invoked when the underlying `client.publish(...)` throws. The publish\n     * failure is non-fatal for the local engine (it already applied the\n     * invalidation), but cross-instance invalidations are lost - wire this\n     * to your alerting pipeline so a long-lived Redis outage does not\n     * silently desync caches across nodes.\n     */\n    onPublishError?: (err: Error, channel: string) => void\n  }\n}\n\nconst DEFAULT_CHANNEL = 'duck-iam:invalidate'\n\n/** Replay window in milliseconds. Signed envelopes older than this are dropped. */\nconst REPLAY_WINDOW_MS = 30_000\n\n/** Wire-format version. Bump when the envelope shape changes incompatibly. */\nconst ENVELOPE_V = 1\n\n/** Module-level latch for the unsigned-mode warning. Fires at most once per process. */\nconst _UNSIGNED_WARNED = { fired: false }\n\n/**\n * Per-channel rate-limited warn state. Rate-limits warns at a tunable\n * interval and surfaces a coalesced suppressed-count so operators see\n * sustained abuse instead of silence after a single benign first warn.\n */\nconst _DROP_WARN_STATE = new Map<string, { lastWarn: number; suppressed: number }>()\n/** Minimum gap between drop warns for a single channel. */\nconst DROP_WARN_WINDOW_MS = 60_000\n\n/**\n * Guard limits applied to incoming wire messages BEFORE the HMAC verifier\n * (pre-auth - must be cheap and stack-safe).\n */\nconst MAX_WIRE_BYTES = 16 * 1024\nconst MAX_PAYLOAD_DEPTH = 8\nconst MAX_PAYLOAD_KEYS = 64\n/** Depth-of-defence cap on {@link canonicalJSON} recursion itself. */\nconst CANONICAL_MAX_DEPTH = 16\n\n/**\n * Iterative walker counting nesting depth + total key count of an\n * already-parsed JSON value. Non-recursive so the guard itself cannot stack-\n * overflow even on adversarial input. Returns `null` if either cap is\n * exceeded.\n */\nfunction _measurePayload(root: unknown): { depth: number; keys: number } | null {\n  if (root === null || typeof root !== 'object') return { depth: 0, keys: 0 }\n  // Stack entries: [node, depth]. Depth of root container itself is 1.\n  const stack: Array<[unknown, number]> = [[root, 1]]\n  let maxDepth = 0\n  let totalKeys = 0\n  while (stack.length > 0) {\n    const top = stack.pop()\n    if (!top) break\n    const [node, depth] = top\n    if (depth > maxDepth) maxDepth = depth\n    if (depth > MAX_PAYLOAD_DEPTH) return null\n    if (Array.isArray(node)) {\n      for (let i = 0; i < node.length; i++) {\n        const child = node[i]\n        if (child !== null && typeof child === 'object') stack.push([child, depth + 1])\n      }\n    } else if (node !== null && typeof node === 'object') {\n      const keys = Object.keys(node as Record<string, unknown>)\n      totalKeys += keys.length\n      if (totalKeys > MAX_PAYLOAD_KEYS) return null\n      const obj = node as Record<string, unknown>\n      for (const key of keys) {\n        const child = obj[key]\n        if (child !== null && typeof child === 'object') stack.push([child, depth + 1])\n      }\n    }\n  }\n  return { depth: maxDepth, keys: totalKeys }\n}\n\n/**\n * Canonical JSON serializer with stable key order. Used as the HMAC pre-image\n * so publisher and verifier agree on the exact byte string regardless of how\n * the host JSON engine orders object keys. Arrays preserve order; objects sort\n * keys lexicographically.\n *\n * Defence in depth: bounded recursion. Callers in the verify path\n * additionally enforce a depth/size/key-count guard on the parsed payload\n * before invoking this function - `_depth` here protects the publish path\n * and any future caller that bypasses the wire guard.\n */\nfunction canonicalJSON(v: unknown, _depth = 0): string {\n  if (_depth > CANONICAL_MAX_DEPTH) throw new Error('canonicalJSON: max depth exceeded')\n  if (v === null || typeof v !== 'object') return JSON.stringify(v)\n  if (Array.isArray(v)) return `[${v.map((x) => canonicalJSON(x, _depth + 1)).join(',')}]`\n  const keys = Object.keys(v as Record<string, unknown>).sort()\n  const obj = v as Record<string, unknown>\n  return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJSON(obj[k], _depth + 1)}`).join(',')}}`\n}\n\n/**\n * Internal envelope produced by the publisher. Wire layout:\n *   { v: 1, sig: <hex>, payload: { instanceId, event, ts } }\n * For unsigned mode, the legacy shape `{ instanceId, event }` is used.\n */\ninterface SignedEnvelope<TRole extends string> {\n  readonly v: 1\n  readonly sig: string\n  readonly payload: {\n    readonly instanceId: string\n    readonly event: IamEngineTypes.IInvalidateEvent<TRole>\n    readonly ts: number\n  }\n}\n\n/**\n * Constant-time hex string compare. Wraps Buffer construction so callers don't\n * have to handle length mismatch (which would short-circuit `timingSafeEqual`\n * and leak a timing oracle on signature length).\n */\nfunction safeHexEqual(a: string, b: string): boolean {\n  if (typeof a !== 'string' || typeof b !== 'string') return false\n  if (a.length !== b.length) return false\n  let ab: Buffer\n  let bb: Buffer\n  try {\n    ab = Buffer.from(a, 'hex')\n    bb = Buffer.from(b, 'hex')\n  } catch {\n    return false\n  }\n  if (ab.length !== bb.length || ab.length === 0) return false\n  return timingSafeEqual(ab, bb)\n}\n\n/**\n * Creates a cross-instance cache-invalidation broadcaster backed by Redis pub/sub.\n *\n * Delivery is at-least-once: every engine's local invalidate methods are\n * idempotent so re-applying the same event is safe. Filters self-published\n * events via an instance UUID embedded in the payload - without this guard\n * every local invalidate would echo back through the subscriber and re-clear\n * caches we just rebuilt.\n *\n * When `init.secret` is set, envelopes are signed with HMAC-SHA256 and\n * verified on receive with a 30-second replay window keyed off `payload.ts`.\n * Without a secret the invalidator falls back to legacy unsigned envelopes\n * (logged once); anyone with PUBLISH rights on the channel can then wipe\n * caches -> set a secret in production.\n *\n * @template TRole - Role identifier union the engine is parameterised over.\n * @param config - Supplies the client and optional channel; see {@link IamRedisInvalidator.IConfig}.\n * @returns An {@link IamEngineTypes.IInvalidator} bound to the configured channel.\n * @example\n * ```ts\n * import { createIamRedisInvalidator } from '@gentleduck/iam/invalidators/redis'\n *\n * const engine = new IamEngine({\n *   adapter,\n *   invalidator: createIamRedisInvalidator({\n *     client: redisPubSub,\n *     secret: process.env.IAM_INVALIDATE_SECRET,\n *   }),\n * })\n * ```\n */\nexport function createIamRedisInvalidator<TRole extends string = string>(\n  config: IamRedisInvalidator.IConfig,\n): IamEngineTypes.IInvalidator<TRole> {\n  const baseChannel = config.channel ?? DEFAULT_CHANNEL\n  // Tenant slug shape-validated to prevent pub/sub wildcard injection.\n  let channel = baseChannel\n  if (config.tenantId !== undefined) {\n    if (!/^[A-Za-z0-9_-]{1,64}$/.test(config.tenantId)) {\n      throw new Error(\n        '[@gentleduck/iam:invalidator:redis] tenantId must match /^[A-Za-z0-9_-]{1,64}$/ (got ' +\n          JSON.stringify(config.tenantId) +\n          ')',\n      )\n    }\n    channel = `${baseChannel}:tenant:${config.tenantId}`\n  }\n  const instanceId = generateInstanceId()\n  const handlers = new Set<(event: IamEngineTypes.IInvalidateEvent<TRole>) => void>()\n  const secret = config.secret ?? null\n\n  if (secret === null && !_UNSIGNED_WARNED.fired) {\n    _UNSIGNED_WARNED.fired = true\n    console.warn(\n      '[@gentleduck/iam:invalidator:redis] `secret` not set - accepting unsigned pub/sub. Anyone with PUBLISH rights on the channel can wipe caches. Pass `secret` to require HMAC-SHA256.',\n    )\n  }\n\n  function warnDropOnce(channelName: string, reason: string): void {\n    const now = Date.now()\n    const state = _DROP_WARN_STATE.get(channelName)\n    if (!state) {\n      _DROP_WARN_STATE.set(channelName, { lastWarn: now, suppressed: 0 })\n      console.warn(\n        `[@gentleduck/iam:invalidator:redis] dropping unverifiable message on channel ${JSON.stringify(channelName)} (${reason}). Further drops within ${DROP_WARN_WINDOW_MS}ms are coalesced.`,\n      )\n      return\n    }\n    if (now - state.lastWarn < DROP_WARN_WINDOW_MS) {\n      state.suppressed++\n      return\n    }\n    // Window elapsed; surface the suppressed count so operators see sustained abuse.\n    const suppressed = state.suppressed\n    state.lastWarn = now\n    state.suppressed = 0\n    console.warn(\n      `[@gentleduck/iam:invalidator:redis] dropping unverifiable message on channel ${JSON.stringify(channelName)} (${reason}). ${suppressed} prior drops coalesced.`,\n    )\n  }\n\n  let subscribed = false\n  const ensureSubscribed = () => {\n    if (subscribed) return\n    subscribed = true\n    void Promise.resolve(\n      config.client.subscribe(channel, (message) => {\n        const parsed = parseIncoming<TRole>(message, secret, channel, warnDropOnce)\n        // Drop messages that originated on this instance - local mutations\n        // already cleared local caches; replaying would just double the work\n        // and risk an invalidation storm under high write QPS.\n        if (!parsed || parsed.instanceId === instanceId) return\n        for (const h of handlers) h(parsed.event)\n      }),\n    )\n  }\n\n  return {\n    publish(event) {\n      let payload: string\n      if (secret !== null) {\n        const inner = { event, instanceId, ts: Date.now() }\n        const sig = createHmac('sha256', secret).update(canonicalJSON(inner)).digest('hex')\n        const envelope: SignedEnvelope<TRole> = { payload: inner, sig, v: ENVELOPE_V }\n        payload = JSON.stringify(envelope)\n      } else {\n        payload = JSON.stringify({ event, instanceId })\n      }\n      try {\n        config.client.publish(channel, payload)\n      } catch (err) {\n        // Publish failure is non-fatal; route to onPublishError so a\n        // long-lived outage does not silently desync caches.\n        const error = err instanceof Error ? err : new Error(String(err))\n        try {\n          config.onPublishError?.(error, channel)\n        } catch {\n          /* operator hook itself threw - preserve fail-soft contract */\n        }\n        if (!config.onPublishError) {\n          warnDropOnce(channel, `publish failed (${error.message})`)\n        }\n      }\n    },\n    subscribe(handler) {\n      ensureSubscribed()\n      handlers.add(handler)\n      return () => {\n        handlers.delete(handler)\n        if (handlers.size === 0) {\n          subscribed = false\n          void config.client.unsubscribe?.(channel)\n        }\n      }\n    },\n  }\n}\n\n/**\n * Decodes and validates an incoming wire message.\n *\n * When `secret` is `null` we accept legacy `{instanceId, event}` only. v:1\n * envelopes are dropped in unsigned mode - accepting them without verifying\n * the HMAC would let an attacker forge the `instanceId` field, which the\n * self-filter uses to ignore own-process replays. A forged match would\n * suppress legitimate cross-instance invalidations. When `secret` is set we\n * require a v:1 envelope, verify the HMAC, and enforce the replay window.\n * Anything else is dropped silently after a one-shot warn per channel.\n *\n * Returned shape is normalized to `{instanceId, event}` so the caller doesn't\n * branch on wire format.\n */\nfunction parseIncoming<TRole extends string>(\n  s: string,\n  secret: string | null,\n  channel: string,\n  warnDropOnce: (channel: string, reason: string) => void,\n): { instanceId: string; event: IamEngineTypes.IInvalidateEvent<TRole> } | null {\n  // Pre-auth size cap; canonicalJSON runs before HMAC verify.\n  if (typeof s !== 'string') return null\n  // Byte length (not `s.length`); surrogate pairs would sneak past the cap.\n  if (Buffer.byteLength(s, 'utf8') > MAX_WIRE_BYTES) {\n    warnDropOnce(channel, 'oversize wire message')\n    return null\n  }\n  let parsed: unknown\n  try {\n    parsed = JSON.parse(s)\n  } catch {\n    if (secret !== null) warnDropOnce(channel, 'invalid JSON')\n    return null\n  }\n  if (typeof parsed !== 'object' || parsed === null) return null\n  // Iterative depth/key-count cap so canonicalJSON pre-image is always safe.\n  if (_measurePayload(parsed) === null) {\n    warnDropOnce(channel, 'payload exceeds depth/key limits')\n    return null\n  }\n\n  // v:1 signed envelope path\n  if (Reflect.get(parsed, 'v') === ENVELOPE_V) {\n    // Refuse v:1 without secret; forged instanceId would silence invalidations.\n    if (secret === null) {\n      warnDropOnce(channel, 'v:1 envelope received without secret configured')\n      return null\n    }\n    const sig = Reflect.get(parsed, 'sig')\n    const payload = Reflect.get(parsed, 'payload')\n    if (typeof sig !== 'string' || typeof payload !== 'object' || payload === null) {\n      warnDropOnce(channel, 'malformed envelope')\n      return null\n    }\n    // Verify signature against canonical pre-image. Use constant-time compare.\n    const expected = createHmac('sha256', secret).update(canonicalJSON(payload)).digest('hex')\n    if (!safeHexEqual(sig, expected)) {\n      warnDropOnce(channel, 'signature mismatch')\n      return null\n    }\n    // Replay window check.\n    const ts = Reflect.get(payload, 'ts')\n    if (typeof ts !== 'number' || !Number.isFinite(ts)) {\n      warnDropOnce(channel, 'missing or invalid ts')\n      return null\n    }\n    const age = Date.now() - ts\n    if (age > REPLAY_WINDOW_MS || age < -REPLAY_WINDOW_MS) {\n      warnDropOnce(channel, 'replay window exceeded')\n      return null\n    }\n    // Shape-check inner payload.\n    const instanceId = Reflect.get(payload, 'instanceId')\n    if (typeof instanceId !== 'string') {\n      warnDropOnce(channel, 'malformed inner payload (instanceId)')\n      return null\n    }\n    const ev = Reflect.get(payload, 'event')\n    if (!_isValidEvent<TRole>(ev)) {\n      warnDropOnce(channel, 'malformed inner payload (event)')\n      return null\n    }\n    return { event: ev, instanceId }\n  }\n\n  // Legacy unsigned envelope: only allowed when no secret is configured.\n  if (secret !== null) {\n    warnDropOnce(channel, 'unsigned message with secret configured')\n    return null\n  }\n  const legacyInstanceId = Reflect.get(parsed, 'instanceId')\n  if (typeof legacyInstanceId !== 'string') {\n    warnDropOnce(channel, 'malformed legacy payload (instanceId)')\n    return null\n  }\n  const ev = Reflect.get(parsed, 'event')\n  if (!_isValidEvent<TRole>(ev)) {\n    warnDropOnce(channel, 'malformed legacy payload (event)')\n    return null\n  }\n  return { event: ev, instanceId: legacyInstanceId }\n}\n\n/**\n * Type predicate for the {@link IamEngineTypes.IInvalidateEvent} discriminated\n * union. Enforces per-kind required fields so a tampered payload cannot\n * trigger an invalidate with an undefined `subjectId` or `roleId`.\n */\nfunction _isValidEvent<TRole extends string>(ev: unknown): ev is IamEngineTypes.IInvalidateEvent<TRole> {\n  if (typeof ev !== 'object' || ev === null || Array.isArray(ev)) return false\n  const kind = Reflect.get(ev, 'kind')\n  if (kind === 'all' || kind === 'policies') return true\n  if (kind === 'roles') {\n    const roleId = Reflect.get(ev, 'roleId')\n    return roleId === undefined || (typeof roleId === 'string' && roleId.length > 0)\n  }\n  if (kind === 'subject') {\n    const subjectId = Reflect.get(ev, 'subjectId')\n    return typeof subjectId === 'string' && subjectId.length > 0\n  }\n  return false\n}\n\nfunction generateInstanceId(): string {\n  if (typeof globalThis.crypto?.randomUUID === 'function') return globalThis.crypto.randomUUID()\n  return `iam-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\n}\n"],"mappings":"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"}