| # 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"] |
| ``` |
| |