mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-24 18:11:12 -05:00
Fix remaining production readiness issues
This commit is contained in:
@@ -68,33 +68,18 @@ serve(async (req) => {
|
||||
});
|
||||
|
||||
// Verify JWT and get authenticated user
|
||||
console.log('🔍 [AUTH DEBUG] Attempting getUser()...', {
|
||||
hasAuthHeader: !!authHeader,
|
||||
authHeaderLength: authHeader?.length,
|
||||
authHeaderPrefix: authHeader?.substring(0, 20) + '...',
|
||||
supabaseUrl,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
const { data: { user }, error: authError } = await supabaseAuth.auth.getUser();
|
||||
|
||||
console.log('🔍 [AUTH DEBUG] getUser() result:', {
|
||||
console.log('[AUTH] User auth result:', {
|
||||
hasUser: !!user,
|
||||
userId: user?.id,
|
||||
userEmail: user?.email,
|
||||
hasError: !!authError,
|
||||
errorMessage: authError?.message,
|
||||
errorName: authError?.name,
|
||||
errorStatus: authError?.status,
|
||||
errorCode: authError?.code
|
||||
hasError: !!authError
|
||||
});
|
||||
|
||||
if (authError || !user) {
|
||||
console.error('❌ [AUTH DEBUG] Auth verification failed:', {
|
||||
error: authError,
|
||||
errorDetails: JSON.stringify(authError),
|
||||
authHeaderPresent: !!authHeader,
|
||||
authHeaderSample: authHeader?.substring(0, 30) + '...'
|
||||
console.error('[AUTH] Auth verification failed:', {
|
||||
error: authError?.message,
|
||||
code: authError?.code
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
@@ -106,7 +91,7 @@ serve(async (req) => {
|
||||
);
|
||||
}
|
||||
|
||||
console.log('✅ [AUTH DEBUG] Authentication successful for user:', user.id);
|
||||
console.log('[AUTH] Authentication successful:', user.id);
|
||||
|
||||
// SECURITY NOTE: Service role key used later in this function
|
||||
// Reason: Need to bypass RLS to write approved changes to entity tables
|
||||
@@ -122,22 +107,18 @@ serve(async (req) => {
|
||||
);
|
||||
|
||||
// Check if user has moderator permissions using service role to bypass RLS
|
||||
console.log('🔍 [ROLE CHECK] Fetching roles for user:', authenticatedUserId);
|
||||
|
||||
const { data: roles, error: rolesError } = await supabase
|
||||
.from('user_roles')
|
||||
.select('role')
|
||||
.eq('user_id', authenticatedUserId);
|
||||
|
||||
console.log('🔍 [ROLE CHECK] Query result:', {
|
||||
roles,
|
||||
error: rolesError,
|
||||
console.log('[ROLE_CHECK] Query result:', {
|
||||
rolesCount: roles?.length,
|
||||
userId: authenticatedUserId
|
||||
error: rolesError?.message
|
||||
});
|
||||
|
||||
if (rolesError) {
|
||||
console.error('❌ [ROLE CHECK] Failed:', rolesError);
|
||||
console.error('[ROLE_CHECK] Failed:', { error: rolesError.message });
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Failed to verify user permissions.' }),
|
||||
{ status: 403, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||
@@ -149,21 +130,17 @@ serve(async (req) => {
|
||||
userRoles.includes('admin') ||
|
||||
userRoles.includes('superuser');
|
||||
|
||||
console.log('🔍 [ROLE CHECK] Result:', {
|
||||
userRoles,
|
||||
isModerator,
|
||||
userId: authenticatedUserId
|
||||
});
|
||||
console.log('[ROLE_CHECK] Result:', { isModerator, userId: authenticatedUserId });
|
||||
|
||||
if (!isModerator) {
|
||||
console.error('❌ [ROLE CHECK] Insufficient permissions');
|
||||
console.error('[ROLE_CHECK] Insufficient permissions');
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Insufficient permissions. Moderator role required.' }),
|
||||
{ status: 403, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
console.log('✅ [ROLE CHECK] User is moderator');
|
||||
console.log('[ROLE_CHECK] User is moderator');
|
||||
|
||||
// Phase 2: AAL2 Enforcement - Check if user has MFA enrolled and requires AAL2
|
||||
// Parse JWT directly from Authorization header to get AAL level
|
||||
@@ -171,17 +148,17 @@ serve(async (req) => {
|
||||
const payload = JSON.parse(atob(jwt.split('.')[1]));
|
||||
const aal = payload.aal || 'aal1';
|
||||
|
||||
console.log('🔍 [AAL CHECK] Session AAL level:', { aal, userId: authenticatedUserId });
|
||||
console.log('[AAL_CHECK] Session AAL level:', { aal, userId: authenticatedUserId });
|
||||
|
||||
// Check if user has MFA enrolled
|
||||
const { data: factorsData } = await supabaseAuth.auth.mfa.listFactors();
|
||||
const hasMFA = factorsData?.totp?.some(f => f.status === 'verified') || false;
|
||||
|
||||
console.log('🔍 [MFA CHECK] MFA status:', { hasMFA, userId: authenticatedUserId });
|
||||
console.log('[MFA_CHECK] MFA status:', { hasMFA, userId: authenticatedUserId });
|
||||
|
||||
// Enforce AAL2 if MFA is enrolled
|
||||
if (hasMFA && aal !== 'aal2') {
|
||||
console.error('❌ [AAL CHECK] AAL2 required but session is at AAL1', { userId: authenticatedUserId });
|
||||
console.error('[AAL_CHECK] AAL2 required but session is at AAL1');
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'MFA verification required',
|
||||
@@ -192,7 +169,7 @@ serve(async (req) => {
|
||||
);
|
||||
}
|
||||
|
||||
console.log('✅ [AAL CHECK] AAL2 check passed', { userId: authenticatedUserId, hasMFA, aal });
|
||||
console.log('[AAL_CHECK] AAL2 check passed:', { userId: authenticatedUserId, hasMFA, aal });
|
||||
|
||||
const { itemIds, submissionId }: ApprovalRequest = await req.json();
|
||||
|
||||
@@ -229,7 +206,7 @@ serve(async (req) => {
|
||||
);
|
||||
}
|
||||
|
||||
console.log('Processing selective approval:', { itemIds, userId: authenticatedUserId, submissionId });
|
||||
console.log('[APPROVAL] Processing selective approval:', { itemIds, userId: authenticatedUserId, submissionId });
|
||||
|
||||
// Fetch all items for the submission
|
||||
const { data: items, error: fetchError } = await supabase
|
||||
@@ -258,9 +235,14 @@ serve(async (req) => {
|
||||
let sortedItems;
|
||||
try {
|
||||
sortedItems = topologicalSort(items);
|
||||
} catch (sortError) {
|
||||
} catch (sortError: unknown) {
|
||||
const errorMessage = sortError instanceof Error ? sortError.message : 'Failed to sort items';
|
||||
console.error('Topological sort failed:', errorMessage);
|
||||
console.error('[APPROVAL ERROR] Topological sort failed:', {
|
||||
submissionId,
|
||||
itemCount: items.length,
|
||||
error: errorMessage,
|
||||
userId: authenticatedUserId
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Invalid submission structure',
|
||||
@@ -284,13 +266,16 @@ serve(async (req) => {
|
||||
// Process items in order
|
||||
for (const item of sortedItems) {
|
||||
try {
|
||||
console.log(`Processing item ${item.id} of type ${item.item_type}`);
|
||||
console.log('[APPROVAL] Processing item:', { itemId: item.id, itemType: item.item_type });
|
||||
|
||||
// Validate entity data with strict validation, passing original_data for edits
|
||||
const validation = validateEntityDataStrict(item.item_type, item.item_data, item.original_data);
|
||||
|
||||
if (validation.blockingErrors.length > 0) {
|
||||
console.error(`❌ Blocking errors for item ${item.id}:`, validation.blockingErrors);
|
||||
console.error('[APPROVAL] Blocking validation errors:', {
|
||||
itemId: item.id,
|
||||
errors: validation.blockingErrors
|
||||
});
|
||||
|
||||
// Fail the entire batch if ANY item has blocking errors
|
||||
return new Response(JSON.stringify({
|
||||
@@ -306,7 +291,10 @@ serve(async (req) => {
|
||||
}
|
||||
|
||||
if (validation.warnings.length > 0) {
|
||||
console.warn(`⚠️ Warnings for item ${item.id}:`, validation.warnings);
|
||||
console.warn('[APPROVAL] Validation warnings:', {
|
||||
itemId: item.id,
|
||||
warnings: validation.warnings
|
||||
});
|
||||
// Continue processing - warnings don't block approval
|
||||
}
|
||||
|
||||
@@ -319,7 +307,7 @@ serve(async (req) => {
|
||||
});
|
||||
|
||||
if (setUserIdError) {
|
||||
console.error('Failed to set user context:', setUserIdError);
|
||||
console.error('[APPROVAL] Failed to set user context:', { error: setUserIdError.message });
|
||||
}
|
||||
|
||||
// Set submission ID for version tracking
|
||||
@@ -330,7 +318,7 @@ serve(async (req) => {
|
||||
});
|
||||
|
||||
if (setSubmissionIdError) {
|
||||
console.error('Failed to set submission context:', setSubmissionIdError);
|
||||
console.error('[APPROVAL] Failed to set submission context:', { error: setSubmissionIdError.message });
|
||||
}
|
||||
|
||||
// Resolve dependencies in item data
|
||||
@@ -390,9 +378,16 @@ serve(async (req) => {
|
||||
success: true
|
||||
});
|
||||
|
||||
console.log(`Successfully approved item ${item.id} -> entity ${entityId}`);
|
||||
} catch (error) {
|
||||
console.error(`Error processing item ${item.id}:`, error);
|
||||
console.log('[APPROVAL SUCCESS]', { itemId: item.id, entityId, itemType: item.item_type });
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
console.error('[APPROVAL ERROR] Item approval failed:', {
|
||||
itemId: item.id,
|
||||
itemType: item.item_type,
|
||||
error: errorMessage,
|
||||
userId: authenticatedUserId,
|
||||
submissionId
|
||||
});
|
||||
|
||||
const isDependencyError = error instanceof Error && (
|
||||
error.message.includes('Missing dependency') ||
|
||||
@@ -404,7 +399,7 @@ serve(async (req) => {
|
||||
itemId: item.id,
|
||||
itemType: item.item_type,
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
error: errorMessage,
|
||||
isDependencyFailure: isDependencyError
|
||||
});
|
||||
}
|
||||
@@ -433,7 +428,10 @@ serve(async (req) => {
|
||||
.eq('id', update.id);
|
||||
|
||||
if (batchApproveError) {
|
||||
console.error(`Failed to approve item ${update.id}:`, batchApproveError);
|
||||
console.error('[APPROVAL] Failed to approve item:', {
|
||||
itemId: update.id,
|
||||
error: batchApproveError.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -461,7 +459,10 @@ serve(async (req) => {
|
||||
.eq('id', update.id);
|
||||
|
||||
if (batchRejectError) {
|
||||
console.error(`Failed to reject item ${update.id}:`, batchRejectError);
|
||||
console.error('[APPROVAL] Failed to reject item:', {
|
||||
itemId: update.id,
|
||||
error: batchRejectError.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -504,7 +505,7 @@ serve(async (req) => {
|
||||
.eq('id', submissionId);
|
||||
|
||||
if (updateError) {
|
||||
console.error('Failed to update submission status:', updateError);
|
||||
console.error('[APPROVAL] Failed to update submission status:', { error: updateError.message });
|
||||
}
|
||||
|
||||
return new Response(
|
||||
@@ -515,8 +516,13 @@ serve(async (req) => {
|
||||
}),
|
||||
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error in process-selective-approval:', error);
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'An unexpected error occurred';
|
||||
console.error('[APPROVAL ERROR] Process failed:', {
|
||||
error: errorMessage,
|
||||
userId: authenticatedUserId,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
return createErrorResponse(
|
||||
error,
|
||||
500,
|
||||
|
||||
Reference in New Issue
Block a user