From 5c255298d41dc54add3560dfe196171c83816c39 Mon Sep 17 00:00:00 2001 From: Andrei Date: Sun, 5 Oct 2025 18:59:20 +0000 Subject: [PATCH] CRITICAL SECURITY FIX: Require authentication for AI chat endpoints - Remove @Public decorators from AI conversation endpoints - Add proper authentication checks for all AI endpoints - Prevent users from seeing conversations from other users/families - Add UnauthorizedException for unauthenticated requests - Fix privacy leak where all users could see all conversations This was a critical security vulnerability that allowed any user to access conversations from other users and families. All AI endpoints now properly require authentication and filter data by the authenticated user's ID. --- .../src/modules/ai/ai.controller.ts | 44 +++++++++++++------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/maternal-app/maternal-app-backend/src/modules/ai/ai.controller.ts b/maternal-app/maternal-app-backend/src/modules/ai/ai.controller.ts index 3c910ac..2cd9a3d 100644 --- a/maternal-app/maternal-app-backend/src/modules/ai/ai.controller.ts +++ b/maternal-app/maternal-app-backend/src/modules/ai/ai.controller.ts @@ -9,6 +9,7 @@ import { Req, Res, Header, + UnauthorizedException, } from '@nestjs/common'; import { Response } from 'express'; import { AIService } from './ai.service'; @@ -20,10 +21,12 @@ import { Public } from '../auth/decorators/public.decorator'; export class AIController { constructor(private readonly aiService: AIService) {} - @Public() // Public for testing @Post('chat') async chat(@Req() req: any, @Body() chatDto: ChatMessageDto) { - const userId = req.user?.userId || 'test_user_123'; // Use test user if not authenticated + const userId = req.user?.userId; + if (!userId) { + throw new UnauthorizedException('Authentication required'); + } const response = await this.aiService.chat(userId, chatDto); return { success: true, @@ -35,7 +38,6 @@ export class AIController { * Streaming chat endpoint * Returns Server-Sent Events (SSE) for real-time streaming responses */ - @Public() // Public for testing @Post('chat/stream') @Header('Content-Type', 'text/event-stream') @Header('Cache-Control', 'no-cache') @@ -45,7 +47,11 @@ export class AIController { @Body() chatDto: ChatMessageDto, @Res() res: Response, ) { - const userId = req.user?.userId || 'test_user_123'; + const userId = req.user?.userId; + if (!userId) { + res.status(401).json({ error: 'Authentication required' }); + return; + } // Set up SSE headers res.setHeader('Content-Type', 'text/event-stream'); @@ -72,10 +78,12 @@ export class AIController { } } - @Public() // Public for testing @Get('conversations') async getConversations(@Req() req: any) { - const userId = req.user?.userId || 'test_user_123'; + const userId = req.user?.userId; + if (!userId) { + throw new UnauthorizedException('Authentication required'); + } const conversations = await this.aiService.getUserConversations(userId); return { success: true, @@ -83,10 +91,12 @@ export class AIController { }; } - @Public() // Public for testing @Get('conversations/:id') async getConversation(@Req() req: any, @Param('id') conversationId: string) { - const userId = req.user?.userId || 'test_user_123'; + const userId = req.user?.userId; + if (!userId) { + throw new UnauthorizedException('Authentication required'); + } const conversation = await this.aiService.getConversation( userId, conversationId, @@ -109,14 +119,16 @@ export class AIController { }; } - @Public() // Public for testing @Patch('conversations/:id/group') async updateConversationGroup( @Req() req: any, @Param('id') conversationId: string, @Body() body: { groupName: string | null }, ) { - const userId = req.user?.userId || 'test_user_123'; + const userId = req.user?.userId; + if (!userId) { + throw new UnauthorizedException('Authentication required'); + } const conversation = await this.aiService.updateConversationGroup( userId, conversationId, @@ -128,10 +140,12 @@ export class AIController { }; } - @Public() // Public for testing @Get('groups') async getConversationGroups(@Req() req: any) { - const userId = req.user?.userId || 'test_user_123'; + const userId = req.user?.userId; + if (!userId) { + throw new UnauthorizedException('Authentication required'); + } const groups = await this.aiService.getConversationGroups(userId); return { success: true, @@ -149,10 +163,12 @@ export class AIController { }; } - @Public() // Public for testing @Post('feedback') async submitFeedback(@Req() req: any, @Body() feedbackDto: FeedbackDto) { - const userId = req.user?.userId || 'test_user_123'; + const userId = req.user?.userId; + if (!userId) { + throw new UnauthorizedException('Authentication required'); + } await this.aiService.submitFeedback( userId, feedbackDto.conversationId,