fix(dashboard): address persistent auth review

This commit is contained in:
2026-10-02 03:02:23 +00:00
parent 91e106d75c
commit 7bb959859a
11 changed files with 885 additions and 347 deletions
+14 -11
View File
@@ -31,6 +31,7 @@ _TOKEN_VERSION = 1
_TOKEN_SALT = "jarca-dashboard-persistent-auth-v1"
_TOKEN_CONTEXT = b"jarca-dashboard-persistent-auth-v1\x00"
_TOKEN_FIELDS = frozenset({"v", "sub", "iat", "exp", "jti"})
_TOKEN_SUBJECT = "dashboard-admin"
@dataclass(frozen=True)
@@ -149,7 +150,7 @@ def log_auth_event(event: str, correlation_id: str) -> None:
def _derived_signing_key(settings: AuthSettings) -> bytes:
return hmac.new(
settings.signing_secret.encode("utf-8"),
_TOKEN_CONTEXT + settings.admin_password.encode("utf-8"),
_TOKEN_CONTEXT + settings.admin_id.encode("utf-8") + b"\x00" + settings.admin_password.encode("utf-8"),
hashlib.sha256,
).digest()
@@ -177,7 +178,7 @@ def issue_persistent_token(
jti = secrets.token_urlsafe(16) if token_id is None else token_id
payload = {
"v": _TOKEN_VERSION,
"sub": settings.admin_id,
"sub": _TOKEN_SUBJECT,
"iat": issued_at,
"exp": expires_at,
"jti": jti,
@@ -215,8 +216,7 @@ def verify_persistent_token(
if (
type(version) is not int
or version != _TOKEN_VERSION
or not isinstance(subject, str)
or not subject
or subject != _TOKEN_SUBJECT
or type(issued_at) is not int
or type(expires_at) is not int
or not isinstance(token_id, str)
@@ -224,9 +224,6 @@ def verify_persistent_token(
or len(token_id) > 256
):
return None
if not hmac.compare_digest(subject, settings.admin_id):
return None
current_time = int(time.time() if now is None else now)
ttl = expires_at - issued_at
if ttl not in {DEFAULT_TOKEN_TTL_SECONDS, REMEMBER_ME_TOKEN_TTL_SECONDS}:
@@ -258,7 +255,7 @@ def _query_error() -> str | None:
return value if isinstance(value, str) else None
def _render_login() -> None:
def _render_login(*, clear_cookie: bool = False) -> None:
st.title("관리자 로그인")
st.caption("Dashboard를 사용하려면 관리자 자격 증명이 필요합니다.")
if _query_error() == "invalid":
@@ -278,6 +275,11 @@ def _render_login() -> None:
""",
unsafe_allow_html=True,
)
if clear_cookie:
st.markdown(
"<meta http-equiv=\"refresh\" content=\"0;url=/api/auth/cleanup\">",
unsafe_allow_html=True,
)
def _render_logout() -> None:
@@ -295,17 +297,18 @@ def _render_logout() -> None:
def require_dashboard_authentication() -> bool:
"""Validate the server-signed cookie before allowing any dashboard access."""
correlation_id = get_auth_correlation_id(st.session_state)
cookie = _request_cookie()
settings = load_auth_settings()
if settings is None:
log_auth_event("configuration_error", correlation_id)
_render_login()
_render_login(clear_cookie=cookie is not None)
st.error(CONFIGURATION_ERROR_MESSAGE)
return False
verified = verify_persistent_token(settings, _request_cookie())
verified = verify_persistent_token(settings, cookie)
if verified is None:
log_auth_event("authentication_required", correlation_id)
_render_login()
_render_login(clear_cookie=cookie is not None)
return False
log_auth_event("token_accepted", correlation_id)