> ## Documentation Index
> Fetch the complete documentation index at: https://docs.metacogna.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Gateway Worker

> Cloudflare Worker implementation details

The Gateway Worker is the main entry point for all API requests. It handles routing, authentication, and request proxying.

## Architecture

The worker is a simple request handler that processes incoming requests and routes them to appropriate handlers:

```typescript theme={null}
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

```typescript theme={null}
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.

<CodeGroup>
  ```typescript Login Handler theme={null}
  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' }
    });
  }
  ```
</CodeGroup>

### handleGuestToken

Generates temporary guest tokens for unauthenticated access.

```typescript theme={null}
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.

```typescript theme={null}
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.

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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 unified `GatewayErrorSchema`:

```typescript theme={null}
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:

```typescript theme={null}
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

```toml theme={null}
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:

```bash theme={null}
cd gateway-api/packages/gateway-worker
bun wrangler deploy
```

Or use the deployment script:

```bash theme={null}
cd gateway-api
./scripts/deploy-gateway.sh
```

## Related Documentation

* [Authentication API](/gateway-api/api/authentication) - Authentication flow
* [Routing API](/gateway-api/api/routing) - Route configuration
* [Shared Package](/gateway-api/backend/shared-package) - JWT utilities
