@frontmcp/auth 1.12.0-rc.1 → 1.12.1

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 (73) hide show
  1. package/authorities/authorities.context.d.ts +7 -0
  2. package/authorities/authorities.context.d.ts.map +1 -1
  3. package/authorities/authorities.engine.d.ts +10 -4
  4. package/authorities/authorities.engine.d.ts.map +1 -1
  5. package/authorities/authorities.evaluator.d.ts +2 -0
  6. package/authorities/authorities.evaluator.d.ts.map +1 -1
  7. package/authorities/authorities.validation.d.ts.map +1 -1
  8. package/authorization/dcr-client.registry.d.ts.map +1 -1
  9. package/authorization/public.authorization.d.ts +3 -2
  10. package/authorization/public.authorization.d.ts.map +1 -1
  11. package/browser/authorities/authorities.context.mjs +5 -0
  12. package/browser/authorities/authorities.engine.mjs +45 -9
  13. package/browser/authorities/authorities.evaluator.mjs +36 -10
  14. package/browser/authorities/authorities.validation.mjs +3 -1
  15. package/browser/authorization/authorization.class.mjs +3 -3
  16. package/browser/authorization/dcr-client.registry.mjs +8 -0
  17. package/browser/authorization/public.authorization.mjs +10 -4
  18. package/browser/cimd/cimd-redis.cache.mjs +2 -0
  19. package/browser/cimd/cimd.service.mjs +16 -7
  20. package/browser/cimd/cimd.validator.mjs +6 -2
  21. package/browser/detection/auth-provider-detection.mjs +15 -0
  22. package/browser/jwks/jwks.service.mjs +9 -9
  23. package/browser/people/loopback-token.auth.mjs +10 -3
  24. package/browser/session/authorization.store.mjs +56 -2
  25. package/browser/session/consent.store.mjs +2 -1
  26. package/browser/session/credentials-accessor.impl.mjs +4 -1
  27. package/browser/session/incremental-auth-ticket.mjs +2 -0
  28. package/browser/session/redis-session.store.mjs +6 -4
  29. package/browser/session/secure-store-backends.mjs +2 -1
  30. package/browser/session/secure-store.factory.mjs +7 -1
  31. package/browser/session/storage/storage-authorization-vault.mjs +8 -2
  32. package/browser/session/storage-authorization.store.mjs +49 -3
  33. package/browser/session/storage-consent.store.mjs +3 -1
  34. package/browser/session/token-storage.factory.mjs +5 -2
  35. package/browser/session/transport-session.types.mjs +6 -0
  36. package/browser/session/utils/session-crypto.utils.mjs +14 -1
  37. package/browser/session/vercel-kv-session.store.mjs +6 -4
  38. package/cimd/cimd-redis.cache.d.ts.map +1 -1
  39. package/cimd/cimd.service.d.ts +1 -0
  40. package/cimd/cimd.service.d.ts.map +1 -1
  41. package/cimd/cimd.validator.d.ts +2 -0
  42. package/cimd/cimd.validator.d.ts.map +1 -1
  43. package/detection/auth-provider-detection.d.ts.map +1 -1
  44. package/esm/index.mjs +5 -5
  45. package/esm/package.json +5 -5
  46. package/index.js +5 -5
  47. package/jwks/jwks.service.d.ts.map +1 -1
  48. package/package.json +5 -5
  49. package/people/loopback-token.auth.d.ts +5 -1
  50. package/people/loopback-token.auth.d.ts.map +1 -1
  51. package/session/authorization.store.d.ts +23 -0
  52. package/session/authorization.store.d.ts.map +1 -1
  53. package/session/consent.store.d.ts +3 -1
  54. package/session/consent.store.d.ts.map +1 -1
  55. package/session/credentials-accessor.impl.d.ts.map +1 -1
  56. package/session/incremental-auth-ticket.d.ts +4 -0
  57. package/session/incremental-auth-ticket.d.ts.map +1 -1
  58. package/session/redis-session.store.d.ts.map +1 -1
  59. package/session/secure-store-backends.d.ts +3 -4
  60. package/session/secure-store-backends.d.ts.map +1 -1
  61. package/session/secure-store.factory.d.ts +2 -1
  62. package/session/secure-store.factory.d.ts.map +1 -1
  63. package/session/storage/storage-authorization-vault.d.ts.map +1 -1
  64. package/session/storage-authorization.store.d.ts +8 -0
  65. package/session/storage-authorization.store.d.ts.map +1 -1
  66. package/session/storage-consent.store.d.ts.map +1 -1
  67. package/session/token-storage.factory.d.ts +2 -1
  68. package/session/token-storage.factory.d.ts.map +1 -1
  69. package/session/transport-session.types.d.ts +5 -0
  70. package/session/transport-session.types.d.ts.map +1 -1
  71. package/session/utils/session-crypto.utils.d.ts +2 -1
  72. package/session/utils/session-crypto.utils.d.ts.map +1 -1
  73. package/session/vercel-kv-session.store.d.ts.map +1 -1
