Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: feedback form blueprint #227

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions src/content/docs/blueprints/feedback-form.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
---
title: "Feedback form protection"
description: "How to protect your feedback form from spam using Arcjet."
---
Feedback forms are often used as tools for gathering valuable insights to improve services, products, or experiences. Spam undermines this integrity and could distract from the original goals of the survey or feedback initiative.

Protecting forms ensures that only genuine, thoughtful feedback is collected and used appropriately. Arcjet can help you protect your feedback forms from spam and invalid contact details.

### Rules

- Shield - Safeguard your forms from SQL injection attacks with automated detection and blocking, ensuring malicious queries never reach your database.
- Bot Detection – Automatically identify and block bot traffic, keeping automated spammers and malicious scripts from submitting your forms.
- Email Validation – Ensure form submissions come from genuine users by validating email addresses, preventing fake or disposable email entries.
- Rate Limiting – Protect your forms from spam and abuse by limiting submissions from a single IP or device, enforcing sensible submission thresholds to stop mass spam attacks.
- Sensitive Data Redaction – Automatically redact sensitive or personally identifiable information (PII) from form submissions, ensuring compliance with data protection laws and reducing the risk of data breaches.


```ts
// ... imports, etc
// See https://docs.arcjet.com/get-started
const aj = arcjet({
key: process.env.ARCJET_KEY!, // Get your site key from https://app.arcjet.com
characteristics: ["ip.src"], // Track requests by IP
rules: [
// Shield protects your app from common attacks e.g. SQL injection:
shield({ mode: "LIVE" }),
// Block all bots. See https://arcjet.com/bot-list
detectBot({
mode: "LIVE",
allow: [],
}),
// Strict email validation to block disposable, invalid, and domains
// with no valid MX records:
validateEmail({
mode: "LIVE",
block: ["DISPOSABLE", "INVALID", "NO_MX_RECORDS"],
}),
// Rate limit forms being submitted from the same IP address:
slidingWindow({
mode: "LIVE",
interval: "10m", // counts requests over a 10-minute sliding window
max: 5, // allows 5 submissions within the window
}),
],
});
```

### Additional checks
You can use the Redact library to ensure no sensitive information is sent to your server by the user:

```ts
import {redact} from "@arcjet/redact";

const text = "please issue a refund to this card right now!!! 5555 5555 5555 4444";

const [redacted] = await redact(text, {
entities: ["credit-card-number"],
});
```

The feedback component is shown below. It will validate the email and ensure no credit card numbers are sent to your server. If the checks succeed, then you can display a success message.

```tsx title="/app/components/Form.tsx"
"use client";

import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Toaster } from "@/components/ui/toaster";
import { useToast } from "@/hooks/use-toast";
import { useRouter } from "next/navigation";
import { useState } from "react";
import {redact} from "@arcjet/redact";

export default function FeedbackForm() {
const [feedback, setFeedback] = useState("");
const [email, setEmail] = useState("[email protected]");
const [isLoading, setIsLoading] = useState(false);
const router = useRouter();
const { toast } = useToast();

async function onSubmit(event: React.FormEvent) {
event.preventDefault();
setIsLoading(true);

const [redacted: redactedFeedback] = await redact(feedback, {
entities: ["credit-card-number"],
});

const response = await fetch("/api/send-feedback", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email, redactedFeedback }),
});

setIsLoading(false);

const responseData = await response.json();

if (response.ok) {
// Show success message
} else {
toast({
title: "Submission Failed",
description:
"There was an error submitting your feedback: " + responseData.message,
variant: "destructive",
});
}
}

return (
<>
<Card className="w-full max-w-md mx-auto">
<CardHeader>
<CardTitle>Feedback Form</CardTitle>
<CardDescription>Enter your details below</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={onSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="amount">How was your experience?</Label>
<Input
id="feedback"
value={feedback}
onChange={(e) => setFeedback(e.target.value)}
required
/>
</div>
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? "Submitting..." : "Submit Feedback"}
</Button>
</form>
</CardContent>
</Card>
<Toaster />
</>
);
}
```
2 changes: 0 additions & 2 deletions src/content/docs/blueprints/sampling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
title: "Sampling traffic"
description: "How to write a sampling function to test your Arcjet security rules on a subset of your traffic."
---

import SelectableContent from "@/components/SelectableContent";
import { Code, TabItem, Tabs } from "@astrojs/starlight/components";
import SamplingMiddlewareJS from "/src/snippets/blueprints/SamplingMiddleware.js?raw";
import SamplingMiddlewareTS from "/src/snippets/blueprints/SamplingMiddleware.ts?raw";
Expand Down
7 changes: 6 additions & 1 deletion src/content/docs/examples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: "Arcjet examples"
description: "Real world examples of how to use Arcjet."
---

import { LinkButton, LinkCard } from "@astrojs/starlight/components";
import { LinkCard } from "@astrojs/starlight/components";

## Apps

Expand Down Expand Up @@ -47,6 +47,11 @@ import { LinkButton, LinkCard } from "@astrojs/starlight/components";
href="/blueprints/payment-form"
description="Protect your payment form from fraudulent credit card transactions."
/>
<LinkCard
title="Feedback form protection"
href="/blueprints/feedback-form"
description="Protect your feedback forms from bots and spam."
/>
<LinkCard
title="Sampling traffic"
href="/blueprints/sampling"
Expand Down
4 changes: 4 additions & 0 deletions src/lib/sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ export const main = [
label: "Payment form protection",
link: "/blueprints/payment-form",
},
{
label: "Feedback form protection",
link: "/blueprints/feedback-form",
},
{
label: "Sampling traffic",
link: "/blueprints/sampling",
Expand Down