@web-ts-toolkit/express-oidc-vault-redis-store 0.43.0 → 0.48.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/README.md +80 -13
- package/index.d.mts +13 -8
- package/index.d.ts +13 -8
- package/index.js +546 -50
- package/index.mjs +548 -51
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ import { createClient } from 'redis';
|
|
|
20
20
|
import { createRedisOidcVaultStore } from '@web-ts-toolkit/express-oidc-vault-redis-store';
|
|
21
21
|
|
|
22
22
|
const redis = createClient({ url: process.env.REDIS_URL });
|
|
23
|
+
redis.on('error', () => console.warn('OIDC vault Redis connection error.'));
|
|
23
24
|
await redis.connect();
|
|
24
25
|
|
|
25
26
|
const storeProvider = createRedisOidcVaultStore({
|
|
@@ -38,6 +39,7 @@ import { createRedisOidcVaultStore } from '@web-ts-toolkit/express-oidc-vault-re
|
|
|
38
39
|
|
|
39
40
|
const app = express();
|
|
40
41
|
const redis = createClient({ url: process.env.REDIS_URL });
|
|
42
|
+
redis.on('error', () => console.warn('OIDC vault Redis connection error.'));
|
|
41
43
|
|
|
42
44
|
await redis.connect();
|
|
43
45
|
|
|
@@ -69,19 +71,37 @@ app.use(
|
|
|
69
71
|
- one-time records are consumed atomically through Redis commands instead of `get` plus `del`
|
|
70
72
|
- session rotation and session indexes are updated atomically so concurrent refreshes do not fork multiple active sessions
|
|
71
73
|
- rotation requires a distinct unused target session ID; missing-source, same-ID, and existing-target rotation conflicts throw `OidcVaultStoreConflictError` without changing source or target records
|
|
72
|
-
- obsolete rotated session IDs are stored as aliases owned by the logical session;
|
|
74
|
+
- obsolete rotated session IDs are stored as aliases owned by the logical session; scoped/direct deletion preserves unexpired aliases while another live member survives, including another issuer/client scope. Deleting through an obsolete ID still revokes its logical lineage
|
|
75
|
+
- alias cleanup runs inside the delete script alongside the primary mutation. It checks keyed primary membership with Redis TTL authority before removing terminated-lineage aliases; empty logical logout uses the same liveness rule in an atomic cleanup script. Rotation cannot interleave between that check and alias removal. The overall indexed traversal is not a global logout snapshot, so later arrivals can survive
|
|
76
|
+
- **Alias-cleanup compatibility:** scoped/direct logout no longer discards surviving lineage handles. Successful delete scripts no longer repeat application-side alias cleanup. A fixed warmed workload of 12 rotated lineages deleted by subject used 52 client commands before and 16 after on both Redis 6.2 and 7.2 (36 fewer sequential round trips); this measures command count, not latency. Lua still materializes whole logical-member sets, probes primaries until finding a survivor, and materializes/deletes all aliases when inactive; large lineages/alias sets remain scale-dependent server work
|
|
73
77
|
- creating or rotating into a previously obsolete session ID removes that stale alias ownership first, so a reused ID cannot invoke an old logical-session meaning
|
|
74
|
-
- `createAuthorizationTransaction` and `createExchangeCode` are upserts; `createSession` is create-only and duplicate session IDs throw `OidcVaultStoreConflictError` without changing the existing record or indexes
|
|
75
|
-
-
|
|
78
|
+
- `createAuthorizationTransaction` and `createExchangeCode` are upserts; `createSession` is create-only and duplicate session IDs throw `OidcVaultStoreConflictError` without changing the existing record or indexes. Portable callers must create sessions with a fresh unused ID and handle `OidcVaultStoreConflictError`: reusing a live ID rejects here but replaces on memory/MongoDB, so only fresh-ID creation is portable
|
|
79
|
+
- portable data consists of strings, finite numbers, booleans, null, arrays, and plain objects; exclude cycles, custom prototypes/serializers, functions, symbols, and undefined properties. This applies to nested provider/user/metadata data, not only top-level metadata
|
|
80
|
+
- portable plain-object/array inputs are captured before asynchronous work, including rotation source reads and scoped deletion scans. Returned session containers are independent from caller input, including nested provider/user/metadata arrays; later caller mutation cannot change the committed portable value or eventual result
|
|
81
|
+
- compatibility: session ownership snapshots do not add a JSON round-trip. The existing JSON serializer still handles native values and `toJSON` hooks (and still rejects unsupported values such as BigInt/cycles). Opaque non-plain objects are not recursively copied and have no portable mutation-isolation guarantee; create/rotate results retain their native values while reads reflect persisted JSON
|
|
76
82
|
- backchannel logout token JTI records are consumed only when `expiresAt` is finite and greater than the store clock at consume time
|
|
77
83
|
- the optional `now` hook controls store-domain timestamps and testable JTI validation only; Redis server time is the authority for Redis key expiry and revocation-index cleanup
|
|
78
84
|
- the client must implement `sendCommand(args)`; official standalone `redis` clients already do, and a Sentinel-wrapped master client or any adapter implementing `OidcVaultRedisClient` does too
|
|
79
85
|
|
|
86
|
+
## Rotation Aliases And Lifetime
|
|
87
|
+
|
|
88
|
+
Rotation preserves the source logical ID when the next session omits it. Each old ID becomes a revocation alias for its immediate successor's logical ID and `expiresAt`. With `A -> B (expiresAt=T1) -> C (expiresAt=T2)`, A stops working at T1 even if T2 is later or absent; B uses C's deadline. Later rotations never extend earlier aliases. `getSession(A)` returns `null`: an alias is never a readable/authentication session.
|
|
89
|
+
|
|
90
|
+
With `A/L1 -> B/L1 -> C/L2`, the new B alias revokes L2; earlier A keeps L1 and its original deadline, never C. Redis does not eagerly retire L1 aliases merely because rotation moves its last member elsewhere (memory does); they can remain until expiry or explicit lineage cleanup. Use distinct logical IDs for unrelated login families rather than relying on retained handles disappearing on reuse.
|
|
91
|
+
|
|
92
|
+
Without successor `expiresAt`, Redis and memory impose no alias time limit; MongoDB uses a finite fallback (default five minutes). Non-expiring Redis sessions and their aliases can grow without a fixed retention or size bound. Use explicit session lifetimes for portable finite windows. After an alias expires, use the live ID or the appropriate logical/subject/provider-session deletion method; core refresh preserves session expiry.
|
|
93
|
+
|
|
94
|
+
## Deletion, Counts And Concurrent Arrivals
|
|
95
|
+
|
|
96
|
+
Deleting a live public ID removes that record; deleting an unexpired alias revokes its logical lineage. Logical deletion and aliases have no issuer/client filter. Subject/provider-session object inputs match each supplied `issuer` and `clientId`; string inputs omit both filters and match across providers. A retained member in another scope keeps its unexpired aliases.
|
|
97
|
+
|
|
98
|
+
Bulk deletion makes one cursor traversal, rechecks scope in each atomic delete script, and can follow a rotated-away source to matching same-lineage successors. Counts sum primary sessions actually deleted by those scripts, including such successors; expired/missing records, stale membership repair and alias cleanup do not contribute. Duplicate cursor visits or overlapping revocations do not double-count an already deleted primary. A concurrent arrival may survive, and `0` is not proof that the scope is empty. An error can follow earlier committed deletions; the complete traversal is not one transaction. MongoDB instead repeats scoped deletion until an empty query and can count expired documents awaiting TTL cleanup; memory counts live deletions during a synchronous map scan. Vault deletion does not revoke outstanding stateless access tokens or call upstream logout.
|
|
99
|
+
|
|
80
100
|
## Redis Version And Topology
|
|
81
101
|
|
|
82
|
-
- **
|
|
102
|
+
- **Supported minimum Redis version: 6.2.** Versions 6.2 and 7.2 are exercised in integration tests. One-time consumption now uses guarded cached Lua, including legacy consumes, so old calls cannot spend guarded records.
|
|
83
103
|
- **Standalone Redis**: supported through the official `redis` `createClient(...)` client (`RedisClientType`). This is the tested topological default.
|
|
84
|
-
- **Redis Sentinel**:
|
|
104
|
+
- **Redis Sentinel**: use an underlying master client retrieved from `redis.createSentinel(...)` (e.g. acquire/use patterns documented by that driver) or an adapter conforming to `OidcVaultRedisClient`. The bare Sentinel root has `sendCommand(isReadonly, args, options?)`, not this store's `sendCommand(args)`. Caller/adapter owns master acquisition, reconnect/failover and release. Automated provider tests exercise standalone 6.2/7.2, not Sentinel failover; replication/persistence guarantees are deployment-owned, and losing unexpired replay reservations during failover weakens replay protection until their window expires.
|
|
85
105
|
- **Redis Cluster**: **not supported.** Cluster-shaped official clients are rejected when the store is created. The package's atomic vault scripts touch several keys without a hash-slot routing adapter, so Cluster routing could send parts of one logical operation to different nodes. Add a Cluster routing/hash-slot adapter before enabling it.
|
|
86
106
|
|
|
87
107
|
## Client Lifecycle And Ownership
|
|
@@ -89,7 +109,7 @@ app.use(
|
|
|
89
109
|
- **Connect the client yourself.** The store never calls `client.connect()`, `client.quit()`, or `client.disconnect()`. Pass an already-connected client and reuse it across requests.
|
|
90
110
|
- **Own `error` listeners, reconnects, and shutdown.** The store reads and writes commands but does not attach `error` listeners, suppress client errors, or close the client. Always register a client `error` listener on production connections; an unhandled client error can crash the process.
|
|
91
111
|
- **Graceful shutdown.** Draining `createSession`/`rotateSession`/`deleteSessionsBy*` in-flight calls, then `client.quit()`, is the supported shutdown order. The store holds no background timers, so no store-side teardown is required beyond verifying in-flight operations have settled.
|
|
92
|
-
- **
|
|
112
|
+
- **Startup and concurrency.** There is no store `ready()` handshake: connect before accepting traffic, and provision permission to execute the documented commands and cached Lua scripts. Server-side scripts protect individual mutations from interleaving; indexed traversal and post-commit maintenance span multiple commands. They do not have a fixed total-work or completion-time bound.
|
|
93
113
|
|
|
94
114
|
## Key Namespace And Migration
|
|
95
115
|
|
|
@@ -107,25 +127,72 @@ app.use(
|
|
|
107
127
|
## Expiry And Index Cleanup
|
|
108
128
|
|
|
109
129
|
- expiring session records are stored with Redis `PXAT`, and revocation index scores use the same absolute expiration timestamp
|
|
130
|
+
- authorization transactions and exchange codes also use `PXAT`. Legacy consumes and `getSession` preserve the key-TTL policy (no application-clock expiry check); legacy consumption uses one cached Lua call. The new transaction/code preflight getters compare payload expiry with Redis `TIME` (normally `GET` + `TIME`); guarded consumes compare payload expiry inside Lua with server time. They reject expired payloads even after externally altered TTLs
|
|
131
|
+
- **TTL integrity assumption:** preserve the store-written key TTLs and matching index scores when restoring or migrating data. Externally removing/extending a TTL can still expose past-dated payloads through legacy consumes or `getSession`; changing only a session TTL can leave it live after index pruning. New guarded operations additionally enforce payload expiry, but do not repair external index/TTL inconsistencies. Shortening a key TTL makes a record unavailable immediately even when payload expiry is later
|
|
110
132
|
- stale revocation memberships are pruned with Redis `TIME`, not the application clock, so a skewed application clock cannot remove a still-live Redis session from subject, provider-session, or logical-session indexes
|
|
111
133
|
- indexed revocation also checks the primary session key before deleting or returning a session; expired primary values are not returned and missing primary values remove the stale membership encountered during traversal
|
|
112
|
-
- indexed revocation
|
|
113
|
-
- each successful session create or rotation
|
|
114
|
-
-
|
|
115
|
-
-
|
|
134
|
+
- indexed revocation uses `ZSCAN COUNT 250` and one `MGET` for the IDs returned in each batch. `COUNT` is a hint, not a hard batch cap (compact sorted sets can return all members); the batch is not further chunked. Response memory and repair concurrency can grow with the returned batch, while deletion scripts run sequentially
|
|
135
|
+
- each successful session create or rotation attempts one `SCAN MATCH <prefix>:* COUNT 100` step, then `TYPE` and, for each recognized sorted-set index, `TIME`/`ZREMRANGEBYSCORE`. **COUNT 100 is a hint, not “up to 100 keys”**: Redis may return more or fewer keys, and the store processes the whole response. Cleanup includes subject, provider-session, logical-session and rotated-alias indexes. Redis removes empty sorted sets; continued successful writes advance the scan even without a logout for that logical session
|
|
136
|
+
- post-commit index maintenance is best-effort and never masquerades as a failed write: once the atomic create/rotate script commits, `createSession`/`rotateSession` resolve with the committed session even if the follow-up `SCAN`/`TYPE`/`TIME`/`ZREMRANGEBYSCORE` maintenance fails. The failure is reported once via a fixed-text `console.warn` identifying only `createSession` or `rotateSession`; adapter error names, messages, codes, causes, and arbitrary thrown values are never inspected or logged. The incremental scan retries on a later create/rotate. Mutation-script failures still reject, and a successful rotation is never retried merely because maintenance failed
|
|
137
|
+
- **Diagnostic compatibility note:** post-commit maintenance warnings no longer include a `Cause:` suffix or adapter error details, which may contain credentials. Log consumers should use the operation name and fixed maintenance-failure text
|
|
138
|
+
- aliases use their immediate successor's expiry score; malformed stale aliases are deleted when their session ID is reused. Alias/index cleanup can materialize a whole logical-member or alias set inside Lua; neither cursor COUNT value bounds that server work
|
|
139
|
+
- there is no fixed stale-index cleanup deadline or memory cap. Expired entries can remain while writes stop or maintenance fails; non-expiring entries and aliases remain until explicitly revoked/cleaned. The incremental scan is opportunistic, not a retention SLA
|
|
116
140
|
|
|
117
141
|
## Stored Data Validation And Corruption Handling
|
|
118
142
|
|
|
119
|
-
- stored records are JSON payloads validated structurally on read; see _Stored Data Characteristics_ for the schema-migration and plaintext-storage posture
|
|
143
|
+
- stored records are JSON payloads validated structurally on read, with session `sessionId`, authorization transaction `state`, and exchange-code `code` required to match the requested key (including each indexed batch member); see _Stored Data Characteristics_ for the schema-migration and plaintext-storage posture
|
|
120
144
|
- malformed one-time authorization transaction and exchange-code records are consumed atomically and return `null`, preventing reuse while failing closed
|
|
121
145
|
- malformed session records are treated as unreadable, deleted when encountered through session reads or indexed revocation, and never returned as authenticated state
|
|
122
|
-
-
|
|
146
|
+
- corruption repair deletes only the observed malformed payload through an atomic server-side compare-and-delete: a fresh same-ID record created after the stale read (session ID reuse is supported) is never destroyed by the repair, and the missing-session logout path applies the same guard instead of an unconditional delete
|
|
147
|
+
- **Identity-validation compatibility note:** shape-valid records stored under the wrong identity follow the malformed-record policy and return `null`. Session repair targets only the observed lookup key, never the embedded session ID; indexed membership repair retains the same generation guard. One-time corruption is removed inside the same atomic consume script, with no follow-up delete that could remove a replacement
|
|
148
|
+
- a stale repair observing corruption therefore preserves a concurrent replacement, while genuine logout of a live session (direct delete or scoped logical/subject/provider-session revocation) still deletes it; indexed revocation removes stale corrupt members and continues processing later valid sessions
|
|
123
149
|
- Redis mutation scripts preflight expected key types before writing so wrong-type keys fail without deterministic partial mutation
|
|
124
|
-
- validation
|
|
150
|
+
- package-generated validation and deterministic script diagnostics use fixed text without stored records. Arbitrary client/adapter failures can still reject operations with sensitive original errors; they are not generally sanitized for application logs. Use fixed operation names and allowlisted categories, not raw errors, connection URLs, session IDs, state/PKCE values, exchange codes or token-valued metric labels. The secret-independent post-commit warning policy above applies specifically to that warning path
|
|
125
151
|
|
|
126
152
|
## Main Exports
|
|
127
153
|
|
|
154
|
+
Use named imports from the package root; there is no default export or public subpath API. Use Node.js `>=22.12.0` for the core dependency's CJS/ESM runtime support. TypeScript apps also need `@types/node` and `@types/express`. Both ESM (`import`, `index.d.mts`) and CJS (`require`, `index.d.ts`) declaration conditions are shipped; consumer imports always use the package name.
|
|
155
|
+
|
|
128
156
|
- `createRedisOidcVaultStore(...)`
|
|
129
157
|
- `OidcVaultRedisStoreRecordError`
|
|
130
158
|
- `type RedisOidcVaultStoreOptions`
|
|
131
159
|
- `type OidcVaultRedisClient`
|
|
160
|
+
|
|
161
|
+
## Device-Binding Store Capabilities
|
|
162
|
+
|
|
163
|
+
The factory returns core `OidcVaultDeviceBindingStoreProvider`, with detached live `getAuthorizationTransaction(state)` and `getExchangeCode(code)`, atomic `consumeAuthorizationTransactionIfMatches({ state, match })` and `consumeExchangeCodeIfMatches({ code, expectedSessionId, match })`, token-free `getSessionRevocationContext(sessionId)`, and `reserveDpopProof({ replayKey, expiresAt })`.
|
|
164
|
+
|
|
165
|
+
Import the stronger provider, match/consume/replay/revocation types and `OidcVaultDpopReplayCapacityError` from the **core package root**; the Redis package exports its factory/options/client and record error only. Either vault opt-in feature requires all six capabilities, while fingerprint-only flows do not reserve proofs. Use the same shared provider as API replayStore; the core shipped README includes complete DPoP issuer/API/browser usage.
|
|
166
|
+
|
|
167
|
+
`match` requires both `deviceBinding` and `browserBindingHash`. Non-null values match exactly; **null requires stored absence**, never BSON/JSON null or “ignore”. Legacy records omit both; guarded unbound records have a cookie hash; bound records have that hash plus `{ type: 'dpop', jkt }`. Hashes are canonical 43-character SHA-256 base64url. Null/malformed/extra binding fields and key-without-hash records are invalid; writes reject and corrupted data returns no credentials. No JWK, proof algorithm, or historical mode is stored.
|
|
168
|
+
|
|
169
|
+
Preflight getters are not locks. The consume script performs shape/identity/expiry, both matches, exchange-session match and deletion atomically; mismatch returns `null` without spending a live record. Matching races have one winner across independent Redis clients. **Legacy consumes now also use this script** and refuse guarded records, including cookie-only records, without spending them. There is no application-side get/unconditional-delete bypass. Malformed read cleanup retains the existing compare-and-delete generation guard.
|
|
170
|
+
|
|
171
|
+
Rotation inherits omitted binding, accepts the same key, and rejects null/malformed, changed, or newly added binding before mutation. Lua also rechecks source identity, credential generation and binding against the preflight source so same-ID replacement cannot make a new lineage inherit an old key or lose index ownership. Target-ID, alias, deletion and post-commit maintenance policies remain as documented above.
|
|
172
|
+
|
|
173
|
+
`getSessionRevocationContext` resolves a live handle or unexpired alias and all live members of its current lineage inside one Lua snapshot. A directly observed live handle always participates even if its reverse-index membership is missing, so peers cannot make that bound handle appear unbound. It returns only `logicalSessionId`, allowlisted `provider.issuer/clientId`, and optional original `deviceBinding`; no tokens/profile/metadata/arbitrary provider fields leave Redis. Alias payloads still contain only their logical ID: absence of an alias binding never means an unbound lineage. Malformed/inconsistent binding/provider authority throws a fixed diagnostic. Expired aliases/empty lineages return `null`, and `getSession(alias)` remains `null`. This call materializes the whole logical-member set and reads member primaries inside Lua; large lineages remain scale-dependent server work.
|
|
174
|
+
|
|
175
|
+
## Per-Request DPoP Replay And Capacity
|
|
176
|
+
|
|
177
|
+
| Redis option | Default | Contract |
|
|
178
|
+
| ---------------------- | ------------ | ----------------------------------------------------------------------- |
|
|
179
|
+
| `client` | required | Connected standalone-shaped client/adapter; caller owns lifecycle |
|
|
180
|
+
| `keyPrefix` | `oidc-vault` | Shared vault/replay namespace; changing it is not migration |
|
|
181
|
+
| `dpopReplayMaxEntries` | `100000` | Positive safe integer, identical on clients sharing the prefix |
|
|
182
|
+
| `now` | `Date.now` | Store timestamps/backchannel preflight only; replay/TTL use server TIME |
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
const storeProvider = createRedisOidcVaultStore({
|
|
186
|
+
client: redis,
|
|
187
|
+
keyPrefix: 'oidc-vault',
|
|
188
|
+
dpopReplayMaxEntries: 100_000,
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`dpopReplayMaxEntries` is a positive safe integer (default **100000**), shared per key prefix across clients. Every client sharing that prefix must configure the same limit. The opaque proof keys are members of **`<keyPrefix>:dpop-proofs`**, a sorted set whose scores are absolute expiry milliseconds. This set is both replay state and capacity/expiry accounting; there is no separate counter that can leak after individual key TTLs.
|
|
193
|
+
|
|
194
|
+
`reserveDpopProof` is normally **one cached `EVALSHA` round trip per proof**. Server `TIME` inside Lua owns the validity window; the optional application `now` hook cannot admit a stale proof or prune live reservations. A safe-integer future expiry is required, with remaining TTL **<=360000 ms**. Invalid/nonfinite/unsafe/fractional/expired (`<= server now`) or overlong input returns `false` without allocation. Duplicate-first `ZSCORE` rejects without extending expiry, even at capacity. Lua reclaims at most **64** expired members with `ZRANGEBYSCORE ... LIMIT 0 64`, plus the requested expired key, then atomically checks `ZCARD` and admits with `ZADD`. No session/alias scan, unbounded `ZREMRANGEBYSCORE`, or live-entry eviction occurs on replay admission.
|
|
195
|
+
|
|
196
|
+
The replay set expires at its latest retained score (`PEXPIREAT`), reclaiming it if traffic stops; this deadline never extends a member's replay window. Expired members awaiting bounded cleanup may conservatively occupy capacity. New keys at capacity throw root `OidcVaultDpopReplayCapacityError` from `@web-ts-toolkit/express-oidc-vault`, with no live eviction or acceptance without replay state. Wrong-type/client failures reject. `NOSCRIPT` recovery loads and retries once, adding two round trips to the normal call; the existing runner never retries an arbitrary committed mutation on transport failure.
|
|
197
|
+
|
|
198
|
+
Provision ACLs for cached scripts and their internal commands, including `TIME`, `TYPE`, `GET`, `DEL`, `ZSCORE`, `ZRANGEBYSCORE`, `ZREM`, `ZCARD`, `ZADD`, `ZREVRANGE` and `PEXPIREAT`. DPoP is a **per-request write**, unlike lower-volume backchannel JTIs (`SET NX PXAT`). Size capacity for unique proofs/second × validity window plus headroom (approved defaults: at most 70 seconds). Share identical namespaces/windows and synchronized clocks across instances; never release reservations after downstream failure, and retries need a fresh proof. Capacity/provider errors must map through orchestration to sanitized replay-unavailable and fail closed. Redis failover durability remains deployment-owned; these command/work bounds are not throughput/latency guarantees.
|
package/index.d.mts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OidcVaultDeviceBindingStoreProvider } from '@web-ts-toolkit/express-oidc-vault';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Thrown when a stored Redis value cannot be parsed or fails structural
|
|
5
|
-
* validation
|
|
5
|
+
* validation (including identity mismatch with the requested key). The message
|
|
6
|
+
* never includes stored token or refresh values.
|
|
6
7
|
* Malformed one-time records are consumed atomically and return `null`; they
|
|
7
8
|
* fail closed without throwing this error. Malformed sessions are deleted when
|
|
8
9
|
* encountered through reads or indexed revocation.
|
|
@@ -33,7 +34,7 @@ declare class OidcVaultRedisStoreRecordError extends Error {
|
|
|
33
34
|
*
|
|
34
35
|
* `sendCommand` is required because atomic session writes, rotations,
|
|
35
36
|
* deletions, and one-time record consumption transit Redis
|
|
36
|
-
*
|
|
37
|
+
* cached Lua/`TYPE`/`TIME`/`ZRANGE`/`ZSCAN`/`MGET` commands through it.
|
|
37
38
|
* It was previously optional in this type but always enforced at runtime.
|
|
38
39
|
*/
|
|
39
40
|
interface OidcVaultRedisClient {
|
|
@@ -58,6 +59,8 @@ interface OidcVaultRedisClient {
|
|
|
58
59
|
* `disconnect()`). The package never calls `connect`, `quit`, or `disconnect`.
|
|
59
60
|
*/
|
|
60
61
|
interface RedisOidcVaultStoreOptions {
|
|
62
|
+
/** Positive safe integer per shared keyPrefix; default 100000. Configure identically on all clients. */
|
|
63
|
+
dpopReplayMaxEntries?: number;
|
|
61
64
|
/** Connected Redis client or compatible adapter implementing {@link OidcVaultRedisClient}. */
|
|
62
65
|
client: OidcVaultRedisClient;
|
|
63
66
|
/**
|
|
@@ -76,7 +79,7 @@ interface RedisOidcVaultStoreOptions {
|
|
|
76
79
|
now?: () => number;
|
|
77
80
|
}
|
|
78
81
|
/**
|
|
79
|
-
* Create an `
|
|
82
|
+
* Create an `OidcVaultDeviceBindingStoreProvider` backed by a connected Redis client.
|
|
80
83
|
*
|
|
81
84
|
* Pass an already-connected official `redis` standalone client (or any adapter
|
|
82
85
|
* implementing {@link OidcVaultRedisClient}). For Redis Sentinel deployments,
|
|
@@ -85,12 +88,14 @@ interface RedisOidcVaultStoreOptions {
|
|
|
85
88
|
* satisfy the structural contract. The store does not connect, disconnect, or
|
|
86
89
|
* attach `error` listeners; it only issues commands.
|
|
87
90
|
*
|
|
88
|
-
* The package
|
|
89
|
-
* not supported and is rejected here.
|
|
91
|
+
* The package supports Redis 6.2 or later. Redis Cluster is
|
|
92
|
+
* not supported and is rejected here. Guarded consumes and per-proof replay use
|
|
93
|
+
* cached atomic Lua; replay is shared per keyPrefix, uses server TIME, and never
|
|
94
|
+
* evicts live entries. All clients must use the same dpopReplayMaxEntries.
|
|
90
95
|
*
|
|
91
96
|
* @param options construction options; see {@link RedisOidcVaultStoreOptions}.
|
|
92
|
-
* @returns a provider satisfying the
|
|
97
|
+
* @returns a provider satisfying the stronger `OidcVaultDeviceBindingStoreProvider` contract.
|
|
93
98
|
*/
|
|
94
|
-
declare function createRedisOidcVaultStore(options: RedisOidcVaultStoreOptions):
|
|
99
|
+
declare function createRedisOidcVaultStore(options: RedisOidcVaultStoreOptions): OidcVaultDeviceBindingStoreProvider;
|
|
95
100
|
|
|
96
101
|
export { type OidcVaultRedisClient, OidcVaultRedisStoreRecordError, type RedisOidcVaultStoreOptions, createRedisOidcVaultStore };
|
package/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OidcVaultDeviceBindingStoreProvider } from '@web-ts-toolkit/express-oidc-vault';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Thrown when a stored Redis value cannot be parsed or fails structural
|
|
5
|
-
* validation
|
|
5
|
+
* validation (including identity mismatch with the requested key). The message
|
|
6
|
+
* never includes stored token or refresh values.
|
|
6
7
|
* Malformed one-time records are consumed atomically and return `null`; they
|
|
7
8
|
* fail closed without throwing this error. Malformed sessions are deleted when
|
|
8
9
|
* encountered through reads or indexed revocation.
|
|
@@ -33,7 +34,7 @@ declare class OidcVaultRedisStoreRecordError extends Error {
|
|
|
33
34
|
*
|
|
34
35
|
* `sendCommand` is required because atomic session writes, rotations,
|
|
35
36
|
* deletions, and one-time record consumption transit Redis
|
|
36
|
-
*
|
|
37
|
+
* cached Lua/`TYPE`/`TIME`/`ZRANGE`/`ZSCAN`/`MGET` commands through it.
|
|
37
38
|
* It was previously optional in this type but always enforced at runtime.
|
|
38
39
|
*/
|
|
39
40
|
interface OidcVaultRedisClient {
|
|
@@ -58,6 +59,8 @@ interface OidcVaultRedisClient {
|
|
|
58
59
|
* `disconnect()`). The package never calls `connect`, `quit`, or `disconnect`.
|
|
59
60
|
*/
|
|
60
61
|
interface RedisOidcVaultStoreOptions {
|
|
62
|
+
/** Positive safe integer per shared keyPrefix; default 100000. Configure identically on all clients. */
|
|
63
|
+
dpopReplayMaxEntries?: number;
|
|
61
64
|
/** Connected Redis client or compatible adapter implementing {@link OidcVaultRedisClient}. */
|
|
62
65
|
client: OidcVaultRedisClient;
|
|
63
66
|
/**
|
|
@@ -76,7 +79,7 @@ interface RedisOidcVaultStoreOptions {
|
|
|
76
79
|
now?: () => number;
|
|
77
80
|
}
|
|
78
81
|
/**
|
|
79
|
-
* Create an `
|
|
82
|
+
* Create an `OidcVaultDeviceBindingStoreProvider` backed by a connected Redis client.
|
|
80
83
|
*
|
|
81
84
|
* Pass an already-connected official `redis` standalone client (or any adapter
|
|
82
85
|
* implementing {@link OidcVaultRedisClient}). For Redis Sentinel deployments,
|
|
@@ -85,12 +88,14 @@ interface RedisOidcVaultStoreOptions {
|
|
|
85
88
|
* satisfy the structural contract. The store does not connect, disconnect, or
|
|
86
89
|
* attach `error` listeners; it only issues commands.
|
|
87
90
|
*
|
|
88
|
-
* The package
|
|
89
|
-
* not supported and is rejected here.
|
|
91
|
+
* The package supports Redis 6.2 or later. Redis Cluster is
|
|
92
|
+
* not supported and is rejected here. Guarded consumes and per-proof replay use
|
|
93
|
+
* cached atomic Lua; replay is shared per keyPrefix, uses server TIME, and never
|
|
94
|
+
* evicts live entries. All clients must use the same dpopReplayMaxEntries.
|
|
90
95
|
*
|
|
91
96
|
* @param options construction options; see {@link RedisOidcVaultStoreOptions}.
|
|
92
|
-
* @returns a provider satisfying the
|
|
97
|
+
* @returns a provider satisfying the stronger `OidcVaultDeviceBindingStoreProvider` contract.
|
|
93
98
|
*/
|
|
94
|
-
declare function createRedisOidcVaultStore(options: RedisOidcVaultStoreOptions):
|
|
99
|
+
declare function createRedisOidcVaultStore(options: RedisOidcVaultStoreOptions): OidcVaultDeviceBindingStoreProvider;
|
|
95
100
|
|
|
96
101
|
export { type OidcVaultRedisClient, OidcVaultRedisStoreRecordError, type RedisOidcVaultStoreOptions, createRedisOidcVaultStore };
|