Public Source Viewer

비나래아카이브 개발자 포털

실제 서비스 구조를 살펴볼 수 있는 공개용 코드 뷰어입니다. 인증, 세션, 외부 연동, 토큰, 관리자 식별 등 보안상 민감한 구현은 파일 단위 또는 줄 단위로 검열됩니다.

Redacted View
src/middleware/csrf.middleware.ts
공개 가능
1 import { Application, Request, Response, NextFunction } from 'express';
2 import csrf from 'csurf';
3
4 const csrfProtection = csrf({
5 cookie: false,
6 value: (req) =>
7 (req.body && req.body._csrf)
8 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
9 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
10 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
11 || ''
12 });
13
14 function isSameOriginRequest(req: Request): boolean {
15 const source = req.get('origin') || req.get('referer');
16 if (!source) return true;
17 try {
18 const url = new URL(source);
19 return url.host === req.get('host');
20 } catch {
21 return false;
22 }
23 }
24
25 export function setupCsrfMiddleware(app: Application): void {
26 app.use((req: Request, res: Response, next: NextFunction) => {
27 const unsafeMethod = ['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method);
28 const isVoicepeakSyncRequest = unsafeMethod
29 && req.path.startsWith('/hinana/voicepeak/archive');
30
31 if (isVoicepeakSyncRequest) {
32 return next();
33 }
34
35 if (unsafeMethod && !isSameOriginRequest(req)) {
36 return res.status(403).send('출처가 올바르지 않습니다.');
37 }
38
39 if (unsafeMethod && req.path === '/toggle-theme') {
40 return next();
41 }
42
43 if (req.method === 'GET') {
44 return csrfProtection(req, res, (err: any) => {
45 if (err) return next(err);
46 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
47 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
48 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
49 httpOnly: false,
50 sameSite: 'lax',
51 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
52 });
53 return next();
54 });
55 }
56
57 if (unsafeMethod) {
58 return csrfProtection(req, res, next);
59 }
60
61 return next();
62 });
63
64 app.use((req: Request, res: Response, next: NextFunction) => {
65 res.locals.theme = req.session.theme || 'light';
66
67 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
68 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
69 }
70
71 next();
72 });
73 }
74
75 export function csrfErrorHandler(err: any, req: Request, res: Response, next: NextFunction): any {
76 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
77 return next(err);
78 }
79
80 console.warn('CSRF 검증 실패:', req.method, req.path, err.message);
81
82 if (req.path === '/chat-gpt' || req.path === '/share-discord' || req.path === '/api/exhibition/review' || req.path.startsWith('/hinana/voicepeak/jobs') || req.path.startsWith('/hinana/voicepeak/archive') || req.path.startsWith('/hinana/voicepeak/sync') || req.path.startsWith('/hinana/japanese-lounge/entries')) {
83 return res.status(403).json({ error: 'CSRF 토큰이 유효하지 않습니다. 페이지를 새로고침 해주세요.' });
84 }
85
86 return res.status(403).send('CSRF 검증 실패. 페이지를 새로고침 후 다시 시도해주세요.');
87 }
88