@frontmcp/auth 1.9.1 → 1.9.3
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.context.d.ts.map +1 -1
- package/authorities/authorities.evaluator.d.ts.map +1 -1
- package/authorization/orchestrated.authorization.d.ts +10 -2
- package/authorization/orchestrated.authorization.d.ts.map +1 -1
- package/cimd/cimd.service.d.ts +13 -1
- package/cimd/cimd.service.d.ts.map +1 -1
- package/context/frontmcp-auth-context.factory.d.ts +7 -5
- package/context/frontmcp-auth-context.factory.d.ts.map +1 -1
- package/context/frontmcp-auth-context.impl.d.ts +2 -1
- package/context/frontmcp-auth-context.impl.d.ts.map +1 -1
- package/detection/auth-provider-detection.d.ts +2 -0
- package/detection/auth-provider-detection.d.ts.map +1 -1
- package/esm/index.mjs +534 -116
- package/esm/package.json +5 -5
- package/fetch/fetch-credential-middleware.d.ts +11 -1
- package/fetch/fetch-credential-middleware.d.ts.map +1 -1
- package/fetch/index.d.ts +2 -2
- package/fetch/index.d.ts.map +1 -1
- package/index.d.ts +3 -3
- package/index.d.ts.map +1 -1
- package/index.js +534 -119
- package/jwks/jwks.service.d.ts +17 -1
- package/jwks/jwks.service.d.ts.map +1 -1
- package/package.json +5 -5
- package/session/authorization.store.d.ts +6 -0
- package/session/authorization.store.d.ts.map +1 -1
- package/session/orchestrated-token.crypto.d.ts +11 -0
- package/session/orchestrated-token.crypto.d.ts.map +1 -1
- package/session/orchestrated-token.store.d.ts +16 -1
- package/session/orchestrated-token.store.d.ts.map +1 -1
- package/session/storage-orchestrated-token.store.d.ts +10 -0
- package/session/storage-orchestrated-token.store.d.ts.map +1 -1
- package/session/utils/auth-token.utils.d.ts.map +1 -1
- package/utils/index.d.ts +1 -0
- package/utils/index.d.ts.map +1 -1
- package/utils/token-scopes.utils.d.ts +11 -0
- package/utils/token-scopes.utils.d.ts.map +1 -0
package/index.js
CHANGED
|
@@ -25,6 +25,245 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
25
25
|
};
|
|
26
26
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
27
|
|
|
28
|
+
// libs/auth/src/cimd/cimd-redis.cache.ts
|
|
29
|
+
var cimd_redis_cache_exports = {};
|
|
30
|
+
__export(cimd_redis_cache_exports, {
|
|
31
|
+
RedisCimdCache: () => RedisCimdCache
|
|
32
|
+
});
|
|
33
|
+
var import_utils43, import_utils44, RedisCimdCache;
|
|
34
|
+
var init_cimd_redis_cache = __esm({
|
|
35
|
+
"libs/auth/src/cimd/cimd-redis.cache.ts"() {
|
|
36
|
+
"use strict";
|
|
37
|
+
import_utils43 = require("@frontmcp/utils");
|
|
38
|
+
import_utils44 = require("@frontmcp/utils");
|
|
39
|
+
init_cimd_cache();
|
|
40
|
+
RedisCimdCache = class {
|
|
41
|
+
redis;
|
|
42
|
+
keyPrefix;
|
|
43
|
+
config;
|
|
44
|
+
constructor(config) {
|
|
45
|
+
if (!config.redis) {
|
|
46
|
+
throw new Error("Redis configuration is required for RedisCimdCache");
|
|
47
|
+
}
|
|
48
|
+
this.config = {
|
|
49
|
+
defaultTtlMs: config.defaultTtlMs ?? 36e5,
|
|
50
|
+
maxTtlMs: config.maxTtlMs ?? 864e5,
|
|
51
|
+
minTtlMs: config.minTtlMs ?? 6e4
|
|
52
|
+
};
|
|
53
|
+
this.keyPrefix = config.redis.keyPrefix ?? "cimd:";
|
|
54
|
+
const redisOptions = {};
|
|
55
|
+
if (config.redis.url) {
|
|
56
|
+
redisOptions.url = config.redis.url;
|
|
57
|
+
} else if (config.redis.host) {
|
|
58
|
+
redisOptions.config = {
|
|
59
|
+
host: config.redis.host,
|
|
60
|
+
port: config.redis.port,
|
|
61
|
+
password: config.redis.password,
|
|
62
|
+
db: config.redis.db,
|
|
63
|
+
tls: config.redis.tls
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
this.redis = new import_utils43.RedisStorageAdapter(redisOptions);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Connect to Redis.
|
|
70
|
+
* Must be called before using any cache operations.
|
|
71
|
+
*/
|
|
72
|
+
async connect() {
|
|
73
|
+
await this.redis.connect();
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Generate a Redis key for a client ID.
|
|
77
|
+
* Uses SHA-256 hash to handle URLs with special characters.
|
|
78
|
+
*/
|
|
79
|
+
cacheKey(clientId) {
|
|
80
|
+
const hash = (0, import_utils44.sha256Hex)(clientId);
|
|
81
|
+
return `${this.keyPrefix}${hash}`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get a cached entry by client_id.
|
|
85
|
+
*
|
|
86
|
+
* @param clientId - The client_id URL
|
|
87
|
+
* @returns The cached entry if valid, or undefined
|
|
88
|
+
*/
|
|
89
|
+
async get(clientId) {
|
|
90
|
+
const key = this.cacheKey(clientId);
|
|
91
|
+
const data = await this.redis.get(key);
|
|
92
|
+
if (!data) {
|
|
93
|
+
return void 0;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const entry = JSON.parse(data);
|
|
97
|
+
if (entry.expiresAt < Date.now()) {
|
|
98
|
+
return void 0;
|
|
99
|
+
}
|
|
100
|
+
return entry;
|
|
101
|
+
} catch {
|
|
102
|
+
await this.redis.delete(key);
|
|
103
|
+
return void 0;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get a stale entry for conditional revalidation.
|
|
108
|
+
*
|
|
109
|
+
* @param clientId - The client_id URL
|
|
110
|
+
* @returns The stale entry (even if expired), or undefined if not cached
|
|
111
|
+
*/
|
|
112
|
+
async getStale(clientId) {
|
|
113
|
+
const key = this.cacheKey(clientId);
|
|
114
|
+
const data = await this.redis.get(key);
|
|
115
|
+
if (!data) {
|
|
116
|
+
return void 0;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
return JSON.parse(data);
|
|
120
|
+
} catch {
|
|
121
|
+
return void 0;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Store a document in the cache.
|
|
126
|
+
*
|
|
127
|
+
* @param clientId - The client_id URL
|
|
128
|
+
* @param document - The metadata document
|
|
129
|
+
* @param headers - HTTP response headers
|
|
130
|
+
*/
|
|
131
|
+
async set(clientId, document, headers) {
|
|
132
|
+
const cacheHeaders = extractCacheHeaders(headers);
|
|
133
|
+
const { ttlMs, etag, lastModified } = parseCacheHeaders(cacheHeaders, this.config);
|
|
134
|
+
const now = Date.now();
|
|
135
|
+
const entry = {
|
|
136
|
+
document,
|
|
137
|
+
expiresAt: now + ttlMs,
|
|
138
|
+
etag,
|
|
139
|
+
lastModified,
|
|
140
|
+
cachedAt: now
|
|
141
|
+
};
|
|
142
|
+
const key = this.cacheKey(clientId);
|
|
143
|
+
const value = JSON.stringify(entry);
|
|
144
|
+
const redisTtlSeconds = Math.ceil((ttlMs + this.config.maxTtlMs * 2) / 1e3);
|
|
145
|
+
await this.redis.set(key, value, { ttlSeconds: redisTtlSeconds });
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Update an existing cache entry (after 304 Not Modified).
|
|
149
|
+
*
|
|
150
|
+
* @param clientId - The client_id URL
|
|
151
|
+
* @param headers - New HTTP headers with updated cache directives
|
|
152
|
+
*/
|
|
153
|
+
async revalidate(clientId, headers) {
|
|
154
|
+
const key = this.cacheKey(clientId);
|
|
155
|
+
const data = await this.redis.get(key);
|
|
156
|
+
if (!data) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const existing = JSON.parse(data);
|
|
161
|
+
const cacheHeaders = extractCacheHeaders(headers);
|
|
162
|
+
const { ttlMs, etag, lastModified } = parseCacheHeaders(cacheHeaders, this.config);
|
|
163
|
+
existing.expiresAt = Date.now() + ttlMs;
|
|
164
|
+
if (etag) existing.etag = etag;
|
|
165
|
+
if (lastModified) existing.lastModified = lastModified;
|
|
166
|
+
const value = JSON.stringify(existing);
|
|
167
|
+
const redisTtlSeconds = Math.ceil((ttlMs + this.config.maxTtlMs * 2) / 1e3);
|
|
168
|
+
await this.redis.set(key, value, { ttlSeconds: redisTtlSeconds });
|
|
169
|
+
return true;
|
|
170
|
+
} catch {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Delete a cache entry.
|
|
176
|
+
*
|
|
177
|
+
* @param clientId - The client_id URL
|
|
178
|
+
* @returns true if an entry was deleted
|
|
179
|
+
*/
|
|
180
|
+
async delete(clientId) {
|
|
181
|
+
const key = this.cacheKey(clientId);
|
|
182
|
+
return this.redis.delete(key);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get conditional request headers for a cached entry.
|
|
186
|
+
*
|
|
187
|
+
* @param clientId - The client_id URL
|
|
188
|
+
* @returns Headers for conditional request, or undefined if not cached
|
|
189
|
+
*/
|
|
190
|
+
async getConditionalHeaders(clientId) {
|
|
191
|
+
const key = this.cacheKey(clientId);
|
|
192
|
+
const data = await this.redis.get(key);
|
|
193
|
+
if (!data) {
|
|
194
|
+
return void 0;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const entry = JSON.parse(data);
|
|
198
|
+
const headers = {};
|
|
199
|
+
if (entry.etag) {
|
|
200
|
+
headers["If-None-Match"] = entry.etag;
|
|
201
|
+
}
|
|
202
|
+
if (entry.lastModified) {
|
|
203
|
+
headers["If-Modified-Since"] = entry.lastModified;
|
|
204
|
+
}
|
|
205
|
+
return Object.keys(headers).length > 0 ? headers : void 0;
|
|
206
|
+
} catch {
|
|
207
|
+
return void 0;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Clear all cached entries.
|
|
212
|
+
* Uses Redis SCAN to find and delete all keys with our prefix.
|
|
213
|
+
*/
|
|
214
|
+
async clear() {
|
|
215
|
+
const keys = await this.redis.keys(`${this.keyPrefix}*`);
|
|
216
|
+
if (keys.length > 0) {
|
|
217
|
+
await this.redis.mdelete(keys);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get the number of cached entries.
|
|
222
|
+
* Uses Redis SCAN to count keys with our prefix.
|
|
223
|
+
*/
|
|
224
|
+
async size() {
|
|
225
|
+
const keys = await this.redis.keys(`${this.keyPrefix}*`);
|
|
226
|
+
return keys.length;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Remove expired entries.
|
|
230
|
+
*
|
|
231
|
+
* Note: Redis handles expiration automatically via TTL.
|
|
232
|
+
* This method is primarily for explicit cleanup of entries that are
|
|
233
|
+
* well past their HTTP cache expiration but still within Redis TTL.
|
|
234
|
+
*
|
|
235
|
+
* @returns Number of entries removed
|
|
236
|
+
*/
|
|
237
|
+
async cleanup() {
|
|
238
|
+
const now = Date.now();
|
|
239
|
+
let removed = 0;
|
|
240
|
+
const keys = await this.redis.keys(`${this.keyPrefix}*`);
|
|
241
|
+
for (const key of keys) {
|
|
242
|
+
const data = await this.redis.get(key);
|
|
243
|
+
if (!data) continue;
|
|
244
|
+
try {
|
|
245
|
+
const entry = JSON.parse(data);
|
|
246
|
+
if (entry.expiresAt + this.config.maxTtlMs * 2 < now) {
|
|
247
|
+
await this.redis.delete(key);
|
|
248
|
+
removed++;
|
|
249
|
+
}
|
|
250
|
+
} catch {
|
|
251
|
+
await this.redis.delete(key);
|
|
252
|
+
removed++;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return removed;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Close the Redis connection.
|
|
259
|
+
*/
|
|
260
|
+
async close() {
|
|
261
|
+
await this.redis.disconnect();
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
28
267
|
// libs/auth/src/cimd/cimd.cache.ts
|
|
29
268
|
function extractCacheHeaders(headers) {
|
|
30
269
|
return {
|
|
@@ -90,6 +329,19 @@ function parseCacheControlHeader(value) {
|
|
|
90
329
|
}
|
|
91
330
|
return result;
|
|
92
331
|
}
|
|
332
|
+
async function createCimdCache(config) {
|
|
333
|
+
const cacheType = config?.type ?? "memory";
|
|
334
|
+
if (cacheType === "redis") {
|
|
335
|
+
if (!config?.redis) {
|
|
336
|
+
throw new Error('Redis configuration is required when cache type is "redis"');
|
|
337
|
+
}
|
|
338
|
+
const { RedisCimdCache: RedisCimdCache2 } = await Promise.resolve().then(() => (init_cimd_redis_cache(), cimd_redis_cache_exports));
|
|
339
|
+
const cache = new RedisCimdCache2(config);
|
|
340
|
+
await cache.connect();
|
|
341
|
+
return cache;
|
|
342
|
+
}
|
|
343
|
+
return new InMemoryCimdCache(config);
|
|
344
|
+
}
|
|
93
345
|
var InMemoryCimdCache;
|
|
94
346
|
var init_cimd_cache = __esm({
|
|
95
347
|
"libs/auth/src/cimd/cimd.cache.ts"() {
|
|
@@ -500,6 +752,7 @@ __export(index_exports, {
|
|
|
500
752
|
privateKeyCredentialSchema: () => privateKeyCredentialSchema,
|
|
501
753
|
progressiveAuthStateSchema: () => progressiveAuthStateSchema,
|
|
502
754
|
providerConfigSchema: () => providerConfigSchema,
|
|
755
|
+
providerHeadersCredentials: () => providerHeadersCredentials,
|
|
503
756
|
publicAccessConfigSchema: () => publicAccessConfigSchema,
|
|
504
757
|
publicAuthOptionsSchema: () => publicAuthOptionsSchema,
|
|
505
758
|
queryApplier: () => queryApplier,
|
|
@@ -517,6 +770,7 @@ __export(index_exports, {
|
|
|
517
770
|
resourceIdRefSchema: () => resourceIdRefSchema,
|
|
518
771
|
resourceScopesFor: () => resourceScopesFor,
|
|
519
772
|
safeDecrypt: () => safeDecrypt,
|
|
773
|
+
scopesFromClaims: () => scopesFromClaims,
|
|
520
774
|
sealPendingLogin: () => sealPendingLogin,
|
|
521
775
|
secureStoreConfigSchema: () => secureStoreConfigSchema,
|
|
522
776
|
secureStoreCustomBackendSchema: () => secureStoreCustomBackendSchema,
|
|
@@ -960,6 +1214,7 @@ var TRANSPARENT_JWT_ALGS = [
|
|
|
960
1214
|
];
|
|
961
1215
|
var MAX_JWKS_FETCH_BYTES = 1048576;
|
|
962
1216
|
var MAX_JWKS_FETCH_HOPS = 3;
|
|
1217
|
+
var UNKNOWN_KID_REFETCH_COOLDOWN_MS = 6e4;
|
|
963
1218
|
var MISSING_EXP_ERROR = 'missing required "exp" claim';
|
|
964
1219
|
function isMissingExpError(error) {
|
|
965
1220
|
if (typeof error !== "object" || error === null) return false;
|
|
@@ -980,6 +1235,8 @@ var JwksService = class {
|
|
|
980
1235
|
orchestratorKey;
|
|
981
1236
|
// Provider JWKS cache (providerId -> jwks + fetchedAt)
|
|
982
1237
|
providerJwks = /* @__PURE__ */ new Map();
|
|
1238
|
+
// The last key refetch an unknown `kid` started, per provider (see refetchForUnknownKid)
|
|
1239
|
+
unknownKidRefetches = /* @__PURE__ */ new Map();
|
|
983
1240
|
// Track if key has been initialized (for async loading)
|
|
984
1241
|
keyInitialized = false;
|
|
985
1242
|
// Promise guard to prevent concurrent key generation
|
|
@@ -1040,6 +1297,9 @@ var JwksService = class {
|
|
|
1040
1297
|
let jwks;
|
|
1041
1298
|
try {
|
|
1042
1299
|
jwks = await this.getJwksForProvider(p);
|
|
1300
|
+
if (kid && !p.jwks?.keys?.length && !jwks?.keys?.some((key) => key.kid === kid)) {
|
|
1301
|
+
jwks = await this.refetchForUnknownKid(p) ?? jwks;
|
|
1302
|
+
}
|
|
1043
1303
|
if (!jwks?.keys?.length) continue;
|
|
1044
1304
|
const JWKS = (0, import_jose.createLocalJWKSet)(jwks);
|
|
1045
1305
|
const issuerConstraint = this.issuerConstraintFor(p);
|
|
@@ -1207,7 +1467,8 @@ var JwksService = class {
|
|
|
1207
1467
|
* 1) inline jwks (if provided) → cache & return
|
|
1208
1468
|
* 2) cached & fresh (TTL) → return
|
|
1209
1469
|
* 3) explicit jwksUri → fetch, cache, return
|
|
1210
|
-
* 4)
|
|
1470
|
+
* 4) `<issuer>/.well-known/jwks.json` → fetch, cache, return
|
|
1471
|
+
* 5) discover jwks_uri → the issuer's OAuth authorization server
|
|
1211
1472
|
* metadata (RFC 8414), else its OpenID configuration (OpenID Connect
|
|
1212
1473
|
* Discovery 1.0 §4, where Google, Okta, Auth0 and Entra publish it),
|
|
1213
1474
|
* then fetch jwks_uri, cache, return
|
|
@@ -1221,11 +1482,42 @@ var JwksService = class {
|
|
|
1221
1482
|
if (cached && Date.now() - cached.fetchedAt < this.opts.providerJwksTtlMs) {
|
|
1222
1483
|
return cached.jwks;
|
|
1223
1484
|
}
|
|
1485
|
+
const fetched = await this.fetchProviderJwks(ref);
|
|
1486
|
+
if (fetched) return fetched;
|
|
1487
|
+
const maxStaleMs = Math.max(this.opts.providerJwksTtlMs * 4, 6e4);
|
|
1488
|
+
if (cached && Date.now() - cached.fetchedAt < maxStaleMs) {
|
|
1489
|
+
return cached.jwks;
|
|
1490
|
+
}
|
|
1491
|
+
return void 0;
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Fetch a provider's keys again for a token whose `kid` the cached keys don't have (a key the
|
|
1495
|
+
* provider started using since), ahead of the cache's TTL. At most one refetch per provider per
|
|
1496
|
+
* {@link UNKNOWN_KID_REFETCH_COOLDOWN_MS}: tokens that arrive meanwhile wait for the one in flight or
|
|
1497
|
+
* use its result, so a flood of tokens with made-up `kid`s can't hammer the provider. Only freshly
|
|
1498
|
+
* fetched keys are returned: a failed refetch never revives cached keys past the stale window.
|
|
1499
|
+
*/
|
|
1500
|
+
refetchForUnknownKid(ref) {
|
|
1501
|
+
let refetch = this.unknownKidRefetches.get(ref.id);
|
|
1502
|
+
if (!refetch || Date.now() - refetch.startedAt >= UNKNOWN_KID_REFETCH_COOLDOWN_MS) {
|
|
1503
|
+
refetch = { startedAt: Date.now(), done: this.fetchProviderJwks(ref) };
|
|
1504
|
+
this.unknownKidRefetches.set(ref.id, refetch);
|
|
1505
|
+
}
|
|
1506
|
+
return refetch.done;
|
|
1507
|
+
}
|
|
1508
|
+
/**
|
|
1509
|
+
* Fetch a provider's keys, and cache them: from `jwksUri`, else the conventional location, else
|
|
1510
|
+
* the `jwks_uri` of the issuer's OAuth or OpenID metadata. Every URL goes through the same scheme
|
|
1511
|
+
* and SSRF checks (`fetchJson`), `jwks_uri` included.
|
|
1512
|
+
*/
|
|
1513
|
+
async fetchProviderJwks(ref) {
|
|
1224
1514
|
if (ref.jwksUri) {
|
|
1225
1515
|
const fromUri = await this.tryFetchJwks(ref.id, ref.jwksUri);
|
|
1226
1516
|
if (fromUri?.keys?.length) return fromUri;
|
|
1227
1517
|
}
|
|
1228
1518
|
const issuer = trimSlash(ref.issuerUrl);
|
|
1519
|
+
const fromWellKnown = await this.tryFetchJwks(ref.id, `${issuer}/.well-known/jwks.json`);
|
|
1520
|
+
if (fromWellKnown?.keys?.length) return fromWellKnown;
|
|
1229
1521
|
for (const document of ["oauth-authorization-server", "openid-configuration"]) {
|
|
1230
1522
|
const meta = await this.tryFetchAsMeta(`${issuer}/.well-known/${document}`);
|
|
1231
1523
|
const uri = meta && typeof meta === "object" && meta["jwks_uri"] ? String(meta["jwks_uri"]) : void 0;
|
|
@@ -1234,10 +1526,6 @@ var JwksService = class {
|
|
|
1234
1526
|
if (fromMeta?.keys?.length) return fromMeta;
|
|
1235
1527
|
}
|
|
1236
1528
|
}
|
|
1237
|
-
const maxStaleMs = Math.max(this.opts.providerJwksTtlMs * 4, 6e4);
|
|
1238
|
-
if (cached && Date.now() - cached.fetchedAt < maxStaleMs) {
|
|
1239
|
-
return cached.jwks;
|
|
1240
|
-
}
|
|
1241
1529
|
return void 0;
|
|
1242
1530
|
}
|
|
1243
1531
|
// ===========================================================================
|
|
@@ -2204,7 +2492,8 @@ function buildRefreshTokenRecord(params) {
|
|
|
2204
2492
|
customClaims: params.customClaims,
|
|
2205
2493
|
federatedLoginUsed: params.federatedLoginUsed,
|
|
2206
2494
|
selectedProviderIds: params.selectedProviderIds,
|
|
2207
|
-
skippedProviderIds: params.skippedProviderIds
|
|
2495
|
+
skippedProviderIds: params.skippedProviderIds,
|
|
2496
|
+
providerTokensId: params.providerTokensId
|
|
2208
2497
|
};
|
|
2209
2498
|
}
|
|
2210
2499
|
function verifyPkce(codeVerifier, challenge) {
|
|
@@ -4248,7 +4537,8 @@ function deriveTypedUser(claims) {
|
|
|
4248
4537
|
...claims,
|
|
4249
4538
|
iss: extractClaimValue(claims, "iss", isString) ?? "",
|
|
4250
4539
|
sid: extractClaimValue(claims, "sid", isString),
|
|
4251
|
-
|
|
4540
|
+
// RFC 9068 §2.2: a client-credentials token names the client as its subject; some IdPs only put it in client_id / azp
|
|
4541
|
+
sub: extractClaimValue(claims, "sub", isString) ?? extractClaimValue(claims, "client_id", isString) ?? extractClaimValue(claims, "azp", isString) ?? "",
|
|
4252
4542
|
exp: extractClaimValue(claims, "exp", isNumber),
|
|
4253
4543
|
iat: extractClaimValue(claims, "iat", isNumber),
|
|
4254
4544
|
aud: extractClaimValue(claims, "aud", isStringOrStringArray),
|
|
@@ -5194,7 +5484,49 @@ var VercelKvSessionStore = class {
|
|
|
5194
5484
|
};
|
|
5195
5485
|
|
|
5196
5486
|
// libs/auth/src/session/orchestrated-token.store.ts
|
|
5487
|
+
var import_utils30 = require("@frontmcp/utils");
|
|
5488
|
+
|
|
5489
|
+
// libs/auth/src/session/orchestrated-token.crypto.ts
|
|
5197
5490
|
var import_utils29 = require("@frontmcp/utils");
|
|
5491
|
+
function recordDropAt(record) {
|
|
5492
|
+
if (!record.refreshToken) return record.expiresAt;
|
|
5493
|
+
return Math.max(record.expiresAt ?? 0, record.updatedAt + REFRESH_TOKEN_TTL_MS);
|
|
5494
|
+
}
|
|
5495
|
+
function hasLiveAccessToken(record, refreshSkewMs = 0) {
|
|
5496
|
+
if (record.expiresAt === void 0) return true;
|
|
5497
|
+
return record.expiresAt - (record.refreshToken ? refreshSkewMs : 0) > Date.now();
|
|
5498
|
+
}
|
|
5499
|
+
var HKDF_SALT = new TextEncoder().encode("frontmcp-token-store");
|
|
5500
|
+
function deriveKeyForRecord(masterKey, compositeKey, cache) {
|
|
5501
|
+
const cached = cache.get(compositeKey);
|
|
5502
|
+
if (cached) {
|
|
5503
|
+
return cached;
|
|
5504
|
+
}
|
|
5505
|
+
const info = new TextEncoder().encode(`orchestrated-token:${compositeKey}`);
|
|
5506
|
+
const derived = (0, import_utils29.hkdfSha256)(masterKey, HKDF_SALT, info, 32);
|
|
5507
|
+
cache.set(compositeKey, derived);
|
|
5508
|
+
return derived;
|
|
5509
|
+
}
|
|
5510
|
+
function encryptRecord(key, record) {
|
|
5511
|
+
const plaintext = JSON.stringify(record);
|
|
5512
|
+
const iv = (0, import_utils29.randomBytes)(12);
|
|
5513
|
+
const { ciphertext, tag } = (0, import_utils29.encryptAesGcm)(key, new TextEncoder().encode(plaintext), iv);
|
|
5514
|
+
return JSON.stringify({
|
|
5515
|
+
iv: Buffer.from(iv).toString("base64url"),
|
|
5516
|
+
tag: Buffer.from(tag).toString("base64url"),
|
|
5517
|
+
data: Buffer.from(ciphertext).toString("base64url")
|
|
5518
|
+
});
|
|
5519
|
+
}
|
|
5520
|
+
function decryptRecord(key, encrypted) {
|
|
5521
|
+
const { iv, tag, data } = JSON.parse(encrypted);
|
|
5522
|
+
const ivBytes = Buffer.from(iv, "base64url");
|
|
5523
|
+
const tagBytes = Buffer.from(tag, "base64url");
|
|
5524
|
+
const ciphertextBytes = Buffer.from(data, "base64url");
|
|
5525
|
+
const plaintext = (0, import_utils29.decryptAesGcm)(key, ciphertextBytes, ivBytes, tagBytes);
|
|
5526
|
+
return JSON.parse(new TextDecoder().decode(plaintext));
|
|
5527
|
+
}
|
|
5528
|
+
|
|
5529
|
+
// libs/auth/src/session/orchestrated-token.store.ts
|
|
5198
5530
|
var InMemoryOrchestratedTokenStore = class {
|
|
5199
5531
|
/** Token storage: Map<compositeKey, ProviderTokenRecord> */
|
|
5200
5532
|
tokens = /* @__PURE__ */ new Map();
|
|
@@ -5206,9 +5538,12 @@ var InMemoryOrchestratedTokenStore = class {
|
|
|
5206
5538
|
cleanupTimer;
|
|
5207
5539
|
/** Default TTL for records */
|
|
5208
5540
|
defaultTtlMs;
|
|
5541
|
+
/** How long before its expiry a refreshable access token stops being returned */
|
|
5542
|
+
refreshSkewMs;
|
|
5209
5543
|
constructor(options = {}) {
|
|
5210
5544
|
this.encryptionKey = options.encryptionKey;
|
|
5211
5545
|
this.defaultTtlMs = options.defaultTtlMs;
|
|
5546
|
+
this.refreshSkewMs = options.refreshSkewMs ?? 0;
|
|
5212
5547
|
const cleanupIntervalMs = options.cleanupIntervalMs ?? 6e4;
|
|
5213
5548
|
this.cleanupTimer = setInterval(() => {
|
|
5214
5549
|
void this.cleanup();
|
|
@@ -5236,7 +5571,7 @@ var InMemoryOrchestratedTokenStore = class {
|
|
|
5236
5571
|
}
|
|
5237
5572
|
const info = new TextEncoder().encode(`orchestrated-token:${compositeKey}`);
|
|
5238
5573
|
const salt = new TextEncoder().encode("frontmcp-token-store");
|
|
5239
|
-
const derivedKey = (0,
|
|
5574
|
+
const derivedKey = (0, import_utils30.hkdfSha256)(this.encryptionKey, salt, info, 32);
|
|
5240
5575
|
this.derivedKeys.set(compositeKey, derivedKey);
|
|
5241
5576
|
return derivedKey;
|
|
5242
5577
|
}
|
|
@@ -5246,8 +5581,8 @@ var InMemoryOrchestratedTokenStore = class {
|
|
|
5246
5581
|
async encryptRecord(compositeKey, record) {
|
|
5247
5582
|
const key = await this.deriveKeyForRecord(compositeKey);
|
|
5248
5583
|
const plaintext = JSON.stringify(record);
|
|
5249
|
-
const iv = (0,
|
|
5250
|
-
const { ciphertext, tag } = (0,
|
|
5584
|
+
const iv = (0, import_utils30.randomBytes)(12);
|
|
5585
|
+
const { ciphertext, tag } = (0, import_utils30.encryptAesGcm)(key, new TextEncoder().encode(plaintext), iv);
|
|
5251
5586
|
return JSON.stringify({
|
|
5252
5587
|
iv: Buffer.from(iv).toString("base64url"),
|
|
5253
5588
|
tag: Buffer.from(tag).toString("base64url"),
|
|
@@ -5263,7 +5598,7 @@ var InMemoryOrchestratedTokenStore = class {
|
|
|
5263
5598
|
const ivBytes = Buffer.from(iv, "base64url");
|
|
5264
5599
|
const tagBytes = Buffer.from(tag, "base64url");
|
|
5265
5600
|
const ciphertextBytes = Buffer.from(data, "base64url");
|
|
5266
|
-
const plaintext = (0,
|
|
5601
|
+
const plaintext = (0, import_utils30.decryptAesGcm)(key, ciphertextBytes, ivBytes, tagBytes);
|
|
5267
5602
|
return JSON.parse(new TextDecoder().decode(plaintext));
|
|
5268
5603
|
}
|
|
5269
5604
|
/**
|
|
@@ -5289,18 +5624,20 @@ var InMemoryOrchestratedTokenStore = class {
|
|
|
5289
5624
|
} else {
|
|
5290
5625
|
record = stored;
|
|
5291
5626
|
}
|
|
5292
|
-
|
|
5627
|
+
const dropAt = recordDropAt(record);
|
|
5628
|
+
if (dropAt && dropAt < Date.now()) {
|
|
5293
5629
|
this.tokens.delete(key);
|
|
5294
5630
|
return null;
|
|
5295
5631
|
}
|
|
5296
5632
|
return record;
|
|
5297
5633
|
}
|
|
5298
5634
|
/**
|
|
5299
|
-
* Retrieve decrypted access token for a provider
|
|
5635
|
+
* Retrieve decrypted access token for a provider, or null once it has expired (a record with a
|
|
5636
|
+
* refresh token keeps that, to renew it)
|
|
5300
5637
|
*/
|
|
5301
5638
|
async getAccessToken(authorizationId, providerId) {
|
|
5302
5639
|
const record = await this.getRecord(authorizationId, providerId);
|
|
5303
|
-
return record
|
|
5640
|
+
return record && hasLiveAccessToken(record, this.refreshSkewMs) ? record.accessToken : null;
|
|
5304
5641
|
}
|
|
5305
5642
|
/**
|
|
5306
5643
|
* Retrieve decrypted refresh token for a provider
|
|
@@ -5393,7 +5730,8 @@ var InMemoryOrchestratedTokenStore = class {
|
|
|
5393
5730
|
} else {
|
|
5394
5731
|
record = stored;
|
|
5395
5732
|
}
|
|
5396
|
-
|
|
5733
|
+
const dropAt = record ? recordDropAt(record) : void 0;
|
|
5734
|
+
if (dropAt && dropAt < now) {
|
|
5397
5735
|
keysToDelete.push(key);
|
|
5398
5736
|
}
|
|
5399
5737
|
}
|
|
@@ -5433,74 +5771,48 @@ var InMemoryOrchestratedTokenStore = class {
|
|
|
5433
5771
|
* @param toAuthId - Target authorization ID (e.g., "def456")
|
|
5434
5772
|
*/
|
|
5435
5773
|
async migrateTokens(fromAuthId, toAuthId) {
|
|
5436
|
-
const
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
if (key.startsWith(prefix)) {
|
|
5440
|
-
keysToMigrate.push(key);
|
|
5441
|
-
}
|
|
5774
|
+
for (const oldKey of await this.copyRecords(fromAuthId, toAuthId)) {
|
|
5775
|
+
this.tokens.delete(oldKey);
|
|
5776
|
+
this.derivedKeys.delete(oldKey);
|
|
5442
5777
|
}
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5778
|
+
}
|
|
5779
|
+
/**
|
|
5780
|
+
* Copy tokens from one authorization ID to another, keeping them under the source ID too.
|
|
5781
|
+
*/
|
|
5782
|
+
async copyTokens(fromAuthId, toAuthId) {
|
|
5783
|
+
await this.copyRecords(fromAuthId, toAuthId);
|
|
5784
|
+
}
|
|
5785
|
+
/** Copy every record of `fromAuthId` to `toAuthId`, skipping a corrupted one; returns the keys copied. */
|
|
5786
|
+
async copyRecords(fromAuthId, toAuthId) {
|
|
5787
|
+
const prefix = `${fromAuthId}:`;
|
|
5788
|
+
const sourceKeys = [...this.tokens.keys()].filter((key) => key.startsWith(prefix));
|
|
5789
|
+
const copiedKeys = [];
|
|
5790
|
+
for (const oldKey of sourceKeys) {
|
|
5446
5791
|
const stored = this.tokens.get(oldKey);
|
|
5447
|
-
if (!stored)
|
|
5448
|
-
|
|
5449
|
-
}
|
|
5792
|
+
if (!stored) continue;
|
|
5793
|
+
const newKey = this.buildKey(toAuthId, oldKey.slice(prefix.length));
|
|
5450
5794
|
if (this.encryptionKey) {
|
|
5451
5795
|
try {
|
|
5452
5796
|
const record = await this.decryptRecord(oldKey, stored);
|
|
5453
|
-
|
|
5454
|
-
this.tokens.set(newKey, encrypted);
|
|
5797
|
+
this.tokens.set(newKey, await this.encryptRecord(newKey, record));
|
|
5455
5798
|
} catch {
|
|
5456
5799
|
continue;
|
|
5457
5800
|
}
|
|
5458
5801
|
} else {
|
|
5459
5802
|
this.tokens.set(newKey, stored);
|
|
5460
5803
|
}
|
|
5461
|
-
|
|
5462
|
-
this.derivedKeys.delete(oldKey);
|
|
5804
|
+
copiedKeys.push(oldKey);
|
|
5463
5805
|
}
|
|
5806
|
+
return copiedKeys;
|
|
5464
5807
|
}
|
|
5465
5808
|
};
|
|
5466
5809
|
|
|
5467
|
-
// libs/auth/src/session/orchestrated-token.crypto.ts
|
|
5468
|
-
var import_utils30 = require("@frontmcp/utils");
|
|
5469
|
-
var HKDF_SALT = new TextEncoder().encode("frontmcp-token-store");
|
|
5470
|
-
function deriveKeyForRecord(masterKey, compositeKey, cache) {
|
|
5471
|
-
const cached = cache.get(compositeKey);
|
|
5472
|
-
if (cached) {
|
|
5473
|
-
return cached;
|
|
5474
|
-
}
|
|
5475
|
-
const info = new TextEncoder().encode(`orchestrated-token:${compositeKey}`);
|
|
5476
|
-
const derived = (0, import_utils30.hkdfSha256)(masterKey, HKDF_SALT, info, 32);
|
|
5477
|
-
cache.set(compositeKey, derived);
|
|
5478
|
-
return derived;
|
|
5479
|
-
}
|
|
5480
|
-
function encryptRecord(key, record) {
|
|
5481
|
-
const plaintext = JSON.stringify(record);
|
|
5482
|
-
const iv = (0, import_utils30.randomBytes)(12);
|
|
5483
|
-
const { ciphertext, tag } = (0, import_utils30.encryptAesGcm)(key, new TextEncoder().encode(plaintext), iv);
|
|
5484
|
-
return JSON.stringify({
|
|
5485
|
-
iv: Buffer.from(iv).toString("base64url"),
|
|
5486
|
-
tag: Buffer.from(tag).toString("base64url"),
|
|
5487
|
-
data: Buffer.from(ciphertext).toString("base64url")
|
|
5488
|
-
});
|
|
5489
|
-
}
|
|
5490
|
-
function decryptRecord(key, encrypted) {
|
|
5491
|
-
const { iv, tag, data } = JSON.parse(encrypted);
|
|
5492
|
-
const ivBytes = Buffer.from(iv, "base64url");
|
|
5493
|
-
const tagBytes = Buffer.from(tag, "base64url");
|
|
5494
|
-
const ciphertextBytes = Buffer.from(data, "base64url");
|
|
5495
|
-
const plaintext = (0, import_utils30.decryptAesGcm)(key, ciphertextBytes, ivBytes, tagBytes);
|
|
5496
|
-
return JSON.parse(new TextDecoder().decode(plaintext));
|
|
5497
|
-
}
|
|
5498
|
-
|
|
5499
5810
|
// libs/auth/src/session/storage-orchestrated-token.store.ts
|
|
5500
5811
|
var StorageOrchestratedTokenStore = class {
|
|
5501
5812
|
storage;
|
|
5502
5813
|
encryptionKey;
|
|
5503
5814
|
defaultTtlMs;
|
|
5815
|
+
refreshSkewMs;
|
|
5504
5816
|
namespace;
|
|
5505
5817
|
storageIsNamespaced;
|
|
5506
5818
|
/** HKDF-derived subkey cache, keyed by composite key. */
|
|
@@ -5508,6 +5820,7 @@ var StorageOrchestratedTokenStore = class {
|
|
|
5508
5820
|
constructor(storage, options = {}) {
|
|
5509
5821
|
this.encryptionKey = options.encryptionKey;
|
|
5510
5822
|
this.defaultTtlMs = options.defaultTtlMs;
|
|
5823
|
+
this.refreshSkewMs = options.refreshSkewMs ?? 0;
|
|
5511
5824
|
this.namespace = options.namespace ?? "otok";
|
|
5512
5825
|
this.storageIsNamespaced = this.isNamespacedStorage(storage);
|
|
5513
5826
|
this.storage = this.storageIsNamespaced ? storage.namespace(this.namespace) : storage;
|
|
@@ -5517,7 +5830,7 @@ var StorageOrchestratedTokenStore = class {
|
|
|
5517
5830
|
// ============================================
|
|
5518
5831
|
async getAccessToken(authorizationId, providerId) {
|
|
5519
5832
|
const record = await this.getRecord(authorizationId, providerId);
|
|
5520
|
-
return record
|
|
5833
|
+
return record && hasLiveAccessToken(record, this.refreshSkewMs) ? record.accessToken : null;
|
|
5521
5834
|
}
|
|
5522
5835
|
async getRefreshToken(authorizationId, providerId) {
|
|
5523
5836
|
const record = await this.getRecord(authorizationId, providerId);
|
|
@@ -5534,7 +5847,7 @@ var StorageOrchestratedTokenStore = class {
|
|
|
5534
5847
|
updatedAt: now
|
|
5535
5848
|
};
|
|
5536
5849
|
const value = this.encryptionKey ? encryptRecord(this.keyFor(compositeKey), record) : JSON.stringify(record);
|
|
5537
|
-
await this.storage.set(this.storageKey(compositeKey), value, this.ttlOptions(record
|
|
5850
|
+
await this.storage.set(this.storageKey(compositeKey), value, this.ttlOptions(recordDropAt(record)));
|
|
5538
5851
|
}
|
|
5539
5852
|
async deleteTokens(authorizationId, providerId) {
|
|
5540
5853
|
const compositeKey = this.compositeKey(authorizationId, providerId);
|
|
@@ -5560,12 +5873,37 @@ var StorageOrchestratedTokenStore = class {
|
|
|
5560
5873
|
return providerIds;
|
|
5561
5874
|
}
|
|
5562
5875
|
async migrateTokens(fromAuthId, toAuthId) {
|
|
5876
|
+
for (const oldComposite of await this.copyRecords(fromAuthId, toAuthId)) {
|
|
5877
|
+
await this.storage.delete(this.storageKey(oldComposite));
|
|
5878
|
+
this.derivedKeys.delete(oldComposite);
|
|
5879
|
+
}
|
|
5880
|
+
}
|
|
5881
|
+
async copyTokens(fromAuthId, toAuthId) {
|
|
5882
|
+
await this.copyRecords(fromAuthId, toAuthId);
|
|
5883
|
+
}
|
|
5884
|
+
/**
|
|
5885
|
+
* Delete all tokens for an authorization id.
|
|
5886
|
+
*/
|
|
5887
|
+
async deleteAllForAuthorization(authorizationId) {
|
|
5888
|
+
const prefix = `${authorizationId}:`;
|
|
5889
|
+
const compositeKeys = await this.listCompositeKeys(`${prefix}*`);
|
|
5890
|
+
for (const compositeKey of compositeKeys) {
|
|
5891
|
+
if (!compositeKey.startsWith(prefix)) continue;
|
|
5892
|
+
await this.storage.delete(this.storageKey(compositeKey));
|
|
5893
|
+
this.derivedKeys.delete(compositeKey);
|
|
5894
|
+
}
|
|
5895
|
+
}
|
|
5896
|
+
// ============================================
|
|
5897
|
+
// Internals
|
|
5898
|
+
// ============================================
|
|
5899
|
+
/** Copy every record of `fromAuthId` to `toAuthId`, skipping a corrupted one; returns the composite keys copied. */
|
|
5900
|
+
async copyRecords(fromAuthId, toAuthId) {
|
|
5563
5901
|
const prefix = `${fromAuthId}:`;
|
|
5564
5902
|
const compositeKeys = await this.listCompositeKeys(`${prefix}*`);
|
|
5903
|
+
const copiedKeys = [];
|
|
5565
5904
|
for (const oldComposite of compositeKeys) {
|
|
5566
5905
|
if (!oldComposite.startsWith(prefix)) continue;
|
|
5567
|
-
const
|
|
5568
|
-
const newComposite = this.compositeKey(toAuthId, providerId);
|
|
5906
|
+
const newComposite = this.compositeKey(toAuthId, oldComposite.slice(prefix.length));
|
|
5569
5907
|
const stored = await this.storage.get(this.storageKey(oldComposite));
|
|
5570
5908
|
if (stored === null) continue;
|
|
5571
5909
|
if (this.encryptionKey) {
|
|
@@ -5576,35 +5914,20 @@ var StorageOrchestratedTokenStore = class {
|
|
|
5576
5914
|
continue;
|
|
5577
5915
|
}
|
|
5578
5916
|
const value = encryptRecord(this.keyFor(newComposite), record);
|
|
5579
|
-
await this.storage.set(this.storageKey(newComposite), value, this.ttlOptions(record
|
|
5917
|
+
await this.storage.set(this.storageKey(newComposite), value, this.ttlOptions(recordDropAt(record)));
|
|
5580
5918
|
} else {
|
|
5581
|
-
let
|
|
5919
|
+
let dropAt;
|
|
5582
5920
|
try {
|
|
5583
|
-
|
|
5921
|
+
dropAt = recordDropAt(JSON.parse(stored));
|
|
5584
5922
|
} catch {
|
|
5585
|
-
|
|
5923
|
+
dropAt = void 0;
|
|
5586
5924
|
}
|
|
5587
|
-
await this.storage.set(this.storageKey(newComposite), stored, this.ttlOptions(
|
|
5925
|
+
await this.storage.set(this.storageKey(newComposite), stored, this.ttlOptions(dropAt));
|
|
5588
5926
|
}
|
|
5589
|
-
|
|
5590
|
-
this.derivedKeys.delete(oldComposite);
|
|
5591
|
-
}
|
|
5592
|
-
}
|
|
5593
|
-
/**
|
|
5594
|
-
* Delete all tokens for an authorization id.
|
|
5595
|
-
*/
|
|
5596
|
-
async deleteAllForAuthorization(authorizationId) {
|
|
5597
|
-
const prefix = `${authorizationId}:`;
|
|
5598
|
-
const compositeKeys = await this.listCompositeKeys(`${prefix}*`);
|
|
5599
|
-
for (const compositeKey of compositeKeys) {
|
|
5600
|
-
if (!compositeKey.startsWith(prefix)) continue;
|
|
5601
|
-
await this.storage.delete(this.storageKey(compositeKey));
|
|
5602
|
-
this.derivedKeys.delete(compositeKey);
|
|
5927
|
+
copiedKeys.push(oldComposite);
|
|
5603
5928
|
}
|
|
5929
|
+
return copiedKeys;
|
|
5604
5930
|
}
|
|
5605
|
-
// ============================================
|
|
5606
|
-
// Internals
|
|
5607
|
-
// ============================================
|
|
5608
5931
|
async getRecord(authorizationId, providerId) {
|
|
5609
5932
|
const compositeKey = this.compositeKey(authorizationId, providerId);
|
|
5610
5933
|
const stored = await this.storage.get(this.storageKey(compositeKey));
|
|
@@ -5627,7 +5950,8 @@ var StorageOrchestratedTokenStore = class {
|
|
|
5627
5950
|
return null;
|
|
5628
5951
|
}
|
|
5629
5952
|
}
|
|
5630
|
-
|
|
5953
|
+
const dropAt = recordDropAt(record);
|
|
5954
|
+
if (dropAt && dropAt < Date.now()) {
|
|
5631
5955
|
await this.storage.delete(this.storageKey(compositeKey));
|
|
5632
5956
|
return null;
|
|
5633
5957
|
}
|
|
@@ -7079,7 +7403,7 @@ var OrchestratedAuthorization = class _OrchestratedAuthorization extends Authori
|
|
|
7079
7403
|
const result = await this.#onTokenRefresh(providerId, refreshToken);
|
|
7080
7404
|
await this.#tokenStore.storeTokens(this.id, providerId, {
|
|
7081
7405
|
accessToken: result.accessToken,
|
|
7082
|
-
refreshToken: result.refreshToken,
|
|
7406
|
+
refreshToken: result.refreshToken ?? refreshToken,
|
|
7083
7407
|
expiresAt: result.expiresIn ? Date.now() + result.expiresIn * 1e3 : void 0
|
|
7084
7408
|
});
|
|
7085
7409
|
const currentState = this.#providerStates.get(providerId);
|
|
@@ -7841,14 +8165,14 @@ var cimdConfigSchema = import_lazy_zod9.z.object({
|
|
|
7841
8165
|
init_cimd_cache();
|
|
7842
8166
|
|
|
7843
8167
|
// libs/auth/src/cimd/cimd.service.ts
|
|
7844
|
-
var
|
|
8168
|
+
var import_utils45 = require("@frontmcp/utils");
|
|
7845
8169
|
init_cimd_cache();
|
|
7846
8170
|
var CimdService = class {
|
|
7847
8171
|
config;
|
|
7848
8172
|
cacheConfig;
|
|
7849
8173
|
securityConfig;
|
|
7850
8174
|
networkConfig;
|
|
7851
|
-
|
|
8175
|
+
cacheBackend;
|
|
7852
8176
|
logger;
|
|
7853
8177
|
/**
|
|
7854
8178
|
* Whether CIMD is enabled.
|
|
@@ -7868,7 +8192,6 @@ var CimdService = class {
|
|
|
7868
8192
|
this.cacheConfig = cimdCacheConfigSchema.parse(this.config.cache ?? {});
|
|
7869
8193
|
this.securityConfig = cimdSecurityConfigSchema.parse(this.config.security ?? {});
|
|
7870
8194
|
this.networkConfig = cimdNetworkConfigSchema.parse(this.config.network ?? {});
|
|
7871
|
-
this.cache = new InMemoryCimdCache(this.cacheConfig);
|
|
7872
8195
|
this.logger.debug("CimdService initialized", {
|
|
7873
8196
|
enabled: this.config.enabled,
|
|
7874
8197
|
cacheDefaultTtlMs: this.cacheConfig.defaultTtlMs,
|
|
@@ -7880,6 +8203,34 @@ var CimdService = class {
|
|
|
7880
8203
|
);
|
|
7881
8204
|
}
|
|
7882
8205
|
}
|
|
8206
|
+
/**
|
|
8207
|
+
* Create the cache now (connecting to Redis with `cache.type: 'redis'`), so a misconfigured or
|
|
8208
|
+
* unreachable cache fails at startup rather than on the first CIMD client.
|
|
8209
|
+
*/
|
|
8210
|
+
async initialize() {
|
|
8211
|
+
await this.cache();
|
|
8212
|
+
}
|
|
8213
|
+
/**
|
|
8214
|
+
* The cache `cache.type` selects, created once through the `createCimdCache` factory. A failed
|
|
8215
|
+
* creation is retried on the next use.
|
|
8216
|
+
*/
|
|
8217
|
+
cache() {
|
|
8218
|
+
if (!this.cacheBackend) {
|
|
8219
|
+
this.cacheBackend = createCimdCache(this.cacheConfig).catch((error) => {
|
|
8220
|
+
this.cacheBackend = void 0;
|
|
8221
|
+
throw error;
|
|
8222
|
+
});
|
|
8223
|
+
}
|
|
8224
|
+
return this.cacheBackend;
|
|
8225
|
+
}
|
|
8226
|
+
/** Close the cache's connection (Redis with `cache.type: 'redis'`), if one was created. */
|
|
8227
|
+
async dispose() {
|
|
8228
|
+
const backend = this.cacheBackend;
|
|
8229
|
+
this.cacheBackend = void 0;
|
|
8230
|
+
if (!backend) return;
|
|
8231
|
+
const createdBackend = await backend.catch(() => void 0);
|
|
8232
|
+
await createdBackend?.close?.();
|
|
8233
|
+
}
|
|
7883
8234
|
/**
|
|
7884
8235
|
* Check if a client_id is a CIMD URL.
|
|
7885
8236
|
*
|
|
@@ -7906,7 +8257,8 @@ var CimdService = class {
|
|
|
7906
8257
|
};
|
|
7907
8258
|
}
|
|
7908
8259
|
validateClientIdUrl(clientId, this.securityConfig);
|
|
7909
|
-
const
|
|
8260
|
+
const cache = await this.cache();
|
|
8261
|
+
const cached = await cache.get(clientId);
|
|
7910
8262
|
if (cached) {
|
|
7911
8263
|
this.logger.debug(`Cache hit for CIMD client: ${clientId}`);
|
|
7912
8264
|
return {
|
|
@@ -7924,8 +8276,8 @@ var CimdService = class {
|
|
|
7924
8276
|
if (this.securityConfig.warnOnLocalhostRedirects && hasOnlyLocalhostRedirectUris(document.redirect_uris)) {
|
|
7925
8277
|
this.logger.warn(`CIMD client "${clientId}" has only localhost redirect URIs - this may be a development client`);
|
|
7926
8278
|
}
|
|
7927
|
-
await
|
|
7928
|
-
const entry = await
|
|
8279
|
+
await cache.set(clientId, document, headers);
|
|
8280
|
+
const entry = await cache.get(clientId);
|
|
7929
8281
|
return {
|
|
7930
8282
|
isCimdClient: true,
|
|
7931
8283
|
metadata: document,
|
|
@@ -7955,11 +8307,12 @@ var CimdService = class {
|
|
|
7955
8307
|
* @param clientId - Optional client_id to clear; clears all if not provided
|
|
7956
8308
|
*/
|
|
7957
8309
|
async clearCache(clientId) {
|
|
8310
|
+
const cache = await this.cache();
|
|
7958
8311
|
if (clientId) {
|
|
7959
|
-
await
|
|
8312
|
+
await cache.delete(clientId);
|
|
7960
8313
|
this.logger.debug(`Cache cleared for: ${clientId}`);
|
|
7961
8314
|
} else {
|
|
7962
|
-
await
|
|
8315
|
+
await cache.clear();
|
|
7963
8316
|
this.logger.debug("Cache cleared");
|
|
7964
8317
|
}
|
|
7965
8318
|
}
|
|
@@ -7967,8 +8320,9 @@ var CimdService = class {
|
|
|
7967
8320
|
* Get cache statistics.
|
|
7968
8321
|
*/
|
|
7969
8322
|
async getCacheStats() {
|
|
8323
|
+
const cache = await this.cache();
|
|
7970
8324
|
return {
|
|
7971
|
-
size: await
|
|
8325
|
+
size: await cache.size()
|
|
7972
8326
|
};
|
|
7973
8327
|
}
|
|
7974
8328
|
/**
|
|
@@ -7980,7 +8334,8 @@ var CimdService = class {
|
|
|
7980
8334
|
const controller = new AbortController();
|
|
7981
8335
|
const timer = setTimeout(() => controller.abort(), this.networkConfig.timeoutMs);
|
|
7982
8336
|
try {
|
|
7983
|
-
const
|
|
8337
|
+
const cache = await this.cache();
|
|
8338
|
+
const conditionalHeaders = await cache.getConditionalHeaders(clientId);
|
|
7984
8339
|
const originalOrigin = new URL(clientId).origin;
|
|
7985
8340
|
const maxRedirects = this.networkConfig.maxRedirects;
|
|
7986
8341
|
let currentUrl = clientId;
|
|
@@ -8003,9 +8358,9 @@ var CimdService = class {
|
|
|
8003
8358
|
redirect: "manual"
|
|
8004
8359
|
});
|
|
8005
8360
|
if (response.status === 304) {
|
|
8006
|
-
const staleEntry = await
|
|
8361
|
+
const staleEntry = await cache.getStale(clientId);
|
|
8007
8362
|
if (staleEntry) {
|
|
8008
|
-
await
|
|
8363
|
+
await cache.revalidate(clientId, response.headers);
|
|
8009
8364
|
this.logger.debug(`CIMD document not modified: ${clientId}`);
|
|
8010
8365
|
return {
|
|
8011
8366
|
document: staleEntry.document,
|
|
@@ -8016,7 +8371,7 @@ var CimdService = class {
|
|
|
8016
8371
|
httpStatus: 304
|
|
8017
8372
|
});
|
|
8018
8373
|
}
|
|
8019
|
-
if ((0,
|
|
8374
|
+
if ((0, import_utils45.isRedirectResponse)(response)) {
|
|
8020
8375
|
const location = response.headers.get("location");
|
|
8021
8376
|
if (!location) {
|
|
8022
8377
|
throw new CimdFetchError(clientId, "Redirect response missing Location header", {
|
|
@@ -8513,7 +8868,7 @@ var tokenRefreshConfigSchema = import_lazy_zod10.z.object({
|
|
|
8513
8868
|
* Refresh token before expiry by this many seconds
|
|
8514
8869
|
* @default 60
|
|
8515
8870
|
*/
|
|
8516
|
-
skewSeconds: import_lazy_zod10.z.number().default(60)
|
|
8871
|
+
skewSeconds: import_lazy_zod10.z.number().nonnegative().default(60)
|
|
8517
8872
|
});
|
|
8518
8873
|
var skippedAppBehaviorSchema = import_lazy_zod10.z.enum(["anonymous", "require-auth"]);
|
|
8519
8874
|
var consentConfigSchema = import_lazy_zod10.z.object({
|
|
@@ -8626,7 +8981,7 @@ var publicAuthOptionsSchema = import_lazy_zod11.z.object({
|
|
|
8626
8981
|
* Anonymous session TTL in seconds
|
|
8627
8982
|
* @default 3600 (1 hour)
|
|
8628
8983
|
*/
|
|
8629
|
-
sessionTtl: import_lazy_zod11.z.number().default(3600),
|
|
8984
|
+
sessionTtl: import_lazy_zod11.z.number().int().positive().default(3600),
|
|
8630
8985
|
/**
|
|
8631
8986
|
* Scopes granted to anonymous sessions
|
|
8632
8987
|
* @default ['anonymous']
|
|
@@ -9073,9 +9428,17 @@ function detectAuthProviders(parentAuth, apps) {
|
|
|
9073
9428
|
`Invalid auth configuration: Parent uses transparent mode but apps have their own auth providers. Transparent mode passes tokens through without modification, which is incompatible with multi-provider setups. Change parent auth to local or remote mode to properly manage tokens for each provider. Detected providers: ${[...providers.keys()].join(", ")}`
|
|
9074
9429
|
);
|
|
9075
9430
|
}
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
|
|
9431
|
+
const sharedEndpointUnenforced = !parentAuth || isPublicMode(parentAuth) || isStaticMode(parentAuth);
|
|
9432
|
+
const unenforcedAppIds = apps.filter((app) => app.auth && !isPublicMode(app.auth) && !staticParentEnforces(parentAuth, app.auth)).map((app) => app.id);
|
|
9433
|
+
if (sharedEndpointUnenforced && unenforcedAppIds.length > 0) {
|
|
9434
|
+
validationErrors.push(
|
|
9435
|
+
`App-level auth is not enforced on the shared endpoint of a server in ${parentAuth?.mode ?? "public"} mode, so the tools of ${unenforcedAppIds.join(", ")} would be served without it. Serve the app on its own endpoint with standalone: true or splitByApp: true, or run the server in local or remote mode with incrementalAuth enabled, which checks each tool call against the apps the caller has authorized (without incrementalAuth, local and remote mode do not check app grants per tool call).`
|
|
9436
|
+
);
|
|
9437
|
+
}
|
|
9438
|
+
const tokenMintingAppIds = apps.filter((app) => app.remoteAuthMode === "forward" && !(parentAuth && isTransparentMode(parentAuth))).map((app) => app.id);
|
|
9439
|
+
if (tokenMintingAppIds.length > 0) {
|
|
9440
|
+
validationErrors.push(
|
|
9441
|
+
`remoteAuth: { mode: 'forward' } on ${tokenMintingAppIds.join(", ")} would send the remote a token this server minted or holds itself: only a server in transparent mode receives its callers' tokens from the identity provider. Use remoteAuth: { mode: 'static', credentials } (or transportOptions.headers) for the remote, or run the server in transparent mode.`
|
|
9079
9442
|
);
|
|
9080
9443
|
}
|
|
9081
9444
|
return {
|
|
@@ -9088,6 +9451,14 @@ function detectAuthProviders(parentAuth, apps) {
|
|
|
9088
9451
|
warnings
|
|
9089
9452
|
};
|
|
9090
9453
|
}
|
|
9454
|
+
function staticParentEnforces(parentAuth, appAuth) {
|
|
9455
|
+
if (!parentAuth || !isStaticMode(parentAuth) || !isStaticMode(appAuth)) return false;
|
|
9456
|
+
const parent = staticAuthOptionsSchema.safeParse(parentAuth);
|
|
9457
|
+
const app = staticAuthOptionsSchema.safeParse(appAuth);
|
|
9458
|
+
if (!parent.success || !app.success) return false;
|
|
9459
|
+
const sameChallenge = parent.data.header.toLowerCase() === app.data.header.toLowerCase() && parent.data.scheme === app.data.scheme;
|
|
9460
|
+
return sameChallenge && parent.data.tokens.every((token) => app.data.tokens.includes(token));
|
|
9461
|
+
}
|
|
9091
9462
|
function getProviderUrl(options) {
|
|
9092
9463
|
if (isTransparentMode(options)) {
|
|
9093
9464
|
return options.provider;
|
|
@@ -9428,6 +9799,16 @@ function isLoopbackRedirectUri(uri) {
|
|
|
9428
9799
|
return false;
|
|
9429
9800
|
}
|
|
9430
9801
|
|
|
9802
|
+
// libs/auth/src/utils/token-scopes.utils.ts
|
|
9803
|
+
function scopesFromClaims(claims) {
|
|
9804
|
+
const scopes = [claims?.scope, claims?.scp].flatMap((claim) => {
|
|
9805
|
+
if (typeof claim === "string") return claim.split(/\s+/).filter(Boolean);
|
|
9806
|
+
if (Array.isArray(claim)) return claim.filter((scope) => typeof scope === "string");
|
|
9807
|
+
return [];
|
|
9808
|
+
});
|
|
9809
|
+
return [...new Set(scopes)];
|
|
9810
|
+
}
|
|
9811
|
+
|
|
9431
9812
|
// libs/auth/src/vault/auth-providers.types.ts
|
|
9432
9813
|
var import_lazy_zod19 = require("@frontmcp/lazy-zod");
|
|
9433
9814
|
var credentialScopeSchema = import_lazy_zod19.z.enum(["global", "user", "session"]);
|
|
@@ -9670,7 +10051,7 @@ var CredentialCache = class {
|
|
|
9670
10051
|
var AUTH_PROVIDERS_ACCESSOR = /* @__PURE__ */ Symbol.for("frontmcp:AUTH_PROVIDERS_ACCESSOR");
|
|
9671
10052
|
|
|
9672
10053
|
// libs/auth/src/vault/auth-providers.accessor.impl.ts
|
|
9673
|
-
var
|
|
10054
|
+
var import_utils48 = require("@frontmcp/utils");
|
|
9674
10055
|
var AuthProvidersAccessorImpl = class {
|
|
9675
10056
|
constructor(registry, vault, cache, loader, context, logger) {
|
|
9676
10057
|
this.registry = registry;
|
|
@@ -9885,7 +10266,7 @@ var AuthProvidersAccessorImpl = class {
|
|
|
9885
10266
|
return { [credential.headerName]: credential.key };
|
|
9886
10267
|
case "basic": {
|
|
9887
10268
|
const value = `${credential.username}:${credential.password}`;
|
|
9888
|
-
const encoded = credential.encodedValue ?? (0,
|
|
10269
|
+
const encoded = credential.encodedValue ?? (0, import_utils48.base64Encode)(new TextEncoder().encode(value));
|
|
9889
10270
|
return { Authorization: `Basic ${encoded}` };
|
|
9890
10271
|
}
|
|
9891
10272
|
case "custom":
|
|
@@ -10445,6 +10826,7 @@ var LazyCredentialLoader = class {
|
|
|
10445
10826
|
};
|
|
10446
10827
|
|
|
10447
10828
|
// libs/auth/src/authorities/authorities.context.ts
|
|
10829
|
+
var import_utils49 = require("@frontmcp/utils");
|
|
10448
10830
|
function resolveDotPath(obj, path) {
|
|
10449
10831
|
if (path in obj) {
|
|
10450
10832
|
return obj[path];
|
|
@@ -10485,6 +10867,10 @@ function signedInSubject(rawSub, mappedSub) {
|
|
|
10485
10867
|
if (rawSub !== void 0 && isAnonymousSubject(rawSub)) return void 0;
|
|
10486
10868
|
return [mappedSub, rawSub].find((candidate) => !isAnonymousSubject(candidate));
|
|
10487
10869
|
}
|
|
10870
|
+
var runtimeEnv = new Proxy(
|
|
10871
|
+
{},
|
|
10872
|
+
{ get: (_target, name) => typeof name === "string" ? (0, import_utils49.getEnv)(name) : void 0 }
|
|
10873
|
+
);
|
|
10488
10874
|
var AuthoritiesContextBuilder = class {
|
|
10489
10875
|
claimsMapping;
|
|
10490
10876
|
claimsResolver;
|
|
@@ -10497,7 +10883,7 @@ var AuthoritiesContextBuilder = class {
|
|
|
10497
10883
|
/**
|
|
10498
10884
|
* Build an evaluation context from AuthInfo and tool input.
|
|
10499
10885
|
*/
|
|
10500
|
-
build(authInfo, input = {}, env =
|
|
10886
|
+
build(authInfo, input = {}, env = runtimeEnv) {
|
|
10501
10887
|
if (this.claimsResolver) {
|
|
10502
10888
|
const resolved = this.claimsResolver(authInfo);
|
|
10503
10889
|
return {
|
|
@@ -10567,9 +10953,9 @@ var FrontMcpAuthContextImpl = class {
|
|
|
10567
10953
|
claims;
|
|
10568
10954
|
roles;
|
|
10569
10955
|
permissions;
|
|
10570
|
-
constructor(source, claimsMapping) {
|
|
10956
|
+
constructor(source, claimsMapping, claimsResolver) {
|
|
10571
10957
|
const rawUser = resolveAuthUser(source);
|
|
10572
|
-
const mappedSub = claimsMapping?.userId ? resolveDotPath(this.buildRawClaims(source), claimsMapping.userId) : void 0;
|
|
10958
|
+
const mappedSub = !claimsResolver && claimsMapping?.userId ? resolveDotPath(this.buildRawClaims(source), claimsMapping.userId) : void 0;
|
|
10573
10959
|
const signedInSub = signedInSubject(rawUser.sub, mappedSub);
|
|
10574
10960
|
const sub = signedInSub ?? (typeof rawUser.sub === "string" ? rawUser.sub : "");
|
|
10575
10961
|
this.user = Object.freeze({
|
|
@@ -10587,6 +10973,13 @@ var FrontMcpAuthContextImpl = class {
|
|
|
10587
10973
|
this.scopes = Object.freeze(
|
|
10588
10974
|
Array.isArray(source.scopes) ? source.scopes.filter((s) => typeof s === "string") : []
|
|
10589
10975
|
);
|
|
10976
|
+
if (claimsResolver) {
|
|
10977
|
+
const resolved = claimsResolver(source);
|
|
10978
|
+
this.claims = Object.freeze({ ...resolved.claims });
|
|
10979
|
+
this.roles = Object.freeze([...toStringArray2(resolved.roles)]);
|
|
10980
|
+
this.permissions = Object.freeze([...toStringArray2(resolved.permissions)]);
|
|
10981
|
+
return;
|
|
10982
|
+
}
|
|
10590
10983
|
const rawClaims = this.buildRawClaims(source);
|
|
10591
10984
|
this.claims = Object.freeze({ ...rawClaims });
|
|
10592
10985
|
if (claimsMapping?.roles) {
|
|
@@ -10647,8 +11040,8 @@ var FrontMcpAuthContextImpl = class {
|
|
|
10647
11040
|
};
|
|
10648
11041
|
|
|
10649
11042
|
// libs/auth/src/context/frontmcp-auth-context.factory.ts
|
|
10650
|
-
function buildAuthContext(authInfo, claimsMapping, pipes) {
|
|
10651
|
-
const base = new FrontMcpAuthContextImpl(authInfo, claimsMapping);
|
|
11043
|
+
function buildAuthContext(authInfo, claimsMapping, pipes, claimsResolver) {
|
|
11044
|
+
const base = new FrontMcpAuthContextImpl(authInfo, claimsMapping, claimsResolver);
|
|
10652
11045
|
if (!pipes || pipes.length === 0) {
|
|
10653
11046
|
return base;
|
|
10654
11047
|
}
|
|
@@ -10900,6 +11293,10 @@ function evaluateCondition(condition, envelope, ctx) {
|
|
|
10900
11293
|
const expected = resolveValue(condition.value, ctx);
|
|
10901
11294
|
return applyOperator(actual, condition.op, expected);
|
|
10902
11295
|
}
|
|
11296
|
+
var REDACTED_ENV_VALUE = "[redacted]";
|
|
11297
|
+
function reportableActual(path, actual) {
|
|
11298
|
+
return path === "env" || path.startsWith("env.") ? REDACTED_ENV_VALUE : actual;
|
|
11299
|
+
}
|
|
10903
11300
|
function evaluateAbac(policy, ctx) {
|
|
10904
11301
|
const envelope = buildContextEnvelope(ctx);
|
|
10905
11302
|
if (policy.match) {
|
|
@@ -10907,10 +11304,11 @@ function evaluateAbac(policy, ctx) {
|
|
|
10907
11304
|
const actual = resolveDotPath(envelope, path);
|
|
10908
11305
|
const expected = resolveValue(expectedRaw, ctx);
|
|
10909
11306
|
if (expected === void 0 || actual !== expected) {
|
|
11307
|
+
const reported = reportableActual(path, actual);
|
|
10910
11308
|
return {
|
|
10911
11309
|
granted: false,
|
|
10912
|
-
deniedBy: `attributes.match: '${path}' expected '${String(expected)}' but got '${String(
|
|
10913
|
-
denial: { kind: "attributes", path: "attributes.match", expected, actual },
|
|
11310
|
+
deniedBy: `attributes.match: '${path}' expected '${String(expected)}' but got '${String(reported)}'`,
|
|
11311
|
+
denial: { kind: "attributes", path: "attributes.match", expected, actual: reported },
|
|
10914
11312
|
evaluatedPolicies: ["attributes.match"]
|
|
10915
11313
|
};
|
|
10916
11314
|
}
|
|
@@ -10921,7 +11319,7 @@ function evaluateAbac(policy, ctx) {
|
|
|
10921
11319
|
const condition = policy.conditions[i];
|
|
10922
11320
|
if (!evaluateCondition(condition, envelope, ctx)) {
|
|
10923
11321
|
const expected = resolveValue(condition.value, ctx);
|
|
10924
|
-
const actual = resolveDotPath(envelope, condition.path);
|
|
11322
|
+
const actual = reportableActual(condition.path, resolveDotPath(envelope, condition.path));
|
|
10925
11323
|
return {
|
|
10926
11324
|
granted: false,
|
|
10927
11325
|
deniedBy: `attributes.conditions: '${condition.path}' failed '${condition.op}' check against '${String(expected)}'`,
|
|
@@ -11660,6 +12058,21 @@ var basicApplier = {
|
|
|
11660
12058
|
function isFrontMcpCredentials(creds) {
|
|
11661
12059
|
return typeof creds === "object" && creds !== null && "provider" in creds && typeof creds.provider === "string" && creds.provider.length > 0;
|
|
11662
12060
|
}
|
|
12061
|
+
function providerHeadersCredentials(resolveHeaders) {
|
|
12062
|
+
return {
|
|
12063
|
+
async applyCredentials(_url, init) {
|
|
12064
|
+
const { credentials, ...cleanInit } = init;
|
|
12065
|
+
if (!isFrontMcpCredentials(credentials)) {
|
|
12066
|
+
return { init: typeof credentials === "string" ? init : cleanInit };
|
|
12067
|
+
}
|
|
12068
|
+
const headers = new Headers(cleanInit.headers);
|
|
12069
|
+
for (const [name, value] of Object.entries(await resolveHeaders(credentials.provider))) {
|
|
12070
|
+
headers.set(name, value);
|
|
12071
|
+
}
|
|
12072
|
+
return { init: { ...cleanInit, headers } };
|
|
12073
|
+
}
|
|
12074
|
+
};
|
|
12075
|
+
}
|
|
11663
12076
|
var FetchCredentialMiddleware = class {
|
|
11664
12077
|
constructor(tokenAccessor, appliers) {
|
|
11665
12078
|
this.tokenAccessor = tokenAccessor;
|
|
@@ -11961,6 +12374,7 @@ var FetchCredentialMiddleware = class {
|
|
|
11961
12374
|
privateKeyCredentialSchema,
|
|
11962
12375
|
progressiveAuthStateSchema,
|
|
11963
12376
|
providerConfigSchema,
|
|
12377
|
+
providerHeadersCredentials,
|
|
11964
12378
|
publicAccessConfigSchema,
|
|
11965
12379
|
publicAuthOptionsSchema,
|
|
11966
12380
|
queryApplier,
|
|
@@ -11978,6 +12392,7 @@ var FetchCredentialMiddleware = class {
|
|
|
11978
12392
|
resourceIdRefSchema,
|
|
11979
12393
|
resourceScopesFor,
|
|
11980
12394
|
safeDecrypt,
|
|
12395
|
+
scopesFromClaims,
|
|
11981
12396
|
sealPendingLogin,
|
|
11982
12397
|
secureStoreConfigSchema,
|
|
11983
12398
|
secureStoreCustomBackendSchema,
|