fix: resolve 8 review/security issues (SPRINT-016 hotfix)

Security:
- JwtGuard + RoleGuard on all sisters endpoints
- Admin-only access for config/sessions/subagents/activity
- ThrottlerGuard on /auth/refresh
- HttpOnly SameSite cookies + CSRF (replaces localStorage)

Code Quality:
- Per-sister draft input (Record<SisterName, string>)
- crypto.randomUUID for optimistic message ids (dedupe ready)
- Polling disabled while WebSocket connected
- SVG keyboard accessibility (role/tabIndex/onKeyDown)
This commit is contained in:
2026-04-09 08:21:28 +09:00
parent bc6904d348
commit 54535e7b5e
12 changed files with 475 additions and 103 deletions

View File

@@ -11,6 +11,7 @@ import { Logger } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { OnEvent } from '@nestjs/event-emitter';
import { ACCESS_TOKEN_COOKIE, parseCookieHeader } from '../auth/auth-cookies';
interface SocketUserPayload {
username?: string;
@@ -50,12 +51,17 @@ export class EventsGateway
handleConnection(client: SocketWithUser) {
// JWT 인증 필수 — 토큰 없거나 유효하지 않으면 disconnect
const authHeader = client.handshake.headers.authorization;
const tokenFromHeader = Array.isArray(authHeader)
? authHeader[0]?.replace('Bearer ', '')
: authHeader?.replace('Bearer ', '');
const tokenFromCookie = parseCookieHeader(client.handshake.headers.cookie)[
ACCESS_TOKEN_COOKIE
];
const token =
(client.handshake.auth?.token as string) ??
(client.handshake.headers.authorization as string)?.replace(
'Bearer ',
'',
);
(client.handshake.auth?.token as string | undefined) ??
tokenFromHeader ??
tokenFromCookie;
if (!token) {
this.logger.debug(`WS rejected (no token): ${client.id}`);