mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 10:31:13 -05:00
Add edge function logging
This commit is contained in:
@@ -3,6 +3,7 @@ import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
|
||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
||||
import { validateEntityData, validateEntityDataStrict } from "./validation.ts";
|
||||
import { createErrorResponse } from "../_shared/errorSanitizer.ts";
|
||||
import { edgeLogger } from "../_shared/logger.ts";
|
||||
|
||||
const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
@@ -70,16 +71,12 @@ serve(async (req) => {
|
||||
// Verify JWT and get authenticated user
|
||||
const { data: { user }, error: authError } = await supabaseAuth.auth.getUser();
|
||||
|
||||
console.log('[AUTH] User auth result:', {
|
||||
hasUser: !!user,
|
||||
userId: user?.id,
|
||||
hasError: !!authError
|
||||
});
|
||||
edgeLogger.info('User auth check', { action: 'approval_auth', hasUser: !!user, userId: user?.id });
|
||||
|
||||
if (authError || !user) {
|
||||
console.error('[AUTH] Auth verification failed:', {
|
||||
error: authError?.message,
|
||||
code: authError?.code
|
||||
edgeLogger.error('Auth verification failed', {
|
||||
action: 'approval_auth',
|
||||
error: authError?.message
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
@@ -91,7 +88,7 @@ serve(async (req) => {
|
||||
);
|
||||
}
|
||||
|
||||
console.log('[AUTH] Authentication successful:', user.id);
|
||||
edgeLogger.info('Authentication successful', { action: 'approval_auth_success', userId: user.id });
|
||||
|
||||
// SECURITY NOTE: Service role key used later in this function
|
||||
// Reason: Need to bypass RLS to write approved changes to entity tables
|
||||
@@ -112,13 +109,10 @@ serve(async (req) => {
|
||||
.select('role')
|
||||
.eq('user_id', authenticatedUserId);
|
||||
|
||||
console.log('[ROLE_CHECK] Query result:', {
|
||||
rolesCount: roles?.length,
|
||||
error: rolesError?.message
|
||||
});
|
||||
edgeLogger.info('Role check query result', { action: 'approval_role_check', userId: authenticatedUserId, rolesCount: roles?.length });
|
||||
|
||||
if (rolesError) {
|
||||
console.error('[ROLE_CHECK] Failed:', { error: rolesError.message });
|
||||
edgeLogger.error('Role check failed', { action: 'approval_role_check', error: rolesError.message });
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Failed to verify user permissions.' }),
|
||||
{ status: 403, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||
@@ -130,17 +124,17 @@ serve(async (req) => {
|
||||
userRoles.includes('admin') ||
|
||||
userRoles.includes('superuser');
|
||||
|
||||
console.log('[ROLE_CHECK] Result:', { isModerator, userId: authenticatedUserId });
|
||||
edgeLogger.info('Role check result', { action: 'approval_role_result', userId: authenticatedUserId, isModerator });
|
||||
|
||||
if (!isModerator) {
|
||||
console.error('[ROLE_CHECK] Insufficient permissions');
|
||||
edgeLogger.error('Insufficient permissions', { action: 'approval_role_insufficient', userId: authenticatedUserId });
|
||||
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');
|
||||
edgeLogger.info('User is moderator', { action: 'approval_role_verified', userId: authenticatedUserId });
|
||||
|
||||
// Phase 2: AAL2 Enforcement - Check if user has MFA enrolled and requires AAL2
|
||||
// Parse JWT directly from Authorization header to get AAL level
|
||||
@@ -148,17 +142,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 });
|
||||
edgeLogger.info('Session AAL level', { action: 'approval_aal_check', userId: authenticatedUserId, aal });
|
||||
|
||||
// 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 });
|
||||
edgeLogger.info('MFA status', { action: 'approval_mfa_check', userId: authenticatedUserId, hasMFA });
|
||||
|
||||
// Enforce AAL2 if MFA is enrolled
|
||||
if (hasMFA && aal !== 'aal2') {
|
||||
console.error('[AAL_CHECK] AAL2 required but session is at AAL1');
|
||||
edgeLogger.error('AAL2 required but session is at AAL1', { action: 'approval_aal_violation', userId: authenticatedUserId });
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'MFA verification required',
|
||||
@@ -169,7 +163,7 @@ serve(async (req) => {
|
||||
);
|
||||
}
|
||||
|
||||
console.log('[AAL_CHECK] AAL2 check passed:', { userId: authenticatedUserId, hasMFA, aal });
|
||||
edgeLogger.info('AAL2 check passed', { action: 'approval_aal_pass', userId: authenticatedUserId, hasMFA, aal });
|
||||
|
||||
const { itemIds, submissionId }: ApprovalRequest = await req.json();
|
||||
|
||||
@@ -206,7 +200,7 @@ serve(async (req) => {
|
||||
);
|
||||
}
|
||||
|
||||
console.log('[APPROVAL] Processing selective approval:', { itemIds, userId: authenticatedUserId, submissionId });
|
||||
edgeLogger.info('Processing selective approval', { action: 'approval_start', itemCount: itemIds.length, userId: authenticatedUserId, submissionId });
|
||||
|
||||
// Fetch all items for the submission
|
||||
const { data: items, error: fetchError } = await supabase
|
||||
@@ -237,11 +231,12 @@ serve(async (req) => {
|
||||
sortedItems = topologicalSort(items);
|
||||
} catch (sortError: unknown) {
|
||||
const errorMessage = sortError instanceof Error ? sortError.message : 'Failed to sort items';
|
||||
console.error('[APPROVAL ERROR] Topological sort failed:', {
|
||||
edgeLogger.error('Topological sort failed', {
|
||||
action: 'approval_sort_fail',
|
||||
submissionId,
|
||||
itemCount: items.length,
|
||||
error: errorMessage,
|
||||
userId: authenticatedUserId
|
||||
userId: authenticatedUserId,
|
||||
error: errorMessage
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
@@ -266,13 +261,14 @@ serve(async (req) => {
|
||||
// Process items in order
|
||||
for (const item of sortedItems) {
|
||||
try {
|
||||
console.log('[APPROVAL] Processing item:', { itemId: item.id, itemType: item.item_type });
|
||||
edgeLogger.info('Processing item', { action: 'approval_process_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('[APPROVAL] Blocking validation errors:', {
|
||||
edgeLogger.error('Blocking validation errors', {
|
||||
action: 'approval_validation_fail',
|
||||
itemId: item.id,
|
||||
errors: validation.blockingErrors
|
||||
});
|
||||
@@ -291,7 +287,8 @@ serve(async (req) => {
|
||||
}
|
||||
|
||||
if (validation.warnings.length > 0) {
|
||||
console.warn('[APPROVAL] Validation warnings:', {
|
||||
edgeLogger.warn('Validation warnings', {
|
||||
action: 'approval_validation_warning',
|
||||
itemId: item.id,
|
||||
warnings: validation.warnings
|
||||
});
|
||||
@@ -378,15 +375,16 @@ serve(async (req) => {
|
||||
success: true
|
||||
});
|
||||
|
||||
console.log('[APPROVAL SUCCESS]', { itemId: item.id, entityId, itemType: item.item_type });
|
||||
edgeLogger.info('Item approval success', { action: 'approval_item_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:', {
|
||||
edgeLogger.error('Item approval failed', {
|
||||
action: 'approval_item_fail',
|
||||
itemId: item.id,
|
||||
itemType: item.item_type,
|
||||
error: errorMessage,
|
||||
userId: authenticatedUserId,
|
||||
submissionId
|
||||
submissionId,
|
||||
error: errorMessage
|
||||
});
|
||||
|
||||
const isDependencyError = error instanceof Error && (
|
||||
@@ -428,9 +426,10 @@ serve(async (req) => {
|
||||
.eq('id', update.id);
|
||||
|
||||
if (batchApproveError) {
|
||||
console.error('[APPROVAL] Failed to approve item:', {
|
||||
itemId: update.id,
|
||||
error: batchApproveError.message
|
||||
edgeLogger.error('Failed to approve item', {
|
||||
action: 'approval_batch_approve',
|
||||
itemId: update.id,
|
||||
error: batchApproveError.message
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -459,9 +458,10 @@ serve(async (req) => {
|
||||
.eq('id', update.id);
|
||||
|
||||
if (batchRejectError) {
|
||||
console.error('[APPROVAL] Failed to reject item:', {
|
||||
itemId: update.id,
|
||||
error: batchRejectError.message
|
||||
edgeLogger.error('Failed to reject item', {
|
||||
action: 'approval_batch_reject',
|
||||
itemId: update.id,
|
||||
error: batchRejectError.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user