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.

53 linesCodeBlameRaw
1/**
2 * The parts of a workspace's single sign-on that the site itself decides:
3 * the cookies that bind a trip to the provider to this browser, where to
4 * go afterwards, and the one address the Worker answers before React
5 * Router (a SAML response, posted from the provider's site).
6 */
7
8import { safeNext } from "./next.ts";
9
10/** The OAuth state of a trip to a workspace's provider, for ten minutes. */
11export const SSO_STATE_COOKIE = "g1t_sso_state";
12/** Why a SAML sign-in did not work, shown once on the page it lands on. */
13export const SSO_ERROR_COOKIE = "g1t_sso_error";
14
15/** The workspace a `POST /auth/sso/<slug>/saml` is for, or null. */
16export function samlPath(pathname: string): string | null {
17 const match = /^\/auth\/sso\/([a-z0-9](?:[a-z0-9-]{0,38}))\/saml\/?$/i.exec(pathname);
18 return match ? match[1].toLowerCase() : null;
19}
20
21/** Where to go once signed in through a workspace's provider. */
22export function afterSso(finished: { slug: string; next: string }): string {
23 const next = safeNext(finished.next);
24 return next === "/" ? `/${finished.slug}` : next;
25}
26
27/** A `Set-Cookie` value carrying `message`, for a minute: Lax, HttpOnly. */
28export function errorCookie(message: string): string {
29 const value = encodeURIComponent(message.slice(0, 600));
30 return `${SSO_ERROR_COOKIE}=${value}; Path=/auth/sso; HttpOnly; Secure; SameSite=Lax; Max-Age=60`;
31}
32
33/** Clears that cookie. */
34export function clearErrorCookie(): string {
35 return `${SSO_ERROR_COOKIE}=; Path=/auth/sso; HttpOnly; Secure; SameSite=Lax; Max-Age=0`;
36}
37
38/** The message in that cookie, or null. */
39export function readErrorCookie(header: string | null): string | null {
40 for (const part of (header ?? "").split(";")) {
41 const [key, ...rest] = part.trim().split("=");
42 if (key !== SSO_ERROR_COOKIE) continue;
43 try {
44 return decodeURIComponent(rest.join("=")) || null;
45 } catch {
46 return null;
47 }
48 }
49 return null;
50}
51
52/** How each protocol is named on the page. */
53export const PROTOCOL_NAMES = { oidc: "OpenID Connect", oauth2: "OAuth 2.0", saml: "SAML 2.0" } as const;