mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 12:31:26 -05:00
Implement Phase 1 by adding: - supabase/functions/_shared/rateLimitMetrics.ts: in-memory rate limit metrics utilities (record, query, stats, clear, and helpers) - supabase/functions/_shared/authHelpers.ts: auth helpers for extracting userId, client IP, and rate-limit keys (code scaffolding)
143 lines
3.5 KiB
TypeScript
143 lines
3.5 KiB
TypeScript
/**
|
|
* 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<string | null> {
|
|
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,
|
|
};
|
|
}
|