@web-ts-toolkit/express-oidc-vault-memory-store 0.31.5 → 0.33.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 +46 -1
- package/index.d.mts +13 -1
- package/index.d.ts +13 -1
- package/index.js +222 -21
- package/index.mjs +222 -21
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ pnpm add @web-ts-toolkit/express-oidc-vault @web-ts-toolkit/express-oidc-vault-m
|
|
|
16
16
|
|
|
17
17
|
## Production Note
|
|
18
18
|
|
|
19
|
-
This package stores authorization transactions, exchange codes, and
|
|
19
|
+
This package stores authorization transactions, exchange codes, sessions, rotated-session aliases, and backchannel logout replay JTIs in process memory.
|
|
20
20
|
|
|
21
21
|
Do not use it for multi-instance or production deployments. Use Redis or MongoDB provider packages for durable or horizontally scaled deployments.
|
|
22
22
|
|
|
@@ -33,12 +33,57 @@ const storeProvider = createMemoryOidcVaultStore();
|
|
|
33
33
|
app.use(
|
|
34
34
|
createOidcVaultMiddleware({
|
|
35
35
|
basePath: '/auth/oidc',
|
|
36
|
+
backendOrigin: 'https://api.example.com',
|
|
37
|
+
config: {
|
|
38
|
+
issuer: process.env.OIDC_ISSUER,
|
|
39
|
+
clientId: process.env.OIDC_CLIENT_ID,
|
|
40
|
+
clientSecret: process.env.OIDC_CLIENT_SECRET,
|
|
41
|
+
},
|
|
42
|
+
frontendRedirectUri: 'https://frontend.example.com/callback',
|
|
36
43
|
storeProvider,
|
|
37
44
|
}),
|
|
38
45
|
);
|
|
39
46
|
```
|
|
40
47
|
|
|
48
|
+
For local development, this is the shortest setup because it has no Redis, MongoDB, or file-system dependency. Sessions are lost on process restart and are not visible to other Node.js instances.
|
|
49
|
+
|
|
50
|
+
### Test-Friendly Clock Override
|
|
51
|
+
|
|
52
|
+
`now()` returns epoch milliseconds and defaults to `Date.now`. Override it only as a deterministic test seam; the returned value must be monotonic enough for the expiry scenarios your test exercises.
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
const storeProvider = createMemoryOidcVaultStore({
|
|
56
|
+
now: () => 1_700_000_000_000,
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
41
60
|
## Main Exports
|
|
42
61
|
|
|
43
62
|
- `createMemoryOidcVaultStore(...)`
|
|
44
63
|
- `type MemoryOidcVaultStoreOptions`
|
|
64
|
+
|
|
65
|
+
## Session Rotation Aliases
|
|
66
|
+
|
|
67
|
+
When a session is rotated, the previous public session ID remains a revocation alias for the current logical session while that logical session is still live. Aliases expire with the rotated target session and are removed when logical, subject, provider-session, direct, or expiry deletion leaves no live session in that logical lineage.
|
|
68
|
+
|
|
69
|
+
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.
|
|
70
|
+
|
|
71
|
+
## Store Contract
|
|
72
|
+
|
|
73
|
+
The store keeps these record kinds in separate in-process maps:
|
|
74
|
+
|
|
75
|
+
- authorization transactions, consumed once by `state`
|
|
76
|
+
- exchange codes, consumed once by `code`
|
|
77
|
+
- sessions, read and deleted by session, logical session, subject, or provider session identifiers
|
|
78
|
+
- rotated-session aliases, used only so an old public session ID can revoke the current logical session
|
|
79
|
+
- backchannel logout token JTIs, consumed once until their expiry time
|
|
80
|
+
|
|
81
|
+
`createAuthorizationTransaction`, `createExchangeCode`, and `createSession` are upserts; creating the same key again replaces the old value. Metadata should be structured-clone compatible and JSON-compatible for portability across the memory, Redis, and MongoDB stores. The memory store clones inputs and returned records with `structuredClone`, so callers retain ownership of their objects and later mutations do not update persisted state.
|
|
82
|
+
|
|
83
|
+
Expiry checks use `expiresAt <= now()` as expired. Cleanup is opportunistic: reads and writes prune expired records in bounded batches or when a specific record is accessed, not on a background timer.
|
|
84
|
+
|
|
85
|
+
`deleteSession(...)`, `deleteSessionsByLogicalSessionId(...)`, `deleteSessionsBySubject(...)`, and `deleteSessionsByProviderSessionId(...)` logically revoke live sessions and remove stale rotation aliases when no live session remains in a logical lineage.
|
|
86
|
+
|
|
87
|
+
## Replay JTI Expiry
|
|
88
|
+
|
|
89
|
+
Backchannel logout token JTI records are consumed only when `expiresAt` is a finite timestamp greater than the store clock at consume time. Expired, equal-to-current-time, `NaN`, and infinite expiries return `false` and are not stored.
|
package/index.d.mts
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { OidcVaultStoreProvider } from '@web-ts-toolkit/express-oidc-vault';
|
|
2
2
|
|
|
3
3
|
interface MemoryOidcVaultStoreOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Store clock in epoch milliseconds.
|
|
6
|
+
*
|
|
7
|
+
* Defaults to `Date.now`. Override this in deterministic tests only; the
|
|
8
|
+
* returned value should move forward enough for the expiry scenarios being
|
|
9
|
+
* tested.
|
|
10
|
+
*/
|
|
4
11
|
now?: () => number;
|
|
5
12
|
}
|
|
6
13
|
/**
|
|
7
|
-
* Create
|
|
14
|
+
* Create a process-local OIDC vault store provider for local development and tests.
|
|
15
|
+
*
|
|
16
|
+
* Records are kept in memory, cloned on read/write, cleaned up opportunistically
|
|
17
|
+
* during store operations, and lost when the Node.js process exits. Do not use
|
|
18
|
+
* this provider when sessions must survive restarts or be shared by multiple
|
|
19
|
+
* application instances.
|
|
8
20
|
*/
|
|
9
21
|
declare function createMemoryOidcVaultStore(options?: MemoryOidcVaultStoreOptions): OidcVaultStoreProvider;
|
|
10
22
|
|
package/index.d.ts
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { OidcVaultStoreProvider } from '@web-ts-toolkit/express-oidc-vault';
|
|
2
2
|
|
|
3
3
|
interface MemoryOidcVaultStoreOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Store clock in epoch milliseconds.
|
|
6
|
+
*
|
|
7
|
+
* Defaults to `Date.now`. Override this in deterministic tests only; the
|
|
8
|
+
* returned value should move forward enough for the expiry scenarios being
|
|
9
|
+
* tested.
|
|
10
|
+
*/
|
|
4
11
|
now?: () => number;
|
|
5
12
|
}
|
|
6
13
|
/**
|
|
7
|
-
* Create
|
|
14
|
+
* Create a process-local OIDC vault store provider for local development and tests.
|
|
15
|
+
*
|
|
16
|
+
* Records are kept in memory, cloned on read/write, cleaned up opportunistically
|
|
17
|
+
* during store operations, and lost when the Node.js process exits. Do not use
|
|
18
|
+
* this provider when sessions must survive restarts or be shared by multiple
|
|
19
|
+
* application instances.
|
|
8
20
|
*/
|
|
9
21
|
declare function createMemoryOidcVaultStore(options?: MemoryOidcVaultStoreOptions): OidcVaultStoreProvider;
|
|
10
22
|
|
package/index.js
CHANGED
|
@@ -26,6 +26,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
26
26
|
var import_express_oidc_vault = require("@web-ts-toolkit/express-oidc-vault");
|
|
27
27
|
var cloneRecord = (value) => structuredClone(value);
|
|
28
28
|
var isExpiredRecord = (value, now) => typeof value.expiresAt === "number" && value.expiresAt <= now;
|
|
29
|
+
var EXPIRY_SWEEP_BATCH_SIZE = 64;
|
|
29
30
|
var matchesProviderScope = (session, input) => {
|
|
30
31
|
if (input.issuer !== void 0 && session.provider?.issuer !== input.issuer) {
|
|
31
32
|
return false;
|
|
@@ -37,109 +38,309 @@ var matchesProviderScope = (session, input) => {
|
|
|
37
38
|
};
|
|
38
39
|
var toSubjectDeleteInput = (input) => typeof input === "string" ? { subject: input } : input;
|
|
39
40
|
var toProviderSessionDeleteInput = (input) => typeof input === "string" ? { providerSessionId: input } : input;
|
|
41
|
+
var toLogicalSessionDeleteInput = (input) => typeof input === "string" ? { logicalSessionId: input } : input;
|
|
40
42
|
var MemoryOidcVaultStore = class {
|
|
41
43
|
constructor(options = {}) {
|
|
42
44
|
this.authorizationTransactions = /* @__PURE__ */ new Map();
|
|
43
45
|
this.exchangeCodes = /* @__PURE__ */ new Map();
|
|
44
46
|
this.sessions = /* @__PURE__ */ new Map();
|
|
47
|
+
this.rotatedSessionAliases = /* @__PURE__ */ new Map();
|
|
48
|
+
this.backchannelLogoutTokenJtis = /* @__PURE__ */ new Map();
|
|
45
49
|
this.now = options.now ?? (() => Date.now());
|
|
46
50
|
}
|
|
47
51
|
async createAuthorizationTransaction(input) {
|
|
48
|
-
this.
|
|
52
|
+
const now = this.now();
|
|
53
|
+
this.authorizationTransactionSweepCursor = this.pruneMapBatch(
|
|
54
|
+
this.authorizationTransactions,
|
|
55
|
+
now,
|
|
56
|
+
this.authorizationTransactionSweepCursor
|
|
57
|
+
);
|
|
49
58
|
this.authorizationTransactions.set(input.state, cloneRecord(input));
|
|
50
59
|
}
|
|
51
60
|
async consumeAuthorizationTransaction(state) {
|
|
52
|
-
this.
|
|
61
|
+
const now = this.now();
|
|
62
|
+
this.authorizationTransactionSweepCursor = this.pruneMapBatch(
|
|
63
|
+
this.authorizationTransactions,
|
|
64
|
+
now,
|
|
65
|
+
this.authorizationTransactionSweepCursor
|
|
66
|
+
);
|
|
53
67
|
const record = this.authorizationTransactions.get(state);
|
|
54
68
|
if (!record) {
|
|
55
69
|
return null;
|
|
56
70
|
}
|
|
57
71
|
this.authorizationTransactions.delete(state);
|
|
72
|
+
if (isExpiredRecord(record, now)) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
58
75
|
return cloneRecord(record);
|
|
59
76
|
}
|
|
60
77
|
async createExchangeCode(input) {
|
|
61
|
-
this.
|
|
78
|
+
const now = this.now();
|
|
79
|
+
this.exchangeCodeSweepCursor = this.pruneMapBatch(this.exchangeCodes, now, this.exchangeCodeSweepCursor);
|
|
62
80
|
this.exchangeCodes.set(input.code, cloneRecord(input));
|
|
63
81
|
}
|
|
64
82
|
async consumeExchangeCode(code) {
|
|
65
|
-
this.
|
|
83
|
+
const now = this.now();
|
|
84
|
+
this.exchangeCodeSweepCursor = this.pruneMapBatch(this.exchangeCodes, now, this.exchangeCodeSweepCursor);
|
|
66
85
|
const record = this.exchangeCodes.get(code);
|
|
67
86
|
if (!record) {
|
|
68
87
|
return null;
|
|
69
88
|
}
|
|
70
89
|
this.exchangeCodes.delete(code);
|
|
90
|
+
if (isExpiredRecord(record, now)) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
71
93
|
return cloneRecord(record);
|
|
72
94
|
}
|
|
73
95
|
async createSession(input) {
|
|
74
|
-
this.pruneExpiredRecords();
|
|
75
96
|
const timestamp = this.now();
|
|
97
|
+
this.pruneSessionsBatch(timestamp);
|
|
98
|
+
this.rotatedSessionAliasSweepCursor = this.pruneMapBatch(
|
|
99
|
+
this.rotatedSessionAliases,
|
|
100
|
+
timestamp,
|
|
101
|
+
this.rotatedSessionAliasSweepCursor
|
|
102
|
+
);
|
|
76
103
|
const session = {
|
|
77
104
|
...cloneRecord(input),
|
|
105
|
+
logicalSessionId: input.logicalSessionId ?? input.sessionId,
|
|
78
106
|
createdAt: input.createdAt ?? timestamp,
|
|
79
107
|
updatedAt: input.updatedAt ?? timestamp
|
|
80
108
|
};
|
|
81
109
|
this.sessions.set(session.sessionId, session);
|
|
110
|
+
this.rotatedSessionAliases.delete(session.sessionId);
|
|
82
111
|
return cloneRecord(session);
|
|
83
112
|
}
|
|
84
113
|
async getSession(sessionId) {
|
|
85
|
-
this.
|
|
114
|
+
const now = this.now();
|
|
86
115
|
const session = this.sessions.get(sessionId);
|
|
116
|
+
if (session && isExpiredRecord(session, now)) {
|
|
117
|
+
this.sessions.delete(sessionId);
|
|
118
|
+
this.removeAliasesForInactiveLogicalSession(session.logicalSessionId ?? session.sessionId);
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
87
121
|
return session ? cloneRecord(session) : null;
|
|
88
122
|
}
|
|
89
123
|
async rotateSession(input) {
|
|
90
|
-
this.
|
|
91
|
-
|
|
124
|
+
const timestamp = this.now();
|
|
125
|
+
const sourceSession = this.sessions.get(input.sessionId);
|
|
126
|
+
if (sourceSession && isExpiredRecord(sourceSession, timestamp)) {
|
|
127
|
+
this.sessions.delete(input.sessionId);
|
|
128
|
+
this.removeAliasesForInactiveLogicalSession(sourceSession.logicalSessionId ?? sourceSession.sessionId);
|
|
129
|
+
}
|
|
130
|
+
const liveSourceSession = this.sessions.get(input.sessionId);
|
|
131
|
+
if (!liveSourceSession) {
|
|
92
132
|
throw new import_express_oidc_vault.OidcVaultStoreConflictError("OIDC vault session no longer exists for rotation.");
|
|
93
133
|
}
|
|
94
|
-
|
|
134
|
+
if (input.nextSession.sessionId === input.sessionId) {
|
|
135
|
+
throw new import_express_oidc_vault.OidcVaultStoreConflictError("OIDC vault session rotation target must use a different session ID.");
|
|
136
|
+
}
|
|
137
|
+
const existingTargetSession = this.sessions.get(input.nextSession.sessionId);
|
|
138
|
+
if (existingTargetSession && isExpiredRecord(existingTargetSession, timestamp)) {
|
|
139
|
+
this.sessions.delete(input.nextSession.sessionId);
|
|
140
|
+
this.removeAliasesForInactiveLogicalSession(
|
|
141
|
+
existingTargetSession.logicalSessionId ?? existingTargetSession.sessionId
|
|
142
|
+
);
|
|
143
|
+
} else if (existingTargetSession) {
|
|
144
|
+
throw new import_express_oidc_vault.OidcVaultStoreConflictError("OIDC vault session rotation target already exists.");
|
|
145
|
+
}
|
|
95
146
|
const nextSession = cloneRecord(input.nextSession);
|
|
96
|
-
const timestamp = this.now();
|
|
97
147
|
const session = {
|
|
98
148
|
...nextSession,
|
|
149
|
+
logicalSessionId: nextSession.logicalSessionId ?? liveSourceSession.logicalSessionId ?? liveSourceSession.sessionId,
|
|
99
150
|
createdAt: nextSession.createdAt ?? timestamp,
|
|
100
151
|
updatedAt: nextSession.updatedAt ?? timestamp
|
|
101
152
|
};
|
|
153
|
+
this.sessions.delete(input.sessionId);
|
|
102
154
|
this.sessions.set(session.sessionId, session);
|
|
155
|
+
this.rotatedSessionAliases.set(input.sessionId, {
|
|
156
|
+
logicalSessionId: session.logicalSessionId ?? session.sessionId,
|
|
157
|
+
expiresAt: session.expiresAt
|
|
158
|
+
});
|
|
103
159
|
return cloneRecord(session);
|
|
104
160
|
}
|
|
105
161
|
async deleteSession(sessionId) {
|
|
106
|
-
this.
|
|
162
|
+
const now = this.now();
|
|
163
|
+
this.pruneSessionsBatch(now);
|
|
164
|
+
this.rotatedSessionAliasSweepCursor = this.pruneMapBatch(
|
|
165
|
+
this.rotatedSessionAliases,
|
|
166
|
+
now,
|
|
167
|
+
this.rotatedSessionAliasSweepCursor
|
|
168
|
+
);
|
|
169
|
+
const session = this.sessions.get(sessionId);
|
|
170
|
+
if (session) {
|
|
171
|
+
const logicalSessionId = session.logicalSessionId ?? session.sessionId;
|
|
172
|
+
this.sessions.delete(sessionId);
|
|
173
|
+
this.rotatedSessionAliases.delete(sessionId);
|
|
174
|
+
this.removeAliasesForInactiveLogicalSession(logicalSessionId);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const alias = this.rotatedSessionAliases.get(sessionId);
|
|
178
|
+
if (alias) {
|
|
179
|
+
if (isExpiredRecord(alias, now)) {
|
|
180
|
+
this.rotatedSessionAliases.delete(sessionId);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
await this.deleteSessionsByLogicalSessionId(alias.logicalSessionId);
|
|
184
|
+
this.rotatedSessionAliases.delete(sessionId);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async deleteSessionsByLogicalSessionId(input) {
|
|
188
|
+
const now = this.now();
|
|
189
|
+
const resolved = toLogicalSessionDeleteInput(input);
|
|
190
|
+
let deleted = 0;
|
|
191
|
+
const expiredLogicalSessionIds = /* @__PURE__ */ new Set();
|
|
192
|
+
for (const [sessionId, session] of this.sessions.entries()) {
|
|
193
|
+
if (isExpiredRecord(session, now)) {
|
|
194
|
+
expiredLogicalSessionIds.add(session.logicalSessionId ?? session.sessionId);
|
|
195
|
+
this.sessions.delete(sessionId);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if ((session.logicalSessionId ?? session.sessionId) === resolved.logicalSessionId) {
|
|
199
|
+
this.sessions.delete(sessionId);
|
|
200
|
+
deleted += 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
this.removeAliasesForInactiveLogicalSessions(expiredLogicalSessionIds);
|
|
204
|
+
this.removeAliasesForLogicalSession(resolved.logicalSessionId);
|
|
205
|
+
return deleted;
|
|
206
|
+
}
|
|
207
|
+
async consumeBackchannelLogoutTokenJti(input) {
|
|
208
|
+
const now = this.now();
|
|
209
|
+
this.backchannelLogoutTokenJtiSweepCursor = this.pruneMapBatch(
|
|
210
|
+
this.backchannelLogoutTokenJtis,
|
|
211
|
+
now,
|
|
212
|
+
this.backchannelLogoutTokenJtiSweepCursor
|
|
213
|
+
);
|
|
214
|
+
if (!Number.isFinite(input.expiresAt) || input.expiresAt <= now) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
const existingRecord = this.backchannelLogoutTokenJtis.get(input.jti);
|
|
218
|
+
if (existingRecord && !isExpiredRecord(existingRecord, now)) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
this.backchannelLogoutTokenJtis.set(input.jti, { expiresAt: input.expiresAt });
|
|
222
|
+
return true;
|
|
107
223
|
}
|
|
108
224
|
async deleteSessionsBySubject(input) {
|
|
109
|
-
this.
|
|
225
|
+
const now = this.now();
|
|
110
226
|
const resolved = toSubjectDeleteInput(input);
|
|
111
227
|
let deleted = 0;
|
|
228
|
+
const logicalSessionIds = /* @__PURE__ */ new Set();
|
|
229
|
+
const expiredLogicalSessionIds = /* @__PURE__ */ new Set();
|
|
112
230
|
for (const [sessionId, session] of this.sessions.entries()) {
|
|
231
|
+
if (isExpiredRecord(session, now)) {
|
|
232
|
+
expiredLogicalSessionIds.add(session.logicalSessionId ?? session.sessionId);
|
|
233
|
+
this.sessions.delete(sessionId);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
113
236
|
if (session.subject === resolved.subject && matchesProviderScope(session, resolved)) {
|
|
237
|
+
logicalSessionIds.add(session.logicalSessionId ?? session.sessionId);
|
|
114
238
|
this.sessions.delete(sessionId);
|
|
115
239
|
deleted += 1;
|
|
116
240
|
}
|
|
117
241
|
}
|
|
242
|
+
this.removeAliasesForInactiveLogicalSessions(expiredLogicalSessionIds);
|
|
243
|
+
this.removeAliasesForInactiveLogicalSessions(logicalSessionIds);
|
|
118
244
|
return deleted;
|
|
119
245
|
}
|
|
120
246
|
async deleteSessionsByProviderSessionId(input) {
|
|
121
|
-
this.
|
|
247
|
+
const now = this.now();
|
|
122
248
|
const resolved = toProviderSessionDeleteInput(input);
|
|
123
249
|
let deleted = 0;
|
|
250
|
+
const logicalSessionIds = /* @__PURE__ */ new Set();
|
|
251
|
+
const expiredLogicalSessionIds = /* @__PURE__ */ new Set();
|
|
124
252
|
for (const [sessionId, session] of this.sessions.entries()) {
|
|
253
|
+
if (isExpiredRecord(session, now)) {
|
|
254
|
+
expiredLogicalSessionIds.add(session.logicalSessionId ?? session.sessionId);
|
|
255
|
+
this.sessions.delete(sessionId);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
125
258
|
if (session.providerSessionId === resolved.providerSessionId && matchesProviderScope(session, resolved)) {
|
|
259
|
+
logicalSessionIds.add(session.logicalSessionId ?? session.sessionId);
|
|
126
260
|
this.sessions.delete(sessionId);
|
|
127
261
|
deleted += 1;
|
|
128
262
|
}
|
|
129
263
|
}
|
|
264
|
+
this.removeAliasesForInactiveLogicalSessions(expiredLogicalSessionIds);
|
|
265
|
+
this.removeAliasesForInactiveLogicalSessions(logicalSessionIds);
|
|
130
266
|
return deleted;
|
|
131
267
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
this.
|
|
136
|
-
this.pruneMap(this.sessions, now);
|
|
137
|
-
}
|
|
138
|
-
pruneMap(map, now) {
|
|
139
|
-
for (const [key, value] of map.entries()) {
|
|
268
|
+
pruneMapBatch(map, now, cursor) {
|
|
269
|
+
let inspected = 0;
|
|
270
|
+
let nextCursor;
|
|
271
|
+
for (const [key, value] of this.entriesAfterCursor(map, cursor)) {
|
|
140
272
|
if (isExpiredRecord(value, now)) {
|
|
141
273
|
map.delete(key);
|
|
142
274
|
}
|
|
275
|
+
inspected += 1;
|
|
276
|
+
nextCursor = key;
|
|
277
|
+
if (inspected >= EXPIRY_SWEEP_BATCH_SIZE) {
|
|
278
|
+
return nextCursor;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return void 0;
|
|
282
|
+
}
|
|
283
|
+
pruneSessionsBatch(now) {
|
|
284
|
+
const expiredLogicalSessionIds = /* @__PURE__ */ new Set();
|
|
285
|
+
let inspected = 0;
|
|
286
|
+
for (const [sessionId, session] of this.entriesAfterCursor(this.sessions, this.sessionSweepCursor)) {
|
|
287
|
+
if (isExpiredRecord(session, now)) {
|
|
288
|
+
expiredLogicalSessionIds.add(session.logicalSessionId ?? session.sessionId);
|
|
289
|
+
this.sessions.delete(sessionId);
|
|
290
|
+
}
|
|
291
|
+
inspected += 1;
|
|
292
|
+
this.sessionSweepCursor = sessionId;
|
|
293
|
+
if (inspected >= EXPIRY_SWEEP_BATCH_SIZE) {
|
|
294
|
+
this.removeAliasesForInactiveLogicalSessions(expiredLogicalSessionIds);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
this.sessionSweepCursor = void 0;
|
|
299
|
+
this.removeAliasesForInactiveLogicalSessions(expiredLogicalSessionIds);
|
|
300
|
+
}
|
|
301
|
+
*entriesAfterCursor(map, cursor) {
|
|
302
|
+
if (!cursor || !map.has(cursor)) {
|
|
303
|
+
yield* map.entries();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
let foundCursor = false;
|
|
307
|
+
for (const entry of map.entries()) {
|
|
308
|
+
if (!foundCursor) {
|
|
309
|
+
foundCursor = entry[0] === cursor;
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
yield entry;
|
|
313
|
+
}
|
|
314
|
+
for (const entry of map.entries()) {
|
|
315
|
+
if (entry[0] === cursor) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
yield entry;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
removeAliasesForInactiveLogicalSessions(logicalSessionIds) {
|
|
322
|
+
for (const logicalSessionId of logicalSessionIds) {
|
|
323
|
+
this.removeAliasesForInactiveLogicalSession(logicalSessionId);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
removeAliasesForInactiveLogicalSession(logicalSessionId) {
|
|
327
|
+
if (!this.hasLiveSessionForLogicalSession(logicalSessionId)) {
|
|
328
|
+
this.removeAliasesForLogicalSession(logicalSessionId);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
hasLiveSessionForLogicalSession(logicalSessionId) {
|
|
332
|
+
for (const session of this.sessions.values()) {
|
|
333
|
+
if ((session.logicalSessionId ?? session.sessionId) === logicalSessionId) {
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
removeAliasesForLogicalSession(logicalSessionId) {
|
|
340
|
+
for (const [sessionId, alias] of this.rotatedSessionAliases.entries()) {
|
|
341
|
+
if (alias.logicalSessionId === logicalSessionId) {
|
|
342
|
+
this.rotatedSessionAliases.delete(sessionId);
|
|
343
|
+
}
|
|
143
344
|
}
|
|
144
345
|
}
|
|
145
346
|
};
|
package/index.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
} from "@web-ts-toolkit/express-oidc-vault";
|
|
5
5
|
var cloneRecord = (value) => structuredClone(value);
|
|
6
6
|
var isExpiredRecord = (value, now) => typeof value.expiresAt === "number" && value.expiresAt <= now;
|
|
7
|
+
var EXPIRY_SWEEP_BATCH_SIZE = 64;
|
|
7
8
|
var matchesProviderScope = (session, input) => {
|
|
8
9
|
if (input.issuer !== void 0 && session.provider?.issuer !== input.issuer) {
|
|
9
10
|
return false;
|
|
@@ -15,109 +16,309 @@ var matchesProviderScope = (session, input) => {
|
|
|
15
16
|
};
|
|
16
17
|
var toSubjectDeleteInput = (input) => typeof input === "string" ? { subject: input } : input;
|
|
17
18
|
var toProviderSessionDeleteInput = (input) => typeof input === "string" ? { providerSessionId: input } : input;
|
|
19
|
+
var toLogicalSessionDeleteInput = (input) => typeof input === "string" ? { logicalSessionId: input } : input;
|
|
18
20
|
var MemoryOidcVaultStore = class {
|
|
19
21
|
constructor(options = {}) {
|
|
20
22
|
this.authorizationTransactions = /* @__PURE__ */ new Map();
|
|
21
23
|
this.exchangeCodes = /* @__PURE__ */ new Map();
|
|
22
24
|
this.sessions = /* @__PURE__ */ new Map();
|
|
25
|
+
this.rotatedSessionAliases = /* @__PURE__ */ new Map();
|
|
26
|
+
this.backchannelLogoutTokenJtis = /* @__PURE__ */ new Map();
|
|
23
27
|
this.now = options.now ?? (() => Date.now());
|
|
24
28
|
}
|
|
25
29
|
async createAuthorizationTransaction(input) {
|
|
26
|
-
this.
|
|
30
|
+
const now = this.now();
|
|
31
|
+
this.authorizationTransactionSweepCursor = this.pruneMapBatch(
|
|
32
|
+
this.authorizationTransactions,
|
|
33
|
+
now,
|
|
34
|
+
this.authorizationTransactionSweepCursor
|
|
35
|
+
);
|
|
27
36
|
this.authorizationTransactions.set(input.state, cloneRecord(input));
|
|
28
37
|
}
|
|
29
38
|
async consumeAuthorizationTransaction(state) {
|
|
30
|
-
this.
|
|
39
|
+
const now = this.now();
|
|
40
|
+
this.authorizationTransactionSweepCursor = this.pruneMapBatch(
|
|
41
|
+
this.authorizationTransactions,
|
|
42
|
+
now,
|
|
43
|
+
this.authorizationTransactionSweepCursor
|
|
44
|
+
);
|
|
31
45
|
const record = this.authorizationTransactions.get(state);
|
|
32
46
|
if (!record) {
|
|
33
47
|
return null;
|
|
34
48
|
}
|
|
35
49
|
this.authorizationTransactions.delete(state);
|
|
50
|
+
if (isExpiredRecord(record, now)) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
36
53
|
return cloneRecord(record);
|
|
37
54
|
}
|
|
38
55
|
async createExchangeCode(input) {
|
|
39
|
-
this.
|
|
56
|
+
const now = this.now();
|
|
57
|
+
this.exchangeCodeSweepCursor = this.pruneMapBatch(this.exchangeCodes, now, this.exchangeCodeSweepCursor);
|
|
40
58
|
this.exchangeCodes.set(input.code, cloneRecord(input));
|
|
41
59
|
}
|
|
42
60
|
async consumeExchangeCode(code) {
|
|
43
|
-
this.
|
|
61
|
+
const now = this.now();
|
|
62
|
+
this.exchangeCodeSweepCursor = this.pruneMapBatch(this.exchangeCodes, now, this.exchangeCodeSweepCursor);
|
|
44
63
|
const record = this.exchangeCodes.get(code);
|
|
45
64
|
if (!record) {
|
|
46
65
|
return null;
|
|
47
66
|
}
|
|
48
67
|
this.exchangeCodes.delete(code);
|
|
68
|
+
if (isExpiredRecord(record, now)) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
49
71
|
return cloneRecord(record);
|
|
50
72
|
}
|
|
51
73
|
async createSession(input) {
|
|
52
|
-
this.pruneExpiredRecords();
|
|
53
74
|
const timestamp = this.now();
|
|
75
|
+
this.pruneSessionsBatch(timestamp);
|
|
76
|
+
this.rotatedSessionAliasSweepCursor = this.pruneMapBatch(
|
|
77
|
+
this.rotatedSessionAliases,
|
|
78
|
+
timestamp,
|
|
79
|
+
this.rotatedSessionAliasSweepCursor
|
|
80
|
+
);
|
|
54
81
|
const session = {
|
|
55
82
|
...cloneRecord(input),
|
|
83
|
+
logicalSessionId: input.logicalSessionId ?? input.sessionId,
|
|
56
84
|
createdAt: input.createdAt ?? timestamp,
|
|
57
85
|
updatedAt: input.updatedAt ?? timestamp
|
|
58
86
|
};
|
|
59
87
|
this.sessions.set(session.sessionId, session);
|
|
88
|
+
this.rotatedSessionAliases.delete(session.sessionId);
|
|
60
89
|
return cloneRecord(session);
|
|
61
90
|
}
|
|
62
91
|
async getSession(sessionId) {
|
|
63
|
-
this.
|
|
92
|
+
const now = this.now();
|
|
64
93
|
const session = this.sessions.get(sessionId);
|
|
94
|
+
if (session && isExpiredRecord(session, now)) {
|
|
95
|
+
this.sessions.delete(sessionId);
|
|
96
|
+
this.removeAliasesForInactiveLogicalSession(session.logicalSessionId ?? session.sessionId);
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
65
99
|
return session ? cloneRecord(session) : null;
|
|
66
100
|
}
|
|
67
101
|
async rotateSession(input) {
|
|
68
|
-
this.
|
|
69
|
-
|
|
102
|
+
const timestamp = this.now();
|
|
103
|
+
const sourceSession = this.sessions.get(input.sessionId);
|
|
104
|
+
if (sourceSession && isExpiredRecord(sourceSession, timestamp)) {
|
|
105
|
+
this.sessions.delete(input.sessionId);
|
|
106
|
+
this.removeAliasesForInactiveLogicalSession(sourceSession.logicalSessionId ?? sourceSession.sessionId);
|
|
107
|
+
}
|
|
108
|
+
const liveSourceSession = this.sessions.get(input.sessionId);
|
|
109
|
+
if (!liveSourceSession) {
|
|
70
110
|
throw new OidcVaultStoreConflictError("OIDC vault session no longer exists for rotation.");
|
|
71
111
|
}
|
|
72
|
-
|
|
112
|
+
if (input.nextSession.sessionId === input.sessionId) {
|
|
113
|
+
throw new OidcVaultStoreConflictError("OIDC vault session rotation target must use a different session ID.");
|
|
114
|
+
}
|
|
115
|
+
const existingTargetSession = this.sessions.get(input.nextSession.sessionId);
|
|
116
|
+
if (existingTargetSession && isExpiredRecord(existingTargetSession, timestamp)) {
|
|
117
|
+
this.sessions.delete(input.nextSession.sessionId);
|
|
118
|
+
this.removeAliasesForInactiveLogicalSession(
|
|
119
|
+
existingTargetSession.logicalSessionId ?? existingTargetSession.sessionId
|
|
120
|
+
);
|
|
121
|
+
} else if (existingTargetSession) {
|
|
122
|
+
throw new OidcVaultStoreConflictError("OIDC vault session rotation target already exists.");
|
|
123
|
+
}
|
|
73
124
|
const nextSession = cloneRecord(input.nextSession);
|
|
74
|
-
const timestamp = this.now();
|
|
75
125
|
const session = {
|
|
76
126
|
...nextSession,
|
|
127
|
+
logicalSessionId: nextSession.logicalSessionId ?? liveSourceSession.logicalSessionId ?? liveSourceSession.sessionId,
|
|
77
128
|
createdAt: nextSession.createdAt ?? timestamp,
|
|
78
129
|
updatedAt: nextSession.updatedAt ?? timestamp
|
|
79
130
|
};
|
|
131
|
+
this.sessions.delete(input.sessionId);
|
|
80
132
|
this.sessions.set(session.sessionId, session);
|
|
133
|
+
this.rotatedSessionAliases.set(input.sessionId, {
|
|
134
|
+
logicalSessionId: session.logicalSessionId ?? session.sessionId,
|
|
135
|
+
expiresAt: session.expiresAt
|
|
136
|
+
});
|
|
81
137
|
return cloneRecord(session);
|
|
82
138
|
}
|
|
83
139
|
async deleteSession(sessionId) {
|
|
84
|
-
this.
|
|
140
|
+
const now = this.now();
|
|
141
|
+
this.pruneSessionsBatch(now);
|
|
142
|
+
this.rotatedSessionAliasSweepCursor = this.pruneMapBatch(
|
|
143
|
+
this.rotatedSessionAliases,
|
|
144
|
+
now,
|
|
145
|
+
this.rotatedSessionAliasSweepCursor
|
|
146
|
+
);
|
|
147
|
+
const session = this.sessions.get(sessionId);
|
|
148
|
+
if (session) {
|
|
149
|
+
const logicalSessionId = session.logicalSessionId ?? session.sessionId;
|
|
150
|
+
this.sessions.delete(sessionId);
|
|
151
|
+
this.rotatedSessionAliases.delete(sessionId);
|
|
152
|
+
this.removeAliasesForInactiveLogicalSession(logicalSessionId);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const alias = this.rotatedSessionAliases.get(sessionId);
|
|
156
|
+
if (alias) {
|
|
157
|
+
if (isExpiredRecord(alias, now)) {
|
|
158
|
+
this.rotatedSessionAliases.delete(sessionId);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
await this.deleteSessionsByLogicalSessionId(alias.logicalSessionId);
|
|
162
|
+
this.rotatedSessionAliases.delete(sessionId);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async deleteSessionsByLogicalSessionId(input) {
|
|
166
|
+
const now = this.now();
|
|
167
|
+
const resolved = toLogicalSessionDeleteInput(input);
|
|
168
|
+
let deleted = 0;
|
|
169
|
+
const expiredLogicalSessionIds = /* @__PURE__ */ new Set();
|
|
170
|
+
for (const [sessionId, session] of this.sessions.entries()) {
|
|
171
|
+
if (isExpiredRecord(session, now)) {
|
|
172
|
+
expiredLogicalSessionIds.add(session.logicalSessionId ?? session.sessionId);
|
|
173
|
+
this.sessions.delete(sessionId);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if ((session.logicalSessionId ?? session.sessionId) === resolved.logicalSessionId) {
|
|
177
|
+
this.sessions.delete(sessionId);
|
|
178
|
+
deleted += 1;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
this.removeAliasesForInactiveLogicalSessions(expiredLogicalSessionIds);
|
|
182
|
+
this.removeAliasesForLogicalSession(resolved.logicalSessionId);
|
|
183
|
+
return deleted;
|
|
184
|
+
}
|
|
185
|
+
async consumeBackchannelLogoutTokenJti(input) {
|
|
186
|
+
const now = this.now();
|
|
187
|
+
this.backchannelLogoutTokenJtiSweepCursor = this.pruneMapBatch(
|
|
188
|
+
this.backchannelLogoutTokenJtis,
|
|
189
|
+
now,
|
|
190
|
+
this.backchannelLogoutTokenJtiSweepCursor
|
|
191
|
+
);
|
|
192
|
+
if (!Number.isFinite(input.expiresAt) || input.expiresAt <= now) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
const existingRecord = this.backchannelLogoutTokenJtis.get(input.jti);
|
|
196
|
+
if (existingRecord && !isExpiredRecord(existingRecord, now)) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
this.backchannelLogoutTokenJtis.set(input.jti, { expiresAt: input.expiresAt });
|
|
200
|
+
return true;
|
|
85
201
|
}
|
|
86
202
|
async deleteSessionsBySubject(input) {
|
|
87
|
-
this.
|
|
203
|
+
const now = this.now();
|
|
88
204
|
const resolved = toSubjectDeleteInput(input);
|
|
89
205
|
let deleted = 0;
|
|
206
|
+
const logicalSessionIds = /* @__PURE__ */ new Set();
|
|
207
|
+
const expiredLogicalSessionIds = /* @__PURE__ */ new Set();
|
|
90
208
|
for (const [sessionId, session] of this.sessions.entries()) {
|
|
209
|
+
if (isExpiredRecord(session, now)) {
|
|
210
|
+
expiredLogicalSessionIds.add(session.logicalSessionId ?? session.sessionId);
|
|
211
|
+
this.sessions.delete(sessionId);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
91
214
|
if (session.subject === resolved.subject && matchesProviderScope(session, resolved)) {
|
|
215
|
+
logicalSessionIds.add(session.logicalSessionId ?? session.sessionId);
|
|
92
216
|
this.sessions.delete(sessionId);
|
|
93
217
|
deleted += 1;
|
|
94
218
|
}
|
|
95
219
|
}
|
|
220
|
+
this.removeAliasesForInactiveLogicalSessions(expiredLogicalSessionIds);
|
|
221
|
+
this.removeAliasesForInactiveLogicalSessions(logicalSessionIds);
|
|
96
222
|
return deleted;
|
|
97
223
|
}
|
|
98
224
|
async deleteSessionsByProviderSessionId(input) {
|
|
99
|
-
this.
|
|
225
|
+
const now = this.now();
|
|
100
226
|
const resolved = toProviderSessionDeleteInput(input);
|
|
101
227
|
let deleted = 0;
|
|
228
|
+
const logicalSessionIds = /* @__PURE__ */ new Set();
|
|
229
|
+
const expiredLogicalSessionIds = /* @__PURE__ */ new Set();
|
|
102
230
|
for (const [sessionId, session] of this.sessions.entries()) {
|
|
231
|
+
if (isExpiredRecord(session, now)) {
|
|
232
|
+
expiredLogicalSessionIds.add(session.logicalSessionId ?? session.sessionId);
|
|
233
|
+
this.sessions.delete(sessionId);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
103
236
|
if (session.providerSessionId === resolved.providerSessionId && matchesProviderScope(session, resolved)) {
|
|
237
|
+
logicalSessionIds.add(session.logicalSessionId ?? session.sessionId);
|
|
104
238
|
this.sessions.delete(sessionId);
|
|
105
239
|
deleted += 1;
|
|
106
240
|
}
|
|
107
241
|
}
|
|
242
|
+
this.removeAliasesForInactiveLogicalSessions(expiredLogicalSessionIds);
|
|
243
|
+
this.removeAliasesForInactiveLogicalSessions(logicalSessionIds);
|
|
108
244
|
return deleted;
|
|
109
245
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
this.
|
|
114
|
-
this.pruneMap(this.sessions, now);
|
|
115
|
-
}
|
|
116
|
-
pruneMap(map, now) {
|
|
117
|
-
for (const [key, value] of map.entries()) {
|
|
246
|
+
pruneMapBatch(map, now, cursor) {
|
|
247
|
+
let inspected = 0;
|
|
248
|
+
let nextCursor;
|
|
249
|
+
for (const [key, value] of this.entriesAfterCursor(map, cursor)) {
|
|
118
250
|
if (isExpiredRecord(value, now)) {
|
|
119
251
|
map.delete(key);
|
|
120
252
|
}
|
|
253
|
+
inspected += 1;
|
|
254
|
+
nextCursor = key;
|
|
255
|
+
if (inspected >= EXPIRY_SWEEP_BATCH_SIZE) {
|
|
256
|
+
return nextCursor;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return void 0;
|
|
260
|
+
}
|
|
261
|
+
pruneSessionsBatch(now) {
|
|
262
|
+
const expiredLogicalSessionIds = /* @__PURE__ */ new Set();
|
|
263
|
+
let inspected = 0;
|
|
264
|
+
for (const [sessionId, session] of this.entriesAfterCursor(this.sessions, this.sessionSweepCursor)) {
|
|
265
|
+
if (isExpiredRecord(session, now)) {
|
|
266
|
+
expiredLogicalSessionIds.add(session.logicalSessionId ?? session.sessionId);
|
|
267
|
+
this.sessions.delete(sessionId);
|
|
268
|
+
}
|
|
269
|
+
inspected += 1;
|
|
270
|
+
this.sessionSweepCursor = sessionId;
|
|
271
|
+
if (inspected >= EXPIRY_SWEEP_BATCH_SIZE) {
|
|
272
|
+
this.removeAliasesForInactiveLogicalSessions(expiredLogicalSessionIds);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
this.sessionSweepCursor = void 0;
|
|
277
|
+
this.removeAliasesForInactiveLogicalSessions(expiredLogicalSessionIds);
|
|
278
|
+
}
|
|
279
|
+
*entriesAfterCursor(map, cursor) {
|
|
280
|
+
if (!cursor || !map.has(cursor)) {
|
|
281
|
+
yield* map.entries();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
let foundCursor = false;
|
|
285
|
+
for (const entry of map.entries()) {
|
|
286
|
+
if (!foundCursor) {
|
|
287
|
+
foundCursor = entry[0] === cursor;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
yield entry;
|
|
291
|
+
}
|
|
292
|
+
for (const entry of map.entries()) {
|
|
293
|
+
if (entry[0] === cursor) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
yield entry;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
removeAliasesForInactiveLogicalSessions(logicalSessionIds) {
|
|
300
|
+
for (const logicalSessionId of logicalSessionIds) {
|
|
301
|
+
this.removeAliasesForInactiveLogicalSession(logicalSessionId);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
removeAliasesForInactiveLogicalSession(logicalSessionId) {
|
|
305
|
+
if (!this.hasLiveSessionForLogicalSession(logicalSessionId)) {
|
|
306
|
+
this.removeAliasesForLogicalSession(logicalSessionId);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
hasLiveSessionForLogicalSession(logicalSessionId) {
|
|
310
|
+
for (const session of this.sessions.values()) {
|
|
311
|
+
if ((session.logicalSessionId ?? session.sessionId) === logicalSessionId) {
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
removeAliasesForLogicalSession(logicalSessionId) {
|
|
318
|
+
for (const [sessionId, alias] of this.rotatedSessionAliases.entries()) {
|
|
319
|
+
if (alias.logicalSessionId === logicalSessionId) {
|
|
320
|
+
this.rotatedSessionAliases.delete(sessionId);
|
|
321
|
+
}
|
|
121
322
|
}
|
|
122
323
|
}
|
|
123
324
|
};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@web-ts-toolkit/express-oidc-vault-memory-store",
|
|
3
3
|
"description": "In-memory store provider for @web-ts-toolkit/express-oidc-vault",
|
|
4
4
|
"homepage": "https://web-ts-toolkit.pages.dev/docs/packages/express-oidc-vault-memory-store",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.33.0",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"keywords": [
|
|
8
8
|
"express",
|
|
@@ -15,17 +15,21 @@
|
|
|
15
15
|
"types": "./index.d.ts",
|
|
16
16
|
"exports": {
|
|
17
17
|
".": {
|
|
18
|
-
"types":
|
|
18
|
+
"types": {
|
|
19
|
+
"import": "./index.d.mts",
|
|
20
|
+
"require": "./index.d.ts",
|
|
21
|
+
"default": "./index.d.ts"
|
|
22
|
+
},
|
|
19
23
|
"import": "./index.mjs",
|
|
20
24
|
"require": "./index.js",
|
|
21
25
|
"default": "./index.js"
|
|
22
26
|
}
|
|
23
27
|
},
|
|
24
28
|
"engines": {
|
|
25
|
-
"node": ">=
|
|
29
|
+
"node": ">=22"
|
|
26
30
|
},
|
|
27
31
|
"dependencies": {
|
|
28
|
-
"@web-ts-toolkit/express-oidc-vault": "0.
|
|
32
|
+
"@web-ts-toolkit/express-oidc-vault": "0.33.0"
|
|
29
33
|
},
|
|
30
34
|
"files": [
|
|
31
35
|
"**/*",
|