M8ion deploy
Deploy backend
45dc2a1
Raw History Blame Contribute Delete
3.12 kB
"""
Real Firebase ID token verification — decoupled from LOCAL_MODE (which only governs
storage) and from REAL_SEND_ENABLED (mail transport), same independent-flag pattern as
backend/mailer_client.py. A request either carries a real Firebase ID token
(REAL_AUTH_ENABLED=true) or, for local/dev/test convenience, an X-Dev-Uid header
(REAL_AUTH_ENABLED=false — the default in tests, see tests/conftest.py).
"""
from __future__ import annotations
import os
import firebase_admin
from fastapi import Header, HTTPException
from firebase_admin import auth as firebase_auth
from backend.firebase_app import get_app
def real_auth_enabled() -> bool:
return os.getenv("REAL_AUTH_ENABLED", "false").lower() == "true"
class AuthError(Exception):
pass
def verify_id_token(token: str) -> dict:
get_app()
try:
return firebase_auth.verify_id_token(token)
except firebase_admin.exceptions.FirebaseError as exc:
raise AuthError(str(exc)) from exc
def current_uid(
authorization: str | None = Header(default=None),
x_dev_uid: str = Header(default="dev-user-1", alias="X-Dev-Uid"),
) -> str:
"""FastAPI dependency — every route that needs "who is calling" uses this instead of
reading X-Dev-Uid directly, so the real/local branch lives in exactly one place."""
if not real_auth_enabled():
return x_dev_uid
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing bearer token")
token = authorization[len("Bearer "):]
try:
claims = verify_id_token(token)
except AuthError as exc:
raise HTTPException(status_code=401, detail=f"Invalid token: {exc}") from exc
# Password sign-ups must prove they own the address before touching anything real.
if not claims.get("email_verified", False):
raise HTTPException(status_code=403, detail="Email not verified")
return claims["uid"]
def current_email(
authorization: str | None = Header(default=None),
x_dev_email: str = Header(default="", alias="X-Dev-Email"),
) -> str:
"""The signed-in address (checked to be verified by current_uid). Used where an
action depends on who the person is, such as accepting an invite."""
if not real_auth_enabled():
return x_dev_email
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing bearer token")
try:
claims = verify_id_token(authorization[len("Bearer "):])
except AuthError as exc:
raise HTTPException(status_code=401, detail=f"Invalid token: {exc}") from exc
return claims.get("email", "")
def lookup_user(uid: str) -> dict:
"""{email, name} for a uid, best effort; empty when unknown or running locally."""
if not real_auth_enabled():
return {}
try:
get_app()
record = firebase_auth.get_user(uid)
except Exception: # noqa: BLE001 — a missing name must not break the team list
return {}
return {"email": record.email or "", "name": record.display_name or ""}