@frontmcp/auth 1.10.16 → 1.11.0-rc.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 (175) hide show
  1. package/browser/authorities/authorities.context.mjs +115 -0
  2. package/browser/authorities/authorities.engine.mjs +272 -0
  3. package/browser/authorities/authorities.errors.mjs +59 -0
  4. package/browser/authorities/authorities.evaluator.mjs +225 -0
  5. package/browser/authorities/authorities.metadata-augment.mjs +18 -0
  6. package/browser/authorities/authorities.registry.mjs +112 -0
  7. package/browser/authorities/authorities.schema.mjs +92 -0
  8. package/browser/authorities/authorities.scope-mapping.mjs +30 -0
  9. package/browser/authorities/authorities.validation.mjs +208 -0
  10. package/browser/authorities/index.mjs +43 -0
  11. package/browser/authorization/authorization.class.mjs +252 -0
  12. package/browser/authorization/authorization.types.mjs +64 -0
  13. package/browser/authorization/dcr-client.registry.mjs +222 -0
  14. package/browser/authorization/index.mjs +41 -0
  15. package/browser/authorization/orchestrated.accessor.mjs +86 -0
  16. package/browser/authorization/orchestrated.authorization.mjs +289 -0
  17. package/browser/authorization/public.authorization.mjs +124 -0
  18. package/browser/authorization/transparent.authorization.mjs +130 -0
  19. package/browser/cimd/cimd-redis.cache.mjs +231 -0
  20. package/browser/cimd/cimd.cache.mjs +215 -0
  21. package/browser/cimd/cimd.errors.mjs +142 -0
  22. package/browser/cimd/cimd.logger.mjs +15 -0
  23. package/browser/cimd/cimd.service.mjs +376 -0
  24. package/browser/cimd/cimd.types.mjs +228 -0
  25. package/browser/cimd/cimd.validator.mjs +217 -0
  26. package/browser/cimd/index.mjs +63 -0
  27. package/browser/common/auth-logger.interface.mjs +14 -0
  28. package/browser/common/index.mjs +6 -0
  29. package/browser/common/jwt.types.mjs +38 -0
  30. package/browser/common/session-user.types.mjs +0 -0
  31. package/browser/common/session.types.mjs +58 -0
  32. package/browser/common/zod-utils.mjs +0 -0
  33. package/browser/consent/consent.types.mjs +94 -0
  34. package/browser/consent/index.mjs +17 -0
  35. package/browser/context/frontmcp-auth-context.factory.mjs +29 -0
  36. package/browser/context/frontmcp-auth-context.impl.mjs +112 -0
  37. package/browser/context/index.mjs +7 -0
  38. package/browser/detection/auth-provider-detection.mjs +176 -0
  39. package/browser/detection/index.mjs +19 -0
  40. package/browser/errors/auth-internal.error.mjs +42 -0
  41. package/browser/errors/auth-internal.errors.mjs +150 -0
  42. package/browser/errors/index.mjs +53 -0
  43. package/browser/fetch/fetch-credential-middleware.mjs +91 -0
  44. package/browser/fetch/index.mjs +19 -0
  45. package/browser/index.mjs +573 -0
  46. package/browser/jwks/index.mjs +9 -0
  47. package/browser/jwks/jose.loader.mjs +28 -0
  48. package/browser/jwks/jwks.service.mjs +521 -0
  49. package/browser/jwks/jwks.utils.mjs +33 -0
  50. package/browser/options/app-auth.schema.mjs +18 -0
  51. package/browser/options/index.mjs +89 -0
  52. package/browser/options/orchestrated.schema.mjs +128 -0
  53. package/browser/options/public.schema.mjs +39 -0
  54. package/browser/options/schema.mjs +16 -0
  55. package/browser/options/shared.schemas.mjs +506 -0
  56. package/browser/options/static.schema.mjs +41 -0
  57. package/browser/options/transparent.schema.mjs +43 -0
  58. package/browser/options/utils.mjs +47 -0
  59. package/browser/people/index.mjs +15 -0
  60. package/browser/people/loopback-token.auth.mjs +115 -0
  61. package/browser/people/people-auth.options.mjs +37 -0
  62. package/browser/session/authorization-vault.mjs +279 -0
  63. package/browser/session/authorization.store.mjs +336 -0
  64. package/browser/session/consent.store.mjs +34 -0
  65. package/browser/session/credential-resume-link.mjs +43 -0
  66. package/browser/session/credentials-accessor.impl.mjs +54 -0
  67. package/browser/session/credentials-accessor.mjs +5 -0
  68. package/browser/session/encrypted-authorization-vault.mjs +405 -0
  69. package/browser/session/federated-auth.session.mjs +176 -0
  70. package/browser/session/incremental-auth-ticket.mjs +42 -0
  71. package/browser/session/index.mjs +266 -0
  72. package/browser/session/orchestrated-token.crypto.mjs +47 -0
  73. package/browser/session/orchestrated-token.store.mjs +286 -0
  74. package/browser/session/pending-login.mjs +37 -0
  75. package/browser/session/redis-session.store.mjs +255 -0
  76. package/browser/session/secure-store-accessor.impl.mjs +78 -0
  77. package/browser/session/secure-store-accessor.mjs +5 -0
  78. package/browser/session/secure-store-backends.mjs +111 -0
  79. package/browser/session/secure-store.factory.mjs +77 -0
  80. package/browser/session/session-credential-vault.factory.mjs +18 -0
  81. package/browser/session/session-credential-vault.mjs +243 -0
  82. package/browser/session/session-crypto.mjs +39 -0
  83. package/browser/session/session-rate-limiter.mjs +126 -0
  84. package/browser/session/session.transport.mjs +16 -0
  85. package/browser/session/signin-binding.mjs +30 -0
  86. package/browser/session/storage/in-memory-authorization-vault.mjs +28 -0
  87. package/browser/session/storage/index.mjs +14 -0
  88. package/browser/session/storage/storage-authorization-vault.mjs +301 -0
  89. package/browser/session/storage/storage-token-store.mjs +83 -0
  90. package/browser/session/storage-authorization.store.mjs +134 -0
  91. package/browser/session/storage-consent.store.mjs +33 -0
  92. package/browser/session/storage-federated-auth.session.mjs +55 -0
  93. package/browser/session/storage-orchestrated-token.store.mjs +197 -0
  94. package/browser/session/token-storage.factory.mjs +58 -0
  95. package/browser/session/token.refresh.mjs +36 -0
  96. package/browser/session/token.vault.mjs +61 -0
  97. package/browser/session/transport-session.types.mjs +108 -0
  98. package/browser/session/utils/auth-token.utils.mjs +58 -0
  99. package/browser/session/utils/index.mjs +23 -0
  100. package/browser/session/utils/session-crypto.utils.mjs +49 -0
  101. package/browser/session/utils/tiny-ttl-cache.mjs +46 -0
  102. package/browser/session/vault-encryption.mjs +187 -0
  103. package/browser/session/vercel-kv-session.store.mjs +230 -0
  104. package/browser/shared-server/index.mjs +17 -0
  105. package/browser/shared-server/shared-server-identity.mjs +55 -0
  106. package/browser/ui/base-layout.mjs +184 -0
  107. package/browser/ui/index.mjs +51 -0
  108. package/browser/ui/local-login.helper.mjs +46 -0
  109. package/browser/ui/templates.mjs +482 -0
  110. package/browser/utils/audience.validator.mjs +110 -0
  111. package/browser/utils/authorization-id.utils.mjs +10 -0
  112. package/browser/utils/index.mjs +45 -0
  113. package/browser/utils/redirect-uri.utils.mjs +22 -0
  114. package/browser/utils/scope-grant.utils.mjs +36 -0
  115. package/browser/utils/token-scopes.utils.mjs +12 -0
  116. package/browser/utils/www-authenticate.utils.mjs +164 -0
  117. package/browser/vault/auth-providers.accessor.impl.mjs +236 -0
  118. package/browser/vault/auth-providers.accessor.mjs +5 -0
  119. package/browser/vault/auth-providers.registry.mjs +140 -0
  120. package/browser/vault/auth-providers.types.mjs +47 -0
  121. package/browser/vault/auth-providers.vault.mjs +206 -0
  122. package/browser/vault/credential-cache.mjs +187 -0
  123. package/browser/vault/credential-helpers.mjs +15 -0
  124. package/browser/vault/credential-loaders/eager-loader.mjs +91 -0
  125. package/browser/vault/credential-loaders/lazy-loader.mjs +120 -0
  126. package/browser/vault/index.mjs +35 -0
  127. package/cimd/cimd.types.d.ts +1 -1
  128. package/cimd/cimd.types.d.ts.map +1 -1
  129. package/common/jwt.types.d.ts +1 -1
  130. package/common/jwt.types.d.ts.map +1 -1
  131. package/common/session.types.d.ts +9 -1
  132. package/common/session.types.d.ts.map +1 -1
  133. package/consent/consent.types.d.ts +1 -1
  134. package/consent/consent.types.d.ts.map +1 -1
  135. package/detection/auth-provider-detection.d.ts +1 -1
  136. package/detection/auth-provider-detection.d.ts.map +1 -1
  137. package/errors/auth-internal.errors.d.ts +6 -0
  138. package/errors/auth-internal.errors.d.ts.map +1 -1
  139. package/errors/index.d.ts +1 -1
  140. package/errors/index.d.ts.map +1 -1
  141. package/esm/index.mjs +2 -2
  142. package/esm/package.json +21 -5
  143. package/index.d.ts +5 -1
  144. package/index.d.ts.map +1 -1
  145. package/index.js +2 -2
  146. package/options/app-auth.schema.d.ts +19 -1
  147. package/options/app-auth.schema.d.ts.map +1 -1
  148. package/options/orchestrated.schema.d.ts +37 -1
  149. package/options/orchestrated.schema.d.ts.map +1 -1
  150. package/options/public.schema.d.ts +1 -1
  151. package/options/public.schema.d.ts.map +1 -1
  152. package/options/schema.d.ts +19 -1
  153. package/options/schema.d.ts.map +1 -1
  154. package/options/shared.schemas.d.ts +10 -14
  155. package/options/shared.schemas.d.ts.map +1 -1
  156. package/options/static.schema.d.ts +1 -1
  157. package/options/static.schema.d.ts.map +1 -1
  158. package/options/transparent.schema.d.ts +1 -1
  159. package/options/transparent.schema.d.ts.map +1 -1
  160. package/package.json +21 -5
  161. package/people/index.d.ts +3 -0
  162. package/people/index.d.ts.map +1 -0
  163. package/people/loopback-token.auth.d.ts +75 -0
  164. package/people/loopback-token.auth.d.ts.map +1 -0
  165. package/people/people-auth.options.d.ts +36 -0
  166. package/people/people-auth.options.d.ts.map +1 -0
  167. package/session/authorization-vault.d.ts +1 -1
  168. package/session/authorization-vault.d.ts.map +1 -1
  169. package/session/encrypted-authorization-vault.d.ts +1 -1
  170. package/session/encrypted-authorization-vault.d.ts.map +1 -1
  171. package/session/secure-store.factory.d.ts.map +1 -1
  172. package/shared-server/index.d.ts +3 -0
  173. package/shared-server/index.d.ts.map +1 -0
  174. package/shared-server/shared-server-identity.d.ts +26 -0
  175. package/shared-server/shared-server-identity.d.ts.map +1 -0
