From 3c63488377815f85a6a4fb2c31e245e1ef937714 Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Thu, 6 Nov 2025 11:42:10 +0100 Subject: [PATCH] fixed ext logins msft and google --- modules/routes/routeSecurityGoogle.py | 39 +++++++++++++++++++++------ modules/routes/routeSecurityMsft.py | 34 ++++++++++++++++++----- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/modules/routes/routeSecurityGoogle.py b/modules/routes/routeSecurityGoogle.py index fbd9a445..de3c3857 100644 --- a/modules/routes/routeSecurityGoogle.py +++ b/modules/routes/routeSecurityGoogle.py @@ -14,6 +14,7 @@ from modules.shared.configuration import APP_CONFIG from modules.interfaces.interfaceDbAppObjects import getInterface, getRootInterface from modules.datamodels.datamodelUam import AuthAuthority, User, ConnectionStatus, UserConnection from modules.security.auth import getCurrentUser, limiter +from modules.security.jwtService import createAccessToken, setAccessTokenCookie, createRefreshToken, setRefreshTokenCookie from modules.shared.timezoneUtils import createExpirationTimestamp, getUtcTimestamp # Configure logger @@ -202,7 +203,7 @@ async def login( ) @router.get("/auth/callback") -async def auth_callback(code: str, state: str, request: Request) -> HTMLResponse: +async def auth_callback(code: str, state: str, request: Request, response: Response) -> HTMLResponse: """Handle Google OAuth callback""" try: # Import Token at function level to avoid scoping issues @@ -337,34 +338,47 @@ async def auth_callback(code: str, state: str, request: Request) -> HTMLResponse ) # Create JWT token data (like Microsoft does) - from modules.security.jwtService import createAccessToken jwt_token_data = { "sub": user.username, "mandateId": str(user.mandateId), "userId": str(user.id), - "authenticationAuthority": AuthAuthority.GOOGLE + "authenticationAuthority": AuthAuthority.GOOGLE.value } # Create JWT access token jwt_token, jwt_expires_at = createAccessToken(jwt_token_data) + + # Create refresh token + refresh_token, _refresh_expires = createRefreshToken(jwt_token_data) + + # Decode token to get jti for database record + from jose import jwt + from modules.security.auth import SECRET_KEY, ALGORITHM + payload = jwt.decode(jwt_token, SECRET_KEY, algorithms=[ALGORITHM]) + jti = payload.get("jti") - # Create JWT token + # Create JWT token with matching id token = Token( + id=jti, userId=user.id, # Use local user's ID authority=AuthAuthority.GOOGLE, tokenAccess=jwt_token, # Use JWT token instead of Google access token tokenRefresh=token_response.get("refresh_token", ""), tokenType="bearer", expiresAt=jwt_expires_at.timestamp(), - createdAt=getUtcTimestamp() + createdAt=getUtcTimestamp(), + mandateId=str(user.mandateId) ) # Save access token (no connectionId) appInterface = getInterface(user) appInterface.saveAccessToken(token) - # Return success page with token data - return HTMLResponse( + # Convert token to dict and ensure proper timestamp handling + token_dict = token.model_dump() + + # Create HTML response + html_response = HTMLResponse( content=f""" Authentication Successful @@ -374,7 +388,7 @@ async def auth_callback(code: str, state: str, request: Request) -> HTMLResponse window.opener.postMessage({{ type: 'google_auth_success', access_token: {json.dumps(token_response["access_token"])}, - token_data: {json.dumps(token.model_dump())} + token_data: {json.dumps(token_dict)} }}, '*'); }} setTimeout(() => window.close(), 1000); @@ -383,6 +397,15 @@ async def auth_callback(code: str, state: str, request: Request) -> HTMLResponse """ ) + + # Set access token as httpOnly cookie (like local login) + # HTMLResponse inherits from Response, so we can set cookies directly on it + setAccessTokenCookie(html_response, jwt_token, expiresDelta=None) + + # Set refresh token as httpOnly cookie + setRefreshTokenCookie(html_response, refresh_token) + + return html_response else: # Handle connection flow if not connection_id or not user_id: diff --git a/modules/routes/routeSecurityMsft.py b/modules/routes/routeSecurityMsft.py index 30c5d33e..9059b3da 100644 --- a/modules/routes/routeSecurityMsft.py +++ b/modules/routes/routeSecurityMsft.py @@ -15,7 +15,7 @@ from modules.interfaces.interfaceDbAppObjects import getInterface, getRootInterf from modules.datamodels.datamodelUam import AuthAuthority, User, ConnectionStatus, UserConnection from modules.datamodels.datamodelSecurity import Token from modules.security.auth import getCurrentUser, limiter -from modules.security.jwtService import createAccessToken +from modules.security.jwtService import createAccessToken, setAccessTokenCookie, createRefreshToken, setRefreshTokenCookie from modules.shared.timezoneUtils import createExpirationTimestamp, getUtcTimestamp # Configure logger @@ -123,7 +123,7 @@ async def login( ) @router.get("/auth/callback") -async def auth_callback(code: str, state: str, request: Request) -> HTMLResponse: +async def auth_callback(code: str, state: str, request: Request, response: Response) -> HTMLResponse: """Handle Microsoft OAuth callback""" try: # Parse state @@ -212,20 +212,31 @@ async def auth_callback(code: str, state: str, request: Request) -> HTMLResponse "sub": user.username, "mandateId": str(user.mandateId), "userId": str(user.id), - "authenticationAuthority": AuthAuthority.MSFT + "authenticationAuthority": AuthAuthority.MSFT.value } # Create JWT access token jwt_token, jwt_expires_at = createAccessToken(jwt_token_data) + + # Create refresh token + refresh_token, _refresh_expires = createRefreshToken(jwt_token_data) + + # Decode token to get jti for database record + from jose import jwt + from modules.security.auth import SECRET_KEY, ALGORITHM + payload = jwt.decode(jwt_token, SECRET_KEY, algorithms=[ALGORITHM]) + jti = payload.get("jti") - # Create JWT token + # Create JWT token with matching id jwt_token_obj = Token( + id=jti, userId=user.id, authority=AuthAuthority.MSFT, tokenAccess=jwt_token, tokenType="bearer", expiresAt=jwt_expires_at.timestamp(), - createdAt=getUtcTimestamp() + createdAt=getUtcTimestamp(), + mandateId=str(user.mandateId) ) # Save JWT access token @@ -236,8 +247,8 @@ async def auth_callback(code: str, state: str, request: Request) -> HTMLResponse # Remove datetime conversion logic - models now handle this automatically # The token model already returns float timestamps - # Return success page with token data - return HTMLResponse( + # Create HTML response + html_response = HTMLResponse( content=f""" Authentication Successful @@ -255,6 +266,15 @@ async def auth_callback(code: str, state: str, request: Request) -> HTMLResponse """ ) + + # Set access token as httpOnly cookie (like local login) + # HTMLResponse inherits from Response, so we can set cookies directly on it + setAccessTokenCookie(html_response, jwt_token, expiresDelta=None) + + # Set refresh token as httpOnly cookie + setRefreshTokenCookie(html_response, refresh_token) + + return html_response else: # Handle connection flow if not connection_id or not user_id: