diff --git a/frontend/src/api/users.js b/frontend/src/api/users.js index aa3d8a1e1..90123e733 100644 --- a/frontend/src/api/users.js +++ b/frontend/src/api/users.js @@ -11,6 +11,7 @@ const usersURLs = { users: `${userPrefix}`, self: `${userPrefix}/self`, userID: id => `${userPrefix}/${id}`, + password: id => `${userPrefix}/${id}/password`, }; export default { @@ -42,6 +43,10 @@ export default { let response = await apiReq.put(usersURLs.userID(user.id), user); return response.data; }, + async changePassword(id, password) { + let response = await apiReq.put(usersURLs.password(id), password); + return response.data; + }, async delete(id) { let response = await apiReq.delete(usersURLs.userID(id)); return response.data; diff --git a/frontend/src/components/Admin/AdminSidebar.vue b/frontend/src/components/Admin/AdminSidebar.vue index 0e98a94b3..e1799ef8d 100644 --- a/frontend/src/components/Admin/AdminSidebar.vue +++ b/frontend/src/components/Admin/AdminSidebar.vue @@ -27,8 +27,10 @@ - Jane Smith - Admin + {{ user.fullName }} + + {{ user.admin ? "Admin" : "User" }} @@ -50,7 +52,7 @@ - + -

{{$t('settings.homepage.home-page')}}

+

{{ $t("settings.homepage.home-page") }}

- + -1) { - console.log("New User", this.editedItem); api.users.update(this.editedItem); } else { api.users.create(this.editedItem); diff --git a/frontend/src/pages/Admin/Profile/index.vue b/frontend/src/pages/Admin/Profile/index.vue index 509a2a886..22d9fbf94 100644 --- a/frontend/src/pages/Admin/Profile/index.vue +++ b/frontend/src/pages/Admin/Profile/index.vue @@ -1,50 +1,98 @@ \ No newline at end of file diff --git a/frontend/src/pages/Admin/index.vue b/frontend/src/pages/Admin/index.vue index 7913d20a4..5f0528fca 100644 --- a/frontend/src/pages/Admin/index.vue +++ b/frontend/src/pages/Admin/index.vue @@ -4,15 +4,70 @@ + diff --git a/frontend/src/store/modules/userSettings.js b/frontend/src/store/modules/userSettings.js index 39c8406de..59a210dfb 100644 --- a/frontend/src/store/modules/userSettings.js +++ b/frontend/src/store/modules/userSettings.js @@ -21,6 +21,7 @@ const state = { isDark: false, isLoggedIn: false, token: "", + userData: {}, }; const mutations = { @@ -46,6 +47,10 @@ const mutations = { axios.defaults.headers.common["Authorization"] = `Bearer ${payload}`; state.token = payload; }, + + setUserData(state, payload) { + state.userData = payload; + }, }; const actions = { @@ -58,7 +63,6 @@ const actions = { } }, - async initTheme({ dispatch, getters }) { //If theme is empty resetTheme if (Object.keys(getters.getActiveTheme).length === 0) { @@ -77,6 +81,7 @@ const getters = { getIsDark: state => state.isDark, getIsLoggedIn: state => state.isLoggedIn, getToken: state => state.token, + getUserData: state => state.userData, }; export default { diff --git a/mealie/core/config.py b/mealie/core/config.py index d322ca232..68afb33ea 100644 --- a/mealie/core/config.py +++ b/mealie/core/config.py @@ -46,6 +46,7 @@ NEXTCLOUD_DIR = MIGRATION_DIR.joinpath("nextcloud") CHOWDOWN_DIR = MIGRATION_DIR.joinpath("chowdown") TEMPLATE_DIR = DATA_DIR.joinpath("templates") SQLITE_DIR = DATA_DIR.joinpath("db") +RECIPE_DATA_DIR = DATA_DIR.joinpath("recipes") TEMP_DIR = DATA_DIR.joinpath(".temp") REQUIRED_DIRS = [ @@ -58,6 +59,7 @@ REQUIRED_DIRS = [ SQLITE_DIR, NEXTCLOUD_DIR, CHOWDOWN_DIR, + RECIPE_DATA_DIR ] ensure_dirs() diff --git a/mealie/db/database.py b/mealie/db/database.py index b74c968c7..4c437bc0e 100644 --- a/mealie/db/database.py +++ b/mealie/db/database.py @@ -61,6 +61,15 @@ class _Users(BaseDocument): self.primary_key = "id" self.sql_model = User + def update_password(self, session, id, password: str): + entry = self._query_one(session=session, match_value=id) + entry.update_password(password) + return_data = entry.dict() + session.commit() + + return return_data + + class Database: def __init__(self) -> None: diff --git a/mealie/db/models/users.py b/mealie/db/models/users.py index ae03d15b9..a24eab08d 100644 --- a/mealie/db/models/users.py +++ b/mealie/db/models/users.py @@ -42,3 +42,6 @@ class User(SqlAlchemyBase, BaseMixins): self.email = email self.family = family self.admin = admin + + def update_password(self, password): + self.password = password diff --git a/mealie/routes/users/auth.py b/mealie/routes/users/auth.py index 368f37036..21378d70f 100644 --- a/mealie/routes/users/auth.py +++ b/mealie/routes/users/auth.py @@ -8,6 +8,7 @@ from fastapi_login.exceptions import InvalidCredentialsException from routes.deps import manager, query_user from schema.user import UserInDB from sqlalchemy.orm.session import Session +from schema.snackbar import SnackResponse router = APIRouter(prefix="/api/auth", tags=["Auth"]) @@ -29,4 +30,4 @@ def token( access_token = manager.create_access_token( data=dict(sub=email), expires=timedelta(hours=2) ) - return {"access_token": access_token, "token_type": "bearer"} + return SnackResponse.success("User Successfully Logged In", {"access_token": access_token, "token_type": "bearer"}) diff --git a/mealie/routes/users/crud.py b/mealie/routes/users/crud.py index 5eec6d796..513fe66e7 100644 --- a/mealie/routes/users/crud.py +++ b/mealie/routes/users/crud.py @@ -1,11 +1,12 @@ from datetime import timedelta -from core.security import get_password_hash +from core.security import get_password_hash, verify_password from db.database import db from db.db_setup import generate_session from fastapi import APIRouter, Depends from routes.deps import manager, query_user -from schema.user import UserBase, UserIn, UserInDB, UserOut +from schema.snackbar import SnackResponse +from schema.user import ChangePassword, UserBase, UserIn, UserInDB, UserOut from sqlalchemy.orm.session import Session router = APIRouter(prefix="/api/users", tags=["Users"]) @@ -17,12 +18,11 @@ async def create_user( current_user=Depends(manager), session: Session = Depends(generate_session), ): - """ Returns a list of all user in the Database """ new_user.password = get_password_hash(new_user.password) data = db.users.create(session, new_user.dict()) - return data + return SnackResponse.success(f"User Created: {new_user.full_name}", data) @router.get("", response_model=list[UserOut]) @@ -38,7 +38,7 @@ async def get_all_users( @router.get("/self", response_model=UserOut) -async def get_user_by_id( +async def get_logged_in_user( current_user: UserInDB = Depends(manager), session: Session = Depends(generate_session), ): @@ -69,8 +69,30 @@ async def update_user( access_token = manager.create_access_token( data=dict(sub=email), expires=timedelta(hours=2) ) - return {"access_token": access_token, "token_type": "bearer"} - return + access_token = {"access_token": access_token, "token_type": "bearer"} + + return SnackResponse.success("User Updated", access_token) + + +@router.put("/{id}/password") +async def update_password( + id: int, + password_change: ChangePassword, + current_user: UserInDB = Depends(manager), + session: Session = Depends(generate_session), +): + """ Resets the User Password""" + + match_passwords = verify_password( + password_change.current_password, current_user.password + ) + match_id = current_user.id == id + + if match_passwords and match_id: + new_password = get_password_hash(password_change.new_password) + db.users.update_password(session, id, new_password) + + return SnackResponse.success("Password Updated") @router.delete("/{id}") @@ -81,5 +103,9 @@ async def delete_user( ): """ Removes a user from the database. Must be the current user or a super user""" + if id == 1: + return SnackResponse.error("Error! Cannot Delete Super User") + if current_user.id == id or current_user.admin: - return db.users.delete(session, id) + db.users.delete(session, id) + return SnackResponse.error(f"User Deleted") diff --git a/mealie/schema/user.py b/mealie/schema/user.py index e80e9e4ad..25986f260 100644 --- a/mealie/schema/user.py +++ b/mealie/schema/user.py @@ -5,6 +5,11 @@ from fastapi_camelcase import CamelModel # from pydantic import EmailStr +class ChangePassword(CamelModel): + current_password: str + new_password: str + + class UserBase(CamelModel): full_name: Optional[str] = None email: str