diff --git a/src/app/api/auth/register-anon/route.js b/src/app/api/auth/register-anon/route.js index 97d1cc0..23d5045 100644 --- a/src/app/api/auth/register-anon/route.js +++ b/src/app/api/auth/register-anon/route.js @@ -62,11 +62,13 @@ export async function POST(request) { } const { inviteToken, username, displayName, publicKey } = body || {}; + const cleanUsername = typeof username === 'string' ? username.trim() : ''; + const cleanDisplayName = typeof displayName === 'string' ? displayName.trim() : ''; if (!inviteToken || typeof inviteToken !== 'string') { return NextResponse.json({ error: 'Invite token is required' }, { status: 400 }); } - if (!username || typeof username !== 'string') { + if (!cleanUsername) { return NextResponse.json({ error: 'Username is required' }, { status: 400 }); } if (!publicKey || typeof publicKey !== 'string') { @@ -116,7 +118,7 @@ export async function POST(request) { const { data: usernameCheck } = await serviceSupabase .from('users') .select('id') - .ilike('username', username) + .ilike('username', cleanUsername) .single(); if (usernameCheck) { return NextResponse.json( @@ -152,8 +154,8 @@ export async function POST(request) { auth_user_id: authUser.id, phone_number: null, account_type: 'anonymous', - username, - display_name: displayName || username, + username: cleanUsername, + display_name: cleanDisplayName || cleanUsername, created_at: new Date().toISOString(), updated_at: new Date().toISOString() }) diff --git a/src/app/api/auth/register-anon/route.test.js b/src/app/api/auth/register-anon/route.test.js index d4bed11..876e9fa 100644 --- a/src/app/api/auth/register-anon/route.test.js +++ b/src/app/api/auth/register-anon/route.test.js @@ -24,15 +24,15 @@ vi.mock('@/lib/invites/verify.js', () => ({ InviteVerificationError: class InviteVerificationError extends Error {} })); -function registerRequest(authorization) { +function registerRequest(authorization, body = { + inviteToken: 'qci1.payload.signature', + username: 'anon-user', + publicKey: 'ml-kem-public-key' +}) { return new Request('https://example.com/api/auth/register-anon', { method: 'POST', headers: authorization ? { authorization } : {}, - body: JSON.stringify({ - inviteToken: 'qci1.payload.signature', - username: 'anon-user', - publicKey: 'ml-kem-public-key' - }) + body: JSON.stringify(body) }); } @@ -70,4 +70,20 @@ describe('register-anon bearer authentication', () => { expect(mocks.serverAuthGetUser).not.toHaveBeenCalled(); expect(mocks.verifyInviteToken).not.toHaveBeenCalled(); }); + + it('rejects whitespace-only usernames before validating the session', async () => { + const { POST } = await import('./route.js'); + + const response = await POST(registerRequest('Bearer access-token-123', { + inviteToken: 'qci1.payload.signature', + username: ' ', + publicKey: 'ml-kem-public-key' + })); + const body = await response.json(); + + expect(response.status).toBe(400); + expect(body.error).toBe('Username is required'); + expect(mocks.createSupabaseServerClient).not.toHaveBeenCalled(); + expect(mocks.verifyInviteToken).not.toHaveBeenCalled(); + }); });