@frontmcp/auth 1.11.2 → 1.11.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/README.md +2 -2
- package/authorities/authorities.context.d.ts.map +1 -1
- package/authorities/authorities.engine.d.ts +1 -1
- package/authorities/authorities.engine.d.ts.map +1 -1
- package/authorities/authorities.evaluator.d.ts.map +1 -1
- package/authorities/authorities.profiles.d.ts +1 -1
- package/authorities/authorities.profiles.d.ts.map +1 -1
- package/authorities/authorities.registry.d.ts +1 -1
- package/authorities/authorities.registry.d.ts.map +1 -1
- package/authorities/authorities.scope-mapping.d.ts +1 -1
- package/authorities/authorities.scope-mapping.d.ts.map +1 -1
- package/authorities/authorities.types.d.ts +4 -0
- package/authorities/authorities.types.d.ts.map +1 -1
- package/authorization/orchestrated.accessor.d.ts.map +1 -1
- package/authorization/public.authorization.d.ts.map +1 -1
- package/authorization/transparent.authorization.d.ts +1 -1
- package/authorization/transparent.authorization.d.ts.map +1 -1
- package/browser/authorities/authorities.context.mjs +3 -1
- package/browser/authorities/authorities.engine.mjs +32 -33
- package/browser/authorities/authorities.evaluator.mjs +42 -16
- package/browser/authorization/dcr-client.registry.mjs +1 -1
- package/browser/authorization/public.authorization.mjs +1 -1
- package/browser/authorization/transparent.authorization.mjs +1 -1
- package/browser/cimd/cimd-client-assertion.mjs +108 -0
- package/browser/cimd/cimd-redis.cache.mjs +5 -3
- package/browser/cimd/cimd.errors.mjs +11 -0
- package/browser/cimd/cimd.service.mjs +127 -31
- package/browser/cimd/cimd.types.mjs +15 -10
- package/browser/cimd/index.mjs +13 -1
- package/browser/errors/auth-internal.error.mjs +3 -2
- package/browser/errors/auth-internal.errors.mjs +8 -1
- package/browser/errors/index.mjs +2 -0
- package/browser/index.mjs +21 -1
- package/browser/jwks/index.mjs +2 -0
- package/browser/jwks/jwks.service.mjs +22 -103
- package/browser/jwks/local-token-keys.mjs +155 -0
- package/browser/options/public.schema.mjs +7 -4
- package/browser/options/shared.schemas.mjs +38 -13
- package/browser/session/federated-auth.session.mjs +2 -0
- package/browser/session/incremental-auth-ticket.mjs +24 -1
- package/browser/session/index.mjs +4 -0
- package/browser/session/redis-session.store.mjs +3 -3
- package/browser/session/session-crypto.mjs +4 -4
- package/browser/session/storage/storage-authorization-vault.mjs +3 -1
- package/browser/session/token.vault.mjs +1 -1
- package/browser/session/utils/index.mjs +9 -1
- package/browser/session/utils/session-crypto.utils.mjs +4 -0
- package/browser/ui/index.mjs +2 -0
- package/browser/ui/templates.mjs +65 -11
- package/browser/upstream/index.mjs +6 -0
- package/browser/upstream/upstream-client-registration.mjs +273 -0
- package/cimd/cimd-client-assertion.d.ts +43 -0
- package/cimd/cimd-client-assertion.d.ts.map +1 -0
- package/cimd/cimd-redis.cache.d.ts +2 -2
- package/cimd/cimd-redis.cache.d.ts.map +1 -1
- package/cimd/cimd.cache.d.ts +1 -1
- package/cimd/cimd.cache.d.ts.map +1 -1
- package/cimd/cimd.errors.d.ts +14 -0
- package/cimd/cimd.errors.d.ts.map +1 -1
- package/cimd/cimd.service.d.ts +37 -2
- package/cimd/cimd.service.d.ts.map +1 -1
- package/cimd/cimd.types.d.ts +6 -0
- package/cimd/cimd.types.d.ts.map +1 -1
- package/cimd/index.d.ts +3 -2
- package/cimd/index.d.ts.map +1 -1
- package/errors/auth-internal.error.d.ts +4 -5
- package/errors/auth-internal.error.d.ts.map +1 -1
- package/errors/auth-internal.errors.d.ts +8 -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 +54 -14
- package/esm/package.json +5 -5
- package/index.d.ts +12 -10
- package/index.d.ts.map +1 -1
- package/index.js +58 -18
- package/jwks/index.d.ts +2 -0
- package/jwks/index.d.ts.map +1 -1
- package/jwks/jwks.service.d.ts +12 -12
- package/jwks/jwks.service.d.ts.map +1 -1
- package/jwks/jwks.types.d.ts +2 -0
- package/jwks/jwks.types.d.ts.map +1 -1
- package/jwks/local-token-keys.d.ts +41 -0
- package/jwks/local-token-keys.d.ts.map +1 -0
- package/options/interfaces.d.ts +64 -15
- package/options/interfaces.d.ts.map +1 -1
- package/options/public.schema.d.ts.map +1 -1
- package/options/shared.schemas.d.ts.map +1 -1
- package/package.json +5 -5
- package/session/authorization.store.d.ts +9 -2
- package/session/authorization.store.d.ts.map +1 -1
- package/session/federated-auth.session.d.ts +8 -0
- package/session/federated-auth.session.d.ts.map +1 -1
- package/session/incremental-auth-ticket.d.ts +10 -0
- package/session/incremental-auth-ticket.d.ts.map +1 -1
- package/session/index.d.ts +2 -2
- package/session/index.d.ts.map +1 -1
- package/session/redis-session.store.d.ts +1 -1
- package/session/redis-session.store.d.ts.map +1 -1
- package/session/session-crypto.d.ts.map +1 -1
- package/session/storage/in-memory-authorization-vault.d.ts.map +1 -1
- package/session/storage/storage-authorization-vault.d.ts +2 -2
- package/session/storage/storage-authorization-vault.d.ts.map +1 -1
- package/session/storage/storage-token-store.d.ts +2 -2
- package/session/storage/storage-token-store.d.ts.map +1 -1
- package/session/token.refresh.d.ts.map +1 -1
- package/session/utils/index.d.ts +1 -1
- package/session/utils/index.d.ts.map +1 -1
- package/session/utils/session-crypto.utils.d.ts +5 -0
- package/session/utils/session-crypto.utils.d.ts.map +1 -1
- package/ui/index.d.ts +2 -2
- package/ui/index.d.ts.map +1 -1
- package/ui/templates.d.ts +33 -0
- package/ui/templates.d.ts.map +1 -1
- package/upstream/index.d.ts +3 -0
- package/upstream/index.d.ts.map +1 -0
- package/upstream/upstream-client-registration.d.ts +50 -0
- package/upstream/upstream-client-registration.d.ts.map +1 -0
- package/vault/auth-providers.accessor.d.ts +1 -1
- package/vault/auth-providers.accessor.d.ts.map +1 -1
- package/vault/auth-providers.registry.d.ts +1 -1
- package/vault/auth-providers.registry.d.ts.map +1 -1
- package/vault/credential-cache.d.ts +1 -1
- package/vault/credential-cache.d.ts.map +1 -1
- package/vault/credential-loaders/eager-loader.d.ts +2 -2
- package/vault/credential-loaders/eager-loader.d.ts.map +1 -1
|
@@ -9,14 +9,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
// libs/auth/src/jwks/jwks.service.ts
|
|
12
|
-
import {
|
|
13
|
-
bytesToHex,
|
|
14
|
-
createKeyPersistence,
|
|
15
|
-
isProduction,
|
|
16
|
-
isRedirectResponse,
|
|
17
|
-
randomBytes,
|
|
18
|
-
rsaVerify
|
|
19
|
-
} from "@frontmcp/utils";
|
|
12
|
+
import { bytesToHex, isProduction, isRedirectResponse, randomBytes, rsaVerify } from "@frontmcp/utils";
|
|
20
13
|
import { resolveAndCheckHostname } from "../cimd/cimd.validator.mjs";
|
|
21
14
|
import { noopAuthLogger } from "../common/auth-logger.interface.mjs";
|
|
22
15
|
import { loadJose } from "./jose.loader.mjs";
|
|
@@ -88,16 +81,12 @@ var JwksService = class {
|
|
|
88
81
|
warnedProviders = /* @__PURE__ */ new Set();
|
|
89
82
|
// Orchestrator signing material
|
|
90
83
|
orchestratorKey;
|
|
84
|
+
// The keys this server's own tokens verify with (see setPublicJwks)
|
|
85
|
+
publicJwks = { keys: [] };
|
|
91
86
|
// Provider JWKS cache (providerId -> jwks + fetchedAt)
|
|
92
87
|
providerJwks = /* @__PURE__ */ new Map();
|
|
93
88
|
// The last key refetch an unknown `kid` started, per provider (see refetchForUnknownKid)
|
|
94
89
|
unknownKidRefetches = /* @__PURE__ */ new Map();
|
|
95
|
-
// Track if key has been initialized (for async loading)
|
|
96
|
-
keyInitialized = false;
|
|
97
|
-
// Promise guard to prevent concurrent key generation
|
|
98
|
-
keyInitPromise;
|
|
99
|
-
// KeyPersistence instance for unified key storage
|
|
100
|
-
keyPersistence;
|
|
101
90
|
constructor(opts) {
|
|
102
91
|
this.logger = opts?.logger ?? noopAuthLogger;
|
|
103
92
|
this.opts = {
|
|
@@ -110,27 +99,15 @@ var JwksService = class {
|
|
|
110
99
|
};
|
|
111
100
|
}
|
|
112
101
|
// ===========================================================================
|
|
113
|
-
// Key Persistence Helpers
|
|
114
|
-
// ===========================================================================
|
|
115
|
-
/**
|
|
116
|
-
* Get or create the KeyPersistence instance.
|
|
117
|
-
* Returns null if persistence is disabled (production).
|
|
118
|
-
*/
|
|
119
|
-
async getKeyPersistence() {
|
|
120
|
-
if (isProduction()) return null;
|
|
121
|
-
if (!this.keyPersistence) {
|
|
122
|
-
this.keyPersistence = await createKeyPersistence({
|
|
123
|
-
baseDir: ".frontmcp/keys"
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
return this.keyPersistence;
|
|
127
|
-
}
|
|
128
|
-
// ===========================================================================
|
|
129
102
|
// Public JWKS (what /.well-known/jwks.json serves)
|
|
130
103
|
// ===========================================================================
|
|
131
|
-
/**
|
|
104
|
+
/** The public keys this server's tokens verify with, served at `/.well-known/jwks.json`; empty for HMAC tokens. */
|
|
132
105
|
async getPublicJwks() {
|
|
133
|
-
return this.
|
|
106
|
+
return { keys: [...this.publicJwks.keys] };
|
|
107
|
+
}
|
|
108
|
+
/** Set the public keys {@link getPublicJwks} serves (`LocalTokenKeys.publicJwks`). */
|
|
109
|
+
setPublicJwks(jwks) {
|
|
110
|
+
this.publicJwks = { keys: [...jwks.keys] };
|
|
134
111
|
}
|
|
135
112
|
// ===========================================================================
|
|
136
113
|
// Scope-aware verification API
|
|
@@ -393,14 +370,20 @@ var JwksService = class {
|
|
|
393
370
|
// ===========================================================================
|
|
394
371
|
// Orchestrator keys (generation/rotation)
|
|
395
372
|
// ===========================================================================
|
|
396
|
-
/**
|
|
373
|
+
/**
|
|
374
|
+
* Return the orchestrator public JWKS (generates/rotates as needed, in memory).
|
|
375
|
+
* @deprecated FrontMCP never signs with this key; `getPublicJwks()` serves the keys its tokens verify with.
|
|
376
|
+
*/
|
|
397
377
|
async getOrchestratorJwks() {
|
|
398
|
-
|
|
378
|
+
this.ensureOrchestratorKey();
|
|
399
379
|
return this.orchestratorKey.publicJwk;
|
|
400
380
|
}
|
|
401
|
-
/**
|
|
381
|
+
/**
|
|
382
|
+
* Return private signing key + kid for issuing orchestrator tokens.
|
|
383
|
+
* @deprecated FrontMCP never signs with this key; configure `signKey` to sign tokens with your own key.
|
|
384
|
+
*/
|
|
402
385
|
async getOrchestratorSigningKey() {
|
|
403
|
-
|
|
386
|
+
this.ensureOrchestratorKey();
|
|
404
387
|
return { kid: this.orchestratorKey.kid, key: this.orchestratorKey.privateKey, alg: this.opts.orchestratorAlg };
|
|
405
388
|
}
|
|
406
389
|
// ===========================================================================
|
|
@@ -469,74 +452,10 @@ var JwksService = class {
|
|
|
469
452
|
clearTimeout(timer);
|
|
470
453
|
}
|
|
471
454
|
}
|
|
472
|
-
|
|
473
|
-
const now = Date.now();
|
|
455
|
+
ensureOrchestratorKey() {
|
|
474
456
|
const maxAge = this.opts.rotateDays * 24 * 60 * 60 * 1e3;
|
|
475
|
-
if (this.orchestratorKey
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
if (this.keyInitPromise) {
|
|
479
|
-
await this.keyInitPromise;
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
this.keyInitPromise = this.initializeOrchestratorKey(now, maxAge);
|
|
483
|
-
try {
|
|
484
|
-
await this.keyInitPromise;
|
|
485
|
-
} finally {
|
|
486
|
-
this.keyInitPromise = void 0;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
async initializeOrchestratorKey(now, maxAge) {
|
|
490
|
-
const persistence = await this.getKeyPersistence();
|
|
491
|
-
if (persistence && !this.keyInitialized) {
|
|
492
|
-
this.keyInitialized = true;
|
|
493
|
-
const loaded = await persistence.getAsymmetric("jwks-orchestrator");
|
|
494
|
-
if (loaded && now - loaded.createdAt <= maxAge) {
|
|
495
|
-
if (loaded.alg !== this.opts.orchestratorAlg) {
|
|
496
|
-
this.logger.warn(
|
|
497
|
-
"[JwksService] Persisted key algorithm (%s) doesn't match config (%s), generating new key",
|
|
498
|
-
loaded.alg,
|
|
499
|
-
this.opts.orchestratorAlg
|
|
500
|
-
);
|
|
501
|
-
} else {
|
|
502
|
-
try {
|
|
503
|
-
const { createPrivateKey } = __require("node:crypto");
|
|
504
|
-
const privateKey = createPrivateKey({
|
|
505
|
-
key: loaded.privateKey,
|
|
506
|
-
format: "jwk"
|
|
507
|
-
});
|
|
508
|
-
this.orchestratorKey = {
|
|
509
|
-
kid: loaded.kid,
|
|
510
|
-
privateKey,
|
|
511
|
-
publicJwk: loaded.publicJwk,
|
|
512
|
-
createdAt: loaded.createdAt
|
|
513
|
-
};
|
|
514
|
-
return;
|
|
515
|
-
} catch (error) {
|
|
516
|
-
this.logger.warn(
|
|
517
|
-
"[JwksService] Failed to load persisted key: %s, generating new key",
|
|
518
|
-
error.message
|
|
519
|
-
);
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
this.orchestratorKey = this.generateKey(this.opts.orchestratorAlg);
|
|
525
|
-
this.keyInitialized = true;
|
|
526
|
-
if (persistence) {
|
|
527
|
-
try {
|
|
528
|
-
await persistence.set({
|
|
529
|
-
type: "asymmetric",
|
|
530
|
-
kid: this.orchestratorKey.kid,
|
|
531
|
-
alg: this.opts.orchestratorAlg,
|
|
532
|
-
privateKey: this.orchestratorKey.privateKey.export({ format: "jwk" }),
|
|
533
|
-
publicJwk: this.orchestratorKey.publicJwk,
|
|
534
|
-
createdAt: this.orchestratorKey.createdAt,
|
|
535
|
-
version: 1
|
|
536
|
-
});
|
|
537
|
-
} catch (error) {
|
|
538
|
-
this.logger.warn("[JwksService] Failed to persist dev key: %s", error.message);
|
|
539
|
-
}
|
|
457
|
+
if (!this.orchestratorKey || Date.now() - this.orchestratorKey.createdAt > maxAge) {
|
|
458
|
+
this.orchestratorKey = this.generateKey(this.opts.orchestratorAlg);
|
|
540
459
|
}
|
|
541
460
|
}
|
|
542
461
|
generateKey(alg) {
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// libs/auth/src/jwks/local-token-keys.ts
|
|
2
|
+
import { base64urlDecode, sha256Base64url } from "@frontmcp/utils";
|
|
3
|
+
import { SigningKeyInvalidError } from "../errors/auth-internal.errors.mjs";
|
|
4
|
+
import { loadJose } from "./jose.loader.mjs";
|
|
5
|
+
var HMAC_MIN_BYTES = { HS256: 32, HS384: 48, HS512: 64 };
|
|
6
|
+
var ALGS_BY_KTY = {
|
|
7
|
+
RSA: ["RS256", "RS384", "RS512", "PS256", "PS384", "PS512"],
|
|
8
|
+
EC: ["ES256", "ES384", "ES512"],
|
|
9
|
+
OKP: ["EdDSA", "Ed25519"]
|
|
10
|
+
};
|
|
11
|
+
var EC_ALG_BY_CURVE = { "P-256": "ES256", "P-384": "ES384", "P-521": "ES512" };
|
|
12
|
+
var PUBLIC_MEMBERS = {
|
|
13
|
+
RSA: ["e", "kty", "n"],
|
|
14
|
+
EC: ["crv", "kty", "x", "y"],
|
|
15
|
+
OKP: ["crv", "kty", "x"]
|
|
16
|
+
};
|
|
17
|
+
var MIN_RSA_MODULUS_BYTES = 256;
|
|
18
|
+
function isAsymmetricKty(kty) {
|
|
19
|
+
return kty === "RSA" || kty === "EC" || kty === "OKP";
|
|
20
|
+
}
|
|
21
|
+
var LocalTokenKeys = class _LocalTokenKeys {
|
|
22
|
+
constructor(alg, kid, publicJwks, algorithms, hmacKey, privateJwk) {
|
|
23
|
+
this.alg = alg;
|
|
24
|
+
this.kid = kid;
|
|
25
|
+
this.publicJwks = publicJwks;
|
|
26
|
+
this.algorithms = algorithms;
|
|
27
|
+
this.hmacKey = hmacKey;
|
|
28
|
+
this.privateJwk = privateJwk;
|
|
29
|
+
}
|
|
30
|
+
alg;
|
|
31
|
+
kid;
|
|
32
|
+
publicJwks;
|
|
33
|
+
algorithms;
|
|
34
|
+
hmacKey;
|
|
35
|
+
privateJwk;
|
|
36
|
+
importedKey;
|
|
37
|
+
/** The published keys as one JWK set, built once: the set imports each key on its first use and keeps it. */
|
|
38
|
+
publicKeySet;
|
|
39
|
+
/** Throws {@link SigningKeyInvalidError} when a configured key can't sign or be published. */
|
|
40
|
+
static fromConfig(config, fallbackSecret, optionPath) {
|
|
41
|
+
const { signKey, jwks } = config;
|
|
42
|
+
if (signKey !== void 0 && !(signKey instanceof Uint8Array) && signKey.kty !== "oct") {
|
|
43
|
+
return _LocalTokenKeys.asymmetric(signKey, jwks?.keys ?? [], optionPath);
|
|
44
|
+
}
|
|
45
|
+
if (jwks?.keys?.length) {
|
|
46
|
+
const hmacSource = signKey ? `${optionPath}.signKey` : "JWT_SECRET";
|
|
47
|
+
throw new SigningKeyInvalidError(
|
|
48
|
+
`${optionPath}.jwks is set, but tokens are signed with an HMAC key (${hmacSource}), which is never published. Set an RSA, EC or Ed25519 private key as ${optionPath}.signKey.`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const hmac = _LocalTokenKeys.hmacKeyOf(signKey, fallbackSecret, `${optionPath}.signKey`);
|
|
52
|
+
return new _LocalTokenKeys(hmac.alg, hmac.kid, { keys: [] }, [hmac.alg], hmac.key, void 0);
|
|
53
|
+
}
|
|
54
|
+
protectedHeader() {
|
|
55
|
+
return this.kid ? { alg: this.alg, typ: "JWT", kid: this.kid } : { alg: this.alg, typ: "JWT" };
|
|
56
|
+
}
|
|
57
|
+
/** The key `SignJWT.sign` takes; a private JWK is imported once. */
|
|
58
|
+
signingKey() {
|
|
59
|
+
if (this.hmacKey) return Promise.resolve(this.hmacKey);
|
|
60
|
+
this.importedKey ??= loadJose().then(({ importJWK }) => importJWK(this.privateJwk, this.alg)).catch((error) => {
|
|
61
|
+
this.importedKey = void 0;
|
|
62
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
63
|
+
throw new SigningKeyInvalidError(`The configured signKey can't be imported: ${reason}`);
|
|
64
|
+
});
|
|
65
|
+
return this.importedKey;
|
|
66
|
+
}
|
|
67
|
+
/** Verify a token's signature, pinned to the algorithms these keys sign with. */
|
|
68
|
+
async verify(token, options) {
|
|
69
|
+
const { createLocalJWKSet, jwtVerify } = await loadJose();
|
|
70
|
+
const verifyOptions = { ...options, algorithms: this.algorithms };
|
|
71
|
+
if (this.hmacKey) return jwtVerify(token, this.hmacKey, verifyOptions);
|
|
72
|
+
this.publicKeySet ??= createLocalJWKSet(this.publicJwks);
|
|
73
|
+
return jwtVerify(token, this.publicKeySet, verifyOptions);
|
|
74
|
+
}
|
|
75
|
+
static asymmetric(signKey, jwksKeys, optionPath) {
|
|
76
|
+
const signing = _LocalTokenKeys.publicKeyOf(signKey, `${optionPath}.signKey`, "private");
|
|
77
|
+
const published = [signing];
|
|
78
|
+
jwksKeys.forEach((key, index) => {
|
|
79
|
+
const publicKey = _LocalTokenKeys.publicKeyOf(key, `${optionPath}.jwks.keys[${index}]`, "public");
|
|
80
|
+
if (!published.some((existing) => existing.kid === publicKey.kid)) published.push(publicKey);
|
|
81
|
+
});
|
|
82
|
+
const algorithms = [...new Set(published.map((key) => key.alg))];
|
|
83
|
+
return new _LocalTokenKeys(signing.alg, signing.kid, { keys: published }, algorithms, void 0, signKey);
|
|
84
|
+
}
|
|
85
|
+
static hmacKeyOf(signKey, fallbackSecret, path) {
|
|
86
|
+
if (signKey === void 0) return { alg: "HS256", key: fallbackSecret };
|
|
87
|
+
if (signKey instanceof Uint8Array) return { alg: "HS256", key: _LocalTokenKeys.longEnough(signKey, "HS256", path) };
|
|
88
|
+
const alg = signKey.alg ?? "HS256";
|
|
89
|
+
if (!(alg in HMAC_MIN_BYTES)) {
|
|
90
|
+
throw new SigningKeyInvalidError(`${path} is an oct key with alg "${alg}"; use HS256, HS384 or HS512.`);
|
|
91
|
+
}
|
|
92
|
+
if (typeof signKey.k !== "string" || signKey.k.length === 0) {
|
|
93
|
+
throw new SigningKeyInvalidError(`${path} is an oct key without "k".`);
|
|
94
|
+
}
|
|
95
|
+
return { alg, kid: signKey.kid, key: _LocalTokenKeys.longEnough(base64urlDecode(signKey.k), alg, path) };
|
|
96
|
+
}
|
|
97
|
+
static longEnough(key, alg, path) {
|
|
98
|
+
const minBytes = HMAC_MIN_BYTES[alg];
|
|
99
|
+
if (key.length < minBytes) {
|
|
100
|
+
throw new SigningKeyInvalidError(
|
|
101
|
+
`${path} is ${key.length} bytes; ${alg} requires at least ${minBytes} (RFC 7518 \xA73.2).`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return key;
|
|
105
|
+
}
|
|
106
|
+
/** The public half of `key` with its `kid` (else its RFC 7638 thumbprint), `alg` and `use`. */
|
|
107
|
+
static publicKeyOf(key, path, expected) {
|
|
108
|
+
if (!isAsymmetricKty(key.kty)) {
|
|
109
|
+
throw new SigningKeyInvalidError(`${path} has kty "${String(key.kty)}"; use an RSA, EC or OKP key.`);
|
|
110
|
+
}
|
|
111
|
+
const isPrivate = typeof key.d === "string";
|
|
112
|
+
if (expected === "private" && !isPrivate) {
|
|
113
|
+
throw new SigningKeyInvalidError(`${path} is a public key (it has no "d"); it must be the private key.`);
|
|
114
|
+
}
|
|
115
|
+
if (expected === "public" && isPrivate) {
|
|
116
|
+
throw new SigningKeyInvalidError(
|
|
117
|
+
`${path} holds a private key; the jwks are published, so list public keys only.`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
if (key.use !== void 0 && key.use !== "sig") {
|
|
121
|
+
throw new SigningKeyInvalidError(`${path} has use "${key.use}"; a signing key needs use "sig" or none.`);
|
|
122
|
+
}
|
|
123
|
+
const alg = key.alg ?? _LocalTokenKeys.defaultAlg(key);
|
|
124
|
+
if (!ALGS_BY_KTY[key.kty].includes(alg)) {
|
|
125
|
+
throw new SigningKeyInvalidError(`${path} has alg "${alg}", which ${key.kty} keys can't sign with.`);
|
|
126
|
+
}
|
|
127
|
+
if (key.kty === "EC" && EC_ALG_BY_CURVE[key.crv ?? ""] !== alg) {
|
|
128
|
+
throw new SigningKeyInvalidError(
|
|
129
|
+
`${path} has curve "${String(key.crv)}" and alg "${alg}"; use P-256/ES256, P-384/ES384 or P-521/ES512.`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
if (key.kty === "OKP" && key.crv !== "Ed25519") {
|
|
133
|
+
throw new SigningKeyInvalidError(`${path} is an OKP key on "${String(key.crv)}"; only Ed25519 signs.`);
|
|
134
|
+
}
|
|
135
|
+
const members = PUBLIC_MEMBERS[key.kty];
|
|
136
|
+
const missing = members.filter((member) => typeof key[member] !== "string");
|
|
137
|
+
if (missing.length > 0) {
|
|
138
|
+
throw new SigningKeyInvalidError(`${path} is missing ${missing.map((member) => `"${member}"`).join(", ")}.`);
|
|
139
|
+
}
|
|
140
|
+
if (key.kty === "RSA" && base64urlDecode(key.n).length < MIN_RSA_MODULUS_BYTES) {
|
|
141
|
+
throw new SigningKeyInvalidError(`${path} is an RSA key shorter than 2048 bits.`);
|
|
142
|
+
}
|
|
143
|
+
const publicMembers = Object.fromEntries(members.map((member) => [member, key[member]]));
|
|
144
|
+
const kid = key.kid ?? sha256Base64url(JSON.stringify(publicMembers));
|
|
145
|
+
return { ...publicMembers, kid, alg, use: "sig" };
|
|
146
|
+
}
|
|
147
|
+
static defaultAlg(key) {
|
|
148
|
+
if (key.kty === "RSA") return "RS256";
|
|
149
|
+
if (key.kty === "OKP") return "EdDSA";
|
|
150
|
+
return EC_ALG_BY_CURVE[key.crv ?? ""] ?? "ES256";
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
export {
|
|
154
|
+
LocalTokenKeys
|
|
155
|
+
};
|
|
@@ -24,13 +24,16 @@ var publicAuthOptionsSchema = z.object({
|
|
|
24
24
|
*/
|
|
25
25
|
publicAccess: publicAccessConfigSchema.optional(),
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
27
|
+
* Public keys published at `/.well-known/jwks.json` beside the `signKey`'s, and accepted on verification
|
|
28
|
+
* (e.g. a retired key's, while its tokens expire). Needs an RSA, EC or Ed25519 `signKey`.
|
|
29
|
+
* @default the `signKey`'s public key only
|
|
29
30
|
*/
|
|
30
31
|
jwks: jsonWebKeySetSchema.optional(),
|
|
31
32
|
/**
|
|
32
|
-
* Private key
|
|
33
|
-
*
|
|
33
|
+
* Private key to sign tokens with, in place of `JWT_SECRET` (HS256).
|
|
34
|
+
* An RSA, EC or Ed25519 private JWK signs with its `alg` (default by key type) and is published at
|
|
35
|
+
* `/.well-known/jwks.json`; a `Uint8Array` or `oct` JWK is an HMAC key and is never published.
|
|
36
|
+
* @default HS256 with `JWT_SECRET`
|
|
34
37
|
*/
|
|
35
38
|
signKey: jwkSchema.or(z.instanceof(Uint8Array)).optional()
|
|
36
39
|
});
|
|
@@ -30,13 +30,16 @@ var publicAccessConfigSchema = z.object({
|
|
|
30
30
|
});
|
|
31
31
|
var localSigningConfigSchema = z.object({
|
|
32
32
|
/**
|
|
33
|
-
* Private key
|
|
34
|
-
*
|
|
33
|
+
* Private key to sign tokens with, in place of `JWT_SECRET` (HS256).
|
|
34
|
+
* An RSA, EC or Ed25519 private JWK signs with its `alg` (default by key type) and is published at
|
|
35
|
+
* `/.well-known/jwks.json`; a `Uint8Array` or `oct` JWK is an HMAC key and is never published.
|
|
36
|
+
* @default HS256 with `JWT_SECRET`
|
|
35
37
|
*/
|
|
36
38
|
signKey: jwkSchema.or(z.instanceof(Uint8Array)).optional(),
|
|
37
39
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
+
* Public keys published at `/.well-known/jwks.json` beside the `signKey`'s, and accepted on verification
|
|
41
|
+
* (e.g. a retired key's, while its tokens expire). Needs an RSA, EC or Ed25519 `signKey`.
|
|
42
|
+
* @default the `signKey`'s public key only
|
|
40
43
|
*/
|
|
41
44
|
jwks: jsonWebKeySetSchema.optional(),
|
|
42
45
|
/**
|
|
@@ -80,7 +83,11 @@ var providerConfigSchema = z.object({
|
|
|
80
83
|
*/
|
|
81
84
|
verifyIssuer: z.boolean().optional(),
|
|
82
85
|
/**
|
|
83
|
-
*
|
|
86
|
+
* Remote mode: without a `clientId`, register this server with the provider by
|
|
87
|
+
* dynamic client registration (RFC 7591) at startup, and sign in with that client.
|
|
88
|
+
* With a persistent `tokenStorage` the client is kept there (its secret encrypted
|
|
89
|
+
* with `JWT_SECRET`) and reused across restarts and instances. Startup fails when
|
|
90
|
+
* the registration fails. Ignored when `clientId` is set, and in transparent mode.
|
|
84
91
|
* @default false
|
|
85
92
|
*/
|
|
86
93
|
dcrEnabled: z.boolean().default(false),
|
|
@@ -88,7 +95,10 @@ var providerConfigSchema = z.object({
|
|
|
88
95
|
authEndpoint: z.string().url().optional(),
|
|
89
96
|
/** Token endpoint override */
|
|
90
97
|
tokenEndpoint: z.string().url().optional(),
|
|
91
|
-
/**
|
|
98
|
+
/**
|
|
99
|
+
* Registration endpoint for `dcrEnabled`.
|
|
100
|
+
* @default the `registration_endpoint` of the provider's RFC 8414 or OpenID metadata
|
|
101
|
+
*/
|
|
92
102
|
registrationEndpoint: z.string().url().optional(),
|
|
93
103
|
/** User info endpoint override */
|
|
94
104
|
userInfoEndpoint: z.string().url().optional()
|
|
@@ -130,7 +140,11 @@ var remoteProviderConfigSchema = z.object({
|
|
|
130
140
|
*/
|
|
131
141
|
scopes: z.array(z.string()).optional(),
|
|
132
142
|
/**
|
|
133
|
-
*
|
|
143
|
+
* Remote mode: without a `clientId`, register this server with the provider by
|
|
144
|
+
* dynamic client registration (RFC 7591) at startup, and sign in with that client.
|
|
145
|
+
* With a persistent `tokenStorage` the client is kept there (its secret encrypted
|
|
146
|
+
* with `JWT_SECRET`) and reused across restarts and instances. Startup fails when
|
|
147
|
+
* the registration fails. Ignored when `clientId` is set, and in transparent mode.
|
|
134
148
|
* @default false
|
|
135
149
|
*/
|
|
136
150
|
dcrEnabled: z.boolean().default(false),
|
|
@@ -143,7 +157,8 @@ var remoteProviderConfigSchema = z.object({
|
|
|
143
157
|
*/
|
|
144
158
|
tokenEndpoint: z.string().url().optional(),
|
|
145
159
|
/**
|
|
146
|
-
* Registration endpoint
|
|
160
|
+
* Registration endpoint for `dcrEnabled`.
|
|
161
|
+
* @default the `registration_endpoint` of the provider's RFC 8414 or OpenID metadata
|
|
147
162
|
*/
|
|
148
163
|
registrationEndpoint: z.string().url().optional(),
|
|
149
164
|
/**
|
|
@@ -212,11 +227,11 @@ var localDcrClientSchema = z.object({
|
|
|
212
227
|
* Token-endpoint auth method. Defaults to `'none'` (public PKCE client).
|
|
213
228
|
*/
|
|
214
229
|
tokenEndpointAuthMethod: z.enum(["none", "client_secret_basic", "client_secret_post"]).default("none"),
|
|
215
|
-
/** Allowed grant types. Defaults to
|
|
216
|
-
grantTypes: z.array(z.enum(["authorization_code", "refresh_token"])).default(["authorization_code"]),
|
|
230
|
+
/** Allowed grant types; without `refresh_token`, the client gets no refresh token. Defaults to both. */
|
|
231
|
+
grantTypes: z.array(z.enum(["authorization_code", "refresh_token"])).default(["authorization_code", "refresh_token"]),
|
|
217
232
|
/** Allowed response types. Defaults to `['code']`. */
|
|
218
233
|
responseTypes: z.array(z.enum(["code"])).default(["code"]),
|
|
219
|
-
/**
|
|
234
|
+
/** Space-delimited scopes this client may be granted; a request for others is narrowed to these. */
|
|
220
235
|
scope: z.string().optional()
|
|
221
236
|
});
|
|
222
237
|
var localDcrConfigSchema = z.object({
|
|
@@ -469,12 +484,22 @@ var incrementalAuthConfigSchema = z.object({
|
|
|
469
484
|
*/
|
|
470
485
|
skippedAppBehavior: skippedAppBehaviorSchema.default("anonymous"),
|
|
471
486
|
/**
|
|
472
|
-
* Allow users to skip app authorization during initial auth flow
|
|
487
|
+
* Allow users to skip app authorization during initial auth flow. With
|
|
488
|
+
* `false`, a sign-in through the provider picker must link the provider of
|
|
489
|
+
* every app with its own `auth`: the picker shows them checked and required,
|
|
490
|
+
* and the callbacks refuse a sign-in that leaves one out or declines it.
|
|
473
491
|
* @default true
|
|
474
492
|
*/
|
|
475
493
|
allowSkip: z.boolean().default(true),
|
|
476
494
|
/**
|
|
477
|
-
* Show
|
|
495
|
+
* Show every provider on one picker page (`true`), or one per page (`false`):
|
|
496
|
+
* the sign-in then walks the picker's providers in order, and each step
|
|
497
|
+
* links or skips one of them (with Back to change an answer). Each step is
|
|
498
|
+
* checked server-side: a CSRF token, and `allowSkip` and
|
|
499
|
+
* `federatedAuth.requiredProviders` refuse skipping a required provider.
|
|
500
|
+
* The last step carries the sign-in fields, and finishes the sign-in as the
|
|
501
|
+
* one-page picker does. A custom `@AuthUi({ slot: 'federated' })` page gets
|
|
502
|
+
* the step in `extras.step` (see `FederatedStep`).
|
|
478
503
|
* @default true
|
|
479
504
|
*/
|
|
480
505
|
showAllAppsAtOnce: z.boolean().default(true)
|
|
@@ -88,6 +88,7 @@ var InMemoryFederatedAuthSessionStore = class {
|
|
|
88
88
|
completedProviders: /* @__PURE__ */ new Map(),
|
|
89
89
|
skippedProviders: [],
|
|
90
90
|
signinBinding: params.signinBinding,
|
|
91
|
+
authorizedAppIds: params.authorizedAppIds,
|
|
91
92
|
createdAt: now,
|
|
92
93
|
expiresAt: now + this.sessionTtlMs
|
|
93
94
|
};
|
|
@@ -121,6 +122,7 @@ function createFederatedAuthSession(params, ttlMs = 15 * 60 * 1e3) {
|
|
|
121
122
|
completedProviders: /* @__PURE__ */ new Map(),
|
|
122
123
|
skippedProviders: [],
|
|
123
124
|
signinBinding: params.signinBinding,
|
|
125
|
+
authorizedAppIds: params.authorizedAppIds,
|
|
124
126
|
createdAt: now,
|
|
125
127
|
expiresAt: now + ttlMs
|
|
126
128
|
};
|
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
// libs/auth/src/session/incremental-auth-ticket.ts
|
|
2
2
|
import { randomUUID, signData, verifyData } from "@frontmcp/utils";
|
|
3
|
+
import { openPendingLogin, sealPendingLogin } from "./pending-login.mjs";
|
|
3
4
|
var DEFAULT_INCREMENTAL_TICKET_TTL_MS = 3e5;
|
|
5
|
+
function profileKey(secret) {
|
|
6
|
+
return new TextEncoder().encode(secret);
|
|
7
|
+
}
|
|
8
|
+
function profileBinding(jti) {
|
|
9
|
+
return `incremental-ticket:${jti}`;
|
|
10
|
+
}
|
|
4
11
|
function signIncrementalAuthTicket(input, secret) {
|
|
12
|
+
const jti = randomUUID();
|
|
13
|
+
const hasProfile = input.email !== void 0 || input.name !== void 0;
|
|
5
14
|
const payload = {
|
|
6
15
|
sub: input.sub,
|
|
7
16
|
appId: input.appId,
|
|
8
17
|
...input.toolId !== void 0 ? { toolId: input.toolId } : {},
|
|
9
18
|
...input.priorAppIds !== void 0 ? { priorAppIds: input.priorAppIds } : {},
|
|
10
|
-
|
|
19
|
+
...hasProfile ? {
|
|
20
|
+
profile: sealPendingLogin(profileKey(secret), profileBinding(jti), {
|
|
21
|
+
sub: input.sub,
|
|
22
|
+
email: input.email,
|
|
23
|
+
name: input.name
|
|
24
|
+
})
|
|
25
|
+
} : {},
|
|
26
|
+
jti,
|
|
11
27
|
exp: Date.now() + (input.ttlMs ?? DEFAULT_INCREMENTAL_TICKET_TTL_MS)
|
|
12
28
|
};
|
|
13
29
|
const signedJson = signData(payload, { secret });
|
|
@@ -27,6 +43,7 @@ function verifyIncrementalAuthTicket(token, secret, now = Date.now()) {
|
|
|
27
43
|
if (typeof payload.jti !== "string" || payload.jti.length === 0) return null;
|
|
28
44
|
if (!Number.isFinite(payload.exp)) return null;
|
|
29
45
|
if (payload.toolId !== void 0 && typeof payload.toolId !== "string") return null;
|
|
46
|
+
if (payload.profile !== void 0 && typeof payload.profile !== "string") return null;
|
|
30
47
|
if (payload.priorAppIds !== void 0 && (!Array.isArray(payload.priorAppIds) || payload.priorAppIds.some((id) => typeof id !== "string"))) {
|
|
31
48
|
return null;
|
|
32
49
|
}
|
|
@@ -35,8 +52,14 @@ function verifyIncrementalAuthTicket(token, secret, now = Date.now()) {
|
|
|
35
52
|
}
|
|
36
53
|
return payload;
|
|
37
54
|
}
|
|
55
|
+
function openIncrementalAuthTicketProfile(payload, secret) {
|
|
56
|
+
if (!payload.profile) return void 0;
|
|
57
|
+
const login = openPendingLogin(profileKey(secret), profileBinding(payload.jti), payload.profile);
|
|
58
|
+
return login?.sub === payload.sub ? { sub: login.sub, email: login.email, name: login.name } : void 0;
|
|
59
|
+
}
|
|
38
60
|
export {
|
|
39
61
|
DEFAULT_INCREMENTAL_TICKET_TTL_MS,
|
|
62
|
+
openIncrementalAuthTicketProfile,
|
|
40
63
|
signIncrementalAuthTicket,
|
|
41
64
|
verifyIncrementalAuthTicket
|
|
42
65
|
};
|
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
import {
|
|
56
56
|
signIncrementalAuthTicket,
|
|
57
57
|
verifyIncrementalAuthTicket,
|
|
58
|
+
openIncrementalAuthTicketProfile,
|
|
58
59
|
DEFAULT_INCREMENTAL_TICKET_TTL_MS
|
|
59
60
|
} from "./incremental-auth-ticket.mjs";
|
|
60
61
|
import { CREDENTIALS_ACCESSOR } from "./credentials-accessor.mjs";
|
|
@@ -100,6 +101,7 @@ import {
|
|
|
100
101
|
decryptSessionJson,
|
|
101
102
|
safeDecrypt,
|
|
102
103
|
resetCachedKey,
|
|
104
|
+
isSessionSecretConfigured,
|
|
103
105
|
TinyTtlCache
|
|
104
106
|
} from "./utils/index.mjs";
|
|
105
107
|
import {
|
|
@@ -219,6 +221,7 @@ export {
|
|
|
219
221
|
isPersistentTokenStorage,
|
|
220
222
|
isRedisTokenStorage,
|
|
221
223
|
isSessionComplete,
|
|
224
|
+
isSessionSecretConfigured,
|
|
222
225
|
isSignedSession,
|
|
223
226
|
isSoonExpiring,
|
|
224
227
|
isSoonExpiringProvider,
|
|
@@ -226,6 +229,7 @@ export {
|
|
|
226
229
|
legacySseTransportStateSchema,
|
|
227
230
|
mtlsCredentialSchema,
|
|
228
231
|
oauthCredentialSchema,
|
|
232
|
+
openIncrementalAuthTicketProfile,
|
|
229
233
|
openPendingLogin,
|
|
230
234
|
pendingIncrementalAuthSchema,
|
|
231
235
|
pkceChallengeSchema,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// libs/auth/src/session/redis-session.store.ts
|
|
2
2
|
import { randomUUID, RedisStorageAdapter } from "@frontmcp/utils";
|
|
3
|
+
import { SessionIdEmptyError } from "../errors/auth-internal.errors.mjs";
|
|
4
|
+
import { signSession, verifyOrParseSession } from "./session-crypto.mjs";
|
|
5
|
+
import { SessionRateLimiter } from "./session-rate-limiter.mjs";
|
|
3
6
|
import {
|
|
4
7
|
storedSessionSchema
|
|
5
8
|
} from "./transport-session.types.mjs";
|
|
6
|
-
import { signSession, verifyOrParseSession } from "./session-crypto.mjs";
|
|
7
|
-
import { SessionRateLimiter } from "./session-rate-limiter.mjs";
|
|
8
|
-
import { SessionIdEmptyError } from "../errors/auth-internal.errors.mjs";
|
|
9
9
|
var RedisSessionStore = class {
|
|
10
10
|
storage;
|
|
11
11
|
keyPrefix;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// libs/auth/src/session/session-crypto.ts
|
|
2
2
|
import {
|
|
3
|
+
getEnv,
|
|
4
|
+
isProduction,
|
|
5
|
+
isSignedData,
|
|
3
6
|
signData,
|
|
4
7
|
verifyData,
|
|
5
|
-
|
|
6
|
-
verifyOrParseData,
|
|
7
|
-
getEnv,
|
|
8
|
-
isProduction
|
|
8
|
+
verifyOrParseData
|
|
9
9
|
} from "@frontmcp/utils";
|
|
10
10
|
import { SessionSecretRequiredError } from "../errors/auth-internal.errors.mjs";
|
|
11
11
|
function getSigningSecret(config) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// libs/auth/src/session/storage/storage-authorization-vault.ts
|
|
2
2
|
import { randomUUID, TypedStorage } from "@frontmcp/utils";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
authorizationVaultEntrySchema
|
|
5
|
+
} from "../authorization-vault.mjs";
|
|
4
6
|
var StorageAuthorizationVault = class {
|
|
5
7
|
storage;
|
|
6
8
|
namespace;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// libs/auth/src/session/token.vault.ts
|
|
2
|
-
import {
|
|
2
|
+
import { base64urlDecode, base64urlEncode, decryptAesGcm, encryptAesGcm, randomBytes } from "@frontmcp/utils";
|
|
3
3
|
var TokenVault = class {
|
|
4
4
|
/** Active key used for new encryptions */
|
|
5
5
|
active;
|
|
@@ -7,7 +7,14 @@ import {
|
|
|
7
7
|
anonymousCallerClaims,
|
|
8
8
|
extractBearerToken
|
|
9
9
|
} from "./auth-token.utils.mjs";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
getKey,
|
|
12
|
+
encryptJson,
|
|
13
|
+
decryptSessionJson,
|
|
14
|
+
safeDecrypt,
|
|
15
|
+
resetCachedKey,
|
|
16
|
+
isSessionSecretConfigured
|
|
17
|
+
} from "./session-crypto.utils.mjs";
|
|
11
18
|
export {
|
|
12
19
|
TinyTtlCache,
|
|
13
20
|
anonymousCallerClaims,
|
|
@@ -18,6 +25,7 @@ export {
|
|
|
18
25
|
getKey,
|
|
19
26
|
getTokenSignatureFingerprint,
|
|
20
27
|
isJwt,
|
|
28
|
+
isSessionSecretConfigured,
|
|
21
29
|
resetCachedKey,
|
|
22
30
|
safeDecrypt
|
|
23
31
|
};
|
|
@@ -17,6 +17,9 @@ function getKey() {
|
|
|
17
17
|
cachedKey = sha256(new TextEncoder().encode(base));
|
|
18
18
|
return cachedKey;
|
|
19
19
|
}
|
|
20
|
+
function isSessionSecretConfigured() {
|
|
21
|
+
return !!getEnv("MCP_SESSION_SECRET");
|
|
22
|
+
}
|
|
20
23
|
function encryptJson(obj) {
|
|
21
24
|
const key = getKey();
|
|
22
25
|
const encrypted = encryptValue(obj, key);
|
|
@@ -44,6 +47,7 @@ export {
|
|
|
44
47
|
decryptSessionJson,
|
|
45
48
|
encryptJson,
|
|
46
49
|
getKey,
|
|
50
|
+
isSessionSecretConfigured,
|
|
47
51
|
resetCachedKey,
|
|
48
52
|
safeDecrypt
|
|
49
53
|
};
|
package/browser/ui/index.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
buildConsentPage,
|
|
17
17
|
buildIncrementalAuthPage,
|
|
18
18
|
buildFederatedLoginPage,
|
|
19
|
+
buildFederatedStepPage,
|
|
19
20
|
buildToolConsentPage,
|
|
20
21
|
buildLoginPage,
|
|
21
22
|
buildConnectPage,
|
|
@@ -35,6 +36,7 @@ export {
|
|
|
35
36
|
buildConsentPage,
|
|
36
37
|
buildErrorPage,
|
|
37
38
|
buildFederatedLoginPage,
|
|
39
|
+
buildFederatedStepPage,
|
|
38
40
|
buildIncrementalAuthPage,
|
|
39
41
|
buildLoginPage,
|
|
40
42
|
buildToolConsentPage,
|