Download auth.py from RRC-ARG/scouting: direct link, hf CLI and curl.
- Browser
- Download file 3.34 kB
-
https://huggingface.co/spaces/RRC-ARG/scouting/resolve/main/auth.py
- Command line
-
hf download hf://spaces/RRC-ARG/scouting/auth.py
-
curl -L -o auth.py https://huggingface.co/spaces/RRC-ARG/scouting/resolve/main/auth.py
3.34 kB
| """ | |
| Shared-password auth via signed cookie. | |
| Set APP_PASSWORD in env. SESSION_SECRET signs the cookie; falls back to a derived value. | |
| """ | |
| import hashlib | |
| import os | |
| import secrets | |
| from fastapi import FastAPI, Request, Response | |
| from fastapi.responses import JSONResponse | |
| from itsdangerous import BadSignature, URLSafeTimedSerializer | |
| from starlette.middleware.base import BaseHTTPMiddleware | |
| APP_PASSWORD = os.environ.get('APP_PASSWORD', '') | |
| SESSION_SECRET = os.environ.get( | |
| 'SESSION_SECRET', | |
| hashlib.sha256(('racing-' + APP_PASSWORD).encode()).hexdigest() if APP_PASSWORD else 'dev-only-secret', | |
| ) | |
| COOKIE_NAME = 'auth' | |
| COOKIE_MAX_AGE = 60 * 60 * 24 * 30 # 30 days | |
| # paths that bypass auth | |
| OPEN_PATHS = {'/api/login', '/api/logout', '/api/auth-status', | |
| '/healthz', '/api/docs', '/openapi.json'} | |
| OPEN_PREFIXES = ('/static/',) | |
| _serializer = URLSafeTimedSerializer(SESSION_SECRET, salt='auth-cookie') | |
| def _sign(): | |
| return _serializer.dumps('ok') | |
| def _verify(token): | |
| try: | |
| _serializer.loads(token, max_age=COOKIE_MAX_AGE) | |
| return True | |
| except (BadSignature, Exception): | |
| return False | |
| class AuthMiddleware(BaseHTTPMiddleware): | |
| async def dispatch(self, request: Request, call_next): | |
| path = request.url.path | |
| # always allow open paths and the index page (frontend self-renders login modal on 401) | |
| if path == '/' or path in OPEN_PATHS or any(path.startswith(p) for p in OPEN_PREFIXES): | |
| return await call_next(request) | |
| # allow if no password configured (dev mode) | |
| if not APP_PASSWORD: | |
| return await call_next(request) | |
| token = request.cookies.get(COOKIE_NAME) | |
| if token and _verify(token): | |
| return await call_next(request) | |
| return JSONResponse(status_code=401, content={'detail': 'unauthorized'}) | |
| def register_auth_routes(app: FastAPI): | |
| async def login(request: Request): | |
| body = await request.json() | |
| password = body.get('password', '') | |
| if not APP_PASSWORD: | |
| # dev mode — accept any password and set cookie so the frontend doesn't loop | |
| response = JSONResponse({'ok': True, 'mode': 'dev'}) | |
| response.set_cookie(COOKIE_NAME, _sign(), max_age=COOKIE_MAX_AGE, | |
| httponly=True, samesite='lax', secure=False) | |
| return response | |
| if not secrets.compare_digest(password, APP_PASSWORD): | |
| return JSONResponse(status_code=401, content={'detail': 'wrong password'}) | |
| response = JSONResponse({'ok': True}) | |
| # SameSite=None + Secure required so the cookie survives the HF iframe (cross-site). | |
| response.set_cookie(COOKIE_NAME, _sign(), max_age=COOKIE_MAX_AGE, | |
| httponly=True, samesite='none', secure=True) | |
| return response | |
| async def logout(response: Response): | |
| response = JSONResponse({'ok': True}) | |
| response.delete_cookie(COOKIE_NAME) | |
| return response | |
| async def auth_status(request: Request): | |
| if not APP_PASSWORD: | |
| return {'auth_required': False} | |
| token = request.cookies.get(COOKIE_NAME) | |
| return {'auth_required': True, 'authenticated': bool(token and _verify(token))} | |