LogicGoInfotechSpaces commited on
Commit
bbc9c59
·
1 Parent(s): 2262841

feat: add Firebase Auth ID token verification + /api/me endpoint; support static password + Firebase ID tokens

Browse files
Files changed (2) hide show
  1. api_server.py +63 -19
  2. firebase_app_check.py +25 -1
api_server.py CHANGED
@@ -15,7 +15,7 @@ from pathlib import Path
15
  from fastapi.responses import FileResponse, StreamingResponse, JSONResponse
16
  import logging
17
  from logging.handlers import BufferingHandler
18
- from firebase_app_check import verify_app_check_token
19
 
20
  # Import face swap functionality
21
  import sys
@@ -31,14 +31,29 @@ API_PASSWORD = os.getenv("API_PASSWORD", "logicgo_videoswap@153")
31
  security = HTTPBearer()
32
 
33
  def verify_api_key(credentials: HTTPAuthorizationCredentials = Security(security)):
34
- """Verify API key from Bearer token"""
35
- if credentials.credentials != API_PASSWORD:
36
- raise HTTPException(
37
- status_code=401,
38
- detail="Invalid authentication credentials",
39
- headers={"WWW-Authenticate": "Bearer"},
40
- )
41
- return credentials.credentials
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
  # CORS middleware
