From f5939941d4f7d4247e7de6e4695fb50c33f40429 Mon Sep 17 00:00:00 2001 From: sirpiglr <49359077-sirpiglr@users.noreply.replit.com> Date: Sat, 13 Dec 2025 03:06:44 +0000 Subject: [PATCH] Implement shared authentication and authorization helpers for API security Creates a new `api/_auth.ts` file containing functions for user authentication, role-based authorization, and compliance event logging, enhancing API security and access control. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9203795e-937a-4306-b81d-b4d5c78c240e Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 21812339-5bf1-448b-9ecf-95db42576ce2 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7c94b7a0-29c7-4f2e-94ef-44b2153872b7/9203795e-937a-4306-b81d-b4d5c78c240e/aPpJgbb Replit-Helium-Checkpoint-Created: true --- api/_auth.ts | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 api/_auth.ts diff --git a/api/_auth.ts b/api/_auth.ts new file mode 100644 index 00000000..58dd4f8d --- /dev/null +++ b/api/_auth.ts @@ -0,0 +1,136 @@ +import type { VercelRequest, VercelResponse } from "@vercel/node"; +import { createClient } from "@supabase/supabase-js"; + +const SUPABASE_URL = process.env.VITE_SUPABASE_URL || process.env.SUPABASE_URL || ""; +const SUPABASE_ANON_KEY = process.env.VITE_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY || ""; +const SUPABASE_SERVICE_ROLE = process.env.SUPABASE_SERVICE_ROLE || ""; + +export interface AuthenticatedUser { + id: string; + email?: string; + user_type?: 'admin' | 'creator' | 'client' | 'staff' | 'user'; + primary_arm?: string; +} + +export interface AuthResult { + user: AuthenticatedUser | null; + error: string | null; + userClient: any | null; + adminClient: any | null; +} + +export function getAdminClient() { + if (!SUPABASE_URL || !SUPABASE_SERVICE_ROLE) { + throw new Error("Supabase admin credentials not configured"); + } + return createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE, { + auth: { autoRefreshToken: false, persistSession: false }, + }); +} + +export function getUserClient(accessToken: string) { + if (!SUPABASE_URL || !SUPABASE_ANON_KEY) { + throw new Error("Supabase credentials not configured"); + } + return createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { + auth: { autoRefreshToken: false, persistSession: false }, + global: { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }, + }); +} + +export async function authenticateRequest(req: VercelRequest): Promise { + const authHeader = req.headers.authorization; + + if (!authHeader?.startsWith('Bearer ')) { + return { user: null, error: 'Unauthorized - Bearer token required', userClient: null, adminClient: null }; + } + + const token = authHeader.split(' ')[1]; + const adminClient = getAdminClient(); + + const { data: { user }, error: authError } = await adminClient.auth.getUser(token); + + if (authError || !user) { + return { user: null, error: 'Invalid or expired token', userClient: null, adminClient: null }; + } + + const { data: profile } = await adminClient + .from('user_profiles') + .select('user_type, primary_arm') + .eq('id', user.id) + .single(); + + const userClient = getUserClient(token); + + return { + user: { + id: user.id, + email: user.email, + user_type: profile?.user_type || 'user', + primary_arm: profile?.primary_arm, + }, + error: null, + userClient, + adminClient, + }; +} + +export function requireAuth(result: AuthResult, res: VercelResponse): result is AuthResult & { user: AuthenticatedUser } { + if (result.error || !result.user) { + res.status(401).json({ error: result.error || 'Unauthorized' }); + return false; + } + return true; +} + +export function requireAdmin(result: AuthResult, res: VercelResponse): boolean { + if (!requireAuth(result, res)) return false; + if (result.user!.user_type !== 'admin') { + res.status(403).json({ error: 'Admin access required' }); + return false; + } + return true; +} + +export function requireRole(result: AuthResult, roles: string[], res: VercelResponse): boolean { + if (!requireAuth(result, res)) return false; + if (!roles.includes(result.user!.user_type || 'user')) { + res.status(403).json({ error: `Requires role: ${roles.join(' or ')}` }); + return false; + } + return true; +} + +export async function logComplianceEvent( + adminClient: any, + event: { + entity_type: string; + entity_id: string; + event_type: string; + event_category: 'compliance' | 'financial' | 'access' | 'data_change' | 'tax_reporting' | 'legal'; + actor_id?: string; + actor_role?: string; + realm_context?: string; + description?: string; + payload?: Record; + sensitive_data_accessed?: boolean; + financial_amount?: number; + legal_entity?: string; + cross_entity_access?: boolean; + }, + req?: VercelRequest +) { + return adminClient.from('nexus_compliance_events').insert({ + ...event, + ip_address: req?.headers['x-forwarded-for']?.toString() || req?.socket?.remoteAddress, + user_agent: req?.headers['user-agent'], + }); +} + +export default async function handler(req: VercelRequest, res: VercelResponse) { + return res.status(501).json({ error: "Not a handler" }); +}