Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/app/api/chat/messages/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,14 @@
const userId = userData.id;
console.log('🔐 [API] ✅ Internal user ID:', userId);

const { conversation_id, encrypted_contents, content_type = 'text', has_attachments = false, reply_to_id } = await request.json();
let body;
try {
body = await request.json();
} catch {
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 });
}

const { conversation_id, encrypted_contents, content_type = 'text', has_attachments = false, reply_to_id } = body;

// Validate required fields
if (!conversation_id || !encrypted_contents) {
Expand Down Expand Up @@ -176,7 +183,7 @@

// Convert JSON string to base64
const base64Content = Buffer.from(jsonEncryptedContent, 'utf8').toString('base64');
console.log('🔐 [API SEND] Converted to base64:', {

Check failure

Code scanning / CodeQL

Remote property injection High

A property name to write to depends on a
user-provided value
.
userId,
base64Length: base64Content?.length || 0,
base64Preview: base64Content?.substring(0, 100) || 'N/A',
Expand Down
66 changes: 66 additions & 0 deletions src/app/api/chat/messages/route.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';

const mocks = vi.hoisted(() => ({
authGetUser: vi.fn(),
serviceFrom: vi.fn(),
userEq: vi.fn()
}));

vi.mock('@supabase/supabase-js', () => ({
createClient: vi.fn(() => ({
auth: {
getUser: mocks.authGetUser
}
}))
}));

vi.mock('@/lib/supabase/service-role.js', () => ({
createServiceRoleClient: vi.fn(() => ({
from: mocks.serviceFrom
}))
}));

function createUsersQuery() {
const query = {
select: vi.fn(() => query),
eq: mocks.userEq,
single: vi.fn().mockResolvedValue({
data: { id: 'internal-user-id' },
error: null
})
};
mocks.userEq.mockReturnValue(query);
return query;
}

describe('POST /api/chat/messages validation', () => {
beforeEach(() => {
vi.resetModules();
vi.clearAllMocks();

mocks.authGetUser.mockResolvedValue({
data: { user: { id: 'auth-user-id' } },
error: null
});
});

it('returns 400 for malformed JSON before participant lookup', async () => {
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({
headers: new Headers({ cookie: 'sb-access-token=valid-token' }),
json: vi.fn().mockRejectedValue(new SyntaxError('Unexpected token'))
});
const body = await response.json();

expect(response.status).toBe(400);
expect(body.error).toBe('Invalid JSON body');
expect(mocks.userEq).toHaveBeenCalledWith('auth_user_id', 'auth-user-id');
expect(mocks.serviceFrom).toHaveBeenCalledTimes(1);
});
});
Loading