Spaces:
Sleeping
Sleeping
File size: 3,652 Bytes
a17af42 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | """
app/routes/auth.py
Auth endpoints: OTP request, OTP verify, logout, profile.
Wraps app/services/auth.py logic.
"""
import logging
from fastapi import APIRouter, Request, Form, HTTPException
from fastapi.responses import JSONResponse
from app.services.auth import (
send_email_otp, verify_email_otp,
create_session, revoke_session, get_user_from_token,
)
from app.models.db import db_conn
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/auth", tags=["auth"])
@router.post("/request-otp")
async def request_otp(email: str = Form(...)):
"""Send a 6-digit OTP to the user's email. Dev mode: returns OTP in response."""
try:
otp = send_email_otp(email)
return JSONResponse({
"sent" : True,
"message": "OTP sent. Check your email.",
# Remove next line in production — only for dev/HF testing
"_dev_otp": otp,
})
except Exception as exc:
logger.error("OTP request failed: %s", exc)
raise HTTPException(status_code=500, detail="Could not send OTP")
@router.post("/verify-otp")
async def verify_otp(
request: Request,
email: str = Form(...),
otp : str = Form(...),
):
"""Verify OTP. Returns session token on success."""
user = verify_email_otp(email, otp)
if not user:
raise HTTPException(status_code=401, detail="Invalid or expired OTP")
device_hint = request.headers.get("user-agent", "")[:100]
token = create_session(user["id"], device_hint)
return JSONResponse({
"token" : token,
"user_id" : user["id"],
"email" : user.get("email", ""),
"is_pro" : bool(user.get("is_pro", 0)),
"message" : "Login successful",
})
@router.post("/logout")
async def logout(request: Request):
"""Revoke the current session token."""
auth = request.headers.get("Authorization", "")
token = auth.removeprefix("Bearer ").strip() if auth.startswith("Bearer ") else None
if token:
revoke_session(token)
return JSONResponse({"logged_out": True})
@router.get("/me")
async def get_me(request: Request):
"""Return current user profile from Bearer token."""
auth = request.headers.get("Authorization", "")
token = auth.removeprefix("Bearer ").strip() if auth.startswith("Bearer ") else None
user = get_user_from_token(token) if token else None
if not user:
raise HTTPException(status_code=401, detail="Not authenticated")
return JSONResponse({
"user_id" : user["id"],
"email" : user.get("email", ""),
"name" : user.get("name", ""),
"is_pro" : bool(user.get("is_pro", 0)),
"streak_days": user.get("streak_days", 0),
"persona" : user.get("persona", "General Adult"),
"language" : user.get("language", "en"),
})
@router.put("/profile")
async def update_profile(
request : Request,
name : str = Form(""),
persona : str = Form("General Adult"),
language: str = Form("en"),
tdee : float = Form(2000),
):
"""Update user profile fields."""
auth = request.headers.get("Authorization", "")
token = auth.removeprefix("Bearer ").strip() if auth.startswith("Bearer ") else None
user = get_user_from_token(token) if token else None
if not user:
raise HTTPException(status_code=401, detail="Not authenticated")
with db_conn() as conn:
conn.execute(
"UPDATE users SET name=?, persona=?, language=?, tdee=? WHERE id=?",
(name, persona, language, tdee, user["id"])
)
return JSONResponse({"updated": True})
|