Shahah / auth.py
Kai72828's picture
Upload 8 files
ac6c361 verified
Raw
History Blame
7.01 kB
"""
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
# ─────────────────────────────────────────────────────────────────────────────
# Credential helpers
# ─────────────────────────────────────────────────────────────────────────────
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
# ─────────────────────────────────────────────────────────────────────────────
# Session helpers
# ─────────────────────────────────────────────────────────────────────────────
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]
# ─────────────────────────────────────────────────────────────────────────────
# Login page UI
# ─────────────────────────────────────────────────────────────────────────────
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)