feat(dashboard): persist administrator login

This commit is contained in:
2026-10-01 13:56:22 +00:00
parent 7bea8b368e
commit c5eaddb4f3
20 changed files with 1343 additions and 884 deletions
@@ -3,7 +3,7 @@
"schema": "gitea-agent.requirement/v1",
"id": "REQ-001",
"title": "Dashboard 관리자 인증",
"revision": "1.0",
"revision": "2.0",
"status": "approved",
"depends_on": [],
"applies_to": ["dashboard"],
@@ -15,479 +15,145 @@
## 1. 목적
Streamlit Dashboard의 모든 금융 데이터 조회·수정·import 기능 앞에 단일 관리자 인증 경계를 두고, 인증되지 않았거나 인증 설정이 유효하지 않은 요청을 fail-closed한다.
Streamlit Dashboard의 모든 금융 데이터 조회·수정·import 기능 앞에 단일 관리자 인증 경계를 두고, 인증되지 않았거나 인증 설정이 유효하지 않은 요청을 fail-closed한다. 인증 성공 후에는 server-signed `Secure`/`HttpOnly` cookie로 같은 browser profile의 새 연결과 새 session에서 인증을 정해진 절대 만료까지 복원한다.
## 2. 배경
Dashboard는 SQLite 가계부를 직접 읽고 쓰며 거래 수정, 계좌 변경, PDF import와 import rollback을 제공한다. 기존 진입점은 인증 없이 DB 경로를 얻고 page view를 dispatch했으므로, 외부 노출 시 금융 데이터와 운영 기능을 보호할 애플리케이션 수준의 경계가 필요하다.
Revision 1.0은 Issue #1에서 단일 관리자 credential과 browser-session-only 인증 경계를 도입했다. Issue #6은 sidebar query-link 이동과 browser 재접속에서 반복 로그인이 발생하는 문제를 해결하기 위해 동일한 인증 기능의 session·logout·배포 계약을 persistent cookie 방식으로 확장한다. 새 기능 ID가 아니라 본 canonical 요구사항의 revision으로 관리한다.
## 3. 범위
### 포함
- 모든 Dashboard 페이지의 공통 관리자 로그인과 로그아웃
- 환경변수 기반 단일 관리자 credential 검증
- 인증 전 DB·gateway·page dispatch 차단
- browser session 범위의 인증 상태
- 실패 로그인 최소 지연, 일반 오류와 인증 event 관찰 가능성
- Compose·환경 예시·운영 문서의 secret 주입 계약
- 모든 Dashboard 페이지 앞의 공통 관리자 인증과 인증 전 DB 차단
- 환경변수 기반 관리자 ID/password 및 독립적인 signing secret
- constant-time credential 비교, 일반 오류, 실패당 최소 1초 지연
- 동일 Dashboard ASGI process의 server-side login/logout HTTP endpoint
- server-signed `Secure`/`HttpOnly` cookie 발급·검증·삭제
- 기본 24시간, 선택 시 30일인 로그인 성공 시각 기준 절대 만료
- sidebar 페이지 이동, 새 탭, browser 종료 후 재접속과 새 Streamlit session 복원
- password/signing-secret/admin-ID rotation에 따른 기존 token 무효화
- credential·secret·token 비노출과 인증 event 관찰 가능성
- Compose, Kubernetes Secret, HTTPS와 운영 제약 문서화
### 제외
- 다중 사용자, 역할 기반 권한과 사용자별 데이터 분리
- 회원가입, 비밀번호 찾기와 credential 관리 UI
- OIDC, SSO, 외부 IdP와 persistent cookie
- SQLite schema와 가계부 도메인 모델 변경
- MCP endpoint 인증, TLS, Ingress와 네트워크 경계 전체 구현
- 다중 사용자, 회원가입, RBAC, 사용자별 데이터 분리
- OAuth/OIDC, SSO, 외부 IdP, MFA, CAPTCHA, 계정 잠금
- 외부 인증 proxy/gateway 또는 외부 session middleware로 인증 책임 이전
- sliding expiration 또는 복원 시 만료 재설정
- DB-backed token/session registry, 개별 원격 session 폐기 UI
- MCP endpoint 인증과 MCP transport 변경
- SQLite 금융 schema와 데이터 모델 변경
- 다른 browser profile·기기 간 로그인 공유
## 4. 용어와 기준
- Dashboard: `dashboard/app.py`를 진입점으로 실행되는 Streamlit 애플리케이션
- 인증 전: 현재 Streamlit browser session의 인증 상태가 참이 아닌 상태
- credential source: 배포 secret이 주입하는 `JARCA_DASHBOARD_ADMIN_ID`와 `JARCA_DASHBOARD_ADMIN_PASSWORD` 환경변수
- browser session: Streamlit이 `st.session_state`를 격리하는 단일 클라이언트 session
- 정본 근거: Gitea Issue `#1`과 source revision `069d4c16fd84e871e8cd9dc8d9ae39b4fce4a6c6`
- **persistent auth token**: version, 관리자 subject, 발급 시각, 절대 만료, token ID만 포함하고 서버가 서명한 bearer token
- **remember me**: 기본 미선택 checkbox이며 token TTL을 24시간에서 30일로 선택
- **rotation**: 관리자 password 또는 signing secret을 교체하여 기존 token 서명 검증을 실패시키는 운영 절차
- **canonical 근거**: Issue #1, Issue #6, source revisions `069d4c16fd84e871e8cd9dc8d9ae39b4fce4a6c6` 및 `7bea8b368e02e98afd4b2dc463fb270b0e6eb297`
## 5. 요구사항
### REQ-001.01 — 공통 인증 경계
Dashboard는 어떤 DB 경로 취득, page dispatch, DB-backed 렌더링, gateway 호출, upload 또는 rollback 처리보다 먼저 인증을 판정해야 한다. 인증 전에는 로그인 화면만 렌더링해야 한다.
Dashboard는 DB path 취득, page dispatch, gateway 호출, upload, rollback 또는 DB-backed 렌더링보다 먼저 현재 설정과 signed cookie를 검증해야 한다. 기존 session boolean만으로 만료·rotation 검증을 우회해서는 안 된다. 실패 시 로그인 화면만 렌더링한다.
**영향 범위**
### REQ-001.02 — 설정과 fail-closed
- 인터페이스: Streamlit Dashboard 진입점과 모든 page view
- 영속성: 인증 전에는 SQLite 읽기·쓰기를 수행하지 않음
- 후속 사용 주체: Dashboard 사용자와 page view
- 마이그레이션: 없음
- 호환성: 인증 성공 후 기존 page query parameter와 페이지 기능 유지
- 배포 전제 조건: 관리자 환경변수 두 개가 유효하게 주입됨
### REQ-001.02 — Credential 설정과 fail-closed
애플리케이션은 `JARCA_DASHBOARD_ADMIN_ID`와 `JARCA_DASHBOARD_ADMIN_PASSWORD`만 credential source로 읽어야 한다. Streamlit secrets 또는 다른 fallback을 사용하지 않으며, 어느 값이든 누락·빈 값·비문자열·읽기 오류이면 일반 설정 오류를 표시하고 인증을 허용하지 않아야 한다.
**영향 범위**
- 인터페이스: process environment
- 영속성: credential을 저장하지 않음
- 후속 사용 주체: Dashboard 인증 경계
- 마이그레이션: 없음
- 호환성: 두 환경변수를 설정하지 않은 기존 Dashboard 배포는 의도적으로 fail-closed
- 배포 전제 조건: 배포 secret의 환경변수 주입
Credential source는 `JARCA_DASHBOARD_ADMIN_ID`, `JARCA_DASHBOARD_ADMIN_PASSWORD`뿐이며 signing source는 `JARCA_DASHBOARD_SIGNING_SECRET`뿐이다. 누락·빈 값·비문자열·read error 또는 UTF-8 32 byte 미만 signing secret은 일반 설정 오류로 fail-closed한다. Streamlit secrets나 fallback을 사용하지 않는다.
### REQ-001.03 — Credential 검증과 비노출
제출된 관리자 ID와 password는 모두 비교하되 password를 포함한 검증은 constant-time primitive를 사용해야 한다. Credential 원문·verifier는 화면, 일반 오류, traceback, URL/query parameter, application log 또는 로그인 완료 후 `st.session_state`에 남지 않아야 한다.
**영향 범위**
- 인터페이스: 관리자 ID·password 입력 form
- 영속성: 없음
- 후속 사용 주체: 현재 Dashboard session
- 마이그레이션: 없음
- 호환성: 없음
- 배포 전제 조건: 평문 password secret은 repository가 아닌 secret store에서 제공
제출 ID와 password는 모두 `hmac.compare_digest`로 비교한다. Credential, signing secret, signing-key material, token, raw cookie와 `Set-Cookie` 값은 UI, traceback, URL/query, application log, 문서 또는 `st.session_state`에 저장하지 않는다. 비민감 correlation ID만 session state에 둘 수 있다.
### REQ-001.04 — 실패 로그인 응답
잘못된 ID 또는 password의 각 제출은 제출 처리 시작부터 일반 오류 응답 완료까지 최소 1초가 걸려야 한다. 두 실패는 동일한 오류를 표시하고 DB·gateway·상태 변경을 수행하지 않아야 한다. 성공 로그인에는 실패 지연을 적용하지 않으며 애플리케이션 수준 잠금은 두지 않는다.
잘못된 ID 또는 password의 각 제출은 처리 시작부터 응답까지 최소 1초가 걸려야 한다. 두 실패는 동일한 오류를 표시하고 cookie·DB·gateway·금융 상태를 변경하지 않는다. 성공에는 실패 지연을 적용하지 않으며 application lock은 두지 않는다.
**영향 범위**
### REQ-001.05 — Persistent session과 절대 만료
- 인터페이스: 로그인 제출과 오류 응답
- 영속성: 없음
- 후속 사용 주체: 미인증 Dashboard 사용자
- 마이그레이션: 없음
- 호환성: 없음
- 배포 전제 조건: 새 session·병렬 요청 우회를 제한할 별도 배포 경계 rate limiting 권고
정상 로그인도 24시간, 기본 미선택 `로그인 유지`를 선택한 로그인은 30일의 절대 만료를 사용한다. 발급 시 `iat`와 `exp`를 고정하고 page 이동, rerun, 활동, 새 tab, browser 재시작 또는 인증 복원으로 token이나 만료를 갱신하지 않는다. 같은 browser profile의 유효 cookie는 새 Streamlit session에서 인증을 복원한다.
### REQ-001.05 — Session과 로그아웃
### REQ-001.06 — Server-side adapter와 cookie
인증 성공 상태는 현재 Streamlit browser session에서만 유지해야 한다. 같은 session의 rerun은 재로그인 없이 Dashboard를 사용할 수 있고, 명시적 로그아웃은 즉시 인증 상태를 폐기해야 하며 새 session은 다시 인증해야 한다.
Streamlit 1.58의 ASGI `App` custom route를 사용해 동일 Dashboard process의 실제 HTTP response에서 cookie를 발급·삭제한다. JavaScript/localStorage/client-side cookie component로 token을 저장하지 않는다. Cookie는 `__Host-` 이름, `Secure`, `HttpOnly`, `SameSite=Lax`, `Path=/`, no `Domain`이며 `Max-Age`는 token TTL과 같다.
**영향 범위**
### REQ-001.07 — Token 서명·claim·rotation
- 인터페이스: `st.session_state`, 로그아웃 button
- 영속성: server-side 영속 session 없음
- 후속 사용 주체: 현재 browser session
- 마이그레이션: 없음
- 호환성: browser 재시작·새 session의 자동 로그인 없음
- 배포 전제 조건: 없음
Token payload는 `v`, 현재 관리자 ID `sub`, server `iat`, `exp`, 무작위 `jti`만 허용한다. Signing key는 독립 signing secret과 현재 관리자 password를 domain-separated HMAC-SHA256으로 결합해 파생하고 SHA-256 signer를 사용한다. Signature를 검증한 뒤 exact schema/type, subject, 허용 TTL, 미래 발급 시각과 server-side 만료를 검증한다. Password, signing secret 또는 관리자 ID 변경은 기존 token을 거부해야 한다.
### REQ-001.06 — 보호 범위와 기존 기능 호환성
### REQ-001.08 — Login/logout HTTP 계약
인증 성공 후 요약, 자산/마감, 거래내역, 확인필요 큐, 미분류, 분류 규칙, 카테고리, 계좌와 Import를 포함한 기존 Dashboard 페이지를 사용할 수 있어야 한다. 이 인증은 Dashboard만 보호하며 MCP endpoint가 인증됐다고 간주해서는 안 된다.
Login endpoint는 trusted ASGI request scheme과 Origin이 모두 same-origin HTTPS인 form POST만 받고 성공 시 hardened cookie와 root redirect를 반환한다. Logout endpoint도 같은 HTTPS 검증 후 동일 name/path/security 속성과 `Max-Age=0`으로 browser cookie를 삭제한다. Logout은 금융 DB를 변경하지 않는다. Stateless token 복사본은 개별 폐기하지 않으며 전체 강제 무효화는 password/signing-secret rotation으로 수행한다.
**영향 범위**
### REQ-001.09 — 보호 범위와 기존 기능 호환성
- 인터페이스: 모든 Dashboard page view
- 영속성: 기존 SQLite schema와 데이터 계약 유지
- 후속 사용 주체: 인증된 Dashboard 사용자
- 마이그레이션: 없음
- 호환성: 인증 후 기존 Dashboard 동작 유지
- 배포 전제 조건: MCP는 별도의 네트워크 또는 후속 인증 요구사항으로 보호
인증 후 기존 page 목록, `page` query parameter, sidebar 이동, gateway와 MCP 동작은 유지한다. 인증은 Dashboard만 보호하고 MCP endpoint 인증을 의미하지 않는다. 금융 SQLite schema/migration은 변경하지 않는다.
### REQ-001.07 — 배포 설정과 운영 제약
### REQ-001.10 — 배포·운영 제약
환경 예시, Compose와 운영 문서는 두 credential 환경변수의 이름·용도·fail-closed 동작을 정의하되 실제 값을 저장하지 않아야 한다. 운영 문서는 1초 지연에 애플리케이션 잠금이 없고 새 session·병렬 요청으로 우회 가능하므로 배포 경계 rate limiting이 필요함을 명시해야 한다.
`.env.example`, Compose와 Kubernetes는 세 secret setting 이름만 정의하고 실제 값을 저장하지 않는다. 공개 browser origin은 HTTPS여야 한다. TLS proxy를 사용하는 배포는 실제 proxy source IP/CIDR만 Uvicorn forwarded-header allowlist에 넣고 Host/Origin을 보존하며 `X-Forwarded-Proto: https`를 설정한다. 기본 allowlist는 loopback으로 fail-closed하고 wildcard trust를 금지한다. 최소 1초 delay는 병렬 brute-force를 막지 않으므로 외부 노출 경계에 rate limiting이 필요하다. 모든 verifier replica는 동일 secret과 token 규칙을 사용해야 한다.
**영향 범위**
### REQ-001.11 — 관찰 가능성
- 인터페이스: `.env`, Compose environment와 운영 문서
- 영속성: repository에 credential 값 저장 금지
- 후속 사용 주체: 배포 운영자
- 마이그레이션: 기존 Dashboard 배포에 secret 주입 필요
- 호환성: MCP container의 설정과 포트는 변경하지 않음
- 배포 전제 조건: secret manager 또는 동등한 안전한 환경변수 주입
Event log는 event name과 비민감 correlation ID만 기록한다. 허용 event에는 설정 오류, 인증 필요, token 수락, login 성공/실패/reject, logout이 포함된다. ID/password/secret/token/cookie/request body와 구체적인 검증 실패 원인은 기록하지 않는다.
### REQ-001.08 — 인증 관찰 가능성
## 6. 인터페이스 요약
설정 오류, 로그인 성공·실패와 로그아웃은 session-local correlation ID와 event 이름만 application log에 기록해야 한다. 관리자 ID, password, verifier, 제출 값과 query parameter를 진단 속성으로 기록하지 않아야 한다.
- `POST /api/auth/login`: `admin_id`, `admin_password`, optional `remember_me=on`
- `POST /api/auth/logout`: 입력 credential 없음
- Cookie: `__Host-jarca_dashboard_auth`
- 설정: 세 `JARCA_DASHBOARD_*` environment variable
- 상세 계약: `docs/interface-contract.md`
**영향 범위**
## 7. 오류 계약
- 인터페이스: application log
- 영속성: correlation ID는 현재 session에만 저장
- 후속 사용 주체: 운영자
- 마이그레이션: 없음
- 호환성: 기존 금융 데이터 log 계약에 영향 없음
- 배포 전제 조건: log 수집기가 민감정보 비수집 계약을 유지
| 조건 | 사용자 결과 | Cookie | DB/gateway/page |
|---|---|---|---|
| 설정 invalid | 일반 설정 오류 | 발급 없음 | 차단 |
| credential invalid | 최소 1초 후 일반 인증 오류 | 발급 없음 | 차단 |
| request malformed/cross-site | 400/403 | 발급 없음 | 차단 |
| cookie 없음·malformed·tampered·expired | 로그인 화면 | 수락 없음 | 차단 |
| subject/TTL/time/rotation 불일치 | 로그인 화면 | 수락 없음 | 차단 |
| logout | root redirect | deletion | 금융 상태 무변경 |
## 6. 제약과 불변조건
## 8. 수용 기준
- 인증 전과 설정 오류·잘못된 credential 상태에서는 금융 DB와 gateway 상태가 변하지 않는다.
- Credential은 환경변수에서만 읽고 repository 문서·설정·test fixture에는 test-only 값 외의 실제 값을 기록하지 않는다.
- 인증 상태는 boolean 결과만 저장하며 credential 원문 또는 verifier를 session에 보존하지 않는다.
- 잘못된 ID와 password는 동일한 사용자 오류와 최소 지연을 사용한다.
- Dashboard와 MCP의 인증 경계를 혼동하지 않는다.
- 실제 운영 DB 또는 실제 credential을 test에 사용하지 않는다.
- [x] AC-001 인증 전 모든 DB/gateway/page 기능이 차단된다.
- [x] AC-002 세 environment setting invalid 상태는 fail-closed한다.
- [x] AC-003 ID/password를 constant-time 비교하고 일반 오류와 최소 1초 지연을 적용한다.
- [x] AC-004 정상 login은 24시간 absolute token/cookie를 발급한다.
- [x] AC-005 기본 미선택 remember-me login은 30일 absolute token/cookie를 발급한다.
- [x] AC-006 Cookie는 `__Host-`, Secure, HttpOnly, SameSite=Lax, Path=/, no Domain이다.
- [x] AC-007 일반 요청·page 이동·복원은 token을 재발급하거나 만료를 연장하지 않는다.
- [x] AC-008 유효 cookie가 새 Streamlit session과 sidebar query page에서 인증을 복원한다.
- [x] AC-009 malformed, oversized, tampered, expired, schema-invalid token은 DB 전에 거부된다.
- [x] AC-010 password, signing secret 또는 관리자 ID rotation이 기존 token을 무효화한다.
- [x] AC-011 logout은 browser cookie를 삭제하고 금융 상태를 변경하지 않는다.
- [x] AC-012 credential, secret, token이 session state·URL·log·문서에 노출되지 않는다.
- [x] AC-013 same-origin HTTPS POST가 아닌 login/logout 요청은 거부된다.
- [x] AC-014 인증 후 기존 Dashboard page와 MCP behavior가 유지된다.
- [x] AC-015 실제 loopback HTTPS와 Streamlit WebSocket에서 cookie 발급·복원·삭제를 검증한다.
- [x] AC-016 금융 DB schema와 data model migration을 추가하지 않는다.
- [x] AC-017 HTTPS, rate-limit, rotation과 stateless logout 제약이 운영 문서에 기록된다.
## 7. 인수 기준
## 9. 검증 매핑
### REQ-001.01-AC01 — 미인증 진입 차단
- Credential/delay/log: `tests/test_dashboard_authentication.py`
- Token/TTL/tamper/rotation: `tests/test_dashboard_persistent_authentication.py`
- HTTP/cookie/logout: `tests/test_dashboard_auth_server.py`
- Streamlit gate/DB ordering/page 복원: `tests/test_dashboard_authentication_app.py`, `tests/test_dashboard_app.py`
- 실제 HTTPS/WebSocket evidence: `scripts/persistent_auth_http_evidence.py`, `docs/evidence/issue-6-persistent-auth-https.json`
- 전체 regression: `uv run --extra dashboard --extra dev pytest`
- 정적 검사: `uv run --extra dashboard --extra dev ruff check dashboard tests scripts`
**판정 기준**
## 10. 추적성
미인증 session은 로그인 화면만 표시하며 `get_db_path()`, page dispatch, DB·gateway·upload·rollback을 호출하지 않는다.
**검증**
`tests/test_dashboard_authentication_app.py`에서 인증 gate가 거부될 때 DB 경로 함수가 호출되지 않고 Dashboard 본문이 렌더링되지 않는지 검증한다.
**검증 분류**
- 수준: 통합
- 백엔드: Streamlit AppTest와 monkeypatch
- 케이스: 설정 누락, 미인증, 각 Dashboard query page
**상태 불변조건**
SQLite와 gateway 상태 무변경
### REQ-001.01-AC02 — Query parameter 우회 금지
**판정 기준**
`page=Import` 등 유효한 page query parameter를 제공해도 미인증 session은 해당 page를 렌더링하지 않는다.
**검증**
Streamlit AppTest로 Import query parameter를 설정하고 로그인 화면만 존재하는지 확인한다.
**검증 분류**
- 수준: 통합
- 백엔드: Streamlit AppTest
- 케이스: `page=Import`
**상태 불변조건**
Upload와 rollback 미호출
### REQ-001.02-AC01 — 환경변수 전용 설정
**판정 기준**
두 exact 환경변수가 있을 때만 설정을 생성하며 다른 이름이나 Streamlit secrets 값은 사용하지 않는다.
**검증**
`tests/test_dashboard_authentication.py`에서 환경 mapping과 무관한 secrets 형태 값을 함께 제공해도 exact 환경변수 값만 선택하는지 검증한다.
**검증 분류**
- 수준: 단위
- 백엔드: pytest
- 케이스: 두 값 존재, 대체 source 존재
**상태 불변조건**
Credential source 고정
### REQ-001.02-AC02 — 설정 오류 fail-closed
**판정 기준**
누락·빈 값·읽기 오류는 동일한 일반 설정 오류로 종료되고 Dashboard 기능을 노출하지 않는다.
**검증**
단위 test와 AppTest로 각 오류 조합과 로그인 input 미노출을 검증한다.
**검증 분류**
- 수준: 단위·통합
- 백엔드: pytest, Streamlit AppTest
- 케이스: ID 누락, password 누락, 빈 값, environment read error
**상태 불변조건**
기존 인증 boolean을 제거하고 DB 미호출
### REQ-001.03-AC01 — Constant-time 비교
**판정 기준**
ID와 password 검증이 모두 `hmac.compare_digest`를 호출하며 한 필드 실패로 다른 비교를 생략하지 않는다.
**검증**
비교 함수를 계측한 단위 test에서 성공·실패 모두 두 비교가 수행되는지 확인한다.
**검증 분류**
- 수준: 단위
- 백엔드: pytest monkeypatch
- 케이스: 정상 credential, 잘못된 두 필드
**상태 불변조건**
Password 일반 동등 비교 금지
### REQ-001.03-AC02 — Credential 비노출
**판정 기준**
로그인 완료 후 제출 ID와 password가 화면·log payload·query parameter 또는 session state에 남지 않는다.
**검증**
실패·성공 AppTest의 body와 session state 표현 및 log helper 단위 test를 검사한다.
**검증 분류**
- 수준: 단위·통합
- 백엔드: pytest, Streamlit AppTest
- 케이스: 성공, 잘못된 ID, 잘못된 password, 설정 오류
**상태 불변조건**
인증 boolean과 비민감 correlation ID만 session에 유지 가능
### REQ-001.04-AC01 — 실패별 최소 지연과 일반 오류
**판정 기준**
잘못된 ID와 잘못된 password 각각의 AppTest 제출은 1초 전에 완료되지 않고 같은 오류 문자열을 표시한다.
**검증**
두 실패 입력을 별도 제출하고 monotonic elapsed time과 화면 오류를 측정한다.
**검증 분류**
- 수준: 통합
- 백엔드: Streamlit AppTest, monotonic clock
- 케이스: 잘못된 ID, 잘못된 password
**상태 불변조건**
인증 false, DB·gateway 무변경
### REQ-001.04-AC02 — 성공 지연·잠금 없음
**판정 기준**
정상 credential은 실패 지연 함수를 사용하지 않고 인증되며, 실패 횟수나 잠금 만료 상태를 생성하지 않는다.
**검증**
단위 test에서 실패 지연의 remaining-time 계산을 검증하고 source·session state 검사에서 failure counter·lock state가 없음을 확인한다.
**검증 분류**
- 수준: 단위·정적
- 백엔드: pytest, source inspection
- 케이스: 정상 credential, 반복 실패 후 재시도
**상태 불변조건**
애플리케이션 잠금 없음
### REQ-001.05-AC01 — Session 유지와 새 session 재인증
**판정 기준**
로그인한 AppTest session은 rerun 후 기존 page를 유지하지만 새 AppTest session은 로그인 화면부터 시작한다.
**검증**
한 session의 로그인·rerun과 별도 session 생성을 연속 검증한다.
**검증 분류**
- 수준: 통합
- 백엔드: Streamlit AppTest
- 케이스: 동일 session rerun, 새 session
**상태 불변조건**
Session 간 인증 상태 격리
### REQ-001.05-AC02 — 로그아웃
**판정 기준**
로그아웃 click 직후 인증 boolean이 폐기되고 로그인 화면으로 돌아가며 Dashboard page가 표시되지 않는다.
**검증**
인증된 AppTest에서 로그아웃 button을 click하고 화면 전이를 확인한다.
**검증 분류**
- 수준: 통합
- 백엔드: Streamlit AppTest
- 케이스: 인증 후 로그아웃
**상태 불변조건**
인증 상태 폐기, 금융 데이터 무변경
### REQ-001.06-AC01 — 인증 후 기존 페이지 호환성
**판정 기준**
Synthetic SQLite fixture와 test-only credential로 인증한 뒤 기존 핵심 페이지가 예외 없이 예상 내용을 렌더링한다.
**검증**
`tests/test_dashboard_app.py`에서 요약, 자산/마감, 분류 규칙, 확인필요 큐와 월정산을 인증 후 검증한다.
**검증 분류**
- 수준: 회귀 통합
- 백엔드: Streamlit AppTest, synthetic SQLite
- 케이스: 기존 핵심 page 목록
**상태 불변조건**
기존 SQLite schema와 gateway contract 유지
### REQ-001.06-AC02 — MCP 범위 분리
**판정 기준**
Dashboard 인증 구현이 MCP server 진입점·transport·port를 변경하지 않고 운영 문서가 MCP 미보호 경계를 명시한다.
**검증**
Base-to-worktree diff와 README를 검사한다.
**검증 분류**
- 수준: 정적
- 백엔드: Git diff, 문서 검사
- 케이스: MCP source·Compose service
**상태 불변조건**
MCP 인증 상태 변경 없음
### REQ-001.07-AC01 — Secret 주입 계약
**판정 기준**
`.env.example`과 Dashboard Compose service에 두 exact 변수 이름이 있고 repository에는 실제 값이 없다.
**검증**
설정 파일 parsing과 base-to-worktree secret scan을 수행한다.
**검증 분류**
- 수준: 정적
- 백엔드: Python·YAML text validation
- 케이스: 환경 예시, Compose Dashboard environment
**상태 불변조건**
실제 credential 비보존
### REQ-001.07-AC02 — Rate limiting 운영 문서
**판정 기준**
README가 최소 1초 실패 지연, 애플리케이션 잠금 부재, 새 session·병렬 요청 우회와 배포 경계 rate limiting 필요성을 함께 설명한다.
**검증**
README 필수 문구 정적 검사를 수행한다.
**검증 분류**
- 수준: 문서
- 백엔드: text validation
- 케이스: Dashboard 운영 안내
**상태 불변조건**
애플리케이션 내부 잠금 추가 금지
### REQ-001.08-AC01 — 비민감 인증 event
**판정 기준**
설정 오류, 성공, 실패와 로그아웃 event가 correlation ID와 함께 기록되고 credential은 log argument에 포함되지 않는다.
**검증**
Logger를 계측한 단위 test와 source 검사를 수행한다.
**검증 분류**
- 수준: 단위·정적
- 백엔드: pytest monkeypatch
- 케이스: 인증 event logging
**상태 불변조건**
Log에 관리자 ID·password·verifier 없음
## 8. 오류 및 충돌 계약
- 설정 오류: `관리자 인증 설정을 확인할 수 없습니다.`만 표시하고 로그인 form과 Dashboard를 노출하지 않는다.
- Credential 오류: ID와 password 오류를 구분하지 않는 일반 오류를 최소 1초 후 표시한다.
- Issue 또는 base drift: 구현 workflow에서 자동 merge·rebase하지 않고 중단한다.
- 중복 로그인 제출: 각 제출을 독립적으로 검증하며 성공 후 현재 session의 인증 boolean만 유지한다.
- 로그아웃 중복 요청: 인증 상태가 없으면 로그인 화면을 유지하고 금융 상태를 변경하지 않는다.
## 9. 데이터 및 인터페이스 계약
- 입력: 환경변수 두 개, 로그인 form의 ID·password, 로그아웃 button
- 출력: 로그인 화면, 일반 설정/credential 오류, 인증된 기존 Dashboard, session-local 인증 boolean
- 비민감 session 데이터: 인증 boolean, login-error boolean, correlation ID
- 영속 데이터: 인증 기능은 새 DB table, column, migration 또는 credential store를 만들지 않는다.
- 비교: ID와 password 모두 `hmac.compare_digest`를 사용하며 두 비교를 수행한다.
## 10. 하위 호환성
인증 후 기존 Dashboard page query parameter, SQLite schema, gateway와 화면 기능을 유지한다. Dashboard는 새 credential 설정 없이는 의도적으로 fail-closed한다. MCP server API, transport와 인증 상태는 변경하지 않는다.
## 11. 추적성
- 근거 이슈: #1
- 구현 PR: #3
- 관련 설계 문서: docs/interface-contract.md, docs/project-structure.md, docs/runtime-flow.md
- 관련 요구사항: 없음
- 최초 요구 Issue: `jsfamily/jarca-ledger-mcp#1`
- 지속 로그인 변경 Issue: `jsfamily/jarca-ledger-mcp#6`
- 최초 구현 PR: `#3`
- Issue #6 구현 PR: 미정
- Issue #6 기준 revision: `7bea8b368e02e98afd4b2dc463fb270b0e6eb297`
- Issue #6 승인 snapshot body SHA-256: `4f734169ec896747fe39fd7f3ab75d1fc0d8cdd4903045e665beba40d80eb1fe`