Public Source Viewer

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

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

Redacted View
src/services/developer-portal.service.ts
공개 가능
1 import fs from 'fs';
2 import path from 'path';
3 import { ROOT_DIR } from '../utils/paths';
4
5 export type PortalFileEntry = {
6 path: string;
7 name: string;
8 directory: string;
9 sensitive: boolean;
10 };
11
12 export type PortalFileView = {
13 entry: PortalFileEntry;
14 lines: { number: number; text: string; redacted: boolean }[];
15 redactedFile: boolean;
16 notice: string | null;
17 };
18
19 const INCLUDE_ROOTS = ['src', 'view/hinana', 'public/js'];
20 const INCLUDE_FILES = ['package.json', 'Dockerfile', 'docker-compose.yml', 'tsconfig.json', 'README', 'DEPLOYMENT.md', 'public/sw.js'];
21 const EXCLUDED_DIRS = new Set([
22 '.git',
23 '.github',
24 '.claude',
25 'archive',
26 'config',
27 'data',
28 'dist',
29 'node_modules',
30 'public/uploads',
31 'public/image',
32 'public/sound',
33 'view/image',
34 'view/css'
35 ]);
36 const ALLOWED_EXTENSIONS = new Set(['.ts', '.js', '.ejs', '.css', '.json', '.md', '.yml', '.yaml', '.html', '']);
37 const PUBLIC_JSON_FILES = new Set(['package.json', 'tsconfig.json']);
38 const FULL_FILE_REDACTIONS = [
39 'src/config/constants.ts',
40 'src/bot/discord-bot.ts',
41 'src/routes/ai.routes.ts',
42 'src/routes/upload.routes.ts',
43 'src/routes/backup.routes.ts',
44 'src/routes/security-dashboard.routes.ts',
45 'src/routes/discord.routes.ts',
46 'src/routes/auth.routes.ts',
47 'src/middleware/security.middleware.ts',
48 'src/middleware/session.middleware.ts',
49 'src/services/file-session-store.service.ts',
50 'src/services/safe-http.service.ts',
51 'src/services/backup.service.ts',
52 'src/services/security-dashboard.service.ts',
53 'src/services/push.service.ts',
54 'view/hinana/ai.ejs',
55 'view/hinana/aiNotes.ejs',
56 'view/hinana/aiView.ejs',
57 'view/hinana/backups.ejs',
58 'view/hinana/securityDashboard.ejs'
59 ];
60 const AUTO_FILE_REDACTION_PATTERNS = [
61 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
62 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
63 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
64 /firebase.*admin/i,
65 /service[-_]?account/i,
66 /admin[-_]?sdk/i
67 ];
68 const REDACT_LINE_PATTERNS = [
69 /process\.env/i,
70 /\bfs\.(readFile|readFileSync|writeFile|writeFileSync|readdir|readdirSync|existsSync|statSync|statfsSync)\b/i,
71 /\bJSON\.(parse|stringify)\b/,
72 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
73 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
74 /api[_-]?key/i,
75 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
76 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
77 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
78 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
79 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
80 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
81 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
82 /username\s*[!=]={1,2}\s*['"`][^'"`]+['"`]/,
83 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
84 /\b\d{15,22}\b/
85 ];
86
87 function toPortalPath(filePath: string): string {
88 return path.relative(ROOT_DIR, filePath).replace(/\\/g, '/');
89 }
90
91 function isExcluded(portalPath: string): boolean {
92 return Array.from(EXCLUDED_DIRS).some((dir) => portalPath === dir || portalPath.startsWith(`${dir}/`));
93 }
94
95 function isAllowedFile(portalPath: string): boolean {
96 if (isExcluded(portalPath)) return false;
97 if (portalPath.includes('/.')) return false;
98 if (portalPath.endsWith('package-lock.json')) return false;
99 const ext = path.extname(portalPath);
100 return ALLOWED_EXTENSIONS.has(ext);
101 }
102
103 function isSensitiveFile(portalPath: string): boolean {
104 if (FULL_FILE_REDACTIONS.includes(portalPath)) return true;
105 if (AUTO_FILE_REDACTION_PATTERNS.some((pattern) => pattern.test(portalPath))) return true;
106
107 const ext = path.extname(portalPath);
108 if (ext === '.json' && !PUBLIC_JSON_FILES.has(portalPath)) return true;
109
110 return false;
111 }
112
113 function walkDirectory(dir: string, entries: PortalFileEntry[]): void {
114 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
115 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
116
117 for (const item of items) {
118 const fullPath = path.join(dir, item.name);
119 const portalPath = toPortalPath(fullPath);
120 if (isExcluded(portalPath)) continue;
121
122 if (item.isDirectory()) {
123 walkDirectory(fullPath, entries);
124 continue;
125 }
126
127 if (!item.isFile() || !isAllowedFile(portalPath)) continue;
128 entries.push({
129 path: portalPath,
130 name: path.basename(portalPath),
131 directory: path.dirname(portalPath).replace(/\\/g, '/'),
132 sensitive: isSensitiveFile(portalPath)
133 });
134 }
135 }
136
137 export function getPortalFiles(): PortalFileEntry[] {
138 const entries: PortalFileEntry[] = [];
139
140 INCLUDE_ROOTS.forEach((root) => walkDirectory(path.join(ROOT_DIR, root), entries));
141 INCLUDE_FILES.forEach((file) => {
142 const fullPath = path.join(ROOT_DIR, file);
143 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
144 entries.push({
145 path: file,
146 name: path.basename(file),
147 directory: path.dirname(file) === '.' ? 'root' : path.dirname(file),
148 sensitive: isSensitiveFile(file)
149 });
150 });
151
152 const unique = new Map(entries.map((entry) => [entry.path, entry]));
153 return Array.from(unique.values()).sort((a, b) => a.path.localeCompare(b.path));
154 }
155
156 export function getPortalFileView(requestedPath: string | undefined): PortalFileView {
157 const files = getPortalFiles();
158 const entry = files.find((item) => item.path === requestedPath) || files[0];
159
160 if (!entry) {
161 return {
162 entry: { path: '', name: '', directory: '', sensitive: false },
163 lines: [],
164 redactedFile: false,
165 notice: '공개 가능한 파일이 없습니다.'
166 };
167 }
168
169 if (entry.sensitive) {
170 return {
171 entry,
172 redactedFile: true,
173 notice: '이 파일은 인증/세션/외부 연동 등 보안상 민감한 구현을 포함하여 공개용 코드 뷰어에서는 내용을 비공개 처리했습니다.',
174 lines: [
175 { number: 1, text: '/* SECURITY REDACTED */', redacted: true },
176 { number: 2, text: '파일명과 위치는 공개하지만, 본문 코드는 보안상 표시하지 않습니다.', redacted: true }
177 ]
178 };
179 }
180
181 const fullPath = path.join(ROOT_DIR, entry.path);
182 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
183 if (stat.size > 120 * 1024) {
184 return {
185 entry,
186 redactedFile: true,
187 notice: '파일이 커서 공개용 코드 뷰어에서는 요약만 표시합니다.',
188 lines: [{ number: 1, text: `/* Large file omitted: ${entry.path} */`, redacted: true }]
189 };
190 }
191
192 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
193 const lines = raw.split('\n').map((line, index) => {
194 const redacted = REDACT_LINE_PATTERNS.some((pattern) => pattern.test(line));
195 return {
196 number: index + 1,
197 text: redacted ? '[SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.' : line,
198 redacted
199 };
200 });
201
202 return {
203 entry,
204 lines,
205 redactedFile: false,
206 notice: null
207 };
208 }
209