1/**
2 * The page for a sign-in link that cannot be trusted (unknown client or
3 * unregistered redirect). It is plain server HTML so it never depends on the
4 * web app, and it must not redirect anywhere.
5 */
6const escapeHtml = (value: string) =>
7 value.replace(
8 /[&<>"']/gu,
9 (character) =>
10 ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[character]!,
11 );
12
13const STYLES = `
14 :root { color-scheme: light dark; font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
15 body { margin: 0; min-height: 100vh; display: grid; place-items: center; padding: 32px 16px; background: #f6f7f9; color: #17191f; }
16 main { width: min(100%, 440px); }
17 h1 { margin: 0 0 8px; font-size: 20px; }
18 p { margin: 0 0 12px; line-height: 1.5; color: #4b5060; }
19 @media (prefers-color-scheme: dark) {
20 body { background: #0f1115; color: #e6e8ee; }
21 p { color: #a0a6b4; }
22 }
23`;
24
25const shell = (title: string, body: string) => `<!doctype html>
26<html lang="en">
27 <head>
28 <meta charset="utf-8" />
29 <meta name="viewport" content="width=device-width, initial-scale=1" />
30 <meta name="color-scheme" content="light dark" />
31 <title>${escapeHtml(title)}</title>
32 <style>${STYLES}</style>
33 </head>
34 <body>
35 <main>${body}</main>
36 </body>
37</html>`;
38
39export function renderErrorPage(description: string): string {
40 return shell(
41 "Sign-in failed",
42 `<h1>This sign-in cannot continue</h1>
43 <p>${escapeHtml(description)}</p>
44 <p>Close this page and start the sign-in again from your agent.</p>`,
45 );
46}