@sekyuriti/attest 0.2.1 → 0.2.3

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.
Files changed (3) hide show
  1. package/README.md +38 -93
  2. package/bin/attest.js +110 -126
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,52 +2,39 @@
2
2
 
3
3
  API protection for Next.js applications. Verify that requests come from real browsers, not bots or scripts.
4
4
 
5
- ## Installation
5
+ ## Quick Start
6
+
7
+ One command setup:
6
8
 
7
9
  ```bash
8
- npm install @sekyuriti/attest
10
+ npx @sekyuriti/attest login
9
11
  ```
10
12
 
11
- ## Quick Start
13
+ This will:
14
+ 1. Open browser for authentication
15
+ 2. Let you select your project
16
+ 3. Auto-add environment variables to `.env.local`
17
+ 4. Auto-inject the ATTEST script into `layout.tsx`
12
18
 
13
- ### 1. Add the script to your frontend
19
+ Done. Your API is protected.
14
20
 
15
- ```html
16
- <script src="https://sekyuriti.build/api/v2/attest/script/YOUR_PROJECT_ID" defer></script>
17
- ```
21
+ ## What It Does
18
22
 
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
23
+ - **Frontend script** automatically signs all `fetch()` and `XMLHttpRequest` calls
24
+ - **Backend verification** validates signatures with SEKYURITI's API
25
+ - **Bots and scripts** can't generate valid signatures without running in a real browser
39
26
 
40
- **Option A: Middleware (recommended)**
27
+ ## Optional: Middleware
41
28
 
42
- Protects all `/api/*` routes automatically:
29
+ Add server-side verification for all API routes:
43
30
 
44
31
  ```ts
45
32
  // middleware.ts
46
33
  import { createAttestMiddleware } from "@sekyuriti/attest/middleware";
47
34
 
48
35
  export const middleware = createAttestMiddleware({
49
- projectId: process.env.ATTEST_PROJECT_ID!,
50
- apiKey: process.env.ATTEST_API_KEY!,
36
+ projectId: process.env.NEXT_PUBLIC_ATTEST_KEY!,
37
+ apiKey: process.env.ATTEST_SECRET_KEY!,
51
38
  });
52
39
 
53
40
  export const config = {
@@ -55,7 +42,7 @@ export const config = {
55
42
  };
56
43
  ```
57
44
 
58
- **Option B: Per-route verification**
45
+ ## Optional: Per-Route Verification
59
46
 
60
47
  ```ts
61
48
  // app/api/protected/route.ts
@@ -63,8 +50,8 @@ import { verifyAttest } from "@sekyuriti/attest";
63
50
 
64
51
  export async function POST(request: Request) {
65
52
  const result = await verifyAttest(request, {
66
- projectId: process.env.ATTEST_PROJECT_ID!,
67
- apiKey: process.env.ATTEST_API_KEY!,
53
+ projectId: process.env.NEXT_PUBLIC_ATTEST_KEY!,
54
+ apiKey: process.env.ATTEST_SECRET_KEY!,
68
55
  });
69
56
 
70
57
  if (!result.attested) {
@@ -75,72 +62,26 @@ export async function POST(request: Request) {
75
62
  }
76
63
  ```
77
64
 
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
- });
65
+ ## CLI Commands
97
66
 
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
- });
67
+ ```bash
68
+ attest login # Authenticate and setup project
69
+ attest logout # Sign out
70
+ attest status # Show account and usage info
71
+ attest init # Re-run setup in current project
72
+ attest whoami # Print current user email
73
+ attest help # Show help
121
74
  ```
122
75
 
123
- ### `createAttestVerifier(config)`
124
-
125
- Create a reusable verifier function.
76
+ ## Environment Variables
126
77
 
