Skip to content

g1t/apps/web/app/routes/workspace/security-bypass.tsx

131 lines6,562 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.

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 <>
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-white disabled:opacity-50">
65 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}