@adcp/sdk 7.8.0 → 7.9.0
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/AGENTS.md +2 -0
- package/bin/adcp.js +3 -1
- package/dist/lib/core/AgentClient.d.ts.map +1 -1
- package/dist/lib/core/SingleAgentClient.d.ts +8 -0
- package/dist/lib/core/SingleAgentClient.d.ts.map +1 -1
- package/dist/lib/core/SingleAgentClient.js +15 -0
- package/dist/lib/core/SingleAgentClient.js.map +1 -1
- package/dist/lib/index.d.ts +2 -2
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +8 -7
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/protocols/mcp.d.ts.map +1 -1
- package/dist/lib/protocols/mcp.js +63 -4
- package/dist/lib/protocols/mcp.js.map +1 -1
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/ctx-metadata/backends/pg.d.ts +12 -3
- package/dist/lib/server/ctx-metadata/backends/pg.d.ts.map +1 -1
- package/dist/lib/server/ctx-metadata/backends/pg.js +59 -23
- package/dist/lib/server/ctx-metadata/backends/pg.js.map +1 -1
- package/dist/lib/server/ctx-metadata/backends/redis.d.ts +163 -0
- package/dist/lib/server/ctx-metadata/backends/redis.d.ts.map +1 -0
- package/dist/lib/server/ctx-metadata/backends/redis.js +204 -0
- package/dist/lib/server/ctx-metadata/backends/redis.js.map +1 -0
- package/dist/lib/server/ctx-metadata/index.d.ts +2 -0
- package/dist/lib/server/ctx-metadata/index.d.ts.map +1 -1
- package/dist/lib/server/ctx-metadata/index.js +3 -1
- package/dist/lib/server/ctx-metadata/index.js.map +1 -1
- package/dist/lib/server/idempotency/backends/redis.d.ts +171 -0
- package/dist/lib/server/idempotency/backends/redis.d.ts.map +1 -0
- package/dist/lib/server/idempotency/backends/redis.js +253 -0
- package/dist/lib/server/idempotency/backends/redis.js.map +1 -0
- package/dist/lib/server/idempotency/index.d.ts +2 -0
- package/dist/lib/server/idempotency/index.d.ts.map +1 -1
- package/dist/lib/server/idempotency/index.js +3 -1
- package/dist/lib/server/idempotency/index.js.map +1 -1
- package/dist/lib/server/idempotency/store.d.ts +44 -0
- package/dist/lib/server/idempotency/store.d.ts.map +1 -1
- package/dist/lib/server/idempotency/store.js.map +1 -1
- package/dist/lib/server/index.d.ts +4 -4
- package/dist/lib/server/index.d.ts.map +1 -1
- package/dist/lib/server/index.js +3 -1
- package/dist/lib/server/index.js.map +1 -1
- package/dist/lib/signing/redis-replay-store.d.ts +180 -0
- package/dist/lib/signing/redis-replay-store.d.ts.map +1 -0
- package/dist/lib/signing/redis-replay-store.js +270 -0
- package/dist/lib/signing/redis-replay-store.js.map +1 -0
- package/dist/lib/signing/server.d.ts +1 -0
- package/dist/lib/signing/server.d.ts.map +1 -1
- package/dist/lib/signing/server.js +4 -2
- package/dist/lib/signing/server.js.map +1 -1
- package/dist/lib/utils/redis-default-prefix-warn.d.ts +60 -0
- package/dist/lib/utils/redis-default-prefix-warn.d.ts.map +1 -0
- package/dist/lib/utils/redis-default-prefix-warn.js +95 -0
- package/dist/lib/utils/redis-default-prefix-warn.js.map +1 -0
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/package.json +8 -2
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis-backed `ReplayStore` for distributed AdCP verifier deployments.
|
|
3
|
+
*
|
|
4
|
+
* Sister of `PostgresReplayStore`. The spec comment on `ReplayStore.insert`
|
|
5
|
+
* (`src/lib/signing/replay.ts`) literally names this as a canonical
|
|
6
|
+
* implementation: "Multi-replica adopters writing Redis / Postgres
|
|
7
|
+
* stores MUST implement this as a single atomic operation (e.g. Redis
|
|
8
|
+
* `SET NX EX`, Postgres `INSERT ... ON CONFLICT DO NOTHING RETURNING`,
|
|
9
|
+
* a `WATCH/MULTI/EXEC` transaction)."
|
|
10
|
+
*
|
|
11
|
+
* **Data model.** One Redis sorted set per `(keyid, scope)` pair. The
|
|
12
|
+
* sorted-set members are nonces; the score is `expiresAt` (unix epoch
|
|
13
|
+
* seconds). Sorted sets give us all three primitives the `ReplayStore`
|
|
14
|
+
* interface needs in single Redis commands:
|
|
15
|
+
*
|
|
16
|
+
* - `has(nonce)` → `ZSCORE` + JS-side `score > now` check.
|
|
17
|
+
* - `isCapHit()` → `ZCOUNT key (now +inf` (active nonces).
|
|
18
|
+
* - `insert()` → a Lua script that runs `ZREMRANGEBYSCORE -inf now`
|
|
19
|
+
* (drop expired) → `ZSCORE` (replay check) → `ZCARD` (cap check) →
|
|
20
|
+
* `ZADD` (insert) + `PEXPIREAT` (extend set's own TTL) atomically.
|
|
21
|
+
*
|
|
22
|
+
* **No sweeper needed.** Expired nonces are dropped at the start of
|
|
23
|
+
* every `insert` (the `ZREMRANGEBYSCORE` step), and Redis evicts the
|
|
24
|
+
* entire sorted set when its `PEXPIREAT` lapses if no further inserts
|
|
25
|
+
* arrive. The Postgres backend's `sweepExpiredReplays` cron has no
|
|
26
|
+
* Redis equivalent — eviction is automatic.
|
|
27
|
+
*
|
|
28
|
+
* **Atomicity.** The Lua script runs as a single uninterruptable
|
|
29
|
+
* operation against the Redis server (Redis is single-threaded; Lua
|
|
30
|
+
* blocks everything else for the script duration). Two concurrent
|
|
31
|
+
* `insert` calls with the same `(keyid, scope, nonce)` are guaranteed
|
|
32
|
+
* to see exactly one `'ok'` and one `'replayed'` — matches the
|
|
33
|
+
* `InMemoryReplayStore` semantics the spec mandates.
|
|
34
|
+
*
|
|
35
|
+
* **Redis memory policy — set this on the deployment.** Each
|
|
36
|
+
* `(keyid, scope)` sorted set is capped (default 100k nonces). A
|
|
37
|
+
* hostile signer with a valid keyid + scope can fill the set to its
|
|
38
|
+
* cap; legitimate fan-out (many sibling verifiers, many scopes) grows
|
|
39
|
+
* the number of sets. Configure your Redis with:
|
|
40
|
+
*
|
|
41
|
+
* - **`maxmemory-policy volatile-lru`** (recommended) — evicts only
|
|
42
|
+
* TTL'd keys. All sets this store writes carry `PEXPIREAT`, so
|
|
43
|
+
* they're all evictable. Safe on a shared Redis instance.
|
|
44
|
+
* - **`maxmemory-policy allkeys-lru`** — only on a dedicated db.
|
|
45
|
+
* Otherwise evicts unrelated app keys.
|
|
46
|
+
* - **`maxmemory-policy noeviction`** (Redis default) — fail-closed:
|
|
47
|
+
* writes start erroring at the memory limit; the verifier will
|
|
48
|
+
* throw and the middleware will return 500 (replay protection
|
|
49
|
+
* stays intact — never fail-open). Operationally noisy but
|
|
50
|
+
* never serves stale replay decisions.
|
|
51
|
+
*
|
|
52
|
+
* Eviction-on-replay implication: if `volatile-lru` evicts a sorted
|
|
53
|
+
* set before its members would have expired naturally, an attacker's
|
|
54
|
+
* later replay of one of those nonces sees a fresh set with no prior
|
|
55
|
+
* entry, returns `'ok'`. The cap is the primary defense; memory-policy
|
|
56
|
+
* eviction is a secondary recovery mode. Size Redis to keep the
|
|
57
|
+
* working set in memory under normal traffic and treat eviction as
|
|
58
|
+
* pressure to scale up, not as a feature.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* import { createClient } from 'redis';
|
|
63
|
+
* import { RedisReplayStore } from '@adcp/sdk/signing/server';
|
|
64
|
+
*
|
|
65
|
+
* const client = createClient({ url: process.env.REDIS_URL });
|
|
66
|
+
* client.on('error', (err) => console.error('redis error', err));
|
|
67
|
+
* await client.connect();
|
|
68
|
+
*
|
|
69
|
+
* const replayStore = new RedisReplayStore(client);
|
|
70
|
+
*
|
|
71
|
+
* app.use(createExpressVerifier({
|
|
72
|
+
* capability: { ... },
|
|
73
|
+
* jwks,
|
|
74
|
+
* replayStore, // <-- shared across instances
|
|
75
|
+
* resolveOperation: mcpToolNameResolver,
|
|
76
|
+
* }));
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
import type { RedisClientType } from 'redis';
|
|
80
|
+
import type { ReplayInsertResult, ReplayStore } from './replay';
|
|
81
|
+
/**
|
|
82
|
+
* Escape-hatch interface for adopters not using the official `redis`
|
|
83
|
+
* client (node-redis v4/v5) — `ioredis`, Upstash, test doubles.
|
|
84
|
+
* Mirrors the methods this store calls.
|
|
85
|
+
*
|
|
86
|
+
* **Footgun: `ioredis.zscore` returns `Promise<string | null>`**, not
|
|
87
|
+
* `Promise<number | null>` like node-redis. The `Number(v)` coercion
|
|
88
|
+
* in the adapter is **mandatory** — without it, the `score > now`
|
|
89
|
+
* comparison inside `has()` becomes a string-vs-number compare that's
|
|
90
|
+
* silently wrong near unix-second boundaries (JS coerces the number
|
|
91
|
+
* to a string for `>`, producing lexicographic ordering — replay
|
|
92
|
+
* decisions break). Test doubles writing their own `zScore` MUST
|
|
93
|
+
* also return `number | null`.
|
|
94
|
+
*
|
|
95
|
+
* @example ioredis adapter
|
|
96
|
+
* ```typescript
|
|
97
|
+
* import Redis from 'ioredis';
|
|
98
|
+
* const ioredis = new Redis(process.env.REDIS_URL!);
|
|
99
|
+
*
|
|
100
|
+
* const client: ReplayRedisLikeClient = {
|
|
101
|
+
* eval: (script, opts) =>
|
|
102
|
+
* ioredis.eval(script, opts.keys.length, ...opts.keys, ...opts.arguments),
|
|
103
|
+
* // Number() coercion is REQUIRED — ioredis.zscore returns string|null.
|
|
104
|
+
* zScore: (k, m) => ioredis.zscore(k, m).then(v => (v === null ? null : Number(v))),
|
|
105
|
+
* zCount: (k, min, max) => ioredis.zcount(k, min, max),
|
|
106
|
+
* ping: () => ioredis.ping(),
|
|
107
|
+
* };
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export interface ReplayRedisLikeClient {
|
|
111
|
+
eval(script: string, options: {
|
|
112
|
+
keys: string[];
|
|
113
|
+
arguments: string[];
|
|
114
|
+
}): Promise<unknown>;
|
|
115
|
+
zScore(key: string, member: string): Promise<number | null>;
|
|
116
|
+
zCount(key: string, min: number | string, max: number | string): Promise<number>;
|
|
117
|
+
ping(): Promise<string>;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Accepted client shape: either a real node-redis client (the typical
|
|
121
|
+
* path — pass `createClient(...)` straight in) or an adapter
|
|
122
|
+
* conforming to `ReplayRedisLikeClient`.
|
|
123
|
+
*/
|
|
124
|
+
export type ReplayRedisBackendClient = RedisClientType<any, any, any> | ReplayRedisLikeClient;
|
|
125
|
+
export interface RedisReplayStoreOptions {
|
|
126
|
+
/**
|
|
127
|
+
* Key prefix prepended to every `(keyid, scope)` Redis key. Defaults
|
|
128
|
+
* to `"adcp:replay:"`.
|
|
129
|
+
*
|
|
130
|
+
* **Sharing a Redis db across deployments? Override this.** The
|
|
131
|
+
* default is fine for a dedicated Redis (or db index). Two verifier
|
|
132
|
+
* deployments sharing the same db with the same default prefix can
|
|
133
|
+
* collide on overlapping `keyid`s — set a deployment-unique prefix
|
|
134
|
+
* or use separate dbs.
|
|
135
|
+
*/
|
|
136
|
+
keyPrefix?: string;
|
|
137
|
+
/**
|
|
138
|
+
* Max retained (unexpired) nonces per `(keyid, scope)` pair before
|
|
139
|
+
* `insert` returns `'rate_abuse'`. Mirrors `PostgresReplayStore`'s
|
|
140
|
+
* cap. Defaults to 100,000.
|
|
141
|
+
*
|
|
142
|
+
* Unlike the Postgres backend, the cap check is **strictly atomic**
|
|
143
|
+
* — it runs inside the same Lua script as the insert. Concurrent
|
|
144
|
+
* inserts at `cap - 1` can never both succeed.
|
|
145
|
+
*/
|
|
146
|
+
cap?: number;
|
|
147
|
+
/**
|
|
148
|
+
* How many seconds past the latest entry's `expiresAt` to keep the
|
|
149
|
+
* sorted set alive in Redis. Defaults to 3600 (1h). See the
|
|
150
|
+
* `DEFAULT_SET_TTL_GRACE_SECONDS` comment for rationale.
|
|
151
|
+
*/
|
|
152
|
+
setTtlGraceSeconds?: number;
|
|
153
|
+
/**
|
|
154
|
+
* Suppress the one-time `console.warn` at construction when the
|
|
155
|
+
* default `keyPrefix` is used against a node-redis client on db 0.
|
|
156
|
+
*/
|
|
157
|
+
suppressDefaultPrefixWarning?: boolean;
|
|
158
|
+
}
|
|
159
|
+
export declare class RedisReplayStore implements ReplayStore {
|
|
160
|
+
private readonly c;
|
|
161
|
+
private readonly keyPrefix;
|
|
162
|
+
private readonly cap;
|
|
163
|
+
private readonly setTtlGraceSeconds;
|
|
164
|
+
constructor(client: ReplayRedisBackendClient, options?: RedisReplayStoreOptions);
|
|
165
|
+
/**
|
|
166
|
+
* Compose the sorted-set Redis key for a `(keyid, scope)` pair.
|
|
167
|
+
* Uses `\x1f` (unit separator) between segments — illegal in RFC
|
|
168
|
+
* 7517 JWK kids and in URLs, so no legitimate input collides.
|
|
169
|
+
*/
|
|
170
|
+
private redisKey;
|
|
171
|
+
/**
|
|
172
|
+
* Probe — ping Redis. Not part of the `ReplayStore` interface; call
|
|
173
|
+
* before serving traffic if you want a boot-time readiness check.
|
|
174
|
+
*/
|
|
175
|
+
probe(): Promise<void>;
|
|
176
|
+
has(keyid: string, scope: string, nonce: string, now: number): Promise<boolean>;
|
|
177
|
+
isCapHit(keyid: string, scope: string, now: number): Promise<boolean>;
|
|
178
|
+
insert(keyid: string, scope: string, nonce: string, ttlSeconds: number, now: number): Promise<ReplayInsertResult>;
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=redis-replay-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-replay-store.d.ts","sourceRoot":"","sources":["../../../src/lib/signing/redis-replay-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6EG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAGhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzF,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5D,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjF,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAAG,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,qBAAqB,CAAC;AAe9F,MAAM,WAAW,uBAAuB;IACtC;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;OAQG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,4BAA4B,CAAC,EAAE,OAAO,CAAC;CACxC;AAuFD,qBAAa,gBAAiB,YAAW,WAAW;IAClD,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAwB;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;gBAEhC,MAAM,EAAE,wBAAwB,EAAE,OAAO,GAAE,uBAA4B;IAwBnF;;;;OAIG;IACH,OAAO,CAAC,QAAQ;IAIhB;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAatB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAU/E,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUrE,MAAM,CACV,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,kBAAkB,CAAC;CA0B/B"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Redis-backed `ReplayStore` for distributed AdCP verifier deployments.
|
|
4
|
+
*
|
|
5
|
+
* Sister of `PostgresReplayStore`. The spec comment on `ReplayStore.insert`
|
|
6
|
+
* (`src/lib/signing/replay.ts`) literally names this as a canonical
|
|
7
|
+
* implementation: "Multi-replica adopters writing Redis / Postgres
|
|
8
|
+
* stores MUST implement this as a single atomic operation (e.g. Redis
|
|
9
|
+
* `SET NX EX`, Postgres `INSERT ... ON CONFLICT DO NOTHING RETURNING`,
|
|
10
|
+
* a `WATCH/MULTI/EXEC` transaction)."
|
|
11
|
+
*
|
|
12
|
+
* **Data model.** One Redis sorted set per `(keyid, scope)` pair. The
|
|
13
|
+
* sorted-set members are nonces; the score is `expiresAt` (unix epoch
|
|
14
|
+
* seconds). Sorted sets give us all three primitives the `ReplayStore`
|
|
15
|
+
* interface needs in single Redis commands:
|
|
16
|
+
*
|
|
17
|
+
* - `has(nonce)` → `ZSCORE` + JS-side `score > now` check.
|
|
18
|
+
* - `isCapHit()` → `ZCOUNT key (now +inf` (active nonces).
|
|
19
|
+
* - `insert()` → a Lua script that runs `ZREMRANGEBYSCORE -inf now`
|
|
20
|
+
* (drop expired) → `ZSCORE` (replay check) → `ZCARD` (cap check) →
|
|
21
|
+
* `ZADD` (insert) + `PEXPIREAT` (extend set's own TTL) atomically.
|
|
22
|
+
*
|
|
23
|
+
* **No sweeper needed.** Expired nonces are dropped at the start of
|
|
24
|
+
* every `insert` (the `ZREMRANGEBYSCORE` step), and Redis evicts the
|
|
25
|
+
* entire sorted set when its `PEXPIREAT` lapses if no further inserts
|
|
26
|
+
* arrive. The Postgres backend's `sweepExpiredReplays` cron has no
|
|
27
|
+
* Redis equivalent — eviction is automatic.
|
|
28
|
+
*
|
|
29
|
+
* **Atomicity.** The Lua script runs as a single uninterruptable
|
|
30
|
+
* operation against the Redis server (Redis is single-threaded; Lua
|
|
31
|
+
* blocks everything else for the script duration). Two concurrent
|
|
32
|
+
* `insert` calls with the same `(keyid, scope, nonce)` are guaranteed
|
|
33
|
+
* to see exactly one `'ok'` and one `'replayed'` — matches the
|
|
34
|
+
* `InMemoryReplayStore` semantics the spec mandates.
|
|
35
|
+
*
|
|
36
|
+
* **Redis memory policy — set this on the deployment.** Each
|
|
37
|
+
* `(keyid, scope)` sorted set is capped (default 100k nonces). A
|
|
38
|
+
* hostile signer with a valid keyid + scope can fill the set to its
|
|
39
|
+
* cap; legitimate fan-out (many sibling verifiers, many scopes) grows
|
|
40
|
+
* the number of sets. Configure your Redis with:
|
|
41
|
+
*
|
|
42
|
+
* - **`maxmemory-policy volatile-lru`** (recommended) — evicts only
|
|
43
|
+
* TTL'd keys. All sets this store writes carry `PEXPIREAT`, so
|
|
44
|
+
* they're all evictable. Safe on a shared Redis instance.
|
|
45
|
+
* - **`maxmemory-policy allkeys-lru`** — only on a dedicated db.
|
|
46
|
+
* Otherwise evicts unrelated app keys.
|
|
47
|
+
* - **`maxmemory-policy noeviction`** (Redis default) — fail-closed:
|
|
48
|
+
* writes start erroring at the memory limit; the verifier will
|
|
49
|
+
* throw and the middleware will return 500 (replay protection
|
|
50
|
+
* stays intact — never fail-open). Operationally noisy but
|
|
51
|
+
* never serves stale replay decisions.
|
|
52
|
+
*
|
|
53
|
+
* Eviction-on-replay implication: if `volatile-lru` evicts a sorted
|
|
54
|
+
* set before its members would have expired naturally, an attacker's
|
|
55
|
+
* later replay of one of those nonces sees a fresh set with no prior
|
|
56
|
+
* entry, returns `'ok'`. The cap is the primary defense; memory-policy
|
|
57
|
+
* eviction is a secondary recovery mode. Size Redis to keep the
|
|
58
|
+
* working set in memory under normal traffic and treat eviction as
|
|
59
|
+
* pressure to scale up, not as a feature.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* import { createClient } from 'redis';
|
|
64
|
+
* import { RedisReplayStore } from '@adcp/sdk/signing/server';
|
|
65
|
+
*
|
|
66
|
+
* const client = createClient({ url: process.env.REDIS_URL });
|
|
67
|
+
* client.on('error', (err) => console.error('redis error', err));
|
|
68
|
+
* await client.connect();
|
|
69
|
+
*
|
|
70
|
+
* const replayStore = new RedisReplayStore(client);
|
|
71
|
+
*
|
|
72
|
+
* app.use(createExpressVerifier({
|
|
73
|
+
* capability: { ... },
|
|
74
|
+
* jwks,
|
|
75
|
+
* replayStore, // <-- shared across instances
|
|
76
|
+
* resolveOperation: mcpToolNameResolver,
|
|
77
|
+
* }));
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
81
|
+
exports.RedisReplayStore = void 0;
|
|
82
|
+
const redis_default_prefix_warn_1 = require("../utils/redis-default-prefix-warn");
|
|
83
|
+
const DEFAULT_KEY_PREFIX = 'adcp:replay:';
|
|
84
|
+
const DEFAULT_CAP = 100_000;
|
|
85
|
+
/**
|
|
86
|
+
* Extra seconds added to the sorted-set's own `PEXPIREAT` past the
|
|
87
|
+
* latest entry's `expiresAt`. Without this, a sorted set whose last
|
|
88
|
+
* entry just expired would be evicted by Redis between `insert` calls,
|
|
89
|
+
* losing the cap-counter accounting briefly. 1 hour is conservative —
|
|
90
|
+
* memory-cheap because the set is also empty (`ZREMRANGEBYSCORE` ran
|
|
91
|
+
* before the eventual eviction) and short enough that abandoned
|
|
92
|
+
* `(keyid, scope)` tuples don't leak indefinitely.
|
|
93
|
+
*/
|
|
94
|
+
const DEFAULT_SET_TTL_GRACE_SECONDS = 3600;
|
|
95
|
+
/**
|
|
96
|
+
* Lua script — atomic replay check, cap check, and insert.
|
|
97
|
+
*
|
|
98
|
+
* KEYS[1] — the sorted-set key for this `(keyid, scope)` pair.
|
|
99
|
+
* ARGV[1] — nonce.
|
|
100
|
+
* ARGV[2] — expiresAt (unix epoch seconds).
|
|
101
|
+
* ARGV[3] — now (unix epoch seconds).
|
|
102
|
+
* ARGV[4] — cap (max retained nonces).
|
|
103
|
+
* ARGV[5] — setTtlGraceSeconds (extra TTL on the sorted set itself).
|
|
104
|
+
*
|
|
105
|
+
* Returns the literal string `'ok'`, `'replayed'`, or `'rate_abuse'`
|
|
106
|
+
* to match the `ReplayInsertResult` enum.
|
|
107
|
+
*
|
|
108
|
+
* Precedence (matches `InMemoryReplayStore` and `PostgresReplayStore`):
|
|
109
|
+
* 1. replay wins,
|
|
110
|
+
* 2. then rate_abuse,
|
|
111
|
+
* 3. then ok.
|
|
112
|
+
*
|
|
113
|
+
* The `ZREMRANGEBYSCORE` step drops entries whose score is `<= now` —
|
|
114
|
+
* matching the pg backend's `expires_at > to_timestamp($4)` "still
|
|
115
|
+
* valid" semantic.
|
|
116
|
+
*/
|
|
117
|
+
const INSERT_LUA = `
|
|
118
|
+
local key = KEYS[1]
|
|
119
|
+
local nonce = ARGV[1]
|
|
120
|
+
local expiresAt = tonumber(ARGV[2])
|
|
121
|
+
local now = tonumber(ARGV[3])
|
|
122
|
+
local cap = tonumber(ARGV[4])
|
|
123
|
+
local grace = tonumber(ARGV[5])
|
|
124
|
+
|
|
125
|
+
-- Drop expired entries first (inclusive: score <= now is expired)
|
|
126
|
+
redis.call('ZREMRANGEBYSCORE', key, '-inf', now)
|
|
127
|
+
|
|
128
|
+
-- Replay check — only unexpired entries can be found by ZSCORE because
|
|
129
|
+
-- the cleanup above ran.
|
|
130
|
+
if redis.call('ZSCORE', key, nonce) then
|
|
131
|
+
return 'replayed'
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
-- Cap check (count of unexpired entries — already pruned above)
|
|
135
|
+
if redis.call('ZCARD', key) >= cap then
|
|
136
|
+
return 'rate_abuse'
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
-- Insert the new nonce.
|
|
140
|
+
redis.call('ZADD', key, expiresAt, nonce)
|
|
141
|
+
|
|
142
|
+
-- Extend set TTL — but only forward, never backward. A short-lived
|
|
143
|
+
-- insert after a long-lived one must NOT shrink the set's eviction
|
|
144
|
+
-- time below the longest-still-valid nonce's expiry; otherwise the
|
|
145
|
+
-- set would evict early and take still-valid nonces with it (replay
|
|
146
|
+
-- bypass: has() returns false because the key disappeared, and the
|
|
147
|
+
-- attacker's retry of the long-lived nonce is wrongly accepted).
|
|
148
|
+
--
|
|
149
|
+
-- Desired set expiry = max(existing PEXPIREAT, expiresAt + grace).
|
|
150
|
+
-- PTTL returns -1 (no TTL set) or ms-remaining. Since ZADD ran above,
|
|
151
|
+
-- the key exists, so PTTL won't return -2. nowMs is passed from the
|
|
152
|
+
-- SDK side as ARGV[6] — using Redis's TIME command would make the
|
|
153
|
+
-- script non-deterministic for replication.
|
|
154
|
+
local desiredExpireAtMs = (expiresAt + grace) * 1000
|
|
155
|
+
local currentTtlMs = redis.call('PTTL', key)
|
|
156
|
+
local nowMs = tonumber(ARGV[6])
|
|
157
|
+
if currentTtlMs < 0 then
|
|
158
|
+
-- -1: no TTL set. (ZADD just wrote the key so -2 is impossible.)
|
|
159
|
+
redis.call('PEXPIREAT', key, desiredExpireAtMs)
|
|
160
|
+
else
|
|
161
|
+
local currentExpireAtMs = nowMs + currentTtlMs
|
|
162
|
+
if desiredExpireAtMs > currentExpireAtMs then
|
|
163
|
+
redis.call('PEXPIREAT', key, desiredExpireAtMs)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
return 'ok'
|
|
167
|
+
`;
|
|
168
|
+
/**
|
|
169
|
+
* Reject non-finite / out-of-range timestamps before they reach Redis.
|
|
170
|
+
* Matches the Postgres backend's defensive guard so a buggy `options.now()`
|
|
171
|
+
* injection can't DoS the verifier with Redis parse errors.
|
|
172
|
+
*/
|
|
173
|
+
function assertFiniteSeconds(label, value) {
|
|
174
|
+
if (!Number.isFinite(value) || value < 0 || value > Number.MAX_SAFE_INTEGER) {
|
|
175
|
+
throw new TypeError(`RedisReplayStore: ${label} must be a finite non-negative number; received ${value}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
class RedisReplayStore {
|
|
179
|
+
c;
|
|
180
|
+
keyPrefix;
|
|
181
|
+
cap;
|
|
182
|
+
setTtlGraceSeconds;
|
|
183
|
+
constructor(client, options = {}) {
|
|
184
|
+
this.c = client;
|
|
185
|
+
this.keyPrefix = options.keyPrefix ?? DEFAULT_KEY_PREFIX;
|
|
186
|
+
this.cap = options.cap ?? DEFAULT_CAP;
|
|
187
|
+
this.setTtlGraceSeconds = options.setTtlGraceSeconds ?? DEFAULT_SET_TTL_GRACE_SECONDS;
|
|
188
|
+
if (!Number.isFinite(this.cap) || this.cap <= 0) {
|
|
189
|
+
throw new Error(`RedisReplayStore: cap must be a positive finite number. Got ${this.cap}.`);
|
|
190
|
+
}
|
|
191
|
+
if (!Number.isFinite(this.setTtlGraceSeconds) || this.setTtlGraceSeconds < 0) {
|
|
192
|
+
throw new Error(`RedisReplayStore: setTtlGraceSeconds must be a non-negative finite number. Got ${this.setTtlGraceSeconds}.`);
|
|
193
|
+
}
|
|
194
|
+
(0, redis_default_prefix_warn_1.maybeWarnOnSharedRedisPrefix)({
|
|
195
|
+
client,
|
|
196
|
+
callerKeyPrefix: options.keyPrefix,
|
|
197
|
+
defaultKeyPrefix: DEFAULT_KEY_PREFIX,
|
|
198
|
+
suppress: options.suppressDefaultPrefixWarning,
|
|
199
|
+
backendName: 'RedisReplayStore',
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Compose the sorted-set Redis key for a `(keyid, scope)` pair.
|
|
204
|
+
* Uses `\x1f` (unit separator) between segments — illegal in RFC
|
|
205
|
+
* 7517 JWK kids and in URLs, so no legitimate input collides.
|
|
206
|
+
*/
|
|
207
|
+
redisKey(keyid, scope) {
|
|
208
|
+
return `${this.keyPrefix}${keyid}\x1f${scope}`;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Probe — ping Redis. Not part of the `ReplayStore` interface; call
|
|
212
|
+
* before serving traffic if you want a boot-time readiness check.
|
|
213
|
+
*/
|
|
214
|
+
async probe() {
|
|
215
|
+
try {
|
|
216
|
+
await this.c.ping();
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
throw new Error(`RedisReplayStore probe failed: Redis is unreachable or misconfigured. ` +
|
|
220
|
+
`The verifier would accept signed requests but every replay-cache write would fail. ` +
|
|
221
|
+
`Check REDIS_URL and that the instance is up. See server logs for the underlying cause.`, { cause: err });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async has(keyid, scope, nonce, now) {
|
|
225
|
+
assertFiniteSeconds('now', now);
|
|
226
|
+
const score = await this.c.zScore(this.redisKey(keyid, scope), nonce);
|
|
227
|
+
// Score === expiresAt. An entry is "present" only if it's still
|
|
228
|
+
// unexpired (score > now). The Lua script also prunes expired
|
|
229
|
+
// entries on insert, but `has` is called independently and can
|
|
230
|
+
// race a never-since-touched key.
|
|
231
|
+
return score !== null && score > now;
|
|
232
|
+
}
|
|
233
|
+
async isCapHit(keyid, scope, now) {
|
|
234
|
+
assertFiniteSeconds('now', now);
|
|
235
|
+
// ZCOUNT with `(now` lower bound is exclusive — only entries
|
|
236
|
+
// strictly in the future count toward the cap. Stale entries
|
|
237
|
+
// beyond their expiresAt don't inflate the count even before the
|
|
238
|
+
// next insert prunes them.
|
|
239
|
+
const active = await this.c.zCount(this.redisKey(keyid, scope), `(${now}`, '+inf');
|
|
240
|
+
return active >= this.cap;
|
|
241
|
+
}
|
|
242
|
+
async insert(keyid, scope, nonce, ttlSeconds, now) {
|
|
243
|
+
assertFiniteSeconds('now', now);
|
|
244
|
+
assertFiniteSeconds('ttlSeconds', ttlSeconds);
|
|
245
|
+
const expiresAt = now + ttlSeconds;
|
|
246
|
+
// `nowMs` (ARGV[6]) is `now * 1000` rather than `Date.now()` so the
|
|
247
|
+
// script's "extend forward only" branch uses the SAME clock as the
|
|
248
|
+
// rest of the verifier path (`now` is the verifier-supplied second-
|
|
249
|
+
// precision time, used for prune + replay + cap). Using `Date.now()`
|
|
250
|
+
// here would let scripts disagree with the surrounding logic when
|
|
251
|
+
// tests inject `options.now()`.
|
|
252
|
+
const result = await this.c.eval(INSERT_LUA, {
|
|
253
|
+
keys: [this.redisKey(keyid, scope)],
|
|
254
|
+
arguments: [
|
|
255
|
+
nonce,
|
|
256
|
+
String(expiresAt),
|
|
257
|
+
String(now),
|
|
258
|
+
String(this.cap),
|
|
259
|
+
String(this.setTtlGraceSeconds),
|
|
260
|
+
String(now * 1000),
|
|
261
|
+
],
|
|
262
|
+
});
|
|
263
|
+
if (result !== 'ok' && result !== 'replayed' && result !== 'rate_abuse') {
|
|
264
|
+
throw new Error(`RedisReplayStore.insert: Lua script returned unexpected value: ${String(result)}`);
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
exports.RedisReplayStore = RedisReplayStore;
|
|
270
|
+
//# sourceMappingURL=redis-replay-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-replay-store.js","sourceRoot":"","sources":["../../../src/lib/signing/redis-replay-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,6 +19,7 @@ export { BrandJsonJwksResolver, BrandJsonResolverError, type BrandAgentType, typ
|
|
|
19
19
|
export { parseSignature, parseSignatureInput, type ParsedSignature, type ParsedSignatureInput } from './parser';
|
|
20
20
|
export { InMemoryReplayStore, type InMemoryReplayStoreOptions, type ReplayInsertResult, type ReplayStore, } from './replay';
|
|
21
21
|
export { PostgresReplayStore, REPLAY_CACHE_MIGRATION, getReplayStoreMigration, sweepExpiredReplays, type PostgresReplayStoreOptions, type SweepExpiredReplaysOptions, } from './postgres-replay-store';
|
|
22
|
+
export { RedisReplayStore, type RedisReplayStoreOptions, type ReplayRedisBackendClient, type ReplayRedisLikeClient, } from './redis-replay-store';
|
|
22
23
|
export { InMemoryRevocationStore, type RevocationStore } from './revocation';
|
|
23
24
|
export { HttpsRevocationStore, type HttpsRevocationStoreOptions } from './revocation-https';
|
|
24
25
|
export { ALLOWED_ALGS, CLOCK_SKEW_TOLERANCE_SECONDS, MANDATORY_COMPONENTS, MAX_SIGNATURE_WINDOW_SECONDS, REQUEST_SIGNING_TAG, RESPONSE_MANDATORY_COMPONENTS, RESPONSE_SIGNING_TAG, type AdcpJsonWebKey, type ContentDigestPolicy, type RevocationSnapshot, type VerifiedSigner, type VerifierCapability, type VerifyResult, } from './types';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/lib/signing/server.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/lib/signing/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,qBAAqB,EACrB,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAClG,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,wBAAwB,EACxB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,gCAAgC,EACrC,KAAK,+BAA+B,GACrC,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3D,OAAO,EACL,qBAAqB,EACrB,KAAK,yBAAyB,EAC9B,sBAAsB,EACtB,KAAK,0BAA0B,EAC/B,qBAAqB,EACrB,KAAK,yBAAyB,GAC/B,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,kBAAkB,EAAE,KAAK,YAAY,EAAE,MAAM,QAAQ,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAChF,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,KAAK,cAAc,EACnB,KAAK,4BAA4B,EACjC,KAAK,0BAA0B,GAChC,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,KAAK,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAChH,OAAO,EACL,mBAAmB,EACnB,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,EACvB,KAAK,WAAW,GACjB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,EACnB,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,GAChC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,uBAAuB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,KAAK,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AAC5F,OAAO,EACL,YAAY,EACZ,4BAA4B,EAC5B,oBAAoB,EACpB,4BAA4B,EAC5B,mBAAmB,EACnB,6BAA6B,EAC7B,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,sBAAsB,EAAE,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAC/E,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,KAAK,6BAA6B,EAClC,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,4BAA4B,EAC5B,mBAAmB,EACnB,KAAK,4BAA4B,EACjC,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,qBAAqB,EAAE,KAAK,WAAW,EAAE,KAAK,wBAAwB,EAAE,MAAM,cAAc,CAAC;AACtG,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EAClB,wBAAwB,EACxB,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,4BAA4B,EACjC,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,SAAS,GACf,MAAM,kBAAkB,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.readIdentityPosture = exports.readBrandJsonUrl = exports.ATTACKER_INFLUENCED = exports.attackerInfluencedFields = void 0;
|
|
3
|
+
exports.createAgentJwksSet = exports.getAgentJwks = exports.resolveAgent = exports.createExpressVerifier = exports.WEBHOOK_SIGNING_TAG = exports.WEBHOOK_MANDATORY_COMPONENTS = exports.verifyWebhookSignature = exports.createWebhookVerifier = exports.verifyResponseSignature = exports.createResponseVerifier = exports.verifyRequestSignature = exports.RESPONSE_SIGNING_TAG = exports.RESPONSE_MANDATORY_COMPONENTS = exports.REQUEST_SIGNING_TAG = exports.MAX_SIGNATURE_WINDOW_SECONDS = exports.MANDATORY_COMPONENTS = exports.CLOCK_SKEW_TOLERANCE_SECONDS = exports.ALLOWED_ALGS = exports.HttpsRevocationStore = exports.InMemoryRevocationStore = exports.RedisReplayStore = exports.sweepExpiredReplays = exports.getReplayStoreMigration = exports.REPLAY_CACHE_MIGRATION = exports.PostgresReplayStore = exports.InMemoryReplayStore = exports.parseSignatureInput = exports.parseSignature = exports.BrandJsonResolverError = exports.BrandJsonJwksResolver = exports.HttpsJwksResolver = exports.StaticJwksResolver = exports.WebhookSignatureError = exports.ResponseSignatureError = exports.RequestSignatureError = exports.verifySignature = exports.jwkToPublicKey = exports.requestContextFromLambda = exports.requestContextFromFetch = exports.requestContextFromExpress = exports.parseContentDigest = exports.contentDigestMatches = exports.computeContentDigest = exports.getHeaderValue = exports.formatSignatureParams = exports.canonicalTargetUri = exports.canonicalMethod = exports.canonicalAuthority = exports.buildSignatureBase = exports.buildResponseSignatureBase = void 0;
|
|
4
|
+
exports.readIdentityPosture = exports.readBrandJsonUrl = exports.ATTACKER_INFLUENCED = exports.attackerInfluencedFields = exports.AgentResolverError = void 0;
|
|
5
5
|
/**
|
|
6
6
|
* Server-side signing surface: what a seller running an AdCP agent needs to
|
|
7
7
|
* verify inbound RFC 9421 signatures — verifier pipeline, Express-shaped
|
|
@@ -52,6 +52,8 @@ Object.defineProperty(exports, "PostgresReplayStore", { enumerable: true, get: f
|
|
|
52
52
|
Object.defineProperty(exports, "REPLAY_CACHE_MIGRATION", { enumerable: true, get: function () { return postgres_replay_store_1.REPLAY_CACHE_MIGRATION; } });
|
|
53
53
|
Object.defineProperty(exports, "getReplayStoreMigration", { enumerable: true, get: function () { return postgres_replay_store_1.getReplayStoreMigration; } });
|
|
54
54
|
Object.defineProperty(exports, "sweepExpiredReplays", { enumerable: true, get: function () { return postgres_replay_store_1.sweepExpiredReplays; } });
|
|
55
|
+
var redis_replay_store_1 = require("./redis-replay-store");
|
|
56
|
+
Object.defineProperty(exports, "RedisReplayStore", { enumerable: true, get: function () { return redis_replay_store_1.RedisReplayStore; } });
|
|
55
57
|
var revocation_1 = require("./revocation");
|
|
56
58
|
Object.defineProperty(exports, "InMemoryRevocationStore", { enumerable: true, get: function () { return revocation_1.InMemoryRevocationStore; } });
|
|
57
59
|
var revocation_https_1 = require("./revocation-https");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/lib/signing/server.ts"],"names":[],"mappings":";;;;AAAA;;;;;;;;;GASG;AACH,+CAWwB;AAVtB,0HAAA,0BAA0B,OAAA;AAC1B,kHAAA,kBAAkB,OAAA;AAClB,kHAAA,kBAAkB,OAAA;AAClB,+GAAA,eAAe,OAAA;AACf,kHAAA,kBAAkB,OAAA;AAClB,qHAAA,qBAAqB,OAAA;AACrB,8GAAA,cAAc,OAAA;AAKhB,mDAAkG;AAAzF,sHAAA,oBAAoB,OAAA;AAAE,sHAAA,oBAAoB,OAAA;AAAE,oHAAA,kBAAkB,OAAA;AACvE,qDAS2B;AARzB,4HAAA,yBAAyB,OAAA;AACzB,0HAAA,uBAAuB,OAAA;AACvB,2HAAA,wBAAwB,OAAA;AAO1B,mCAA2D;AAAlD,wGAAA,cAAc,OAAA;AAAE,yGAAA,eAAe,OAAA;AACxC,mCAOkB;AANhB,+GAAA,qBAAqB,OAAA;AAErB,gHAAA,sBAAsB,OAAA;AAEtB,+GAAA,qBAAqB,OAAA;AAGvB,+BAA+D;AAAtD,0GAAA,kBAAkB,OAAA;AAC3B,2CAAgF;AAAvE,+GAAA,iBAAiB,OAAA;AAC1B,2CAMsB;AALpB,mHAAA,qBAAqB,OAAA;AACrB,oHAAA,sBAAsB,OAAA;AAKxB,mCAAgH;AAAvG,wGAAA,cAAc,OAAA;AAAE,6GAAA,mBAAmB,OAAA;AAC5C,mCAKkB;AAJhB,6GAAA,mBAAmB,OAAA;AAKrB,iEAOiC;AAN/B,4HAAA,mBAAmB,OAAA;AACnB,+HAAA,sBAAsB,OAAA;AACtB,gIAAA,uBAAuB,OAAA;AACvB,4HAAA,mBAAmB,OAAA;AAIrB,2CAA6E;AAApE,qHAAA,uBAAuB,OAAA;AAChC,uDAA4F;AAAnF,wHAAA,oBAAoB,OAAA;AAC7B,iCAciB;AAbf,qGAAA,YAAY,OAAA;AACZ,qHAAA,4BAA4B,OAAA;AAC5B,6GAAA,oBAAoB,OAAA;AACpB,qHAAA,4BAA4B,OAAA;AAC5B,4GAAA,mBAAmB,OAAA;AACnB,sHAAA,6BAA6B,OAAA;AAC7B,6GAAA,oBAAoB,OAAA;AAQtB,uCAA+E;AAAtE,kHAAA,sBAAsB,OAAA;AAC/B,yDAM6B;AAL3B,2HAAA,sBAAsB,OAAA;AACtB,4HAAA,uBAAuB,OAAA;AAKzB,uDAQ4B;AAP1B,yHAAA,qBAAqB,OAAA;AACrB,0HAAA,sBAAsB,OAAA;AACtB,gIAAA,4BAA4B,OAAA;AAC5B,uHAAA,mBAAmB,OAAA;AAKrB,2CAAsG;AAA7F,mHAAA,qBAAqB,OAAA;AAC9B,mDAwB0B;AAvBxB,8GAAA,YAAY,OAAA;AACZ,8GAAA,YAAY,OAAA;AACZ,oHAAA,kBAAkB,OAAA;AAClB,oHAAA,kBAAkB,OAAA;AAClB,0HAAA,wBAAwB,OAAA;AACxB,qHAAA,mBAAmB,OAAA;AACnB,kHAAA,gBAAgB,OAAA;AAChB,qHAAA,mBAAmB,OAAA"}
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/lib/signing/server.ts"],"names":[],"mappings":";;;;AAAA;;;;;;;;;GASG;AACH,+CAWwB;AAVtB,0HAAA,0BAA0B,OAAA;AAC1B,kHAAA,kBAAkB,OAAA;AAClB,kHAAA,kBAAkB,OAAA;AAClB,+GAAA,eAAe,OAAA;AACf,kHAAA,kBAAkB,OAAA;AAClB,qHAAA,qBAAqB,OAAA;AACrB,8GAAA,cAAc,OAAA;AAKhB,mDAAkG;AAAzF,sHAAA,oBAAoB,OAAA;AAAE,sHAAA,oBAAoB,OAAA;AAAE,oHAAA,kBAAkB,OAAA;AACvE,qDAS2B;AARzB,4HAAA,yBAAyB,OAAA;AACzB,0HAAA,uBAAuB,OAAA;AACvB,2HAAA,wBAAwB,OAAA;AAO1B,mCAA2D;AAAlD,wGAAA,cAAc,OAAA;AAAE,yGAAA,eAAe,OAAA;AACxC,mCAOkB;AANhB,+GAAA,qBAAqB,OAAA;AAErB,gHAAA,sBAAsB,OAAA;AAEtB,+GAAA,qBAAqB,OAAA;AAGvB,+BAA+D;AAAtD,0GAAA,kBAAkB,OAAA;AAC3B,2CAAgF;AAAvE,+GAAA,iBAAiB,OAAA;AAC1B,2CAMsB;AALpB,mHAAA,qBAAqB,OAAA;AACrB,oHAAA,sBAAsB,OAAA;AAKxB,mCAAgH;AAAvG,wGAAA,cAAc,OAAA;AAAE,6GAAA,mBAAmB,OAAA;AAC5C,mCAKkB;AAJhB,6GAAA,mBAAmB,OAAA;AAKrB,iEAOiC;AAN/B,4HAAA,mBAAmB,OAAA;AACnB,+HAAA,sBAAsB,OAAA;AACtB,gIAAA,uBAAuB,OAAA;AACvB,4HAAA,mBAAmB,OAAA;AAIrB,2DAK8B;AAJ5B,sHAAA,gBAAgB,OAAA;AAKlB,2CAA6E;AAApE,qHAAA,uBAAuB,OAAA;AAChC,uDAA4F;AAAnF,wHAAA,oBAAoB,OAAA;AAC7B,iCAciB;AAbf,qGAAA,YAAY,OAAA;AACZ,qHAAA,4BAA4B,OAAA;AAC5B,6GAAA,oBAAoB,OAAA;AACpB,qHAAA,4BAA4B,OAAA;AAC5B,4GAAA,mBAAmB,OAAA;AACnB,sHAAA,6BAA6B,OAAA;AAC7B,6GAAA,oBAAoB,OAAA;AAQtB,uCAA+E;AAAtE,kHAAA,sBAAsB,OAAA;AAC/B,yDAM6B;AAL3B,2HAAA,sBAAsB,OAAA;AACtB,4HAAA,uBAAuB,OAAA;AAKzB,uDAQ4B;AAP1B,yHAAA,qBAAqB,OAAA;AACrB,0HAAA,sBAAsB,OAAA;AACtB,gIAAA,4BAA4B,OAAA;AAC5B,uHAAA,mBAAmB,OAAA;AAKrB,2CAAsG;AAA7F,mHAAA,qBAAqB,OAAA;AAC9B,mDAwB0B;AAvBxB,8GAAA,YAAY,OAAA;AACZ,8GAAA,YAAY,OAAA;AACZ,oHAAA,kBAAkB,OAAA;AAClB,oHAAA,kBAAkB,OAAA;AAClB,0HAAA,wBAAwB,OAAA;AACxB,qHAAA,mBAAmB,OAAA;AACnB,kHAAA,gBAAgB,OAAA;AAChB,qHAAA,mBAAmB,OAAA"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared default-prefix-on-db-0 warning helper for Redis backends.
|
|
3
|
+
*
|
|
4
|
+
* Every Redis-backed SDK store (idempotency, ctx-metadata, replay)
|
|
5
|
+
* prefixes its keys (`"adcp:idem:"`, `"adcp:ctx_meta:"`, `"adcp:replay:"`)
|
|
6
|
+
* so a shared Redis instance can host multiple AdCP servers — or AdCP
|
|
7
|
+
* alongside other apps — without collision. But two AdCP deployments
|
|
8
|
+
* sharing the *same* db with the *same* default prefix collide on any
|
|
9
|
+
* overlapping principal/account/keyid namespace.
|
|
10
|
+
*
|
|
11
|
+
* This helper emits a one-time `console.warn` at backend construction
|
|
12
|
+
* when the default prefix meets a node-redis client we can confidently
|
|
13
|
+
* identify as being on db 0 (the most likely signal of a shared,
|
|
14
|
+
* non-dedicated Redis). Once per process across every backend that uses
|
|
15
|
+
* it — operators standing up multiple Redis backends shouldn't see N
|
|
16
|
+
* identical warnings.
|
|
17
|
+
*
|
|
18
|
+
* Best-effort: stays silent for escape-hatch clients (ioredis, Upstash,
|
|
19
|
+
* test doubles) where we can't introspect the db index. Prefer
|
|
20
|
+
* false-negative over a noisy false-positive warning.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Detect the Redis db index when the client is a node-redis v4/v5
|
|
24
|
+
* `RedisClientType`. Returns `null` for clients we can't introspect.
|
|
25
|
+
*
|
|
26
|
+
* Reads `client.options.database` (numeric, present when the client was
|
|
27
|
+
* constructed with `createClient({ database: N })`) or parses the path
|
|
28
|
+
* component of `client.options.url`. Escape-hatch clients (no `options`
|
|
29
|
+
* object of the canonical shape) return `null` and skip the warn.
|
|
30
|
+
*/
|
|
31
|
+
export declare function detectNodeRedisDbIndex(client: unknown): number | null;
|
|
32
|
+
export interface WarnOnSharedRedisPrefixOptions {
|
|
33
|
+
/** The Redis client passed to the backend constructor. */
|
|
34
|
+
client: unknown;
|
|
35
|
+
/** `options.keyPrefix` as the caller passed it — `undefined` means default. */
|
|
36
|
+
callerKeyPrefix: string | undefined;
|
|
37
|
+
/** The backend's own default prefix (e.g., `"adcp:idem:"`). */
|
|
38
|
+
defaultKeyPrefix: string;
|
|
39
|
+
/** Caller-supplied suppression switch. */
|
|
40
|
+
suppress: boolean | undefined;
|
|
41
|
+
/** Backend label for the warning text (e.g., `"redisBackend"`, `"redisCtxMetadataStore"`). */
|
|
42
|
+
backendName: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Emit the one-time warn if (a) the caller used the default `keyPrefix`,
|
|
46
|
+
* (b) didn't pass `suppress`, (c) we can confidently see db 0 from the
|
|
47
|
+
* client. Otherwise silent.
|
|
48
|
+
*
|
|
49
|
+
* **Once per process.** All Redis backends share the warn flag — an
|
|
50
|
+
* adopter who configures three Redis-backed stores against the same
|
|
51
|
+
* misconfigured Redis sees one warning total, not three.
|
|
52
|
+
*/
|
|
53
|
+
export declare function maybeWarnOnSharedRedisPrefix(options: WarnOnSharedRedisPrefixOptions): void;
|
|
54
|
+
/**
|
|
55
|
+
* Test-only escape hatch to reset the once-warn flag between test runs.
|
|
56
|
+
* Not exported through any public index — adopters can't reach it from
|
|
57
|
+
* outside the SDK.
|
|
58
|
+
*/
|
|
59
|
+
export declare function __resetDefaultPrefixWarningForTests(): void;
|
|
60
|
+
//# sourceMappingURL=redis-default-prefix-warn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-default-prefix-warn.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/redis-default-prefix-warn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAkBrE;AAED,MAAM,WAAW,8BAA8B;IAC7C,0DAA0D;IAC1D,MAAM,EAAE,OAAO,CAAC;IAChB,+EAA+E;IAC/E,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,+DAA+D;IAC/D,gBAAgB,EAAE,MAAM,CAAC;IACzB,0CAA0C;IAC1C,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,8FAA8F;IAC9F,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,8BAA8B,GAAG,IAAI,CAc1F;AAED;;;;GAIG;AACH,wBAAgB,mCAAmC,IAAI,IAAI,CAE1D"}
|