diff --git a/src/app/api/chat/messages/route.js b/src/app/api/chat/messages/route.js index 3e42fe0..d5b3452 100644 --- a/src/app/api/chat/messages/route.js +++ b/src/app/api/chat/messages/route.js @@ -118,6 +118,12 @@ export async function POST(request) { return NextResponse.json({ error: 'encrypted_contents must be an object with user_id -> encrypted_content mappings' }, { status: 400 }); } + if (Object.entries(encrypted_contents).some(([, encryptedContent]) => ( + typeof encryptedContent !== 'string' || encryptedContent.trim().length === 0 + ))) { + return NextResponse.json({ error: 'encrypted_contents values must be non-empty strings' }, { status: 400 }); + } + // Verify user is a participant in the conversation const { data: participant, error: participantError } = await getServiceRoleClient() .from('conversation_participants') diff --git a/src/app/api/chat/messages/route.test.js b/src/app/api/chat/messages/route.test.js index 19e3980..3341bef 100644 --- a/src/app/api/chat/messages/route.test.js +++ b/src/app/api/chat/messages/route.test.js @@ -72,4 +72,36 @@ describe('POST /api/chat/messages validation', () => { expect(mocks.userEq).toHaveBeenCalledWith('auth_user_id', 'auth-user-id'); expect(mocks.serviceFrom).toHaveBeenCalledTimes(1); }); + + it.each([ + ['non-string values', { 'recipient-1': { ciphertext: 'not-a-string' } }], + ['blank values', { 'recipient-1': ' ' }] + ])('rejects encrypted_contents %s before participant lookup', async (_label, encrypted_contents) => { + mocks.serviceFrom.mockImplementation((table) => { + if (table === 'users') return createUsersQuery(); + if (table === 'conversation_participants') throw new Error('Participant query should not run'); + throw new Error(`Unexpected table: ${table}`); + }); + + const { POST } = await import('./route.js'); + const response = await POST( + new Request('https://qrypt.chat/api/chat/messages', { + method: 'POST', + headers: { + cookie: 'sb-access-token=valid-token', + 'content-type': 'application/json' + }, + body: JSON.stringify({ + conversation_id: 'conversation-1', + encrypted_contents + }) + }) + ); + const body = await response.json(); + + expect(response.status).toBe(400); + expect(body.error).toBe('encrypted_contents values must be non-empty strings'); + expect(mocks.userEq).toHaveBeenCalledWith('auth_user_id', 'auth-user-id'); + expect(mocks.serviceFrom).toHaveBeenCalledTimes(1); + }); });