GitHub Actions commited on
Commit
da64db2
Β·
1 Parent(s): 727aa6e

πŸš€ Auto-deploy from GitHub

Browse files
app/api/v1/endpoints/download.py CHANGED
@@ -3,6 +3,7 @@ from fastapi.responses import FileResponse
3
  from pathlib import Path
4
  from ....services.database import get_supabase_client # Corrected import
5
  from ....core.config import settings # Import settings
 
6
  from supabase import Client
7
 
8
  router = APIRouter()
@@ -10,7 +11,8 @@ router = APIRouter()
10
  @router.get("/download/image/{card_id}") # Changed to path parameter
11
  async def download_generated_image(
12
  card_id: str, # card_id from path
13
- supabase: Client = Depends(get_supabase_client)
 
14
  ):
15
  """
16
  Download a custom generated image using the card_id to find the image path from Supabase.
 
3
  from pathlib import Path
4
  from ....services.database import get_supabase_client # Corrected import
5
  from ....core.config import settings # Import settings
6
+ from ....core.auth import verify_hf_token
7
  from supabase import Client
8
 
9
  router = APIRouter()
 
11
  @router.get("/download/image/{card_id}") # Changed to path parameter
12
  async def download_generated_image(
13
  card_id: str, # card_id from path
14
+ supabase: Client = Depends(get_supabase_client),
15
+ authenticated: bool = Depends(verify_hf_token)
16
  ):
17
  """
18
  Download a custom generated image using the card_id to find the image path from Supabase.
app/api/v1/endpoints/generate.py CHANGED
@@ -10,6 +10,7 @@ from ....services.database import get_supabase_client, save_card
10
  from ....core.config import settings
11
  from ....core.model_loader import get_generator
12
  from ....core.constraints import generate_with_retry, check_constraints
 
13
  from fastapi.concurrency import run_in_threadpool # Importieren
14
 
15
  load_dotenv()
@@ -26,7 +27,8 @@ async def generate_qr_code_async(*args, **kwargs):
26
  @router.post("/generate", response_model=CardGenerateResponse)
27
  async def generate_endpoint(
28
  request: CardGenerateRequest,
29
- supabase: Client = Depends(get_supabase_client)
 
30
  ):
31
  try:
32
  lang = request.lang or "de"
 
10
  from ....core.config import settings
11
  from ....core.model_loader import get_generator
12
  from ....core.constraints import generate_with_retry, check_constraints
13
+ from ....core.auth import verify_hf_token
14
  from fastapi.concurrency import run_in_threadpool # Importieren
15
 
16
  load_dotenv()
 
27
  @router.post("/generate", response_model=CardGenerateResponse)
28
  async def generate_endpoint(
29
  request: CardGenerateRequest,
30
+ supabase: Client = Depends(get_supabase_client),
31
+ authenticated: bool = Depends(verify_hf_token)
32
  ):
33
  try:
34
  lang = request.lang or "de"
app/api/v1/endpoints/health.py CHANGED
@@ -1,5 +1,6 @@
1
- from fastapi import APIRouter, HTTPException
2
  from ..schemas.health_schema import HealthResponse
 
3
  import httpx
4
  import asyncio
5
  from typing import Optional
@@ -25,7 +26,7 @@ async def check_huggingface_space():
25
  return "unreachable"
26
 
27
  @router.get("/health", response_model=HealthResponse)
28
- async def health_check():
29
  """
30
  Health check endpoint that verifies server status, model loading status and HuggingFace space availability
31
  """
@@ -51,4 +52,16 @@ async def health_check():
51
  huggingface_space_url=HF_SPACE_URL
52
  )
53
  except Exception as e:
54
- raise HTTPException(status_code=500, detail=f"Health check failed: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException, Depends
2
  from ..schemas.health_schema import HealthResponse
3
+ from ....core.auth import verify_hf_token
4
  import httpx
5
  import asyncio
6
  from typing import Optional
 
26
  return "unreachable"
27
 
28
  @router.get("/health", response_model=HealthResponse)
29
+ async def health_check(authenticated: bool = Depends(verify_hf_token)):
30
  """
31
  Health check endpoint that verifies server status, model loading status and HuggingFace space availability
32
  """
 
52
  huggingface_space_url=HF_SPACE_URL
53
  )
54
  except Exception as e:
