diff --git a/mealie/core/dependencies/dependencies.py b/mealie/core/dependencies/dependencies.py index 951140624baa..7b575483533b 100644 --- a/mealie/core/dependencies/dependencies.py +++ b/mealie/core/dependencies/dependencies.py @@ -9,7 +9,7 @@ from sqlalchemy.orm.session import Session from mealie.core.config import app_dirs, settings from mealie.db.database import db from mealie.db.db_setup import generate_session -from mealie.schema.user import LongLiveTokenInDB, TokenData, UserInDB +from mealie.schema.user import LongLiveTokenInDB, TokenData, PrivateUser oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/token") oauth2_scheme_soft_fail = OAuth2PasswordBearer(tokenUrl="/api/auth/token", auto_error=False) @@ -48,7 +48,7 @@ async def is_logged_in(token: str = Depends(oauth2_scheme_soft_fail), session=De return False -async def get_current_user(token: str = Depends(oauth2_scheme), session=Depends(generate_session)) -> UserInDB: +async def get_current_user(token: str = Depends(oauth2_scheme), session=Depends(generate_session)) -> PrivateUser: credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", @@ -75,13 +75,13 @@ async def get_current_user(token: str = Depends(oauth2_scheme), session=Depends( return user -async def get_admin_user(current_user=Depends(get_current_user)) -> UserInDB: +async def get_admin_user(current_user=Depends(get_current_user)) -> PrivateUser: if not current_user.admin: raise HTTPException(status.HTTP_403_FORBIDDEN) return current_user -def validate_long_live_token(session: Session, client_token: str, id: int) -> UserInDB: +def validate_long_live_token(session: Session, client_token: str, id: int) -> PrivateUser: tokens: list[LongLiveTokenInDB] = db.api_tokens.get(session, id, "parent_id", limit=9999) diff --git a/mealie/core/dependencies/grouped.py b/mealie/core/dependencies/grouped.py index 05b8999d7c7a..6bfaefe763d8 100644 --- a/mealie/core/dependencies/grouped.py +++ b/mealie/core/dependencies/grouped.py @@ -1,6 +1,8 @@ from fastapi import BackgroundTasks, Depends from sqlalchemy.orm.session import Session +from mealie.schema.user.user import PrivateUser + from .dependencies import generate_session, get_current_user, is_logged_in @@ -21,9 +23,9 @@ class ReadDeps: session: Session = Depends(generate_session), user=Depends(is_logged_in), ): - self.session = session - self.background_tasks = background_tasks - self.user = user + self.session: Session = session + self.bg_tasks: BackgroundTasks = background_tasks + self.user: bool = user class WriteDeps: @@ -34,7 +36,7 @@ class WriteDeps: Args: background_tasks: BackgroundTasks session: Session - user: bool + user: UserInDB """ def __init__( @@ -43,6 +45,6 @@ class WriteDeps: session: Session = Depends(generate_session), user=Depends(get_current_user), ): - self.session = session - self.background_tasks = background_tasks - self.user = user + self.session: Session = session + self.bg_task: BackgroundTasks = background_tasks + self.user: PrivateUser = user diff --git a/mealie/core/security.py b/mealie/core/security.py index 48bcfc939b34..6a56f86a3cb3 100644 --- a/mealie/core/security.py +++ b/mealie/core/security.py @@ -6,7 +6,7 @@ from passlib.context import CryptContext from mealie.core.config import settings from mealie.db.database import db -from mealie.schema.user import UserInDB +from mealie.schema.user import PrivateUser pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") ALGORITHM = "HS256" @@ -27,8 +27,8 @@ def create_file_token(file_path: Path) -> bool: return create_access_token(token_data, expires_delta=timedelta(minutes=30)) -def authenticate_user(session, email: str, password: str) -> UserInDB: - user: UserInDB = db.users.get(session, email, "email", any_case=True) +def authenticate_user(session, email: str, password: str) -> PrivateUser: + user: PrivateUser = db.users.get(session, email, "email", any_case=True) if not user: user = db.users.get(session, email, "username", any_case=True) @@ -53,7 +53,7 @@ def verify_password(plain_password: str, hashed_password: str) -> bool: return pwd_context.verify(plain_password, hashed_password) -def get_password_hash(password: str) -> str: +def hash_password(password: str) -> str: """Takes in a raw password and hashes it. Used prior to saving a new password to the database. diff --git a/mealie/db/data_access_layer/db_access.py b/mealie/db/data_access_layer/db_access.py index f03cbb5fa1f6..d88b0e3b1fc3 100644 --- a/mealie/db/data_access_layer/db_access.py +++ b/mealie/db/data_access_layer/db_access.py @@ -27,7 +27,7 @@ from mealie.schema.recipe import ( RecipeCategoryResponse, RecipeTagResponse, ) -from mealie.schema.user import GroupInDB, LongLiveTokenInDB, SignUpOut, UserInDB +from mealie.schema.user import GroupInDB, LongLiveTokenInDB, SignUpOut, PrivateUser from ._base_access_model import BaseAccessModel from .recipe_access_model import RecipeDataAccessModel @@ -78,7 +78,7 @@ class DatabaseAccessLayer: self.events = BaseAccessModel(DEFAULT_PK, Event, EventSchema) # Users / Groups - self.users = UserDataAccessModel(DEFAULT_PK, User, UserInDB) + self.users = UserDataAccessModel(DEFAULT_PK, User, PrivateUser) self.api_tokens = BaseAccessModel(DEFAULT_PK, LongLiveToken, LongLiveTokenInDB) self.groups = GroupDataAccessModel(DEFAULT_PK, Group, GroupInDB) self.meals = BaseAccessModel(DEFAULT_PK, MealPlan, MealPlanOut) diff --git a/mealie/db/data_access_layer/user_access_model.py b/mealie/db/data_access_layer/user_access_model.py index 067d6d7e41a7..f66dbfd06936 100644 --- a/mealie/db/data_access_layer/user_access_model.py +++ b/mealie/db/data_access_layer/user_access_model.py @@ -1,10 +1,10 @@ from mealie.db.models.users import User -from mealie.schema.user.user import UserInDB +from mealie.schema.user.user import PrivateUser from ._base_access_model import BaseAccessModel -class UserDataAccessModel(BaseAccessModel[UserInDB, User]): +class UserDataAccessModel(BaseAccessModel[PrivateUser, User]): def update_password(self, session, id, password: str): entry = self._query_one(session=session, match_value=id) entry.update_password(password) diff --git a/mealie/db/init_db.py b/mealie/db/init_db.py index 6b0c73ada9e9..26c9bdc628e9 100644 --- a/mealie/db/init_db.py +++ b/mealie/db/init_db.py @@ -2,7 +2,7 @@ from sqlalchemy.orm import Session from mealie.core import root_logger from mealie.core.config import settings -from mealie.core.security import get_password_hash +from mealie.core.security import hash_password from mealie.db.data_initialization.init_units_foods import default_recipe_unit_init from mealie.db.database import db from mealie.db.db_setup import create_session, engine @@ -48,7 +48,7 @@ def default_user_init(session: Session): "full_name": "Change Me", "username": "admin", "email": settings.DEFAULT_EMAIL, - "password": get_password_hash(settings.DEFAULT_PASSWORD), + "password": hash_password(settings.DEFAULT_PASSWORD), "group": settings.DEFAULT_GROUP, "admin": True, } diff --git a/mealie/routes/auth/auth.py b/mealie/routes/auth/auth.py index f4994ee6d812..0a1065293f01 100644 --- a/mealie/routes/auth/auth.py +++ b/mealie/routes/auth/auth.py @@ -8,7 +8,7 @@ from mealie.core.dependencies import get_current_user from mealie.core.security import authenticate_user from mealie.db.db_setup import generate_session from mealie.routes.routers import UserAPIRouter -from mealie.schema.user import UserInDB +from mealie.schema.user import PrivateUser from mealie.services.events import create_user_event public_router = APIRouter(tags=["Users: Authentication"]) @@ -26,7 +26,7 @@ def get_token( email = data.username password = data.password - user: UserInDB = authenticate_user(session, email, password) + user: PrivateUser = authenticate_user(session, email, password) if not user: background_tasks.add_task( @@ -42,7 +42,7 @@ def get_token( @user_router.get("/refresh") -async def refresh_token(current_user: UserInDB = Depends(get_current_user)): +async def refresh_token(current_user: PrivateUser = Depends(get_current_user)): """ Use a valid token to get another token""" access_token = security.create_access_token(data=dict(sub=current_user.email)) return {"access_token": access_token, "token_type": "bearer"} diff --git a/mealie/routes/groups/crud.py b/mealie/routes/groups/crud.py index 648fa200e127..c5786ae747e5 100644 --- a/mealie/routes/groups/crud.py +++ b/mealie/routes/groups/crud.py @@ -5,7 +5,7 @@ from mealie.core.dependencies import get_current_user from mealie.db.database import db from mealie.db.db_setup import generate_session from mealie.routes.routers import AdminAPIRouter, UserAPIRouter -from mealie.schema.user import GroupBase, GroupInDB, UpdateGroup, UserInDB +from mealie.schema.user import GroupBase, GroupInDB, UpdateGroup, PrivateUser from mealie.services.events import create_group_event admin_router = AdminAPIRouter(prefix="/groups", tags=["Groups: CRUD"]) @@ -14,11 +14,11 @@ user_router = UserAPIRouter(prefix="/groups", tags=["Groups: CRUD"]) @user_router.get("/self", response_model=GroupInDB) async def get_current_user_group( - current_user: UserInDB = Depends(get_current_user), + current_user: PrivateUser = Depends(get_current_user), session: Session = Depends(generate_session), ): """ Returns the Group Data for the Current User """ - current_user: UserInDB + current_user: PrivateUser return db.groups.get(session, current_user.group, "name") @@ -62,7 +62,7 @@ async def update_group_data( async def delete_user_group( background_tasks: BackgroundTasks, id: int, - current_user: UserInDB = Depends(get_current_user), + current_user: PrivateUser = Depends(get_current_user), session: Session = Depends(generate_session), ): """ Removes a user group from the database """ diff --git a/mealie/routes/mealplans/crud.py b/mealie/routes/mealplans/crud.py index 4813050b4611..fba4b7d39fe3 100644 --- a/mealie/routes/mealplans/crud.py +++ b/mealie/routes/mealplans/crud.py @@ -7,7 +7,7 @@ from mealie.db.database import db from mealie.db.db_setup import generate_session from mealie.routes.routers import UserAPIRouter from mealie.schema.meal_plan import MealPlanIn, MealPlanOut -from mealie.schema.user import GroupInDB, UserInDB +from mealie.schema.user import GroupInDB, PrivateUser from mealie.services.events import create_group_event from mealie.services.image import image from mealie.services.meal_services import get_todays_meal, set_mealplan_dates @@ -18,7 +18,7 @@ public_router = APIRouter(prefix="/api/meal-plans", tags=["Meal Plan"]) @router.get("/all", response_model=list[MealPlanOut]) def get_all_meals( - current_user: UserInDB = Depends(get_current_user), + current_user: PrivateUser = Depends(get_current_user), session: Session = Depends(generate_session), ): """ Returns a list of all available Meal Plan """ @@ -27,7 +27,7 @@ def get_all_meals( @router.get("/this-week", response_model=MealPlanOut) -def get_this_week(session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)): +def get_this_week(session: Session = Depends(generate_session), current_user: PrivateUser = Depends(get_current_user)): """ Returns the meal plan data for this week """ plans = db.groups.get_meals(session, current_user.group) if plans: @@ -35,7 +35,7 @@ def get_this_week(session: Session = Depends(generate_session), current_user: Us @router.get("/today", tags=["Meal Plan"]) -def get_today(session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)): +def get_today(session: Session = Depends(generate_session), current_user: PrivateUser = Depends(get_current_user)): """ Returns the recipe slug for the meal scheduled for today. If no meal is scheduled nothing is returned @@ -78,7 +78,7 @@ def create_meal_plan( background_tasks: BackgroundTasks, data: MealPlanIn, session: Session = Depends(generate_session), - current_user: UserInDB = Depends(get_current_user), + current_user: PrivateUser = Depends(get_current_user), ): """ Creates a meal plan database entry """ set_mealplan_dates(data) @@ -94,7 +94,7 @@ def update_meal_plan( plan_id: str, meal_plan: MealPlanIn, session: Session = Depends(generate_session), - current_user: UserInDB = Depends(get_current_user), + current_user: PrivateUser = Depends(get_current_user), ): """ Updates a meal plan based off ID """ set_mealplan_dates(meal_plan) @@ -113,7 +113,7 @@ def delete_meal_plan( background_tasks: BackgroundTasks, plan_id, session: Session = Depends(generate_session), - current_user: UserInDB = Depends(get_current_user), + current_user: PrivateUser = Depends(get_current_user), ): """ Removes a meal plan from the database """ diff --git a/mealie/routes/mealplans/helpers.py b/mealie/routes/mealplans/helpers.py index 7b7bb161c17d..0e2dbc0ee3cc 100644 --- a/mealie/routes/mealplans/helpers.py +++ b/mealie/routes/mealplans/helpers.py @@ -8,7 +8,7 @@ from mealie.db.db_setup import generate_session from mealie.routes.routers import UserAPIRouter from mealie.schema.meal_plan import ListItem, MealPlanOut, ShoppingListIn, ShoppingListOut from mealie.schema.recipe import Recipe -from mealie.schema.user import UserInDB +from mealie.schema.user import PrivateUser logger = get_logger() @@ -19,7 +19,7 @@ router = UserAPIRouter(prefix="/api/meal-plans", tags=["Meal Plan"]) def get_shopping_list( id: str, session: Session = Depends(generate_session), - current_user: UserInDB = Depends(get_current_user), + current_user: PrivateUser = Depends(get_current_user), ): mealplan: MealPlanOut = db.meals.get(session, id) diff --git a/mealie/routes/recipe/comments.py b/mealie/routes/recipe/comments.py index a50c593f21c3..d3e8ca3a3463 100644 --- a/mealie/routes/recipe/comments.py +++ b/mealie/routes/recipe/comments.py @@ -8,7 +8,7 @@ from mealie.db.database import db from mealie.db.db_setup import generate_session from mealie.routes.routers import UserAPIRouter from mealie.schema.recipe import CommentOut, CreateComment, SaveComment -from mealie.schema.user import UserInDB +from mealie.schema.user import PrivateUser router = UserAPIRouter() @@ -18,7 +18,7 @@ async def create_comment( slug: str, new_comment: CreateComment, session: Session = Depends(generate_session), - current_user: UserInDB = Depends(get_current_user), + current_user: PrivateUser = Depends(get_current_user), ): """ Create comment in the Database """ @@ -31,7 +31,7 @@ async def update_comment( id: int, new_comment: CreateComment, session: Session = Depends(generate_session), - current_user: UserInDB = Depends(get_current_user), + current_user: PrivateUser = Depends(get_current_user), ): """ Update comment in the Database """ old_comment: CommentOut = db.comments.get(session, id) @@ -44,7 +44,7 @@ async def update_comment( @router.delete("/{slug}/comments/{id}") async def delete_comment( - id: int, session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user) + id: int, session: Session = Depends(generate_session), current_user: PrivateUser = Depends(get_current_user) ): """ Delete comment from the Database """ comment: CommentOut = db.comments.get(session, id) diff --git a/mealie/routes/shopping_lists/__init__.py b/mealie/routes/shopping_lists/__init__.py index daa14751f8cb..336bef277d4a 100644 --- a/mealie/routes/shopping_lists/__init__.py +++ b/mealie/routes/shopping_lists/__init__.py @@ -6,7 +6,7 @@ from mealie.db.database import db from mealie.db.db_setup import generate_session from mealie.routes.routers import UserAPIRouter from mealie.schema.meal_plan import ShoppingListIn, ShoppingListOut -from mealie.schema.user import UserInDB +from mealie.schema.user import PrivateUser router = UserAPIRouter(prefix="/shopping-lists", tags=["Shopping Lists: CRUD"]) @@ -14,7 +14,7 @@ router = UserAPIRouter(prefix="/shopping-lists", tags=["Shopping Lists: CRUD"]) @router.post("", response_model=ShoppingListOut) async def create_shopping_list( list_in: ShoppingListIn, - current_user: UserInDB = Depends(get_current_user), + current_user: PrivateUser = Depends(get_current_user), session: Session = Depends(generate_session), ): """ Create Shopping List in the Database """ diff --git a/mealie/routes/site_settings/site_settings.py b/mealie/routes/site_settings/site_settings.py index 782cb1acff71..ac912c2f0347 100644 --- a/mealie/routes/site_settings/site_settings.py +++ b/mealie/routes/site_settings/site_settings.py @@ -6,7 +6,7 @@ from mealie.db.database import db from mealie.db.db_setup import generate_session from mealie.routes.routers import AdminAPIRouter from mealie.schema.admin import SiteSettings -from mealie.schema.user import GroupInDB, UserInDB +from mealie.schema.user import GroupInDB, PrivateUser from mealie.utils.post_webhooks import post_webhooks public_router = APIRouter(prefix="/api/site-settings", tags=["Settings"]) @@ -31,7 +31,7 @@ def update_settings( @admin_router.post("/webhooks/test") def test_webhooks( - current_user: UserInDB = Depends(get_current_user), + current_user: PrivateUser = Depends(get_current_user), session: Session = Depends(generate_session), ): """ Run the function to test your webhooks """ diff --git a/mealie/routes/users/_helpers.py b/mealie/routes/users/_helpers.py index da6a6da7c531..1669f4e13468 100644 --- a/mealie/routes/users/_helpers.py +++ b/mealie/routes/users/_helpers.py @@ -1,9 +1,9 @@ from fastapi import HTTPException, status -from mealie.schema.user.user import UserInDB +from mealie.schema.user.user import PrivateUser -def assert_user_change_allowed(id: int, current_user: UserInDB): +def assert_user_change_allowed(id: int, current_user: PrivateUser): if current_user.id != id and not current_user.admin: # only admins can edit other users raise HTTPException(status.HTTP_403_FORBIDDEN, detail="NOT_AN_ADMIN") diff --git a/mealie/routes/users/api_tokens.py b/mealie/routes/users/api_tokens.py index 246a0fd0e6a1..1ba34db00245 100644 --- a/mealie/routes/users/api_tokens.py +++ b/mealie/routes/users/api_tokens.py @@ -9,7 +9,7 @@ from mealie.core.security import create_access_token from mealie.db.database import db from mealie.db.db_setup import generate_session from mealie.routes.routers import UserAPIRouter -from mealie.schema.user import CreateToken, LoingLiveTokenIn, LongLiveTokenInDB, UserInDB +from mealie.schema.user import CreateToken, LoingLiveTokenIn, LongLiveTokenInDB, PrivateUser router = UserAPIRouter() @@ -17,7 +17,7 @@ router = UserAPIRouter() @router.post("/api-tokens", status_code=status.HTTP_201_CREATED) async def create_api_token( token_name: LoingLiveTokenIn, - current_user: UserInDB = Depends(get_current_user), + current_user: PrivateUser = Depends(get_current_user), session: Session = Depends(generate_session), ): """ Create api_token in the Database """ @@ -42,7 +42,7 @@ async def create_api_token( @router.delete("/api-tokens/{token_id}") async def delete_api_token( token_id: int, - current_user: UserInDB = Depends(get_current_user), + current_user: PrivateUser = Depends(get_current_user), session: Session = Depends(generate_session), ): """ Delete api_token from the Database """ diff --git a/mealie/routes/users/crud.py b/mealie/routes/users/crud.py index 47fbf1c164c4..0582781a1225 100644 --- a/mealie/routes/users/crud.py +++ b/mealie/routes/users/crud.py @@ -3,12 +3,12 @@ from sqlalchemy.orm.session import Session from mealie.core import security from mealie.core.dependencies import get_current_user -from mealie.core.security import get_password_hash +from mealie.core.security import hash_password from mealie.db.database import db from mealie.db.db_setup import generate_session from mealie.routes.routers import AdminAPIRouter, UserAPIRouter from mealie.routes.users._helpers import assert_user_change_allowed -from mealie.schema.user import UserBase, UserIn, UserInDB, UserOut +from mealie.schema.user import UserBase, UserIn, PrivateUser, UserOut from mealie.services.events import create_user_event user_router = UserAPIRouter(prefix="") @@ -24,22 +24,20 @@ async def get_all_users(session: Session = Depends(generate_session)): async def create_user( background_tasks: BackgroundTasks, new_user: UserIn, - current_user: UserInDB = Depends(get_current_user), + current_user: PrivateUser = Depends(get_current_user), session: Session = Depends(generate_session), ): - new_user.password = get_password_hash(new_user.password) + new_user.password = hash_password(new_user.password) background_tasks.add_task( create_user_event, "User Created", f"Created by {current_user.full_name}", session=session ) + return db.users.create(session, new_user.dict()) @admin_router.get("/{id}", response_model=UserOut) -async def get_user( - id: int, - session: Session = Depends(generate_session), -): +async def get_user(id: int, session: Session = Depends(generate_session)): return db.users.get(session, id) @@ -48,7 +46,7 @@ def delete_user( background_tasks: BackgroundTasks, id: int, session: Session = Depends(generate_session), - current_user: UserInDB = Depends(get_current_user), + current_user: PrivateUser = Depends(get_current_user), ): """ Removes a user from the database. Must be the current user or a super user""" @@ -66,7 +64,7 @@ def delete_user( @user_router.get("/self", response_model=UserOut) async def get_logged_in_user( - current_user: UserInDB = Depends(get_current_user), + current_user: PrivateUser = Depends(get_current_user), ): return current_user.dict() @@ -75,7 +73,7 @@ async def get_logged_in_user( async def update_user( id: int, new_data: UserBase, - current_user: UserInDB = Depends(get_current_user), + current_user: PrivateUser = Depends(get_current_user), session: Session = Depends(generate_session), ): diff --git a/mealie/routes/users/favorites.py b/mealie/routes/users/favorites.py index bbe96ab783e9..4c59f0cd2106 100644 --- a/mealie/routes/users/favorites.py +++ b/mealie/routes/users/favorites.py @@ -6,7 +6,7 @@ from mealie.db.database import db from mealie.db.db_setup import generate_session from mealie.routes.routers import UserAPIRouter from mealie.routes.users._helpers import assert_user_change_allowed -from mealie.schema.user import UserFavorites, UserInDB +from mealie.schema.user import UserFavorites, PrivateUser user_router = UserAPIRouter() @@ -21,7 +21,7 @@ async def get_favorites(id: str, session: Session = Depends(generate_session)): @user_router.post("/{id}/favorites/{slug}") def add_favorite( slug: str, - current_user: UserInDB = Depends(get_current_user), + current_user: PrivateUser = Depends(get_current_user), session: Session = Depends(generate_session), ): """ Adds a Recipe to the users favorites """ @@ -35,7 +35,7 @@ def add_favorite( @user_router.delete("/{id}/favorites/{slug}") def remove_favorite( slug: str, - current_user: UserInDB = Depends(get_current_user), + current_user: PrivateUser = Depends(get_current_user), session: Session = Depends(generate_session), ): """ Adds a Recipe to the users favorites """ diff --git a/mealie/routes/users/images.py b/mealie/routes/users/images.py index 902ed64ef7da..5256841c4533 100644 --- a/mealie/routes/users/images.py +++ b/mealie/routes/users/images.py @@ -8,7 +8,7 @@ from mealie.core.config import app_dirs from mealie.core.dependencies import get_current_user from mealie.routes.routers import UserAPIRouter from mealie.routes.users._helpers import assert_user_change_allowed -from mealie.schema.user import UserInDB +from mealie.schema.user import PrivateUser public_router = APIRouter(prefix="", tags=["Users: Images"]) user_router = UserAPIRouter(prefix="", tags=["Users: Images"]) @@ -28,7 +28,7 @@ async def get_user_image(id: str): def update_user_image( id: str, profile_image: UploadFile = File(...), - current_user: UserInDB = Depends(get_current_user), + current_user: PrivateUser = Depends(get_current_user), ): """ Updates a User Image """ diff --git a/mealie/routes/users/passwords.py b/mealie/routes/users/passwords.py index 71dc8b917d6c..31f5de3861c6 100644 --- a/mealie/routes/users/passwords.py +++ b/mealie/routes/users/passwords.py @@ -1,42 +1,25 @@ -from fastapi import Depends, HTTPException, status +from fastapi import Depends from sqlalchemy.orm.session import Session from mealie.core.config import settings -from mealie.core.dependencies import get_current_user -from mealie.core.security import get_password_hash, verify_password +from mealie.core.security import hash_password from mealie.db.database import db from mealie.db.db_setup import generate_session from mealie.routes.routers import UserAPIRouter -from mealie.routes.users._helpers import assert_user_change_allowed -from mealie.schema.user import ChangePassword, UserInDB +from mealie.schema.user import ChangePassword +from mealie.services.user.user_service import UserService user_router = UserAPIRouter(prefix="") @user_router.put("/{id}/reset-password") -async def reset_user_password( - id: int, - session: Session = Depends(generate_session), -): - - new_password = get_password_hash(settings.DEFAULT_PASSWORD) +async def reset_user_password(id: int, session: Session = Depends(generate_session)): + new_password = hash_password(settings.DEFAULT_PASSWORD) db.users.update_password(session, id, new_password) @user_router.put("/{id}/password") -def update_password( - id: int, - password_change: ChangePassword, - current_user: UserInDB = Depends(get_current_user), - session: Session = Depends(generate_session), -): +def update_password(password_change: ChangePassword, user_service: UserService = Depends(UserService.write_existing)): """ Resets the User Password""" - assert_user_change_allowed(id, current_user) - match_passwords = verify_password(password_change.current_password, current_user.password) - - if not (match_passwords): - raise HTTPException(status.HTTP_400_BAD_REQUEST) - - new_password = get_password_hash(password_change.new_password) - db.users.update_password(session, id, new_password) + return user_service.change_password(password_change) diff --git a/mealie/routes/users/sign_up.py b/mealie/routes/users/sign_up.py index 9f038edc413b..90ed545201f8 100644 --- a/mealie/routes/users/sign_up.py +++ b/mealie/routes/users/sign_up.py @@ -4,11 +4,11 @@ from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status from sqlalchemy.orm.session import Session from mealie.core.dependencies import get_admin_user -from mealie.core.security import get_password_hash +from mealie.core.security import hash_password from mealie.db.database import db from mealie.db.db_setup import generate_session from mealie.routes.routers import AdminAPIRouter -from mealie.schema.user import SignUpIn, SignUpOut, SignUpToken, UserIn, UserInDB +from mealie.schema.user import SignUpIn, SignUpOut, SignUpToken, UserIn, PrivateUser from mealie.services.events import create_user_event public_router = APIRouter(prefix="/sign-ups") @@ -16,9 +16,7 @@ admin_router = AdminAPIRouter(prefix="/sign-ups") @admin_router.get("", response_model=list[SignUpOut]) -async def get_all_open_sign_ups( - session: Session = Depends(generate_session), -): +async def get_all_open_sign_ups(session: Session = Depends(generate_session)): """ Returns a list of open sign up links """ return db.sign_ups.get_all(session) @@ -28,7 +26,7 @@ async def get_all_open_sign_ups( async def create_user_sign_up_key( background_tasks: BackgroundTasks, key_data: SignUpIn, - current_user: UserInDB = Depends(get_admin_user), + current_user: PrivateUser = Depends(get_admin_user), session: Session = Depends(generate_session), ): """ Generates a Random Token that a new user can sign up with """ @@ -47,10 +45,7 @@ async def create_user_sign_up_key( @public_router.post("/{token}") async def create_user_with_token( - background_tasks: BackgroundTasks, - token: str, - new_user: UserIn, - session: Session = Depends(generate_session), + background_tasks: BackgroundTasks, token: str, new_user: UserIn, session: Session = Depends(generate_session) ): """ Creates a user with a valid sign up token """ @@ -61,7 +56,7 @@ async def create_user_with_token( # Create User new_user.admin = db_entry.admin - new_user.password = get_password_hash(new_user.password) + new_user.password = hash_password(new_user.password) db.users.create(session, new_user.dict()) # DeleteToken @@ -72,9 +67,6 @@ async def create_user_with_token( @admin_router.delete("/{token}") -async def delete_token( - token: str, - session: Session = Depends(generate_session), -): +async def delete_token(token: str, session: Session = Depends(generate_session)): """ Removed a token from the database """ db.sign_ups.delete(session, token) diff --git a/mealie/schema/user/user.py b/mealie/schema/user/user.py index e916e69aed16..773314eae247 100644 --- a/mealie/schema/user/user.py +++ b/mealie/schema/user/user.py @@ -107,7 +107,7 @@ class UserFavorites(UserBase): } -class UserInDB(UserOut): +class PrivateUser(UserOut): password: str class Config: @@ -142,7 +142,7 @@ class GroupInDB(UpdateGroup): class LongLiveTokenInDB(CreateToken): id: int - user: UserInDB + user: PrivateUser class Config: orm_mode = True diff --git a/mealie/services/backups/imports.py b/mealie/services/backups/imports.py index 6eb8efb24a98..742705eabe6b 100644 --- a/mealie/services/backups/imports.py +++ b/mealie/services/backups/imports.py @@ -22,7 +22,7 @@ from mealie.schema.admin import ( ) from mealie.schema.events import EventNotificationIn from mealie.schema.recipe import CommentOut, Recipe -from mealie.schema.user import UpdateGroup, UserInDB +from mealie.schema.user import UpdateGroup, PrivateUser from mealie.services.image import minify @@ -215,7 +215,7 @@ class ImportDatabase: def import_users(self): users_file = self.import_dir.joinpath("users", "users.json") - users = ImportDatabase.read_models_file(users_file, UserInDB) + users = ImportDatabase.read_models_file(users_file, PrivateUser) user_imports = [] for user in users: if user.id == 1: # Update Default User diff --git a/mealie/services/recipe/recipe_service.py b/mealie/services/recipe/recipe_service.py index b0924d4f4565..8f3a8ae4a5b2 100644 --- a/mealie/services/recipe/recipe_service.py +++ b/mealie/services/recipe/recipe_service.py @@ -13,7 +13,7 @@ from mealie.core.root_logger import get_logger from mealie.db.database import get_database from mealie.db.db_setup import SessionLocal from mealie.schema.recipe.recipe import CreateRecipe, Recipe -from mealie.schema.user.user import UserInDB +from mealie.schema.user.user import PrivateUser from mealie.services.events import create_recipe_event logger = get_logger(module=__name__) @@ -29,7 +29,7 @@ class RecipeService: recipe: Recipe # Required for proper type hints - def __init__(self, session: Session, user: UserInDB, background_tasks: BackgroundTasks = None) -> None: + def __init__(self, session: Session, user: PrivateUser, background_tasks: BackgroundTasks = None) -> None: self.session = session or SessionLocal() self.user = user self.background_tasks = background_tasks @@ -58,7 +58,7 @@ class RecipeService: Returns: RecipeService: The Recipe Service class with a populated recipe attribute """ - new_class = cls(deps.session, deps.user, deps.background_tasks) + new_class = cls(deps.session, deps.user, deps.bg_tasks) new_class.assert_existing(slug) return new_class @@ -80,7 +80,7 @@ class RecipeService: Returns: RecipeService: The Recipe Service class with a populated recipe attribute """ - new_class = cls(deps.session, deps.user, deps.background_tasks) + new_class = cls(deps.session, deps.user, deps.bg_task) new_class.assert_existing(slug) return new_class @@ -92,7 +92,7 @@ class RecipeService: HTTPException: 400 Bad Request """ - return cls(deps.session, deps.user, deps.background_tasks) + return cls(deps.session, deps.user, deps.bg_task) def pupulate_recipe(self, slug: str) -> Recipe: """Populates the recipe attribute with the recipe from the database. diff --git a/mealie/services/user/user_service.py b/mealie/services/user/user_service.py new file mode 100644 index 000000000000..e0e8fa7afbbe --- /dev/null +++ b/mealie/services/user/user_service.py @@ -0,0 +1,61 @@ +from fastapi import BackgroundTasks, Depends, HTTPException, status +from sqlalchemy.orm.session import Session + +from mealie.core.config import get_app_dirs, get_settings +from mealie.core.dependencies import WriteDeps +from mealie.core.root_logger import get_logger +from mealie.core.security import hash_password, verify_password +from mealie.db.database import get_database +from mealie.db.db_setup import SessionLocal +from mealie.schema.recipe.recipe import Recipe +from mealie.schema.user.user import ChangePassword, PrivateUser +from mealie.services.events import create_user_event + +logger = get_logger(module=__name__) + + +class UserService: + """""" + + def __init__(self, session: Session, acting_user: PrivateUser, background_tasks: BackgroundTasks = None) -> None: + self.session = session or SessionLocal() + self.acting_user = acting_user + self.background_tasks = background_tasks + self.recipe: Recipe = None + + # Global Singleton Dependency Injection + self.db = get_database() + self.app_dirs = get_app_dirs() + self.settings = get_settings() + + @classmethod + def write_existing(cls, id: int, deps: WriteDeps = Depends()): + new_instance = cls(session=deps.session, acting_user=deps.user, background_tasks=deps.bg_task) + new_instance._populate_target_user(id) + new_instance._assert_user_change_allowed() + return new_instance + + def _assert_user_change_allowed(self) -> None: + if self.acting_user.id != self.target_user.id and not self.acting_user.admin: + # only admins can edit other users + raise HTTPException(status.HTTP_403_FORBIDDEN, detail="NOT_AN_ADMIN") + + def _populate_target_user(self, id: int = None): + if id: + self.target_user = self.db.users.get(self.session, id) + if not self.target_user: + raise HTTPException(status.HTTP_404_NOT_FOUND) + else: + self.target_user = self.acting_user + + def _create_event(self, title: str, message: str) -> None: + self.background_tasks.add_task(create_user_event, title, message, self.session) + + def change_password(self, password_change: ChangePassword) -> PrivateUser: + """""" + if not verify_password(password_change.current_password, self.target_user.password): + raise HTTPException(status.HTTP_400_BAD_REQUEST) + + self.target_user.password = hash_password(password_change.new_password) + + return self.db.users.update_password(self.session, self.target_user.id, self.target_user.password)