Merge pull request #157 from valueonag/feat/demo-system-readieness

Feat/demo system readieness
This commit is contained in:
Patrick Motsch 2026-05-08 14:36:02 +02:00 committed by GitHub
commit 92dc6172f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 59 additions and 24 deletions

View file

@ -4,6 +4,8 @@
APP_ENV_TYPE = int
APP_ENV_LABEL = Integration Instance
APP_API_URL = https://gateway-int.poweron.swiss
# Force SameSite=None+Secure for auth cookies (cross-site UI on poweron-center.net). Optional if APP_API_URL is https://
APP_COOKIE_SECURE = true
APP_KEY_SYSVAR = CONFIG_KEY
APP_INIT_PASS_ADMIN_SECRET = INT_ENC:Z0FBQUFBQm8xSVRjWm41MWZ4TUZGaVlrX3pWZWNwakJsY3Facm0wLVZDd1VKeTFoZEVZQnItcEdUUnVJS1NXeDBpM2xKbGRsYmxOSmRhc29PZjJSU2txQjdLbUVrTTE1NEJjUXBHbV9NOVJWZUR3QlJkQnJvTEU9
APP_INIT_PASS_EVENT_SECRET = INT_ENC:Z0FBQUFBQm8xSVRjdmtrakgxa0djekZVNGtTZV8wM2I5UUpCZllveVBMWXROYk5yS3BiV3JEelJSM09VYTRONHpnY3VtMGxDRk5JTEZSRFhtcDZ0RVRmZ1RicTFhb3c5dVZRQ1o4SmlkLVpPTW5MMTU2eTQ0Vkk9
@ -19,7 +21,7 @@ APP_JWT_KEY_SECRET = INT_ENC:Z0FBQUFBQm8xSVRjNUctb2RwU25iR3ZnanBOdHZhWUtIajZ1RnZ
APP_TOKEN_EXPIRY=300
# CORS Configuration
APP_ALLOWED_ORIGINS=http://localhost:8080,http://localhost:5176,https://playground.poweron.swiss,https://playground-int.poweron.swiss,https://nyla.poweron.swiss,https://nyla-int.poweron.swiss
APP_ALLOWED_ORIGINS=http://localhost:8080,http://localhost:5176,https://playground.poweron.swiss,https://playground-int.poweron.swiss,https://nyla.poweron.swiss,https://nyla-int.poweron.swiss,https://nyla.poweron-center.net,https://nyla-int.poweron-center.net
# Logging configuration
APP_LOGGING_LOG_LEVEL = DEBUG

View file

@ -7,6 +7,7 @@ APP_KEY_SYSVAR = CONFIG_KEY
APP_INIT_PASS_ADMIN_SECRET = PROD_ENC:Z0FBQUFBQnBDM1Z3UnJRV0sySFlDblpXUlREclREaW1WbUt6bGtQYkdrNkZDOXNOLXFua1hqeFF2RHJnRXJ5VlVGV3hOZm41QjZOMlNTb0duYXNxZi05dXVTc2xDVkx0SVBFLUhncVo5T0VUZHE0UTZLWWw3ck09
APP_INIT_PASS_EVENT_SECRET = PROD_ENC:Z0FBQUFBQnBDM1Z3QVpIY19DQVZSSzJmc2F0VEZvQlU1cHBhTEgxdHdnR3g4eW01aTEzYTUxc1gxTDR1RVVpSHRXYjV6N1BLZUdCUGlfOW1qdy0xSHFVRkNBcGZvaGlSSkZycXRuUllaWnpyVGRoeFg1dGEyNUk9
APP_API_URL = https://gateway-prod.poweron.swiss
APP_COOKIE_SECURE = true
# PostgreSQL DB Host
DB_HOST=gateway-prod-server.postgres.database.azure.com
@ -19,7 +20,7 @@ APP_JWT_KEY_SECRET = PROD_ENC:Z0FBQUFBQnBDM1Z3elhfV0Rnd2pQRjlMdkVwX1FnSmRhSzNZUl
APP_TOKEN_EXPIRY=300
# CORS Configuration
APP_ALLOWED_ORIGINS=http://localhost:8080,http://localhost:5176,https://playground.poweron.swiss,https://playground-int.poweron.swiss,https://nyla.poweron.swiss,https://nyla-int.poweron.swiss
APP_ALLOWED_ORIGINS=http://localhost:8080,http://localhost:5176,https://playground.poweron.swiss,https://playground-int.poweron.swiss,https://nyla.poweron.swiss,https://nyla-int.poweron.swiss,https://nyla.poweron-center.net,https://nyla-int.poweron-center.net
# Logging configuration
APP_LOGGING_LOG_LEVEL = DEBUG

View file

