FIX: mandant wird erstellt wenn sich ein user mit microsoft oder google das erste mal registriert, damit er die app tatsächlich nutzen kann
This commit is contained in:
parent
66b44e5c78
commit
0438177934
5 changed files with 94 additions and 45 deletions
55
modules/auth/homeMandateService.py
Normal file
55
modules/auth/homeMandateService.py
Normal file
|
|
@ -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}")
|
||||||
|
|
@ -26,6 +26,7 @@ from modules.auth import (
|
||||||
setAccessTokenCookie,
|
setAccessTokenCookie,
|
||||||
setRefreshTokenCookie,
|
setRefreshTokenCookie,
|
||||||
)
|
)
|
||||||
|
from modules.auth.homeMandateService import ensureHomeMandate
|
||||||
from modules.auth.mfaService import (
|
from modules.auth.mfaService import (
|
||||||
generateSetup,
|
generateSetup,
|
||||||
confirmSetup,
|
confirmSetup,
|
||||||
|
|
@ -229,6 +230,20 @@ def mfaVerify(
|
||||||
)
|
)
|
||||||
userInterface.saveAccessToken(dbToken)
|
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)
|
logger.info("MFA verify successful for user %s", username)
|
||||||
|
|
||||||
# Mark device as trusted so MFA is skipped on next login from this device
|
# Mark device as trusted so MFA is skipped on next login from this device
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ from modules.auth import (
|
||||||
)
|
)
|
||||||
from modules.auth.tokenManager import TokenManager
|
from modules.auth.tokenManager import TokenManager
|
||||||
from modules.auth.oauthProviderConfig import googleAuthScopes, googleDataScopes
|
from modules.auth.oauthProviderConfig import googleAuthScopes, googleDataScopes
|
||||||
|
from modules.auth.homeMandateService import ensureHomeMandate
|
||||||
from modules.shared.timeUtils import createExpirationTimestamp, getUtcTimestamp, parseTimestamp
|
from modules.shared.timeUtils import createExpirationTimestamp, getUtcTimestamp, parseTimestamp
|
||||||
from modules.shared.i18nRegistry import apiRouteContext
|
from modules.shared.i18nRegistry import apiRouteContext
|
||||||
routeApiMsg = apiRouteContext("routeSecurityGoogle")
|
routeApiMsg = apiRouteContext("routeSecurityGoogle")
|
||||||
|
|
@ -278,6 +279,11 @@ async def auth_login_callback(
|
||||||
)
|
)
|
||||||
# --- end MFA gate -----------------------------------------------------
|
# --- 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 = {
|
jwt_token_data = {
|
||||||
"sub": user.username,
|
"sub": user.username,
|
||||||
"userId": str(user.id),
|
"userId": str(user.id),
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,13 @@ from jose import jwt
|
||||||
# Import auth modules
|
# Import auth modules
|
||||||
from modules.auth import getCurrentUser, limiter, SECRET_KEY, ALGORITHM, getRequestContext, RequestContext
|
from modules.auth import getCurrentUser, limiter, SECRET_KEY, ALGORITHM, getRequestContext, RequestContext
|
||||||
from modules.auth import createAccessToken, createRefreshToken, setAccessTokenCookie, setRefreshTokenCookie, clearAccessTokenCookie, clearRefreshTokenCookie
|
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.datamodelUam import User, UserInDB, AuthAuthority, Mandate
|
||||||
from modules.datamodels.datamodelSecurity import Token, TokenPurpose
|
from modules.datamodels.datamodelSecurity import Token, TokenPurpose
|
||||||
from modules.shared.configuration import APP_CONFIG
|
from modules.shared.configuration import APP_CONFIG
|
||||||
from modules.shared.timeUtils import getUtcTimestamp
|
from modules.shared.timeUtils import getUtcTimestamp
|
||||||
from modules.shared.i18nRegistry import apiRouteContext
|
from modules.shared.i18nRegistry import apiRouteContext
|
||||||
|
from modules.auth.homeMandateService import ensureHomeMandate
|
||||||
routeApiMsg = apiRouteContext("routeSecurityLocal")
|
routeApiMsg = apiRouteContext("routeSecurityLocal")
|
||||||
|
|
||||||
# Configure logger
|
# 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")
|
@router.post("/login")
|
||||||
@limiter.limit("30/minute")
|
@limiter.limit("30/minute")
|
||||||
def login(
|
def login(
|
||||||
|
|
@ -364,7 +322,7 @@ def login(
|
||||||
|
|
||||||
# Ensure user has a Home mandate (created on first login if missing)
|
# Ensure user has a Home mandate (created on first login if missing)
|
||||||
try:
|
try:
|
||||||
_ensureHomeMandate(rootInterface, user)
|
ensureHomeMandate(rootInterface, user)
|
||||||
except Exception as homeErr:
|
except Exception as homeErr:
|
||||||
logger.error(f"Error ensuring Home mandate for user {user.username}: {homeErr}")
|
logger.error(f"Error ensuring Home mandate for user {user.username}: {homeErr}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ from modules.auth import (
|
||||||
)
|
)
|
||||||
from modules.auth.tokenManager import TokenManager
|
from modules.auth.tokenManager import TokenManager
|
||||||
from modules.auth.oauthProviderConfig import msftAuthScopes, msftDataScopes, msftDataScopesForRefresh
|
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.timeUtils import createExpirationTimestamp, getUtcTimestamp, parseTimestamp
|
||||||
from modules.shared.i18nRegistry import apiRouteContext
|
from modules.shared.i18nRegistry import apiRouteContext
|
||||||
routeApiMsg = apiRouteContext("routeSecurityMsft")
|
routeApiMsg = apiRouteContext("routeSecurityMsft")
|
||||||
|
|
@ -251,6 +252,20 @@ async def auth_login_callback(
|
||||||
)
|
)
|
||||||
# --- end MFA gate -----------------------------------------------------
|
# --- 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 = {
|
jwt_token_data = {
|
||||||
"sub": user.username,
|
"sub": user.username,
|
||||||
"userId": str(user.id),
|
"userId": str(user.id),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue