From 07c8063436549f84e0c606cc75d0846dc7d181cd Mon Sep 17 00:00:00 2001 From: aiirvizionz Date: Sun, 12 Jul 2026 16:06:14 -0600 Subject: [PATCH] Normalize avatar bearer token parsing --- src/app/api/auth/upload-avatar/route.js | 14 ++++++-- src/app/api/auth/upload-avatar/route.test.js | 35 ++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/app/api/auth/upload-avatar/route.js b/src/app/api/auth/upload-avatar/route.js index 8e0bf85f..2b0c142e 100644 --- a/src/app/api/auth/upload-avatar/route.js +++ b/src/app/api/auth/upload-avatar/route.js @@ -3,13 +3,23 @@ import { createClient } from '@supabase/supabase-js'; const supabaseAuth = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY); +function getBearerToken(authHeader) { + if (typeof authHeader !== 'string') return null; + + const match = authHeader.match(/^Bearer\s+(.+)$/i); + const token = match?.[1]?.trim(); + + return token || null; +} + async function authenticateBearerToken(request) { const authHeader = request.headers.get('authorization'); - if (!authHeader || !authHeader.startsWith('Bearer ')) { + const token = getBearerToken(authHeader); + + if (!token) { return { error: 'Authentication required' }; } - const token = authHeader.substring(7); const { data: { user }, error } = await supabaseAuth.auth.getUser(token); if (error || !user?.id) { diff --git a/src/app/api/auth/upload-avatar/route.test.js b/src/app/api/auth/upload-avatar/route.test.js index 45b458b3..a5a928db 100644 --- a/src/app/api/auth/upload-avatar/route.test.js +++ b/src/app/api/auth/upload-avatar/route.test.js @@ -24,6 +24,15 @@ function avatarRequest(method) { }); } +function avatarRequestWithAuthorization(method, authorization) { + return new Request('https://example.com/api/auth/upload-avatar', { + method, + headers: { + authorization + } + }); +} + describe('upload-avatar authentication', () => { beforeEach(() => { vi.resetModules(); @@ -62,4 +71,30 @@ describe('upload-avatar authentication', () => { expect(mocks.createClient).toHaveBeenCalledTimes(1); expect(mocks.createClient).toHaveBeenCalledWith('https://example.supabase.co', 'anon-key'); }); + + it('normalizes bearer scheme casing and extra spaces before validating the token', async () => { + const { DELETE } = await import('./route.js'); + + const response = await DELETE(avatarRequestWithAuthorization('DELETE', `bearer ${forgedToken} `)); + const body = await response.json(); + + expect(response.status).toBe(401); + expect(body.error).toBe('Invalid authentication token'); + expect(mocks.authGetUser).toHaveBeenCalledWith(forgedToken); + expect(mocks.createClient).toHaveBeenCalledTimes(1); + expect(mocks.createClient).toHaveBeenCalledWith('https://example.supabase.co', 'anon-key'); + }); + + it('does not validate an empty bearer header', async () => { + const { DELETE } = await import('./route.js'); + + const response = await DELETE(avatarRequestWithAuthorization('DELETE', 'Bearer ')); + const body = await response.json(); + + expect(response.status).toBe(401); + expect(body.error).toBe('Authentication required'); + expect(mocks.authGetUser).not.toHaveBeenCalled(); + expect(mocks.createClient).toHaveBeenCalledTimes(1); + expect(mocks.createClient).toHaveBeenCalledWith('https://example.supabase.co', 'anon-key'); + }); });