Files
wehub-resource-sync 3cd11ababe
Check Markdown links / linkChecker (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:38:56 +08:00

138 lines
3.4 KiB
TypeScript

import { PluginContext, PluginHandler, PluginParameters } from '../types';
import { jwtVerify, importJWK, JWTHeaderParameters, JWK } from 'jose';
interface JWKSCacheOptions {
maxAge: number; // seconds
clockTolerance?: number; // seconds
maxTokenAge?: string; // 1d, 1h, 1m, 1s
}
async function getMatchingKey(token: string, jwks: any) {
const header = JSON.parse(
Buffer.from(token.split('.')[0], 'base64url').toString()
) as JWTHeaderParameters;
if (!header.kid) return null;
const jwk = (jwks.keys || []).find((key: JWK) => key.kid === header.kid);
if (!jwk) return null;
return importJWK(jwk, jwk.alg);
}
async function fetchAndCacheJWKS(
jwksUri: string,
cacheKey: string,
maxAge: number,
putInCacheWithValue?: Function
) {
const res = await fetch(jwksUri);
if (!res.ok) throw new Error(`Failed to fetch JWKS from ${jwksUri}`);
const jwks = await res.json();
await putInCacheWithValue?.(cacheKey, jwks, maxAge);
return jwks;
}
export const handler: PluginHandler = async (
context: PluginContext,
parameters: PluginParameters,
eventType: string,
options
) => {
let error = null;
let verdict = false;
let data = null;
try {
const jwtAuthHeader = parameters.headerKey || 'Authorization';
const jwksUri = parameters.jwksUri;
if (!jwksUri) throw new Error('Missing JWKS URI');
const authHeader = context.request?.headers?.[jwtAuthHeader];
if (!authHeader) {
return {
error,
verdict,
data: {
verdict: false,
explanation: 'Missing authorization header',
},
};
}
const token = authHeader.replace(/^Bearer\s+/i, '').trim();
const cacheKey = `jwks:${jwksUri}`;
const cacheOptions: JWKSCacheOptions = {
maxAge: parameters.cacheMaxAge || 86400, // 24h
clockTolerance: parameters.clockTolerance || 5, // 5s
maxTokenAge: parameters.maxTokenAge || '1d',
};
let jwks = await options?.getFromCacheByKey?.(cacheKey);
if (!jwks) {
jwks = await fetchAndCacheJWKS(
jwksUri,
cacheKey,
cacheOptions.maxAge,
options?.putInCacheWithValue
);
}
let key = null;
try {
key = await getMatchingKey(token, jwks);
if (!key) {
jwks = await fetchAndCacheJWKS(
jwksUri,
cacheKey,
cacheOptions.maxAge,
options?.putInCacheWithValue
);
key = await getMatchingKey(token, jwks);
}
if (!key) {
return {
error,
verdict,
data: {
verdict: false,
explanation: 'No matching key found for kid',
},
};
}
} catch (e: any) {
return {
error: e,
verdict,
data: {
verdict: false,
explanation: `JWT validation error: ${e.message}`,
},
};
}
try {
await jwtVerify(token, key, {
clockTolerance: cacheOptions.clockTolerance,
maxTokenAge: cacheOptions.maxTokenAge,
});
verdict = true;
data = {
verdict,
explanation: 'JWT token validation succeeded',
};
} catch (e: any) {
data = {
verdict: false,
explanation: `JWT validation error: ${e.message}`,
};
}
} catch (e: any) {
error = e;
data = {
verdict: false,
explanation: `JWT validation error: ${e.message}`,
};
}
return { error, verdict, data };
};