/** * Authentication Helper Functions * * Utilities for extracting user information from requests, * handling JWTs, and generating rate limit keys. */ import { createClient } from 'jsr:@supabase/supabase-js@2'; /** * Extract user ID from Authorization header JWT * Returns null if not authenticated or invalid token */ export function extractUserIdFromAuth(req: Request): string | null { try { const authHeader = req.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return null; } const token = authHeader.substring(7); // Decode JWT (just the payload, no verification needed for ID extraction) const parts = token.split('.'); if (parts.length !== 3) { return null; } const payload = JSON.parse(atob(parts[1])); return payload.sub || null; } catch (error) { console.error('Error extracting user ID from auth:', error); return null; } } /** * Get client IP address from request * Handles various proxy headers */ export function getClientIP(req: Request): string { // Check common proxy headers in order of preference const forwardedFor = req.headers.get('x-forwarded-for'); if (forwardedFor) { // x-forwarded-for can contain multiple IPs, take the first one return forwardedFor.split(',')[0].trim(); } const realIP = req.headers.get('x-real-ip'); if (realIP) { return realIP.trim(); } const cfConnectingIP = req.headers.get('cf-connecting-ip'); if (cfConnectingIP) { return cfConnectingIP.trim(); } // Fallback to a default value return 'unknown'; } /** * Generate a rate limit key for the request * Prefers user ID, falls back to IP */ export function getRateLimitKey(req: Request, prefix: string = 'user'): string { const userId = extractUserIdFromAuth(req); if (userId) { return `${prefix}:${userId}`; } const clientIP = getClientIP(req); return `${prefix}:ip:${clientIP}`; } /** * Verify JWT token and get user ID using Supabase client * More robust than manual decoding, verifies signature */ export async function verifyAuthAndGetUserId( req: Request, supabaseUrl: string, supabaseServiceKey: string ): Promise { try { const authHeader = req.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return null; } const token = authHeader.substring(7); // Create a Supabase client for verification const supabase = createClient(supabaseUrl, supabaseServiceKey); // Verify the JWT const { data: { user }, error } = await supabase.auth.getUser(token); if (error || !user) { return null; } return user.id; } catch (error) { console.error('Error verifying auth token:', error); return null; } } /** * Check if request has valid authentication */ export function hasValidAuth(req: Request): boolean { const authHeader = req.headers.get('Authorization'); return authHeader !== null && authHeader.startsWith('Bearer '); } /** * Extract request metadata for logging */ export interface RequestMetadata { userId: string | null; clientIP: string; userAgent: string | null; referer: string | null; method: string; path: string; } export function extractRequestMetadata(req: Request): RequestMetadata { const url = new URL(req.url); return { userId: extractUserIdFromAuth(req), clientIP: getClientIP(req), userAgent: req.headers.get('user-agent'), referer: req.headers.get('referer'), method: req.method, path: url.pathname, }; }