Public Source Viewer

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

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

Redacted View
src/middleware/csrf.middleware.ts
공개 가능
1 import { Application, Request, Response, NextFunction } from 'express';
2 import { createHmac, randomBytes, timingSafeEqual } from 'crypto';
3
4 // csurf(미유지보수) 대체 자체 구현.
5 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
6 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
7 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
8
9 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
10 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
11 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
12 }
13 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
14 }
15
16 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
17 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
18 }
19
20 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
21 const salt = randomBytes(8).toString('hex');
22 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
23 }
24
25 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
26 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
27 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
28 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
29 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
30 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
31 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
32 return mac.length === expected.length && timingSafeEqual(mac, expected);
33 }
34
35 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
36 return (req.body && req.body._csrf)
37 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
38 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
39 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
40 || '';
41 }
42
43 function csrfError(): Error & { code: string } {
44 const err = new Error('CSRF 토큰이 유효하지 않습니다.') as Error & { code: string };
45 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
46 return err;
47 }
48
49 function isSameOriginRequest(req: Request): boolean {
50 const source = req.get('origin') || req.get('referer');
51 if (!source) return true;
52 try {
53 const url = new URL(source);
54 return url.host === req.get('host');
55 } catch {
56 return false;
57 }
58 }
59
60 export function setupCsrfMiddleware(app: Application): void {
61 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
62 app.use((req: Request, _res: Response, next: NextFunction) => {
63 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
64 next();
65 });
66
67 app.use((req: Request, res: Response, next: NextFunction) => {
68 const unsafeMethod = ['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method);
69 const isVoicepeakSyncRequest = unsafeMethod
70 && req.path.startsWith('/hinana/voicepeak/archive');
71
72 if (isVoicepeakSyncRequest) {
73 return next();
74 }
75
76 if (unsafeMethod && !isSameOriginRequest(req)) {
77 return res.status(403).send('출처가 올바르지 않습니다.');
78 }
79
80 if (unsafeMethod && req.path === '/toggle-theme') {
81 return next();
82 }
83
84 if (req.method === 'GET') {
85 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
86 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
87 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
88 httpOnly: false,
89 sameSite: 'lax',
90 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
91 });
92 return next();
93 }
94
95 if (unsafeMethod) {
96 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
97 return next(csrfError());
98 }
99 return next();
100 }
101
102 return next();
103 });
104
105 app.use((req: Request, res: Response, next: NextFunction) => {
106 res.locals.theme = req.session.theme || 'light';
107
108 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
109 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
110 }
111
112 next();
113 });
114 }
115
116 export function csrfErrorHandler(err: any, req: Request, res: Response, next: NextFunction): any {
117 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
118 return next(err);
119 }
120
121 console.warn('CSRF 검증 실패:', req.method, req.path, err.message);
122
123 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')) {
124 return res.status(403).json({ error: 'CSRF 토큰이 유효하지 않습니다. 페이지를 새로고침 해주세요.' });
125 }
126
127 return res.status(403).send('CSRF 검증 실패. 페이지를 새로고침 후 다시 시도해주세요.');
128 }
129