Architecture
The worker is a simple request handler that processes incoming requests and routes them to appropriate handlers:Copy
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// Handle OPTIONS (CORS preflight)
if (request.method === 'OPTIONS') {
return handleOptions();
}
// Handle authentication endpoints
if (url.pathname === '/auth/login') {
return handleLogin(request, env);
}
if (url.pathname === '/auth/guest') {
return handleGuestToken(request, env);
}
// Handle route proxying
for (const match of ROUTES) {
if (url.pathname.startsWith(match.prefix)) {
return handleProxy(request, env, match);
}
}
// 404 for unmatched routes
return errorResponse(ProjectRoute.CORE, 404, 'NOT_FOUND', 'Route not handled');
},
};
Environment Interface
Copy
interface Env {
ACCOUNTS: R2Bucket; // R2 bucket for admin credentials
GATEWAY_JWT_SECRET: string; // JWT signing secret
BUILD_SERVICE_URL: string; // Build service endpoint
KV_SERVICE_URL: string; // KV service endpoint
CORE_SERVICE_URL: string; // Core service endpoint
}
Key Functions
handleLogin
Authenticates admin users and issues JWT tokens.Copy
async function handleLogin(request: Request, env: Env) {
const { username, password } = await request.json();
// Get credentials from R2
const key = `auth/admins/${username}.json`;
const recordObject = await env.ACCOUNTS.get(key);
if (!recordObject) {
return errorResponse(ProjectRoute.AUTH, 401, 'INVALID_CREDENTIALS', 'Invalid credentials');
}
const record = JSON.parse(await recordObject.text());
// Verify password hash
const computedHash = await hashCredential(record.salt, password);
if (computedHash !== record.hash) {
return errorResponse(ProjectRoute.AUTH, 401, 'INVALID_CREDENTIALS', 'Invalid credentials');
}
// Generate JWT token
const token = await createGatewayToken({
projectId: record.username || username,
route: ProjectRoute.AUTH,
scopes: ['admin'],
environment: 'production',
issuedBy: 'gateway.metacogna.ai',
}, env.GATEWAY_JWT_SECRET);
return jsonResponse({
success: true,
token,
user: { username: record.username, role: record.role || 'admin' }
});
}
handleGuestToken
Generates temporary guest tokens for unauthenticated access.Copy
async function handleGuestToken(request: Request, env: Env) {
const body = await request.json();
const route = normalizeRoute(body.route); // Defaults to BUILD
const token = await createGatewayToken({
projectId: 'guest',
route,
scopes: ['guest'],
environment: 'staging',
issuedBy: 'gateway.metacogna.ai',
}, env.GATEWAY_JWT_SECRET);
return jsonResponse({ success: true, token, route });
}
handleProxy
Proxies requests to downstream services with authentication verification.Copy
async function handleProxy(request: Request, env: Env, match: RouteMatch) {
const targetBase = env[match.envKey];
if (!targetBase) {
return errorResponse(match.route, 502, 'MISSING_TARGET', `Missing target for ${match.prefix}`);
}
// Verify token if required
const shouldVerify = match.route === ProjectRoute.BUILD ||
request.headers.has('Authorization');
if (shouldVerify) {
try {
await verifyToken(request, env, match.route);
} catch (err: any) {
const code = err?.message === 'missing_token' ? 'MISSING_TOKEN' : 'INVALID_TOKEN';
const status = err?.message === 'missing_token' ? 401 : 403;
return errorResponse(match.route, status, code, 'Unauthorized request');
}
}
// Rewrite URL and forward request
const url = new URL(request.url);
const targetUrl = rewriteUrl(url, match, targetBase);
const headers = new Headers(request.headers);
headers.delete('Authorization');
headers.set('X-Gateway-Route', match.route);
const proxyReq = new Request(targetUrl.toString(), {
method: request.method,
headers,
body: request.body,
redirect: 'manual',
});
return fetch(proxyReq);
}
verifyToken
Verifies JWT tokens and validates route matching.Copy
async function verifyToken(request: Request, env: Env, route: ProjectRoute) {
const auth = request.headers.get('Authorization');
if (!auth || !auth.startsWith('Bearer ')) {
throw new Error('missing_token');
}
const token = auth.replace('Bearer ', '').trim();
const { claims } = await verifyGatewayToken(token, env.GATEWAY_JWT_SECRET);
// Validate route matches (CORE and AUTH have elevated privileges)
if (claims.route !== route &&
claims.route !== ProjectRoute.CORE &&
claims.route !== ProjectRoute.AUTH) {
throw new Error('route_mismatch');
}
return claims;
}
Helper Functions
Password Hashing
Copy
async function hashCredential(salt: string, password: string) {
const encoder = new TextEncoder();
const buffer = encoder.encode(`${salt}${password}`);
const digest = await crypto.subtle.digest('SHA-256', buffer);
return toBase64(digest);
}
URL Rewriting
Copy
function rewriteUrl(url: URL, match: RouteMatch, target: string) {
const strippedPath = url.pathname.substring(match.prefix.length) || '/';
const targetUrl = new URL(target);
targetUrl.pathname = `${targetUrl.pathname.replace(/\/$/, '')}${strippedPath}`;
targetUrl.search = url.search;
return targetUrl;
}
Error Responses
All errors follow the unifiedGatewayErrorSchema:
Copy
const errorResponse = (
route: ProjectRoute,
status: number,
code: string,
message: string,
details?: any
) => jsonResponse(
GatewayErrorSchema.parse({
route,
status,
code,
message,
requestId: crypto.randomUUID(),
timestamp: new Date().toISOString(),
details,
}),
status,
);
CORS Handling
The gateway handles CORS preflight requests:Copy
function handleOptions() {
return new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Authorization, Content-Type',
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS',
'Access-Control-Max-Age': '86400',
},
});
}
Configuration
wrangler.toml
Copy
name = "metacogna-gateway"
main = "src/index.ts"
compatibility_date = "2024-07-01"
workers_dev = false
routes = [
{ pattern = "api.metacogna.ai/*", zone_name = "metacogna.ai" }
]
[vars]
GATEWAY_JWT_SECRET = "changeme"
BUILD_SERVICE_URL = "https://build.metacogna.ai"
KV_SERVICE_URL = "https://kv.metacogna.ai"
CORE_SERVICE_URL = "https://parti.metacogna.ai"
[[r2_buckets]]
binding = "ACCOUNTS"
bucket_name = "metacogna-accounts"
[[services]]
binding = "CORE_SERVICE"
service = "metacogna"
[[services]]
binding = "PORTAL_SERVICE"
service = "metacogna-ai-worker"
Deployment
Deploy the gateway worker:Copy
cd gateway-api/packages/gateway-worker
bun wrangler deploy
Copy
cd gateway-api
./scripts/deploy-gateway.sh
Related Documentation
- Authentication API - Authentication flow
- Routing API - Route configuration
- Shared Package - JWT utilities