- auth.js: realm_access.roles/resource_access 대신 ID 토큰 claims.groups 사용 (group-membership 매퍼, full.path=false 대비 앞 슬래시 제거 후 비교) - config.js: adminRole(ADMIN_ROLE, "admin") → adminGroup(ADMIN_GROUP, "DevOps") - server.js: 세션 user 주석 roles → groups - CLAUDE.md: 인증 모델·Kubero Env 문서 갱신 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
71 lines
5.1 KiB
Markdown
71 lines
5.1 KiB
Markdown
# CLAUDE.md
|
|
|
|
AI DEV — 사내 개발 포털. 직원이 한 곳에서 **사내 개발 사이트로 이동**하고, **만든 프로젝트를
|
|
공유·코멘트**하는 곳. Keycloak SSO(사번 로그인) + Node(Express+EJS) + Postgres.
|
|
|
|
## 이 저장소를 다룰 때 (먼저 읽기)
|
|
- **사용자는 개발 전문가가 아닌 사내 IT 기획자/직원**이다. 변경은 **단순하게** 유지한다.
|
|
- 무거운 도구는 피한다: **프런트엔드 프레임워크(React 등)·번들러·빌드 단계는 도입하지 않는다.**
|
|
서버 렌더링(EJS) 유지.
|
|
- 단, **빌드가 필요 없는 범위의 정리·분할은 허용**한다 — CSS 를 역할별 파일로 나눠 다중 `<link>`,
|
|
JS 를 브라우저 **네이티브 ESM 모듈**(`<script type="module">` + `import`)로 분할 등.
|
|
- 새 npm 의존성은 꼭 필요할 때만, 이유와 함께 추가한다.
|
|
- **UI 문구와 코드 주석은 한국어**로 쓴다.
|
|
- **비밀값은 코드에 넣지 않는다.** 환경변수(`.project-env` 로컬 / Kubero Env 배포)로만 다룬다.
|
|
- 할 일·작업 단위는 [`PLANS.md`](./PLANS.md), 디자인 방향은 [`design-refs/`](./design-refs/) 참고.
|
|
|
|
## 스택
|
|
- **Node 22 ESM** (`"type": "module"`) — import 경로에 `.js` 확장자 **필수**.
|
|
- **Express 4 + EJS** — 서버 사이드 렌더링, 빌드 없음.
|
|
- **Postgres** (`pg`) — OpenEverest `appdb`, schema `portal`.
|
|
- **Keycloak OIDC** (`openid-client` v5) — Authorization Code.
|
|
- 정적 CSS 한 장: `public/style.css`(라이트/다크 CSS 변수). 가벼운 바닐라 JS: `public/app.js`. 로고: `public/logo.svg`.
|
|
- 프레임워크/번들러 없이 **점진적 향상**: 폼 POST 기본 동작 + JS 있으면 fetch 로 즉시 반영.
|
|
|
|
## 명령
|
|
- `npm run dev` — `node --watch`, http://localhost:3000
|
|
- `npm start` — 프로덕션 (`node index.js`)
|
|
- `npm run db:init` — `schema.sql` 적용 (멱등, 최초 1회)
|
|
- 로컬 환경변수 로드: `cp .project-env.example .project-env` 로 값을 채운 뒤
|
|
`set -a; . ./.project-env; set +a`
|
|
|
|
## 구조 맵
|
|
- `index.js` → `src/server.js` 진입 (Kubero buildpack run = `node index.js`)
|
|
- `src/server.js` — Express 앱, 라우트, 부팅
|
|
- `src/config.js` — 환경변수 설정
|
|
- `src/auth.js` — Keycloak OIDC (로그인 리다이렉트 / 콜백 / `requireAuth` 가드)
|
|
- `src/db.js` — Postgres 풀 + 쿼리 함수 (`hearts`❤️/`stars`⭐/`comments`/`projects`)
|
|
- `src/sites.js` — 도구 카탈로그(개발/인프라) + `toolsForUser(isAdmin)` role 필터
|
|
- `src/helpers.js` — 뷰 헬퍼: `icon`(Octicon SVG)·`identicon`(아바타 SVG)·`langColor`·`langFromTags` (res.locals 노출)
|
|
- `views/` — `_header`, `_project_detail`(모달/단독 공용 상세), `dashboard`, `login`, `project` (EJS)
|
|
- `public/` — `style.css`(CSS 변수 라이트/다크 단일 파일), `app.js`(테마·모달·검색/정렬·반응 fetch·이스터에그), `logo.svg`
|
|
- `schema.sql` — DB 스키마(멱등), `scripts/init-db.js` 가 적용
|
|
- `deploy/portal-route.yaml` — Gateway HTTPRoute (portal.bokdev.in)
|
|
- `design-refs/` — 디자인 레퍼런스(.dc.html). 화면 변경 시 시각 기준.
|
|
- `.claude/` — Claude Code 훅·설정
|
|
|
|
## 인증 모델
|
|
- **사번 = Keycloak `preferred_username`.** 세션에 `req.session.user = { sabun, name, email, groups, isAdmin }`.
|
|
- **관리자 = `groups` 클레임에 `ADMIN_GROUP`(기본 `DevOps`) 포함.** (portal 클라이언트의 group-membership
|
|
매퍼가 ID 토큰에 `groups` 를 넣어줌. `full.path=false` 라 앞 슬래시는 떼고 비교.) 인프라 도구(OpenEverest/MinIO/
|
|
Keycloak/LiteLLM)는 관리자에게만 렌더(`sites.js` 의 `adminOnly` + `toolsForUser`).
|
|
- 미인증으로 보호 페이지 진입 시 중간 화면 없이 곧장 Keycloak 로그인으로 리다이렉트
|
|
(`requireAuth` → `loginRedirect`). OIDC discovery 실패 시에만 `login.ejs` 폴백.
|
|
- ⚠️ **알려진 문제**: `/logout` 은 로컬 세션만 파기 → Keycloak SSO 세션이 남아 재진입 시 자동
|
|
재로그인된다(= 로그아웃 안 됨). 세션이 인메모리라 재시작/다중 replica 시 로그인이 풀린다.
|
|
자세한 내용과 해결 작업은 [`PLANS.md`](./PLANS.md) 참고.
|
|
|
|
## 배포
|
|
- **Kubero NodeJS buildpack** (`npm install` → `node index.js`). 평소 Dockerfile 불필요.
|
|
- `Dockerfile` 은 Kubero buildpack 의 harbor push 실패를 우회하는 **수동 push 용**.
|
|
- 도메인 `portal.bokdev.in` (`deploy/portal-route.yaml`). 도메인 추가 시 Keycloak `portal`
|
|
client redirect 에 `https://<도메인>/auth/callback` 등록 필요.
|
|
- Kubero Env: `BASE_URL`, `DATABASE_URL`, `OIDC_*`, `SESSION_SECRET`, `PORTAL_BRAND`, `ADMIN_GROUP`, (선택) 도구 `*_URL`.
|
|
|
|
## 컨벤션
|
|
- 라우트는 `src/server.js` 에, DB 접근은 `src/db.js` 의 함수로만 (인라인 SQL 산재 금지).
|
|
- 새 화면을 추가할 때는 `views/_header` 를 include 하고 기존 `.box` / `.btn` / `.tag` 등
|
|
CSS 클래스를 재사용한다. 새 스타일은 `public/style.css` 한 곳에.
|
|
- DB 스키마 변경은 `schema.sql` 에 멱등(`IF NOT EXISTS`)으로 추가하고 `npm run db:init` 재실행.
|
|
- 브랜드명은 하드코딩하지 말고 `PORTAL_BRAND`(= `res.locals.brand`)를 사용한다.
|