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: