@frontmcp/auth 1.10.16 → 1.11.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/browser/authorities/authorities.context.mjs +115 -0
  2. package/browser/authorities/authorities.engine.mjs +272 -0
  3. package/browser/authorities/authorities.errors.mjs +59 -0
  4. package/browser/authorities/authorities.evaluator.mjs +225 -0
  5. package/browser/authorities/authorities.metadata-augment.mjs +18 -0
  6. package/browser/authorities/authorities.registry.mjs +112 -0
  7. package/browser/authorities/authorities.schema.mjs +92 -0
  8. package/browser/authorities/authorities.scope-mapping.mjs +30 -0
  9. package/browser/authorities/authorities.validation.mjs +208 -0
  10. package/browser/authorities/index.mjs +43 -0
  11. package/browser/authorization/authorization.class.mjs +252 -0
  12. package/browser/authorization/authorization.types.mjs +64 -0
  13. package/browser/authorization/dcr-client.registry.mjs +222 -0
  14. package/browser/authorization/index.mjs +41 -0
  15. package/browser/authorization/orchestrated.accessor.mjs +86 -0
  16. package/browser/authorization/orchestrated.authorization.mjs +289 -0
  17. package/browser/authorization/public.authorization.mjs +124 -0
  18. package/browser/authorization/transparent.authorization.mjs +130 -0
  19. package/browser/cimd/cimd-redis.cache.mjs +231 -0
  20. package/browser/cimd/cimd.cache.mjs +215 -0
  21. package/browser/cimd/cimd.errors.mjs +142 -0
  22. package/browser/cimd/cimd.logger.mjs +15 -0
  23. package/browser/cimd/cimd.service.mjs +376 -0
  24. package/browser/cimd/cimd.types.mjs +228 -0
  25. package/browser/cimd/cimd.validator.mjs +217 -0
  26. package/browser/cimd/index.mjs +63 -0
  27. package/browser/common/auth-logger.interface.mjs +14 -0
  28. package/browser/common/index.mjs +6 -0
  29. package/browser/common/jwt.types.mjs +38 -0
  30. package/browser/common/session-user.types.mjs +0 -0
  31. package/browser/common/session.types.mjs +58 -0
  32. package/browser/common/zod-utils.mjs +0 -0
  33. package/browser/consent/consent.types.mjs +94 -0
  34. package/browser/consent/index.mjs +17 -0
  35. package/browser/context/frontmcp-auth-context.factory.mjs +29 -0
  36. package/browser/context/frontmcp-auth-context.impl.mjs +112 -0
  37. package/browser/context/index.mjs +7 -0
  38. package/browser/detection/auth-provider-detection.mjs +176 -0
  39. package/browser/detection/index.mjs +19 -0
  40. package/browser/errors/auth-internal.error.mjs +42 -0
  41. package/browser/errors/auth-internal.errors.mjs +150 -0
  42. package/browser/errors/index.mjs +53 -0
  43. package/browser/fetch/fetch-credential-middleware.mjs +91 -0
  44. package/browser/fetch/index.mjs +19 -0
  45. package/browser/index.mjs +573 -0
  46. package/browser/jwks/index.mjs +9 -0
  47. package/browser/jwks/jose.loader.mjs +28 -0
  48. package/browser/jwks/jwks.service.mjs +521 -0
  49. package/browser/jwks/jwks.utils.mjs +33 -0
  50. package/browser/options/app-auth.schema.mjs +18 -0
  51. package/browser/options/index.mjs +89 -0
  52. package/browser/options/orchestrated.schema.mjs +128 -0
  53. package/browser/options/public.schema.mjs +39 -0
  54. package/browser/options/schema.mjs +16 -0
  55. package/browser/options/shared.schemas.mjs +506 -0
  56. package/browser/options/static.schema.mjs +41 -0
  57. package/browser/options/transparent.schema.mjs +43 -0
  58. package/browser/options/utils.mjs +47 -0
  59. package/browser/people/index.mjs +15 -0
  60. package/browser/people/loopback-token.auth.mjs +115 -0
  61. package/browser/people/people-auth.options.mjs +37 -0
  62. package/browser/session/authorization-vault.mjs +279 -0
  63. package/browser/session/authorization.store.mjs +336 -0
  64. package/browser/session/consent.store.mjs +34 -0
  65. package/browser/session/credential-resume-link.mjs +43 -0
  66. package/browser/session/credentials-accessor.impl.mjs +54 -0
  67. package/browser/session/credentials-accessor.mjs +5 -0
  68. package/browser/session/encrypted-authorization-vault.mjs +405 -0
  69. package/browser/session/federated-auth.session.mjs +176 -0
  70. package/browser/session/incremental-auth-ticket.mjs +42 -0
  71. package/browser/session/index.mjs +266 -0
  72. package/browser/session/orchestrated-token.crypto.mjs +47 -0
  73. package/browser/session/orchestrated-token.store.mjs +286 -0
  74. package/browser/session/pending-login.mjs +37 -0
  75. package/browser/session/redis-session.store.mjs +255 -0
  76. package/browser/session/secure-store-accessor.impl.mjs +78 -0
  77. package/browser/session/secure-store-accessor.mjs +5 -0
  78. package/browser/session/secure-store-backends.mjs +111 -0
  79. package/browser/session/secure-store.factory.mjs +77 -0
  80. package/browser/session/session-credential-vault.factory.mjs +18 -0
  81. package/browser/session/session-credential-vault.mjs +243 -0
  82. package/browser/session/session-crypto.mjs +39 -0
  83. package/browser/session/session-rate-limiter.mjs +126 -0
  84. package/browser/session/session.transport.mjs +16 -0
  85. package/browser/session/signin-binding.mjs +30 -0
  86. package/browser/session/storage/in-memory-authorization-vault.mjs +28 -0
  87. package/browser/session/storage/index.mjs +14 -0
  88. package/browser/session/storage/storage-authorization-vault.mjs +301 -0
  89. package/browser/session/storage/storage-token-store.mjs +83 -0
  90. package/browser/session/storage-authorization.store.mjs +134 -0
  91. package/browser/session/storage-consent.store.mjs +33 -0
  92. package/browser/session/storage-federated-auth.session.mjs +55 -0
  93. package/browser/session/storage-orchestrated-token.store.mjs +197 -0
  94. package/browser/session/token-storage.factory.mjs +58 -0
  95. package/browser/session/token.refresh.mjs +36 -0
  96. package/browser/session/token.vault.mjs +61 -0
  97. package/browser/session/transport-session.types.mjs +108 -0
  98. package/browser/session/utils/auth-token.utils.mjs +58 -0
  99. package/browser/session/utils/index.mjs +23 -0
  100. package/browser/session/utils/session-crypto.utils.mjs +49 -0
  101. package/browser/session/utils/tiny-ttl-cache.mjs +46 -0
  102. package/browser/session/vault-encryption.mjs +187 -0
  103. package/browser/session/vercel-kv-session.store.mjs +230 -0
  104. package/browser/shared-server/index.mjs +17 -0
  105. package/browser/shared-server/shared-server-identity.mjs +55 -0
  106. package/browser/ui/base-layout.mjs +184 -0
  107. package/browser/ui/index.mjs +51 -0
  108. package/browser/ui/local-login.helper.mjs +46 -0
  109. package/browser/ui/templates.mjs +482 -0
  110. package/browser/utils/audience.validator.mjs +110 -0
  111. package/browser/utils/authorization-id.utils.mjs +10 -0
  112. package/browser/utils/index.mjs +45 -0
  113. package/browser/utils/redirect-uri.utils.mjs +22 -0
  114. package/browser/utils/scope-grant.utils.mjs +36 -0
  115. package/browser/utils/token-scopes.utils.mjs +12 -0
  116. package/browser/utils/www-authenticate.utils.mjs +164 -0
  117. package/browser/vault/auth-providers.accessor.impl.mjs +236 -0
  118. package/browser/vault/auth-providers.accessor.mjs +5 -0
  119. package/browser/vault/auth-providers.registry.mjs +140 -0
  120. package/browser/vault/auth-providers.types.mjs +47 -0
  121. package/browser/vault/auth-providers.vault.mjs +206 -0
  122. package/browser/vault/credential-cache.mjs +187 -0
  123. package/browser/vault/credential-helpers.mjs +15 -0
  124. package/browser/vault/credential-loaders/eager-loader.mjs +91 -0
  125. package/browser/vault/credential-loaders/lazy-loader.mjs +120 -0
  126. package/browser/vault/index.mjs +35 -0
  127. package/cimd/cimd.types.d.ts +1 -1
  128. package/cimd/cimd.types.d.ts.map +1 -1
  129. package/common/jwt.types.d.ts +1 -1
  130. package/common/jwt.types.d.ts.map +1 -1
  131. package/common/session.types.d.ts +9 -1
  132. package/common/session.types.d.ts.map +1 -1
  133. package/consent/consent.types.d.ts +1 -1
  134. package/consent/consent.types.d.ts.map +1 -1
  135. package/detection/auth-provider-detection.d.ts +1 -1
  136. package/detection/auth-provider-detection.d.ts.map +1 -1
  137. package/errors/auth-internal.errors.d.ts +6 -0
  138. package/errors/auth-internal.errors.d.ts.map +1 -1
  139. package/errors/index.d.ts +1 -1
  140. package/errors/index.d.ts.map +1 -1
  141. package/esm/index.mjs +2 -2
  142. package/esm/package.json +21 -5
  143. package/index.d.ts +5 -1
  144. package/index.d.ts.map +1 -1
  145. package/index.js +2 -2
  146. package/options/app-auth.schema.d.ts +19 -1
  147. package/options/app-auth.schema.d.ts.map +1 -1
  148. package/options/orchestrated.schema.d.ts +37 -1
  149. package/options/orchestrated.schema.d.ts.map +1 -1
  150. package/options/public.schema.d.ts +1 -1
  151. package/options/public.schema.d.ts.map +1 -1
  152. package/options/schema.d.ts +19 -1
  153. package/options/schema.d.ts.map +1 -1
  154. package/options/shared.schemas.d.ts +10 -14
  155. package/options/shared.schemas.d.ts.map +1 -1
  156. package/options/static.schema.d.ts +1 -1
  157. package/options/static.schema.d.ts.map +1 -1
  158. package/options/transparent.schema.d.ts +1 -1
  159. package/options/transparent.schema.d.ts.map +1 -1
  160. package/package.json +21 -5
  161. package/people/index.d.ts +3 -0
  162. package/people/index.d.ts.map +1 -0
  163. package/people/loopback-token.auth.d.ts +75 -0
  164. package/people/loopback-token.auth.d.ts.map +1 -0
  165. package/people/people-auth.options.d.ts +36 -0
  166. package/people/people-auth.options.d.ts.map +1 -0
  167. package/session/authorization-vault.d.ts +1 -1
  168. package/session/authorization-vault.d.ts.map +1 -1
  169. package/session/encrypted-authorization-vault.d.ts +1 -1
  170. package/session/encrypted-authorization-vault.d.ts.map +1 -1
  171. package/session/secure-store.factory.d.ts.map +1 -1
  172. package/shared-server/index.d.ts +3 -0
  173. package/shared-server/index.d.ts.map +1 -0
  174. package/shared-server/shared-server-identity.d.ts +26 -0
  175. package/shared-server/shared-server-identity.d.ts.map +1 -0
