@frontmcp/auth 1.3.0 → 1.4.0
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.
- package/authorities/authorities.profiles.d.ts +14 -1
- package/authorities/authorities.profiles.d.ts.map +1 -1
- package/authorization/dcr-client.registry.d.ts +83 -0
- package/authorization/dcr-client.registry.d.ts.map +1 -0
- package/authorization/index.d.ts +2 -0
- package/authorization/index.d.ts.map +1 -1
- package/cimd/cimd.service.d.ts +2 -9
- package/cimd/cimd.service.d.ts.map +1 -1
- package/common/session.types.d.ts +4 -4
- package/esm/index.mjs +1821 -214
- package/esm/package.json +5 -5
- package/index.d.ts +5 -5
- package/index.d.ts.map +1 -1
- package/index.js +2030 -383
- package/jwks/jwks.service.d.ts +2 -4
- package/jwks/jwks.service.d.ts.map +1 -1
- package/options/app-auth.schema.d.ts +226 -0
- package/options/app-auth.schema.d.ts.map +1 -1
- package/options/index.d.ts +4 -4
- package/options/index.d.ts.map +1 -1
- package/options/interfaces.d.ts +580 -4
- package/options/interfaces.d.ts.map +1 -1
- package/options/orchestrated.schema.d.ts +492 -1
- package/options/orchestrated.schema.d.ts.map +1 -1
- package/options/schema.d.ts +226 -0
- package/options/schema.d.ts.map +1 -1
- package/options/shared.schemas.d.ts +237 -2
- package/options/shared.schemas.d.ts.map +1 -1
- package/package.json +5 -5
- package/session/authorization.store.d.ts +117 -47
- package/session/authorization.store.d.ts.map +1 -1
- package/session/consent.store.d.ts +86 -0
- package/session/consent.store.d.ts.map +1 -0
- package/session/credential-resume-link.d.ts +64 -0
- package/session/credential-resume-link.d.ts.map +1 -0
- package/session/credentials-accessor.d.ts +102 -0
- package/session/credentials-accessor.d.ts.map +1 -0
- package/session/credentials-accessor.impl.d.ts +46 -0
- package/session/credentials-accessor.impl.d.ts.map +1 -0
- package/session/index.d.ts +32 -2
- package/session/index.d.ts.map +1 -1
- package/session/orchestrated-token.crypto.d.ts +33 -0
- package/session/orchestrated-token.crypto.d.ts.map +1 -0
- package/session/secure-store-accessor.d.ts +77 -0
- package/session/secure-store-accessor.d.ts.map +1 -0
- package/session/secure-store-accessor.impl.d.ts +61 -0
- package/session/secure-store-accessor.impl.d.ts.map +1 -0
- package/session/secure-store-backends.d.ts +75 -0
- package/session/secure-store-backends.d.ts.map +1 -0
- package/session/secure-store.d.ts +89 -0
- package/session/secure-store.d.ts.map +1 -0
- package/session/secure-store.factory.d.ts +62 -0
- package/session/secure-store.factory.d.ts.map +1 -0
- package/session/session-credential-vault.d.ts +153 -0
- package/session/session-credential-vault.d.ts.map +1 -0
- package/session/session-credential-vault.factory.d.ts +50 -0
- package/session/session-credential-vault.factory.d.ts.map +1 -0
- package/session/storage-authorization.store.d.ts +51 -0
- package/session/storage-authorization.store.d.ts.map +1 -0
- package/session/storage-consent.store.d.ts +33 -0
- package/session/storage-consent.store.d.ts.map +1 -0
- package/session/storage-federated-auth.session.d.ts +36 -0
- package/session/storage-federated-auth.session.d.ts.map +1 -0
- package/session/storage-orchestrated-token.store.d.ts +72 -0
- package/session/storage-orchestrated-token.store.d.ts.map +1 -0
- package/session/token-storage.factory.d.ts +46 -0
- package/session/token-storage.factory.d.ts.map +1 -0
- package/ui/index.d.ts +3 -2
- package/ui/index.d.ts.map +1 -1
- package/ui/local-login.helper.d.ts +31 -0
- package/ui/local-login.helper.d.ts.map +1 -0
- package/ui/templates.d.ts +118 -2
- package/ui/templates.d.ts.map +1 -1
- package/vault/auth-providers.types.d.ts +3 -3
package/index.js
CHANGED
|
@@ -244,6 +244,7 @@ __export(index_exports, {
|
|
|
244
244
|
AuthorityDeniedError: () => AuthorityDeniedError,
|
|
245
245
|
AuthorizationBase: () => AuthorizationBase,
|
|
246
246
|
CDN: () => CDN,
|
|
247
|
+
CREDENTIALS_ACCESSOR: () => CREDENTIALS_ACCESSOR,
|
|
247
248
|
CimdClientIdMismatchError: () => CimdClientIdMismatchError,
|
|
248
249
|
CimdDisabledError: () => CimdDisabledError,
|
|
249
250
|
CimdError: () => CimdError,
|
|
@@ -255,12 +256,16 @@ __export(index_exports, {
|
|
|
255
256
|
CredentialCache: () => CredentialCache,
|
|
256
257
|
CredentialProviderAlreadyRegisteredError: () => CredentialProviderAlreadyRegisteredError,
|
|
257
258
|
CredentialStorageError: () => CredentialStorageError,
|
|
259
|
+
CredentialsAccessorImpl: () => CredentialsAccessorImpl,
|
|
260
|
+
DEFAULT_RESUME_TTL_MS: () => DEFAULT_RESUME_TTL_MS,
|
|
258
261
|
DEFAULT_THEME: () => DEFAULT_THEME,
|
|
262
|
+
DcrClientRegistry: () => DcrClientRegistry,
|
|
259
263
|
EagerCredentialLoader: () => EagerCredentialLoader,
|
|
260
264
|
ElicitationSecretRequiredError: () => ElicitationSecretRequiredError,
|
|
261
265
|
EncryptedRedisVault: () => EncryptedRedisVault,
|
|
262
|
-
EncryptedStorageError: () =>
|
|
263
|
-
|
|
266
|
+
EncryptedStorageError: () => import_utils22.EncryptedStorageError,
|
|
267
|
+
EncryptedStorageSecureStoreBackend: () => EncryptedStorageSecureStoreBackend,
|
|
268
|
+
EncryptedTypedStorage: () => import_utils22.EncryptedTypedStorage,
|
|
264
269
|
EncryptionContextNotSetError: () => EncryptionContextNotSetError,
|
|
265
270
|
EncryptionKeyNotConfiguredError: () => EncryptionKeyNotConfiguredError,
|
|
266
271
|
FetchCredentialMiddleware: () => FetchCredentialMiddleware,
|
|
@@ -268,6 +273,7 @@ __export(index_exports, {
|
|
|
268
273
|
InMemoryAuthorizationStore: () => InMemoryAuthorizationStore,
|
|
269
274
|
InMemoryAuthorizationVault: () => InMemoryAuthorizationVault,
|
|
270
275
|
InMemoryCimdCache: () => InMemoryCimdCache,
|
|
276
|
+
InMemoryConsentStore: () => InMemoryConsentStore,
|
|
271
277
|
InMemoryFederatedAuthSessionStore: () => InMemoryFederatedAuthSessionStore,
|
|
272
278
|
InMemoryOrchestratedTokenStore: () => InMemoryOrchestratedTokenStore,
|
|
273
279
|
InMemoryStoreRequiredError: () => InMemoryStoreRequiredError,
|
|
@@ -285,11 +291,18 @@ __export(index_exports, {
|
|
|
285
291
|
RedirectUriMismatchError: () => RedirectUriMismatchError,
|
|
286
292
|
RedisAuthorizationStore: () => RedisAuthorizationStore,
|
|
287
293
|
RedisSessionStore: () => RedisSessionStore,
|
|
294
|
+
SECURE_STORE_ACCESSOR: () => SECURE_STORE_ACCESSOR,
|
|
288
295
|
ScopeDeniedError: () => ScopeDeniedError,
|
|
296
|
+
SecureStoreAccessorImpl: () => SecureStoreAccessorImpl,
|
|
297
|
+
SessionCredentialVault: () => SessionCredentialVault,
|
|
289
298
|
SessionIdEmptyError: () => SessionIdEmptyError,
|
|
290
299
|
SessionRateLimiter: () => SessionRateLimiter,
|
|
291
300
|
SessionSecretRequiredError: () => SessionSecretRequiredError,
|
|
301
|
+
StorageAuthorizationStore: () => StorageAuthorizationStore,
|
|
292
302
|
StorageAuthorizationVault: () => StorageAuthorizationVault,
|
|
303
|
+
StorageConsentStore: () => StorageConsentStore,
|
|
304
|
+
StorageFederatedAuthSessionStore: () => StorageFederatedAuthSessionStore,
|
|
305
|
+
StorageOrchestratedTokenStore: () => StorageOrchestratedTokenStore,
|
|
293
306
|
StorageTokenStore: () => StorageTokenStore,
|
|
294
307
|
TinyTtlCache: () => TinyTtlCache,
|
|
295
308
|
TokenLeakDetectedError: () => TokenLeakDetectedError,
|
|
@@ -298,7 +311,7 @@ __export(index_exports, {
|
|
|
298
311
|
TokenVault: () => TokenVault,
|
|
299
312
|
TransparentAuthorization: () => TransparentAuthorization,
|
|
300
313
|
TransportIdGenerator: () => TransportIdGenerator,
|
|
301
|
-
TypedStorage: () =>
|
|
314
|
+
TypedStorage: () => import_utils21.TypedStorage,
|
|
302
315
|
VaultEncryption: () => VaultEncryption,
|
|
303
316
|
VaultLoadError: () => VaultLoadError,
|
|
304
317
|
VaultNotFoundError: () => VaultNotFoundError,
|
|
@@ -321,6 +334,7 @@ __export(index_exports, {
|
|
|
321
334
|
authProviderMappingSchema: () => authProviderMappingSchema,
|
|
322
335
|
authProvidersVaultOptionsSchema: () => authProvidersVaultOptionsSchema,
|
|
323
336
|
authUserSchema: () => authUserSchema,
|
|
337
|
+
authenticateFnSchema: () => authenticateFnSchema,
|
|
324
338
|
authoritiesClaimsMappingSchema: () => authoritiesClaimsMappingSchema,
|
|
325
339
|
authoritiesConfigSchema: () => authoritiesConfigSchema,
|
|
326
340
|
authoritiesMetadataSchema: () => authoritiesMetadataSchema,
|
|
@@ -335,7 +349,11 @@ __export(index_exports, {
|
|
|
335
349
|
bearerApplier: () => bearerApplier,
|
|
336
350
|
bearerCredentialSchema: () => bearerCredentialSchema,
|
|
337
351
|
buildAuthContext: () => buildAuthContext,
|
|
352
|
+
buildCodeRecord: () => buildCodeRecord,
|
|
353
|
+
buildConnectPage: () => buildConnectPage,
|
|
354
|
+
buildConnectSuccessPage: () => buildConnectSuccessPage,
|
|
338
355
|
buildConsentPage: () => buildConsentPage,
|
|
356
|
+
buildCredentialResumeUrl: () => buildCredentialResumeUrl,
|
|
339
357
|
buildErrorPage: () => buildErrorPage,
|
|
340
358
|
buildFederatedLoginPage: () => buildFederatedLoginPage,
|
|
341
359
|
buildIncrementalAuthPage: () => buildIncrementalAuthPage,
|
|
@@ -343,7 +361,9 @@ __export(index_exports, {
|
|
|
343
361
|
buildInvalidRequestHeader: () => buildInvalidRequestHeader,
|
|
344
362
|
buildInvalidTokenHeader: () => buildInvalidTokenHeader,
|
|
345
363
|
buildLoginPage: () => buildLoginPage,
|
|
364
|
+
buildPendingRecord: () => buildPendingRecord,
|
|
346
365
|
buildPrmUrl: () => buildPrmUrl,
|
|
366
|
+
buildRefreshTokenRecord: () => buildRefreshTokenRecord,
|
|
347
367
|
buildToolConsentPage: () => buildToolConsentPage,
|
|
348
368
|
buildUnauthorizedHeader: () => buildUnauthorizedHeader,
|
|
349
369
|
buildWwwAuthenticate: () => buildWwwAuthenticate,
|
|
@@ -356,6 +376,7 @@ __export(index_exports, {
|
|
|
356
376
|
clientMetadataDocumentSchema: () => clientMetadataDocumentSchema,
|
|
357
377
|
completeCurrentProvider: () => completeCurrentProvider,
|
|
358
378
|
consentConfigSchema: () => consentConfigSchema,
|
|
379
|
+
consentRecordKey: () => consentRecordKey,
|
|
359
380
|
consentSelectionSchema: () => consentSelectionSchema,
|
|
360
381
|
consentStateSchema: () => consentStateSchema,
|
|
361
382
|
consentToolItemSchema: () => consentToolItemSchema,
|
|
@@ -363,15 +384,18 @@ __export(index_exports, {
|
|
|
363
384
|
createEncryptedVault: () => createEncryptedVault,
|
|
364
385
|
createFederatedAuthSession: () => createFederatedAuthSession,
|
|
365
386
|
createLayout: () => createLayout,
|
|
387
|
+
createSecureStore: () => createSecureStore,
|
|
388
|
+
createSessionCredentialVault: () => createSessionCredentialVault,
|
|
389
|
+
createTokenStorageAdapter: () => createTokenStorageAdapter,
|
|
366
390
|
credentialProviderConfigSchema: () => credentialProviderConfigSchema,
|
|
367
391
|
credentialSchema: () => credentialSchema,
|
|
368
392
|
credentialScopeSchema: () => credentialScopeSchema,
|
|
369
393
|
credentialTypeSchema: () => credentialTypeSchema,
|
|
370
394
|
customCredentialSchema: () => customCredentialSchema,
|
|
371
395
|
decodeJwtPayloadSafe: () => decodeJwtPayloadSafe,
|
|
372
|
-
decryptAesGcm: () =>
|
|
396
|
+
decryptAesGcm: () => import_utils32.decryptAesGcm,
|
|
373
397
|
decryptSessionJson: () => decryptSessionJson,
|
|
374
|
-
decryptValue: () =>
|
|
398
|
+
decryptValue: () => import_utils32.decryptValue,
|
|
375
399
|
defaultSessionRateLimiter: () => defaultSessionRateLimiter,
|
|
376
400
|
deriveAuthorizationId: () => deriveAuthorizationId,
|
|
377
401
|
deriveExpectedAudience: () => deriveExpectedAudience,
|
|
@@ -379,9 +403,9 @@ __export(index_exports, {
|
|
|
379
403
|
deriveTypedUser: () => deriveTypedUser,
|
|
380
404
|
detectAuthProviders: () => detectAuthProviders,
|
|
381
405
|
detectedAuthProviderSchema: () => detectedAuthProviderSchema,
|
|
382
|
-
encryptAesGcm: () =>
|
|
406
|
+
encryptAesGcm: () => import_utils32.encryptAesGcm,
|
|
383
407
|
encryptJson: () => encryptJson,
|
|
384
|
-
encryptValue: () =>
|
|
408
|
+
encryptValue: () => import_utils32.encryptValue,
|
|
385
409
|
encryptedBlobSchema: () => encryptedBlobSchema,
|
|
386
410
|
encryptedDataSchema: () => encryptedDataSchema,
|
|
387
411
|
encryptedVaultEntrySchema: () => encryptedVaultEntrySchema,
|
|
@@ -396,7 +420,9 @@ __export(index_exports, {
|
|
|
396
420
|
federatedSelectionSchema: () => federatedSelectionSchema,
|
|
397
421
|
flatRemoteProviderFields: () => flatRemoteProviderFields,
|
|
398
422
|
fromSessionRecord: () => fromSessionRecord,
|
|
423
|
+
generateAuthorizationCode: () => generateAuthorizationCode,
|
|
399
424
|
generatePkceChallenge: () => generatePkceChallenge,
|
|
425
|
+
generateRefreshTokenValue: () => generateRefreshTokenValue,
|
|
400
426
|
getCredentialOptionsSchema: () => getCredentialOptionsSchema,
|
|
401
427
|
getKey: () => getKey,
|
|
402
428
|
getNextProvider: () => getNextProvider,
|
|
@@ -405,7 +431,7 @@ __export(index_exports, {
|
|
|
405
431
|
getTokenSignatureFingerprint: () => getTokenSignatureFingerprint,
|
|
406
432
|
hasOnlyLocalhostRedirectUris: () => hasOnlyLocalhostRedirectUris,
|
|
407
433
|
headerApplier: () => headerApplier,
|
|
408
|
-
hkdfSha256: () =>
|
|
434
|
+
hkdfSha256: () => import_utils32.hkdfSha256,
|
|
409
435
|
incrementalAuthConfigSchema: () => incrementalAuthConfigSchema,
|
|
410
436
|
isCimdClientId: () => isCimdClientId,
|
|
411
437
|
isFrontMcpCredentials: () => isFrontMcpCredentials,
|
|
@@ -414,12 +440,15 @@ __export(index_exports, {
|
|
|
414
440
|
isOrchestratedLocal: () => isOrchestratedLocal,
|
|
415
441
|
isOrchestratedMode: () => isOrchestratedMode,
|
|
416
442
|
isOrchestratedRemote: () => isOrchestratedRemote,
|
|
443
|
+
isPersistentTokenStorage: () => isPersistentTokenStorage,
|
|
417
444
|
isPublicMode: () => isPublicMode,
|
|
445
|
+
isRedisTokenStorage: () => isRedisTokenStorage,
|
|
418
446
|
isRemoteMode: () => isRemoteMode,
|
|
419
447
|
isSessionComplete: () => isSessionComplete,
|
|
420
|
-
isSignedSession: () =>
|
|
448
|
+
isSignedSession: () => import_utils14.isSignedData,
|
|
421
449
|
isSoonExpiring: () => isSoonExpiring,
|
|
422
450
|
isSoonExpiringProvider: () => isSoonExpiringProvider,
|
|
451
|
+
isSqliteTokenStorage: () => isSqliteTokenStorage,
|
|
423
452
|
isTransparentMode: () => isTransparentMode,
|
|
424
453
|
jsonWebKeySetSchema: () => jsonWebKeySetSchema,
|
|
425
454
|
jwkParametersSchema: () => jwkParametersSchema,
|
|
@@ -428,7 +457,10 @@ __export(index_exports, {
|
|
|
428
457
|
llmSafeAuthContextSchema: () => llmSafeAuthContextSchema,
|
|
429
458
|
loadingStrategySchema: () => loadingStrategySchema,
|
|
430
459
|
localAuthSchema: () => localAuthSchema,
|
|
460
|
+
localDcrClientSchema: () => localDcrClientSchema,
|
|
461
|
+
localDcrConfigSchema: () => localDcrConfigSchema,
|
|
431
462
|
localSigningConfigSchema: () => localSigningConfigSchema,
|
|
463
|
+
loginConfigSchema: () => loginConfigSchema,
|
|
432
464
|
mtlsCredentialSchema: () => mtlsCredentialSchema,
|
|
433
465
|
noopLogger: () => noopLogger,
|
|
434
466
|
normalizeIssuer: () => normalizeIssuer,
|
|
@@ -454,14 +486,20 @@ __export(index_exports, {
|
|
|
454
486
|
redisVaultEntrySchema: () => redisVaultEntrySchema,
|
|
455
487
|
remoteAuthSchema: () => remoteAuthSchema,
|
|
456
488
|
remoteProviderConfigSchema: () => remoteProviderConfigSchema,
|
|
489
|
+
renderLocalLoginPage: () => renderLocalLoginPage,
|
|
457
490
|
renderToHtml: () => renderToHtml,
|
|
458
491
|
resetCachedKey: () => resetCachedKey,
|
|
459
492
|
resolveRequiredScopes: () => resolveRequiredScopes,
|
|
460
493
|
resourceIdRefSchema: () => resourceIdRefSchema,
|
|
461
494
|
safeDecrypt: () => safeDecrypt,
|
|
495
|
+
secureStoreConfigSchema: () => secureStoreConfigSchema,
|
|
496
|
+
secureStoreCustomBackendSchema: () => secureStoreCustomBackendSchema,
|
|
497
|
+
secureStoreEncryptionSchema: () => secureStoreEncryptionSchema,
|
|
498
|
+
secureStoreScopeSchema: () => secureStoreScopeSchema,
|
|
462
499
|
serviceAccountCredentialSchema: () => serviceAccountCredentialSchema,
|
|
463
500
|
sessionIdPayloadSchema: () => sessionIdPayloadSchema,
|
|
464
501
|
sessionJwtPayloadSchema: () => sessionJwtPayloadSchema,
|
|
502
|
+
signCredentialResumeToken: () => signCredentialResumeToken,
|
|
465
503
|
signSession: () => signSession,
|
|
466
504
|
skippedAppBehaviorSchema: () => skippedAppBehaviorSchema,
|
|
467
505
|
sseTransportStateSchema: () => sseTransportStateSchema,
|
|
@@ -472,20 +510,24 @@ __export(index_exports, {
|
|
|
472
510
|
storedSessionSchema: () => storedSessionSchema,
|
|
473
511
|
streamableHttpTransportStateSchema: () => streamableHttpTransportStateSchema,
|
|
474
512
|
toEpochSeconds: () => toEpochSeconds,
|
|
513
|
+
toLoginExtraFields: () => toLoginExtraFields,
|
|
475
514
|
toSessionRecord: () => toSessionRecord,
|
|
476
515
|
tokenRefreshConfigSchema: () => tokenRefreshConfigSchema,
|
|
477
516
|
tokenStorageConfigSchema: () => tokenStorageConfigSchema,
|
|
517
|
+
tokenStorageSqliteSchema: () => tokenStorageSqliteSchema,
|
|
478
518
|
transparentAuthOptionsSchema: () => transparentAuthOptionsSchema,
|
|
479
519
|
transportProtocolSchema: () => transportProtocolSchema,
|
|
480
520
|
transportSessionSchema: () => transportSessionSchema,
|
|
481
521
|
transportStateSchema: () => transportStateSchema,
|
|
482
522
|
trimSlash: () => trimSlash,
|
|
483
523
|
tryJwtExp: () => tryJwtExp,
|
|
524
|
+
upstreamProviderSchema: () => upstreamProviderSchema,
|
|
484
525
|
userClaimSchema: () => userClaimSchema,
|
|
485
526
|
validateAudience: () => validateAudience,
|
|
486
527
|
validateClientIdUrl: () => validateClientIdUrl,
|
|
487
528
|
vaultConsentRecordSchema: () => vaultConsentRecordSchema,
|
|
488
529
|
vaultFederatedRecordSchema: () => vaultFederatedRecordSchema,
|
|
530
|
+
verifyCredentialResumeToken: () => verifyCredentialResumeToken,
|
|
489
531
|
verifyOrParseSession: () => verifyOrParseSession,
|
|
490
532
|
verifyPkce: () => verifyPkce,
|
|
491
533
|
verifySession: () => verifySession,
|
|
@@ -497,6 +539,18 @@ module.exports = __toCommonJS(index_exports);
|
|
|
497
539
|
var import_jose = require("jose");
|
|
498
540
|
var import_utils = require("@frontmcp/utils");
|
|
499
541
|
|
|
542
|
+
// libs/auth/src/common/auth-logger.interface.ts
|
|
543
|
+
var noopAuthLogger = {
|
|
544
|
+
info() {
|
|
545
|
+
},
|
|
546
|
+
warn() {
|
|
547
|
+
},
|
|
548
|
+
error() {
|
|
549
|
+
},
|
|
550
|
+
debug() {
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
|
|
500
554
|
// libs/auth/src/jwks/jwks.utils.ts
|
|
501
555
|
function trimSlash(s) {
|
|
502
556
|
const str = s ?? "";
|
|
@@ -526,18 +580,6 @@ function decodeJwtPayloadSafe(token) {
|
|
|
526
580
|
}
|
|
527
581
|
}
|
|
528
582
|
|
|
529
|
-
// libs/auth/src/common/auth-logger.interface.ts
|
|
530
|
-
var noopAuthLogger = {
|
|
531
|
-
info() {
|
|
532
|
-
},
|
|
533
|
-
warn() {
|
|
534
|
-
},
|
|
535
|
-
error() {
|
|
536
|
-
},
|
|
537
|
-
debug() {
|
|
538
|
-
}
|
|
539
|
-
};
|
|
540
|
-
|
|
541
583
|
// libs/auth/src/jwks/jwks.service.ts
|
|
542
584
|
var WEAK_KEY_WARNING = `
|
|
543
585
|
\u26A0\uFE0F SECURITY WARNING: OAuth provider is using an RSA key smaller than 2048 bits.
|
|
@@ -596,28 +638,6 @@ var JwksService = class {
|
|
|
596
638
|
// ===========================================================================
|
|
597
639
|
// Scope-aware verification API
|
|
598
640
|
// ===========================================================================
|
|
599
|
-
/** Verify a token issued by the gateway itself (orchestrated mode). */
|
|
600
|
-
async verifyGatewayToken(token, expectedIssuer) {
|
|
601
|
-
try {
|
|
602
|
-
const payload = decodeJwtPayloadSafe(token);
|
|
603
|
-
if (!payload) {
|
|
604
|
-
return {
|
|
605
|
-
ok: false,
|
|
606
|
-
error: "invalid bearer token"
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
return {
|
|
610
|
-
ok: true,
|
|
611
|
-
issuer: expectedIssuer,
|
|
612
|
-
sub: payload["sub"],
|
|
613
|
-
payload,
|
|
614
|
-
header: (0, import_jose.decodeProtectedHeader)(token)
|
|
615
|
-
};
|
|
616
|
-
} catch (err) {
|
|
617
|
-
const message = err instanceof Error ? err.message : "verification_failed";
|
|
618
|
-
return { ok: false, error: message };
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
641
|
/**
|
|
622
642
|
* Verify a token against candidate transparent providers.
|
|
623
643
|
* Ensures JWKS are available (cached/TTL/AS discovery) per provider.
|
|
@@ -1262,32 +1282,52 @@ function buildFederatedLoginPage(params) {
|
|
|
1262
1282
|
return wideLayout(content, { title: "Select Providers" });
|
|
1263
1283
|
}
|
|
1264
1284
|
function buildToolConsentPage(params) {
|
|
1265
|
-
const {
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1285
|
+
const {
|
|
1286
|
+
tools,
|
|
1287
|
+
clientName,
|
|
1288
|
+
pendingAuthId,
|
|
1289
|
+
csrfToken,
|
|
1290
|
+
callbackPath,
|
|
1291
|
+
userName,
|
|
1292
|
+
userEmail,
|
|
1293
|
+
groupByApp = true,
|
|
1294
|
+
showDescriptions = true,
|
|
1295
|
+
customMessage,
|
|
1296
|
+
allowSelectAll = true,
|
|
1297
|
+
requireSelection = true,
|
|
1298
|
+
defaultSelectedTools,
|
|
1299
|
+
error,
|
|
1300
|
+
hiddenFields = []
|
|
1301
|
+
} = params;
|
|
1302
|
+
const defaultSet = defaultSelectedTools ? new Set(defaultSelectedTools) : void 0;
|
|
1303
|
+
const isChecked = (toolId) => defaultSet ? defaultSet.has(toolId) : true;
|
|
1304
|
+
const checkedCount = tools.filter((t) => isChecked(t.toolId)).length;
|
|
1305
|
+
const renderTool = (tool) => {
|
|
1306
|
+
const desc = showDescriptions && tool.description ? `<p class="text-sm text-gray-500 mt-0.5">${escapeHtml3(tool.description)}</p>` : "";
|
|
1307
|
+
return `<label class="flex items-start gap-3 p-3 bg-white rounded-lg cursor-pointer hover:bg-gray-50">
|
|
1308
|
+
<input type="checkbox" name="tools" value="${escapeHtml3(tool.toolId)}" class="mt-0.5 w-5 h-5 rounded border-gray-300"${isChecked(tool.toolId) ? " checked" : ""}>
|
|
1284
1309
|
<div>
|
|
1285
1310
|
<span class="font-medium text-gray-900">${escapeHtml3(tool.toolName)}</span>
|
|
1286
1311
|
${desc}
|
|
1287
1312
|
</div>
|
|
1288
1313
|
</label>`;
|
|
1289
|
-
|
|
1290
|
-
|
|
1314
|
+
};
|
|
1315
|
+
let toolSection;
|
|
1316
|
+
if (groupByApp) {
|
|
1317
|
+
const toolsByApp = {};
|
|
1318
|
+
for (const tool of tools) {
|
|
1319
|
+
if (!toolsByApp[tool.appId]) {
|
|
1320
|
+
toolsByApp[tool.appId] = { appName: tool.appName, tools: [] };
|
|
1321
|
+
}
|
|
1322
|
+
toolsByApp[tool.appId].tools.push(tool);
|
|
1323
|
+
}
|
|
1324
|
+
const toggleAllButton = allowSelectAll ? `<button type="button" class="text-sm text-blue-600 hover:text-blue-800"
|
|
1325
|
+
onclick="const container = this.closest('.bg-gray-50').querySelector('[data-app]'); const cbs = container.querySelectorAll('input[name=tools]'); const allChecked = [...cbs].every(cb => cb.checked); cbs.forEach(cb => cb.checked = !allChecked); updateCount();">
|
|
1326
|
+
Toggle All
|
|
1327
|
+
</button>` : "";
|
|
1328
|
+
toolSection = Object.entries(toolsByApp).map(([appId, { appName, tools: appTools }]) => {
|
|
1329
|
+
const toolItems = appTools.map(renderTool).join("\n");
|
|
1330
|
+
return `<div class="bg-gray-50 rounded-xl overflow-hidden">
|
|
1291
1331
|
<div class="flex items-center justify-between px-4 py-3 bg-gray-100">
|
|
1292
1332
|
<div class="flex items-center gap-3">
|
|
1293
1333
|
<div class="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center text-white font-bold text-sm">
|
|
@@ -1295,51 +1335,70 @@ function buildToolConsentPage(params) {
|
|
|
1295
1335
|
</div>
|
|
1296
1336
|
<span class="font-semibold text-gray-900">${escapeHtml3(appName)}</span>
|
|
1297
1337
|
</div>
|
|
1298
|
-
|
|
1299
|
-
onclick="const container = this.closest('.bg-gray-50').querySelector('[data-app]'); const cbs = container.querySelectorAll('input[name=tools]'); const allChecked = [...cbs].every(cb => cb.checked); cbs.forEach(cb => cb.checked = !allChecked); updateCount();">
|
|
1300
|
-
Toggle All
|
|
1301
|
-
</button>
|
|
1338
|
+
${toggleAllButton}
|
|
1302
1339
|
</div>
|
|
1303
1340
|
<div class="p-4 space-y-2" data-app="${escapeHtml3(appId)}">
|
|
1304
1341
|
${toolItems}
|
|
1305
1342
|
</div>
|
|
1306
1343
|
</div>`;
|
|
1307
|
-
|
|
1344
|
+
}).join("\n");
|
|
1345
|
+
} else {
|
|
1346
|
+
toolSection = `<div class="bg-gray-50 rounded-xl p-4 space-y-2" data-app="__all__">
|
|
1347
|
+
${tools.map(renderTool).join("\n")}
|
|
1348
|
+
</div>`;
|
|
1349
|
+
}
|
|
1350
|
+
const userInfo = userName || userEmail ? `<div class="p-3 bg-gray-50 rounded-lg mb-6 text-sm">
|
|
1351
|
+
<span class="text-gray-500">Signed in as: </span>
|
|
1352
|
+
<span class="font-medium text-gray-900">${escapeHtml3(userName || userEmail || "")}</span>
|
|
1353
|
+
</div>` : "";
|
|
1354
|
+
const errorBanner = error ? `<div class="mb-6 p-4 bg-red-50 border border-red-200 rounded-lg text-sm text-red-800">${escapeHtml3(error)}</div>` : "";
|
|
1355
|
+
const hiddenInputs = hiddenFields.map((f) => `<input type="hidden" name="${escapeHtml3(f.name)}" value="${escapeHtml3(f.value)}">`).join("\n ");
|
|
1356
|
+
const selectAllControl = allowSelectAll ? `<label class="flex items-center gap-3 cursor-pointer">
|
|
1357
|
+
<input type="checkbox" id="select-all" class="w-5 h-5 rounded border-gray-300"${checkedCount === tools.length && tools.length > 0 ? " checked" : ""}
|
|
1358
|
+
onchange="document.querySelectorAll('input[name=tools]').forEach(cb => cb.checked = this.checked); updateCount();">
|
|
1359
|
+
<span class="text-gray-700">Select all tools</span>
|
|
1360
|
+
</label>` : "<span></span>";
|
|
1361
|
+
const requireSelectionScript = requireSelection ? `
|
|
1362
|
+
const submitBtn = document.getElementById('consent-submit');
|
|
1363
|
+
if (submitBtn) submitBtn.disabled = checked.length === 0;` : "";
|
|
1308
1364
|
const updateCountScript = `
|
|
1309
1365
|
<script>
|
|
1310
1366
|
function updateCount() {
|
|
1311
1367
|
const all = document.querySelectorAll('input[name="tools"]');
|
|
1312
1368
|
const checked = document.querySelectorAll('input[name="tools"]:checked');
|
|
1313
|
-
document.getElementById('selection-count')
|
|
1314
|
-
|
|
1369
|
+
const countEl = document.getElementById('selection-count');
|
|
1370
|
+
if (countEl) countEl.textContent = checked.length + ' of ' + all.length + ' selected';
|
|
1371
|
+
const selectAll = document.getElementById('select-all');
|
|
1372
|
+
if (selectAll) selectAll.checked = all.length > 0 && all.length === checked.length;${requireSelectionScript}
|
|
1315
1373
|
}
|
|
1316
1374
|
document.querySelectorAll('input[name="tools"]').forEach(cb => cb.addEventListener('change', updateCount));
|
|
1375
|
+
updateCount();
|
|
1317
1376
|
</script>`;
|
|
1377
|
+
const subtitle = customMessage ? escapeHtml3(customMessage) : `Choose which tools ${escapeHtml3(clientName)} can access. You can change this later.`;
|
|
1318
1378
|
const content = `
|
|
1319
1379
|
<h1 class="text-3xl font-bold text-gray-900 mb-4">Select Tools to Enable</h1>
|
|
1320
1380
|
<p class="text-gray-600 mb-6">
|
|
1321
|
-
|
|
1381
|
+
${subtitle}
|
|
1322
1382
|
</p>
|
|
1323
1383
|
|
|
1384
|
+
${errorBanner}
|
|
1324
1385
|
${userInfo}
|
|
1325
1386
|
|
|
1326
|
-
<form method="
|
|
1387
|
+
<form method="GET" action="${escapeHtml3(callbackPath)}" id="consent-form">
|
|
1327
1388
|
<input type="hidden" name="csrf" value="${escapeHtml3(csrfToken)}">
|
|
1328
1389
|
<input type="hidden" name="pending_auth_id" value="${escapeHtml3(pendingAuthId)}">
|
|
1390
|
+
<input type="hidden" name="consent_submitted" value="1">
|
|
1391
|
+
${hiddenInputs}
|
|
1329
1392
|
|
|
1330
1393
|
<!-- Select all toggle -->
|
|
1331
1394
|
<div class="flex items-center justify-between mb-6">
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
onchange="document.querySelectorAll('input[name=tools]').forEach(cb => cb.checked = this.checked); updateCount();">
|
|
1335
|
-
<span class="text-gray-700">Select all tools</span>
|
|
1336
|
-
</label>
|
|
1337
|
-
<span id="selection-count" class="text-sm text-gray-500">${tools.length} of ${tools.length} selected</span>
|
|
1395
|
+
${selectAllControl}
|
|
1396
|
+
<span id="selection-count" class="text-sm text-gray-500">${checkedCount} of ${tools.length} selected</span>
|
|
1338
1397
|
</div>
|
|
1339
1398
|
|
|
1340
|
-
<!-- Tool groups
|
|
1399
|
+
<!-- Tool groups -->
|
|
1341
1400
|
<div class="space-y-6 mb-8">
|
|
1342
|
-
${
|
|
1401
|
+
${toolSection}
|
|
1343
1402
|
</div>
|
|
1344
1403
|
|
|
1345
1404
|
<!-- Buttons -->
|
|
@@ -1348,8 +1407,8 @@ function buildToolConsentPage(params) {
|
|
|
1348
1407
|
class="flex-1 px-6 py-3 text-gray-700 bg-gray-100 hover:bg-gray-200 font-medium rounded-lg transition-colors">
|
|
1349
1408
|
Cancel
|
|
1350
1409
|
</button>
|
|
1351
|
-
<button type="submit"
|
|
1352
|
-
class="flex-1 px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors">
|
|
1410
|
+
<button type="submit" id="consent-submit"
|
|
1411
|
+
class="flex-1 px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
|
|
1353
1412
|
Confirm Selection
|
|
1354
1413
|
</button>
|
|
1355
1414
|
</div>
|
|
@@ -1357,22 +1416,42 @@ function buildToolConsentPage(params) {
|
|
|
1357
1416
|
${updateCountScript}`;
|
|
1358
1417
|
return extraWideLayout(content, { title: "Select Tools" });
|
|
1359
1418
|
}
|
|
1419
|
+
function buildLoginFieldHtml(field) {
|
|
1420
|
+
const id = `field-${field.name}`;
|
|
1421
|
+
const safeName = escapeHtml3(field.name);
|
|
1422
|
+
const requiredAttr = field.required ? " required" : "";
|
|
1423
|
+
const labelText = escapeHtml3(field.label ?? field.name);
|
|
1424
|
+
const safeValue = field.value !== void 0 ? escapeHtml3(field.value) : "";
|
|
1425
|
+
if (field.type === "hidden") {
|
|
1426
|
+
return `<input type="hidden" name="${safeName}" value="${safeValue}">`;
|
|
1427
|
+
}
|
|
1428
|
+
const inputClass = "w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
|
|
1429
|
+
let control;
|
|
1430
|
+
if (field.type === "select") {
|
|
1431
|
+
const opts = (field.options ?? []).map(
|
|
1432
|
+
(o) => `<option value="${escapeHtml3(o.value)}"${o.value === field.value ? " selected" : ""}>${escapeHtml3(
|
|
1433
|
+
o.label
|
|
1434
|
+
)}</option>`
|
|
1435
|
+
).join("");
|
|
1436
|
+
control = `<select id="${escapeHtml3(id)}" name="${safeName}"${requiredAttr} class="${inputClass}">${opts}</select>`;
|
|
1437
|
+
} else {
|
|
1438
|
+
const placeholder = field.placeholder ? ` placeholder="${escapeHtml3(field.placeholder)}"` : "";
|
|
1439
|
+
control = `<input type="${escapeHtml3(field.type)}" id="${escapeHtml3(
|
|
1440
|
+
id
|
|
1441
|
+
)}" name="${safeName}"${requiredAttr}${placeholder} value="${safeValue}" class="${inputClass}">`;
|
|
1442
|
+
}
|
|
1443
|
+
return `<div class="mb-4">
|
|
1444
|
+
<label for="${escapeHtml3(id)}" class="block text-sm font-medium text-gray-700 mb-2">${labelText}</label>
|
|
1445
|
+
${control}
|
|
1446
|
+
</div>`;
|
|
1447
|
+
}
|
|
1360
1448
|
function buildLoginPage(params) {
|
|
1361
|
-
const { clientName, scope, pendingAuthId, callbackPath } = params;
|
|
1449
|
+
const { clientName, scope, pendingAuthId, callbackPath, title, subtitle, logoUri, error, extraFields } = params;
|
|
1362
1450
|
const scopesHtml = scope ? `<div class="p-4 bg-gray-50 rounded-lg mb-6">
|
|
1363
1451
|
<p class="text-xs font-medium text-gray-500 uppercase tracking-wide mb-2">Requested permissions</p>
|
|
1364
1452
|
<p class="text-gray-700">${scope.split(" ").map((s) => escapeHtml3(s)).join(", ") || "Default access"}</p>
|
|
1365
1453
|
</div>` : "";
|
|
1366
|
-
const
|
|
1367
|
-
<div class="bg-white rounded-2xl shadow-xl p-8">
|
|
1368
|
-
<h1 class="text-3xl font-bold text-gray-900 mb-2 text-center">Sign In</h1>
|
|
1369
|
-
<p class="text-gray-600 mb-8 text-center">Authorize access to ${escapeHtml3(clientName)}</p>
|
|
1370
|
-
|
|
1371
|
-
${scopesHtml}
|
|
1372
|
-
|
|
1373
|
-
<form method="GET" action="${escapeHtml3(callbackPath)}">
|
|
1374
|
-
<input type="hidden" name="pending_auth_id" value="${escapeHtml3(pendingAuthId)}">
|
|
1375
|
-
|
|
1454
|
+
const defaultFieldsHtml = `
|
|
1376
1455
|
<!-- Email -->
|
|
1377
1456
|
<div class="mb-4">
|
|
1378
1457
|
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">Email</label>
|
|
@@ -1385,7 +1464,27 @@ function buildLoginPage(params) {
|
|
|
1385
1464
|
<label for="name" class="block text-sm font-medium text-gray-700 mb-2">Name (optional)</label>
|
|
1386
1465
|
<input type="text" id="name" name="name" placeholder="Your name"
|
|
1387
1466
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
1388
|
-
</div
|
|
1467
|
+
</div>`;
|
|
1468
|
+
const fieldsHtml = extraFields && extraFields.length > 0 ? extraFields.map((f) => buildLoginFieldHtml(f)).join("\n") : defaultFieldsHtml;
|
|
1469
|
+
const logoHtml = logoUri ? `<div class="flex justify-center mb-4"><img src="${escapeHtml3(
|
|
1470
|
+
logoUri
|
|
1471
|
+
)}" alt="${escapeHtml3(clientName)}" class="h-12 w-12 rounded-lg object-cover"></div>` : "";
|
|
1472
|
+
const errorHtml = error ? `<div class="p-3 mb-6 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">${escapeHtml3(error)}</div>` : "";
|
|
1473
|
+
const headingText = escapeHtml3(title ?? "Sign In");
|
|
1474
|
+
const subtitleText = subtitle ? escapeHtml3(subtitle) : `Authorize access to ${escapeHtml3(clientName)}`;
|
|
1475
|
+
const content = `
|
|
1476
|
+
<div class="bg-white rounded-2xl shadow-xl p-8">
|
|
1477
|
+
${logoHtml}
|
|
1478
|
+
<h1 class="text-3xl font-bold text-gray-900 mb-2 text-center">${headingText}</h1>
|
|
1479
|
+
<p class="text-gray-600 mb-8 text-center">${subtitleText}</p>
|
|
1480
|
+
|
|
1481
|
+
${errorHtml}
|
|
1482
|
+
|
|
1483
|
+
${scopesHtml}
|
|
1484
|
+
|
|
1485
|
+
<form method="GET" action="${escapeHtml3(callbackPath)}">
|
|
1486
|
+
<input type="hidden" name="pending_auth_id" value="${escapeHtml3(pendingAuthId)}">
|
|
1487
|
+
${fieldsHtml}
|
|
1389
1488
|
|
|
1390
1489
|
<!-- Submit -->
|
|
1391
1490
|
<button type="submit"
|
|
@@ -1396,7 +1495,51 @@ function buildLoginPage(params) {
|
|
|
1396
1495
|
|
|
1397
1496
|
<p class="text-center text-sm text-gray-500 mt-6">Client: ${escapeHtml3(clientName)}</p>
|
|
1398
1497
|
</div>`;
|
|
1399
|
-
return centeredCardLayout(content, { title: "Sign In" });
|
|
1498
|
+
return centeredCardLayout(content, { title: title ?? "Sign In" });
|
|
1499
|
+
}
|
|
1500
|
+
function buildConnectPage(params) {
|
|
1501
|
+
const { token, field, action, title, subtitle, logoUri, error } = params;
|
|
1502
|
+
const logoHtml = logoUri ? `<div class="flex justify-center mb-4"><img src="${escapeHtml3(
|
|
1503
|
+
logoUri
|
|
1504
|
+
)}" alt="Connect" class="h-12 w-12 rounded-lg object-cover"></div>` : "";
|
|
1505
|
+
const errorHtml = error ? `<div class="p-3 mb-6 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">${escapeHtml3(error)}</div>` : "";
|
|
1506
|
+
const headingText = escapeHtml3(title ?? "Connect credential");
|
|
1507
|
+
const subtitleText = subtitle ? escapeHtml3(subtitle) : `Connect "${escapeHtml3(field.name)}" to continue`;
|
|
1508
|
+
const content = `
|
|
1509
|
+
<div class="bg-white rounded-2xl shadow-xl p-8">
|
|
1510
|
+
${logoHtml}
|
|
1511
|
+
<h1 class="text-3xl font-bold text-gray-900 mb-2 text-center">${headingText}</h1>
|
|
1512
|
+
<p class="text-gray-600 mb-8 text-center">${subtitleText}</p>
|
|
1513
|
+
|
|
1514
|
+
${errorHtml}
|
|
1515
|
+
|
|
1516
|
+
<form method="POST" action="${escapeHtml3(action)}">
|
|
1517
|
+
<input type="hidden" name="token" value="${escapeHtml3(token)}">
|
|
1518
|
+
${buildLoginFieldHtml(field)}
|
|
1519
|
+
|
|
1520
|
+
<button type="submit"
|
|
1521
|
+
class="w-full px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors">
|
|
1522
|
+
Connect
|
|
1523
|
+
</button>
|
|
1524
|
+
</form>
|
|
1525
|
+
</div>`;
|
|
1526
|
+
return centeredCardLayout(content, { title: title ?? "Connect credential" });
|
|
1527
|
+
}
|
|
1528
|
+
function buildConnectSuccessPage(params) {
|
|
1529
|
+
const { key, message } = params;
|
|
1530
|
+
const content = `
|
|
1531
|
+
<div class="bg-white rounded-2xl shadow-xl p-8 text-center">
|
|
1532
|
+
<div class="flex justify-center mb-6">
|
|
1533
|
+
<div class="w-16 h-16 rounded-full bg-green-100 flex items-center justify-center">
|
|
1534
|
+
<svg class="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1535
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
1536
|
+
</svg>
|
|
1537
|
+
</div>
|
|
1538
|
+
</div>
|
|
1539
|
+
<h1 class="text-2xl font-bold text-gray-900 mb-4">Credential connected</h1>
|
|
1540
|
+
<p class="text-gray-600">${escapeHtml3(message ?? `"${key}" is now connected. You can return to your session.`)}</p>
|
|
1541
|
+
</div>`;
|
|
1542
|
+
return centeredCardLayout(content, { title: "Connected" });
|
|
1400
1543
|
}
|
|
1401
1544
|
function buildErrorPage(params) {
|
|
1402
1545
|
const { error, description } = params;
|
|
@@ -1423,6 +1566,37 @@ function renderToHtml(html, _options) {
|
|
|
1423
1566
|
return html;
|
|
1424
1567
|
}
|
|
1425
1568
|
|
|
1569
|
+
// libs/auth/src/ui/local-login.helper.ts
|
|
1570
|
+
function toLoginExtraFields(fields, values) {
|
|
1571
|
+
if (!fields) return [];
|
|
1572
|
+
return Object.entries(fields).map(([name, def]) => ({
|
|
1573
|
+
name,
|
|
1574
|
+
type: def.type,
|
|
1575
|
+
label: def.label,
|
|
1576
|
+
required: def.required,
|
|
1577
|
+
placeholder: def.placeholder,
|
|
1578
|
+
options: def.options,
|
|
1579
|
+
value: values?.[name]
|
|
1580
|
+
}));
|
|
1581
|
+
}
|
|
1582
|
+
function renderLocalLoginPage(login, ctx, values) {
|
|
1583
|
+
if (login?.render) {
|
|
1584
|
+
return login.render({ ...ctx, values });
|
|
1585
|
+
}
|
|
1586
|
+
const extraFields = login?.fields ? toLoginExtraFields(login.fields, values) : void 0;
|
|
1587
|
+
return buildLoginPage({
|
|
1588
|
+
clientName: ctx.clientName,
|
|
1589
|
+
scope: ctx.scopes.join(" "),
|
|
1590
|
+
pendingAuthId: ctx.pendingAuthId,
|
|
1591
|
+
callbackPath: ctx.callbackPath,
|
|
1592
|
+
title: login?.title,
|
|
1593
|
+
subtitle: login?.subtitle,
|
|
1594
|
+
logoUri: login?.logoUri ?? ctx.logoUri,
|
|
1595
|
+
error: ctx.error,
|
|
1596
|
+
extraFields
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1426
1600
|
// libs/auth/src/session/authorization.store.ts
|
|
1427
1601
|
var import_lazy_zod = require("@frontmcp/lazy-zod");
|
|
1428
1602
|
var import_utils4 = require("@frontmcp/utils");
|
|
@@ -1450,8 +1624,83 @@ var authorizationCodeRecordSchema = import_lazy_zod.z.object({
|
|
|
1450
1624
|
skippedProviderIds: import_lazy_zod.z.array(import_lazy_zod.z.string()).optional(),
|
|
1451
1625
|
consentEnabled: import_lazy_zod.z.boolean().optional(),
|
|
1452
1626
|
federatedLoginUsed: import_lazy_zod.z.boolean().optional(),
|
|
1453
|
-
pendingAuthId: import_lazy_zod.z.string().optional()
|
|
1627
|
+
pendingAuthId: import_lazy_zod.z.string().optional(),
|
|
1628
|
+
// Progressive/Incremental authorization: granted app-id set.
|
|
1629
|
+
authorizedAppIds: import_lazy_zod.z.array(import_lazy_zod.z.string()).optional(),
|
|
1630
|
+
// Custom claims from a local authenticate() verifier (Checkpoint 3a).
|
|
1631
|
+
customClaims: import_lazy_zod.z.record(import_lazy_zod.z.string(), import_lazy_zod.z.unknown()).optional()
|
|
1454
1632
|
});
|
|
1633
|
+
var AUTH_CODE_TTL_MS = 60 * 1e3;
|
|
1634
|
+
var PENDING_AUTH_TTL_MS = 10 * 60 * 1e3;
|
|
1635
|
+
var REFRESH_TOKEN_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1636
|
+
function generateAuthorizationCode() {
|
|
1637
|
+
return (0, import_utils4.randomUUID)().replace(/-/g, "") + (0, import_utils4.randomUUID)().replace(/-/g, "");
|
|
1638
|
+
}
|
|
1639
|
+
function generateRefreshTokenValue() {
|
|
1640
|
+
return (0, import_utils4.randomUUID)() + "-" + (0, import_utils4.randomUUID)();
|
|
1641
|
+
}
|
|
1642
|
+
function buildCodeRecord(params) {
|
|
1643
|
+
const now = Date.now();
|
|
1644
|
+
return {
|
|
1645
|
+
code: generateAuthorizationCode(),
|
|
1646
|
+
clientId: params.clientId,
|
|
1647
|
+
redirectUri: params.redirectUri,
|
|
1648
|
+
scopes: params.scopes,
|
|
1649
|
+
pkce: params.pkce,
|
|
1650
|
+
userSub: params.userSub,
|
|
1651
|
+
userEmail: params.userEmail,
|
|
1652
|
+
userName: params.userName,
|
|
1653
|
+
state: params.state,
|
|
1654
|
+
resource: params.resource,
|
|
1655
|
+
createdAt: now,
|
|
1656
|
+
expiresAt: now + AUTH_CODE_TTL_MS,
|
|
1657
|
+
used: false,
|
|
1658
|
+
selectedToolIds: params.selectedToolIds,
|
|
1659
|
+
selectedProviderIds: params.selectedProviderIds,
|
|
1660
|
+
skippedProviderIds: params.skippedProviderIds,
|
|
1661
|
+
consentEnabled: params.consentEnabled,
|
|
1662
|
+
federatedLoginUsed: params.federatedLoginUsed,
|
|
1663
|
+
pendingAuthId: params.pendingAuthId,
|
|
1664
|
+
authorizedAppIds: params.authorizedAppIds,
|
|
1665
|
+
customClaims: params.customClaims
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
function buildPendingRecord(params) {
|
|
1669
|
+
const now = Date.now();
|
|
1670
|
+
return {
|
|
1671
|
+
id: (0, import_utils4.randomUUID)(),
|
|
1672
|
+
clientId: params.clientId,
|
|
1673
|
+
redirectUri: params.redirectUri,
|
|
1674
|
+
scopes: params.scopes,
|
|
1675
|
+
pkce: params.pkce,
|
|
1676
|
+
state: params.state,
|
|
1677
|
+
resource: params.resource,
|
|
1678
|
+
createdAt: now,
|
|
1679
|
+
expiresAt: now + PENDING_AUTH_TTL_MS,
|
|
1680
|
+
isIncremental: params.isIncremental,
|
|
1681
|
+
targetAppId: params.targetAppId,
|
|
1682
|
+
targetToolId: params.targetToolId,
|
|
1683
|
+
existingSessionId: params.existingSessionId,
|
|
1684
|
+
existingAuthorizationId: params.existingAuthorizationId,
|
|
1685
|
+
priorAuthorizedAppIds: params.priorAuthorizedAppIds,
|
|
1686
|
+
federatedLogin: params.federatedLogin,
|
|
1687
|
+
consent: params.consent,
|
|
1688
|
+
authUiCsrf: params.authUiCsrf
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
function buildRefreshTokenRecord(params) {
|
|
1692
|
+
const now = Date.now();
|
|
1693
|
+
return {
|
|
1694
|
+
token: generateRefreshTokenValue(),
|
|
1695
|
+
clientId: params.clientId,
|
|
1696
|
+
userSub: params.userSub,
|
|
1697
|
+
scopes: params.scopes,
|
|
1698
|
+
resource: params.resource,
|
|
1699
|
+
createdAt: now,
|
|
1700
|
+
expiresAt: now + REFRESH_TOKEN_TTL_MS,
|
|
1701
|
+
revoked: false
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1455
1704
|
function verifyPkce(codeVerifier, challenge) {
|
|
1456
1705
|
if (challenge.method !== "S256") {
|
|
1457
1706
|
return false;
|
|
@@ -1467,17 +1716,11 @@ var InMemoryAuthorizationStore = class {
|
|
|
1467
1716
|
codes = /* @__PURE__ */ new Map();
|
|
1468
1717
|
pending = /* @__PURE__ */ new Map();
|
|
1469
1718
|
refreshTokens = /* @__PURE__ */ new Map();
|
|
1470
|
-
/** Default TTL for authorization codes (60 seconds) */
|
|
1471
|
-
codeTtlMs = 60 * 1e3;
|
|
1472
|
-
/** Default TTL for pending authorizations (10 minutes) */
|
|
1473
|
-
pendingTtlMs = 10 * 60 * 1e3;
|
|
1474
|
-
/** Default TTL for refresh tokens (30 days) */
|
|
1475
|
-
refreshTtlMs = 30 * 24 * 60 * 60 * 1e3;
|
|
1476
1719
|
generateCode() {
|
|
1477
|
-
return (
|
|
1720
|
+
return generateAuthorizationCode();
|
|
1478
1721
|
}
|
|
1479
1722
|
generateRefreshToken() {
|
|
1480
|
-
return (
|
|
1723
|
+
return generateRefreshTokenValue();
|
|
1481
1724
|
}
|
|
1482
1725
|
async storeAuthorizationCode(record) {
|
|
1483
1726
|
this.codes.set(record.code, record);
|
|
@@ -1556,76 +1799,22 @@ var InMemoryAuthorizationStore = class {
|
|
|
1556
1799
|
}
|
|
1557
1800
|
}
|
|
1558
1801
|
/**
|
|
1559
|
-
* Create an authorization code record with defaults
|
|
1802
|
+
* Create an authorization code record with defaults.
|
|
1560
1803
|
*/
|
|
1561
1804
|
createCodeRecord(params) {
|
|
1562
|
-
|
|
1563
|
-
return {
|
|
1564
|
-
code: this.generateCode(),
|
|
1565
|
-
clientId: params.clientId,
|
|
1566
|
-
redirectUri: params.redirectUri,
|
|
1567
|
-
scopes: params.scopes,
|
|
1568
|
-
pkce: params.pkce,
|
|
1569
|
-
userSub: params.userSub,
|
|
1570
|
-
userEmail: params.userEmail,
|
|
1571
|
-
userName: params.userName,
|
|
1572
|
-
state: params.state,
|
|
1573
|
-
resource: params.resource,
|
|
1574
|
-
createdAt: now,
|
|
1575
|
-
expiresAt: now + this.codeTtlMs,
|
|
1576
|
-
used: false,
|
|
1577
|
-
// Consent and Federated Login Data
|
|
1578
|
-
selectedToolIds: params.selectedToolIds,
|
|
1579
|
-
selectedProviderIds: params.selectedProviderIds,
|
|
1580
|
-
skippedProviderIds: params.skippedProviderIds,
|
|
1581
|
-
consentEnabled: params.consentEnabled,
|
|
1582
|
-
federatedLoginUsed: params.federatedLoginUsed,
|
|
1583
|
-
// Token migration ID (for federated auth)
|
|
1584
|
-
pendingAuthId: params.pendingAuthId
|
|
1585
|
-
};
|
|
1805
|
+
return buildCodeRecord(params);
|
|
1586
1806
|
}
|
|
1587
1807
|
/**
|
|
1588
|
-
* Create a pending authorization record with defaults
|
|
1808
|
+
* Create a pending authorization record with defaults.
|
|
1589
1809
|
*/
|
|
1590
1810
|
createPendingRecord(params) {
|
|
1591
|
-
|
|
1592
|
-
return {
|
|
1593
|
-
id: (0, import_utils4.randomUUID)(),
|
|
1594
|
-
clientId: params.clientId,
|
|
1595
|
-
redirectUri: params.redirectUri,
|
|
1596
|
-
scopes: params.scopes,
|
|
1597
|
-
pkce: params.pkce,
|
|
1598
|
-
state: params.state,
|
|
1599
|
-
resource: params.resource,
|
|
1600
|
-
createdAt: now,
|
|
1601
|
-
expiresAt: now + this.pendingTtlMs,
|
|
1602
|
-
// Progressive/Incremental Authorization Fields
|
|
1603
|
-
isIncremental: params.isIncremental,
|
|
1604
|
-
targetAppId: params.targetAppId,
|
|
1605
|
-
targetToolId: params.targetToolId,
|
|
1606
|
-
existingSessionId: params.existingSessionId,
|
|
1607
|
-
existingAuthorizationId: params.existingAuthorizationId,
|
|
1608
|
-
// Federated Login State
|
|
1609
|
-
federatedLogin: params.federatedLogin,
|
|
1610
|
-
// Consent State
|
|
1611
|
-
consent: params.consent
|
|
1612
|
-
};
|
|
1811
|
+
return buildPendingRecord(params);
|
|
1613
1812
|
}
|
|
1614
1813
|
/**
|
|
1615
|
-
* Create a refresh token record with defaults
|
|
1814
|
+
* Create a refresh token record with defaults.
|
|
1616
1815
|
*/
|
|
1617
1816
|
createRefreshTokenRecord(params) {
|
|
1618
|
-
|
|
1619
|
-
return {
|
|
1620
|
-
token: this.generateRefreshToken(),
|
|
1621
|
-
clientId: params.clientId,
|
|
1622
|
-
userSub: params.userSub,
|
|
1623
|
-
scopes: params.scopes,
|
|
1624
|
-
resource: params.resource,
|
|
1625
|
-
createdAt: now,
|
|
1626
|
-
expiresAt: now + this.refreshTtlMs,
|
|
1627
|
-
revoked: false
|
|
1628
|
-
};
|
|
1817
|
+
return buildRefreshTokenRecord(params);
|
|
1629
1818
|
}
|
|
1630
1819
|
};
|
|
1631
1820
|
var RedisAuthorizationStore = class {
|
|
@@ -1637,10 +1826,19 @@ var RedisAuthorizationStore = class {
|
|
|
1637
1826
|
return `${this.namespace}${type}:${id}`;
|
|
1638
1827
|
}
|
|
1639
1828
|
generateCode() {
|
|
1640
|
-
return (
|
|
1829
|
+
return generateAuthorizationCode();
|
|
1641
1830
|
}
|
|
1642
1831
|
generateRefreshToken() {
|
|
1643
|
-
return (
|
|
1832
|
+
return generateRefreshTokenValue();
|
|
1833
|
+
}
|
|
1834
|
+
createCodeRecord(params) {
|
|
1835
|
+
return buildCodeRecord(params);
|
|
1836
|
+
}
|
|
1837
|
+
createPendingRecord(params) {
|
|
1838
|
+
return buildPendingRecord(params);
|
|
1839
|
+
}
|
|
1840
|
+
createRefreshTokenRecord(params) {
|
|
1841
|
+
return buildRefreshTokenRecord(params);
|
|
1644
1842
|
}
|
|
1645
1843
|
async storeAuthorizationCode(record) {
|
|
1646
1844
|
const ttl = Math.max(Math.ceil((record.expiresAt - Date.now()) / 1e3), 1);
|
|
@@ -1702,6 +1900,130 @@ var RedisAuthorizationStore = class {
|
|
|
1702
1900
|
}
|
|
1703
1901
|
};
|
|
1704
1902
|
|
|
1903
|
+
// libs/auth/src/session/storage-authorization.store.ts
|
|
1904
|
+
var import_utils5 = require("@frontmcp/utils");
|
|
1905
|
+
var StorageAuthorizationStore = class {
|
|
1906
|
+
codeStore;
|
|
1907
|
+
pendingStore;
|
|
1908
|
+
refreshStore;
|
|
1909
|
+
namespace;
|
|
1910
|
+
storageIsNamespaced;
|
|
1911
|
+
constructor(storage, options = {}) {
|
|
1912
|
+
this.namespace = options.namespace ?? "oauth";
|
|
1913
|
+
this.storageIsNamespaced = this.isNamespacedStorage(storage);
|
|
1914
|
+
const base = this.storageIsNamespaced ? storage.namespace(this.namespace) : storage;
|
|
1915
|
+
this.codeStore = new import_utils5.TypedStorage(base);
|
|
1916
|
+
this.pendingStore = new import_utils5.TypedStorage(base);
|
|
1917
|
+
this.refreshStore = new import_utils5.TypedStorage(base);
|
|
1918
|
+
}
|
|
1919
|
+
// ============================================
|
|
1920
|
+
// Record builders (pure; shared with in-memory store)
|
|
1921
|
+
// ============================================
|
|
1922
|
+
generateCode() {
|
|
1923
|
+
return generateAuthorizationCode();
|
|
1924
|
+
}
|
|
1925
|
+
generateRefreshToken() {
|
|
1926
|
+
return generateRefreshTokenValue();
|
|
1927
|
+
}
|
|
1928
|
+
createCodeRecord(params) {
|
|
1929
|
+
return buildCodeRecord(params);
|
|
1930
|
+
}
|
|
1931
|
+
createPendingRecord(params) {
|
|
1932
|
+
return buildPendingRecord(params);
|
|
1933
|
+
}
|
|
1934
|
+
createRefreshTokenRecord(params) {
|
|
1935
|
+
return buildRefreshTokenRecord(params);
|
|
1936
|
+
}
|
|
1937
|
+
// ============================================
|
|
1938
|
+
// Authorization code operations
|
|
1939
|
+
// ============================================
|
|
1940
|
+
async storeAuthorizationCode(record) {
|
|
1941
|
+
await this.codeStore.set(this.key("code", record.code), record, this.ttl(record.expiresAt));
|
|
1942
|
+
}
|
|
1943
|
+
async getAuthorizationCode(code) {
|
|
1944
|
+
const record = await this.codeStore.get(this.key("code", code));
|
|
1945
|
+
if (!record) return null;
|
|
1946
|
+
if (Date.now() > record.expiresAt) {
|
|
1947
|
+
await this.codeStore.delete(this.key("code", code));
|
|
1948
|
+
return null;
|
|
1949
|
+
}
|
|
1950
|
+
return record;
|
|
1951
|
+
}
|
|
1952
|
+
async markCodeUsed(code) {
|
|
1953
|
+
const record = await this.getAuthorizationCode(code);
|
|
1954
|
+
if (record) {
|
|
1955
|
+
record.used = true;
|
|
1956
|
+
await this.codeStore.set(this.key("code", code), record, this.ttl(record.expiresAt));
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
async deleteAuthorizationCode(code) {
|
|
1960
|
+
await this.codeStore.delete(this.key("code", code));
|
|
1961
|
+
}
|
|
1962
|
+
// ============================================
|
|
1963
|
+
// Pending authorization operations
|
|
1964
|
+
// ============================================
|
|
1965
|
+
async storePendingAuthorization(record) {
|
|
1966
|
+
await this.pendingStore.set(this.key("pending", record.id), record, this.ttl(record.expiresAt));
|
|
1967
|
+
}
|
|
1968
|
+
async getPendingAuthorization(id) {
|
|
1969
|
+
const record = await this.pendingStore.get(this.key("pending", id));
|
|
1970
|
+
if (!record) return null;
|
|
1971
|
+
if (Date.now() > record.expiresAt) {
|
|
1972
|
+
await this.pendingStore.delete(this.key("pending", id));
|
|
1973
|
+
return null;
|
|
1974
|
+
}
|
|
1975
|
+
return record;
|
|
1976
|
+
}
|
|
1977
|
+
async deletePendingAuthorization(id) {
|
|
1978
|
+
await this.pendingStore.delete(this.key("pending", id));
|
|
1979
|
+
}
|
|
1980
|
+
// ============================================
|
|
1981
|
+
// Refresh token operations
|
|
1982
|
+
// ============================================
|
|
1983
|
+
async storeRefreshToken(record) {
|
|
1984
|
+
await this.refreshStore.set(this.key("refresh", record.token), record, this.ttl(record.expiresAt));
|
|
1985
|
+
}
|
|
1986
|
+
async getRefreshToken(token) {
|
|
1987
|
+
const record = await this.refreshStore.get(this.key("refresh", token));
|
|
1988
|
+
if (!record) return null;
|
|
1989
|
+
if (Date.now() > record.expiresAt || record.revoked) {
|
|
1990
|
+
return null;
|
|
1991
|
+
}
|
|
1992
|
+
return record;
|
|
1993
|
+
}
|
|
1994
|
+
async revokeRefreshToken(token) {
|
|
1995
|
+
const record = await this.refreshStore.get(this.key("refresh", token));
|
|
1996
|
+
if (record) {
|
|
1997
|
+
record.revoked = true;
|
|
1998
|
+
await this.refreshStore.set(this.key("refresh", token), record, this.ttl(record.expiresAt));
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
async rotateRefreshToken(oldToken, newRecord) {
|
|
2002
|
+
await this.revokeRefreshToken(oldToken);
|
|
2003
|
+
newRecord.previousToken = oldToken;
|
|
2004
|
+
await this.storeRefreshToken(newRecord);
|
|
2005
|
+
}
|
|
2006
|
+
// ============================================
|
|
2007
|
+
// Utility
|
|
2008
|
+
// ============================================
|
|
2009
|
+
async cleanup() {
|
|
2010
|
+
}
|
|
2011
|
+
// ============================================
|
|
2012
|
+
// Internals
|
|
2013
|
+
// ============================================
|
|
2014
|
+
ttl(expiresAtMs) {
|
|
2015
|
+
const ttl = Math.ceil((expiresAtMs - Date.now()) / 1e3);
|
|
2016
|
+
return { ttlSeconds: ttl > 0 ? ttl : 1 };
|
|
2017
|
+
}
|
|
2018
|
+
key(type, id) {
|
|
2019
|
+
const composite = `${type}:${id}`;
|
|
2020
|
+
return this.storageIsNamespaced ? composite : `${this.namespace}:${composite}`;
|
|
2021
|
+
}
|
|
2022
|
+
isNamespacedStorage(storage) {
|
|
2023
|
+
return "namespace" in storage && typeof storage.namespace === "function";
|
|
2024
|
+
}
|
|
2025
|
+
};
|
|
2026
|
+
|
|
1705
2027
|
// libs/auth/src/session/authorization-vault.ts
|
|
1706
2028
|
var import_lazy_zod2 = require("@frontmcp/lazy-zod");
|
|
1707
2029
|
var credentialTypeSchema = import_lazy_zod2.z.enum([
|
|
@@ -1963,9 +2285,12 @@ var authorizationVaultEntrySchema = import_lazy_zod2.z.object({
|
|
|
1963
2285
|
skippedAppIds: import_lazy_zod2.z.array(import_lazy_zod2.z.string())
|
|
1964
2286
|
});
|
|
1965
2287
|
|
|
2288
|
+
// libs/auth/src/session/session-credential-vault.ts
|
|
2289
|
+
var import_utils7 = require("@frontmcp/utils");
|
|
2290
|
+
|
|
1966
2291
|
// libs/auth/src/session/vault-encryption.ts
|
|
1967
2292
|
var import_lazy_zod3 = require("@frontmcp/lazy-zod");
|
|
1968
|
-
var
|
|
2293
|
+
var import_utils6 = require("@frontmcp/utils");
|
|
1969
2294
|
var encryptedDataSchema = import_lazy_zod3.z.object({
|
|
1970
2295
|
/** Version for future algorithm changes */
|
|
1971
2296
|
v: import_lazy_zod3.z.literal(1),
|
|
@@ -2005,7 +2330,7 @@ var VaultEncryption = class {
|
|
|
2005
2330
|
];
|
|
2006
2331
|
const ikm = new TextEncoder().encode(ikmParts.join(""));
|
|
2007
2332
|
const infoBytes = new TextEncoder().encode(this.hkdfInfo);
|
|
2008
|
-
const key = await (0,
|
|
2333
|
+
const key = await (0, import_utils6.hkdfSha256)(ikm, new Uint8Array(0), infoBytes, 32);
|
|
2009
2334
|
return key;
|
|
2010
2335
|
}
|
|
2011
2336
|
/**
|
|
@@ -2032,7 +2357,7 @@ var VaultEncryption = class {
|
|
|
2032
2357
|
];
|
|
2033
2358
|
const ikm = new TextEncoder().encode(ikmParts.join(""));
|
|
2034
2359
|
const infoBytes = new TextEncoder().encode(this.hkdfInfo);
|
|
2035
|
-
const key = await (0,
|
|
2360
|
+
const key = await (0, import_utils6.hkdfSha256)(ikm, new Uint8Array(0), infoBytes, 32);
|
|
2036
2361
|
return key;
|
|
2037
2362
|
}
|
|
2038
2363
|
/**
|
|
@@ -2046,14 +2371,14 @@ var VaultEncryption = class {
|
|
|
2046
2371
|
if (key.length !== 32) {
|
|
2047
2372
|
throw new Error("Encryption key must be 32 bytes");
|
|
2048
2373
|
}
|
|
2049
|
-
const iv = (0,
|
|
2050
|
-
const { ciphertext, tag } = await (0,
|
|
2374
|
+
const iv = (0, import_utils6.randomBytes)(12);
|
|
2375
|
+
const { ciphertext, tag } = await (0, import_utils6.encryptAesGcm)(key, new TextEncoder().encode(plaintext), iv);
|
|
2051
2376
|
return {
|
|
2052
2377
|
v: 1,
|
|
2053
2378
|
alg: "aes-256-gcm",
|
|
2054
|
-
iv: (0,
|
|
2055
|
-
ct: (0,
|
|
2056
|
-
tag: (0,
|
|
2379
|
+
iv: (0, import_utils6.base64urlEncode)(iv),
|
|
2380
|
+
ct: (0, import_utils6.base64urlEncode)(ciphertext),
|
|
2381
|
+
tag: (0, import_utils6.base64urlEncode)(tag)
|
|
2057
2382
|
};
|
|
2058
2383
|
}
|
|
2059
2384
|
/**
|
|
@@ -2073,74 +2398,707 @@ var VaultEncryption = class {
|
|
|
2073
2398
|
throw new Error("Invalid encrypted data format");
|
|
2074
2399
|
}
|
|
2075
2400
|
const { iv, ct, tag } = parsed.data;
|
|
2076
|
-
const ivBuffer = (0,
|
|
2077
|
-
const ciphertext = (0,
|
|
2078
|
-
const tagBuffer = (0,
|
|
2401
|
+
const ivBuffer = (0, import_utils6.base64urlDecode)(iv);
|
|
2402
|
+
const ciphertext = (0, import_utils6.base64urlDecode)(ct);
|
|
2403
|
+
const tagBuffer = (0, import_utils6.base64urlDecode)(tag);
|
|
2079
2404
|
try {
|
|
2080
|
-
const plaintext = await (0,
|
|
2405
|
+
const plaintext = await (0, import_utils6.decryptAesGcm)(key, ciphertext, ivBuffer, tagBuffer);
|
|
2081
2406
|
return new TextDecoder().decode(plaintext);
|
|
2082
2407
|
} catch (_error) {
|
|
2083
2408
|
throw new Error("Decryption failed: invalid key or corrupted data");
|
|
2084
2409
|
}
|
|
2085
2410
|
}
|
|
2086
|
-
/**
|
|
2087
|
-
* Encrypt a JavaScript object (serializes to JSON first)
|
|
2088
|
-
*
|
|
2089
|
-
* @param data - Object to encrypt
|
|
2090
|
-
* @param key - Encryption key
|
|
2091
|
-
* @returns Encrypted data
|
|
2092
|
-
*/
|
|
2093
|
-
async encryptObject(data, key) {
|
|
2094
|
-
return this.encrypt(JSON.stringify(data), key);
|
|
2411
|
+
/**
|
|
2412
|
+
* Encrypt a JavaScript object (serializes to JSON first)
|
|
2413
|
+
*
|
|
2414
|
+
* @param data - Object to encrypt
|
|
2415
|
+
* @param key - Encryption key
|
|
2416
|
+
* @returns Encrypted data
|
|
2417
|
+
*/
|
|
2418
|
+
async encryptObject(data, key) {
|
|
2419
|
+
return this.encrypt(JSON.stringify(data), key);
|
|
2420
|
+
}
|
|
2421
|
+
/**
|
|
2422
|
+
* Decrypt and parse a JavaScript object
|
|
2423
|
+
*
|
|
2424
|
+
* @param encrypted - Encrypted data
|
|
2425
|
+
* @param key - Encryption key
|
|
2426
|
+
* @returns Decrypted and parsed object
|
|
2427
|
+
*/
|
|
2428
|
+
async decryptObject(encrypted, key) {
|
|
2429
|
+
const plaintext = await this.decrypt(encrypted, key);
|
|
2430
|
+
return JSON.parse(plaintext);
|
|
2431
|
+
}
|
|
2432
|
+
/**
|
|
2433
|
+
* Check if data is in encrypted format
|
|
2434
|
+
*
|
|
2435
|
+
* @param data - Data to check
|
|
2436
|
+
* @returns True if data appears to be encrypted
|
|
2437
|
+
*/
|
|
2438
|
+
isEncrypted(data) {
|
|
2439
|
+
return encryptedDataSchema.safeParse(data).success;
|
|
2440
|
+
}
|
|
2441
|
+
};
|
|
2442
|
+
var encryptedVaultEntrySchema = import_lazy_zod3.z.object({
|
|
2443
|
+
/** Vault ID (maps to JWT jti claim) */
|
|
2444
|
+
id: import_lazy_zod3.z.string(),
|
|
2445
|
+
/** User subject identifier */
|
|
2446
|
+
userSub: import_lazy_zod3.z.string(),
|
|
2447
|
+
/** User email (unencrypted for display) */
|
|
2448
|
+
userEmail: import_lazy_zod3.z.string().optional(),
|
|
2449
|
+
/** User name (unencrypted for display) */
|
|
2450
|
+
userName: import_lazy_zod3.z.string().optional(),
|
|
2451
|
+
/** Client ID that created this session */
|
|
2452
|
+
clientId: import_lazy_zod3.z.string(),
|
|
2453
|
+
/** Creation timestamp */
|
|
2454
|
+
createdAt: import_lazy_zod3.z.number(),
|
|
2455
|
+
/** Last access timestamp */
|
|
2456
|
+
lastAccessAt: import_lazy_zod3.z.number(),
|
|
2457
|
+
/** Encrypted sensitive data (provider tokens, credentials, consent) */
|
|
2458
|
+
encryptedData: encryptedDataSchema,
|
|
2459
|
+
/** Apps that are fully authorized (unencrypted for quick lookup) */
|
|
2460
|
+
authorizedAppIds: import_lazy_zod3.z.array(import_lazy_zod3.z.string()),
|
|
2461
|
+
/** Apps that were skipped (unencrypted for quick lookup) */
|
|
2462
|
+
skippedAppIds: import_lazy_zod3.z.array(import_lazy_zod3.z.string()),
|
|
2463
|
+
/** Pending auth IDs (unencrypted for lookup, actual URLs encrypted) */
|
|
2464
|
+
pendingAuthIds: import_lazy_zod3.z.array(import_lazy_zod3.z.string()).default([])
|
|
2465
|
+
});
|
|
2466
|
+
|
|
2467
|
+
// libs/auth/src/session/session-credential-vault.ts
|
|
2468
|
+
var DEFAULT_KEY_PREFIX = "mcp:cred:";
|
|
2469
|
+
var DEFAULT_PENDING_TTL_MS = 6e5;
|
|
2470
|
+
var SessionCredentialVault = class {
|
|
2471
|
+
storage;
|
|
2472
|
+
encryption;
|
|
2473
|
+
keyPrefix;
|
|
2474
|
+
ttlMs;
|
|
2475
|
+
pendingTtlMs;
|
|
2476
|
+
logger;
|
|
2477
|
+
/** Cache of derived AES keys, keyed by `${sub}:${vaultId}`. */
|
|
2478
|
+
keyCache = /* @__PURE__ */ new Map();
|
|
2479
|
+
constructor(options) {
|
|
2480
|
+
this.storage = options.storage;
|
|
2481
|
+
this.keyPrefix = options.keyPrefix ?? DEFAULT_KEY_PREFIX;
|
|
2482
|
+
this.ttlMs = options.ttlMs;
|
|
2483
|
+
this.pendingTtlMs = options.pendingTtlMs ?? DEFAULT_PENDING_TTL_MS;
|
|
2484
|
+
this.logger = options.logger ?? noopAuthLogger;
|
|
2485
|
+
const pepper = options.pepper ?? resolveVaultPepper(this.logger);
|
|
2486
|
+
this.encryption = new VaultEncryption({ pepper, hkdfInfo: "frontmcp-session-credential-vault-v1" });
|
|
2487
|
+
}
|
|
2488
|
+
// ============================================
|
|
2489
|
+
// Storage-key helpers
|
|
2490
|
+
// ============================================
|
|
2491
|
+
/** `cred:idx:<sub>` → current vaultId (the sub → vaultId indirection). */
|
|
2492
|
+
indexKey(sub) {
|
|
2493
|
+
return `${this.keyPrefix}idx:${sub}`;
|
|
2494
|
+
}
|
|
2495
|
+
/** `cred:keys:<sub>:<vaultId>` → JSON array of credential key names (the SET). */
|
|
2496
|
+
keysKey(sub, vaultId) {
|
|
2497
|
+
return `${this.keyPrefix}keys:${sub}:${vaultId}`;
|
|
2498
|
+
}
|
|
2499
|
+
/** `cred:data:<sub>:<vaultId>:<key>` → encrypted credential record. */
|
|
2500
|
+
dataKey(sub, vaultId, key) {
|
|
2501
|
+
return `${this.keyPrefix}data:${sub}:${vaultId}:${key}`;
|
|
2502
|
+
}
|
|
2503
|
+
/** `cred:pending:<pendingId>` → encrypted pending-authorize accumulator. */
|
|
2504
|
+
pendingKey(pendingId) {
|
|
2505
|
+
return `${this.keyPrefix}pending:${pendingId}`;
|
|
2506
|
+
}
|
|
2507
|
+
/** Convert the configured `ttlMs` into `SetOptions` (storage uses `ttlSeconds`). */
|
|
2508
|
+
get credentialSetOpts() {
|
|
2509
|
+
return this.ttlMs ? { ttlSeconds: Math.ceil(this.ttlMs / 1e3) } : void 0;
|
|
2510
|
+
}
|
|
2511
|
+
// ============================================
|
|
2512
|
+
// Key derivation
|
|
2513
|
+
// ============================================
|
|
2514
|
+
/**
|
|
2515
|
+
* Derive (and cache) the AES-256 key for a `(sub, vaultId)` pair. The vaultId
|
|
2516
|
+
* acts as the HKDF salt, so rotating it makes prior ciphertext undecryptable.
|
|
2517
|
+
*/
|
|
2518
|
+
deriveKey(sub, vaultId) {
|
|
2519
|
+
const cacheKey = `${sub}:${vaultId}`;
|
|
2520
|
+
const cached = this.keyCache.get(cacheKey);
|
|
2521
|
+
if (cached) return cached;
|
|
2522
|
+
const promise = this.encryption.deriveKey({ jti: vaultId, vaultKey: vaultId, sub, iat: 0 });
|
|
2523
|
+
this.keyCache.set(cacheKey, promise);
|
|
2524
|
+
return promise;
|
|
2525
|
+
}
|
|
2526
|
+
// ============================================
|
|
2527
|
+
// vaultId indirection
|
|
2528
|
+
// ============================================
|
|
2529
|
+
/**
|
|
2530
|
+
* Read the current vaultId for a subject, or undefined when the subject has no
|
|
2531
|
+
* live vault (never authorized, or rotated away).
|
|
2532
|
+
*/
|
|
2533
|
+
async getVaultId(sub) {
|
|
2534
|
+
const raw = await this.storage.get(this.indexKey(sub));
|
|
2535
|
+
return raw ?? void 0;
|
|
2536
|
+
}
|
|
2537
|
+
/**
|
|
2538
|
+
* Mint a FRESH vaultId for a subject and point the indirection at it. Called
|
|
2539
|
+
* at the start of each authorize so reconnect = empty vault. Returns the new
|
|
2540
|
+
* vaultId. The old vaultId's ciphertext is left in storage but is
|
|
2541
|
+
* unreachable (no index pointer) and undecryptable (different derived key);
|
|
2542
|
+
* it expires via TTL when configured.
|
|
2543
|
+
*/
|
|
2544
|
+
async rotateVault(sub) {
|
|
2545
|
+
const vaultId = (0, import_utils7.randomUUID)();
|
|
2546
|
+
await this.storage.set(this.indexKey(sub), vaultId, this.credentialSetOpts);
|
|
2547
|
+
return vaultId;
|
|
2548
|
+
}
|
|
2549
|
+
// ============================================
|
|
2550
|
+
// Credential CRUD (committed vault)
|
|
2551
|
+
// ============================================
|
|
2552
|
+
/**
|
|
2553
|
+
* Encrypt + store a single credential into the vault identified by
|
|
2554
|
+
* `(sub, vaultId)` and add its key to the key set. Used both to commit
|
|
2555
|
+
* authorize-time credentials and to additively add a credential mid-session.
|
|
2556
|
+
*/
|
|
2557
|
+
async store(sub, vaultId, key, value) {
|
|
2558
|
+
const aesKey = await this.deriveKey(sub, vaultId);
|
|
2559
|
+
const enc = await this.encryption.encryptObject(this.normalize(value), aesKey);
|
|
2560
|
+
const record = { enc };
|
|
2561
|
+
await this.storage.set(this.dataKey(sub, vaultId, key), JSON.stringify(record), this.credentialSetOpts);
|
|
2562
|
+
await this.addKeyToSet(sub, vaultId, key);
|
|
2563
|
+
}
|
|
2564
|
+
/**
|
|
2565
|
+
* Decrypt and return a credential by key for a subject's CURRENT vault.
|
|
2566
|
+
* Returns undefined when there is no live vault or no such key, or when
|
|
2567
|
+
* decryption fails (e.g. a stale pointer to rotated-away ciphertext).
|
|
2568
|
+
*/
|
|
2569
|
+
async get(sub, key) {
|
|
2570
|
+
const vaultId = await this.getVaultId(sub);
|
|
2571
|
+
if (!vaultId) return void 0;
|
|
2572
|
+
const raw = await this.storage.get(this.dataKey(sub, vaultId, key));
|
|
2573
|
+
if (!raw) return void 0;
|
|
2574
|
+
try {
|
|
2575
|
+
const record = JSON.parse(raw);
|
|
2576
|
+
const aesKey = await this.deriveKey(sub, vaultId);
|
|
2577
|
+
return await this.encryption.decryptObject(record.enc, aesKey);
|
|
2578
|
+
} catch (err) {
|
|
2579
|
+
this.logger.warn(`SessionCredentialVault: failed to read credential "${key}": ${errMsg(err)}`);
|
|
2580
|
+
return void 0;
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
/**
|
|
2584
|
+
* List the credential keys available in a subject's current vault. Returns an
|
|
2585
|
+
* empty array when there is no live vault (e.g. after rotation on reconnect).
|
|
2586
|
+
*/
|
|
2587
|
+
async list(sub) {
|
|
2588
|
+
const vaultId = await this.getVaultId(sub);
|
|
2589
|
+
if (!vaultId) return [];
|
|
2590
|
+
return this.readKeySet(sub, vaultId);
|
|
2591
|
+
}
|
|
2592
|
+
/**
|
|
2593
|
+
* Remove a credential by key from a subject's current vault (deletes the
|
|
2594
|
+
* ciphertext and drops the key from the set). No-op when absent.
|
|
2595
|
+
*/
|
|
2596
|
+
async remove(sub, key) {
|
|
2597
|
+
const vaultId = await this.getVaultId(sub);
|
|
2598
|
+
if (!vaultId) return;
|
|
2599
|
+
await this.storage.delete(this.dataKey(sub, vaultId, key));
|
|
2600
|
+
const keys = await this.readKeySet(sub, vaultId);
|
|
2601
|
+
const next = keys.filter((k) => k !== key);
|
|
2602
|
+
if (next.length !== keys.length) {
|
|
2603
|
+
await this.writeKeySet(sub, vaultId, next);
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
// ============================================
|
|
2607
|
+
// Authorize-time accumulation + commit
|
|
2608
|
+
// ============================================
|
|
2609
|
+
/**
|
|
2610
|
+
* Stage credentials returned by `authenticate()` under a pending id, encrypted
|
|
2611
|
+
* against a freshly-rotated vaultId for `sub`. They are NOT visible to tools
|
|
2612
|
+
* until {@link commit} flushes them — this lets the authorize flow accumulate
|
|
2613
|
+
* credentials before the subject's identity is final.
|
|
2614
|
+
*
|
|
2615
|
+
* @returns the vaultId the credentials were staged against.
|
|
2616
|
+
*/
|
|
2617
|
+
async stage(pendingId, sub, credentials) {
|
|
2618
|
+
const vaultId = await this.rotateVault(sub);
|
|
2619
|
+
const creds = {};
|
|
2620
|
+
for (const cred of credentials) {
|
|
2621
|
+
const aesKey = await this.deriveKey(sub, vaultId);
|
|
2622
|
+
const { key, ...value } = cred;
|
|
2623
|
+
creds[key] = { enc: await this.encryption.encryptObject(this.normalize(value), aesKey) };
|
|
2624
|
+
}
|
|
2625
|
+
const batch = { sub, vaultId, creds };
|
|
2626
|
+
await this.storage.set(this.pendingKey(pendingId), JSON.stringify(batch), {
|
|
2627
|
+
ttlSeconds: Math.ceil(this.pendingTtlMs / 1e3)
|
|
2628
|
+
});
|
|
2629
|
+
return vaultId;
|
|
2630
|
+
}
|
|
2631
|
+
/**
|
|
2632
|
+
* Flush a staged batch into the live vault, binding it to `sub`. The staged
|
|
2633
|
+
* batch's `sub` must equal the supplied `sub` (the one baked into the minted
|
|
2634
|
+
* token) — a mismatch is refused so credentials can never be committed under a
|
|
2635
|
+
* different identity than the one that will read them.
|
|
2636
|
+
*
|
|
2637
|
+
* @returns true when a batch was committed, false when none was staged.
|
|
2638
|
+
*/
|
|
2639
|
+
async commit(pendingId, sub) {
|
|
2640
|
+
const raw = await this.storage.get(this.pendingKey(pendingId));
|
|
2641
|
+
if (!raw) return false;
|
|
2642
|
+
let batch;
|
|
2643
|
+
try {
|
|
2644
|
+
batch = JSON.parse(raw);
|
|
2645
|
+
} catch {
|
|
2646
|
+
await this.storage.delete(this.pendingKey(pendingId));
|
|
2647
|
+
return false;
|
|
2648
|
+
}
|
|
2649
|
+
if (batch.sub !== sub) {
|
|
2650
|
+
this.logger.warn("SessionCredentialVault: pending batch subject mismatch on commit; discarding");
|
|
2651
|
+
await this.storage.delete(this.pendingKey(pendingId));
|
|
2652
|
+
return false;
|
|
2653
|
+
}
|
|
2654
|
+
await this.storage.set(this.indexKey(sub), batch.vaultId, this.credentialSetOpts);
|
|
2655
|
+
const keys = [];
|
|
2656
|
+
for (const [key, record] of Object.entries(batch.creds)) {
|
|
2657
|
+
await this.storage.set(this.dataKey(sub, batch.vaultId, key), JSON.stringify(record), this.credentialSetOpts);
|
|
2658
|
+
keys.push(key);
|
|
2659
|
+
}
|
|
2660
|
+
await this.writeKeySet(sub, batch.vaultId, keys);
|
|
2661
|
+
await this.storage.delete(this.pendingKey(pendingId));
|
|
2662
|
+
return true;
|
|
2663
|
+
}
|
|
2664
|
+
// ============================================
|
|
2665
|
+
// Key-set helpers
|
|
2666
|
+
// ============================================
|
|
2667
|
+
async readKeySet(sub, vaultId) {
|
|
2668
|
+
const raw = await this.storage.get(this.keysKey(sub, vaultId));
|
|
2669
|
+
if (!raw) return [];
|
|
2670
|
+
try {
|
|
2671
|
+
const parsed = JSON.parse(raw);
|
|
2672
|
+
return Array.isArray(parsed) ? parsed.filter((k) => typeof k === "string") : [];
|
|
2673
|
+
} catch {
|
|
2674
|
+
return [];
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
async writeKeySet(sub, vaultId, keys) {
|
|
2678
|
+
const unique = [...new Set(keys)];
|
|
2679
|
+
await this.storage.set(this.keysKey(sub, vaultId), JSON.stringify(unique), this.credentialSetOpts);
|
|
2680
|
+
}
|
|
2681
|
+
async addKeyToSet(sub, vaultId, key) {
|
|
2682
|
+
const keys = await this.readKeySet(sub, vaultId);
|
|
2683
|
+
if (!keys.includes(key)) {
|
|
2684
|
+
keys.push(key);
|
|
2685
|
+
await this.writeKeySet(sub, vaultId, keys);
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
/** Strip undefined metadata so the encrypted JSON stays minimal. */
|
|
2689
|
+
normalize(value) {
|
|
2690
|
+
return value.metadata === void 0 ? { secret: value.secret } : { secret: value.secret, metadata: value.metadata };
|
|
2691
|
+
}
|
|
2692
|
+
};
|
|
2693
|
+
function resolveVaultPepper(logger) {
|
|
2694
|
+
const fromEnv = (0, import_utils7.getEnv)("VAULT_SECRET") ?? (0, import_utils7.getEnv)("JWT_SECRET");
|
|
2695
|
+
if (fromEnv && fromEnv.trim().length > 0) return fromEnv;
|
|
2696
|
+
logger.warn(
|
|
2697
|
+
"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."
|
|
2698
|
+
);
|
|
2699
|
+
return (0, import_utils7.randomUUID)() + (0, import_utils7.randomUUID)();
|
|
2700
|
+
}
|
|
2701
|
+
function errMsg(err) {
|
|
2702
|
+
return err instanceof Error ? err.message : String(err);
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
// libs/auth/src/session/token-storage.factory.ts
|
|
2706
|
+
var import_utils8 = require("@frontmcp/utils");
|
|
2707
|
+
function isRedisTokenStorage(config) {
|
|
2708
|
+
return typeof config === "object" && config !== null && "redis" in config && !!config.redis;
|
|
2709
|
+
}
|
|
2710
|
+
function isSqliteTokenStorage(config) {
|
|
2711
|
+
return typeof config === "object" && config !== null && "sqlite" in config && !!config.sqlite;
|
|
2712
|
+
}
|
|
2713
|
+
function isPersistentTokenStorage(config) {
|
|
2714
|
+
return config !== void 0 && config !== "memory";
|
|
2715
|
+
}
|
|
2716
|
+
async function createTokenStorageAdapter(config) {
|
|
2717
|
+
if (config === void 0 || config === "memory") {
|
|
2718
|
+
const adapter = new import_utils8.MemoryStorageAdapter();
|
|
2719
|
+
await adapter.connect();
|
|
2720
|
+
return adapter;
|
|
2721
|
+
}
|
|
2722
|
+
if (isSqliteTokenStorage(config)) {
|
|
2723
|
+
const { SqliteStorageAdapter } = require("@frontmcp/storage-sqlite");
|
|
2724
|
+
const adapter = new SqliteStorageAdapter({
|
|
2725
|
+
path: config.sqlite.path,
|
|
2726
|
+
encryption: config.sqlite.encryption,
|
|
2727
|
+
ttlCleanupIntervalMs: config.sqlite.ttlCleanupIntervalMs ?? 6e4,
|
|
2728
|
+
walMode: config.sqlite.walMode ?? true
|
|
2729
|
+
});
|
|
2730
|
+
await adapter.connect();
|
|
2731
|
+
return adapter;
|
|
2732
|
+
}
|
|
2733
|
+
if (isRedisTokenStorage(config)) {
|
|
2734
|
+
const { host, port, password, db, tls, keyPrefix } = config.redis;
|
|
2735
|
+
const adapter = new import_utils8.RedisStorageAdapter({
|
|
2736
|
+
config: { host, port, password, db, tls },
|
|
2737
|
+
keyPrefix: keyPrefix ?? ""
|
|
2738
|
+
});
|
|
2739
|
+
await adapter.connect();
|
|
2740
|
+
return adapter;
|
|
2741
|
+
}
|
|
2742
|
+
const fallback = new import_utils8.MemoryStorageAdapter();
|
|
2743
|
+
await fallback.connect();
|
|
2744
|
+
return fallback;
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
// libs/auth/src/session/session-credential-vault.factory.ts
|
|
2748
|
+
async function createSessionCredentialVault(options) {
|
|
2749
|
+
const storage = options.storage ?? await createTokenStorageAdapter(options.tokenStorage);
|
|
2750
|
+
const vault = new SessionCredentialVault({
|
|
2751
|
+
storage,
|
|
2752
|
+
pepper: options.pepper,
|
|
2753
|
+
keyPrefix: options.keyPrefix,
|
|
2754
|
+
ttlMs: options.ttlMs,
|
|
2755
|
+
logger: options.logger
|
|
2756
|
+
});
|
|
2757
|
+
const type = options.tokenStorage === void 0 || options.tokenStorage === "memory" ? "memory" : "sqlite" in options.tokenStorage ? "sqlite" : "redis";
|
|
2758
|
+
return { vault, type };
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
// libs/auth/src/session/credential-resume-link.ts
|
|
2762
|
+
var import_utils9 = require("@frontmcp/utils");
|
|
2763
|
+
var DEFAULT_RESUME_TTL_MS = 6e5;
|
|
2764
|
+
function signCredentialResumeToken(input, secret) {
|
|
2765
|
+
const payload = {
|
|
2766
|
+
sub: input.sub,
|
|
2767
|
+
key: input.key,
|
|
2768
|
+
...input.context !== void 0 ? { context: input.context } : {},
|
|
2769
|
+
exp: Date.now() + (input.ttlMs ?? DEFAULT_RESUME_TTL_MS)
|
|
2770
|
+
};
|
|
2771
|
+
const signedJson = (0, import_utils9.signData)(payload, { secret });
|
|
2772
|
+
return Buffer.from(signedJson, "utf8").toString("base64url");
|
|
2773
|
+
}
|
|
2774
|
+
function verifyCredentialResumeToken(token, secret, now = Date.now()) {
|
|
2775
|
+
let signedJson;
|
|
2776
|
+
try {
|
|
2777
|
+
signedJson = Buffer.from(token, "base64url").toString("utf8");
|
|
2778
|
+
} catch {
|
|
2779
|
+
return null;
|
|
2780
|
+
}
|
|
2781
|
+
const payload = (0, import_utils9.verifyData)(signedJson, { secret });
|
|
2782
|
+
if (!payload) return null;
|
|
2783
|
+
if (typeof payload.sub !== "string" || typeof payload.key !== "string" || !Number.isFinite(payload.exp)) {
|
|
2784
|
+
return null;
|
|
2785
|
+
}
|
|
2786
|
+
if (payload.context !== void 0 && typeof payload.context !== "string") {
|
|
2787
|
+
return null;
|
|
2788
|
+
}
|
|
2789
|
+
if (now >= payload.exp) {
|
|
2790
|
+
return null;
|
|
2791
|
+
}
|
|
2792
|
+
return payload;
|
|
2793
|
+
}
|
|
2794
|
+
function buildCredentialResumeUrl(basePath, token) {
|
|
2795
|
+
const trimmed = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
|
|
2796
|
+
return `${trimmed}/oauth/connect?token=${encodeURIComponent(token)}`;
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
// libs/auth/src/session/credentials-accessor.ts
|
|
2800
|
+
var CREDENTIALS_ACCESSOR = /* @__PURE__ */ Symbol.for("frontmcp:CREDENTIALS_ACCESSOR");
|
|
2801
|
+
|
|
2802
|
+
// libs/auth/src/session/credentials-accessor.impl.ts
|
|
2803
|
+
var CredentialsAccessorImpl = class {
|
|
2804
|
+
vault;
|
|
2805
|
+
resolveSub;
|
|
2806
|
+
signingSecret;
|
|
2807
|
+
basePath;
|
|
2808
|
+
resumeTtlMs;
|
|
2809
|
+
logger;
|
|
2810
|
+
constructor(deps) {
|
|
2811
|
+
this.vault = deps.vault;
|
|
2812
|
+
this.resolveSub = deps.resolveSub;
|
|
2813
|
+
this.signingSecret = deps.signingSecret;
|
|
2814
|
+
this.basePath = deps.basePath;
|
|
2815
|
+
this.resumeTtlMs = deps.resumeTtlMs;
|
|
2816
|
+
this.logger = deps.logger ?? noopAuthLogger;
|
|
2817
|
+
}
|
|
2818
|
+
async get(key) {
|
|
2819
|
+
const sub = this.resolveSub();
|
|
2820
|
+
if (!sub) return void 0;
|
|
2821
|
+
return this.vault.get(sub, key);
|
|
2822
|
+
}
|
|
2823
|
+
async list() {
|
|
2824
|
+
const sub = this.resolveSub();
|
|
2825
|
+
if (!sub) return [];
|
|
2826
|
+
return this.vault.list(sub);
|
|
2827
|
+
}
|
|
2828
|
+
async requireConnect(options) {
|
|
2829
|
+
const { key, context } = options;
|
|
2830
|
+
const sub = this.resolveSub();
|
|
2831
|
+
if (sub) {
|
|
2832
|
+
const existing = await this.vault.get(sub, key);
|
|
2833
|
+
if (existing) {
|
|
2834
|
+
return { connected: true, key, credential: existing };
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
const token = signCredentialResumeToken(
|
|
2838
|
+
{ sub: sub ?? "", key, context, ttlMs: this.resumeTtlMs },
|
|
2839
|
+
this.signingSecret
|
|
2840
|
+
);
|
|
2841
|
+
const resumeUrl = buildCredentialResumeUrl(this.basePath, token);
|
|
2842
|
+
this.logger.debug(`CredentialsAccessor: credential "${key}" not connected; issued resume link`);
|
|
2843
|
+
return {
|
|
2844
|
+
connected: false,
|
|
2845
|
+
key,
|
|
2846
|
+
resumeUrl,
|
|
2847
|
+
message: `Credential "${key}" is not connected. Open the resume URL to connect it.`
|
|
2848
|
+
};
|
|
2849
|
+
}
|
|
2850
|
+
};
|
|
2851
|
+
|
|
2852
|
+
// libs/auth/src/session/secure-store-backends.ts
|
|
2853
|
+
var DEFAULT_KEY_PREFIX2 = "mcp:secret:";
|
|
2854
|
+
var EncryptedStorageSecureStoreBackend = class {
|
|
2855
|
+
storage;
|
|
2856
|
+
encryption;
|
|
2857
|
+
keyPrefix;
|
|
2858
|
+
logger;
|
|
2859
|
+
/** Cache of derived AES keys, keyed by namespace. */
|
|
2860
|
+
keyCache = /* @__PURE__ */ new Map();
|
|
2861
|
+
constructor(options) {
|
|
2862
|
+
this.storage = options.storage;
|
|
2863
|
+
this.keyPrefix = options.keyPrefix ?? DEFAULT_KEY_PREFIX2;
|
|
2864
|
+
this.logger = options.logger ?? noopAuthLogger;
|
|
2865
|
+
this.encryption = new VaultEncryption({
|
|
2866
|
+
pepper: options.pepper,
|
|
2867
|
+
hkdfInfo: "frontmcp-secure-store-v1"
|
|
2868
|
+
});
|
|
2869
|
+
}
|
|
2870
|
+
// ============================================
|
|
2871
|
+
// Storage-key helpers
|
|
2872
|
+
// ============================================
|
|
2873
|
+
/** `secret:keys:<namespace>` → JSON array of key names. */
|
|
2874
|
+
keysKey(namespace) {
|
|
2875
|
+
return `${this.keyPrefix}keys:${namespace}`;
|
|
2876
|
+
}
|
|
2877
|
+
/** `secret:data:<namespace>:<key>` → encrypted record. */
|
|
2878
|
+
dataKey(namespace, key) {
|
|
2879
|
+
return `${this.keyPrefix}data:${namespace}:${key}`;
|
|
2880
|
+
}
|
|
2881
|
+
/**
|
|
2882
|
+
* Derive (and cache) the AES-256 key for a namespace. The namespace acts as
|
|
2883
|
+
* the HKDF salt material, so values are cryptographically isolated per scope.
|
|
2884
|
+
*/
|
|
2885
|
+
deriveKey(namespace) {
|
|
2886
|
+
const cached = this.keyCache.get(namespace);
|
|
2887
|
+
if (cached) return cached;
|
|
2888
|
+
const promise = this.encryption.deriveKey({ jti: namespace, vaultKey: namespace, sub: namespace, iat: 0 });
|
|
2889
|
+
this.keyCache.set(namespace, promise);
|
|
2890
|
+
return promise;
|
|
2891
|
+
}
|
|
2892
|
+
setOpts(ttlMs) {
|
|
2893
|
+
return ttlMs && ttlMs > 0 ? { ttlSeconds: Math.ceil(ttlMs / 1e3) } : void 0;
|
|
2894
|
+
}
|
|
2895
|
+
// ============================================
|
|
2896
|
+
// SecureStoreBackend
|
|
2897
|
+
// ============================================
|
|
2898
|
+
async get(namespace, key) {
|
|
2899
|
+
const raw = await this.storage.get(this.dataKey(namespace, key));
|
|
2900
|
+
if (!raw) return null;
|
|
2901
|
+
try {
|
|
2902
|
+
const record = JSON.parse(raw);
|
|
2903
|
+
const aesKey = await this.deriveKey(namespace);
|
|
2904
|
+
return await this.encryption.decrypt(record.enc, aesKey);
|
|
2905
|
+
} catch (err) {
|
|
2906
|
+
this.logger.warn(`SecureStore: failed to read secret "${key}": ${errMsg2(err)}`);
|
|
2907
|
+
return null;
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
async set(namespace, key, value, ttlMs) {
|
|
2911
|
+
const aesKey = await this.deriveKey(namespace);
|
|
2912
|
+
const enc = await this.encryption.encrypt(value, aesKey);
|
|
2913
|
+
const record = { enc };
|
|
2914
|
+
const opts = this.setOpts(ttlMs);
|
|
2915
|
+
await this.storage.set(this.dataKey(namespace, key), JSON.stringify(record), opts);
|
|
2916
|
+
await this.addKeyToSet(namespace, key, opts);
|
|
2917
|
+
}
|
|
2918
|
+
async delete(namespace, key) {
|
|
2919
|
+
const existed = await this.storage.delete(this.dataKey(namespace, key));
|
|
2920
|
+
const keys = await this.readKeySet(namespace);
|
|
2921
|
+
const next = keys.filter((k) => k !== key);
|
|
2922
|
+
if (next.length !== keys.length) {
|
|
2923
|
+
await this.writeKeySet(namespace, next);
|
|
2924
|
+
}
|
|
2925
|
+
return existed;
|
|
2926
|
+
}
|
|
2927
|
+
async list(namespace) {
|
|
2928
|
+
return this.readKeySet(namespace);
|
|
2929
|
+
}
|
|
2930
|
+
// ============================================
|
|
2931
|
+
// Key-set helpers (the namespace's key SET)
|
|
2932
|
+
// ============================================
|
|
2933
|
+
async readKeySet(namespace) {
|
|
2934
|
+
const raw = await this.storage.get(this.keysKey(namespace));
|
|
2935
|
+
if (!raw) return [];
|
|
2936
|
+
try {
|
|
2937
|
+
const parsed = JSON.parse(raw);
|
|
2938
|
+
return Array.isArray(parsed) ? parsed.filter((k) => typeof k === "string") : [];
|
|
2939
|
+
} catch {
|
|
2940
|
+
return [];
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
async writeKeySet(namespace, keys, opts) {
|
|
2944
|
+
const unique = [...new Set(keys)];
|
|
2945
|
+
await this.storage.set(this.keysKey(namespace), JSON.stringify(unique), opts);
|
|
2946
|
+
}
|
|
2947
|
+
async addKeyToSet(namespace, key, opts) {
|
|
2948
|
+
const keys = await this.readKeySet(namespace);
|
|
2949
|
+
if (!keys.includes(key)) {
|
|
2950
|
+
keys.push(key);
|
|
2951
|
+
await this.writeKeySet(namespace, keys, opts);
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
};
|
|
2955
|
+
function errMsg2(err) {
|
|
2956
|
+
return err instanceof Error ? err.message : String(err);
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
// libs/auth/src/session/secure-store-accessor.ts
|
|
2960
|
+
var SECURE_STORE_ACCESSOR = /* @__PURE__ */ Symbol.for("frontmcp:SECURE_STORE_ACCESSOR");
|
|
2961
|
+
|
|
2962
|
+
// libs/auth/src/session/secure-store-accessor.impl.ts
|
|
2963
|
+
var import_utils10 = require("@frontmcp/utils");
|
|
2964
|
+
var GLOBAL_NAMESPACE = "global";
|
|
2965
|
+
var SecureStoreAccessorImpl = class {
|
|
2966
|
+
backend;
|
|
2967
|
+
scope;
|
|
2968
|
+
resolveSub;
|
|
2969
|
+
resolveSessionId;
|
|
2970
|
+
ttlMs;
|
|
2971
|
+
logger;
|
|
2972
|
+
constructor(deps) {
|
|
2973
|
+
this.backend = deps.backend;
|
|
2974
|
+
this.scope = deps.scope ?? "user";
|
|
2975
|
+
this.resolveSub = deps.resolveSub;
|
|
2976
|
+
this.resolveSessionId = deps.resolveSessionId;
|
|
2977
|
+
this.ttlMs = deps.ttlMs;
|
|
2978
|
+
this.logger = deps.logger ?? noopAuthLogger;
|
|
2979
|
+
}
|
|
2980
|
+
/**
|
|
2981
|
+
* Resolve the namespace for the current request from the configured scope.
|
|
2982
|
+
* Returns undefined when the scope's identity is unavailable (anonymous user
|
|
2983
|
+
* under `user` scope, or no session under `session` scope), in which case
|
|
2984
|
+
* reads return empty and writes are skipped.
|
|
2985
|
+
*/
|
|
2986
|
+
resolveNamespace() {
|
|
2987
|
+
switch (this.scope) {
|
|
2988
|
+
case "global":
|
|
2989
|
+
return GLOBAL_NAMESPACE;
|
|
2990
|
+
case "session": {
|
|
2991
|
+
const sid = this.resolveSessionId();
|
|
2992
|
+
return sid ? `s:${(0, import_utils10.sha256Hex)(sid).slice(0, 32)}` : void 0;
|
|
2993
|
+
}
|
|
2994
|
+
case "user":
|
|
2995
|
+
default: {
|
|
2996
|
+
const sub = this.resolveSub();
|
|
2997
|
+
return sub ? `u:${(0, import_utils10.sha256Hex)(sub).slice(0, 32)}` : void 0;
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
async get(key) {
|
|
3002
|
+
const namespace = this.resolveNamespace();
|
|
3003
|
+
if (!namespace) return void 0;
|
|
3004
|
+
const raw = await this.backend.get(namespace, key);
|
|
3005
|
+
if (raw === null) return void 0;
|
|
3006
|
+
try {
|
|
3007
|
+
return JSON.parse(raw);
|
|
3008
|
+
} catch (err) {
|
|
3009
|
+
this.logger.warn(`SecureStore: failed to parse secret "${key}": ${errMsg3(err)}`);
|
|
3010
|
+
return void 0;
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
async set(key, value, options) {
|
|
3014
|
+
const namespace = this.resolveNamespace();
|
|
3015
|
+
if (!namespace) {
|
|
3016
|
+
this.logger.debug(`SecureStore: no ${this.scope} scope for request; skipping set("${key}")`);
|
|
3017
|
+
return;
|
|
3018
|
+
}
|
|
3019
|
+
const ttlMs = options?.ttlMs ?? this.ttlMs;
|
|
3020
|
+
await this.backend.set(namespace, key, JSON.stringify(value), ttlMs);
|
|
2095
3021
|
}
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
* @param key - Encryption key
|
|
2101
|
-
* @returns Decrypted and parsed object
|
|
2102
|
-
*/
|
|
2103
|
-
async decryptObject(encrypted, key) {
|
|
2104
|
-
const plaintext = await this.decrypt(encrypted, key);
|
|
2105
|
-
return JSON.parse(plaintext);
|
|
3022
|
+
async delete(key) {
|
|
3023
|
+
const namespace = this.resolveNamespace();
|
|
3024
|
+
if (!namespace) return false;
|
|
3025
|
+
return this.backend.delete(namespace, key);
|
|
2106
3026
|
}
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
* @returns True if data appears to be encrypted
|
|
2112
|
-
*/
|
|
2113
|
-
isEncrypted(data) {
|
|
2114
|
-
return encryptedDataSchema.safeParse(data).success;
|
|
3027
|
+
async list() {
|
|
3028
|
+
const namespace = this.resolveNamespace();
|
|
3029
|
+
if (!namespace) return [];
|
|
3030
|
+
return this.backend.list(namespace);
|
|
2115
3031
|
}
|
|
2116
3032
|
};
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
3033
|
+
function errMsg3(err) {
|
|
3034
|
+
return err instanceof Error ? err.message : String(err);
|
|
3035
|
+
}
|
|
3036
|
+
|
|
3037
|
+
// libs/auth/src/session/secure-store.factory.ts
|
|
3038
|
+
var import_utils11 = require("@frontmcp/utils");
|
|
3039
|
+
function isCustomBackendConfig(config) {
|
|
3040
|
+
return typeof config === "object" && config !== null && "backend" in config && !!config.backend;
|
|
3041
|
+
}
|
|
3042
|
+
function isSqliteConfig(config) {
|
|
3043
|
+
return typeof config === "object" && config !== null && "sqlite" in config && !!config.sqlite;
|
|
3044
|
+
}
|
|
3045
|
+
function isRedisConfig(config) {
|
|
3046
|
+
return typeof config === "object" && config !== null && "redis" in config && !!config.redis;
|
|
3047
|
+
}
|
|
3048
|
+
function readScope(config) {
|
|
3049
|
+
if (typeof config === "object" && config !== null && "scope" in config && config.scope) {
|
|
3050
|
+
return config.scope;
|
|
3051
|
+
}
|
|
3052
|
+
return "user";
|
|
3053
|
+
}
|
|
3054
|
+
function readTtlMs(config) {
|
|
3055
|
+
if (typeof config === "object" && config !== null && "ttlMs" in config) {
|
|
3056
|
+
return config.ttlMs;
|
|
3057
|
+
}
|
|
3058
|
+
return void 0;
|
|
3059
|
+
}
|
|
3060
|
+
function readPepper(config) {
|
|
3061
|
+
if (typeof config === "object" && config !== null && "encryption" in config && config.encryption) {
|
|
3062
|
+
return config.encryption.pepper;
|
|
3063
|
+
}
|
|
3064
|
+
return void 0;
|
|
3065
|
+
}
|
|
3066
|
+
function toTokenStorageConfig(config) {
|
|
3067
|
+
if (isSqliteConfig(config)) {
|
|
3068
|
+
return { sqlite: config.sqlite };
|
|
3069
|
+
}
|
|
3070
|
+
if (isRedisConfig(config)) {
|
|
3071
|
+
return { redis: config.redis };
|
|
3072
|
+
}
|
|
3073
|
+
return "memory";
|
|
3074
|
+
}
|
|
3075
|
+
async function createSecureStore(options) {
|
|
3076
|
+
const { config, pepper, storage, logger } = options;
|
|
3077
|
+
const scope = readScope(config);
|
|
3078
|
+
const ttlMs = readTtlMs(config);
|
|
3079
|
+
if (isCustomBackendConfig(config)) {
|
|
3080
|
+
return { backend: config.backend, scope, ttlMs, kind: "custom" };
|
|
3081
|
+
}
|
|
3082
|
+
const kind = isSqliteConfig(config) ? "sqlite" : isRedisConfig(config) ? "redis" : "memory";
|
|
3083
|
+
let adapter;
|
|
3084
|
+
if (storage) {
|
|
3085
|
+
adapter = storage;
|
|
3086
|
+
} else if (kind === "memory") {
|
|
3087
|
+
adapter = new import_utils11.MemoryStorageAdapter();
|
|
3088
|
+
await adapter.connect();
|
|
3089
|
+
} else {
|
|
3090
|
+
adapter = await createTokenStorageAdapter(toTokenStorageConfig(config));
|
|
3091
|
+
}
|
|
3092
|
+
const backend = new EncryptedStorageSecureStoreBackend({
|
|
3093
|
+
storage: adapter,
|
|
3094
|
+
pepper: readPepper(config) ?? pepper,
|
|
3095
|
+
logger
|
|
3096
|
+
});
|
|
3097
|
+
return { backend, scope, ttlMs, kind };
|
|
3098
|
+
}
|
|
2141
3099
|
|
|
2142
3100
|
// libs/auth/src/session/token.vault.ts
|
|
2143
|
-
var
|
|
3101
|
+
var import_utils12 = require("@frontmcp/utils");
|
|
2144
3102
|
var TokenVault = class {
|
|
2145
3103
|
/** Active key used for new encryptions */
|
|
2146
3104
|
active;
|
|
@@ -2169,14 +3127,14 @@ var TokenVault = class {
|
|
|
2169
3127
|
this.keys.set(k.kid, k.key);
|
|
2170
3128
|
}
|
|
2171
3129
|
async encrypt(plaintext, opts) {
|
|
2172
|
-
const iv = (0,
|
|
2173
|
-
const { ciphertext, tag } = await (0,
|
|
3130
|
+
const iv = (0, import_utils12.randomBytes)(12);
|
|
3131
|
+
const { ciphertext, tag } = await (0, import_utils12.encryptAesGcm)(this.active.key, new TextEncoder().encode(plaintext), iv);
|
|
2174
3132
|
return {
|
|
2175
3133
|
alg: "A256GCM",
|
|
2176
3134
|
kid: this.active.kid,
|
|
2177
|
-
iv: (0,
|
|
2178
|
-
tag: (0,
|
|
2179
|
-
data: (0,
|
|
3135
|
+
iv: (0, import_utils12.base64urlEncode)(iv),
|
|
3136
|
+
tag: (0, import_utils12.base64urlEncode)(tag),
|
|
3137
|
+
data: (0, import_utils12.base64urlEncode)(ciphertext),
|
|
2180
3138
|
exp: opts?.exp,
|
|
2181
3139
|
meta: opts?.meta
|
|
2182
3140
|
};
|
|
@@ -2190,16 +3148,16 @@ var TokenVault = class {
|
|
|
2190
3148
|
}
|
|
2191
3149
|
const key = this.keys.get(blob.kid);
|
|
2192
3150
|
if (!key) throw new Error(`vault_unknown_kid:${blob.kid}`);
|
|
2193
|
-
const iv = (0,
|
|
2194
|
-
const tag = (0,
|
|
2195
|
-
const data = (0,
|
|
2196
|
-
const plaintext = await (0,
|
|
3151
|
+
const iv = (0, import_utils12.base64urlDecode)(blob.iv);
|
|
3152
|
+
const tag = (0, import_utils12.base64urlDecode)(blob.tag);
|
|
3153
|
+
const data = (0, import_utils12.base64urlDecode)(blob.data);
|
|
3154
|
+
const plaintext = await (0, import_utils12.decryptAesGcm)(key, data, iv, tag);
|
|
2197
3155
|
return new TextDecoder().decode(plaintext);
|
|
2198
3156
|
}
|
|
2199
3157
|
};
|
|
2200
3158
|
|
|
2201
3159
|
// libs/auth/src/session/index.ts
|
|
2202
|
-
var
|
|
3160
|
+
var import_utils32 = require("@frontmcp/utils");
|
|
2203
3161
|
|
|
2204
3162
|
// libs/auth/src/session/transport-session.types.ts
|
|
2205
3163
|
var import_lazy_zod4 = require("@frontmcp/lazy-zod");
|
|
@@ -2295,10 +3253,10 @@ var redisConfigSchema = import_lazy_zod4.z.object({
|
|
|
2295
3253
|
});
|
|
2296
3254
|
|
|
2297
3255
|
// libs/auth/src/session/session-crypto.ts
|
|
2298
|
-
var
|
|
3256
|
+
var import_utils14 = require("@frontmcp/utils");
|
|
2299
3257
|
|
|
2300
3258
|
// libs/auth/src/errors/auth-internal.error.ts
|
|
2301
|
-
var
|
|
3259
|
+
var import_utils13 = require("@frontmcp/utils");
|
|
2302
3260
|
var AuthInternalError = class extends Error {
|
|
2303
3261
|
/**
|
|
2304
3262
|
* Unique error ID for tracking in logs.
|
|
@@ -2320,7 +3278,7 @@ var AuthInternalError = class extends Error {
|
|
|
2320
3278
|
super(message);
|
|
2321
3279
|
this.name = this.constructor.name;
|
|
2322
3280
|
this.code = code;
|
|
2323
|
-
this.errorId = `err_${(0,
|
|
3281
|
+
this.errorId = `err_${(0, import_utils13.bytesToHex)((0, import_utils13.randomBytes)(8))}`;
|
|
2324
3282
|
Error.captureStackTrace(this, this.constructor);
|
|
2325
3283
|
}
|
|
2326
3284
|
/**
|
|
@@ -2443,9 +3401,9 @@ var AuthFlowError = class extends AuthInternalError {
|
|
|
2443
3401
|
|
|
2444
3402
|
// libs/auth/src/session/session-crypto.ts
|
|
2445
3403
|
function getSigningSecret(config) {
|
|
2446
|
-
const secret = config?.secret || (0,
|
|
3404
|
+
const secret = config?.secret || (0, import_utils14.getEnv)("MCP_SESSION_SECRET");
|
|
2447
3405
|
if (!secret) {
|
|
2448
|
-
if ((0,
|
|
3406
|
+
if ((0, import_utils14.isProduction)()) {
|
|
2449
3407
|
throw new SessionSecretRequiredError("session signing");
|
|
2450
3408
|
}
|
|
2451
3409
|
console.warn("[SessionCrypto] MCP_SESSION_SECRET not set. Using insecure default for development only.");
|
|
@@ -2457,13 +3415,13 @@ function resolveConfig(config) {
|
|
|
2457
3415
|
return { secret: getSigningSecret(config) };
|
|
2458
3416
|
}
|
|
2459
3417
|
function signSession(session, config) {
|
|
2460
|
-
return (0,
|
|
3418
|
+
return (0, import_utils14.signData)(session, resolveConfig(config));
|
|
2461
3419
|
}
|
|
2462
3420
|
function verifySession(signedData, config) {
|
|
2463
|
-
return (0,
|
|
3421
|
+
return (0, import_utils14.verifyData)(signedData, resolveConfig(config));
|
|
2464
3422
|
}
|
|
2465
3423
|
function verifyOrParseSession(data, config) {
|
|
2466
|
-
return (0,
|
|
3424
|
+
return (0, import_utils14.verifyOrParseData)(data, resolveConfig(config));
|
|
2467
3425
|
}
|
|
2468
3426
|
|
|
2469
3427
|
// libs/auth/src/session/session-rate-limiter.ts
|
|
@@ -2582,7 +3540,7 @@ var SessionRateLimiter = class {
|
|
|
2582
3540
|
var defaultSessionRateLimiter = new SessionRateLimiter();
|
|
2583
3541
|
|
|
2584
3542
|
// libs/auth/src/session/session.transport.ts
|
|
2585
|
-
var
|
|
3543
|
+
var import_utils15 = require("@frontmcp/utils");
|
|
2586
3544
|
var TransportIdGenerator = class {
|
|
2587
3545
|
/**
|
|
2588
3546
|
* Create a transport session ID.
|
|
@@ -2591,7 +3549,7 @@ var TransportIdGenerator = class {
|
|
|
2591
3549
|
* @returns A transport session ID (UUID without dashes)
|
|
2592
3550
|
*/
|
|
2593
3551
|
static createId() {
|
|
2594
|
-
return (0,
|
|
3552
|
+
return (0, import_utils15.randomUUID)().replace(/-/g, "");
|
|
2595
3553
|
}
|
|
2596
3554
|
};
|
|
2597
3555
|
|
|
@@ -2639,7 +3597,7 @@ var TinyTtlCache = class {
|
|
|
2639
3597
|
};
|
|
2640
3598
|
|
|
2641
3599
|
// libs/auth/src/session/utils/auth-token.utils.ts
|
|
2642
|
-
var
|
|
3600
|
+
var import_utils16 = require("@frontmcp/utils");
|
|
2643
3601
|
function isJwt(token) {
|
|
2644
3602
|
if (!token) return false;
|
|
2645
3603
|
return token.split(".").length === 3;
|
|
@@ -2649,7 +3607,7 @@ function getTokenSignatureFingerprint(token) {
|
|
|
2649
3607
|
const sig = token.split(".")[2];
|
|
2650
3608
|
if (sig) return sig;
|
|
2651
3609
|
}
|
|
2652
|
-
return (0,
|
|
3610
|
+
return (0, import_utils16.sha256Base64url)(token);
|
|
2653
3611
|
}
|
|
2654
3612
|
function extractClaimValue(claims, key, validator) {
|
|
2655
3613
|
const value = claims[key];
|
|
@@ -2681,26 +3639,26 @@ function extractBearerToken(header) {
|
|
|
2681
3639
|
}
|
|
2682
3640
|
|
|
2683
3641
|
// libs/auth/src/session/utils/session-crypto.utils.ts
|
|
2684
|
-
var
|
|
3642
|
+
var import_utils17 = require("@frontmcp/utils");
|
|
2685
3643
|
var cachedKey = null;
|
|
2686
3644
|
function getKey() {
|
|
2687
3645
|
if (cachedKey) return cachedKey;
|
|
2688
|
-
const secret = (0,
|
|
3646
|
+
const secret = (0, import_utils17.getEnv)("MCP_SESSION_SECRET");
|
|
2689
3647
|
if (!secret) {
|
|
2690
|
-
if ((0,
|
|
3648
|
+
if ((0, import_utils17.isProduction)()) {
|
|
2691
3649
|
throw new SessionSecretRequiredError("session ID encryption");
|
|
2692
3650
|
}
|
|
2693
3651
|
console.warn(
|
|
2694
3652
|
"[SessionIdUtils] Using machine ID as session encryption secret - NOT SECURE FOR PRODUCTION. Set MCP_SESSION_SECRET environment variable for secure session encryption."
|
|
2695
3653
|
);
|
|
2696
3654
|
}
|
|
2697
|
-
const base = secret || (0,
|
|
2698
|
-
cachedKey = (0,
|
|
3655
|
+
const base = secret || (0, import_utils17.getMachineId)();
|
|
3656
|
+
cachedKey = (0, import_utils17.sha256)(new TextEncoder().encode(base));
|
|
2699
3657
|
return cachedKey;
|
|
2700
3658
|
}
|
|
2701
3659
|
function encryptJson(obj) {
|
|
2702
3660
|
const key = getKey();
|
|
2703
|
-
const encrypted = (0,
|
|
3661
|
+
const encrypted = (0, import_utils17.encryptValue)(obj, key);
|
|
2704
3662
|
return `${encrypted.iv}.${encrypted.tag}.${encrypted.data}`;
|
|
2705
3663
|
}
|
|
2706
3664
|
function decryptSessionJson(sessionId) {
|
|
@@ -2709,7 +3667,7 @@ function decryptSessionJson(sessionId) {
|
|
|
2709
3667
|
const [ivB64, tagB64, ctB64] = parts;
|
|
2710
3668
|
if (!ivB64 || !tagB64 || !ctB64) return null;
|
|
2711
3669
|
const key = getKey();
|
|
2712
|
-
return (0,
|
|
3670
|
+
return (0, import_utils17.decryptValue)({ alg: "A256GCM", iv: ivB64, tag: tagB64, data: ctB64 }, key);
|
|
2713
3671
|
}
|
|
2714
3672
|
function safeDecrypt(sessionId) {
|
|
2715
3673
|
try {
|
|
@@ -2723,11 +3681,11 @@ function resetCachedKey() {
|
|
|
2723
3681
|
}
|
|
2724
3682
|
|
|
2725
3683
|
// libs/auth/src/session/storage/index.ts
|
|
2726
|
-
var
|
|
2727
|
-
var
|
|
3684
|
+
var import_utils21 = require("@frontmcp/utils");
|
|
3685
|
+
var import_utils22 = require("@frontmcp/utils");
|
|
2728
3686
|
|
|
2729
3687
|
// libs/auth/src/session/storage/storage-token-store.ts
|
|
2730
|
-
var
|
|
3688
|
+
var import_utils18 = require("@frontmcp/utils");
|
|
2731
3689
|
var StorageTokenStore = class {
|
|
2732
3690
|
storage;
|
|
2733
3691
|
namespace;
|
|
@@ -2739,13 +3697,13 @@ var StorageTokenStore = class {
|
|
|
2739
3697
|
this.defaultTtlSeconds = options.defaultTtlSeconds;
|
|
2740
3698
|
this.storageIsNamespaced = "namespace" in storage && typeof storage.namespace === "function";
|
|
2741
3699
|
const namespacedStorage = this.storageIsNamespaced ? storage.namespace(this.namespace) : storage;
|
|
2742
|
-
this.storage = new
|
|
3700
|
+
this.storage = new import_utils18.TypedStorage(namespacedStorage);
|
|
2743
3701
|
}
|
|
2744
3702
|
/**
|
|
2745
3703
|
* Allocate a new unique ID for a token record.
|
|
2746
3704
|
*/
|
|
2747
3705
|
allocId() {
|
|
2748
|
-
return (0,
|
|
3706
|
+
return (0, import_utils18.randomUUID)();
|
|
2749
3707
|
}
|
|
2750
3708
|
/**
|
|
2751
3709
|
* Store an encrypted token blob.
|
|
@@ -2808,7 +3766,7 @@ var StorageTokenStore = class {
|
|
|
2808
3766
|
};
|
|
2809
3767
|
|
|
2810
3768
|
// libs/auth/src/session/storage/storage-authorization-vault.ts
|
|
2811
|
-
var
|
|
3769
|
+
var import_utils19 = require("@frontmcp/utils");
|
|
2812
3770
|
var StorageAuthorizationVault = class {
|
|
2813
3771
|
storage;
|
|
2814
3772
|
namespace;
|
|
@@ -2817,7 +3775,7 @@ var StorageAuthorizationVault = class {
|
|
|
2817
3775
|
this.namespace = options.namespace ?? "vault";
|
|
2818
3776
|
this.pendingAuthTtlMs = options.pendingAuthTtlMs ?? 10 * 60 * 1e3;
|
|
2819
3777
|
const namespacedStorage = this.isNamespacedStorage(storage) ? storage.namespace(this.namespace) : storage;
|
|
2820
|
-
this.storage = new
|
|
3778
|
+
this.storage = new import_utils19.TypedStorage(namespacedStorage, {
|
|
2821
3779
|
schema: options.validateOnRead ? authorizationVaultEntrySchema : void 0,
|
|
2822
3780
|
throwOnInvalid: false
|
|
2823
3781
|
});
|
|
@@ -2828,7 +3786,7 @@ var StorageAuthorizationVault = class {
|
|
|
2828
3786
|
async create(params) {
|
|
2829
3787
|
const now = Date.now();
|
|
2830
3788
|
const entry = {
|
|
2831
|
-
id: (0,
|
|
3789
|
+
id: (0, import_utils19.randomUUID)(),
|
|
2832
3790
|
userSub: params.userSub,
|
|
2833
3791
|
userEmail: params.userEmail,
|
|
2834
3792
|
userName: params.userName,
|
|
@@ -2902,7 +3860,7 @@ var StorageAuthorizationVault = class {
|
|
|
2902
3860
|
}
|
|
2903
3861
|
const now = Date.now();
|
|
2904
3862
|
const pendingAuth = {
|
|
2905
|
-
id: (0,
|
|
3863
|
+
id: (0, import_utils19.randomUUID)(),
|
|
2906
3864
|
appId: params.appId,
|
|
2907
3865
|
toolId: params.toolId,
|
|
2908
3866
|
authUrl: params.authUrl,
|
|
@@ -3106,11 +4064,11 @@ var StorageAuthorizationVault = class {
|
|
|
3106
4064
|
};
|
|
3107
4065
|
|
|
3108
4066
|
// libs/auth/src/session/storage/in-memory-authorization-vault.ts
|
|
3109
|
-
var
|
|
4067
|
+
var import_utils20 = require("@frontmcp/utils");
|
|
3110
4068
|
var InMemoryAuthorizationVault = class extends StorageAuthorizationVault {
|
|
3111
4069
|
memoryAdapter;
|
|
3112
4070
|
constructor(options = {}) {
|
|
3113
|
-
const memoryAdapter = new
|
|
4071
|
+
const memoryAdapter = new import_utils20.MemoryStorageAdapter();
|
|
3114
4072
|
super(memoryAdapter, {
|
|
3115
4073
|
namespace: options.namespace ?? "vault",
|
|
3116
4074
|
pendingAuthTtlMs: options.pendingAuthTtlMs
|
|
@@ -3131,7 +4089,7 @@ var InMemoryAuthorizationVault = class extends StorageAuthorizationVault {
|
|
|
3131
4089
|
};
|
|
3132
4090
|
|
|
3133
4091
|
// libs/auth/src/session/redis-session.store.ts
|
|
3134
|
-
var
|
|
4092
|
+
var import_utils23 = require("@frontmcp/utils");
|
|
3135
4093
|
var RedisSessionStore = class {
|
|
3136
4094
|
storage;
|
|
3137
4095
|
keyPrefix;
|
|
@@ -3153,14 +4111,14 @@ var RedisSessionStore = class {
|
|
|
3153
4111
|
});
|
|
3154
4112
|
}
|
|
3155
4113
|
if ("redis" in config && config.redis) {
|
|
3156
|
-
this.storage = new
|
|
4114
|
+
this.storage = new import_utils23.RedisStorageAdapter({
|
|
3157
4115
|
client: config.redis,
|
|
3158
4116
|
keyPrefix: this.keyPrefix
|
|
3159
4117
|
});
|
|
3160
4118
|
this.externalInstance = true;
|
|
3161
4119
|
} else {
|
|
3162
4120
|
const redisConfig = config;
|
|
3163
|
-
this.storage = new
|
|
4121
|
+
this.storage = new import_utils23.RedisStorageAdapter({
|
|
3164
4122
|
config: {
|
|
3165
4123
|
host: redisConfig.host,
|
|
3166
4124
|
port: redisConfig.port ?? 6379,
|
|
@@ -3342,7 +4300,7 @@ var RedisSessionStore = class {
|
|
|
3342
4300
|
* Allocate a new session ID
|
|
3343
4301
|
*/
|
|
3344
4302
|
allocId() {
|
|
3345
|
-
return (0,
|
|
4303
|
+
return (0, import_utils23.randomUUID)();
|
|
3346
4304
|
}
|
|
3347
4305
|
/**
|
|
3348
4306
|
* Disconnect from Redis (only if we created the connection)
|
|
@@ -3378,7 +4336,7 @@ var RedisSessionStore = class {
|
|
|
3378
4336
|
};
|
|
3379
4337
|
|
|
3380
4338
|
// libs/auth/src/session/vercel-kv-session.store.ts
|
|
3381
|
-
var
|
|
4339
|
+
var import_utils24 = require("@frontmcp/utils");
|
|
3382
4340
|
var VercelKvSessionStore = class {
|
|
3383
4341
|
storage;
|
|
3384
4342
|
keyPrefix;
|
|
@@ -3398,7 +4356,7 @@ var VercelKvSessionStore = class {
|
|
|
3398
4356
|
maxRequests: this.security.rateLimiting?.maxRequests
|
|
3399
4357
|
});
|
|
3400
4358
|
}
|
|
3401
|
-
this.storage = new
|
|
4359
|
+
this.storage = new import_utils24.VercelKvStorageAdapter({
|
|
3402
4360
|
url: config.url,
|
|
3403
4361
|
token: config.token,
|
|
3404
4362
|
keyPrefix: this.keyPrefix
|
|
@@ -3572,7 +4530,7 @@ var VercelKvSessionStore = class {
|
|
|
3572
4530
|
* Allocate a new session ID
|
|
3573
4531
|
*/
|
|
3574
4532
|
allocId() {
|
|
3575
|
-
return (0,
|
|
4533
|
+
return (0, import_utils24.randomUUID)();
|
|
3576
4534
|
}
|
|
3577
4535
|
/**
|
|
3578
4536
|
* Disconnect from Vercel KV
|
|
@@ -3601,7 +4559,7 @@ var VercelKvSessionStore = class {
|
|
|
3601
4559
|
};
|
|
3602
4560
|
|
|
3603
4561
|
// libs/auth/src/session/orchestrated-token.store.ts
|
|
3604
|
-
var
|
|
4562
|
+
var import_utils25 = require("@frontmcp/utils");
|
|
3605
4563
|
var InMemoryOrchestratedTokenStore = class {
|
|
3606
4564
|
/** Token storage: Map<compositeKey, ProviderTokenRecord> */
|
|
3607
4565
|
tokens = /* @__PURE__ */ new Map();
|
|
@@ -3643,7 +4601,7 @@ var InMemoryOrchestratedTokenStore = class {
|
|
|
3643
4601
|
}
|
|
3644
4602
|
const info = new TextEncoder().encode(`orchestrated-token:${compositeKey}`);
|
|
3645
4603
|
const salt = new TextEncoder().encode("frontmcp-token-store");
|
|
3646
|
-
const derivedKey = (0,
|
|
4604
|
+
const derivedKey = (0, import_utils25.hkdfSha256)(this.encryptionKey, salt, info, 32);
|
|
3647
4605
|
this.derivedKeys.set(compositeKey, derivedKey);
|
|
3648
4606
|
return derivedKey;
|
|
3649
4607
|
}
|
|
@@ -3653,8 +4611,8 @@ var InMemoryOrchestratedTokenStore = class {
|
|
|
3653
4611
|
async encryptRecord(compositeKey, record) {
|
|
3654
4612
|
const key = await this.deriveKeyForRecord(compositeKey);
|
|
3655
4613
|
const plaintext = JSON.stringify(record);
|
|
3656
|
-
const iv = (0,
|
|
3657
|
-
const { ciphertext, tag } = (0,
|
|
4614
|
+
const iv = (0, import_utils25.randomBytes)(12);
|
|
4615
|
+
const { ciphertext, tag } = (0, import_utils25.encryptAesGcm)(key, new TextEncoder().encode(plaintext), iv);
|
|
3658
4616
|
return JSON.stringify({
|
|
3659
4617
|
iv: Buffer.from(iv).toString("base64url"),
|
|
3660
4618
|
tag: Buffer.from(tag).toString("base64url"),
|
|
@@ -3670,7 +4628,7 @@ var InMemoryOrchestratedTokenStore = class {
|
|
|
3670
4628
|
const ivBytes = Buffer.from(iv, "base64url");
|
|
3671
4629
|
const tagBytes = Buffer.from(tag, "base64url");
|
|
3672
4630
|
const ciphertextBytes = Buffer.from(data, "base64url");
|
|
3673
|
-
const plaintext = (0,
|
|
4631
|
+
const plaintext = (0, import_utils25.decryptAesGcm)(key, ciphertextBytes, ivBytes, tagBytes);
|
|
3674
4632
|
return JSON.parse(new TextDecoder().decode(plaintext));
|
|
3675
4633
|
}
|
|
3676
4634
|
/**
|
|
@@ -3778,101 +4736,307 @@ var InMemoryOrchestratedTokenStore = class {
|
|
|
3778
4736
|
}
|
|
3779
4737
|
}
|
|
3780
4738
|
}
|
|
3781
|
-
return providerIds;
|
|
4739
|
+
return providerIds;
|
|
4740
|
+
}
|
|
4741
|
+
/**
|
|
4742
|
+
* Clean up expired tokens
|
|
4743
|
+
*/
|
|
4744
|
+
async cleanup() {
|
|
4745
|
+
const now = Date.now();
|
|
4746
|
+
const keysToDelete = [];
|
|
4747
|
+
for (const [key, stored] of this.tokens.entries()) {
|
|
4748
|
+
let record = null;
|
|
4749
|
+
if (this.encryptionKey) {
|
|
4750
|
+
if (typeof stored === "string") {
|
|
4751
|
+
try {
|
|
4752
|
+
record = await this.decryptRecord(key, stored);
|
|
4753
|
+
} catch {
|
|
4754
|
+
keysToDelete.push(key);
|
|
4755
|
+
continue;
|
|
4756
|
+
}
|
|
4757
|
+
}
|
|
4758
|
+
} else {
|
|
4759
|
+
record = stored;
|
|
4760
|
+
}
|
|
4761
|
+
if (record?.expiresAt && record.expiresAt < now) {
|
|
4762
|
+
keysToDelete.push(key);
|
|
4763
|
+
}
|
|
4764
|
+
}
|
|
4765
|
+
for (const key of keysToDelete) {
|
|
4766
|
+
this.tokens.delete(key);
|
|
4767
|
+
this.derivedKeys.delete(key);
|
|
4768
|
+
}
|
|
4769
|
+
}
|
|
4770
|
+
/**
|
|
4771
|
+
* Stop the cleanup timer
|
|
4772
|
+
*/
|
|
4773
|
+
dispose() {
|
|
4774
|
+
if (this.cleanupTimer) {
|
|
4775
|
+
clearInterval(this.cleanupTimer);
|
|
4776
|
+
this.cleanupTimer = void 0;
|
|
4777
|
+
}
|
|
4778
|
+
}
|
|
4779
|
+
/**
|
|
4780
|
+
* Get total number of stored token records (for testing/monitoring)
|
|
4781
|
+
*/
|
|
4782
|
+
get size() {
|
|
4783
|
+
return this.tokens.size;
|
|
4784
|
+
}
|
|
4785
|
+
/**
|
|
4786
|
+
* Clear all tokens (for testing)
|
|
4787
|
+
*/
|
|
4788
|
+
clear() {
|
|
4789
|
+
this.tokens.clear();
|
|
4790
|
+
this.derivedKeys.clear();
|
|
4791
|
+
}
|
|
4792
|
+
/**
|
|
4793
|
+
* Migrate tokens from one authorization ID to another.
|
|
4794
|
+
* Used when tokens are stored with a pending ID during federated auth
|
|
4795
|
+
* and need to be accessible under the real authorization ID.
|
|
4796
|
+
*
|
|
4797
|
+
* @param fromAuthId - Source authorization ID (e.g., "pending:abc123")
|
|
4798
|
+
* @param toAuthId - Target authorization ID (e.g., "def456")
|
|
4799
|
+
*/
|
|
4800
|
+
async migrateTokens(fromAuthId, toAuthId) {
|
|
4801
|
+
const prefix = `${fromAuthId}:`;
|
|
4802
|
+
const keysToMigrate = [];
|
|
4803
|
+
for (const key of this.tokens.keys()) {
|
|
4804
|
+
if (key.startsWith(prefix)) {
|
|
4805
|
+
keysToMigrate.push(key);
|
|
4806
|
+
}
|
|
4807
|
+
}
|
|
4808
|
+
for (const oldKey of keysToMigrate) {
|
|
4809
|
+
const providerId = oldKey.slice(prefix.length);
|
|
4810
|
+
const newKey = this.buildKey(toAuthId, providerId);
|
|
4811
|
+
const stored = this.tokens.get(oldKey);
|
|
4812
|
+
if (!stored) {
|
|
4813
|
+
continue;
|
|
4814
|
+
}
|
|
4815
|
+
if (this.encryptionKey) {
|
|
4816
|
+
try {
|
|
4817
|
+
const record = await this.decryptRecord(oldKey, stored);
|
|
4818
|
+
const encrypted = await this.encryptRecord(newKey, record);
|
|
4819
|
+
this.tokens.set(newKey, encrypted);
|
|
4820
|
+
} catch {
|
|
4821
|
+
continue;
|
|
4822
|
+
}
|
|
4823
|
+
} else {
|
|
4824
|
+
this.tokens.set(newKey, stored);
|
|
4825
|
+
}
|
|
4826
|
+
this.tokens.delete(oldKey);
|
|
4827
|
+
this.derivedKeys.delete(oldKey);
|
|
4828
|
+
}
|
|
4829
|
+
}
|
|
4830
|
+
};
|
|
4831
|
+
|
|
4832
|
+
// libs/auth/src/session/orchestrated-token.crypto.ts
|
|
4833
|
+
var import_utils26 = require("@frontmcp/utils");
|
|
4834
|
+
var HKDF_SALT = new TextEncoder().encode("frontmcp-token-store");
|
|
4835
|
+
function deriveKeyForRecord(masterKey, compositeKey, cache) {
|
|
4836
|
+
const cached = cache.get(compositeKey);
|
|
4837
|
+
if (cached) {
|
|
4838
|
+
return cached;
|
|
4839
|
+
}
|
|
4840
|
+
const info = new TextEncoder().encode(`orchestrated-token:${compositeKey}`);
|
|
4841
|
+
const derived = (0, import_utils26.hkdfSha256)(masterKey, HKDF_SALT, info, 32);
|
|
4842
|
+
cache.set(compositeKey, derived);
|
|
4843
|
+
return derived;
|
|
4844
|
+
}
|
|
4845
|
+
function encryptRecord(key, record) {
|
|
4846
|
+
const plaintext = JSON.stringify(record);
|
|
4847
|
+
const iv = (0, import_utils26.randomBytes)(12);
|
|
4848
|
+
const { ciphertext, tag } = (0, import_utils26.encryptAesGcm)(key, new TextEncoder().encode(plaintext), iv);
|
|
4849
|
+
return JSON.stringify({
|
|
4850
|
+
iv: Buffer.from(iv).toString("base64url"),
|
|
4851
|
+
tag: Buffer.from(tag).toString("base64url"),
|
|
4852
|
+
data: Buffer.from(ciphertext).toString("base64url")
|
|
4853
|
+
});
|
|
4854
|
+
}
|
|
4855
|
+
function decryptRecord(key, encrypted) {
|
|
4856
|
+
const { iv, tag, data } = JSON.parse(encrypted);
|
|
4857
|
+
const ivBytes = Buffer.from(iv, "base64url");
|
|
4858
|
+
const tagBytes = Buffer.from(tag, "base64url");
|
|
4859
|
+
const ciphertextBytes = Buffer.from(data, "base64url");
|
|
4860
|
+
const plaintext = (0, import_utils26.decryptAesGcm)(key, ciphertextBytes, ivBytes, tagBytes);
|
|
4861
|
+
return JSON.parse(new TextDecoder().decode(plaintext));
|
|
4862
|
+
}
|
|
4863
|
+
|
|
4864
|
+
// libs/auth/src/session/storage-orchestrated-token.store.ts
|
|
4865
|
+
var StorageOrchestratedTokenStore = class {
|
|
4866
|
+
storage;
|
|
4867
|
+
encryptionKey;
|
|
4868
|
+
defaultTtlMs;
|
|
4869
|
+
namespace;
|
|
4870
|
+
storageIsNamespaced;
|
|
4871
|
+
/** HKDF-derived subkey cache, keyed by composite key. */
|
|
4872
|
+
derivedKeys = /* @__PURE__ */ new Map();
|
|
4873
|
+
constructor(storage, options = {}) {
|
|
4874
|
+
this.encryptionKey = options.encryptionKey;
|
|
4875
|
+
this.defaultTtlMs = options.defaultTtlMs;
|
|
4876
|
+
this.namespace = options.namespace ?? "otok";
|
|
4877
|
+
this.storageIsNamespaced = this.isNamespacedStorage(storage);
|
|
4878
|
+
this.storage = this.storageIsNamespaced ? storage.namespace(this.namespace) : storage;
|
|
4879
|
+
}
|
|
4880
|
+
// ============================================
|
|
4881
|
+
// TokenStore interface
|
|
4882
|
+
// ============================================
|
|
4883
|
+
async getAccessToken(authorizationId, providerId) {
|
|
4884
|
+
const record = await this.getRecord(authorizationId, providerId);
|
|
4885
|
+
return record?.accessToken ?? null;
|
|
4886
|
+
}
|
|
4887
|
+
async getRefreshToken(authorizationId, providerId) {
|
|
4888
|
+
const record = await this.getRecord(authorizationId, providerId);
|
|
4889
|
+
return record?.refreshToken ?? null;
|
|
4890
|
+
}
|
|
4891
|
+
async storeTokens(authorizationId, providerId, tokens) {
|
|
4892
|
+
const compositeKey = this.compositeKey(authorizationId, providerId);
|
|
4893
|
+
const now = Date.now();
|
|
4894
|
+
const record = {
|
|
4895
|
+
accessToken: tokens.accessToken,
|
|
4896
|
+
refreshToken: tokens.refreshToken,
|
|
4897
|
+
expiresAt: tokens.expiresAt ?? (this.defaultTtlMs ? now + this.defaultTtlMs : void 0),
|
|
4898
|
+
createdAt: now,
|
|
4899
|
+
updatedAt: now
|
|
4900
|
+
};
|
|
4901
|
+
const value = this.encryptionKey ? encryptRecord(this.keyFor(compositeKey), record) : JSON.stringify(record);
|
|
4902
|
+
await this.storage.set(this.storageKey(compositeKey), value, this.ttlOptions(record.expiresAt));
|
|
4903
|
+
}
|
|
4904
|
+
async deleteTokens(authorizationId, providerId) {
|
|
4905
|
+
const compositeKey = this.compositeKey(authorizationId, providerId);
|
|
4906
|
+
await this.storage.delete(this.storageKey(compositeKey));
|
|
4907
|
+
this.derivedKeys.delete(compositeKey);
|
|
4908
|
+
}
|
|
4909
|
+
async hasTokens(authorizationId, providerId) {
|
|
4910
|
+
const record = await this.getRecord(authorizationId, providerId);
|
|
4911
|
+
return record !== null;
|
|
4912
|
+
}
|
|
4913
|
+
async getProviderIds(authorizationId) {
|
|
4914
|
+
const prefix = `${authorizationId}:`;
|
|
4915
|
+
const compositeKeys = await this.listCompositeKeys(`${prefix}*`);
|
|
4916
|
+
const providerIds = [];
|
|
4917
|
+
for (const compositeKey of compositeKeys) {
|
|
4918
|
+
if (!compositeKey.startsWith(prefix)) continue;
|
|
4919
|
+
const providerId = compositeKey.slice(prefix.length);
|
|
4920
|
+
const record = await this.getRecord(authorizationId, providerId);
|
|
4921
|
+
if (record) {
|
|
4922
|
+
providerIds.push(providerId);
|
|
4923
|
+
}
|
|
4924
|
+
}
|
|
4925
|
+
return providerIds;
|
|
4926
|
+
}
|
|
4927
|
+
async migrateTokens(fromAuthId, toAuthId) {
|
|
4928
|
+
const prefix = `${fromAuthId}:`;
|
|
4929
|
+
const compositeKeys = await this.listCompositeKeys(`${prefix}*`);
|
|
4930
|
+
for (const oldComposite of compositeKeys) {
|
|
4931
|
+
if (!oldComposite.startsWith(prefix)) continue;
|
|
4932
|
+
const providerId = oldComposite.slice(prefix.length);
|
|
4933
|
+
const newComposite = this.compositeKey(toAuthId, providerId);
|
|
4934
|
+
const stored = await this.storage.get(this.storageKey(oldComposite));
|
|
4935
|
+
if (stored === null) continue;
|
|
4936
|
+
if (this.encryptionKey) {
|
|
4937
|
+
let record;
|
|
4938
|
+
try {
|
|
4939
|
+
record = decryptRecord(this.keyFor(oldComposite), stored);
|
|
4940
|
+
} catch {
|
|
4941
|
+
continue;
|
|
4942
|
+
}
|
|
4943
|
+
const value = encryptRecord(this.keyFor(newComposite), record);
|
|
4944
|
+
await this.storage.set(this.storageKey(newComposite), value, this.ttlOptions(record.expiresAt));
|
|
4945
|
+
} else {
|
|
4946
|
+
let expiresAt;
|
|
4947
|
+
try {
|
|
4948
|
+
expiresAt = JSON.parse(stored).expiresAt;
|
|
4949
|
+
} catch {
|
|
4950
|
+
expiresAt = void 0;
|
|
4951
|
+
}
|
|
4952
|
+
await this.storage.set(this.storageKey(newComposite), stored, this.ttlOptions(expiresAt));
|
|
4953
|
+
}
|
|
4954
|
+
await this.storage.delete(this.storageKey(oldComposite));
|
|
4955
|
+
this.derivedKeys.delete(oldComposite);
|
|
4956
|
+
}
|
|
3782
4957
|
}
|
|
3783
4958
|
/**
|
|
3784
|
-
*
|
|
4959
|
+
* Delete all tokens for an authorization id.
|
|
3785
4960
|
*/
|
|
3786
|
-
async
|
|
3787
|
-
const
|
|
3788
|
-
const
|
|
3789
|
-
for (const
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
4961
|
+
async deleteAllForAuthorization(authorizationId) {
|
|
4962
|
+
const prefix = `${authorizationId}:`;
|
|
4963
|
+
const compositeKeys = await this.listCompositeKeys(`${prefix}*`);
|
|
4964
|
+
for (const compositeKey of compositeKeys) {
|
|
4965
|
+
if (!compositeKey.startsWith(prefix)) continue;
|
|
4966
|
+
await this.storage.delete(this.storageKey(compositeKey));
|
|
4967
|
+
this.derivedKeys.delete(compositeKey);
|
|
4968
|
+
}
|
|
4969
|
+
}
|
|
4970
|
+
// ============================================
|
|
4971
|
+
// Internals
|
|
4972
|
+
// ============================================
|
|
4973
|
+
async getRecord(authorizationId, providerId) {
|
|
4974
|
+
const compositeKey = this.compositeKey(authorizationId, providerId);
|
|
4975
|
+
const stored = await this.storage.get(this.storageKey(compositeKey));
|
|
4976
|
+
if (stored === null) {
|
|
4977
|
+
return null;
|
|
4978
|
+
}
|
|
4979
|
+
let record;
|
|
4980
|
+
if (this.encryptionKey) {
|
|
4981
|
+
try {
|
|
4982
|
+
record = decryptRecord(this.keyFor(compositeKey), stored);
|
|
4983
|
+
} catch {
|
|
4984
|
+
await this.storage.delete(this.storageKey(compositeKey));
|
|
4985
|
+
return null;
|
|
3802
4986
|
}
|
|
3803
|
-
|
|
3804
|
-
|
|
4987
|
+
} else {
|
|
4988
|
+
try {
|
|
4989
|
+
record = JSON.parse(stored);
|
|
4990
|
+
} catch {
|
|
4991
|
+
await this.storage.delete(this.storageKey(compositeKey));
|
|
4992
|
+
return null;
|
|
3805
4993
|
}
|
|
3806
4994
|
}
|
|
3807
|
-
|
|
3808
|
-
this.
|
|
3809
|
-
|
|
4995
|
+
if (record.expiresAt && record.expiresAt < Date.now()) {
|
|
4996
|
+
await this.storage.delete(this.storageKey(compositeKey));
|
|
4997
|
+
return null;
|
|
3810
4998
|
}
|
|
4999
|
+
return record;
|
|
3811
5000
|
}
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
dispose() {
|
|
3816
|
-
if (this.cleanupTimer) {
|
|
3817
|
-
clearInterval(this.cleanupTimer);
|
|
3818
|
-
this.cleanupTimer = void 0;
|
|
5001
|
+
keyFor(compositeKey) {
|
|
5002
|
+
if (!this.encryptionKey) {
|
|
5003
|
+
throw new EncryptionKeyNotConfiguredError();
|
|
3819
5004
|
}
|
|
5005
|
+
return deriveKeyForRecord(this.encryptionKey, compositeKey, this.derivedKeys);
|
|
3820
5006
|
}
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
*/
|
|
3824
|
-
get size() {
|
|
3825
|
-
return this.tokens.size;
|
|
5007
|
+
compositeKey(authorizationId, providerId) {
|
|
5008
|
+
return `${authorizationId}:${providerId}`;
|
|
3826
5009
|
}
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
*/
|
|
3830
|
-
clear() {
|
|
3831
|
-
this.tokens.clear();
|
|
3832
|
-
this.derivedKeys.clear();
|
|
5010
|
+
storageKey(compositeKey) {
|
|
5011
|
+
return this.storageIsNamespaced ? compositeKey : `${this.namespace}:${compositeKey}`;
|
|
3833
5012
|
}
|
|
3834
5013
|
/**
|
|
3835
|
-
*
|
|
3836
|
-
*
|
|
3837
|
-
* and need to be accessible under the real authorization ID.
|
|
3838
|
-
*
|
|
3839
|
-
* @param fromAuthId - Source authorization ID (e.g., "pending:abc123")
|
|
3840
|
-
* @param toAuthId - Target authorization ID (e.g., "def456")
|
|
5014
|
+
* List composite keys (authorizationId:providerId) matching a glob over the
|
|
5015
|
+
* composite-key space, stripping any non-namespaced prefix we added.
|
|
3841
5016
|
*/
|
|
3842
|
-
async
|
|
3843
|
-
const
|
|
3844
|
-
const
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
keysToMigrate.push(key);
|
|
3848
|
-
}
|
|
5017
|
+
async listCompositeKeys(compositeGlob) {
|
|
5018
|
+
const pattern = this.storageIsNamespaced ? compositeGlob : `${this.namespace}:${compositeGlob}`;
|
|
5019
|
+
const raw = await this.storage.keys(pattern);
|
|
5020
|
+
if (this.storageIsNamespaced) {
|
|
5021
|
+
return raw;
|
|
3849
5022
|
}
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
}
|
|
3857
|
-
if (this.encryptionKey) {
|
|
3858
|
-
try {
|
|
3859
|
-
const record = await this.decryptRecord(oldKey, stored);
|
|
3860
|
-
const encrypted = await this.encryptRecord(newKey, record);
|
|
3861
|
-
this.tokens.set(newKey, encrypted);
|
|
3862
|
-
} catch {
|
|
3863
|
-
continue;
|
|
3864
|
-
}
|
|
3865
|
-
} else {
|
|
3866
|
-
this.tokens.set(newKey, stored);
|
|
3867
|
-
}
|
|
3868
|
-
this.tokens.delete(oldKey);
|
|
3869
|
-
this.derivedKeys.delete(oldKey);
|
|
5023
|
+
const stripPrefix = `${this.namespace}:`;
|
|
5024
|
+
return raw.map((k) => k.startsWith(stripPrefix) ? k.slice(stripPrefix.length) : k);
|
|
5025
|
+
}
|
|
5026
|
+
ttlOptions(expiresAtMs) {
|
|
5027
|
+
if (!expiresAtMs) {
|
|
5028
|
+
return void 0;
|
|
3870
5029
|
}
|
|
5030
|
+
const ttl = Math.ceil((expiresAtMs - Date.now()) / 1e3);
|
|
5031
|
+
return { ttlSeconds: ttl > 0 ? ttl : 1 };
|
|
5032
|
+
}
|
|
5033
|
+
isNamespacedStorage(storage) {
|
|
5034
|
+
return "namespace" in storage && typeof storage.namespace === "function";
|
|
3871
5035
|
}
|
|
3872
5036
|
};
|
|
3873
5037
|
|
|
3874
5038
|
// libs/auth/src/session/federated-auth.session.ts
|
|
3875
|
-
var
|
|
5039
|
+
var import_utils27 = require("@frontmcp/utils");
|
|
3876
5040
|
function toSessionRecord(session) {
|
|
3877
5041
|
return {
|
|
3878
5042
|
...session,
|
|
@@ -3947,7 +5111,7 @@ var InMemoryFederatedAuthSessionStore = class {
|
|
|
3947
5111
|
createSession(params) {
|
|
3948
5112
|
const now = Date.now();
|
|
3949
5113
|
return {
|
|
3950
|
-
id: (0,
|
|
5114
|
+
id: (0, import_utils27.randomUUID)(),
|
|
3951
5115
|
pendingAuthId: params.pendingAuthId,
|
|
3952
5116
|
clientId: params.clientId,
|
|
3953
5117
|
redirectUri: params.redirectUri,
|
|
@@ -3979,7 +5143,7 @@ var InMemoryFederatedAuthSessionStore = class {
|
|
|
3979
5143
|
function createFederatedAuthSession(params, ttlMs = 15 * 60 * 1e3) {
|
|
3980
5144
|
const now = Date.now();
|
|
3981
5145
|
return {
|
|
3982
|
-
id: (0,
|
|
5146
|
+
id: (0, import_utils27.randomUUID)(),
|
|
3983
5147
|
pendingAuthId: params.pendingAuthId,
|
|
3984
5148
|
clientId: params.clientId,
|
|
3985
5149
|
redirectUri: params.redirectUri,
|
|
@@ -4035,9 +5199,119 @@ function startNextProvider(session, pkce, state) {
|
|
|
4035
5199
|
return providerId;
|
|
4036
5200
|
}
|
|
4037
5201
|
|
|
5202
|
+
// libs/auth/src/session/storage-federated-auth.session.ts
|
|
5203
|
+
var import_utils28 = require("@frontmcp/utils");
|
|
5204
|
+
var StorageFederatedAuthSessionStore = class {
|
|
5205
|
+
storage;
|
|
5206
|
+
namespace;
|
|
5207
|
+
storageIsNamespaced;
|
|
5208
|
+
constructor(storage, options = {}) {
|
|
5209
|
+
this.namespace = options.namespace ?? "fed";
|
|
5210
|
+
this.storageIsNamespaced = this.isNamespacedStorage(storage);
|
|
5211
|
+
const namespacedStorage = this.storageIsNamespaced ? storage.namespace(this.namespace) : storage;
|
|
5212
|
+
this.storage = new import_utils28.TypedStorage(namespacedStorage);
|
|
5213
|
+
}
|
|
5214
|
+
async store(session) {
|
|
5215
|
+
await this.put(session);
|
|
5216
|
+
}
|
|
5217
|
+
async update(session) {
|
|
5218
|
+
await this.put(session);
|
|
5219
|
+
}
|
|
5220
|
+
async get(id) {
|
|
5221
|
+
const record = await this.storage.get(this.key(id));
|
|
5222
|
+
if (!record) {
|
|
5223
|
+
return null;
|
|
5224
|
+
}
|
|
5225
|
+
if (Date.now() > record.expiresAt) {
|
|
5226
|
+
await this.storage.delete(this.key(id));
|
|
5227
|
+
return null;
|
|
5228
|
+
}
|
|
5229
|
+
return fromSessionRecord(record);
|
|
5230
|
+
}
|
|
5231
|
+
async delete(id) {
|
|
5232
|
+
await this.storage.delete(this.key(id));
|
|
5233
|
+
}
|
|
5234
|
+
async put(session) {
|
|
5235
|
+
const record = toSessionRecord(session);
|
|
5236
|
+
const ttlSeconds = this.calculateTtl(record.expiresAt);
|
|
5237
|
+
await this.storage.set(this.key(session.id), record, ttlSeconds ? { ttlSeconds } : void 0);
|
|
5238
|
+
}
|
|
5239
|
+
calculateTtl(expiresAtMs) {
|
|
5240
|
+
const ttl = Math.ceil((expiresAtMs - Date.now()) / 1e3);
|
|
5241
|
+
return ttl > 0 ? ttl : 1;
|
|
5242
|
+
}
|
|
5243
|
+
key(id) {
|
|
5244
|
+
return this.storageIsNamespaced ? id : `${this.namespace}:${id}`;
|
|
5245
|
+
}
|
|
5246
|
+
isNamespacedStorage(storage) {
|
|
5247
|
+
return "namespace" in storage && typeof storage.namespace === "function";
|
|
5248
|
+
}
|
|
5249
|
+
};
|
|
5250
|
+
|
|
5251
|
+
// libs/auth/src/session/consent.store.ts
|
|
5252
|
+
function consentRecordKey(userSub, clientId) {
|
|
5253
|
+
return `consent:${userSub}:${clientId}`;
|
|
5254
|
+
}
|
|
5255
|
+
var InMemoryConsentStore = class {
|
|
5256
|
+
records = /* @__PURE__ */ new Map();
|
|
5257
|
+
async get(userSub, clientId) {
|
|
5258
|
+
const record = this.records.get(consentRecordKey(userSub, clientId));
|
|
5259
|
+
if (!record) return null;
|
|
5260
|
+
return { ...record, selectedToolIds: [...record.selectedToolIds], seenToolIds: [...record.seenToolIds] };
|
|
5261
|
+
}
|
|
5262
|
+
async set(record) {
|
|
5263
|
+
this.records.set(consentRecordKey(record.userSub, record.clientId), {
|
|
5264
|
+
...record,
|
|
5265
|
+
selectedToolIds: [...record.selectedToolIds],
|
|
5266
|
+
seenToolIds: [...record.seenToolIds]
|
|
5267
|
+
});
|
|
5268
|
+
}
|
|
5269
|
+
async delete(userSub, clientId) {
|
|
5270
|
+
this.records.delete(consentRecordKey(userSub, clientId));
|
|
5271
|
+
}
|
|
5272
|
+
/** Number of remembered records (for testing/monitoring). */
|
|
5273
|
+
get size() {
|
|
5274
|
+
return this.records.size;
|
|
5275
|
+
}
|
|
5276
|
+
/** Clear all remembered records (for testing). */
|
|
5277
|
+
clear() {
|
|
5278
|
+
this.records.clear();
|
|
5279
|
+
}
|
|
5280
|
+
};
|
|
5281
|
+
|
|
5282
|
+
// libs/auth/src/session/storage-consent.store.ts
|
|
5283
|
+
var import_utils29 = require("@frontmcp/utils");
|
|
5284
|
+
var StorageConsentStore = class {
|
|
5285
|
+
storage;
|
|
5286
|
+
namespace;
|
|
5287
|
+
storageIsNamespaced;
|
|
5288
|
+
constructor(storage, options = {}) {
|
|
5289
|
+
this.namespace = options.namespace ?? "remembered-consent";
|
|
5290
|
+
this.storageIsNamespaced = this.isNamespacedStorage(storage);
|
|
5291
|
+
const namespacedStorage = this.storageIsNamespaced ? storage.namespace(this.namespace) : storage;
|
|
5292
|
+
this.storage = new import_utils29.TypedStorage(namespacedStorage);
|
|
5293
|
+
}
|
|
5294
|
+
async get(userSub, clientId) {
|
|
5295
|
+
return this.storage.get(this.key(userSub, clientId));
|
|
5296
|
+
}
|
|
5297
|
+
async set(record) {
|
|
5298
|
+
await this.storage.set(this.key(record.userSub, record.clientId), record);
|
|
5299
|
+
}
|
|
5300
|
+
async delete(userSub, clientId) {
|
|
5301
|
+
await this.storage.delete(this.key(userSub, clientId));
|
|
5302
|
+
}
|
|
5303
|
+
key(userSub, clientId) {
|
|
5304
|
+
const base = consentRecordKey(userSub, clientId);
|
|
5305
|
+
return this.storageIsNamespaced ? base : `${this.namespace}:${base}`;
|
|
5306
|
+
}
|
|
5307
|
+
isNamespacedStorage(storage) {
|
|
5308
|
+
return "namespace" in storage && typeof storage.namespace === "function";
|
|
5309
|
+
}
|
|
5310
|
+
};
|
|
5311
|
+
|
|
4038
5312
|
// libs/auth/src/session/encrypted-authorization-vault.ts
|
|
4039
5313
|
var import_lazy_zod5 = require("@frontmcp/lazy-zod");
|
|
4040
|
-
var
|
|
5314
|
+
var import_utils30 = require("@frontmcp/utils");
|
|
4041
5315
|
var redisVaultEntrySchema = import_lazy_zod5.z.object({
|
|
4042
5316
|
/** Vault ID */
|
|
4043
5317
|
id: import_lazy_zod5.z.string(),
|
|
@@ -4062,7 +5336,7 @@ var redisVaultEntrySchema = import_lazy_zod5.z.object({
|
|
|
4062
5336
|
/** Encrypted sensitive data blob */
|
|
4063
5337
|
encrypted: encryptedDataSchema
|
|
4064
5338
|
});
|
|
4065
|
-
var encryptionContextStorage = new
|
|
5339
|
+
var encryptionContextStorage = new import_utils30.AsyncLocalStorage();
|
|
4066
5340
|
var EncryptedRedisVault = class {
|
|
4067
5341
|
constructor(redis, encryption, namespace = "vault:") {
|
|
4068
5342
|
this.redis = redis;
|
|
@@ -4195,7 +5469,7 @@ var EncryptedRedisVault = class {
|
|
|
4195
5469
|
async create(params) {
|
|
4196
5470
|
const now = Date.now();
|
|
4197
5471
|
const entry = {
|
|
4198
|
-
id: (0,
|
|
5472
|
+
id: (0, import_utils30.randomUUID)(),
|
|
4199
5473
|
userSub: params.userSub,
|
|
4200
5474
|
userEmail: params.userEmail,
|
|
4201
5475
|
userName: params.userName,
|
|
@@ -4254,7 +5528,7 @@ var EncryptedRedisVault = class {
|
|
|
4254
5528
|
}
|
|
4255
5529
|
const now = Date.now();
|
|
4256
5530
|
const pendingAuth = {
|
|
4257
|
-
id: (0,
|
|
5531
|
+
id: (0, import_utils30.randomUUID)(),
|
|
4258
5532
|
appId: params.appId,
|
|
4259
5533
|
toolId: params.toolId,
|
|
4260
5534
|
authUrl: params.authUrl,
|
|
@@ -4432,7 +5706,7 @@ function createEncryptedVault(redis, config = {}) {
|
|
|
4432
5706
|
}
|
|
4433
5707
|
|
|
4434
5708
|
// libs/auth/src/session/token.refresh.ts
|
|
4435
|
-
var
|
|
5709
|
+
var import_utils31 = require("@frontmcp/utils");
|
|
4436
5710
|
function toEpochSeconds(exp) {
|
|
4437
5711
|
if (exp == null) return void 0;
|
|
4438
5712
|
if (exp instanceof Date) return Math.floor(exp.getTime() / 1e3);
|
|
@@ -4454,7 +5728,7 @@ function tryJwtExp(token) {
|
|
|
4454
5728
|
const parts = token.split(".");
|
|
4455
5729
|
if (parts.length < 2) return void 0;
|
|
4456
5730
|
try {
|
|
4457
|
-
const json = JSON.parse(new TextDecoder().decode((0,
|
|
5731
|
+
const json = JSON.parse(new TextDecoder().decode((0, import_utils31.base64urlDecode)(parts[1])));
|
|
4458
5732
|
const e = json?.exp;
|
|
4459
5733
|
return typeof e === "number" ? toEpochSeconds(e) : void 0;
|
|
4460
5734
|
} catch {
|
|
@@ -4517,7 +5791,7 @@ var progressiveAuthStateSchema = import_lazy_zod6.z.object({
|
|
|
4517
5791
|
});
|
|
4518
5792
|
|
|
4519
5793
|
// libs/auth/src/authorization/authorization.class.ts
|
|
4520
|
-
var
|
|
5794
|
+
var import_utils34 = require("@frontmcp/utils");
|
|
4521
5795
|
var AuthorizationBase = class _AuthorizationBase {
|
|
4522
5796
|
id;
|
|
4523
5797
|
isAnonymous;
|
|
@@ -4565,14 +5839,14 @@ var AuthorizationBase = class _AuthorizationBase {
|
|
|
4565
5839
|
* @param fingerprint - Optional client fingerprint for tracking
|
|
4566
5840
|
*/
|
|
4567
5841
|
createTransportSession(protocol, fingerprint) {
|
|
4568
|
-
const sessionId = (0,
|
|
5842
|
+
const sessionId = (0, import_utils34.randomUUID)();
|
|
4569
5843
|
const session = {
|
|
4570
5844
|
id: sessionId,
|
|
4571
5845
|
authorizationId: this.id,
|
|
4572
5846
|
protocol,
|
|
4573
5847
|
createdAt: Date.now(),
|
|
4574
5848
|
expiresAt: this.expiresAt,
|
|
4575
|
-
nodeId: (0,
|
|
5849
|
+
nodeId: (0, import_utils34.getMachineId)(),
|
|
4576
5850
|
clientFingerprint: fingerprint
|
|
4577
5851
|
};
|
|
4578
5852
|
this.#sessions.set(session.id, session);
|
|
@@ -4702,7 +5976,7 @@ var AuthorizationBase = class _AuthorizationBase {
|
|
|
4702
5976
|
*/
|
|
4703
5977
|
static isJwtHeader(segment) {
|
|
4704
5978
|
try {
|
|
4705
|
-
const bytes = (0,
|
|
5979
|
+
const bytes = (0, import_utils34.base64urlDecode)(segment);
|
|
4706
5980
|
const text = new TextDecoder().decode(bytes);
|
|
4707
5981
|
const parsed = JSON.parse(text);
|
|
4708
5982
|
return typeof parsed === "object" && parsed !== null && typeof parsed.alg === "string";
|
|
@@ -4754,7 +6028,7 @@ var AuthorizationBase = class _AuthorizationBase {
|
|
|
4754
6028
|
};
|
|
4755
6029
|
|
|
4756
6030
|
// libs/auth/src/authorization/public.authorization.ts
|
|
4757
|
-
var
|
|
6031
|
+
var import_utils35 = require("@frontmcp/utils");
|
|
4758
6032
|
var PublicAuthorization = class _PublicAuthorization extends AuthorizationBase {
|
|
4759
6033
|
mode = "public";
|
|
4760
6034
|
/**
|
|
@@ -4790,7 +6064,7 @@ var PublicAuthorization = class _PublicAuthorization extends AuthorizationBase {
|
|
|
4790
6064
|
allowedTools = "all",
|
|
4791
6065
|
allowedPrompts = "all"
|
|
4792
6066
|
} = ctx;
|
|
4793
|
-
const uuid = (0,
|
|
6067
|
+
const uuid = (0, import_utils35.randomUUID)();
|
|
4794
6068
|
const sub = `${prefix}:${uuid}`;
|
|
4795
6069
|
const user = {
|
|
4796
6070
|
sub,
|
|
@@ -4874,7 +6148,7 @@ var PublicAuthorization = class _PublicAuthorization extends AuthorizationBase {
|
|
|
4874
6148
|
};
|
|
4875
6149
|
|
|
4876
6150
|
// libs/auth/src/authorization/transparent.authorization.ts
|
|
4877
|
-
var
|
|
6151
|
+
var import_utils36 = require("@frontmcp/utils");
|
|
4878
6152
|
var TransparentAuthorization = class _TransparentAuthorization extends AuthorizationBase {
|
|
4879
6153
|
mode = "transparent";
|
|
4880
6154
|
/**
|
|
@@ -4974,7 +6248,7 @@ var TransparentAuthorization = class _TransparentAuthorization extends Authoriza
|
|
|
4974
6248
|
static generateAuthorizationId(token) {
|
|
4975
6249
|
const parts = token.split(".");
|
|
4976
6250
|
const signature = parts[2] || token;
|
|
4977
|
-
return (0,
|
|
6251
|
+
return (0, import_utils36.sha256Hex)(signature).substring(0, 16);
|
|
4978
6252
|
}
|
|
4979
6253
|
/**
|
|
4980
6254
|
* Get the issuer from the token claims
|
|
@@ -5000,11 +6274,11 @@ var TransparentAuthorization = class _TransparentAuthorization extends Authoriza
|
|
|
5000
6274
|
};
|
|
5001
6275
|
|
|
5002
6276
|
// libs/auth/src/utils/authorization-id.utils.ts
|
|
5003
|
-
var
|
|
6277
|
+
var import_utils37 = require("@frontmcp/utils");
|
|
5004
6278
|
function deriveAuthorizationId(token) {
|
|
5005
6279
|
const parts = token.split(".");
|
|
5006
6280
|
const signature = parts[2] || token;
|
|
5007
|
-
return (0,
|
|
6281
|
+
return (0, import_utils37.sha256Hex)(signature).substring(0, 16);
|
|
5008
6282
|
}
|
|
5009
6283
|
|
|
5010
6284
|
// libs/auth/src/authorization/orchestrated.authorization.ts
|
|
@@ -5371,6 +6645,103 @@ var OrchestratedAuthAccessorAdapter = class {
|
|
|
5371
6645
|
}
|
|
5372
6646
|
};
|
|
5373
6647
|
|
|
6648
|
+
// libs/auth/src/authorization/dcr-client.registry.ts
|
|
6649
|
+
var import_utils38 = require("@frontmcp/utils");
|
|
6650
|
+
function globToRegExp(pattern) {
|
|
6651
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, (ch) => ch === "*" ? "\0" : `\\${ch}`);
|
|
6652
|
+
const withWildcards = escaped.split("\0").join(".*");
|
|
6653
|
+
return new RegExp(`^${withWildcards}$`);
|
|
6654
|
+
}
|
|
6655
|
+
var DcrClientRegistry = class {
|
|
6656
|
+
constructor(config = {}) {
|
|
6657
|
+
this.config = config;
|
|
6658
|
+
this.redirectMatchers = config.allowedRedirectUris?.map(globToRegExp);
|
|
6659
|
+
this.seedPreRegisteredClients();
|
|
6660
|
+
}
|
|
6661
|
+
clients = /* @__PURE__ */ new Map();
|
|
6662
|
+
redirectMatchers;
|
|
6663
|
+
/** Seed `dcr.clients` so the authorize/token flows accept them without DCR. */
|
|
6664
|
+
seedPreRegisteredClients() {
|
|
6665
|
+
const clients = this.config.clients ?? [];
|
|
6666
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
6667
|
+
for (const c of clients) {
|
|
6668
|
+
this.clients.set(c.clientId, {
|
|
6669
|
+
client_id: c.clientId,
|
|
6670
|
+
client_secret: c.clientSecret,
|
|
6671
|
+
token_endpoint_auth_method: c.tokenEndpointAuthMethod ?? "none",
|
|
6672
|
+
grant_types: c.grantTypes ?? ["authorization_code"],
|
|
6673
|
+
response_types: c.responseTypes ?? ["code"],
|
|
6674
|
+
redirect_uris: c.redirectUris,
|
|
6675
|
+
client_name: c.clientName,
|
|
6676
|
+
scope: c.scope,
|
|
6677
|
+
created_at: now,
|
|
6678
|
+
dev: false,
|
|
6679
|
+
preRegistered: true
|
|
6680
|
+
});
|
|
6681
|
+
}
|
|
6682
|
+
}
|
|
6683
|
+
/** Look up a client by id (pre-registered or dynamically registered). */
|
|
6684
|
+
get(clientId) {
|
|
6685
|
+
return this.clients.get(clientId);
|
|
6686
|
+
}
|
|
6687
|
+
/** Whether a client id is known to the registry. */
|
|
6688
|
+
has(clientId) {
|
|
6689
|
+
return this.clients.has(clientId);
|
|
6690
|
+
}
|
|
6691
|
+
/** Add a dynamically-registered client (called by the register flow). */
|
|
6692
|
+
register(client) {
|
|
6693
|
+
this.clients.set(client.client_id, client);
|
|
6694
|
+
}
|
|
6695
|
+
/** Whether a redirect_uri allowlist is configured. */
|
|
6696
|
+
hasRedirectAllowlist() {
|
|
6697
|
+
return !!this.redirectMatchers && this.redirectMatchers.length > 0;
|
|
6698
|
+
}
|
|
6699
|
+
/**
|
|
6700
|
+
* Whether `redirectUri` is permitted by the configured allowlist. Always
|
|
6701
|
+
* `true` when no allowlist is configured (preserves default behavior).
|
|
6702
|
+
*/
|
|
6703
|
+
isRedirectUriAllowed(redirectUri) {
|
|
6704
|
+
if (!this.redirectMatchers || this.redirectMatchers.length === 0) {
|
|
6705
|
+
return true;
|
|
6706
|
+
}
|
|
6707
|
+
return this.redirectMatchers.some((re) => re.test(redirectUri));
|
|
6708
|
+
}
|
|
6709
|
+
/** Whether a client-id allowlist is configured. */
|
|
6710
|
+
hasClientIdAllowlist() {
|
|
6711
|
+
return !!this.config.allowedClientIds && this.config.allowedClientIds.length > 0;
|
|
6712
|
+
}
|
|
6713
|
+
/**
|
|
6714
|
+
* Whether `clientId` is permitted by the configured client-id allowlist.
|
|
6715
|
+
* Always `true` when no allowlist is configured (preserves default behavior).
|
|
6716
|
+
*/
|
|
6717
|
+
isClientIdAllowed(clientId) {
|
|
6718
|
+
const allow = this.config.allowedClientIds;
|
|
6719
|
+
if (!allow || allow.length === 0) {
|
|
6720
|
+
return true;
|
|
6721
|
+
}
|
|
6722
|
+
return allow.includes(clientId);
|
|
6723
|
+
}
|
|
6724
|
+
/** Whether an initial access token is required for DCR registration. */
|
|
6725
|
+
requiresInitialAccessToken() {
|
|
6726
|
+
return typeof this.config.initialAccessToken === "string" && this.config.initialAccessToken.length > 0;
|
|
6727
|
+
}
|
|
6728
|
+
/**
|
|
6729
|
+
* Constant-time check of a presented bearer token against the configured
|
|
6730
|
+
* initial access token. Returns `false` when none is configured (callers
|
|
6731
|
+
* should gate on {@link requiresInitialAccessToken} first).
|
|
6732
|
+
*/
|
|
6733
|
+
verifyInitialAccessToken(presented) {
|
|
6734
|
+
const expected = this.config.initialAccessToken;
|
|
6735
|
+
if (!expected) {
|
|
6736
|
+
return false;
|
|
6737
|
+
}
|
|
6738
|
+
if (typeof presented !== "string" || presented.length === 0) {
|
|
6739
|
+
return false;
|
|
6740
|
+
}
|
|
6741
|
+
return (0, import_utils38.timingSafeEqual)((0, import_utils38.sha256)(presented), (0, import_utils38.sha256)(expected));
|
|
6742
|
+
}
|
|
6743
|
+
};
|
|
6744
|
+
|
|
5374
6745
|
// libs/auth/src/common/jwt.types.ts
|
|
5375
6746
|
var import_lazy_zod7 = require("@frontmcp/lazy-zod");
|
|
5376
6747
|
var jwkParametersSchema = import_lazy_zod7.z.object({
|
|
@@ -6318,10 +7689,15 @@ var CimdService = class {
|
|
|
6318
7689
|
}
|
|
6319
7690
|
}
|
|
6320
7691
|
};
|
|
7692
|
+
var LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set(["127.0.0.1", "::1", "[::1]", "localhost"]);
|
|
7693
|
+
function isLoopbackHostname(hostname) {
|
|
7694
|
+
return LOOPBACK_HOSTNAMES.has(hostname.toLowerCase());
|
|
7695
|
+
}
|
|
6321
7696
|
function normalizeRedirectUri(uri) {
|
|
6322
7697
|
try {
|
|
6323
7698
|
const url = new URL(uri);
|
|
6324
|
-
|
|
7699
|
+
const host = isLoopbackHostname(url.hostname) ? url.hostname.toLowerCase() : url.host.toLowerCase();
|
|
7700
|
+
let normalized = `${url.protocol.toLowerCase()}//${host}`;
|
|
6325
7701
|
normalized += url.pathname.replace(/\/+$/, "") || "/";
|
|
6326
7702
|
if (url.search) normalized += url.search;
|
|
6327
7703
|
return normalized;
|
|
@@ -6452,6 +7828,105 @@ var remoteProviderConfigSchema = import_lazy_zod10.z.object({
|
|
|
6452
7828
|
*/
|
|
6453
7829
|
userInfoEndpoint: import_lazy_zod10.z.string().url().optional()
|
|
6454
7830
|
});
|
|
7831
|
+
var upstreamProviderSchema = import_lazy_zod10.z.object({
|
|
7832
|
+
/** Stable provider id (e.g. `'github'`). Used in `this.orchestration.getToken(id)`. */
|
|
7833
|
+
id: import_lazy_zod10.z.string().min(1),
|
|
7834
|
+
/** Human-readable display name. Defaults to `id` when omitted. */
|
|
7835
|
+
name: import_lazy_zod10.z.string().optional(),
|
|
7836
|
+
/** Authorization endpoint (canonical name). */
|
|
7837
|
+
authorizationEndpoint: import_lazy_zod10.z.string().url().optional(),
|
|
7838
|
+
/** Authorization endpoint (alias for {@link authorizationEndpoint}). */
|
|
7839
|
+
authorizeUrl: import_lazy_zod10.z.string().url().optional(),
|
|
7840
|
+
/** Token endpoint (canonical name). */
|
|
7841
|
+
tokenEndpoint: import_lazy_zod10.z.string().url().optional(),
|
|
7842
|
+
/** Token endpoint (alias for {@link tokenEndpoint}). */
|
|
7843
|
+
tokenUrl: import_lazy_zod10.z.string().url().optional(),
|
|
7844
|
+
/** OAuth client id issued by the upstream provider. */
|
|
7845
|
+
clientId: import_lazy_zod10.z.string().min(1),
|
|
7846
|
+
/** OAuth client secret (confidential clients). */
|
|
7847
|
+
clientSecret: import_lazy_zod10.z.string().optional(),
|
|
7848
|
+
/** Scopes to request from the upstream provider. */
|
|
7849
|
+
scopes: import_lazy_zod10.z.array(import_lazy_zod10.z.string()).optional(),
|
|
7850
|
+
/** User info endpoint (optional, used to enrich the session identity). */
|
|
7851
|
+
userInfoEndpoint: import_lazy_zod10.z.string().url().optional(),
|
|
7852
|
+
/** JWKS URI for upstream id_token validation (optional). */
|
|
7853
|
+
jwksUri: import_lazy_zod10.z.string().url().optional()
|
|
7854
|
+
}).refine((p) => !!(p.authorizationEndpoint ?? p.authorizeUrl), {
|
|
7855
|
+
message: "authorizationEndpoint (or authorizeUrl) is required",
|
|
7856
|
+
path: ["authorizationEndpoint"]
|
|
7857
|
+
}).refine((p) => !!(p.tokenEndpoint ?? p.tokenUrl), {
|
|
7858
|
+
message: "tokenEndpoint (or tokenUrl) is required",
|
|
7859
|
+
path: ["tokenEndpoint"]
|
|
7860
|
+
}).refine((p) => !(p.authorizationEndpoint && p.authorizeUrl), {
|
|
7861
|
+
message: "provide only one of authorizationEndpoint or authorizeUrl",
|
|
7862
|
+
path: ["authorizationEndpoint"]
|
|
7863
|
+
}).refine((p) => !(p.tokenEndpoint && p.tokenUrl), {
|
|
7864
|
+
message: "provide only one of tokenEndpoint or tokenUrl",
|
|
7865
|
+
path: ["tokenEndpoint"]
|
|
7866
|
+
});
|
|
7867
|
+
var localDcrClientSchema = import_lazy_zod10.z.object({
|
|
7868
|
+
/** Stable, pre-assigned client identifier the client authenticates with. */
|
|
7869
|
+
clientId: import_lazy_zod10.z.string().min(1),
|
|
7870
|
+
/**
|
|
7871
|
+
* Confidential-client secret. Omit for public clients
|
|
7872
|
+
* (`token_endpoint_auth_method: 'none'`, the default).
|
|
7873
|
+
*/
|
|
7874
|
+
clientSecret: import_lazy_zod10.z.string().min(1).optional(),
|
|
7875
|
+
/** Exact redirect URIs this client is allowed to use (at least one). */
|
|
7876
|
+
redirectUris: import_lazy_zod10.z.array(import_lazy_zod10.z.string().url()).min(1, "At least one redirect_uri is required"),
|
|
7877
|
+
/** Human-readable name shown on consent/login screens. */
|
|
7878
|
+
clientName: import_lazy_zod10.z.string().optional(),
|
|
7879
|
+
/**
|
|
7880
|
+
* Token-endpoint auth method. Defaults to `'none'` (public PKCE client).
|
|
7881
|
+
*/
|
|
7882
|
+
tokenEndpointAuthMethod: import_lazy_zod10.z.enum(["none", "client_secret_basic", "client_secret_post"]).default("none"),
|
|
7883
|
+
/** Allowed grant types. Defaults to `['authorization_code']`. */
|
|
7884
|
+
grantTypes: import_lazy_zod10.z.array(import_lazy_zod10.z.enum(["authorization_code", "refresh_token"])).default(["authorization_code"]),
|
|
7885
|
+
/** Allowed response types. Defaults to `['code']`. */
|
|
7886
|
+
responseTypes: import_lazy_zod10.z.array(import_lazy_zod10.z.enum(["code"])).default(["code"]),
|
|
7887
|
+
/** Optional space-delimited default scope string. */
|
|
7888
|
+
scope: import_lazy_zod10.z.string().optional()
|
|
7889
|
+
});
|
|
7890
|
+
var localDcrConfigSchema = import_lazy_zod10.z.object({
|
|
7891
|
+
/**
|
|
7892
|
+
* Whether `POST /oauth/register` is mounted/active and whether
|
|
7893
|
+
* `registration_endpoint` is advertised in AS metadata.
|
|
7894
|
+
*
|
|
7895
|
+
* When unset (default), the historical guard applies: enabled in
|
|
7896
|
+
* development, disabled in production. Set explicitly to force it on or off
|
|
7897
|
+
* regardless of `NODE_ENV`.
|
|
7898
|
+
*/
|
|
7899
|
+
enabled: import_lazy_zod10.z.boolean().optional(),
|
|
7900
|
+
/**
|
|
7901
|
+
* Allowlist of redirect URIs. Each entry is matched EXACTLY or as a simple
|
|
7902
|
+
* glob (`*` matches any run of characters). When set, both DCR registrations
|
|
7903
|
+
* and `/oauth/authorize` requests are rejected unless their `redirect_uri`
|
|
7904
|
+
* matches an entry. When unset, no redirect_uri allowlist is enforced.
|
|
7905
|
+
*/
|
|
7906
|
+
allowedRedirectUris: import_lazy_zod10.z.array(import_lazy_zod10.z.string().min(1)).optional(),
|
|
7907
|
+
/**
|
|
7908
|
+
* Allowlist of client ids. When set, only these `client_id`s may register
|
|
7909
|
+
* (DCR registrations are forced to one of these ids) or be used at
|
|
7910
|
+
* `/oauth/authorize`. When unset, any client id is accepted (subject to the
|
|
7911
|
+
* other checks). CIMD URL client ids are validated by the CIMD layer and are
|
|
7912
|
+
* exempt from this allowlist.
|
|
7913
|
+
*/
|
|
7914
|
+
allowedClientIds: import_lazy_zod10.z.array(import_lazy_zod10.z.string().min(1)).optional(),
|
|
7915
|
+
/**
|
|
7916
|
+
* Initial Access Token (RFC 7591 §3). When set, `POST /oauth/register`
|
|
7917
|
+
* requires an `Authorization: Bearer <token>` header that matches this value
|
|
7918
|
+
* (constant-time compared); requests without it (or with a wrong value) are
|
|
7919
|
+
* rejected `401 invalid_token`. When unset, registration is unauthenticated
|
|
7920
|
+
* (the historical behavior).
|
|
7921
|
+
*/
|
|
7922
|
+
initialAccessToken: import_lazy_zod10.z.string().min(1).optional(),
|
|
7923
|
+
/**
|
|
7924
|
+
* Pre-registered trusted clients seeded into the registry at startup. These
|
|
7925
|
+
* are accepted by the authorize/token flows WITHOUT a DCR round-trip, which
|
|
7926
|
+
* lets you disable DCR entirely while still shipping known clients.
|
|
7927
|
+
*/
|
|
7928
|
+
clients: import_lazy_zod10.z.array(localDcrClientSchema).optional()
|
|
7929
|
+
});
|
|
6455
7930
|
var flatRemoteProviderFields = {
|
|
6456
7931
|
/**
|
|
6457
7932
|
* OAuth provider base URL (required)
|
|
@@ -6467,7 +7942,64 @@ var flatRemoteProviderFields = {
|
|
|
6467
7942
|
/** Advanced provider configuration */
|
|
6468
7943
|
providerConfig: providerConfigSchema.optional()
|
|
6469
7944
|
};
|
|
6470
|
-
var
|
|
7945
|
+
var tokenStorageSqliteSchema = import_lazy_zod10.z.object({
|
|
7946
|
+
/** Path to the `.sqlite` database file used for auth persistence. */
|
|
7947
|
+
path: import_lazy_zod10.z.string().min(1),
|
|
7948
|
+
/**
|
|
7949
|
+
* Encryption configuration for at-rest encryption of stored values.
|
|
7950
|
+
* Independent of the AES-GCM blob encryption already applied to upstream
|
|
7951
|
+
* provider tokens by the orchestrated-token store.
|
|
7952
|
+
*/
|
|
7953
|
+
encryption: import_lazy_zod10.z.object({
|
|
7954
|
+
secret: import_lazy_zod10.z.string().min(1)
|
|
7955
|
+
}).optional(),
|
|
7956
|
+
/**
|
|
7957
|
+
* Interval in milliseconds for purging expired keys.
|
|
7958
|
+
* @default 60000
|
|
7959
|
+
*/
|
|
7960
|
+
ttlCleanupIntervalMs: import_lazy_zod10.z.number().int().nonnegative().optional(),
|
|
7961
|
+
/**
|
|
7962
|
+
* Enable WAL mode for better read concurrency.
|
|
7963
|
+
* @default true
|
|
7964
|
+
*/
|
|
7965
|
+
walMode: import_lazy_zod10.z.boolean().optional()
|
|
7966
|
+
});
|
|
7967
|
+
var tokenStorageConfigSchema = import_lazy_zod10.z.union([
|
|
7968
|
+
import_lazy_zod10.z.literal("memory"),
|
|
7969
|
+
import_lazy_zod10.z.object({ redis: redisConfigSchema }),
|
|
7970
|
+
import_lazy_zod10.z.object({ sqlite: tokenStorageSqliteSchema })
|
|
7971
|
+
]);
|
|
7972
|
+
var secureStoreScopeSchema = import_lazy_zod10.z.enum(["user", "session", "global"]);
|
|
7973
|
+
var secureStoreEncryptionSchema = import_lazy_zod10.z.object({
|
|
7974
|
+
/**
|
|
7975
|
+
* Server pepper mixed into HKDF key derivation (defense-in-depth). Defaults to
|
|
7976
|
+
* `VAULT_SECRET ?? JWT_SECRET` at runtime when omitted.
|
|
7977
|
+
*/
|
|
7978
|
+
pepper: import_lazy_zod10.z.string().min(1).optional()
|
|
7979
|
+
});
|
|
7980
|
+
var secureStoreCustomBackendSchema = import_lazy_zod10.z.custom(
|
|
7981
|
+
(v) => typeof v === "object" && v !== null && typeof v.get === "function" && typeof v.set === "function" && typeof v.delete === "function" && typeof v.list === "function",
|
|
7982
|
+
{ message: "secureStore.backend must implement get/set/delete/list (SecureStoreBackend)" }
|
|
7983
|
+
);
|
|
7984
|
+
var secureStoreCommonFields = {
|
|
7985
|
+
/** Namespace scope. @default 'user' */
|
|
7986
|
+
scope: secureStoreScopeSchema.optional(),
|
|
7987
|
+
/** Default TTL (ms) for stored secrets. @default undefined (no expiry) */
|
|
7988
|
+
ttlMs: import_lazy_zod10.z.number().int().positive().optional(),
|
|
7989
|
+
/** At-rest encryption settings (built-in backings only). */
|
|
7990
|
+
encryption: secureStoreEncryptionSchema.optional()
|
|
7991
|
+
};
|
|
7992
|
+
var secureStoreConfigSchema = import_lazy_zod10.z.union([
|
|
7993
|
+
import_lazy_zod10.z.literal("memory"),
|
|
7994
|
+
// Backing-bearing variants MUST precede the bare-object variant: Zod object
|
|
7995
|
+
// schemas are non-strict (extra keys allowed), so a bare `z.object({ scope })`
|
|
7996
|
+
// would otherwise match `{ sqlite, scope }` first and silently strip the
|
|
7997
|
+
// backing. Ordering most-specific-first keeps sqlite/redis/custom intact.
|
|
7998
|
+
import_lazy_zod10.z.object({ sqlite: tokenStorageSqliteSchema, ...secureStoreCommonFields }),
|
|
7999
|
+
import_lazy_zod10.z.object({ redis: redisConfigSchema, ...secureStoreCommonFields }),
|
|
8000
|
+
import_lazy_zod10.z.object({ backend: secureStoreCustomBackendSchema, ...secureStoreCommonFields }),
|
|
8001
|
+
import_lazy_zod10.z.object({ ...secureStoreCommonFields })
|
|
8002
|
+
]);
|
|
6471
8003
|
var tokenRefreshConfigSchema = import_lazy_zod10.z.object({
|
|
6472
8004
|
/**
|
|
6473
8005
|
* Enable automatic token refresh
|
|
@@ -6534,7 +8066,22 @@ var federatedAuthConfigSchema = import_lazy_zod10.z.object({
|
|
|
6534
8066
|
* - 'format': validate only "federated:{sessionId}:{nonce}" format
|
|
6535
8067
|
* @default 'strict'
|
|
6536
8068
|
*/
|
|
6537
|
-
stateValidation: import_lazy_zod10.z.enum(["strict", "format"]).default("strict")
|
|
8069
|
+
stateValidation: import_lazy_zod10.z.enum(["strict", "format"]).default("strict"),
|
|
8070
|
+
/**
|
|
8071
|
+
* Minimum number of upstream providers a user must link before a FrontMCP JWT
|
|
8072
|
+
* is minted. The federated callback refuses to issue a token until at least
|
|
8073
|
+
* this many providers have been authorized. Defaults to `1` at runtime when
|
|
8074
|
+
* `providers` are configured (i.e. "no JWT until ≥1 linked"). Left optional
|
|
8075
|
+
* here so existing configs without `providers` are completely unaffected.
|
|
8076
|
+
*/
|
|
8077
|
+
minProviders: import_lazy_zod10.z.number().int().positive().optional(),
|
|
8078
|
+
/**
|
|
8079
|
+
* Provider ids that MUST be among the linked providers before a JWT is minted.
|
|
8080
|
+
* The federated callback rejects the login unless every id listed here is in
|
|
8081
|
+
* the user's selected providers. Optional — omit to allow any combination that
|
|
8082
|
+
* satisfies {@link minProviders}.
|
|
8083
|
+
*/
|
|
8084
|
+
requiredProviders: import_lazy_zod10.z.array(import_lazy_zod10.z.string()).optional()
|
|
6538
8085
|
});
|
|
6539
8086
|
var incrementalAuthConfigSchema = import_lazy_zod10.z.object({
|
|
6540
8087
|
/**
|
|
@@ -6635,9 +8182,19 @@ var transparentAuthOptionsSchema = import_lazy_zod12.z.object({
|
|
|
6635
8182
|
|
|
6636
8183
|
// libs/auth/src/options/orchestrated.schema.ts
|
|
6637
8184
|
var import_lazy_zod13 = require("@frontmcp/lazy-zod");
|
|
8185
|
+
var authSlotSchema = import_lazy_zod13.z.enum(["login", "consent", "incremental", "federated", "error"]);
|
|
8186
|
+
var authUiMapSchema = import_lazy_zod13.z.partialRecord(authSlotSchema, import_lazy_zod13.z.string().min(1));
|
|
8187
|
+
var authExtraHandlerSchema = import_lazy_zod13.z.custom((v) => typeof v === "function", {
|
|
8188
|
+
message: "auth.extras entries must be handler functions (input, ctx) => AuthExtraResult."
|
|
8189
|
+
});
|
|
8190
|
+
var authExtrasMapSchema = import_lazy_zod13.z.record(import_lazy_zod13.z.string().min(1), authExtraHandlerSchema);
|
|
6638
8191
|
var sharedAuthFields = {
|
|
6639
8192
|
local: localSigningConfigSchema.optional(),
|
|
6640
8193
|
tokenStorage: tokenStorageConfigSchema.default("memory"),
|
|
8194
|
+
// #470 — backing for the general session secure-secret store (`this.secureStore`).
|
|
8195
|
+
// Optional (no default) so an unset value resolves to the in-memory encrypted
|
|
8196
|
+
// default at runtime; existing configs are unaffected.
|
|
8197
|
+
secureStore: secureStoreConfigSchema.optional(),
|
|
6641
8198
|
allowDefaultPublic: import_lazy_zod13.z.boolean().default(false),
|
|
6642
8199
|
anonymousScopes: import_lazy_zod13.z.array(import_lazy_zod13.z.string()).default(["anonymous"]),
|
|
6643
8200
|
publicAccess: publicAccessConfigSchema.optional(),
|
|
@@ -6646,11 +8203,59 @@ var sharedAuthFields = {
|
|
|
6646
8203
|
refresh: tokenRefreshConfigSchema.optional(),
|
|
6647
8204
|
expectedAudience: import_lazy_zod13.z.union([import_lazy_zod13.z.string(), import_lazy_zod13.z.array(import_lazy_zod13.z.string())]).optional(),
|
|
6648
8205
|
incrementalAuth: incrementalAuthConfigSchema.optional(),
|
|
6649
|
-
cimd: cimdConfigSchema.optional()
|
|
8206
|
+
cimd: cimdConfigSchema.optional(),
|
|
8207
|
+
// #469 — custom auth UI as a slot→file map + extras name→handler map (per-app
|
|
8208
|
+
// under splitByApp). Both optional; omitting them serves the built-in pages.
|
|
8209
|
+
ui: authUiMapSchema.optional(),
|
|
8210
|
+
extras: authExtrasMapSchema.optional()
|
|
6650
8211
|
};
|
|
8212
|
+
var loginFieldSchema = import_lazy_zod13.z.object({
|
|
8213
|
+
type: import_lazy_zod13.z.enum(["text", "password", "email", "select", "hidden"]),
|
|
8214
|
+
label: import_lazy_zod13.z.string().optional(),
|
|
8215
|
+
required: import_lazy_zod13.z.boolean().optional(),
|
|
8216
|
+
placeholder: import_lazy_zod13.z.string().optional(),
|
|
8217
|
+
options: import_lazy_zod13.z.array(import_lazy_zod13.z.object({ value: import_lazy_zod13.z.string(), label: import_lazy_zod13.z.string() })).optional()
|
|
8218
|
+
});
|
|
8219
|
+
var loginConfigSchema = import_lazy_zod13.z.object({
|
|
8220
|
+
title: import_lazy_zod13.z.string().optional(),
|
|
8221
|
+
subtitle: import_lazy_zod13.z.string().optional(),
|
|
8222
|
+
logoUri: import_lazy_zod13.z.string().optional(),
|
|
8223
|
+
fields: import_lazy_zod13.z.record(import_lazy_zod13.z.string(), loginFieldSchema).optional(),
|
|
8224
|
+
// Full HTML override — a function, validated structurally like other callbacks
|
|
8225
|
+
// in the codebase (see http/cors handlers using z.custom<…>).
|
|
8226
|
+
render: import_lazy_zod13.z.custom((v) => typeof v === "function", {
|
|
8227
|
+
message: "login.render must be a function"
|
|
8228
|
+
}).optional(),
|
|
8229
|
+
subject: import_lazy_zod13.z.object({
|
|
8230
|
+
fromField: import_lazy_zod13.z.string().optional(),
|
|
8231
|
+
strategy: import_lazy_zod13.z.enum(["per-session", "per-account"]).optional()
|
|
8232
|
+
}).optional()
|
|
8233
|
+
});
|
|
8234
|
+
var authenticateFnSchema = import_lazy_zod13.z.custom((v) => typeof v === "function", {
|
|
8235
|
+
message: "authenticate must be a function"
|
|
8236
|
+
});
|
|
6651
8237
|
var localAuthSchema = import_lazy_zod13.z.object({
|
|
6652
8238
|
mode: import_lazy_zod13.z.literal("local"),
|
|
6653
|
-
...sharedAuthFields
|
|
8239
|
+
...sharedAuthFields,
|
|
8240
|
+
// #468 — opt-out of the email requirement at /oauth/callback for
|
|
8241
|
+
// single-operator local setups. Defaults preserve the historical behavior
|
|
8242
|
+
// (email required) so existing configs are unaffected.
|
|
8243
|
+
requireEmail: import_lazy_zod13.z.boolean().default(true),
|
|
8244
|
+
anonymousSubject: import_lazy_zod13.z.string().min(1).default("local-operator"),
|
|
8245
|
+
// Checkpoint 3a — declarative login customization + custom verification.
|
|
8246
|
+
// Both optional; omitting them preserves the default login path exactly.
|
|
8247
|
+
login: loginConfigSchema.optional(),
|
|
8248
|
+
authenticate: authenticateFnSchema.optional(),
|
|
8249
|
+
// Multi-OAuth-provider orchestration — declare upstream providers (GitHub,
|
|
8250
|
+
// Slack, Jira, …) to federate at /oauth/authorize. Optional; omitting it
|
|
8251
|
+
// preserves the default single-operator local login exactly.
|
|
8252
|
+
providers: import_lazy_zod13.z.array(upstreamProviderSchema).optional(),
|
|
8253
|
+
// Local-AS Dynamic Client Registration control surface (#462) — gate
|
|
8254
|
+
// `POST /oauth/register`, allowlist redirect_uris/client_ids, require an
|
|
8255
|
+
// initial access token, and seed pre-registered trusted clients. Optional;
|
|
8256
|
+
// omitting it preserves today's behavior exactly (DCR on in dev, off in prod;
|
|
8257
|
+
// no allowlist; no initial access token).
|
|
8258
|
+
dcr: localDcrConfigSchema.optional()
|
|
6654
8259
|
});
|
|
6655
8260
|
var remoteAuthSchema = import_lazy_zod13.z.object({
|
|
6656
8261
|
mode: import_lazy_zod13.z.literal("remote"),
|
|
@@ -7447,7 +9052,7 @@ var CredentialCache = class {
|
|
|
7447
9052
|
var AUTH_PROVIDERS_ACCESSOR = /* @__PURE__ */ Symbol.for("frontmcp:AUTH_PROVIDERS_ACCESSOR");
|
|
7448
9053
|
|
|
7449
9054
|
// libs/auth/src/vault/auth-providers.accessor.impl.ts
|
|
7450
|
-
var
|
|
9055
|
+
var import_utils41 = require("@frontmcp/utils");
|
|
7451
9056
|
var AuthProvidersAccessorImpl = class {
|
|
7452
9057
|
constructor(registry, vault, cache, loader, context, logger) {
|
|
7453
9058
|
this.registry = registry;
|
|
@@ -7656,7 +9261,7 @@ var AuthProvidersAccessorImpl = class {
|
|
|
7656
9261
|
return { [credential.headerName]: credential.key };
|
|
7657
9262
|
case "basic": {
|
|
7658
9263
|
const value = `${credential.username}:${credential.password}`;
|
|
7659
|
-
const encoded = credential.encodedValue ?? (0,
|
|
9264
|
+
const encoded = credential.encodedValue ?? (0, import_utils41.base64Encode)(new TextEncoder().encode(value));
|
|
7660
9265
|
return { Authorization: `Basic ${encoded}` };
|
|
7661
9266
|
}
|
|
7662
9267
|
case "custom":
|
|
@@ -9192,6 +10797,7 @@ var FetchCredentialMiddleware = class {
|
|
|
9192
10797
|
AuthorityDeniedError,
|
|
9193
10798
|
AuthorizationBase,
|
|
9194
10799
|
CDN,
|
|
10800
|
+
CREDENTIALS_ACCESSOR,
|
|
9195
10801
|
CimdClientIdMismatchError,
|
|
9196
10802
|
CimdDisabledError,
|
|
9197
10803
|
CimdError,
|
|
@@ -9203,11 +10809,15 @@ var FetchCredentialMiddleware = class {
|
|
|
9203
10809
|
CredentialCache,
|
|
9204
10810
|
CredentialProviderAlreadyRegisteredError,
|
|
9205
10811
|
CredentialStorageError,
|
|
10812
|
+
CredentialsAccessorImpl,
|
|
10813
|
+
DEFAULT_RESUME_TTL_MS,
|
|
9206
10814
|
DEFAULT_THEME,
|
|
10815
|
+
DcrClientRegistry,
|
|
9207
10816
|
EagerCredentialLoader,
|
|
9208
10817
|
ElicitationSecretRequiredError,
|
|
9209
10818
|
EncryptedRedisVault,
|
|
9210
10819
|
EncryptedStorageError,
|
|
10820
|
+
EncryptedStorageSecureStoreBackend,
|
|
9211
10821
|
EncryptedTypedStorage,
|
|
9212
10822
|
EncryptionContextNotSetError,
|
|
9213
10823
|
EncryptionKeyNotConfiguredError,
|
|
@@ -9216,6 +10826,7 @@ var FetchCredentialMiddleware = class {
|
|
|
9216
10826
|
InMemoryAuthorizationStore,
|
|
9217
10827
|
InMemoryAuthorizationVault,
|
|
9218
10828
|
InMemoryCimdCache,
|
|
10829
|
+
InMemoryConsentStore,
|
|
9219
10830
|
InMemoryFederatedAuthSessionStore,
|
|
9220
10831
|
InMemoryOrchestratedTokenStore,
|
|
9221
10832
|
InMemoryStoreRequiredError,
|
|
@@ -9233,11 +10844,18 @@ var FetchCredentialMiddleware = class {
|
|
|
9233
10844
|
RedirectUriMismatchError,
|
|
9234
10845
|
RedisAuthorizationStore,
|
|
9235
10846
|
RedisSessionStore,
|
|
10847
|
+
SECURE_STORE_ACCESSOR,
|
|
9236
10848
|
ScopeDeniedError,
|
|
10849
|
+
SecureStoreAccessorImpl,
|
|
10850
|
+
SessionCredentialVault,
|
|
9237
10851
|
SessionIdEmptyError,
|
|
9238
10852
|
SessionRateLimiter,
|
|
9239
10853
|
SessionSecretRequiredError,
|
|
10854
|
+
StorageAuthorizationStore,
|
|
9240
10855
|
StorageAuthorizationVault,
|
|
10856
|
+
StorageConsentStore,
|
|
10857
|
+
StorageFederatedAuthSessionStore,
|
|
10858
|
+
StorageOrchestratedTokenStore,
|
|
9241
10859
|
StorageTokenStore,
|
|
9242
10860
|
TinyTtlCache,
|
|
9243
10861
|
TokenLeakDetectedError,
|
|
@@ -9269,6 +10887,7 @@ var FetchCredentialMiddleware = class {
|
|
|
9269
10887
|
authProviderMappingSchema,
|
|
9270
10888
|
authProvidersVaultOptionsSchema,
|
|
9271
10889
|
authUserSchema,
|
|
10890
|
+
authenticateFnSchema,
|
|
9272
10891
|
authoritiesClaimsMappingSchema,
|
|
9273
10892
|
authoritiesConfigSchema,
|
|
9274
10893
|
authoritiesMetadataSchema,
|
|
@@ -9283,7 +10902,11 @@ var FetchCredentialMiddleware = class {
|
|
|
9283
10902
|
bearerApplier,
|
|
9284
10903
|
bearerCredentialSchema,
|
|
9285
10904
|
buildAuthContext,
|
|
10905
|
+
buildCodeRecord,
|
|
10906
|
+
buildConnectPage,
|
|
10907
|
+
buildConnectSuccessPage,
|
|
9286
10908
|
buildConsentPage,
|
|
10909
|
+
buildCredentialResumeUrl,
|
|
9287
10910
|
buildErrorPage,
|
|
9288
10911
|
buildFederatedLoginPage,
|
|
9289
10912
|
buildIncrementalAuthPage,
|
|
@@ -9291,7 +10914,9 @@ var FetchCredentialMiddleware = class {
|
|
|
9291
10914
|
buildInvalidRequestHeader,
|
|
9292
10915
|
buildInvalidTokenHeader,
|
|
9293
10916
|
buildLoginPage,
|
|
10917
|
+
buildPendingRecord,
|
|
9294
10918
|
buildPrmUrl,
|
|
10919
|
+
buildRefreshTokenRecord,
|
|
9295
10920
|
buildToolConsentPage,
|
|
9296
10921
|
buildUnauthorizedHeader,
|
|
9297
10922
|
buildWwwAuthenticate,
|
|
@@ -9304,6 +10929,7 @@ var FetchCredentialMiddleware = class {
|
|
|
9304
10929
|
clientMetadataDocumentSchema,
|
|
9305
10930
|
completeCurrentProvider,
|
|
9306
10931
|
consentConfigSchema,
|
|
10932
|
+
consentRecordKey,
|
|
9307
10933
|
consentSelectionSchema,
|
|
9308
10934
|
consentStateSchema,
|
|
9309
10935
|
consentToolItemSchema,
|
|
@@ -9311,6 +10937,9 @@ var FetchCredentialMiddleware = class {
|
|
|
9311
10937
|
createEncryptedVault,
|
|
9312
10938
|
createFederatedAuthSession,
|
|
9313
10939
|
createLayout,
|
|
10940
|
+
createSecureStore,
|
|
10941
|
+
createSessionCredentialVault,
|
|
10942
|
+
createTokenStorageAdapter,
|
|
9314
10943
|
credentialProviderConfigSchema,
|
|
9315
10944
|
credentialSchema,
|
|
9316
10945
|
credentialScopeSchema,
|
|
@@ -9344,7 +10973,9 @@ var FetchCredentialMiddleware = class {
|
|
|
9344
10973
|
federatedSelectionSchema,
|
|
9345
10974
|
flatRemoteProviderFields,
|
|
9346
10975
|
fromSessionRecord,
|
|
10976
|
+
generateAuthorizationCode,
|
|
9347
10977
|
generatePkceChallenge,
|
|
10978
|
+
generateRefreshTokenValue,
|
|
9348
10979
|
getCredentialOptionsSchema,
|
|
9349
10980
|
getKey,
|
|
9350
10981
|
getNextProvider,
|
|
@@ -9362,12 +10993,15 @@ var FetchCredentialMiddleware = class {
|
|
|
9362
10993
|
isOrchestratedLocal,
|
|
9363
10994
|
isOrchestratedMode,
|
|
9364
10995
|
isOrchestratedRemote,
|
|
10996
|
+
isPersistentTokenStorage,
|
|
9365
10997
|
isPublicMode,
|
|
10998
|
+
isRedisTokenStorage,
|
|
9366
10999
|
isRemoteMode,
|
|
9367
11000
|
isSessionComplete,
|
|
9368
11001
|
isSignedSession,
|
|
9369
11002
|
isSoonExpiring,
|
|
9370
11003
|
isSoonExpiringProvider,
|
|
11004
|
+
isSqliteTokenStorage,
|
|
9371
11005
|
isTransparentMode,
|
|
9372
11006
|
jsonWebKeySetSchema,
|
|
9373
11007
|
jwkParametersSchema,
|
|
@@ -9376,7 +11010,10 @@ var FetchCredentialMiddleware = class {
|
|
|
9376
11010
|
llmSafeAuthContextSchema,
|
|
9377
11011
|
loadingStrategySchema,
|
|
9378
11012
|
localAuthSchema,
|
|
11013
|
+
localDcrClientSchema,
|
|
11014
|
+
localDcrConfigSchema,
|
|
9379
11015
|
localSigningConfigSchema,
|
|
11016
|
+
loginConfigSchema,
|
|
9380
11017
|
mtlsCredentialSchema,
|
|
9381
11018
|
noopLogger,
|
|
9382
11019
|
normalizeIssuer,
|
|
@@ -9402,14 +11039,20 @@ var FetchCredentialMiddleware = class {
|
|
|
9402
11039
|
redisVaultEntrySchema,
|
|
9403
11040
|
remoteAuthSchema,
|
|
9404
11041
|
remoteProviderConfigSchema,
|
|
11042
|
+
renderLocalLoginPage,
|
|
9405
11043
|
renderToHtml,
|
|
9406
11044
|
resetCachedKey,
|
|
9407
11045
|
resolveRequiredScopes,
|
|
9408
11046
|
resourceIdRefSchema,
|
|
9409
11047
|
safeDecrypt,
|
|
11048
|
+
secureStoreConfigSchema,
|
|
11049
|
+
secureStoreCustomBackendSchema,
|
|
11050
|
+
secureStoreEncryptionSchema,
|
|
11051
|
+
secureStoreScopeSchema,
|
|
9410
11052
|
serviceAccountCredentialSchema,
|
|
9411
11053
|
sessionIdPayloadSchema,
|
|
9412
11054
|
sessionJwtPayloadSchema,
|
|
11055
|
+
signCredentialResumeToken,
|
|
9413
11056
|
signSession,
|
|
9414
11057
|
skippedAppBehaviorSchema,
|
|
9415
11058
|
sseTransportStateSchema,
|
|
@@ -9420,20 +11063,24 @@ var FetchCredentialMiddleware = class {
|
|
|
9420
11063
|
storedSessionSchema,
|
|
9421
11064
|
streamableHttpTransportStateSchema,
|
|
9422
11065
|
toEpochSeconds,
|
|
11066
|
+
toLoginExtraFields,
|
|
9423
11067
|
toSessionRecord,
|
|
9424
11068
|
tokenRefreshConfigSchema,
|
|
9425
11069
|
tokenStorageConfigSchema,
|
|
11070
|
+
tokenStorageSqliteSchema,
|
|
9426
11071
|
transparentAuthOptionsSchema,
|
|
9427
11072
|
transportProtocolSchema,
|
|
9428
11073
|
transportSessionSchema,
|
|
9429
11074
|
transportStateSchema,
|
|
9430
11075
|
trimSlash,
|
|
9431
11076
|
tryJwtExp,
|
|
11077
|
+
upstreamProviderSchema,
|
|
9432
11078
|
userClaimSchema,
|
|
9433
11079
|
validateAudience,
|
|
9434
11080
|
validateClientIdUrl,
|
|
9435
11081
|
vaultConsentRecordSchema,
|
|
9436
11082
|
vaultFederatedRecordSchema,
|
|
11083
|
+
verifyCredentialResumeToken,
|
|
9437
11084
|
verifyOrParseSession,
|
|
9438
11085
|
verifyPkce,
|
|
9439
11086
|
verifySession,
|