From 9f8f7cc1127d8e2019957dbd120bab84a473eb9b Mon Sep 17 00:00:00 2001 From: Jeroen Vijgen Date: Wed, 16 Jul 2025 18:43:52 +0000 Subject: [PATCH] Refresh some magic numbers with status codes, add delete for account and make people able to update password --- .../src/modules/organizations/router.py | 6 +-- api/asset_manager/src/modules/users/router.py | 39 +++++++++++++--- .../src/modules/users/schemas.py | 4 ++ .../src/tests/test_users_routes/test_users.py | 45 +++++++++++++++++-- 4 files changed, 80 insertions(+), 14 deletions(-) diff --git a/api/asset_manager/src/modules/organizations/router.py b/api/asset_manager/src/modules/organizations/router.py index ba4be69a..75b92191 100644 --- a/api/asset_manager/src/modules/organizations/router.py +++ b/api/asset_manager/src/modules/organizations/router.py @@ -1,5 +1,5 @@ import uuid -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, status from typing import Annotated, List @@ -27,7 +27,7 @@ async def all_active_organizations( organizations: List[Organization] = [] if len(memberships) < 1: - raise HTTPException(status_code=404, detail="No active organizations found!") + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="No active organizations found!") for member in memberships: organizations.append(member.organization) @@ -35,7 +35,7 @@ async def all_active_organizations( return organizations -@router.delete("/{org_id}", status_code=204) +@router.delete("/{org_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_organization( user: Annotated[User, Depends(get_current_active_user)], org_id: uuid.UUID ) -> None: diff --git a/api/asset_manager/src/modules/users/router.py b/api/asset_manager/src/modules/users/router.py index ed19541b..a61ee9df 100644 --- a/api/asset_manager/src/modules/users/router.py +++ b/api/asset_manager/src/modules/users/router.py @@ -1,13 +1,14 @@ -from typing import Annotated +from typing import Annotated, List from fastapi import APIRouter, Depends from fastapi import HTTPException, status from tortoise.expressions import Q +from modules.auth.models import Token from modules.users.utils import get_current_active_user -from modules.users.schemas import register_model, update_user_model -from modules.users.models import User +from modules.users.schemas import delete_user_model, register_model, update_user_model +from modules.users.models import Membership, User from modules.users.schemas import user_model from config import settings @@ -54,8 +55,10 @@ async def create_user(user: register_model): @router.put("/me", status_code=status.HTTP_204_NO_CONTENT) -async def update_user(user: Annotated[User, Depends(get_current_active_user)], - updated_user: update_user_model): +async def update_user( + user: Annotated[User, Depends(get_current_active_user)], + updated_user: update_user_model, +): if updated_user.email: user.email = updated_user.email if updated_user.name: @@ -63,12 +66,34 @@ async def update_user(user: Annotated[User, Depends(get_current_active_user)], if updated_user.surname: user.surname = updated_user.surname - if updated_user.old_password and updated_user.password and updated_user.validate_password: - user.update_password(updated_user.old_password, updated_user.password, updated_user.validate_password) + if ( + updated_user.old_password + and updated_user.password + and updated_user.validate_password + ): + user.update_password( + updated_user.old_password, + updated_user.password, + updated_user.validate_password, + ) await user.save() +@router.delete("/me", status_code=status.HTTP_204_NO_CONTENT) +async def update_user( + user: Annotated[User, Depends(get_current_active_user)], +): + memberships: List[Membership] = await Membership.filter(Q(user__id=user.id) & Q(disabled=False)) + for membership in memberships: + await membership.acl.delete() + await membership.delete() + tokens: List[Token] = await Token.filter(Q(user__id=user.id) & Q(disabled=False)) + for token in tokens: + await token.delete() + await user.delete() + + @router.get("/me", response_model=user_model) async def get_user(user: Annotated[User, Depends(get_current_active_user)]): return user diff --git a/api/asset_manager/src/modules/users/schemas.py b/api/asset_manager/src/modules/users/schemas.py index 50134f37..5196064b 100644 --- a/api/asset_manager/src/modules/users/schemas.py +++ b/api/asset_manager/src/modules/users/schemas.py @@ -21,3 +21,7 @@ class update_user_model(BaseModel): old_password: str | None password: str | None validate_password: str | None + +class delete_user_model(BaseModel): + password: str + diff --git a/api/asset_manager/src/tests/test_users_routes/test_users.py b/api/asset_manager/src/tests/test_users_routes/test_users.py index 12a0808b..d2fe5017 100644 --- a/api/asset_manager/src/tests/test_users_routes/test_users.py +++ b/api/asset_manager/src/tests/test_users_routes/test_users.py @@ -5,6 +5,7 @@ from httpx import AsyncClient from unittest.mock import ANY from modules.users.models import User + class TestAccounts(Test): async def test_setup_new_account(self, client: AsyncClient): # Ensure account is never available. Prevents account already being available. @@ -42,7 +43,6 @@ class TestAccounts(Test): async def test_me_route(self, client: AsyncClient, create_user_with_org): _, _, _, tokens = await create_user_with_org() - account = await client.get( "https://localhost/api/v1/users/me", headers={"Authorization": f"Bearer {tokens.access_token}"}, @@ -64,7 +64,6 @@ class TestAccounts(Test): async def test_update_me_route(self, client: AsyncClient, create_user_with_org): _, _, _, tokens = await create_user_with_org() - account = await client.get( "https://localhost/api/v1/users/me", headers={"Authorization": f"Bearer {tokens.access_token}"}, @@ -82,7 +81,7 @@ class TestAccounts(Test): "surname": "user", "username": "user", } - + account = await client.put( "https://localhost/api/v1/users/me", json={ @@ -114,4 +113,42 @@ class TestAccounts(Test): "name": "awesome", "surname": "bluey", "username": "user", - } \ No newline at end of file + } + + async def test_remove_account(self, client: AsyncClient, create_user_with_org): + _, _, _, tokens = await create_user_with_org(email="sup3rus3r@gmail.com") + + account = await client.get( + "https://localhost/api/v1/users/me", + headers={"Authorization": f"Bearer {tokens.access_token}"}, + ) + + assert account.status_code == 200 + assert account.json() == { + "created_at": ANY, + "disabled": False, + "disabled_at": None, + "email": "sup3rus3r@gmail.com", + "id": ANY, + "modified_at": ANY, + "name": "awesome", + "surname": "user", + "username": "user", + } + + delete = await client.delete( + "https://localhost/api/v1/users/me", + headers={"Authorization": f"Bearer {tokens.access_token}"}, + ) + + assert delete.status_code == 204 + + old = await client.get( + "https://localhost/api/v1/users/me", + headers={"Authorization": f"Bearer {tokens.access_token}"}, + ) + + assert old.status_code == 401 + assert old.json() == { + "detail": "The requested token does not exist or you are not logged in.", + }