From 497e6080a3cb703621ae9b17f3ca2b0e1c4c3672 Mon Sep 17 00:00:00 2001 From: haolou Date: Mon, 20 Oct 2025 14:51:38 +0800 Subject: [PATCH] fix: create apis for magicleaps password things avoiding calling code depot --- .../backend/application/signin_hub.py | 6 ++ .../backend/business/signin_manager.py | 17 ++++++ .../backend/infra/auth/user_auth_handler.py | 25 +++++++++ .../services/auth/user_auth_service.py | 7 +++ .../webapi/routes/signin/__init__.py | 2 + .../signin/update_magicleaps_user_password.py | 55 +++++++++++++++++++ 6 files changed, 112 insertions(+) create mode 100644 apps/authentication/webapi/routes/signin/update_magicleaps_user_password.py diff --git a/apps/authentication/backend/application/signin_hub.py b/apps/authentication/backend/application/signin_hub.py index 5455c4f..b5055d6 100644 --- a/apps/authentication/backend/application/signin_hub.py +++ b/apps/authentication/backend/application/signin_hub.py @@ -95,6 +95,12 @@ class SignInHub: user_id=user_id, password=password ) + @log_entry_exit_async + async def update_magicleaps_user_password(self, user_id: str, password: str) -> dict[str, any]: + return await self.signin_manager.update_magicleaps_user_password( + user_id=user_id, password=password + ) + @log_entry_exit_async async def send_email_code(self, sender_id: str, email: str) -> dict[str, any]: result = await self.signin_manager.send_email_code(sender_id, email) diff --git a/apps/authentication/backend/business/signin_manager.py b/apps/authentication/backend/business/signin_manager.py index 460efa0..c7c5403 100644 --- a/apps/authentication/backend/business/signin_manager.py +++ b/apps/authentication/backend/business/signin_manager.py @@ -368,6 +368,23 @@ class SignInManager: ) return {"succeeded": True} + async def update_magicleaps_user_password(self, user_id: str, password: str) -> dict[str, any]: + error_message = """ + Password does not pass complexity requirements: + - At least one lowercase character + - At least one uppercase character + - At least one digit + - At least one special character (punctuation, brackets, quotes, etc.) + """ + if not check_password_complexity(password): + raise InvalidDataError(error_message) + + user_flid = await self.user_auth_service.get_user_flid(user_id) + await self.user_auth_service.save_magicleaps_password_auth_method( + user_id, user_flid, password + ) + return {"succeeded": True} + async def send_email_code(self, sender_id: str, email: str) -> bool: mail_code = await self.user_auth_service.generate_auth_code_for_object( email, AuthType.EMAIL diff --git a/apps/authentication/backend/infra/auth/user_auth_handler.py b/apps/authentication/backend/infra/auth/user_auth_handler.py index 2661fcb..37afd5f 100644 --- a/apps/authentication/backend/infra/auth/user_auth_handler.py +++ b/apps/authentication/backend/infra/auth/user_auth_handler.py @@ -235,6 +235,31 @@ class UserAuthHandler: if not result: raise Exception("Failed to update user password in code depot") + async def save_magicleaps_password_auth_method(self, user_id: str, user_flid, password: str): + """save password auth method to user_password doc for MagicLeaps users (skips depot service) + + Args: + user_id (str): user id + password (str): user password + """ + password_hashed = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()) + + user_password = await UserPasswordDoc.find( + UserPasswordDoc.user_id == user_id + ).first_or_none() + + if user_password is None: + new_user_password = UserPasswordDoc( + user_id=user_id, password=password_hashed + ) + await new_user_password.create() + else: + user_password.password = password_hashed + await user_password.save() + + # Skip depot service call for MagicLeaps users + # MagicLeaps users don't exist in Gitea, so we don't update depot password + async def reset_password(self, user_id: str): """clean password auth method from user_password doc diff --git a/apps/authentication/backend/services/auth/user_auth_service.py b/apps/authentication/backend/services/auth/user_auth_service.py index bab173f..cd529a3 100644 --- a/apps/authentication/backend/services/auth/user_auth_service.py +++ b/apps/authentication/backend/services/auth/user_auth_service.py @@ -51,3 +51,10 @@ class UserAuthService: return await self.user_auth_handler.save_password_auth_method( user_id, user_flid, password ) + + async def save_magicleaps_password_auth_method( + self, user_id: str, user_flid: str, password: str + ): + return await self.user_auth_handler.save_magicleaps_password_auth_method( + user_id, user_flid, password + ) diff --git a/apps/authentication/webapi/routes/signin/__init__.py b/apps/authentication/webapi/routes/signin/__init__.py index 2ee91b6..9294111 100644 --- a/apps/authentication/webapi/routes/signin/__init__.py +++ b/apps/authentication/webapi/routes/signin/__init__.py @@ -5,6 +5,7 @@ from .signin_with_email_and_password import router as se_router from .signin_with_email_and_code import router as sw_router from .signin_with_magicleaps_email_and_code import router as swm_router from .update_user_password import router as up_router +from .update_magicleaps_user_password import router as ump_router from .update_new_user_flid import router as uu_router from .reset_password_through_email import router as rp_router from .sign_out import router as so_router @@ -17,6 +18,7 @@ router.include_router(tms_router, prefix="/signin", tags=["signin"]) router.include_router(sw_router, prefix="/signin", tags=["signin"]) router.include_router(swm_router, prefix="/signin", tags=["signin"]) router.include_router(up_router, prefix="/signin", tags=["signin"]) +router.include_router(ump_router, prefix="/signin", tags=["signin"]) router.include_router(se_router, prefix="/signin", tags=["signin"]) router.include_router(so_router, prefix="/signin", tags=["signin"]) router.include_router(rp_router, prefix="/signin", tags=["signin"]) diff --git a/apps/authentication/webapi/routes/signin/update_magicleaps_user_password.py b/apps/authentication/webapi/routes/signin/update_magicleaps_user_password.py new file mode 100644 index 0000000..66ec03d --- /dev/null +++ b/apps/authentication/webapi/routes/signin/update_magicleaps_user_password.py @@ -0,0 +1,55 @@ +from backend.application.signin_hub import SignInHub +from pydantic import BaseModel +from fastapi import APIRouter, Security, HTTPException +from common.token.token_manager import TokenManager +from fastapi.encoders import jsonable_encoder +from fastapi.responses import JSONResponse +from starlette.status import HTTP_401_UNAUTHORIZED +from jose import jwt +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from common.config.app_settings import app_settings + +router = APIRouter() +token_manager = TokenManager() +# Web API +# update_magicleaps_user_password +# + + +class RequestIn(BaseModel): + password: str + password2: str + + +@router.post( + "/update-magicleaps-user-password", + operation_id="magicleaps_update_user_password", + summary="update MagicLeaps user's sign-in password", + description="Update the MagicLeaps user's sign-in password without updating code depot. If the password was not set yet, this will enable the user to log in using the password", + response_description="signin_type:0 meaning simplified(using email) signin, \ + 1 meaning standard(using FLID and passward) signin", +) +async def update_magicleaps_user_password( + item: RequestIn, + credentials: HTTPAuthorizationCredentials = Security(HTTPBearer()), +): + payload = jwt.decode( + credentials.credentials, + app_settings.JWT_SECRET_KEY, + algorithms=[app_settings.JWT_ALGORITHM], + ) + + user_id = payload.get("subject").get("id") + if not user_id: + raise HTTPException( + status_code=HTTP_401_UNAUTHORIZED, detail="Could not validate credentials" + ) + if item.password != item.password2: + return JSONResponse( + content=jsonable_encoder( + {"error": "password and password2 are not the same"} + ) + ) + else: + result = await SignInHub().update_magicleaps_user_password(user_id, item.password) + return JSONResponse(content=jsonable_encoder(result))