55
+ raise HTTPException(status_code=500, detail=f"Health check failed: {str(e)}")
56
+
57
+ @router.get("/health/public")
58
+ async def public_health_check():
59
+ """
60
+ Public health check endpoint (no authentication required)
61
+ Returns basic server status only
62
+ """
63
+ return {
64
+ "status": "healthy",
65
+ "server": "running",
66
+ "message": "Server is operational"
67
+ }
app/core/auth.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Authentication middleware for HuggingFace API token validation
3
+ """
4
+ from fastapi import HTTPException, status, Depends
5
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
6
+ from typing import Optional
7
+ import os
8
+ from dotenv import load_dotenv
9
+
10
+ load_dotenv()
11
+
12
+ security = HTTPBearer(auto_error=False)
13
+
14
+ def get_hf_api_key() -> str:
15
+ """Get HuggingFace API key from environment"""
16
+ return os.getenv("HF_API_KEY", "")
17
+
18
+ def verify_hf_token(credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)) -> bool:
19
+ """
20
+ Verify HuggingFace API token
21
+ Returns True if no authentication is required (public space) or if token is valid
22
+ """
23
+ expected_token = get_hf_api_key()
24
+
25
+ # If no HF_API_KEY is set, allow public access
26
+ if not expected_token:
27
+ return True
28
+
29
+ # If HF_API_KEY is set but no credentials provided, deny access
30
+ if not credentials:
31
+ raise HTTPException(
32
+ status_code=status.HTTP_401_UNAUTHORIZED,
33
+ detail="Authentication required. Please provide a valid HuggingFace API token.",
34
+ headers={"WWW-Authenticate": "Bearer"},
35
+ )
36
+
37
+ # Verify the token matches
38
+ if credentials.credentials != expected_token:
39
+ raise HTTPException(
40
+ status_code=status.HTTP_401_UNAUTHORIZED,
41
+ detail="Invalid authentication token",
42
+ headers={"WWW-Authenticate": "Bearer"},
43
+ )
44
+
45
+ return True
46
+
47
+ def optional_auth(credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)) -> bool:
48
+ """
49
+ Optional authentication - doesn't raise errors if no token provided
50
+ Used for endpoints that can work with or without authentication
51
+ """
52
+ expected_token = get_hf_api_key()
53
+
54
+ # If no HF_API_KEY is set, allow public access
55
+ if not expected_token:
56
+ return True
57
+
58
+ # If no credentials provided, still allow access (optional auth)
59
+ if not credentials:
60
+ return False
61
+
62
+ # If credentials provided, verify them
63
+ return credentials.credentials == expected_token
curl_test_commands.md DELETED
@@ -1,121 +0,0 @@
1
- # Direct curl commands for testing the /generate endpoint
2
-
3
- ## Standard test (German):
4
- curl -X POST "http://localhost:8000/api/v1/generate" \
5
- -H "Content-Type: application/json" \
6
- -H "Accept: application/json" \
7
- -d '{
8
- "terms": ["Liebe", "Erfolg", "GlΓΌck", "Herausforderung", "Wachstum"],
9
- "card_date": "1990-05-15",
10
- "lang": "de",
11
- "card_design_id_override": 1,
12
- "symbol_ids_override": [1, 2]
13
- }'
14
-
15
- ## English test:
16
- curl -X POST "http://localhost:7860/api/v1/generate" \
17
- -H "Content-Type: application/json" \
18
- -H "Accept: application/json" \
19
- -d '{
20
- "terms": ["Love", "Success", "Happiness", "Challenge", "Growth"],
21
- "card_date": "1985-12-25",
22
- "lang": "en",
23
- "card_design_id_override": 2,
24
- "symbol_ids_override": [3, 4, 5]
25
- }'
26
-
27
- ## Minimal test (only required fields):
28
- curl -X POST "http://localhost:7860/api/v1/generate" \
29
- -H "Content-Type: application/json" \
30
- -d '{
31
- "terms": ["Test1", "Test2", "Test3", "Test4", "Test5"],
32
- "card_date": "2000-01-01"
33
- }'
34
-
35
- ## Pretty printed JSON response:
36
- curl -X POST "http://localhost:7860/api/v1/generate" \
37
- -H "Content-Type: application/json" \
38
- -d '{
39
- "terms": ["Mut", "Hoffnung", "Freude", "Inspiration", "KreativitΓ€t"],
40
- "card_date": "1995-07-20",
41
- "lang": "de"
42
- }' | jq .
43
-
44
- # HUGGING FACE SPACE TESTS
45
- # HOST_URL: https://ch404-cardserver.hf.space/
46
-
47
- ## Health Check (Hugging Face):
48
- curl -X GET "https://ch404-cardserver.hf.space/api/v1/health" \
49
- -H "Accept: application/json"
50
-
51
- ## Standard test (German) - Hugging Face:
52
- curl -X POST "https://ch404-cardserver.hf.space/api/v1/generate" \
53
- -H "Content-Type: application/json" \
54
- -H "Accept: application/json" \
55
- -d '{
56
- "terms": ["Liebe", "Erfolg", "GlΓΌck", "Herausforderung", "Wachstum"],
57
- "card_date": "1990-05-15",
58
- "lang": "de",
59
- "card_design_id_override": 1,
60
- "symbol_ids_override": [1, 2]
61
- }'
62
-
63
- ## English test - Hugging Face:
64
- curl -X POST "https://ch404-cardserver.hf.space/api/v1/generate" \
65
- -H "Content-Type: application/json" \
66
- -H "Accept: application/json" \
67
- -d '{
68
- "terms": ["Love", "Success", "Happiness", "Challenge", "Growth"],
69
- "card_date": "1985-12-25",
70
- "lang": "en",
71
- "card_design_id_override": 2,
72
- "symbol_ids_override": [3, 4, 5]
73
- }'
74
-
75
- ## Minimal test (only required fields) - Hugging Face:
76
- curl -X POST "https://ch404-cardserver.hf.space/api/v1/generate" \
77
- -H "Content-Type: application/json" \
78
- -d '{
79
- "terms": ["Test1", "Test2", "Test3", "Test4", "Test5"],
80
- "card_date": "2000-01-01"
81
- }'
82
-
83
- ## Pretty printed JSON response - Hugging Face:
84
- curl -X POST "https://ch404-cardserver.hf.space/api/v1/generate" \
85
- -H "Content-Type: application/json" \
86
- -d '{
87
- "terms": ["Mut", "Hoffnung", "Freude", "Inspiration", "KreativitΓ€t"],
88
- "card_date": "1995-07-20",
89
- "lang": "de"
90
- }' | jq .
91
-
92
- ## Debug: Raw response without jq - Hugging Face:
93
- curl -X POST "https://ch404-cardserver.hf.space/api/v1/generate" \
94
- -H "Content-Type: application/json" \
95
- -d '{
96
- "terms": ["Mut", "Hoffnung", "Freude", "Inspiration", "KreativitΓ€t"],
97
- "card_date": "1995-07-20",
98
- "lang": "de"
99
- }' -v
100
-
101
- ## Debug: Check if service is running - Hugging Face:
102
- curl -X GET "https://ch404-cardserver.hf.space/" -v
103
-
104
- ## Debug: Check specific health endpoint - Hugging Face:
105
- curl -X GET "https://ch404-cardserver.hf.space/api/v1/health" -v
106
-
107
- ## Debug: Test different port (7860) - Hugging Face:
108
- curl -X GET "https://ch404-cardserver.hf.space:7860/api/v1/health" -v
109
-
110
- ## Debug: Check docs endpoint - Hugging Face:
111
- curl -X GET "https://ch404-cardserver.hf.space/docs" -v
112
-
113
- ## Download test - Hugging Face:
114
- curl -X GET "https://ch404-cardserver.hf.space/api/v1/download/YOUR_CARD_ID" \
115
- -H "Accept: image/png" \
116
- --output card_from_hf.png
117
-
118
- ## Status check with verbose output - Hugging Face:
119
- curl -X GET "https://ch404-cardserver.hf.space/api/v1/health" \
120
- -H "Accept: application/json" \
121
- -v
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/images/generated/375677ce-67f8-4afc-aeae-7b993441b2a5.png ADDED
static/images/generated/7b7c33a0-fdb1-44ff-8642-64b6adca011c.png ADDED
static/images/qr/c01a736a-0618-44cf-ae9e-65189c0bc752.png ADDED
static/images/qr/d5983203-7dce-4d8b-97c1-fe40517dace5.png ADDED
tests/test_authentication.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script to verify bearer token authentication for all endpoints
4
+ """
5
+ import requests
6
+ import os
7
+ from dotenv import load_dotenv
8
+
9
+ load_dotenv()
10
+
11
+ # Configuration
12
+ BASE_URL = "http://127.0.0.1:8000" # Change to your deployment URL when testing live
13
+ HF_API_KEY = os.getenv("HF_API_KEY")
14
+
15
+ def test_endpoint(endpoint, method="GET", headers=None, json_data=None):
16
+ """Test an endpoint and return the response"""
17
+ url = f"{BASE_URL}{endpoint}"
18
+ try:
19
+ if method == "GET":
20
+ response = requests.get(url, headers=headers)
21
+ elif method == "POST":
22
+ response = requests.post(url, headers=headers, json=json_data)
23
+
24
+ return {
25
+ "status_code": response.status_code,
26
+ "success": response.status_code < 400,
27
+ "response": response.json() if response.headers.get("content-type", "").startswith("application/json") else response.text
28
+ }
29
+ except Exception as e:
30
+ return {
31
+ "status_code": None,
32
+ "success": False,
33
+ "error": str(e)
34
+ }
35
+
36
+ def main():
37
+ """Run authentication tests"""
38
+ print("πŸ” Testing Bearer Token Authentication")
39
+ print("=" * 50)
40
+
41
+ if not HF_API_KEY:
42
+ print("❌ HF_API_KEY not found in environment variables")
43
+ return
44
+
45
+ # Test headers
46
+ auth_headers = {
47
+ "Authorization": f"Bearer {HF_API_KEY}",
48
+ "Content-Type": "application/json"
49
+ }
50
+
51
+ no_auth_headers = {
52
+ "Content-Type": "application/json"
53
+ }
54
+
55
+ invalid_auth_headers = {
56
+ "Authorization": "Bearer invalid_token_123",
57
+ "Content-Type": "application/json"
58
+ }
59
+
60
+ tests = [
61
+ # Public endpoint (should work without auth)
62
+ {
63
+ "name": "Public Health Check (No Auth)",
64
+ "endpoint": "/api/v1/health/public",
65
+ "method": "GET",
66
+ "headers": no_auth_headers,
67
+ "should_succeed": True
68
+ },
69
+
70
+ # Protected endpoints without auth (should fail)
71
+ {
72
+ "name": "Protected Health Check (No Auth)",
73
+ "endpoint": "/api/v1/health",
74
+ "method": "GET",
75
+ "headers": no_auth_headers,
76
+ "should_succeed": False
77
+ },
78
+
79
+ # Protected endpoints with invalid auth (should fail)
80
+ {
81
+ "name": "Protected Health Check (Invalid Auth)",
82
+ "endpoint": "/api/v1/health",
83
+ "method": "GET",
84
+ "headers": invalid_auth_headers,
85
+ "should_succeed": False
86
+ },
87
+
88
+ # Protected endpoints with valid auth (should succeed)
89
+ {
90
+ "name": "Protected Health Check (Valid Auth)",
91
+ "endpoint": "/api/v1/health",
92
+ "method": "GET",
93
+ "headers": auth_headers,
94
+ "should_succeed": True
95
+ },
96
+
97
+ # Test generate endpoint with auth
98
+ {
99
+ "name": "Generate Endpoint (Valid Auth)",
100
+ "endpoint": "/api/v1/generate",
101
+ "method": "POST",
102
+ "headers": auth_headers,
103
+ "json_data": {
104
+ "terms": ["test", "card"],
105
+ "card_date": "2024-01-01",
106
+ "lang": "en"
107
+ },
108
+ "should_succeed": True
109
+ },
110
+
111
+ # Test generate endpoint without auth
112
+ {
113
+ "name": "Generate Endpoint (No Auth)",
114
+ "endpoint": "/api/v1/generate",
115
+ "method": "POST",
116
+ "headers": no_auth_headers,
117
+ "json_data": {
118
+ "terms": ["test", "card"],
119
+ "card_date": "2024-01-01",
120
+ "lang": "en"
121
+ },
122
+ "should_succeed": False
123
+ }
124
+ ]
125
+
126
+ results = []
127
+ for test in tests:
128
+ print(f"\nπŸ§ͺ Testing: {test['name']}")
129
+
130
+ result = test_endpoint(
131
+ test["endpoint"],
132
+ test["method"],
133
+ test["headers"],
134
+ test.get("json_data")
135
+ )
136
+
137
+ expected_success = test["should_succeed"]
138
+ actual_success = result["success"]
139
+
140
+ if expected_success == actual_success:
141
+ status = "βœ… PASS"
142
+ else:
143
+ status = "❌ FAIL"
144
+
145
+ print(f" {status} - Status: {result['status_code']}")
146
+
147
+ if not result["success"] and "error" in result:
148
+ print(f" Error: {result['error']}")
149
+ elif "response" in result:
150
+ # Print first few lines of response for debugging
151
+ response_str = str(result["response"])
152
+ if len(response_str) > 100:
153
+ response_str = response_str[:100] + "..."
154
+ print(f" Response: {response_str}")
155
+
156
+ results.append({
157
+ "test": test["name"],
158
+ "passed": expected_success == actual_success,
159
+ "status_code": result["status_code"]
160
+ })
161
+
162
+ # Summary
163
+ print("\n" + "=" * 50)
164
+ print("πŸ“Š Test Summary")
165
+ print("=" * 50)
166
+
167
+ passed = sum(1 for r in results if r["passed"])
168
+ total = len(results)
169
+
170
+ print(f"βœ… Passed: {passed}/{total}")
171
+ print(f"❌ Failed: {total - passed}/{total}")
172
+
173
+ if passed == total:
174
+ print("\nπŸŽ‰ All authentication tests passed!")
175
+ else:
176
+ print("\n⚠️ Some tests failed. Check the output above.")
177
+ print("\nFailed tests:")
178
+ for result in results:
179
+ if not result["passed"]:
180
+ print(f" - {result['test']}")
181
+
182
+ if __name__ == "__main__":
183
+ main()