1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-07-25 08:09:41 +02:00
mealie/mealie/routes/users/crud.py
Kuchenpirat bf616f9db5
fix: prevent users from updating their own household privileges (#4928)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2025-01-22 16:06:41 +00:00

112 lines
4.7 KiB
Python

from fastapi import Depends, HTTPException, status
from pydantic import UUID4
from mealie.core.security import hash_password
from mealie.core.security.providers.credentials_provider import CredentialsProvider
from mealie.db.models.users.users import AuthMethod
from mealie.routes._base import BaseAdminController, BaseUserController, controller
from mealie.routes._base.mixins import HttpRepo
from mealie.routes._base.routers import AdminAPIRouter, UserAPIRouter
from mealie.routes.users._helpers import assert_user_change_allowed
from mealie.schema.response import ErrorResponse, SuccessResponse
from mealie.schema.response.pagination import PaginationQuery
from mealie.schema.user import ChangePassword, UserBase, UserIn, UserOut
from mealie.schema.user.user import UserPagination, UserRatings, UserRatingSummary
user_router = UserAPIRouter(prefix="/users", tags=["Users: CRUD"])
admin_router = AdminAPIRouter(prefix="/users", tags=["Users: Admin CRUD"])
@controller(admin_router)
class AdminUserController(BaseAdminController):
@property
def mixins(self) -> HttpRepo:
return HttpRepo[UserIn, UserOut, UserBase](self.repos.users, self.logger)
@admin_router.get("", response_model=UserPagination)
def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):
"""Returns all users from all groups"""
response = self.repos.users.page_all(
pagination=q,
override=UserOut,
)
response.set_pagination_guides(admin_router.url_path_for("get_all"), q.model_dump())
return response
@admin_router.post("", response_model=UserOut, status_code=201)
def create_user(self, new_user: UserIn):
new_user.password = hash_password(new_user.password)
return self.mixins.create_one(new_user)
@admin_router.get("/{item_id}", response_model=UserOut)
def get_user(self, item_id: UUID4):
return self.mixins.get_one(item_id)
@admin_router.delete("/{item_id}")
def delete_user(self, item_id: UUID4):
self.mixins.delete_one(item_id)
@controller(user_router)
class UserController(BaseUserController):
@user_router.get("/self", response_model=UserOut)
def get_logged_in_user(self):
return self.user
@user_router.get("/self/ratings", response_model=UserRatings[UserRatingSummary])
def get_logged_in_user_ratings(self):
return UserRatings(ratings=self.repos.user_ratings.get_by_user(self.user.id))
@user_router.get("/self/ratings/{recipe_id}", response_model=UserRatingSummary)
def get_logged_in_user_rating_for_recipe(self, recipe_id: UUID4):
user_rating = self.repos.user_ratings.get_by_user_and_recipe(self.user.id, recipe_id)
if user_rating:
return user_rating
else:
raise HTTPException(
status.HTTP_404_NOT_FOUND,
ErrorResponse.respond("User has not rated this recipe"),
)
@user_router.get("/self/favorites", response_model=UserRatings[UserRatingSummary])
def get_logged_in_user_favorites(self):
return UserRatings(ratings=self.repos.user_ratings.get_by_user(self.user.id, favorites_only=True))
@user_router.put("/password")
def update_password(self, password_change: ChangePassword):
"""Resets the User Password"""
if self.user.password == "LDAP" or self.user.auth_method == AuthMethod.LDAP:
raise HTTPException(
status.HTTP_400_BAD_REQUEST, ErrorResponse.respond(self.t("user.ldap-update-password-unavailable"))
)
if not CredentialsProvider.verify_password(password_change.current_password, self.user.password):
raise HTTPException(
status.HTTP_400_BAD_REQUEST, ErrorResponse.respond(self.t("user.invalid-current-password"))
)
self.user.password = hash_password(password_change.new_password)
try:
self.repos.users.update_password(self.user.id, self.user.password)
except Exception as e:
raise HTTPException(
status.HTTP_400_BAD_REQUEST,
ErrorResponse.respond("Failed to update password"),
) from e
return SuccessResponse.respond(self.t("user.password-updated"))
@user_router.put("/{item_id}")
def update_user(self, item_id: UUID4, new_data: UserBase):
assert_user_change_allowed(item_id, self.user, new_data)
try:
self.repos.users.update(item_id, new_data.model_dump())
except Exception as e:
raise HTTPException(
status.HTTP_400_BAD_REQUEST,
ErrorResponse.respond("Failed to update user"),
) from e
return SuccessResponse.respond(self.t("user.user-updated"))