44
  app.add_middleware(
@@ -340,7 +355,7 @@ async def process_face_swap(job_id: str, source_image_path: str, target_video_pa
340
  @app.post("/api/source-image", response_model=SourceImageResponse)
341
  async def upload_source_image(
342
  file: UploadFile = File(...),
343
- api_key: str = Depends(verify_api_key),
344
  _app_check_ok: bool = Depends(verify_app_check)
345
  ):
346
  """Upload and store source image in MongoDB"""
@@ -377,7 +392,7 @@ async def upload_source_image(
377
  @app.post("/api/target-video", response_model=TargetVideoResponse)
378
  async def upload_target_video(
379
  file: UploadFile = File(...),
380
- api_key: str = Depends(verify_api_key),
381
  _app_check_ok: bool = Depends(verify_app_check)
382
  ):
383
  """Upload and store target video in MongoDB"""
@@ -415,7 +430,7 @@ async def upload_target_video(
415
  async def start_face_swap(
416
  request: FaceSwapRequest,
417
  background_tasks: BackgroundTasks,
418
- api_key: str = Depends(verify_api_key),
419
  _app_check_ok: bool = Depends(verify_app_check)
420
  ):
421
  """Start face swap processing"""
@@ -460,7 +475,7 @@ async def start_face_swap(
460
  raise HTTPException(status_code=500, detail=f"Error starting face swap: {str(e)}")
461
 
462
  @app.get("/api/job/{job_id}", response_model=JobStatus)
463
- async def get_job_status(job_id: str, api_key: str = Depends(verify_api_key), _app_check_ok: bool = Depends(verify_app_check)):
464
  """Get job status"""
465
  job = await jobs_collection.find_one({"job_id": job_id})
466
  if not job:
@@ -480,7 +495,7 @@ async def get_job_status(job_id: str, api_key: str = Depends(verify_api_key), _a
480
  )
481
 
482
  @app.get("/api/result-video/{result_video_id}")
483
- async def get_result_video(result_video_id: str, api_key: str = Depends(verify_api_key), _app_check_ok: bool = Depends(verify_app_check)):
484
  """Get result video file"""
485
  result = await result_videos_collection.find_one({"_id": ObjectId(result_video_id)})
486
  if not result:
@@ -496,7 +511,7 @@ async def get_result_video(result_video_id: str, api_key: str = Depends(verify_a
496
  )
497
 
498
  @app.get("/api/source-images", response_model=List[SourceImageResponse])
499
- async def list_source_images(api_key: str = Depends(verify_api_key), _app_check_ok: bool = Depends(verify_app_check)):
500
  """List all source images"""
501
  cursor = source_images_collection.find().sort("uploaded_at", -1)
502
  images = []
@@ -511,7 +526,7 @@ async def list_source_images(api_key: str = Depends(verify_api_key), _app_check_
511
  return images
512
 
513
  @app.get("/api/target-videos", response_model=List[TargetVideoResponse])
514
- async def list_target_videos(api_key: str = Depends(verify_api_key), _app_check_ok: bool = Depends(verify_app_check)):
515
  """List all target videos"""
516
  cursor = target_videos_collection.find().sort("uploaded_at", -1)
517
  videos = []
@@ -526,7 +541,7 @@ async def list_target_videos(api_key: str = Depends(verify_api_key), _app_check_
526
  return videos
527
 
528
  @app.get("/api/result-videos", response_model=List[ResultVideoResponse])
529
- async def list_result_videos(api_key: str = Depends(verify_app_check), _app_check_ok: bool = Depends(verify_app_check)):
530
  """List all result videos"""
531
  cursor = result_videos_collection.find().sort("created_at", -1)
532
  results = []
@@ -543,7 +558,7 @@ async def list_result_videos(api_key: str = Depends(verify_app_check), _app_chec
543
  return results
544
 
545
  @app.get("/api/health")
546
- async def api_health(api_key: str = Depends(verify_api_key), _app_check_ok: bool = Depends(verify_app_check)):
547
  """Health check endpoint with GPU status (requires authentication)"""
548
  import onnxruntime
549
  available_providers = onnxruntime.get_available_providers()
@@ -589,7 +604,7 @@ async def get_api_logs(
589
  limit: int = 100,
590
  level: Optional[str] = None,
591
  endpoint: Optional[str] = None,
592
- api_key: str = Depends(verify_api_key)
593
  ):
594
  """Get API logs from MongoDB"""
595
  if api_logs_collection is None:
@@ -623,6 +638,35 @@ async def get_api_logs(
623
  except Exception as e:
624
  raise HTTPException(status_code=500, detail=f"Error fetching logs: {str(e)}")
625
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
626
  if __name__ == "__main__":
627
  import uvicorn
628
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
15
  from fastapi.responses import FileResponse, StreamingResponse, JSONResponse
16
  import logging
17
  from logging.handlers import BufferingHandler
18
+ from firebase_app_check import verify_app_check_token, verify_firebase_id_token
19
 
20
  # Import face swap functionality
21
  import sys
 
31
  security = HTTPBearer()
32
 
33
  def verify_api_key(credentials: HTTPAuthorizationCredentials = Security(security)):
34
+ """Verify API key from Bearer token.
35
+
36
+ Supports two authentication methods:
37
+ 1. Static password (API_PASSWORD)
38
+ 2. Firebase ID token (from Firebase Auth)
39
+ """
40
+ token = credentials.credentials
41
+
42
+ # Try static password first
43
+ if token == API_PASSWORD:
44
+ return {"auth_mode": "static", "token": token}
45
+
46
+ # Try Firebase ID token
47
+ firebase_claims = verify_firebase_id_token(token)
48
+ if firebase_claims:
49
+ return {"auth_mode": "firebase", "claims": firebase_claims}
50
+
51
+ # If neither works, raise error
52
+ raise HTTPException(
53
+ status_code=401,
54
+ detail="Invalid authentication credentials. Use static password or Firebase ID token.",
55
+ headers={"WWW-Authenticate": "Bearer"},
56
+ )
57
 
58
  # CORS middleware
59
  app.add_middleware(
 
355
  @app.post("/api/source-image", response_model=SourceImageResponse)
356
  async def upload_source_image(
357
  file: UploadFile = File(...),
358
+ api_key: dict = Depends(verify_api_key),
359
  _app_check_ok: bool = Depends(verify_app_check)
360
  ):
361
  """Upload and store source image in MongoDB"""
 
392
  @app.post("/api/target-video", response_model=TargetVideoResponse)
393
  async def upload_target_video(
394
  file: UploadFile = File(...),
395
+ api_key: dict = Depends(verify_api_key),
396
  _app_check_ok: bool = Depends(verify_app_check)
397
  ):
398
  """Upload and store target video in MongoDB"""
 
430
  async def start_face_swap(
431
  request: FaceSwapRequest,
432
  background_tasks: BackgroundTasks,
433
+ api_key: dict = Depends(verify_api_key),
434
  _app_check_ok: bool = Depends(verify_app_check)
435
  ):
436
  """Start face swap processing"""
 
475
  raise HTTPException(status_code=500, detail=f"Error starting face swap: {str(e)}")
476
 
477
  @app.get("/api/job/{job_id}", response_model=JobStatus)
478
+ async def get_job_status(job_id: str, api_key: dict = Depends(verify_api_key), _app_check_ok: bool = Depends(verify_app_check)):
479
  """Get job status"""
480
  job = await jobs_collection.find_one({"job_id": job_id})
481
  if not job:
 
495
  )
496
 
497
  @app.get("/api/result-video/{result_video_id}")
498
+ async def get_result_video(result_video_id: str, api_key: dict = Depends(verify_api_key), _app_check_ok: bool = Depends(verify_app_check)):
499
  """Get result video file"""
500
  result = await result_videos_collection.find_one({"_id": ObjectId(result_video_id)})
501
  if not result:
 
511
  )
512
 
513
  @app.get("/api/source-images", response_model=List[SourceImageResponse])
514
+ async def list_source_images(api_key: dict = Depends(verify_api_key), _app_check_ok: bool = Depends(verify_app_check)):
515
  """List all source images"""
516
  cursor = source_images_collection.find().sort("uploaded_at", -1)
517
  images = []
 
526
  return images
527
 
528
  @app.get("/api/target-videos", response_model=List[TargetVideoResponse])
529
+ async def list_target_videos(api_key: dict = Depends(verify_api_key), _app_check_ok: bool = Depends(verify_app_check)):
530
  """List all target videos"""
531
  cursor = target_videos_collection.find().sort("uploaded_at", -1)
532
  videos = []
 
541
  return videos
542
 
543
  @app.get("/api/result-videos", response_model=List[ResultVideoResponse])
544
+ async def list_result_videos(api_key: dict = Depends(verify_api_key), _app_check_ok: bool = Depends(verify_app_check)):
545
  """List all result videos"""
546
  cursor = result_videos_collection.find().sort("created_at", -1)
547
  results = []
 
558
  return results
559
 
560
  @app.get("/api/health")
561
+ async def api_health(api_key: dict = Depends(verify_api_key), _app_check_ok: bool = Depends(verify_app_check)):
562
  """Health check endpoint with GPU status (requires authentication)"""
563
  import onnxruntime
564
  available_providers = onnxruntime.get_available_providers()
 
604
  limit: int = 100,
605
  level: Optional[str] = None,
606
  endpoint: Optional[str] = None,
607
+ api_key: dict = Depends(verify_api_key)
608
  ):
609
  """Get API logs from MongoDB"""
610
  if api_logs_collection is None:
 
638
  except Exception as e:
639
  raise HTTPException(status_code=500, detail=f"Error fetching logs: {str(e)}")
640
 
641
+ # User info endpoint
642
+ @app.get("/api/me")
643
+ async def get_current_user(api_key: dict = Depends(verify_api_key)):
644
+ """Get current authenticated user info"""
645
+ auth_mode = api_key.get("auth_mode")
646
+
647
+ if auth_mode == "firebase":
648
+ claims = api_key.get("claims", {})
649
+ return {
650
+ "auth_mode": "firebase",
651
+ "user_id": claims.get("uid"),
652
+ "email": claims.get("email"),
653
+ "email_verified": claims.get("email_verified", False),
654
+ "name": claims.get("name"),
655
+ "picture": claims.get("picture"),
656
+ "firebase": {
657
+ "sign_in_provider": claims.get("firebase", {}).get("sign_in_provider"),
658
+ "iss": claims.get("iss"),
659
+ "aud": claims.get("aud"),
660
+ "auth_time": claims.get("auth_time"),
661
+ "exp": claims.get("exp")
662
+ }
663
+ }
664
+ else:
665
+ return {
666
+ "auth_mode": "static",
667
+ "message": "Using static API password authentication"
668
+ }
669
+
670
  if __name__ == "__main__":
671
  import uvicorn
672
  uvicorn.run(app, host="0.0.0.0", port=7860)
firebase_app_check.py CHANGED
@@ -4,7 +4,7 @@ from typing import Optional
4
 
5
  try:
6
  import firebase_admin
7
- from firebase_admin import credentials
8
  # App Check verification is available in newer firebase_admin versions
9
  try:
10
  from firebase_admin import app_check
@@ -13,6 +13,7 @@ try:
13
  except ImportError:
14
  firebase_admin = None
15
  credentials = None
 
16
  app_check = None
17
 
18
  _initialized = False
@@ -94,4 +95,27 @@ def verify_app_check_token(token: Optional[str]) -> bool:
94
  print(f"Warning: App Check token verification failed: {e}")
95
  return False
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
 
4
 
5
  try:
6
  import firebase_admin
7
+ from firebase_admin import credentials, auth
8
  # App Check verification is available in newer firebase_admin versions
9
  try:
10
  from firebase_admin import app_check
 
13
  except ImportError:
14
  firebase_admin = None
15
  credentials = None
16
+ auth = None
17
  app_check = None
18
 
19
  _initialized = False
 
95
  print(f"Warning: App Check token verification failed: {e}")
96
  return False
97
 
98
+ def verify_firebase_id_token(id_token: Optional[str]) -> Optional[dict]:
99
+ """Verify Firebase ID token and return decoded claims.
100
+
101
+ Returns:
102
+ dict: Decoded token claims (user info) if valid, None if invalid/not available
103
+ """
104
+ if not id_token:
105
+ return None
106
+
107
+ if auth is None:
108
+ return None
109
+
110
+ if not initialize_firebase():
111
+ return None
112
+
113
+ try:
114
+ # Verify the ID token
115
+ decoded_token = auth.verify_id_token(id_token)
116
+ return decoded_token
117
+ except Exception as e:
118
+ print(f"Warning: Firebase ID token verification failed: {e}")
119
+ return None
120
+
121