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
11 changes: 9 additions & 2 deletions Documents/OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,11 @@ renaming:
run the local gates.
2. On the server, copy `current` to a new directory next to it with
`cp -a`, which keeps owners, modes and SELinux labels, and replace the
changed parts inside the copy.
changed parts inside the copy. Unpack archives with `--no-same-owner` and
run `chown -R root:root` on the replaced parts: an archive made on a
developer machine carries that machine's user and group IDs, which name
no account on the server, or a different one. Install Nginx and systemd
files with LF line endings.
3. Start the new API on a free loopback port as the service user with
`PORT=<port> dotnet site-api/Progmasoft.Tala.Website.Api.dll`, request
`/health` and `/api/v1/site`, and stop it.
Expand Down Expand Up @@ -237,7 +241,10 @@ boundary is clear.
## Monitoring and privacy

The main language routes answer 200, and `/api/v1/site` returns only the
website's non-sensitive state. Expected forum responses are 200 for the five
website's non-sensitive state. The three sites answer 200 only for files and
for the routes of the application, `/` and `/docs/<page>`; every other path,
such as a scanner's `/wp-login.php`, answers 404, with the application as
the body so that a visitor still reaches the landing page. Expected forum responses are 200 for the five
static files and 404 for other paths. Expected legacy redirect responses are
308. In maintenance mode the main domain answers 503 with `Retry-After`. A 200
from a retired login or registry route is a regression.
Expand Down
6 changes: 3 additions & 3 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions frontend/tests/site-integrity.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,38 @@ test("maintenance mode preserves the legacy redirect without opening the languag
assert.match(live, /location \^~ \/api\/ \{\s*return 404;/);
});

test("only the routes of the application get the app shell; other paths answer 404", () => {
const live = readFileSync(join(websiteRoot, "ops/nginx/tala.conf"), "utf8");
assert.doesNotMatch(live, /try_files \$uri \$uri\/ \/index\.html;/);
assert.equal(live.match(/try_files \$uri \$uri\/ @application;/g)?.length, 3);
assert.equal(live.match(/error_page 404 \/index\.html;/g)?.length, 3);
const patterns = [...live.matchAll(/if \(\$uri !~ "([^"]+)"\)/g)].map((match) => match[1]);
assert.equal(new Set(patterns).size, 1, "the three sites must accept the same routes");
const route = new RegExp(patterns[0]);
const pages = [
"public/sitemap.xml",
"public/hosts/talac/sitemap.xml",
"public/hosts/tazel/sitemap.xml",
]
.flatMap(locations)
.map((url) => new URL(url).pathname);
assert.ok(pages.length > 3);
for (const path of pages) {
assert.match(path, route, `${path} is in a sitemap but would answer 404`);
}
for (const path of [
"/wp-login.php",
"/wp-admin/install.php",
"/docs",
"/docs/",
"/docs/a/b",
"/.env",
"/a",
]) {
assert.doesNotMatch(path, route, `${path} would be served as a page`);
}
});

test("the former domain lands on the same path of tala.run and tala.run has no ViGet host", () => {
const live = readFileSync(join(websiteRoot, "ops/nginx/tala.conf"), "utf8");
const forum = readFileSync(join(websiteRoot, "ops/nginx/forum.conf"), "utf8");
Expand Down
48 changes: 45 additions & 3 deletions ops/nginx/tala.conf
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,23 @@ server {

location / {
# Vue Router uses history URLs; real files still win before the app shell.
try_files $uri $uri/ /index.html;
try_files $uri $uri/ @application;
}

# The application has two kinds of routes: the landing page and
# /docs/<page>. Any other path that is not a file is answered with 404, so
# that unknown addresses, including the probes for /wp-login.php and
# friends, are not served as pages. The body of the 404 is still the
# application, whose router takes a visitor to the landing page.
location @application {
if ($uri !~ "^/(docs/[a-z0-9-]+/?)?$") {
return 404;
}
rewrite ^ /index.html break;
}

error_page 404 /index.html;

location ~ /\. {
deny all;
}
Expand Down Expand Up @@ -141,9 +155,23 @@ server {
}

location / {
try_files $uri $uri/ /index.html;
try_files $uri $uri/ @application;
}

# The application has two kinds of routes: the landing page and
# /docs/<page>. Any other path that is not a file is answered with 404, so
# that unknown addresses, including the probes for /wp-login.php and
# friends, are not served as pages. The body of the 404 is still the
# application, whose router takes a visitor to the landing page.
location @application {
if ($uri !~ "^/(docs/[a-z0-9-]+/?)?$") {
return 404;
}
rewrite ^ /index.html break;
}

error_page 404 /index.html;

location ~ /\. {
deny all;
}
Expand Down Expand Up @@ -187,9 +215,23 @@ server {
}

location / {
try_files $uri $uri/ /index.html;
try_files $uri $uri/ @application;
}

# The application has two kinds of routes: the landing page and
# /docs/<page>. Any other path that is not a file is answered with 404, so
# that unknown addresses, including the probes for /wp-login.php and
# friends, are not served as pages. The body of the 404 is still the
# application, whose router takes a visitor to the landing page.
location @application {
if ($uri !~ "^/(docs/[a-z0-9-]+/?)?$") {
return 404;
}
rewrite ^ /index.html break;
}

error_page 404 /index.html;

location ~ /\. {
deny all;
}
Expand Down
Loading