File size: 13,435 Bytes
f2cb332
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62fb8f5
 
 
 
 
eb8703c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b2c1ed6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dfb5283
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# SNAP 인증 톡합 β€” 컀밋 λ©”μ‹œμ§€ 및 λ³€κ²½ λ‚΄μ—­

---

## 컀밋 λ©”μ‹œμ§€

```
feat: LDAP 인증 톡합 β€” 둜그인 κ²Œμ΄νŒ… + HttpOnly μΏ ν‚€ + UserMenu

SKμ‹€νŠΈλ‘  사내 AD(Active Directory) 기반 LDAP 인증을 SNAP μ›Ή UI에 톡합.
BFF(Express)κ°€ 인증 μ„œλ²„(10.150.6.47:8090)λ₯Ό ν”„λ‘μ‹œν•˜μ—¬
HttpOnly μΏ ν‚€λ‘œ JWTλ₯Ό κ΄€λ¦¬ν•˜λŠ” ꡬ쑰.

## μ£Όμš” λ³€κ²½

### server.ts β€” BFF 인증 라우트 μΆ”κ°€
- POST /api/auth/login: 인증 μ„œλ²„ ν”„λ‘μ‹œ + HttpOnly μΏ ν‚€ Set-Cookie
- POST /api/auth/logout: μΏ ν‚€ μ‚­μ œ + 인증 μ„œλ²„ λΈ”λž™λ¦¬μŠ€νŠΈ 등둝
- cookie-parser 미듀웨어 μΆ”κ°€
- AUTH_API_BASE ν™˜κ²½λ³€μˆ˜ (κΈ°λ³Έ: http://10.150.6.47:8090)

### src/App.tsx β€” 인증 UI μ»΄ν¬λ„ŒνŠΈ μΆ”κ°€ (1326쀄 β†’ 1657쀄)
- AuthProvider: μ „μ—­ 인증 μƒνƒœ (login/logout/user) + sessionStorage 동기화
- LoginScreen: 전체화면 둜그인 UI (μ‚¬λ²ˆ/λΉ„λ°€λ²ˆν˜Έ, SK λ””μžμΈ 토큰 적용)
- UserMenu: Navbar 우츑 μ‚¬μš©μž 아바타 + 이름 + λ“œλ‘­λ‹€μš΄(λΆ€μ„œ/직책/λ‘œκ·Έμ•„μ›ƒ)
- AuthGate: 둜그인 ν•„μˆ˜ κ²Œμ΄νŒ… (λΉ„λ‘œκ·ΈμΈ β†’ LoginScreen, 둜그인 β†’ AppContent)
- κΈ°μ‘΄ App() β†’ AppContent()둜 λ¦¬λ„€μž„, μƒˆ App()이 AuthProvider 래퍼

### src/types.ts β€” AuthUser μΈν„°νŽ˜μ΄μŠ€ μΆ”κ°€
- name, username, department, company, email, section, title ν•„λ“œ
- 인증 μ„œλ²„ /login 응닡 ꡬ쑰와 1:1 λ§€ν•‘

## 인증 흐름
1. μ‚¬μš©μž 접속 β†’ AuthGateκ°€ sessionStorage 확인
2. λΉ„λ‘œκ·ΈμΈ β†’ LoginScreen ν‘œμ‹œ
3. μ‚¬λ²ˆ/λΉ„λ°€λ²ˆν˜Έ μž…λ ₯ β†’ POST /api/auth/login
4. BFF β†’ 인증 μ„œλ²„(form-urlencoded) β†’ LDAP bind
5. 성곡 β†’ BFFκ°€ JWTλ₯Ό HttpOnly μΏ ν‚€λ‘œ set + μ‚¬μš©μž 정보λ₯Ό JSON λ°˜ν™˜
6. Reactκ°€ sessionStorage에 μ‚¬μš©μž 정보 μ €μž₯ β†’ AppContent ν‘œμ‹œ
7. λ‘œκ·Έμ•„μ›ƒ β†’ μΏ ν‚€ μ‚­μ œ + 인증 μ„œλ²„ λΈ”λž™λ¦¬μŠ€νŠΈ 등둝 β†’ LoginScreen 볡귀

## λ³΄μ•ˆ
- JWTλŠ” HttpOnly μΏ ν‚€λ‘œλ§Œ 보관 (XSS λ°©μ–΄)
- 토큰이 React μ½”λ“œμ— λ…ΈμΆœλ˜μ§€ μ•ŠμŒ
- 인증 μ„œλ²„ IPκ°€ ν΄λΌμ΄μ–ΈνŠΈμ— λ…ΈμΆœλ˜μ§€ μ•ŠμŒ (BFF ν”„λ‘μ‹œ)
- SameSite=Lax (CSRF κΈ°λ³Έ λ°©μ–΄)
- sessionStorage: νƒ­ μ’…λ£Œ μ‹œ μ„Έμ…˜ 만료

## μ˜μ‘΄μ„± μΆ”κ°€
- cookie-parser
- @types/cookie-parser (devDependencies)

## ν™˜κ²½λ³€μˆ˜ μΆ”κ°€
- AUTH_API_BASE: 인증 μ„œλ²„ URL (κΈ°λ³Έ: http://10.150.6.47:8090)
```

