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.
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
|||||||
Req,
|
Req,
|
||||||
Res,
|
Res,
|
||||||
Header,
|
Header,
|
||||||
|
UnauthorizedException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { AIService } from './ai.service';
|
import { AIService } from './ai.service';
|
||||||
@@ -20,10 +21,12 @@ import { Public } from '../auth/decorators/public.decorator';
|
|||||||
export class AIController {
|
export class AIController {
|
||||||
constructor(private readonly aiService: AIService) {}
|
constructor(private readonly aiService: AIService) {}
|
||||||
|
|
||||||
@Public() // Public for testing
|
|
||||||
@Post('chat')
|
@Post('chat')
|
||||||
async chat(@Req() req: any, @Body() chatDto: ChatMessageDto) {
|
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);
|
const response = await this.aiService.chat(userId, chatDto);
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -35,7 +38,6 @@ export class AIController {
|
|||||||
* Streaming chat endpoint
|
* Streaming chat endpoint
|
||||||
* Returns Server-Sent Events (SSE) for real-time streaming responses
|
* Returns Server-Sent Events (SSE) for real-time streaming responses
|
||||||
*/
|
*/
|
||||||
@Public() // Public for testing
|
|
||||||
@Post('chat/stream')
|
@Post('chat/stream')
|
||||||
@Header('Content-Type', 'text/event-stream')
|
@Header('Content-Type', 'text/event-stream')
|
||||||
@Header('Cache-Control', 'no-cache')
|
@Header('Cache-Control', 'no-cache')
|
||||||
@@ -45,7 +47,11 @@ export class AIController {
|
|||||||
@Body() chatDto: ChatMessageDto,
|
@Body() chatDto: ChatMessageDto,
|
||||||
@Res() res: Response,
|
@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
|
// Set up SSE headers
|
||||||
res.setHeader('Content-Type', 'text/event-stream');
|
res.setHeader('Content-Type', 'text/event-stream');
|
||||||
@@ -72,10 +78,12 @@ export class AIController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Public() // Public for testing
|
|
||||||
@Get('conversations')
|
@Get('conversations')
|
||||||
async getConversations(@Req() req: any) {
|
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);
|
const conversations = await this.aiService.getUserConversations(userId);
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -83,10 +91,12 @@ export class AIController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Public() // Public for testing
|
|
||||||
@Get('conversations/:id')
|
@Get('conversations/:id')
|
||||||
async getConversation(@Req() req: any, @Param('id') conversationId: string) {
|
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(
|
const conversation = await this.aiService.getConversation(
|
||||||
userId,
|
userId,
|
||||||
conversationId,
|
conversationId,
|
||||||
@@ -109,14 +119,16 @@ export class AIController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Public() // Public for testing
|
|
||||||
@Patch('conversations/:id/group')
|
@Patch('conversations/:id/group')
|
||||||
async updateConversationGroup(
|
async updateConversationGroup(
|
||||||
@Req() req: any,
|
@Req() req: any,
|
||||||
@Param('id') conversationId: string,
|
@Param('id') conversationId: string,
|
||||||
@Body() body: { groupName: string | null },
|
@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(
|
const conversation = await this.aiService.updateConversationGroup(
|
||||||
userId,
|
userId,
|
||||||
conversationId,
|
conversationId,
|
||||||
@@ -128,10 +140,12 @@ export class AIController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Public() // Public for testing
|
|
||||||
@Get('groups')
|
@Get('groups')
|
||||||
async getConversationGroups(@Req() req: any) {
|
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);
|
const groups = await this.aiService.getConversationGroups(userId);
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -149,10 +163,12 @@ export class AIController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Public() // Public for testing
|
|
||||||
@Post('feedback')
|
@Post('feedback')
|
||||||
async submitFeedback(@Req() req: any, @Body() feedbackDto: FeedbackDto) {
|
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(
|
await this.aiService.submitFeedback(
|
||||||
userId,
|
userId,
|
||||||
feedbackDto.conversationId,
|
feedbackDto.conversationId,
|
||||||
|
|||||||
Reference in New Issue
Block a user