from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm from sqlalchemy.orm import Session from app.core.database import get_db from app.core.security import verify_password, get_password_hash, create_access_token from app.core.deps import get_current_active_user from app.models.user import User from app.models.tenant import Tenant from app.schemas.auth import Token, UserLogin, UserRegister from app.schemas.user import UserRead, UserUpdate router = APIRouter() @router.post("/register", response_model=UserRead, status_code=status.HTTP_201_CREATED) async def register( user_data: UserRegister, db: Session = Depends(get_db) ): """ Register a new user Creates a new user with hashed password. If tenant_id is not provided, a default tenant is created or used. """ # Check if username already exists existing_user = db.query(User).filter(User.username == user_data.username).first() if existing_user: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Username already registered" ) # Handle tenant_id tenant_id = user_data.tenant_id if tenant_id is None: # Create or get default tenant default_tenant = db.query(Tenant).filter(Tenant.name == "default").first() if not default_tenant: default_tenant = Tenant(name="default", description="Default tenant") db.add(default_tenant) db.commit() db.refresh(default_tenant) tenant_id = default_tenant.id else: # Verify tenant exists tenant = db.query(Tenant).filter(Tenant.id == tenant_id).first() if not tenant: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Tenant not found" ) # Create new user with hashed password hashed_password = get_password_hash(user_data.password) new_user = User( username=user_data.username, password_hash=hashed_password, role=user_data.role, tenant_id=tenant_id ) db.add(new_user) db.commit() db.refresh(new_user) return new_user @router.post("/login", response_model=Token) async def login( form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db) ): """ Authenticate user and return JWT token Uses OAuth2 password flow for compatibility with OpenAPI docs. """ # Find user by username user = db.query(User).filter(User.username == form_data.username).first() if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password", headers={"WWW-Authenticate": "Bearer"}, ) # Verify password if not verify_password(form_data.password, user.password_hash): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password", headers={"WWW-Authenticate": "Bearer"}, ) # Check if user is active if not user.is_active: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Inactive user" ) # Create access token access_token = create_access_token( data={ "sub": user.id, "tenant_id": user.tenant_id, "role": user.role } ) return {"access_token": access_token, "token_type": "bearer"} @router.get("/me", response_model=UserRead) async def get_current_user_profile( current_user: User = Depends(get_current_active_user) ): """ Get current user profile Returns the profile of the authenticated user. """ return current_user @router.put("/me", response_model=UserRead) async def update_current_user_profile( user_update: UserUpdate, current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db) ): """ Update current user profile Allows users to update their own profile information. """ # Update username if provided if user_update.username is not None: # Check if new username is already taken existing_user = db.query(User).filter( User.username == user_update.username, User.id != current_user.id ).first() if existing_user: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Username already taken" ) current_user.username = user_update.username # Update password if provided if user_update.password is not None: current_user.password_hash = get_password_hash(user_update.password) # Users cannot change their own role through this endpoint # (admin users should use the admin endpoints in /users) db.commit() db.refresh(current_user) return current_user