| from fastapi import APIRouter, Depends, HTTPException, status |
| from sqlalchemy.orm import Session |
| from sqlalchemy.exc import IntegrityError |
| from datetime import datetime |
|
|
| from auth.database import get_db |
| from auth.models import User |
| from auth.schemas import ( |
| SignupSchema, |
| LoginSchema, |
| TokenSchema, |
| ) |
| from auth.security import ( |
| hash_password, |
| verify_password, |
| create_access_token, |
| decode_token, |
| ) |
|
|
| router = APIRouter( |
| prefix="/api/auth", |
| tags=["Authentication"], |
| ) |
|
|
|
|
| |
| |
| |
|
|
| @router.post("/signup", status_code=201) |
| def signup( |
| data: SignupSchema, |
| db: Session = Depends(get_db), |
| ): |
|
|
| try: |
|
|
| |
| |
| |
|
|
| email = data.email.strip().lower() |
| username = data.username.strip() |
|
|
| |
| |
| |
|
|
| existing_email = ( |
| db.query(User) |
| .filter(User.email == email) |
| .first() |
| ) |
|
|
| if existing_email: |
| raise HTTPException( |
| status_code=status.HTTP_409_CONFLICT, |
| detail="Email already registered", |
| ) |
|
|
| |
| |
| |
|
|
| existing_username = ( |
| db.query(User) |
| .filter(User.username == username) |
| .first() |
| ) |
|
|
| if existing_username: |
| raise HTTPException( |
| status_code=status.HTTP_409_CONFLICT, |
| detail="Username already taken", |
| ) |
|
|
| |
| |
| |
|
|
| password_hash = hash_password(data.password) |
|
|
| |
| |
| |
|
|
| user = User( |
| email=email, |
| username=username, |
| password_hash=password_hash, |
| created_at=datetime.utcnow(), |
| ) |
|
|
| db.add(user) |
| db.commit() |
| db.refresh(user) |
|
|
| |
| |
| |
|
|
| token = create_access_token( |
| { |
| "sub": str(user.id), |
| "email": user.email, |
| } |
| ) |
|
|
| return { |
| "status": "success", |
| "message": "Account created successfully", |
| "token": token, |
| "user": { |
| "id": str(user.id), |
| "email": user.email, |
| "username": user.username, |
| "created_at": ( |
| user.created_at.isoformat() |
| if user.created_at |
| else None |
| ), |
| }, |
| } |
|
|
| except HTTPException: |
| raise |
|
|
| except IntegrityError as e: |
|
|
| db.rollback() |
|
|
| raise HTTPException( |
| status_code=status.HTTP_409_CONFLICT, |
| detail="User already exists", |
| ) |
|
|
| except ValueError as e: |
|
|
| db.rollback() |
|
|
| raise HTTPException( |
| status_code=status.HTTP_400_BAD_REQUEST, |
| detail=str(e), |
| ) |
|
|
| except Exception as e: |
|
|
| db.rollback() |
|
|
| print("SIGNUP ERROR:", str(e)) |
|
|
| raise HTTPException( |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| detail=f"Signup failed: {str(e)}", |
| ) |
|
|
|
|
| |
| |
| |
|
|
| @router.post("/login") |
| def login( |
| data: LoginSchema, |
| db: Session = Depends(get_db), |
| ): |
|
|
| try: |
|
|
| email = data.email.strip().lower() |
|
|
| user = ( |
| db.query(User) |
| .filter(User.email == email) |
| .first() |
| ) |
|
|
| if not user: |
|
|
| raise HTTPException( |
| status_code=status.HTTP_401_UNAUTHORIZED, |
| detail="Invalid email or password", |
| ) |
|
|
| if not verify_password( |
| data.password, |
| user.password_hash, |
| ): |
|
|
| raise HTTPException( |
| status_code=status.HTTP_401_UNAUTHORIZED, |
| detail="Invalid email or password", |
| ) |
|
|
| token = create_access_token( |
| { |
| "sub": str(user.id), |
| "email": user.email, |
| } |
| ) |
|
|
| return { |
| "status": "success", |
| "message": "Login successful", |
| "token": token, |
| "user": { |
| "id": str(user.id), |
| "email": user.email, |
| "username": user.username, |
| "created_at": ( |
| user.created_at.isoformat() |
| if user.created_at |
| else None |
| ), |
| }, |
| } |
|
|
| except HTTPException: |
| raise |
|
|
| except Exception as e: |
|
|
| print("LOGIN ERROR:", str(e)) |
|
|
| raise HTTPException( |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| detail=f"Login failed: {str(e)}", |
| ) |
|
|
|
|
| |
| |
| |
|
|
| @router.post("/verify") |
| def verify_token( |
| data: TokenSchema, |
| ): |
|
|
| try: |
|
|
| payload = decode_token(data.token) |
|
|
| if not payload: |
|
|
| raise HTTPException( |
| status_code=status.HTTP_401_UNAUTHORIZED, |
| detail="Invalid token", |
| ) |
|
|
| return { |
| "valid": True, |
| "payload": payload, |
| } |
|
|
| except HTTPException: |
| raise |
|
|
| except Exception as e: |
|
|
| print("VERIFY ERROR:", str(e)) |
|
|
| raise HTTPException( |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| detail=f"Token verification failed: {str(e)}", |
| ) |
|
|
|
|
| |
| |
| |
|
|
| @router.post("/refresh") |
| def refresh_token( |
| data: TokenSchema, |
| db: Session = Depends(get_db), |
| ): |
|
|
| try: |
|
|
| payload = decode_token(data.token) |
|
|
| user_id = payload.get("sub") |
|
|
| if not user_id: |
|
|
| raise HTTPException( |
| status_code=status.HTTP_401_UNAUTHORIZED, |
| detail="Invalid token payload", |
| ) |
|
|
| user = ( |
| db.query(User) |
| .filter(User.id == user_id) |
| .first() |
| ) |
|
|
| if not user: |
|
|
| raise HTTPException( |
| status_code=status.HTTP_404_NOT_FOUND, |
| detail="User not found", |
| ) |
|
|
| new_token = create_access_token( |
| { |
| "sub": str(user.id), |
| "email": user.email, |
| } |
| ) |
|
|
| return { |
| "status": "success", |
| "token": new_token, |
| } |
|
|
| except HTTPException: |
| raise |
|
|
| except Exception as e: |
|
|
| print("REFRESH ERROR:", str(e)) |
|
|
| raise HTTPException( |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| detail=f"Refresh failed: {str(e)}", |
| ) |
|
|
|
|
| |
| |
| |
|
|
| @router.post("/logout") |
| def logout(): |
|
|
| return { |
| "status": "success", |
| "message": "Logout successful", |
| } |