Spaces:
Running
Running
Download server/auth.py from P01yH3dr0n/client: direct link, hf CLI and curl.
- Browser
- Download file 4.99 kB
-
https://huggingface.co/spaces/P01yH3dr0n/client/resolve/main/server/auth.py
- Command line
-
hf download hf://spaces/P01yH3dr0n/client/server/auth.py
-
curl -L -o auth.py https://huggingface.co/spaces/P01yH3dr0n/client/resolve/main/server/auth.py
4.99 kB
| """Identity-bound sessions without IP restrictions.""" | |
| import os | |
| import secrets | |
| import time | |
| from server.users import users | |
| _sessions = {} | |
| SESSION_COOKIE = "session" | |
| SESSION_MAX_AGE = 86400 * 30 | |
| def get_credentials(): | |
| return os.getenv("USERNAME", ""), os.getenv("PASSWORD", "") | |
| def auth_enabled(): | |
| return True | |
| def verify_credentials(username, password): | |
| name, secret = get_credentials() | |
| if name and secret and secrets.compare_digest(username.encode(), name.encode()) and secrets.compare_digest(password.encode(), secret.encode()): | |
| return {"id": "admin", "username": name, "admin": True} | |
| return users.authenticate(username, password) | |
| def create_session(identity): | |
| now = time.time() | |
| for token, row in list(_sessions.items()): | |
| if row["expiry"] < now: | |
| _sessions.pop(token, None) | |
| token = secrets.token_urlsafe(32) | |
| _sessions[token] = {**identity, "expiry": now + SESSION_MAX_AGE} | |
| return token | |
| def session_identity(token): | |
| row = _sessions.get(token) | |
| if not row or row["expiry"] < time.time(): | |
| _sessions.pop(token, None) | |
| return None | |
| if not row["admin"] and row["id"] not in users.records: | |
| return None | |
| return row | |
| def validate_session(token): | |
| return session_identity(token) is not None | |
| def revoke_user(uid): | |
| for token, row in list(_sessions.items()): | |
| if row["id"] == uid: | |
| _sessions.pop(token, None) | |
| def is_authenticated(request): | |
| return session_identity(request.cookies.get(SESSION_COOKIE, "")) is not None | |
| def login_page_html(error: str = "") -> str: | |
| error_html = f'<div class="login-error">{error}</div>' if error else '' | |
| return f'''<!DOCTYPE html> | |
| <html lang="zh-CN"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>NAI Studio - 登录</title> | |
| <style> | |
| * {{ box-sizing: border-box; margin: 0; padding: 0; }} | |
| body {{ | |
| font-family: 'Segoe UI', 'Noto Sans SC', sans-serif; | |
| background: #1a1a2e; | |
| color: #e0e0e0; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| min-height: 100vh; | |
| }} | |
| .login-card {{ | |
| background: #16213e; | |
| border: 1px solid #2a3456; | |
| border-radius: 12px; | |
| padding: 40px 36px; | |
| width: 100%; | |
| max-width: 380px; | |
| box-shadow: 0 8px 32px rgba(0,0,0,0.4); | |
| }} | |
| .login-card h1 {{ | |
| text-align: center; | |
| font-size: 1.5rem; | |
| color: #6c63ff; | |
| margin-bottom: 8px; | |
| }} | |
| .login-card .subtitle {{ | |
| text-align: center; | |
| font-size: 0.8rem; | |
| color: #6b7394; | |
| margin-bottom: 28px; | |
| }} | |
| .login-field {{ | |
| margin-bottom: 16px; | |
| }} | |
| .login-field label {{ | |
| display: block; | |
| font-size: 0.8rem; | |
| color: #a0a8c0; | |
| margin-bottom: 6px; | |
| font-weight: 600; | |
| }} | |
| .login-field input {{ | |
| width: 100%; | |
| padding: 10px 14px; | |
| background: #1a2340; | |
| border: 1px solid #2a3456; | |
| border-radius: 8px; | |
| color: #e0e0e0; | |
| font-size: 0.95rem; | |
| outline: none; | |
| transition: border-color 0.2s; | |
| }} | |
| .login-field input:focus {{ | |
| border-color: #6c63ff; | |
| box-shadow: 0 0 0 2px rgba(108,99,255,0.3); | |
| }} | |
| .login-btn {{ | |
| width: 100%; | |
| padding: 12px; | |
| background: #6c63ff; | |
| color: #fff; | |
| border: none; | |
| border-radius: 8px; | |
| font-size: 1rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: background 0.2s; | |
| margin-top: 8px; | |
| }} | |
| .login-btn:hover {{ background: #5a52d5; }} | |
| .login-error {{ | |
| background: rgba(244,67,54,0.1); | |
| border: 1px solid rgba(244,67,54,0.3); | |
| color: #f44336; | |
| padding: 8px 12px; | |
| border-radius: 6px; | |
| font-size: 0.85rem; | |
| margin-bottom: 16px; | |
| text-align: center; | |
| }} | |
| </style> | |
| </head> | |
| <body> | |
| <div class="login-card"> | |
| <h1>NAI Studio</h1> | |
| <div class="subtitle">请登录以继续</div> | |
| {error_html} | |
| <form method="POST" action="/login"> | |
| <div class="login-field"> | |
| <label for="username">用户名</label> | |
| <input type="text" id="username" name="username" required autofocus> | |
| </div> | |
| <div class="login-field"> | |
| <label for="password">密码</label> | |
| <input type="password" id="password" name="password" required> | |
| </div> | |
| <button type="submit" class="login-btn">登录</button> | |
| </form> | |
| </div> | |
| </body> | |
| </html>''' |