@frontmcp/auth 1.10.16 → 1.11.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/browser/authorities/authorities.context.mjs +115 -0
- package/browser/authorities/authorities.engine.mjs +272 -0
- package/browser/authorities/authorities.errors.mjs +59 -0
- package/browser/authorities/authorities.evaluator.mjs +225 -0
- package/browser/authorities/authorities.metadata-augment.mjs +18 -0
- package/browser/authorities/authorities.registry.mjs +112 -0
- package/browser/authorities/authorities.schema.mjs +92 -0
- package/browser/authorities/authorities.scope-mapping.mjs +30 -0
- package/browser/authorities/authorities.validation.mjs +208 -0
- package/browser/authorities/index.mjs +43 -0
- package/browser/authorization/authorization.class.mjs +252 -0
- package/browser/authorization/authorization.types.mjs +64 -0
- package/browser/authorization/dcr-client.registry.mjs +222 -0
- package/browser/authorization/index.mjs +41 -0
- package/browser/authorization/orchestrated.accessor.mjs +86 -0
- package/browser/authorization/orchestrated.authorization.mjs +289 -0
- package/browser/authorization/public.authorization.mjs +124 -0
- package/browser/authorization/transparent.authorization.mjs +130 -0
- package/browser/cimd/cimd-redis.cache.mjs +231 -0
- package/browser/cimd/cimd.cache.mjs +215 -0
- package/browser/cimd/cimd.errors.mjs +142 -0
- package/browser/cimd/cimd.logger.mjs +15 -0
- package/browser/cimd/cimd.service.mjs +376 -0
- package/browser/cimd/cimd.types.mjs +228 -0
- package/browser/cimd/cimd.validator.mjs +217 -0
- package/browser/cimd/index.mjs +63 -0
- package/browser/common/auth-logger.interface.mjs +14 -0
- package/browser/common/index.mjs +6 -0
- package/browser/common/jwt.types.mjs +38 -0
- package/browser/common/session-user.types.mjs +0 -0
- package/browser/common/session.types.mjs +58 -0
- package/browser/common/zod-utils.mjs +0 -0
- package/browser/consent/consent.types.mjs +94 -0
- package/browser/consent/index.mjs +17 -0
- package/browser/context/frontmcp-auth-context.factory.mjs +29 -0
- package/browser/context/frontmcp-auth-context.impl.mjs +112 -0
- package/browser/context/index.mjs +7 -0
- package/browser/detection/auth-provider-detection.mjs +176 -0
- package/browser/detection/index.mjs +19 -0
- package/browser/errors/auth-internal.error.mjs +42 -0
- package/browser/errors/auth-internal.errors.mjs +150 -0
- package/browser/errors/index.mjs +53 -0
- package/browser/fetch/fetch-credential-middleware.mjs +91 -0
- package/browser/fetch/index.mjs +19 -0
- package/browser/index.mjs +573 -0
- package/browser/jwks/index.mjs +9 -0
- package/browser/jwks/jose.loader.mjs +28 -0
- package/browser/jwks/jwks.service.mjs +521 -0
- package/browser/jwks/jwks.utils.mjs +33 -0
- package/browser/options/app-auth.schema.mjs +18 -0
- package/browser/options/index.mjs +89 -0
- package/browser/options/orchestrated.schema.mjs +128 -0
- package/browser/options/public.schema.mjs +39 -0
- package/browser/options/schema.mjs +16 -0
- package/browser/options/shared.schemas.mjs +506 -0
- package/browser/options/static.schema.mjs +41 -0
- package/browser/options/transparent.schema.mjs +43 -0
- package/browser/options/utils.mjs +47 -0
- package/browser/people/index.mjs +15 -0
- package/browser/people/loopback-token.auth.mjs +115 -0
- package/browser/people/people-auth.options.mjs +37 -0
- package/browser/session/authorization-vault.mjs +279 -0
- package/browser/session/authorization.store.mjs +336 -0
- package/browser/session/consent.store.mjs +34 -0
- package/browser/session/credential-resume-link.mjs +43 -0
- package/browser/session/credentials-accessor.impl.mjs +54 -0
- package/browser/session/credentials-accessor.mjs +5 -0
- package/browser/session/encrypted-authorization-vault.mjs +405 -0
- package/browser/session/federated-auth.session.mjs +176 -0
- package/browser/session/incremental-auth-ticket.mjs +42 -0
- package/browser/session/index.mjs +266 -0
- package/browser/session/orchestrated-token.crypto.mjs +47 -0
- package/browser/session/orchestrated-token.store.mjs +286 -0
- package/browser/session/pending-login.mjs +37 -0
- package/browser/session/redis-session.store.mjs +255 -0
- package/browser/session/secure-store-accessor.impl.mjs +78 -0
- package/browser/session/secure-store-accessor.mjs +5 -0
- package/browser/session/secure-store-backends.mjs +111 -0
- package/browser/session/secure-store.factory.mjs +77 -0
- package/browser/session/session-credential-vault.factory.mjs +18 -0
- package/browser/session/session-credential-vault.mjs +243 -0
- package/browser/session/session-crypto.mjs +39 -0
- package/browser/session/session-rate-limiter.mjs +126 -0
- package/browser/session/session.transport.mjs +16 -0
- package/browser/session/signin-binding.mjs +30 -0
- package/browser/session/storage/in-memory-authorization-vault.mjs +28 -0
- package/browser/session/storage/index.mjs +14 -0
- package/browser/session/storage/storage-authorization-vault.mjs +301 -0
- package/browser/session/storage/storage-token-store.mjs +83 -0
- package/browser/session/storage-authorization.store.mjs +134 -0
- package/browser/session/storage-consent.store.mjs +33 -0
- package/browser/session/storage-federated-auth.session.mjs +55 -0
- package/browser/session/storage-orchestrated-token.store.mjs +197 -0
- package/browser/session/token-storage.factory.mjs +58 -0
- package/browser/session/token.refresh.mjs +36 -0
- package/browser/session/token.vault.mjs +61 -0
- package/browser/session/transport-session.types.mjs +108 -0
- package/browser/session/utils/auth-token.utils.mjs +58 -0
- package/browser/session/utils/index.mjs +23 -0
- package/browser/session/utils/session-crypto.utils.mjs +49 -0
- package/browser/session/utils/tiny-ttl-cache.mjs +46 -0
- package/browser/session/vault-encryption.mjs +187 -0
- package/browser/session/vercel-kv-session.store.mjs +230 -0
- package/browser/shared-server/index.mjs +17 -0
- package/browser/shared-server/shared-server-identity.mjs +55 -0
- package/browser/ui/base-layout.mjs +184 -0
- package/browser/ui/index.mjs +51 -0
- package/browser/ui/local-login.helper.mjs +46 -0
- package/browser/ui/templates.mjs +482 -0
- package/browser/utils/audience.validator.mjs +110 -0
- package/browser/utils/authorization-id.utils.mjs +10 -0
- package/browser/utils/index.mjs +45 -0
- package/browser/utils/redirect-uri.utils.mjs +22 -0
- package/browser/utils/scope-grant.utils.mjs +36 -0
- package/browser/utils/token-scopes.utils.mjs +12 -0
- package/browser/utils/www-authenticate.utils.mjs +164 -0
- package/browser/vault/auth-providers.accessor.impl.mjs +236 -0
- package/browser/vault/auth-providers.accessor.mjs +5 -0
- package/browser/vault/auth-providers.registry.mjs +140 -0
- package/browser/vault/auth-providers.types.mjs +47 -0
- package/browser/vault/auth-providers.vault.mjs +206 -0
- package/browser/vault/credential-cache.mjs +187 -0
- package/browser/vault/credential-helpers.mjs +15 -0
- package/browser/vault/credential-loaders/eager-loader.mjs +91 -0
- package/browser/vault/credential-loaders/lazy-loader.mjs +120 -0
- package/browser/vault/index.mjs +35 -0
- package/cimd/cimd.types.d.ts +1 -1
- package/cimd/cimd.types.d.ts.map +1 -1
- package/common/jwt.types.d.ts +1 -1
- package/common/jwt.types.d.ts.map +1 -1
- package/common/session.types.d.ts +9 -1
- package/common/session.types.d.ts.map +1 -1
- package/consent/consent.types.d.ts +1 -1
- package/consent/consent.types.d.ts.map +1 -1
- package/detection/auth-provider-detection.d.ts +1 -1
- package/detection/auth-provider-detection.d.ts.map +1 -1
- package/errors/auth-internal.errors.d.ts +6 -0
- package/errors/auth-internal.errors.d.ts.map +1 -1
- package/errors/index.d.ts +1 -1
- package/errors/index.d.ts.map +1 -1
- package/esm/index.mjs +2 -2
- package/esm/package.json +21 -5
- package/index.d.ts +5 -1
- package/index.d.ts.map +1 -1
- package/index.js +2 -2
- package/options/app-auth.schema.d.ts +19 -1
- package/options/app-auth.schema.d.ts.map +1 -1
- package/options/orchestrated.schema.d.ts +37 -1
- package/options/orchestrated.schema.d.ts.map +1 -1
- package/options/public.schema.d.ts +1 -1
- package/options/public.schema.d.ts.map +1 -1
- package/options/schema.d.ts +19 -1
- package/options/schema.d.ts.map +1 -1
- package/options/shared.schemas.d.ts +10 -14
- package/options/shared.schemas.d.ts.map +1 -1
- package/options/static.schema.d.ts +1 -1
- package/options/static.schema.d.ts.map +1 -1
- package/options/transparent.schema.d.ts +1 -1
- package/options/transparent.schema.d.ts.map +1 -1
- package/package.json +21 -5
- package/people/index.d.ts +3 -0
- package/people/index.d.ts.map +1 -0
- package/people/loopback-token.auth.d.ts +75 -0
- package/people/loopback-token.auth.d.ts.map +1 -0
- package/people/people-auth.options.d.ts +36 -0
- package/people/people-auth.options.d.ts.map +1 -0
- package/session/authorization-vault.d.ts +1 -1
- package/session/authorization-vault.d.ts.map +1 -1
- package/session/encrypted-authorization-vault.d.ts +1 -1
- package/session/encrypted-authorization-vault.d.ts.map +1 -1
- package/session/secure-store.factory.d.ts.map +1 -1
- package/shared-server/index.d.ts +3 -0
- package/shared-server/index.d.ts.map +1 -0
- package/shared-server/shared-server-identity.d.ts +26 -0
- package/shared-server/shared-server-identity.d.ts.map +1 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// libs/auth/src/vault/auth-providers.registry.ts
|
|
2
|
+
import { CredentialProviderAlreadyRegisteredError } from "../errors/auth-internal.errors.mjs";
|
|
3
|
+
var AuthProvidersRegistry = class {
|
|
4
|
+
providers = /* @__PURE__ */ new Map();
|
|
5
|
+
defaultCacheTtl;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.defaultCacheTtl = options?.defaultCacheTtl ?? 36e5;
|
|
8
|
+
if (options?.providers) {
|
|
9
|
+
for (const provider of options.providers) {
|
|
10
|
+
this.register(provider);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Register a credential provider
|
|
16
|
+
*
|
|
17
|
+
* @param config - Provider configuration
|
|
18
|
+
* @throws CredentialProviderAlreadyRegisteredError if provider with same name already registered
|
|
19
|
+
*/
|
|
20
|
+
register(config) {
|
|
21
|
+
if (this.providers.has(config.name)) {
|
|
22
|
+
throw new CredentialProviderAlreadyRegisteredError(config.name);
|
|
23
|
+
}
|
|
24
|
+
const normalized = this.normalize(config);
|
|
25
|
+
this.providers.set(config.name, normalized);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Unregister a credential provider
|
|
29
|
+
*
|
|
30
|
+
* @param name - Provider name to unregister
|
|
31
|
+
* @returns true if provider was unregistered, false if not found
|
|
32
|
+
*/
|
|
33
|
+
unregister(name) {
|
|
34
|
+
return this.providers.delete(name);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get a provider configuration by name
|
|
38
|
+
*
|
|
39
|
+
* @param name - Provider name
|
|
40
|
+
* @returns Provider config or undefined if not found
|
|
41
|
+
*/
|
|
42
|
+
get(name) {
|
|
43
|
+
return this.providers.get(name);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Check if a provider is registered
|
|
47
|
+
*
|
|
48
|
+
* @param name - Provider name
|
|
49
|
+
* @returns true if provider is registered
|
|
50
|
+
*/
|
|
51
|
+
has(name) {
|
|
52
|
+
return this.providers.has(name);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get all registered provider names
|
|
56
|
+
*/
|
|
57
|
+
getNames() {
|
|
58
|
+
return [...this.providers.keys()];
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get all provider configurations
|
|
62
|
+
*/
|
|
63
|
+
getAll() {
|
|
64
|
+
return [...this.providers.values()];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get providers by scope
|
|
68
|
+
*
|
|
69
|
+
* @param scope - Credential scope to filter by
|
|
70
|
+
*/
|
|
71
|
+
getByScope(scope) {
|
|
72
|
+
return this.getAll().filter((p) => p.scope === scope);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get providers by loading strategy
|
|
76
|
+
*
|
|
77
|
+
* @param loading - Loading strategy to filter by
|
|
78
|
+
*/
|
|
79
|
+
getByLoading(loading) {
|
|
80
|
+
return this.getAll().filter((p) => p.loading === loading);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get providers that are required
|
|
84
|
+
*/
|
|
85
|
+
getRequired() {
|
|
86
|
+
return this.getAll().filter((p) => p.required);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get providers that should be eagerly loaded
|
|
90
|
+
*/
|
|
91
|
+
getEager() {
|
|
92
|
+
return this.getByLoading("eager");
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get providers that should be lazily loaded
|
|
96
|
+
*/
|
|
97
|
+
getLazy() {
|
|
98
|
+
return this.getByLoading("lazy");
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get the number of registered providers
|
|
102
|
+
*/
|
|
103
|
+
get size() {
|
|
104
|
+
return this.providers.size;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Check if registry is empty
|
|
108
|
+
*/
|
|
109
|
+
isEmpty() {
|
|
110
|
+
return this.providers.size === 0;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Clear all registered providers
|
|
114
|
+
*/
|
|
115
|
+
clear() {
|
|
116
|
+
this.providers.clear();
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Normalize provider config with defaults
|
|
120
|
+
*/
|
|
121
|
+
normalize(config) {
|
|
122
|
+
return {
|
|
123
|
+
name: config.name,
|
|
124
|
+
description: config.description,
|
|
125
|
+
scope: config.scope,
|
|
126
|
+
loading: config.loading,
|
|
127
|
+
cacheTtl: config.cacheTtl ?? this.defaultCacheTtl,
|
|
128
|
+
factory: config.factory,
|
|
129
|
+
refresh: config.refresh,
|
|
130
|
+
toHeaders: config.toHeaders,
|
|
131
|
+
metadata: config.metadata,
|
|
132
|
+
required: config.required ?? false
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
var AUTH_PROVIDERS_REGISTRY = /* @__PURE__ */ Symbol.for("frontmcp:AUTH_PROVIDERS_REGISTRY");
|
|
137
|
+
export {
|
|
138
|
+
AUTH_PROVIDERS_REGISTRY,
|
|
139
|
+
AuthProvidersRegistry
|
|
140
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// libs/auth/src/vault/auth-providers.types.ts
|
|
2
|
+
import { internalZ as z } from "@frontmcp/lazy-zod";
|
|
3
|
+
var credentialScopeSchema = z.enum(["global", "user", "session"]);
|
|
4
|
+
var loadingStrategySchema = z.enum(["eager", "lazy"]);
|
|
5
|
+
var getCredentialOptionsSchema = z.object({
|
|
6
|
+
forceRefresh: z.boolean().optional(),
|
|
7
|
+
scopes: z.array(z.string()).optional(),
|
|
8
|
+
timeout: z.number().positive().optional()
|
|
9
|
+
}).strict();
|
|
10
|
+
var credentialProviderConfigSchema = z.object({
|
|
11
|
+
name: z.string().min(1),
|
|
12
|
+
description: z.string().optional(),
|
|
13
|
+
scope: credentialScopeSchema,
|
|
14
|
+
loading: loadingStrategySchema,
|
|
15
|
+
cacheTtl: z.number().nonnegative().optional(),
|
|
16
|
+
// Functions validated at runtime, not via Zod (Zod 4 compatibility)
|
|
17
|
+
factory: z.any(),
|
|
18
|
+
refresh: z.any().optional(),
|
|
19
|
+
toHeaders: z.any().optional(),
|
|
20
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
21
|
+
required: z.boolean().optional()
|
|
22
|
+
}).strict();
|
|
23
|
+
var authProviderMappingSchema = z.union([
|
|
24
|
+
z.string(),
|
|
25
|
+
z.object({
|
|
26
|
+
name: z.string().min(1),
|
|
27
|
+
required: z.boolean().optional().default(true),
|
|
28
|
+
scopes: z.array(z.string()).optional(),
|
|
29
|
+
alias: z.string().optional()
|
|
30
|
+
}).strict()
|
|
31
|
+
]);
|
|
32
|
+
var authProvidersVaultOptionsSchema = z.object({
|
|
33
|
+
enabled: z.boolean().optional(),
|
|
34
|
+
useSharedStorage: z.boolean().optional().default(true),
|
|
35
|
+
namespace: z.string().optional().default("authproviders:"),
|
|
36
|
+
defaultCacheTtl: z.number().nonnegative().optional().default(36e5),
|
|
37
|
+
maxCredentialsPerSession: z.number().positive().optional().default(100),
|
|
38
|
+
providers: z.array(credentialProviderConfigSchema).optional()
|
|
39
|
+
}).strict();
|
|
40
|
+
export {
|
|
41
|
+
authProviderMappingSchema,
|
|
42
|
+
authProvidersVaultOptionsSchema,
|
|
43
|
+
credentialProviderConfigSchema,
|
|
44
|
+
credentialScopeSchema,
|
|
45
|
+
getCredentialOptionsSchema,
|
|
46
|
+
loadingStrategySchema
|
|
47
|
+
};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// libs/auth/src/vault/auth-providers.vault.ts
|
|
2
|
+
import { extractCredentialExpiry } from "./credential-helpers.mjs";
|
|
3
|
+
import { AuthInvalidInputError, CredentialStorageError } from "../errors/auth-internal.errors.mjs";
|
|
4
|
+
var AuthProvidersVault = class {
|
|
5
|
+
constructor(baseVault, namespace = "authproviders:", logger) {
|
|
6
|
+
this.baseVault = baseVault;
|
|
7
|
+
this.namespace = namespace;
|
|
8
|
+
this.logger = logger;
|
|
9
|
+
}
|
|
10
|
+
baseVault;
|
|
11
|
+
namespace;
|
|
12
|
+
logger;
|
|
13
|
+
/**
|
|
14
|
+
* Store a credential in the vault
|
|
15
|
+
*
|
|
16
|
+
* @param sessionId - Current session ID
|
|
17
|
+
* @param providerId - Provider name
|
|
18
|
+
* @param credential - Credential to store
|
|
19
|
+
* @param scope - Credential scope
|
|
20
|
+
* @param userId - User ID (required for user scope)
|
|
21
|
+
*/
|
|
22
|
+
async storeCredential(sessionId, providerId, credential, scope, userId) {
|
|
23
|
+
const vaultKey = this.buildVaultKey(sessionId, scope, userId);
|
|
24
|
+
const appCredential = {
|
|
25
|
+
appId: this.namespace,
|
|
26
|
+
providerId,
|
|
27
|
+
credential,
|
|
28
|
+
acquiredAt: Date.now(),
|
|
29
|
+
isValid: true,
|
|
30
|
+
expiresAt: extractCredentialExpiry(credential)
|
|
31
|
+
};
|
|
32
|
+
try {
|
|
33
|
+
await this.baseVault.addAppCredential(vaultKey, appCredential);
|
|
34
|
+
this.logger?.debug(`Stored credential "${providerId}" with scope "${scope}"`);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
this.logger?.warn(`Failed to store credential "${providerId}":`, error);
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get a credential from the vault
|
|
42
|
+
*
|
|
43
|
+
* @param sessionId - Current session ID
|
|
44
|
+
* @param providerId - Provider name
|
|
45
|
+
* @param scope - Credential scope
|
|
46
|
+
* @param userId - User ID (required for user scope)
|
|
47
|
+
* @returns Credential or null if not found
|
|
48
|
+
*/
|
|
49
|
+
async getCredential(sessionId, providerId, scope, userId) {
|
|
50
|
+
const vaultKey = this.buildVaultKey(sessionId, scope, userId);
|
|
51
|
+
try {
|
|
52
|
+
const appCredential = await this.baseVault.getCredential(vaultKey, this.namespace, providerId);
|
|
53
|
+
if (!appCredential) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
if (!appCredential.isValid) {
|
|
57
|
+
this.logger?.debug(`Credential "${providerId}" is marked as invalid`);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
if (appCredential.expiresAt && Date.now() >= appCredential.expiresAt) {
|
|
61
|
+
this.logger?.debug(`Credential "${providerId}" has expired`);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return appCredential.credential;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
this.logger?.warn(`Failed to get credential "${providerId}":`, error);
|
|
67
|
+
if (error instanceof CredentialStorageError) {
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
throw new CredentialStorageError(
|
|
71
|
+
`Storage error while retrieving credential "${providerId}": ${error instanceof Error ? error.message : String(error)}`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Remove a credential from the vault
|
|
77
|
+
*
|
|
78
|
+
* @param sessionId - Current session ID
|
|
79
|
+
* @param providerId - Provider name
|
|
80
|
+
* @param scope - Credential scope
|
|
81
|
+
* @param userId - User ID (required for user scope)
|
|
82
|
+
*/
|
|
83
|
+
async removeCredential(sessionId, providerId, scope, userId) {
|
|
84
|
+
const vaultKey = this.buildVaultKey(sessionId, scope, userId);
|
|
85
|
+
try {
|
|
86
|
+
await this.baseVault.removeAppCredential(vaultKey, this.namespace, providerId);
|
|
87
|
+
this.logger?.debug(`Removed credential "${providerId}"`);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
this.logger?.warn(`Failed to remove credential "${providerId}":`, error);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Invalidate a credential (mark as invalid without removing)
|
|
94
|
+
*
|
|
95
|
+
* @param sessionId - Current session ID
|
|
96
|
+
* @param providerId - Provider name
|
|
97
|
+
* @param scope - Credential scope
|
|
98
|
+
* @param reason - Reason for invalidation
|
|
99
|
+
* @param userId - User ID (required for user scope)
|
|
100
|
+
*/
|
|
101
|
+
async invalidateCredential(sessionId, providerId, scope, reason, userId) {
|
|
102
|
+
const vaultKey = this.buildVaultKey(sessionId, scope, userId);
|
|
103
|
+
try {
|
|
104
|
+
await this.baseVault.invalidateCredential(vaultKey, this.namespace, providerId, reason);
|
|
105
|
+
this.logger?.debug(`Invalidated credential "${providerId}": ${reason}`);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
this.logger?.warn(`Failed to invalidate credential "${providerId}":`, error);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Update OAuth credential tokens (for refresh)
|
|
112
|
+
*
|
|
113
|
+
* @param sessionId - Current session ID
|
|
114
|
+
* @param providerId - Provider name
|
|
115
|
+
* @param scope - Credential scope
|
|
116
|
+
* @param tokens - New tokens
|
|
117
|
+
* @param userId - User ID (required for user scope)
|
|
118
|
+
*/
|
|
119
|
+
async refreshOAuthCredential(sessionId, providerId, scope, tokens, userId) {
|
|
120
|
+
const vaultKey = this.buildVaultKey(sessionId, scope, userId);
|
|
121
|
+
try {
|
|
122
|
+
await this.baseVault.refreshOAuthCredential(vaultKey, this.namespace, providerId, tokens);
|
|
123
|
+
this.logger?.debug(`Refreshed OAuth credential "${providerId}"`);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
this.logger?.warn(`Failed to refresh OAuth credential "${providerId}":`, error);
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get all credentials for a session
|
|
131
|
+
*
|
|
132
|
+
* @param sessionId - Current session ID
|
|
133
|
+
* @param scope - Optional scope filter
|
|
134
|
+
* @param userId - User ID (required for user scope)
|
|
135
|
+
*/
|
|
136
|
+
async getAllCredentials(sessionId, scope, userId) {
|
|
137
|
+
if (scope) {
|
|
138
|
+
const vaultKey = this.buildVaultKey(sessionId, scope, userId);
|
|
139
|
+
try {
|
|
140
|
+
return await this.baseVault.getAppCredentials(vaultKey, this.namespace);
|
|
141
|
+
} catch {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const credentials = [];
|
|
146
|
+
try {
|
|
147
|
+
const sessionCreds = await this.baseVault.getAppCredentials(
|
|
148
|
+
this.buildVaultKey(sessionId, "session"),
|
|
149
|
+
this.namespace
|
|
150
|
+
);
|
|
151
|
+
credentials.push(...sessionCreds);
|
|
152
|
+
} catch {
|
|
153
|
+
}
|
|
154
|
+
if (userId) {
|
|
155
|
+
try {
|
|
156
|
+
const userCreds = await this.baseVault.getAppCredentials(
|
|
157
|
+
this.buildVaultKey(sessionId, "user", userId),
|
|
158
|
+
this.namespace
|
|
159
|
+
);
|
|
160
|
+
credentials.push(...userCreds);
|
|
161
|
+
} catch {
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const globalCreds = await this.baseVault.getAppCredentials(
|
|
166
|
+
this.buildVaultKey(sessionId, "global"),
|
|
167
|
+
this.namespace
|
|
168
|
+
);
|
|
169
|
+
credentials.push(...globalCreds);
|
|
170
|
+
} catch {
|
|
171
|
+
}
|
|
172
|
+
return credentials;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Build vault key based on scope
|
|
176
|
+
*
|
|
177
|
+
* Key patterns:
|
|
178
|
+
* - global: `authproviders:global`
|
|
179
|
+
* - user: `authproviders:user:{userId}`
|
|
180
|
+
* - session: `authproviders:session:{sessionId}`
|
|
181
|
+
*/
|
|
182
|
+
buildVaultKey(sessionId, scope, userId) {
|
|
183
|
+
switch (scope) {
|
|
184
|
+
case "global":
|
|
185
|
+
return `${this.namespace}global`;
|
|
186
|
+
case "user":
|
|
187
|
+
if (!userId) {
|
|
188
|
+
throw new AuthInvalidInputError(
|
|
189
|
+
`userId is required for user-scoped credentials (namespace: ${this.namespace})`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
return `${this.namespace}user:${userId}`;
|
|
193
|
+
case "session":
|
|
194
|
+
return `${this.namespace}session:${sessionId}`;
|
|
195
|
+
default: {
|
|
196
|
+
const _exhaustive = scope;
|
|
197
|
+
throw new AuthInvalidInputError(`Unknown credential scope: ${_exhaustive}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
var AUTH_PROVIDERS_VAULT = /* @__PURE__ */ Symbol.for("frontmcp:AUTH_PROVIDERS_VAULT");
|
|
203
|
+
export {
|
|
204
|
+
AUTH_PROVIDERS_VAULT,
|
|
205
|
+
AuthProvidersVault
|
|
206
|
+
};
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// libs/auth/src/vault/credential-cache.ts
|
|
2
|
+
var CredentialCache = class {
|
|
3
|
+
cache = /* @__PURE__ */ new Map();
|
|
4
|
+
maxSize;
|
|
5
|
+
stats = { hits: 0, misses: 0, evictions: 0, size: 0 };
|
|
6
|
+
constructor(maxSize = 100) {
|
|
7
|
+
this.maxSize = maxSize;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Get a cached credential
|
|
11
|
+
*
|
|
12
|
+
* @param providerId - Provider name
|
|
13
|
+
* @returns Resolved credential or undefined if not cached or expired
|
|
14
|
+
*/
|
|
15
|
+
get(providerId) {
|
|
16
|
+
const entry = this.cache.get(providerId);
|
|
17
|
+
if (!entry) {
|
|
18
|
+
this.stats.misses++;
|
|
19
|
+
return void 0;
|
|
20
|
+
}
|
|
21
|
+
if (this.isExpired(entry)) {
|
|
22
|
+
this.cache.delete(providerId);
|
|
23
|
+
this.stats.size = this.cache.size;
|
|
24
|
+
this.stats.misses++;
|
|
25
|
+
this.stats.evictions++;
|
|
26
|
+
return void 0;
|
|
27
|
+
}
|
|
28
|
+
if (entry.resolved.expiresAt && Date.now() >= entry.resolved.expiresAt) {
|
|
29
|
+
this.cache.delete(providerId);
|
|
30
|
+
this.stats.size = this.cache.size;
|
|
31
|
+
this.stats.misses++;
|
|
32
|
+
this.stats.evictions++;
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
this.stats.hits++;
|
|
36
|
+
return entry.resolved;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Cache a resolved credential
|
|
40
|
+
*
|
|
41
|
+
* @param providerId - Provider name
|
|
42
|
+
* @param resolved - Resolved credential to cache
|
|
43
|
+
* @param ttl - TTL in milliseconds (0 = no TTL, rely on credential expiry)
|
|
44
|
+
*/
|
|
45
|
+
set(providerId, resolved, ttl = 0) {
|
|
46
|
+
if (this.cache.size >= this.maxSize && !this.cache.has(providerId)) {
|
|
47
|
+
this.evictOldest();
|
|
48
|
+
}
|
|
49
|
+
const entry = {
|
|
50
|
+
resolved,
|
|
51
|
+
cachedAt: Date.now(),
|
|
52
|
+
ttl
|
|
53
|
+
};
|
|
54
|
+
this.cache.set(providerId, entry);
|
|
55
|
+
this.stats.size = this.cache.size;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Check if a credential is cached and valid
|
|
59
|
+
*
|
|
60
|
+
* @param providerId - Provider name
|
|
61
|
+
* @returns true if cached and not expired
|
|
62
|
+
*/
|
|
63
|
+
has(providerId) {
|
|
64
|
+
const entry = this.cache.get(providerId);
|
|
65
|
+
if (!entry) return false;
|
|
66
|
+
if (this.isExpired(entry)) {
|
|
67
|
+
this.cache.delete(providerId);
|
|
68
|
+
this.stats.size = this.cache.size;
|
|
69
|
+
this.stats.evictions++;
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Invalidate (remove) a cached credential
|
|
76
|
+
*
|
|
77
|
+
* @param providerId - Provider name to invalidate
|
|
78
|
+
* @returns true if credential was removed
|
|
79
|
+
*/
|
|
80
|
+
invalidate(providerId) {
|
|
81
|
+
const deleted = this.cache.delete(providerId);
|
|
82
|
+
if (deleted) {
|
|
83
|
+
this.stats.size = this.cache.size;
|
|
84
|
+
}
|
|
85
|
+
return deleted;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Invalidate all cached credentials
|
|
89
|
+
*/
|
|
90
|
+
invalidateAll() {
|
|
91
|
+
this.cache.clear();
|
|
92
|
+
this.stats.size = 0;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Invalidate credentials by scope
|
|
96
|
+
*
|
|
97
|
+
* @param scope - Credential scope to invalidate
|
|
98
|
+
*/
|
|
99
|
+
invalidateByScope(scope) {
|
|
100
|
+
for (const [key, entry] of this.cache) {
|
|
101
|
+
if (entry.resolved.scope === scope) {
|
|
102
|
+
this.cache.delete(key);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
this.stats.size = this.cache.size;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get all cached provider IDs
|
|
109
|
+
*/
|
|
110
|
+
keys() {
|
|
111
|
+
this.cleanup();
|
|
112
|
+
return [...this.cache.keys()];
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Get cache size
|
|
116
|
+
*/
|
|
117
|
+
get size() {
|
|
118
|
+
return this.cache.size;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get cache statistics
|
|
122
|
+
*/
|
|
123
|
+
getStats() {
|
|
124
|
+
return { ...this.stats };
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Reset cache statistics
|
|
128
|
+
*/
|
|
129
|
+
resetStats() {
|
|
130
|
+
this.stats = { hits: 0, misses: 0, evictions: 0, size: this.cache.size };
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Clean up expired entries
|
|
134
|
+
*/
|
|
135
|
+
cleanup() {
|
|
136
|
+
const now = Date.now();
|
|
137
|
+
for (const [key, entry] of this.cache) {
|
|
138
|
+
if (this.isExpiredAt(entry, now)) {
|
|
139
|
+
this.cache.delete(key);
|
|
140
|
+
this.stats.evictions++;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
this.stats.size = this.cache.size;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Check if entry is expired based on TTL
|
|
147
|
+
*/
|
|
148
|
+
isExpired(entry) {
|
|
149
|
+
return this.isExpiredAt(entry, Date.now());
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Check if entry is expired at a given timestamp
|
|
153
|
+
*/
|
|
154
|
+
isExpiredAt(entry, now) {
|
|
155
|
+
if (entry.ttl > 0 && now - entry.cachedAt >= entry.ttl) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
if (entry.resolved.expiresAt && now >= entry.resolved.expiresAt) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
if (!entry.resolved.isValid) {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Evict the oldest entry from cache
|
|
168
|
+
*/
|
|
169
|
+
evictOldest() {
|
|
170
|
+
let oldestKey;
|
|
171
|
+
let oldestTime = Infinity;
|
|
172
|
+
for (const [key, entry] of this.cache) {
|
|
173
|
+
if (entry.cachedAt < oldestTime) {
|
|
174
|
+
oldestTime = entry.cachedAt;
|
|
175
|
+
oldestKey = key;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (oldestKey) {
|
|
179
|
+
this.cache.delete(oldestKey);
|
|
180
|
+
this.stats.evictions++;
|
|
181
|
+
this.stats.size = this.cache.size;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
export {
|
|
186
|
+
CredentialCache
|
|
187
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// libs/auth/src/vault/credential-helpers.ts
|
|
2
|
+
function extractCredentialExpiry(credential) {
|
|
3
|
+
switch (credential.type) {
|
|
4
|
+
case "oauth":
|
|
5
|
+
case "oauth_pkce":
|
|
6
|
+
case "bearer":
|
|
7
|
+
case "service_account":
|
|
8
|
+
return credential.expiresAt;
|
|
9
|
+
default:
|
|
10
|
+
return void 0;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
extractCredentialExpiry
|
|
15
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// libs/auth/src/vault/credential-loaders/eager-loader.ts
|
|
2
|
+
import { extractCredentialExpiry } from "../credential-helpers.mjs";
|
|
3
|
+
var EagerCredentialLoader = class {
|
|
4
|
+
constructor(registry, cache, logger) {
|
|
5
|
+
this.registry = registry;
|
|
6
|
+
this.cache = cache;
|
|
7
|
+
this.logger = logger;
|
|
8
|
+
}
|
|
9
|
+
registry;
|
|
10
|
+
cache;
|
|
11
|
+
logger;
|
|
12
|
+
/**
|
|
13
|
+
* Load all eager credentials for a session.
|
|
14
|
+
* Called during session initialization.
|
|
15
|
+
*
|
|
16
|
+
* @param context - Factory context with session/user info
|
|
17
|
+
* @returns Map of provider name to resolved credential
|
|
18
|
+
*/
|
|
19
|
+
async loadForSession(context) {
|
|
20
|
+
const startTime = Date.now();
|
|
21
|
+
const eagerProviders = this.registry.getEager();
|
|
22
|
+
if (eagerProviders.length === 0) {
|
|
23
|
+
return {
|
|
24
|
+
loaded: /* @__PURE__ */ new Map(),
|
|
25
|
+
failed: /* @__PURE__ */ new Map(),
|
|
26
|
+
duration: Date.now() - startTime
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
this.logger?.debug(`Loading ${eagerProviders.length} eager credential providers`);
|
|
30
|
+
const loaded = /* @__PURE__ */ new Map();
|
|
31
|
+
const failed = /* @__PURE__ */ new Map();
|
|
32
|
+
const results = await Promise.allSettled(
|
|
33
|
+
eagerProviders.map(async (config) => {
|
|
34
|
+
const credential = await this.loadOne(config, context);
|
|
35
|
+
return { name: config.name, credential, config };
|
|
36
|
+
})
|
|
37
|
+
);
|
|
38
|
+
for (const result of results) {
|
|
39
|
+
if (result.status === "fulfilled") {
|
|
40
|
+
const { name, credential, config } = result.value;
|
|
41
|
+
if (credential) {
|
|
42
|
+
const resolved = this.wrapCredential(name, credential, config);
|
|
43
|
+
loaded.set(name, resolved);
|
|
44
|
+
this.cache.set(name, resolved, config.cacheTtl);
|
|
45
|
+
this.logger?.debug(`Loaded eager credential: ${name}`);
|
|
46
|
+
} else if (config.required) {
|
|
47
|
+
failed.set(name, new Error(`Required credential "${name}" returned null`));
|
|
48
|
+
this.logger?.warn(`Required eager credential "${name}" returned null`);
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
const providerName = result.reason?.providerName || "unknown";
|
|
52
|
+
const error = result.reason instanceof Error ? result.reason : new Error(String(result.reason));
|
|
53
|
+
failed.set(providerName, error);
|
|
54
|
+
this.logger?.warn(`Failed to load eager credential "${providerName}":`, error.message);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const duration = Date.now() - startTime;
|
|
58
|
+
this.logger?.debug(`Eager loading complete: ${loaded.size} loaded, ${failed.size} failed, ${duration}ms`);
|
|
59
|
+
return { loaded, failed, duration };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Load a single credential
|
|
63
|
+
*/
|
|
64
|
+
async loadOne(config, context) {
|
|
65
|
+
try {
|
|
66
|
+
return await config.factory({
|
|
67
|
+
...context,
|
|
68
|
+
metadata: config.metadata
|
|
69
|
+
});
|
|
70
|
+
} catch (error) {
|
|
71
|
+
error.providerName = config.name;
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Wrap credential with resolved metadata
|
|
77
|
+
*/
|
|
78
|
+
wrapCredential(providerId, credential, config) {
|
|
79
|
+
return {
|
|
80
|
+
credential,
|
|
81
|
+
providerId,
|
|
82
|
+
acquiredAt: Date.now(),
|
|
83
|
+
expiresAt: extractCredentialExpiry(credential),
|
|
84
|
+
isValid: true,
|
|
85
|
+
scope: config.scope
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
export {
|
|
90
|
+
EagerCredentialLoader
|
|
91
|
+
};
|