@bloomneo/appkit 1.2.9
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/LICENSE +21 -0
- package/README.md +902 -0
- package/bin/appkit.js +71 -0
- package/bin/commands/generate.js +1050 -0
- package/bin/templates/backend/README.md.template +39 -0
- package/bin/templates/backend/api.http.template +0 -0
- package/bin/templates/backend/docs/APPKIT_CLI.md +507 -0
- package/bin/templates/backend/docs/APPKIT_COMMENTS_GUIDELINES.md +61 -0
- package/bin/templates/backend/docs/APPKIT_LLM_GUIDE.md +2539 -0
- package/bin/templates/backend/package.json.template +34 -0
- package/bin/templates/backend/src/api/features/welcome/welcome.http.template +29 -0
- package/bin/templates/backend/src/api/features/welcome/welcome.route.ts.template +36 -0
- package/bin/templates/backend/src/api/features/welcome/welcome.service.ts.template +88 -0
- package/bin/templates/backend/src/api/features/welcome/welcome.types.ts.template +18 -0
- package/bin/templates/backend/src/api/lib/api-router.ts.template +84 -0
- package/bin/templates/backend/src/api/server.ts.template +188 -0
- package/bin/templates/backend/tsconfig.api.json.template +24 -0
- package/bin/templates/backend/tsconfig.json.template +40 -0
- package/bin/templates/feature/feature.http.template +63 -0
- package/bin/templates/feature/feature.route.ts.template +36 -0
- package/bin/templates/feature/feature.service.ts.template +81 -0
- package/bin/templates/feature/feature.types.ts.template +23 -0
- package/bin/templates/feature-db/feature.http.template +63 -0
- package/bin/templates/feature-db/feature.model.ts.template +74 -0
- package/bin/templates/feature-db/feature.route.ts.template +58 -0
- package/bin/templates/feature-db/feature.service.ts.template +231 -0
- package/bin/templates/feature-db/feature.types.ts.template +25 -0
- package/bin/templates/feature-db/schema-addition.prisma.template +9 -0
- package/bin/templates/feature-db/seeding/README.md.template +57 -0
- package/bin/templates/feature-db/seeding/feature.seed.js.template +67 -0
- package/bin/templates/feature-user/schema-addition.prisma.template +19 -0
- package/bin/templates/feature-user/user.http.template +157 -0
- package/bin/templates/feature-user/user.model.ts.template +244 -0
- package/bin/templates/feature-user/user.route.ts.template +379 -0
- package/bin/templates/feature-user/user.seed.js.template +182 -0
- package/bin/templates/feature-user/user.service.ts.template +426 -0
- package/bin/templates/feature-user/user.types.ts.template +127 -0
- package/dist/auth/auth.d.ts +182 -0
- package/dist/auth/auth.d.ts.map +1 -0
- package/dist/auth/auth.js +477 -0
- package/dist/auth/auth.js.map +1 -0
- package/dist/auth/defaults.d.ts +104 -0
- package/dist/auth/defaults.d.ts.map +1 -0
- package/dist/auth/defaults.js +374 -0
- package/dist/auth/defaults.js.map +1 -0
- package/dist/auth/index.d.ts +70 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +94 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/cache/cache.d.ts +118 -0
- package/dist/cache/cache.d.ts.map +1 -0
- package/dist/cache/cache.js +249 -0
- package/dist/cache/cache.js.map +1 -0
- package/dist/cache/defaults.d.ts +63 -0
- package/dist/cache/defaults.d.ts.map +1 -0
- package/dist/cache/defaults.js +193 -0
- package/dist/cache/defaults.js.map +1 -0
- package/dist/cache/index.d.ts +101 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +203 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/strategies/memory.d.ts +138 -0
- package/dist/cache/strategies/memory.d.ts.map +1 -0
- package/dist/cache/strategies/memory.js +348 -0
- package/dist/cache/strategies/memory.js.map +1 -0
- package/dist/cache/strategies/redis.d.ts +105 -0
- package/dist/cache/strategies/redis.d.ts.map +1 -0
- package/dist/cache/strategies/redis.js +318 -0
- package/dist/cache/strategies/redis.js.map +1 -0
- package/dist/config/config.d.ts +62 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +107 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/defaults.d.ts +44 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +217 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +105 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +163 -0
- package/dist/config/index.js.map +1 -0
- package/dist/database/adapters/mongoose.d.ts +106 -0
- package/dist/database/adapters/mongoose.d.ts.map +1 -0
- package/dist/database/adapters/mongoose.js +480 -0
- package/dist/database/adapters/mongoose.js.map +1 -0
- package/dist/database/adapters/prisma.d.ts +106 -0
- package/dist/database/adapters/prisma.d.ts.map +1 -0
- package/dist/database/adapters/prisma.js +494 -0
- package/dist/database/adapters/prisma.js.map +1 -0
- package/dist/database/defaults.d.ts +87 -0
- package/dist/database/defaults.d.ts.map +1 -0
- package/dist/database/defaults.js +271 -0
- package/dist/database/defaults.js.map +1 -0
- package/dist/database/index.d.ts +137 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +490 -0
- package/dist/database/index.js.map +1 -0
- package/dist/email/defaults.d.ts +100 -0
- package/dist/email/defaults.d.ts.map +1 -0
- package/dist/email/defaults.js +400 -0
- package/dist/email/defaults.js.map +1 -0
- package/dist/email/email.d.ts +139 -0
- package/dist/email/email.d.ts.map +1 -0
- package/dist/email/email.js +316 -0
- package/dist/email/email.js.map +1 -0
- package/dist/email/index.d.ts +176 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/email/index.js +251 -0
- package/dist/email/index.js.map +1 -0
- package/dist/email/strategies/console.d.ts +90 -0
- package/dist/email/strategies/console.d.ts.map +1 -0
- package/dist/email/strategies/console.js +268 -0
- package/dist/email/strategies/console.js.map +1 -0
- package/dist/email/strategies/resend.d.ts +84 -0
- package/dist/email/strategies/resend.d.ts.map +1 -0
- package/dist/email/strategies/resend.js +266 -0
- package/dist/email/strategies/resend.js.map +1 -0
- package/dist/email/strategies/smtp.d.ts +77 -0
- package/dist/email/strategies/smtp.d.ts.map +1 -0
- package/dist/email/strategies/smtp.js +286 -0
- package/dist/email/strategies/smtp.js.map +1 -0
- package/dist/error/defaults.d.ts +40 -0
- package/dist/error/defaults.d.ts.map +1 -0
- package/dist/error/defaults.js +75 -0
- package/dist/error/defaults.js.map +1 -0
- package/dist/error/error.d.ts +140 -0
- package/dist/error/error.d.ts.map +1 -0
- package/dist/error/error.js +200 -0
- package/dist/error/error.js.map +1 -0
- package/dist/error/index.d.ts +145 -0
- package/dist/error/index.d.ts.map +1 -0
- package/dist/error/index.js +145 -0
- package/dist/error/index.js.map +1 -0
- package/dist/event/defaults.d.ts +111 -0
- package/dist/event/defaults.d.ts.map +1 -0
- package/dist/event/defaults.js +378 -0
- package/dist/event/defaults.js.map +1 -0
- package/dist/event/event.d.ts +171 -0
- package/dist/event/event.d.ts.map +1 -0
- package/dist/event/event.js +391 -0
- package/dist/event/event.js.map +1 -0
- package/dist/event/index.d.ts +173 -0
- package/dist/event/index.d.ts.map +1 -0
- package/dist/event/index.js +302 -0
- package/dist/event/index.js.map +1 -0
- package/dist/event/strategies/memory.d.ts +122 -0
- package/dist/event/strategies/memory.d.ts.map +1 -0
- package/dist/event/strategies/memory.js +331 -0
- package/dist/event/strategies/memory.js.map +1 -0
- package/dist/event/strategies/redis.d.ts +115 -0
- package/dist/event/strategies/redis.d.ts.map +1 -0
- package/dist/event/strategies/redis.js +434 -0
- package/dist/event/strategies/redis.js.map +1 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/defaults.d.ts +67 -0
- package/dist/logger/defaults.d.ts.map +1 -0
- package/dist/logger/defaults.js +213 -0
- package/dist/logger/defaults.js.map +1 -0
- package/dist/logger/index.d.ts +84 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +101 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/logger.d.ts +165 -0
- package/dist/logger/logger.d.ts.map +1 -0
- package/dist/logger/logger.js +843 -0
- package/dist/logger/logger.js.map +1 -0
- package/dist/logger/transports/console.d.ts +102 -0
- package/dist/logger/transports/console.d.ts.map +1 -0
- package/dist/logger/transports/console.js +276 -0
- package/dist/logger/transports/console.js.map +1 -0
- package/dist/logger/transports/database.d.ts +153 -0
- package/dist/logger/transports/database.d.ts.map +1 -0
- package/dist/logger/transports/database.js +539 -0
- package/dist/logger/transports/database.js.map +1 -0
- package/dist/logger/transports/file.d.ts +146 -0
- package/dist/logger/transports/file.d.ts.map +1 -0
- package/dist/logger/transports/file.js +464 -0
- package/dist/logger/transports/file.js.map +1 -0
- package/dist/logger/transports/http.d.ts +128 -0
- package/dist/logger/transports/http.d.ts.map +1 -0
- package/dist/logger/transports/http.js +401 -0
- package/dist/logger/transports/http.js.map +1 -0
- package/dist/logger/transports/webhook.d.ts +152 -0
- package/dist/logger/transports/webhook.d.ts.map +1 -0
- package/dist/logger/transports/webhook.js +485 -0
- package/dist/logger/transports/webhook.js.map +1 -0
- package/dist/queue/defaults.d.ts +66 -0
- package/dist/queue/defaults.d.ts.map +1 -0
- package/dist/queue/defaults.js +205 -0
- package/dist/queue/defaults.js.map +1 -0
- package/dist/queue/index.d.ts +124 -0
- package/dist/queue/index.d.ts.map +1 -0
- package/dist/queue/index.js +116 -0
- package/dist/queue/index.js.map +1 -0
- package/dist/queue/queue.d.ts +156 -0
- package/dist/queue/queue.d.ts.map +1 -0
- package/dist/queue/queue.js +387 -0
- package/dist/queue/queue.js.map +1 -0
- package/dist/queue/transports/database.d.ts +165 -0
- package/dist/queue/transports/database.d.ts.map +1 -0
- package/dist/queue/transports/database.js +595 -0
- package/dist/queue/transports/database.js.map +1 -0
- package/dist/queue/transports/memory.d.ts +143 -0
- package/dist/queue/transports/memory.d.ts.map +1 -0
- package/dist/queue/transports/memory.js +415 -0
- package/dist/queue/transports/memory.js.map +1 -0
- package/dist/queue/transports/redis.d.ts +203 -0
- package/dist/queue/transports/redis.d.ts.map +1 -0
- package/dist/queue/transports/redis.js +744 -0
- package/dist/queue/transports/redis.js.map +1 -0
- package/dist/security/defaults.d.ts +64 -0
- package/dist/security/defaults.d.ts.map +1 -0
- package/dist/security/defaults.js +159 -0
- package/dist/security/defaults.js.map +1 -0
- package/dist/security/index.d.ts +110 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +160 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/security.d.ts +138 -0
- package/dist/security/security.d.ts.map +1 -0
- package/dist/security/security.js +419 -0
- package/dist/security/security.js.map +1 -0
- package/dist/storage/defaults.d.ts +79 -0
- package/dist/storage/defaults.d.ts.map +1 -0
- package/dist/storage/defaults.js +358 -0
- package/dist/storage/defaults.js.map +1 -0
- package/dist/storage/index.d.ts +153 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +242 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/storage.d.ts +151 -0
- package/dist/storage/storage.d.ts.map +1 -0
- package/dist/storage/storage.js +439 -0
- package/dist/storage/storage.js.map +1 -0
- package/dist/storage/strategies/local.d.ts +117 -0
- package/dist/storage/strategies/local.d.ts.map +1 -0
- package/dist/storage/strategies/local.js +368 -0
- package/dist/storage/strategies/local.js.map +1 -0
- package/dist/storage/strategies/r2.d.ts +130 -0
- package/dist/storage/strategies/r2.d.ts.map +1 -0
- package/dist/storage/strategies/r2.js +470 -0
- package/dist/storage/strategies/r2.js.map +1 -0
- package/dist/storage/strategies/s3.d.ts +121 -0
- package/dist/storage/strategies/s3.d.ts.map +1 -0
- package/dist/storage/strategies/s3.js +461 -0
- package/dist/storage/strategies/s3.js.map +1 -0
- package/dist/util/defaults.d.ts +77 -0
- package/dist/util/defaults.d.ts.map +1 -0
- package/dist/util/defaults.js +193 -0
- package/dist/util/defaults.js.map +1 -0
- package/dist/util/index.d.ts +97 -0
- package/dist/util/index.d.ts.map +1 -0
- package/dist/util/index.js +165 -0
- package/dist/util/index.js.map +1 -0
- package/dist/util/util.d.ts +145 -0
- package/dist/util/util.d.ts.map +1 -0
- package/dist/util/util.js +481 -0
- package/dist/util/util.js.map +1 -0
- package/package.json +234 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core security class with CSRF, rate limiting, sanitization, and encryption
|
|
3
|
+
* @module @bloomneo/appkit/security
|
|
4
|
+
* @file src/security/security.ts
|
|
5
|
+
*
|
|
6
|
+
* @llm-rule WHEN: Building apps that need security protection (CSRF, rate limiting, input sanitization, encryption)
|
|
7
|
+
* @llm-rule AVOID: Using directly - always get instance via securityClass.get()
|
|
8
|
+
* @llm-rule NOTE: Provides enterprise-grade security with CSRF tokens, rate limiting, XSS prevention, and AES-256-GCM encryption
|
|
9
|
+
*/
|
|
10
|
+
import type { SecurityConfig } from './defaults.js';
|
|
11
|
+
export interface ExpressRequest {
|
|
12
|
+
method: string;
|
|
13
|
+
session?: any;
|
|
14
|
+
body?: any;
|
|
15
|
+
headers?: Record<string, string | string[] | undefined>;
|
|
16
|
+
query?: any;
|
|
17
|
+
ip?: string;
|
|
18
|
+
connection?: {
|
|
19
|
+
remoteAddress?: string;
|
|
20
|
+
};
|
|
21
|
+
csrfToken?: () => string;
|
|
22
|
+
[key: string]: any;
|
|
23
|
+
}
|
|
24
|
+
export interface ExpressResponse {
|
|
25
|
+
setHeader?: (name: string, value: string | number) => void;
|
|
26
|
+
[key: string]: any;
|
|
27
|
+
}
|
|
28
|
+
export interface ExpressNextFunction {
|
|
29
|
+
(error?: any): void;
|
|
30
|
+
}
|
|
31
|
+
export type ExpressMiddleware = (req: ExpressRequest, res: ExpressResponse, next: ExpressNextFunction) => void;
|
|
32
|
+
export interface CSRFOptions {
|
|
33
|
+
secret?: string;
|
|
34
|
+
tokenField?: string;
|
|
35
|
+
headerField?: string;
|
|
36
|
+
expiryMinutes?: number;
|
|
37
|
+
}
|
|
38
|
+
export interface RateLimitOptions {
|
|
39
|
+
maxRequests?: number;
|
|
40
|
+
windowMs?: number;
|
|
41
|
+
message?: string;
|
|
42
|
+
keyGenerator?: (req: ExpressRequest) => string;
|
|
43
|
+
}
|
|
44
|
+
export interface InputOptions {
|
|
45
|
+
maxLength?: number;
|
|
46
|
+
trim?: boolean;
|
|
47
|
+
removeXSS?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export interface HTMLOptions {
|
|
50
|
+
allowedTags?: string[];
|
|
51
|
+
stripAllTags?: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Security class with enterprise-grade protection functionality
|
|
55
|
+
*/
|
|
56
|
+
export declare class SecurityClass {
|
|
57
|
+
config: SecurityConfig;
|
|
58
|
+
private requestStore;
|
|
59
|
+
private cleanupInitialized;
|
|
60
|
+
constructor(config: SecurityConfig);
|
|
61
|
+
/**
|
|
62
|
+
* Creates CSRF protection middleware for forms and AJAX requests
|
|
63
|
+
* @llm-rule WHEN: Protecting forms and state-changing requests from CSRF attacks
|
|
64
|
+
* @llm-rule AVOID: Using without session middleware - CSRF requires sessions for token storage
|
|
65
|
+
* @llm-rule NOTE: Automatically validates tokens on POST/PUT/DELETE/PATCH requests, adds req.csrfToken() method
|
|
66
|
+
*/
|
|
67
|
+
forms(options?: CSRFOptions): ExpressMiddleware;
|
|
68
|
+
/**
|
|
69
|
+
* Creates rate limiting middleware with configurable limits and windows
|
|
70
|
+
* @llm-rule WHEN: Protecting endpoints from abuse and brute force attacks
|
|
71
|
+
* @llm-rule AVOID: Using same limits for all endpoints - auth should have stricter limits than API
|
|
72
|
+
* @llm-rule NOTE: Uses in-memory storage with automatic cleanup, sets standard rate limit headers
|
|
73
|
+
*/
|
|
74
|
+
requests(maxRequests?: number, windowMs?: number, options?: RateLimitOptions): ExpressMiddleware;
|
|
75
|
+
/**
|
|
76
|
+
* Cleans text input with XSS prevention and length limiting
|
|
77
|
+
* @llm-rule WHEN: Processing any user text input before storage or display
|
|
78
|
+
* @llm-rule AVOID: Storing raw user input - always clean to prevent XSS attacks
|
|
79
|
+
* @llm-rule NOTE: Removes dangerous patterns like <script>, javascript:, event handlers
|
|
80
|
+
*/
|
|
81
|
+
input(text: any, options?: InputOptions): string;
|
|
82
|
+
/**
|
|
83
|
+
* Cleans HTML content allowing only specified safe tags
|
|
84
|
+
* @llm-rule WHEN: Processing user HTML content like rich text editor input
|
|
85
|
+
* @llm-rule AVOID: Allowing all HTML tags - only whitelist safe formatting tags
|
|
86
|
+
* @llm-rule NOTE: Removes script, iframe, object tags and dangerous attributes like onclick
|
|
87
|
+
*/
|
|
88
|
+
html(html: any, options?: HTMLOptions): string;
|
|
89
|
+
/**
|
|
90
|
+
* Escapes HTML special characters for safe display in HTML content
|
|
91
|
+
* @llm-rule WHEN: Displaying user text content in HTML without allowing any HTML tags
|
|
92
|
+
* @llm-rule AVOID: Direct interpolation of user content in HTML - always escape first
|
|
93
|
+
* @llm-rule NOTE: Converts &, <, >, quotes to HTML entities for safe display
|
|
94
|
+
*/
|
|
95
|
+
escape(text: any): string;
|
|
96
|
+
/**
|
|
97
|
+
* Encrypts sensitive data using AES-256-GCM with authentication
|
|
98
|
+
* @llm-rule WHEN: Storing sensitive data like SSNs, credit cards, personal info
|
|
99
|
+
* @llm-rule AVOID: Storing sensitive data in plain text - always encrypt before database storage
|
|
100
|
+
* @llm-rule NOTE: Uses random IV per encryption, includes authentication tag to prevent tampering
|
|
101
|
+
*/
|
|
102
|
+
encrypt(data: string | Buffer, key?: string | Buffer, associatedData?: Buffer): string;
|
|
103
|
+
/**
|
|
104
|
+
* Decrypts previously encrypted data with authentication verification
|
|
105
|
+
* @llm-rule WHEN: Retrieving sensitive data that was encrypted with encrypt() method
|
|
106
|
+
* @llm-rule AVOID: Using with data not encrypted by this module - will fail authentication
|
|
107
|
+
* @llm-rule NOTE: Automatically verifies authentication tag to detect tampering
|
|
108
|
+
*/
|
|
109
|
+
decrypt(encryptedData: string, key?: string | Buffer, associatedData?: Buffer): string;
|
|
110
|
+
/**
|
|
111
|
+
* Generates a cryptographically secure 256-bit encryption key
|
|
112
|
+
* @llm-rule WHEN: Setting up encryption for the first time or rotating keys
|
|
113
|
+
* @llm-rule AVOID: Using weak or predictable keys - always use this method for key generation
|
|
114
|
+
* @llm-rule NOTE: Returns 64-character hex string suitable for VOILA_SECURITY_ENCRYPTION_KEY
|
|
115
|
+
*/
|
|
116
|
+
generateKey(): string;
|
|
117
|
+
/**
|
|
118
|
+
* Generates a cryptographically secure CSRF token
|
|
119
|
+
*/
|
|
120
|
+
private generateCSRFToken;
|
|
121
|
+
/**
|
|
122
|
+
* Verifies CSRF token using timing-safe comparison
|
|
123
|
+
*/
|
|
124
|
+
private verifyCSRFToken;
|
|
125
|
+
/**
|
|
126
|
+
* Gets unique identifier for the client
|
|
127
|
+
*/
|
|
128
|
+
private getClientKey;
|
|
129
|
+
/**
|
|
130
|
+
* Initializes cleanup interval for memory management
|
|
131
|
+
*/
|
|
132
|
+
private initializeCleanup;
|
|
133
|
+
/**
|
|
134
|
+
* Validates encryption key format and length
|
|
135
|
+
*/
|
|
136
|
+
private validateEncryptionKey;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/security/security.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core security class with CSRF, rate limiting, sanitization, and encryption
|
|
3
|
+
* @module @bloomneo/appkit/security
|
|
4
|
+
* @file src/security/security.ts
|
|
5
|
+
*
|
|
6
|
+
* @llm-rule WHEN: Building apps that need security protection (CSRF, rate limiting, input sanitization, encryption)
|
|
7
|
+
* @llm-rule AVOID: Using directly - always get instance via securityClass.get()
|
|
8
|
+
* @llm-rule NOTE: Provides enterprise-grade security with CSRF tokens, rate limiting, XSS prevention, and AES-256-GCM encryption
|
|
9
|
+
*/
|
|
10
|
+
import crypto from 'crypto';
|
|
11
|
+
import { createSecurityError } from './defaults.js';
|
|
12
|
+
/**
|
|
13
|
+
* Security class with enterprise-grade protection functionality
|
|
14
|
+
*/
|
|
15
|
+
export class SecurityClass {
|
|
16
|
+
config;
|
|
17
|
+
requestStore;
|
|
18
|
+
cleanupInitialized;
|
|
19
|
+
constructor(config) {
|
|
20
|
+
this.config = config;
|
|
21
|
+
this.requestStore = new Map();
|
|
22
|
+
this.cleanupInitialized = false;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Creates CSRF protection middleware for forms and AJAX requests
|
|
26
|
+
* @llm-rule WHEN: Protecting forms and state-changing requests from CSRF attacks
|
|
27
|
+
* @llm-rule AVOID: Using without session middleware - CSRF requires sessions for token storage
|
|
28
|
+
* @llm-rule NOTE: Automatically validates tokens on POST/PUT/DELETE/PATCH requests, adds req.csrfToken() method
|
|
29
|
+
*/
|
|
30
|
+
forms(options = {}) {
|
|
31
|
+
const csrfSecret = options.secret || this.config.csrf.secret;
|
|
32
|
+
if (!csrfSecret) {
|
|
33
|
+
throw createSecurityError('CSRF secret required. Set VOILA_SECURITY_CSRF_SECRET or VOILA_AUTH_SECRET environment variable', 500);
|
|
34
|
+
}
|
|
35
|
+
const tokenField = options.tokenField || this.config.csrf.tokenField;
|
|
36
|
+
const headerField = options.headerField || this.config.csrf.headerField;
|
|
37
|
+
const expiryMinutes = options.expiryMinutes || this.config.csrf.expiryMinutes;
|
|
38
|
+
return (req, res, next) => {
|
|
39
|
+
// Ensure session exists
|
|
40
|
+
if (!req.session || typeof req.session !== 'object') {
|
|
41
|
+
const error = createSecurityError('Session required for CSRF protection', 500);
|
|
42
|
+
return next(error);
|
|
43
|
+
}
|
|
44
|
+
// Add token generation method to request
|
|
45
|
+
req.csrfToken = () => this.generateCSRFToken(req.session, expiryMinutes);
|
|
46
|
+
// Skip CSRF verification for safe HTTP methods
|
|
47
|
+
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
|
|
48
|
+
return next();
|
|
49
|
+
}
|
|
50
|
+
// Extract token from request
|
|
51
|
+
const token = (req.body && req.body[tokenField]) ||
|
|
52
|
+
(req.headers && req.headers[headerField.toLowerCase()]) ||
|
|
53
|
+
(req.query && req.query[tokenField]);
|
|
54
|
+
// Verify token
|
|
55
|
+
if (!this.verifyCSRFToken(token, req.session)) {
|
|
56
|
+
const error = createSecurityError('Invalid or missing CSRF token', 403);
|
|
57
|
+
return next(error);
|
|
58
|
+
}
|
|
59
|
+
next();
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Creates rate limiting middleware with configurable limits and windows
|
|
64
|
+
* @llm-rule WHEN: Protecting endpoints from abuse and brute force attacks
|
|
65
|
+
* @llm-rule AVOID: Using same limits for all endpoints - auth should have stricter limits than API
|
|
66
|
+
* @llm-rule NOTE: Uses in-memory storage with automatic cleanup, sets standard rate limit headers
|
|
67
|
+
*/
|
|
68
|
+
requests(maxRequests, windowMs, options = {}) {
|
|
69
|
+
// Handle argument polymorphism
|
|
70
|
+
if (typeof maxRequests === 'object') {
|
|
71
|
+
options = maxRequests;
|
|
72
|
+
maxRequests = options.maxRequests;
|
|
73
|
+
windowMs = options.windowMs;
|
|
74
|
+
}
|
|
75
|
+
else if (typeof windowMs === 'object') {
|
|
76
|
+
options = windowMs;
|
|
77
|
+
windowMs = options.windowMs;
|
|
78
|
+
}
|
|
79
|
+
// Use provided values or config defaults
|
|
80
|
+
const max = maxRequests || this.config.rateLimit.maxRequests;
|
|
81
|
+
const window = windowMs || this.config.rateLimit.windowMs;
|
|
82
|
+
const message = options.message || this.config.rateLimit.message;
|
|
83
|
+
const keyGenerator = options.keyGenerator || this.getClientKey;
|
|
84
|
+
// Validate configuration
|
|
85
|
+
if (max < 0 || window <= 0) {
|
|
86
|
+
throw createSecurityError('Invalid rate limit configuration', 500);
|
|
87
|
+
}
|
|
88
|
+
// Initialize cleanup for memory management
|
|
89
|
+
this.initializeCleanup(window);
|
|
90
|
+
return (req, res, next) => {
|
|
91
|
+
const key = keyGenerator(req);
|
|
92
|
+
const now = Date.now();
|
|
93
|
+
// Get or create rate limit record
|
|
94
|
+
let record = this.requestStore.get(key);
|
|
95
|
+
if (!record) {
|
|
96
|
+
record = { count: 0, resetTime: now + window };
|
|
97
|
+
this.requestStore.set(key, record);
|
|
98
|
+
}
|
|
99
|
+
else if (now > record.resetTime) {
|
|
100
|
+
// Reset if window has passed
|
|
101
|
+
record.count = 0;
|
|
102
|
+
record.resetTime = now + window;
|
|
103
|
+
}
|
|
104
|
+
// Increment request count
|
|
105
|
+
record.count++;
|
|
106
|
+
// Set rate limit headers
|
|
107
|
+
if (res.setHeader) {
|
|
108
|
+
res.setHeader('X-RateLimit-Limit', max);
|
|
109
|
+
res.setHeader('X-RateLimit-Remaining', Math.max(0, max - record.count));
|
|
110
|
+
res.setHeader('X-RateLimit-Reset', Math.ceil(record.resetTime / 1000));
|
|
111
|
+
}
|
|
112
|
+
// Check if limit exceeded
|
|
113
|
+
if (record.count > max) {
|
|
114
|
+
const retryAfter = Math.ceil((record.resetTime - now) / 1000);
|
|
115
|
+
if (res.setHeader) {
|
|
116
|
+
res.setHeader('Retry-After', retryAfter);
|
|
117
|
+
}
|
|
118
|
+
const error = createSecurityError(message, 429, {
|
|
119
|
+
retryAfter,
|
|
120
|
+
limit: max,
|
|
121
|
+
remaining: 0,
|
|
122
|
+
resetTime: record.resetTime,
|
|
123
|
+
});
|
|
124
|
+
return next(error);
|
|
125
|
+
}
|
|
126
|
+
next();
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Cleans text input with XSS prevention and length limiting
|
|
131
|
+
* @llm-rule WHEN: Processing any user text input before storage or display
|
|
132
|
+
* @llm-rule AVOID: Storing raw user input - always clean to prevent XSS attacks
|
|
133
|
+
* @llm-rule NOTE: Removes dangerous patterns like <script>, javascript:, event handlers
|
|
134
|
+
*/
|
|
135
|
+
input(text, options = {}) {
|
|
136
|
+
if (typeof text !== 'string') {
|
|
137
|
+
return '';
|
|
138
|
+
}
|
|
139
|
+
const maxLength = options.maxLength || this.config.sanitization.maxLength;
|
|
140
|
+
const trim = options.trim !== false;
|
|
141
|
+
const removeXSS = options.removeXSS !== false;
|
|
142
|
+
let result = text;
|
|
143
|
+
// Trim whitespace
|
|
144
|
+
if (trim) {
|
|
145
|
+
result = result.trim();
|
|
146
|
+
}
|
|
147
|
+
// Basic XSS prevention
|
|
148
|
+
if (removeXSS) {
|
|
149
|
+
result = result
|
|
150
|
+
.replace(/[<>]/g, '') // Remove angle brackets
|
|
151
|
+
.replace(/javascript:/gi, '') // Remove javascript: protocol
|
|
152
|
+
.replace(/on\w+\s*=/gi, '') // Remove inline event handlers
|
|
153
|
+
.replace(/data:/gi, '') // Remove data: protocol
|
|
154
|
+
.replace(/vbscript:/gi, '') // Remove vbscript: protocol
|
|
155
|
+
.replace(/expression\s*\(/gi, '') // Remove CSS expressions
|
|
156
|
+
.replace(/url\s*\(/gi, ''); // Remove CSS url() functions
|
|
157
|
+
}
|
|
158
|
+
// Length limiting
|
|
159
|
+
if (result.length > maxLength) {
|
|
160
|
+
result = result.substring(0, maxLength);
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Cleans HTML content allowing only specified safe tags
|
|
166
|
+
* @llm-rule WHEN: Processing user HTML content like rich text editor input
|
|
167
|
+
* @llm-rule AVOID: Allowing all HTML tags - only whitelist safe formatting tags
|
|
168
|
+
* @llm-rule NOTE: Removes script, iframe, object tags and dangerous attributes like onclick
|
|
169
|
+
*/
|
|
170
|
+
html(html, options = {}) {
|
|
171
|
+
if (typeof html !== 'string') {
|
|
172
|
+
return '';
|
|
173
|
+
}
|
|
174
|
+
const allowedTags = options.allowedTags || this.config.sanitization.allowedTags;
|
|
175
|
+
const stripAllTags = options.stripAllTags !== undefined
|
|
176
|
+
? options.stripAllTags
|
|
177
|
+
: this.config.sanitization.stripAllTags;
|
|
178
|
+
let result = html;
|
|
179
|
+
// Strip all tags if requested
|
|
180
|
+
if (stripAllTags) {
|
|
181
|
+
return result.replace(/<[^>]*>/g, '');
|
|
182
|
+
}
|
|
183
|
+
// Remove dangerous elements
|
|
184
|
+
result = result
|
|
185
|
+
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') // Remove script tags
|
|
186
|
+
.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '') // Remove iframe tags
|
|
187
|
+
.replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, '') // Remove object tags
|
|
188
|
+
.replace(/<embed\b[^>]*>/gi, '') // Remove embed tags
|
|
189
|
+
.replace(/<form\b[^<]*(?:(?!<\/form>)<[^<]*)*<\/form>/gi, '') // Remove form tags
|
|
190
|
+
.replace(/\s+on\w+\s*=\s*["'][^"']*["']/gi, '') // Remove inline event handlers
|
|
191
|
+
.replace(/javascript\s*:/gi, '') // Remove javascript: protocol
|
|
192
|
+
.replace(/data\s*:/gi, '') // Remove data: protocol
|
|
193
|
+
.replace(/vbscript\s*:/gi, '') // Remove vbscript: protocol
|
|
194
|
+
.replace(/expression\s*\(/gi, ''); // Remove CSS expressions
|
|
195
|
+
// Filter allowed tags if specified
|
|
196
|
+
if (allowedTags.length > 0) {
|
|
197
|
+
try {
|
|
198
|
+
const allowedPattern = allowedTags
|
|
199
|
+
.map(tag => tag.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'))
|
|
200
|
+
.join('|');
|
|
201
|
+
const tagPattern = new RegExp(`<(?!\/?(?:${allowedPattern})\\b)[^>]+>`, 'gi');
|
|
202
|
+
result = result.replace(tagPattern, '');
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
console.warn('HTML sanitization: Invalid allowed tags, stripping all tags');
|
|
206
|
+
result = result.replace(/<[^>]*>/g, '');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Escapes HTML special characters for safe display in HTML content
|
|
213
|
+
* @llm-rule WHEN: Displaying user text content in HTML without allowing any HTML tags
|
|
214
|
+
* @llm-rule AVOID: Direct interpolation of user content in HTML - always escape first
|
|
215
|
+
* @llm-rule NOTE: Converts &, <, >, quotes to HTML entities for safe display
|
|
216
|
+
*/
|
|
217
|
+
escape(text) {
|
|
218
|
+
if (typeof text !== 'string') {
|
|
219
|
+
return '';
|
|
220
|
+
}
|
|
221
|
+
const HTML_ESCAPE_MAP = {
|
|
222
|
+
'&': '&',
|
|
223
|
+
'<': '<',
|
|
224
|
+
'>': '>',
|
|
225
|
+
'"': '"',
|
|
226
|
+
"'": ''',
|
|
227
|
+
'/': '/',
|
|
228
|
+
'`': '`',
|
|
229
|
+
'=': '=',
|
|
230
|
+
};
|
|
231
|
+
return text.replace(/[&<>"'/`=]/g, (char) => HTML_ESCAPE_MAP[char]);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Encrypts sensitive data using AES-256-GCM with authentication
|
|
235
|
+
* @llm-rule WHEN: Storing sensitive data like SSNs, credit cards, personal info
|
|
236
|
+
* @llm-rule AVOID: Storing sensitive data in plain text - always encrypt before database storage
|
|
237
|
+
* @llm-rule NOTE: Uses random IV per encryption, includes authentication tag to prevent tampering
|
|
238
|
+
*/
|
|
239
|
+
encrypt(data, key, associatedData) {
|
|
240
|
+
if (!data) {
|
|
241
|
+
throw createSecurityError('Data to encrypt cannot be empty');
|
|
242
|
+
}
|
|
243
|
+
const encryptionKey = key || this.config.encryption.key;
|
|
244
|
+
if (!encryptionKey) {
|
|
245
|
+
throw createSecurityError('Encryption key required. Provide as argument or set VOILA_SECURITY_ENCRYPTION_KEY environment variable', 500);
|
|
246
|
+
}
|
|
247
|
+
this.validateEncryptionKey(encryptionKey);
|
|
248
|
+
const keyBuffer = typeof encryptionKey === 'string'
|
|
249
|
+
? Buffer.from(encryptionKey, 'hex')
|
|
250
|
+
: encryptionKey;
|
|
251
|
+
try {
|
|
252
|
+
// Generate random IV for each encryption
|
|
253
|
+
const iv = crypto.randomBytes(this.config.encryption.ivLength);
|
|
254
|
+
const cipher = crypto.createCipheriv(this.config.encryption.algorithm, keyBuffer, iv);
|
|
255
|
+
// Set AAD if provided
|
|
256
|
+
if (associatedData) {
|
|
257
|
+
if (!Buffer.isBuffer(associatedData)) {
|
|
258
|
+
throw createSecurityError('Associated data must be a Buffer');
|
|
259
|
+
}
|
|
260
|
+
cipher.setAAD(associatedData);
|
|
261
|
+
}
|
|
262
|
+
// Encrypt data
|
|
263
|
+
const dataBuffer = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf8');
|
|
264
|
+
let encrypted = cipher.update(dataBuffer);
|
|
265
|
+
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
|
266
|
+
// Get authentication tag
|
|
267
|
+
const authTag = cipher.getAuthTag();
|
|
268
|
+
// Combine IV, ciphertext, and auth tag
|
|
269
|
+
return `${iv.toString('hex')}:${encrypted.toString('hex')}:${authTag.toString('hex')}`;
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
throw createSecurityError(`Encryption failed: ${error.message}`, 500);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Decrypts previously encrypted data with authentication verification
|
|
277
|
+
* @llm-rule WHEN: Retrieving sensitive data that was encrypted with encrypt() method
|
|
278
|
+
* @llm-rule AVOID: Using with data not encrypted by this module - will fail authentication
|
|
279
|
+
* @llm-rule NOTE: Automatically verifies authentication tag to detect tampering
|
|
280
|
+
*/
|
|
281
|
+
decrypt(encryptedData, key, associatedData) {
|
|
282
|
+
if (!encryptedData || typeof encryptedData !== 'string') {
|
|
283
|
+
throw createSecurityError('Encrypted data must be a non-empty string');
|
|
284
|
+
}
|
|
285
|
+
const decryptionKey = key || this.config.encryption.key;
|
|
286
|
+
if (!decryptionKey) {
|
|
287
|
+
throw createSecurityError('Decryption key required. Provide as argument or set VOILA_SECURITY_ENCRYPTION_KEY environment variable', 500);
|
|
288
|
+
}
|
|
289
|
+
this.validateEncryptionKey(decryptionKey);
|
|
290
|
+
const keyBuffer = typeof decryptionKey === 'string'
|
|
291
|
+
? Buffer.from(decryptionKey, 'hex')
|
|
292
|
+
: decryptionKey;
|
|
293
|
+
// Parse encrypted data format
|
|
294
|
+
const parts = encryptedData.split(':');
|
|
295
|
+
if (parts.length !== 3) {
|
|
296
|
+
throw createSecurityError('Invalid encrypted data format. Expected IV:ciphertext:authTag');
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
const iv = Buffer.from(parts[0], 'hex');
|
|
300
|
+
const encrypted = Buffer.from(parts[1], 'hex');
|
|
301
|
+
const authTag = Buffer.from(parts[2], 'hex');
|
|
302
|
+
// Validate component lengths
|
|
303
|
+
if (iv.length !== this.config.encryption.ivLength ||
|
|
304
|
+
authTag.length !== this.config.encryption.tagLength) {
|
|
305
|
+
throw createSecurityError('Invalid IV or authentication tag length');
|
|
306
|
+
}
|
|
307
|
+
// Create decipher
|
|
308
|
+
const decipher = crypto.createDecipheriv(this.config.encryption.algorithm, keyBuffer, iv);
|
|
309
|
+
// Set AAD if provided
|
|
310
|
+
if (associatedData) {
|
|
311
|
+
if (!Buffer.isBuffer(associatedData)) {
|
|
312
|
+
throw createSecurityError('Associated data must be a Buffer');
|
|
313
|
+
}
|
|
314
|
+
decipher.setAAD(associatedData);
|
|
315
|
+
}
|
|
316
|
+
// Set authentication tag
|
|
317
|
+
decipher.setAuthTag(authTag);
|
|
318
|
+
// Decrypt data
|
|
319
|
+
let decrypted = decipher.update(encrypted);
|
|
320
|
+
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|
321
|
+
return decrypted.toString('utf8');
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
if (error.code === 'EBADTAG') {
|
|
325
|
+
throw createSecurityError('Authentication failed: Data may be tampered with or incorrect key/AAD provided', 401);
|
|
326
|
+
}
|
|
327
|
+
throw createSecurityError(`Decryption failed: ${error.message}`, 500);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Generates a cryptographically secure 256-bit encryption key
|
|
332
|
+
* @llm-rule WHEN: Setting up encryption for the first time or rotating keys
|
|
333
|
+
* @llm-rule AVOID: Using weak or predictable keys - always use this method for key generation
|
|
334
|
+
* @llm-rule NOTE: Returns 64-character hex string suitable for VOILA_SECURITY_ENCRYPTION_KEY
|
|
335
|
+
*/
|
|
336
|
+
generateKey() {
|
|
337
|
+
try {
|
|
338
|
+
return crypto.randomBytes(this.config.encryption.keyLength).toString('hex');
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
throw createSecurityError(`Key generation failed: ${error.message}`, 500);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// Private helper methods
|
|
345
|
+
/**
|
|
346
|
+
* Generates a cryptographically secure CSRF token
|
|
347
|
+
*/
|
|
348
|
+
generateCSRFToken(session, expiryMinutes) {
|
|
349
|
+
if (!session || typeof session !== 'object') {
|
|
350
|
+
throw createSecurityError('Session object required for CSRF token generation', 500);
|
|
351
|
+
}
|
|
352
|
+
const token = crypto.randomBytes(16).toString('hex');
|
|
353
|
+
session.csrfToken = token;
|
|
354
|
+
session.csrfTokenExpiry = Date.now() + expiryMinutes * 60 * 1000;
|
|
355
|
+
return token;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Verifies CSRF token using timing-safe comparison
|
|
359
|
+
*/
|
|
360
|
+
verifyCSRFToken(token, session) {
|
|
361
|
+
if (!token || typeof token !== 'string' || !session?.csrfToken) {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
// Check expiry
|
|
365
|
+
if (session.csrfTokenExpiry && Date.now() > session.csrfTokenExpiry) {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
const expectedBuffer = Buffer.from(session.csrfToken, 'hex');
|
|
370
|
+
const actualBuffer = Buffer.from(token, 'hex');
|
|
371
|
+
if (expectedBuffer.length !== actualBuffer.length) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
return crypto.timingSafeEqual(expectedBuffer, actualBuffer);
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Gets unique identifier for the client
|
|
382
|
+
*/
|
|
383
|
+
getClientKey = (req) => {
|
|
384
|
+
return req.ip ||
|
|
385
|
+
req.connection?.remoteAddress ||
|
|
386
|
+
(req.headers && req.headers['x-forwarded-for']?.split(',')[0]?.trim()) ||
|
|
387
|
+
'unknown';
|
|
388
|
+
};
|
|
389
|
+
/**
|
|
390
|
+
* Initializes cleanup interval for memory management
|
|
391
|
+
*/
|
|
392
|
+
initializeCleanup(windowMs) {
|
|
393
|
+
if (this.cleanupInitialized)
|
|
394
|
+
return;
|
|
395
|
+
const cleanupInterval = Math.min(windowMs, 60 * 1000);
|
|
396
|
+
setInterval(() => {
|
|
397
|
+
const now = Date.now();
|
|
398
|
+
for (const [key, record] of this.requestStore.entries()) {
|
|
399
|
+
if (now > record.resetTime) {
|
|
400
|
+
this.requestStore.delete(key);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}, cleanupInterval).unref();
|
|
404
|
+
this.cleanupInitialized = true;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Validates encryption key format and length
|
|
408
|
+
*/
|
|
409
|
+
validateEncryptionKey(key) {
|
|
410
|
+
if (!key) {
|
|
411
|
+
throw createSecurityError('Encryption key is required', 500);
|
|
412
|
+
}
|
|
413
|
+
const keyBuffer = typeof key === 'string' ? Buffer.from(key, 'hex') : key;
|
|
414
|
+
if (keyBuffer.length !== this.config.encryption.keyLength) {
|
|
415
|
+
throw createSecurityError(`Invalid key length. Expected ${this.config.encryption.keyLength} bytes, got ${keyBuffer.length} bytes`, 500);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
//# sourceMappingURL=security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.js","sourceRoot":"","sources":["../../src/security/security.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart defaults and environment validation for file storage with auto-strategy detection
|
|
3
|
+
* @module @bloomneo/appkit/storage
|
|
4
|
+
* @file src/storage/defaults.ts
|
|
5
|
+
*
|
|
6
|
+
* @llm-rule WHEN: App startup - need to configure storage system and connection strategy
|
|
7
|
+
* @llm-rule AVOID: Calling multiple times - expensive environment parsing, use lazy loading in get()
|
|
8
|
+
* @llm-rule NOTE: Called once at startup, cached globally for performance
|
|
9
|
+
* @llm-rule NOTE: Auto-detects Local vs S3 vs R2 based on environment variables
|
|
10
|
+
*/
|
|
11
|
+
export interface LocalConfig {
|
|
12
|
+
dir: string;
|
|
13
|
+
baseUrl: string;
|
|
14
|
+
maxFileSize: number;
|
|
15
|
+
allowedTypes: string[];
|
|
16
|
+
createDirs: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface S3Config {
|
|
19
|
+
bucket: string;
|
|
20
|
+
region: string;
|
|
21
|
+
endpoint?: string;
|
|
22
|
+
accessKeyId: string;
|
|
23
|
+
secretAccessKey: string;
|
|
24
|
+
forcePathStyle: boolean;
|
|
25
|
+
signedUrlExpiry: number;
|
|
26
|
+
cdnUrl?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface R2Config {
|
|
29
|
+
bucket: string;
|
|
30
|
+
accountId: string;
|
|
31
|
+
accessKeyId: string;
|
|
32
|
+
secretAccessKey: string;
|
|
33
|
+
cdnUrl?: string;
|
|
34
|
+
signedUrlExpiry: number;
|
|
35
|
+
}
|
|
36
|
+
export interface StorageConfig {
|
|
37
|
+
strategy: 'local' | 's3' | 'r2';
|
|
38
|
+
local?: LocalConfig;
|
|
39
|
+
s3?: S3Config;
|
|
40
|
+
r2?: R2Config;
|
|
41
|
+
environment: {
|
|
42
|
+
isDevelopment: boolean;
|
|
43
|
+
isProduction: boolean;
|
|
44
|
+
isTest: boolean;
|
|
45
|
+
nodeEnv: string;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Gets smart defaults using environment variables with auto-strategy detection
|
|
50
|
+
* @llm-rule WHEN: App startup to get production-ready storage configuration
|
|
51
|
+
* @llm-rule AVOID: Calling repeatedly - expensive validation, cache the result
|
|
52
|
+
* @llm-rule NOTE: Auto-detects strategy: S3/R2 env vars → Cloud, nothing → Local
|
|
53
|
+
*/
|
|
54
|
+
export declare function getSmartDefaults(): StorageConfig;
|
|
55
|
+
/**
|
|
56
|
+
* Gets storage configuration summary for debugging and health checks
|
|
57
|
+
* @llm-rule WHEN: Debugging storage configuration or building health check endpoints
|
|
58
|
+
* @llm-rule AVOID: Exposing sensitive connection details - this only shows safe info
|
|
59
|
+
*/
|
|
60
|
+
export declare function getConfigSummary(): {
|
|
61
|
+
strategy: string;
|
|
62
|
+
local: boolean;
|
|
63
|
+
s3: boolean;
|
|
64
|
+
r2: boolean;
|
|
65
|
+
environment: string;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Checks if cloud storage is available and properly configured
|
|
69
|
+
* @llm-rule WHEN: Conditional logic based on storage capabilities
|
|
70
|
+
* @llm-rule AVOID: Complex storage detection - just use storage normally, strategy handles it
|
|
71
|
+
*/
|
|
72
|
+
export declare function hasCloudStorage(): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Gets recommended configuration for different deployment types
|
|
75
|
+
* @llm-rule WHEN: Setting up storage for specific deployment scenarios
|
|
76
|
+
* @llm-rule AVOID: Default config for specialized deployments - needs specific tuning
|
|
77
|
+
*/
|
|
78
|
+
export declare function getDeploymentConfig(type: 'development' | 'staging' | 'production'): Partial<StorageConfig>;
|
|
79
|
+
//# sourceMappingURL=defaults.d.ts.map
|