Spaces:
Sleeping
Sleeping
github-actions[bot] commited on
Commit ·
3fe4567
1
Parent(s): dd79664
Sync from GitHub b32aec24fbce3d4b772838e2ed3310b56213b520
Browse files- README.md +1 -3
- app.py +0 -3
- auth/oauth.py +2 -10
- auth/session.py +4 -33
- frontend/app.py +0 -3
- tests/test_auth_mode.py +0 -42
- tests/test_hf_oauth_settings.py +0 -14
README.md
CHANGED
|
@@ -266,7 +266,7 @@ This repo now includes:
|
|
| 266 |
1. Create or switch your Space to **Docker** SDK.
|
| 267 |
2. Push this repo to your Space (or use the GitHub Action sync workflow already in `.github/workflows/deploy-hf-space.yml`).
|
| 268 |
3. In Space variables/secrets, set at minimum:
|
| 269 |
-
- `AUTH_MODE=
|
| 270 |
- `APP_SESSION_SECRET=<strong-random-secret>`
|
| 271 |
- `STORAGE_BASE_DIR=/data`
|
| 272 |
- `OPENAI_API_KEY=<key>` (if using OpenAI features)
|
|
@@ -276,6 +276,4 @@ This repo now includes:
|
|
| 276 |
- `HF_OAUTH_REDIRECT_URI` (your Space callback URL)
|
| 277 |
- `AUTH_SUCCESS_REDIRECT_URL` (your Space URL)
|
| 278 |
|
| 279 |
-
With `AUTH_MODE=auto`, backend auth switches to OAuth automatically when both `HF_OAUTH_CLIENT_ID` and `HF_OAUTH_CLIENT_SECRET` are configured.
|
| 280 |
-
|
| 281 |
The container exposes Streamlit on port `7860` and points the frontend to the internal backend via `BACKEND_URL=http://127.0.0.1:8000`.
|
|
|
|
| 266 |
1. Create or switch your Space to **Docker** SDK.
|
| 267 |
2. Push this repo to your Space (or use the GitHub Action sync workflow already in `.github/workflows/deploy-hf-space.yml`).
|
| 268 |
3. In Space variables/secrets, set at minimum:
|
| 269 |
+
- `AUTH_MODE=dev` (or `hf_oauth`)
|
| 270 |
- `APP_SESSION_SECRET=<strong-random-secret>`
|
| 271 |
- `STORAGE_BASE_DIR=/data`
|
| 272 |
- `OPENAI_API_KEY=<key>` (if using OpenAI features)
|
|
|
|
| 276 |
- `HF_OAUTH_REDIRECT_URI` (your Space callback URL)
|
| 277 |
- `AUTH_SUCCESS_REDIRECT_URL` (your Space URL)
|
| 278 |
|
|
|
|
|
|
|
| 279 |
The container exposes Streamlit on port `7860` and points the frontend to the internal backend via `BACKEND_URL=http://127.0.0.1:8000`.
|
app.py
CHANGED
|
@@ -12,7 +12,6 @@ from uuid import uuid4
|
|
| 12 |
from fastapi.concurrency import run_in_threadpool
|
| 13 |
from fastapi import APIRouter, BackgroundTasks, Depends, FastAPI, File, Form, HTTPException, Request, UploadFile, status
|
| 14 |
from fastapi.responses import FileResponse, RedirectResponse
|
| 15 |
-
from dotenv import load_dotenv
|
| 16 |
from pydantic import BaseModel, Field
|
| 17 |
from sqlalchemy import text
|
| 18 |
from sqlalchemy.orm import Session
|
|
@@ -42,8 +41,6 @@ from src.artifacts.podcast_generator import PodcastGenerator
|
|
| 42 |
from utils.llm_client import LLMConfigError, generate_chat_completion
|
| 43 |
from utils.prompt_templates import build_rag_system_prompt, build_rag_user_prompt
|
| 44 |
|
| 45 |
-
load_dotenv()
|
| 46 |
-
|
| 47 |
|
| 48 |
@asynccontextmanager
|
| 49 |
async def lifespan(_: FastAPI):
|
|
|
|
| 12 |
from fastapi.concurrency import run_in_threadpool
|
| 13 |
from fastapi import APIRouter, BackgroundTasks, Depends, FastAPI, File, Form, HTTPException, Request, UploadFile, status
|
| 14 |
from fastapi.responses import FileResponse, RedirectResponse
|
|
|
|
| 15 |
from pydantic import BaseModel, Field
|
| 16 |
from sqlalchemy import text
|
| 17 |
from sqlalchemy.orm import Session
|
|
|
|
| 41 |
from utils.llm_client import LLMConfigError, generate_chat_completion
|
| 42 |
from utils.prompt_templates import build_rag_system_prompt, build_rag_user_prompt
|
| 43 |
|
|
|
|
|
|
|
| 44 |
|
| 45 |
@asynccontextmanager
|
| 46 |
async def lifespan(_: FastAPI):
|
auth/oauth.py
CHANGED
|
@@ -13,14 +13,6 @@ class HFOAuthError(RuntimeError):
|
|
| 13 |
pass
|
| 14 |
|
| 15 |
|
| 16 |
-
def _first_env(*names: str) -> str:
|
| 17 |
-
for name in names:
|
| 18 |
-
value = os.getenv(name, "").strip()
|
| 19 |
-
if value:
|
| 20 |
-
return value
|
| 21 |
-
return ""
|
| 22 |
-
|
| 23 |
-
|
| 24 |
@dataclass(frozen=True)
|
| 25 |
class HFOAuthSettings:
|
| 26 |
client_id: str
|
|
@@ -32,8 +24,8 @@ class HFOAuthSettings:
|
|
| 32 |
|
| 33 |
|
| 34 |
def get_hf_oauth_settings() -> HFOAuthSettings:
|
| 35 |
-
client_id =
|
| 36 |
-
client_secret =
|
| 37 |
if not client_id or not client_secret:
|
| 38 |
raise HFOAuthError("HF OAuth client configuration is missing.")
|
| 39 |
|
|
|
|
| 13 |
pass
|
| 14 |
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
@dataclass(frozen=True)
|
| 17 |
class HFOAuthSettings:
|
| 18 |
client_id: str
|
|
|
|
| 24 |
|
| 25 |
|
| 26 |
def get_hf_oauth_settings() -> HFOAuthSettings:
|
| 27 |
+
client_id = os.getenv("HF_OAUTH_CLIENT_ID", "").strip()
|
| 28 |
+
client_secret = os.getenv("HF_OAUTH_CLIENT_SECRET", "").strip()
|
| 29 |
if not client_id or not client_secret:
|
| 30 |
raise HFOAuthError("HF OAuth client configuration is missing.")
|
| 31 |
|
auth/session.py
CHANGED
|
@@ -30,43 +30,14 @@ class AuthBridgeTokenError(ValueError):
|
|
| 30 |
pass
|
| 31 |
|
| 32 |
|
| 33 |
-
def _first_env(*names: str) -> str:
|
| 34 |
-
for name in names:
|
| 35 |
-
value = os.getenv(name, "").strip()
|
| 36 |
-
if value:
|
| 37 |
-
return value
|
| 38 |
-
return ""
|
| 39 |
-
|
| 40 |
-
|
| 41 |
def get_auth_mode() -> str:
|
| 42 |
-
configured = os.getenv("AUTH_MODE",
|
| 43 |
-
if configured
|
| 44 |
-
return AUTH_MODE_DEV
|
| 45 |
-
if configured in {AUTH_MODE_HF, "oauth"}:
|
| 46 |
-
return AUTH_MODE_HF
|
| 47 |
-
if not configured or configured == "auto":
|
| 48 |
-
has_hf_client = bool(_first_env("HF_OAUTH_CLIENT_ID", "OAUTH_CLIENT_ID"))
|
| 49 |
-
has_hf_secret = bool(_first_env("HF_OAUTH_CLIENT_SECRET", "OAUTH_CLIENT_SECRET"))
|
| 50 |
-
if has_hf_client and has_hf_secret:
|
| 51 |
-
return AUTH_MODE_HF
|
| 52 |
-
return AUTH_MODE_DEV
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
def _resolve_session_secret() -> str:
|
| 56 |
-
configured = os.getenv("APP_SESSION_SECRET", "").strip()
|
| 57 |
-
if configured:
|
| 58 |
-
return configured
|
| 59 |
-
if get_auth_mode() == AUTH_MODE_HF:
|
| 60 |
-
# In HF Spaces OAuth deployments, OAUTH_CLIENT_SECRET is usually injected.
|
| 61 |
-
oauth_secret = _first_env("HF_OAUTH_CLIENT_SECRET", "OAUTH_CLIENT_SECRET")
|
| 62 |
-
if oauth_secret:
|
| 63 |
-
return oauth_secret
|
| 64 |
-
return DEFAULT_DEV_SESSION_SECRET
|
| 65 |
|
| 66 |
|
| 67 |
def configure_session_middleware(app) -> None:
|
| 68 |
"""Attach Starlette session middleware once during app setup."""
|
| 69 |
-
secret =
|
| 70 |
auth_mode = get_auth_mode()
|
| 71 |
if auth_mode == AUTH_MODE_HF and (not secret or secret == DEFAULT_DEV_SESSION_SECRET):
|
| 72 |
raise RuntimeError("APP_SESSION_SECRET must be set to a non-default value in hf_oauth mode.")
|
|
@@ -90,7 +61,7 @@ def configure_session_middleware(app) -> None:
|
|
| 90 |
|
| 91 |
|
| 92 |
def _bridge_serializer() -> URLSafeTimedSerializer:
|
| 93 |
-
secret =
|
| 94 |
return URLSafeTimedSerializer(secret_key=secret, salt=AUTH_BRIDGE_SALT)
|
| 95 |
|
| 96 |
|
|
|
|
| 30 |
pass
|
| 31 |
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
def get_auth_mode() -> str:
|
| 34 |
+
configured = os.getenv("AUTH_MODE", AUTH_MODE_DEV).strip().lower()
|
| 35 |
+
return configured if configured in {AUTH_MODE_DEV, AUTH_MODE_HF} else AUTH_MODE_DEV
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
|
| 38 |
def configure_session_middleware(app) -> None:
|
| 39 |
"""Attach Starlette session middleware once during app setup."""
|
| 40 |
+
secret = os.getenv("APP_SESSION_SECRET", DEFAULT_DEV_SESSION_SECRET).strip()
|
| 41 |
auth_mode = get_auth_mode()
|
| 42 |
if auth_mode == AUTH_MODE_HF and (not secret or secret == DEFAULT_DEV_SESSION_SECRET):
|
| 43 |
raise RuntimeError("APP_SESSION_SECRET must be set to a non-default value in hf_oauth mode.")
|
|
|
|
| 61 |
|
| 62 |
|
| 63 |
def _bridge_serializer() -> URLSafeTimedSerializer:
|
| 64 |
+
secret = os.getenv("APP_SESSION_SECRET", DEFAULT_DEV_SESSION_SECRET)
|
| 65 |
return URLSafeTimedSerializer(secret_key=secret, salt=AUTH_BRIDGE_SALT)
|
| 66 |
|
| 67 |
|
frontend/app.py
CHANGED
|
@@ -5,9 +5,6 @@ from typing import Any
|
|
| 5 |
|
| 6 |
import requests
|
| 7 |
import streamlit as st
|
| 8 |
-
from dotenv import load_dotenv
|
| 9 |
-
|
| 10 |
-
load_dotenv()
|
| 11 |
|
| 12 |
st.set_page_config(page_title="NotebookLM Clone", page_icon="📚", layout="wide")
|
| 13 |
|
|
|
|
| 5 |
|
| 6 |
import requests
|
| 7 |
import streamlit as st
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
st.set_page_config(page_title="NotebookLM Clone", page_icon="📚", layout="wide")
|
| 10 |
|
tests/test_auth_mode.py
DELETED
|
@@ -1,42 +0,0 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
from auth.session import AUTH_MODE_DEV, AUTH_MODE_HF, get_auth_mode
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
def test_get_auth_mode_dev_explicit(monkeypatch):
|
| 7 |
-
monkeypatch.setenv("AUTH_MODE", "dev")
|
| 8 |
-
monkeypatch.delenv("HF_OAUTH_CLIENT_ID", raising=False)
|
| 9 |
-
monkeypatch.delenv("HF_OAUTH_CLIENT_SECRET", raising=False)
|
| 10 |
-
assert get_auth_mode() == AUTH_MODE_DEV
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
def test_get_auth_mode_oauth_alias(monkeypatch):
|
| 14 |
-
monkeypatch.setenv("AUTH_MODE", "oauth")
|
| 15 |
-
assert get_auth_mode() == AUTH_MODE_HF
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
def test_get_auth_mode_auto_switches_to_hf(monkeypatch):
|
| 19 |
-
monkeypatch.setenv("AUTH_MODE", "auto")
|
| 20 |
-
monkeypatch.setenv("HF_OAUTH_CLIENT_ID", "client-id")
|
| 21 |
-
monkeypatch.setenv("HF_OAUTH_CLIENT_SECRET", "client-secret")
|
| 22 |
-
monkeypatch.delenv("OAUTH_CLIENT_ID", raising=False)
|
| 23 |
-
monkeypatch.delenv("OAUTH_CLIENT_SECRET", raising=False)
|
| 24 |
-
assert get_auth_mode() == AUTH_MODE_HF
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
def test_get_auth_mode_auto_falls_back_to_dev(monkeypatch):
|
| 28 |
-
monkeypatch.setenv("AUTH_MODE", "auto")
|
| 29 |
-
monkeypatch.delenv("HF_OAUTH_CLIENT_ID", raising=False)
|
| 30 |
-
monkeypatch.delenv("HF_OAUTH_CLIENT_SECRET", raising=False)
|
| 31 |
-
monkeypatch.delenv("OAUTH_CLIENT_ID", raising=False)
|
| 32 |
-
monkeypatch.delenv("OAUTH_CLIENT_SECRET", raising=False)
|
| 33 |
-
assert get_auth_mode() == AUTH_MODE_DEV
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
def test_get_auth_mode_auto_switches_to_hf_with_space_oauth_vars(monkeypatch):
|
| 37 |
-
monkeypatch.setenv("AUTH_MODE", "auto")
|
| 38 |
-
monkeypatch.delenv("HF_OAUTH_CLIENT_ID", raising=False)
|
| 39 |
-
monkeypatch.delenv("HF_OAUTH_CLIENT_SECRET", raising=False)
|
| 40 |
-
monkeypatch.setenv("OAUTH_CLIENT_ID", "space-client-id")
|
| 41 |
-
monkeypatch.setenv("OAUTH_CLIENT_SECRET", "space-client-secret")
|
| 42 |
-
assert get_auth_mode() == AUTH_MODE_HF
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tests/test_hf_oauth_settings.py
DELETED
|
@@ -1,14 +0,0 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
from auth.oauth import get_hf_oauth_settings
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
def test_hf_oauth_settings_accept_space_oauth_vars(monkeypatch):
|
| 7 |
-
monkeypatch.delenv("HF_OAUTH_CLIENT_ID", raising=False)
|
| 8 |
-
monkeypatch.delenv("HF_OAUTH_CLIENT_SECRET", raising=False)
|
| 9 |
-
monkeypatch.setenv("OAUTH_CLIENT_ID", "space-client-id")
|
| 10 |
-
monkeypatch.setenv("OAUTH_CLIENT_SECRET", "space-client-secret")
|
| 11 |
-
|
| 12 |
-
settings = get_hf_oauth_settings()
|
| 13 |
-
assert settings.client_id == "space-client-id"
|
| 14 |
-
assert settings.client_secret == "space-client-secret"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|