Public: anyone can read this; the crew can change it.
The /__forge/ door links "Report a problem" on a public app, no account needed #7923
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/7855-the-forge-door-links-report-a-problem-on → main 2 commits fork 2 threads · 2 unresolved
3 files
changed +30
−0
src/apps/door.ts +4
| @@ -27,6 +27,8 @@ export type DoorFacts = { | ||
| 27 | 27 | pulls?: DoorPullLine[] | undefined; |
| 28 | 28 | /** Who of the crew is working here now, as the pill's crew line says it. */ |
| 29 | 29 | crew?: CrewNow | undefined; |
| 30 | /** This host's own report door, only for a public app anyone may report on. */ | |
| 31 | report?: string | undefined; | |
| 30 | 32 | }; |
| 31 | 33 | |
| 32 | 34 | /** How many pulls the door lists; one more read than this means there are others. */ |
| @@ -132,6 +134,8 @@ export function doorPage(facts: DoorFacts, head: boolean): Response { | ||
| 132 | 134 | if (facts.tell !== undefined) links.push(link(facts.tell, "Tell the crew")); |
| 133 | 135 | else if (facts.signin !== undefined) |
| 134 | 136 | links.push(link(facts.signin, "Sign in to tell the crew")); |
| 137 | if (facts.report !== undefined) | |
| 138 | links.push(link(facts.report, "Report a problem, no account needed")); | |
| 135 | 139 | const terminal = |
| 136 | 140 | facts.ssh === undefined |
| 137 | 141 | ? "" |
src/apps/proxy.ts +2
| @@ -921,6 +921,7 @@ export async function proxyApp( | ||
| 921 | 921 | ), |
| 922 | 922 | ); |
| 923 | 923 | const ssh = doorSsh(runtime, target, signed?.who); |
| 924 | const report = reportUrl(runtime, target); | |
| 924 | 925 | const where = timePhase(request, "pull", () => |
| 925 | 926 | pillWhere(runtime, target, signed?.who), |
| 926 | 927 | ); |
| @@ -947,6 +948,7 @@ export async function proxyApp( | ||
| 947 | 948 | ...(serving === undefined ? {} : { serving }), |
| 948 | 949 | forgeUrl: runtime.options.forgeUrl, |
| 949 | 950 | ...(ssh === undefined ? {} : { ssh }), |
| 951 | ...(report === undefined ? {} : { report: "/__forge/report" }), | |
| 950 | 952 | ...(tell === undefined ? { signin: "/__forge/signin" } : { tell }), |
| 951 | 953 | }, |
| 952 | 954 | request.method === "HEAD", |
test/apps-door.test.ts +24
| @@ -480,3 +480,27 @@ test("the door names the crew run working here, linked, to who may see it", asyn | ||
| 480 | 480 | finishRun(app.db, hidden.id, { state: "ok", summary: "d" }); |
| 481 | 481 | expect(await (await door(`notes.${DOMAIN}`, cookie)).text()).not.toContain("is working on this"); |
| 482 | 482 | }); |
| 483 | ||
| 484 | test("a public app's door links the report form on its own host; a private one's does not", async () => { | |
| 485 | const html = await (await door(`notes.${DOMAIN}`)).text(); | |
Also assert the link actually resolves: request /__forge/report on the same public host and expect a 303 to the forge form, so the door never links a dead path. |
||
| 486 | expect(html).toContain('<a href="/__forge/report">Report a problem, no account needed</a>'); | |
| 487 | // The link resolves: the same public host answers it with the forge's form. | |
| 488 | const gone = await fetch(`${servers.url}/__forge/report`, { | |
| 489 | headers: { host: `notes.${DOMAIN}` }, | |
| 490 | redirect: "manual", | |
| 491 | }); | |
| 492 | expect(gone.status).toBe(303); | |
| 493 | const to = new URL(gone.headers.get("location")!); | |
| 494 | expect(`${to.origin}${to.pathname}`).toBe(`${servers.url}/alice/notes/report`); | |
| 495 | const owner = getUserById(app.db, alice)!; | |
| 496 | const made = await newApp(app, owner, { name: "closed", description: "x", visibility: "private" }); | |
| 497 | if (!made.ok) throw new Error(String(made.error)); | |
| 498 | const closed = getAppByRepo(app.db, made.value.repo.id)!; | |
| 499 | const target = { app: closed, env: "production" } as AppTarget; | |
| 500 | const grant = signGrant(dataDir, target, 60_000, Date.now(), { id: alice, name: "alice", role: "admin" }); | |
| 501 | const mine = await ( | |
| 502 | await door(`closed.${DOMAIN}`, `forge_grant_${closed.id.slice(-8)}=${grant}`) | |
| 503 | ).text(); | |
| 504 | expect(mine).toContain("<h1>closed</h1>"); | |
| 505 | expect(mine).not.toContain("/__forge/report"); | |
| 506 | }); |
new above
offline
Call reportUrl once, above, as a const (like ssh and tell), and spread
report: "/__forge/report"from it; wrap this line.