Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions infra/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,10 @@ module containerAppFrontend 'br/public:avm/res/app/container-app:0.18.1' = {
name: 'REACT_APP_MSAL_REDIRECT_URL'
value: '/'
}
{
name: 'ALLOWED_ORIGINS'
value: 'https://${frontEndContainerAppName}.${containerAppsEnvironment.outputs.defaultDomain}'
}
]
resources: {
cpu: '1'
Expand Down
10 changes: 7 additions & 3 deletions infra/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"_generator": {
"name": "bicep",
"version": "0.41.2.15936",
"templateHash": "8495628770560205121"
"templateHash": "10582002328170601028"
}
},
"parameters": {
Expand Down Expand Up @@ -26120,8 +26120,8 @@
},
"dependsOn": [
"appIdentity",
"[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageQueue)]",
"[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageBlob)]",
"[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageQueue)]",
"virtualNetwork"
]
},
Expand Down Expand Up @@ -33808,9 +33808,9 @@
},
"dependsOn": [
"aiFoundryAiServices",
"[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]",
"[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]",
"[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)]",
"[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]",
"virtualNetwork"
]
},
Expand Down Expand Up @@ -40908,6 +40908,10 @@
{
"name": "REACT_APP_MSAL_REDIRECT_URL",
"value": "/"
},
{
"name": "ALLOWED_ORIGINS",
"value": "[format('https://{0}.{1}', variables('frontEndContainerAppName'), reference('containerAppsEnvironment').outputs.defaultDomain.value)]"
}
],
"resources": {
Expand Down
4 changes: 4 additions & 0 deletions infra/main_custom.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -1229,6 +1229,10 @@ module containerAppFrontend 'br/public:avm/res/app/container-app:0.18.1' = {
name: 'APP_ENV'
value: 'prod'
}
{
name: 'ALLOWED_ORIGINS'
value: 'https://${frontEndContainerAppName}.${containerAppsEnvironment.outputs.defaultDomain}'
}
]
resources: {
cpu: '1'
Expand Down
40 changes: 27 additions & 13 deletions src/frontend/frontend_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@

import uvicorn
from dotenv import load_dotenv
from fastapi import FastAPI
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, HTMLResponse
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
from fastapi.staticfiles import StaticFiles

# Load environment variables from .env file
load_dotenv()

app = FastAPI()

# Read allowed origins from environment; fall back to same-origin only
_allowed_origins = os.getenv("ALLOWED_ORIGINS", "").split(",")
_allowed_origins = [o.strip() for o in _allowed_origins if o.strip()]

app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_origins=_allowed_origins,
allow_methods=["GET"],
allow_headers=["*"],
)

Expand All @@ -35,20 +39,27 @@ async def serve_index():


@app.get("/config")
async def get_config():
async def get_config(request: Request):
# Only serve config to same-origin requests by checking the Referer/Origin
origin = request.headers.get("origin") or ""
referer = request.headers.get("referer") or ""
host = request.headers.get("host") or ""
if origin and not origin.endswith(host):
return JSONResponse(status_code=403, content={"detail": "Forbidden"})

config = {
"API_URL": os.getenv("API_URL", "API_URL not set"),
"API_URL": os.getenv("API_URL", ""),
"REACT_APP_MSAL_AUTH_CLIENTID": os.getenv(
"REACT_APP_MSAL_AUTH_CLIENTID", "Client ID not set"
"REACT_APP_MSAL_AUTH_CLIENTID", ""
),
"REACT_APP_MSAL_AUTH_AUTHORITY": os.getenv(
"REACT_APP_MSAL_AUTH_AUTHORITY", "Authority not set"
"REACT_APP_MSAL_AUTH_AUTHORITY", ""
),
"REACT_APP_MSAL_REDIRECT_URL": os.getenv(
"REACT_APP_MSAL_REDIRECT_URL", "Redirect URL not set"
"REACT_APP_MSAL_REDIRECT_URL", ""
),
"REACT_APP_MSAL_POST_REDIRECT_URL": os.getenv(
"REACT_APP_MSAL_POST_REDIRECT_URL", "Post Redirect URL not set"
"REACT_APP_MSAL_POST_REDIRECT_URL", ""
),
"REACT_APP_WEB_SCOPE": os.getenv(
"REACT_APP_WEB_SCOPE", ""
Expand All @@ -63,9 +74,12 @@ async def get_config():

@app.get("/{full_path:path}")
async def serve_app(full_path: str):
# First check if file exists in build directory
file_path = os.path.join(BUILD_DIR, full_path)
if os.path.exists(file_path):
# Resolve the requested path and ensure it stays within BUILD_DIR
file_path = os.path.realpath(os.path.join(BUILD_DIR, full_path))
build_dir_real = os.path.realpath(BUILD_DIR)
if not file_path.startswith(build_dir_real + os.sep) and file_path != build_dir_real:
return FileResponse(INDEX_HTML)
if os.path.isfile(file_path):
return FileResponse(file_path)
# Otherwise serve index.html for client-side routing
return FileResponse(INDEX_HTML)
Expand Down
Loading