@sekyuriti/attest 0.1.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 ADDED
@@ -0,0 +1,154 @@
1
+ # @sekyuriti/attest
2
+
3
+ API protection for Next.js applications. Verify that requests come from real browsers, not bots or scripts.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @sekyuriti/attest
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ### 1. Add the script to your frontend
14
+
15
+ ```html
16
+ <script src="https://sekyuriti.build/api/v2/attest/script/YOUR_PROJECT_ID" defer></script>
17
+ ```
18
+
19
+ Or in Next.js:
20
+
21
+ ```tsx
22
+ // app/layout.tsx
23
+ export default function RootLayout({ children }) {
24
+ return (
25
+ <html>
26
+ <head>
27
+ <script
28
+ src={`https://sekyuriti.build/api/v2/attest/script/${process.env.NEXT_PUBLIC_ATTEST_PROJECT_ID}`}
29
+ defer
30
+ />
31
+ </head>
32
+ <body>{children}</body>
33
+ </html>
34
+ );
35
+ }
36
+ ```
37
+
38
+ ### 2. Protect your API routes
39
+
40
+ **Option A: Middleware (recommended)**
41
+
42
+ Protects all `/api/*` routes automatically:
43
+
44
+ ```ts
45
+ // middleware.ts
46
+ import { createAttestMiddleware } from "@sekyuriti/attest/middleware";
47
+
48
+ export const middleware = createAttestMiddleware({
49
+ projectId: process.env.ATTEST_PROJECT_ID!,
50
+ apiKey: process.env.ATTEST_API_KEY!,
51
+ });
52
+
53
+ export const config = {
54
+ matcher: "/api/:path*",
55
+ };
56
+ ```
57
+
58
+ **Option B: Per-route verification**
59
+
60
+ ```ts
61
+ // app/api/protected/route.ts
62
+ import { verifyAttest } from "@sekyuriti/attest";
63
+
64
+ export async function POST(request: Request) {
65
+ const result = await verifyAttest(request, {
66
+ projectId: process.env.ATTEST_PROJECT_ID!,
67
+ apiKey: process.env.ATTEST_API_KEY!,
68
+ });
69
+
70
+ if (!result.attested) {
71
+ return Response.json({ error: "Not attested" }, { status: 403 });
72
+ }
73
+
74
+ // Handle request...
75
+ }
76
+ ```
77
+
78
+ ## Environment Variables
79
+
80
+ ```env
81
+ ATTEST_PROJECT_ID=ATST_xxxxxxxxxxxx
82
+ ATTEST_API_KEY=sk_xxxxxxxxxxxx
83
+ NEXT_PUBLIC_ATTEST_PROJECT_ID=ATST_xxxxxxxxxxxx
84
+ ```
85
+
86
+ ## API Reference
87
+
88
+ ### `verifyAttest(request, config)`
89
+
90
+ Verify a single request.
91
+
92
+ ```ts
93
+ const result = await verifyAttest(request, {
94
+ projectId: "ATST_xxx",
95
+ apiKey: "sk_xxx",
96
+ });
97
+
98
+ // result.attested: boolean
99
+ // result.fingerprint: string (if attested)
100
+ // result.reason: string (if not attested)
101
+ ```
102
+
103
+ ### `createAttestMiddleware(config)`
104
+
105
+ Create middleware for automatic verification.
106
+
107
+ ```ts
108
+ const middleware = createAttestMiddleware({
109
+ projectId: "ATST_xxx",
110
+ apiKey: "sk_xxx",
111
+
112
+ // Optional settings
113
+ protectedRoutes: ["/api/*"], // Routes to protect
114
+ excludeRoutes: ["/api/health"], // Routes to skip
115
+ allowUnauthenticated: false, // Allow requests without headers
116
+
117
+ // Custom handlers
118
+ onBlocked: (req, result) => Response.json({ error: result.reason }, { status: 403 }),
119
+ onAllowed: (req, result) => console.log("Verified:", result.fingerprint),
120
+ });
121
+ ```
122
+
123
+ ### `createAttestVerifier(config)`
124
+
125
+ Create a reusable verifier function.
126
+
127
+ ```ts
128
+ const verify = createAttestVerifier({
129
+ projectId: process.env.ATTEST_PROJECT_ID!,
130
+ apiKey: process.env.ATTEST_API_KEY!,
131
+ });
132
+
133
+ // Use in multiple routes
134
+ const result = await verify(request);
135
+ ```
136
+
137
+ ## How It Works
138
+
139
+ 1. **Frontend script** automatically signs all `fetch()` and `XMLHttpRequest` calls
140
+ 2. **Signatures** are added as headers: `X-Attest-Timestamp`, `X-Attest-Signature`, `X-Attest-Fingerprint`
141
+ 3. **Backend verification** validates signatures with SEKYURITI's API
142
+ 4. **Bots and scripts** can't generate valid signatures without running in a real browser
143
+
144
+ ## Protection Features
145
+
146
+ - DevTools detection
147
+ - Bot/headless browser detection
148
+ - Request signing with HMAC-SHA256
149
+ - Browser fingerprinting
150
+ - Timestamp validation
151
+
152
+ ## License
153
+
154
+ MIT
@@ -0,0 +1,92 @@
1
+ /**
2
+ * @sekyuriti/attest
3
+ *
4
+ * API protection for Next.js applications.
5
+ * Verify that requests come from real browsers, not bots or scripts.
6
+ */
7
+ interface AttestConfig {
8
+ /** Your ATTEST project ID (starts with ATST_) */
9
+ projectId: string;
10
+ /** Your ATTEST API key (keep this secret, server-side only) */
11
+ apiKey: string;
12
+ /** Custom verify URL (optional, for self-hosted) */
13
+ verifyUrl?: string;
14
+ }
15
+ interface AttestResult {
16
+ /** Whether the request passed verification */
17
+ attested: boolean;
18
+ /** Browser fingerprint (if attested) */
19
+ fingerprint?: string;
20
+ /** Verification timestamp */
21
+ timestamp?: number;
22
+ /** Reason for failure (if not attested) */
23
+ reason?: string;
24
+ /** Warning message (e.g., approaching rate limit) */
25
+ warning?: string;
26
+ /** Current usage stats */
27
+ usage?: {
28
+ used: number;
29
+ limit: number;
30
+ percent: number;
31
+ };
32
+ }
33
+ interface AttestHeaders {
34
+ timestamp: string | null;
35
+ signature: string | null;
36
+ fingerprint: string | null;
37
+ project: string | null;
38
+ }
39
+ /**
40
+ * Extract ATTEST headers from a request
41
+ */
42
+ declare function getAttestHeaders(request: Request): AttestHeaders;
43
+ /**
44
+ * Check if a request has ATTEST headers
45
+ */
46
+ declare function hasAttestHeaders(request: Request): boolean;
47
+ /**
48
+ * Verify a request with ATTEST
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * import { verifyAttest } from "@sekyuriti/attest";
53
+ *
54
+ * export async function POST(request: Request) {
55
+ * const result = await verifyAttest(request, {
56
+ * projectId: process.env.ATTEST_PROJECT_ID!,
57
+ * apiKey: process.env.ATTEST_API_KEY!,
58
+ * });
59
+ *
60
+ * if (!result.attested) {
61
+ * return Response.json({ error: "Not attested" }, { status: 403 });
62
+ * }
63
+ *
64
+ * // ... handle request
65
+ * }
66
+ * ```
67
+ */
68
+ declare function verifyAttest(request: Request, config: AttestConfig): Promise<AttestResult>;
69
+ /**
70
+ * Create a configured verifier function
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * import { createAttestVerifier } from "@sekyuriti/attest";
75
+ *
76
+ * const verify = createAttestVerifier({
77
+ * projectId: process.env.ATTEST_PROJECT_ID!,
78
+ * apiKey: process.env.ATTEST_API_KEY!,
79
+ * });
80
+ *
81
+ * export async function POST(request: Request) {
82
+ * const result = await verify(request);
83
+ * if (!result.attested) {
84
+ * return Response.json({ error: "Not attested" }, { status: 403 });
85
+ * }
86
+ * // ...
87
+ * }
88
+ * ```
89
+ */
90
+ declare function createAttestVerifier(config: AttestConfig): (request: Request) => Promise<AttestResult>;
91
+
92
+ export { type AttestConfig, type AttestHeaders, type AttestResult, createAttestVerifier, getAttestHeaders, hasAttestHeaders, verifyAttest };
@@ -0,0 +1,92 @@
1
+ /**
2
+ * @sekyuriti/attest
3
+ *
4
+ * API protection for Next.js applications.
5
+ * Verify that requests come from real browsers, not bots or scripts.
6
+ */
7
+ interface AttestConfig {
8
+ /** Your ATTEST project ID (starts with ATST_) */
9
+ projectId: string;
10
+ /** Your ATTEST API key (keep this secret, server-side only) */
11
+ apiKey: string;
12
+ /** Custom verify URL (optional, for self-hosted) */
13
+ verifyUrl?: string;
14
+ }
15
+ interface AttestResult {
16
+ /** Whether the request passed verification */
17
+ attested: boolean;
18
+ /** Browser fingerprint (if attested) */
19
+ fingerprint?: string;
20
+ /** Verification timestamp */
21
+ timestamp?: number;
22
+ /** Reason for failure (if not attested) */
23
+ reason?: string;
24
+ /** Warning message (e.g., approaching rate limit) */
25
+ warning?: string;
26
+ /** Current usage stats */
27
+ usage?: {
28
+ used: number;
29
+ limit: number;
30
+ percent: number;
31
+ };
32
+ }
33
+ interface AttestHeaders {
34
+ timestamp: string | null;
35
+ signature: string | null;
36
+ fingerprint: string | null;
37
+ project: string | null;
38
+ }
39
+ /**
40
+ * Extract ATTEST headers from a request
41
+ */
42
+ declare function getAttestHeaders(request: Request): AttestHeaders;
43
+ /**
44
+ * Check if a request has ATTEST headers
45
+ */
46
+ declare function hasAttestHeaders(request: Request): boolean;
47
+ /**
48
+ * Verify a request with ATTEST
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * import { verifyAttest } from "@sekyuriti/attest";
53
+ *
54
+ * export async function POST(request: Request) {
55
+ * const result = await verifyAttest(request, {
56
+ * projectId: process.env.ATTEST_PROJECT_ID!,
57
+ * apiKey: process.env.ATTEST_API_KEY!,
58
+ * });
59
+ *
60
+ * if (!result.attested) {
61
+ * return Response.json({ error: "Not attested" }, { status: 403 });
62
+ * }
63
+ *
64
+ * // ... handle request
65
+ * }
66
+ * ```
67
+ */
68
+ declare function verifyAttest(request: Request, config: AttestConfig): Promise<AttestResult>;
69
+ /**
70
+ * Create a configured verifier function
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * import { createAttestVerifier } from "@sekyuriti/attest";
75
+ *
76
+ * const verify = createAttestVerifier({
77
+ * projectId: process.env.ATTEST_PROJECT_ID!,
78
+ * apiKey: process.env.ATTEST_API_KEY!,
79
+ * });
80
+ *
81
+ * export async function POST(request: Request) {
82
+ * const result = await verify(request);
83
+ * if (!result.attested) {
84
+ * return Response.json({ error: "Not attested" }, { status: 403 });
85
+ * }
86
+ * // ...
87
+ * }
88
+ * ```
89
+ */
90
+ declare function createAttestVerifier(config: AttestConfig): (request: Request) => Promise<AttestResult>;
91
+
92
+ export { type AttestConfig, type AttestHeaders, type AttestResult, createAttestVerifier, getAttestHeaders, hasAttestHeaders, verifyAttest };
package/dist/index.js ADDED
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ createAttestVerifier: () => createAttestVerifier,
24
+ getAttestHeaders: () => getAttestHeaders,
25
+ hasAttestHeaders: () => hasAttestHeaders,
26
+ verifyAttest: () => verifyAttest
27
+ });
28
+ module.exports = __toCommonJS(src_exports);
29
+ var ATTEST_VERIFY_URL = "https://sekyuriti.build/api/v2/attest/verify";
30
+ function getAttestHeaders(request) {
31
+ return {
32
+ timestamp: request.headers.get("x-attest-timestamp"),
33
+ signature: request.headers.get("x-attest-signature"),
34
+ fingerprint: request.headers.get("x-attest-fingerprint"),
35
+ project: request.headers.get("x-attest-project")
36
+ };
37
+ }
38
+ function hasAttestHeaders(request) {
39
+ const headers = getAttestHeaders(request);
40
+ return !!(headers.timestamp && headers.signature && headers.fingerprint);
41
+ }
42
+ async function verifyAttest(request, config) {
43
+ const headers = getAttestHeaders(request);
44
+ if (!headers.timestamp || !headers.signature || !headers.fingerprint) {
45
+ return {
46
+ attested: false,
47
+ reason: "Missing ATTEST headers"
48
+ };
49
+ }
50
+ try {
51
+ const response = await fetch(config.verifyUrl || ATTEST_VERIFY_URL, {
52
+ method: "POST",
53
+ headers: {
54
+ "Content-Type": "application/json"
55
+ },
56
+ body: JSON.stringify({
57
+ project_id: config.projectId,
58
+ api_key: config.apiKey,
59
+ timestamp: parseInt(headers.timestamp, 10),
60
+ signature: headers.signature,
61
+ fingerprint: headers.fingerprint
62
+ })
63
+ });
64
+ if (!response.ok) {
65
+ return {
66
+ attested: false,
67
+ reason: `Verification service error: ${response.status}`
68
+ };
69
+ }
70
+ return await response.json();
71
+ } catch (error) {
72
+ console.error("[@sekyuriti/attest] Verification failed:", error);
73
+ return {
74
+ attested: true,
75
+ reason: "Verification service unavailable"
76
+ };
77
+ }
78
+ }
79
+ function createAttestVerifier(config) {
80
+ return (request) => verifyAttest(request, config);
81
+ }
82
+ // Annotate the CommonJS export names for ESM import in node:
83
+ 0 && (module.exports = {
84
+ createAttestVerifier,
85
+ getAttestHeaders,
86
+ hasAttestHeaders,
87
+ verifyAttest
88
+ });
89
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @sekyuriti/attest\n *\n * API protection for Next.js applications.\n * Verify that requests come from real browsers, not bots or scripts.\n */\n\nconst ATTEST_VERIFY_URL = \"https://sekyuriti.build/api/v2/attest/verify\";\n\nexport interface AttestConfig {\n /** Your ATTEST project ID (starts with ATST_) */\n projectId: string;\n /** Your ATTEST API key (keep this secret, server-side only) */\n apiKey: string;\n /** Custom verify URL (optional, for self-hosted) */\n verifyUrl?: string;\n}\n\nexport interface AttestResult {\n /** Whether the request passed verification */\n attested: boolean;\n /** Browser fingerprint (if attested) */\n fingerprint?: string;\n /** Verification timestamp */\n timestamp?: number;\n /** Reason for failure (if not attested) */\n reason?: string;\n /** Warning message (e.g., approaching rate limit) */\n warning?: string;\n /** Current usage stats */\n usage?: {\n used: number;\n limit: number;\n percent: number;\n };\n}\n\nexport interface AttestHeaders {\n timestamp: string | null;\n signature: string | null;\n fingerprint: string | null;\n project: string | null;\n}\n\n/**\n * Extract ATTEST headers from a request\n */\nexport function getAttestHeaders(request: Request): AttestHeaders {\n return {\n timestamp: request.headers.get(\"x-attest-timestamp\"),\n signature: request.headers.get(\"x-attest-signature\"),\n fingerprint: request.headers.get(\"x-attest-fingerprint\"),\n project: request.headers.get(\"x-attest-project\"),\n };\n}\n\n/**\n * Check if a request has ATTEST headers\n */\nexport function hasAttestHeaders(request: Request): boolean {\n const headers = getAttestHeaders(request);\n return !!(headers.timestamp && headers.signature && headers.fingerprint);\n}\n\n/**\n * Verify a request with ATTEST\n *\n * @example\n * ```ts\n * import { verifyAttest } from \"@sekyuriti/attest\";\n *\n * export async function POST(request: Request) {\n * const result = await verifyAttest(request, {\n * projectId: process.env.ATTEST_PROJECT_ID!,\n * apiKey: process.env.ATTEST_API_KEY!,\n * });\n *\n * if (!result.attested) {\n * return Response.json({ error: \"Not attested\" }, { status: 403 });\n * }\n *\n * // ... handle request\n * }\n * ```\n */\nexport async function verifyAttest(\n request: Request,\n config: AttestConfig\n): Promise<AttestResult> {\n const headers = getAttestHeaders(request);\n\n // If no ATTEST headers, request is not attested\n if (!headers.timestamp || !headers.signature || !headers.fingerprint) {\n return {\n attested: false,\n reason: \"Missing ATTEST headers\",\n };\n }\n\n try {\n const response = await fetch(config.verifyUrl || ATTEST_VERIFY_URL, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n project_id: config.projectId,\n api_key: config.apiKey,\n timestamp: parseInt(headers.timestamp, 10),\n signature: headers.signature,\n fingerprint: headers.fingerprint,\n }),\n });\n\n if (!response.ok) {\n return {\n attested: false,\n reason: `Verification service error: ${response.status}`,\n };\n }\n\n return (await response.json()) as AttestResult;\n } catch (error) {\n // Fail open - don't break the app if ATTEST service is down\n console.error(\"[@sekyuriti/attest] Verification failed:\", error);\n return {\n attested: true,\n reason: \"Verification service unavailable\",\n };\n }\n}\n\n/**\n * Create a configured verifier function\n *\n * @example\n * ```ts\n * import { createAttestVerifier } from \"@sekyuriti/attest\";\n *\n * const verify = createAttestVerifier({\n * projectId: process.env.ATTEST_PROJECT_ID!,\n * apiKey: process.env.ATTEST_API_KEY!,\n * });\n *\n * export async function POST(request: Request) {\n * const result = await verify(request);\n * if (!result.attested) {\n * return Response.json({ error: \"Not attested\" }, { status: 403 });\n * }\n * // ...\n * }\n * ```\n */\nexport function createAttestVerifier(config: AttestConfig) {\n return (request: Request) => verifyAttest(request, config);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,IAAM,oBAAoB;AAwCnB,SAAS,iBAAiB,SAAiC;AAChE,SAAO;AAAA,IACL,WAAW,QAAQ,QAAQ,IAAI,oBAAoB;AAAA,IACnD,WAAW,QAAQ,QAAQ,IAAI,oBAAoB;AAAA,IACnD,aAAa,QAAQ,QAAQ,IAAI,sBAAsB;AAAA,IACvD,SAAS,QAAQ,QAAQ,IAAI,kBAAkB;AAAA,EACjD;AACF;AAKO,SAAS,iBAAiB,SAA2B;AAC1D,QAAM,UAAU,iBAAiB,OAAO;AACxC,SAAO,CAAC,EAAE,QAAQ,aAAa,QAAQ,aAAa,QAAQ;AAC9D;AAuBA,eAAsB,aACpB,SACA,QACuB;AACvB,QAAM,UAAU,iBAAiB,OAAO;AAGxC,MAAI,CAAC,QAAQ,aAAa,CAAC,QAAQ,aAAa,CAAC,QAAQ,aAAa;AACpE,WAAO;AAAA,MACL,UAAU;AAAA,MACV,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,OAAO,aAAa,mBAAmB;AAAA,MAClE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACnB,YAAY,OAAO;AAAA,QACnB,SAAS,OAAO;AAAA,QAChB,WAAW,SAAS,QAAQ,WAAW,EAAE;AAAA,QACzC,WAAW,QAAQ;AAAA,QACnB,aAAa,QAAQ;AAAA,MACvB,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,aAAO;AAAA,QACL,UAAU;AAAA,QACV,QAAQ,+BAA+B,SAAS,MAAM;AAAA,MACxD;AAAA,IACF;AAEA,WAAQ,MAAM,SAAS,KAAK;AAAA,EAC9B,SAAS,OAAO;AAEd,YAAQ,MAAM,4CAA4C,KAAK;AAC/D,WAAO;AAAA,MACL,UAAU;AAAA,MACV,QAAQ;AAAA,IACV;AAAA,EACF;AACF;AAuBO,SAAS,qBAAqB,QAAsB;AACzD,SAAO,CAAC,YAAqB,aAAa,SAAS,MAAM;AAC3D;","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,61 @@
1
+ // src/index.ts
2
+ var ATTEST_VERIFY_URL = "https://sekyuriti.build/api/v2/attest/verify";
3
+ function getAttestHeaders(request) {
4
+ return {
5
+ timestamp: request.headers.get("x-attest-timestamp"),
6
+ signature: request.headers.get("x-attest-signature"),
7
+ fingerprint: request.headers.get("x-attest-fingerprint"),
8
+ project: request.headers.get("x-attest-project")
9
+ };
10
+ }
11
+ function hasAttestHeaders(request) {
12
+ const headers = getAttestHeaders(request);
13
+ return !!(headers.timestamp && headers.signature && headers.fingerprint);
14
+ }
15
+ async function verifyAttest(request, config) {
16
+ const headers = getAttestHeaders(request);
17
+ if (!headers.timestamp || !headers.signature || !headers.fingerprint) {
18
+ return {
19
+ attested: false,
20
+ reason: "Missing ATTEST headers"
21
+ };
22
+ }
23
+ try {
24
+ const response = await fetch(config.verifyUrl || ATTEST_VERIFY_URL, {
25
+ method: "POST",
26
+ headers: {
27
+ "Content-Type": "application/json"
28
+ },
29
+ body: JSON.stringify({
30
+ project_id: config.projectId,
31
+ api_key: config.apiKey,
32
+ timestamp: parseInt(headers.timestamp, 10),
33
+ signature: headers.signature,
34
+ fingerprint: headers.fingerprint
35
+ })
36
+ });
37
+ if (!response.ok) {
38
+ return {
39
+ attested: false,
40
+ reason: `Verification service error: ${response.status}`
41
+ };
42
+ }
43
+ return await response.json();
44
+ } catch (error) {
45
+ console.error("[@sekyuriti/attest] Verification failed:", error);
46
+ return {
47
+ attested: true,
48
+ reason: "Verification service unavailable"
49
+ };
50
+ }
51
+ }
52
+ function createAttestVerifier(config) {
53
+ return (request) => verifyAttest(request, config);
54
+ }
55
+ export {
56
+ createAttestVerifier,
57
+ getAttestHeaders,
58
+ hasAttestHeaders,
59
+ verifyAttest
60
+ };
61
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @sekyuriti/attest\n *\n * API protection for Next.js applications.\n * Verify that requests come from real browsers, not bots or scripts.\n */\n\nconst ATTEST_VERIFY_URL = \"https://sekyuriti.build/api/v2/attest/verify\";\n\nexport interface AttestConfig {\n /** Your ATTEST project ID (starts with ATST_) */\n projectId: string;\n /** Your ATTEST API key (keep this secret, server-side only) */\n apiKey: string;\n /** Custom verify URL (optional, for self-hosted) */\n verifyUrl?: string;\n}\n\nexport interface AttestResult {\n /** Whether the request passed verification */\n attested: boolean;\n /** Browser fingerprint (if attested) */\n fingerprint?: string;\n /** Verification timestamp */\n timestamp?: number;\n /** Reason for failure (if not attested) */\n reason?: string;\n /** Warning message (e.g., approaching rate limit) */\n warning?: string;\n /** Current usage stats */\n usage?: {\n used: number;\n limit: number;\n percent: number;\n };\n}\n\nexport interface AttestHeaders {\n timestamp: string | null;\n signature: string | null;\n fingerprint: string | null;\n project: string | null;\n}\n\n/**\n * Extract ATTEST headers from a request\n */\nexport function getAttestHeaders(request: Request): AttestHeaders {\n return {\n timestamp: request.headers.get(\"x-attest-timestamp\"),\n signature: request.headers.get(\"x-attest-signature\"),\n fingerprint: request.headers.get(\"x-attest-fingerprint\"),\n project: request.headers.get(\"x-attest-project\"),\n };\n}\n\n/**\n * Check if a request has ATTEST headers\n */\nexport function hasAttestHeaders(request: Request): boolean {\n const headers = getAttestHeaders(request);\n return !!(headers.timestamp && headers.signature && headers.fingerprint);\n}\n\n/**\n * Verify a request with ATTEST\n *\n * @example\n * ```ts\n * import { verifyAttest } from \"@sekyuriti/attest\";\n *\n * export async function POST(request: Request) {\n * const result = await verifyAttest(request, {\n * projectId: process.env.ATTEST_PROJECT_ID!,\n * apiKey: process.env.ATTEST_API_KEY!,\n * });\n *\n * if (!result.attested) {\n * return Response.json({ error: \"Not attested\" }, { status: 403 });\n * }\n *\n * // ... handle request\n * }\n * ```\n */\nexport async function verifyAttest(\n request: Request,\n config: AttestConfig\n): Promise<AttestResult> {\n const headers = getAttestHeaders(request);\n\n // If no ATTEST headers, request is not attested\n if (!headers.timestamp || !headers.signature || !headers.fingerprint) {\n return {\n attested: false,\n reason: \"Missing ATTEST headers\",\n };\n }\n\n try {\n const response = await fetch(config.verifyUrl || ATTEST_VERIFY_URL, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n project_id: config.projectId,\n api_key: config.apiKey,\n timestamp: parseInt(headers.timestamp, 10),\n signature: headers.signature,\n fingerprint: headers.fingerprint,\n }),\n });\n\n if (!response.ok) {\n return {\n attested: false,\n reason: `Verification service error: ${response.status}`,\n };\n }\n\n return (await response.json()) as AttestResult;\n } catch (error) {\n // Fail open - don't break the app if ATTEST service is down\n console.error(\"[@sekyuriti/attest] Verification failed:\", error);\n return {\n attested: true,\n reason: \"Verification service unavailable\",\n };\n }\n}\n\n/**\n * Create a configured verifier function\n *\n * @example\n * ```ts\n * import { createAttestVerifier } from \"@sekyuriti/attest\";\n *\n * const verify = createAttestVerifier({\n * projectId: process.env.ATTEST_PROJECT_ID!,\n * apiKey: process.env.ATTEST_API_KEY!,\n * });\n *\n * export async function POST(request: Request) {\n * const result = await verify(request);\n * if (!result.attested) {\n * return Response.json({ error: \"Not attested\" }, { status: 403 });\n * }\n * // ...\n * }\n * ```\n */\nexport function createAttestVerifier(config: AttestConfig) {\n return (request: Request) => verifyAttest(request, config);\n}\n"],"mappings":";AAOA,IAAM,oBAAoB;AAwCnB,SAAS,iBAAiB,SAAiC;AAChE,SAAO;AAAA,IACL,WAAW,QAAQ,QAAQ,IAAI,oBAAoB;AAAA,IACnD,WAAW,QAAQ,QAAQ,IAAI,oBAAoB;AAAA,IACnD,aAAa,QAAQ,QAAQ,IAAI,sBAAsB;AAAA,IACvD,SAAS,QAAQ,QAAQ,IAAI,kBAAkB;AAAA,EACjD;AACF;AAKO,SAAS,iBAAiB,SAA2B;AAC1D,QAAM,UAAU,iBAAiB,OAAO;AACxC,SAAO,CAAC,EAAE,QAAQ,aAAa,QAAQ,aAAa,QAAQ;AAC9D;AAuBA,eAAsB,aACpB,SACA,QACuB;AACvB,QAAM,UAAU,iBAAiB,OAAO;AAGxC,MAAI,CAAC,QAAQ,aAAa,CAAC,QAAQ,aAAa,CAAC,QAAQ,aAAa;AACpE,WAAO;AAAA,MACL,UAAU;AAAA,MACV,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,OAAO,aAAa,mBAAmB;AAAA,MAClE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACnB,YAAY,OAAO;AAAA,QACnB,SAAS,OAAO;AAAA,QAChB,WAAW,SAAS,QAAQ,WAAW,EAAE;AAAA,QACzC,WAAW,QAAQ;AAAA,QACnB,aAAa,QAAQ;AAAA,MACvB,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,aAAO;AAAA,QACL,UAAU;AAAA,QACV,QAAQ,+BAA+B,SAAS,MAAM;AAAA,MACxD;AAAA,IACF;AAEA,WAAQ,MAAM,SAAS,KAAK;AAAA,EAC9B,SAAS,OAAO;AAEd,YAAQ,MAAM,4CAA4C,KAAK;AAC/D,WAAO;AAAA,MACL,UAAU;AAAA,MACV,QAAQ;AAAA,IACV;AAAA,EACF;AACF;AAuBO,SAAS,qBAAqB,QAAsB;AACzD,SAAO,CAAC,YAAqB,aAAa,SAAS,MAAM;AAC3D;","names":[]}
@@ -0,0 +1,75 @@
1
+ import { NextRequest } from 'next/server';
2
+ import { AttestConfig, AttestResult } from './index.mjs';
3
+
4
+ /**
5
+ * @sekyuriti/attest/middleware
6
+ *
7
+ * Next.js middleware for automatic ATTEST verification.
8
+ * Protects all matching routes with a single file.
9
+ */
10
+
11
+ interface AttestMiddlewareConfig extends AttestConfig {
12
+ /**
13
+ * Routes to protect (glob patterns)
14
+ * @default ["/api/*"]
15
+ */
16
+ protectedRoutes?: string[];
17
+ /**
18
+ * Routes to exclude from protection (glob patterns)
19
+ * @default []
20
+ */
21
+ excludeRoutes?: string[];
22
+ /**
23
+ * Allow requests without ATTEST headers (passthrough mode)
24
+ * Useful for gradual rollout
25
+ * @default false
26
+ */
27
+ allowUnauthenticated?: boolean;
28
+ /**
29
+ * Custom handler for blocked requests
30
+ */
31
+ onBlocked?: (request: NextRequest, result: AttestResult) => Response | Promise<Response>;
32
+ /**
33
+ * Custom handler for allowed requests (for logging, etc.)
34
+ */
35
+ onAllowed?: (request: NextRequest, result: AttestResult) => void | Promise<void>;
36
+ }
37
+ /**
38
+ * Create ATTEST middleware for Next.js
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * // middleware.ts
43
+ * import { createAttestMiddleware } from "@sekyuriti/attest/middleware";
44
+ *
45
+ * export const middleware = createAttestMiddleware({
46
+ * projectId: process.env.ATTEST_PROJECT_ID!,
47
+ * apiKey: process.env.ATTEST_API_KEY!,
48
+ * });
49
+ *
50
+ * export const config = {
51
+ * matcher: "/api/:path*",
52
+ * };
53
+ * ```
54
+ */
55
+ declare function createAttestMiddleware(config: AttestMiddlewareConfig): (request: NextRequest) => Promise<Response>;
56
+ /**
57
+ * Simple middleware that uses environment variables
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * // middleware.ts
62
+ * export { attestMiddleware as middleware } from "@sekyuriti/attest/middleware";
63
+ *
64
+ * export const config = {
65
+ * matcher: "/api/:path*",
66
+ * };
67
+ * ```
68
+ *
69
+ * Requires environment variables:
70
+ * - ATTEST_PROJECT_ID
71
+ * - ATTEST_API_KEY
72
+ */
73
+ declare const attestMiddleware: (request: NextRequest) => Promise<Response>;
74
+
75
+ export { type AttestMiddlewareConfig, attestMiddleware, createAttestMiddleware };
@@ -0,0 +1,75 @@
1
+ import { NextRequest } from 'next/server';
2
+ import { AttestConfig, AttestResult } from './index.js';
3
+
4
+ /**
5
+ * @sekyuriti/attest/middleware
6
+ *
7
+ * Next.js middleware for automatic ATTEST verification.
8
+ * Protects all matching routes with a single file.
9
+ */
10
+
11
+ interface AttestMiddlewareConfig extends AttestConfig {
12
+ /**
13
+ * Routes to protect (glob patterns)
14
+ * @default ["/api/*"]
15
+ */
16
+ protectedRoutes?: string[];
17
+ /**
18
+ * Routes to exclude from protection (glob patterns)
19
+ * @default []
20
+ */
21
+ excludeRoutes?: string[];
22
+ /**
23
+ * Allow requests without ATTEST headers (passthrough mode)
24
+ * Useful for gradual rollout
25
+ * @default false
26
+ */
27
+ allowUnauthenticated?: boolean;
28
+ /**
29
+ * Custom handler for blocked requests
30
+ */
31
+ onBlocked?: (request: NextRequest, result: AttestResult) => Response | Promise<Response>;
32
+ /**
33
+ * Custom handler for allowed requests (for logging, etc.)
34
+ */
35
+ onAllowed?: (request: NextRequest, result: AttestResult) => void | Promise<void>;
36
+ }
37
+ /**
38
+ * Create ATTEST middleware for Next.js
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * // middleware.ts
43
+ * import { createAttestMiddleware } from "@sekyuriti/attest/middleware";
44
+ *
45
+ * export const middleware = createAttestMiddleware({
46
+ * projectId: process.env.ATTEST_PROJECT_ID!,
47
+ * apiKey: process.env.ATTEST_API_KEY!,
48
+ * });
49
+ *
50
+ * export const config = {
51
+ * matcher: "/api/:path*",
52
+ * };
53
+ * ```
54
+ */
55
+ declare function createAttestMiddleware(config: AttestMiddlewareConfig): (request: NextRequest) => Promise<Response>;
56
+ /**
57
+ * Simple middleware that uses environment variables
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * // middleware.ts
62
+ * export { attestMiddleware as middleware } from "@sekyuriti/attest/middleware";
63
+ *
64
+ * export const config = {
65
+ * matcher: "/api/:path*",
66
+ * };
67
+ * ```
68
+ *
69
+ * Requires environment variables:
70
+ * - ATTEST_PROJECT_ID
71
+ * - ATTEST_API_KEY
72
+ */
73
+ declare const attestMiddleware: (request: NextRequest) => Promise<Response>;
74
+
75
+ export { type AttestMiddlewareConfig, attestMiddleware, createAttestMiddleware };
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/middleware.ts
21
+ var middleware_exports = {};
22
+ __export(middleware_exports, {
23
+ attestMiddleware: () => attestMiddleware,
24
+ createAttestMiddleware: () => createAttestMiddleware
25
+ });
26
+ module.exports = __toCommonJS(middleware_exports);
27
+ var import_server = require("next/server");
28
+
29
+ // src/index.ts
30
+ var ATTEST_VERIFY_URL = "https://sekyuriti.build/api/v2/attest/verify";
31
+ function getAttestHeaders(request) {
32
+ return {
33
+ timestamp: request.headers.get("x-attest-timestamp"),
34
+ signature: request.headers.get("x-attest-signature"),
35
+ fingerprint: request.headers.get("x-attest-fingerprint"),
36
+ project: request.headers.get("x-attest-project")
37
+ };
38
+ }
39
+ async function verifyAttest(request, config) {
40
+ const headers = getAttestHeaders(request);
41
+ if (!headers.timestamp || !headers.signature || !headers.fingerprint) {
42
+ return {
43
+ attested: false,
44
+ reason: "Missing ATTEST headers"
45
+ };
46
+ }
47
+ try {
48
+ const response = await fetch(config.verifyUrl || ATTEST_VERIFY_URL, {
49
+ method: "POST",
50
+ headers: {
51
+ "Content-Type": "application/json"
52
+ },
53
+ body: JSON.stringify({
54
+ project_id: config.projectId,
55
+ api_key: config.apiKey,
56
+ timestamp: parseInt(headers.timestamp, 10),
57
+ signature: headers.signature,
58
+ fingerprint: headers.fingerprint
59
+ })
60
+ });
61
+ if (!response.ok) {
62
+ return {
63
+ attested: false,
64
+ reason: `Verification service error: ${response.status}`
65
+ };
66
+ }
67
+ return await response.json();
68
+ } catch (error) {
69
+ console.error("[@sekyuriti/attest] Verification failed:", error);
70
+ return {
71
+ attested: true,
72
+ reason: "Verification service unavailable"
73
+ };
74
+ }
75
+ }
76
+
77
+ // src/middleware.ts
78
+ function matchesPattern(path, pattern) {
79
+ const regexPattern = pattern.replace(/\*/g, ".*").replace(/\?/g, ".");
80
+ return new RegExp(`^${regexPattern}$`).test(path);
81
+ }
82
+ function matchesAnyPattern(path, patterns) {
83
+ return patterns.some((pattern) => matchesPattern(path, pattern));
84
+ }
85
+ function createAttestMiddleware(config) {
86
+ const {
87
+ protectedRoutes = ["/api/*"],
88
+ excludeRoutes = [],
89
+ allowUnauthenticated = false,
90
+ onBlocked,
91
+ onAllowed,
92
+ ...attestConfig
93
+ } = config;
94
+ return async function middleware(request) {
95
+ const path = request.nextUrl.pathname;
96
+ const isProtected = matchesAnyPattern(path, protectedRoutes);
97
+ const isExcluded = matchesAnyPattern(path, excludeRoutes);
98
+ if (!isProtected || isExcluded) {
99
+ return import_server.NextResponse.next();
100
+ }
101
+ const result = await verifyAttest(request, attestConfig);
102
+ if (!result.attested) {
103
+ if (allowUnauthenticated && result.reason === "Missing ATTEST headers") {
104
+ return import_server.NextResponse.next();
105
+ }
106
+ if (onBlocked) {
107
+ return onBlocked(request, result);
108
+ }
109
+ return import_server.NextResponse.json(
110
+ {
111
+ error: "Request not attested",
112
+ reason: result.reason
113
+ },
114
+ { status: 403 }
115
+ );
116
+ }
117
+ if (onAllowed) {
118
+ await onAllowed(request, result);
119
+ }
120
+ const response = import_server.NextResponse.next();
121
+ if (result.fingerprint) {
122
+ response.headers.set("x-attest-verified-fingerprint", result.fingerprint);
123
+ }
124
+ return response;
125
+ };
126
+ }
127
+ var attestMiddleware = createAttestMiddleware({
128
+ projectId: process.env.ATTEST_PROJECT_ID || "",
129
+ apiKey: process.env.ATTEST_API_KEY || ""
130
+ });
131
+ // Annotate the CommonJS export names for ESM import in node:
132
+ 0 && (module.exports = {
133
+ attestMiddleware,
134
+ createAttestMiddleware
135
+ });
136
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware.ts","../src/index.ts"],"sourcesContent":["/**\n * @sekyuriti/attest/middleware\n *\n * Next.js middleware for automatic ATTEST verification.\n * Protects all matching routes with a single file.\n */\n\nimport { NextResponse } from \"next/server\";\nimport type { NextRequest } from \"next/server\";\nimport { verifyAttest, type AttestConfig, type AttestResult } from \"./index\";\n\nexport interface AttestMiddlewareConfig extends AttestConfig {\n /**\n * Routes to protect (glob patterns)\n * @default [\"/api/*\"]\n */\n protectedRoutes?: string[];\n\n /**\n * Routes to exclude from protection (glob patterns)\n * @default []\n */\n excludeRoutes?: string[];\n\n /**\n * Allow requests without ATTEST headers (passthrough mode)\n * Useful for gradual rollout\n * @default false\n */\n allowUnauthenticated?: boolean;\n\n /**\n * Custom handler for blocked requests\n */\n onBlocked?: (request: NextRequest, result: AttestResult) => Response | Promise<Response>;\n\n /**\n * Custom handler for allowed requests (for logging, etc.)\n */\n onAllowed?: (request: NextRequest, result: AttestResult) => void | Promise<void>;\n}\n\n/**\n * Check if a path matches a glob pattern\n */\nfunction matchesPattern(path: string, pattern: string): boolean {\n // Convert glob to regex\n const regexPattern = pattern\n .replace(/\\*/g, \".*\")\n .replace(/\\?/g, \".\");\n return new RegExp(`^${regexPattern}$`).test(path);\n}\n\n/**\n * Check if a path matches any of the patterns\n */\nfunction matchesAnyPattern(path: string, patterns: string[]): boolean {\n return patterns.some((pattern) => matchesPattern(path, pattern));\n}\n\n/**\n * Create ATTEST middleware for Next.js\n *\n * @example\n * ```ts\n * // middleware.ts\n * import { createAttestMiddleware } from \"@sekyuriti/attest/middleware\";\n *\n * export const middleware = createAttestMiddleware({\n * projectId: process.env.ATTEST_PROJECT_ID!,\n * apiKey: process.env.ATTEST_API_KEY!,\n * });\n *\n * export const config = {\n * matcher: \"/api/:path*\",\n * };\n * ```\n */\nexport function createAttestMiddleware(config: AttestMiddlewareConfig) {\n const {\n protectedRoutes = [\"/api/*\"],\n excludeRoutes = [],\n allowUnauthenticated = false,\n onBlocked,\n onAllowed,\n ...attestConfig\n } = config;\n\n return async function middleware(request: NextRequest) {\n const path = request.nextUrl.pathname;\n\n // Check if this path should be protected\n const isProtected = matchesAnyPattern(path, protectedRoutes);\n const isExcluded = matchesAnyPattern(path, excludeRoutes);\n\n if (!isProtected || isExcluded) {\n return NextResponse.next();\n }\n\n // Verify with ATTEST\n const result = await verifyAttest(request, attestConfig);\n\n if (!result.attested) {\n // Allow through if configured and no headers present\n if (allowUnauthenticated && result.reason === \"Missing ATTEST headers\") {\n return NextResponse.next();\n }\n\n // Custom blocked handler\n if (onBlocked) {\n return onBlocked(request, result);\n }\n\n // Default blocked response\n return NextResponse.json(\n {\n error: \"Request not attested\",\n reason: result.reason,\n },\n { status: 403 }\n );\n }\n\n // Call allowed handler if provided\n if (onAllowed) {\n await onAllowed(request, result);\n }\n\n // Add fingerprint to headers for downstream use\n const response = NextResponse.next();\n if (result.fingerprint) {\n response.headers.set(\"x-attest-verified-fingerprint\", result.fingerprint);\n }\n\n return response;\n };\n}\n\n/**\n * Simple middleware that uses environment variables\n *\n * @example\n * ```ts\n * // middleware.ts\n * export { attestMiddleware as middleware } from \"@sekyuriti/attest/middleware\";\n *\n * export const config = {\n * matcher: \"/api/:path*\",\n * };\n * ```\n *\n * Requires environment variables:\n * - ATTEST_PROJECT_ID\n * - ATTEST_API_KEY\n */\nexport const attestMiddleware = createAttestMiddleware({\n projectId: process.env.ATTEST_PROJECT_ID || \"\",\n apiKey: process.env.ATTEST_API_KEY || \"\",\n});\n","/**\n * @sekyuriti/attest\n *\n * API protection for Next.js applications.\n * Verify that requests come from real browsers, not bots or scripts.\n */\n\nconst ATTEST_VERIFY_URL = \"https://sekyuriti.build/api/v2/attest/verify\";\n\nexport interface AttestConfig {\n /** Your ATTEST project ID (starts with ATST_) */\n projectId: string;\n /** Your ATTEST API key (keep this secret, server-side only) */\n apiKey: string;\n /** Custom verify URL (optional, for self-hosted) */\n verifyUrl?: string;\n}\n\nexport interface AttestResult {\n /** Whether the request passed verification */\n attested: boolean;\n /** Browser fingerprint (if attested) */\n fingerprint?: string;\n /** Verification timestamp */\n timestamp?: number;\n /** Reason for failure (if not attested) */\n reason?: string;\n /** Warning message (e.g., approaching rate limit) */\n warning?: string;\n /** Current usage stats */\n usage?: {\n used: number;\n limit: number;\n percent: number;\n };\n}\n\nexport interface AttestHeaders {\n timestamp: string | null;\n signature: string | null;\n fingerprint: string | null;\n project: string | null;\n}\n\n/**\n * Extract ATTEST headers from a request\n */\nexport function getAttestHeaders(request: Request): AttestHeaders {\n return {\n timestamp: request.headers.get(\"x-attest-timestamp\"),\n signature: request.headers.get(\"x-attest-signature\"),\n fingerprint: request.headers.get(\"x-attest-fingerprint\"),\n project: request.headers.get(\"x-attest-project\"),\n };\n}\n\n/**\n * Check if a request has ATTEST headers\n */\nexport function hasAttestHeaders(request: Request): boolean {\n const headers = getAttestHeaders(request);\n return !!(headers.timestamp && headers.signature && headers.fingerprint);\n}\n\n/**\n * Verify a request with ATTEST\n *\n * @example\n * ```ts\n * import { verifyAttest } from \"@sekyuriti/attest\";\n *\n * export async function POST(request: Request) {\n * const result = await verifyAttest(request, {\n * projectId: process.env.ATTEST_PROJECT_ID!,\n * apiKey: process.env.ATTEST_API_KEY!,\n * });\n *\n * if (!result.attested) {\n * return Response.json({ error: \"Not attested\" }, { status: 403 });\n * }\n *\n * // ... handle request\n * }\n * ```\n */\nexport async function verifyAttest(\n request: Request,\n config: AttestConfig\n): Promise<AttestResult> {\n const headers = getAttestHeaders(request);\n\n // If no ATTEST headers, request is not attested\n if (!headers.timestamp || !headers.signature || !headers.fingerprint) {\n return {\n attested: false,\n reason: \"Missing ATTEST headers\",\n };\n }\n\n try {\n const response = await fetch(config.verifyUrl || ATTEST_VERIFY_URL, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n project_id: config.projectId,\n api_key: config.apiKey,\n timestamp: parseInt(headers.timestamp, 10),\n signature: headers.signature,\n fingerprint: headers.fingerprint,\n }),\n });\n\n if (!response.ok) {\n return {\n attested: false,\n reason: `Verification service error: ${response.status}`,\n };\n }\n\n return (await response.json()) as AttestResult;\n } catch (error) {\n // Fail open - don't break the app if ATTEST service is down\n console.error(\"[@sekyuriti/attest] Verification failed:\", error);\n return {\n attested: true,\n reason: \"Verification service unavailable\",\n };\n }\n}\n\n/**\n * Create a configured verifier function\n *\n * @example\n * ```ts\n * import { createAttestVerifier } from \"@sekyuriti/attest\";\n *\n * const verify = createAttestVerifier({\n * projectId: process.env.ATTEST_PROJECT_ID!,\n * apiKey: process.env.ATTEST_API_KEY!,\n * });\n *\n * export async function POST(request: Request) {\n * const result = await verify(request);\n * if (!result.attested) {\n * return Response.json({ error: \"Not attested\" }, { status: 403 });\n * }\n * // ...\n * }\n * ```\n */\nexport function createAttestVerifier(config: AttestConfig) {\n return (request: Request) => verifyAttest(request, config);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,110 @@
1
+ // src/middleware.ts
2
+ import { NextResponse } from "next/server";
3
+
4
+ // src/index.ts
5
+ var ATTEST_VERIFY_URL = "https://sekyuriti.build/api/v2/attest/verify";
6
+ function getAttestHeaders(request) {
7
+ return {
8
+ timestamp: request.headers.get("x-attest-timestamp"),
9
+ signature: request.headers.get("x-attest-signature"),
10
+ fingerprint: request.headers.get("x-attest-fingerprint"),
11
+ project: request.headers.get("x-attest-project")
12
+ };
13
+ }
14
+ async function verifyAttest(request, config) {
15
+ const headers = getAttestHeaders(request);
16
+ if (!headers.timestamp || !headers.signature || !headers.fingerprint) {
17
+ return {
18
+ attested: false,
19
+ reason: "Missing ATTEST headers"
20
+ };
21
+ }
22
+ try {
23
+ const response = await fetch(config.verifyUrl || ATTEST_VERIFY_URL, {
24
+ method: "POST",
25
+ headers: {
26
+ "Content-Type": "application/json"
27
+ },
28
+ body: JSON.stringify({
29
+ project_id: config.projectId,
30
+ api_key: config.apiKey,
31
+ timestamp: parseInt(headers.timestamp, 10),
32
+ signature: headers.signature,
33
+ fingerprint: headers.fingerprint
34
+ })
35
+ });
36
+ if (!response.ok) {
37
+ return {
38
+ attested: false,
39
+ reason: `Verification service error: ${response.status}`
40
+ };
41
+ }
42
+ return await response.json();
43
+ } catch (error) {
44
+ console.error("[@sekyuriti/attest] Verification failed:", error);
45
+ return {
46
+ attested: true,
47
+ reason: "Verification service unavailable"
48
+ };
49
+ }
50
+ }
51
+
52
+ // src/middleware.ts
53
+ function matchesPattern(path, pattern) {
54
+ const regexPattern = pattern.replace(/\*/g, ".*").replace(/\?/g, ".");
55
+ return new RegExp(`^${regexPattern}$`).test(path);
56
+ }
57
+ function matchesAnyPattern(path, patterns) {
58
+ return patterns.some((pattern) => matchesPattern(path, pattern));
59
+ }
60
+ function createAttestMiddleware(config) {
61
+ const {
62
+ protectedRoutes = ["/api/*"],
63
+ excludeRoutes = [],
64
+ allowUnauthenticated = false,
65
+ onBlocked,
66
+ onAllowed,
67
+ ...attestConfig
68
+ } = config;
69
+ return async function middleware(request) {
70
+ const path = request.nextUrl.pathname;
71
+ const isProtected = matchesAnyPattern(path, protectedRoutes);
72
+ const isExcluded = matchesAnyPattern(path, excludeRoutes);
73
+ if (!isProtected || isExcluded) {
74
+ return NextResponse.next();
75
+ }
76
+ const result = await verifyAttest(request, attestConfig);
77
+ if (!result.attested) {
78
+ if (allowUnauthenticated && result.reason === "Missing ATTEST headers") {
79
+ return NextResponse.next();
80
+ }
81
+ if (onBlocked) {
82
+ return onBlocked(request, result);
83
+ }
84
+ return NextResponse.json(
85
+ {
86
+ error: "Request not attested",
87
+ reason: result.reason
88
+ },
89
+ { status: 403 }
90
+ );
91
+ }
92
+ if (onAllowed) {
93
+ await onAllowed(request, result);
94
+ }
95
+ const response = NextResponse.next();
96
+ if (result.fingerprint) {
97
+ response.headers.set("x-attest-verified-fingerprint", result.fingerprint);
98
+ }
99
+ return response;
100
+ };
101
+ }
102
+ var attestMiddleware = createAttestMiddleware({
103
+ projectId: process.env.ATTEST_PROJECT_ID || "",
104
+ apiKey: process.env.ATTEST_API_KEY || ""
105
+ });
106
+ export {
107
+ attestMiddleware,
108
+ createAttestMiddleware
109
+ };
110
+ //# sourceMappingURL=middleware.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware.ts","../src/index.ts"],"sourcesContent":["/**\n * @sekyuriti/attest/middleware\n *\n * Next.js middleware for automatic ATTEST verification.\n * Protects all matching routes with a single file.\n */\n\nimport { NextResponse } from \"next/server\";\nimport type { NextRequest } from \"next/server\";\nimport { verifyAttest, type AttestConfig, type AttestResult } from \"./index\";\n\nexport interface AttestMiddlewareConfig extends AttestConfig {\n /**\n * Routes to protect (glob patterns)\n * @default [\"/api/*\"]\n */\n protectedRoutes?: string[];\n\n /**\n * Routes to exclude from protection (glob patterns)\n * @default []\n */\n excludeRoutes?: string[];\n\n /**\n * Allow requests without ATTEST headers (passthrough mode)\n * Useful for gradual rollout\n * @default false\n */\n allowUnauthenticated?: boolean;\n\n /**\n * Custom handler for blocked requests\n */\n onBlocked?: (request: NextRequest, result: AttestResult) => Response | Promise<Response>;\n\n /**\n * Custom handler for allowed requests (for logging, etc.)\n */\n onAllowed?: (request: NextRequest, result: AttestResult) => void | Promise<void>;\n}\n\n/**\n * Check if a path matches a glob pattern\n */\nfunction matchesPattern(path: string, pattern: string): boolean {\n // Convert glob to regex\n const regexPattern = pattern\n .replace(/\\*/g, \".*\")\n .replace(/\\?/g, \".\");\n return new RegExp(`^${regexPattern}$`).test(path);\n}\n\n/**\n * Check if a path matches any of the patterns\n */\nfunction matchesAnyPattern(path: string, patterns: string[]): boolean {\n return patterns.some((pattern) => matchesPattern(path, pattern));\n}\n\n/**\n * Create ATTEST middleware for Next.js\n *\n * @example\n * ```ts\n * // middleware.ts\n * import { createAttestMiddleware } from \"@sekyuriti/attest/middleware\";\n *\n * export const middleware = createAttestMiddleware({\n * projectId: process.env.ATTEST_PROJECT_ID!,\n * apiKey: process.env.ATTEST_API_KEY!,\n * });\n *\n * export const config = {\n * matcher: \"/api/:path*\",\n * };\n * ```\n */\nexport function createAttestMiddleware(config: AttestMiddlewareConfig) {\n const {\n protectedRoutes = [\"/api/*\"],\n excludeRoutes = [],\n allowUnauthenticated = false,\n onBlocked,\n onAllowed,\n ...attestConfig\n } = config;\n\n return async function middleware(request: NextRequest) {\n const path = request.nextUrl.pathname;\n\n // Check if this path should be protected\n const isProtected = matchesAnyPattern(path, protectedRoutes);\n const isExcluded = matchesAnyPattern(path, excludeRoutes);\n\n if (!isProtected || isExcluded) {\n return NextResponse.next();\n }\n\n // Verify with ATTEST\n const result = await verifyAttest(request, attestConfig);\n\n if (!result.attested) {\n // Allow through if configured and no headers present\n if (allowUnauthenticated && result.reason === \"Missing ATTEST headers\") {\n return NextResponse.next();\n }\n\n // Custom blocked handler\n if (onBlocked) {\n return onBlocked(request, result);\n }\n\n // Default blocked response\n return NextResponse.json(\n {\n error: \"Request not attested\",\n reason: result.reason,\n },\n { status: 403 }\n );\n }\n\n // Call allowed handler if provided\n if (onAllowed) {\n await onAllowed(request, result);\n }\n\n // Add fingerprint to headers for downstream use\n const response = NextResponse.next();\n if (result.fingerprint) {\n response.headers.set(\"x-attest-verified-fingerprint\", result.fingerprint);\n }\n\n return response;\n };\n}\n\n/**\n * Simple middleware that uses environment variables\n *\n * @example\n * ```ts\n * // middleware.ts\n * export { attestMiddleware as middleware } from \"@sekyuriti/attest/middleware\";\n *\n * export const config = {\n * matcher: \"/api/:path*\",\n * };\n * ```\n *\n * Requires environment variables:\n * - ATTEST_PROJECT_ID\n * - ATTEST_API_KEY\n */\nexport const attestMiddleware = createAttestMiddleware({\n projectId: process.env.ATTEST_PROJECT_ID || \"\",\n apiKey: process.env.ATTEST_API_KEY || \"\",\n});\n","/**\n * @sekyuriti/attest\n *\n * API protection for Next.js applications.\n * Verify that requests come from real browsers, not bots or scripts.\n */\n\nconst ATTEST_VERIFY_URL = \"https://sekyuriti.build/api/v2/attest/verify\";\n\nexport interface AttestConfig {\n /** Your ATTEST project ID (starts with ATST_) */\n projectId: string;\n /** Your ATTEST API key (keep this secret, server-side only) */\n apiKey: string;\n /** Custom verify URL (optional, for self-hosted) */\n verifyUrl?: string;\n}\n\nexport interface AttestResult {\n /** Whether the request passed verification */\n attested: boolean;\n /** Browser fingerprint (if attested) */\n fingerprint?: string;\n /** Verification timestamp */\n timestamp?: number;\n /** Reason for failure (if not attested) */\n reason?: string;\n /** Warning message (e.g., approaching rate limit) */\n warning?: string;\n /** Current usage stats */\n usage?: {\n used: number;\n limit: number;\n percent: number;\n };\n}\n\nexport interface AttestHeaders {\n timestamp: string | null;\n signature: string | null;\n fingerprint: string | null;\n project: string | null;\n}\n\n/**\n * Extract ATTEST headers from a request\n */\nexport function getAttestHeaders(request: Request): AttestHeaders {\n return {\n timestamp: request.headers.get(\"x-attest-timestamp\"),\n signature: request.headers.get(\"x-attest-signature\"),\n fingerprint: request.headers.get(\"x-attest-fingerprint\"),\n project: request.headers.get(\"x-attest-project\"),\n };\n}\n\n/**\n * Check if a request has ATTEST headers\n */\nexport function hasAttestHeaders(request: Request): boolean {\n const headers = getAttestHeaders(request);\n return !!(headers.timestamp && headers.signature && headers.fingerprint);\n}\n\n/**\n * Verify a request with ATTEST\n *\n * @example\n * ```ts\n * import { verifyAttest } from \"@sekyuriti/attest\";\n *\n * export async function POST(request: Request) {\n * const result = await verifyAttest(request, {\n * projectId: process.env.ATTEST_PROJECT_ID!,\n * apiKey: process.env.ATTEST_API_KEY!,\n * });\n *\n * if (!result.attested) {\n * return Response.json({ error: \"Not attested\" }, { status: 403 });\n * }\n *\n * // ... handle request\n * }\n * ```\n */\nexport async function verifyAttest(\n request: Request,\n config: AttestConfig\n): Promise<AttestResult> {\n const headers = getAttestHeaders(request);\n\n // If no ATTEST headers, request is not attested\n if (!headers.timestamp || !headers.signature || !headers.fingerprint) {\n return {\n attested: false,\n reason: \"Missing ATTEST headers\",\n };\n }\n\n try {\n const response = await fetch(config.verifyUrl || ATTEST_VERIFY_URL, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n project_id: config.projectId,\n api_key: config.apiKey,\n timestamp: parseInt(headers.timestamp, 10),\n signature: headers.signature,\n fingerprint: headers.fingerprint,\n }),\n });\n\n if (!response.ok) {\n return {\n attested: false,\n reason: `Verification service error: ${response.status}`,\n };\n }\n\n return (await response.json()) as AttestResult;\n } catch (error) {\n // Fail open - don't break the app if ATTEST service is down\n console.error(\"[@sekyuriti/attest] Verification failed:\", error);\n return {\n attested: true,\n reason: \"Verification service unavailable\",\n };\n }\n}\n\n/**\n * Create a configured verifier function\n *\n * @example\n * ```ts\n * import { createAttestVerifier } from \"@sekyuriti/attest\";\n *\n * const verify = createAttestVerifier({\n * projectId: process.env.ATTEST_PROJECT_ID!,\n * apiKey: process.env.ATTEST_API_KEY!,\n * });\n *\n * export async function POST(request: Request) {\n * const result = await verify(request);\n * if (!result.attested) {\n * return Response.json({ error: \"Not attested\" }, { status: 403 });\n * }\n * // ...\n * }\n * ```\n */\nexport function createAttestVerifier(config: AttestConfig) {\n return (request: Request) => verifyAttest(request, config);\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@sekyuriti/attest",
3
+ "version": "0.1.0",
4
+ "description": "API protection middleware for Next.js - verify requests with ATTEST",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ },
14
+ "./middleware": {
15
+ "types": "./dist/middleware.d.ts",
16
+ "import": "./dist/middleware.mjs",
17
+ "require": "./dist/middleware.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "dev": "tsup --watch",
26
+ "prepublishOnly": "npm run build"
27
+ },
28
+ "keywords": [
29
+ "api-protection",
30
+ "bot-detection",
31
+ "security",
32
+ "nextjs",
33
+ "middleware",
34
+ "attest",
35
+ "sekyuriti"
36
+ ],
37
+ "author": "SEKYURITI",
38
+ "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/sekyuriti/attest"
42
+ },
43
+ "homepage": "https://sekyuriti.build/attest",
44
+ "peerDependencies": {
45
+ "next": ">=13.0.0"
46
+ },
47
+ "devDependencies": {
48
+ "next": "^15.0.0",
49
+ "tsup": "^8.0.0",
50
+ "typescript": "^5.0.0"
51
+ }
52
+ }