Public Source Viewer
비나래아카이브 개발자 포털
실제 서비스 구조를 살펴볼 수 있는 공개용 코드 뷰어입니다. 인증, 세션, 외부 연동, 토큰, 관리자 식별 등 보안상 민감한 구현은 파일 단위 또는 줄 단위로 검열됩니다.
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