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.
@@ -0,0 +1,534 @@
1
+ import { NextRequest } from 'next/server';
2
+
3
+ /**
4
+ * Validation error details
5
+ */
6
+ interface ValidationError {
7
+ field: string;
8
+ message: string;
9
+ code: string;
10
+ path?: string[];
11
+ received?: unknown;
12
+ expected?: string;
13
+ }
14
+ /**
15
+ * Validation result
16
+ */
17
+ interface ValidationResult<T = unknown> {
18
+ success: boolean;
19
+ data?: T;
20
+ errors?: ValidationError[];
21
+ }
22
+ /**
23
+ * Generic schema interface (Zod-compatible)
24
+ */
25
+ interface Schema<T = unknown> {
26
+ parse: (data: unknown) => T;
27
+ safeParse: (data: unknown) => {
28
+ success: true;
29
+ data: T;
30
+ } | {
31
+ success: false;
32
+ error: {
33
+ issues: SchemaIssue[];
34
+ };
35
+ };
36
+ }
37
+ interface SchemaIssue {
38
+ path: (string | number)[];
39
+ message: string;
40
+ code: string;
41
+ }
42
+ /**
43
+ * Built-in field types for schema-less validation
44
+ */
45
+ type FieldType = 'string' | 'number' | 'boolean' | 'email' | 'url' | 'uuid' | 'date' | 'array' | 'object';
46
+ /**
47
+ * Field validation rules (without Zod)
48
+ */
49
+ interface FieldRule {
50
+ type: FieldType;
51
+ required?: boolean;
52
+ minLength?: number;
53
+ maxLength?: number;
54
+ pattern?: RegExp;
55
+ min?: number;
56
+ max?: number;
57
+ integer?: boolean;
58
+ minItems?: number;
59
+ maxItems?: number;
60
+ items?: FieldRule;
61
+ custom?: (value: unknown) => boolean | string;
62
+ message?: string;
63
+ }
64
+ /**
65
+ * Custom schema definition (without Zod)
66
+ */
67
+ type CustomSchema = Record<string, FieldRule>;
68
+ /**
69
+ * Validation config for middleware
70
+ */
71
+ interface ValidationConfig<TBody = unknown, TQuery = unknown, TParams = unknown> {
72
+ body?: Schema<TBody> | CustomSchema;
73
+ query?: Schema<TQuery> | CustomSchema;
74
+ params?: Schema<TParams> | CustomSchema;
75
+ strict?: boolean;
76
+ stripUnknown?: boolean;
77
+ onError?: (req: NextRequest, errors: ValidationError[]) => Response | Promise<Response>;
78
+ }
79
+ /**
80
+ * Validated request context
81
+ */
82
+ interface ValidatedContext<TBody = unknown, TQuery = unknown, TParams = unknown> {
83
+ body: TBody;
84
+ query: TQuery;
85
+ params: TParams;
86
+ }
87
+ /**
88
+ * XSS sanitization modes
89
+ */
90
+ type SanitizeMode = 'escape' | 'strip' | 'allow-safe';
91
+ /**
92
+ * XSS sanitization config
93
+ */
94
+ interface SanitizeConfig {
95
+ mode?: SanitizeMode;
96
+ allowedTags?: string[];
97
+ allowedAttributes?: Record<string, string[]>;
98
+ allowedProtocols?: string[];
99
+ maxLength?: number;
100
+ stripNull?: boolean;
101
+ }
102
+ /**
103
+ * Sanitization middleware config
104
+ */
105
+ interface SanitizationMiddlewareConfig {
106
+ fields?: string[];
107
+ deep?: boolean;
108
+ mode?: SanitizeMode;
109
+ allowedTags?: string[];
110
+ skip?: (req: NextRequest) => boolean | Promise<boolean>;
111
+ onSanitized?: (req: NextRequest, changes: SanitizationChange[]) => void;
112
+ }
113
+ interface SanitizationChange {
114
+ field: string;
115
+ original: string;
116
+ sanitized: string;
117
+ }
118
+ /**
119
+ * SQL injection detection config
120
+ */
121
+ interface SQLProtectionConfig {
122
+ fields?: string[];
123
+ deep?: boolean;
124
+ mode?: 'detect' | 'block' | 'sanitize';
125
+ customPatterns?: RegExp[];
126
+ allowList?: string[];
127
+ onDetection?: (req: NextRequest, detections: SQLDetection[]) => Response | void | Promise<Response | void>;
128
+ }
129
+ interface SQLDetection {
130
+ field: string;
131
+ value: string;
132
+ pattern: string;
133
+ severity: 'low' | 'medium' | 'high';
134
+ }
135
+ /**
136
+ * Path traversal prevention config
137
+ */
138
+ interface PathValidationConfig {
139
+ allowAbsolute?: boolean;
140
+ allowedPrefixes?: string[];
141
+ allowedExtensions?: string[];
142
+ blockedExtensions?: string[];
143
+ maxDepth?: number;
144
+ maxLength?: number;
145
+ normalize?: boolean;
146
+ }
147
+ interface PathValidationResult {
148
+ valid: boolean;
149
+ sanitized?: string;
150
+ reason?: string;
151
+ }
152
+ /**
153
+ * Content-Type validation config
154
+ */
155
+ interface ContentTypeConfig {
156
+ allowed: string[];
157
+ strict?: boolean;
158
+ charset?: string;
159
+ onInvalid?: (req: NextRequest, contentType: string | null) => Response | Promise<Response>;
160
+ }
161
+ /**
162
+ * File validation config
163
+ */
164
+ interface FileValidationConfig {
165
+ maxSize?: number;
166
+ minSize?: number;
167
+ allowedTypes?: string[];
168
+ blockedTypes?: string[];
169
+ allowedExtensions?: string[];
170
+ blockedExtensions?: string[];
171
+ maxFiles?: number;
172
+ validateMagicNumbers?: boolean;
173
+ sanitizeFilename?: boolean;
174
+ onInvalid?: (req: NextRequest, errors: FileValidationError[]) => Response | Promise<Response>;
175
+ }
176
+ interface FileValidationError {
177
+ filename: string;
178
+ field?: string;
179
+ code: 'size_exceeded' | 'size_too_small' | 'type_not_allowed' | 'extension_not_allowed' | 'invalid_content' | 'too_many_files';
180
+ message: string;
181
+ details?: Record<string, unknown>;
182
+ }
183
+ interface FileInfo {
184
+ filename: string;
185
+ size: number;
186
+ type: string;
187
+ extension: string;
188
+ field?: string;
189
+ }
190
+ /**
191
+ * Magic number signatures for file type validation
192
+ */
193
+ interface MagicNumber {
194
+ type: string;
195
+ extension: string;
196
+ signature: number[];
197
+ offset?: number;
198
+ }
199
+
200
+ type RouteHandler = (req: NextRequest) => Response | Promise<Response>;
201
+ /**
202
+ * Validation middleware
203
+ * Validates request body, query, and params against schemas
204
+ */
205
+ declare function withValidation<TBody = unknown, TQuery = unknown, TParams = unknown>(handler: (req: NextRequest, ctx: {
206
+ validated: ValidatedContext<TBody, TQuery, TParams>;
207
+ }) => Response | Promise<Response>, config: ValidationConfig<TBody, TQuery, TParams> & {
208
+ routeParams?: Record<string, string | string[]>;
209
+ }): RouteHandler;
210
+ /**
211
+ * XSS Sanitization middleware
212
+ * Sanitizes string values in request body
213
+ */
214
+ declare function withSanitization(handler: (req: NextRequest, ctx: {
215
+ sanitized: unknown;
216
+ changes: SanitizationChange[];
217
+ }) => Response | Promise<Response>, config?: SanitizationMiddlewareConfig): RouteHandler;
218
+ /**
219
+ * XSS Detection middleware
220
+ * Blocks requests with potential XSS payloads in body and query parameters
221
+ */
222
+ declare function withXSSProtection(handler: RouteHandler, config?: {
223
+ fields?: string[];
224
+ deep?: boolean;
225
+ checkQuery?: boolean;
226
+ onDetection?: (req: NextRequest, field: string, value: string) => Response | void | Promise<Response | void>;
227
+ }): RouteHandler;
228
+ /**
229
+ * SQL Injection Protection middleware
230
+ */
231
+ declare function withSQLProtection(handler: RouteHandler, config?: SQLProtectionConfig): RouteHandler;
232
+ /**
233
+ * Content-Type validation middleware
234
+ */
235
+ declare function withContentType(handler: RouteHandler, config: ContentTypeConfig): RouteHandler;
236
+ /**
237
+ * File upload validation middleware
238
+ */
239
+ declare function withFileValidation(handler: (req: NextRequest, ctx: {
240
+ files: Map<string, FileInfo[]>;
241
+ }) => Response | Promise<Response>, config?: FileValidationConfig): RouteHandler;
242
+ /**
243
+ * Combined validation middleware
244
+ * Combines schema validation, sanitization, and protection
245
+ */
246
+ declare function withSecureValidation<TBody = unknown, TQuery = unknown, TParams = unknown>(handler: (req: NextRequest, ctx: {
247
+ validated: ValidatedContext<TBody, TQuery, TParams>;
248
+ files?: Map<string, FileInfo[]>;
249
+ }) => Response | Promise<Response>, config: {
250
+ schema?: ValidationConfig<TBody, TQuery, TParams>;
251
+ routeParams?: Record<string, string | string[]>;
252
+ contentType?: ContentTypeConfig;
253
+ files?: FileValidationConfig;
254
+ sanitize?: SanitizationMiddlewareConfig;
255
+ xss?: {
256
+ enabled: boolean;
257
+ fields?: string[];
258
+ };
259
+ sql?: SQLProtectionConfig;
260
+ onError?: (req: NextRequest, errors: ValidationError[]) => Response | Promise<Response>;
261
+ }): RouteHandler;
262
+
263
+ /**
264
+ * Validate data against a schema (Zod or custom)
265
+ */
266
+ declare function validate<T>(data: unknown, schema: Schema<T> | CustomSchema): ValidationResult<T>;
267
+ /**
268
+ * Extract and validate request body
269
+ */
270
+ declare function validateBody<T>(request: NextRequest, schema: Schema<T> | CustomSchema): Promise<ValidationResult<T>>;
271
+ /**
272
+ * Extract and validate query parameters
273
+ */
274
+ declare function validateQuery<T>(request: NextRequest, schema: Schema<T> | CustomSchema): ValidationResult<T>;
275
+ /**
276
+ * Validate path parameters (from URL pattern)
277
+ */
278
+ declare function validateParams<T>(params: Record<string, string | string[]>, schema: Schema<T> | CustomSchema): ValidationResult<T>;
279
+ /**
280
+ * Combined request validation
281
+ */
282
+ declare function validateRequest<TBody = unknown, TQuery = unknown, TParams = unknown>(request: NextRequest, config: {
283
+ body?: Schema<TBody> | CustomSchema;
284
+ query?: Schema<TQuery> | CustomSchema;
285
+ params?: Schema<TParams> | CustomSchema;
286
+ routeParams?: Record<string, string | string[]>;
287
+ }): Promise<{
288
+ success: boolean;
289
+ data?: ValidatedContext<TBody, TQuery, TParams>;
290
+ errors?: ValidationError[];
291
+ }>;
292
+ /**
293
+ * Default validation error response
294
+ */
295
+ declare function defaultValidationErrorResponse(errors: ValidationError[]): Response;
296
+ /**
297
+ * Create a validation function for a schema
298
+ */
299
+ declare function createValidator<T>(schema: Schema<T> | CustomSchema): (data: unknown) => ValidationResult<T>;
300
+ /**
301
+ * Check if all validation results are successful
302
+ */
303
+ declare function allValid(...results: ValidationResult[]): boolean;
304
+ /**
305
+ * Merge validation errors from multiple results
306
+ */
307
+ declare function mergeErrors(...results: ValidationResult[]): ValidationError[];
308
+
309
+ /**
310
+ * Common MIME types
311
+ */
312
+ declare const MIME_TYPES: {
313
+ readonly TEXT_PLAIN: "text/plain";
314
+ readonly TEXT_HTML: "text/html";
315
+ readonly TEXT_CSS: "text/css";
316
+ readonly TEXT_JAVASCRIPT: "text/javascript";
317
+ readonly JSON: "application/json";
318
+ readonly FORM_URLENCODED: "application/x-www-form-urlencoded";
319
+ readonly MULTIPART_FORM: "multipart/form-data";
320
+ readonly XML: "application/xml";
321
+ readonly PDF: "application/pdf";
322
+ readonly ZIP: "application/zip";
323
+ readonly GZIP: "application/gzip";
324
+ readonly OCTET_STREAM: "application/octet-stream";
325
+ readonly IMAGE_PNG: "image/png";
326
+ readonly IMAGE_JPEG: "image/jpeg";
327
+ readonly IMAGE_GIF: "image/gif";
328
+ readonly IMAGE_WEBP: "image/webp";
329
+ readonly IMAGE_SVG: "image/svg+xml";
330
+ readonly AUDIO_MP3: "audio/mpeg";
331
+ readonly AUDIO_WAV: "audio/wav";
332
+ readonly AUDIO_OGG: "audio/ogg";
333
+ readonly VIDEO_MP4: "video/mp4";
334
+ readonly VIDEO_WEBM: "video/webm";
335
+ };
336
+ /**
337
+ * Parse Content-Type header
338
+ */
339
+ declare function parseContentType(header: string | null): {
340
+ type: string;
341
+ subtype: string;
342
+ mediaType: string;
343
+ charset?: string;
344
+ boundary?: string;
345
+ parameters: Record<string, string>;
346
+ };
347
+ /**
348
+ * Check if Content-Type matches allowed types
349
+ */
350
+ declare function isAllowedContentType(contentType: string | null, allowedTypes: string[], strict?: boolean): boolean;
351
+ /**
352
+ * Validate Content-Type header
353
+ */
354
+ declare function validateContentType(request: NextRequest, config: ContentTypeConfig): {
355
+ valid: boolean;
356
+ contentType: string | null;
357
+ reason?: string;
358
+ };
359
+ /**
360
+ * Default Content-Type validation error response
361
+ */
362
+ declare function defaultContentTypeErrorResponse(contentType: string | null, reason: string): Response;
363
+ /**
364
+ * Check if request has JSON content type
365
+ */
366
+ declare function isJsonRequest(request: NextRequest): boolean;
367
+ /**
368
+ * Check if request has form content type
369
+ */
370
+ declare function isFormRequest(request: NextRequest): boolean;
371
+ /**
372
+ * Check if request has multipart content type
373
+ */
374
+ declare function isMultipartRequest(request: NextRequest): boolean;
375
+ /**
376
+ * Get boundary from multipart Content-Type
377
+ */
378
+ declare function getMultipartBoundary(request: NextRequest): string | null;
379
+
380
+ /**
381
+ * Default file size limits
382
+ */
383
+ declare const DEFAULT_MAX_FILE_SIZE: number;
384
+ declare const DEFAULT_MAX_FILES = 10;
385
+ /**
386
+ * Dangerous file extensions to block by default
387
+ */
388
+ declare const DANGEROUS_EXTENSIONS: string[];
389
+ /**
390
+ * Check magic number signature
391
+ */
392
+ declare function checkMagicNumber(bytes: Uint8Array, magicNumber: MagicNumber): boolean;
393
+ /**
394
+ * Detect file type from magic number
395
+ */
396
+ declare function detectFileType(bytes: Uint8Array): {
397
+ type: string;
398
+ extension: string;
399
+ } | null;
400
+ /**
401
+ * Validate a single file
402
+ */
403
+ declare function validateFile(file: File, config?: FileValidationConfig): Promise<{
404
+ valid: boolean;
405
+ info: FileInfo;
406
+ errors: FileValidationError[];
407
+ }>;
408
+ /**
409
+ * Validate multiple files
410
+ */
411
+ declare function validateFiles(files: File[], config?: FileValidationConfig): Promise<{
412
+ valid: boolean;
413
+ infos: FileInfo[];
414
+ errors: FileValidationError[];
415
+ }>;
416
+ /**
417
+ * Extract files from FormData
418
+ */
419
+ declare function extractFilesFromFormData(formData: FormData): Map<string, File[]>;
420
+ /**
421
+ * Validate files from a request
422
+ */
423
+ declare function validateFilesFromRequest(request: NextRequest, config?: FileValidationConfig): Promise<{
424
+ valid: boolean;
425
+ files: Map<string, FileInfo[]>;
426
+ errors: FileValidationError[];
427
+ }>;
428
+ /**
429
+ * Default file validation error response
430
+ */
431
+ declare function defaultFileErrorResponse(errors: FileValidationError[]): Response;
432
+
433
+ /**
434
+ * Escape HTML special characters
435
+ */
436
+ declare function escapeHtml(str: string): string;
437
+ /**
438
+ * Unescape HTML entities
439
+ */
440
+ declare function unescapeHtml(str: string): string;
441
+ /**
442
+ * Strip all HTML tags
443
+ */
444
+ declare function stripHtml(str: string): string;
445
+ /**
446
+ * Check if a URL is safe
447
+ */
448
+ declare function isSafeUrl(url: string, allowedProtocols?: string[]): boolean;
449
+ /**
450
+ * Sanitize HTML with allowed tags
451
+ */
452
+ declare function sanitizeHtml(str: string, allowedTags?: string[], allowedAttributes?: Record<string, string[]>, allowedProtocols?: string[]): string;
453
+ /**
454
+ * Detect if string contains potential XSS
455
+ */
456
+ declare function detectXSS(str: string): boolean;
457
+ /**
458
+ * Main sanitize function
459
+ */
460
+ declare function sanitize(input: string, config?: SanitizeConfig): string;
461
+ /**
462
+ * Sanitize object values recursively
463
+ */
464
+ declare function sanitizeObject<T>(obj: T, config?: SanitizeConfig): T;
465
+ /**
466
+ * Sanitize specific fields in an object
467
+ */
468
+ declare function sanitizeFields<T extends Record<string, unknown>>(obj: T, fields: string[], config?: SanitizeConfig): T;
469
+
470
+ /**
471
+ * Detect SQL injection in a string
472
+ */
473
+ declare function detectSQLInjection(input: string, options?: {
474
+ customPatterns?: RegExp[];
475
+ checkEncoded?: boolean;
476
+ minSeverity?: 'low' | 'medium' | 'high';
477
+ }): SQLDetection[];
478
+ /**
479
+ * Check if string contains SQL injection (boolean check)
480
+ */
481
+ declare function hasSQLInjection(input: string, minSeverity?: 'low' | 'medium' | 'high'): boolean;
482
+ /**
483
+ * Sanitize input to prevent SQL injection
484
+ * NOTE: This should NOT be a replacement for parameterized queries!
485
+ */
486
+ declare function sanitizeSQLInput(input: string): string;
487
+ /**
488
+ * Detect SQL injection in object fields
489
+ */
490
+ declare function detectSQLInjectionInObject(obj: unknown, options?: {
491
+ fields?: string[];
492
+ deep?: boolean;
493
+ customPatterns?: RegExp[];
494
+ minSeverity?: 'low' | 'medium' | 'high';
495
+ }): SQLDetection[];
496
+ /**
497
+ * Check if value is in allowlist (safe values)
498
+ */
499
+ declare function isAllowedValue(value: string, allowList: string[]): boolean;
500
+
501
+ /**
502
+ * Check if path contains traversal patterns
503
+ */
504
+ declare function hasPathTraversal(path: string): boolean;
505
+ /**
506
+ * Validate and sanitize a path
507
+ */
508
+ declare function validatePath(path: string, config?: PathValidationConfig): PathValidationResult;
509
+ /**
510
+ * Sanitize a path by removing dangerous elements
511
+ */
512
+ declare function sanitizePath(path: string, config?: PathValidationConfig): string;
513
+ /**
514
+ * Check if a path is within a base directory (safe containment)
515
+ */
516
+ declare function isPathContained(path: string, baseDir: string): boolean;
517
+ /**
518
+ * Get the file extension from a path
519
+ */
520
+ declare function getExtension(path: string): string;
521
+ /**
522
+ * Get the filename from a path
523
+ */
524
+ declare function getFilename(path: string): string;
525
+ /**
526
+ * Sanitize a filename (remove dangerous characters)
527
+ */
528
+ declare function sanitizeFilename(filename: string): string;
529
+ /**
530
+ * Check if path is a hidden file (starts with dot)
531
+ */
532
+ declare function isHiddenPath(path: string): boolean;
533
+
534
+ export { defaultFileErrorResponse as $, validateParams as A, validateRequest as B, type CustomSchema as C, createValidator as D, allValid as E, type FieldRule as F, mergeErrors as G, defaultValidationErrorResponse as H, validateContentType as I, parseContentType as J, isAllowedContentType as K, isJsonRequest as L, type MagicNumber as M, isFormRequest as N, isMultipartRequest as O, type PathValidationConfig as P, getMultipartBoundary as Q, defaultContentTypeErrorResponse as R, type Schema as S, MIME_TYPES as T, validateFile as U, type ValidationError as V, validateFiles as W, validateFilesFromRequest as X, extractFilesFromFormData as Y, detectFileType as Z, checkMagicNumber as _, type ValidationResult as a, DEFAULT_MAX_FILE_SIZE as a0, DEFAULT_MAX_FILES as a1, DANGEROUS_EXTENSIONS as a2, sanitize as a3, sanitizeObject as a4, sanitizeFields as a5, escapeHtml as a6, unescapeHtml as a7, stripHtml as a8, sanitizeHtml as a9, detectXSS as aa, isSafeUrl as ab, detectSQLInjection as ac, detectSQLInjectionInObject as ad, hasSQLInjection as ae, sanitizeSQLInput as af, isAllowedValue as ag, validatePath as ah, sanitizePath as ai, hasPathTraversal as aj, isPathContained as ak, getExtension as al, getFilename as am, sanitizeFilename as an, isHiddenPath as ao, type SchemaIssue as b, type FieldType as c, type ValidationConfig as d, type ValidatedContext as e, type SanitizeMode as f, type SanitizeConfig as g, type SanitizationMiddlewareConfig as h, type SanitizationChange as i, type SQLProtectionConfig as j, type SQLDetection as k, type PathValidationResult as l, type ContentTypeConfig as m, type FileValidationConfig as n, type FileValidationError as o, type FileInfo as p, withSanitization as q, withXSSProtection as r, withSQLProtection as s, withContentType as t, withFileValidation as u, withSecureValidation as v, withValidation as w, validate as x, validateBody as y, validateQuery as z };