From 6a207c7e79abc9400724caa3ee873f1e579deeb0 Mon Sep 17 00:00:00 2001 From: haolou Date: Thu, 16 Oct 2025 18:50:04 +0800 Subject: [PATCH 1/3] fix: fix the small issue of user_id not found --- .../user_profile/user_profile_handler.py | 2 +- .../webapi/providers/database.py | 32 ++++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/apps/authentication/backend/infra/user_profile/user_profile_handler.py b/apps/authentication/backend/infra/user_profile/user_profile_handler.py index 168f84c..5a79fb7 100644 --- a/apps/authentication/backend/infra/user_profile/user_profile_handler.py +++ b/apps/authentication/backend/infra/user_profile/user_profile_handler.py @@ -94,7 +94,7 @@ class UserProfileHandler: async def create_provider_profile(self, user_id: str) -> ProviderProfileDoc: provider_profile = await ProviderProfileDoc.find_one( - ProviderProfileDoc.user_id == user_id + {"user_id": user_id} ) if provider_profile: return provider_profile diff --git a/apps/authentication/webapi/providers/database.py b/apps/authentication/webapi/providers/database.py index e1a2cf8..79d5166 100644 --- a/apps/authentication/webapi/providers/database.py +++ b/apps/authentication/webapi/providers/database.py @@ -10,7 +10,7 @@ from backend.models.user.models import ( AuthCodeDoc, UsageLogDoc ) -from backend.models.user_profile.models import BasicProfileDoc +from backend.models.user_profile.models import BasicProfileDoc, ProviderProfileDoc from backend.models.permission.models import PermissionDoc, RoleDoc, UserRoleDoc from common.config.app_settings import app_settings from common.log.module_logger import ModuleLogger @@ -25,7 +25,7 @@ import os MAIN_CLIENT: Optional[AsyncIOMotorClient] = None TENANT_CACHE: Optional['TenantDBCache'] = None -# Define document models +# Define document models document_models = [ UsageLogDoc, UserAccountDoc, @@ -34,6 +34,7 @@ document_models = [ UserMobileDoc, AuthCodeDoc, BasicProfileDoc, + ProviderProfileDoc, PermissionDoc, RoleDoc, UserRoleDoc @@ -46,6 +47,7 @@ tenant_document_models = [ UserMobileDoc, AuthCodeDoc, BasicProfileDoc, + ProviderProfileDoc, PermissionDoc, RoleDoc, UserRoleDoc @@ -58,7 +60,7 @@ class TenantDBCache: Uses main_db.tenant_doc to resolve mongodb_uri; caches clients with LRU. Database instances are created fresh each time from cached clients. """ - + def __init__(self, main_db: AsyncIOMotorDatabase, max_size: int = 64): self.main_db = main_db self.max_size = max_size @@ -69,13 +71,13 @@ class TenantDBCache: async def get_initialized_db(self, product_id: str) -> AsyncIOMotorDatabase: """Get tenant database with Beanie already initialized""" - + # fast-path: check if client is cached cached_client = self._cache.get(product_id) if cached_client: await self.module_logger.log_info(f"Found cached client for {product_id}") self._cache.move_to_end(product_id) - + # Get fresh database instance from cached client db = cached_client.get_default_database() if db is not None: @@ -95,7 +97,7 @@ class TenantDBCache: if cached_client: await self.module_logger.log_info(f"Double-check found cached client for {product_id}") self._cache.move_to_end(product_id) - + # Get fresh database instance from cached client db = cached_client.get_default_database() if db is not None: @@ -149,7 +151,7 @@ class TenantDBCache: detail=f"No default database found for tenant {product_id}", headers={"X-Error-Message": f"No default database found for tenant {product_id}"} ) - + # Initialize Beanie for this tenant database await init_beanie(database=db, document_models=tenant_document_models) await self.module_logger.log_info(f"Beanie initialization completed for new tenant database {product_id}") @@ -201,7 +203,7 @@ def register(app): @app.on_event("startup") async def start_database(): await initiate_database(app) - + @app.on_event("shutdown") async def shutdown_database(): await cleanup_database() @@ -217,9 +219,9 @@ async def check_database_initialized() -> ProbeResult: async def initiate_database(app): """Initialize main database and tenant cache""" global MAIN_CLIENT, TENANT_CACHE - + module_logger = ModuleLogger(sender_id="DatabaseInit") - + # 1) Create main/catalog client + DB MAIN_CLIENT = AsyncIOMotorClient(app_settings.MONGODB_URI) main_db = MAIN_CLIENT[app_settings.MONGODB_NAME] @@ -234,20 +236,20 @@ async def initiate_database(app): # 4) Store on app state for middleware to access app.state.main_db = main_db app.state.tenant_cache = TENANT_CACHE - + await module_logger.log_info("Database and tenant cache initialized successfully") async def cleanup_database(): """Cleanup database connections and cache""" global MAIN_CLIENT, TENANT_CACHE - + module_logger = ModuleLogger(sender_id="DatabaseCleanup") - + if TENANT_CACHE: await TENANT_CACHE.aclose() - + if MAIN_CLIENT: MAIN_CLIENT.close() - + await module_logger.log_info("Database connections closed successfully") \ No newline at end of file From 497e6080a3cb703621ae9b17f3ca2b0e1c4c3672 Mon Sep 17 00:00:00 2001 From: haolou Date: Mon, 20 Oct 2025 14:51:38 +0800 Subject: [PATCH 2/3] 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)) From 2036c4b9ae5bd9d1d7b00daead0e047b6d9e4ebb Mon Sep 17 00:00:00 2001 From: haolou Date: Mon, 20 Oct 2025 15:03:59 +0800 Subject: [PATCH 3/3] fix: change name to more general ones --- .../backend/application/signin_hub.py | 4 ++-- .../backend/business/signin_manager.py | 4 ++-- .../backend/infra/auth/user_auth_handler.py | 7 +++---- .../backend/services/auth/user_auth_service.py | 4 ++-- .../webapi/routes/signin/__init__.py | 4 ++-- ...assword.py => update_user_password_no_depot.py} | 14 +++++++------- 6 files changed, 18 insertions(+), 19 deletions(-) rename apps/authentication/webapi/routes/signin/{update_magicleaps_user_password.py => update_user_password_no_depot.py} (74%) diff --git a/apps/authentication/backend/application/signin_hub.py b/apps/authentication/backend/application/signin_hub.py index b5055d6..834f302 100644 --- a/apps/authentication/backend/application/signin_hub.py +++ b/apps/authentication/backend/application/signin_hub.py @@ -96,8 +96,8 @@ class SignInHub: ) @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( + async def update_user_password_no_depot(self, user_id: str, password: str) -> dict[str, any]: + return await self.signin_manager.update_user_password_no_depot( user_id=user_id, password=password ) diff --git a/apps/authentication/backend/business/signin_manager.py b/apps/authentication/backend/business/signin_manager.py index c7c5403..2469024 100644 --- a/apps/authentication/backend/business/signin_manager.py +++ b/apps/authentication/backend/business/signin_manager.py @@ -368,7 +368,7 @@ class SignInManager: ) return {"succeeded": True} - async def update_magicleaps_user_password(self, user_id: str, password: str) -> dict[str, any]: + async def update_user_password_no_depot(self, user_id: str, password: str) -> dict[str, any]: error_message = """ Password does not pass complexity requirements: - At least one lowercase character @@ -380,7 +380,7 @@ class SignInManager: 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( + await self.user_auth_service.save_password_auth_method_no_depot( user_id, user_flid, password ) return {"succeeded": True} diff --git a/apps/authentication/backend/infra/auth/user_auth_handler.py b/apps/authentication/backend/infra/auth/user_auth_handler.py index 37afd5f..1d5bdf1 100644 --- a/apps/authentication/backend/infra/auth/user_auth_handler.py +++ b/apps/authentication/backend/infra/auth/user_auth_handler.py @@ -235,8 +235,8 @@ 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) + async def save_password_auth_method_no_depot(self, user_id: str, user_flid, password: str): + """save password auth method to user_password doc without updating depot service Args: user_id (str): user id @@ -257,8 +257,7 @@ class UserAuthHandler: 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 + # Skip depot service call - 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 cd529a3..38cde61 100644 --- a/apps/authentication/backend/services/auth/user_auth_service.py +++ b/apps/authentication/backend/services/auth/user_auth_service.py @@ -52,9 +52,9 @@ class UserAuthService: user_id, user_flid, password ) - async def save_magicleaps_password_auth_method( + async def save_password_auth_method_no_depot( self, user_id: str, user_flid: str, password: str ): - return await self.user_auth_handler.save_magicleaps_password_auth_method( + return await self.user_auth_handler.save_password_auth_method_no_depot( user_id, user_flid, password ) diff --git a/apps/authentication/webapi/routes/signin/__init__.py b/apps/authentication/webapi/routes/signin/__init__.py index 9294111..1e6aab1 100644 --- a/apps/authentication/webapi/routes/signin/__init__.py +++ b/apps/authentication/webapi/routes/signin/__init__.py @@ -5,7 +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_user_password_no_depot import router as upnd_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 @@ -18,7 +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(upnd_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_user_password_no_depot.py similarity index 74% rename from apps/authentication/webapi/routes/signin/update_magicleaps_user_password.py rename to apps/authentication/webapi/routes/signin/update_user_password_no_depot.py index 66ec03d..7781984 100644 --- a/apps/authentication/webapi/routes/signin/update_magicleaps_user_password.py +++ b/apps/authentication/webapi/routes/signin/update_user_password_no_depot.py @@ -12,7 +12,7 @@ from common.config.app_settings import app_settings router = APIRouter() token_manager = TokenManager() # Web API -# update_magicleaps_user_password +# update_user_password_no_depot # @@ -22,14 +22,14 @@ class RequestIn(BaseModel): @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", + "/update-user-password-no-depot", + operation_id="user_update_user_password_no_depot", + summary="update user's sign-in password without depot service", + description="Update the 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( +async def update_user_password_no_depot( item: RequestIn, credentials: HTTPAuthorizationCredentials = Security(HTTPBearer()), ): @@ -51,5 +51,5 @@ async def update_magicleaps_user_password( ) ) else: - result = await SignInHub().update_magicleaps_user_password(user_id, item.password) + result = await SignInHub().update_user_password_no_depot(user_id, item.password) return JSONResponse(content=jsonable_encoder(result))