pr_01m47d24b0e6n91zwymwxg0vpx/apps/web/app/routes/auth-github-callback.tsx

73 lines2,980 bytesCodeBlame

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.

Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1import { Link, data, redirect } from "react-router";
2
3import type { Route } from "./+types/auth-github-callback";
4import { AuthCard } from "../components/auth-card";
5import { ContinueWithGithub } from "../components/github";
6import { PENDING_COOKIE, STATE_COOKIE, cookie, readCookie, stateMatches } from "../lib/github";
7import { githubSignIn } from "../lib/github.server";
8import { page } from "../lib/meta";
9import { safeNext } from "../lib/next";
10import { startSession } from "../lib/session.server";
11
12export function meta(args: Route.MetaArgs) {
13 return page(args, { title: "Signing in with GitHub · g1t" });
14}
15
16/**
17 * Where GitHub returns. The state must match the cookie this browser was
18 * given; identity then redeems it once, exchanges the code with the PKCE
19 * verifier, and says what happens next.
20 */
21export async function loader({ request }: Route.LoaderArgs) {
22 const url = new URL(request.url);
23 const clear = cookie(STATE_COOKIE, "", 0);
24 const failed = (error: string) => data({ error }, { headers: { "set-cookie": clear } });
25 if (url.searchParams.get("error")) {
26 // access_denied: the person cancelled on GitHub.
27 return failed("GitHub sign-in was cancelled.");
28 }
29 const code = url.searchParams.get("code");
30 const state = url.searchParams.get("state");
31 if (!code || !stateMatches(readCookie(request.headers.get("cookie"), STATE_COOKIE), state)) {
32 return failed("This sign-in did not start in this browser, or took too long. Start again.");
33 }
34 const finished = await githubSignIn.finish(state!, code);
35 if (!finished.ok) return failed(finished.error.message);
36 const done = finished.value;
37 const headers = new Headers({ "set-cookie": clear });
38 switch (done.kind) {
39 case "signed_in":
40 headers.append("set-cookie", startSession(done.signedIn.sessionToken));
41 throw redirect(safeNext(done.next), { headers });
42 case "linked":
43 throw redirect(safeNext(done.next === "/" ? "/settings#github" : done.next), { headers });
44 case "needs_link":
45 headers.append("set-cookie", cookie(PENDING_COOKIE, done.pending, 1800));
46 throw redirect(`/login?github=link${done.next === "/" ? "" : `&next=${encodeURIComponent(done.next)}`}`, { headers });
47 case "needs_username":
48 headers.append("set-cookie", cookie(PENDING_COOKIE, done.pending, 1800));
49 throw redirect("/auth/github/username", { headers });
50 }
51}
52
53export default function GithubCallback({ loaderData }: Route.ComponentProps) {
54 const error = loaderData?.error;
55 return (
56 <AuthCard
57 title="Signing in with GitHub"
58 subtitle="That did not work"
59 footer={
60 <Link to="/login" className="text-fg underline underline-offset-4">
61 Sign in another way
62 </Link>
63 }
64 >
65 <p className="text-sm text-danger" role="alert">
66 {error}
67 </p>
68 <div className="mt-6">
69 <ContinueWithGithub href="/auth/github" label="Try again with GitHub" />
70 </div>
71 </AuthCard>
72 );
73}