---

## λ³€κ²½ 파일 λͺ©λ‘

| 파일 | μƒνƒœ | λ³€κ²½ λ‚΄μš© |
|------|------|-----------|
| `server.ts` | Modified | cookie-parser + 인증 라우트 2개 μΆ”κ°€ |
| `src/App.tsx` | Modified | AuthProvider, LoginScreen, UserMenu, AuthGate μΆ”κ°€ |
| `src/types.ts` | Modified | AuthUser μΈν„°νŽ˜μ΄μŠ€ μΆ”κ°€ |
| `package.json` | Modified | cookie-parser μ˜μ‘΄μ„± μΆ”κ°€ |

---

## Git λͺ…λ Ή

```bash
# 1. νŒ¨ν‚€μ§€ μ„€μΉ˜ (이미 ν–ˆλ‹€λ©΄ μƒλž΅)
npm install cookie-parser
npm install -D @types/cookie-parser

# 2. μŠ€ν…Œμ΄μ§•
git add server.ts src/App.tsx src/types.ts package.json package-lock.json

# 3. 컀밋
git commit -m "feat: LDAP 인증 톡합 β€” 둜그인 κ²Œμ΄νŒ… + HttpOnly μΏ ν‚€ + UserMenu"

# 4. ν‘Έμ‹œ
git push origin main
```

---

## μ•„ν‚€ν…μ²˜ λ‹€μ΄μ–΄κ·Έλž¨

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Browser    β”‚      β”‚  Express BFF   β”‚      β”‚ Auth Server  β”‚      β”‚ AD/LDAP  β”‚
β”‚   (React)    β”‚      β”‚  (server.ts)   β”‚      β”‚  (Starlette) β”‚      β”‚          β”‚
β”‚              β”‚      β”‚                β”‚      β”‚              β”‚      β”‚          β”‚
β”‚  LoginScreen │─────►│ /api/auth/     │─────►│ /login       │─────►│ LDAP     β”‚
β”‚  μ‚¬λ²ˆ+λΉ„λ²ˆ   β”‚ POST β”‚  login         β”‚ POST β”‚              β”‚ bind β”‚  bind    β”‚
β”‚              β”‚      β”‚                β”‚      β”‚              β”‚      β”‚          β”‚
β”‚              │◄─────│ Set-Cookie:    │◄─────│ {user+token} │◄─────│ 인증결과 β”‚
β”‚  sessionStor β”‚ JSON β”‚ snap_auth=JWT  β”‚      β”‚              β”‚      β”‚          β”‚
β”‚  age μ €μž₯    β”‚(user)β”‚ (HttpOnly)     β”‚      β”‚              β”‚      β”‚          β”‚
β”‚              β”‚      β”‚                β”‚      β”‚              β”‚      β”‚          β”‚
β”‚  UserMenu    β”‚      β”‚                β”‚      β”‚              β”‚      β”‚          β”‚
β”‚  이름+λΆ€μ„œ   β”‚      β”‚                β”‚      β”‚              β”‚      β”‚          β”‚
β”‚  λ‘œκ·Έμ•„μ›ƒ    │─────►│ /api/auth/     │─────►│ /logout      β”‚      β”‚          β”‚
β”‚              β”‚ POST β”‚  logout        β”‚ POST β”‚ Redis λΈ”λž™   β”‚      β”‚          β”‚
β”‚              │◄─────│ μΏ ν‚€ μ‚­μ œ     β”‚      β”‚  리슀트 등둝 β”‚      β”‚          β”‚
β”‚              β”‚      β”‚                β”‚      β”‚              β”‚      β”‚          β”‚
β”‚  검색 μš”μ²­   │─────►│ /api/search    │─────►│              β”‚      β”‚          β”‚
β”‚  (μΏ ν‚€ μžλ™) β”‚      β”‚ (κΈ°μ‘΄ 동일)    β”‚      β”‚ qp-backend   β”‚      β”‚          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

---

## μ»΄ν¬λ„ŒνŠΈ ꡬ쑰 (App.tsx)

