@serialsubscriptions/platform-integration 0.0.79

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.
Files changed (60) hide show
  1. package/README.md +1 -0
  2. package/lib/SSIProject.d.ts +343 -0
  3. package/lib/SSIProject.js +429 -0
  4. package/lib/SSIProjectApi.d.ts +384 -0
  5. package/lib/SSIProjectApi.js +534 -0
  6. package/lib/SSISubscribedFeatureApi.d.ts +387 -0
  7. package/lib/SSISubscribedFeatureApi.js +511 -0
  8. package/lib/SSISubscribedLimitApi.d.ts +384 -0
  9. package/lib/SSISubscribedLimitApi.js +534 -0
  10. package/lib/SSISubscribedPlanApi.d.ts +384 -0
  11. package/lib/SSISubscribedPlanApi.js +537 -0
  12. package/lib/SubscribedPlanManager.d.ts +380 -0
  13. package/lib/SubscribedPlanManager.js +288 -0
  14. package/lib/UsageApi.d.ts +128 -0
  15. package/lib/UsageApi.js +224 -0
  16. package/lib/auth.server.d.ts +192 -0
  17. package/lib/auth.server.js +579 -0
  18. package/lib/cache/SSICache.d.ts +40 -0
  19. package/lib/cache/SSICache.js +134 -0
  20. package/lib/cache/backends/MemoryCacheBackend.d.ts +15 -0
  21. package/lib/cache/backends/MemoryCacheBackend.js +46 -0
  22. package/lib/cache/backends/RedisCacheBackend.d.ts +27 -0
  23. package/lib/cache/backends/RedisCacheBackend.js +95 -0
  24. package/lib/cache/constants.d.ts +7 -0
  25. package/lib/cache/constants.js +10 -0
  26. package/lib/cache/types.d.ts +27 -0
  27. package/lib/cache/types.js +2 -0
  28. package/lib/frontend/index.d.ts +1 -0
  29. package/lib/frontend/index.js +6 -0
  30. package/lib/frontend/session/SessionClient.d.ts +24 -0
  31. package/lib/frontend/session/SessionClient.js +145 -0
  32. package/lib/index.d.ts +15 -0
  33. package/lib/index.js +38 -0
  34. package/lib/lib/session/SessionClient.d.ts +11 -0
  35. package/lib/lib/session/SessionClient.js +47 -0
  36. package/lib/lib/session/index.d.ts +3 -0
  37. package/lib/lib/session/index.js +3 -0
  38. package/lib/lib/session/stores/MemoryStore.d.ts +7 -0
  39. package/lib/lib/session/stores/MemoryStore.js +23 -0
  40. package/lib/lib/session/stores/index.d.ts +1 -0
  41. package/lib/lib/session/stores/index.js +1 -0
  42. package/lib/lib/session/types.d.ts +37 -0
  43. package/lib/lib/session/types.js +1 -0
  44. package/lib/session/SessionClient.d.ts +19 -0
  45. package/lib/session/SessionClient.js +132 -0
  46. package/lib/session/SessionManager.d.ts +139 -0
  47. package/lib/session/SessionManager.js +443 -0
  48. package/lib/stateStore.d.ts +5 -0
  49. package/lib/stateStore.js +9 -0
  50. package/lib/storage/SSIStorage.d.ts +24 -0
  51. package/lib/storage/SSIStorage.js +117 -0
  52. package/lib/storage/backends/MemoryBackend.d.ts +10 -0
  53. package/lib/storage/backends/MemoryBackend.js +44 -0
  54. package/lib/storage/backends/PostgresBackend.d.ts +24 -0
  55. package/lib/storage/backends/PostgresBackend.js +106 -0
  56. package/lib/storage/backends/RedisBackend.d.ts +19 -0
  57. package/lib/storage/backends/RedisBackend.js +78 -0
  58. package/lib/storage/types.d.ts +27 -0
  59. package/lib/storage/types.js +2 -0
  60. package/package.json +71 -0
