From ac97916c6092d3d8f913921787c205fae571ba4a Mon Sep 17 00:00:00 2001 From: Tofik Hasanov Date: Sat, 14 Mar 2026 17:41:58 -0400 Subject: [PATCH] feat(auth): add SessionOnlyGuard to enforce user session authentication for assistant chat --- .../assistant-chat.controller.ts | 9 ++---- apps/api/src/auth/session-only.guard.ts | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 apps/api/src/auth/session-only.guard.ts diff --git a/apps/api/src/assistant-chat/assistant-chat.controller.ts b/apps/api/src/assistant-chat/assistant-chat.controller.ts index 4693acfa8..746d33e40 100644 --- a/apps/api/src/assistant-chat/assistant-chat.controller.ts +++ b/apps/api/src/assistant-chat/assistant-chat.controller.ts @@ -24,6 +24,7 @@ import { streamText, convertToModelMessages, stepCountIs, type UIMessage } from import type { Response, Request } from 'express'; import { AuthContext } from '../auth/auth-context.decorator'; import { HybridAuthGuard } from '../auth/hybrid-auth.guard'; +import { SessionOnlyGuard } from '../auth/session-only.guard'; import { PermissionGuard } from '../auth/permission.guard'; import { RequirePermission } from '../auth/require-permission.decorator'; import type { AuthContext as AuthContextType } from '../auth/types'; @@ -36,7 +37,7 @@ import { RolesService } from '../roles/roles.service'; @ApiTags('Assistant Chat') @Controller({ path: 'assistant-chat', version: '1' }) -@UseGuards(HybridAuthGuard, PermissionGuard) +@UseGuards(HybridAuthGuard, SessionOnlyGuard, PermissionGuard) @RequirePermission('app', 'read') @ApiSecurity('apikey') export class AssistantChatController { @@ -55,12 +56,6 @@ export class AssistantChatController { throw new BadRequestException('Organization ID is required'); } - if (auth.isApiKey) { - throw new BadRequestException( - 'Assistant chat is only available for user-authenticated requests.', - ); - } - if (!auth.userId) { throw new BadRequestException('User ID is required'); } diff --git a/apps/api/src/auth/session-only.guard.ts b/apps/api/src/auth/session-only.guard.ts new file mode 100644 index 000000000..daab82f7a --- /dev/null +++ b/apps/api/src/auth/session-only.guard.ts @@ -0,0 +1,29 @@ +import { + CanActivate, + ExecutionContext, + ForbiddenException, + Injectable, +} from '@nestjs/common'; +import { AuthenticatedRequest } from './types'; + +/** + * Guard that rejects API key and service token auth. + * Use on endpoints that require a real user session (e.g., assistant chat). + * + * Place between HybridAuthGuard and PermissionGuard: + * @UseGuards(HybridAuthGuard, SessionOnlyGuard, PermissionGuard) + */ +@Injectable() +export class SessionOnlyGuard implements CanActivate { + canActivate(context: ExecutionContext): boolean { + const request = context.switchToHttp().getRequest(); + + if (request.isApiKey || request.isServiceToken) { + throw new ForbiddenException( + 'This endpoint is only available for user-authenticated requests.', + ); + } + + return true; + } +}