voltjs-framework 1.0.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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1265 -0
  3. package/bin/volt.js +139 -0
  4. package/package.json +56 -0
  5. package/src/api/graphql.js +399 -0
  6. package/src/api/rest.js +204 -0
  7. package/src/api/websocket.js +285 -0
  8. package/src/cli/build.js +111 -0
  9. package/src/cli/create.js +371 -0
  10. package/src/cli/db.js +106 -0
  11. package/src/cli/dev.js +114 -0
  12. package/src/cli/generate.js +278 -0
  13. package/src/cli/lint.js +172 -0
  14. package/src/cli/routes.js +118 -0
  15. package/src/cli/start.js +42 -0
  16. package/src/cli/test.js +138 -0
  17. package/src/core/app.js +701 -0
  18. package/src/core/config.js +232 -0
  19. package/src/core/middleware.js +133 -0
  20. package/src/core/plugins.js +88 -0
  21. package/src/core/react-renderer.js +244 -0
  22. package/src/core/renderer.js +337 -0
  23. package/src/core/router.js +183 -0
  24. package/src/database/index.js +461 -0
  25. package/src/database/migration.js +192 -0
  26. package/src/database/model.js +285 -0
  27. package/src/database/query.js +394 -0
  28. package/src/database/seeder.js +89 -0
  29. package/src/index.js +156 -0
  30. package/src/security/auth.js +425 -0
  31. package/src/security/cors.js +80 -0
  32. package/src/security/csrf.js +125 -0
  33. package/src/security/encryption.js +110 -0
  34. package/src/security/helmet.js +103 -0
  35. package/src/security/index.js +75 -0
  36. package/src/security/rateLimit.js +119 -0
  37. package/src/security/sanitizer.js +113 -0
  38. package/src/security/xss.js +110 -0
  39. package/src/ui/component.js +224 -0
  40. package/src/ui/reactive.js +503 -0
  41. package/src/ui/template.js +448 -0
  42. package/src/utils/cache.js +216 -0
  43. package/src/utils/collection.js +772 -0
  44. package/src/utils/cron.js +213 -0
  45. package/src/utils/date.js +223 -0
  46. package/src/utils/events.js +181 -0
  47. package/src/utils/excel.js +482 -0
  48. package/src/utils/form.js +547 -0
  49. package/src/utils/hash.js +121 -0
  50. package/src/utils/http.js +461 -0
  51. package/src/utils/logger.js +186 -0
  52. package/src/utils/mail.js +347 -0
  53. package/src/utils/paginator.js +179 -0
  54. package/src/utils/pdf.js +417 -0
  55. package/src/utils/queue.js +199 -0
  56. package/src/utils/schema.js +985 -0
  57. package/src/utils/sms.js +243 -0
  58. package/src/utils/storage.js +348 -0
  59. package/src/utils/string.js +236 -0
  60. package/src/utils/validation.js +318 -0