@ -19,10 +19,28 @@ ALGORITHM = APP_CONFIG.get("Auth_ALGORITHM")
ACCESS_TOKEN_EXPIRE_MINUTES = int(APP_CONFIG.get("APP_TOKEN_EXPIRY"))
REFRESH_TOKEN_EXPIRE_DAYS = int(APP_CONFIG.get("APP_REFRESH_TOKEN_EXPIRY", "7"))
# Cookie security settings - use secure cookies based on whether API uses HTTPS
# Cookies must have secure=True on HTTPS sites, secure=False on HTTP sites
APP_API_URL = APP_CONFIG.get("APP_API_URL", "http://localhost:8000")
USE_SECURE_COOKIES = APP_API_URL.startswith("https://") if APP_API_URL else False
def _cookiePolicy() -> Tuple[bool, str, str]:
"""
Return (useSecure, samesiteStarlette, samesiteSetCookieHeader).
Evaluated on each Set-Cookie so policy is not frozen at module import (config refresh / load order).
Cross-origin SPA + API: SameSite=None and Secure=True so credentialed fetch sends cookies.
HTTP dev: Lax + Secure=False.
APP_COOKIE_SECURE: explicit true/false (1/0, yes/no) overrides the APP_API_URL heuristic.
"""
explicit = (APP_CONFIG.get("APP_COOKIE_SECURE") or "").strip().lower()
if explicit in ("1", "true", "yes"):
useSecure = True
elif explicit in ("0", "false", "no"):
useSecure = False
else:
apiUrl = (APP_CONFIG.get("APP_API_URL") or "").strip()
useSecure = apiUrl.startswith("https://")
samesite = "none" if useSecure else "lax"
samesiteHeader = "None" if useSecure else "Lax"
return useSecure, samesite, samesiteHeader
def createAccessToken(data: dict, expiresDelta: Optional[timedelta] = None) -> Tuple[str, "datetime"]:
@ -54,13 +72,14 @@ def createRefreshToken(data: dict) -> Tuple[str, "datetime"]:
def setAccessTokenCookie(response: Response, token: str, expiresDelta: Optional[timedelta] = None) -> None:
"""Set access token as httpOnly cookie."""
useSecure, samesite, _ = _cookiePolicy()
maxAge = int(expiresDelta.total_seconds()) if expiresDelta else ACCESS_TOKEN_EXPIRE_MINUTES * 60
response.set_cookie(
key="auth_token",
value=token,
httponly=True,
secure=USE_SECURE_COOKIES, # Only secure in production (HTTPS)
samesite="strict",
secure=useSecure,
samesite=samesite,
path="/",
max_age=maxAge
)
@ -68,12 +87,13 @@ def setAccessTokenCookie(response: Response, token: str, expiresDelta: Optional[
def setRefreshTokenCookie(response: Response, token: str) -> None:
"""Set refresh token as httpOnly cookie."""
useSecure, samesite, _ = _cookiePolicy()
response.set_cookie(
key="refresh_token",
value=token,
httponly=True,
secure=USE_SECURE_COOKIES, # Only secure in production (HTTPS)
samesite="strict",
secure=useSecure,
samesite=samesite,
path="/",
max_age=REFRESH_TOKEN_EXPIRE_DAYS * 24 * 60 * 60
)
@ -84,17 +104,23 @@ def clearAccessTokenCookie(response: Response) -> None:
Clear access token cookie by setting it to expire immediately.
Uses both raw header manipulation and FastAPI's delete_cookie for maximum browser compatibility.
"""
# Build secure flag based on environment
secure_flag = "; Secure" if USE_SECURE_COOKIES else ""
useSecure, samesite, samesiteHeader = _cookiePolicy()
secure_flag = "; Secure" if useSecure else ""
# Primary method: Raw Set-Cookie header for guaranteed deletion
response.headers.append(
"Set-Cookie",
f"auth_token=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly{secure_flag}; SameSite=Strict"
f"auth_token=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly{secure_flag}; SameSite={samesiteHeader}"
)
# Fallback: Also use FastAPI's built-in method (match SameSite/Secure for invalidation)
response.delete_cookie(
key="auth_token",
path="/",
secure=useSecure,
httponly=True,
samesite=samesite,
)
# Fallback: Also use FastAPI's built-in method
response.delete_cookie(key="auth_token", path="/")
def clearRefreshTokenCookie(response: Response) -> None:
@ -102,16 +128,22 @@ def clearRefreshTokenCookie(response: Response) -> None:
Clear refresh token cookie by setting it to expire immediately.
Uses both raw header manipulation and FastAPI's delete_cookie for maximum browser compatibility.
"""
# Build secure flag based on environment
secure_flag = "; Secure" if USE_SECURE_COOKIES else ""
useSecure, samesite, samesiteHeader = _cookiePolicy()
secure_flag = "; Secure" if useSecure else ""
# Primary method: Raw Set-Cookie header for guaranteed deletion
response.headers.append(
"Set-Cookie",
f"refresh_token=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly{secure_flag}; SameSite=Strict"
f"refresh_token=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly{secure_flag}; SameSite={samesiteHeader}"
)
# Fallback: Also use FastAPI's built-in method (match SameSite/Secure for invalidation)
response.delete_cookie(
key="refresh_token",
path="/",
secure=useSecure,
httponly=True,
samesite=samesite,
)
# Fallback: Also use FastAPI's built-in method
response.delete_cookie(key="refresh_token", path="/")