""" LandveX Admin Backend — Security utilities JWT tokens, password hashing, and auth helpers """ from datetime import datetime, timedelta, timezone from typing import Optional, Dict, Any from jose import JWTError, jwt from passlib.context import CryptContext from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from app.core.config import settings pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") security = HTTPBearer(auto_error=False) def verify_password(plain_password: str, hashed_password: str) -> bool: """Verify a plain password against a hashed password.""" return pwd_context.verify(plain_password, hashed_password) def get_password_hash(password: str) -> str: """Hash a password.""" return pwd_context.hash(password) def create_access_token( data: Dict[str, Any], expires_delta: Optional[timedelta] = None ) -> str: """Create a JWT access token.""" to_encode = data.copy() if expires_delta: expire = datetime.now(timezone.utc) + expires_delta else: expire = datetime.now(timezone.utc) + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) to_encode.update({"exp": expire, "type": "access"}) encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) return encoded_jwt def create_refresh_token(data: Dict[str, Any]) -> str: """Create a JWT refresh token.""" to_encode = data.copy() expire = datetime.now(timezone.utc) + timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS) to_encode.update({"exp": expire, "type": "refresh"}) encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) return encoded_jwt def decode_token(token: str) -> Optional[Dict[str, Any]]: """Decode and validate a JWT token.""" try: payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) return payload except JWTError: return None async def get_current_user( credentials: HTTPAuthorizationCredentials = Depends(security) ) -> Dict[str, Any]: """Get the current authenticated user from the JWT token.""" if credentials is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Authentication required", headers={"WWW-Authenticate": "Bearer"}, ) payload = decode_token(credentials.credentials) if payload is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or expired token", headers={"WWW-Authenticate": "Bearer"}, ) user_id = payload.get("sub") if user_id is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token payload", headers={"WWW-Authenticate": "Bearer"}, ) return { "id": user_id, "email": payload.get("email"), "role": payload.get("role", "user"), "tenant_id": payload.get("tenant_id"), "permissions": payload.get("permissions", []), } async def require_admin( current_user: Dict[str, Any] = Depends(get_current_user) ) -> Dict[str, Any]: """Require admin role.""" if current_user.get("role") != "admin": raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required" ) return current_user async def require_tenant_access( tenant_id: str, current_user: Dict[str, Any] = Depends(get_current_user) ) -> Dict[str, Any]: """Require access to a specific tenant.""" user_role = current_user.get("role", "") user_tenant = current_user.get("tenant_id") # Admin can access all tenants if user_role == "admin": return current_user # Users can only access their own tenant if user_tenant != tenant_id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=f"Access denied for tenant {tenant_id}" ) return current_user