diff --git a/src/app/api/files/upload-url/route.js b/src/app/api/files/upload-url/route.js index 17837997..4fa68353 100644 --- a/src/app/api/files/upload-url/route.js +++ b/src/app/api/files/upload-url/route.js @@ -41,6 +41,25 @@ export async function POST(request, { params } = {}) { encryptedMetadata } = parsedBody.body; + // Validate inputs before profile/conversation lookups + if (!conversationId || !messageId || !encryptedMetadata) { + console.error('UPLOAD-URL: Missing required fields'); + return NextResponse.json({ error: 'Missing required fields' }, { status: 400 }); + } + + if (typeof encryptedMetadata !== 'object' || Array.isArray(encryptedMetadata)) { + console.error('UPLOAD-URL: Invalid encrypted metadata'); + return NextResponse.json({ error: 'Invalid encrypted metadata' }, { status: 400 }); + } + + // Extract one encrypted copy to get file size for validation + // We can't decrypt it, but we can get the size from any user's copy + const firstUserId = Object.keys(encryptedMetadata)[0]; + if (!firstUserId) { + console.error('UPLOAD-URL: No encrypted metadata found'); + return NextResponse.json({ error: 'Invalid encrypted metadata' }, { status: 400 }); + } + // Get the internal user ID from the users table using auth_user_id const { data: internalUser, error: userError } = await supabase .from('users') @@ -56,20 +75,6 @@ export async function POST(request, { params } = {}) { const userId = internalUser.id; console.log(`📁 [UPLOAD-URL] Using internal user ID: ${userId}`); - // Validate inputs - if (!conversationId || !messageId || !encryptedMetadata) { - console.error( '📁 [UPLOAD-URL] Missing required fields'); - return NextResponse.json({ error: 'Missing required fields' }, { status: 400 }); - } - - // Extract one encrypted copy to get file size for validation - // We can't decrypt it, but we can get the size from any user's copy - const firstUserId = Object.keys(encryptedMetadata)[0]; - if (!firstUserId) { - console.error( '📁 [UPLOAD-URL] No encrypted metadata found'); - return NextResponse.json({ error: 'Invalid encrypted metadata' }, { status: 400 }); - } - // Validate user has access to the conversation const { data: participant, error: participantError } = await supabase .from('conversation_participants') diff --git a/src/app/api/files/upload-url/route.test.js b/src/app/api/files/upload-url/route.test.js index 725dbe5f..3b5f39b2 100644 --- a/src/app/api/files/upload-url/route.test.js +++ b/src/app/api/files/upload-url/route.test.js @@ -51,4 +51,22 @@ describe('POST /api/files/upload-url validation', () => { expect(body.error).toBe('Request body must be a JSON object'); expect(mocks.from).not.toHaveBeenCalled(); }); + + it('rejects non-object encrypted metadata before database work', async () => { + const { POST } = await import('./route.js'); + const request = { + json: vi.fn().mockResolvedValue({ + conversationId: 'conversation-1', + messageId: 'message-1', + encryptedMetadata: 'not-a-recipient-map' + }) + }; + + const response = await POST(request); + const body = await response.json(); + + expect(response.status).toBe(400); + expect(body.error).toBe('Invalid encrypted metadata'); + expect(mocks.from).not.toHaveBeenCalled(); + }); });