Skip to content
89 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)1/**
2 * The confirmation gate: a signed-in person whose account has not confirmed
3 * its email address is sent to /confirm-email from every page, except the
4 * pages that page needs (confirming by link, signing out and in again,
5 * resetting a password) and the public pages about g1t: its policies,
6 * security, support, status and prices. No Workers or React imports, so it
7 * can be tested under Node.
8 */
9
10/** Where an account confirms its address: the code, a new one, a new address. */
11export const CONFIRM_PATH = "/confirm-email";
12
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)13/** Where a person answers the workspace invitations waiting for them. */
14export const INVITATIONS_PATH = "/invitations";
15
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)16/** Pages a pending account can open as they are. */
17const OPEN = new Set([
18 CONFIRM_PATH,
19 // The link in the email, which confirms whoever follows it.
20 "/verify",
21 "/logout",
22 "/login",
23 "/login/two-factor",
24 "/register",
25 "/forgot",
26 "/reset",
27 // Who makes g1t and the promises it keeps.
28 "/policies",
29 "/security",
30 "/support",
31 "/status",
32 "/status.json",
33 "/pricing",
34]);
35
36/** Prefixes of the same: each policy, signing in with GitHub, well-known files. */
37const OPEN_UNDER = ["/policies/", "/auth/github", "/.well-known/"];
38
39type Pending = { kind?: string; verified?: boolean } | null | undefined;
40
41/** The page a data request (`/foo.data`, `/_root.data`) is for. */
42export function pageOf(pathname: string): string {
43 if (!pathname.endsWith(".data")) return pathname;
44 const page = pathname.slice(0, -".data".length);
45 return page === "/_root" || page === "" ? "/" : page;
46}
47
48/** Whether a pending account may open `pathname` as it is. */
49export function openWhilePending(pathname: string): boolean {
50 const page = pageOf(pathname);
51 const path = page.length > 1 ? page.replace(/\/+$/, "") : page;
52 return OPEN.has(path) || OPEN_UNDER.some((prefix) => path.startsWith(prefix));
53}
54
55/**
56 * Where to send `viewer` instead of `pathname` + `search`: the confirmation
57 * page, with where they were going as `next`; null when the page is theirs
58 * to open (confirmed, signed out, an agent or a workspace, or a page in the
59 * list above).
60 */
61export function confirmGate(pathname: string, search: string, viewer: Pending): string | null {
62 if (!viewer || (viewer.kind ?? "user") !== "user" || viewer.verified) return null;
63 if (openWhilePending(pathname)) return null;
64 const page = pageOf(pathname);
65 // A data request's own parameters are not where they were going.
66 const params = new URLSearchParams(search);
67 params.delete("_routes");
68 const query = params.toString();
69 const next = page === "/" && !query ? "" : `?next=${encodeURIComponent(page + (query ? `?${query}` : ""))}`;
70 return `${CONFIRM_PATH}${next}`;
71}
72
73/** What the confirmation page says once a code or link has worked. */
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)74export function confirmedLine(done: { joined?: string | null; invitedTo?: string | null; inviteLapsed?: string | null }): string {
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)75 if (done.inviteLapsed) return done.inviteLapsed;
76 if (done.joined) return `Your email address is confirmed, and you have joined ${done.joined}.`;
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)77 if (done.invitedTo) return `Your email address is confirmed. You are invited to join ${done.invitedTo}: accept or decline the invitation next.`;
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)78 return "Your email address is confirmed.";
79}
80
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)81/**
82 * Where to go once confirmed: the invitation the invite brought, to accept or
83 * decline; else back where they were going, else the workspace joined, else home.
84 */
85export function afterConfirming(next: string, joined?: string | null, invitedTo?: string | null): string {
86 if (invitedTo) return INVITATIONS_PATH;
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)87 if (next && next !== "/") return next;
88 return joined ? `/${joined}` : "/";
89}

This file's history is long; its oldest lines are credited to the oldest commit read.