From 04381779342d5e392194e1c13c2cf455b84650c6 Mon Sep 17 00:00:00 2001 From: Ida Date: Fri, 12 Jun 2026 09:28:11 +0200 Subject: [PATCH] =?UTF-8?q?FIX:=20mandant=20wird=20erstellt=20wenn=20sich?= =?UTF-8?q?=20ein=20user=20mit=20microsoft=20oder=20google=20das=20erste?= =?UTF-8?q?=20mal=20registriert,=20damit=20er=20die=20app=20tats=C3=A4chli?= =?UTF-8?q?ch=20nutzen=20kann?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/auth/homeMandateService.py | 55 +++++++++++++++++++++++++++ modules/routes/routeMfa.py | 15 ++++++++ modules/routes/routeSecurityGoogle.py | 6 +++ modules/routes/routeSecurityLocal.py | 48 ++--------------------- modules/routes/routeSecurityMsft.py | 15 ++++++++ 5 files changed, 94 insertions(+), 45 deletions(-) create mode 100644 modules/auth/homeMandateService.py diff --git a/modules/auth/homeMandateService.py b/modules/auth/homeMandateService.py new file mode 100644 index 00000000..97e70683 --- /dev/null +++ b/modules/auth/homeMandateService.py @@ -0,0 +1,55 @@ +# Copyright (c) 2026 PowerOn AG +# All rights reserved. +"""Ensure new users receive a Home mandate on first login.""" + +import logging + +logger = logging.getLogger(__name__) + + +def ensureHomeMandate(rootInterface, user) -> None: + """Ensure user has a Home mandate, but only if they have no mandate memberships + AND no pending invitations. + + Invited users should NOT get a Home mandate — they join existing mandates via + invitation acceptance and can create their own later via onboarding. + """ + userId = str(user.id) + userMandates = rootInterface.getUserMandates(userId) + + if userMandates: + for um in userMandates: + mandate = rootInterface.getMandate(um.mandateId) + if mandate and (mandate.name or "").startswith("Home ") and not mandate.isSystem: + return + logger.debug( + f"User {user.username} has {len(userMandates)} mandate(s) but no Home — skipping auto-creation" + ) + return + + try: + normalizedEmail = (user.email or "").strip().lower() if user.email else None + pendingByUsername = rootInterface.getInvitationsByTargetUsername(user.username) + pendingByEmail = ( + rootInterface.getInvitationsByEmail(normalizedEmail) if normalizedEmail else [] + ) + seenIds = set() + for inv in pendingByUsername + pendingByEmail: + if inv.id in seenIds: + continue + seenIds.add(inv.id) + if not inv.revokedAt and (inv.currentUses or 0) < (inv.maxUses or 1): + logger.info( + f"User {user.username} has pending invitation(s) — skipping Home mandate creation" + ) + return + except Exception as e: + logger.warning(f"Could not check pending invitations for {user.username}: {e}") + + homeMandateLabel = f"Home {user.username}" + rootInterface._provisionMandateForUser( + userId=userId, + mandateLabel=homeMandateLabel, + planKey="TRIAL_14D", + ) + logger.info(f"Created Home mandate '{homeMandateLabel}' for user {user.username}") diff --git a/modules/routes/routeMfa.py b/modules/routes/routeMfa.py index 6dafdf4e..eb66965b 100644 --- a/modules/routes/routeMfa.py +++ b/modules/routes/routeMfa.py @@ -26,6 +26,7 @@ from modules.auth import ( setAccessTokenCookie, setRefreshTokenCookie, ) +from modules.auth.homeMandateService import ensureHomeMandate from modules.auth.mfaService import ( generateSetup, confirmSetup, @@ -229,6 +230,20 @@ def mfaVerify( ) userInterface.saveAccessToken(dbToken) + try: + ensureHomeMandate(rootInterface, user) + except Exception as homeErr: + logger.error(f"Error ensuring Home mandate for user {username}: {homeErr}") + + try: + activatedCount = rootInterface._activatePendingSubscriptions(userId) + if activatedCount > 0: + logger.info( + f"Activated {activatedCount} pending subscription(s) for user {username} after MFA" + ) + except Exception as subErr: + logger.error(f"Error activating subscriptions after MFA verify: {subErr}") + logger.info("MFA verify successful for user %s", username) # Mark device as trusted so MFA is skipped on next login from this device diff --git a/modules/routes/routeSecurityGoogle.py b/modules/routes/routeSecurityGoogle.py index a363f7bf..d94876bd 100644 --- a/modules/routes/routeSecurityGoogle.py +++ b/modules/routes/routeSecurityGoogle.py @@ -34,6 +34,7 @@ from modules.auth import ( ) from modules.auth.tokenManager import TokenManager from modules.auth.oauthProviderConfig import googleAuthScopes, googleDataScopes +from modules.auth.homeMandateService import ensureHomeMandate from modules.shared.timeUtils import createExpirationTimestamp, getUtcTimestamp, parseTimestamp from modules.shared.i18nRegistry import apiRouteContext routeApiMsg = apiRouteContext("routeSecurityGoogle") @@ -278,6 +279,11 @@ async def auth_login_callback( ) # --- end MFA gate ----------------------------------------------------- + try: + ensureHomeMandate(rootInterface, user) + except Exception as homeErr: + logger.error(f"Error ensuring Home mandate for user {user.username}: {homeErr}") + jwt_token_data = { "sub": user.username, "userId": str(user.id), diff --git a/modules/routes/routeSecurityLocal.py b/modules/routes/routeSecurityLocal.py index 2b873e26..04a2c666 100644 --- a/modules/routes/routeSecurityLocal.py +++ b/modules/routes/routeSecurityLocal.py @@ -16,12 +16,13 @@ from jose import jwt # Import auth modules from modules.auth import getCurrentUser, limiter, SECRET_KEY, ALGORITHM, getRequestContext, RequestContext from modules.auth import createAccessToken, createRefreshToken, setAccessTokenCookie, setRefreshTokenCookie, clearAccessTokenCookie, clearRefreshTokenCookie -from modules.interfaces.interfaceDbApp import getInterface, getRootInterface, getRootInterface as _getRootIf +from modules.interfaces.interfaceDbApp import getInterface, getRootInterface from modules.datamodels.datamodelUam import User, UserInDB, AuthAuthority, Mandate from modules.datamodels.datamodelSecurity import Token, TokenPurpose from modules.shared.configuration import APP_CONFIG from modules.shared.timeUtils import getUtcTimestamp from modules.shared.i18nRegistry import apiRouteContext +from modules.auth.homeMandateService import ensureHomeMandate routeApiMsg = apiRouteContext("routeSecurityLocal") # Configure logger @@ -174,49 +175,6 @@ router = APIRouter( } ) -def _ensureHomeMandate(rootInterface, user) -> None: - """Ensure user has a Home mandate, but only if they have no mandate memberships - AND no pending invitations. - - Invited users should NOT get a Home mandate — they join existing mandates via - invitation acceptance and can create their own later via onboarding. - """ - userId = str(user.id) - userMandates = rootInterface.getUserMandates(userId) - - if userMandates: - for um in userMandates: - mandate = rootInterface.getMandate(um.mandateId) - if mandate and (mandate.name or "").startswith("Home ") and not mandate.isSystem: - return - logger.debug(f"User {user.username} has {len(userMandates)} mandate(s) but no Home — skipping auto-creation") - return - - try: - appIf = _getRootIf() - normalizedEmail = (user.email or "").strip().lower() if user.email else None - pendingByUsername = appIf.getInvitationsByTargetUsername(user.username) - pendingByEmail = appIf.getInvitationsByEmail(normalizedEmail) if normalizedEmail else [] - seenIds = set() - for inv in pendingByUsername + pendingByEmail: - if inv.id in seenIds: - continue - seenIds.add(inv.id) - if not inv.revokedAt and (inv.currentUses or 0) < (inv.maxUses or 1): - logger.info(f"User {user.username} has pending invitation(s) — skipping Home mandate creation") - return - except Exception as e: - logger.warning(f"Could not check pending invitations for {user.username}: {e}") - - homeMandateLabel = f"Home {user.username}" - rootInterface._provisionMandateForUser( - userId=userId, - mandateLabel=homeMandateLabel, - planKey="TRIAL_14D", - ) - logger.info(f"Created Home mandate '{homeMandateLabel}' for user {user.username}") - - @router.post("/login") @limiter.limit("30/minute") def login( @@ -364,7 +322,7 @@ def login( # Ensure user has a Home mandate (created on first login if missing) try: - _ensureHomeMandate(rootInterface, user) + ensureHomeMandate(rootInterface, user) except Exception as homeErr: logger.error(f"Error ensuring Home mandate for user {user.username}: {homeErr}") diff --git a/modules/routes/routeSecurityMsft.py b/modules/routes/routeSecurityMsft.py index 45d3deda..6325cfa8 100644 --- a/modules/routes/routeSecurityMsft.py +++ b/modules/routes/routeSecurityMsft.py @@ -35,6 +35,7 @@ from modules.auth import ( ) from modules.auth.tokenManager import TokenManager from modules.auth.oauthProviderConfig import msftAuthScopes, msftDataScopes, msftDataScopesForRefresh +from modules.auth.homeMandateService import ensureHomeMandate from modules.shared.timeUtils import createExpirationTimestamp, getUtcTimestamp, parseTimestamp from modules.shared.i18nRegistry import apiRouteContext routeApiMsg = apiRouteContext("routeSecurityMsft") @@ -251,6 +252,20 @@ async def auth_login_callback( ) # --- end MFA gate ----------------------------------------------------- + try: + ensureHomeMandate(rootInterface, user) + except Exception as homeErr: + logger.error(f"Error ensuring Home mandate for user {user.username}: {homeErr}") + + try: + activatedCount = rootInterface._activatePendingSubscriptions(str(user.id)) + if activatedCount > 0: + logger.info( + f"Activated {activatedCount} pending subscription(s) for user {user.username}" + ) + except Exception as subErr: + logger.error(f"Error activating subscriptions on Microsoft login: {subErr}") + jwt_token_data = { "sub": user.username, "userId": str(user.id),