11 KiB
schema, id, title, revision, status, depends_on, applies_to, language
| schema | id | title | revision | status | depends_on | applies_to | language | |
|---|---|---|---|---|---|---|---|---|
| gitea-agent.requirement/v1 | REQ-001 | Dashboard 관리자 인증 | 2.0 | approved |
|
ko |
REQ-001 — Dashboard 관리자 인증
1. 목적
Streamlit Dashboard의 모든 금융 데이터 조회·수정·import 기능 앞에 단일 관리자 인증 경계를 두고, 인증되지 않았거나 인증 설정이 유효하지 않은 요청을 fail-closed한다. 인증 성공 후에는 server-signed Secure/HttpOnly cookie로 같은 browser profile의 새 연결과 새 session에서 인증을 정해진 절대 만료까지 복원한다.
2. 배경
Revision 1.0은 Issue #1에서 단일 관리자 credential과 browser-session-only 인증 경계를 도입했다. Issue #6은 sidebar query-link 이동과 browser 재접속에서 반복 로그인이 발생하는 문제를 해결하기 위해 동일한 인증 기능의 session·logout·배포 계약을 persistent cookie 방식으로 확장한다. 새 기능 ID가 아니라 본 canonical 요구사항의 revision으로 관리한다.
3. 범위
포함
- 모든 Dashboard 페이지 앞의 공통 관리자 인증과 인증 전 DB 차단
- 환경변수 기반 관리자 ID/password 및 독립적인 signing secret
- constant-time credential 비교, 일반 오류, 실패당 최소 1초 지연
- 동일 Dashboard ASGI process의 server-side login/logout HTTP endpoint
- server-signed
Secure/HttpOnlycookie 발급·검증·삭제 - 기본 24시간, 선택 시 30일인 로그인 성공 시각 기준 절대 만료
- sidebar 페이지 이동, 새 탭, browser 종료 후 재접속과 새 Streamlit session 복원
- password/signing-secret/admin-ID rotation에 따른 기존 token 무효화
- credential·secret·token 비노출과 인증 event 관찰 가능성
- Compose, Kubernetes Secret, HTTPS와 운영 제약 문서화
제외
- 다중 사용자, 회원가입, 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. 용어와 기준
- 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 path 취득, page dispatch, gateway 호출, upload, rollback 또는 DB-backed 렌더링보다 먼저 현재 설정과 signed cookie를 검증해야 한다. 기존 session boolean만으로 만료·rotation 검증을 우회해서는 안 된다. 실패 시 로그인 화면만 렌더링한다.
REQ-001.02 — 설정과 fail-closed
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는 모두 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초가 걸려야 한다. 두 실패는 동일한 오류를 표시하고 cookie·DB·gateway·금융 상태를 변경하지 않는다. 성공에는 실패 지연을 적용하지 않으며 application lock은 두지 않는다.
REQ-001.05 — Persistent session과 절대 만료
정상 로그인도 24시간, 기본 미선택 로그인 유지를 선택한 로그인은 30일의 절대 만료를 사용한다. 발급 시 iat와 exp를 고정하고 page 이동, rerun, 활동, 새 tab, browser 재시작 또는 인증 복원으로 token이나 만료를 갱신하지 않는다. 같은 browser profile의 유효 cookie는 새 Streamlit session에서 인증을 복원한다.
REQ-001.06 — Server-side adapter와 cookie
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
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.08 — Login/logout HTTP 계약
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 — 보호 범위와 기존 기능 호환성
인증 후 기존 page 목록, page query parameter, sidebar 이동, gateway와 MCP 동작은 유지한다. 인증은 Dashboard만 보호하고 MCP endpoint 인증을 의미하지 않는다. 금융 SQLite schema/migration은 변경하지 않는다.
REQ-001.10 — 배포·운영 제약
.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 — 관찰 가능성
Event log는 event name과 비민감 correlation ID만 기록한다. 허용 event에는 설정 오류, 인증 필요, token 수락, login 성공/실패/reject, logout이 포함된다. ID/password/secret/token/cookie/request body와 구체적인 검증 실패 원인은 기록하지 않는다.
6. 인터페이스 요약
POST /api/auth/login:admin_id,admin_password, optionalremember_me=onPOST /api/auth/logout: 입력 credential 없음- Cookie:
__Host-jarca_dashboard_auth - 설정: 세
JARCA_DASHBOARD_*environment variable - 상세 계약:
docs/interface-contract.md
7. 오류 계약
| 조건 | 사용자 결과 | 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 | 금융 상태 무변경 |
8. 수용 기준
- AC-001 인증 전 모든 DB/gateway/page 기능이 차단된다.
- AC-002 세 environment setting invalid 상태는 fail-closed한다.
- AC-003 ID/password를 constant-time 비교하고 일반 오류와 최소 1초 지연을 적용한다.
- AC-004 정상 login은 24시간 absolute token/cookie를 발급한다.
- AC-005 기본 미선택 remember-me login은 30일 absolute token/cookie를 발급한다.
- AC-006 Cookie는
__Host-, Secure, HttpOnly, SameSite=Lax, Path=/, no Domain이다. - AC-007 일반 요청·page 이동·복원은 token을 재발급하거나 만료를 연장하지 않는다.
- AC-008 유효 cookie가 새 Streamlit session과 sidebar query page에서 인증을 복원한다.
- AC-009 malformed, oversized, tampered, expired, schema-invalid token은 DB 전에 거부된다.
- AC-010 password, signing secret 또는 관리자 ID rotation이 기존 token을 무효화한다.
- AC-011 logout은 browser cookie를 삭제하고 금융 상태를 변경하지 않는다.
- AC-012 credential, secret, token이 session state·URL·log·문서에 노출되지 않는다.
- AC-013 same-origin HTTPS POST가 아닌 login/logout 요청은 거부된다.
- AC-014 인증 후 기존 Dashboard page와 MCP behavior가 유지된다.
- AC-015 실제 loopback HTTPS와 Streamlit WebSocket에서 cookie 발급·복원·삭제를 검증한다.
- AC-016 금융 DB schema와 data model migration을 추가하지 않는다.
- AC-017 HTTPS, rate-limit, rotation과 stateless logout 제약이 운영 문서에 기록된다.
9. 검증 매핑
- 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. 추적성
- 최초 요구 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