fix(security): add nonce-based CSP header to frontend route
This commit is contained in:
@@ -7,6 +7,7 @@ from typing import Optional
|
|||||||
import jwt
|
import jwt
|
||||||
import requests
|
import requests
|
||||||
import os
|
import os
|
||||||
|
import secrets
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
|
||||||
app = FastAPI(title="OIDC Token Validator")
|
app = FastAPI(title="OIDC Token Validator")
|
||||||
@@ -326,9 +327,27 @@ async def validate_token(request: TokenValidationRequest):
|
|||||||
@app.get("/", response_class=HTMLResponse)
|
@app.get("/", response_class=HTMLResponse)
|
||||||
async def read_root():
|
async def read_root():
|
||||||
"""Serve the frontend HTML file"""
|
"""Serve the frontend HTML file"""
|
||||||
|
nonce = secrets.token_urlsafe(16)
|
||||||
with open("templates/index.html", "r") as file:
|
with open("templates/index.html", "r") as file:
|
||||||
html_content = file.read()
|
html_content = file.read()
|
||||||
return HTMLResponse(content=html_content, status_code=200)
|
html_content = html_content.replace(
|
||||||
|
'<script>', f'<script nonce="{nonce}">', 1
|
||||||
|
)
|
||||||
|
csp = (
|
||||||
|
"default-src 'self'; "
|
||||||
|
f"script-src 'self' 'nonce-{nonce}' https://cdn.jsdelivr.net; "
|
||||||
|
"style-src 'self' https://cdn.jsdelivr.net; "
|
||||||
|
"connect-src 'self' https://login.infomaniak.com https://api.coingecko.com; "
|
||||||
|
"img-src 'self'; "
|
||||||
|
"object-src 'none'; "
|
||||||
|
"base-uri 'self'; "
|
||||||
|
"frame-ancestors 'none'"
|
||||||
|
)
|
||||||
|
return HTMLResponse(
|
||||||
|
content=html_content,
|
||||||
|
status_code=200,
|
||||||
|
headers={"Content-Security-Policy": csp},
|
||||||
|
)
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
async def health_check():
|
async def health_check():
|
||||||
|
|||||||
@@ -31,6 +31,26 @@ def test_get_client_config():
|
|||||||
assert isinstance(data["client_id"], str)
|
assert isinstance(data["client_id"], str)
|
||||||
assert len(data["client_id"]) > 0
|
assert len(data["client_id"]) > 0
|
||||||
|
|
||||||
|
def test_root_serves_csp_with_nonce():
|
||||||
|
"""Test that GET / returns a CSP header with a nonce and the inline script carries it."""
|
||||||
|
response = client.get("/")
|
||||||
|
assert response.status_code == 200
|
||||||
|
csp = response.headers.get("content-security-policy", "")
|
||||||
|
assert "nonce-" in csp
|
||||||
|
assert "script-src" in csp
|
||||||
|
assert "cdn.jsdelivr.net" in csp
|
||||||
|
assert "connect-src" in csp
|
||||||
|
assert "login.infomaniak.com" in csp
|
||||||
|
assert "api.coingecko.com" in csp
|
||||||
|
assert "object-src 'none'" in csp
|
||||||
|
body = response.text
|
||||||
|
assert "<script nonce=" in body
|
||||||
|
import re
|
||||||
|
match = re.search(r"nonce-([A-Za-z0-9_-]+)", csp)
|
||||||
|
assert match is not None
|
||||||
|
nonce = match.group(1)
|
||||||
|
assert f'nonce="{nonce}"' in body
|
||||||
|
|
||||||
@patch('main.verify_token')
|
@patch('main.verify_token')
|
||||||
def test_validate_token_success(mock_verify_token):
|
def test_validate_token_success(mock_verify_token):
|
||||||
"""Test successful token validation."""
|
"""Test successful token validation."""
|
||||||
|
|||||||
Reference in New Issue
Block a user