feat(dashboard): persist administrator login
This commit is contained in:
+66
-81
@@ -1,101 +1,86 @@
|
||||
# Dashboard 관리자 인증 프로젝트 구조
|
||||
# Project Structure — Dashboard Persistent Authentication
|
||||
|
||||
## 목적과 경계
|
||||
## 목적과 범위
|
||||
|
||||
Dashboard 진입점 앞의 인증 경계, 인증 구현, 기존 page view, test와 배포 설정의 책임 및 단방향 의존을 정의한다.
|
||||
Issue #6의 signed persistent authentication을 기존 Dashboard page 구조와 분리하여 배치하는 규칙을 정의한다. DB schema와 MCP 구조는 변경하지 않는다.
|
||||
|
||||
- 포함 경계: `dashboard/`, Dashboard 인증 test, 환경 예시, Compose와 인증 설계 문서
|
||||
- 제외 경계: MCP adapter·server 인증, 가계부 application·SQLite schema 변경
|
||||
## Source 영역
|
||||
|
||||
## 디렉터리 구조
|
||||
| 경로 | 책임 | 주요 공개 요소 | 금지 의존성 |
|
||||
|---|---|---|---|
|
||||
| `dashboard/server.py` | Streamlit ASGI app과 인증 route 조립 | `app` | DB/gateway 직접 접근 |
|
||||
| `dashboard/auth_server.py` | same-origin login/logout HTTP response와 hardened cookie | `create_auth_routes()` | page view, SQLite |
|
||||
| `dashboard/authentication.py` | 설정, credential 비교, token 발급·검증, Streamlit gate | `require_dashboard_authentication()` | 금융 DB/gateway |
|
||||
| `dashboard/app.py` | gate 성공 후 기존 page dispatch | `main()` | token signing 세부 구현 |
|
||||
| `dashboard/common.py` | 기존 DB/UI helper | 기존 API | 인증 결정 |
|
||||
| `dashboard/page_views/` | 기존 금융 화면 | `page_*` | 인증 transport |
|
||||
|
||||
### dashboard-runtime — Dashboard runtime
|
||||
`dashboard.server:app`이 운영 진입점이다. 인증 endpoint는 Streamlit 1.58의 public ASGI `App(routes=...)` 확장 지점에 같은 process로 결합한다.
|
||||
|
||||
```text
|
||||
dashboard/
|
||||
├── app.py
|
||||
├── authentication.py
|
||||
├── common.py
|
||||
└── page_views/
|
||||
```
|
||||
## Test 영역
|
||||
|
||||
| 경로 | 종류 | 책임 | 공개 진입점 | 내부 구현 경계 | 편집 정책 | 생성 주체 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `dashboard/app.py` | Streamlit 진입점 | page config 후 인증 gate를 호출하고 인증된 요청만 기존 Dashboard로 dispatch | `main()` | 인증 정책 세부 구현을 포함하지 않음 | DB 경로 취득 전 gate 유지 | 수동 |
|
||||
| `dashboard/authentication.py` | 인증 모듈 | 환경변수 설정, constant-time 검증, 실패 지연, session·로그아웃과 event log | `require_dashboard_authentication()` | 가계부 DB·gateway·page view에 의존하지 않음 | credential 비보존과 fail-closed 유지 | 수동 |
|
||||
| `dashboard/common.py` | Dashboard 공통 지원 | DB 경로, UI와 gateway helper | 기존 함수 | 인증 상태를 결정하지 않음 | 인증된 dispatch 이후 사용 | 수동 |
|
||||
| `dashboard/page_views/` | 화면 모듈 | 기존 금융 조회·쓰기·import UI | 각 `page_*` 함수 | 인증 구현에 역방향 의존하지 않음 | `app.py`의 인증된 dispatch로만 공개 | 수동 |
|
||||
| 경로 | 검증 책임 |
|
||||
|---|---|
|
||||
| `tests/test_dashboard_authentication.py` | 기존 credential 비교·지연·safe log |
|
||||
| `tests/test_dashboard_persistent_authentication.py` | token claims, TTL, tamper, rotation, malformed input |
|
||||
| `tests/test_dashboard_auth_server.py` | login/logout response, cookie 속성, same-origin, fail-closed |
|
||||
| `tests/test_dashboard_authentication_app.py` | Streamlit gate, DB 이전 차단, cookie 복원·rotation |
|
||||
| `tests/test_dashboard_app.py` | 인증 이후 기존 핵심 page regression |
|
||||
| `scripts/persistent_auth_http_evidence.py` | 실제 loopback HTTPS cookie/reconnect/logout evidence |
|
||||
|
||||
### dashboard-tests — Dashboard 인증·회귀 test
|
||||
Test는 synthetic DB와 test-only credential/secret만 사용하고 token·secret 원문을 출력하지 않는다.
|
||||
|
||||
```text
|
||||
tests/
|
||||
├── test_dashboard_authentication.py
|
||||
├── test_dashboard_authentication_app.py
|
||||
└── test_dashboard_app.py
|
||||
```
|
||||
## Config·배포·문서 영역
|
||||
|
||||
| 경로 | 종류 | 책임 | 공개 진입점 | 내부 구현 경계 | 편집 정책 | 생성 주체 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `tests/test_dashboard_authentication.py` | 단위 test | 설정·constant-time 비교·지연·비민감 log 검증 | pytest | 실제 credential과 운영 DB 금지 | test-only 값 사용 | 수동 |
|
||||
| `tests/test_dashboard_authentication_app.py` | Streamlit 통합 test | fail-closed, 우회 금지, 1초 지연, session과 logout 검증 | pytest AppTest | synthetic fixture만 사용 | acceptance criterion 대응 유지 | 수동 |
|
||||
| `tests/test_dashboard_app.py` | 기존 기능 회귀 test | 인증 후 핵심 page 호환성 검증 | pytest AppTest | synthetic fixture만 사용 | 로그인 후 기존 assertion 유지 | 수동 |
|
||||
|
||||
### deployment-docs — 배포 설정과 계약 문서
|
||||
|
||||
```text
|
||||
.env.example
|
||||
docker-compose.yml
|
||||
README.md
|
||||
docs/
|
||||
├── requirements/req-001_dashboard-administrator-authentication.md
|
||||
├── interface-contract.md
|
||||
├── project-structure.md
|
||||
└── runtime-flow.md
|
||||
```
|
||||
|
||||
| 경로 | 종류 | 책임 | 공개 진입점 | 내부 구현 경계 | 편집 정책 | 생성 주체 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `.env.example` | 설정 예시 | credential 변수 이름과 용도 | `.env` 복사 | 실제 secret 값 금지 | 빈 값만 제공 | 수동 |
|
||||
| `docker-compose.yml` | local deployment | Dashboard container로 host secret 환경변수 전달 | Compose service | MCP service credential 계약 변경 금지 | required-variable interpolation으로 누락 시 시작 거부 | 수동 |
|
||||
| `README.md` | 운영 안내 | 설정, fail-closed, 보호 범위와 rate-limit 제약 | 사용자 문서 | 실제 secret 값 금지 | 구현과 함께 갱신 | 수동 |
|
||||
| `docs/` 인증 문서 | 정본 계약 | 요구사항·인터페이스·구조·흐름 | Markdown | 데이터 모델 문서 생성 없음 | harness template 구조 유지 | gitea-agent impl workflow |
|
||||
| 경로 | 책임 |
|
||||
|---|---|
|
||||
| `pyproject.toml`, `uv.lock` | `itsdangerous`, `starlette`, `uvicorn` 직접 dependency 고정 |
|
||||
| `.env.example` | 세 environment 변수의 이름만 제공 |
|
||||
| `docker-compose.yml` | signing secret 전달과 ASGI server command |
|
||||
| `deploy/k8s/base/deployment.yaml` | Kubernetes Secret `signing-secret` 전달과 ASGI command |
|
||||
| `README.md`, `deploy/k8s/README.md` | HTTPS, rotation, expiry, rate-limit, logout 제약 |
|
||||
| `docs/requirements/req-001_dashboard-administrator-authentication.md` | Issue #1·#6 통합 canonical 승인 명세 |
|
||||
| `docs/interface-contract.md` | HTTP/cookie/token 계약 |
|
||||
| `docs/runtime-flow.md` | 발급·복원·실패·logout 순서 |
|
||||
|
||||
## 의존 방향
|
||||
|
||||
| 출발 영역 | 대상 영역 | 허용 여부 | 규칙 | 공유 계약 위치 |
|
||||
|---|---|---|---|---|
|
||||
| `dashboard-app` | `dashboard-authentication` | 허용 | DB 경로와 page dispatch보다 먼저 gate 호출 | `docs/runtime-flow.md` |
|
||||
| `dashboard-authentication` | `dashboard-common` | 금지 | 인증은 가계부 DB·gateway와 독립 | `docs/interface-contract.md` |
|
||||
| `dashboard-authentication` | `dashboard-page-views` | 금지 | 인증 모듈은 page view를 import·호출하지 않음 | `docs/interface-contract.md` |
|
||||
| `dashboard-app` | `dashboard-common` | 인증 후 허용 | gate가 참일 때만 DB/UI helper 사용 | `docs/runtime-flow.md` |
|
||||
| `dashboard-app` | `dashboard-page-views` | 인증 후 허용 | gate가 참일 때만 선택 page dispatch | `docs/runtime-flow.md` |
|
||||
| `dashboard-page-views` | `dashboard-authentication` | 금지 | page별 인증 분기나 역방향 의존을 만들지 않음 | `docs/requirements/req-001_dashboard-administrator-authentication.md` |
|
||||
| `dashboard-authentication` | `mcp-adapter` | 금지 | 이번 인증은 MCP를 보호하지 않음 | `README.md` |
|
||||
```text
|
||||
browser
|
||||
├─ POST /api/auth/login|logout → dashboard.auth_server
|
||||
└─ Streamlit handshake cookie → dashboard.authentication
|
||||
|
||||
dashboard.server → dashboard.auth_server + Streamlit App(dashboard/app.py)
|
||||
dashboard.app → dashboard.authentication → itsdangerous
|
||||
→ (인증 성공 후) dashboard.common/page_views
|
||||
```
|
||||
|
||||
`auth_server`와 `authentication`은 금융 DB를 import하지 않는다. Page module은 cookie와 token module을 직접 호출하지 않는다.
|
||||
|
||||
## 상태·데이터 배치
|
||||
|
||||
- Credential/signing secret: process environment에서 request/rerun 동안만 참조
|
||||
- Auth token: browser의 `Secure`/`HttpOnly` cookie에만 저장
|
||||
- Correlation ID: 비민감 값으로 `st.session_state`에 저장 가능
|
||||
- 금융 데이터: 기존 SQLite
|
||||
- Token registry/revocation table/migration: 생성하지 않음
|
||||
|
||||
따라서 `docs/data-model.md`는 추가하지 않는다.
|
||||
|
||||
## 변경 규칙
|
||||
|
||||
| 구분 | 규칙 |
|
||||
|---|---|
|
||||
| 새 모듈·파일 배치 | Dashboard 공통 인증은 `dashboard/authentication.py`, page 기능은 기존 `dashboard/page_views/`에 둔다. |
|
||||
| 이름·패키지 | 환경변수는 `JARCA_DASHBOARD_` prefix, 비공개 session key는 `_jarca_dashboard_` prefix를 사용한다. |
|
||||
| 소스·테스트 대응 | 인증 pure helper는 `test_dashboard_authentication.py`, Streamlit 경계는 `test_dashboard_authentication_app.py`, 기존 page 호환성은 `test_dashboard_app.py`에서 검증한다. |
|
||||
| 생성 산출물 | DB schema·migration·credential file을 생성하지 않는다. Requirement와 설계 문서만 `docs/`에 추가한다. |
|
||||
- 인증 환경변수는 `JARCA_DASHBOARD_` prefix를 사용한다.
|
||||
- Cookie name은 host-only scope를 강제하는 `__Host-` prefix를 사용한다.
|
||||
- Token schema/TTL/cookie attributes 변경은 REQ-001과 interface contract를 함께 변경한다.
|
||||
- Secret, token, cookie header, credential 값을 source·fixture·log·문서에 기록하지 않는다.
|
||||
- Streamlit private Tornado/WebSocket internals나 client-side cookie component를 사용하지 않는다.
|
||||
|
||||
## 검증
|
||||
|
||||
- 구조 검사 명령:
|
||||
```text
|
||||
uv run --extra dashboard --extra dev pytest
|
||||
uv run --extra dashboard --extra dev ruff check dashboard tests scripts
|
||||
uv run python -m compileall -q dashboard tests scripts
|
||||
```
|
||||
|
||||
```text
|
||||
python -c "from pathlib import Path; required=['dashboard/app.py','dashboard/authentication.py','tests/test_dashboard_authentication.py','tests/test_dashboard_authentication_app.py']; assert all(Path(p).is_file() for p in required)"
|
||||
```
|
||||
|
||||
- 구조 검사 기대 결과: exit code 0이며 모든 인증 source·test 경로가 존재한다.
|
||||
- 정적 분석 명령:
|
||||
|
||||
```text
|
||||
uv run --extra dashboard --extra dev ruff check dashboard tests
|
||||
```
|
||||
|
||||
- 정적 분석 기대 결과: 새 syntax·undefined-name 오류가 없다.
|
||||
- 문서와 실제 트리의 정합성: base-to-worktree 변경 파일을 본 문서의 세 영역과 대조하고 `docs/data-model.md`와 DB migration이 생성되지 않았는지 확인한다.
|
||||
Diff에서 DB migration과 `docs/data-model.md`가 추가되지 않았는지 확인한다.
|
||||
|
||||
Reference in New Issue
Block a user