@@ -0,0 +1,110 @@
1
+ // libs/auth/src/utils/audience.validator.ts
2
+ function validateAudience(tokenAudience, options) {
3
+ const { expectedAudiences, allowNoAudience = false, caseSensitive = true, allowWildcards = false } = options;
4
+ if (tokenAudience === void 0 || tokenAudience === null) {
5
+ if (allowNoAudience) {
6
+ return { valid: true };
7
+ }
8
+ return {
9
+ valid: false,
10
+ error: "Token is missing audience claim"
11
+ };
12
+ }
13
+ if (expectedAudiences.length === 0) {
14
+ return {
15
+ valid: false,
16
+ error: "No expected audiences configured - cannot validate token"
17
+ };
18
+ }
19
+ const tokenAuds = Array.isArray(tokenAudience) ? tokenAudience : [tokenAudience];
20
+ for (const tokenAud of tokenAuds) {
21
+ for (const expectedAud of expectedAudiences) {
22
+ if (matchesAudience(tokenAud, expectedAud, caseSensitive, allowWildcards)) {
23
+ return { valid: true, matchedAudience: tokenAud };
24
+ }
25
+ }
26
+ }
27
+ return {
28
+ valid: false,
29
+ error: `Token audience does not match expected audiences. Got: ${tokenAuds.join(
30
+ ", "
31
+ )}. Expected one of: ${expectedAudiences.join(", ")}`
32
+ };
33
+ }
34
+ function matchesAudience(tokenAud, expectedAud, caseSensitive, allowWildcards) {
35
+ if (caseSensitive) {
36
+ if (tokenAud === expectedAud) return true;
37
+ } else {
38
+ if (tokenAud.toLowerCase() === expectedAud.toLowerCase()) return true;
39
+ }
40
+ if (allowWildcards && expectedAud.includes("*")) {
41
+ const wildcardCount = (expectedAud.match(/\*/g) || []).length;
42
+ if (wildcardCount > 2) {
43
+ return false;
44
+ }
45
+ const pattern = expectedAud.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^.]*");
46
+ const regex = new RegExp(`^${pattern}$`, caseSensitive ? "" : "i");
47
+ if (regex.test(tokenAud)) return true;
48
+ }
49
+ return false;
50
+ }
51
+ function createAudienceValidator(options) {
52
+ return (audience) => validateAudience(audience, options);
53
+ }
54
+ function deriveExpectedAudience(resourceUrl) {
55
+ const audiences = [];
56
+ try {
57
+ const url = new URL(resourceUrl);
58
+ audiences.push(resourceUrl.replace(/\/$/, ""));
59
+ if (url.pathname !== "/" && url.pathname !== "") {
60
+ audiences.push(url.origin);
61
+ }
62
+ } catch {
63
+ audiences.push(resourceUrl);
64
+ }
65
+ return [...new Set(audiences)];
66
+ }
67
+ var AudienceValidator = class _AudienceValidator {
68
+ options;
69
+ constructor(options = {}) {
70
+ this.options = {
71
+ expectedAudiences: [...options.expectedAudiences ?? []],
72
+ allowNoAudience: options.allowNoAudience ?? false,
73
+ caseSensitive: options.caseSensitive ?? true,
74
+ allowWildcards: options.allowWildcards ?? false
75
+ };
76
+ }
77
+ /**
78
+ * Validate an audience claim
79
+ */
80
+ validate(audience) {
81
+ return validateAudience(audience, this.options);
82
+ }
83
+ /**
84
+ * Add expected audiences
85
+ */
86
+ addAudiences(...audiences) {
87
+ this.options.expectedAudiences.push(...audiences);
88
+ }
89
+ /**
90
+ * Set expected audiences (replace existing)
91
+ */
92
+ setAudiences(audiences) {
93
+ this.options.expectedAudiences = audiences;
94
+ }
95
+ /**
96
+ * Create validator from resource URL
97
+ */
98
+ static fromResourceUrl(resourceUrl, options = {}) {
99
+ return new _AudienceValidator({
100
+ ...options,
101
+ expectedAudiences: deriveExpectedAudience(resourceUrl)
102
+ });
103
+ }
104
+ };
105
+ export {
106
+ AudienceValidator,
107
+ createAudienceValidator,
108
+ deriveExpectedAudience,
109
+ validateAudience
110
+ };
@@ -0,0 +1,10 @@
1
+ // libs/auth/src/utils/authorization-id.utils.ts
2
+ import { sha256Hex } from "@frontmcp/utils";
3
+ function deriveAuthorizationId(token) {
4
+ const parts = token.split(".");
5
+ const signature = parts[2] || token;
6
+ return sha256Hex(signature).substring(0, 16);
7
+ }
8
+ export {
9
+ deriveAuthorizationId
10
+ };
@@ -0,0 +1,45 @@
1
+ // libs/auth/src/utils/index.ts
2
+ import {
3
+ buildWwwAuthenticate,
4
+ buildPrmUrl,
5
+ buildUnauthorizedHeader,
6
+ buildInvalidTokenHeader,
7
+ buildInsufficientScopeHeader,
8
+ buildInvalidRequestHeader,
9
+ parseWwwAuthenticate
10
+ } from "./www-authenticate.utils.mjs";
11
+ import {
12
+ validateAudience,
13
+ createAudienceValidator,
14
+ deriveExpectedAudience,
15
+ AudienceValidator
16
+ } from "./audience.validator.mjs";
17
+ import { deriveAuthorizationId } from "./authorization-id.utils.mjs";
18
+ import {
19
+ advertisedScopes,
20
+ DEFAULT_ALLOWED_SCOPES,
21
+ grantScopes,
22
+ resourceScopesFor
23
+ } from "./scope-grant.utils.mjs";
24
+ import { isLoopbackRedirectUri } from "./redirect-uri.utils.mjs";
25
+ import { scopesFromClaims } from "./token-scopes.utils.mjs";
26
+ export {
27
+ AudienceValidator,
28
+ DEFAULT_ALLOWED_SCOPES,
29
+ advertisedScopes,
30
+ buildInsufficientScopeHeader,
31
+ buildInvalidRequestHeader,
32
+ buildInvalidTokenHeader,
33
+ buildPrmUrl,
34
+ buildUnauthorizedHeader,
35
+ buildWwwAuthenticate,
36
+ createAudienceValidator,
37
+ deriveAuthorizationId,
38
+ deriveExpectedAudience,
39
+ grantScopes,
40
+ isLoopbackRedirectUri,
41
+ parseWwwAuthenticate,
42
+ resourceScopesFor,
43
+ scopesFromClaims,
44
+ validateAudience
45
+ };
@@ -0,0 +1,22 @@
1
+ // libs/auth/src/utils/redirect-uri.utils.ts
2
+ function isLoopbackRedirectUri(uri) {
3
+ let url;
4
+ try {
5
+ url = new URL(uri);
6
+ } catch {
7
+ return false;
8
+ }
9
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
10
+ if (url.username || url.password) return false;
11
+ const host = url.hostname.replace(/^\[|\]$/g, "").toLowerCase();
12
+ if (host === "localhost" || host === "::1") return true;
13
+ const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
14
+ if (m) {
15
+ const octets = m.slice(1).map((n) => Number(n));
16
+ return octets.every((o) => o >= 0 && o <= 255) && octets[0] === 127;
17
+ }
18
+ return false;
19
+ }
20
+ export {
21
+ isLoopbackRedirectUri
22
+ };
@@ -0,0 +1,36 @@
1
+ // libs/auth/src/utils/scope-grant.utils.ts
2
+ import { globMatch } from "../authorization/dcr-client.registry.mjs";
3
+ var DEFAULT_ALLOWED_SCOPES = Object.freeze([
4
+ "openid",
5
+ "profile",
6
+ "email",
7
+ "offline_access"
8
+ ]);
9
+ function grantScopes(requested, allowed) {
10
+ const patterns = allowed ?? DEFAULT_ALLOWED_SCOPES;
11
+ const granted = [];
12
+ for (const scope of requested) {
13
+ if (!scope || granted.includes(scope)) continue;
14
+ if (patterns.some((pattern) => globMatch(pattern, scope))) granted.push(scope);
15
+ }
16
+ return granted;
17
+ }
18
+ function advertisedScopes(allowed) {
19
+ const scopes = [];
20
+ for (const scope of allowed ?? DEFAULT_ALLOWED_SCOPES) {
21
+ if (scope && !scope.includes("*") && !scopes.includes(scope)) scopes.push(scope);
22
+ }
23
+ return scopes;
24
+ }
25
+ function resourceScopesFor(options) {
26
+ const mode = options?.mode ?? "public";
27
+ if (mode === "local" || mode === "remote") return advertisedScopes(options?.allowedScopes);
28
+ const listed = mode === "public" ? options?.anonymousScopes ?? ["anonymous"] : mode === "static" ? options?.scopes ?? ["static"] : mode === "transparent" ? [...options?.requiredScopes ?? [], ...options?.scopes ?? []] : [];
29
+ return advertisedScopes(listed);
30
+ }
31
+ export {
32
+ DEFAULT_ALLOWED_SCOPES,
33
+ advertisedScopes,
34
+ grantScopes,
35
+ resourceScopesFor
36
+ };
@@ -0,0 +1,12 @@
1
+ // libs/auth/src/utils/token-scopes.utils.ts
2
+ function scopesFromClaims(claims) {
3
+ const scopes = [claims?.scope, claims?.scp].flatMap((claim) => {
4
+ if (typeof claim === "string") return claim.split(/\s+/).filter(Boolean);
5
+ if (Array.isArray(claim)) return claim.filter((scope) => typeof scope === "string");
6
+ return [];
7
+ });
8
+ return [...new Set(scopes)];
9
+ }
10
+ export {
11
+ scopesFromClaims
12
+ };
@@ -0,0 +1,164 @@
1
+ // libs/auth/src/utils/www-authenticate.utils.ts
2
+ function buildWwwAuthenticate(options = {}) {
3
+ const parts = ["Bearer"];
4
+ const params = [];
5
+ if (options.resourceMetadataUrl) {
6
+ params.push(`resource_metadata="${escapeQuotedString(options.resourceMetadataUrl)}"`);
7
+ }
8
+ if (options.realm) {
9
+ params.push(`realm="${escapeQuotedString(options.realm)}"`);
10
+ }
11
+ if (options.error) {
12
+ params.push(`error="${options.error}"`);
13
+ }
14
+ if (options.errorDescription) {
15
+ params.push(`error_description="${escapeQuotedString(options.errorDescription)}"`);
16
+ }
17
+ if (options.errorUri) {
18
+ params.push(`error_uri="${escapeQuotedString(options.errorUri)}"`);
19
+ }
20
+ if (options.scope) {
21
+ const scopeValue = Array.isArray(options.scope) ? options.scope.join(" ") : options.scope;
22
+ params.push(`scope="${escapeQuotedString(scopeValue)}"`);
23
+ }
24
+ if (params.length > 0) {
25
+ parts.push(params.join(", "));
26
+ }
27
+ return parts.join(" ");
28
+ }
29
+ function buildPrmUrl(baseUrl, entryPath, routeBase) {
30
+ const normalizedEntry = normalizePathSegment(entryPath);
31
+ const normalizedRoute = normalizePathSegment(routeBase);
32
+ return `${baseUrl}/.well-known/oauth-protected-resource${normalizedEntry}${normalizedRoute}`;
33
+ }
34
+ function buildUnauthorizedHeader(prmUrl) {
35
+ return buildWwwAuthenticate({
36
+ resourceMetadataUrl: prmUrl
37
+ });
38
+ }
39
+ function buildInvalidTokenHeader(prmUrl, description) {
40
+ return buildWwwAuthenticate({
41
+ resourceMetadataUrl: prmUrl,
42
+ error: "invalid_token",
43
+ errorDescription: description ?? "The access token is invalid or expired"
44
+ });
45
+ }
46
+ function buildInsufficientScopeHeader(prmUrl, requiredScopes, description) {
47
+ return buildWwwAuthenticate({
48
+ resourceMetadataUrl: prmUrl,
49
+ error: "insufficient_scope",
50
+ scope: requiredScopes,
51
+ errorDescription: description ?? "The request requires higher privileges"
52
+ });
53
+ }
54
+ function buildInvalidRequestHeader(prmUrl, description) {
55
+ return buildWwwAuthenticate({
56
+ resourceMetadataUrl: prmUrl,
57
+ error: "invalid_request",
58
+ errorDescription: description ?? "The request is missing required parameters"
59
+ });
60
+ }
61
+ function parseWwwAuthenticate(header) {
62
+ const result = {};
63
+ if (!header.toLowerCase().startsWith("bearer")) {
64
+ return result;
65
+ }
66
+ const paramString = header.substring(6).trim();
67
+ const params = parseQuotedParams(paramString);
68
+ for (const [key, value] of params) {
69
+ const unescapedValue = unescapeQuotedString(value);
70
+ switch (key.toLowerCase()) {
71
+ case "resource_metadata":
72
+ result.resourceMetadataUrl = unescapedValue;
73
+ break;
74
+ case "realm":
75
+ result.realm = unescapedValue;
76
+ break;
77
+ case "error":
78
+ result.error = unescapedValue;
79
+ break;
80
+ case "error_description":
81
+ result.errorDescription = unescapedValue;
82
+ break;
83
+ case "error_uri":
84
+ result.errorUri = unescapedValue;
85
+ break;
86
+ case "scope":
87
+ result.scope = unescapedValue;
88
+ break;
89
+ }
90
+ }
91
+ return result;
92
+ }
93
+ function parseQuotedParams(input) {
94
+ const result = [];
95
+ let i = 0;
96
+ const len = input.length;
97
+ while (i < len) {
98
+ while (i < len && (input[i] === " " || input[i] === "," || input[i] === " ")) {
99
+ i++;
100
+ }
101
+ if (i >= len) break;
102
+ const keyStart = i;
103
+ while (i < len && /\w/.test(input[i])) {
104
+ i++;
105
+ }
106
+ const key = input.slice(keyStart, i);
107
+ if (!key) break;
108
+ while (i < len && (input[i] === " " || input[i] === " ")) {
109
+ i++;
110
+ }
111
+ if (i >= len || input[i] !== "=") {
112
+ while (i < len && input[i] !== ",") i++;
113
+ continue;
114
+ }
115
+ i++;
116
+ while (i < len && (input[i] === " " || input[i] === " ")) {
117
+ i++;
118
+ }
119
+ if (i >= len || input[i] !== '"') {
120
+ while (i < len && input[i] !== ",") i++;
121
+ continue;
122
+ }
123
+ i++;
124
+ let value = "";
125
+ while (i < len && input[i] !== '"') {
126
+ if (input[i] === "\\" && i + 1 < len) {
127
+ value += input[i + 1];
128
+ i += 2;
129
+ } else {
130
+ value += input[i];
131
+ i++;
132
+ }
133
+ }
134
+ if (i < len && input[i] === '"') {
135
+ i++;
136
+ }
137
+ result.push([key, value]);
138
+ }
139
+ return result;
140
+ }
141
+ function escapeQuotedString(value) {
142
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
143
+ }
144
+ function unescapeQuotedString(value) {
145
+ return value.replace(/\\(.)/g, "$1");
146
+ }
147
+ function normalizePathSegment(path) {
148
+ if (!path || path === "/") return "";
149
+ const normalized = path.startsWith("/") ? path : `/${path}`;
150
+ let end = normalized.length;
151
+ while (end > 0 && normalized[end - 1] === "/") {
152
+ end--;
153
+ }
154
+ return normalized.slice(0, end);
155
+ }
156
+ export {
157
+ buildInsufficientScopeHeader,
158
+ buildInvalidRequestHeader,
159
+ buildInvalidTokenHeader,
160
+ buildPrmUrl,
161
+ buildUnauthorizedHeader,
162
+ buildWwwAuthenticate,
163
+ parseWwwAuthenticate
164
+ };
@@ -0,0 +1,236 @@
1
+ // libs/auth/src/vault/auth-providers.accessor.impl.ts
2
+ import { extractCredentialExpiry } from "./credential-helpers.mjs";
3
+ import { base64Encode } from "@frontmcp/utils";
4
+ var AuthProvidersAccessorImpl = class {
5
+ constructor(registry, vault, cache, loader, context, logger) {
6
+ this.registry = registry;
7
+ this.vault = vault;
8
+ this.cache = cache;
9
+ this.loader = loader;
10
+ this.context = context;
11
+ this.logger = logger;
12
+ }
13
+ registry;
14
+ vault;
15
+ cache;
16
+ loader;
17
+ context;
18
+ logger;
19
+ async get(providerName, options) {
20
+ const config = this.registry.get(providerName);
21
+ if (!config) {
22
+ this.logger?.debug(`Provider "${providerName}" is not registered`);
23
+ return null;
24
+ }
25
+ if (!options?.forceRefresh) {
26
+ const cached = this.cache.get(providerName);
27
+ if (cached && this.isValid(cached)) {
28
+ this.logger?.debug(`Cache hit for "${providerName}"`);
29
+ return cached;
30
+ }
31
+ }
32
+ if (!options?.forceRefresh) {
33
+ const fromVault = await this.loadFromVault(providerName, config);
34
+ if (fromVault && this.isValid(fromVault)) {
35
+ this.cache.set(providerName, fromVault, config.cacheTtl);
36
+ this.logger?.debug(`Vault hit for "${providerName}"`);
37
+ return fromVault;
38
+ }
39
+ }
40
+ const loaded = await this.loader.load(config, this.context);
41
+ if (!loaded) {
42
+ return null;
43
+ }
44
+ await this.storeInVault(providerName, loaded, config);
45
+ this.cache.set(providerName, loaded, config.cacheTtl);
46
+ return loaded;
47
+ }
48
+ async getMany(providerNames, options) {
49
+ const results = /* @__PURE__ */ new Map();
50
+ const promises = providerNames.map(async (name) => {
51
+ const result = await this.get(name, options);
52
+ return { name, result };
53
+ });
54
+ const settled = await Promise.allSettled(promises);
55
+ for (const outcome of settled) {
56
+ if (outcome.status === "fulfilled") {
57
+ results.set(outcome.value.name, outcome.value.result);
58
+ } else {
59
+ this.logger?.warn("Failed to load credential:", outcome.reason);
60
+ }
61
+ }
62
+ return results;
63
+ }
64
+ async headers(providerName) {
65
+ const resolved = await this.get(providerName);
66
+ if (!resolved) {
67
+ return {};
68
+ }
69
+ const config = this.registry.get(providerName);
70
+ if (config?.toHeaders) {
71
+ return config.toHeaders(resolved.credential);
72
+ }
73
+ return this.defaultHeaders(resolved.credential);
74
+ }
75
+ async headersMany(providerNames) {
76
+ const allHeaders = {};
77
+ for (const name of providerNames) {
78
+ const headers = await this.headers(name);
79
+ Object.assign(allHeaders, headers);
80
+ }
81
+ return allHeaders;
82
+ }
83
+ async refresh(providerName) {
84
+ const config = this.registry.get(providerName);
85
+ if (!config) {
86
+ this.logger?.debug(`Cannot refresh: provider "${providerName}" not registered`);
87
+ return null;
88
+ }
89
+ const existing = this.cache.get(providerName);
90
+ const existingCredential = existing?.credential;
91
+ try {
92
+ const refreshed = await this.loader.refresh(config, {
93
+ ...this.context,
94
+ existingCredential
95
+ });
96
+ if (!refreshed) {
97
+ return null;
98
+ }
99
+ await this.storeInVault(providerName, refreshed, config);
100
+ this.cache.set(providerName, refreshed, config.cacheTtl);
101
+ return refreshed;
102
+ } catch (error) {
103
+ this.logger?.warn(`Failed to refresh credential "${providerName}":`, error);
104
+ return null;
105
+ }
106
+ }
107
+ async has(providerName) {
108
+ const cached = this.cache.get(providerName);
109
+ if (cached) {
110
+ if (this.isValid(cached)) {
111
+ return true;
112
+ }
113
+ this.cache.invalidate(providerName);
114
+ }
115
+ const config = this.registry.get(providerName);
116
+ if (!config) {
117
+ return false;
118
+ }
119
+ const fromVault = await this.loadFromVault(providerName, config);
120
+ return fromVault !== null && this.isValid(fromVault);
121
+ }
122
+ isRegistered(providerName) {
123
+ return this.registry.has(providerName);
124
+ }
125
+ invalidate(providerName) {
126
+ this.cache.invalidate(providerName);
127
+ this.logger?.debug(`Invalidated cache for "${providerName}"`);
128
+ }
129
+ invalidateAll() {
130
+ this.cache.invalidateAll();
131
+ this.logger?.debug("Invalidated all cached credentials");
132
+ }
133
+ listProviders() {
134
+ return this.registry.getNames();
135
+ }
136
+ async listAvailable() {
137
+ const available = [];
138
+ for (const name of this.registry.getNames()) {
139
+ if (await this.has(name)) {
140
+ available.push(name);
141
+ }
142
+ }
143
+ return available;
144
+ }
145
+ /**
146
+ * Load credential from vault storage
147
+ */
148
+ async loadFromVault(providerName, config) {
149
+ try {
150
+ const credential = await this.vault.getCredential(
151
+ this.context.sessionId,
152
+ providerName,
153
+ config.scope,
154
+ this.context.userSub
155
+ );
156
+ if (!credential) {
157
+ return null;
158
+ }
159
+ return {
160
+ credential,
161
+ providerId: providerName,
162
+ acquiredAt: Date.now(),
163
+ // We don't store acquiredAt in vault
164
+ expiresAt: extractCredentialExpiry(credential),
165
+ isValid: true,
166
+ scope: config.scope
167
+ };
168
+ } catch (error) {
169
+ this.logger?.warn(`Failed to load from vault "${providerName}":`, error);
170
+ return null;
171
+ }
172
+ }
173
+ /**
174
+ * Store credential in vault storage
175
+ */
176
+ async storeInVault(providerName, resolved, config) {
177
+ try {
178
+ await this.vault.storeCredential(
179
+ this.context.sessionId,
180
+ providerName,
181
+ resolved.credential,
182
+ config.scope,
183
+ this.context.userSub
184
+ );
185
+ } catch (error) {
186
+ this.logger?.warn(`Failed to store in vault "${providerName}":`, error);
187
+ }
188
+ }
189
+ /**
190
+ * Check if a resolved credential is still valid
191
+ */
192
+ isValid(resolved) {
193
+ if (!resolved.isValid) {
194
+ return false;
195
+ }
196
+ if (resolved.expiresAt && Date.now() >= resolved.expiresAt) {
197
+ return false;
198
+ }
199
+ return true;
200
+ }
201
+ /**
202
+ * Generate default headers for a credential type
203
+ */
204
+ defaultHeaders(credential) {
205
+ switch (credential.type) {
206
+ case "oauth":
207
+ case "oauth_pkce":
208
+ return { Authorization: `${credential.tokenType} ${credential.accessToken}` };
209
+ case "bearer":
210
+ return { Authorization: `Bearer ${credential.token}` };
211
+ case "api_key":
212
+ if (credential.headerPrefix) {
213
+ return { [credential.headerName]: `${credential.headerPrefix}${credential.key}` };
214
+ }
215
+ return { [credential.headerName]: credential.key };
216
+ case "basic": {
217
+ const value = `${credential.username}:${credential.password}`;
218
+ const encoded = credential.encodedValue ?? base64Encode(new TextEncoder().encode(value));
219
+ return { Authorization: `Basic ${encoded}` };
220
+ }
221
+ case "custom":
222
+ return credential.headers ?? {};
223
+ // SSH keys, mTLS, private keys, service accounts don't have standard HTTP headers
224
+ case "ssh_key":
225
+ case "mtls":
226
+ case "private_key":
227
+ case "service_account":
228
+ return {};
229
+ default:
230
+ return {};
231
+ }
232
+ }
233
+ };
234
+ export {
235
+ AuthProvidersAccessorImpl
236
+ };
@@ -0,0 +1,5 @@
1
+ // libs/auth/src/vault/auth-providers.accessor.ts
2
+ var AUTH_PROVIDERS_ACCESSOR = /* @__PURE__ */ Symbol.for("frontmcp:AUTH_PROVIDERS_ACCESSOR");
3
+ export {
4
+ AUTH_PROVIDERS_ACCESSOR
5
+ };