@frontmcp/auth 1.10.16 → 1.11.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/browser/authorities/authorities.context.mjs +115 -0
- package/browser/authorities/authorities.engine.mjs +272 -0
- package/browser/authorities/authorities.errors.mjs +59 -0
- package/browser/authorities/authorities.evaluator.mjs +225 -0
- package/browser/authorities/authorities.metadata-augment.mjs +18 -0
- package/browser/authorities/authorities.registry.mjs +112 -0
- package/browser/authorities/authorities.schema.mjs +92 -0
- package/browser/authorities/authorities.scope-mapping.mjs +30 -0
- package/browser/authorities/authorities.validation.mjs +208 -0
- package/browser/authorities/index.mjs +43 -0
- package/browser/authorization/authorization.class.mjs +252 -0
- package/browser/authorization/authorization.types.mjs +64 -0
- package/browser/authorization/dcr-client.registry.mjs +222 -0
- package/browser/authorization/index.mjs +41 -0
- package/browser/authorization/orchestrated.accessor.mjs +86 -0
- package/browser/authorization/orchestrated.authorization.mjs +289 -0
- package/browser/authorization/public.authorization.mjs +124 -0
- package/browser/authorization/transparent.authorization.mjs +130 -0
- package/browser/cimd/cimd-redis.cache.mjs +231 -0
- package/browser/cimd/cimd.cache.mjs +215 -0
- package/browser/cimd/cimd.errors.mjs +142 -0
- package/browser/cimd/cimd.logger.mjs +15 -0
- package/browser/cimd/cimd.service.mjs +376 -0
- package/browser/cimd/cimd.types.mjs +228 -0
- package/browser/cimd/cimd.validator.mjs +217 -0
- package/browser/cimd/index.mjs +63 -0
- package/browser/common/auth-logger.interface.mjs +14 -0
- package/browser/common/index.mjs +6 -0
- package/browser/common/jwt.types.mjs +38 -0
- package/browser/common/session-user.types.mjs +0 -0
- package/browser/common/session.types.mjs +58 -0
- package/browser/common/zod-utils.mjs +0 -0
- package/browser/consent/consent.types.mjs +94 -0
- package/browser/consent/index.mjs +17 -0
- package/browser/context/frontmcp-auth-context.factory.mjs +29 -0
- package/browser/context/frontmcp-auth-context.impl.mjs +112 -0
- package/browser/context/index.mjs +7 -0
- package/browser/detection/auth-provider-detection.mjs +176 -0
- package/browser/detection/index.mjs +19 -0
- package/browser/errors/auth-internal.error.mjs +42 -0
- package/browser/errors/auth-internal.errors.mjs +150 -0
- package/browser/errors/index.mjs +53 -0
- package/browser/fetch/fetch-credential-middleware.mjs +91 -0
- package/browser/fetch/index.mjs +19 -0
- package/browser/index.mjs +573 -0
- package/browser/jwks/index.mjs +9 -0
- package/browser/jwks/jose.loader.mjs +28 -0
- package/browser/jwks/jwks.service.mjs +521 -0
- package/browser/jwks/jwks.utils.mjs +33 -0
- package/browser/options/app-auth.schema.mjs +18 -0
- package/browser/options/index.mjs +89 -0
- package/browser/options/orchestrated.schema.mjs +128 -0
- package/browser/options/public.schema.mjs +39 -0
- package/browser/options/schema.mjs +16 -0
- package/browser/options/shared.schemas.mjs +506 -0
- package/browser/options/static.schema.mjs +41 -0
- package/browser/options/transparent.schema.mjs +43 -0
- package/browser/options/utils.mjs +47 -0
- package/browser/people/index.mjs +15 -0
- package/browser/people/loopback-token.auth.mjs +115 -0
- package/browser/people/people-auth.options.mjs +37 -0
- package/browser/session/authorization-vault.mjs +279 -0
- package/browser/session/authorization.store.mjs +336 -0
- package/browser/session/consent.store.mjs +34 -0
- package/browser/session/credential-resume-link.mjs +43 -0
- package/browser/session/credentials-accessor.impl.mjs +54 -0
- package/browser/session/credentials-accessor.mjs +5 -0
- package/browser/session/encrypted-authorization-vault.mjs +405 -0
- package/browser/session/federated-auth.session.mjs +176 -0
- package/browser/session/incremental-auth-ticket.mjs +42 -0
- package/browser/session/index.mjs +266 -0
- package/browser/session/orchestrated-token.crypto.mjs +47 -0
- package/browser/session/orchestrated-token.store.mjs +286 -0
- package/browser/session/pending-login.mjs +37 -0
- package/browser/session/redis-session.store.mjs +255 -0
- package/browser/session/secure-store-accessor.impl.mjs +78 -0
- package/browser/session/secure-store-accessor.mjs +5 -0
- package/browser/session/secure-store-backends.mjs +111 -0
- package/browser/session/secure-store.factory.mjs +77 -0
- package/browser/session/session-credential-vault.factory.mjs +18 -0
- package/browser/session/session-credential-vault.mjs +243 -0
- package/browser/session/session-crypto.mjs +39 -0
- package/browser/session/session-rate-limiter.mjs +126 -0
- package/browser/session/session.transport.mjs +16 -0
- package/browser/session/signin-binding.mjs +30 -0
- package/browser/session/storage/in-memory-authorization-vault.mjs +28 -0
- package/browser/session/storage/index.mjs +14 -0
- package/browser/session/storage/storage-authorization-vault.mjs +301 -0
- package/browser/session/storage/storage-token-store.mjs +83 -0
- package/browser/session/storage-authorization.store.mjs +134 -0
- package/browser/session/storage-consent.store.mjs +33 -0
- package/browser/session/storage-federated-auth.session.mjs +55 -0
- package/browser/session/storage-orchestrated-token.store.mjs +197 -0
- package/browser/session/token-storage.factory.mjs +58 -0
- package/browser/session/token.refresh.mjs +36 -0
- package/browser/session/token.vault.mjs +61 -0
- package/browser/session/transport-session.types.mjs +108 -0
- package/browser/session/utils/auth-token.utils.mjs +58 -0
- package/browser/session/utils/index.mjs +23 -0
- package/browser/session/utils/session-crypto.utils.mjs +49 -0
- package/browser/session/utils/tiny-ttl-cache.mjs +46 -0
- package/browser/session/vault-encryption.mjs +187 -0
- package/browser/session/vercel-kv-session.store.mjs +230 -0
- package/browser/shared-server/index.mjs +17 -0
- package/browser/shared-server/shared-server-identity.mjs +55 -0
- package/browser/ui/base-layout.mjs +184 -0
- package/browser/ui/index.mjs +51 -0
- package/browser/ui/local-login.helper.mjs +46 -0
- package/browser/ui/templates.mjs +482 -0
- package/browser/utils/audience.validator.mjs +110 -0
- package/browser/utils/authorization-id.utils.mjs +10 -0
- package/browser/utils/index.mjs +45 -0
- package/browser/utils/redirect-uri.utils.mjs +22 -0
- package/browser/utils/scope-grant.utils.mjs +36 -0
- package/browser/utils/token-scopes.utils.mjs +12 -0
- package/browser/utils/www-authenticate.utils.mjs +164 -0
- package/browser/vault/auth-providers.accessor.impl.mjs +236 -0
- package/browser/vault/auth-providers.accessor.mjs +5 -0
- package/browser/vault/auth-providers.registry.mjs +140 -0
- package/browser/vault/auth-providers.types.mjs +47 -0
- package/browser/vault/auth-providers.vault.mjs +206 -0
- package/browser/vault/credential-cache.mjs +187 -0
- package/browser/vault/credential-helpers.mjs +15 -0
- package/browser/vault/credential-loaders/eager-loader.mjs +91 -0
- package/browser/vault/credential-loaders/lazy-loader.mjs +120 -0
- package/browser/vault/index.mjs +35 -0
- package/cimd/cimd.types.d.ts +1 -1
- package/cimd/cimd.types.d.ts.map +1 -1
- package/common/jwt.types.d.ts +1 -1
- package/common/jwt.types.d.ts.map +1 -1
- package/common/session.types.d.ts +9 -1
- package/common/session.types.d.ts.map +1 -1
- package/consent/consent.types.d.ts +1 -1
- package/consent/consent.types.d.ts.map +1 -1
- package/detection/auth-provider-detection.d.ts +1 -1
- package/detection/auth-provider-detection.d.ts.map +1 -1
- package/errors/auth-internal.errors.d.ts +6 -0
- package/errors/auth-internal.errors.d.ts.map +1 -1
- package/errors/index.d.ts +1 -1
- package/errors/index.d.ts.map +1 -1
- package/esm/index.mjs +2 -2
- package/esm/package.json +21 -5
- package/index.d.ts +5 -1
- package/index.d.ts.map +1 -1
- package/index.js +2 -2
- package/options/app-auth.schema.d.ts +19 -1
- package/options/app-auth.schema.d.ts.map +1 -1
- package/options/orchestrated.schema.d.ts +37 -1
- package/options/orchestrated.schema.d.ts.map +1 -1
- package/options/public.schema.d.ts +1 -1
- package/options/public.schema.d.ts.map +1 -1
- package/options/schema.d.ts +19 -1
- package/options/schema.d.ts.map +1 -1
- package/options/shared.schemas.d.ts +10 -14
- package/options/shared.schemas.d.ts.map +1 -1
- package/options/static.schema.d.ts +1 -1
- package/options/static.schema.d.ts.map +1 -1
- package/options/transparent.schema.d.ts +1 -1
- package/options/transparent.schema.d.ts.map +1 -1
- package/package.json +21 -5
- package/people/index.d.ts +3 -0
- package/people/index.d.ts.map +1 -0
- package/people/loopback-token.auth.d.ts +75 -0
- package/people/loopback-token.auth.d.ts.map +1 -0
- package/people/people-auth.options.d.ts +36 -0
- package/people/people-auth.options.d.ts.map +1 -0
- package/session/authorization-vault.d.ts +1 -1
- package/session/authorization-vault.d.ts.map +1 -1
- package/session/encrypted-authorization-vault.d.ts +1 -1
- package/session/encrypted-authorization-vault.d.ts.map +1 -1
- package/session/secure-store.factory.d.ts.map +1 -1
- package/shared-server/index.d.ts +3 -0
- package/shared-server/index.d.ts.map +1 -0
- package/shared-server/shared-server-identity.d.ts +26 -0
- package/shared-server/shared-server-identity.d.ts.map +1 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
|
|
2
|
+
const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
|
|
3
|
+
// __frontmcpCreateRequire
|
|
4
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
6
|
+
}) : x)(function(x) {
|
|
7
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
8
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
// libs/auth/src/cimd/cimd.validator.ts
|
|
12
|
+
import { classifyIpAddress } from "@frontmcp/utils";
|
|
13
|
+
import { CimdSecurityError, InvalidClientIdUrlError } from "./cimd.errors.mjs";
|
|
14
|
+
function isCimdClientId(clientId, allowInsecure = false) {
|
|
15
|
+
if (!clientId || typeof clientId !== "string") {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const url = new URL(clientId);
|
|
20
|
+
if (url.protocol === "https:") {
|
|
21
|
+
} else if (url.protocol === "http:" && allowInsecure && isLocalhostHost(url.hostname)) {
|
|
22
|
+
} else {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (!url.pathname || url.pathname === "/") {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function isLocalhostHost(hostname) {
|
|
34
|
+
const lower = hostname.toLowerCase();
|
|
35
|
+
return lower === "localhost" || lower === "127.0.0.1" || lower === "[::1]" || lower.endsWith(".localhost");
|
|
36
|
+
}
|
|
37
|
+
function validateClientIdUrl(clientId, securityConfig) {
|
|
38
|
+
if (!clientId || typeof clientId !== "string") {
|
|
39
|
+
throw new InvalidClientIdUrlError(clientId || "", "client_id must be a non-empty string");
|
|
40
|
+
}
|
|
41
|
+
let url;
|
|
42
|
+
try {
|
|
43
|
+
url = new URL(clientId);
|
|
44
|
+
} catch {
|
|
45
|
+
throw new InvalidClientIdUrlError(clientId, "Invalid URL format");
|
|
46
|
+
}
|
|
47
|
+
const allowInsecure = securityConfig?.allowInsecureForTesting ?? false;
|
|
48
|
+
if (url.protocol === "https:") {
|
|
49
|
+
} else if (url.protocol === "http:" && allowInsecure && isLocalhostHost(url.hostname)) {
|
|
50
|
+
} else {
|
|
51
|
+
throw new InvalidClientIdUrlError(clientId, `CIMD requires HTTPS, got ${url.protocol.replace(":", "")}`);
|
|
52
|
+
}
|
|
53
|
+
if (!url.pathname || url.pathname === "/") {
|
|
54
|
+
throw new InvalidClientIdUrlError(
|
|
55
|
+
clientId,
|
|
56
|
+
"CIMD client_id URL must have a path component (e.g., /oauth/client-metadata.json)"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
const config = {
|
|
60
|
+
blockPrivateIPs: securityConfig?.blockPrivateIPs ?? true,
|
|
61
|
+
allowedDomains: securityConfig?.allowedDomains,
|
|
62
|
+
blockedDomains: securityConfig?.blockedDomains
|
|
63
|
+
};
|
|
64
|
+
if (config.allowedDomains?.length) {
|
|
65
|
+
if (!isDomainInList(url.hostname, config.allowedDomains)) {
|
|
66
|
+
throw new CimdSecurityError(clientId, `Domain "${url.hostname}" is not in the allowed domains list`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (config.blockedDomains?.length) {
|
|
70
|
+
if (isDomainInList(url.hostname, config.blockedDomains)) {
|
|
71
|
+
throw new CimdSecurityError(clientId, `Domain "${url.hostname}" is blocked`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (config.blockPrivateIPs && !allowInsecure) {
|
|
75
|
+
const ssrfCheck = checkSsrfProtection(url.hostname);
|
|
76
|
+
if (!ssrfCheck.allowed) {
|
|
77
|
+
throw new CimdSecurityError(clientId, ssrfCheck.reason);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return url;
|
|
81
|
+
}
|
|
82
|
+
function checkSsrfProtection(hostname) {
|
|
83
|
+
const lowercaseHostname = hostname.toLowerCase();
|
|
84
|
+
if (lowercaseHostname === "localhost" || lowercaseHostname === "localhost.localdomain" || lowercaseHostname.endsWith(".localhost")) {
|
|
85
|
+
return { allowed: false, reason: "Localhost addresses are not allowed" };
|
|
86
|
+
}
|
|
87
|
+
if (isIpAddress(hostname)) {
|
|
88
|
+
const ipCheck = checkIpAddress(hostname);
|
|
89
|
+
if (!ipCheck.allowed) {
|
|
90
|
+
return ipCheck;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { allowed: true, reason: "" };
|
|
94
|
+
}
|
|
95
|
+
async function resolveAndCheckHostname(hostname) {
|
|
96
|
+
const literal = checkSsrfProtection(hostname);
|
|
97
|
+
if (!literal.allowed) return literal;
|
|
98
|
+
if (isIpAddress(hostname)) return { allowed: true, reason: "" };
|
|
99
|
+
let dns;
|
|
100
|
+
try {
|
|
101
|
+
dns = __require("node:dns");
|
|
102
|
+
} catch {
|
|
103
|
+
return { allowed: true, reason: "" };
|
|
104
|
+
}
|
|
105
|
+
let addresses;
|
|
106
|
+
try {
|
|
107
|
+
const result = await dns.promises.lookup(hostname, { all: true, verbatim: true });
|
|
108
|
+
addresses = Array.isArray(result) ? result : [result];
|
|
109
|
+
} catch {
|
|
110
|
+
return { allowed: false, reason: `DNS resolution failed for "${hostname}"; refusing to fetch an unvalidated host` };
|
|
111
|
+
}
|
|
112
|
+
if (addresses.length === 0) {
|
|
113
|
+
return { allowed: false, reason: `Host "${hostname}" resolved to no addresses` };
|
|
114
|
+
}
|
|
115
|
+
for (const { address } of addresses) {
|
|
116
|
+
if (!address) continue;
|
|
117
|
+
const ipCheck = checkIpAddress(address);
|
|
118
|
+
if (!ipCheck.allowed) {
|
|
119
|
+
return {
|
|
120
|
+
allowed: false,
|
|
121
|
+
reason: `Host "${hostname}" resolves to a blocked address (${address}): ${ipCheck.reason}`
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { allowed: true, reason: "" };
|
|
126
|
+
}
|
|
127
|
+
async function assertHostNotSsrf(hostname, clientId) {
|
|
128
|
+
const check = await resolveAndCheckHostname(hostname);
|
|
129
|
+
if (!check.allowed) {
|
|
130
|
+
throw new CimdSecurityError(clientId, check.reason);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function isIpAddress(hostname) {
|
|
134
|
+
const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
135
|
+
if (ipv4Pattern.test(hostname)) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
const cleanHostname = hostname.replace(/^\[|\]$/g, "");
|
|
139
|
+
if (cleanHostname.includes(":")) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
var BLOCKED_RANGE_REASONS = /* @__PURE__ */ new Map([
|
|
145
|
+
["0.0.0.0/8", "Current network addresses (0.x.x.x) are not allowed"],
|
|
146
|
+
["10.0.0.0/8", "Private IP addresses (10.x.x.x) are not allowed"],
|
|
147
|
+
["100.64.0.0/10", "Carrier-grade NAT addresses (100.64-127.x.x) are not allowed"],
|
|
148
|
+
["127.0.0.0/8", "Loopback addresses (127.x.x.x) are not allowed"],
|
|
149
|
+
["169.254.0.0/16", "Link-local addresses (169.254.x.x) are not allowed"],
|
|
150
|
+
["172.16.0.0/12", "Private IP addresses (172.16-31.x.x) are not allowed"],
|
|
151
|
+
["192.0.0.0/24", "IETF protocol-assignment addresses (192.0.0.0/24) are not allowed"],
|
|
152
|
+
["192.168.0.0/16", "Private IP addresses (192.168.x.x) are not allowed"],
|
|
153
|
+
["224.0.0.0/4", "Multicast addresses are not allowed"],
|
|
154
|
+
["255.255.255.255/32", "Broadcast address is not allowed"],
|
|
155
|
+
["::/128", "IPv6 unspecified address (::) is not allowed"],
|
|
156
|
+
["::1/128", "IPv6 loopback address (::1) is not allowed"],
|
|
157
|
+
["fc00::/7", "IPv6 unique local addresses (fc00::/7) are not allowed"],
|
|
158
|
+
["fe80::/10", "IPv6 link-local addresses (fe80::/10) are not allowed"]
|
|
159
|
+
]);
|
|
160
|
+
function checkIpAddress(ip) {
|
|
161
|
+
const classification = classifyIpAddress(ip);
|
|
162
|
+
if (!classification) {
|
|
163
|
+
const bareIp = ip.replace(/^\[|\]$/g, "");
|
|
164
|
+
return {
|
|
165
|
+
allowed: false,
|
|
166
|
+
reason: bareIp.includes(":") ? `Malformed IPv6 address: ${bareIp}` : "Invalid IPv4 address"
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (classification.range === "public") {
|
|
170
|
+
return { allowed: true, reason: "" };
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
allowed: false,
|
|
174
|
+
reason: BLOCKED_RANGE_REASONS.get(classification.cidr) ?? `Addresses in the ${classification.range} range (${classification.cidr}) are not allowed`
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function isDomainInList(hostname, domainList) {
|
|
178
|
+
const lowerHostname = hostname.toLowerCase();
|
|
179
|
+
for (const domain of domainList) {
|
|
180
|
+
const lowerDomain = domain.toLowerCase();
|
|
181
|
+
if (lowerHostname === lowerDomain) {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
if (lowerHostname.endsWith("." + lowerDomain)) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
if (lowerDomain.startsWith("*.")) {
|
|
188
|
+
const baseDomain = lowerDomain.slice(2);
|
|
189
|
+
if (lowerHostname === baseDomain || lowerHostname.endsWith("." + baseDomain)) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
function hasOnlyLocalhostRedirectUris(redirectUris) {
|
|
197
|
+
if (!redirectUris.length) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
return redirectUris.every((uri) => {
|
|
201
|
+
try {
|
|
202
|
+
const url = new URL(uri);
|
|
203
|
+
const hostname = url.hostname.toLowerCase();
|
|
204
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname.endsWith(".localhost");
|
|
205
|
+
} catch {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
export {
|
|
211
|
+
assertHostNotSsrf,
|
|
212
|
+
checkSsrfProtection,
|
|
213
|
+
hasOnlyLocalhostRedirectUris,
|
|
214
|
+
isCimdClientId,
|
|
215
|
+
resolveAndCheckHostname,
|
|
216
|
+
validateClientIdUrl
|
|
217
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// libs/auth/src/cimd/index.ts
|
|
2
|
+
import { noopLogger } from "./cimd.logger.mjs";
|
|
3
|
+
import {
|
|
4
|
+
clientMetadataDocumentSchema,
|
|
5
|
+
cimdCacheConfigSchema,
|
|
6
|
+
cimdRedisCacheConfigSchema,
|
|
7
|
+
cimdSecurityConfigSchema,
|
|
8
|
+
cimdNetworkConfigSchema,
|
|
9
|
+
cimdConfigSchema
|
|
10
|
+
} from "./cimd.types.mjs";
|
|
11
|
+
import {
|
|
12
|
+
CimdError,
|
|
13
|
+
InvalidClientIdUrlError,
|
|
14
|
+
CimdFetchError,
|
|
15
|
+
CimdValidationError,
|
|
16
|
+
CimdClientIdMismatchError,
|
|
17
|
+
CimdSecurityError,
|
|
18
|
+
RedirectUriMismatchError,
|
|
19
|
+
CimdResponseTooLargeError,
|
|
20
|
+
CimdDisabledError
|
|
21
|
+
} from "./cimd.errors.mjs";
|
|
22
|
+
import {
|
|
23
|
+
isCimdClientId,
|
|
24
|
+
validateClientIdUrl,
|
|
25
|
+
checkSsrfProtection,
|
|
26
|
+
hasOnlyLocalhostRedirectUris
|
|
27
|
+
} from "./cimd.validator.mjs";
|
|
28
|
+
import {
|
|
29
|
+
InMemoryCimdCache,
|
|
30
|
+
createCimdCache,
|
|
31
|
+
extractCacheHeaders,
|
|
32
|
+
parseCacheHeaders
|
|
33
|
+
} from "./cimd.cache.mjs";
|
|
34
|
+
import { RedisCimdCache } from "./cimd-redis.cache.mjs";
|
|
35
|
+
import { CimdService } from "./cimd.service.mjs";
|
|
36
|
+
export {
|
|
37
|
+
CimdClientIdMismatchError,
|
|
38
|
+
CimdDisabledError,
|
|
39
|
+
CimdError,
|
|
40
|
+
CimdFetchError,
|
|
41
|
+
CimdResponseTooLargeError,
|
|
42
|
+
CimdSecurityError,
|
|
43
|
+
CimdService,
|
|
44
|
+
CimdValidationError,
|
|
45
|
+
InMemoryCimdCache,
|
|
46
|
+
InvalidClientIdUrlError,
|
|
47
|
+
RedirectUriMismatchError,
|
|
48
|
+
RedisCimdCache,
|
|
49
|
+
checkSsrfProtection,
|
|
50
|
+
cimdCacheConfigSchema,
|
|
51
|
+
cimdConfigSchema,
|
|
52
|
+
cimdNetworkConfigSchema,
|
|
53
|
+
cimdRedisCacheConfigSchema,
|
|
54
|
+
cimdSecurityConfigSchema,
|
|
55
|
+
clientMetadataDocumentSchema,
|
|
56
|
+
createCimdCache,
|
|
57
|
+
extractCacheHeaders,
|
|
58
|
+
hasOnlyLocalhostRedirectUris,
|
|
59
|
+
isCimdClientId,
|
|
60
|
+
noopLogger,
|
|
61
|
+
parseCacheHeaders,
|
|
62
|
+
validateClientIdUrl
|
|
63
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// libs/auth/src/common/index.ts
|
|
2
|
+
export { noopAuthLogger } from "./auth-logger.interface.mjs";
|
|
3
|
+
import "./zod-utils.mjs";
|
|
4
|
+
import "./session-user.types.mjs";
|
|
5
|
+
export { jsonWebKeySetSchema, jwkParametersSchema, jwkSchema } from "./jwt.types.mjs";
|
|
6
|
+
export { aiPlatformTypeSchema, authorizationSchema, sessionIdPayloadSchema, sessionIdSchema, userClaimSchema } from "./session.types.mjs";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// libs/auth/src/common/jwt.types.ts
|
|
2
|
+
import { internalZ as z } from "@frontmcp/lazy-zod";
|
|
3
|
+
var jwkParametersSchema = z.object({
|
|
4
|
+
kty: z.string().optional(),
|
|
5
|
+
alg: z.string().optional(),
|
|
6
|
+
key_ops: z.array(z.string()).optional(),
|
|
7
|
+
ext: z.boolean().optional(),
|
|
8
|
+
use: z.string().optional(),
|
|
9
|
+
x5c: z.array(z.string()).optional(),
|
|
10
|
+
x5t: z.string().optional(),
|
|
11
|
+
"x5t#S256": z.string().optional(),
|
|
12
|
+
x5u: z.string().optional(),
|
|
13
|
+
kid: z.string().optional()
|
|
14
|
+
});
|
|
15
|
+
var jwkSchema = jwkParametersSchema.extend({
|
|
16
|
+
crv: z.string().optional(),
|
|
17
|
+
d: z.string().optional(),
|
|
18
|
+
dp: z.string().optional(),
|
|
19
|
+
dq: z.string().optional(),
|
|
20
|
+
e: z.string().optional(),
|
|
21
|
+
k: z.string().optional(),
|
|
22
|
+
n: z.string().optional(),
|
|
23
|
+
p: z.string().optional(),
|
|
24
|
+
q: z.string().optional(),
|
|
25
|
+
qi: z.string().optional(),
|
|
26
|
+
x: z.string().optional(),
|
|
27
|
+
y: z.string().optional(),
|
|
28
|
+
pub: z.string().optional(),
|
|
29
|
+
priv: z.string().optional()
|
|
30
|
+
});
|
|
31
|
+
var jsonWebKeySetSchema = z.object({
|
|
32
|
+
keys: z.array(jwkSchema)
|
|
33
|
+
});
|
|
34
|
+
export {
|
|
35
|
+
jsonWebKeySetSchema,
|
|
36
|
+
jwkParametersSchema,
|
|
37
|
+
jwkSchema
|
|
38
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// libs/auth/src/common/session.types.ts
|
|
2
|
+
import { internalZ as z } from "@frontmcp/lazy-zod";
|
|
3
|
+
var aiPlatformTypeSchema = z.enum([
|
|
4
|
+
"openai",
|
|
5
|
+
"claude",
|
|
6
|
+
"claude-code",
|
|
7
|
+
"codex",
|
|
8
|
+
"gemini",
|
|
9
|
+
"cursor",
|
|
10
|
+
"continue",
|
|
11
|
+
"cody",
|
|
12
|
+
"generic-mcp",
|
|
13
|
+
"ext-apps",
|
|
14
|
+
"unknown"
|
|
15
|
+
]);
|
|
16
|
+
var userClaimSchema = z.object({
|
|
17
|
+
iss: z.string(),
|
|
18
|
+
sid: z.string().optional(),
|
|
19
|
+
sub: z.string(),
|
|
20
|
+
exp: z.number().optional(),
|
|
21
|
+
iat: z.number().optional(),
|
|
22
|
+
aud: z.union([z.string(), z.array(z.string())]).optional(),
|
|
23
|
+
email: z.string().optional(),
|
|
24
|
+
username: z.string().optional(),
|
|
25
|
+
preferred_username: z.string().optional(),
|
|
26
|
+
name: z.string().optional(),
|
|
27
|
+
picture: z.string().optional()
|
|
28
|
+
}).passthrough();
|
|
29
|
+
var sessionIdPayloadSchema = z.object({
|
|
30
|
+
nodeId: z.string(),
|
|
31
|
+
authSig: z.string(),
|
|
32
|
+
uuid: z.string().uuid(),
|
|
33
|
+
iat: z.number(),
|
|
34
|
+
protocol: z.enum(["legacy-sse", "sse", "streamable-http", "stateful-http", "stateless-http"]).optional(),
|
|
35
|
+
isPublic: z.boolean().optional(),
|
|
36
|
+
platformType: aiPlatformTypeSchema.optional(),
|
|
37
|
+
clientName: z.string().optional(),
|
|
38
|
+
clientVersion: z.string().optional(),
|
|
39
|
+
supportsElicitation: z.boolean().optional(),
|
|
40
|
+
skillsOnlyMode: z.boolean().optional()
|
|
41
|
+
});
|
|
42
|
+
var sessionIdSchema = z.object({
|
|
43
|
+
id: z.string(),
|
|
44
|
+
/** Payload is optional - may be undefined when session validation failed but ID is passed for transport lookup */
|
|
45
|
+
payload: sessionIdPayloadSchema.optional()
|
|
46
|
+
});
|
|
47
|
+
var authorizationSchema = z.object({
|
|
48
|
+
token: z.string(),
|
|
49
|
+
session: sessionIdSchema.optional(),
|
|
50
|
+
user: userClaimSchema
|
|
51
|
+
});
|
|
52
|
+
export {
|
|
53
|
+
aiPlatformTypeSchema,
|
|
54
|
+
authorizationSchema,
|
|
55
|
+
sessionIdPayloadSchema,
|
|
56
|
+
sessionIdSchema,
|
|
57
|
+
userClaimSchema
|
|
58
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// libs/auth/src/consent/consent.types.ts
|
|
2
|
+
import { internalZ as z } from "@frontmcp/lazy-zod";
|
|
3
|
+
import { consentConfigSchema } from "../options/shared.schemas.mjs";
|
|
4
|
+
var consentToolItemSchema = z.object({
|
|
5
|
+
/** Tool ID (e.g., 'slack:send_message') */
|
|
6
|
+
id: z.string().min(1),
|
|
7
|
+
/** Tool name for display */
|
|
8
|
+
name: z.string().min(1),
|
|
9
|
+
/** Tool description */
|
|
10
|
+
description: z.string().optional(),
|
|
11
|
+
/** App ID this tool belongs to */
|
|
12
|
+
appId: z.string().min(1),
|
|
13
|
+
/** App name for display */
|
|
14
|
+
appName: z.string().min(1),
|
|
15
|
+
/** Whether the tool is selected by default */
|
|
16
|
+
defaultSelected: z.boolean().default(true),
|
|
17
|
+
/** Whether this tool requires specific scopes */
|
|
18
|
+
requiredScopes: z.array(z.string()).optional(),
|
|
19
|
+
/** Category for grouping (e.g., 'read', 'write', 'admin') */
|
|
20
|
+
category: z.string().optional()
|
|
21
|
+
});
|
|
22
|
+
var consentSelectionSchema = z.object({
|
|
23
|
+
/** Selected tool IDs */
|
|
24
|
+
selectedTools: z.array(z.string()),
|
|
25
|
+
/** Whether all tools were selected */
|
|
26
|
+
allSelected: z.boolean(),
|
|
27
|
+
/** Timestamp when consent was given */
|
|
28
|
+
consentedAt: z.string().datetime(),
|
|
29
|
+
/** Consent version for tracking changes */
|
|
30
|
+
consentVersion: z.string().default("1.0")
|
|
31
|
+
});
|
|
32
|
+
var consentStateSchema = z.object({
|
|
33
|
+
/** Whether consent flow is enabled */
|
|
34
|
+
enabled: z.boolean(),
|
|
35
|
+
/** Available tools for consent */
|
|
36
|
+
availableTools: z.array(consentToolItemSchema),
|
|
37
|
+
/** Pre-selected tools (from previous consent or defaults) */
|
|
38
|
+
preselectedTools: z.array(z.string()).optional(),
|
|
39
|
+
/** Whether to show all tools or group by app */
|
|
40
|
+
groupByApp: z.boolean().default(true),
|
|
41
|
+
/** Custom consent message */
|
|
42
|
+
customMessage: z.string().optional()
|
|
43
|
+
});
|
|
44
|
+
var federatedProviderItemSchema = z.object({
|
|
45
|
+
/** Provider ID (derived or explicit) */
|
|
46
|
+
id: z.string().min(1),
|
|
47
|
+
/** Provider display name */
|
|
48
|
+
name: z.string().min(1),
|
|
49
|
+
/** Provider description */
|
|
50
|
+
description: z.string().optional(),
|
|
51
|
+
/** Provider icon URL or emoji */
|
|
52
|
+
icon: z.string().optional(),
|
|
53
|
+
/** Provider type */
|
|
54
|
+
type: z.enum(["local", "remote", "transparent"]),
|
|
55
|
+
/** OAuth provider URL (for remote providers) */
|
|
56
|
+
providerUrl: z.string().url().optional(),
|
|
57
|
+
/** Apps using this provider */
|
|
58
|
+
appIds: z.array(z.string()),
|
|
59
|
+
/** App names using this provider */
|
|
60
|
+
appNames: z.array(z.string()),
|
|
61
|
+
/** Scopes required by this provider */
|
|
62
|
+
scopes: z.array(z.string()),
|
|
63
|
+
/** Whether this is the primary/parent provider */
|
|
64
|
+
isPrimary: z.boolean(),
|
|
65
|
+
/** Whether this provider is optional (can be skipped) */
|
|
66
|
+
isOptional: z.boolean().default(false)
|
|
67
|
+
});
|
|
68
|
+
var federatedLoginStateSchema = z.object({
|
|
69
|
+
/** All available providers */
|
|
70
|
+
providers: z.array(federatedProviderItemSchema),
|
|
71
|
+
/** Primary provider ID (if any) */
|
|
72
|
+
primaryProviderId: z.string().optional(),
|
|
73
|
+
/** Whether user can skip optional providers */
|
|
74
|
+
allowSkip: z.boolean().default(true),
|
|
75
|
+
/** Pre-selected provider IDs (from previous session) */
|
|
76
|
+
preselectedProviders: z.array(z.string()).optional()
|
|
77
|
+
});
|
|
78
|
+
var federatedSelectionSchema = z.object({
|
|
79
|
+
/** Selected provider IDs */
|
|
80
|
+
selectedProviders: z.array(z.string()),
|
|
81
|
+
/** Skipped provider IDs */
|
|
82
|
+
skippedProviders: z.array(z.string()),
|
|
83
|
+
/** Provider-specific metadata */
|
|
84
|
+
providerMetadata: z.record(z.string(), z.unknown()).optional()
|
|
85
|
+
});
|
|
86
|
+
export {
|
|
87
|
+
consentConfigSchema,
|
|
88
|
+
consentSelectionSchema,
|
|
89
|
+
consentStateSchema,
|
|
90
|
+
consentToolItemSchema,
|
|
91
|
+
federatedLoginStateSchema,
|
|
92
|
+
federatedProviderItemSchema,
|
|
93
|
+
federatedSelectionSchema
|
|
94
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// libs/auth/src/consent/index.ts
|
|
2
|
+
import {
|
|
3
|
+
consentToolItemSchema,
|
|
4
|
+
consentSelectionSchema,
|
|
5
|
+
consentStateSchema,
|
|
6
|
+
federatedProviderItemSchema,
|
|
7
|
+
federatedLoginStateSchema,
|
|
8
|
+
federatedSelectionSchema
|
|
9
|
+
} from "./consent.types.mjs";
|
|
10
|
+
export {
|
|
11
|
+
consentSelectionSchema,
|
|
12
|
+
consentStateSchema,
|
|
13
|
+
consentToolItemSchema,
|
|
14
|
+
federatedLoginStateSchema,
|
|
15
|
+
federatedProviderItemSchema,
|
|
16
|
+
federatedSelectionSchema
|
|
17
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// libs/auth/src/context/frontmcp-auth-context.factory.ts
|
|
2
|
+
import { FrontMcpAuthContextImpl } from "./frontmcp-auth-context.impl.mjs";
|
|
3
|
+
function buildAuthContext(authInfo, claimsMapping, pipes, claimsResolver) {
|
|
4
|
+
const base = new FrontMcpAuthContextImpl(authInfo, claimsMapping, claimsResolver);
|
|
5
|
+
if (!pipes || pipes.length === 0) {
|
|
6
|
+
return base;
|
|
7
|
+
}
|
|
8
|
+
return runPipes(base, pipes);
|
|
9
|
+
}
|
|
10
|
+
async function runPipes(base, pipes) {
|
|
11
|
+
const extensions = {};
|
|
12
|
+
for (const pipe of pipes) {
|
|
13
|
+
try {
|
|
14
|
+
const result = await pipe(base.claims);
|
|
15
|
+
if (result && typeof result === "object") {
|
|
16
|
+
Object.assign(extensions, result);
|
|
17
|
+
}
|
|
18
|
+
} catch (error) {
|
|
19
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20
|
+
console.warn(`[FrontMcpAuth] pipe failed: ${message}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const merged = Object.create(Object.getPrototypeOf(base));
|
|
24
|
+
Object.assign(merged, base, extensions);
|
|
25
|
+
return Object.freeze(merged);
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
buildAuthContext
|
|
29
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// libs/auth/src/context/frontmcp-auth-context.impl.ts
|
|
2
|
+
import {
|
|
3
|
+
resolveAuthUser,
|
|
4
|
+
resolveDotPath,
|
|
5
|
+
signedInSubject
|
|
6
|
+
} from "../authorities/authorities.context.mjs";
|
|
7
|
+
function toStringArray(value) {
|
|
8
|
+
if (Array.isArray(value)) {
|
|
9
|
+
return value.filter((v) => typeof v === "string");
|
|
10
|
+
}
|
|
11
|
+
if (typeof value === "string") {
|
|
12
|
+
return value.split(/\s+/).filter(Boolean);
|
|
13
|
+
}
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
var FrontMcpAuthContextImpl = class {
|
|
17
|
+
user;
|
|
18
|
+
isAnonymous;
|
|
19
|
+
mode;
|
|
20
|
+
sessionId;
|
|
21
|
+
scopes;
|
|
22
|
+
claims;
|
|
23
|
+
roles;
|
|
24
|
+
permissions;
|
|
25
|
+
constructor(source, claimsMapping, claimsResolver) {
|
|
26
|
+
const rawUser = resolveAuthUser(source);
|
|
27
|
+
const mappedSub = !claimsResolver && claimsMapping?.userId ? resolveDotPath(this.buildRawClaims(source), claimsMapping.userId) : void 0;
|
|
28
|
+
const signedInSub = signedInSubject(rawUser.sub, mappedSub);
|
|
29
|
+
const sub = signedInSub ?? (typeof rawUser.sub === "string" ? rawUser.sub : "");
|
|
30
|
+
this.user = Object.freeze({
|
|
31
|
+
sub,
|
|
32
|
+
name: rawUser.name,
|
|
33
|
+
email: rawUser.email,
|
|
34
|
+
picture: rawUser.picture
|
|
35
|
+
});
|
|
36
|
+
this.isAnonymous = signedInSub === void 0;
|
|
37
|
+
const extraAuth = source.extra?.["authorization"];
|
|
38
|
+
this.mode = String(
|
|
39
|
+
source.extra?.["authMode"] ?? extraAuth?.["mode"] ?? (source.extra?.["isPublic"] === true || rawUser["anonymous"] === true ? "public" : "authenticated")
|
|
40
|
+
);
|
|
41
|
+
this.sessionId = typeof source.sessionId === "string" ? source.sessionId : "";
|
|
42
|
+
this.scopes = Object.freeze(
|
|
43
|
+
Array.isArray(source.scopes) ? source.scopes.filter((s) => typeof s === "string") : []
|
|
44
|
+
);
|
|
45
|
+
if (claimsResolver) {
|
|
46
|
+
const resolved = claimsResolver(source);
|
|
47
|
+
this.claims = Object.freeze({ ...resolved.claims });
|
|
48
|
+
this.roles = Object.freeze([...toStringArray(resolved.roles)]);
|
|
49
|
+
this.permissions = Object.freeze([...toStringArray(resolved.permissions)]);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const rawClaims = this.buildRawClaims(source);
|
|
53
|
+
this.claims = Object.freeze({ ...rawClaims });
|
|
54
|
+
if (claimsMapping?.roles) {
|
|
55
|
+
this.roles = Object.freeze([...toStringArray(resolveDotPath(rawClaims, claimsMapping.roles))]);
|
|
56
|
+
} else {
|
|
57
|
+
this.roles = Object.freeze([
|
|
58
|
+
...toStringArray(rawUser["roles"] ?? extraAuth?.["scopes"] ?? [])
|
|
59
|
+
]);
|
|
60
|
+
}
|
|
61
|
+
if (claimsMapping?.permissions) {
|
|
62
|
+
this.permissions = Object.freeze([...toStringArray(resolveDotPath(rawClaims, claimsMapping.permissions))]);
|
|
63
|
+
} else {
|
|
64
|
+
this.permissions = Object.freeze([...toStringArray(rawUser["permissions"] ?? [])]);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// -- Query methods -------------------------------------------------
|
|
68
|
+
hasRole(role) {
|
|
69
|
+
return this.roles.includes(role);
|
|
70
|
+
}
|
|
71
|
+
hasAllRoles(roles) {
|
|
72
|
+
return roles.every((r) => this.roles.includes(r));
|
|
73
|
+
}
|
|
74
|
+
hasAnyRole(roles) {
|
|
75
|
+
return roles.some((r) => this.roles.includes(r));
|
|
76
|
+
}
|
|
77
|
+
hasPermission(permission) {
|
|
78
|
+
return this.permissions.includes(permission);
|
|
79
|
+
}
|
|
80
|
+
hasAllPermissions(permissions) {
|
|
81
|
+
return permissions.every((p) => this.permissions.includes(p));
|
|
82
|
+
}
|
|
83
|
+
hasAnyPermission(permissions) {
|
|
84
|
+
return permissions.some((p) => this.permissions.includes(p));
|
|
85
|
+
}
|
|
86
|
+
hasScope(scope) {
|
|
87
|
+
return this.scopes.includes(scope);
|
|
88
|
+
}
|
|
89
|
+
hasAllScopes(scopes) {
|
|
90
|
+
return scopes.every((s) => this.scopes.includes(s));
|
|
91
|
+
}
|
|
92
|
+
hasAnyScope(scopes) {
|
|
93
|
+
return scopes.some((s) => this.scopes.includes(s));
|
|
94
|
+
}
|
|
95
|
+
// -- Internal helpers ----------------------------------------------
|
|
96
|
+
/**
|
|
97
|
+
* Build the merged raw claims map from user fields + extra.authorization.claims.
|
|
98
|
+
* Mirrors the logic in AuthoritiesContextBuilder.build().
|
|
99
|
+
*/
|
|
100
|
+
buildRawClaims(source) {
|
|
101
|
+
const rawUser = resolveAuthUser(source);
|
|
102
|
+
const extraAuth = source.extra?.["authorization"];
|
|
103
|
+
const authorizationClaims = extraAuth?.["claims"] ?? {};
|
|
104
|
+
return {
|
|
105
|
+
...authorizationClaims,
|
|
106
|
+
...rawUser
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
export {
|
|
111
|
+
FrontMcpAuthContextImpl
|
|
112
|
+
};
|