Skip to content

g1t/apps/web/app/routes/repo/security-patterns.tsx

84 lines4,243 bytesCodeBlame
1import { ArrowLeft } from "lucide-react";
2import { Link, data, useNavigate } from "react-router";
3
4import type { Route } from "./+types/security-patterns";
5import { page } from "../../lib/meta";
6import { ActivationPrompt, PatternEditor, SectionHeader, patternFields } from "../../components/security-suite";
7import { securitySuite } from "../../lib/services.server";
8import { assertSameOrigin, getViewer, managesSecurity, requireUser, unwrap } from "../../lib/session.server";
9import { refusal, requireInsider } from "../../lib/access.server";
10import { activationPrice } from "../../lib/security-suite.server";
11
12export function meta({ params, ...args }: Route.MetaArgs) {
13 return page(args, { title: `Custom pattern · ${params.owner}/${params.repo} · g1t` });
14}
15
16export async function loader({ params, context, request }: Route.LoaderArgs) {
17 const viewer = getViewer(context) ?? requireUser(context, request);
18 await requireInsider(context, params, "manage_integrations");
19 const repo = { namespace: params.owner, name: params.repo };
20 const id = new URL(request.url).searchParams.get("id");
21 const [list, price] = await Promise.all([securitySuite.patterns(params.owner, repo, viewer), activationPrice(params.owner, viewer)]);
22 const patterns = unwrap(list);
23 const pattern = id ? patterns.patterns.find((found) => found.id === id && found.scope === "repository") : null;
24 if (id && !pattern) throw data("No such pattern.", { status: 404 });
25 return { pattern: pattern ?? null, entitled: patterns.entitled, price, owner: managesSecurity(viewer, params.owner) };
26}
27
28export async function action({ params, context, request }: Route.ActionArgs) {
29 assertSameOrigin(request);
30 const user = requireUser(context, request);
31 const refused = await refusal(context, params, "manage_integrations");
32 if (refused) return { ok: false, error: refused };
33 const repo = { namespace: params.owner, name: params.repo };
34 const form = await request.formData();
35 const intent = String(form.get("intent") ?? "");
36 if (intent === "save_pattern") {
37 const saved = await securitySuite.savePattern(user, params.owner, repo, patternFields(form));
38 return saved.ok ? { ok: true, saved: saved.value } : { ok: false, error: saved.error.message };
39 }
40 if (intent === "dry_run") {
41 const fields = patternFields(form);
42 const ran = await securitySuite.dryRun(user, params.owner, repo, fields);
43 return ran.ok ? { ok: true, dryRun: ran.value } : { ok: false, error: ran.error.message };
44 }
45 if (intent === "delete_pattern") {
46 const deleted = await securitySuite.deletePattern(user, params.owner, repo, String(form.get("id") ?? ""));
47 return deleted.ok ? { ok: true } : { ok: false, error: deleted.error.message };
48 }
49 return { ok: false, error: "Unknown action." };
50}
51
52export default function PatternPage({ loaderData, params }: Route.ComponentProps) {
53 const { pattern, entitled, price, owner } = loaderData;
54 const base = `/${params.owner}/${params.repo}`;
55 const navigate = useNavigate();
56 return (
57 <div className="max-w-3xl space-y-6">
58 <Link to={`${base}/security/secret-scanning#patterns`} className="inline-flex items-center gap-1.5 text-sm text-muted hover:text-fg">
59 <ArrowLeft size={14} /> Secret scanning
60 </Link>
61 <SectionHeader
62 title={pattern ? pattern.name : "New custom pattern"}
63 about="A secret format of this repository's own. Published, push protection refuses pushes that add a match, and the history is scanned again for it."
64 />
65 {!entitled ? (
66 <ActivationPrompt workspace={params.owner} feature="Custom patterns" monthlyCents={price} isOwner={owner} />
67 ) : (
68 <PatternEditor
69 action={`${base}/security/secret-scanning/patterns`}
70 onDone={pattern ? undefined : () => navigate(`${base}/security/secret-scanning#patterns`)}
71 draft={{
72 id: pattern?.id,
73 name: pattern?.name ?? "",
74 pattern: pattern?.pattern ?? "",
75 before: pattern?.before ?? "",
76 after: pattern?.after ?? "",
77 testStrings: (pattern?.testStrings ?? []).join("\n"),
78 published: pattern?.state === "published",
79 }}
80 />
81 )}
82 </div>
83 );
84}