@@ -1,6 +1,7 @@
1
1
  // libs/auth/src/session/authorization.store.ts
2
2
  import { internalZ as z } from "@frontmcp/lazy-zod";
3
3
  import { randomUUID, sha256Base64url } from "@frontmcp/utils";
4
+ import { AuthFlowError } from "../errors/auth-internal.errors.mjs";
4
5
  var pkceChallengeSchema = z.object({
5
6
  challenge: z.string().min(43).max(128),
6
7
  method: z.literal("S256")
@@ -116,6 +117,25 @@ function buildRefreshTokenRecord(params) {
116
117
  providerTokensId: params.providerTokensId
117
118
  };
118
119
  }
120
+ function isRedeemableCode(record) {
121
+ return !!record && !record.used && Date.now() <= record.expiresAt;
122
+ }
123
+ function isRotatableRefreshToken(record) {
124
+ return !!record && !record.revoked && Date.now() <= record.expiresAt;
125
+ }
126
+ function authorizationCodeRedeemedError() {
127
+ return new AuthFlowError(
128
+ "This authorization code was already redeemed by another token request, or it expired. Start a new authorization to get a new code.",
129
+ "AUTHORIZATION_CODE_REDEEMED"
130
+ );
131
+ }
132
+ function refreshTokenRedeemedError() {
133
+ return new AuthFlowError(
134
+ "This refresh token was already rotated by another token request, or it was revoked or expired. Sign in again to get a new refresh token.",
135
+ "REFRESH_TOKEN_REDEEMED"
136
+ );
137
+ }
138
+ var COMPARE_AND_DELETE_SCRIPT = `if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end return 0`;
119
139
  function verifyPkce(codeVerifier, challenge) {
120
140
  if (challenge.method !== "S256") {
121
141
  return false;
@@ -151,6 +171,7 @@ var InMemoryAuthorizationStore = class {
151
171
  }
152
172
  async markCodeUsed(code, issuedRefreshToken) {
153
173
  const record = this.codes.get(code);
174
+ if (issuedRefreshToken === void 0 && !isRedeemableCode(record)) throw authorizationCodeRedeemedError();
154
175
  if (record) {
155
176
  record.used = true;
156
177
  if (issuedRefreshToken) record.issuedRefreshToken = issuedRefreshToken;
@@ -192,7 +213,9 @@ var InMemoryAuthorizationStore = class {
192
213
  }
193
214
  }
194
215
  async rotateRefreshToken(oldToken, newRecord) {
195
- await this.revokeRefreshToken(oldToken);
216
+ const record = this.refreshTokens.get(oldToken);
217
+ if (!isRotatableRefreshToken(record)) throw refreshTokenRedeemedError();
218
+ record.revoked = true;
196
219
  newRecord.previousToken = oldToken;
197
220
  await this.storeRefreshToken(newRecord);
198
221
  }
@@ -268,6 +291,14 @@ var RedisAuthorizationStore = class {
268
291
  return JSON.parse(data);
269
292
  }
270
293
  async markCodeUsed(code, issuedRefreshToken) {
294
+ if (issuedRefreshToken === void 0) {
295
+ const claimed = await this.claim("code", code, isRedeemableCode, (record2) => ({
296
+ ...record2,
297
+ used: true
298
+ }));
299
+ if (!claimed) throw authorizationCodeRedeemedError();
300
+ return;
301
+ }
271
302
  const record = await this.getAuthorizationCode(code);
272
303
  if (record) {
273
304
  record.used = true;
@@ -311,12 +342,31 @@ var RedisAuthorizationStore = class {
311
342
  }
312
343
  }
313
344
  async rotateRefreshToken(oldToken, newRecord) {
314
- await this.revokeRefreshToken(oldToken);
345
+ const claimed = await this.claim("refresh", oldToken, isRotatableRefreshToken, (record) => ({
346
+ ...record,
347
+ revoked: true
348
+ }));
349
+ if (!claimed) throw refreshTokenRedeemedError();
315
350
  newRecord.previousToken = oldToken;
316
351
  await this.storeRefreshToken(newRecord);
317
352
  }
318
353
  async cleanup() {
319
354
  }
355
+ /**
356
+ * Replace the record under `type:id` with `update(record)` while `isLive(record)`. The compare-and-delete lets
357
+ * one request win: false when the record is missing or not live, or another request changed it first.
358
+ */
359
+ async claim(type, id, isLive, update) {
360
+ const key = this.key(type, id);
361
+ const raw = await this.redis.get(key);
362
+ if (!raw) return false;
363
+ const record = JSON.parse(raw);
364
+ if (!isLive(record)) return false;
365
+ if (await this.redis.eval(COMPARE_AND_DELETE_SCRIPT, 1, key, raw) !== 1) return false;
366
+ const ttl = Math.ceil((record.expiresAt - Date.now()) / 1e3);
367
+ await this.redis.set(key, JSON.stringify(update(record)), "EX", Math.max(ttl, 1));
368
+ return true;
369
+ }
320
370
  };
321
371
  export {
322
372
  AUTH_CODE_TTL_MS,
@@ -325,12 +375,16 @@ export {
325
375
  REFRESH_TOKEN_TTL_MS,
326
376
  RedisAuthorizationStore,
327
377
  authorizationCodeRecordSchema,
378
+ authorizationCodeRedeemedError,
328
379
  buildCodeRecord,
329
380
  buildPendingRecord,
330
381
  buildRefreshTokenRecord,
331
382
  generateAuthorizationCode,
332
383
  generatePkceChallenge,
333
384
  generateRefreshTokenValue,
385
+ isRedeemableCode,
386
+ isRotatableRefreshToken,
334
387
  pkceChallengeSchema,
388
+ refreshTokenRedeemedError,
335
389
  verifyPkce
336
390
  };
@@ -1,6 +1,7 @@
1
1
  // libs/auth/src/session/consent.store.ts
2
2
  function consentRecordKey(userSub, clientId) {
3
- return `consent:${userSub}:${clientId}`;
3
+ const escapedSub = userSub.replace(/%/g, "%25").replace(/:/g, "%3A");
4
+ return `consent:${escapedSub}:${clientId}`;
4
5
  }
5
6
  var InMemoryConsentStore = class {
6
7
  records = /* @__PURE__ */ new Map();
@@ -1,4 +1,5 @@
1
1
  // libs/auth/src/session/credentials-accessor.impl.ts
2
+ import { escapeControlCharacters } from "@frontmcp/utils";
2
3
  import { noopAuthLogger } from "../common/auth-logger.interface.mjs";
3
4
  import { buildCredentialResumeUrl, signCredentialResumeToken } from "./credential-resume-link.mjs";
4
5
  var CredentialsAccessorImpl = class {
@@ -44,7 +45,9 @@ var CredentialsAccessorImpl = class {
44
45
  }
45
46
  const token = signCredentialResumeToken({ sub, key, context, ttlMs: this.resumeTtlMs }, this.signingSecret);
46
47
  const resumeUrl = buildCredentialResumeUrl(this.basePath, token);
47
- this.logger.debug(`CredentialsAccessor: credential "${key}" not connected; issued resume link`);
48
+ this.logger.debug(
49
+ `CredentialsAccessor: credential "${escapeControlCharacters(key)}" not connected; issued resume link`
50
+ );
48
51
  return {
49
52
  connected: false,
50
53
  key,
@@ -23,6 +23,7 @@ function signIncrementalAuthTicket(input, secret) {
23
23
  name: input.name
24
24
  })
25
25
  } : {},
26
+ ...input.audience !== void 0 ? { aud: input.audience } : {},
26
27
  jti,
27
28
  exp: Date.now() + (input.ttlMs ?? DEFAULT_INCREMENTAL_TICKET_TTL_MS)
28
29
  };
@@ -44,6 +45,7 @@ function verifyIncrementalAuthTicket(token, secret, now = Date.now()) {
44
45
  if (!Number.isFinite(payload.exp)) return null;
45
46
  if (payload.toolId !== void 0 && typeof payload.toolId !== "string") return null;
46
47
  if (payload.profile !== void 0 && typeof payload.profile !== "string") return null;
48
+ if (payload.aud !== void 0 && typeof payload.aud !== "string") return null;
47
49
  if (payload.priorAppIds !== void 0 && (!Array.isArray(payload.priorAppIds) || payload.priorAppIds.some((id) => typeof id !== "string"))) {
48
50
  return null;
49
51
  }
@@ -1,9 +1,10 @@
1
1
  // libs/auth/src/session/redis-session.store.ts
2
2
  import { randomUUID, RedisStorageAdapter } from "@frontmcp/utils";
3
3
  import { SessionIdEmptyError } from "../errors/auth-internal.errors.mjs";
4
- import { signSession, verifyOrParseSession } from "./session-crypto.mjs";
4
+ import { signSession, verifySession } from "./session-crypto.mjs";
5
5
  import { SessionRateLimiter } from "./session-rate-limiter.mjs";
6
6
  import {
7
+ sessionMaxLifetimeAt,
7
8
  storedSessionSchema
8
9
  } from "./transport-session.types.mjs";
9
10
  var RedisSessionStore = class {
@@ -110,7 +111,7 @@ var RedisSessionStore = class {
110
111
  try {
111
112
  let parsed;
112
113
  if (this.security.enableSigning) {
113
- parsed = verifyOrParseSession(raw, { secret: this.security.signingSecret });
114
+ parsed = verifySession(raw, { secret: this.security.signingSecret });
114
115
  if (!parsed) {
115
116
  this.logger?.warn("[RedisSessionStore] Session signature verification failed", {
116
117
  sessionId: sessionId.slice(0, 20)
@@ -131,10 +132,11 @@ var RedisSessionStore = class {
131
132
  return null;
132
133
  }
133
134
  const session = result.data;
134
- if (session.maxLifetimeAt && session.maxLifetimeAt < Date.now()) {
135
+ const maxLifetimeAt = sessionMaxLifetimeAt(session, this.security.maxLifetimeMs);
136
+ if (maxLifetimeAt && maxLifetimeAt < Date.now()) {
135
137
  this.logger?.info("[RedisSessionStore] Session exceeded max lifetime", {
136
138
  sessionId: sessionId.slice(0, 20),
137
- maxLifetimeAt: session.maxLifetimeAt
139
+ maxLifetimeAt
138
140
  });
139
141
  await this.delete(sessionId);
140
142
  return null;
@@ -1,4 +1,5 @@
1
1
  // libs/auth/src/session/secure-store-backends.ts
2
+ import { base64urlEncode, randomBytes } from "@frontmcp/utils";
2
3
  import { noopAuthLogger } from "../common/auth-logger.interface.mjs";
3
4
  import { VaultEncryption } from "./vault-encryption.mjs";
4
5
  var DEFAULT_KEY_PREFIX = "mcp:secret:";
@@ -14,7 +15,7 @@ var EncryptedStorageSecureStoreBackend = class {
14
15
  this.keyPrefix = options.keyPrefix ?? DEFAULT_KEY_PREFIX;
15
16
  this.logger = options.logger ?? noopAuthLogger;
16
17
  this.encryption = new VaultEncryption({
17
- pepper: options.pepper,
18
+ pepper: options.pepper?.trim() ? options.pepper : base64urlEncode(randomBytes(32)),
18
19
  hkdfInfo: "frontmcp-secure-store-v1"
19
20
  });
20
21
  }
@@ -56,6 +56,12 @@ async function createSecureStore(options) {
56
56
  return { backend: config.backend, scope, ttlMs, kind: "custom" };
57
57
  }
58
58
  const kind = isSqliteConfig(config) ? "sqlite" : isRedisConfig(config) ? "redis" : "memory";
59
+ const effectivePepper = readPepper(config) ?? pepper;
60
+ if (kind !== "memory" && !effectivePepper?.trim()) {
61
+ throw new SecureStoreConfigError(
62
+ `the ${kind} backing encrypts the secrets with a pepper, and none was given. Set VAULT_SECRET or JWT_SECRET (or pass pepper to createSecureStore()), or set secureStore.encryption.pepper.`
63
+ );
64
+ }
59
65
  let adapter;
60
66
  if (storage) {
61
67
  adapter = storage;
@@ -67,7 +73,7 @@ async function createSecureStore(options) {
67
73
  }
68
74
  const backend = new EncryptedStorageSecureStoreBackend({
69
75
  storage: adapter,
70
- pepper: readPepper(config) ?? pepper,
76
+ pepper: effectivePepper,
71
77
  logger
72
78
  });
73
79
  return { backend, scope, ttlMs, kind, ...storage ? {} : { close: () => adapter.disconnect() } };
@@ -1,5 +1,9 @@
1
1
  // libs/auth/src/session/storage/storage-authorization-vault.ts
2
- import { randomUUID, TypedStorage } from "@frontmcp/utils";
2
+ import {
3
+ escapeControlCharacters,
4
+ randomUUID,
5
+ TypedStorage
6
+ } from "@frontmcp/utils";
3
7
  import {
4
8
  authorizationVaultEntrySchema
5
9
  } from "../authorization-vault.mjs";
@@ -45,7 +49,9 @@ var StorageAuthorizationVault = class {
45
49
  async update(id, updates) {
46
50
  const entry = await this.get(id);
47
51
  if (!entry) {
48
- console.warn(`[StorageAuthorizationVault] Update failed: vault entry not found for id ${id}`);
52
+ console.warn(
53
+ `[StorageAuthorizationVault] Update failed: vault entry not found for id ${escapeControlCharacters(id)}`
54
+ );
49
55
  return;
50
56
  }
51
57
  const updated = {
@@ -1,11 +1,15 @@
1
1
  // libs/auth/src/session/storage-authorization.store.ts
2
- import { TypedStorage } from "@frontmcp/utils";
2
+ import { StorageNotSupportedError, TypedStorage } from "@frontmcp/utils";
3
3
  import {
4
+ authorizationCodeRedeemedError,
4
5
  buildCodeRecord,
5
6
  buildPendingRecord,
6
7
  buildRefreshTokenRecord,
7
8
  generateAuthorizationCode,
8
- generateRefreshTokenValue
9
+ generateRefreshTokenValue,
10
+ isRedeemableCode,
11
+ isRotatableRefreshToken,
12
+ refreshTokenRedeemedError
9
13
  } from "./authorization.store.mjs";
10
14
  var StorageAuthorizationStore = class {
11
15
  codeStore;
@@ -13,10 +17,12 @@ var StorageAuthorizationStore = class {
13
17
  refreshStore;
14
18
  namespace;
15
19
  storageIsNamespaced;
20
+ base;
16
21
  constructor(storage, options = {}) {
17
22
  this.namespace = options.namespace ?? "oauth";
18
23
  this.storageIsNamespaced = this.isNamespacedStorage(storage);
19
24
  const base = this.storageIsNamespaced ? storage.namespace(this.namespace) : storage;
25
+ this.base = base;
20
26
  this.codeStore = new TypedStorage(base);
21
27
  this.pendingStore = new TypedStorage(base);
22
28
  this.refreshStore = new TypedStorage(base);
@@ -55,6 +61,14 @@ var StorageAuthorizationStore = class {
55
61
  return record;
56
62
  }
57
63
  async markCodeUsed(code, issuedRefreshToken) {
64
+ if (issuedRefreshToken === void 0) {
65
+ const claimed = await this.claim(this.codeStore, this.key("code", code), isRedeemableCode, (record2) => ({
66
+ ...record2,
67
+ used: true
68
+ }));
69
+ if (!claimed) throw authorizationCodeRedeemedError();
70
+ return;
71
+ }
58
72
  const record = await this.getAuthorizationCode(code);
59
73
  if (record) {
60
74
  record.used = true;
@@ -105,7 +119,16 @@ var StorageAuthorizationStore = class {
105
119
  }
106
120
  }
107
121
  async rotateRefreshToken(oldToken, newRecord) {
108
- await this.revokeRefreshToken(oldToken);
122
+ const claimed = await this.claim(
123
+ this.refreshStore,
124
+ this.key("refresh", oldToken),
125
+ isRotatableRefreshToken,
126
+ (record) => ({
127
+ ...record,
128
+ revoked: true
129
+ })
130
+ );
131
+ if (!claimed) throw refreshTokenRedeemedError();
109
132
  newRecord.previousToken = oldToken;
110
133
  await this.storeRefreshToken(newRecord);
111
134
  }
@@ -117,6 +140,29 @@ var StorageAuthorizationStore = class {
117
140
  // ============================================
118
141
  // Internals
119
142
  // ============================================
143
+ /**
144
+ * Replace the record under `key` with `update(record)` while `isLive(record)`. The compare-and-delete lets one
145
+ * request win: false when the record is missing or not live, or another request changed it first.
146
+ */
147
+ async claim(store, key, isLive, update) {
148
+ const raw = await this.base.get(key);
149
+ if (raw === null) return false;
150
+ const record = JSON.parse(raw);
151
+ if (!isLive(record) || !await this.deleteIfEquals(key, raw)) return false;
152
+ await store.set(key, update(record), this.ttl(record.expiresAt));
153
+ return true;
154
+ }
155
+ /** Delete `key` only while it holds `expectedValue`, on an adapter that can do that in one step. */
156
+ deleteIfEquals(key, expectedValue) {
157
+ if (!this.base.deleteIfEquals) {
158
+ throw new StorageNotSupportedError(
159
+ "deleteIfEquals",
160
+ "this storage adapter",
161
+ "Use the memory, Redis, Upstash, Vercel KV or SQLite adapter, so an authorization code or refresh token is redeemed once."
162
+ );
163
+ }
164
+ return this.base.deleteIfEquals(key, expectedValue);
165
+ }
120
166
  ttl(expiresAtMs) {
121
167
  const ttl = Math.ceil((expiresAtMs - Date.now()) / 1e3);
122
168
  return { ttlSeconds: ttl > 0 ? ttl : 1 };
@@ -12,7 +12,9 @@ var StorageConsentStore = class {
12
12
  this.storage = new TypedStorage(namespacedStorage);
13
13
  }
14
14
  async get(userSub, clientId) {
15
- return this.storage.get(this.key(userSub, clientId));
15
+ const record = await this.storage.get(this.key(userSub, clientId));
16
+ if (!record || record.userSub !== userSub || record.clientId !== clientId) return null;
17
+ return record;
16
18
  }
17
19
  async set(record) {
18
20
  await this.storage.set(this.key(record.userSub, record.clientId), record);
@@ -9,7 +9,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
9
9
  });
10
10
 
11
11
  // libs/auth/src/session/token-storage.factory.ts
12
- import { MemoryStorageAdapter, RedisStorageAdapter } from "@frontmcp/utils";
12
+ import { MemoryStorageAdapter, RedisStorageAdapter, requireOptionalModule } from "@frontmcp/utils";
13
13
  function isRedisTokenStorage(config) {
14
14
  return typeof config === "object" && config !== null && "redis" in config && !!config.redis;
15
15
  }
@@ -26,7 +26,10 @@ async function createTokenStorageAdapter(config) {
26
26
  return adapter;
27
27
  }
28
28
  if (isSqliteTokenStorage(config)) {
29
- const { SqliteStorageAdapter } = __require("@frontmcp/storage-sqlite");
29
+ const { SqliteStorageAdapter } = requireOptionalModule(
30
+ "@frontmcp/storage-sqlite",
31
+ () => __require("@frontmcp/storage-sqlite")
32
+ );
30
33
  const adapter = new SqliteStorageAdapter({
31
34
  path: config.sqlite.path,
32
35
  encryption: config.sqlite.encryption,
@@ -1,5 +1,10 @@
1
1
  // libs/auth/src/session/transport-session.types.ts
2
2
  import { internalZ as z } from "@frontmcp/lazy-zod";
3
+ function sessionMaxLifetimeAt(session, maxLifetimeMs) {
4
+ const configured = maxLifetimeMs && maxLifetimeMs > 0 ? session.createdAt + maxLifetimeMs : void 0;
5
+ const deadlines = [session.maxLifetimeAt, configured].filter((deadline) => !!deadline);
6
+ return deadlines.length > 0 ? Math.min(...deadlines) : void 0;
7
+ }
3
8
  var transportProtocolSchema = z.enum([
4
9
  "legacy-sse",
5
10
  "sse",
@@ -97,6 +102,7 @@ export {
97
102
  legacySseTransportStateSchema,
98
103
  redisConfigSchema,
99
104
  sessionJwtPayloadSchema,
105
+ sessionMaxLifetimeAt,
100
106
  sseTransportStateSchema,
101
107
  statefulHttpTransportStateSchema,
102
108
  statelessHttpTransportStateSchema,
@@ -1,5 +1,13 @@
1
1
  // libs/auth/src/session/utils/session-crypto.utils.ts
2
- import { decryptValue, encryptValue, getEnv, getMachineId, isProduction, sha256 } from "@frontmcp/utils";
2
+ import {
3
+ decryptValue,
4
+ encryptValue,
5
+ getEnv,
6
+ getMachineId,
7
+ getRuntimeContext,
8
+ isProduction,
9
+ sha256
10
+ } from "@frontmcp/utils";
3
11
  import { SessionSecretRequiredError } from "../../errors/auth-internal.errors.mjs";
4
12
  var cachedKey = null;
5
13
  function getKey() {
@@ -9,6 +17,11 @@ function getKey() {
9
17
  if (isProduction()) {
10
18
  throw new SessionSecretRequiredError("session ID encryption");
11
19
  }
20
+ if (getRuntimeContext().deployment === "distributed") {
21
+ throw new SessionSecretRequiredError(
22
+ "session ID encryption in a distributed deployment, whose responses carry the machine id: set MCP_SESSION_SECRET (openssl rand -hex 32)"
23
+ );
24
+ }
12
25
  console.warn(
13
26
  "[SessionIdUtils] Using machine ID as session encryption secret - NOT SECURE FOR PRODUCTION. Set MCP_SESSION_SECRET environment variable for secure session encryption."
14
27
  );
@@ -1,9 +1,10 @@
1
1
  // libs/auth/src/session/vercel-kv-session.store.ts
2
2
  import { randomUUID, VercelKvStorageAdapter } from "@frontmcp/utils";
3
3
  import { SessionIdEmptyError } from "../errors/auth-internal.errors.mjs";
4
- import { signSession, verifyOrParseSession } from "./session-crypto.mjs";
4
+ import { signSession, verifySession } from "./session-crypto.mjs";
5
5
  import { SessionRateLimiter } from "./session-rate-limiter.mjs";
6
6
  import {
7
+ sessionMaxLifetimeAt,
7
8
  storedSessionSchema
8
9
  } from "./transport-session.types.mjs";
9
10
  var VercelKvSessionStore = class {
@@ -92,7 +93,7 @@ var VercelKvSessionStore = class {
92
93
  try {
93
94
  let parsed;
94
95
  if (this.security.enableSigning) {
95
- parsed = verifyOrParseSession(raw, { secret: this.security.signingSecret });
96
+ parsed = verifySession(raw, { secret: this.security.signingSecret });
96
97
  if (!parsed) {
97
98
  this.logger?.warn("[VercelKvSessionStore] Session signature verification failed", {
98
99
  sessionId: sessionId.slice(0, 20)
@@ -113,10 +114,11 @@ var VercelKvSessionStore = class {
113
114
  return null;
114
115
  }
115
116
  const session = result.data;
116
- if (session.maxLifetimeAt && session.maxLifetimeAt < Date.now()) {
117
+ const maxLifetimeAt = sessionMaxLifetimeAt(session, this.security.maxLifetimeMs);
118
+ if (maxLifetimeAt && maxLifetimeAt < Date.now()) {
117
119
  this.logger?.info("[VercelKvSessionStore] Session exceeded max lifetime", {
118
120
  sessionId: sessionId.slice(0, 20),
119
- maxLifetimeAt: session.maxLifetimeAt
121
+ maxLifetimeAt
120
122
  });
121
123
  await this.delete(sessionId);
122
124
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"cimd-redis.cache.d.ts","sourceRoot":"","sources":["../../src/cimd/cimd-redis.cache.ts"],"names":[],"mappings":"AAQA,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAa5E;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,cAAe,YAAW,gBAAgB;IACrD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;gBAElC,MAAM,EAAE,eAAe;IA+BnC;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B;;;OAGG;IACH,OAAO,CAAC,QAAQ;IAKhB;;;;;OAKG;IACG,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IA0BhE;;;;;OAKG;IACG,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAerE;;;;;;OAMG;IACG,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,sBAAsB,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAsB9F;;;;;OAKG;IACG,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IA4BtE;;;;;OAKG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKhD;;;;;OAKG;IACG,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IA0B1F;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IAK7B;;;;;;;;OAQG;IACG,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC;IA2BhC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
1
+ {"version":3,"file":"cimd-redis.cache.d.ts","sourceRoot":"","sources":["../../src/cimd/cimd-redis.cache.ts"],"names":[],"mappings":"AAQA,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAa5E;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,cAAe,YAAW,gBAAgB;IACrD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;gBAElC,MAAM,EAAE,eAAe;IAiCnC;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B;;;OAGG;IACH,OAAO,CAAC,QAAQ;IAKhB;;;;;OAKG;IACG,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IA0BhE;;;;;OAKG;IACG,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAerE;;;;;;OAMG;IACG,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,sBAAsB,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAsB9F;;;;;OAKG;IACG,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IA4BtE;;;;;OAKG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKhD;;;;;OAKG;IACG,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IA0B1F;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IAK7B;;;;;;;;OAQG;IACG,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC;IA2BhC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
@@ -122,6 +122,7 @@ export declare class CimdService {
122
122
  *
123
123
  * @param clientId - The URL from which the document was fetched
124
124
  * @param document - The document to validate
125
+ * @returns The parsed document, with the schema's defaults for the fields it leaves out
125
126
  */
126
127
  private validateDocument;
127
128
  }
@@ -1 +1 @@
1
- {"version":3,"file":"cimd.service.d.ts","sourceRoot":"","sources":["../../src/cimd/cimd.service.ts"],"names":[],"mappings":"AAWA,OAAO,EAAyB,KAAK,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAY3F,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EASL,KAAK,eAAe,EAEpB,KAAK,oBAAoB,EAEzB,KAAK,sBAAsB,EAC5B,MAAM,cAAc,CAAC;AAmBtB;;;;GAIG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkB;IAC9C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IAClD,OAAO,CAAC,YAAY,CAAC,CAA4B;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;IAC3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;IAEpC;;OAEG;IACH,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;;;;OAKG;gBACS,MAAM,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC;IAyBlE;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC;;;OAGG;IACH,OAAO,CAAC,KAAK;IAUb,2FAA2F;IACrF,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ9B;;;;;OAKG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIzC;;;;;;;;OAQG;IACG,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAqD5E;;;;;;OAMG;IACH,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAUhF;;;;;;;;OAQG;IACH,qBAAqB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAmB7D;;;;;;;OAOG;IACG,qBAAqB,CAAC,QAAQ,EAAE,sBAAsB,EAAE,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAQzG;;;;;OAKG;YACW,UAAU;IA2BxB,mHAAmH;IACnH,OAAO,CAAC,iBAAiB;IAezB;;;;OAIG;IACG,UAAU,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWlD;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAOhD;;OAEG;YACW,qBAAqB;IAenC;;;;;OAKG;YACW,SAAS;IAqJvB;;OAEG;YACW,qBAAqB;IAyCnC;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;CAiBzB"}
1
+ {"version":3,"file":"cimd.service.d.ts","sourceRoot":"","sources":["../../src/cimd/cimd.service.ts"],"names":[],"mappings":"AAWA,OAAO,EAAyB,KAAK,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAY3F,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EASL,KAAK,eAAe,EAEpB,KAAK,oBAAoB,EAEzB,KAAK,sBAAsB,EAC5B,MAAM,cAAc,CAAC;AAyBtB;;;;GAIG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkB;IAC9C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IAClD,OAAO,CAAC,YAAY,CAAC,CAA4B;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;IAC3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;IAEpC;;OAEG;IACH,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;;;;OAKG;gBACS,MAAM,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC;IAyBlE;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC;;;OAGG;IACH,OAAO,CAAC,KAAK;IAUb,2FAA2F;IACrF,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ9B;;;;;OAKG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIzC;;;;;;;;OAQG;IACG,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAqD5E;;;;;;OAMG;IACH,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAUhF;;;;;;;;OAQG;IACH,qBAAqB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAmB7D;;;;;;;OAOG;IACG,qBAAqB,CAAC,QAAQ,EAAE,sBAAsB,EAAE,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAQzG;;;;;OAKG;YACW,UAAU;IA2BxB,mHAAmH;IACnH,OAAO,CAAC,iBAAiB;IAezB;;;;OAIG;IACG,UAAU,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWlD;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAOhD;;OAEG;YACW,qBAAqB;IAenC;;;;;OAKG;YACW,SAAS;IAsJvB;;OAEG;YACW,qBAAqB;IAyCnC;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;CAmBzB"}
@@ -11,6 +11,8 @@ import type { CimdSecurityConfig } from './cimd.types';
11
11
  * @returns true if this is a CIMD client_id
12
12
  */
13
13
  export declare function isCimdClientId(clientId: string, allowInsecure?: boolean): boolean;
14
+ /** Whether private addresses are refused for this host: with blockPrivateIPs, for every host but a localhost one under allowInsecureForTesting. */
15
+ export declare function blocksPrivateAddressesFor(hostname: string, securityConfig?: Partial<CimdSecurityConfig>): boolean;
14
16
  /**
15
17
  * Validate a client_id URL for CIMD usage.
16
18
  *
@@ -1 +1 @@
1
- {"version":3,"file":"cimd.validator.d.ts","sourceRoot":"","sources":["../../src/cimd/cimd.validator.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,UAAQ,GAAG,OAAO,CA4B/E;AAUD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,GAAG,CA6DvG;AAED;;GAEG;AACH,UAAU,eAAe;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAqBrE;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAgDxF;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKzF;AAyGD;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,CAgB5E"}
1
+ {"version":3,"file":"cimd.validator.d.ts","sourceRoot":"","sources":["../../src/cimd/cimd.validator.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,UAAQ,GAAG,OAAO,CA4B/E;AAUD,mJAAmJ;AACnJ,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,OAAO,CAGjH;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,GAAG,CA4DvG;AAED;;GAEG;AACH,UAAU,eAAe;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAqBrE;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAgDxF;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKzF;AAyGD;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,CAgB5E"}
@@ -1 +1 @@
1
- {"version":3,"file":"auth-provider-detection.d.ts","sourceRoot":"","sources":["../../src/detection/auth-provider-detection.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,SAAS,IAAI,CAAC,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAcrD,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;gCAOrC,CAAC;AAEH,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;;;;;;gCAQ5C,CAAC;AAMH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAC9E,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iCAAiC,CAAC,CAAC;AAE5F,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,wCAAwC;IACxC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAMD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAiB7D;AAyBD,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,WAAW,GAAG,SAAS,EACnC,IAAI,EAAE,WAAW,EAAE,GAClB,2BAA2B,CA+F7B;AA6BD,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,WAAW,GAAG,SAAS,EAChC,UAAU,EAAE,WAAW,GAAG,SAAS,GAClC,OAAO,CAaT;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,2BAA2B,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAGtG;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,2BAA2B,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAGpG"}
1
+ {"version":3,"file":"auth-provider-detection.d.ts","sourceRoot":"","sources":["../../src/detection/auth-provider-detection.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,SAAS,IAAI,CAAC,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAcrD,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;gCAOrC,CAAC;AAEH,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;;;;;;gCAQ5C,CAAC;AAMH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAC9E,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iCAAiC,CAAC,CAAC;AAE5F,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,wCAAwC;IACxC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAMD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAiB7D;AAyBD,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,WAAW,GAAG,SAAS,EACnC,IAAI,EAAE,WAAW,EAAE,GAClB,2BAA2B,CAyG7B;AAyCD,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,WAAW,GAAG,SAAS,EAChC,UAAU,EAAE,WAAW,GAAG,SAAS,GAClC,OAAO,CAaT;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,2BAA2B,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAGtG;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,2BAA2B,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAGpG"}