@@ -0,0 +1,443 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SessionManager = exports.sessionRoles = void 0;
4
+ const SSIStorage_1 = require("../storage/SSIStorage");
5
+ const crypto_1 = require("crypto");
6
+ const auth_server_1 = require("../auth.server");
7
+ exports.sessionRoles = {
8
+ allRoles: ['platform_admin', 'member', 'owner', 'admin', 'billing', 'readonly'],
9
+ adminRoles: ['platform_admin', 'owner', 'admin'],
10
+ userRoles: ['member', 'owner', 'admin', 'billing', 'readonly'],
11
+ userAdminRoles: ['platform_admin', 'owner', 'admin']
12
+ };
13
+ /** Defaults & env */
14
+ const DEFAULT_COOKIE_NAME = process.env.SSI_COOKIE_NAME ?? 'ssi_session';
15
+ const DEFAULT_COOKIE_PATH = process.env.SSI_COOKIE_PATH ?? '/';
16
+ const DEFAULT_COOKIE_SAMESITE = (process.env.SSI_COOKIE_SAMESITE ?? 'Lax');
17
+ const DEFAULT_COOKIE_SECURE = (process.env.SSI_COOKIE_SECURE ?? 'true').toLowerCase() !== 'false';
18
+ // 7 days (seconds)
19
+ const DEFAULT_REFRESH_TTL_SECONDS = Number(process.env.SSI_SESSION_REFRESH_TTL_SECONDS ?? 60 * 60 * 24 * 7);
20
+ // Domain resolution: explicit > derive from NEXTAUTH_URL > undefined
21
+ function resolveCookieDomain() {
22
+ const fromEnv = process.env.SSI_COOKIE_DOMAIN?.trim();
23
+ if (fromEnv)
24
+ return fromEnv;
25
+ const nextAuth = process.env.NEXTAUTH_URL?.trim();
26
+ if (!nextAuth)
27
+ return undefined;
28
+ try {
29
+ const u = new URL(nextAuth);
30
+ return u.hostname || undefined;
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ }
36
+ class SessionManager {
37
+ constructor(storage, authConfig) {
38
+ // Per-request-instance guard so TTL/refresh is evaluated at most once per session
39
+ this.freshnessOnce = new Map();
40
+ this.storage = storage.withClass('session'); // pin class="session"
41
+ this.authConfig = authConfig;
42
+ }
43
+ static fromEnv(cookieHeaderOrRequestOrConfig, authConfig) {
44
+ // Handle case where first arg is AuthConfig (no cookie header/request)
45
+ let actualAuthConfig;
46
+ let cookieHeaderOrRequest;
47
+ if (cookieHeaderOrRequestOrConfig &&
48
+ typeof cookieHeaderOrRequestOrConfig === 'object' &&
49
+ !(cookieHeaderOrRequestOrConfig instanceof Request) &&
50
+ ('clientId' in cookieHeaderOrRequestOrConfig || 'issuerBaseUrl' in cookieHeaderOrRequestOrConfig)) {
51
+ // First arg is AuthConfig
52
+ actualAuthConfig = cookieHeaderOrRequestOrConfig;
53
+ }
54
+ else {
55
+ // First arg is cookie header/request, second is optional AuthConfig
56
+ cookieHeaderOrRequest = cookieHeaderOrRequestOrConfig;
57
+ actualAuthConfig = authConfig;
58
+ }
59
+ const session = new SessionManager(SSIStorage_1.SSIStorage.fromEnv(), actualAuthConfig);
60
+ if (cookieHeaderOrRequest instanceof Request) {
61
+ const cookieHeader = cookieHeaderOrRequest.headers.get('cookie');
62
+ if (cookieHeader) {
63
+ session._sessionId = session.parseCookies(cookieHeader);
64
+ }
65
+ }
66
+ else if (cookieHeaderOrRequest) {
67
+ session._sessionId = session.parseCookies(cookieHeaderOrRequest);
68
+ }
69
+ // When called with no arguments, we cannot auto-detect cookies synchronously
70
+ // because Next.js headers() is async. Use fromEnvAsync() for auto-detection,
71
+ // or pass the Request object or cookie header string explicitly.
72
+ return session;
73
+ }
74
+ static async fromEnvAsync(cookieHeaderOrRequestOrConfig, authConfig) {
75
+ // Handle case where first arg is AuthConfig (no cookie header/request)
76
+ let actualAuthConfig;
77
+ let cookieHeaderOrRequest;
78
+ if (cookieHeaderOrRequestOrConfig &&
79
+ typeof cookieHeaderOrRequestOrConfig === 'object' &&
80
+ !(cookieHeaderOrRequestOrConfig instanceof Request) &&
81
+ ('clientId' in cookieHeaderOrRequestOrConfig || 'issuerBaseUrl' in cookieHeaderOrRequestOrConfig)) {
82
+ // First arg is AuthConfig
83
+ actualAuthConfig = cookieHeaderOrRequestOrConfig;
84
+ }
85
+ else {
86
+ // First arg is cookie header/request, second is optional AuthConfig
87
+ cookieHeaderOrRequest = cookieHeaderOrRequestOrConfig;
88
+ actualAuthConfig = authConfig;
89
+ }
90
+ const session = new SessionManager(SSIStorage_1.SSIStorage.fromEnv(), actualAuthConfig);
91
+ if (cookieHeaderOrRequest instanceof Request) {
92
+ const cookieHeader = cookieHeaderOrRequest.headers.get('cookie');
93
+ if (cookieHeader) {
94
+ session._sessionId = session.parseCookies(cookieHeader);
95
+ }
96
+ }
97
+ else if (cookieHeaderOrRequest) {
98
+ session._sessionId = session.parseCookies(cookieHeaderOrRequest);
99
+ }
100
+ else {
101
+ // When called with no arguments, try to auto-detect cookie header from Next.js context
102
+ try {
103
+ // Dynamic import of next/headers - only available in Next.js server context
104
+ // @ts-expect-error - next/headers types are available at runtime but TypeScript can't resolve them during package build
105
+ const { headers } = await import('next/headers');
106
+ const headersList = await headers();
107
+ if (headersList && typeof headersList.get === 'function') {
108
+ const cookieHeader = headersList.get('cookie');
109
+ if (cookieHeader) {
110
+ session._sessionId = session.parseCookies(cookieHeader);
111
+ }
112
+ }
113
+ }
114
+ catch {
115
+ // Not in Next.js context or headers() not available - silently continue
116
+ // This is expected when called outside of Next.js or when creating new sessions
117
+ }
118
+ }
119
+ return session;
120
+ }
121
+ parseCookies(header) {
122
+ const cookieName = process.env.SSI_COOKIE_NAME ?? 'ssi_session';
123
+ for (const part of header.split(/;\s*/)) {
124
+ const idx = part.indexOf('=');
125
+ if (idx === -1)
126
+ continue;
127
+ const k = decodeURIComponent(part.slice(0, idx).trim());
128
+ const v = decodeURIComponent(part.slice(idx + 1).trim());
129
+ if (k === cookieName)
130
+ return v;
131
+ }
132
+ return undefined;
133
+ }
134
+ get sessionId() {
135
+ return this._sessionId;
136
+ }
137
+ /** Cryptographically strong, URL-safe session id */
138
+ generateSessionId() {
139
+ // 32 bytes => 43 char base64url
140
+ return (0, crypto_1.randomBytes)(32).toString('base64url');
141
+ }
142
+ async setSession(authOrInput, tokenResponseOrOptions, optionsMaybe) {
143
+ // Handle AuthServer overload (duck-typed to avoid cross-bundle instanceof issues)
144
+ const looksLikeAuthServer = !!authOrInput && typeof authOrInput.getLastTokenResponse === 'function';
145
+ if (looksLikeAuthServer) {
146
+ // Determine if second arg is TokenResponse or options
147
+ const isTokenResponse = tokenResponseOrOptions && 'raw' in tokenResponseOrOptions;
148
+ const tokenResponse = isTokenResponse ? tokenResponseOrOptions : authOrInput.getLastTokenResponse();
149
+ const options = isTokenResponse ? optionsMaybe : tokenResponseOrOptions;
150
+ if (!tokenResponse)
151
+ throw new Error('Missing TokenResponse: pass tokens explicitly or call after handleCallback/refreshTokens');
152
+ const sessionId = options?.sessionId ?? this.generateSessionId();
153
+ const refreshTtl = options?.refreshTtlSeconds ?? DEFAULT_REFRESH_TTL_SECONDS;
154
+ // Use provider's expires_in for access/id tokens when available; allow explicit overrides
155
+ const providerTtl = tokenResponse.expires_in && tokenResponse.expires_in > 0 ? tokenResponse.expires_in : undefined;
156
+ const accessTtl = options?.accessTokenTtlSeconds ?? providerTtl ?? 300;
157
+ const idTtl = options?.idTokenTtlSeconds ?? providerTtl ?? accessTtl;
158
+ if (!tokenResponse.id_token || !tokenResponse.access_token || !tokenResponse.refresh_token) {
159
+ throw new Error('TokenResponse must contain id_token, access_token, and refresh_token');
160
+ }
161
+ await Promise.all([
162
+ this.storage.set(sessionId, 'id_token', tokenResponse.id_token, idTtl),
163
+ this.storage.set(sessionId, 'access_token', tokenResponse.access_token, accessTtl),
164
+ this.storage.set(sessionId, 'refresh_token', tokenResponse.refresh_token, refreshTtl),
165
+ ]);
166
+ const setCookieHeader = this.buildSessionCookie(sessionId, {
167
+ maxAge: refreshTtl,
168
+ ...(options?.cookie ?? {}),
169
+ });
170
+ return { sessionId, setCookieHeader };
171
+ }
172
+ // Handle original SetSessionInput overload
173
+ const input = authOrInput;
174
+ const sessionId = input.sessionId ?? this.generateSessionId();
175
+ const refreshTtl = input.refreshTtlSeconds && input.refreshTtlSeconds > 0
176
+ ? input.refreshTtlSeconds
177
+ : DEFAULT_REFRESH_TTL_SECONDS;
178
+ const idTtl = input.idTokenTtlSeconds ?? refreshTtl;
179
+ const accessTtl = input.accessTokenTtlSeconds ?? refreshTtl;
180
+ await Promise.all([
181
+ this.storage.set(sessionId, 'id_token', input.id_token, idTtl),
182
+ this.storage.set(sessionId, 'access_token', input.access_token, accessTtl),
183
+ this.storage.set(sessionId, 'refresh_token', input.refresh_token, refreshTtl),
184
+ ]);
185
+ const setCookieHeader = this.buildSessionCookie(sessionId, {
186
+ maxAge: refreshTtl,
187
+ ...(input.cookie ?? {}),
188
+ });
189
+ return { sessionId, setCookieHeader };
190
+ }
191
+ /** Build a single Set-Cookie header string for the session id */
192
+ buildSessionCookie(sessionId, overrides) {
193
+ const opts = {
194
+ name: DEFAULT_COOKIE_NAME,
195
+ domain: resolveCookieDomain(),
196
+ path: DEFAULT_COOKIE_PATH,
197
+ sameSite: DEFAULT_COOKIE_SAMESITE,
198
+ secure: DEFAULT_COOKIE_SECURE,
199
+ httpOnly: true,
200
+ maxAge: DEFAULT_REFRESH_TTL_SECONDS,
201
+ ...overrides,
202
+ };
203
+ // If SameSite=None, Secure must be true (enforce for safety)
204
+ if (opts.sameSite === 'None')
205
+ opts.secure = true;
206
+ const parts = [
207
+ `${encodeURIComponent(opts.name)}=${encodeURIComponent(sessionId)}`,
208
+ `Path=${opts.path}`,
209
+ `Max-Age=${Math.max(0, Math.floor(opts.maxAge))}`,
210
+ `Expires=${new Date(Date.now() + opts.maxAge * 1000).toUTCString()}`,
211
+ `SameSite=${opts.sameSite}`,
212
+ opts.secure ? 'Secure' : '',
213
+ opts.httpOnly ? 'HttpOnly' : '',
214
+ opts.domain ? `Domain=${opts.domain}` : '',
215
+ ].filter(Boolean);
216
+ return parts.join('; ');
217
+ }
218
+ /**
219
+ * Get session data by key. For 'claims', decodes the id_token payload
220
+ * WITHOUT verification (use your verifier upstream for security).
221
+ */
222
+ async getSessionData(sessionId, dataKey) {
223
+ const keyToCheck = dataKey === 'claims' ? 'id_token' : dataKey;
224
+ try {
225
+ await this.ensureFreshOnce(sessionId);
226
+ }
227
+ catch {
228
+ return null;
229
+ }
230
+ const value = await this.storage.get(sessionId, keyToCheck);
231
+ if (dataKey === 'claims') {
232
+ const idToken = value;
233
+ if (!idToken)
234
+ return null;
235
+ const auth = this.getAuth();
236
+ try {
237
+ const claims = await auth.verifyAndDecodeJwt(idToken);
238
+ return claims;
239
+ }
240
+ catch {
241
+ try {
242
+ const parts = idToken.split('.');
243
+ if (parts.length !== 3)
244
+ return null;
245
+ const payload = parts[1];
246
+ const json = Buffer.from(payload.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8');
247
+ const obj = JSON.parse(json);
248
+ return (obj && typeof obj === 'object') ? obj : null;
249
+ }
250
+ catch {
251
+ return null;
252
+ }
253
+ }
254
+ }
255
+ return value;
256
+ }
257
+ ensureFreshOnce(sessionId) {
258
+ const existing = this.freshnessOnce.get(sessionId);
259
+ if (existing)
260
+ return existing;
261
+ const op = (async () => {
262
+ // Determine need based on id_token TTL once per request instance
263
+ const currentIdToken = (await this.storage.get(sessionId, 'id_token'));
264
+ let needsRefresh = false;
265
+ if (!currentIdToken)
266
+ needsRefresh = true;
267
+ else {
268
+ const ttl = await this.storage.getRemainingTtl(sessionId, 'id_token');
269
+ if (typeof ttl === 'number' && ttl < 60)
270
+ needsRefresh = true;
271
+ }
272
+ if (!needsRefresh) {
273
+ // But if another instance is refreshing now, wait to avoid stale reads
274
+ const inflight = SessionManager.inFlightRefresh.get(sessionId);
275
+ if (inflight)
276
+ await inflight;
277
+ return;
278
+ }
279
+ // Coordinate refresh across instances
280
+ const doRefresh = async () => {
281
+ const existingRefresh = SessionManager.inFlightRefresh.get(sessionId);
282
+ if (existingRefresh)
283
+ return existingRefresh;
284
+ const p = (async () => {
285
+ const refreshToken = (await this.storage.get(sessionId, 'refresh_token'));
286
+ if (!refreshToken)
287
+ throw new Error('Missing refresh_token');
288
+ const auth = this.getAuth();
289
+ const tokens = await auth.refreshTokens(refreshToken);
290
+ if (!tokens || !tokens.id_token || !tokens.access_token || !tokens.refresh_token) {
291
+ throw new Error('Incomplete token response');
292
+ }
293
+ const [prevAccessTtl, prevIdTtl, prevRefreshTtl] = await Promise.all([
294
+ this.storage.getRemainingTtl(sessionId, 'access_token'),
295
+ this.storage.getRemainingTtl(sessionId, 'id_token'),
296
+ this.storage.getRemainingTtl(sessionId, 'refresh_token'),
297
+ ]);
298
+ const accessTtl = (typeof tokens.expires_in === 'number' && tokens.expires_in > 0)
299
+ ? tokens.expires_in
300
+ : (typeof prevAccessTtl === 'number' ? prevAccessTtl : 300);
301
+ const idTtl = (typeof tokens.expires_in === 'number' && tokens.expires_in > 0)
302
+ ? tokens.expires_in
303
+ : (typeof prevIdTtl === 'number' ? prevIdTtl : accessTtl);
304
+ const refreshTtl = (typeof prevRefreshTtl === 'number' && prevRefreshTtl > 0)
305
+ ? prevRefreshTtl
306
+ : DEFAULT_REFRESH_TTL_SECONDS;
307
+ await Promise.all([
308
+ this.storage.set(sessionId, 'id_token', tokens.id_token, idTtl),
309
+ this.storage.set(sessionId, 'access_token', tokens.access_token, accessTtl),
310
+ this.storage.set(sessionId, 'refresh_token', tokens.refresh_token, refreshTtl),
311
+ ]);
312
+ })();
313
+ SessionManager.inFlightRefresh.set(sessionId, p);
314
+ try {
315
+ await p;
316
+ }
317
+ finally {
318
+ SessionManager.inFlightRefresh.delete(sessionId);
319
+ }
320
+ };
321
+ await doRefresh();
322
+ })();
323
+ this.freshnessOnce.set(sessionId, op);
324
+ return op;
325
+ }
326
+ /**
327
+ * Delete all keys under this session object (id/access/refresh).
328
+ */
329
+ async clearSession(sessionId) {
330
+ const deleteCookieHeader = this.buildSessionCookie('', { maxAge: 0 });
331
+ const keys = (await this.storage.keysForObject(sessionId)) ?? [];
332
+ if (!keys.length)
333
+ return deleteCookieHeader;
334
+ // Keys are fully-qualified: container/class/object_id/key
335
+ // Extract the trailing logical key and delete via the storage API
336
+ const deletions = keys.map(fullKey => {
337
+ const parts = fullKey.split('/');
338
+ const logicalKey = parts.slice(3).join('/');
339
+ return this.storage.del(sessionId, logicalKey);
340
+ });
341
+ await Promise.all(deletions);
342
+ // Return Set-Cookie header to clear the browser cookie
343
+ return deleteCookieHeader;
344
+ }
345
+ /**
346
+ * Refresh session (stub): you’ll likely:
347
+ * 1) read refresh_token
348
+ * 2) call your token endpoint
349
+ * 3) update stored tokens + extend TTL
350
+ * 4) optionally rotate session id & cookie
351
+ */
352
+ async refreshSession(_sessionId) {
353
+ throw new Error('refreshSession not implemented yet');
354
+ }
355
+ /** Returns remaining TTL in seconds for this session's id_token, or null if none */
356
+ async getSessionTtlSeconds(sessionId) {
357
+ return this.storage.getRemainingTtl(sessionId, 'id_token');
358
+ }
359
+ async getVerifiedClaims(sessionId) {
360
+ if (!sessionId)
361
+ return null;
362
+ try {
363
+ await this.ensureFreshOnce(sessionId);
364
+ }
365
+ catch {
366
+ return null;
367
+ }
368
+ const idToken = (await this.storage.get(sessionId, 'id_token'));
369
+ if (!idToken)
370
+ return null;
371
+ try {
372
+ const claims = await this.getAuth().verifyAndDecodeJwt(idToken);
373
+ return claims && typeof claims === 'object'
374
+ ? claims
375
+ : null;
376
+ }
377
+ catch {
378
+ return null;
379
+ }
380
+ }
381
+ async getRoles(sessionId) {
382
+ const claims = await this.getVerifiedClaims(sessionId);
383
+ if (!claims)
384
+ return null;
385
+ const roles = claims.roles;
386
+ if (Array.isArray(roles))
387
+ return roles.map(String);
388
+ if (typeof roles === 'string')
389
+ return [roles];
390
+ return null;
391
+ }
392
+ async hasRole(roleName) {
393
+ const sessionId = this.sessionId;
394
+ if (!sessionId)
395
+ throw new Error('SessionManager.hasRole requires sessionId; call SessionManager.fromEnv first.');
396
+ if (!roleName)
397
+ return false;
398
+ const roles = await this.getRoles(sessionId);
399
+ if (!roles)
400
+ return false;
401
+ return roles.includes(roleName);
402
+ }
403
+ async hasRoleOneOf(roleNames) {
404
+ const sessionId = this.sessionId;
405
+ if (!sessionId)
406
+ throw new Error('SessionManager.hasRoleOneOf requires sessionId; call SessionManager.fromEnv first.');
407
+ if (!roleNames?.length)
408
+ return false;
409
+ const roles = await this.getRoles(sessionId);
410
+ if (!roles)
411
+ return false;
412
+ for (const roleName of roleNames) {
413
+ if (roleName && roles.includes(roleName))
414
+ return true;
415
+ }
416
+ return false;
417
+ }
418
+ /**
419
+ * Get a specific claim from the session's ID token.
420
+ * Requires `sessionId` to be set (via `fromEnv()` with cookies).
421
+ */
422
+ async getClaim(claimName) {
423
+ const sessionId = this.sessionId;
424
+ if (!sessionId)
425
+ throw new Error('SessionManager.getClaim requires sessionId; call SessionManager.fromEnv first.');
426
+ if (!claimName)
427
+ return undefined;
428
+ const claims = await this.getVerifiedClaims(sessionId);
429
+ if (!claims || typeof claims !== 'object')
430
+ return undefined;
431
+ return claims[claimName];
432
+ }
433
+ getAuth() {
434
+ if (!this.auth) {
435
+ // Use provided authConfig or fall back to env-driven configuration inside AuthServer
436
+ this.auth = new auth_server_1.AuthServer(this.authConfig);
437
+ }
438
+ return this.auth;
439
+ }
440
+ }
441
+ exports.SessionManager = SessionManager;
442
+ // Ensure only one refresh runs per session at a time
443
+ SessionManager.inFlightRefresh = new Map();
@@ -0,0 +1,5 @@
1
+ import { SSIStorage } from "./storage/SSIStorage";
2
+ declare global {
3
+ var __SSI_STATE_STORAGE__: SSIStorage | undefined;
4
+ }
5
+ export declare const stateStorage: SSIStorage;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stateStorage = void 0;
4
+ // packages/platform-integration/src/stateStore.ts
5
+ const SSIStorage_1 = require("./storage/SSIStorage");
6
+ // Reuse across hot-reloads in dev and across imports.
7
+ // Default state storage uses SSIStorage with 'state' class namespace
8
+ exports.stateStorage = globalThis.__SSI_STATE_STORAGE__ ??
9
+ (globalThis.__SSI_STATE_STORAGE__ = SSIStorage_1.SSIStorage.fromEnv().withClass('state'));
@@ -0,0 +1,24 @@
1
+ import type { KVValue, StorageInitOpts } from './types';
2
+ export declare class SSIStorage {
3
+ private backend;
4
+ private container;
5
+ private cls?;
6
+ private static __shared?;
7
+ private constructor();
8
+ static fromEnv(): SSIStorage;
9
+ static init(opts: StorageInitOpts): SSIStorage;
10
+ /**
11
+ * Returns a storage instance pinned to a class ("session", "nonce", etc.).
12
+ */
13
+ withClass(cls?: string): SSIStorage;
14
+ private composeKey;
15
+ get(objectId: string, key: string): Promise<KVValue | null>;
16
+ set(objectId: string, key: string, value: KVValue, ttlSeconds?: number): Promise<void>;
17
+ del(objectId: string, key: string): Promise<void>;
18
+ getRemainingTtl(objectId: string, key: string): Promise<number | null>;
19
+ /**
20
+ * List all keys under this container/class/objectId (if backend supports it).
21
+ */
22
+ keysForObject(objectId: string): Promise<string[]>;
23
+ close(): Promise<void>;
24
+ }
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SSIStorage = void 0;
4
+ const MemoryBackend_1 = require("./backends/MemoryBackend");
5
+ const RedisBackend_1 = require("./backends/RedisBackend");
6
+ const PostgresBackend_1 = require("./backends/PostgresBackend");
7
+ function envBool(v) {
8
+ if (v === undefined)
9
+ return undefined;
10
+ return ['1', 'true', 'yes', 'on'].includes(v.toLowerCase());
11
+ }
12
+ /**
13
+ * Creates a namespaced key according to the required format:
14
+ * container/[class]/[object_id]/[key]
15
+ */
16
+ function keyFor(container, cls, objectId, key) {
17
+ const safeClass = (cls && cls.length > 0) ? cls : '_';
18
+ return `${container}/${safeClass}/${objectId}/${key}`;
19
+ }
20
+ class SSIStorage {
21
+ constructor(backend, container, cls) {
22
+ this.backend = backend;
23
+ this.container = container;
24
+ this.cls = cls;
25
+ }
26
+ static fromEnv() {
27
+ if (SSIStorage.__shared)
28
+ return SSIStorage.__shared;
29
+ const backend = process.env.SSI_STORAGE_BACKEND || 'memory';
30
+ const container = process.env.SSI_STORAGE_CONTAINER || 'ssi_storage';
31
+ const host = process.env.SSI_STORAGE_HOST;
32
+ const user = process.env.SSI_STORAGE_USER;
33
+ const password = process.env.SSI_STORAGE_PASSWORD;
34
+ const port = process.env.SSI_STORAGE_PORT ? Number(process.env.SSI_STORAGE_PORT) : undefined;
35
+ const database = process.env.SSI_STORAGE_DATABASE;
36
+ const ssl = envBool(process.env.SSI_STORAGE_SSL);
37
+ const url = process.env.SSI_STORAGE_URL;
38
+ SSIStorage.__shared = SSIStorage.init({
39
+ backend, container, host, user, password, port, database, ssl, url,
40
+ });
41
+ return SSIStorage.__shared;
42
+ }
43
+ static init(opts) {
44
+ const backendType = opts.backend || 'memory';
45
+ const container = opts.container;
46
+ let backend;
47
+ if (backendType === 'memory') {
48
+ backend = new MemoryBackend_1.MemoryBackend();
49
+ }
50
+ else if (backendType === 'redis') {
51
+ backend = new RedisBackend_1.RedisBackend({
52
+ url: opts.url,
53
+ host: opts.host,
54
+ port: opts.port,
55
+ user: opts.user,
56
+ password: opts.password,
57
+ tls: opts.ssl,
58
+ });
59
+ }
60
+ else if (backendType === 'postgres') {
61
+ backend = new PostgresBackend_1.PostgresBackend({
62
+ url: opts.url,
63
+ host: opts.host,
64
+ port: opts.port,
65
+ user: opts.user,
66
+ password: opts.password,
67
+ database: opts.database,
68
+ ssl: opts.ssl,
69
+ table: container, // table name equals container
70
+ });
71
+ }
72
+ else {
73
+ throw new Error(`Unsupported backend: ${backendType}`);
74
+ }
75
+ return new SSIStorage(backend, container);
76
+ }
77
+ /**
78
+ * Returns a storage instance pinned to a class ("session", "nonce", etc.).
79
+ */
80
+ withClass(cls) {
81
+ return new SSIStorage(this.backend, this.container, cls);
82
+ // shares the same backend connection; creates a new namespacer
83
+ }
84
+ composeKey(objectId, key) {
85
+ return keyFor(this.container, this.cls, objectId, key);
86
+ }
87
+ async get(objectId, key) {
88
+ return this.backend.get(this.composeKey(objectId, key));
89
+ }
90
+ async set(objectId, key, value, ttlSeconds) {
91
+ await this.backend.set(this.composeKey(objectId, key), value, ttlSeconds);
92
+ }
93
+ async del(objectId, key) {
94
+ await this.backend.del(this.composeKey(objectId, key));
95
+ }
96
+ async getRemainingTtl(objectId, key) {
97
+ return this.backend.getRemainingTtl(this.composeKey(objectId, key));
98
+ }
99
+ /**
100
+ * List all keys under this container/class/objectId (if backend supports it).
101
+ */
102
+ async keysForObject(objectId) {
103
+ if (!this.backend.keys)
104
+ return [];
105
+ const prefix = `${this.container}/${this.cls ?? '_'}/${objectId}/`;
106
+ return this.backend.keys(prefix);
107
+ }
108
+ async close() {
109
+ if (this.backend.close)
110
+ await this.backend.close();
111
+ if (!this.cls) {
112
+ // Only clear singleton for the root instance
113
+ SSIStorage.__shared = undefined;
114
+ }
115
+ }
116
+ }
117
+ exports.SSIStorage = SSIStorage;
@@ -0,0 +1,10 @@
1
+ import type { Backend, KVValue } from '../types';
2
+ export declare class MemoryBackend implements Backend {
3
+ private cache;
4
+ constructor();
5
+ get(key: string): Promise<KVValue | null>;
6
+ set(key: string, value: KVValue, ttlSeconds?: number): Promise<void>;
7
+ del(key: string): Promise<void>;
8
+ keys(prefix: string): Promise<string[]>;
9
+ getRemainingTtl(key: string): Promise<number | null>;
10
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MemoryBackend = void 0;
7
+ const node_cache_1 = __importDefault(require("node-cache"));
8
+ class MemoryBackend {
9
+ constructor() {
10
+ // Share a single NodeCache across route bundles/process modules
11
+ const g = globalThis;
12
+ if (!g.__SSI_NODE_CACHE) {
13
+ // Checkperiod keeps stale entries tidy without extra timers
14
+ g.__SSI_NODE_CACHE = new node_cache_1.default({ stdTTL: 0, checkperiod: 120, useClones: false });
15
+ }
16
+ this.cache = g.__SSI_NODE_CACHE;
17
+ }
18
+ async get(key) {
19
+ const v = this.cache.get(key);
20
+ return typeof v === 'undefined' ? null : v;
21
+ }
22
+ async set(key, value, ttlSeconds) {
23
+ if (ttlSeconds && ttlSeconds > 0)
24
+ this.cache.set(key, value, ttlSeconds);
25
+ else
26
+ this.cache.set(key, value);
27
+ }
28
+ async del(key) {
29
+ this.cache.del(key);
30
+ }
31
+ async keys(prefix) {
32
+ return this.cache.keys().filter(k => k.startsWith(prefix));
33
+ }
34
+ async getRemainingTtl(key) {
35
+ const expiresAt = this.cache.getTtl(key);
36
+ if (typeof expiresAt === 'undefined' || expiresAt === 0)
37
+ return null;
38
+ const msRemaining = expiresAt - Date.now();
39
+ if (msRemaining <= 0)
40
+ return 0;
41
+ return Math.ceil(msRemaining / 1000);
42
+ }
43
+ }
44
+ exports.MemoryBackend = MemoryBackend;
@@ -0,0 +1,24 @@
1
+ import type { Backend, KVValue } from '../types';
2
+ type PgOpts = {
3
+ url?: string;
4
+ host?: string;
5
+ port?: number;
6
+ user?: string;
7
+ password?: string;
8
+ database?: string;
9
+ ssl?: boolean;
10
+ table: string;
11
+ };
12
+ export declare class PostgresBackend implements Backend {
13
+ private client;
14
+ private table;
15
+ constructor(opts: PgOpts);
16
+ private ensure;
17
+ private parsePrefixedKey;
18
+ get(prefixedKey: string): Promise<KVValue | null>;
19
+ set(prefixedKey: string, value: KVValue, ttlSeconds?: number): Promise<void>;
20
+ del(prefixedKey: string): Promise<void>;
21
+ close(): Promise<void>;
22
+ getRemainingTtl(prefixedKey: string): Promise<number | null>;
23
+ }
24
+ export {};