# Interface Contract — Dashboard 관리자 인증 ## 목적과 범위 Dashboard의 인증 설정, server-side login/logout HTTP endpoint, signed cookie와 Streamlit 인증 경계 계약을 정의한다. - 포함: 관리자 credential, signing secret, login/logout POST, token/cookie, fail-closed page gate - 제외: MCP 인증, OAuth/OIDC, 다중 사용자, token registry, 외부 인증 middleware - 공개 origin은 HTTPS여야 한다. ## 인터페이스 목록 | 인터페이스 | 종류 | 호출 주체 | 제공 주체 | 버전 | 상태 | |---|---|---|---|---|---| | `dashboard-auth-config` | environment | 배포 시스템 | `dashboard.authentication` | 2.0 | active | | `POST /api/auth/login` | HTTPS | browser form | `dashboard.auth_server` | 1.0 | active | | `POST /api/auth/logout` | HTTPS | browser form | `dashboard.auth_server` | 1.0 | active | | `dashboard-auth-cookie` | Secure cookie | browser/Streamlit handshake | `dashboard.auth_server` | 1.0 | active | | `dashboard-auth-gate` | Python/Streamlit | `dashboard.app` | `dashboard.authentication` | 2.0 | active | ## 상세 계약 ### dashboard-auth-config | 이름 | 타입 | 필수 | 제약 | 민감정보 | |---|---|---|---|---| | `JARCA_DASHBOARD_ADMIN_ID` | string | 예 | non-empty | 예 | | `JARCA_DASHBOARD_ADMIN_PASSWORD` | string | 예 | non-empty | 예 | | `JARCA_DASHBOARD_SIGNING_SECRET` | string | 예 | UTF-8 32 byte 이상 | 예 | 누락·빈 값·비문자열·read error·짧은 signing secret은 일반 설정 오류로 fail-closed한다. Secret 원문이나 read-error 원인은 사용자 응답과 log에 포함하지 않는다. ### POST /api/auth/login - Content-Type: `application/x-www-form-urlencoded` - Transport: trusted ASGI scope의 request scheme이 `https`여야 함 - Origin: request host·effective port와 같은 HTTPS origin만 허용 - 최대 body: 4096 bytes - 입력: | 필드 | 타입 | 필수 | 의미 | |---|---|---|---| | `admin_id` | string | 예 | 제출 관리자 ID | | `admin_password` | string | 예 | 제출 password | | `remember_me` | string | 아니요 | 값이 `on`이면 30일, 아니면 24시간 | - 성공: `303 Location: /`, hardened cookie 1개 발급 - credential 실패: 최소 1초 후 `303 Location: /?auth_error=invalid`, cookie 없음 - 설정 실패: `303 Location: /?auth_error=unavailable`, cookie 없음 - origin 실패: `403`, cookie 없음 - plain HTTP 또는 신뢰되지 않은 forwarded scheme: `403`, cookie 없음 - malformed/oversized request: `400`, cookie 없음 - ID와 password는 모두 `hmac.compare_digest`로 비교한다. ### dashboard-auth-cookie | 속성 | 값 | |---|---| | 이름 | `__Host-jarca_dashboard_auth` | | `Secure` | true | | `HttpOnly` | true | | `SameSite` | `Lax` | | `Path` | `/` | | `Domain` | 없음 | | `Max-Age` | 86400 또는 2592000 | Token payload는 `v`, `sub`, `iat`, `exp`, `jti`만 허용한다. 서명 key는 signing secret과 현재 admin password를 domain-separated HMAC-SHA256으로 결합해 파생하며 `itsdangerous.URLSafeSerializer` SHA-256 signer를 사용한다. Token은 일반 요청에서 갱신하지 않는다. ### POST /api/auth/logout - Same-origin HTTPS POST만 허용한다. - 성공: `303 Location: /`과 동일 name/path/security 속성의 `Max-Age=0` cookie deletion - 금융 상태와 DB는 변경하지 않는다. - Stateless token 복사본은 개별 폐기하지 않는다. 강제 무효화는 password/signing-secret rotation으로 수행한다. ### dashboard-auth-gate `dashboard.app.main()`은 page config 직후 gate를 호출한다. Gate는 현재 설정으로 cookie signature와 strict claims를 검증한다. 유효할 때만 DB path, gateway와 page dispatch를 실행한다. Token, credential, signing material은 `st.session_state`에 저장하지 않는다. 비민감 correlation ID만 session state에 둘 수 있다. ## 호환성·순서·재시도 - 기존 `page` query parameter와 page dispatch는 유효 인증 이후 그대로 유지한다. - REQ-001 revision 2.0이 revision 1.0의 session-only boolean 계약을 signed-cookie 계약으로 대체한다. - 설정 판정 → cookie 검증 → DB path → page dispatch 순서를 유지한다. - password, signing secret 또는 관리자 ID 변경은 기존 token을 거부한다. - 반복 실패는 application lock을 만들지 않으며 각 credential 실패에 최소 1초 지연을 적용한다. ## 예시 ```text POST /api/auth/login (same-origin HTTPS) admin_id=&admin_password=&remember_me=on → 303 / → Set-Cookie: __Host-jarca_dashboard_auth=; Secure; HttpOnly; SameSite=Lax; Path=/; Max-Age=2592000 ``` ## 검증 - Token: `tests/test_dashboard_persistent_authentication.py` - HTTP endpoint/cookie: `tests/test_dashboard_auth_server.py` - Gate/DB ordering: `tests/test_dashboard_authentication_app.py` - HTTPS attributes/reconnect/logout: `scripts/persistent_auth_http_evidence.py` - 실제 secret/token 값은 test output과 evidence에 남기지 않는다.