@ingram-tech/bot-protection 0.1.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/dist/fields.d.ts +11 -2
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +11 -2
- package/dist/fields.js.map +1 -1
- package/dist/honeypot.d.ts +2 -1
- package/dist/honeypot.d.ts.map +1 -1
- package/dist/honeypot.js +5 -1
- package/dist/honeypot.js.map +1 -1
- package/dist/react.d.ts +31 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +51 -0
- package/dist/react.js.map +1 -0
- package/dist/verify.d.ts +5 -0
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +2 -1
- package/dist/verify.js.map +1 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -59,6 +59,61 @@ export async function POST(request: Request) {
|
|
|
59
59
|
`verifyHuman` also accepts a plain object (`{ formData: { ...fields } }`) and a
|
|
60
60
|
`timing` window (`{ minMs, maxMs }`). Pass `botid: false` to skip layer 3.
|
|
61
61
|
|
|
62
|
+
### Client forms (JSON POST)
|
|
63
|
+
|
|
64
|
+
For client components that POST JSON instead of a server-rendered `<form>`, use
|
|
65
|
+
the `/react` hook. It fetches the token from your route's `GET` on mount and
|
|
66
|
+
hands you the fields to merge into the body. The route's `GET` returns the
|
|
67
|
+
token; its `POST` verifies:
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
"use client";
|
|
71
|
+
import { HoneypotInput, useBotProtection } from "@ingram-tech/bot-protection/react";
|
|
72
|
+
|
|
73
|
+
export function ContactForm() {
|
|
74
|
+
const { honeypotRef, botFields } = useBotProtection("/api/contact");
|
|
75
|
+
|
|
76
|
+
async function onSubmit(values: FormValues) {
|
|
77
|
+
await fetch("/api/contact", {
|
|
78
|
+
method: "POST",
|
|
79
|
+
headers: { "content-type": "application/json" },
|
|
80
|
+
body: JSON.stringify({ ...values, ...botFields() }),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return (
|
|
84
|
+
<form onSubmit={/* ... */}>
|
|
85
|
+
{/* ...your real fields... */}
|
|
86
|
+
<HoneypotInput inputRef={honeypotRef} />
|
|
87
|
+
</form>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
// app/api/contact/route.ts
|
|
94
|
+
import { createFormToken, verifyHuman } from "@ingram-tech/bot-protection";
|
|
95
|
+
|
|
96
|
+
export const GET = () => Response.json({ token: createFormToken() });
|
|
97
|
+
|
|
98
|
+
export async function POST(request: Request) {
|
|
99
|
+
const body = await request.json();
|
|
100
|
+
const result = await verifyHuman({ formData: body });
|
|
101
|
+
if (!result.ok) return Response.json({ ok: true }); // silently drop
|
|
102
|
+
// ...send the email / save the lead...
|
|
103
|
+
return Response.json({ ok: true });
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The honeypot field defaults to a name browsers and password managers won't
|
|
108
|
+
autofill (filling it would falsely flag real users). If that default collides
|
|
109
|
+
with a real field in your form, override it on both sides — they must match:
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
<HoneypotField token={token} field="subject_trap" />
|
|
113
|
+
// ...and on the server:
|
|
114
|
+
await verifyHuman({ formData, honeypotField: "subject_trap" });
|
|
115
|
+
```
|
|
116
|
+
|
|
62
117
|
## Vercel BotID wiring (per app, layer 3 only)
|
|
63
118
|
|
|
64
119
|
```ts
|
package/dist/fields.d.ts
CHANGED
|
@@ -2,8 +2,17 @@
|
|
|
2
2
|
* Shared field names used by the honeypot component and the server verifier.
|
|
3
3
|
* Kept in one place so the client and server never drift.
|
|
4
4
|
*/
|
|
5
|
-
/**
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Default hidden field real users never fill; bots auto-complete it.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately *not* a name browsers or password managers autofill — anything
|
|
9
|
+
* containing company / url / email / name / phone / address / organization /
|
|
10
|
+
* website gets filled for real users, which trips the trap and silently drops
|
|
11
|
+
* legitimate submissions. If this default would collide with a real field in
|
|
12
|
+
* your form (e.g. a genuine "subject"), override it: pass the same name as
|
|
13
|
+
* `field` to `<HoneypotField/>` and `honeypotField` to `verifyHuman()`.
|
|
14
|
+
*/
|
|
15
|
+
export declare const HONEYPOT_FIELD = "contact_detail";
|
|
7
16
|
/** Hidden field carrying the signed timing token. */
|
|
8
17
|
export declare const TOKEN_FIELD = "_bp_token";
|
|
9
18
|
//# sourceMappingURL=fields.d.ts.map
|
package/dist/fields.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../src/fields.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH
|
|
1
|
+
{"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../src/fields.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAE/C,qDAAqD;AACrD,eAAO,MAAM,WAAW,cAAc,CAAC"}
|
package/dist/fields.js
CHANGED
|
@@ -2,8 +2,17 @@
|
|
|
2
2
|
* Shared field names used by the honeypot component and the server verifier.
|
|
3
3
|
* Kept in one place so the client and server never drift.
|
|
4
4
|
*/
|
|
5
|
-
/**
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Default hidden field real users never fill; bots auto-complete it.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately *not* a name browsers or password managers autofill — anything
|
|
9
|
+
* containing company / url / email / name / phone / address / organization /
|
|
10
|
+
* website gets filled for real users, which trips the trap and silently drops
|
|
11
|
+
* legitimate submissions. If this default would collide with a real field in
|
|
12
|
+
* your form (e.g. a genuine "subject"), override it: pass the same name as
|
|
13
|
+
* `field` to `<HoneypotField/>` and `honeypotField` to `verifyHuman()`.
|
|
14
|
+
*/
|
|
15
|
+
export const HONEYPOT_FIELD = "contact_detail";
|
|
7
16
|
/** Hidden field carrying the signed timing token. */
|
|
8
17
|
export const TOKEN_FIELD = "_bp_token";
|
|
9
18
|
//# sourceMappingURL=fields.js.map
|
package/dist/fields.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fields.js","sourceRoot":"","sources":["../src/fields.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH
|
|
1
|
+
{"version":3,"file":"fields.js","sourceRoot":"","sources":["../src/fields.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAE/C,qDAAqD;AACrD,MAAM,CAAC,MAAM,WAAW,GAAG,WAAW,CAAC"}
|
package/dist/honeypot.d.ts
CHANGED
package/dist/honeypot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"honeypot.d.ts","sourceRoot":"","sources":["../src/honeypot.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"honeypot.d.ts","sourceRoot":"","sources":["../src/honeypot.tsx"],"names":[],"mappings":"AA2BA,eAAO,MAAM,aAAa,GAAI,mBAG3B;IACF,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CACf,4CAmBA,CAAC"}
|
package/dist/honeypot.js
CHANGED
|
@@ -9,6 +9,10 @@ import { HONEYPOT_FIELD, TOKEN_FIELD } from "./fields";
|
|
|
9
9
|
* Real users never see or fill the honeypot (off-screen, aria-hidden,
|
|
10
10
|
* tabIndex -1, autocomplete off); bots that fill every field give themselves
|
|
11
11
|
* away.
|
|
12
|
+
*
|
|
13
|
+
* Pass `field` to use a custom honeypot name — it must match the
|
|
14
|
+
* `honeypotField` you pass to `verifyHuman()`, or the trap reads the wrong
|
|
15
|
+
* field and fails open.
|
|
12
16
|
*/
|
|
13
17
|
const hidden = {
|
|
14
18
|
position: "absolute",
|
|
@@ -18,5 +22,5 @@ const hidden = {
|
|
|
18
22
|
height: "1px",
|
|
19
23
|
overflow: "hidden",
|
|
20
24
|
};
|
|
21
|
-
export const HoneypotField = ({ token }) => (_jsxs("div", { "aria-hidden": "true", style: hidden, children: [_jsxs("label", { children: ["Leave this field empty", _jsx("input", { type: "text", name:
|
|
25
|
+
export const HoneypotField = ({ token, field = HONEYPOT_FIELD, }) => (_jsxs("div", { "aria-hidden": "true", style: hidden, children: [_jsxs("label", { children: ["Leave this field empty", _jsx("input", { type: "text", name: field, tabIndex: -1, autoComplete: "off", defaultValue: "", "data-1p-ignore": true, "data-lpignore": "true", "data-form-type": "other" })] }), _jsx("input", { type: "hidden", name: TOKEN_FIELD, defaultValue: token })] }));
|
|
22
26
|
//# sourceMappingURL=honeypot.js.map
|
package/dist/honeypot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"honeypot.js","sourceRoot":"","sources":["../src/honeypot.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvD
|
|
1
|
+
{"version":3,"file":"honeypot.js","sourceRoot":"","sources":["../src/honeypot.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvD;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,GAAkB;IAC7B,QAAQ,EAAE,UAAU;IACpB,IAAI,EAAE,SAAS;IACf,GAAG,EAAE,SAAS;IACd,KAAK,EAAE,KAAK;IACZ,MAAM,EAAE,KAAK;IACb,QAAQ,EAAE,QAAQ;CAClB,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAC7B,KAAK,EACL,KAAK,GAAG,cAAc,GAItB,EAAE,EAAE,CAAC,CACL,8BAAiB,MAAM,EAAC,KAAK,EAAE,MAAM,aACpC,sDAEC,gBACC,IAAI,EAAC,MAAM,EACX,IAAI,EAAE,KAAK,EACX,QAAQ,EAAE,CAAC,CAAC,EACZ,YAAY,EAAC,KAAK,EAClB,YAAY,EAAC,EAAE,2CAID,MAAM,oBACL,OAAO,GACrB,IACK,EACR,gBAAO,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,GAAI,IAC1D,CACN,CAAC"}
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type RefObject } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Client-side bot protection for forms that POST JSON to your own route.
|
|
4
|
+
*
|
|
5
|
+
* Fetches a signed timing token from `tokenEndpoint` on mount — the route's GET
|
|
6
|
+
* handler should return `{ token: createFormToken() }` — then exposes a honeypot
|
|
7
|
+
* ref plus a `botFields()` helper returning the fields to spread into the
|
|
8
|
+
* request body. The matching POST handler calls `verifyHuman({ formData: body })`.
|
|
9
|
+
*
|
|
10
|
+
* Pair with {@link HoneypotInput}, which renders the trap and wires the ref.
|
|
11
|
+
*
|
|
12
|
+
* @param tokenEndpoint - GET route that mints the token (e.g. "/api/contact").
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const { honeypotRef, botFields } = useBotProtection("/api/contact");
|
|
16
|
+
* // ...in the form: <HoneypotInput inputRef={honeypotRef} />
|
|
17
|
+
* // ...on submit: body: JSON.stringify({ ...values, ...botFields() })
|
|
18
|
+
*/
|
|
19
|
+
export declare function useBotProtection(tokenEndpoint: string): {
|
|
20
|
+
honeypotRef: RefObject<HTMLInputElement | null>;
|
|
21
|
+
botFields: () => Record<string, string>;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Visually-hidden honeypot input for {@link useBotProtection}. Real users never
|
|
25
|
+
* see or fill it (off-screen, aria-hidden, tabIndex -1, autofill opted out);
|
|
26
|
+
* bots that fill every field give themselves away.
|
|
27
|
+
*/
|
|
28
|
+
export declare function HoneypotInput({ inputRef, }: {
|
|
29
|
+
inputRef: RefObject<HTMLInputElement | null>;
|
|
30
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
31
|
+
//# sourceMappingURL=react.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAA+B,MAAM,OAAO,CAAC;AAGpE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM;;qBAW/B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;EAM5C;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAC7B,QAAQ,GACR,EAAE;IACF,QAAQ,EAAE,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;CAC7C,2CAuBA"}
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { HONEYPOT_FIELD, TOKEN_FIELD } from "./fields";
|
|
5
|
+
/**
|
|
6
|
+
* Client-side bot protection for forms that POST JSON to your own route.
|
|
7
|
+
*
|
|
8
|
+
* Fetches a signed timing token from `tokenEndpoint` on mount — the route's GET
|
|
9
|
+
* handler should return `{ token: createFormToken() }` — then exposes a honeypot
|
|
10
|
+
* ref plus a `botFields()` helper returning the fields to spread into the
|
|
11
|
+
* request body. The matching POST handler calls `verifyHuman({ formData: body })`.
|
|
12
|
+
*
|
|
13
|
+
* Pair with {@link HoneypotInput}, which renders the trap and wires the ref.
|
|
14
|
+
*
|
|
15
|
+
* @param tokenEndpoint - GET route that mints the token (e.g. "/api/contact").
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* const { honeypotRef, botFields } = useBotProtection("/api/contact");
|
|
19
|
+
* // ...in the form: <HoneypotInput inputRef={honeypotRef} />
|
|
20
|
+
* // ...on submit: body: JSON.stringify({ ...values, ...botFields() })
|
|
21
|
+
*/
|
|
22
|
+
export function useBotProtection(tokenEndpoint) {
|
|
23
|
+
const [token, setToken] = useState("");
|
|
24
|
+
const honeypotRef = useRef(null);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
fetch(tokenEndpoint)
|
|
27
|
+
.then((res) => res.json())
|
|
28
|
+
.then((data) => setToken(data.token ?? ""))
|
|
29
|
+
.catch(() => { });
|
|
30
|
+
}, [tokenEndpoint]);
|
|
31
|
+
const botFields = () => ({
|
|
32
|
+
[HONEYPOT_FIELD]: honeypotRef.current?.value ?? "",
|
|
33
|
+
[TOKEN_FIELD]: token,
|
|
34
|
+
});
|
|
35
|
+
return { honeypotRef, botFields };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Visually-hidden honeypot input for {@link useBotProtection}. Real users never
|
|
39
|
+
* see or fill it (off-screen, aria-hidden, tabIndex -1, autofill opted out);
|
|
40
|
+
* bots that fill every field give themselves away.
|
|
41
|
+
*/
|
|
42
|
+
export function HoneypotInput({ inputRef, }) {
|
|
43
|
+
return (_jsx("input", { ref: inputRef, type: "text", name: HONEYPOT_FIELD, tabIndex: -1, autoComplete: "off", "aria-hidden": "true", defaultValue: "", "data-1p-ignore": true, "data-lpignore": "true", "data-form-type": "other", style: {
|
|
44
|
+
position: "absolute",
|
|
45
|
+
left: "-9999px",
|
|
46
|
+
width: "1px",
|
|
47
|
+
height: "1px",
|
|
48
|
+
overflow: "hidden",
|
|
49
|
+
} }));
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAkB,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,gBAAgB,CAAC,aAAqB;IACrD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IACvC,MAAM,WAAW,GAAG,MAAM,CAAmB,IAAI,CAAC,CAAC;IAEnD,SAAS,CAAC,GAAG,EAAE;QACd,KAAK,CAAC,aAAa,CAAC;aAClB,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;aACzB,IAAI,CAAC,CAAC,IAAwB,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;aAC9D,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACnB,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;IAEpB,MAAM,SAAS,GAAG,GAA2B,EAAE,CAAC,CAAC;QAChD,CAAC,cAAc,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE;QAClD,CAAC,WAAW,CAAC,EAAE,KAAK;KACpB,CAAC,CAAC;IAEH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC;AACnC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,EAC7B,QAAQ,GAGR;IACA,OAAO,CACN,gBACC,GAAG,EAAE,QAAQ,EACb,IAAI,EAAC,MAAM,EACX,IAAI,EAAE,cAAc,EACpB,QAAQ,EAAE,CAAC,CAAC,EACZ,YAAY,EAAC,KAAK,iBACN,MAAM,EAClB,YAAY,EAAC,EAAE,2CAGD,MAAM,oBACL,OAAO,EACtB,KAAK,EAAE;YACN,QAAQ,EAAE,UAAU;YACpB,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,KAAK;YACZ,MAAM,EAAE,KAAK;YACb,QAAQ,EAAE,QAAQ;SAClB,GACA,CACF,CAAC;AACH,CAAC"}
|
package/dist/verify.d.ts
CHANGED
|
@@ -11,6 +11,11 @@ export interface VerifyOptions {
|
|
|
11
11
|
timing?: TokenCheck;
|
|
12
12
|
/** Run the Vercel BotID layer (default true). Set false to skip. */
|
|
13
13
|
botid?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Honeypot field name to check. Must match the `field` rendered by
|
|
16
|
+
* `<HoneypotField/>`. Defaults to {@link HONEYPOT_FIELD}.
|
|
17
|
+
*/
|
|
18
|
+
honeypotField?: string;
|
|
14
19
|
}
|
|
15
20
|
/**
|
|
16
21
|
* Run all configured layers in order (cheapest first):
|
package/dist/verify.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,UAAU,EAAmB,MAAM,SAAS,CAAC;AAE3D,MAAM,WAAW,YAAY;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC7B,0DAA0D;IAC1D,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,0CAA0C;IAC1C,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,oEAAoE;IACpE,KAAK,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,UAAU,EAAmB,MAAM,SAAS,CAAC;AAE3D,MAAM,WAAW,YAAY;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC7B,0DAA0D;IAC1D,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,0CAA0C;IAC1C,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,oEAAoE;IACpE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAUD;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,GAAU,SAAS,aAAa,KAAG,OAAO,CAAC,YAAY,CAsB9E,CAAC"}
|
package/dist/verify.js
CHANGED
|
@@ -19,7 +19,8 @@ const getField = (formData, name) => {
|
|
|
19
19
|
* what tripped them.
|
|
20
20
|
*/
|
|
21
21
|
export const verifyHuman = async (options) => {
|
|
22
|
-
|
|
22
|
+
const honeypotField = options.honeypotField ?? HONEYPOT_FIELD;
|
|
23
|
+
if (getField(options.formData, honeypotField).trim() !== "") {
|
|
23
24
|
return { ok: false, reason: "honeypot" };
|
|
24
25
|
}
|
|
25
26
|
// Timing layer only applies when a secret is configured; otherwise it's
|
package/dist/verify.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAmB,eAAe,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAmB,eAAe,EAAE,MAAM,SAAS,CAAC;AAsB3D,MAAM,QAAQ,GAAG,CAAC,QAAmC,EAAE,IAAY,EAAU,EAAE;IAC9E,MAAM,KAAK,GACV,QAAQ,YAAY,QAAQ;QAC3B,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;QACpB,CAAC,CAAE,QAAoC,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAC/C,CAAC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAAE,OAAsB,EAAyB,EAAE;IAClF,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,cAAc,CAAC;IAC9D,IAAI,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC7D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC1C,CAAC;IAED,wEAAwE;IACxE,+EAA+E;IAC/E,IAAI,YAAY,EAAE,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,eAAe,CAC5B,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,WAAW,CAAC,EACvC,OAAO,CAAC,MAAM,CACd,CAAC;QACF,IAAI,CAAC,KAAK,CAAC,EAAE;YAAE,OAAO,KAAK,CAAC;IAC7B,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;QAC7B,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ,EAAE,CAAC;QACnC,IAAI,KAAK;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAClD,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACrB,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ingram-tech/bot-protection",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Invisible, layered bot protection for forms — honeypot + signed timing token + Vercel BotID.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
"types": "./dist/honeypot.d.ts",
|
|
25
25
|
"import": "./dist/honeypot.js"
|
|
26
26
|
},
|
|
27
|
+
"./react": {
|
|
28
|
+
"types": "./dist/react.d.ts",
|
|
29
|
+
"import": "./dist/react.js"
|
|
30
|
+
},
|
|
27
31
|
"./fields": {
|
|
28
32
|
"types": "./dist/fields.d.ts",
|
|
29
33
|
"import": "./dist/fields.js"
|
|
@@ -35,7 +39,7 @@
|
|
|
35
39
|
"test": "vitest run"
|
|
36
40
|
},
|
|
37
41
|
"devDependencies": {
|
|
38
|
-
"@ingram-tech/typescript-config": "
|
|
42
|
+
"@ingram-tech/typescript-config": "workspace:*",
|
|
39
43
|
"@types/node": "^20.0.0",
|
|
40
44
|
"@types/react": "^19.0.0",
|
|
41
45
|
"react": "^19.0.0",
|