@frontmcp/auth 1.10.15 → 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,197 @@
1
+ // libs/auth/src/session/storage-orchestrated-token.store.ts
2
+ import { EncryptionKeyNotConfiguredError } from "../errors/auth-internal.errors.mjs";
3
+ import {
4
+ decryptRecord,
5
+ deriveKeyForRecord,
6
+ encryptRecord,
7
+ hasLiveAccessToken,
8
+ recordDropAt
9
+ } from "./orchestrated-token.crypto.mjs";
10
+ var StorageOrchestratedTokenStore = class {
11
+ storage;
12
+ encryptionKey;
13
+ defaultTtlMs;
14
+ refreshSkewMs;
15
+ namespace;
16
+ storageIsNamespaced;
17
+ /** HKDF-derived subkey cache, keyed by composite key. */
18
+ derivedKeys = /* @__PURE__ */ new Map();
19
+ constructor(storage, options = {}) {
20
+ this.encryptionKey = options.encryptionKey;
21
+ this.defaultTtlMs = options.defaultTtlMs;
22
+ this.refreshSkewMs = options.refreshSkewMs ?? 0;
23
+ this.namespace = options.namespace ?? "otok";
24
+ this.storageIsNamespaced = this.isNamespacedStorage(storage);
25
+ this.storage = this.storageIsNamespaced ? storage.namespace(this.namespace) : storage;
26
+ }
27
+ // ============================================
28
+ // TokenStore interface
29
+ // ============================================
30
+ async getAccessToken(authorizationId, providerId) {
31
+ const record = await this.getRecord(authorizationId, providerId);
32
+ return record && hasLiveAccessToken(record, this.refreshSkewMs) ? record.accessToken : null;
33
+ }
34
+ async getRefreshToken(authorizationId, providerId) {
35
+ const record = await this.getRecord(authorizationId, providerId);
36
+ return record?.refreshToken ?? null;
37
+ }
38
+ async storeTokens(authorizationId, providerId, tokens) {
39
+ const compositeKey = this.compositeKey(authorizationId, providerId);
40
+ const now = Date.now();
41
+ const record = {
42
+ accessToken: tokens.accessToken,
43
+ refreshToken: tokens.refreshToken,
44
+ expiresAt: tokens.expiresAt ?? (this.defaultTtlMs ? now + this.defaultTtlMs : void 0),
45
+ createdAt: now,
46
+ updatedAt: now
47
+ };
48
+ const value = this.encryptionKey ? encryptRecord(this.keyFor(compositeKey), record) : JSON.stringify(record);
49
+ await this.storage.set(this.storageKey(compositeKey), value, this.ttlOptions(recordDropAt(record)));
50
+ }
51
+ async deleteTokens(authorizationId, providerId) {
52
+ const compositeKey = this.compositeKey(authorizationId, providerId);
53
+ await this.storage.delete(this.storageKey(compositeKey));
54
+ this.derivedKeys.delete(compositeKey);
55
+ }
56
+ async hasTokens(authorizationId, providerId) {
57
+ const record = await this.getRecord(authorizationId, providerId);
58
+ return record !== null;
59
+ }
60
+ async getProviderIds(authorizationId) {
61
+ const prefix = `${authorizationId}:`;
62
+ const compositeKeys = await this.listCompositeKeys(`${prefix}*`);
63
+ const providerIds = [];
64
+ for (const compositeKey of compositeKeys) {
65
+ if (!compositeKey.startsWith(prefix)) continue;
66
+ const providerId = compositeKey.slice(prefix.length);
67
+ const record = await this.getRecord(authorizationId, providerId);
68
+ if (record) {
69
+ providerIds.push(providerId);
70
+ }
71
+ }
72
+ return providerIds;
73
+ }
74
+ async migrateTokens(fromAuthId, toAuthId) {
75
+ for (const oldComposite of await this.copyRecords(fromAuthId, toAuthId)) {
76
+ await this.storage.delete(this.storageKey(oldComposite));
77
+ this.derivedKeys.delete(oldComposite);
78
+ }
79
+ }
80
+ async copyTokens(fromAuthId, toAuthId) {
81
+ await this.copyRecords(fromAuthId, toAuthId);
82
+ }
83
+ /**
84
+ * Delete all tokens for an authorization id.
85
+ */
86
+ async deleteAllForAuthorization(authorizationId) {
87
+ const prefix = `${authorizationId}:`;
88
+ const compositeKeys = await this.listCompositeKeys(`${prefix}*`);
89
+ for (const compositeKey of compositeKeys) {
90
+ if (!compositeKey.startsWith(prefix)) continue;
91
+ await this.storage.delete(this.storageKey(compositeKey));
92
+ this.derivedKeys.delete(compositeKey);
93
+ }
94
+ }
95
+ // ============================================
96
+ // Internals
97
+ // ============================================
98
+ /** Copy every record of `fromAuthId` to `toAuthId`, skipping a corrupted one; returns the composite keys copied. */
99
+ async copyRecords(fromAuthId, toAuthId) {
100
+ const prefix = `${fromAuthId}:`;
101
+ const compositeKeys = await this.listCompositeKeys(`${prefix}*`);
102
+ const copiedKeys = [];
103
+ for (const oldComposite of compositeKeys) {
104
+ if (!oldComposite.startsWith(prefix)) continue;
105
+ const newComposite = this.compositeKey(toAuthId, oldComposite.slice(prefix.length));
106
+ const stored = await this.storage.get(this.storageKey(oldComposite));
107
+ if (stored === null) continue;
108
+ if (this.encryptionKey) {
109
+ let record;
110
+ try {
111
+ record = decryptRecord(this.keyFor(oldComposite), stored);
112
+ } catch {
113
+ continue;
114
+ }
115
+ const value = encryptRecord(this.keyFor(newComposite), record);
116
+ await this.storage.set(this.storageKey(newComposite), value, this.ttlOptions(recordDropAt(record)));
117
+ } else {
118
+ let dropAt;
119
+ try {
120
+ dropAt = recordDropAt(JSON.parse(stored));
121
+ } catch {
122
+ dropAt = void 0;
123
+ }
124
+ await this.storage.set(this.storageKey(newComposite), stored, this.ttlOptions(dropAt));
125
+ }
126
+ copiedKeys.push(oldComposite);
127
+ }
128
+ return copiedKeys;
129
+ }
130
+ async getRecord(authorizationId, providerId) {
131
+ const compositeKey = this.compositeKey(authorizationId, providerId);
132
+ const stored = await this.storage.get(this.storageKey(compositeKey));
133
+ if (stored === null) {
134
+ return null;
135
+ }
136
+ let record;
137
+ if (this.encryptionKey) {
138
+ try {
139
+ record = decryptRecord(this.keyFor(compositeKey), stored);
140
+ } catch {
141
+ await this.storage.delete(this.storageKey(compositeKey));
142
+ return null;
143
+ }
144
+ } else {
145
+ try {
146
+ record = JSON.parse(stored);
147
+ } catch {
148
+ await this.storage.delete(this.storageKey(compositeKey));
149
+ return null;
150
+ }
151
+ }
152
+ const dropAt = recordDropAt(record);
153
+ if (dropAt && dropAt < Date.now()) {
154
+ await this.storage.delete(this.storageKey(compositeKey));
155
+ return null;
156
+ }
157
+ return record;
158
+ }
159
+ keyFor(compositeKey) {
160
+ if (!this.encryptionKey) {
161
+ throw new EncryptionKeyNotConfiguredError();
162
+ }
163
+ return deriveKeyForRecord(this.encryptionKey, compositeKey, this.derivedKeys);
164
+ }
165
+ compositeKey(authorizationId, providerId) {
166
+ return `${authorizationId}:${providerId}`;
167
+ }
168
+ storageKey(compositeKey) {
169
+ return this.storageIsNamespaced ? compositeKey : `${this.namespace}:${compositeKey}`;
170
+ }
171
+ /**
172
+ * List composite keys (authorizationId:providerId) matching a glob over the
173
+ * composite-key space, stripping any non-namespaced prefix we added.
174
+ */
175
+ async listCompositeKeys(compositeGlob) {
176
+ const pattern = this.storageIsNamespaced ? compositeGlob : `${this.namespace}:${compositeGlob}`;
177
+ const raw = await this.storage.keys(pattern);
178
+ if (this.storageIsNamespaced) {
179
+ return raw;
180
+ }
181
+ const stripPrefix = `${this.namespace}:`;
182
+ return raw.map((k) => k.startsWith(stripPrefix) ? k.slice(stripPrefix.length) : k);
183
+ }
184
+ ttlOptions(expiresAtMs) {
185
+ if (!expiresAtMs) {
186
+ return void 0;
187
+ }
188
+ const ttl = Math.ceil((expiresAtMs - Date.now()) / 1e3);
189
+ return { ttlSeconds: ttl > 0 ? ttl : 1 };
190
+ }
191
+ isNamespacedStorage(storage) {
192
+ return "namespace" in storage && typeof storage.namespace === "function";
193
+ }
194
+ };
195
+ export {
196
+ StorageOrchestratedTokenStore
197
+ };
@@ -0,0 +1,58 @@
1
+ var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
2
+ const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
3
+ // __frontmcpCreateRequire
4
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
6
+ }) : x)(function(x) {
7
+ if (typeof require !== "undefined") return require.apply(this, arguments);
8
+ throw Error('Dynamic require of "' + x + '" is not supported');
9
+ });
10
+
11
+ // libs/auth/src/session/token-storage.factory.ts
12
+ import { MemoryStorageAdapter, RedisStorageAdapter } from "@frontmcp/utils";
13
+ function isRedisTokenStorage(config) {
14
+ return typeof config === "object" && config !== null && "redis" in config && !!config.redis;
15
+ }
16
+ function isSqliteTokenStorage(config) {
17
+ return typeof config === "object" && config !== null && "sqlite" in config && !!config.sqlite;
18
+ }
19
+ function isPersistentTokenStorage(config) {
20
+ return config !== void 0 && config !== "memory";
21
+ }
22
+ async function createTokenStorageAdapter(config) {
23
+ if (config === void 0 || config === "memory") {
24
+ const adapter = new MemoryStorageAdapter();
25
+ await adapter.connect();
26
+ return adapter;
27
+ }
28
+ if (isSqliteTokenStorage(config)) {
29
+ const { SqliteStorageAdapter } = __require("@frontmcp/storage-sqlite");
30
+ const adapter = new SqliteStorageAdapter({
31
+ path: config.sqlite.path,
32
+ encryption: config.sqlite.encryption,
33
+ ttlCleanupIntervalMs: config.sqlite.ttlCleanupIntervalMs ?? 6e4,
34
+ walMode: config.sqlite.walMode ?? true,
35
+ busyTimeoutMs: config.sqlite.busyTimeoutMs
36
+ });
37
+ await adapter.connect();
38
+ return adapter;
39
+ }
40
+ if (isRedisTokenStorage(config)) {
41
+ const { host, port, password, db, tls, keyPrefix } = config.redis;
42
+ const adapter = new RedisStorageAdapter({
43
+ config: { host, port, password, db, tls },
44
+ keyPrefix: keyPrefix ?? ""
45
+ });
46
+ await adapter.connect();
47
+ return adapter;
48
+ }
49
+ const fallback = new MemoryStorageAdapter();
50
+ await fallback.connect();
51
+ return fallback;
52
+ }
53
+ export {
54
+ createTokenStorageAdapter,
55
+ isPersistentTokenStorage,
56
+ isRedisTokenStorage,
57
+ isSqliteTokenStorage
58
+ };
@@ -0,0 +1,36 @@
1
+ // libs/auth/src/session/token.refresh.ts
2
+ import { base64urlDecode } from "@frontmcp/utils";
3
+ function toEpochSeconds(exp) {
4
+ if (exp == null) return void 0;
5
+ if (exp instanceof Date) return Math.floor(exp.getTime() / 1e3);
6
+ return exp > 1e12 ? Math.floor(exp / 1e3) : Math.floor(exp);
7
+ }
8
+ function isSoonExpiring(exp, skewSec = 60) {
9
+ const expSec = toEpochSeconds(exp);
10
+ if (expSec == null) return false;
11
+ const now = Math.floor(Date.now() / 1e3);
12
+ return expSec <= now + Math.max(0, skewSec);
13
+ }
14
+ function isSoonExpiringProvider(sessionLike, providerId, skewSec = 60) {
15
+ const snap = sessionLike.authorizedProviders[providerId];
16
+ if (!snap) return false;
17
+ return isSoonExpiring(snap.exp, skewSec);
18
+ }
19
+ function tryJwtExp(token) {
20
+ if (!token) return void 0;
21
+ const parts = token.split(".");
22
+ if (parts.length < 2) return void 0;
23
+ try {
24
+ const json = JSON.parse(new TextDecoder().decode(base64urlDecode(parts[1])));
25
+ const e = json?.exp;
26
+ return typeof e === "number" ? toEpochSeconds(e) : void 0;
27
+ } catch {
28
+ return void 0;
29
+ }
30
+ }
31
+ export {
32
+ isSoonExpiring,
33
+ isSoonExpiringProvider,
34
+ toEpochSeconds,
35
+ tryJwtExp
36
+ };
@@ -0,0 +1,61 @@
1
+ // libs/auth/src/session/token.vault.ts
2
+ import { encryptAesGcm, decryptAesGcm, randomBytes, base64urlEncode, base64urlDecode } from "@frontmcp/utils";
3
+ var TokenVault = class {
4
+ /** Active key used for new encryptions */
5
+ active;
6
+ /** All known keys by kid for decryption (includes active) */
7
+ keys = /* @__PURE__ */ new Map();
8
+ constructor(keys) {
9
+ if (!Array.isArray(keys) || keys.length === 0) {
10
+ throw new Error("TokenVault requires at least one key");
11
+ }
12
+ for (const k of keys) {
13
+ if (!(k.key instanceof Uint8Array) || k.key.length !== 32) {
14
+ throw new Error(`TokenVault key "${k.kid}" must be a 32-byte Uint8Array for AES-256-GCM`);
15
+ }
16
+ if (this.keys.has(k.kid)) {
17
+ throw new Error(`TokenVault duplicate kid: "${k.kid}"`);
18
+ }
19
+ this.keys.set(k.kid, k.key);
20
+ }
21
+ this.active = keys[0];
22
+ }
23
+ rotateTo(k) {
24
+ if (!(k.key instanceof Uint8Array) || k.key.length !== 32) {
25
+ throw new Error(`TokenVault key "${k.kid}" must be a 32-byte Uint8Array for AES-256-GCM`);
26
+ }
27
+ this.active = k;
28
+ this.keys.set(k.kid, k.key);
29
+ }
30
+ async encrypt(plaintext, opts) {
31
+ const iv = randomBytes(12);
32
+ const { ciphertext, tag } = await encryptAesGcm(this.active.key, new TextEncoder().encode(plaintext), iv);
33
+ return {
34
+ alg: "A256GCM",
35
+ kid: this.active.kid,
36
+ iv: base64urlEncode(iv),
37
+ tag: base64urlEncode(tag),
38
+ data: base64urlEncode(ciphertext),
39
+ exp: opts?.exp,
40
+ meta: opts?.meta
41
+ };
42
+ }
43
+ async decrypt(blob) {
44
+ if (blob.exp !== void 0) {
45
+ const nowSeconds = Math.floor(Date.now() / 1e3);
46
+ if (nowSeconds > blob.exp) {
47
+ throw new Error(`vault_expired:${blob.kid}`);
48
+ }
49
+ }
50
+ const key = this.keys.get(blob.kid);
51
+ if (!key) throw new Error(`vault_unknown_kid:${blob.kid}`);
52
+ const iv = base64urlDecode(blob.iv);
53
+ const tag = base64urlDecode(blob.tag);
54
+ const data = base64urlDecode(blob.data);
55
+ const plaintext = await decryptAesGcm(key, data, iv, tag);
56
+ return new TextDecoder().decode(plaintext);
57
+ }
58
+ };
59
+ export {
60
+ TokenVault
61
+ };
@@ -0,0 +1,108 @@
1
+ // libs/auth/src/session/transport-session.types.ts
2
+ import { internalZ as z } from "@frontmcp/lazy-zod";
3
+ var transportProtocolSchema = z.enum([
4
+ "legacy-sse",
5
+ "sse",
6
+ "streamable-http",
7
+ "stateful-http",
8
+ "stateless-http"
9
+ ]);
10
+ var sseTransportStateSchema = z.object({
11
+ type: z.literal("sse"),
12
+ lastEventId: z.string().optional(),
13
+ lastPing: z.number().optional(),
14
+ connectionState: z.enum(["connecting", "open", "closed"]).optional()
15
+ });
16
+ var streamableHttpTransportStateSchema = z.object({
17
+ type: z.literal("streamable-http"),
18
+ requestSeq: z.number(),
19
+ activeStreamId: z.string().optional(),
20
+ pendingRequests: z.array(z.string()).optional()
21
+ });
22
+ var statefulHttpTransportStateSchema = z.object({
23
+ type: z.literal("stateful-http"),
24
+ requestSeq: z.number(),
25
+ pendingResponses: z.array(z.string()).optional(),
26
+ lastActivity: z.number().optional()
27
+ });
28
+ var statelessHttpTransportStateSchema = z.object({
29
+ type: z.literal("stateless-http"),
30
+ requestCount: z.number(),
31
+ windowStart: z.number().optional()
32
+ });
33
+ var legacySseTransportStateSchema = z.object({
34
+ type: z.literal("legacy-sse"),
35
+ messagePath: z.string(),
36
+ lastEventId: z.string().optional(),
37
+ connectionState: z.enum(["connecting", "open", "closed"]).optional()
38
+ });
39
+ var transportStateSchema = z.discriminatedUnion("type", [
40
+ sseTransportStateSchema,
41
+ streamableHttpTransportStateSchema,
42
+ statefulHttpTransportStateSchema,
43
+ statelessHttpTransportStateSchema,
44
+ legacySseTransportStateSchema
45
+ ]);
46
+ var transportSessionSchema = z.object({
47
+ id: z.string(),
48
+ authorizationId: z.string(),
49
+ protocol: transportProtocolSchema,
50
+ createdAt: z.number(),
51
+ expiresAt: z.number().optional(),
52
+ nodeId: z.string(),
53
+ clientFingerprint: z.string().optional(),
54
+ transportState: transportStateSchema.optional()
55
+ });
56
+ var sessionJwtPayloadSchema = z.object({
57
+ sid: z.string(),
58
+ aid: z.string(),
59
+ proto: transportProtocolSchema,
60
+ nid: z.string(),
61
+ iat: z.number(),
62
+ exp: z.number().optional()
63
+ });
64
+ var encryptedBlobSchema = z.object({
65
+ alg: z.literal("A256GCM"),
66
+ kid: z.string().optional(),
67
+ iv: z.string(),
68
+ tag: z.string(),
69
+ data: z.string(),
70
+ exp: z.number().optional(),
71
+ meta: z.record(z.string(), z.unknown()).optional()
72
+ });
73
+ var storedSessionSchema = z.object({
74
+ session: transportSessionSchema,
75
+ authorizationId: z.string(),
76
+ tokens: z.record(z.string(), encryptedBlobSchema).optional(),
77
+ createdAt: z.number(),
78
+ lastAccessedAt: z.number(),
79
+ initialized: z.boolean().optional(),
80
+ maxLifetimeAt: z.number().optional(),
81
+ clientCapabilities: z.record(z.string(), z.unknown()).optional(),
82
+ reassignedAt: z.number().optional(),
83
+ reassignedFrom: z.string().optional()
84
+ });
85
+ var redisConfigSchema = z.object({
86
+ host: z.string().min(1),
87
+ port: z.number().int().positive().optional().default(6379),
88
+ password: z.string().optional(),
89
+ db: z.number().int().nonnegative().optional().default(0),
90
+ tls: z.boolean().optional().default(false),
91
+ keyPrefix: z.string().optional().default("mcp:session:"),
92
+ defaultTtlMs: z.number().int().positive().optional().default(36e5)
93
+ // 1 hour default
94
+ });
95
+ export {
96
+ encryptedBlobSchema,
97
+ legacySseTransportStateSchema,
98
+ redisConfigSchema,
99
+ sessionJwtPayloadSchema,
100
+ sseTransportStateSchema,
101
+ statefulHttpTransportStateSchema,
102
+ statelessHttpTransportStateSchema,
103
+ storedSessionSchema,
104
+ streamableHttpTransportStateSchema,
105
+ transportProtocolSchema,
106
+ transportSessionSchema,
107
+ transportStateSchema
108
+ };
@@ -0,0 +1,58 @@
1
+ // libs/auth/src/session/utils/auth-token.utils.ts
2
+ import { sha256Base64url } from "@frontmcp/utils";
3
+ function isJwt(token) {
4
+ if (!token) return false;
5
+ return token.split(".").length === 3;
6
+ }
7
+ function getTokenSignatureFingerprint(token) {
8
+ if (isJwt(token)) {
9
+ const sig = token.split(".")[2];
10
+ if (sig) return sig;
11
+ }
12
+ return sha256Base64url(token);
13
+ }
14
+ function extractClaimValue(claims, key, validator) {
15
+ const value = claims[key];
16
+ return validator(value) ? value : void 0;
17
+ }
18
+ var isString = (value) => typeof value === "string";
19
+ var isNumber = (value) => typeof value === "number";
20
+ var isStringOrStringArray = (value) => typeof value === "string" || Array.isArray(value);
21
+ function deriveTypedUser(claims) {
22
+ return {
23
+ ...claims,
24
+ iss: extractClaimValue(claims, "iss", isString) ?? "",
25
+ sid: extractClaimValue(claims, "sid", isString),
26
+ // RFC 9068 \u00a72.2: a client-credentials token names the client as its subject; some IdPs only put it in client_id / azp
27
+ sub: extractClaimValue(claims, "sub", isString) ?? extractClaimValue(claims, "client_id", isString) ?? extractClaimValue(claims, "azp", isString) ?? "",
28
+ exp: extractClaimValue(claims, "exp", isNumber),
29
+ iat: extractClaimValue(claims, "iat", isNumber),
30
+ aud: extractClaimValue(claims, "aud", isStringOrStringArray),
31
+ email: extractClaimValue(claims, "email", isString),
32
+ preferred_username: extractClaimValue(claims, "preferred_username", isString),
33
+ username: extractClaimValue(claims, "username", isString),
34
+ name: extractClaimValue(claims, "name", isString),
35
+ picture: extractClaimValue(claims, "picture", isString)
36
+ };
37
+ }
38
+ function anonymousCallerClaims(options, anonymousId) {
39
+ const { issuer, scopes = ["anonymous"], subject } = options;
40
+ return {
41
+ sub: subject ?? `anon:${anonymousId}`,
42
+ iss: issuer,
43
+ name: subject ? "Static token" : "Anonymous",
44
+ scope: scopes.join(" ")
45
+ };
46
+ }
47
+ function extractBearerToken(header) {
48
+ if (!header) return void 0;
49
+ const m = header.match(/^\s*Bearer\s+(\S+)\s*$/i);
50
+ return m ? m[1] : void 0;
51
+ }
52
+ export {
53
+ anonymousCallerClaims,
54
+ deriveTypedUser,
55
+ extractBearerToken,
56
+ getTokenSignatureFingerprint,
57
+ isJwt
58
+ };
@@ -0,0 +1,23 @@
1
+ // libs/auth/src/session/utils/index.ts
2
+ import { TinyTtlCache } from "./tiny-ttl-cache.mjs";
3
+ import {
4
+ isJwt,
5
+ getTokenSignatureFingerprint,
6
+ deriveTypedUser,
7
+ anonymousCallerClaims,
8
+ extractBearerToken
9
+ } from "./auth-token.utils.mjs";
10
+ import { getKey, encryptJson, decryptSessionJson, safeDecrypt, resetCachedKey } from "./session-crypto.utils.mjs";
11
+ export {
12
+ TinyTtlCache,
13
+ anonymousCallerClaims,
14
+ decryptSessionJson,
15
+ deriveTypedUser,
16
+ encryptJson,
17
+ extractBearerToken,
18
+ getKey,
19
+ getTokenSignatureFingerprint,
20
+ isJwt,
21
+ resetCachedKey,
22
+ safeDecrypt
23
+ };
@@ -0,0 +1,49 @@
1
+ // libs/auth/src/session/utils/session-crypto.utils.ts
2
+ import { decryptValue, encryptValue, getEnv, getMachineId, isProduction, sha256 } from "@frontmcp/utils";
3
+ import { SessionSecretRequiredError } from "../../errors/auth-internal.errors.mjs";
4
+ var cachedKey = null;
5
+ function getKey() {
6
+ if (cachedKey) return cachedKey;
7
+ const secret = getEnv("MCP_SESSION_SECRET");
8
+ if (!secret) {
9
+ if (isProduction()) {
10
+ throw new SessionSecretRequiredError("session ID encryption");
11
+ }
12
+ console.warn(
13
+ "[SessionIdUtils] Using machine ID as session encryption secret - NOT SECURE FOR PRODUCTION. Set MCP_SESSION_SECRET environment variable for secure session encryption."
14
+ );
15
+ }
16
+ const base = secret || getMachineId();
17
+ cachedKey = sha256(new TextEncoder().encode(base));
18
+ return cachedKey;
19
+ }
20
+ function encryptJson(obj) {
21
+ const key = getKey();
22
+ const encrypted = encryptValue(obj, key);
23
+ return `${encrypted.iv}.${encrypted.tag}.${encrypted.data}`;
24
+ }
25
+ function decryptSessionJson(sessionId) {
26
+ const parts = sessionId.split(".");
27
+ if (parts.length !== 3) return null;
28
+ const [ivB64, tagB64, ctB64] = parts;
29
+ if (!ivB64 || !tagB64 || !ctB64) return null;
30
+ const key = getKey();
31
+ return decryptValue({ alg: "A256GCM", iv: ivB64, tag: tagB64, data: ctB64 }, key);
32
+ }
33
+ function safeDecrypt(sessionId) {
34
+ try {
35
+ return decryptSessionJson(sessionId);
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+ function resetCachedKey() {
41
+ cachedKey = null;
42
+ }
43
+ export {
44
+ decryptSessionJson,
45
+ encryptJson,
46
+ getKey,
47
+ resetCachedKey,
48
+ safeDecrypt
49
+ };
@@ -0,0 +1,46 @@
1
+ // libs/auth/src/session/utils/tiny-ttl-cache.ts
2
+ var TinyTtlCache = class {
3
+ constructor(ttlMs) {
4
+ this.ttlMs = ttlMs;
5
+ }
6
+ ttlMs;
7
+ map = /* @__PURE__ */ new Map();
8
+ get(k) {
9
+ const hit = this.map.get(k);
10
+ if (!hit) return void 0;
11
+ if (hit.exp < Date.now()) {
12
+ this.map.delete(k);
13
+ return void 0;
14
+ }
15
+ return hit.v;
16
+ }
17
+ set(k, v) {
18
+ this.map.set(k, { v, exp: Date.now() + this.ttlMs });
19
+ }
20
+ delete(k) {
21
+ return this.map.delete(k);
22
+ }
23
+ clear() {
24
+ this.map.clear();
25
+ }
26
+ size() {
27
+ return this.map.size;
28
+ }
29
+ /**
30
+ * Remove all expired entries
31
+ */
32
+ cleanup() {
33
+ const now = Date.now();
34
+ let removed = 0;
35
+ for (const [k, { exp }] of this.map) {
36
+ if (exp < now) {
37
+ this.map.delete(k);
38
+ removed++;
39
+ }
40
+ }
41
+ return removed;
42
+ }
43
+ };
44
+ export {
45
+ TinyTtlCache
46
+ };