""" Authentication endpoints. POST /auth/register – create a new account (always customer, unless the registered e-mail matches settings.admin_email) POST /auth/login – exchange credentials for a JWT Bearer token GET /auth/me – return the currently logged-in user's profile Login flow ----------- 1. Client POSTs e-mail + password as form data (OAuth2 compatible). 2. We look up the user, verify the password, and issue a JWT. 3. The admin e-mail guard: if this is the hardcoded admin address, we force is_admin=True in the DB on every login. This prevents accidental lockout. 4. Client stores the token and sends it as ``Authorization: Bearer `` on subsequent requests. """ from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm from sqlalchemy.orm import Session from app.auth.security import ( create_access_token, get_current_user, hash_password, verify_password, ) from app.config import settings from app.database import get_db from app.models.user import User from app.schemas.user import TokenResponse, UserCreate, UserRead router = APIRouter(prefix="/auth", tags=["auth"]) @router.post("/register", response_model=UserRead, status_code=status.HTTP_201_CREATED) def register(body: UserCreate, db: Session = Depends(get_db)) -> User: """ Register a new user account. - E-mail must be unique. - If the e-mail matches the configured admin address the account is immediately given admin rights (regardless of what the caller sends). - All other accounts are customers (is_admin=False). """ # Reject duplicate e-mails early with a clear error. existing = db.query(User).filter(User.email == body.email).first() if existing is not None: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="An account with this e-mail already exists", ) # The hardcoded admin e-mail is always an admin. is_admin = body.email.lower() == settings.admin_email.lower() user = User( email=body.email.lower(), display_name=body.display_name, hashed_password=hash_password(body.password), is_admin=is_admin, ) db.add(user) db.commit() db.refresh(user) return user @router.post("/login", response_model=TokenResponse) def login( form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db), ) -> dict: """ Log in and receive a JWT Bearer token. Uses the standard OAuth2 ``application/x-www-form-urlencoded`` format so that Swagger UI's "Authorize" button works without extra setup. Fields: ``username`` (= e-mail), ``password`` """ # username field holds the e-mail (OAuth2 standard naming) user = db.query(User).filter(User.email == form_data.username.lower()).first() if user is None or not verify_password(form_data.password, user.hashed_password): # Return the same generic message for both "user not found" and # "wrong password" to avoid leaking which e-mail addresses exist. raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect e-mail or password", headers={"WWW-Authenticate": "Bearer"}, ) if not user.is_active: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Account is disabled – contact the administrator", ) # Enforce the "admin e-mail is always admin" rule on every login. if user.email == settings.admin_email.lower() and not user.is_admin: user.is_admin = True db.commit() db.refresh(user) token = create_access_token(subject=user.email) return {"access_token": token, "token_type": "bearer", "user": user} @router.get("/me", response_model=UserRead) def get_me(current_user: User = Depends(get_current_user)) -> User: """Return the profile of the currently authenticated user.""" return current_user