Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

72 linesCodeBlameRaw
1import { Link, data, redirect } from "react-router";
2
3import { ssoAddresses } from "@g1t/contracts";
4
5import type { Route } from "./+types/auth-sso";
6import { AuthCard } from "../components/auth-card";
7import { cookie } from "../lib/github";
8import { page } from "../lib/meta";
9import { SSO_STATE_COOKIE } from "../lib/sso";
10import { sso } from "../lib/sso.server";
11import { nextPath, requireUser, sessionTokenOf } from "../lib/session.server";
12
13export function meta({ params, ...args }: Route.MetaArgs) {
14 return page(args, { title: `Single sign-on · ${params.owner} · g1t` });
15}
16
17/**
18 * Sends the signed-in person to a workspace's identity provider. Signed
19 * out, they sign in to g1t first and come back here. The state the
20 * provider will send back is kept in a short-lived cookie (OpenID Connect
21 * and OAuth 2.0); a SAML request's id binds its answer instead.
22 */
23export async function loader({ request, params, context }: Route.LoaderArgs) {
24 const user = requireUser(context, request);
25 const slug = params.owner.toLowerCase();
26 const token = sessionTokenOf(request);
27 if (!token) {
28 return data({ slug, error: "Single sign-on is for people signed in on this site, not for access tokens." }, { status: 403 });
29 }
30 const summary = await sso.summary(slug);
31 if (!summary.enabled) return data({ slug, error: `${slug} has not set up single sign-on.` }, { status: 404 });
32 const addresses = ssoAddresses(new URL(request.url).origin, slug);
33 const started = await sso.start({
34 user,
35 session_token: token,
36 slug,
37 redirect_uri: summary.protocol === "saml" ? addresses.acsUrl : addresses.callbackUrl,
38 entity_id: addresses.entityId,
39 next: nextPath(request),
40 });
41 if (!started.ok) return { slug, error: started.error.message };
42 const headers = started.value.state ? { "set-cookie": cookie(SSO_STATE_COOKIE, started.value.state, 600) } : undefined;
43 throw redirect(started.value.url, { headers });
44}
45
46export default function SsoStart({ loaderData }: Route.ComponentProps) {
47 return <SsoProblem slug={loaderData.slug} error={loaderData.error} />;
48}
49
50/** What a single sign-on that did not work says, with the ways on. */
51export function SsoProblem({ slug, error }: { slug: string; error: string }) {
52 return (
53 <AuthCard
54 title="Single sign-on"
55 subtitle={`Signing in through ${slug}'s identity provider`}
56 footer={
57 <Link to="/" className="text-fg underline underline-offset-4">
58 Back to g1t
59 </Link>
60 }
61 >
62 <p className="text-sm text-danger" role="alert">
63 {error}
64 </p>
65 <p className="mt-6 text-sm">
66 <Link to={`/auth/sso/${slug}`} className="font-medium text-accent underline underline-offset-4">
67 Try again
68 </Link>
69 </p>
70 </AuthCard>
71 );
72}