import { createClient } from "@/lib/supabase/server" import { createAdminClient } from "@/lib/supabase/admin" import { headers } from "next/headers" export type SecurityEventType = 'login_success' | 'login_failed' | 'otp_sent' | 'otp_verified' | 'otp_failed' | 'logout' | 'master_otp_used' export async function logActivity( userId: string | null, eventType: SecurityEventType, details: Record = {} ) { try { // Use Admin Client to bypass RLS for inserting logs // This is crucial because logging often happens when user is not yet authenticated (e.g. login failed) const supabase = await createAdminClient() || await createClient() const headersList = await headers() let ip = headersList.get("x-forwarded-for") || headersList.get("x-real-ip") || 'unknown' if (ip.includes(',')) ip = ip.split(',')[0].trim() if (ip === '::1') ip = '127.0.0.1' const userAgent = headersList.get("user-agent") || 'unknown' await supabase.from('auth_logs').insert({ user_id: userId, event_type: eventType, ip_address: ip, user_agent: userAgent, details }) } catch (error) { console.error('Failed to log activity:', error) // Fail silently to not block user flow } } export async function checkRateLimit(action: string): Promise<{ blocked: boolean, remaining?: number, resetTime?: Date }> { const MAX_ATTEMPTS = 5 const WINDOW_MINUTES = 10 try { const supabase = await createAdminClient() || await createClient() const headersList = await headers() let ip = headersList.get("x-forwarded-for") || headersList.get("x-real-ip") || 'unknown' if (ip.includes(',')) ip = ip.split(',')[0].trim() if (ip === '::1') ip = '127.0.0.1' // Clean up old limits // Clean up old limits (logic simplified, variable unused) // Check current limit const { data: limit } = await supabase .from('rate_limits') .select('*') .eq('ip_address', ip) .eq('action', action) .single() if (!limit) { return { blocked: false, remaining: MAX_ATTEMPTS } } // If blocked if (limit.blocked_until && new Date(limit.blocked_until) > new Date()) { return { blocked: true, resetTime: new Date(limit.blocked_until) } } // If window expired, reset code handling happens in increment logic usually. // But here we just check. // Actually, simpler logic: // We will increment on failure. This function just checks if currently blocked. return { blocked: false, remaining: MAX_ATTEMPTS - (limit.count || 0) } } catch (error) { console.error('Rate limit check failed:', error) return { blocked: false } // Fail open } } export async function incrementRateLimit(action: string) { const BLOCK_DURATION_MINUTES = 15 try { const supabase = await createAdminClient() || await createClient() const headersList = await headers() let ip = headersList.get("x-forwarded-for") || headersList.get("x-real-ip") || 'unknown' if (ip.includes(',')) ip = ip.split(',')[0].trim() if (ip === '::1') ip = '127.0.0.1' const { data: limit } = await supabase .from('rate_limits') .select('*') .eq('ip_address', ip) .eq('action', action) .single() if (limit) { // Check if we should reset (if last attempt was long ago) const lastAttempt = new Date(limit.last_attempt) const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000) let newCount = limit.count + 1 let blockedUntil = null if (lastAttempt < tenMinutesAgo) { newCount = 1 // Reset if window passed } if (newCount >= 5) { blockedUntil = new Date(Date.now() + BLOCK_DURATION_MINUTES * 60 * 1000).toISOString() } await supabase .from('rate_limits') .update({ count: newCount, last_attempt: new Date().toISOString(), blocked_until: blockedUntil }) .eq('id', limit.id) } else { await supabase.from('rate_limits').insert({ ip_address: ip, action, count: 1, last_attempt: new Date().toISOString() }) } } catch (error) { console.error('Rate limit increment failed:', error) } }