Add API_PUBLIC_URL environment variable for public API access

- Added API_PUBLIC_URL to deploy-config and manifest.yml
- Updated app config to load API_PUBLIC_URL from environment
- Used API_PUBLIC_URL in CSP configuration for connect-src
This commit is contained in:
Beverly Nguyen
2025-05-19 14:41:41 -07:00
parent a7787cb919
commit 3c1574d070
10 changed files with 27 additions and 20 deletions
+2 -2
View File
@@ -171,7 +171,7 @@
"filename": "app/config.py", "filename": "app/config.py",
"hashed_secret": "577a4c667e4af8682ca431857214b3a920883efc", "hashed_secret": "577a4c667e4af8682ca431857214b3a920883efc",
"is_verified": false, "is_verified": false,
"line_number": 120, "line_number": 122,
"is_secret": false "is_secret": false
} }
], ],
@@ -644,5 +644,5 @@
} }
] ]
}, },
"generated_at": "2025-05-12T16:50:20Z" "generated_at": "2025-05-19T21:41:23Z"
} }
+7 -7
View File
@@ -141,7 +141,7 @@ navigation = {
def _csp(config): def _csp(config):
asset_domain = config["ASSET_DOMAIN"] asset_domain = config["ASSET_DOMAIN"]
logo_domain = config["LOGO_CDN_DOMAIN"] logo_domain = config["LOGO_CDN_DOMAIN"]
api_host_name = config["API_HOST_NAME"] api_public_url = config["API_PUBLIC_URL"]
csp = { csp = {
"default-src": ["'self'", asset_domain], "default-src": ["'self'", asset_domain],
@@ -172,14 +172,14 @@ def _csp(config):
"img-src": ["'self'", asset_domain, logo_domain], "img-src": ["'self'", asset_domain, logo_domain],
} }
if api_host_name: if api_public_url:
csp["connect-src"].append(api_host_name) csp["connect-src"].append(api_public_url)
# this is for web socket # this is for web socket
if api_host_name.startswith("http://"): if api_public_url.startswith("http://"):
ws_url = api_host_name.replace("http://", "ws://") ws_url = api_public_url.replace("http://", "ws://")
csp["connect-src"].append(ws_url) csp["connect-src"].append(ws_url)
elif api_host_name.startswith("https://"): elif api_public_url.startswith("https://"):
ws_url = api_host_name.replace("https://", "wss://") ws_url = api_public_url.replace("https://", "wss://")
csp["connect-src"].append(ws_url) csp["connect-src"].append(ws_url)
return csp return csp
+2
View File
@@ -12,6 +12,8 @@ class Config(object):
NOTIFY_APP_NAME = "admin" NOTIFY_APP_NAME = "admin"
NOTIFY_ENVIRONMENT = getenv("NOTIFY_ENVIRONMENT", "development") NOTIFY_ENVIRONMENT = getenv("NOTIFY_ENVIRONMENT", "development")
API_HOST_NAME = getenv("API_HOST_NAME", "localhost") API_HOST_NAME = getenv("API_HOST_NAME", "localhost")
API_PUBLIC_URL = getenv("API_PUBLIC_URL", "localhost")
ADMIN_BASE_URL = getenv("ADMIN_BASE_URL", "http://localhost:6012") ADMIN_BASE_URL = getenv("ADMIN_BASE_URL", "http://localhost:6012")
HEADER_COLOUR = ( HEADER_COLOUR = (
"#81878b" # mix(govuk-colour("dark-grey"), govuk-colour("mid-grey")) "#81878b" # mix(govuk-colour("dark-grey"), govuk-colour("mid-grey"))
+2 -2
View File
@@ -58,11 +58,11 @@ def view_job(service_id, job_id):
filter_args = parse_filter_args(request.args) filter_args = parse_filter_args(request.args)
filter_args["status"] = set_status_filters(filter_args) filter_args["status"] = set_status_filters(filter_args)
api_host_name = os.environ.get("API_HOST_NAME") api_public_url = os.environ.get("API_PUBLIC_URL")
return render_template( return render_template(
"views/jobs/job.html", "views/jobs/job.html",
api_host_name=api_host_name, api_public_url=api_public_url,
FEATURE_SOCKET_ENABLED=current_app.config["FEATURE_SOCKET_ENABLED"], FEATURE_SOCKET_ENABLED=current_app.config["FEATURE_SOCKET_ENABLED"],
job=job, job=job,
status=request.args.get("status", ""), status=request.args.get("status", ""),
+1 -1
View File
@@ -14,7 +14,7 @@
{% if not job.finished_processing %} {% if not job.finished_processing %}
<p class="max-width-full">This page refreshes automatically to show the latest message activity delivery rates, details, and reports.<br>You can watch it in progress or check back later.</p> <p class="max-width-full">This page refreshes automatically to show the latest message activity delivery rates, details, and reports.<br>You can watch it in progress or check back later.</p>
{% endif %} {% endif %}
<div data-job-id="{{ job.id }}" data-feature="{{FEATURE_SOCKET_ENABLED | lower}}" data-host="{{ api_host_name }}"> <div data-job-id="{{ job.id }}" data-feature="{{FEATURE_SOCKET_ENABLED | lower}}" data-host="{{ api_public_url }}">
{% if not job.finished_processing %} {% if not job.finished_processing %}
<div <div
data-resource="{{ updates_url }}" data-resource="{{ updates_url }}"
+1
View File
@@ -8,3 +8,4 @@ redis_enabled: 1
nr_agent_id: '1134302465' nr_agent_id: '1134302465'
nr_app_id: '1083160688' nr_app_id: '1083160688'
FEATURE_SOCKET_ENABLED: true FEATURE_SOCKET_ENABLED: true
API_PUBLIC_URL: https://notify-api-demo.app.cloud.gov
+1
View File
@@ -8,3 +8,4 @@ redis_enabled: 1
nr_agent_id: '1050708682' nr_agent_id: '1050708682'
nr_app_id: '1050708682' nr_app_id: '1050708682'
FEATURE_SOCKET_ENABLED: false FEATURE_SOCKET_ENABLED: false
API_PUBLIC_URL: https://notify-api-production.app.cloud.gov
+1
View File
@@ -8,3 +8,4 @@ redis_enabled: 1
nr_agent_id: '1134291385' nr_agent_id: '1134291385'
nr_app_id: '1031640326' nr_app_id: '1031640326'
FEATURE_SOCKET_ENABLED: false FEATURE_SOCKET_ENABLED: false
API_PUBLIC_URL: https://notify-api-staging.app.cloud.gov
+2
View File
@@ -61,5 +61,7 @@ applications:
LOGIN_DOT_GOV_INITIAL_SIGNIN_URL: ((LOGIN_DOT_GOV_INITIAL_SIGNIN_URL)) LOGIN_DOT_GOV_INITIAL_SIGNIN_URL: ((LOGIN_DOT_GOV_INITIAL_SIGNIN_URL))
LOGIN_DOT_GOV_CERTS_URL: ((LOGIN_DOT_GOV_CERTS_URL)) LOGIN_DOT_GOV_CERTS_URL: ((LOGIN_DOT_GOV_CERTS_URL))
API_PUBLIC_URL: ((API_PUBLIC_URL))
# feature flagging # feature flagging
FEATURE_SOCKET_ENABLED: ((FEATURE_SOCKET_ENABLED)) FEATURE_SOCKET_ENABLED: ((FEATURE_SOCKET_ENABLED))
+8 -8
View File
@@ -33,15 +33,15 @@ def test_owasp_useful_headers_set(
) )
assert search(r"style-src 'self' static\.example\.com 'nonce-.*';", csp) assert search(r"style-src 'self' static\.example\.com 'nonce-.*';", csp)
assert search(r"img-src 'self' static\.example\.com static-logos\.test\.com", csp) assert search(r"img-src 'self' static\.example\.com static-logos\.test\.com", csp)
api_host_name = current_app.config.get("API_HOST_NAME") api_public_url = current_app.config.get("API_PUBLIC_URL")
assert api_host_name is not None, f"API_HOST_NAME: {api_host_name} — is missing" assert api_public_url is not None, f"API_PUBLIC_URL: {api_public_url} — is missing"
assert api_host_name in csp assert api_public_url in csp
if api_host_name.startswith("http://"): if api_public_url.startswith("http://"):
assert api_host_name.replace("http://", "ws://") in csp assert api_public_url.replace("http://", "ws://") in csp
elif api_host_name.startswith("https://"): elif api_public_url.startswith("https://"):
assert api_host_name.replace("https://", "wss://") in csp assert api_public_url.replace("https://", "wss://") in csp
else: else:
raise AssertionError( raise AssertionError(
f"Unexpected API_HOST_NAME format: {api_host_name} — must start with 'http://' or 'https://'" f"Unexpected API_PUBLIC_URL format: {api_public_url} — must start with 'http://' or 'https://'"
) )