127
- ```ts
128
- const verify = createAttestVerifier({
129
- projectId: process.env.ATTEST_PROJECT_ID!,
130
- apiKey: process.env.ATTEST_API_KEY!,
131
- });
78
+ Auto-generated by `attest login`:
132
79
 
133
- // Use in multiple routes
134
- const result = await verify(request);
80
+ ```env
81
+ NEXT_PUBLIC_ATTEST_KEY=your_public_key
82
+ ATTEST_SECRET_KEY=your_secret_key
135
83
  ```
136
84
 
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
85
  ## Protection Features
145
86
 
146
87
  - DevTools detection
@@ -149,6 +90,10 @@ const result = await verify(request);
149
90
  - Browser fingerprinting
150
91
  - Timestamp validation
151
92
 
93
+ ## Documentation
94
+
95
+ https://sekyuriti.build/docs/attest
96
+
152
97
  ## License
153
98
 
154
99
  MIT
package/bin/attest.js CHANGED
@@ -139,7 +139,109 @@ function printHeader() {
139
139
  logBold(" █▀▀ █▀▀ █▄▀ █▄█ █ █ █▀█ █ ▀█▀ █");
140
140
  logBold(" ▄▄█ ██▄ █ █ █ █▄█ █▀▄ █ █ █");
141
141
  log("");
142
- logDim(" ATTEST CLI v0.2.0");
142
+ logDim(" ATTEST CLI v0.2.1");
143
+ log("");
144
+ }
145
+
146
+ // ═══════════════════════════════════════════════════════════════════
147
+ // INIT HELPER (used by both login and init commands)
148
+ // ═══════════════════════════════════════════════════════════════════
149
+
150
+ async function runInit(publicKey, apiKey) {
151
+ let steps = [];
152
+
153
+ // Step 1: Add .env variables
154
+ const envPath = path.join(process.cwd(), ".env");
155
+ const envLocalPath = path.join(process.cwd(), ".env.local");
156
+ let targetEnvPath = envLocalPath;
157
+
158
+ if (fs.existsSync(envLocalPath)) {
159
+ targetEnvPath = envLocalPath;
160
+ } else if (fs.existsSync(envPath)) {
161
+ targetEnvPath = envPath;
162
+ }
163
+
164
+ const envVars = `
165
+ # ATTEST Configuration
166
+ NEXT_PUBLIC_ATTEST_KEY=${publicKey}
167
+ ATTEST_SECRET_KEY=${apiKey}
168
+ `.trim();
169
+
170
+ if (fs.existsSync(targetEnvPath)) {
171
+ let content = fs.readFileSync(targetEnvPath, "utf-8");
172
+ if (!content.includes("ATTEST_KEY")) {
173
+ content += "\n\n" + envVars + "\n";
174
+ fs.writeFileSync(targetEnvPath, content);
175
+ steps.push(`Added ATTEST config to ${path.basename(targetEnvPath)}`);
176
+ } else {
177
+ steps.push(`ATTEST config already in ${path.basename(targetEnvPath)}`);
178
+ }
179
+ } else {
180
+ fs.writeFileSync(envLocalPath, envVars + "\n");
181
+ steps.push("Created .env.local with ATTEST config");
182
+ }
183
+
184
+ // Step 2: Find and update layout.tsx
185
+ const layoutPaths = [
186
+ path.join(process.cwd(), "src/app/layout.tsx"),
187
+ path.join(process.cwd(), "app/layout.tsx"),
188
+ path.join(process.cwd(), "src/app/layout.js"),
189
+ path.join(process.cwd(), "app/layout.js"),
190
+ ];
191
+
192
+ let layoutPath = null;
193
+ for (const p of layoutPaths) {
194
+ if (fs.existsSync(p)) {
195
+ layoutPath = p;
196
+ break;
197
+ }
198
+ }
199
+
200
+ const scriptTag = `<Script
201
+ src="https://sekyuriti.build/api/v2/attest/script/${publicKey}"
202
+ strategy="beforeInteractive"
203
+ />`;
204
+
205
+ if (layoutPath) {
206
+ let layoutContent = fs.readFileSync(layoutPath, "utf-8");
207
+
208
+ if (layoutContent.includes("sekyuriti.build/api/v2/attest/script")) {
209
+ steps.push("ATTEST script already in layout");
210
+ } else {
211
+ // Check if Script is already imported
212
+ const hasScriptImport = layoutContent.includes("from 'next/script'") || layoutContent.includes('from "next/script"');
213
+
214
+ if (!hasScriptImport) {
215
+ // Add Script import after the first import line
216
+ layoutContent = layoutContent.replace(
217
+ /(import .+ from ['"][^'"]+['"];?\n)/,
218
+ `$1import Script from "next/script";\n`
219
+ );
220
+ }
221
+
222
+ // Add script tag after <body> or <body className=...>
223
+ if (layoutContent.includes("<body")) {
224
+ layoutContent = layoutContent.replace(
225
+ /(<body[^>]*>)/,
226
+ `$1\n ${scriptTag}`
227
+ );
228
+ fs.writeFileSync(layoutPath, layoutContent);
229
+ steps.push(`Added ATTEST script to ${path.basename(layoutPath)}`);
230
+ } else {
231
+ steps.push("Could not find <body> tag in layout");
232
+ }
233
+ }
234
+ } else {
235
+ steps.push("No layout.tsx found - add script manually");
236
+ }
237
+
238
+ // Print results
239
+ for (const step of steps) {
240
+ log(` ${c.bold}✓${c.reset} ${step}`);
241
+ }
242
+
243
+ log("");
244
+ log(" Documentation: https://sekyuriti.build/docs/attest");
143
245
  log("");
144
246
  }
145
247
 
@@ -211,6 +313,11 @@ async function cmdLogin() {
211
313
  apiKey: data.apiKey,
212
314
  });
213
315
 
316
+ // Auto-run init after successful login
317
+ log(" Setting up your project...");
318
+ log("");
319
+ await runInit(data.publicKey, data.apiKey);
320
+
214
321
  return;
215
322
  }
216
323
  }
@@ -334,132 +441,9 @@ async function cmdInit() {
334
441
  return;
335
442
  }
336
443
 
337
- let steps = [];
338
-
339
- // Step 1: Add .env variables
340
- const envPath = path.join(process.cwd(), ".env");
341
- const envLocalPath = path.join(process.cwd(), ".env.local");
342
- let targetEnvPath = envLocalPath;
343
-
344
- if (fs.existsSync(envLocalPath)) {
345
- targetEnvPath = envLocalPath;
346
- } else if (fs.existsSync(envPath)) {
347
- targetEnvPath = envPath;
348
- }
349
-
350
- const envVars = `
351
- # ATTEST Configuration
352
- NEXT_PUBLIC_ATTEST_KEY=${config.publicKey}
353
- ATTEST_SECRET_KEY=${config.apiKey}
354
- `.trim();
355
-
356
- if (fs.existsSync(targetEnvPath)) {
357
- let content = fs.readFileSync(targetEnvPath, "utf-8");
358
- if (!content.includes("ATTEST_KEY")) {
359
- content += "\n\n" + envVars + "\n";
360
- fs.writeFileSync(targetEnvPath, content);
361
- steps.push(`Added ATTEST config to ${path.basename(targetEnvPath)}`);
362
- } else {
363
- steps.push(`ATTEST config already in ${path.basename(targetEnvPath)}`);
364
- }
365
- } else {
366
- fs.writeFileSync(envLocalPath, envVars + "\n");
367
- steps.push("Created .env.local with ATTEST config");
368
- }
369
-
370
- // Step 2: Find and update layout.tsx
371
- const layoutPaths = [
372
- path.join(process.cwd(), "src/app/layout.tsx"),
373
- path.join(process.cwd(), "app/layout.tsx"),
374
- path.join(process.cwd(), "src/app/layout.js"),
375
- path.join(process.cwd(), "app/layout.js"),
376
- ];
377
-
378
- let layoutPath = null;
379
- for (const p of layoutPaths) {
380
- if (fs.existsSync(p)) {
381
- layoutPath = p;
382
- break;
383
- }
384
- }
385
-
386
- const scriptTag = `<Script
387
- src="https://sekyuriti.build/api/v2/attest/script/${config.publicKey}"
388
- strategy="beforeInteractive"
389
- />`;
390
-
391
- if (layoutPath) {
392
- let layoutContent = fs.readFileSync(layoutPath, "utf-8");
393
-
394
- if (layoutContent.includes("sekyuriti.build/api/v2/attest/script")) {
395
- steps.push("ATTEST script already in layout");
396
- } else {
397
- // Check if Script is already imported
398
- const hasScriptImport = layoutContent.includes("from 'next/script'") || layoutContent.includes('from "next/script"');
399
-
400
- if (!hasScriptImport) {
401
- // Add Script import after the first import line
402
- layoutContent = layoutContent.replace(
403
- /(import .+ from ['"][^'"]+['"];?\n)/,
404
- `$1import Script from "next/script";\n`
405
- );
406
- }
407
-
408
- // Add script tag after <body> or <body className=...>
409
- if (layoutContent.includes("<body")) {
410
- layoutContent = layoutContent.replace(
411
- /(<body[^>]*>)/,
412
- `$1\n ${scriptTag}`
413
- );
414
- fs.writeFileSync(layoutPath, layoutContent);
415
- steps.push(`Added ATTEST script to ${path.basename(layoutPath)}`);
416
- } else {
417
- steps.push("Could not find <body> tag in layout");
418
- }
419
- }
420
- } else {
421
- steps.push("No layout.tsx found - add script manually");
422
- }
423
-
424
- // Step 3: Check for middleware.ts and offer to add verification
425
- const middlewarePaths = [
426
- path.join(process.cwd(), "middleware.ts"),
427
- path.join(process.cwd(), "src/middleware.ts"),
428
- path.join(process.cwd(), "middleware.js"),
429
- path.join(process.cwd(), "src/middleware.js"),
430
- ];
431
-
432
- let hasMiddleware = false;
433
- for (const p of middlewarePaths) {
434
- if (fs.existsSync(p)) {
435
- hasMiddleware = true;
436
- break;
437
- }
438
- }
439
-
440
- // Print results
441
- log(" SETUP COMPLETE");
442
- log("");
443
-
444
- for (const step of steps) {
445
- log(` ${c.bold}✓${c.reset} ${step}`);
446
- }
447
-
448
- log("");
449
-
450
- if (!hasMiddleware) {
451
- log(" Optional: Add middleware for server-side verification:");
452
- log("");
453
- log(` ${c.dim}// middleware.ts${c.reset}`);
454
- log(` ${c.dim}import { createAttestMiddleware } from '@sekyuriti/attest/middleware';${c.reset}`);
455
- log(` ${c.dim}export default createAttestMiddleware({${c.reset}`);
456
- log(` ${c.dim} protectedPaths: ['/api/'],${c.reset}`);
457
- log(` ${c.dim}});${c.reset}`);
458
- log("");
459
- }
460
-
461
- log(" Documentation: https://sekyuriti.build/docs/attest");
444
+ log(" Setting up ATTEST...");
462
445
  log("");
446
+ await runInit(config.publicKey, config.apiKey);
463
447
  }
464
448
 
465
449
  // ═══════════════════════════════════════════════════════════════════
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sekyuriti/attest",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "API protection middleware for Next.js - verify requests with ATTEST",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",