@@ -0,0 +1,243 @@
1
+ // libs/auth/src/session/session-credential-vault.ts
2
+ import { getEnv, randomUUID } from "@frontmcp/utils";
3
+ import { noopAuthLogger } from "../common/auth-logger.interface.mjs";
4
+ import { VaultEncryption } from "./vault-encryption.mjs";
5
+ var DEFAULT_KEY_PREFIX = "mcp:cred:";
6
+ var DEFAULT_PENDING_TTL_MS = 6e5;
7
+ var SessionCredentialVault = class {
8
+ storage;
9
+ encryption;
10
+ keyPrefix;
11
+ ttlMs;
12
+ pendingTtlMs;
13
+ logger;
14
+ /** Cache of derived AES keys, keyed by `${sub}:${vaultId}`. */
15
+ keyCache = /* @__PURE__ */ new Map();
16
+ constructor(options) {
17
+ this.storage = options.storage;
18
+ this.keyPrefix = options.keyPrefix ?? DEFAULT_KEY_PREFIX;
19
+ this.ttlMs = options.ttlMs;
20
+ this.pendingTtlMs = options.pendingTtlMs ?? DEFAULT_PENDING_TTL_MS;
21
+ this.logger = options.logger ?? noopAuthLogger;
22
+ const pepper = options.pepper ?? resolveVaultPepper(this.logger);
23
+ this.encryption = new VaultEncryption({ pepper, hkdfInfo: "frontmcp-session-credential-vault-v1" });
24
+ }
25
+ // ============================================
26
+ // Storage-key helpers
27
+ // ============================================
28
+ /** `cred:idx:<sub>` \u2192 current vaultId (the sub \u2192 vaultId indirection). */
29
+ indexKey(sub) {
30
+ return `${this.keyPrefix}idx:${sub}`;
31
+ }
32
+ /** `cred:keys:<sub>:<vaultId>` \u2192 JSON array of credential key names (the SET). */
33
+ keysKey(sub, vaultId) {
34
+ return `${this.keyPrefix}keys:${sub}:${vaultId}`;
35
+ }
36
+ /** `cred:data:<sub>:<vaultId>:<key>` \u2192 encrypted credential record. */
37
+ dataKey(sub, vaultId, key) {
38
+ return `${this.keyPrefix}data:${sub}:${vaultId}:${key}`;
39
+ }
40
+ /** `cred:pending:<pendingId>` \u2192 encrypted pending-authorize accumulator. */
41
+ pendingKey(pendingId) {
42
+ return `${this.keyPrefix}pending:${pendingId}`;
43
+ }
44
+ /** Convert the configured `ttlMs` into `SetOptions` (storage uses `ttlSeconds`). */
45
+ get credentialSetOpts() {
46
+ return this.ttlMs ? { ttlSeconds: Math.ceil(this.ttlMs / 1e3) } : void 0;
47
+ }
48
+ // ============================================
49
+ // Key derivation
50
+ // ============================================
51
+ /**
52
+ * Derive (and cache) the AES-256 key for a `(sub, vaultId)` pair. The vaultId
53
+ * acts as the HKDF salt, so rotating it makes prior ciphertext undecryptable.
54
+ */
55
+ deriveKey(sub, vaultId) {
56
+ const cacheKey = `${sub}:${vaultId}`;
57
+ const cached = this.keyCache.get(cacheKey);
58
+ if (cached) return cached;
59
+ const promise = this.encryption.deriveKey({ jti: vaultId, vaultKey: vaultId, sub, iat: 0 });
60
+ this.keyCache.set(cacheKey, promise);
61
+ return promise;
62
+ }
63
+ // ============================================
64
+ // vaultId indirection
65
+ // ============================================
66
+ /**
67
+ * Read the current vaultId for a subject, or undefined when the subject has no
68
+ * live vault (never authorized, or rotated away).
69
+ */
70
+ async getVaultId(sub) {
71
+ const raw = await this.storage.get(this.indexKey(sub));
72
+ return raw ?? void 0;
73
+ }
74
+ /**
75
+ * Mint a FRESH vaultId for a subject and point the indirection at it. Called
76
+ * at the start of each authorize so reconnect = empty vault. Returns the new
77
+ * vaultId. The old vaultId's ciphertext is left in storage but is
78
+ * unreachable (no index pointer) and undecryptable (different derived key);
79
+ * it expires via TTL when configured.
80
+ */
81
+ async rotateVault(sub) {
82
+ const vaultId = randomUUID();
83
+ await this.storage.set(this.indexKey(sub), vaultId, this.credentialSetOpts);
84
+ return vaultId;
85
+ }
86
+ // ============================================
87
+ // Credential CRUD (committed vault)
88
+ // ============================================
89
+ /**
90
+ * Encrypt + store a single credential into the vault identified by
91
+ * `(sub, vaultId)` and add its key to the key set. Used both to commit
92
+ * authorize-time credentials and to additively add a credential mid-session.
93
+ */
94
+ async store(sub, vaultId, key, value) {
95
+ const aesKey = await this.deriveKey(sub, vaultId);
96
+ const enc = await this.encryption.encryptObject(this.normalize(value), aesKey);
97
+ const record = { enc };
98
+ await this.storage.set(this.dataKey(sub, vaultId, key), JSON.stringify(record), this.credentialSetOpts);
99
+ await this.addKeyToSet(sub, vaultId, key);
100
+ }
101
+ /**
102
+ * Decrypt and return a credential by key for a subject's CURRENT vault.
103
+ * Returns undefined when there is no live vault or no such key, or when
104
+ * decryption fails (e.g. a stale pointer to rotated-away ciphertext).
105
+ */
106
+ async get(sub, key) {
107
+ const vaultId = await this.getVaultId(sub);
108
+ if (!vaultId) return void 0;
109
+ const raw = await this.storage.get(this.dataKey(sub, vaultId, key));
110
+ if (!raw) return void 0;
111
+ try {
112
+ const record = JSON.parse(raw);
113
+ const aesKey = await this.deriveKey(sub, vaultId);
114
+ return await this.encryption.decryptObject(record.enc, aesKey);
115
+ } catch (err) {
116
+ this.logger.warn(`SessionCredentialVault: failed to read credential "${key}": ${errMsg(err)}`);
117
+ return void 0;
118
+ }
119
+ }
120
+ /**
121
+ * List the credential keys available in a subject's current vault. Returns an
122
+ * empty array when there is no live vault (e.g. after rotation on reconnect).
123
+ */
124
+ async list(sub) {
125
+ const vaultId = await this.getVaultId(sub);
126
+ if (!vaultId) return [];
127
+ return this.readKeySet(sub, vaultId);
128
+ }
129
+ /**
130
+ * Remove a credential by key from a subject's current vault (deletes the
131
+ * ciphertext and drops the key from the set). No-op when absent.
132
+ */
133
+ async remove(sub, key) {
134
+ const vaultId = await this.getVaultId(sub);
135
+ if (!vaultId) return;
136
+ await this.storage.delete(this.dataKey(sub, vaultId, key));
137
+ const keys = await this.readKeySet(sub, vaultId);
138
+ const next = keys.filter((k) => k !== key);
139
+ if (next.length !== keys.length) {
140
+ await this.writeKeySet(sub, vaultId, next);
141
+ }
142
+ }
143
+ // ============================================
144
+ // Authorize-time accumulation + commit
145
+ // ============================================
146
+ /**
147
+ * Stage credentials returned by `authenticate()` under a pending id, encrypted
148
+ * against a freshly-rotated vaultId for `sub`. They are NOT visible to tools
149
+ * until {@link commit} flushes them \u2014 this lets the authorize flow accumulate
150
+ * credentials before the subject's identity is final.
151
+ *
152
+ * @returns the vaultId the credentials were staged against.
153
+ */
154
+ async stage(pendingId, sub, credentials) {
155
+ const vaultId = await this.rotateVault(sub);
156
+ const creds = {};
157
+ for (const cred of credentials) {
158
+ const aesKey = await this.deriveKey(sub, vaultId);
159
+ const { key, ...value } = cred;
160
+ creds[key] = { enc: await this.encryption.encryptObject(this.normalize(value), aesKey) };
161
+ }
162
+ const batch = { sub, vaultId, creds };
163
+ await this.storage.set(this.pendingKey(pendingId), JSON.stringify(batch), {
164
+ ttlSeconds: Math.ceil(this.pendingTtlMs / 1e3)
165
+ });
166
+ return vaultId;
167
+ }
168
+ /**
169
+ * Flush a staged batch into the live vault, binding it to `sub`. The staged
170
+ * batch's `sub` must equal the supplied `sub` (the one baked into the minted
171
+ * token) \u2014 a mismatch is refused so credentials can never be committed under a
172
+ * different identity than the one that will read them.
173
+ *
174
+ * @returns true when a batch was committed, false when none was staged.
175
+ */
176
+ async commit(pendingId, sub) {
177
+ const raw = await this.storage.get(this.pendingKey(pendingId));
178
+ if (!raw) return false;
179
+ let batch;
180
+ try {
181
+ batch = JSON.parse(raw);
182
+ } catch {
183
+ await this.storage.delete(this.pendingKey(pendingId));
184
+ return false;
185
+ }
186
+ if (batch.sub !== sub) {
187
+ this.logger.warn("SessionCredentialVault: pending batch subject mismatch on commit; discarding");
188
+ await this.storage.delete(this.pendingKey(pendingId));
189
+ return false;
190
+ }
191
+ await this.storage.set(this.indexKey(sub), batch.vaultId, this.credentialSetOpts);
192
+ const keys = [];
193
+ for (const [key, record] of Object.entries(batch.creds)) {
194
+ await this.storage.set(this.dataKey(sub, batch.vaultId, key), JSON.stringify(record), this.credentialSetOpts);
195
+ keys.push(key);
196
+ }
197
+ await this.writeKeySet(sub, batch.vaultId, keys);
198
+ await this.storage.delete(this.pendingKey(pendingId));
199
+ return true;
200
+ }
201
+ // ============================================
202
+ // Key-set helpers
203
+ // ============================================
204
+ async readKeySet(sub, vaultId) {
205
+ const raw = await this.storage.get(this.keysKey(sub, vaultId));
206
+ if (!raw) return [];
207
+ try {
208
+ const parsed = JSON.parse(raw);
209
+ return Array.isArray(parsed) ? parsed.filter((k) => typeof k === "string") : [];
210
+ } catch {
211
+ return [];
212
+ }
213
+ }
214
+ async writeKeySet(sub, vaultId, keys) {
215
+ const unique = [...new Set(keys)];
216
+ await this.storage.set(this.keysKey(sub, vaultId), JSON.stringify(unique), this.credentialSetOpts);
217
+ }
218
+ async addKeyToSet(sub, vaultId, key) {
219
+ const keys = await this.readKeySet(sub, vaultId);
220
+ if (!keys.includes(key)) {
221
+ keys.push(key);
222
+ await this.writeKeySet(sub, vaultId, keys);
223
+ }
224
+ }
225
+ /** Strip undefined metadata so the encrypted JSON stays minimal. */
226
+ normalize(value) {
227
+ return value.metadata === void 0 ? { secret: value.secret } : { secret: value.secret, metadata: value.metadata };
228
+ }
229
+ };
230
+ function resolveVaultPepper(logger) {
231
+ const fromEnv = getEnv("VAULT_SECRET") ?? getEnv("JWT_SECRET");
232
+ if (fromEnv && fromEnv.trim().length > 0) return fromEnv;
233
+ logger.warn(
234
+ "SessionCredentialVault: neither VAULT_SECRET nor JWT_SECRET is set; using a random per-process pepper. Stored credentials will NOT survive a restart and cannot be shared across instances. Set VAULT_SECRET (or JWT_SECRET) for persistent, multi-instance credential storage."
235
+ );
236
+ return randomUUID() + randomUUID();
237
+ }
238
+ function errMsg(err) {
239
+ return err instanceof Error ? err.message : String(err);
240
+ }
241
+ export {
242
+ SessionCredentialVault
243
+ };
@@ -0,0 +1,39 @@
1
+ // libs/auth/src/session/session-crypto.ts
2
+ import {
3
+ signData,
4
+ verifyData,
5
+ isSignedData,
6
+ verifyOrParseData,
7
+ getEnv,
8
+ isProduction
9
+ } from "@frontmcp/utils";
10
+ import { SessionSecretRequiredError } from "../errors/auth-internal.errors.mjs";
11
+ function getSigningSecret(config) {
12
+ const secret = config?.secret || getEnv("MCP_SESSION_SECRET");
13
+ if (!secret) {
14
+ if (isProduction()) {
15
+ throw new SessionSecretRequiredError("session signing");
16
+ }
17
+ console.warn("[SessionCrypto] MCP_SESSION_SECRET not set. Using insecure default for development only.");
18
+ return "insecure-dev-secret-do-not-use-in-production";
19
+ }
20
+ return secret;
21
+ }
22
+ function resolveConfig(config) {
23
+ return { secret: getSigningSecret(config) };
24
+ }
25
+ function signSession(session, config) {
26
+ return signData(session, resolveConfig(config));
27
+ }
28
+ function verifySession(signedData, config) {
29
+ return verifyData(signedData, resolveConfig(config));
30
+ }
31
+ function verifyOrParseSession(data, config) {
32
+ return verifyOrParseData(data, resolveConfig(config));
33
+ }
34
+ export {
35
+ isSignedData as isSignedSession,
36
+ signSession,
37
+ verifyOrParseSession,
38
+ verifySession
39
+ };
@@ -0,0 +1,126 @@
1
+ // libs/auth/src/session/session-rate-limiter.ts
2
+ var SessionRateLimiter = class {
3
+ windowMs;
4
+ maxRequests;
5
+ requests = /* @__PURE__ */ new Map();
6
+ cleanupIntervalMs;
7
+ cleanupTimer = null;
8
+ constructor(config = {}) {
9
+ this.windowMs = config.windowMs ?? 6e4;
10
+ this.maxRequests = config.maxRequests ?? 100;
11
+ this.cleanupIntervalMs = config.cleanupIntervalMs ?? 6e4;
12
+ }
13
+ /**
14
+ * Start the periodic cleanup timer on first use (idempotent). Deferred out of
15
+ * the constructor so constructing an instance at module scope has no side
16
+ * effects \u2014 see the constructor note on V8-isolate global-scope restrictions.
17
+ */
18
+ ensureCleanupTimer() {
19
+ if (this.cleanupTimer || this.cleanupIntervalMs <= 0) return;
20
+ this.cleanupTimer = setInterval(() => this.cleanup(), this.cleanupIntervalMs);
21
+ if (typeof this.cleanupTimer.unref === "function") this.cleanupTimer.unref();
22
+ }
23
+ /**
24
+ * Check if a request is allowed for the given key.
25
+ *
26
+ * @param key - Identifier for rate limiting (e.g., client IP, session ID prefix)
27
+ * @returns Rate limit result with allowed status and metadata
28
+ */
29
+ check(key) {
30
+ this.ensureCleanupTimer();
31
+ const now = Date.now();
32
+ const windowStart = now - this.windowMs;
33
+ let timestamps = this.requests.get(key);
34
+ if (!timestamps) {
35
+ timestamps = [];
36
+ this.requests.set(key, timestamps);
37
+ }
38
+ const validTimestamps = timestamps.filter((t) => t > windowStart);
39
+ const oldestInWindow = validTimestamps[0] ?? now;
40
+ const resetAt = oldestInWindow + this.windowMs;
41
+ if (validTimestamps.length >= this.maxRequests) {
42
+ return {
43
+ allowed: false,
44
+ remaining: 0,
45
+ resetAt,
46
+ retryAfterMs: resetAt - now
47
+ };
48
+ }
49
+ validTimestamps.push(now);
50
+ this.requests.set(key, validTimestamps);
51
+ return {
52
+ allowed: true,
53
+ remaining: this.maxRequests - validTimestamps.length,
54
+ resetAt
55
+ };
56
+ }
57
+ /**
58
+ * Check if a request would be allowed without recording it.
59
+ * Useful for pre-checking without consuming quota.
60
+ *
61
+ * @param key - Identifier for rate limiting
62
+ * @returns true if request would be allowed
63
+ */
64
+ wouldAllow(key) {
65
+ const now = Date.now();
66
+ const windowStart = now - this.windowMs;
67
+ const timestamps = this.requests.get(key);
68
+ if (!timestamps) return true;
69
+ const validCount = timestamps.filter((t) => t > windowStart).length;
70
+ return validCount < this.maxRequests;
71
+ }
72
+ /**
73
+ * Reset rate limit for a specific key.
74
+ * Useful for testing or after successful authentication.
75
+ *
76
+ * @param key - Identifier to reset
77
+ */
78
+ reset(key) {
79
+ this.requests.delete(key);
80
+ }
81
+ /**
82
+ * Clean up expired entries from all keys.
83
+ * Called automatically on configured interval, but can be called manually.
84
+ */
85
+ cleanup() {
86
+ const now = Date.now();
87
+ const windowStart = now - this.windowMs;
88
+ for (const [key, timestamps] of this.requests.entries()) {
89
+ const valid = timestamps.filter((t) => t > windowStart);
90
+ if (valid.length === 0) {
91
+ this.requests.delete(key);
92
+ } else if (valid.length < timestamps.length) {
93
+ this.requests.set(key, valid);
94
+ }
95
+ }
96
+ }
97
+ /**
98
+ * Get current statistics for monitoring.
99
+ */
100
+ getStats() {
101
+ let totalRequests = 0;
102
+ for (const timestamps of this.requests.values()) {
103
+ totalRequests += timestamps.length;
104
+ }
105
+ return {
106
+ totalKeys: this.requests.size,
107
+ totalRequests
108
+ };
109
+ }
110
+ /**
111
+ * Stop the automatic cleanup timer.
112
+ * Call this when disposing of the rate limiter.
113
+ */
114
+ dispose() {
115
+ if (this.cleanupTimer) {
116
+ clearInterval(this.cleanupTimer);
117
+ this.cleanupTimer = null;
118
+ }
119
+ this.requests.clear();
120
+ }
121
+ };
122
+ var defaultSessionRateLimiter = new SessionRateLimiter();
123
+ export {
124
+ SessionRateLimiter,
125
+ defaultSessionRateLimiter
126
+ };
@@ -0,0 +1,16 @@
1
+ // libs/auth/src/session/session.transport.ts
2
+ import { randomUUID } from "@frontmcp/utils";
3
+ var TransportIdGenerator = class {
4
+ /**
5
+ * Create a transport session ID.
6
+ * Generates JWT-style IDs for distributed session support.
7
+ *
8
+ * @returns A transport session ID (UUID without dashes)
9
+ */
10
+ static createId() {
11
+ return randomUUID().replace(/-/g, "");
12
+ }
13
+ };
14
+ export {
15
+ TransportIdGenerator
16
+ };
@@ -0,0 +1,30 @@
1
+ // libs/auth/src/session/signin-binding.ts
2
+ import { base64urlEncode, getCookie, randomBytes, sha256Hex, timingSafeEqual } from "@frontmcp/utils";
3
+ var SIGNIN_BINDING_COOKIE_PREFIX = "frontmcp_signin_";
4
+ var SIGNIN_BINDING_SECURE_COOKIE_PREFIX = `__Host-${SIGNIN_BINDING_COOKIE_PREFIX}`;
5
+ var SIGNIN_BINDING_MAX_AGE_SECONDS = 30 * 60;
6
+ function signinBindingCookieName(pendingAuthId, options = {}) {
7
+ const prefix = options.secure ? SIGNIN_BINDING_SECURE_COOKIE_PREFIX : SIGNIN_BINDING_COOKIE_PREFIX;
8
+ return `${prefix}${sha256Hex(`signin:${pendingAuthId}`).slice(0, 16)}`;
9
+ }
10
+ function createSigninBinding(pendingAuthId, options = {}) {
11
+ const value = base64urlEncode(randomBytes(32));
12
+ return { cookieName: signinBindingCookieName(pendingAuthId, options), value, hash: sha256Hex(value) };
13
+ }
14
+ function signinBindingMatches(cookieHeader, pendingAuthId, expectedHash, options = {}) {
15
+ if (!expectedHash) return false;
16
+ const value = getCookie(cookieHeader, signinBindingCookieName(pendingAuthId, options));
17
+ if (!value) return false;
18
+ const encoder = new TextEncoder();
19
+ const actual = encoder.encode(sha256Hex(value));
20
+ const expected = encoder.encode(expectedHash);
21
+ return actual.length === expected.length && timingSafeEqual(actual, expected);
22
+ }
23
+ export {
24
+ SIGNIN_BINDING_COOKIE_PREFIX,
25
+ SIGNIN_BINDING_MAX_AGE_SECONDS,
26
+ SIGNIN_BINDING_SECURE_COOKIE_PREFIX,
27
+ createSigninBinding,
28
+ signinBindingCookieName,
29
+ signinBindingMatches
30
+ };
@@ -0,0 +1,28 @@
1
+ // libs/auth/src/session/storage/in-memory-authorization-vault.ts
2
+ import { MemoryStorageAdapter } from "@frontmcp/utils";
3
+ import { StorageAuthorizationVault } from "./storage-authorization-vault.mjs";
4
+ var InMemoryAuthorizationVault = class extends StorageAuthorizationVault {
5
+ memoryAdapter;
6
+ constructor(options = {}) {
7
+ const memoryAdapter = new MemoryStorageAdapter();
8
+ super(memoryAdapter, {
9
+ namespace: options.namespace ?? "vault",
10
+ pendingAuthTtlMs: options.pendingAuthTtlMs
11
+ });
12
+ this.memoryAdapter = memoryAdapter;
13
+ void this.memoryAdapter.connect();
14
+ }
15
+ /**
16
+ * Clear all stored data.
17
+ * Useful for testing.
18
+ */
19
+ async clear() {
20
+ const keys = await this.memoryAdapter.keys("*");
21
+ for (const key of keys) {
22
+ await this.memoryAdapter.delete(key);
23
+ }
24
+ }
25
+ };
26
+ export {
27
+ InMemoryAuthorizationVault
28
+ };
@@ -0,0 +1,14 @@
1
+ // libs/auth/src/session/storage/index.ts
2
+ import { TypedStorage } from "@frontmcp/utils";
3
+ import { EncryptedTypedStorage, EncryptedStorageError } from "@frontmcp/utils";
4
+ import { StorageTokenStore } from "./storage-token-store.mjs";
5
+ import { StorageAuthorizationVault } from "./storage-authorization-vault.mjs";
6
+ import { InMemoryAuthorizationVault } from "./in-memory-authorization-vault.mjs";
7
+ export {
8
+ EncryptedStorageError,
9
+ EncryptedTypedStorage,
10
+ InMemoryAuthorizationVault,
11
+ StorageAuthorizationVault,
12
+ StorageTokenStore,
13
+ TypedStorage
14
+ };