```
App (export default)
 └─ AuthProvider (μ „μ—­ 인증 μƒνƒœ)
     └─ AuthGate (κ²Œμ΄νŒ… λΆ„κΈ°)
         β”œβ”€ isLoading  β†’ μŠ€ν”Όλ„ˆ
         β”œβ”€ !user      β†’ LoginScreen (전체화면 둜그인)
         └─ user       β†’ AppContent (κΈ°μ‘΄ SNAP UI)
                          β”œβ”€ Navbar
                          β”‚   β”œβ”€ SNAP 둜고 + SONI/QMS 링크
                          β”‚   └─ UserMenu (μ‚¬μš©μž 아바타 + λ“œλ‘­λ‹€μš΄)
                          β”œβ”€ 검색 ν™”λ©΄ (view: 'search')
                          β”œβ”€ κ²°κ³Ό ν™”λ©΄ (view: 'results')
                          └─ DetailModal (상세 λͺ¨λ‹¬)
```

---

## ν™˜κ²½λ³€μˆ˜ 전체 λͺ©λ‘ (BFF)

| λ³€μˆ˜ | κΈ°λ³Έκ°’ | μ„€λͺ… |
|------|--------|------|
| `PORT` | `8888` | Express μ„œλ²„ 포트 |
| `QP_API_BASE` | `http://10.150.6.47:18503` | FastAPI λ°±μ—”λ“œ URL |
| `AUTH_API_BASE` | `http://10.150.6.47:8090` | LDAP 인증 μ„œλ²„ URL |
| `SNAP_DATA_DIR` | `../snap_data` | νŽ˜μ΄μ§€ 이미지 폴더 |
| `NODE_ENV` | (μ—†μŒ) | `production`이면 dist/ 정적 μ„œλΉ™ |


### μž‘μ—… 루트
seok@PSESL25717702:/mnt/d/dev/LLM/QP/service$ ls
README.md  assets  index.html  metadata.json  node_modules  package-lock.json  package.json  server.ts  src  tsconfig.json  vite.config.ts

-------------
# 배포 μ—λŸ¬ 
```bash
seok@PSESL25717702:/mnt/d/dev/LLM/QP/service$ npm run build

> react-example@0.0.0 build
> vite build

vite v6.4.2 building for production...
βœ“ 2072 modules transformed.
dist/index.html                     0.55 kB β”‚ gzip:   0.40 kB
dist/assets/favicon-CIqKAyFz.ico    1.62 kB
dist/assets/index-BtKlGMUt.css     41.21 kB β”‚ gzip:   7.45 kB
dist/assets/index-D94KCa__.js     370.44 kB β”‚ gzip: 114.85 kB
βœ“ built in 46.73s
seok@PSESL25717702:/mnt/d/dev/LLM/QP/service$ npm start

> react-example@0.0.0 start
> node server.ts

node:internal/modules/esm/get_format:219
  throw new ERR_UNKNOWN_FILE_EXTENSION(ext, filepath);
        ^

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for D:\dev\LLM\QP\service\server.ts
    at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:219:9)
    at defaultGetFormat (node:internal/modules/esm/get_format:245:36)
    at defaultLoad (node:internal/modules/esm/load:120:22)
    at async ModuleLoader.loadAndTranslate (node:internal/modules/esm/loader:580:32)
    at async ModuleJob._link (node:internal/modules/esm/module_job:116:19) {
  code: 'ERR_UNKNOWN_FILE_EXTENSION'
}

Node.js v22.16.0
seok@PSESL25717702:/mnt/d/dev/LLM/QP/service$
```


