@msal95/fileguard 0.1.1

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,380 @@
1
+ // fileguard — TypeScript definitions
2
+ // Works with Node.js >=18, React >=18, Express, Next.js App Router, Fastify
3
+
4
+ /// <reference types="node" />
5
+
6
+ // ── Error codes ───────────────────────────────────────────────────────────────
7
+
8
+ export type ErrorCode =
9
+ | 'FILE_TOO_LARGE'
10
+ | 'INVALID_EXTENSION'
11
+ | 'INVALID_MIME_TYPE'
12
+ | 'INVALID_MAGIC_BYTES'
13
+ | 'ZIP_BOMB_DETECTED'
14
+ | 'POLYGLOT_DETECTED'
15
+ | 'UNSAFE_FILENAME'
16
+ | 'VIRUS_DETECTED'
17
+ | 'RATE_LIMIT_EXCEEDED'
18
+ | 'STORAGE_ERROR'
19
+
20
+ // ── Result shapes ─────────────────────────────────────────────────────────────
21
+
22
+ export interface UploadData {
23
+ url: string | null
24
+ filename: string
25
+ size: number
26
+ mimeType: string
27
+ storage: 'local' | 's3' | 'cloudinary' | string
28
+ }
29
+
30
+ export interface SuccessResult {
31
+ success: true
32
+ data: UploadData
33
+ }
34
+
35
+ export interface FailResult {
36
+ success: false
37
+ error: ErrorCode
38
+ message: string
39
+ }
40
+
41
+ export type Result = SuccessResult | FailResult
42
+
43
+ export interface ValidationSuccess {
44
+ success: true
45
+ sanitizedFilename: string
46
+ }
47
+
48
+ export type ValidationResult = ValidationSuccess | FailResult
49
+
50
+ export interface ScanSuccess {
51
+ success: true
52
+ skipped?: boolean
53
+ }
54
+
55
+ export type ScanResult = ScanSuccess | FailResult
56
+
57
+ // ── Config ────────────────────────────────────────────────────────────────────
58
+
59
+ export interface ScanConfig {
60
+ /** Always true — cannot be disabled. */
61
+ magicBytes?: boolean
62
+ /** Always true for archive types. */
63
+ zipBomb?: boolean
64
+ /** Always true. */
65
+ polyglot?: boolean
66
+ /** Opt-in: requires clamscan peer dep. Default: false. */
67
+ clamav?: boolean
68
+ /** Opt-in: requires a VirusTotal API key. Default: false. */
69
+ virustotal?: boolean
70
+ }
71
+
72
+ export interface RateLimitConfig {
73
+ enabled?: boolean
74
+ maxUploads?: number
75
+ windowMs?: number
76
+ }
77
+
78
+ export interface AuditConfig {
79
+ enabled?: boolean
80
+ logPath?: string
81
+ }
82
+
83
+ export interface ClamAVOptions {
84
+ clamdscan?: {
85
+ host?: string
86
+ port?: number
87
+ timeout?: number
88
+ localFallback?: boolean
89
+ path?: string
90
+ configFile?: string
91
+ multiscan?: boolean
92
+ reloadDb?: boolean
93
+ active?: boolean
94
+ bypassRest?: boolean
95
+ }
96
+ preference?: string
97
+ }
98
+
99
+ export interface VirusTotalOptions {
100
+ apiKey: string
101
+ /** Milliseconds between poll attempts. Default: 5000. */
102
+ pollIntervalMs?: number
103
+ /** Maximum polling attempts before skipping. Default: 3. */
104
+ maxPolls?: number
105
+ }
106
+
107
+ export interface FileguardConfig {
108
+ // File validation
109
+ maxFileSize?: number
110
+ allowedExtensions?: string[]
111
+ allowedMimeTypes?: string[]
112
+ sanitizeFilename?: boolean
113
+ scan?: ScanConfig
114
+ rateLimit?: RateLimitConfig
115
+ audit?: AuditConfig
116
+
117
+ // Storage
118
+ storage?: 'local' | 's3' | 'cloudinary'
119
+
120
+ // Local storage
121
+ localPath?: string
122
+
123
+ // S3 storage
124
+ bucket?: string
125
+ region?: string
126
+ prefix?: string
127
+ endpoint?: string
128
+
129
+ // Cloudinary storage
130
+ cloudName?: string
131
+ apiKey?: string
132
+ apiSecret?: string
133
+ resourceType?: 'auto' | 'image' | 'video' | 'raw'
134
+ folder?: string
135
+
136
+ // Optional scanner options
137
+ clamavOptions?: ClamAVOptions
138
+ virustotalOptions?: VirusTotalOptions
139
+
140
+ // Next.js adapter
141
+ fieldName?: string
142
+ }
143
+
144
+ // ── File input ────────────────────────────────────────────────────────────────
145
+
146
+ export interface FileInput {
147
+ buffer: Buffer
148
+ filename: string
149
+ mimeType: string
150
+ size: number
151
+ sanitizedFilename?: string
152
+ }
153
+
154
+ // ── Errors ────────────────────────────────────────────────────────────────────
155
+
156
+ export declare class UploadError extends Error {
157
+ readonly code: ErrorCode
158
+ constructor(code: ErrorCode, message: string)
159
+ }
160
+
161
+ export declare function failResult(code: ErrorCode, message: string): FailResult
162
+ export declare function successResult(data: UploadData): SuccessResult
163
+
164
+ // ── createGuard ───────────────────────────────────────────────────────────────
165
+
166
+ export interface ProcessMeta {
167
+ /** User ID or IP address used for rate limiting. */
168
+ key?: string
169
+ }
170
+
171
+ export interface Guard {
172
+ process(file: FileInput, meta?: ProcessMeta): Promise<Result>
173
+ }
174
+
175
+ export declare function createGuard(config?: FileguardConfig): Guard
176
+ export { createGuard as fileguard }
177
+
178
+ // ── Core validation ───────────────────────────────────────────────────────────
179
+
180
+ export declare function validateFile(
181
+ file: FileInput,
182
+ config?: FileguardConfig
183
+ ): Promise<ValidationResult>
184
+
185
+ export declare function checkFileSize(
186
+ size: number,
187
+ maxFileSize: number
188
+ ): { success: true } | FailResult
189
+
190
+ export declare function checkExtension(
191
+ filename: string,
192
+ allowedExtensions: string[]
193
+ ): { success: true } | FailResult
194
+
195
+ export declare function checkMimeType(
196
+ mimeType: string,
197
+ allowedMimeTypes: string[]
198
+ ): { success: true } | FailResult
199
+
200
+ export declare function mergeConfig(userConfig?: Partial<FileguardConfig>): FileguardConfig
201
+
202
+ // ── Sanitizer ─────────────────────────────────────────────────────────────────
203
+
204
+ export interface SanitizeResult {
205
+ sanitizedFilename: string
206
+ wasUnsafe: boolean
207
+ }
208
+
209
+ export declare function sanitizeFilename(filename: string): SanitizeResult
210
+
211
+ // ── Rate limiter ──────────────────────────────────────────────────────────────
212
+
213
+ export interface RateLimiter {
214
+ check(key: string): { success: true } | FailResult
215
+ reset(key: string): void
216
+ }
217
+
218
+ export declare function createRateLimiter(
219
+ config?: Pick<RateLimitConfig, 'maxUploads' | 'windowMs'>
220
+ ): RateLimiter
221
+
222
+ // ── Audit logger ──────────────────────────────────────────────────────────────
223
+
224
+ export interface Logger {
225
+ log(event: Record<string, unknown>): Promise<void>
226
+ }
227
+
228
+ export declare function createLogger(config?: AuditConfig): Logger
229
+
230
+ // ── Scanners ──────────────────────────────────────────────────────────────────
231
+
232
+ export interface DetectedType {
233
+ ext: string
234
+ mime: string
235
+ }
236
+
237
+ export declare function detectFileType(buffer: Buffer): Promise<DetectedType | null>
238
+
239
+ export declare function validateMagicBytes(
240
+ buffer: Buffer,
241
+ declaredMime: string,
242
+ allowedMimeTypes: string[]
243
+ ): Promise<{ success: true; detectedMime: string; detectedExt: string } | FailResult>
244
+
245
+ export declare function checkZipBomb(
246
+ buffer: Buffer,
247
+ options?: { ratioThreshold?: number; maxFiles?: number }
248
+ ): { success: true } | FailResult
249
+
250
+ export declare function checkPolyglot(buffer: Buffer): { success: true } | FailResult
251
+
252
+ export declare function scanWithClamAV(
253
+ buffer: Buffer,
254
+ clamavOptions?: ClamAVOptions
255
+ ): Promise<ScanResult>
256
+
257
+ export declare function scanWithVirusTotal(
258
+ buffer: Buffer,
259
+ config?: VirusTotalOptions & { pollIntervalMs?: number; maxPolls?: number }
260
+ ): Promise<ScanResult>
261
+
262
+ // ── Storage adapters ──────────────────────────────────────────────────────────
263
+
264
+ export interface LocalConfig {
265
+ localPath?: string
266
+ }
267
+
268
+ export interface S3Config {
269
+ bucket: string
270
+ region?: string
271
+ prefix?: string
272
+ endpoint?: string
273
+ }
274
+
275
+ export interface CloudinaryConfig {
276
+ cloudName: string
277
+ apiKey: string
278
+ apiSecret: string
279
+ resourceType?: 'auto' | 'image' | 'video' | 'raw'
280
+ folder?: string
281
+ }
282
+
283
+ export declare function localStore(file: FileInput, config?: LocalConfig): Promise<Result>
284
+ export declare function s3Store(file: FileInput, config: S3Config): Promise<Result>
285
+ export declare function cloudinaryStore(file: FileInput, config: CloudinaryConfig): Promise<Result>
286
+
287
+ export type StorageAdapter = typeof localStore | typeof s3Store | typeof cloudinaryStore
288
+
289
+ export declare function getStorageAdapter(
290
+ type: 'local' | 's3' | 'cloudinary'
291
+ ): Promise<StorageAdapter>
292
+
293
+ // ── Framework adapters ────────────────────────────────────────────────────────
294
+
295
+ // Express
296
+ export interface ExpressRequest {
297
+ headers: Record<string, string | string[] | undefined>
298
+ pipe<T>(destination: T): T
299
+ uploadResult?: Result
300
+ [key: string]: unknown
301
+ }
302
+
303
+ export interface ExpressResponse {
304
+ [key: string]: unknown
305
+ }
306
+
307
+ export type NextFunction = (err?: unknown) => void
308
+
309
+ export declare function createExpressMiddleware(
310
+ config?: FileguardConfig
311
+ ): (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => void
312
+
313
+ // Next.js App Router
314
+ export declare function createNextHandler(
315
+ config?: FileguardConfig
316
+ ): (request: Request) => Promise<Response>
317
+
318
+ // Fastify
319
+ export declare function createFastifyPlugin(
320
+ config?: FileguardConfig
321
+ ): (fastify: unknown) => Promise<void>
322
+
323
+ // ── React UI components ───────────────────────────────────────────────────────
324
+ // React (>=18) must be installed as a peer dependency.
325
+
326
+ export interface UploadError {
327
+ error: ErrorCode
328
+ message: string
329
+ file: File
330
+ }
331
+
332
+ export interface DropZoneProps {
333
+ onUpload?: (file: File) => void
334
+ onError?: (err: UploadError) => void
335
+ accept?: string[]
336
+ maxSize?: number
337
+ headless?: boolean
338
+ multiple?: boolean
339
+ children?: unknown
340
+ className?: string
341
+ style?: Record<string, string | number>
342
+ [prop: string]: unknown
343
+ }
344
+
345
+ export interface UploadButtonProps {
346
+ onUpload?: (file: File) => void
347
+ onError?: (err: UploadError) => void
348
+ accept?: string[]
349
+ maxSize?: number
350
+ multiple?: boolean
351
+ headless?: boolean
352
+ disabled?: boolean
353
+ children?: unknown
354
+ className?: string
355
+ style?: Record<string, string | number>
356
+ [prop: string]: unknown
357
+ }
358
+
359
+ export interface ProgressBarProps {
360
+ progress?: number
361
+ label?: string
362
+ headless?: boolean
363
+ className?: string
364
+ style?: Record<string, string | number>
365
+ [prop: string]: unknown
366
+ }
367
+
368
+ export interface FilePreviewProps {
369
+ file: File | null
370
+ onRemove?: () => void
371
+ headless?: boolean
372
+ className?: string
373
+ style?: Record<string, string | number>
374
+ [prop: string]: unknown
375
+ }
376
+
377
+ export declare function DropZone(props: DropZoneProps): unknown
378
+ export declare function UploadButton(props: UploadButtonProps): unknown
379
+ export declare function ProgressBar(props: ProgressBarProps): unknown
380
+ export declare function FilePreview(props: FilePreviewProps): unknown