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: 3 additions & 2 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import { defineConfig } from "astro/config";
import pagefind from "astro-pagefind";

export default defineConfig({
site: "https://letsauth.eu",
site: "https://www.letsauth.eu",
trailingSlash: "always",
// Bunny Storage serves <folder>/index.html for "/folder/" but has nothing to match "/folder"
// or an extensionless page, so pages are folders and every internal link ends in a slash.
build: { format: "directory" },
// Bundled scripts, styles and fonts go to /assets/ instead of Astro's default /_astro/.
build: { format: "directory", assets: "assets" },
// Old addresses from before the page tree was flattened (docs/architecture.md, "Pages"). Each
// becomes a small page that forwards to the new address; the JSON exports moved too but a
// forwarding page is HTML, so those need the edge rules listed in docs/deploy.md.
Expand Down
7 changes: 4 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,10 @@ scan. [`scanner.md`](scanner.md) has the request list, the summary and the job.
- The scanner image goes to Scaleway's container registry through
`.github/workflows/scanner-deploy.yml`, also only after a green CI run.
- Analytics is Plausible (EU), emitted only when `PUBLIC_PLAUSIBLE_DOMAIN` is set. No cookies
that require consent and no third-party script: `src/lib/analytics.ts` posts to `/r/e.json`
on letsauth.eu, which a Bunny edge rule forwards to plausible.io. A prerendered page counts
only once it is shown. [`deploy.md`](deploy.md) has the proxy setup.
that require consent and no third-party request: the personal Plausible script loads from
`/r/s.js` and posts to `/r/e.json` on letsauth.eu, which Bunny edge rules forward to
plausible.io. A prerendered page counts only once it is shown. [`deploy.md`](deploy.md) has
the proxy setup.

## Workflow

Expand Down
32 changes: 18 additions & 14 deletions docs/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,28 +43,32 @@ call alone. Rotate both from the Bunny dashboard and update the secrets.

## Plausible through letsauth.eu

Pages do not load Plausible's script. `src/lib/analytics.ts`, bundled into each page, posts
events to `/r/e.json` on letsauth.eu, and a pull zone edge rule forwards that path to
Pages load the site's personal Plausible script from `/r/s.js` and it sends events to
`/r/e.json`, both on letsauth.eu. Two pull zone edge rules forward them to plausible.io: the
script to `https://plausible.io/js/<personal script>` (cached for an hour), the events to
`https://plausible.io/api/event` with the visitor's IP in `X-Forwarded-For` (without it,
Plausible sees every visit come from the CDN and its bot filter drops them). The path is neutral
on purpose: blocklists match `/js/script.js` and `/analytics/`, and it has an extension because
Bunny answers an extensionless path with a 301 before an edge rule applies
([Plausible's proxy guide](https://plausible.io/docs/proxy/introduction)).
Plausible sees every visit come from the CDN and its bot filter drops them). The paths are
neutral on purpose: blocklists match `/js/script.js` and `/analytics/`, and they have an
extension because Bunny answers an extensionless path with a 301 before an edge rule applies
([Plausible's proxy guide](https://plausible.io/docs/proxy/introduction)). The personal script
name is `PLAUSIBLE_SCRIPT` in `src/lib/reach.ts` (Plausible, site settings, "Site
installation"); change it there if Plausible issues a new one.

The rules live in `scripts/lib/bunny-edge-rules.ts`. `pnpm edge-rules` prints them;
`pnpm edge-rules --apply` (with `BUNNY_API_KEY` and `BUNNY_PULL_ZONE_ID`) syncs them. The deploy
workflow runs the sync before the upload once `BUNNY_SYNC_EDGE_RULES` is `true`, and fails while
`PLAUSIBLE_DOMAIN` is set without it, because the pages would post to a path nothing forwards.
`PLAUSIBLE_DOMAIN` is set without it, because the pages would load a path nothing forwards.
Only rules whose description starts with `[repo] ` are created, updated or deleted; rules made
by hand in the dashboard, such as the redirects below, are left alone.

The script reports only on the production host, never under automation (Playwright,
Lighthouse), and not in a browser where `localStorage.plausible_ignore` is `"true"`. It sends a
pageview when a page is shown: a page prerendered by speculation rules waits for
`prerenderingchange`, so it counts once if opened and never if not. A page restored from the
back/forward cache counts again, as with Plausible's own script. It also sends
`Outbound Link: Click`, `File Download` (the JSON exports) and `404` with a `path` property;
add those three as custom event goals in the Plausible site settings to see them.
Outbound link clicks and file downloads are the script's own, switched on in the Plausible site
settings; the init snippet adds `json` to the download extensions so the exports count.
Pageviews are sent by `src/lib/reach.ts` (a neutral name, because bundles are named after
their modules and blocklists match "analytics" in a path) (`autoCapturePageviews: false`), only on
letsauth.eu or www.letsauth.eu and never under automation (Playwright, Lighthouse): a page
prerendered by speculation rules waits for `prerenderingchange`, so it counts once if opened and
never if not. A back/forward cache restore and an island's in-place navigation count again. A
404 also sends a `404` event with a `path` property; add it as a custom event goal to see it.

## Manual deploy

Expand Down
2 changes: 1 addition & 1 deletion public/.well-known/security.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ Contact: mailto:letsauth@janbrennenstuhl.eu
Contact: https://github.com/letsauthlab/letsauth/security/advisories/new
Expires: 2027-10-14T00:00:00.000Z
Preferred-Languages: en, de
Canonical: https://letsauth.eu/.well-known/security.txt
Canonical: https://www.letsauth.eu/.well-known/security.txt
Policy: https://github.com/letsauthlab/letsauth/blob/main/SECURITY.md
2 changes: 1 addition & 1 deletion public/robots.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
User-agent: *
Allow: /
Sitemap: https://letsauth.eu/sitemap-index.xml
Sitemap: https://www.letsauth.eu/sitemap-index.xml
23 changes: 22 additions & 1 deletion scripts/lib/bunny-edge-rules.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PLAUSIBLE_EVENT_PATH } from "../../src/lib/analytics";
import { PLAUSIBLE_EVENT_PATH, PLAUSIBLE_SCRIPT, PLAUSIBLE_SCRIPT_PATH } from "../../src/lib/reach";

/**
* The Bunny pull zone edge rules this repository owns. Each carries `MANAGED_PREFIX` in its
Expand Down Expand Up @@ -51,6 +51,27 @@ export function edgeRules(): EdgeRule[] {
// Plausible proxy (https://plausible.io/docs/proxy/introduction). Host is set in case the
// pull zone forwards the visitor's; X-Forwarded-For carries the visitor's IP, without which
// Plausible sees every visit come from the CDN and its bot filter drops them.
{
Description: `${MANAGED_PREFIX}Plausible script`,
Enabled: true,
ActionType: Action.OriginUrl,
ActionParameter1: `https://plausible.io/js/${PLAUSIBLE_SCRIPT}`,
ActionParameter2: null,
ActionParameter3: null,
Triggers: [
{
Type: Trigger.Url,
PatternMatches: [`*${PLAUSIBLE_SCRIPT_PATH}`],
PatternMatchingType: Match.Any,
Parameter1: null,
},
],
TriggerMatchingType: Match.Any,
ExtraActions: [
extra(Action.SetRequestHeader, "Host", "plausible.io"),
extra(Action.OverrideCacheTime, "3600"),
],
},
{
Description: `${MANAGED_PREFIX}Plausible events`,
Enabled: true,
Expand Down
2 changes: 1 addition & 1 deletion scripts/lib/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function brokenLinks(directory: string): string[] {
if (entry.isDirectory()) visit(file);
else if (entry.isFile() && entry.name.endsWith(".html")) {
const route = `/${relative(root, file).split(sep).join("/")}`;
const base = new URL(route, "https://letsauth.eu");
const base = new URL(route, "https://www.letsauth.eu");
// Astro emits quoted hrefs; fragments are not file targets and are intentionally ignored.
for (const match of readFileSync(file, "utf8").matchAll(
/\bhref\s*=\s*["']([^"']*)["']/gi,
Expand Down
26 changes: 0 additions & 26 deletions src/components/Analytics.astro

This file was deleted.

2 changes: 1 addition & 1 deletion src/components/Explorer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
* Durations come from the motion tokens, which are 0ms under reduced motion.
*/
import { onMount } from "svelte";
import { SOFT_NAVIGATION } from "@/lib/analytics";
import { durationToken, reducedMotion } from "@/lib/motion";
import { SOFT_NAVIGATION } from "@/lib/reach";

/** M6: how long the chain must sit in view, untouched, before the scene starts by itself. */
const SETTLE_MS = 1000;
Expand Down
23 changes: 23 additions & 0 deletions src/components/Reach.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
// Plausible through the site's own address; src/lib/reach.ts has the rules.
import { PLAUSIBLE_SCRIPT_PATH, plausibleInit } from "@/lib/reach";

interface Props {
notFound?: boolean;
}

const { notFound = false } = Astro.props;
const host = (Astro.site ?? new URL("https://www.letsauth.eu")).hostname;
---

<meta name="reach" data-host={host} data-not-found={notFound ? "" : undefined} />
<script is:inline set:html={plausibleInit()} />
<script is:inline async src={PLAUSIBLE_SCRIPT_PATH}></script>
<script>
import { readConfig, startAnalytics, type Track } from "@/lib/reach";

const config = readConfig(document.querySelector<HTMLElement>('meta[name="reach"]'));
const track: Track = (name, options) =>
(window as unknown as { plausible: Track }).plausible(name, options);
if (config) startAnalytics(window, config, track);
</script>
6 changes: 3 additions & 3 deletions src/layouts/Base.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import "@/styles/global.css";
// (the fontsource CSS declares them with font-display: swap; Vite gives both the same hashed URL).
import fontBody from "@fontsource-variable/geist/files/geist-latin-wght-normal.woff2?url";
import fontMono from "@fontsource-variable/geist-mono/files/geist-mono-latin-wght-normal.woff2?url";
import Analytics from "@/components/Analytics.astro";
import BrandLogo from "@/components/BrandLogo.astro";
import Reach from "@/components/Reach.astro";
import { LAB, LEGAL_LINKS, OPERATOR, REPO } from "@/lib/site";

interface Props {
Expand All @@ -20,7 +20,7 @@ interface Props {
}

const { title, description, path, noindex = false, image = "/og.png" } = Astro.props;
const site = Astro.site ?? new URL("https://letsauth.eu");
const site = Astro.site ?? new URL("https://www.letsauth.eu");
const canonical = new URL(path ?? Astro.url.pathname, site);
const plausibleDomain = import.meta.env.PUBLIC_PLAUSIBLE_DOMAIN as string | undefined;
const fullTitle =
Expand Down Expand Up @@ -149,7 +149,7 @@ const nav = [
if (t === "light" || t === "dark") document.documentElement.dataset.theme = t;
} catch {}
</script>
{plausibleDomain && <Analytics domain={plausibleDomain} notFound={here === "/404.html"} />}
{plausibleDomain && <Reach notFound={here === "/404.html"} />}
<slot name="head" />
</head>
<body>
Expand Down
128 changes: 0 additions & 128 deletions src/lib/analytics.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/lib/breaches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export function buildBreachesExport(data: Dataset, generatedAt = new Date().toIS
version: BREACHES_EXPORT_VERSION,
generatedAt,
license: licenseLine(),
source: "https://letsauth.eu/breaches/",
source: "https://www.letsauth.eu/breaches/",
vulnerabilities: breachList(data).map(({ vulnerability, replay }) => ({
...vulnerability,
replay: replay ? `/explorer/${replay.scenario.id}/${replay.toggle.id}/` : null,
Expand Down
2 changes: 1 addition & 1 deletion src/lib/explorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export function buildExplorerExport(data: Dataset, generatedAt = new Date().toIS
version: EXPLORER_EXPORT_VERSION,
generatedAt,
license: licenseLine(),
source: "https://letsauth.eu/explorer/",
source: "https://www.letsauth.eu/explorer/",
scenarios: scenarioList(data).map((s) => s.scenario),
};
}
4 changes: 2 additions & 2 deletions src/lib/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function buildExport(data: Dataset, generatedAt = new Date().toISOString(
version: EXPORT_VERSION,
generatedAt,
license: licenseLine(),
source: "https://letsauth.eu/mcp/",
source: "https://www.letsauth.eu/mcp/",
spec: { id: spec.id, name: spec.name, currentRevision: current.id, revisions: spec.revisions },
features: data.features,
servers: matrix.rows,
Expand Down Expand Up @@ -67,7 +67,7 @@ export function buildServerExport(
version: EXPORT_VERSION,
generatedAt,
license: licenseLine(),
source: `https://letsauth.eu/mcp/servers/${server.id}/`,
source: `https://www.letsauth.eu/mcp/servers/${server.id}/`,
spec: {
id: spec.id,
name: spec.name,
Expand Down
Loading
Loading