diff --git a/.env.example b/.env.example index 6ad9c8c..ac8e27d 100644 --- a/.env.example +++ b/.env.example @@ -17,7 +17,6 @@ FRONTEND_PATH_RESET_PASSWORD= FRONTEND_PATH_ACTIVATE_ACCOUNT= FRONTEND_PATH_LOGIN= REGISTER_WITH_EMAIL= -MONGO_FS_BUCKET_NAME= LIST_ROLES_ENDPOINT_SECURITY_ENABLED= REGISTER_USER_ENDPOINT_SECURITY_ENABLED= @@ -79,3 +78,8 @@ REDIS_PASSWORD=unsta REDIS_LOG_LEVEL=warning REDIS_EXPIRE_CACHE=300 CACHE_DB_URL=redis://redis:6379/0 + +# VALIDATE TOKEN AND TRAILHUB CLIENT ENDPOINT +API_AUTH_URL_BASE=https://telemed-test.carein.cloud +API_TRAILHUB_ENDPOINT=/logs +API_AUTH_VALIDATE_TOKEN_ENDPOINT=/check-validate-access-token diff --git a/pyproject.toml b/pyproject.toml index 5d23581..5dedea5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ pydantic-settings = "^2.5.2" pwdlib = {extras = ["argon2", "bcrypt"], version = "^0.2.1"} user-agents = "^2.2.0" python-multipart = "^0.0.12" +cachetools = "^5.5.0" [tool.poetry.group.test.dependencies] diff --git a/src/common b/src/common index 3e4311d..5c8c466 160000 --- a/src/common +++ b/src/common @@ -1 +1 @@ -Subproject commit 3e4311d9ce68e659260ec52ac8712efc515fa341 +Subproject commit 5c8c466e9b420e5ff79043e66a650fd39767a02a diff --git a/src/config/settings.py b/src/config/settings.py index 8e76dd3..2bba1f7 100644 --- a/src/config/settings.py +++ b/src/config/settings.py @@ -25,7 +25,7 @@ class AuthBaseConfig(BaseSettings): LIST_PARAMETERS_ENDPOINT_SECURITY_ENABLED: Optional[bool] = Field( ..., alias="LIST_PARAMETERS_ENDPOINT_SECURITY_ENABLED" ) - USE_GRIDFS_STORAGE: Optional[bool] = Field(default=False, alias="USE_GRIDFS_STORAGE") + USE_TRACK_ACTIVITY_LOGS: Optional[bool] = Field(default=True, alias="USE_TRACK_ACTIVITY_LOGS") # USER MODEL NAME USER_MODEL_NAME: str = Field(..., alias="USER_MODEL_NAME") @@ -40,7 +40,6 @@ class AuthBaseConfig(BaseSettings): # DATABASE CONFIG MONGO_DB: str = Field(..., alias="MONGO_DB") - MONGO_FS_BUCKET_NAME: str = Field(default="auth", alias="MONGO_FS_BUCKET_NAME") MONGODB_URI: str = Field(..., alias="MONGODB_URI") # REDIS CONFIG @@ -52,6 +51,11 @@ class AuthBaseConfig(BaseSettings): RATE_LIMIT_REQUEST: Optional[int] = Field(default=5, alias="RATE_LIMIT_REQUEST") RATE_LIMIT_INTERVAL: Optional[int] = Field(default=3600, alias="RATE_LIMIT_INTERVAL") + # VALIDATE TOKEN AND TRAILHUB CLIENT ENDPOINT + API_AUTH_URL_BASE: str = Field(..., alias="API_AUTH_URL_BASE") + API_TRAILHUB_ENDPOINT: str = Field(..., alias="API_TRAILHUB_ENDPOINT") + API_AUTH_VALIDATE_TOKEN_ENDPOINT: str = Field(..., alias="API_AUTH_VALIDATE_TOKEN_ENDPOINT") + @lru_cache def get_settings() -> AuthBaseConfig: diff --git a/src/routers/auth.py b/src/routers/auth.py index 71aca18..a61cac9 100644 --- a/src/routers/auth.py +++ b/src/routers/auth.py @@ -6,6 +6,7 @@ from slugify import slugify from src.common.helpers.caching import custom_key_builder, delete_custom_key +from src.common.services.trailhub_client import send_event from src.config import enable_endpoint, settings from src.middleware import AuthorizedHTTPBearer from src.models import User @@ -22,7 +23,7 @@ VerifyOTP, ) from src.services import auth -from src.shared import mail_service, sms_service +from src.shared import API_TRAILHUB_ENDPOINT, API_VERIFY_ACCESS_TOKEN_ENDPOINT, mail_service, sms_service auth_router = APIRouter(prefix="", tags=["AUTH"], redirect_slashes=False) @@ -30,11 +31,33 @@ @auth_router.post("/signup", summary="Signup", status_code=status.HTTP_201_CREATED) -async def register(bg: BackgroundTasks, payload: RequestChangePassword = Body(...)): +async def register(request: Request, bg: BackgroundTasks, payload: RequestChangePassword = Body(...)): if settings.REGISTER_WITH_EMAIL: - return await auth.signup_with_email(bg, payload.email) + result = await auth.signup_with_email(bg, payload.email) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f"'{payload.email}' has signed up.", + user_id=None, + ) + return result else: - return await auth.signup_with_phonenumber(bg, payload) + result = await auth.signup_with_phonenumber(bg, payload) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f"'{payload.phonenumber}' has signed up successfully.", + user_id=None, + ) + return result if bool(settings.REGISTER_WITH_EMAIL): @@ -47,13 +70,39 @@ async def register(bg: BackgroundTasks, payload: RequestChangePassword = Body(.. summary="Complete registration if you are registered with an e-mail address.", description="Complete registration if you are registered with an e-mail address.", ) - async def register_completed(token: str, bg: BackgroundTasks, payload: UserBaseSchema = Body(...)): - return await auth.completed_register_with_email(token, payload, bg) + async def register_completed( + request: Request, token: str, bg: BackgroundTasks, payload: UserBaseSchema = Body(...) + ): + result = await auth.completed_register_with_email(token, payload, bg) + + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f"'{payload.phonenumber}' has completed registration.", + user_id=None, + ) + + return result @auth_router.post("/login", summary="Login", status_code=status.HTTP_200_OK) -async def login(request: Request, payload: LoginUser = Body(...)): - return await auth.login(request, payload) +async def login(request: Request, bg: BackgroundTasks, payload: LoginUser = Body(...)): + result = await auth.login(request, payload) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=settings.API_AUTH_VALIDATE_TOKEN_ENDPOINT, + trailhub_url=settings.API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f"'{payload.phonenumber}' has logged in.", + user_id=None, + ) + return result if not bool(settings.REGISTER_WITH_EMAIL): @@ -64,12 +113,34 @@ async def login(request: Request, payload: LoginUser = Body(...)): summary="Verify OTP Code", status_code=status.HTTP_200_OK, ) - async def verif_otp_code(payload: VerifyOTP = Body(...)): - return await auth.verify_otp(payload) + async def verif_otp_code(request: Request, bg: BackgroundTasks, payload: VerifyOTP = Body(...)): + result = await auth.verify_otp(payload) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f"'{payload.phonenumber}' has verified OTP code.", + user_id=None, + ) + return result @auth_router.post("/resend-otp", summary="Resend OTP Code", status_code=status.HTTP_200_OK) - async def resend_otp_code(bg: BackgroundTasks, payload: PhonenumberModel = Body(...)): - return await auth.resend_otp(bg, payload) + async def resend_otp_code(request: Request, bg: BackgroundTasks, payload: PhonenumberModel = Body(...)): + result = await auth.resend_otp(bg, payload) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f"'{payload.phonenumber}' has resent OTP code.", + user_id=None, + ) + return result @auth_router.get( @@ -105,40 +176,104 @@ async def check_validate_access_token(token: str): @auth_router.put("/change-password/{id}", summary="Set up a password for the user.", status_code=status.HTTP_200_OK) -async def change_password(id: PydanticObjectId, payload: ChangePassword = Body(...)): - return await auth.change_password(user_id=id, payload=payload) +async def change_password( + request: Request, bg: BackgroundTasks, id: PydanticObjectId, payload: ChangePassword = Body(...) +): + result = await auth.change_password(user_id=id, payload=payload) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message="has changed password.", + user_id=str(id), + ) + return result if bool(settings.REGISTER_WITH_EMAIL): @auth_router.post("/request-password-reset", summary="Request a password reset.", status_code=status.HTTP_200_OK) - async def request_password_reset_with_email(bg: BackgroundTasks, payload: EmailModelMixin = Body(...)): - return await auth.request_password_reset_with_email(bg=bg, email=payload.email) + async def request_password_reset_with_email( + request: Request, bg: BackgroundTasks, payload: EmailModelMixin = Body(...) + ): + result = await auth.request_password_reset_with_email(bg=bg, email=payload.email) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f"'{payload.email}' has requested a password reset.", + user_id=None, + ) + return result if not bool(settings.REGISTER_WITH_EMAIL): @auth_router.post("/request-password-reset", summary="Request a password reset.", status_code=status.HTTP_200_OK) - async def request_password_reset_with_phonenumber(bg: BackgroundTasks, payload: PhonenumberModel = Body(...)): - return await auth.request_password_reset_with_phonenumber(bg=bg, payload=payload) + async def request_password_reset_with_phonenumber( + request: Request, bg: BackgroundTasks, payload: PhonenumberModel = Body(...) + ): + result = await auth.request_password_reset_with_phonenumber(bg=bg, payload=payload) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f"{payload.phonenumber} has requested a password reset.", + user_id=None, + ) + return result if bool(settings.REGISTER_WITH_EMAIL): @auth_router.post("/reset-password-completed", summary="Request a password reset.", status_code=status.HTTP_200_OK) async def email_reset_password_completed( + request: Request, bg: BackgroundTasks, token: str = Query(..., alias="token", description="Reset password token"), payload: ChangePassword = Body(...), ): - return await auth.reset_password_completed_with_email(bg, token=token, payload=payload) + result = await auth.reset_password_completed_with_email(bg, token=token, payload=payload) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message="has reset password.", + user_id=None, + ) + return result if not bool(settings.REGISTER_WITH_EMAIL): @auth_router.post("/reset-password-completed", summary="Request a password reset.", status_code=status.HTTP_200_OK) - async def phonenumber_reset_password_completed(payload: ChangePasswordWithOTPCode = Body(...)): - return await auth.reset_password_completed_with_phonenumber(payload) + async def phonenumber_reset_password_completed( + request: Request, bg: BackgroundTasks, payload: ChangePasswordWithOTPCode = Body(...) + ): + result = await auth.reset_password_completed_with_phonenumber(payload) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f"'{payload.phonenumber}' has reset password.", + user_id=None, + ) + return result if bool(enable_endpoint.SHOW_CHECK_USER_ATTRIBUTE_ENDPOINT): @@ -155,15 +290,35 @@ async def check_user_attributes( @auth_router.post("-sms", summary="Send a message", status_code=status.HTTP_200_OK, include_in_schema=False) -async def send_sms(background: BackgroundTasks, payload: SendSmsMessage = Body(...)): +async def send_sms(request: Request, background: BackgroundTasks, payload: SendSmsMessage = Body(...)): phone = payload.phone_number.replace("+", "") await sms_service.send_sms(background, recipient=phone, message=payload.message) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=background, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f"'{payload.phone_number}' has sent a message.", + user_id=None, + ) return {"message": "SMS sent successfully."} @auth_router.post("-email", summary="Send a e-mail", status_code=status.HTTP_200_OK, include_in_schema=False) -async def send_email(background: BackgroundTasks, payload: SendEmailMessage = Body(...)): +async def send_email(request: Request, background: BackgroundTasks, payload: SendEmailMessage = Body(...)): mail_service.send_email_background( background, recipients=payload.recipients, subject=payload.subject, body=payload.message ) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=background, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f"'{payload.recipients}' has sent a message.", + user_id=None, + ) return {"message": "E-mail sent successfully."} diff --git a/src/routers/params.py b/src/routers/params.py index 11a3d8b..3ae043a 100644 --- a/src/routers/params.py +++ b/src/routers/params.py @@ -1,16 +1,18 @@ from typing import Optional from beanie import PydanticObjectId -from fastapi import APIRouter, Body, Depends, Query, status +from fastapi import APIRouter, BackgroundTasks, Body, Depends, Query, Request, status from fastapi_pagination.ext.beanie import paginate from pymongo import ASCENDING, DESCENDING from src.common.helpers.pagination import customize_page +from src.common.services.trailhub_client import send_event from src.config import settings from src.middleware import AuthorizedHTTPBearer, CheckPermissionsHandler from src.models import Params from src.schemas import FilterParams, ParamsModel from src.services import params +from src.shared import API_TRAILHUB_ENDPOINT, API_VERIFY_ACCESS_TOKEN_ENDPOINT from src.shared.utils import SortEnum param_router = APIRouter(prefix="/parameters", tags=["PARAMETERS"]) @@ -26,8 +28,19 @@ summary="Add new parameter", status_code=status.HTTP_201_CREATED, ) -async def create(payload: ParamsModel = Body(...)): - return await params.create(payload) +async def create(request: Request, bg: BackgroundTasks, payload: ParamsModel = Body(...)): + result = await params.create(payload) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f" has created a new parameter with the name '{payload.type}:{payload.name}'", + user_id=None, + ) + return result @param_router.get( @@ -85,8 +98,19 @@ async def read(id: PydanticObjectId): summary="Update param", status_code=status.HTTP_202_ACCEPTED, ) -async def update(id: PydanticObjectId, payload: ParamsModel = Body(...)): - return await params.update(id=PydanticObjectId(id), param=payload) +async def update(request: Request, bg: BackgroundTasks, id: PydanticObjectId, payload: ParamsModel = Body(...)): + result = await params.update(id=PydanticObjectId(id), param=payload) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f" has updated the parameter with the name '{id}:{payload.type}:{payload.name}'", + user_id=None, + ) + return result @param_router.delete( @@ -98,5 +122,15 @@ async def update(id: PydanticObjectId, payload: ParamsModel = Body(...)): summary="Remove param", status_code=status.HTTP_204_NO_CONTENT, ) -async def delete(id: PydanticObjectId): +async def delete(request: Request, bg: BackgroundTasks, id: PydanticObjectId): + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f" has deleted the parameter with the name '{id}'", + user_id=None, + ) return await params.delete(id=PydanticObjectId(id)) diff --git a/src/routers/roles.py b/src/routers/roles.py index 510c1d8..4056084 100644 --- a/src/routers/roles.py +++ b/src/routers/roles.py @@ -1,18 +1,20 @@ from typing import Optional, Set from beanie import PydanticObjectId -from fastapi import APIRouter, Body, Depends, Query, status +from fastapi import APIRouter, BackgroundTasks, Body, Depends, Query, Request, status from fastapi_pagination.ext.beanie import paginate from pymongo import ASCENDING, DESCENDING from slugify import slugify from src.common.helpers.caching import delete_custom_key from src.common.helpers.pagination import customize_page +from src.common.services.trailhub_client import send_event from src.config import enable_endpoint, settings from src.middleware import AuthorizedHTTPBearer, CheckPermissionsHandler from src.models import Role from src.schemas import RoleModel from src.services import roles +from src.shared import API_TRAILHUB_ENDPOINT, API_VERIFY_ACCESS_TOKEN_ENDPOINT from src.shared.utils import SortEnum service_appname_slug = slugify(settings.APP_NAME) @@ -39,8 +41,19 @@ summary="Create role", status_code=status.HTTP_201_CREATED, ) -async def create_role(payload: RoleModel = Body(...)): - return await roles.create_role(role=payload) +async def create_role(request: Request, bg: BackgroundTasks, payload: RoleModel = Body(...)): + result = await roles.create_role(role=payload) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f" has created a new role with the name '{payload.name}'", + user_id=None, + ) + return result @role_router.get( @@ -92,8 +105,19 @@ async def ger_role(id: PydanticObjectId): summary="Update role", status_code=status.HTTP_200_OK, ) -async def update_role(id: PydanticObjectId, payload: RoleModel = Body(...)): - return await roles.update_role(role_id=PydanticObjectId(id), update_role=payload) +async def update_role(request: Request, bg: BackgroundTasks, id: PydanticObjectId, payload: RoleModel = Body(...)): + result = await roles.update_role(role_id=PydanticObjectId(id), update_role=payload) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f" has created a new role with the name '{id}:{payload.name}'", + user_id=None, + ) + return result @role_router.delete( @@ -105,8 +129,19 @@ async def update_role(id: PydanticObjectId, payload: RoleModel = Body(...)): summary="Delete role", status_code=status.HTTP_204_NO_CONTENT, ) -async def delete_role(id: PydanticObjectId): - return await roles.delete_role(role_id=PydanticObjectId(id)) +async def delete_role(request: Request, bg: BackgroundTasks, id: PydanticObjectId): + result = await roles.delete_role(role_id=PydanticObjectId(id)) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f" has deleted the role with the name '{id}'", + user_id=None, + ) + return result if bool(enable_endpoint.SHOW_MEMBERS_IN_ROLE_ENDPOINT): @@ -145,7 +180,20 @@ async def get_role_members( summary="Assign permissions to role", status_code=status.HTTP_200_OK, ) -async def manage_permission_to_role(id: PydanticObjectId, payload: Set[str] = Body(...)): +async def manage_permission_to_role( + request: Request, bg: BackgroundTasks, id: PydanticObjectId, payload: Set[str] = Body(...) +): + result = await roles.assign_permissions_to_role(role_id=PydanticObjectId(id), permission_codes=payload) await delete_custom_key(service_appname_slug + "access") await delete_custom_key(service_appname_slug + "validate") - return await roles.assign_permissions_to_role(role_id=PydanticObjectId(id), permission_codes=payload) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f" has assigned permissions to the role with the ID '{id}'", + user_id=None, + ) + return result diff --git a/src/routers/users.py b/src/routers/users.py index 61e8a78..710261c 100644 --- a/src/routers/users.py +++ b/src/routers/users.py @@ -1,16 +1,18 @@ from typing import Optional from beanie import PydanticObjectId -from fastapi import APIRouter, Body, Depends, Query, Request, status +from fastapi import APIRouter, BackgroundTasks, Body, Depends, Query, Request, status from fastapi_pagination.async_paginator import paginate from pymongo import ASCENDING, DESCENDING from src.common.helpers.pagination import customize_page +from src.common.services.trailhub_client import send_event from src.config import settings from src.middleware import AuthorizedHTTPBearer, CheckPermissionsHandler, CheckUserAccessHandler from src.models import User, UserOut from src.schemas import CreateUser, UpdatePassword, UpdateUser from src.services import roles, users +from src.shared import API_TRAILHUB_ENDPOINT, API_VERIFY_ACCESS_TOKEN_ENDPOINT from src.shared.utils import AccountAction, SortEnum user_router = APIRouter(prefix="/users", tags=["USERS"], redirect_slashes=False) @@ -39,11 +41,33 @@ summary="Add new user", include_in_schema=False, ) -async def create_user(request: Request, payload: CreateUser = Body(...)): +async def create_user(request: Request, bg: BackgroundTasks, payload: CreateUser = Body(...)): if request.url.path.endswith("/add"): - return await users.create_first_user(payload) + result = await users.create_first_user(payload) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f" has created a new user with the email '{payload.email}'", + user_id=None, + ) + return result else: - return await users.create_user(payload) + result = await users.create_user(payload) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f" has created a new user with the email '{payload.email}'", + user_id=None, + ) + return result @user_router.get( @@ -107,7 +131,7 @@ async def listing_users( mode="json", exclude={"password", "is_primary", "attributes.otp_secret", "attributes.otp_created_at"}, ), - extras={"role_info": {"name": role_data.name, "slug": role_data.slug} if role_data else None} + extras={"role_info": {"name": role_data.name, "slug": role_data.slug} if role_data else None}, ) ) return await paginate(users_output) @@ -141,11 +165,26 @@ async def get_user(id: PydanticObjectId): mode="json", exclude={"password", "is_primary", "attributes.otp_secret", "attributes.otp_created_at"}, ), - extras={"role_info": {"name": role_data.name, "slug": role_data.slug} if role_data else None} + extras={"role_info": {"name": role_data.name, "slug": role_data.slug} if role_data else None}, ) return result +@user_router.get( + "/{id}/attributes", + dependencies=[ + Depends(AuthorizedHTTPBearer), + Depends(CheckPermissionsHandler(required_permissions={"auth:can-display-user"})), + ], + response_model_exclude={"is_admin", "password", "is_primary", "attributes.otp_secret", "attributes.otp_created_at"}, + summary="Get single user attributes only", + status_code=status.HTTP_200_OK, +) +async def get_user_attributes(id: PydanticObjectId): + user_data = await users.get_one_user(user_id=PydanticObjectId(id)) + return user_data.attributes + + @user_router.patch( "/{id}", response_model=User, @@ -158,8 +197,20 @@ async def get_user(id: PydanticObjectId): summary="Update user information", status_code=status.HTTP_200_OK, ) -async def update_user(id: PydanticObjectId, payload: UpdateUser = Body(...)): - return await users.update_user(user_id=PydanticObjectId(id), update_user=payload) +async def update_user(request: Request, bg: BackgroundTasks, id: PydanticObjectId, payload: UpdateUser = Body(...)): + # TODO: Ajouter une vérification pour voir si l'utilisateur met à jour son propre compte. + result = await users.update_user(user_id=PydanticObjectId(id), update_user=payload) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f" has updated the user with the email '{id}:{payload.fullname}'", + user_id=id, + ) + return result @user_router.put( @@ -170,8 +221,22 @@ async def update_user(id: PydanticObjectId, payload: UpdateUser = Body(...)): Depends(CheckPermissionsHandler(required_permissions={"auth:can-update-user"})), ], ) -async def update_user_password(id: PydanticObjectId, payload: UpdatePassword = Body(...)): - return await users.update_user_password(user_id=PydanticObjectId(id), payload=payload) +async def update_user_password( + request: Request, bg: BackgroundTasks, id: PydanticObjectId, payload: UpdatePassword = Body(...) +): + # TODO: Ajouter une vérification pour voir si l'utilisateur met à jour son propre compte. + result = await users.update_user_password(user_id=PydanticObjectId(id), payload=payload) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f" has updated the password of the user with the email '{id}'", + user_id=str(id), + ) + return result @user_router.put( @@ -184,8 +249,21 @@ async def update_user_password(id: PydanticObjectId, payload: UpdatePassword = B summary="Activate or deactivate user account", status_code=status.HTTP_202_ACCEPTED, ) -async def activate_user_account(id: PydanticObjectId, action: AccountAction): - return await users.activate_user_account(user_id=PydanticObjectId(id), action=action) +async def activate_user_account(request: Request, bg: BackgroundTasks, id: PydanticObjectId, action: AccountAction): + # TODO: Ajouter une vérification pour voir si l'utilisateur met à jour son propre compte. + + result = await users.activate_user_account(user_id=PydanticObjectId(id), action=action) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f" has {'activate' if action == AccountAction.ACTIVATE else 'deactivate'} the account user '{id}'", + user_id=str(id), + ) + return result @user_router.delete( @@ -198,5 +276,16 @@ async def activate_user_account(id: PydanticObjectId, action: AccountAction): summary="Delete one user", status_code=status.HTTP_204_NO_CONTENT, ) -async def delete_user(id: PydanticObjectId): - return await users.delete_user_account(user_id=PydanticObjectId(id)) +async def delete_user(request: Request, bg: BackgroundTasks, id: PydanticObjectId): + result = await users.delete_user_account(user_id=PydanticObjectId(id)) + if settings.USE_TRACK_ACTIVITY_LOGS: + await send_event( + request=request, + bg=bg, + oauth_url=API_VERIFY_ACCESS_TOKEN_ENDPOINT, + trailhub_url=API_TRAILHUB_ENDPOINT, + source=settings.APP_NAME.lower(), + message=f" has deleted the user with the email '{id}'", + user_id=str(id), + ) + return result diff --git a/src/shared/__init__.py b/src/shared/__init__.py index 5c0e8d2..d60b09e 100644 --- a/src/shared/__init__.py +++ b/src/shared/__init__.py @@ -1,10 +1,18 @@ from .send_email import email_sender_handler from .send_sms import sms_sender_handler from .utils import TokenBlacklistHandler, GenerateOPTKey +from .url_patterns import API_TRAILHUB_ENDPOINT, API_VERIFY_ACCESS_TOKEN_ENDPOINT mail_service = email_sender_handler() sms_service = sms_sender_handler() blacklist_token = TokenBlacklistHandler() otp_service = GenerateOPTKey() -__all__ = ["mail_service", "otp_service", "sms_service", "blacklist_token"] +__all__ = [ + "mail_service", + "otp_service", + "sms_service", + "blacklist_token", + "API_TRAILHUB_ENDPOINT", + "API_VERIFY_ACCESS_TOKEN_ENDPOINT", +] diff --git a/src/shared/url_patterns.py b/src/shared/url_patterns.py new file mode 100644 index 0000000..6c1777e --- /dev/null +++ b/src/shared/url_patterns.py @@ -0,0 +1,7 @@ +from urllib.parse import urljoin + +from src.config import settings + + +API_VERIFY_ACCESS_TOKEN_ENDPOINT = urljoin(settings.API_AUTH_URL_BASE, settings.API_AUTH_VALIDATE_TOKEN_ENDPOINT) +API_TRAILHUB_ENDPOINT = urljoin(settings.API_AUTH_URL_BASE, settings.API_TRAILHUB_ENDPOINT) diff --git a/tests/.test.env b/tests/.test.env index 123044a..ee2b0f1 100644 --- a/tests/.test.env +++ b/tests/.test.env @@ -16,6 +16,7 @@ BLACKLIST_TOKEN_FILE='.tokens.txt' ENABLE_OTP_CODE=True OTP_CODE_DIGIT_LENGTH=4 REGISTER_WITH_EMAIL=False +USE_TRACK_ACTIVITY_LOGS=False COMPRESS_MIN_SIZE=1000 RATE_LIMIT_REQUEST=5 @@ -79,3 +80,8 @@ SMS_URL=https://locahost.com/sms/send # REOIS CONFIG REDIS_EXPIRE_CACHE=3600 REDIS_URL=redis://localhost + +# VALIDATE TOKEN AND TRAILHUB CLIENT ENDPOINT +API_AUTH_URL_BASE=http://localhost:8000 +API_TRAILHUB_ENDPOINT=/logs +API_AUTH_VALIDATE_TOKEN_ENDPOINT=/check-validate-access-token