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
5 changes: 5 additions & 0 deletions docs/engineering/login_organisation_preferences.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,8 @@ DGX-local Mongo and Mac/Atlas are separate authority stores. Configure and
verify each independently; federation does not replicate these authority or
preference records. A stored default is not proof of a successful Google login:
validate normal issuance, resulting tab context and actual effect namespace.

Template-generated static URLs carry a release prefix, inherited by relative
ES-module imports. This prevents a new backend footer/build from accompanying
old cached login-bootstrap JavaScript. A different release gets different asset
URLs; an old release prefix is never served with the new release's bytes.
3 changes: 3 additions & 0 deletions src/backend/server/utils_flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -1544,6 +1544,9 @@ def create_flask_app(

_install_request_timing_middleware(app)
_configure_flask_app_core(app, list_models_func, generate_func)
from .versioned_static import install_versioned_static

install_versioned_static(app, get_runtime_code_version())
from ..security.cloudflare_access import install_cloudflare_access

install_cloudflare_access(app)
Expand Down
22 changes: 22 additions & 0 deletions src/backend/server/versioned_static.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Release-scoped static paths keep relative ES-module graphs coherent."""
from hashlib import sha256

from flask import abort


def install_versioned_static(app, version: str) -> None:
build = sha256(version.encode()).hexdigest()[:20]

@app.url_defaults
def version_static_urls(endpoint, values):
if endpoint == "static" and "filename" in values:
filename = values["filename"]
if not filename.startswith("build/"):
values["filename"] = f"build/{build}/{filename}"

@app.get(f"{app.static_url_path}/build/<build_id>/<path:filename>")
def versioned_static(build_id, filename):
if build_id != build:
# Never return new bytes under an old release's URL.
abort(404)
return app.send_static_file(filename)
34 changes: 34 additions & 0 deletions tests/backend/test_versioned_static.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from pathlib import Path
from urllib.parse import urljoin

from flask import Flask, url_for
from src.backend.server.versioned_static import install_versioned_static


def test_release_paths_include_relative_modules_and_styles(tmp_path):
(tmp_path / 'js').mkdir()
(tmp_path / 'js' / 'main.js').write_text("import './apiService.js';")
(tmp_path / 'js' / 'apiService.js').write_text('export const release = 2;')
(tmp_path / 'styles.css').write_text('body {}')
app = Flask(__name__, static_folder=str(tmp_path), static_url_path='/static')
install_versioned_static(app, 'release-2')
with app.test_request_context():
entry = url_for('static', filename='js/main.js')
css = url_for('static', filename='styles.css')
assert entry.startswith('/static/build/')
client = app.test_client()
assert client.get(entry).data == b"import './apiService.js';"
assert client.get(urljoin(entry, './apiService.js')).data == b'export const release = 2;'
assert client.get(css).status_code == 200
assert client.get('/static/build/old-release/js/main.js').status_code == 404
assert client.get(entry.replace('/js/main.js', '/../../private.env')).status_code == 404


def test_build_change_changes_entry_url_without_rewriting_templates(tmp_path):
urls=[]
for version in ('release-1','release-2'):
app=Flask(__name__,static_folder=str(tmp_path),static_url_path='/static')
install_versioned_static(app,version)
with app.test_request_context():
urls.append(url_for('static',filename='js/main.js'))
assert urls[0] != urls[1]
Loading