Skip to content
131 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.

Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1import { Link, data, useFetcher, useSearchParams } from "react-router";
2
3import type { BypassRequest } from "@g1t/contracts";
4
5import type { Route } from "./+types/security-bypass";
6import { page } from "../../lib/meta";
7import { CARD, FilterSelect } from "../../components/security-suite";
8import { WorkspaceSecurityHeading, WorkspaceSecurityTabs } from "../../components/workspace-security-tabs";
9import { TimeAgo } from "../../components/ui";
10import { Badge } from "../../components/ui/badge";
11import { securitySuite } from "../../lib/services.server";
12import { assertSameOrigin, getViewer, requireUser, roleIn, unwrap } from "../../lib/session.server";
13
14export function meta({ params, ...args }: Route.MetaArgs) {
15 return page(args, { title: `Bypass requests · ${params.owner} · g1t` });
16}
17
18const STATES = new Set(["pending", "approved", "denied", "cancelled"]);
19
20export async function loader({ params, context, request }: Route.LoaderArgs) {
21 const viewer = getViewer(context);
22 if (!roleIn(viewer, params.owner)) throw data(null, { status: 404 });
23 const state = new URL(request.url).searchParams.get("state") ?? "pending";
24 const [list, pending] = await Promise.all([
25 securitySuite.bypassRequests(params.owner, viewer, STATES.has(state) ? state : null),
26 securitySuite.bypassRequests(params.owner, viewer, "pending"),
27 ]);
28 return { requests: unwrap(list), pending: pending.ok ? pending.value.length : 0, me: viewer?.username ?? "" };
29}
30
31export async function action({ params, context, request }: Route.ActionArgs) {
32 assertSameOrigin(request);
33 const user = requireUser(context, request);
34 const form = await request.formData();
35 const decision = String(form.get("decision") ?? "");
36 if (decision !== "approve" && decision !== "deny" && decision !== "cancel") return { ok: false, error: "Choose approve or deny." };
37 const done = await securitySuite.reviewBypass(user, params.owner, String(form.get("id") ?? ""), decision, String(form.get("comment") ?? "").trim().slice(0, 500));
38 return done.ok ? { ok: true } : { ok: false, error: done.error.message };
39}
40
41const STATE_TONE = { pending: "warn", approved: "accent", denied: "danger", cancelled: "neutral" } as const;
42
43function Review({ request, mine }: { request: BypassRequest; mine: boolean }) {
44 const fetcher = useFetcher<{ ok: boolean; error?: string }>();
45 const busy = fetcher.state !== "idle";
46 return (
47 <fetcher.Form method="post" className="mt-3 flex flex-col gap-2 sm:flex-row sm:items-center">
48 <input type="hidden" name="id" value={request.id} />
49 {!mine && (
50 <input
51 name="comment"
52 maxLength={500}
53 placeholder="Comment (optional)"
54 className="h-8 min-w-0 grow rounded-md border border-line bg-bg px-2.5 text-[0.8125rem] outline-none hover:border-line-strong focus:border-accent-dim"
55 />
56 )}
57 <div className="flex gap-2">
58 {mine ? (
59 <button type="submit" name="decision" value="cancel" disabled={busy} className="rounded-md border border-line px-2.5 py-1 text-xs font-medium text-muted hover:text-fg disabled:opacity-50">
60 Cancel request
61 </button>
62 ) : (
63 <>
You choose how g1t looks: Auto follows your system, Light and Dark stay put, set from the account menu, the phone's You and help sheet, Settings → Account or the command palette; the page is drawn in your choice from the first byte, every colour has a light value that reads at 4.5:1 or better, code and diffs are highlighted for both, diagrams and the document editor follow along, the product tour and the g1t mark's avatar stay dark, and Dark is what you see until you choose; the workspaces guide says how.64 <button type="submit" name="decision" value="approve" disabled={busy} className="rounded-md bg-fg px-3 py-1 text-xs font-medium text-bg hover:bg-fg-hover disabled:opacity-50">
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar65 Approve
66 </button>
67 <button type="submit" name="decision" value="deny" disabled={busy} className="rounded-md border border-danger/40 px-3 py-1 text-xs font-medium text-danger hover:bg-danger/10 disabled:opacity-50">
68 Deny
69 </button>
70 </>
71 )}
72 </div>
73 {fetcher.data?.error && <span className="text-xs text-danger">{fetcher.data.error}</span>}
74 </fetcher.Form>
75 );
76}
77
78export default function BypassRequests({ loaderData, params }: Route.ComponentProps) {
79 const { requests, pending, me } = loaderData;
80 const [search, setSearch] = useSearchParams();
81 const state = search.get("state") ?? "pending";
82 return (
83 <div>
84 <WorkspaceSecurityHeading
85 title="Bypass requests"
86 about="With delegated bypass on, people who push a blocked secret ask to push it anyway, and the workspace's owners and the repository's admins decide. Each decision is recorded on the alert and in the audit log."
87 />
88 <WorkspaceSecurityTabs owner={params.owner} pending={pending} />
89 <div className="mb-4">
90 <FilterSelect
91 label="State"
92 value={STATES.has(state) ? state : "all"}
93 options={[["pending", "Pending"], ["approved", "Approved"], ["denied", "Denied"], ["cancelled", "Cancelled"], ["all", "All"]]}
94 onChange={(value) => setSearch(value === "pending" ? {} : { state: value }, { replace: true })}
95 />
96 </div>
97 {requests.length === 0 ? (
98 <p className="rounded-xl border border-dashed border-line px-4 py-6 text-sm text-muted">
99 No {state === "all" ? "" : `${state} `}requests. Delegated bypass is turned on in this workspace's Security settings.
100 </p>
101 ) : (
102 <ul className="space-y-3">
103 {requests.map((request) => (
104 <li key={request.id} className={`${CARD} p-4`}>
105 <div className="flex flex-wrap items-center gap-2">
106 <Link to={`/${request.workspace}/${request.repo}/security/secret-scanning/${request.secretId}`} className="text-sm font-medium first-letter:uppercase hover:underline">
107 {request.label}
108 </Link>
109 <Badge tone={STATE_TONE[request.state]}>{request.state}</Badge>
110 <span className="font-mono text-xs text-faint">
111 {request.repo} · {request.path}:{request.line}
112 </span>
113 </div>
114 <p className="mt-1.5 text-sm text-muted">
115 {request.requester} asked <TimeAgo at={request.createdAt} />: {request.reason.replaceAll("_", " ")}
116 {request.comment && <> · “{request.comment}”</>}
117 </p>
118 {request.reviewer && (
119 <p className="mt-1 text-xs text-faint">
120 {request.state} by {request.reviewer}
121 {request.reviewComment && <>: “{request.reviewComment}”</>}
122 </p>
123 )}
124 {request.state === "pending" && <Review request={request} mine={request.requester.toLowerCase() === me.toLowerCase()} />}
125 </li>
126 ))}
127 </ul>
128 )}
129 </div>
130 );
131}