edge-master
Version:
A Micro Framework for Edges
149 lines (148 loc) • 4.7 kB
JavaScript
import { InterceptorType } from '../types/interceptor';
const defaultGetToken = (req) => {
const authHeader = req.headers.get('Authorization');
if (!authHeader)
return null;
// Support "Bearer <token>" format
const parts = authHeader.split(' ');
if (parts.length === 2 && parts[0].toLowerCase() === 'bearer') {
return parts[1];
}
// Support direct token
return authHeader;
};
const defaultOnAuthenticationFailed = (req, error) => {
return new Response(JSON.stringify({
error: 'Unauthorized',
message: error.message || 'Invalid or missing authentication token',
}), {
status: 401,
headers: {
'Content-Type': 'application/json',
'WWW-Authenticate': 'Bearer',
},
});
};
/**
* Creates a JWT authentication interceptor
*
* Example usage:
* ```typescript
* import { jwtInterceptor } from 'edge-master/interceptors';
*
* // Using a JWT library like jose
* const jwt = jwtInterceptor({
* verify: async (token) => {
* const { payload } = await jwtVerify(token, secret);
* return payload;
* },
* exclude: ['/public', '/login'],
* });
*
* app.addInterceptor(jwt);
* ```
*/
export function jwtInterceptor(options) {
const { verify, getToken = defaultGetToken, exclude, onAuthenticationFailed = defaultOnAuthenticationFailed, stateKey = 'user', } = options;
const shouldExclude = (req) => {
if (!exclude)
return false;
if (typeof exclude === 'function') {
return exclude(req);
}
const url = new URL(req.url);
return exclude.some(pattern => {
// Exact match
if (url.pathname === pattern)
return true;
// Prefix match
if (pattern.endsWith('*')) {
const prefix = pattern.slice(0, -1);
return url.pathname.startsWith(prefix);
}
return false;
});
};
return {
type: InterceptorType.Request,
async intercept(ctx) {
const req = ctx.reqCtx.req;
// Skip authentication for excluded paths
if (shouldExclude(req)) {
return req;
}
try {
// Extract token
const token = getToken(req);
if (!token) {
throw new Error('No authentication token provided');
}
// Verify and decode token
const payload = await verify(token);
// Store payload in context state
ctx.state.set(stateKey, payload);
return req;
}
catch (error) {
// Authentication failed
ctx.responder(onAuthenticationFailed(req, error));
return req; // This won't be used as responder short-circuits
}
},
};
}
export function apiKeyInterceptor(options) {
const { verify, headerName = 'X-API-Key', exclude, onAuthenticationFailed = (req) => {
return new Response(JSON.stringify({
error: 'Unauthorized',
message: 'Invalid or missing API key',
}), {
status: 401,
headers: {
'Content-Type': 'application/json',
},
});
}, } = options;
const shouldExclude = (req) => {
if (!exclude)
return false;
if (typeof exclude === 'function') {
return exclude(req);
}
const url = new URL(req.url);
return exclude.some(pattern => {
if (url.pathname === pattern)
return true;
if (pattern.endsWith('*')) {
const prefix = pattern.slice(0, -1);
return url.pathname.startsWith(prefix);
}
return false;
});
};
return {
type: InterceptorType.Request,
async intercept(ctx) {
const req = ctx.reqCtx.req;
// Skip authentication for excluded paths
if (shouldExclude(req)) {
return req;
}
const apiKey = req.headers.get(headerName);
if (!apiKey) {
ctx.responder(onAuthenticationFailed(req));
return req;
}
try {
const isValid = await verify(apiKey);
if (!isValid) {
ctx.responder(onAuthenticationFailed(req));
}
}
catch (error) {
ctx.responder(onAuthenticationFailed(req));
}
return req;
},
};
}