@upstash/context7-mcp 3.0.0 → 3.1.0
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/dist/lib/jwt.js +63 -9
- package/package.json +1 -1
package/dist/lib/jwt.js
CHANGED
|
@@ -1,17 +1,71 @@
|
|
|
1
1
|
import * as jose from "jose";
|
|
2
|
-
import { CLERK_DOMAIN } from "./constants.js";
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
2
|
+
import { CLERK_DOMAIN, CONTEXT7_API_BASE_URL } from "./constants.js";
|
|
3
|
+
const CLERK_ISSUER = `https://${CLERK_DOMAIN}`;
|
|
4
|
+
const clerkJwks = jose.createRemoteJWKSet(new URL(`https://${CLERK_DOMAIN}/.well-known/jwks.json`));
|
|
5
|
+
const ENTRA_V2_ISSUER_RE = /^https:\/\/login\.microsoftonline\.com\/[0-9a-f-]{36}\/v2\.0$/;
|
|
6
|
+
const jwksByTenant = new Map();
|
|
7
|
+
function entraJwks(tenantId) {
|
|
8
|
+
let jwks = jwksByTenant.get(tenantId);
|
|
9
|
+
if (!jwks) {
|
|
10
|
+
jwks = jose.createRemoteJWKSet(new URL(`https://login.microsoftonline.com/${tenantId}/discovery/v2.0/keys`));
|
|
11
|
+
jwksByTenant.set(tenantId, jwks);
|
|
12
|
+
}
|
|
13
|
+
return jwks;
|
|
14
|
+
}
|
|
15
|
+
const CONFIG_TTL_MS = 5 * 60 * 1000;
|
|
16
|
+
const configByAudience = new Map();
|
|
17
|
+
async function fetchEntraConfig(audience) {
|
|
18
|
+
const now = Date.now();
|
|
19
|
+
const cached = configByAudience.get(audience);
|
|
20
|
+
if (cached && cached.expiresAt > now)
|
|
21
|
+
return cached.value;
|
|
22
|
+
try {
|
|
23
|
+
const res = await fetch(`${CONTEXT7_API_BASE_URL}/v2/entra/config/${encodeURIComponent(audience)}`);
|
|
24
|
+
if (res.ok) {
|
|
25
|
+
const value = (await res.json());
|
|
26
|
+
configByAudience.set(audience, { value, expiresAt: now + CONFIG_TTL_MS });
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
if (res.status === 404) {
|
|
30
|
+
// Authoritative "not configured" response — safe to cache the miss so we
|
|
31
|
+
// don't hammer the app on every token verification.
|
|
32
|
+
configByAudience.set(audience, { value: null, expiresAt: now + CONFIG_TTL_MS });
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Network or JSON parse error: transient. Fall through without caching so
|
|
38
|
+
// the next request retries.
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
6
42
|
export function isJWT(token) {
|
|
7
|
-
|
|
8
|
-
return parts.length === 3;
|
|
43
|
+
return token.split(".").length === 3;
|
|
9
44
|
}
|
|
10
45
|
export async function validateJWT(token) {
|
|
11
46
|
try {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
47
|
+
const decoded = jose.decodeJwt(token);
|
|
48
|
+
const iss = typeof decoded.iss === "string" ? decoded.iss : "";
|
|
49
|
+
if (ENTRA_V2_ISSUER_RE.test(iss)) {
|
|
50
|
+
const audience = typeof decoded.aud === "string" ? decoded.aud : "";
|
|
51
|
+
if (!audience)
|
|
52
|
+
return { valid: false, error: "Missing audience" };
|
|
53
|
+
const config = await fetchEntraConfig(audience);
|
|
54
|
+
if (!config)
|
|
55
|
+
return { valid: false, error: "Unknown audience" };
|
|
56
|
+
const { payload } = await jose.jwtVerify(token, entraJwks(config.tenantId), {
|
|
57
|
+
issuer: `https://login.microsoftonline.com/${config.tenantId}/v2.0`,
|
|
58
|
+
audience,
|
|
59
|
+
});
|
|
60
|
+
if (config.requiredScope) {
|
|
61
|
+
const scopes = String(payload.scp ?? "").split(" ");
|
|
62
|
+
if (!scopes.includes(config.requiredScope)) {
|
|
63
|
+
return { valid: false, error: "Missing required scope" };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return { valid: true };
|
|
67
|
+
}
|
|
68
|
+
await jose.jwtVerify(token, clerkJwks, { issuer: CLERK_ISSUER });
|
|
15
69
|
return { valid: true };
|
|
16
70
|
}
|
|
17
71
|
catch (error) {
|