@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,266 @@
1
+ // libs/auth/src/session/index.ts
2
+ import {
3
+ InMemoryAuthorizationStore,
4
+ RedisAuthorizationStore,
5
+ verifyPkce,
6
+ generatePkceChallenge,
7
+ generateAuthorizationCode,
8
+ generateRefreshTokenValue,
9
+ buildCodeRecord,
10
+ buildPendingRecord,
11
+ buildRefreshTokenRecord,
12
+ AUTH_CODE_TTL_MS,
13
+ PENDING_AUTH_TTL_MS,
14
+ REFRESH_TOKEN_TTL_MS,
15
+ pkceChallengeSchema,
16
+ authorizationCodeRecordSchema
17
+ } from "./authorization.store.mjs";
18
+ import { sealPendingLogin, openPendingLogin } from "./pending-login.mjs";
19
+ import {
20
+ SIGNIN_BINDING_COOKIE_PREFIX,
21
+ SIGNIN_BINDING_SECURE_COOKIE_PREFIX,
22
+ SIGNIN_BINDING_MAX_AGE_SECONDS,
23
+ createSigninBinding,
24
+ signinBindingCookieName,
25
+ signinBindingMatches
26
+ } from "./signin-binding.mjs";
27
+ import { StorageAuthorizationStore } from "./storage-authorization.store.mjs";
28
+ import {
29
+ credentialTypeSchema,
30
+ oauthCredentialSchema,
31
+ apiKeyCredentialSchema,
32
+ basicAuthCredentialSchema,
33
+ bearerCredentialSchema,
34
+ privateKeyCredentialSchema,
35
+ mtlsCredentialSchema,
36
+ customCredentialSchema,
37
+ sshKeyCredentialSchema,
38
+ serviceAccountCredentialSchema,
39
+ pkceOAuthCredentialSchema,
40
+ credentialSchema,
41
+ appCredentialSchema,
42
+ vaultConsentRecordSchema,
43
+ vaultFederatedRecordSchema,
44
+ pendingIncrementalAuthSchema,
45
+ authorizationVaultEntrySchema
46
+ } from "./authorization-vault.mjs";
47
+ import { SessionCredentialVault } from "./session-credential-vault.mjs";
48
+ import { createSessionCredentialVault } from "./session-credential-vault.factory.mjs";
49
+ import {
50
+ signCredentialResumeToken,
51
+ verifyCredentialResumeToken,
52
+ buildCredentialResumeUrl,
53
+ DEFAULT_RESUME_TTL_MS
54
+ } from "./credential-resume-link.mjs";
55
+ import {
56
+ signIncrementalAuthTicket,
57
+ verifyIncrementalAuthTicket,
58
+ DEFAULT_INCREMENTAL_TICKET_TTL_MS
59
+ } from "./incremental-auth-ticket.mjs";
60
+ import { CREDENTIALS_ACCESSOR } from "./credentials-accessor.mjs";
61
+ import { CredentialsAccessorImpl } from "./credentials-accessor.impl.mjs";
62
+ import { EncryptedStorageSecureStoreBackend } from "./secure-store-backends.mjs";
63
+ import { SECURE_STORE_ACCESSOR } from "./secure-store-accessor.mjs";
64
+ import { SecureStoreAccessorImpl } from "./secure-store-accessor.impl.mjs";
65
+ import { createSecureStore } from "./secure-store.factory.mjs";
66
+ import { encryptedDataSchema, encryptedVaultEntrySchema, VaultEncryption } from "./vault-encryption.mjs";
67
+ import { TokenVault } from "./token.vault.mjs";
68
+ import {
69
+ hkdfSha256,
70
+ encryptValue,
71
+ decryptValue,
72
+ encryptAesGcm,
73
+ decryptAesGcm
74
+ } from "@frontmcp/utils";
75
+ import {
76
+ transportProtocolSchema,
77
+ sseTransportStateSchema,
78
+ streamableHttpTransportStateSchema,
79
+ statefulHttpTransportStateSchema,
80
+ statelessHttpTransportStateSchema,
81
+ legacySseTransportStateSchema,
82
+ transportStateSchema,
83
+ transportSessionSchema,
84
+ sessionJwtPayloadSchema,
85
+ encryptedBlobSchema,
86
+ storedSessionSchema,
87
+ redisConfigSchema
88
+ } from "./transport-session.types.mjs";
89
+ import { signSession, verifySession, isSignedSession, verifyOrParseSession } from "./session-crypto.mjs";
90
+ import { SessionRateLimiter, defaultSessionRateLimiter } from "./session-rate-limiter.mjs";
91
+ import { TransportIdGenerator } from "./session.transport.mjs";
92
+ import {
93
+ isJwt,
94
+ getTokenSignatureFingerprint,
95
+ deriveTypedUser,
96
+ anonymousCallerClaims,
97
+ extractBearerToken,
98
+ getKey,
99
+ encryptJson,
100
+ decryptSessionJson,
101
+ safeDecrypt,
102
+ resetCachedKey,
103
+ TinyTtlCache
104
+ } from "./utils/index.mjs";
105
+ import {
106
+ TypedStorage,
107
+ EncryptedTypedStorage,
108
+ EncryptedStorageError,
109
+ StorageTokenStore,
110
+ StorageAuthorizationVault,
111
+ InMemoryAuthorizationVault
112
+ } from "./storage/index.mjs";
113
+ import { RedisSessionStore } from "./redis-session.store.mjs";
114
+ import { VercelKvSessionStore } from "./vercel-kv-session.store.mjs";
115
+ import { InMemoryOrchestratedTokenStore } from "./orchestrated-token.store.mjs";
116
+ import { StorageOrchestratedTokenStore } from "./storage-orchestrated-token.store.mjs";
117
+ import {
118
+ InMemoryFederatedAuthSessionStore,
119
+ toSessionRecord,
120
+ fromSessionRecord,
121
+ createFederatedAuthSession,
122
+ isSessionComplete,
123
+ getNextProvider,
124
+ completeCurrentProvider,
125
+ startNextProvider
126
+ } from "./federated-auth.session.mjs";
127
+ import { StorageFederatedAuthSessionStore } from "./storage-federated-auth.session.mjs";
128
+ import { InMemoryConsentStore, consentRecordKey } from "./consent.store.mjs";
129
+ import { StorageConsentStore } from "./storage-consent.store.mjs";
130
+ import {
131
+ createTokenStorageAdapter,
132
+ isRedisTokenStorage,
133
+ isSqliteTokenStorage,
134
+ isPersistentTokenStorage
135
+ } from "./token-storage.factory.mjs";
136
+ import { redisVaultEntrySchema, EncryptedRedisVault, createEncryptedVault } from "./encrypted-authorization-vault.mjs";
137
+ import { toEpochSeconds, isSoonExpiring, isSoonExpiringProvider, tryJwtExp } from "./token.refresh.mjs";
138
+ export {
139
+ AUTH_CODE_TTL_MS,
140
+ CREDENTIALS_ACCESSOR,
141
+ CredentialsAccessorImpl,
142
+ DEFAULT_INCREMENTAL_TICKET_TTL_MS,
143
+ DEFAULT_RESUME_TTL_MS,
144
+ EncryptedRedisVault,
145
+ EncryptedStorageError,
146
+ EncryptedStorageSecureStoreBackend,
147
+ EncryptedTypedStorage,
148
+ InMemoryAuthorizationStore,
149
+ InMemoryAuthorizationVault,
150
+ InMemoryConsentStore,
151
+ InMemoryFederatedAuthSessionStore,
152
+ InMemoryOrchestratedTokenStore,
153
+ PENDING_AUTH_TTL_MS,
154
+ REFRESH_TOKEN_TTL_MS,
155
+ RedisAuthorizationStore,
156
+ RedisSessionStore,
157
+ SECURE_STORE_ACCESSOR,
158
+ SIGNIN_BINDING_COOKIE_PREFIX,
159
+ SIGNIN_BINDING_MAX_AGE_SECONDS,
160
+ SIGNIN_BINDING_SECURE_COOKIE_PREFIX,
161
+ SecureStoreAccessorImpl,
162
+ SessionCredentialVault,
163
+ SessionRateLimiter,
164
+ StorageAuthorizationStore,
165
+ StorageAuthorizationVault,
166
+ StorageConsentStore,
167
+ StorageFederatedAuthSessionStore,
168
+ StorageOrchestratedTokenStore,
169
+ StorageTokenStore,
170
+ TinyTtlCache,
171
+ TokenVault,
172
+ TransportIdGenerator,
173
+ TypedStorage,
174
+ VaultEncryption,
175
+ VercelKvSessionStore,
176
+ anonymousCallerClaims,
177
+ apiKeyCredentialSchema,
178
+ appCredentialSchema,
179
+ authorizationCodeRecordSchema,
180
+ authorizationVaultEntrySchema,
181
+ basicAuthCredentialSchema,
182
+ bearerCredentialSchema,
183
+ buildCodeRecord,
184
+ buildCredentialResumeUrl,
185
+ buildPendingRecord,
186
+ buildRefreshTokenRecord,
187
+ completeCurrentProvider,
188
+ consentRecordKey,
189
+ createEncryptedVault,
190
+ createFederatedAuthSession,
191
+ createSecureStore,
192
+ createSessionCredentialVault,
193
+ createSigninBinding,
194
+ createTokenStorageAdapter,
195
+ credentialSchema,
196
+ credentialTypeSchema,
197
+ customCredentialSchema,
198
+ decryptAesGcm,
199
+ decryptSessionJson,
200
+ decryptValue,
201
+ defaultSessionRateLimiter,
202
+ deriveTypedUser,
203
+ encryptAesGcm,
204
+ encryptJson,
205
+ encryptValue,
206
+ encryptedBlobSchema,
207
+ encryptedDataSchema,
208
+ encryptedVaultEntrySchema,
209
+ extractBearerToken,
210
+ fromSessionRecord,
211
+ generateAuthorizationCode,
212
+ generatePkceChallenge,
213
+ generateRefreshTokenValue,
214
+ getKey,
215
+ getNextProvider,
216
+ getTokenSignatureFingerprint,
217
+ hkdfSha256,
218
+ isJwt,
219
+ isPersistentTokenStorage,
220
+ isRedisTokenStorage,
221
+ isSessionComplete,
222
+ isSignedSession,
223
+ isSoonExpiring,
224
+ isSoonExpiringProvider,
225
+ isSqliteTokenStorage,
226
+ legacySseTransportStateSchema,
227
+ mtlsCredentialSchema,
228
+ oauthCredentialSchema,
229
+ openPendingLogin,
230
+ pendingIncrementalAuthSchema,
231
+ pkceChallengeSchema,
232
+ pkceOAuthCredentialSchema,
233
+ privateKeyCredentialSchema,
234
+ redisConfigSchema,
235
+ redisVaultEntrySchema,
236
+ resetCachedKey,
237
+ safeDecrypt,
238
+ sealPendingLogin,
239
+ serviceAccountCredentialSchema,
240
+ sessionJwtPayloadSchema,
241
+ signCredentialResumeToken,
242
+ signIncrementalAuthTicket,
243
+ signSession,
244
+ signinBindingCookieName,
245
+ signinBindingMatches,
246
+ sseTransportStateSchema,
247
+ sshKeyCredentialSchema,
248
+ startNextProvider,
249
+ statefulHttpTransportStateSchema,
250
+ statelessHttpTransportStateSchema,
251
+ storedSessionSchema,
252
+ streamableHttpTransportStateSchema,
253
+ toEpochSeconds,
254
+ toSessionRecord,
255
+ transportProtocolSchema,
256
+ transportSessionSchema,
257
+ transportStateSchema,
258
+ tryJwtExp,
259
+ vaultConsentRecordSchema,
260
+ vaultFederatedRecordSchema,
261
+ verifyCredentialResumeToken,
262
+ verifyIncrementalAuthTicket,
263
+ verifyOrParseSession,
264
+ verifyPkce,
265
+ verifySession
266
+ };
@@ -0,0 +1,47 @@
1
+ // libs/auth/src/session/orchestrated-token.crypto.ts
2
+ import { decryptAesGcm, encryptAesGcm, hkdfSha256, randomBytes } from "@frontmcp/utils";
3
+ import { REFRESH_TOKEN_TTL_MS } from "./authorization.store.mjs";
4
+ function recordDropAt(record) {
5
+ if (!record.refreshToken) return record.expiresAt;
6
+ return Math.max(record.expiresAt ?? 0, record.updatedAt + REFRESH_TOKEN_TTL_MS);
7
+ }
8
+ function hasLiveAccessToken(record, refreshSkewMs = 0) {
9
+ if (record.expiresAt === void 0) return true;
10
+ return record.expiresAt - (record.refreshToken ? refreshSkewMs : 0) > Date.now();
11
+ }
12
+ var HKDF_SALT = new TextEncoder().encode("frontmcp-token-store");
13
+ function deriveKeyForRecord(masterKey, compositeKey, cache) {
14
+ const cached = cache.get(compositeKey);
15
+ if (cached) {
16
+ return cached;
17
+ }
18
+ const info = new TextEncoder().encode(`orchestrated-token:${compositeKey}`);
19
+ const derived = hkdfSha256(masterKey, HKDF_SALT, info, 32);
20
+ cache.set(compositeKey, derived);
21
+ return derived;
22
+ }
23
+ function encryptRecord(key, record) {
24
+ const plaintext = JSON.stringify(record);
25
+ const iv = randomBytes(12);
26
+ const { ciphertext, tag } = encryptAesGcm(key, new TextEncoder().encode(plaintext), iv);
27
+ return JSON.stringify({
28
+ iv: Buffer.from(iv).toString("base64url"),
29
+ tag: Buffer.from(tag).toString("base64url"),
30
+ data: Buffer.from(ciphertext).toString("base64url")
31
+ });
32
+ }
33
+ function decryptRecord(key, encrypted) {
34
+ const { iv, tag, data } = JSON.parse(encrypted);
35
+ const ivBytes = Buffer.from(iv, "base64url");
36
+ const tagBytes = Buffer.from(tag, "base64url");
37
+ const ciphertextBytes = Buffer.from(data, "base64url");
38
+ const plaintext = decryptAesGcm(key, ciphertextBytes, ivBytes, tagBytes);
39
+ return JSON.parse(new TextDecoder().decode(plaintext));
40
+ }
41
+ export {
42
+ decryptRecord,
43
+ deriveKeyForRecord,
44
+ encryptRecord,
45
+ hasLiveAccessToken,
46
+ recordDropAt
47
+ };
@@ -0,0 +1,286 @@
1
+ // libs/auth/src/session/orchestrated-token.store.ts
2
+ import { decryptAesGcm, encryptAesGcm, hkdfSha256, randomBytes } from "@frontmcp/utils";
3
+ import { EncryptionKeyNotConfiguredError } from "../errors/auth-internal.errors.mjs";
4
+ import { hasLiveAccessToken, recordDropAt } from "./orchestrated-token.crypto.mjs";
5
+ var InMemoryOrchestratedTokenStore = class {
6
+ /** Token storage: Map<compositeKey, ProviderTokenRecord> */
7
+ tokens = /* @__PURE__ */ new Map();
8
+ /** Encryption key for secure storage */
9
+ encryptionKey;
10
+ /** Derived keys cache for HKDF */
11
+ derivedKeys = /* @__PURE__ */ new Map();
12
+ /** Cleanup interval timer */
13
+ cleanupTimer;
14
+ /** Default TTL for records */
15
+ defaultTtlMs;
16
+ /** How long before its expiry a refreshable access token stops being returned */
17
+ refreshSkewMs;
18
+ constructor(options = {}) {
19
+ this.encryptionKey = options.encryptionKey;
20
+ this.defaultTtlMs = options.defaultTtlMs;
21
+ this.refreshSkewMs = options.refreshSkewMs ?? 0;
22
+ const cleanupIntervalMs = options.cleanupIntervalMs ?? 6e4;
23
+ this.cleanupTimer = setInterval(() => {
24
+ void this.cleanup();
25
+ }, cleanupIntervalMs);
26
+ if (this.cleanupTimer.unref) {
27
+ this.cleanupTimer.unref();
28
+ }
29
+ }
30
+ /**
31
+ * Build composite key from authorizationId and providerId
32
+ */
33
+ buildKey(authorizationId, providerId) {
34
+ return `${authorizationId}:${providerId}`;
35
+ }
36
+ /**
37
+ * Derive encryption key for a specific composite key using HKDF
38
+ */
39
+ async deriveKeyForRecord(compositeKey) {
40
+ if (!this.encryptionKey) {
41
+ throw new EncryptionKeyNotConfiguredError();
42
+ }
43
+ const cached = this.derivedKeys.get(compositeKey);
44
+ if (cached) {
45
+ return cached;
46
+ }
47
+ const info = new TextEncoder().encode(`orchestrated-token:${compositeKey}`);
48
+ const salt = new TextEncoder().encode("frontmcp-token-store");
49
+ const derivedKey = hkdfSha256(this.encryptionKey, salt, info, 32);
50
+ this.derivedKeys.set(compositeKey, derivedKey);
51
+ return derivedKey;
52
+ }
53
+ /**
54
+ * Encrypt a token record
55
+ */
56
+ async encryptRecord(compositeKey, record) {
57
+ const key = await this.deriveKeyForRecord(compositeKey);
58
+ const plaintext = JSON.stringify(record);
59
+ const iv = randomBytes(12);
60
+ const { ciphertext, tag } = encryptAesGcm(key, new TextEncoder().encode(plaintext), iv);
61
+ return JSON.stringify({
62
+ iv: Buffer.from(iv).toString("base64url"),
63
+ tag: Buffer.from(tag).toString("base64url"),
64
+ data: Buffer.from(ciphertext).toString("base64url")
65
+ });
66
+ }
67
+ /**
68
+ * Decrypt a token record
69
+ */
70
+ async decryptRecord(compositeKey, encrypted) {
71
+ const key = await this.deriveKeyForRecord(compositeKey);
72
+ const { iv, tag, data } = JSON.parse(encrypted);
73
+ const ivBytes = Buffer.from(iv, "base64url");
74
+ const tagBytes = Buffer.from(tag, "base64url");
75
+ const ciphertextBytes = Buffer.from(data, "base64url");
76
+ const plaintext = decryptAesGcm(key, ciphertextBytes, ivBytes, tagBytes);
77
+ return JSON.parse(new TextDecoder().decode(plaintext));
78
+ }
79
+ /**
80
+ * Get raw record (handles encryption if enabled)
81
+ */
82
+ async getRecord(authorizationId, providerId) {
83
+ const key = this.buildKey(authorizationId, providerId);
84
+ const stored = this.tokens.get(key);
85
+ if (!stored) {
86
+ return null;
87
+ }
88
+ let record;
89
+ if (this.encryptionKey) {
90
+ if (typeof stored !== "string") {
91
+ return null;
92
+ }
93
+ try {
94
+ record = await this.decryptRecord(key, stored);
95
+ } catch {
96
+ this.tokens.delete(key);
97
+ return null;
98
+ }
99
+ } else {
100
+ record = stored;
101
+ }
102
+ const dropAt = recordDropAt(record);
103
+ if (dropAt && dropAt < Date.now()) {
104
+ this.tokens.delete(key);
105
+ return null;
106
+ }
107
+ return record;
108
+ }
109
+ /**
110
+ * Retrieve decrypted access token for a provider, or null once it has expired (a record with a
111
+ * refresh token keeps that, to renew it)
112
+ */
113
+ async getAccessToken(authorizationId, providerId) {
114
+ const record = await this.getRecord(authorizationId, providerId);
115
+ return record && hasLiveAccessToken(record, this.refreshSkewMs) ? record.accessToken : null;
116
+ }
117
+ /**
118
+ * Retrieve decrypted refresh token for a provider
119
+ */
120
+ async getRefreshToken(authorizationId, providerId) {
121
+ const record = await this.getRecord(authorizationId, providerId);
122
+ return record?.refreshToken ?? null;
123
+ }
124
+ /**
125
+ * Store tokens for a provider
126
+ */
127
+ async storeTokens(authorizationId, providerId, tokens) {
128
+ const key = this.buildKey(authorizationId, providerId);
129
+ const now = Date.now();
130
+ const record = {
131
+ accessToken: tokens.accessToken,
132
+ refreshToken: tokens.refreshToken,
133
+ expiresAt: tokens.expiresAt ?? (this.defaultTtlMs ? now + this.defaultTtlMs : void 0),
134
+ createdAt: now,
135
+ updatedAt: now
136
+ };
137
+ if (this.encryptionKey) {
138
+ const encrypted = await this.encryptRecord(key, record);
139
+ this.tokens.set(key, encrypted);
140
+ } else {
141
+ this.tokens.set(key, record);
142
+ }
143
+ }
144
+ /**
145
+ * Delete tokens for a provider
146
+ */
147
+ async deleteTokens(authorizationId, providerId) {
148
+ const key = this.buildKey(authorizationId, providerId);
149
+ this.tokens.delete(key);
150
+ this.derivedKeys.delete(key);
151
+ }
152
+ /**
153
+ * Check if tokens exist for a provider
154
+ */
155
+ async hasTokens(authorizationId, providerId) {
156
+ const record = await this.getRecord(authorizationId, providerId);
157
+ return record !== null;
158
+ }
159
+ /**
160
+ * Delete all tokens for an authorization
161
+ */
162
+ async deleteAllForAuthorization(authorizationId) {
163
+ const prefix = `${authorizationId}:`;
164
+ for (const key of this.tokens.keys()) {
165
+ if (key.startsWith(prefix)) {
166
+ this.tokens.delete(key);
167
+ this.derivedKeys.delete(key);
168
+ }
169
+ }
170
+ }
171
+ /**
172
+ * Get all provider IDs for an authorization
173
+ */
174
+ async getProviderIds(authorizationId) {
175
+ const prefix = `${authorizationId}:`;
176
+ const providerIds = [];
177
+ for (const key of this.tokens.keys()) {
178
+ if (key.startsWith(prefix)) {
179
+ const providerId = key.slice(prefix.length);
180
+ const record = await this.getRecord(authorizationId, providerId);
181
+ if (record) {
182
+ providerIds.push(providerId);
183
+ }
184
+ }
185
+ }
186
+ return providerIds;
187
+ }
188
+ /**
189
+ * Clean up expired tokens
190
+ */
191
+ async cleanup() {
192
+ const now = Date.now();
193
+ const keysToDelete = [];
194
+ for (const [key, stored] of this.tokens.entries()) {
195
+ let record = null;
196
+ if (this.encryptionKey) {
197
+ if (typeof stored === "string") {
198
+ try {
199
+ record = await this.decryptRecord(key, stored);
200
+ } catch {
201
+ keysToDelete.push(key);
202
+ continue;
203
+ }
204
+ }
205
+ } else {
206
+ record = stored;
207
+ }
208
+ const dropAt = record ? recordDropAt(record) : void 0;
209
+ if (dropAt && dropAt < now) {
210
+ keysToDelete.push(key);
211
+ }
212
+ }
213
+ for (const key of keysToDelete) {
214
+ this.tokens.delete(key);
215
+ this.derivedKeys.delete(key);
216
+ }
217
+ }
218
+ /**
219
+ * Stop the cleanup timer
220
+ */
221
+ dispose() {
222
+ if (this.cleanupTimer) {
223
+ clearInterval(this.cleanupTimer);
224
+ this.cleanupTimer = void 0;
225
+ }
226
+ }
227
+ /**
228
+ * Get total number of stored token records (for testing/monitoring)
229
+ */
230
+ get size() {
231
+ return this.tokens.size;
232
+ }
233
+ /**
234
+ * Clear all tokens (for testing)
235
+ */
236
+ clear() {
237
+ this.tokens.clear();
238
+ this.derivedKeys.clear();
239
+ }
240
+ /**
241
+ * Migrate tokens from one authorization ID to another.
242
+ * Used when tokens are stored with a pending ID during federated auth
243
+ * and need to be accessible under the real authorization ID.
244
+ *
245
+ * @param fromAuthId - Source authorization ID (e.g., "pending:abc123")
246
+ * @param toAuthId - Target authorization ID (e.g., "def456")
247
+ */
248
+ async migrateTokens(fromAuthId, toAuthId) {
249
+ for (const oldKey of await this.copyRecords(fromAuthId, toAuthId)) {
250
+ this.tokens.delete(oldKey);
251
+ this.derivedKeys.delete(oldKey);
252
+ }
253
+ }
254
+ /**
255
+ * Copy tokens from one authorization ID to another, keeping them under the source ID too.
256
+ */
257
+ async copyTokens(fromAuthId, toAuthId) {
258
+ await this.copyRecords(fromAuthId, toAuthId);
259
+ }
260
+ /** Copy every record of `fromAuthId` to `toAuthId`, skipping a corrupted one; returns the keys copied. */
261
+ async copyRecords(fromAuthId, toAuthId) {
262
+ const prefix = `${fromAuthId}:`;
263
+ const sourceKeys = [...this.tokens.keys()].filter((key) => key.startsWith(prefix));
264
+ const copiedKeys = [];
265
+ for (const oldKey of sourceKeys) {
266
+ const stored = this.tokens.get(oldKey);
267
+ if (!stored) continue;
268
+ const newKey = this.buildKey(toAuthId, oldKey.slice(prefix.length));
269
+ if (this.encryptionKey) {
270
+ try {
271
+ const record = await this.decryptRecord(oldKey, stored);
272
+ this.tokens.set(newKey, await this.encryptRecord(newKey, record));
273
+ } catch {
274
+ continue;
275
+ }
276
+ } else {
277
+ this.tokens.set(newKey, stored);
278
+ }
279
+ copiedKeys.push(oldKey);
280
+ }
281
+ return copiedKeys;
282
+ }
283
+ };
284
+ export {
285
+ InMemoryOrchestratedTokenStore
286
+ };
@@ -0,0 +1,37 @@
1
+ // libs/auth/src/session/pending-login.ts
2
+ import {
3
+ base64urlDecode,
4
+ base64urlEncode,
5
+ decryptAesGcm,
6
+ encryptAesGcm,
7
+ hkdfSha256,
8
+ randomBytes
9
+ } from "@frontmcp/utils";
10
+ var SALT = new TextEncoder().encode("frontmcp-pending-login");
11
+ function keyFor(secret, pendingAuthId) {
12
+ return hkdfSha256(secret, SALT, new TextEncoder().encode(`pending-login:${pendingAuthId}`), 32);
13
+ }
14
+ function sealPendingLogin(secret, pendingAuthId, state) {
15
+ const iv = randomBytes(12);
16
+ const { ciphertext, tag } = encryptAesGcm(
17
+ keyFor(secret, pendingAuthId),
18
+ new TextEncoder().encode(JSON.stringify(state)),
19
+ iv
20
+ );
21
+ return [iv, tag, ciphertext].map((part) => base64urlEncode(part)).join(".");
22
+ }
23
+ function openPendingLogin(secret, pendingAuthId, sealed) {
24
+ try {
25
+ const [iv, tag, ciphertext] = sealed.split(".").map((part) => base64urlDecode(part));
26
+ if (!iv || !tag || !ciphertext) return void 0;
27
+ const plaintext = decryptAesGcm(keyFor(secret, pendingAuthId), ciphertext, iv, tag);
28
+ const state = JSON.parse(new TextDecoder().decode(plaintext));
29
+ return typeof state?.sub === "string" && state.sub.length > 0 ? state : void 0;
30
+ } catch {
31
+ return void 0;
32
+ }
33
+ }
34
+ export {
35
+ openPendingLogin,
36
+ sealPendingLogin
37
+ };