First commit
This commit is contained in:
@@ -0,0 +1,333 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from jose import JWTError, jwt
|
||||
from passlib.context import CryptContext
|
||||
from fastapi import HTTPException, status
|
||||
from datetime import datetime, timedelta
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from models import User, UserAudit
|
||||
from schemas import UserCreate, UserUpdate
|
||||
|
||||
# Configuración de seguridad
|
||||
SECRET_KEY = os.getenv("SECRET_KEY")
|
||||
if not SECRET_KEY:
|
||||
raise RuntimeError("SECRET_KEY no configurada. Abortando inicio.")
|
||||
if len(SECRET_KEY) < 20:
|
||||
raise RuntimeError("SECRET_KEY demasiado corta")
|
||||
ALGORITHM = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
class AuthService:
|
||||
@staticmethod
|
||||
def hash_password(password: str) -> str:
|
||||
return pwd_context.hash(password)
|
||||
|
||||
@staticmethod
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
@staticmethod
|
||||
def authenticate_user(db: Session, email: str, password: str) -> Optional[User]:
|
||||
user = db.query(User).filter(User.email == email, User.active == True).first()
|
||||
if not user:
|
||||
return None
|
||||
if not AuthService.verify_password(password, user.password):
|
||||
return None
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
|
||||
to_encode = data.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
|
||||
to_encode.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
@staticmethod
|
||||
def verify_token(token: str):
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
try:
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
user_id: int = payload.get("user_id")
|
||||
if user_id is None:
|
||||
raise credentials_exception
|
||||
return user_id
|
||||
except JWTError:
|
||||
raise credentials_exception
|
||||
|
||||
class UserService:
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
def get_user_by_id(self, user_id: int) -> Optional[User]:
|
||||
return self.db.query(User).filter(User.id == user_id).first()
|
||||
|
||||
def get_user_by_email(self, email: str) -> Optional[User]:
|
||||
return self.db.query(User).filter(User.email == email).first()
|
||||
|
||||
def get_all_users(self, skip: int = 0, limit: int = 100):
|
||||
return self.db.query(User).offset(skip).limit(limit).all()
|
||||
|
||||
def get_pending_users(self, skip: int = 0, limit: int = 100):
|
||||
return self.db.query(User).filter(User.active == False).offset(skip).limit(limit).all()
|
||||
|
||||
def count_active_admins(self) -> int:
|
||||
"""Helper function to count the number of active admin users."""
|
||||
return self.db.query(User).filter(User.rol == 'admin', User.active == True).count()
|
||||
|
||||
def create_user(self, user_data: UserCreate, updater_id: Optional[int] = None, ip_address: str = None) -> User:
|
||||
if self.get_user_by_email(user_data.email):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Failed to create user"
|
||||
)
|
||||
|
||||
if user_data.rol not in ['admin', 'operator']:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid role specified"
|
||||
)
|
||||
|
||||
if user_data.password and len(user_data.password) < 8:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Password must be at least 8 characters long"
|
||||
)
|
||||
|
||||
hashed_password = AuthService.hash_password(user_data.password)
|
||||
|
||||
user = User(
|
||||
name=user_data.name,
|
||||
email=user_data.email,
|
||||
password=hashed_password,
|
||||
rol=user_data.rol,
|
||||
active=user_data.active
|
||||
)
|
||||
|
||||
self.db.add(user)
|
||||
self.db.flush()
|
||||
|
||||
# If no updater_id provided (unauthenticated creation), mark the user as the updater (self-created)
|
||||
audit_updater = updater_id if updater_id is not None else user.id
|
||||
|
||||
audit = UserAudit(
|
||||
user_id=user.id,
|
||||
updater_id=audit_updater,
|
||||
action="create",
|
||||
after_value=f"User created with role: {user.rol}",
|
||||
snapshot={
|
||||
"name": user.name,
|
||||
"email": user.email,
|
||||
"rol": user.rol,
|
||||
"active": user.active
|
||||
},
|
||||
ip_address=ip_address
|
||||
)
|
||||
self.db.add(audit)
|
||||
self.db.commit()
|
||||
self.db.refresh(user)
|
||||
|
||||
return user
|
||||
|
||||
def update_user(self, user_id: int, user_data: UserUpdate, updater_id: int, ip_address: str = None) -> User:
|
||||
user = self.get_user_by_id(user_id)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="User not found"
|
||||
)
|
||||
|
||||
if user_data.rol not in ['admin', 'operator']:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid role specified"
|
||||
)
|
||||
|
||||
if user_data.password and len(user_data.password) < 8:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Password must be at least 8 characters long"
|
||||
)
|
||||
|
||||
changes = []
|
||||
snapshot = {
|
||||
"name": user.name,
|
||||
"email": user.email,
|
||||
"rol": user.rol,
|
||||
"active": user.active
|
||||
}
|
||||
|
||||
for field, value in user_data.dict(exclude_unset=True).items():
|
||||
if hasattr(user, field):
|
||||
old_value = getattr(user, field)
|
||||
|
||||
if field == "password":
|
||||
value = AuthService.hash_password(value)
|
||||
old_value = "***"
|
||||
|
||||
if old_value != value:
|
||||
setattr(user, field, value)
|
||||
|
||||
audit = UserAudit(
|
||||
user_id=user_id,
|
||||
updater_id=updater_id,
|
||||
action="update",
|
||||
updated_attribute=field,
|
||||
before_value=str(old_value),
|
||||
after_value=str(value) if field != "password" else "***",
|
||||
snapshot=snapshot,
|
||||
ip_address=ip_address
|
||||
)
|
||||
self.db.add(audit)
|
||||
changes.append(field)
|
||||
|
||||
if changes:
|
||||
self.db.commit()
|
||||
self.db.refresh(user)
|
||||
|
||||
return user
|
||||
|
||||
def change_user_role(self, user_id: int, new_role: str, updater_id: int, ip_address: str = None) -> User:
|
||||
user = self.get_user_by_id(user_id)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="User not found"
|
||||
)
|
||||
|
||||
old_role = user.rol
|
||||
snapshot = {
|
||||
"name": user.name,
|
||||
"email": user.email,
|
||||
"rol": user.rol,
|
||||
"active": user.active
|
||||
}
|
||||
|
||||
user.rol = new_role
|
||||
|
||||
audit = UserAudit(
|
||||
user_id=user_id,
|
||||
updater_id=updater_id,
|
||||
action="role_change",
|
||||
updated_attribute="rol",
|
||||
before_value=old_role,
|
||||
after_value=new_role,
|
||||
snapshot=snapshot,
|
||||
ip_address=ip_address
|
||||
)
|
||||
self.db.add(audit)
|
||||
self.db.commit()
|
||||
self.db.refresh(user)
|
||||
|
||||
return user
|
||||
|
||||
def deactivate_user(self, user_id: int, updater_id: int, ip_address: str = None) -> User:
|
||||
user = self.get_user_by_id(user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
user.active = False
|
||||
|
||||
audit = UserAudit(
|
||||
user_id=user_id,
|
||||
updater_id=updater_id,
|
||||
action="deactivate",
|
||||
updated_attribute="active",
|
||||
before_value="True",
|
||||
after_value="False",
|
||||
snapshot={
|
||||
"name": user.name,
|
||||
"email": user.email,
|
||||
"rol": user.rol,
|
||||
"active": True
|
||||
},
|
||||
ip_address=ip_address
|
||||
)
|
||||
self.db.add(audit)
|
||||
self.db.commit()
|
||||
self.db.refresh(user)
|
||||
|
||||
return user
|
||||
|
||||
def activate_user(self, user_id: int, updater_id: int, ip_address: str = None) -> User:
|
||||
user = self.get_user_by_id(user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
user.active = True
|
||||
|
||||
audit = UserAudit(
|
||||
user_id=user_id,
|
||||
updater_id=updater_id,
|
||||
action="activate",
|
||||
updated_attribute="active",
|
||||
before_value="False",
|
||||
after_value="True",
|
||||
snapshot={
|
||||
"name": user.name,
|
||||
"email": user.email,
|
||||
"rol": user.rol,
|
||||
"active": False
|
||||
},
|
||||
ip_address=ip_address
|
||||
)
|
||||
self.db.add(audit)
|
||||
self.db.commit()
|
||||
self.db.refresh(user)
|
||||
|
||||
return user
|
||||
|
||||
def get_user_modifications(self, user_id: int, skip: int = 0, limit: int = 50):
|
||||
user = self.get_user_by_id(user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
return (self.db.query(UserAudit)
|
||||
.filter(UserAudit.user_id == user_id)
|
||||
.order_by(UserAudit.modification_date.desc())
|
||||
.offset(skip)
|
||||
.limit(limit)
|
||||
.all())
|
||||
|
||||
def get_users_modifications(self, skip: int = 0, limit: int = 100):
|
||||
return (self.db.query(UserAudit)
|
||||
.order_by(UserAudit.modification_date.desc())
|
||||
.offset(skip)
|
||||
.limit(limit)
|
||||
.all())
|
||||
|
||||
def delete_user(self, user_id: int, updater_id: int, ip_address: str = None):
|
||||
user = self.get_user_by_id(user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
snapshot = {
|
||||
"name": user.name,
|
||||
"email": user.email,
|
||||
"rol": user.rol,
|
||||
"active": user.active
|
||||
}
|
||||
|
||||
self.db.delete(user)
|
||||
|
||||
audit = UserAudit(
|
||||
user_id=user_id,
|
||||
updater_id=updater_id,
|
||||
action="delete",
|
||||
after_value="User deleted",
|
||||
snapshot=snapshot,
|
||||
ip_address=ip_address
|
||||
)
|
||||
self.db.add(audit)
|
||||
self.db.commit()
|
||||
Reference in New Issue
Block a user