|
1 | 1 | import { describe, expect, test } from "bun:test"; |
2 | 2 |
|
| 3 | +import { |
| 4 | + PRODUCT_GITHUB_LABEL, |
| 5 | + PRODUCT_GITHUB_URL, |
| 6 | + PRODUCT_SITE_LABEL, |
| 7 | + PRODUCT_SITE_URL, |
| 8 | +} from "../branding.js"; |
3 | 9 | import { callbackPageHtml, humanizeIdentifier } from "./callback-page.js"; |
4 | 10 |
|
5 | 11 | describe("humanizeIdentifier", () => { |
@@ -42,8 +48,49 @@ describe("callbackPageHtml", () => { |
42 | 48 | ); |
43 | 49 | }); |
44 | 50 |
|
45 | | - test("the page reaches for nothing off the machine", () => { |
| 51 | + test("the footer links to the product site and the GitHub org", () => { |
46 | 52 | const html = callbackPageHtml({ subject: "linear" }); |
47 | | - expect(html).not.toMatch(/https?:\/\/(?!www\.w3\.org)/); |
| 53 | + const link = (url: string, label: string) => |
| 54 | + `<a href="${url}" target="_blank" rel="noopener noreferrer">${label}</a>`; |
| 55 | + expect(html).toContain(link(PRODUCT_SITE_URL, PRODUCT_SITE_LABEL)); |
| 56 | + expect(html).toContain(link(PRODUCT_GITHUB_URL, PRODUCT_GITHUB_LABEL)); |
48 | 57 | }); |
| 58 | + |
| 59 | + test("each footer label names the destination its URL actually points at", () => { |
| 60 | + expect(PRODUCT_SITE_URL).toContain(PRODUCT_SITE_LABEL); |
| 61 | + expect(PRODUCT_GITHUB_URL).toContain(PRODUCT_GITHUB_LABEL); |
| 62 | + }); |
| 63 | + |
| 64 | + // An allowlist rather than a shape match: an unexpected origin fails loudly |
| 65 | + // instead of passing because it happened to be wrapped in an anchor tag. |
| 66 | + const allowedOrigins = new Set([ |
| 67 | + PRODUCT_SITE_URL, |
| 68 | + PRODUCT_GITHUB_URL, |
| 69 | + // The SVG namespace the wordmark declares; a URI, never fetched. |
| 70 | + "http://www.w3.org/2000/svg", |
| 71 | + ]); |
| 72 | + |
| 73 | + const offMachineOrigins = (html: string): readonly string[] => { |
| 74 | + // Scheme-qualified and protocol-relative alike, since either would load. |
| 75 | + const found = |
| 76 | + html.match( |
| 77 | + /(?:[a-z][a-z0-9+.-]*:)?\/\/[a-z0-9-]+(?:\.[a-z0-9-]+)+[^"'`)\s<>]*/gi, |
| 78 | + ) ?? []; |
| 79 | + return found.filter( |
| 80 | + (ref) => ![...allowedOrigins].some((origin) => ref.startsWith(origin)), |
| 81 | + ); |
| 82 | + }; |
| 83 | + |
| 84 | + for (const [outcome, page] of [ |
| 85 | + ["success", { subject: "linear" }], |
| 86 | + ["failure", { subject: "linear", error: "access_denied" }], |
| 87 | + ] as const) { |
| 88 | + test(`the ${outcome} page names no off-machine origin beyond the footer links`, () => { |
| 89 | + const html = callbackPageHtml(page); |
| 90 | + expect(offMachineOrigins(html)).toEqual([]); |
| 91 | + expect(html).not.toMatch( |
| 92 | + /\b(?:fetch|XMLHttpRequest|WebSocket|EventSource|sendBeacon|importScripts)\s*\(/, |
| 93 | + ); |
| 94 | + }); |
| 95 | + } |
49 | 96 | }); |
0 commit comments