nextjs-secure 0.3.0 → 0.6.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 CHANGED
@@ -4,14 +4,18 @@
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
6
6
  [![Next.js](https://img.shields.io/badge/Next.js-13+-black.svg)](https://nextjs.org/)
7
+ [![Tests](https://img.shields.io/badge/tests-467%20passing-brightgreen.svg)]()
7
8
 
8
- Production-ready security middleware for Next.js App Router. Zero config, maximum protection.
9
+ Production-ready security middleware for Next.js 13+ App Router. Zero config, maximum protection.
9
10
 
10
11
  ```typescript
11
- import { withRateLimit } from 'nextjs-secure'
12
+ import { withRateLimit, withJWT, withValidation } from 'nextjs-secure'
12
13
 
13
- export const GET = withRateLimit(
14
- async (req) => Response.json({ message: 'Hello!' }),
14
+ export const POST = withRateLimit(
15
+ withJWT(
16
+ withValidation(handler, { body: schema }),
17
+ { secret: process.env.JWT_SECRET }
18
+ ),
15
19
  { limit: 100, window: '15m' }
16
20
  )
17
21
  ```
@@ -27,6 +31,7 @@ Building secure APIs in Next.js shouldn't require hours of boilerplate. Most pro
27
31
  - **Edge Ready** - Works on Vercel Edge, Cloudflare Workers, Node.js
28
32
  - **Flexible** - Memory, Redis, or Upstash storage backends
29
33
  - **Lightweight** - No bloated dependencies, tree-shakeable
34
+ - **Complete** - Rate limiting, auth, CSRF, headers, validation, audit logging
30
35
 
31
36
  ## Installation
32
37
 
@@ -42,26 +47,19 @@ pnpm add nextjs-secure
42
47
 
43
48
  - [Quick Start](#quick-start)
44
49
  - [Rate Limiting](#rate-limiting)
45
- - [Basic Usage](#basic-usage)
46
- - [Algorithms](#algorithms)
47
- - [Storage Backends](#storage-backends)
48
- - [Custom Identifiers](#custom-identifiers)
49
- - [Response Customization](#response-customization)
50
50
  - [CSRF Protection](#csrf-protection)
51
- - [Basic Setup](#basic-setup)
52
- - [Client-Side Usage](#client-side-usage)
53
- - [Configuration](#configuration-1)
54
- - [Manual Validation](#manual-validation)
55
51
  - [Security Headers](#security-headers)
56
- - [Quick Start](#quick-start-1)
57
- - [Presets](#presets)
58
- - [Custom Configuration](#custom-configuration)
52
+ - [Authentication](#authentication)
53
+ - [Input Validation](#input-validation)
54
+ - [Audit Logging](#audit-logging)
59
55
  - [Utilities](#utilities)
60
56
  - [API Reference](#api-reference)
61
57
  - [Examples](#examples)
62
58
  - [Roadmap](#roadmap)
63
59
  - [Contributing](#contributing)
64
60
 
61
+ ---
62
+
65
63
  ## Quick Start
66
64
 
67
65
  ### Protect an API Route
@@ -75,48 +73,53 @@ export const GET = withRateLimit(
75
73
  const posts = await db.posts.findMany()
76
74
  return Response.json(posts)
77
75
  },
78
- {
79
- limit: 100, // 100 requests
80
- window: '15m' // per 15 minutes
81
- }
76
+ { limit: 100, window: '15m' }
82
77
  )
83
78
  ```
84
79
 
85
- ### Create a Reusable Limiter
80
+ ### Full Security Stack
86
81
 
87
82
  ```typescript
88
- // lib/rate-limit.ts
89
- import { createRateLimiter } from 'nextjs-secure'
90
-
91
- export const apiLimiter = createRateLimiter({
92
- limit: 100,
93
- window: '15m',
94
- })
95
-
96
- export const strictLimiter = createRateLimiter({
97
- limit: 10,
98
- window: '1m',
99
- })
100
- ```
83
+ // app/api/admin/users/route.ts
84
+ import { withRateLimit, withJWT, withRoles, withValidation, withAuditLog } from 'nextjs-secure'
85
+ import { MemoryStore } from 'nextjs-secure/audit'
101
86
 
102
- ```typescript
103
- // app/api/users/route.ts
104
- import { apiLimiter, strictLimiter } from '@/lib/rate-limit'
87
+ const auditStore = new MemoryStore({ maxEntries: 1000 })
105
88
 
106
- export const GET = apiLimiter(async (req) => {
107
- // ...
108
- })
89
+ const schema = {
90
+ email: { type: 'email', required: true },
91
+ role: { type: 'string', enum: ['user', 'admin'] }
92
+ }
109
93
 
110
- export const POST = strictLimiter(async (req) => {
111
- // ...
112
- })
94
+ export const POST = withAuditLog(
95
+ withRateLimit(
96
+ withJWT(
97
+ withRoles(
98
+ withValidation(
99
+ async (req, ctx) => {
100
+ // ctx.user = authenticated user
101
+ // ctx.validated = validated body
102
+ return Response.json({ success: true })
103
+ },
104
+ { body: schema }
105
+ ),
106
+ { roles: ['admin'] }
107
+ ),
108
+ { secret: process.env.JWT_SECRET }
109
+ ),
110
+ { limit: 10, window: '1m' }
111
+ ),
112
+ { store: auditStore }
113
+ )
113
114
  ```
114
115
 
116
+ ---
117
+
115
118
  ## Rate Limiting
116
119
 
117
- ### Basic Usage
120
+ Protect your APIs from abuse with configurable rate limiting.
118
121
 
119
- The `withRateLimit` higher-order function wraps your route handler:
122
+ ### Basic Usage
120
123
 
121
124
  ```typescript
122
125
  import { withRateLimit } from 'nextjs-secure'
@@ -138,29 +141,18 @@ export const GET = withRateLimit(handler, {
138
141
  ### Algorithms
139
142
 
140
143
  #### Sliding Window (Default)
141
-
142
- Prevents request bursts at window boundaries. Uses weighted counting between current and previous windows.
144
+ Prevents request bursts at window boundaries.
143
145
 
144
146
  ```typescript
145
147
  export const GET = withRateLimit(handler, {
146
148
  limit: 100,
147
149
  window: '15m',
148
- algorithm: 'sliding-window', // default
150
+ algorithm: 'sliding-window',
149
151
  })
150
152
  ```
151
153
 
152
- **How it works:**
153
- ```
154
- Window 1: |----[80 requests]-----|
155
- Window 2: |--[30 requests]-------|
156
- ^ 50% through window 2
157
-
158
- Weighted count = 30 + (80 × 0.5) = 70 requests
159
- ```
160
-
161
154
  #### Fixed Window
162
-
163
- Simple counter that resets at fixed intervals. Lower memory usage but allows bursts at boundaries.
155
+ Simple counter that resets at fixed intervals.
164
156
 
165
157
  ```typescript
166
158
  export const GET = withRateLimit(handler, {
@@ -170,83 +162,45 @@ export const GET = withRateLimit(handler, {
170
162
  })
171
163
  ```
172
164
 
173
- **Burst scenario:**
174
- ```
175
- Window 1: |------------------[100]| <- 100 requests at :59
176
- Window 2: |[100]------------------| <- 100 requests at :00
177
- 200 requests in 2 seconds!
178
- ```
179
-
180
165
  #### Token Bucket
181
-
182
- Allows controlled bursts while maintaining average rate. Tokens refill continuously.
166
+ Allows controlled bursts while maintaining average rate.
183
167
 
184
168
  ```typescript
185
169
  export const GET = withRateLimit(handler, {
186
- limit: 100, // Bucket capacity
187
- window: '1m', // Full refill time
170
+ limit: 100,
171
+ window: '1m',
188
172
  algorithm: 'token-bucket',
189
173
  })
190
174
  ```
191
175
 
192
- **Use case:** APIs where occasional bursts are acceptable but average rate must be controlled.
193
-
194
176
  ### Storage Backends
195
177
 
196
178
  #### Memory Store (Default)
197
179
 
198
- Built-in, zero-config. Perfect for development and single-instance deployments.
199
-
200
180
  ```typescript
201
181
  import { withRateLimit, MemoryStore } from 'nextjs-secure'
202
182
 
203
183
  const store = new MemoryStore({
204
- cleanupInterval: 60000, // Cleanup every minute
205
- maxKeys: 10000, // LRU eviction after 10k keys
184
+ cleanupInterval: 60000,
185
+ maxKeys: 10000,
206
186
  })
207
187
 
208
- export const GET = withRateLimit(handler, {
209
- limit: 100,
210
- window: '15m',
211
- store,
212
- })
188
+ export const GET = withRateLimit(handler, { limit: 100, window: '15m', store })
213
189
  ```
214
190
 
215
- **Limitations:**
216
- - Data lost on restart
217
- - Not shared between instances
218
- - Not suitable for serverless (cold starts)
219
-
220
191
  #### Redis Store
221
192
 
222
- For distributed deployments. Works with ioredis, node-redis, or any compatible client.
223
-
224
193
  ```typescript
225
194
  import Redis from 'ioredis'
226
195
  import { withRateLimit, createRedisStore } from 'nextjs-secure/rate-limit'
227
196
 
228
197
  const redis = new Redis(process.env.REDIS_URL)
198
+ const store = createRedisStore({ client: redis, prefix: 'myapp:rl' })
229
199
 
230
- const store = createRedisStore({
231
- client: redis,
232
- prefix: 'myapp:rl', // Key prefix
233
- })
234
-
235
- export const GET = withRateLimit(handler, {
236
- limit: 100,
237
- window: '15m',
238
- store,
239
- })
200
+ export const GET = withRateLimit(handler, { limit: 100, window: '15m', store })
240
201
  ```
241
202
 
242
- **Features:**
243
- - Atomic operations via Lua scripts
244
- - Automatic key expiration
245
- - Cluster-ready
246
-
247
- #### Upstash Store
248
-
249
- Optimized for serverless and edge. Uses HTTP-based Redis.
203
+ #### Upstash Store (Edge/Serverless)
250
204
 
251
205
  ```typescript
252
206
  import { withRateLimit, createUpstashStore } from 'nextjs-secure/rate-limit'
@@ -256,41 +210,20 @@ const store = createUpstashStore({
256
210
  token: process.env.UPSTASH_REDIS_REST_TOKEN,
257
211
  })
258
212
 
259
- export const GET = withRateLimit(handler, {
260
- limit: 100,
261
- window: '15m',
262
- store,
263
- })
264
-
265
- // Or from environment variables
266
- import { createUpstashStoreFromEnv } from 'nextjs-secure/rate-limit'
267
- const store = createUpstashStoreFromEnv()
213
+ export const GET = withRateLimit(handler, { limit: 100, window: '15m', store })
268
214
  ```
269
215
 
270
- **Benefits:**
271
- - No TCP connections
272
- - Works on Edge Runtime
273
- - Global distribution support
274
-
275
216
  ### Custom Identifiers
276
217
 
277
- By default, rate limiting is per-IP. Customize with the `identifier` option:
278
-
279
- #### By API Key
280
-
281
218
  ```typescript
219
+ // By API Key
282
220
  export const GET = withRateLimit(handler, {
283
221
  limit: 1000,
284
222
  window: '1h',
285
- identifier: (req) => {
286
- return req.headers.get('x-api-key') ?? 'anonymous'
287
- },
223
+ identifier: (req) => req.headers.get('x-api-key') ?? 'anonymous',
288
224
  })
289
- ```
290
225
 
291
- #### By User ID
292
-
293
- ```typescript
226
+ // By User ID
294
227
  export const GET = withRateLimit(handler, {
295
228
  limit: 100,
296
229
  window: '15m',
@@ -301,104 +234,20 @@ export const GET = withRateLimit(handler, {
301
234
  })
302
235
  ```
303
236
 
304
- #### By Route + IP
305
-
306
- ```typescript
307
- export const GET = withRateLimit(handler, {
308
- limit: 100,
309
- window: '15m',
310
- identifier: (req) => {
311
- const ip = req.headers.get('x-forwarded-for') ?? '127.0.0.1'
312
- return `${req.nextUrl.pathname}:${ip}`
313
- },
314
- })
315
- ```
316
-
317
- ### Response Customization
318
-
319
- #### Custom Error Response
320
-
321
- ```typescript
322
- export const GET = withRateLimit(handler, {
323
- limit: 100,
324
- window: '15m',
325
- onLimit: (req, info) => {
326
- return Response.json(
327
- {
328
- error: 'rate_limit_exceeded',
329
- message: `Too many requests. Try again in ${info.retryAfter} seconds.`,
330
- limit: info.limit,
331
- reset: new Date(info.reset * 1000).toISOString(),
332
- },
333
- { status: 429 }
334
- )
335
- },
336
- })
337
- ```
338
-
339
- #### Skip Certain Requests
340
-
341
- ```typescript
342
- export const GET = withRateLimit(handler, {
343
- limit: 100,
344
- window: '15m',
345
- skip: (req) => {
346
- // Skip for internal services
347
- const key = req.headers.get('x-internal-key')
348
- return key === process.env.INTERNAL_API_KEY
349
- },
350
- })
351
- ```
352
-
353
- #### Disable Headers
354
-
355
- ```typescript
356
- export const GET = withRateLimit(handler, {
357
- limit: 100,
358
- window: '15m',
359
- headers: false, // Don't add X-RateLimit-* headers
360
- })
361
- ```
362
-
363
237
  ### Response Headers
364
238
 
365
- When `headers: true` (default), responses include:
366
-
367
- | Header | Description | Example |
368
- |--------|-------------|---------|
369
- | `X-RateLimit-Limit` | Maximum requests allowed | `100` |
370
- | `X-RateLimit-Remaining` | Requests remaining | `95` |
371
- | `X-RateLimit-Reset` | Unix timestamp when limit resets | `1699999999` |
372
- | `Retry-After` | Seconds until retry (only on 429) | `60` |
373
-
374
- ### Manual Rate Limit Check
375
-
376
- For existing handlers or complex logic:
377
-
378
- ```typescript
379
- import { checkRateLimit } from 'nextjs-secure'
380
-
381
- export async function GET(request: NextRequest) {
382
- const { success, info, headers } = await checkRateLimit(request, {
383
- limit: 100,
384
- window: '15m',
385
- })
386
-
387
- if (!success) {
388
- return Response.json(
389
- { error: 'Rate limited' },
390
- { status: 429, headers }
391
- )
392
- }
239
+ | Header | Description |
240
+ |--------|-------------|
241
+ | `X-RateLimit-Limit` | Maximum requests allowed |
242
+ | `X-RateLimit-Remaining` | Requests remaining |
243
+ | `X-RateLimit-Reset` | Unix timestamp when limit resets |
244
+ | `Retry-After` | Seconds until retry (only on 429) |
393
245
 
394
- // Your logic here
395
- return Response.json({ data: '...' }, { headers })
396
- }
397
- ```
246
+ ---
398
247
 
399
248
  ## CSRF Protection
400
249
 
401
- Protect your forms against Cross-Site Request Forgery attacks using the double submit cookie pattern.
250
+ Protect forms against Cross-Site Request Forgery attacks.
402
251
 
403
252
  ### Basic Setup
404
253
 
@@ -408,7 +257,6 @@ import { generateCSRF } from 'nextjs-secure/csrf'
408
257
 
409
258
  export async function GET() {
410
259
  const { token, cookieHeader } = await generateCSRF()
411
-
412
260
  return Response.json(
413
261
  { csrfToken: token },
414
262
  { headers: { 'Set-Cookie': cookieHeader } }
@@ -422,7 +270,6 @@ import { withCSRF } from 'nextjs-secure/csrf'
422
270
 
423
271
  export const POST = withCSRF(async (req) => {
424
272
  const data = await req.json()
425
- // Safe to process - CSRF validated
426
273
  return Response.json({ success: true })
427
274
  })
428
275
  ```
@@ -438,111 +285,51 @@ fetch('/api/submit', {
438
285
  method: 'POST',
439
286
  headers: {
440
287
  'Content-Type': 'application/json',
441
- 'x-csrf-token': csrfToken // Token in header
288
+ 'x-csrf-token': csrfToken
442
289
  },
443
290
  body: JSON.stringify({ data: '...' })
444
291
  })
445
292
  ```
446
293
 
447
- Or include in form body:
448
-
449
- ```typescript
450
- fetch('/api/submit', {
451
- method: 'POST',
452
- headers: { 'Content-Type': 'application/json' },
453
- body: JSON.stringify({
454
- _csrf: csrfToken, // Token in body
455
- data: '...'
456
- })
457
- })
458
- ```
459
-
460
294
  ### Configuration
461
295
 
462
296
  ```typescript
463
- import { withCSRF } from 'nextjs-secure/csrf'
464
-
465
297
  export const POST = withCSRF(handler, {
466
- // Cookie settings
467
298
  cookie: {
468
- name: '__csrf', // Cookie name
469
- httpOnly: true, // Not accessible via JS
470
- secure: true, // HTTPS only
471
- sameSite: 'strict', // Strict same-site policy
472
- maxAge: 86400 // 24 hours
299
+ name: '__csrf',
300
+ httpOnly: true,
301
+ secure: true,
302
+ sameSite: 'strict',
303
+ maxAge: 86400
473
304
  },
474
-
475
- // Where to look for token
476
- headerName: 'x-csrf-token', // Header name
477
- fieldName: '_csrf', // Body field name
478
-
479
- // Token settings
480
- secret: process.env.CSRF_SECRET, // Signing secret
481
- tokenLength: 32, // Token size in bytes
482
-
483
- // Protected methods (default: POST, PUT, PATCH, DELETE)
484
- protectedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'],
485
-
486
- // Skip protection conditionally
305
+ headerName: 'x-csrf-token',
306
+ fieldName: '_csrf',
307
+ secret: process.env.CSRF_SECRET,
487
308
  skip: (req) => req.headers.get('x-api-key') === 'trusted',
488
-
489
- // Custom error response
490
- onError: (req, reason) => {
491
- return new Response(`CSRF failed: ${reason}`, { status: 403 })
492
- }
493
309
  })
494
310
  ```
495
311
 
496
- ### Manual Validation
497
-
498
- ```typescript
499
- import { validateCSRF } from 'nextjs-secure/csrf'
500
-
501
- export async function POST(req) {
502
- const result = await validateCSRF(req)
503
-
504
- if (!result.valid) {
505
- console.log('CSRF failed:', result.reason)
506
- // reason: 'missing_cookie' | 'invalid_cookie' | 'missing_token' | 'token_mismatch'
507
- return Response.json({ error: result.reason }, { status: 403 })
508
- }
509
-
510
- // Continue processing
511
- }
512
- ```
513
-
514
- ### Environment Variable
515
-
516
- Set `CSRF_SECRET` in your environment:
517
-
518
- ```env
519
- CSRF_SECRET=your-secret-key-min-32-chars-recommended
520
- ```
312
+ ---
521
313
 
522
314
  ## Security Headers
523
315
 
524
- Add security headers to your responses with pre-configured presets or custom configuration.
316
+ Add security headers to protect against common attacks.
525
317
 
526
318
  ### Quick Start
527
319
 
528
320
  ```typescript
529
321
  import { withSecurityHeaders } from 'nextjs-secure/headers'
530
322
 
531
- // Use strict preset (default)
532
- export const GET = withSecurityHeaders(async (req) => {
533
- return Response.json({ data: 'protected' })
534
- })
323
+ export const GET = withSecurityHeaders(handler)
535
324
  ```
536
325
 
537
326
  ### Presets
538
327
 
539
- Three presets available: `strict`, `relaxed`, `api`
540
-
541
328
  ```typescript
542
329
  // Strict: Maximum security (default)
543
330
  export const GET = withSecurityHeaders(handler, { preset: 'strict' })
544
331
 
545
- // Relaxed: Development-friendly, allows inline scripts
332
+ // Relaxed: Development-friendly
546
333
  export const GET = withSecurityHeaders(handler, { preset: 'relaxed' })
547
334
 
548
335
  // API: Optimized for JSON APIs
@@ -552,36 +339,22 @@ export const GET = withSecurityHeaders(handler, { preset: 'api' })
552
339
  ### Custom Configuration
553
340
 
554
341
  ```typescript
555
- import { withSecurityHeaders } from 'nextjs-secure/headers'
556
-
557
342
  export const GET = withSecurityHeaders(handler, {
558
343
  config: {
559
- // Content-Security-Policy
560
344
  contentSecurityPolicy: {
561
345
  defaultSrc: ["'self'"],
562
346
  scriptSrc: ["'self'", "'unsafe-inline'"],
563
347
  styleSrc: ["'self'", "'unsafe-inline'"],
564
348
  imgSrc: ["'self'", 'data:', 'https:'],
565
349
  },
566
-
567
- // Strict-Transport-Security
568
350
  strictTransportSecurity: {
569
351
  maxAge: 31536000,
570
352
  includeSubDomains: true,
571
353
  preload: true,
572
354
  },
573
-
574
- // Other headers
575
- xFrameOptions: 'DENY', // or 'SAMEORIGIN'
576
- xContentTypeOptions: true, // X-Content-Type-Options: nosniff
355
+ xFrameOptions: 'DENY',
356
+ xContentTypeOptions: true,
577
357
  referrerPolicy: 'strict-origin-when-cross-origin',
578
-
579
- // Cross-Origin headers
580
- crossOriginOpenerPolicy: 'same-origin',
581
- crossOriginEmbedderPolicy: 'require-corp',
582
- crossOriginResourcePolicy: 'same-origin',
583
-
584
- // Permissions-Policy (disable features)
585
358
  permissionsPolicy: {
586
359
  camera: [],
587
360
  microphone: [],
@@ -591,31 +364,6 @@ export const GET = withSecurityHeaders(handler, {
591
364
  })
592
365
  ```
593
366
 
594
- ### Disable Specific Headers
595
-
596
- ```typescript
597
- export const GET = withSecurityHeaders(handler, {
598
- config: {
599
- contentSecurityPolicy: false, // Disable CSP
600
- xFrameOptions: false, // Disable X-Frame-Options
601
- }
602
- })
603
- ```
604
-
605
- ### Manual Header Creation
606
-
607
- ```typescript
608
- import { createSecurityHeaders } from 'nextjs-secure/headers'
609
-
610
- export async function GET() {
611
- const headers = createSecurityHeaders({ preset: 'api' })
612
-
613
- return new Response(JSON.stringify({ ok: true }), {
614
- headers,
615
- })
616
- }
617
- ```
618
-
619
367
  ### Available Headers
620
368
 
621
369
  | Header | Description |
@@ -630,243 +378,640 @@ export async function GET() {
630
378
  | Cross-Origin-Embedder-Policy | Controls embedding |
631
379
  | Cross-Origin-Resource-Policy | Controls resource sharing |
632
380
 
633
- ## Utilities
381
+ ---
634
382
 
635
- ### Duration Parsing
383
+ ## Authentication
384
+
385
+ Flexible authentication supporting JWT, API keys, sessions, and RBAC.
386
+
387
+ ### JWT Authentication
636
388
 
637
389
  ```typescript
638
- import { parseDuration, formatDuration } from 'nextjs-secure'
390
+ import { withJWT } from 'nextjs-secure/auth'
639
391
 
640
- parseDuration('15m') // 900000
641
- parseDuration('1h 30m') // 5400000
642
- parseDuration('2d') // 172800000
392
+ export const GET = withJWT(
393
+ async (req, ctx) => {
394
+ return Response.json({ user: ctx.user })
395
+ },
396
+ {
397
+ secret: process.env.JWT_SECRET,
398
+ algorithms: ['HS256', 'RS256'],
399
+ issuer: 'https://myapp.com',
400
+ audience: 'my-api',
401
+ }
402
+ )
403
+ ```
643
404
 
644
- formatDuration(900000) // '15m'
645
- formatDuration(5400000) // '1h 30m'
646
- formatDuration(90061000) // '1d 1h 1m 1s'
405
+ ### API Key Authentication
406
+
407
+ ```typescript
408
+ import { withAPIKey } from 'nextjs-secure/auth'
409
+
410
+ export const GET = withAPIKey(
411
+ async (req, ctx) => {
412
+ return Response.json({ user: ctx.user })
413
+ },
414
+ {
415
+ validate: async (apiKey) => {
416
+ const user = await db.users.findByApiKey(apiKey)
417
+ return user || null
418
+ },
419
+ headerName: 'x-api-key',
420
+ queryParam: 'api_key',
421
+ }
422
+ )
647
423
  ```
648
424
 
649
- ### IP Utilities
425
+ ### Session Authentication
650
426
 
651
427
  ```typescript
652
- import { getClientIp, anonymizeIp, isPrivateIp } from 'nextjs-secure'
428
+ import { withSession } from 'nextjs-secure/auth'
653
429
 
654
- // Extract client IP from request
655
- const ip = getClientIp(request)
430
+ export const GET = withSession(
431
+ async (req, ctx) => {
432
+ return Response.json({ user: ctx.user })
433
+ },
434
+ {
435
+ validate: async (sessionId) => {
436
+ const session = await db.sessions.find(sessionId)
437
+ return session?.user || null
438
+ },
439
+ cookieName: 'session',
440
+ }
441
+ )
442
+ ```
656
443
 
657
- // Handles: cf-connecting-ip, x-real-ip, x-forwarded-for, etc.
658
- const ip = getClientIp(request, {
659
- trustProxy: true,
660
- customHeaders: ['x-custom-ip'],
661
- fallback: '0.0.0.0',
662
- })
444
+ ### Role-Based Access Control
663
445
 
664
- // Anonymize for logging (GDPR compliant)
665
- anonymizeIp('192.168.1.100') // '192.168.1.xxx'
446
+ ```typescript
447
+ import { withJWT, withRoles } from 'nextjs-secure/auth'
666
448
 
667
- // Check if private
668
- isPrivateIp('192.168.1.1') // true
669
- isPrivateIp('8.8.8.8') // false
449
+ export const GET = withJWT(
450
+ withRoles(
451
+ async (req, ctx) => {
452
+ return Response.json({ admin: true })
453
+ },
454
+ {
455
+ roles: ['admin', 'moderator'],
456
+ permissions: ['users:read', 'users:write'],
457
+ }
458
+ ),
459
+ { secret: process.env.JWT_SECRET }
460
+ )
670
461
  ```
671
462
 
672
- ## API Reference
463
+ ### Combined Authentication
464
+
465
+ ```typescript
466
+ import { withAuth } from 'nextjs-secure/auth'
467
+
468
+ export const GET = withAuth(
469
+ async (req, ctx) => {
470
+ return Response.json({ user: ctx.user })
471
+ },
472
+ {
473
+ jwt: { secret: process.env.JWT_SECRET },
474
+ apiKey: { validate: (key) => db.apiKeys.findUser(key) },
475
+ session: { validate: (id) => db.sessions.findUser(id) },
476
+ rbac: { roles: ['user', 'admin'] },
477
+ }
478
+ )
479
+ ```
480
+
481
+ ### Optional Authentication
482
+
483
+ ```typescript
484
+ import { withOptionalAuth } from 'nextjs-secure/auth'
485
+
486
+ export const GET = withOptionalAuth(
487
+ async (req, ctx) => {
488
+ if (ctx.user) {
489
+ return Response.json({ user: ctx.user })
490
+ }
491
+ return Response.json({ guest: true })
492
+ },
493
+ { jwt: { secret: process.env.JWT_SECRET } }
494
+ )
495
+ ```
673
496
 
674
- ### `withRateLimit(handler, config)`
497
+ ---
675
498
 
676
- Wraps a route handler with rate limiting.
499
+ ## Input Validation
500
+
501
+ Validate and sanitize user input to prevent attacks.
502
+
503
+ ### Schema Validation
677
504
 
678
505
  ```typescript
679
- interface RateLimitConfig {
680
- limit: number
681
- window: string | number
682
- algorithm?: 'sliding-window' | 'fixed-window' | 'token-bucket'
683
- identifier?: 'ip' | 'user' | ((req: NextRequest) => string | Promise<string>)
684
- store?: RateLimitStore
685
- headers?: boolean
686
- skip?: (req: NextRequest) => boolean | Promise<boolean>
687
- onLimit?: (req: NextRequest, info: RateLimitInfo) => Response | Promise<Response>
688
- prefix?: string
689
- message?: string
690
- statusCode?: number
506
+ import { withValidation } from 'nextjs-secure/validation'
507
+
508
+ // Built-in schema
509
+ const schema = {
510
+ email: { type: 'email', required: true },
511
+ password: { type: 'string', minLength: 8, maxLength: 100 },
512
+ age: { type: 'number', min: 18, max: 120 },
513
+ role: { type: 'string', enum: ['user', 'admin'] },
691
514
  }
515
+
516
+ export const POST = withValidation(handler, { body: schema })
517
+
518
+ // Or use Zod
519
+ import { z } from 'zod'
520
+
521
+ const zodSchema = z.object({
522
+ email: z.string().email(),
523
+ password: z.string().min(8),
524
+ })
525
+
526
+ export const POST = withValidation(handler, { body: zodSchema })
692
527
  ```
693
528
 
694
- ### `createRateLimiter(config)`
529
+ ### XSS Protection
530
+
531
+ ```typescript
532
+ import { withXSSProtection, withSanitization, sanitize, detectXSS } from 'nextjs-secure/validation'
533
+
534
+ // Block XSS attempts
535
+ export const POST = withXSSProtection(handler)
695
536
 
696
- Creates a reusable rate limiter function.
537
+ // Sanitize specific fields
538
+ export const POST = withSanitization(handler, {
539
+ fields: ['content', 'bio'],
540
+ mode: 'escape', // 'escape' | 'strip' | 'allow-safe'
541
+ })
542
+
543
+ // Manual sanitization
544
+ const clean = sanitize(userInput, {
545
+ mode: 'allow-safe',
546
+ allowedTags: ['b', 'i', 'em', 'strong'],
547
+ })
697
548
 
698
- ### `checkRateLimit(request, config)`
549
+ // Detection only
550
+ const { hasXSS, matches } = detectXSS(input)
551
+ ```
699
552
 
700
- Manually check rate limit without wrapping.
553
+ ### SQL Injection Protection
701
554
 
702
- Returns:
703
555
  ```typescript
704
- {
705
- success: boolean
706
- info: RateLimitInfo
707
- headers: Headers
708
- response?: Response // Only if rate limited
556
+ import { withSQLProtection, detectSQLInjection, hasSQLInjection } from 'nextjs-secure/validation'
557
+
558
+ // Block SQL injection
559
+ export const POST = withSQLProtection(handler, {
560
+ mode: 'block', // 'block' | 'detect'
561
+ minSeverity: 'medium', // 'low' | 'medium' | 'high'
562
+ })
563
+
564
+ // Manual detection
565
+ const result = detectSQLInjection(input)
566
+ // { hasSQLi: true, severity: 'high', patterns: ['UNION SELECT'] }
567
+
568
+ // Simple check
569
+ if (hasSQLInjection(input)) {
570
+ // Block request
709
571
  }
710
572
  ```
711
573
 
712
- ### `RateLimitInfo`
574
+ ### Path Traversal Prevention
713
575
 
714
576
  ```typescript
715
- interface RateLimitInfo {
716
- limit: number // Max requests
717
- remaining: number // Requests left
718
- reset: number // Unix timestamp
719
- limited: boolean // Whether rate limited
720
- retryAfter?: number // Seconds until retry
577
+ import { validatePath, sanitizePath, sanitizeFilename } from 'nextjs-secure/validation'
578
+
579
+ // Validate path
580
+ const result = validatePath(userPath, {
581
+ basePath: '/uploads',
582
+ allowedExtensions: ['.jpg', '.png'],
583
+ maxDepth: 3,
584
+ })
585
+
586
+ if (!result.valid) {
587
+ console.log(result.reason) // 'traversal_detected', 'invalid_extension', etc.
721
588
  }
589
+
590
+ // Sanitize
591
+ const safePath = sanitizePath('../../../etc/passwd') // 'etc/passwd'
592
+ const safeFilename = sanitizeFilename('../../evil.exe') // 'evil.exe'
722
593
  ```
723
594
 
724
- ## Examples
595
+ ### File Validation
596
+
597
+ ```typescript
598
+ import { withFileValidation, validateFile } from 'nextjs-secure/validation'
725
599
 
726
- ### Different Limits per HTTP Method
600
+ export const POST = withFileValidation(handler, {
601
+ maxSize: 5 * 1024 * 1024, // 5MB
602
+ allowedTypes: ['image/jpeg', 'image/png', 'application/pdf'],
603
+ validateMagicNumbers: true, // Check actual file content
604
+ maxFiles: 10,
605
+ })
606
+
607
+ // Manual validation
608
+ const result = await validateFile(file, {
609
+ maxSize: 5 * 1024 * 1024,
610
+ allowedTypes: ['image/jpeg'],
611
+ })
612
+ ```
613
+
614
+ ### Combined Security Validation
727
615
 
728
616
  ```typescript
729
- // app/api/posts/route.ts
730
- import { withRateLimit } from 'nextjs-secure'
617
+ import { withSecureValidation } from 'nextjs-secure/validation'
731
618
 
732
- // Generous limit for reads
733
- export const GET = withRateLimit(getHandler, {
734
- limit: 1000,
735
- window: '15m',
619
+ export const POST = withSecureValidation(handler, {
620
+ xss: true,
621
+ sql: { minSeverity: 'medium' },
622
+ contentType: ['application/json'],
623
+ })
624
+ ```
625
+
626
+ ---
627
+
628
+ ## Audit Logging
629
+
630
+ Track requests and security events for monitoring and compliance.
631
+
632
+ ### Request Logging
633
+
634
+ ```typescript
635
+ import { withAuditLog, MemoryStore, ConsoleStore } from 'nextjs-secure/audit'
636
+
637
+ const store = new MemoryStore({ maxEntries: 1000 })
638
+
639
+ export const POST = withAuditLog(handler, {
640
+ store,
641
+ include: {
642
+ ip: true,
643
+ userAgent: true,
644
+ headers: false,
645
+ query: true,
646
+ response: true,
647
+ duration: true,
648
+ },
649
+ exclude: {
650
+ paths: ['/health', '/metrics'],
651
+ methods: ['OPTIONS'],
652
+ statusCodes: [304],
653
+ },
654
+ pii: {
655
+ fields: ['password', 'token', 'ssn', 'creditCard'],
656
+ mode: 'mask', // 'mask' | 'hash' | 'remove'
657
+ },
658
+ })
659
+ ```
660
+
661
+ ### Storage Backends
662
+
663
+ ```typescript
664
+ import { MemoryStore, ConsoleStore, createDatadogStore, MultiStore } from 'nextjs-secure/audit'
665
+
666
+ // Memory (development)
667
+ const memoryStore = new MemoryStore({ maxEntries: 1000, ttl: 3600000 })
668
+
669
+ // Console (development)
670
+ const consoleStore = new ConsoleStore({ colorize: true, level: 'info' })
671
+
672
+ // Datadog (production)
673
+ const datadogStore = createDatadogStore({
674
+ apiKey: process.env.DATADOG_API_KEY,
675
+ service: 'my-api',
676
+ environment: 'production',
677
+ })
678
+
679
+ // Multiple stores
680
+ const multiStore = new MultiStore([consoleStore, datadogStore])
681
+ ```
682
+
683
+ ### Security Event Tracking
684
+
685
+ ```typescript
686
+ import { createSecurityTracker, trackSecurityEvent } from 'nextjs-secure/audit'
687
+
688
+ const tracker = createSecurityTracker({ store })
689
+
690
+ // Authentication failures
691
+ await tracker.authFailed({
692
+ ip: '192.168.1.1',
693
+ email: 'user@example.com',
694
+ reason: 'Invalid password',
736
695
  })
737
696
 
738
- // Strict limit for writes
739
- export const POST = withRateLimit(postHandler, {
697
+ // Rate limit exceeded
698
+ await tracker.rateLimitExceeded({
699
+ ip: '192.168.1.1',
700
+ endpoint: '/api/login',
740
701
  limit: 10,
741
- window: '1m',
702
+ window: '15m',
742
703
  })
743
704
 
744
- // Very strict for deletes
745
- export const DELETE = withRateLimit(deleteHandler, {
746
- limit: 5,
747
- window: '1h',
705
+ // XSS detected
706
+ await tracker.xssDetected({
707
+ ip: '192.168.1.1',
708
+ field: 'comment',
709
+ endpoint: '/api/comments',
710
+ })
711
+
712
+ // SQL injection detected
713
+ await tracker.sqliDetected({
714
+ ip: '192.168.1.1',
715
+ field: 'username',
716
+ pattern: 'UNION SELECT',
717
+ severity: 'high',
718
+ endpoint: '/api/users',
719
+ })
720
+
721
+ // CSRF validation failure
722
+ await tracker.csrfInvalid({
723
+ ip: '192.168.1.1',
724
+ endpoint: '/api/transfer',
725
+ reason: 'Token mismatch',
726
+ })
727
+
728
+ // IP blocked
729
+ await tracker.ipBlocked({
730
+ ip: '192.168.1.1',
731
+ reason: 'Too many failed attempts',
732
+ duration: 3600,
733
+ })
734
+
735
+ // Custom events
736
+ await tracker.custom({
737
+ message: 'Suspicious activity detected',
738
+ severity: 'high',
739
+ details: { pattern: 'automated_scanning' },
748
740
  })
749
741
  ```
750
742
 
751
- ### Tiered Rate Limiting
743
+ ### PII Redaction
752
744
 
753
745
  ```typescript
754
- // lib/rate-limit.ts
755
- import { createRateLimiter, createUpstashStore } from 'nextjs-secure/rate-limit'
746
+ import { redactObject, redactEmail, redactCreditCard, redactIP, DEFAULT_PII_FIELDS } from 'nextjs-secure/audit'
756
747
 
757
- const store = createUpstashStore({
758
- url: process.env.UPSTASH_REDIS_REST_URL!,
759
- token: process.env.UPSTASH_REDIS_REST_TOKEN!,
748
+ // Redact object
749
+ const safeData = redactObject(userData, {
750
+ fields: DEFAULT_PII_FIELDS,
751
+ mode: 'mask',
760
752
  })
761
753
 
762
- // Free tier: 100 req/day
763
- export const freeLimiter = createRateLimiter({
764
- limit: 100,
765
- window: '1d',
766
- store,
767
- identifier: async (req) => {
768
- const apiKey = req.headers.get('x-api-key')
769
- return `free:${apiKey}`
770
- },
754
+ // Specific redactors
755
+ redactEmail('john@example.com') // '****@example.com'
756
+ redactCreditCard('4111111111111111') // '**** **** **** 1111'
757
+ redactIP('192.168.1.100') // '192.168.*.*'
758
+ ```
759
+
760
+ ### Request ID & Timing
761
+
762
+ ```typescript
763
+ import { withRequestId, withTiming } from 'nextjs-secure/audit'
764
+
765
+ // Add request ID to responses
766
+ export const GET = withRequestId(handler, {
767
+ headerName: 'x-request-id',
768
+ generateId: () => `req_${Date.now()}`,
771
769
  })
772
770
 
773
- // Pro tier: 10000 req/day
774
- export const proLimiter = createRateLimiter({
775
- limit: 10000,
776
- window: '1d',
777
- store,
778
- identifier: async (req) => {
779
- const apiKey = req.headers.get('x-api-key')
780
- return `pro:${apiKey}`
781
- },
771
+ // Add response timing
772
+ export const GET = withTiming(handler, {
773
+ headerName: 'x-response-time',
774
+ log: true,
782
775
  })
783
776
  ```
784
777
 
785
- ### With Authentication
778
+ ### Log Formatters
786
779
 
787
780
  ```typescript
788
- import { withRateLimit } from 'nextjs-secure'
789
- import { getServerSession } from 'next-auth'
781
+ import { JSONFormatter, TextFormatter, CLFFormatter, StructuredFormatter } from 'nextjs-secure/audit'
790
782
 
791
- export const GET = withRateLimit(
792
- async (req, ctx) => {
793
- const session = await getServerSession()
783
+ // JSON (default)
784
+ const jsonFormatter = new JSONFormatter({ pretty: true })
794
785
 
795
- if (!session) {
796
- return Response.json({ error: 'Unauthorized' }, { status: 401 })
797
- }
786
+ // Human-readable text
787
+ const textFormatter = new TextFormatter({
788
+ template: '{timestamp} [{level}] {message}',
789
+ })
798
790
 
799
- return Response.json({ user: session.user })
800
- },
801
- {
802
- limit: 100,
803
- window: '15m',
804
- identifier: async (req) => {
805
- const session = await getServerSession()
806
- return session?.user?.id ?? 'anonymous'
807
- },
808
- }
809
- )
791
+ // Apache/Nginx Common Log Format
792
+ const clfFormatter = new CLFFormatter()
793
+
794
+ // Key=value (ELK/Splunk)
795
+ const structuredFormatter = new StructuredFormatter({
796
+ delimiter: ' ',
797
+ kvSeparator: '=',
798
+ })
810
799
  ```
811
800
 
812
- ### Webhook Endpoint
801
+ ---
802
+
803
+ ## Utilities
804
+
805
+ ### Duration Parsing
813
806
 
814
807
  ```typescript
815
- import { withRateLimit } from 'nextjs-secure'
816
- import { headers } from 'next/headers'
817
- import crypto from 'crypto'
808
+ import { parseDuration, formatDuration } from 'nextjs-secure'
818
809
 
819
- export const POST = withRateLimit(
820
- async (req) => {
821
- const body = await req.text()
822
- const signature = headers().get('x-webhook-signature')
823
-
824
- // Verify signature
825
- const expected = crypto
826
- .createHmac('sha256', process.env.WEBHOOK_SECRET!)
827
- .update(body)
828
- .digest('hex')
829
-
830
- if (signature !== expected) {
831
- return Response.json({ error: 'Invalid signature' }, { status: 401 })
832
- }
810
+ parseDuration('15m') // 900000
811
+ parseDuration('1h 30m') // 5400000
812
+ parseDuration('2d') // 172800000
813
+
814
+ formatDuration(900000) // '15m'
815
+ formatDuration(5400000) // '1h 30m'
816
+ ```
833
817
 
834
- // Process webhook
835
- const data = JSON.parse(body)
836
- await processWebhook(data)
818
+ ### IP Utilities
837
819
 
838
- return Response.json({ received: true })
839
- },
820
+ ```typescript
821
+ import { getClientIp, anonymizeIp, isPrivateIp, isLocalhost } from 'nextjs-secure'
822
+
823
+ // Extract client IP
824
+ const ip = getClientIp(request, {
825
+ trustProxy: true,
826
+ customHeaders: ['x-custom-ip'],
827
+ })
828
+
829
+ // Anonymize for GDPR
830
+ anonymizeIp('192.168.1.100') // '192.168.1.xxx'
831
+
832
+ // Check IP type
833
+ isPrivateIp('192.168.1.1') // true
834
+ isPrivateIp('8.8.8.8') // false
835
+ isLocalhost('127.0.0.1') // true
836
+ ```
837
+
838
+ ---
839
+
840
+ ## API Reference
841
+
842
+ ### Rate Limiting
843
+
844
+ | Function | Description |
845
+ |----------|-------------|
846
+ | `withRateLimit(handler, config)` | Wrap handler with rate limiting |
847
+ | `createRateLimiter(config)` | Create reusable rate limiter |
848
+ | `checkRateLimit(request, config)` | Manual rate limit check |
849
+ | `getRateLimitStatus(key, config)` | Get current status without incrementing |
850
+ | `resetRateLimit(key, config)` | Reset rate limit for key |
851
+
852
+ ### CSRF
853
+
854
+ | Function | Description |
855
+ |----------|-------------|
856
+ | `withCSRF(handler, config)` | Wrap handler with CSRF protection |
857
+ | `generateCSRF(config)` | Generate CSRF token and cookie |
858
+ | `validateCSRF(request, config)` | Manual CSRF validation |
859
+
860
+ ### Security Headers
861
+
862
+ | Function | Description |
863
+ |----------|-------------|
864
+ | `withSecurityHeaders(handler, config)` | Add security headers |
865
+ | `createSecurityHeaders(config)` | Create headers object |
866
+ | `buildCSP(config)` | Build CSP header string |
867
+ | `getPreset(name)` | Get preset configuration |
868
+
869
+ ### Authentication
870
+
871
+ | Function | Description |
872
+ |----------|-------------|
873
+ | `withJWT(handler, config)` | JWT authentication |
874
+ | `withAPIKey(handler, config)` | API key authentication |
875
+ | `withSession(handler, config)` | Session authentication |
876
+ | `withAuth(handler, config)` | Combined authentication |
877
+ | `withRoles(handler, config)` | Role-based access control |
878
+ | `withOptionalAuth(handler, config)` | Optional authentication |
879
+ | `verifyJWT(token, config)` | Verify JWT token |
880
+ | `decodeJWT(token)` | Decode JWT without verification |
881
+
882
+ ### Validation
883
+
884
+ | Function | Description |
885
+ |----------|-------------|
886
+ | `withValidation(handler, config)` | Schema validation |
887
+ | `withXSSProtection(handler)` | Block XSS attempts |
888
+ | `withSanitization(handler, config)` | Sanitize input |
889
+ | `withSQLProtection(handler, config)` | Block SQL injection |
890
+ | `withFileValidation(handler, config)` | File upload validation |
891
+ | `sanitize(input, config)` | Manual sanitization |
892
+ | `detectXSS(input)` | Detect XSS patterns |
893
+ | `detectSQLInjection(input)` | Detect SQL injection |
894
+ | `validatePath(path, config)` | Validate file path |
895
+
896
+ ### Audit Logging
897
+
898
+ | Function | Description |
899
+ |----------|-------------|
900
+ | `withAuditLog(handler, config)` | Request logging |
901
+ | `withRequestId(handler, config)` | Add request ID |
902
+ | `withTiming(handler, config)` | Add response timing |
903
+ | `createSecurityTracker(config)` | Create event tracker |
904
+ | `trackSecurityEvent(store, event)` | Track single event |
905
+ | `redactObject(obj, config)` | Redact PII from object |
906
+
907
+ ---
908
+
909
+ ## Examples
910
+
911
+ ### Complete API with All Security Features
912
+
913
+ ```typescript
914
+ // lib/security.ts
915
+ import { createRateLimiter, MemoryStore } from 'nextjs-secure/rate-limit'
916
+ import { createSecurityTracker, MemoryStore as AuditStore } from 'nextjs-secure/audit'
917
+
918
+ export const apiLimiter = createRateLimiter({
919
+ limit: 100,
920
+ window: '15m',
921
+ store: new MemoryStore(),
922
+ })
923
+
924
+ export const strictLimiter = createRateLimiter({
925
+ limit: 5,
926
+ window: '1m',
927
+ })
928
+
929
+ export const auditStore = new AuditStore({ maxEntries: 10000 })
930
+ export const securityTracker = createSecurityTracker({ store: auditStore })
931
+ ```
932
+
933
+ ```typescript
934
+ // app/api/users/route.ts
935
+ import { withJWT, withRoles } from 'nextjs-secure/auth'
936
+ import { withValidation } from 'nextjs-secure/validation'
937
+ import { withAuditLog } from 'nextjs-secure/audit'
938
+ import { apiLimiter, auditStore, securityTracker } from '@/lib/security'
939
+
940
+ const createUserSchema = {
941
+ email: { type: 'email', required: true },
942
+ name: { type: 'string', minLength: 2, maxLength: 100 },
943
+ role: { type: 'string', enum: ['user', 'admin'] },
944
+ }
945
+
946
+ async function createUser(req, ctx) {
947
+ const { email, name, role } = ctx.validated
948
+ const user = await db.users.create({ email, name, role })
949
+ return Response.json(user, { status: 201 })
950
+ }
951
+
952
+ export const POST = withAuditLog(
953
+ apiLimiter(
954
+ withJWT(
955
+ withRoles(
956
+ withValidation(createUser, { body: createUserSchema }),
957
+ { roles: ['admin'] }
958
+ ),
959
+ { secret: process.env.JWT_SECRET }
960
+ )
961
+ ),
840
962
  {
841
- limit: 1000,
842
- window: '1m',
843
- identifier: (req) => {
844
- // Rate limit by webhook source
845
- return req.headers.get('x-webhook-source') ?? 'unknown'
846
- },
963
+ store: auditStore,
964
+ include: { ip: true, userAgent: true },
965
+ pii: { fields: ['password'], mode: 'remove' },
847
966
  }
848
967
  )
849
968
  ```
850
969
 
970
+ ### Tiered Rate Limiting
971
+
972
+ ```typescript
973
+ import { createRateLimiter, createUpstashStore } from 'nextjs-secure/rate-limit'
974
+
975
+ const store = createUpstashStore({
976
+ url: process.env.UPSTASH_REDIS_REST_URL,
977
+ token: process.env.UPSTASH_REDIS_REST_TOKEN,
978
+ })
979
+
980
+ const freeLimiter = createRateLimiter({
981
+ limit: 100,
982
+ window: '1d',
983
+ store,
984
+ identifier: (req) => `free:${req.headers.get('x-api-key')}`,
985
+ })
986
+
987
+ const proLimiter = createRateLimiter({
988
+ limit: 10000,
989
+ window: '1d',
990
+ store,
991
+ identifier: (req) => `pro:${req.headers.get('x-api-key')}`,
992
+ })
993
+
994
+ export async function GET(req) {
995
+ const tier = await getUserTier(req)
996
+ const limiter = tier === 'pro' ? proLimiter : freeLimiter
997
+ return limiter(handler)(req)
998
+ }
999
+ ```
1000
+
1001
+ ---
1002
+
851
1003
  ## Roadmap
852
1004
 
853
- - [x] Rate Limiting (v0.1.0)
854
- - [x] Sliding window algorithm
855
- - [x] Fixed window algorithm
856
- - [x] Token bucket algorithm
857
- - [x] Memory store
858
- - [x] Redis store
859
- - [x] Upstash store
860
- - [ ] Authentication (v0.2.0)
861
- - [ ] JWT validation
862
- - [ ] Supabase provider
863
- - [ ] NextAuth provider
864
- - [ ] Clerk provider
865
- - [ ] RBAC support
866
- - [ ] CSRF Protection (v0.3.0)
867
- - [ ] Security Headers (v0.4.0)
868
- - [ ] Input Validation (v0.5.0)
869
- - [ ] Audit Logging (v0.6.0)
1005
+ - [x] **v0.1.x** - Rate Limiting
1006
+ - [x] **v0.2.0** - CSRF Protection
1007
+ - [x] **v0.3.0** - Security Headers
1008
+ - [x] **v0.4.0** - Authentication
1009
+ - [x] **v0.5.0** - Input Validation
1010
+ - [x] **v0.6.0** - Audit Logging
1011
+
1012
+ See [ROADMAP.md](ROADMAP.md) for detailed progress and future plans.
1013
+
1014
+ ---
870
1015
 
871
1016
  ## Contributing
872
1017
 
@@ -887,10 +1032,20 @@ npm test
887
1032
  npm run build
888
1033
  ```
889
1034
 
1035
+ ### Running Tests
1036
+
1037
+ ```bash
1038
+ npm run test # Watch mode
1039
+ npm run test:run # Single run
1040
+ npm run test:coverage # With coverage
1041
+ ```
1042
+
1043
+ ---
1044
+
890
1045
  ## License
891
1046
 
892
1047
  MIT License - see [LICENSE](LICENSE) for details.
893
1048
 
894
1049
  ---
895
1050
 
896
- **Star this repo** if you find it useful!
1051
+ **Made with security in mind for the Next.js community.**