@@ -0,0 +1,80 @@
1
+ /**
2
+ * VoltJS CORS Handler
3
+ *
4
+ * Cross-Origin Resource Sharing configuration.
5
+ * Secure by default, fully customizable.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ class CORSHandler {
11
+ /** Apply CORS headers to a response */
12
+ static apply(req, res, options = {}) {
13
+ const config = {
14
+ origin: options.origin || '*',
15
+ methods: options.methods || ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
16
+ allowedHeaders: options.allowedHeaders || ['Content-Type', 'Authorization', 'X-Requested-With', 'X-CSRF-Token'],
17
+ exposedHeaders: options.exposedHeaders || [],
18
+ credentials: options.credentials !== undefined ? options.credentials : true,
19
+ maxAge: options.maxAge || 86400,
20
+ };
21
+
22
+ // Handle origin
23
+ const requestOrigin = req.headers.origin;
24
+
25
+ if (typeof config.origin === 'function') {
26
+ const allowed = config.origin(requestOrigin);
27
+ if (allowed) {
28
+ res.setHeader('Access-Control-Allow-Origin', typeof allowed === 'string' ? allowed : requestOrigin);
29
+ }
30
+ } else if (Array.isArray(config.origin)) {
31
+ if (config.origin.includes(requestOrigin)) {
32
+ res.setHeader('Access-Control-Allow-Origin', requestOrigin);
33
+ }
34
+ } else if (config.origin === '*' && !config.credentials) {
35
+ res.setHeader('Access-Control-Allow-Origin', '*');
36
+ } else if (requestOrigin) {
37
+ if (config.origin === '*' || config.origin === requestOrigin) {
38
+ res.setHeader('Access-Control-Allow-Origin', requestOrigin);
39
+ }
40
+ }
41
+
42
+ // Methods
43
+ res.setHeader('Access-Control-Allow-Methods', config.methods.join(', '));
44
+
45
+ // Allowed headers
46
+ res.setHeader('Access-Control-Allow-Headers', config.allowedHeaders.join(', '));
47
+
48
+ // Exposed headers
49
+ if (config.exposedHeaders.length > 0) {
50
+ res.setHeader('Access-Control-Expose-Headers', config.exposedHeaders.join(', '));
51
+ }
52
+
53
+ // Credentials
54
+ if (config.credentials) {
55
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
56
+ }
57
+
58
+ // Max age (for preflight cache)
59
+ res.setHeader('Access-Control-Max-Age', String(config.maxAge));
60
+
61
+ // Vary header
62
+ res.setHeader('Vary', 'Origin');
63
+ }
64
+
65
+ /** Middleware: CORS */
66
+ static middleware(options = {}) {
67
+ return (req, res) => {
68
+ CORSHandler.apply(req, res, options);
69
+
70
+ // Handle preflight requests
71
+ if (req.method === 'OPTIONS') {
72
+ res.writeHead(204);
73
+ res.end();
74
+ return false;
75
+ }
76
+ };
77
+ }
78
+ }
79
+
80
+ module.exports = { CORSHandler };
@@ -0,0 +1,125 @@
1
+ /**
2
+ * VoltJS CSRF Protection
3
+ *
4
+ * Protects against Cross-Site Request Forgery attacks using
5
+ * double-submit cookie pattern and synchronizer tokens.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const crypto = require('crypto');
11
+
12
+ class CSRF {
13
+ constructor(options = {}) {
14
+ this._secret = options.secret || crypto.randomBytes(32).toString('hex');
15
+ this._tokenLength = options.tokenLength || 32;
16
+ this._cookieName = options.cookieName || '_volt_csrf';
17
+ this._headerName = options.headerName || 'x-csrf-token';
18
+ this._fieldName = options.fieldName || '_csrf';
19
+ this._safeMethods = new Set(['GET', 'HEAD', 'OPTIONS']);
20
+ this._excludePaths = new Set(options.exclude || []);
21
+ this._tokens = new Map();
22
+ }
23
+
24
+ /** Generate a CSRF token */
25
+ generateToken(sessionId = 'default') {
26
+ const token = crypto.randomBytes(this._tokenLength).toString('hex');
27
+ const salt = crypto.randomBytes(8).toString('hex');
28
+ const hash = crypto
29
+ .createHmac('sha256', this._secret)
30
+ .update(`${token}${salt}`)
31
+ .digest('hex');
32
+
33
+ this._tokens.set(token, {
34
+ hash,
35
+ sessionId,
36
+ createdAt: Date.now(),
37
+ expiresAt: Date.now() + 60 * 60 * 1000, // 1 hour
38
+ });
39
+
40
+ return token;
41
+ }
42
+
43
+ /** Verify a CSRF token */
44
+ verifyToken(token, sessionId = 'default') {
45
+ const stored = this._tokens.get(token);
46
+ if (!stored) return false;
47
+ if (stored.expiresAt < Date.now()) {
48
+ this._tokens.delete(token);
49
+ return false;
50
+ }
51
+ if (stored.sessionId !== sessionId) return false;
52
+
53
+ // Delete after use (one-time use tokens)
54
+ this._tokens.delete(token);
55
+ return true;
56
+ }
57
+
58
+ /** Middleware: CSRF protection */
59
+ middleware(options = {}) {
60
+ return (req, res) => {
61
+ // Skip safe methods
62
+ if (this._safeMethods.has(req.method)) {
63
+ // Attach token generator to response for forms
64
+ req.csrfToken = () => {
65
+ const token = this.generateToken(req.cookies?.[this._cookieName] || 'default');
66
+ res.cookie(this._cookieName, token, {
67
+ httpOnly: true,
68
+ sameSite: 'Strict',
69
+ secure: req.headers['x-forwarded-proto'] === 'https',
70
+ });
71
+ return token;
72
+ };
73
+ return;
74
+ }
75
+
76
+ // Skip excluded paths
77
+ if (this._excludePaths.has(req.path)) return;
78
+
79
+ // Get token from request
80
+ const token =
81
+ req.body?.[this._fieldName] ||
82
+ req.headers[this._headerName] ||
83
+ req.query?.[this._fieldName];
84
+
85
+ if (!token) {
86
+ res.json({ error: 'CSRF token missing' }, 403);
87
+ return false;
88
+ }
89
+
90
+ if (!this.verifyToken(token)) {
91
+ res.json({ error: 'CSRF token invalid' }, 403);
92
+ return false;
93
+ }
94
+
95
+ // Generate new token for next request
96
+ req.csrfToken = () => this.generateToken();
97
+ };
98
+ }
99
+
100
+ /** Cleanup expired tokens periodically */
101
+ cleanup() {
102
+ const now = Date.now();
103
+ for (const [token, data] of this._tokens) {
104
+ if (data.expiresAt < now) {
105
+ this._tokens.delete(token);
106
+ }
107
+ }
108
+ }
109
+
110
+ /** Start auto-cleanup */
111
+ startAutoCleanup(intervalMs = 300000) {
112
+ this._cleanupInterval = setInterval(() => this.cleanup(), intervalMs);
113
+ return this;
114
+ }
115
+
116
+ /** Stop auto-cleanup */
117
+ stopAutoCleanup() {
118
+ if (this._cleanupInterval) {
119
+ clearInterval(this._cleanupInterval);
120
+ }
121
+ return this;
122
+ }
123
+ }
124
+
125
+ module.exports = { CSRF };
@@ -0,0 +1,110 @@
1
+ /**
2
+ * VoltJS Encryption Utilities
3
+ *
4
+ * AES-256-GCM encryption, key derivation, and token generation.
5
+ * Uses Node.js native crypto — zero external dependencies.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const crypto = require('crypto');
11
+
12
+ class Encryption {
13
+ constructor(secretKey) {
14
+ this._key = secretKey
15
+ ? crypto.scryptSync(secretKey, 'volt-salt', 32)
16
+ : crypto.randomBytes(32);
17
+ }
18
+
19
+ /** Encrypt a string using AES-256-GCM */
20
+ encrypt(plaintext) {
21
+ const iv = crypto.randomBytes(16);
22
+ const cipher = crypto.createCipheriv('aes-256-gcm', this._key, iv);
23
+
24
+ let encrypted = cipher.update(plaintext, 'utf8', 'hex');
25
+ encrypted += cipher.final('hex');
26
+ const authTag = cipher.getAuthTag();
27
+
28
+ return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
29
+ }
30
+
31
+ /** Decrypt an AES-256-GCM encrypted string */
32
+ decrypt(ciphertext) {
33
+ try {
34
+ const [ivHex, authTagHex, encrypted] = ciphertext.split(':');
35
+ const iv = Buffer.from(ivHex, 'hex');
36
+ const authTag = Buffer.from(authTagHex, 'hex');
37
+ const decipher = crypto.createDecipheriv('aes-256-gcm', this._key, iv);
38
+ decipher.setAuthTag(authTag);
39
+
40
+ let decrypted = decipher.update(encrypted, 'hex', 'utf8');
41
+ decrypted += decipher.final('utf8');
42
+ return decrypted;
43
+ } catch {
44
+ throw new Error('Decryption failed — invalid ciphertext or key');
45
+ }
46
+ }
47
+
48
+ /** Encrypt an object (serializes to JSON) */
49
+ encryptObject(obj) {
50
+ return this.encrypt(JSON.stringify(obj));
51
+ }
52
+
53
+ /** Decrypt to an object */
54
+ decryptObject(ciphertext) {
55
+ return JSON.parse(this.decrypt(ciphertext));
56
+ }
57
+
58
+ /** Generate a cryptographically secure random token */
59
+ static generateToken(length = 32) {
60
+ return crypto.randomBytes(length).toString('hex');
61
+ }
62
+
63
+ /** Generate a secure random string (URL-safe) */
64
+ static generateUrlSafeToken(length = 32) {
65
+ return crypto.randomBytes(length).toString('base64url');
66
+ }
67
+
68
+ /** Generate a numeric OTP */
69
+ static generateOTP(digits = 6) {
70
+ const max = Math.pow(10, digits);
71
+ const otp = crypto.randomInt(0, max);
72
+ return String(otp).padStart(digits, '0');
73
+ }
74
+
75
+ /** SHA-256 hash */
76
+ static sha256(input) {
77
+ return crypto.createHash('sha256').update(input).digest('hex');
78
+ }
79
+
80
+ /** SHA-512 hash */
81
+ static sha512(input) {
82
+ return crypto.createHash('sha512').update(input).digest('hex');
83
+ }
84
+
85
+ /** MD5 hash (not for security — for checksums) */
86
+ static md5(input) {
87
+ return crypto.createHash('md5').update(input).digest('hex');
88
+ }
89
+
90
+ /** HMAC-SHA256 */
91
+ static hmac(input, secret) {
92
+ return crypto.createHmac('sha256', secret).update(input).digest('hex');
93
+ }
94
+
95
+ /** Compare two strings in constant time (prevent timing attacks) */
96
+ static timingSafeCompare(a, b) {
97
+ if (typeof a !== 'string' || typeof b !== 'string') return false;
98
+ if (a.length !== b.length) return false;
99
+ return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
100
+ }
101
+
102
+ /** Derive a key from password using scrypt */
103
+ static deriveKey(password, salt, keyLength = 32) {
104
+ const actualSalt = salt || crypto.randomBytes(16).toString('hex');
105
+ const key = crypto.scryptSync(password, actualSalt, keyLength);
106
+ return { key: key.toString('hex'), salt: actualSalt };
107
+ }
108
+ }
109
+
110
+ module.exports = { Encryption };
@@ -0,0 +1,103 @@
1
+ /**
2
+ * VoltJS Security Headers (Helmet)
3
+ *
4
+ * Sets secure HTTP headers to protect against common web vulnerabilities.
5
+ * All protections enabled by default.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ class SecurityHeaders {
11
+ /** Apply all security headers */
12
+ static apply(res, options = {}) {
13
+ const headers = {
14
+ // Prevent MIME type sniffing
15
+ 'X-Content-Type-Options': 'nosniff',
16
+
17
+ // Prevent clickjacking
18
+ 'X-Frame-Options': options.frameOptions || 'DENY',
19
+
20
+ // XSS filter
21
+ 'X-XSS-Protection': '1; mode=block',
22
+
23
+ // Referrer policy
24
+ 'Referrer-Policy': options.referrerPolicy || 'strict-origin-when-cross-origin',
25
+
26
+ // Strict Transport Security
27
+ 'Strict-Transport-Security': options.hsts || 'max-age=31536000; includeSubDomains; preload',
28
+
29
+ // Content Security Policy
30
+ 'Content-Security-Policy': options.csp || SecurityHeaders._defaultCSP(options),
31
+
32
+ // Permissions Policy
33
+ 'Permissions-Policy': options.permissionsPolicy || SecurityHeaders._defaultPermissions(),
34
+
35
+ // Prevent browsers from caching sensitive pages
36
+ ...(options.noCache && {
37
+ 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
38
+ 'Pragma': 'no-cache',
39
+ 'Expires': '0',
40
+ 'Surrogate-Control': 'no-store',
41
+ }),
42
+
43
+ // Cross-Origin headers
44
+ 'Cross-Origin-Opener-Policy': options.coop || 'same-origin',
45
+ 'Cross-Origin-Resource-Policy': options.corp || 'same-origin',
46
+
47
+ // Remove X-Powered-By (optionally re-add as VoltJS)
48
+ 'X-Powered-By': options.poweredBy !== false ? 'VoltJS' : undefined,
49
+ };
50
+
51
+ for (const [key, value] of Object.entries(headers)) {
52
+ if (value !== undefined) {
53
+ res.setHeader(key, value);
54
+ }
55
+ }
56
+ }
57
+
58
+ /** Middleware: Apply security headers */
59
+ static middleware(options = {}) {
60
+ return (req, res) => {
61
+ SecurityHeaders.apply(res, options);
62
+ };
63
+ }
64
+
65
+ /** Default Content Security Policy */
66
+ static _defaultCSP(options = {}) {
67
+ const directives = {
68
+ 'default-src': ["'self'"],
69
+ 'script-src': ["'self'"],
70
+ 'style-src': ["'self'", "'unsafe-inline'"],
71
+ 'img-src': ["'self'", 'data:', 'https:'],
72
+ 'font-src': ["'self'"],
73
+ 'connect-src': ["'self'"],
74
+ 'media-src': ["'self'"],
75
+ 'object-src': ["'none'"],
76
+ 'frame-src': ["'none'"],
77
+ 'base-uri': ["'self'"],
78
+ 'form-action': ["'self'"],
79
+ 'frame-ancestors': ["'none'"],
80
+ ...(options.cspDirectives || {}),
81
+ };
82
+
83
+ return Object.entries(directives)
84
+ .map(([key, values]) => `${key} ${values.join(' ')}`)
85
+ .join('; ');
86
+ }
87
+
88
+ /** Default Permissions Policy */
89
+ static _defaultPermissions() {
90
+ return [
91
+ 'camera=()',
92
+ 'microphone=()',
93
+ 'geolocation=()',
94
+ 'payment=()',
95
+ 'usb=()',
96
+ 'magnetometer=()',
97
+ 'gyroscope=()',
98
+ 'accelerometer=()',
99
+ ].join(', ');
100
+ }
101
+ }
102
+
103
+ module.exports = { SecurityHeaders };
@@ -0,0 +1,75 @@
1
+ /**
2
+ * VoltJS Security Module
3
+ *
4
+ * All-in-one security for your application.
5
+ * Everything is ON by default — secure out of the box.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const { Auth } = require('./auth');
11
+ const { CSRF } = require('./csrf');
12
+ const { XSSProtection } = require('./xss');
13
+ const { CORSHandler } = require('./cors');
14
+ const { RateLimiter } = require('./rateLimit');
15
+ const { Encryption } = require('./encryption');
16
+ const { SecurityHeaders } = require('./helmet');
17
+ const { InputSanitizer } = require('./sanitizer');
18
+
19
+ class Security {
20
+ /**
21
+ * Apply all security middleware at once
22
+ */
23
+ static all(options = {}) {
24
+ return (req, res, next) => {
25
+ // Apply security headers
26
+ SecurityHeaders.apply(res, options.headers);
27
+
28
+ // CORS
29
+ CORSHandler.apply(req, res, options.cors);
30
+
31
+ // Rate limiting
32
+ const rateResult = RateLimiter.check(req, options.rateLimit);
33
+ if (!rateResult.allowed) {
34
+ res.writeHead(429, { 'Content-Type': 'application/json', 'Retry-After': rateResult.retryAfter });
35
+ res.end(JSON.stringify({ error: 'Too many requests', retryAfter: rateResult.retryAfter }));
36
+ return;
37
+ }
38
+
39
+ // XSS sanitize input
40
+ if (req.body && options.xss !== false) {
41
+ req.body = XSSProtection.sanitizeObject(req.body);
42
+ }
43
+ if (req.query && options.xss !== false) {
44
+ req.query = XSSProtection.sanitizeObject(req.query);
45
+ }
46
+
47
+ // Input sanitization
48
+ if (options.sanitize !== false) {
49
+ InputSanitizer.sanitizeRequest(req);
50
+ }
51
+
52
+ next();
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Quick setup: add all security to a Volt app
58
+ */
59
+ static secure(app, options = {}) {
60
+ app.use(Security.all(options));
61
+ return app;
62
+ }
63
+ }
64
+
65
+ module.exports = {
66
+ Security,
67
+ Auth,
68
+ CSRF,
69
+ XSSProtection,
70
+ CORSHandler,
71
+ RateLimiter,
72
+ Encryption,
73
+ SecurityHeaders,
74
+ InputSanitizer,
75
+ };
@@ -0,0 +1,119 @@
1
+ /**
2
+ * VoltJS Rate Limiter
3
+ *
4
+ * Protects against brute force and DDoS attacks.
5
+ * Memory-based by default, supports custom stores.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ class RateLimiter {
11
+ constructor(options = {}) {
12
+ this._windowMs = options.windowMs || 15 * 60 * 1000; // 15 min
13
+ this._max = options.max || 100;
14
+ this._message = options.message || 'Too many requests, please try again later';
15
+ this._statusCode = options.statusCode || 429;
16
+ this._keyGenerator = options.keyGenerator || ((req) => {
17
+ return req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
18
+ });
19
+ this._skipIf = options.skipIf || null;
20
+ this._store = new Map();
21
+ this._cleanup = setInterval(() => this._cleanupExpired(), this._windowMs);
22
+ }
23
+
24
+ /** Check if a request is rate limited */
25
+ check(req) {
26
+ const key = this._keyGenerator(req);
27
+ const now = Date.now();
28
+
29
+ if (!this._store.has(key)) {
30
+ this._store.set(key, { count: 1, startTime: now, resetTime: now + this._windowMs });
31
+ return { allowed: true, remaining: this._max - 1, resetTime: now + this._windowMs };
32
+ }
33
+
34
+ const entry = this._store.get(key);
35
+
36
+ // Check if window has expired
37
+ if (now > entry.resetTime) {
38
+ this._store.set(key, { count: 1, startTime: now, resetTime: now + this._windowMs });
39
+ return { allowed: true, remaining: this._max - 1, resetTime: now + this._windowMs };
40
+ }
41
+
42
+ entry.count++;
43
+ this._store.set(key, entry);
44
+
45
+ if (entry.count > this._max) {
46
+ return {
47
+ allowed: false,
48
+ remaining: 0,
49
+ resetTime: entry.resetTime,
50
+ retryAfter: Math.ceil((entry.resetTime - now) / 1000),
51
+ };
52
+ }
53
+
54
+ return { allowed: true, remaining: this._max - entry.count, resetTime: entry.resetTime };
55
+ }
56
+
57
+ /** Middleware: Rate limiting */
58
+ static middleware(options = {}) {
59
+ const limiter = new RateLimiter(options);
60
+
61
+ return (req, res) => {
62
+ // Skip if condition met
63
+ if (limiter._skipIf && limiter._skipIf(req)) return;
64
+
65
+ const result = limiter.check(req);
66
+
67
+ // Set rate limit headers
68
+ res.setHeader('X-RateLimit-Limit', limiter._max);
69
+ res.setHeader('X-RateLimit-Remaining', Math.max(0, result.remaining));
70
+ res.setHeader('X-RateLimit-Reset', Math.ceil(result.resetTime / 1000));
71
+
72
+ if (!result.allowed) {
73
+ res.setHeader('Retry-After', result.retryAfter);
74
+ res.json({
75
+ error: limiter._message,
76
+ retryAfter: result.retryAfter,
77
+ }, limiter._statusCode);
78
+ return false;
79
+ }
80
+ };
81
+ }
82
+
83
+ /** Create API-specific rate limiter (stricter) */
84
+ static apiLimiter(options = {}) {
85
+ return RateLimiter.middleware({
86
+ windowMs: options.windowMs || 60 * 1000, // 1 minute
87
+ max: options.max || 30,
88
+ ...options,
89
+ });
90
+ }
91
+
92
+ /** Create auth-specific rate limiter (very strict) */
93
+ static authLimiter(options = {}) {
94
+ return RateLimiter.middleware({
95
+ windowMs: options.windowMs || 15 * 60 * 1000, // 15 min
96
+ max: options.max || 5,
97
+ message: 'Too many login attempts, please try again later',
98
+ ...options,
99
+ });
100
+ }
101
+
102
+ /** Clean up expired entries */
103
+ _cleanupExpired() {
104
+ const now = Date.now();
105
+ for (const [key, entry] of this._store) {
106
+ if (now > entry.resetTime) {
107
+ this._store.delete(key);
108
+ }
109
+ }
110
+ }
111
+
112
+ /** Destroy the rate limiter (clear interval) */
113
+ destroy() {
114
+ clearInterval(this._cleanup);
115
+ this._store.clear();
116
+ }
117
+ }
118
+
119
+ module.exports = { RateLimiter };