File tree Expand file tree Collapse file tree
src/app/api/files/upload-complete Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -70,6 +70,15 @@ export async function POST(request, { params } = {}) {
7070 return NextResponse . json ( { error : 'Invalid encrypted metadata' } , { status : 400 } ) ;
7171 }
7272
73+ // Only complete the object created by upload-url for this authenticated
74+ // user, conversation, and file id. Without this check a caller could
75+ // register an unrelated object path against an authorized message.
76+ const expectedStoragePath = `${ user . id } /${ conversationId } /${ fileId } ` ;
77+ if ( storagePath !== expectedStoragePath ) {
78+ console . error ( 'UPLOAD-COMPLETE: Storage path does not match upload request' ) ;
79+ return NextResponse . json ( { error : 'Storage path does not match upload request' } , { status : 403 } ) ;
80+ }
81+
7382 // Get the internal user ID from the users table using auth_user_id
7483 const { data : internalUser , error : userError } = await supabase
7584 . from ( 'users' )
Original file line number Diff line number Diff line change @@ -131,4 +131,27 @@ describe('POST /api/files/upload-complete validation', () => {
131131 expect ( mocks . from ) . not . toHaveBeenCalled ( ) ;
132132 expect ( mocks . broadcastToRoom ) . not . toHaveBeenCalled ( ) ;
133133 } ) ;
134+
135+ it ( 'rejects a storage path that is not bound to the authenticated upload' , async ( ) => {
136+ const { POST } = await import ( './route.js' ) ;
137+ const request = {
138+ json : vi . fn ( ) . mockResolvedValue ( {
139+ storagePath : 'another-user/conversation-1/file-1' ,
140+ fileId : 'file-1' ,
141+ metadata : {
142+ messageId : 'message-1' ,
143+ conversationId : 'conversation-1' ,
144+ encryptedMetadata : { 'user-1' : 'encrypted-metadata' }
145+ }
146+ } )
147+ } ;
148+
149+ const response = await POST ( request ) ;
150+ const body = await response . json ( ) ;
151+
152+ expect ( response . status ) . toBe ( 403 ) ;
153+ expect ( body . error ) . toBe ( 'Storage path does not match upload request' ) ;
154+ expect ( mocks . from ) . not . toHaveBeenCalled ( ) ;
155+ expect ( mocks . broadcastToRoom ) . not . toHaveBeenCalled ( ) ;
156+ } ) ;
134157} ) ;
You can’t perform that action at this time.
0 commit comments