## why??
```bash
h200_1_user@dgx-h200:/home/Projects/projects/qp/images$ docker ps -a --filter "name=snap"
CONTAINER ID   IMAGE      COMMAND                  CREATED         STATUS                     PORTS                                             NAMES
045ccb3438cb   snap:1.0   "docker-entrypoint.s…"   3 minutes ago   Up 3 minutes (unhealthy)   0.0.0.0:18504->18505/tcp, [::]:18504->18505/tcp   snap
h200_1_user@dgx-h200:/home/Projects/projects/qp/images$ docker logs --tail 200 snap
[snap_data] serving from /data/snap_data
Server running on http://localhost:18505
[auth] Auth server: http://10.150.6.47:8090
[logs] Activity logs: /data/logs
h200_1_user@dgx-h200:/home/Projects/projects/qp/images$ docker inspect snap | grep -A 30 "Health"
            "Health": {
                "Status": "unhealthy",
                "FailingStreak": 9,
                "Log": [
                    {
                        "Start": "2026-05-07T17:57:37.139147043+09:00",
                        "End": "2026-05-07T17:57:37.251677715+09:00",
                        "ExitCode": 1,
                        "Output": "wget: can't connect to remote host: Connection refused\n"
                    },
                    {
                        "Start": "2026-05-07T17:58:07.25268173+09:00",
                        "End": "2026-05-07T17:58:07.298976582+09:00",
                        "ExitCode": 1,
                        "Output": "wget: can't connect to remote host: Connection refused\n"
                    },
                    {
                        "Start": "2026-05-07T17:58:37.299534102+09:00",
                        "End": "2026-05-07T17:58:37.344486575+09:00",
                        "ExitCode": 1,
                        "Output": "wget: can't connect to remote host: Connection refused\n"
                    },
                    {
                        "Start": "2026-05-07T17:59:07.345563587+09:00",
                        "End": "2026-05-07T17:59:07.390899772+09:00",
                        "ExitCode": 1,
                        "Output": "wget: can't connect to remote host: Connection refused\n"
                    },
                    {
                        "Start": "2026-05-07T17:59:37.392160333+09:00",
                        "End": "2026-05-07T17:59:37.43758568+09:00",
--
            "Healthcheck": {
                "Test": [
                    "CMD-SHELL",
                    "wget -qO- http://localhost:18505/ > /dev/null || exit 1"
                ],
                "Interval": 30000000000,
                "Timeout": 5000000000,
                "StartPeriod": 15000000000,
                "Retries": 3
            },
            "Image": "snap:1.0",
            "Volumes": null,
            "WorkingDir": "/app",
            "Entrypoint": [
                "docker-entrypoint.sh"
            ],
            "Labels": {}
        },
        "NetworkSettings": {
            "SandboxID": "074deb5cc38a95e97f0cd8eb91353d72f0db1fd10fdd602673c55e6423e86abc",
            "SandboxKey": "/var/run/docker/netns/074deb5cc38a",
            "Ports": {
                "18505/tcp": [
                    {
                        "HostIp": "0.0.0.0",
                        "HostPort": "18504"
                    },
                    {
                        "HostIp": "::",
                        "HostPort": "18504"
                    }
```

---
```
# ─────────────────────────────────────────────
# SNAP Frontend + BFF (React + Express)
# Multi-stage: Vite λΉŒλ“œ β†’ κ²½λŸ‰ λŸ°νƒ€μž„
#
# λ³€κ²½ 이λ ₯:
# - LDAP 인증 톡합 (cookie-parser, /api/auth/*)
# - ν™œλ™ 둜그 μ‹œμŠ€ν…œ (/data/logs 마운트 ꢌμž₯)
# ─────────────────────────────────────────────

# ─── Build stage ───
FROM node:20-alpine AS builder

WORKDIR /app

# μ˜μ‘΄μ„± λ¨Όμ € μ„€μΉ˜ (도컀 λ ˆμ΄μ–΄ 캐싱)
COPY package.json package-lock.json ./
RUN npm ci

# μ†ŒμŠ€ 전체 볡사 β†’ Vite λΉŒλ“œ
# (App.tsx, main.tsx, index.css, types.ts, searchPipeline.ts λͺ¨λ‘ ν•„μš”)
COPY . .
RUN npm run build
# β†’ /app/dist 에 React λΉŒλ“œ κ²°κ³Ό (HTML/JS/CSS λ²ˆλ“€)


# ─── Runtime stage ───
FROM node:20-alpine

WORKDIR /app

# λŸ°νƒ€μž„ μ˜μ‘΄μ„±λ§Œ μ„€μΉ˜ (devDependencies μ œμ™Έ)
COPY package.json package-lock.json ./
RUN npm ci --omit=dev && \
    npm install --no-save tsx@^4.21.0 && \
    npm cache clean --force

# Vite λΉŒλ“œ κ²°κ³Ό (HTML/JS/CSS λ²ˆλ“€)
COPY --from=builder /app/dist ./dist

# BFF μ„œλ²„ μ½”λ“œ + λŸ°νƒ€μž„ import λŒ€μƒ
# server.tsκ°€ src/services/searchPipeline.ts, src/types.tsλ₯Ό import 함
COPY server.ts ./
COPY src ./src
COPY tsconfig.json ./

# ν™œλ™ 둜그 디렉토리 생성 (ν˜ΈμŠ€νŠΈμ—μ„œ -v둜 λ§ˆμš΄νŠΈν•  μœ„μΉ˜)
# 마운트 μ•ˆ ν•˜λ©΄ μ»¨ν…Œμ΄λ„ˆ λ‚΄λΆ€ μž„μ‹œ 폴더에 기둝됨 (μž¬μ‹œμž‘ μ‹œ μ†Œμ‹€)
RUN mkdir -p /data/logs && chmod 755 /data/logs

# ─── ν™˜κ²½λ³€μˆ˜ (λŸ°νƒ€μž„μ— docker run --env-file둜 μ£Όμž…) ───
ENV NODE_ENV=production
ENV PORT=18505

EXPOSE 18505

# Healthcheck β€” 30μ΄ˆλ§ˆλ‹€ / 응닡 확인
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
    CMD wget -qO- http://localhost:18505/ > /dev/null || exit 1

CMD ["npx", "tsx", "server.ts"]
```