| """ |
| auth.py β Authentication & Session Validation |
| Credentials via HF Secrets (env vars in Docker). |
| |
| Set in HF Space Secrets: |
| SUBTITLE_USER β login username |
| SUBTITLE_PASS β login password |
| """ |
|
|
| import os |
| import hmac |
| import hashlib |
| import streamlit as st |
|
|
|
|
| |
| |
| |
|
|
| def _get_credentials() -> tuple[str, str]: |
| """Read from HF Secrets β env vars. Local fallback for dev.""" |
| username = os.environ.get("SUBTITLE_USER", "admin") |
| password = os.environ.get("SUBTITLE_PASS", "subtitle2024") |
| return username, password |
|
|
|
|
| def _sha256(value: str) -> str: |
| return hashlib.sha256(value.encode("utf-8")).hexdigest() |
|
|
|
|
| def verify_credentials(username: str, password: str) -> bool: |
| """Constant-time comparison to prevent timing attacks.""" |
| valid_user, valid_pass = _get_credentials() |
| user_ok = hmac.compare_digest(_sha256(username), _sha256(valid_user)) |
| pass_ok = hmac.compare_digest(_sha256(password), _sha256(valid_pass)) |
| return user_ok and pass_ok |
|
|
|
|
| |
| |
| |
|
|
| def is_authenticated() -> bool: |
| return st.session_state.get("authenticated", False) |
|
|
|
|
| def login(username: str, password: str) -> bool: |
| if verify_credentials(username, password): |
| st.session_state["authenticated"] = True |
| st.session_state["current_user"] = username |
| return True |
| return False |
|
|
|
|
| def logout() -> None: |
| """Wipe entire session β returns user to login screen.""" |
| for key in list(st.session_state.keys()): |
| del st.session_state[key] |
|
|
|
|
| |
| |
| |
|
|
| def render_login_page() -> None: |
| st.markdown(""" |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=DM+Mono:wght@400;500&family=DM+Sans:wght@300;400;500;600&display=swap'); |
| |
| html, body, [data-testid="stAppViewContainer"] { |
| background: #0d0f14 !important; |
| } |
| [data-testid="stHeader"], [data-testid="stToolbar"], |
| footer, #MainMenu { display: none !important; } |
| |
| .login-logo { |
| font-family: 'DM Serif Display', serif; |
| font-size: 2.3rem; color: #f5f0e8; |
| text-align: center; letter-spacing: -0.02em; |
| } |
| .login-sub { |
| font-family: 'DM Mono', monospace; |
| font-size: 0.7rem; color: #4b5563; |
| text-align: center; letter-spacing: 0.18em; |
| text-transform: uppercase; margin-bottom: 0.6rem; |
| } |
| .accent-bar { |
| width: 48px; height: 3px; |
| background: linear-gradient(90deg, #e8c97e, #c9a84c); |
| border-radius: 2px; margin: 0 auto 2rem; |
| } |
| div[data-testid="stTextInput"] label { |
| font-family: 'DM Mono', monospace !important; |
| font-size: 0.72rem !important; color: #6b7280 !important; |
| letter-spacing: 0.1em !important; text-transform: uppercase !important; |
| } |
| div[data-testid="stTextInput"] input { |
| background: #0d0f14 !important; |
| border: 1px solid #1e2330 !important; |
| border-radius: 8px !important; color: #f5f0e8 !important; |
| font-family: 'DM Mono', monospace !important; font-size: 0.9rem !important; |
| } |
| div[data-testid="stTextInput"] input:focus { |
| border-color: #e8c97e !important; |
| box-shadow: 0 0 0 3px rgba(232,201,126,0.1) !important; |
| } |
| .stButton > button { |
| background: linear-gradient(135deg, #e8c97e, #c9a84c) !important; |
| color: #0d0f14 !important; |
| font-family: 'DM Sans', sans-serif !important; |
| font-weight: 600 !important; border: none !important; |
| border-radius: 8px !important; padding: 0.65rem 1.4rem !important; |
| font-size: 0.92rem !important; margin-top: 0.4rem !important; |
| } |
| .login-err { |
| background: rgba(239,68,68,0.1); |
| border: 1px solid rgba(239,68,68,0.25); |
| border-radius: 8px; color: #fca5a5; |
| font-family: 'DM Sans', sans-serif; |
| font-size: 0.82rem; padding: 0.55rem 0.9rem; |
| text-align: center; margin-top: 0.5rem; |
| } |
| .login-footer { |
| font-family: 'DM Mono', monospace; |
| font-size: 0.62rem; color: #1f2937; |
| text-align: center; margin-top: 1.8rem; letter-spacing: 0.1em; |
| } |
| </style> |
| """, unsafe_allow_html=True) |
|
|
| _, col, _ = st.columns([1, 1.6, 1]) |
| with col: |
| st.markdown('<div style="height:10vh"></div>', unsafe_allow_html=True) |
| st.markdown('<div class="login-logo">SubSync</div>', unsafe_allow_html=True) |
| st.markdown('<div class="login-sub">Myanmar Subtitle Translator</div>', unsafe_allow_html=True) |
| st.markdown('<div class="accent-bar"></div>', unsafe_allow_html=True) |
|
|
| with st.container(border=True): |
| username = st.text_input("Username", key="li_user", placeholder="username") |
| password = st.text_input("Password", type="password", |
| key="li_pass", placeholder="β’β’β’β’β’β’β’β’") |
|
|
| if st.button("Sign In β", key="li_btn", use_container_width=True): |
| if not username or not password: |
| st.markdown( |
| '<div class="login-err">β Username ααΎαα·αΊ Password ααα·αΊαα«</div>', |
| unsafe_allow_html=True) |
| elif login(username, password): |
| st.rerun() |
| else: |
| st.markdown( |
| '<div class="login-err">β Username ααα―α· Password ααΎα¬αΈαααΊ</div>', |
| unsafe_allow_html=True) |
|
|
| st.markdown( |
| '<div class="login-footer">PROTECTED Β· AUTHORIZED ACCESS ONLY</div>', |
| unsafe_allow_html=True) |
|
|