Skip to content

Commit 67c15f4

Browse files
authored
fix(messages): reject invalid encrypted content values (#219)
1 parent 73ad44b commit 67c15f4

2 files changed

Lines changed: 38 additions & 0 deletions

File tree

src/app/api/chat/messages/route.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ export async function POST(request) {
118118
return NextResponse.json({ error: 'encrypted_contents must be an object with user_id -> encrypted_content mappings' }, { status: 400 });
119119
}
120120

121+
if (Object.entries(encrypted_contents).some(([, encryptedContent]) => (
122+
typeof encryptedContent !== 'string' || encryptedContent.trim().length === 0
123+
))) {
124+
return NextResponse.json({ error: 'encrypted_contents values must be non-empty strings' }, { status: 400 });
125+
}
126+
121127
// Verify user is a participant in the conversation
122128
const { data: participant, error: participantError } = await getServiceRoleClient()
123129
.from('conversation_participants')

src/app/api/chat/messages/route.test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,36 @@ describe('POST /api/chat/messages validation', () => {
7272
expect(mocks.userEq).toHaveBeenCalledWith('auth_user_id', 'auth-user-id');
7373
expect(mocks.serviceFrom).toHaveBeenCalledTimes(1);
7474
});
75+
76+
it.each([
77+
['non-string values', { 'recipient-1': { ciphertext: 'not-a-string' } }],
78+
['blank values', { 'recipient-1': ' ' }]
79+
])('rejects encrypted_contents %s before participant lookup', async (_label, encrypted_contents) => {
80+
mocks.serviceFrom.mockImplementation((table) => {
81+
if (table === 'users') return createUsersQuery();
82+
if (table === 'conversation_participants') throw new Error('Participant query should not run');
83+
throw new Error(`Unexpected table: ${table}`);
84+
});
85+
86+
const { POST } = await import('./route.js');
87+
const response = await POST(
88+
new Request('https://qrypt.chat/api/chat/messages', {
89+
method: 'POST',
90+
headers: {
91+
cookie: 'sb-access-token=valid-token',
92+
'content-type': 'application/json'
93+
},
94+
body: JSON.stringify({
95+
conversation_id: 'conversation-1',
96+
encrypted_contents
97+
})
98+
})
99+
);
100+
const body = await response.json();
101+
102+
expect(response.status).toBe(400);
103+
expect(body.error).toBe('encrypted_contents values must be non-empty strings');
104+
expect(mocks.userEq).toHaveBeenCalledWith('auth_user_id', 'auth-user-id');
105+
expect(mocks.serviceFrom).toHaveBeenCalledTimes(1);
106+
});
75107
});

0 commit comments

Comments
 (0)