Renecto commited on
Commit
87ab4d0
Β·
verified Β·
1 Parent(s): e94e965

Deploy habadashi_login gateway

Browse files
Files changed (6) hide show
  1. LOCAL_TESTING.md +119 -0
  2. README.md +37 -12
  3. app.py +148 -0
  4. bootstrap.py +75 -0
  5. login.py +52 -0
  6. requirements.txt +18 -0
LOCAL_TESTING.md ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Habadashi Login Gateway - Local Testing Guide
2
+
3
+ ## Prerequisites
4
+
5
+ 1. Environment variables must be set:
6
+ - `SUPABASE_URL`: Your Supabase project URL
7
+ - `SUPABASE_KEY`: Your Supabase anon/service key
8
+ - `HF_TOKEN`: Hugging Face token with access to private `DLPO/habadashi` Space
9
+ - `OPENAI_KEY`: OpenAI API key (required by ver20)
10
+ - `CLIENTPOOL`: Client pool configuration (required by ver20)
11
+
12
+ 2. Install dependencies:
13
+ ```bash
14
+ pip install -r requirements.txt
15
+ ```
16
+
17
+ ## Local Testing Steps
18
+
19
+ ### Step 1: Set environment variables
20
+
21
+ **PowerShell (Windows):**
22
+ ```powershell
23
+ $env:SUPABASE_URL="your-supabase-url"
24
+ $env:SUPABASE_KEY="your-supabase-key"
25
+ $env:HF_TOKEN="your-hf-token"
26
+ $env:OPENAI_KEY="your-openai-key"
27
+ $env:CLIENTPOOL="your-client-pool"
28
+ ```
29
+
30
+ **Bash (Linux/Mac):**
31
+ ```bash
32
+ export SUPABASE_URL="your-supabase-url"
33
+ export SUPABASE_KEY="your-supabase-key"
34
+ export HF_TOKEN="your-hf-token"
35
+ export OPENAI_KEY="your-openai-key"
36
+ export CLIENTPOOL="your-client-pool"
37
+ ```
38
+
39
+ ### Step 2: Test bootstrap (download private app)
40
+
41
+ ```bash
42
+ python bootstrap.py
43
+ ```
44
+
45
+ Expected output:
46
+ - `πŸ”„ Downloading private app from DLPO/habadashi...`
47
+ - `βœ… Download complete: ./private_app`
48
+ - `πŸŽ‰ Bootstrap test successful!`
49
+
50
+ This will create a `./private_app/` directory with ver20 files.
51
+
52
+ ### Step 3: Start the application
53
+
54
+ ```bash
55
+ uvicorn app:app --host 0.0.0.0 --port 7860
56
+ ```
57
+
58
+ Or use Gradio's default launch (if app.py is configured to run directly):
59
+ ```bash
60
+ python app.py
61
+ ```
62
+
63
+ ### Step 4: Verify routes
64
+
65
+ 1. **Root route**: http://localhost:7860/
66
+ - Should redirect to `/login/` if not authenticated
67
+ - Should redirect to `/app/` if authenticated
68
+
69
+ 2. **Login UI**: http://localhost:7860/login/
70
+ - Enter email and password
71
+ - On success, should set cookie and redirect to `/app/`
72
+
73
+ 3. **Protected app**: http://localhost:7860/app/
74
+ - Should show ver20 Gradio interface if authenticated
75
+ - Should redirect to login if not authenticated
76
+
77
+ 4. **Logout**: http://localhost:7860/logout
78
+ - Should clear cookie and redirect to `/login/`
79
+
80
+ ## Troubleshooting
81
+
82
+ ### Issue: "HF_TOKEN not found"
83
+ - Make sure `HF_TOKEN` is set in environment variables
84
+ - Verify token has access to private `DLPO/habadashi` Space
85
+
86
+ ### Issue: "Failed to import ver20 app"
87
+ - Check that `./private_app/app.py` exists
88
+ - Verify ver20's `app.py` exports `app` variable (Gradio Blocks instance)
89
+ - Check Python path is correctly set
90
+
91
+ ### Issue: "SUPABASE_URL and SUPABASE_KEY must be set"
92
+ - Set both environment variables before running the app
93
+
94
+ ### Issue: Cookie not persisting (localhost HTTP)
95
+ - For local development, you may need to modify cookie settings in `login.py`
96
+ - Remove `Secure` flag for HTTP testing: `SameSite=Lax;` (remove `Secure;`)
97
+
98
+ ## Deployment to HF Space
99
+
100
+ Once local testing is successful, deploy to public HF Space:
101
+
102
+ 1. Create new public Space: `DLPO/habadashi_login`
103
+ 2. Set secrets in Space settings:
104
+ - `SUPABASE_URL`
105
+ - `SUPABASE_KEY`
106
+ - `HF_TOKEN`
107
+ - `OPENAI_KEY`
108
+ - `CLIENTPOOL`
109
+ 3. Upload files:
110
+ - `app.py`
111
+ - `bootstrap.py`
112
+ - `login.py`
113
+ - `requirements.txt`
114
+ - `README.md`
115
+
116
+ The Space will automatically:
117
+ 1. Download ver20 from private `DLPO/habadashi` on startup
118
+ 2. Mount login UI at `/login/`
119
+ 3. Mount ver20 app at `/app/` (protected by auth)
README.md CHANGED
@@ -1,12 +1,37 @@
1
- ---
2
- title: Habadashi Login
3
- emoji: πŸ”₯
4
- colorFrom: indigo
5
- colorTo: green
6
- sdk: gradio
7
- sdk_version: 6.6.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Habadashi Login Gateway
3
+ emoji: πŸ”
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: gradio
7
+ sdk_version: 5.49.1
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+
12
+ # Habadashi Login Gateway
13
+
14
+ Public gateway with Supabase authentication that dynamically loads the private ver20 application from `DLPO/habadashi` Space.
15
+
16
+ ## Features
17
+
18
+ - Supabase email/password authentication
19
+ - Cookie-based session management
20
+ - Dynamic loading of private application code (source code not exposed in public repo)
21
+ - Role-based access control via profiles table
22
+
23
+ ## Environment Variables (Set in HF Space Secrets)
24
+
25
+ - `SUPABASE_URL`: Your Supabase project URL
26
+ - `SUPABASE_KEY`: Your Supabase anon/service key
27
+ - `HF_TOKEN`: Hugging Face token with access to private `DLPO/habadashi` Space
28
+ - `OPENAI_KEY`: OpenAI API key (required by ver20)
29
+ - `CLIENTPOOL`: Client pool configuration (required by ver20)
30
+
31
+ ## Architecture
32
+
33
+ 1. User visits public Space
34
+ 2. Bootstrap downloads ver20 from private `DLPO/habadashi` Space
35
+ 3. User logs in with Supabase credentials
36
+ 4. On successful login, ver20 Gradio app is mounted at `/app/`
37
+ 5. User profile and organization info loaded from `profiles` table
app.py ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Habadashi Login Gateway - Main entry point
4
+ Public Space that loads private ver20 app dynamically
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ from pathlib import Path
10
+ from fastapi import FastAPI, Request, Depends, HTTPException
11
+ from fastapi.responses import RedirectResponse
12
+ import gradio as gr
13
+ from supabase import create_client, Client
14
+
15
+ # Import bootstrap to download private app
16
+ from bootstrap import download_private_app
17
+ from login import create_login_ui
18
+
19
+ # --- Bootstrap: Download private app at startup ---
20
+ print("πŸš€ Starting Habadashi Login Gateway...")
21
+ try:
22
+ private_app_dir = download_private_app()
23
+
24
+ # Add private app to Python path so we can import it
25
+ private_app_path = str(private_app_dir.resolve())
26
+ if private_app_path not in sys.path:
27
+ sys.path.insert(0, private_app_path)
28
+ print(f"βœ… Added to Python path: {private_app_path}")
29
+
30
+ except Exception as e:
31
+ print(f"❌ Failed to bootstrap private app: {e}")
32
+ print("⚠️ Application will start but /app/ route will not work")
33
+ private_app_dir = None
34
+
35
+ # --- Supabase Setup ---
36
+ SUPABASE_URL = os.environ.get("SUPABASE_URL")
37
+ SUPABASE_KEY = os.environ.get("SUPABASE_KEY")
38
+
39
+ if not SUPABASE_URL or not SUPABASE_KEY:
40
+ raise ValueError(
41
+ "SUPABASE_URL and SUPABASE_KEY must be set in environment variables. "
42
+ "Please configure them in HF Space Secrets."
43
+ )
44
+
45
+ supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
46
+
47
+ # --- FastAPI App ---
48
+ app = FastAPI()
49
+
50
+ # --- Authentication Handler (for login UI) ---
51
+ def handle_login(email, password):
52
+ """Handle login attempt via Supabase"""
53
+ try:
54
+ res = supabase.auth.sign_in_with_password({"email": email, "password": password})
55
+ if res.session:
56
+ return (
57
+ gr.update(visible=False),
58
+ gr.update(visible=True, value=f"### βœ… γƒ­γ‚°γ‚€γƒ³ζˆεŠŸ: {email}"),
59
+ res.session.access_token
60
+ )
61
+ except Exception as e:
62
+ return gr.update(), gr.update(value=f"❌ エラー: {str(e)}"), None
63
+
64
+ # --- Authentication Dependency ---
65
+ def get_current_user(request: Request):
66
+ """Verify token from cookie and fetch user profile"""
67
+ token = request.cookies.get("sb_access_token")
68
+
69
+ print(f"--- Auth Check ---")
70
+ print(f"Token present: {bool(token)}")
71
+
72
+ if not token:
73
+ return None
74
+
75
+ try:
76
+ # Verify token with Supabase
77
+ res = supabase.auth.get_user(token)
78
+ user_id = res.user.id
79
+
80
+ # Fetch profile from profiles table (with organization name)
81
+ profile_res = supabase.from_("profiles").select(
82
+ "email, org_id, role, display_name, organizations(name)"
83
+ ).eq("id", user_id).single().execute()
84
+
85
+ profile_data = profile_res.data
86
+ user_dict = {
87
+ "user_id": user_id,
88
+ "email": profile_data.get("email"),
89
+ "display_name": profile_data.get("display_name"),
90
+ "role": profile_data.get("role"),
91
+ "org_name": (profile_data.get("organizations") or {}).get("name")
92
+ }
93
+
94
+ print(f"Auth Success: {user_dict['email']}")
95
+ return user_dict
96
+
97
+ except Exception as e:
98
+ print(f"Auth Error: {e}")
99
+ return None
100
+
101
+ # --- Create UI instances ---
102
+ login_ui = create_login_ui(handle_login)
103
+
104
+ # Import ver20 app (Gradio Blocks)
105
+ ver20_app = None
106
+ if private_app_dir:
107
+ try:
108
+ # Import app module from downloaded private app
109
+ from app import app as ver20_blocks
110
+ ver20_app = ver20_blocks
111
+ print("βœ… Successfully imported ver20 Gradio app")
112
+ except Exception as e:
113
+ print(f"❌ Failed to import ver20 app: {e}")
114
+ print(f" Make sure ver20/app.py exports 'app' variable (Gradio Blocks)")
115
+
116
+ # --- Routes ---
117
+ @app.get("/")
118
+ async def root(user=Depends(get_current_user)):
119
+ """Root route - redirect to login or app based on auth status"""
120
+ if isinstance(user, dict) and user.get("user_id"):
121
+ return RedirectResponse(url="/app/")
122
+ return RedirectResponse(url="/login/")
123
+
124
+ @app.get("/logout")
125
+ async def logout():
126
+ """Logout route - clear cookie and redirect to login"""
127
+ response = RedirectResponse(url="/login/")
128
+ response.delete_cookie("sb_access_token")
129
+ return response
130
+
131
+ # --- Mount Gradio UIs ---
132
+ # Login UI (public)
133
+ app = gr.mount_gradio_app(app, login_ui, path="/login")
134
+
135
+ # Ver20 App (protected)
136
+ if ver20_app:
137
+ app = gr.mount_gradio_app(app, ver20_app, path="/app", auth_dependency=get_current_user)
138
+ print("βœ… Mounted ver20 app at /app/ (protected)")
139
+ else:
140
+ # Fallback: simple placeholder if ver20 failed to load
141
+ with gr.Blocks() as fallback_ui:
142
+ gr.Markdown("# ⚠️ Application Not Available")
143
+ gr.Markdown("The private application failed to load. Please check logs.")
144
+
145
+ app = gr.mount_gradio_app(app, fallback_ui, path="/app", auth_dependency=get_current_user)
146
+ print("⚠️ Mounted fallback UI at /app/ (ver20 failed to load)")
147
+
148
+ print("πŸŽ‰ Habadashi Login Gateway ready!")
bootstrap.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Bootstrap module - Downloads ver20 from private DLPO/habadashi Space
4
+ """
5
+
6
+ import os
7
+ from pathlib import Path
8
+ from huggingface_hub import snapshot_download
9
+
10
+ def get_hf_token():
11
+ """Get HF_TOKEN from environment variable"""
12
+ token = os.environ.get("HF_TOKEN")
13
+ if not token:
14
+ raise ValueError(
15
+ "HF_TOKEN not found. Please set HF_TOKEN environment variable "
16
+ "in HF Space Secrets to access private repository."
17
+ )
18
+ return token
19
+
20
+ def download_private_app(force_download: bool = False):
21
+ """
22
+ Download ver20 application from private DLPO/habadashi Space
23
+
24
+ Args:
25
+ force_download: If True, re-download even if already cached
26
+
27
+ Returns:
28
+ Path: Local directory containing downloaded ver20 files
29
+ """
30
+ repo_id = "DLPO/habadashi"
31
+ repo_type = "space"
32
+ local_dir = Path("./private_app")
33
+
34
+ # Check if already downloaded (skip if exists and not forced)
35
+ if local_dir.exists() and not force_download:
36
+ # Verify essential files exist
37
+ app_py = local_dir / "app.py"
38
+ if app_py.exists():
39
+ print(f"βœ… Private app already downloaded: {local_dir}")
40
+ return local_dir
41
+
42
+ print(f"πŸ”„ Downloading private app from {repo_id}...")
43
+
44
+ try:
45
+ token = get_hf_token()
46
+
47
+ # Download entire Space repository
48
+ snapshot_download(
49
+ repo_id=repo_id,
50
+ repo_type=repo_type,
51
+ local_dir=str(local_dir),
52
+ local_dir_use_symlinks=False,
53
+ token=token,
54
+ )
55
+
56
+ print(f"βœ… Download complete: {local_dir}")
57
+
58
+ # Verify essential structure
59
+ app_py = local_dir / "app.py"
60
+ if not app_py.exists():
61
+ raise FileNotFoundError(
62
+ f"app.py not found in downloaded repository. "
63
+ f"Expected at: {app_py}"
64
+ )
65
+
66
+ return local_dir
67
+
68
+ except Exception as e:
69
+ print(f"❌ Failed to download private app: {e}")
70
+ raise
71
+
72
+ if __name__ == "__main__":
73
+ # Test download
74
+ download_private_app()
75
+ print("πŸŽ‰ Bootstrap test successful!")
login.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+
3
+ def create_login_ui(handle_login_fn):
4
+ """
5
+ Create Gradio login UI
6
+
7
+ Args:
8
+ handle_login_fn: Function to handle login (email, password) -> (form_update, status_update, token)
9
+
10
+ Returns:
11
+ Gradio Blocks UI for login
12
+ """
13
+ with gr.Blocks(title="Login") as ui:
14
+ gr.Markdown("# πŸ” Habadashi Login")
15
+
16
+ with gr.Column(visible=True) as login_form:
17
+ email_input = gr.Textbox(label="Email")
18
+ pass_input = gr.Textbox(label="Password", type="password")
19
+ login_btn = gr.Button("Login", variant="primary")
20
+
21
+ status_msg = gr.Markdown("")
22
+
23
+ # Hidden textbox to store token and trigger cookie setting via JS
24
+ token_storage = gr.Textbox(visible=False, elem_id="token_storage")
25
+
26
+ # External login handler (from app.py) is bound here
27
+ login_btn.click(
28
+ handle_login_fn,
29
+ inputs=[email_input, pass_input],
30
+ outputs=[login_form, status_msg, token_storage]
31
+ )
32
+
33
+ # When token is set, use JavaScript to save cookie and redirect
34
+ token_storage.change(
35
+ None,
36
+ inputs=[token_storage],
37
+ js="""(token) => {
38
+ if (!token || token === "") return;
39
+
40
+ // Set cookie
41
+ document.cookie = `sb_access_token=${token}; path=/; max-age=3600; SameSite=None; Secure;`;
42
+
43
+ console.log("Cookie set, redirecting...");
44
+
45
+ // Wait briefly then redirect
46
+ setTimeout(() => {
47
+ window.location.href = '/app/';
48
+ }, 200);
49
+ }"""
50
+ )
51
+
52
+ return ui
requirements.txt ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Public Gateway dependencies
2
+ fastapi
3
+ uvicorn
4
+ gradio
5
+ supabase
6
+ python-multipart
7
+ huggingface_hub
8
+
9
+ # ver20 dependencies (from ver20/requirements.txt)
10
+ gradio_client
11
+ requests
12
+ openai
13
+ Pillow
14
+ matplotlib
15
+ scipy
16
+ pyyaml
17
+ pytest
18
+ pytest-cov