create-phoenixjs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +196 -0
- package/package.json +31 -0
- package/template/README.md +62 -0
- package/template/app/controllers/ExampleController.ts +61 -0
- package/template/artisan +2 -0
- package/template/bootstrap/app.ts +44 -0
- package/template/bunfig.toml +7 -0
- package/template/config/database.ts +25 -0
- package/template/config/plugins.ts +7 -0
- package/template/config/security.ts +158 -0
- package/template/framework/cli/Command.ts +17 -0
- package/template/framework/cli/ConsoleApplication.ts +55 -0
- package/template/framework/cli/artisan.ts +16 -0
- package/template/framework/cli/commands/MakeControllerCommand.ts +41 -0
- package/template/framework/cli/commands/MakeMiddlewareCommand.ts +41 -0
- package/template/framework/cli/commands/MakeModelCommand.ts +36 -0
- package/template/framework/cli/commands/MakeValidatorCommand.ts +42 -0
- package/template/framework/controller/Controller.ts +222 -0
- package/template/framework/core/Application.ts +208 -0
- package/template/framework/core/Container.ts +100 -0
- package/template/framework/core/Kernel.ts +297 -0
- package/template/framework/database/DatabaseAdapter.ts +18 -0
- package/template/framework/database/PrismaAdapter.ts +65 -0
- package/template/framework/database/SqlAdapter.ts +117 -0
- package/template/framework/gateway/Gateway.ts +109 -0
- package/template/framework/gateway/GatewayManager.ts +150 -0
- package/template/framework/gateway/WebSocketAdapter.ts +159 -0
- package/template/framework/gateway/WebSocketGateway.ts +182 -0
- package/template/framework/http/Request.ts +608 -0
- package/template/framework/http/Response.ts +525 -0
- package/template/framework/http/Server.ts +161 -0
- package/template/framework/http/UploadedFile.ts +145 -0
- package/template/framework/middleware/Middleware.ts +50 -0
- package/template/framework/middleware/Pipeline.ts +89 -0
- package/template/framework/plugin/Plugin.ts +26 -0
- package/template/framework/plugin/PluginManager.ts +61 -0
- package/template/framework/routing/RouteRegistry.ts +185 -0
- package/template/framework/routing/Router.ts +280 -0
- package/template/framework/security/CorsMiddleware.ts +151 -0
- package/template/framework/security/CsrfMiddleware.ts +121 -0
- package/template/framework/security/HelmetMiddleware.ts +138 -0
- package/template/framework/security/InputSanitizerMiddleware.ts +134 -0
- package/template/framework/security/RateLimiterMiddleware.ts +189 -0
- package/template/framework/security/SecurityManager.ts +128 -0
- package/template/framework/validation/Validator.ts +482 -0
- package/template/package.json +24 -0
- package/template/routes/api.ts +56 -0
- package/template/server.ts +29 -0
- package/template/tsconfig.json +49 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PhoenixJS - Input Sanitizer Middleware
|
|
3
|
+
*
|
|
4
|
+
* Sanitizes request input to prevent XSS and other injection attacks.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Middleware, NextFunction } from '@framework/middleware/Middleware';
|
|
8
|
+
import type { FrameworkRequest } from '@framework/http/Request';
|
|
9
|
+
import type { SanitizerConfig } from '@/config/security';
|
|
10
|
+
|
|
11
|
+
export class InputSanitizerMiddleware implements Middleware {
|
|
12
|
+
constructor(private config: SanitizerConfig) { }
|
|
13
|
+
|
|
14
|
+
async handle(request: FrameworkRequest, next: NextFunction): Promise<Response> {
|
|
15
|
+
// Skip if sanitization is disabled
|
|
16
|
+
if (!this.config.enabled) {
|
|
17
|
+
return next(request);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Sanitize the request input
|
|
21
|
+
// Note: We sanitize the parsed body when it's accessed
|
|
22
|
+
// The sanitization happens at the framework level
|
|
23
|
+
|
|
24
|
+
return next(request);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Sanitize a value (string, object, or array)
|
|
29
|
+
*/
|
|
30
|
+
sanitize(value: unknown, fieldPath: string = ''): unknown {
|
|
31
|
+
// Skip if field is in skip list
|
|
32
|
+
if (this.shouldSkipField(fieldPath)) {
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (typeof value === 'string') {
|
|
37
|
+
return this.sanitizeString(value);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (Array.isArray(value)) {
|
|
41
|
+
return value.map((item, index) => this.sanitize(item, `${fieldPath}[${index}]`));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (value !== null && typeof value === 'object') {
|
|
45
|
+
const sanitized: Record<string, unknown> = {};
|
|
46
|
+
for (const [key, val] of Object.entries(value as Record<string, unknown>)) {
|
|
47
|
+
const newPath = fieldPath ? `${fieldPath}.${key}` : key;
|
|
48
|
+
sanitized[key] = this.sanitize(val, newPath);
|
|
49
|
+
}
|
|
50
|
+
return sanitized;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Sanitize a string value
|
|
58
|
+
*/
|
|
59
|
+
private sanitizeString(value: string): string {
|
|
60
|
+
let result = value;
|
|
61
|
+
|
|
62
|
+
// Trim whitespace
|
|
63
|
+
if (this.config.trim) {
|
|
64
|
+
result = result.trim();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Strip HTML tags
|
|
68
|
+
if (this.config.stripTags) {
|
|
69
|
+
result = this.stripHtmlTags(result);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Escape HTML entities
|
|
73
|
+
if (this.config.escapeHtml) {
|
|
74
|
+
result = this.escapeHtmlEntities(result);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Strip HTML tags from a string
|
|
82
|
+
*/
|
|
83
|
+
private stripHtmlTags(value: string): string {
|
|
84
|
+
// Remove script and style tags completely (with content)
|
|
85
|
+
let result = value.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
|
|
86
|
+
result = result.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '');
|
|
87
|
+
|
|
88
|
+
// Remove all other HTML tags (but keep content)
|
|
89
|
+
result = result.replace(/<[^>]*>/g, '');
|
|
90
|
+
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Escape HTML entities
|
|
96
|
+
*/
|
|
97
|
+
private escapeHtmlEntities(value: string): string {
|
|
98
|
+
const entities: Record<string, string> = {
|
|
99
|
+
'&': '&',
|
|
100
|
+
'<': '<',
|
|
101
|
+
'>': '>',
|
|
102
|
+
'"': '"',
|
|
103
|
+
"'": ''',
|
|
104
|
+
'/': '/',
|
|
105
|
+
'`': '`',
|
|
106
|
+
'=': '=',
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return value.replace(/[&<>"'`=/]/g, (char) => entities[char] || char);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Check if a field should be skipped
|
|
114
|
+
*/
|
|
115
|
+
private shouldSkipField(fieldPath: string): boolean {
|
|
116
|
+
const fieldName = fieldPath.split('.').pop() || fieldPath;
|
|
117
|
+
return this.config.skipFields.includes(fieldName);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Create an Input Sanitizer middleware instance with the given config
|
|
123
|
+
*/
|
|
124
|
+
export function sanitizer(config: Partial<SanitizerConfig> = {}): InputSanitizerMiddleware {
|
|
125
|
+
const defaultConfig: SanitizerConfig = {
|
|
126
|
+
enabled: true,
|
|
127
|
+
stripTags: true,
|
|
128
|
+
escapeHtml: true,
|
|
129
|
+
trim: true,
|
|
130
|
+
skipFields: ['password', 'html', 'content'],
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return new InputSanitizerMiddleware({ ...defaultConfig, ...config });
|
|
134
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PhoenixJS - Rate Limiter Middleware
|
|
3
|
+
*
|
|
4
|
+
* Provides rate limiting to protect against abuse.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Middleware, NextFunction } from '@framework/middleware/Middleware';
|
|
8
|
+
import type { FrameworkRequest } from '@framework/http/Request';
|
|
9
|
+
import type { RateLimitConfig } from '@/config/security';
|
|
10
|
+
import { FrameworkResponse } from '@framework/http/Response';
|
|
11
|
+
|
|
12
|
+
interface RateLimitEntry {
|
|
13
|
+
count: number;
|
|
14
|
+
resetTime: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class RateLimiterMiddleware implements Middleware {
|
|
18
|
+
private store: Map<string, RateLimitEntry> = new Map();
|
|
19
|
+
private cleanupInterval: ReturnType<typeof setInterval> | null = null;
|
|
20
|
+
|
|
21
|
+
constructor(private config: RateLimitConfig) {
|
|
22
|
+
// Start cleanup interval to prevent memory leaks
|
|
23
|
+
this.startCleanup();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async handle(request: FrameworkRequest, next: NextFunction): Promise<Response> {
|
|
27
|
+
// Check if rate limiting is enabled
|
|
28
|
+
if (!this.config.enabled) {
|
|
29
|
+
return next(request);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check if this request should be skipped
|
|
33
|
+
if (this.config.skip && this.config.skip(request.getOriginal())) {
|
|
34
|
+
return next(request);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Get the rate limit key (default: IP address)
|
|
38
|
+
const key = this.getKey(request);
|
|
39
|
+
|
|
40
|
+
// Get or create rate limit entry
|
|
41
|
+
const entry = this.getEntry(key);
|
|
42
|
+
|
|
43
|
+
// Check if rate limit exceeded
|
|
44
|
+
if (entry.count >= this.config.max) {
|
|
45
|
+
const retryAfter = Math.ceil((entry.resetTime - Date.now()) / 1000);
|
|
46
|
+
return this.createRateLimitResponse(entry, retryAfter);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Increment count
|
|
50
|
+
entry.count++;
|
|
51
|
+
this.store.set(key, entry);
|
|
52
|
+
|
|
53
|
+
// Process the request
|
|
54
|
+
const response = await next(request);
|
|
55
|
+
|
|
56
|
+
// Add rate limit headers
|
|
57
|
+
return this.addRateLimitHeaders(response, entry);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get the rate limit key for a request
|
|
62
|
+
*/
|
|
63
|
+
private getKey(request: FrameworkRequest): string {
|
|
64
|
+
if (this.config.keyGenerator) {
|
|
65
|
+
return this.config.keyGenerator(request.getOriginal());
|
|
66
|
+
}
|
|
67
|
+
// Default: use IP address (with fallback for unknown)
|
|
68
|
+
return request.ip() || 'unknown';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get or create a rate limit entry
|
|
73
|
+
*/
|
|
74
|
+
private getEntry(key: string): RateLimitEntry {
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
let entry = this.store.get(key);
|
|
77
|
+
|
|
78
|
+
// If no entry or window expired, create new one
|
|
79
|
+
if (!entry || now >= entry.resetTime) {
|
|
80
|
+
entry = {
|
|
81
|
+
count: 0,
|
|
82
|
+
resetTime: now + this.config.windowMs,
|
|
83
|
+
};
|
|
84
|
+
this.store.set(key, entry);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return entry;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Create a rate limit exceeded response
|
|
92
|
+
*/
|
|
93
|
+
private createRateLimitResponse(entry: RateLimitEntry, retryAfter: number): Response {
|
|
94
|
+
const headers: Record<string, string> = {
|
|
95
|
+
'Content-Type': 'application/json',
|
|
96
|
+
'Retry-After': retryAfter.toString(),
|
|
97
|
+
'X-RateLimit-Limit': this.config.max.toString(),
|
|
98
|
+
'X-RateLimit-Remaining': '0',
|
|
99
|
+
'X-RateLimit-Reset': Math.ceil(entry.resetTime / 1000).toString(),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return new Response(
|
|
103
|
+
JSON.stringify({
|
|
104
|
+
error: true,
|
|
105
|
+
message: this.config.message,
|
|
106
|
+
}),
|
|
107
|
+
{
|
|
108
|
+
status: 429,
|
|
109
|
+
headers,
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Add rate limit headers to a response
|
|
116
|
+
*/
|
|
117
|
+
private addRateLimitHeaders(response: Response, entry: RateLimitEntry): Response {
|
|
118
|
+
const headers = new Headers(response.headers);
|
|
119
|
+
const remaining = Math.max(0, this.config.max - entry.count);
|
|
120
|
+
|
|
121
|
+
headers.set('X-RateLimit-Limit', this.config.max.toString());
|
|
122
|
+
headers.set('X-RateLimit-Remaining', remaining.toString());
|
|
123
|
+
headers.set('X-RateLimit-Reset', Math.ceil(entry.resetTime / 1000).toString());
|
|
124
|
+
|
|
125
|
+
return new Response(response.body, {
|
|
126
|
+
status: response.status,
|
|
127
|
+
statusText: response.statusText,
|
|
128
|
+
headers,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Start cleanup interval to remove expired entries
|
|
134
|
+
*/
|
|
135
|
+
private startCleanup(): void {
|
|
136
|
+
// Clean up every minute
|
|
137
|
+
this.cleanupInterval = setInterval(() => {
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
for (const [key, entry] of this.store.entries()) {
|
|
140
|
+
if (now >= entry.resetTime) {
|
|
141
|
+
this.store.delete(key);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}, 60000);
|
|
145
|
+
|
|
146
|
+
// Allow the process to exit even if the interval is running
|
|
147
|
+
if (this.cleanupInterval.unref) {
|
|
148
|
+
this.cleanupInterval.unref();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Stop the cleanup interval (for testing)
|
|
154
|
+
*/
|
|
155
|
+
stopCleanup(): void {
|
|
156
|
+
if (this.cleanupInterval) {
|
|
157
|
+
clearInterval(this.cleanupInterval);
|
|
158
|
+
this.cleanupInterval = null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Clear all rate limit entries (for testing)
|
|
164
|
+
*/
|
|
165
|
+
clear(): void {
|
|
166
|
+
this.store.clear();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get current stats (for testing/debugging)
|
|
171
|
+
*/
|
|
172
|
+
getStats(): { totalKeys: number } {
|
|
173
|
+
return { totalKeys: this.store.size };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Create a Rate Limiter middleware instance with the given config
|
|
179
|
+
*/
|
|
180
|
+
export function rateLimit(config: Partial<RateLimitConfig> = {}): RateLimiterMiddleware {
|
|
181
|
+
const defaultConfig: RateLimitConfig = {
|
|
182
|
+
enabled: true,
|
|
183
|
+
windowMs: 60000, // 1 minute
|
|
184
|
+
max: 100,
|
|
185
|
+
message: 'Too many requests, please try again later.',
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
return new RateLimiterMiddleware({ ...defaultConfig, ...config });
|
|
189
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PhoenixJS - Security Manager
|
|
3
|
+
*
|
|
4
|
+
* Central manager for all security features.
|
|
5
|
+
* Provides factory methods for security middleware.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Application } from '@framework/core/Application';
|
|
9
|
+
import type { SecurityConfig } from '@/config/security';
|
|
10
|
+
import { CorsMiddleware, cors } from '@framework/security/CorsMiddleware';
|
|
11
|
+
import { HelmetMiddleware, helmet } from '@framework/security/HelmetMiddleware';
|
|
12
|
+
import { RateLimiterMiddleware, rateLimit } from '@framework/security/RateLimiterMiddleware';
|
|
13
|
+
import { InputSanitizerMiddleware, sanitizer } from '@framework/security/InputSanitizerMiddleware';
|
|
14
|
+
import { CsrfMiddleware, csrf } from '@framework/security/CsrfMiddleware';
|
|
15
|
+
import type { MiddlewareHandler } from '@framework/middleware/Middleware';
|
|
16
|
+
|
|
17
|
+
export class SecurityManager {
|
|
18
|
+
private corsMiddleware: CorsMiddleware | null = null;
|
|
19
|
+
private helmetMiddleware: HelmetMiddleware | null = null;
|
|
20
|
+
private rateLimiterMiddleware: RateLimiterMiddleware | null = null;
|
|
21
|
+
private sanitizerMiddleware: InputSanitizerMiddleware | null = null;
|
|
22
|
+
private csrfMiddleware: CsrfMiddleware | null = null;
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
private app: Application,
|
|
26
|
+
private config?: SecurityConfig
|
|
27
|
+
) { }
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get CORS middleware
|
|
31
|
+
*/
|
|
32
|
+
getCorsMiddleware(): CorsMiddleware {
|
|
33
|
+
if (!this.corsMiddleware) {
|
|
34
|
+
this.corsMiddleware = cors(this.config?.cors);
|
|
35
|
+
}
|
|
36
|
+
return this.corsMiddleware;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get Helmet middleware
|
|
41
|
+
*/
|
|
42
|
+
getHelmetMiddleware(): HelmetMiddleware {
|
|
43
|
+
if (!this.helmetMiddleware) {
|
|
44
|
+
this.helmetMiddleware = helmet(this.config?.helmet);
|
|
45
|
+
}
|
|
46
|
+
return this.helmetMiddleware;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get Rate Limiter middleware
|
|
51
|
+
*/
|
|
52
|
+
getRateLimiterMiddleware(): RateLimiterMiddleware {
|
|
53
|
+
if (!this.rateLimiterMiddleware) {
|
|
54
|
+
this.rateLimiterMiddleware = rateLimit(this.config?.rateLimit);
|
|
55
|
+
}
|
|
56
|
+
return this.rateLimiterMiddleware;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get Input Sanitizer middleware
|
|
61
|
+
*/
|
|
62
|
+
getSanitizerMiddleware(): InputSanitizerMiddleware {
|
|
63
|
+
if (!this.sanitizerMiddleware) {
|
|
64
|
+
this.sanitizerMiddleware = sanitizer(this.config?.sanitizer);
|
|
65
|
+
}
|
|
66
|
+
return this.sanitizerMiddleware;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get CSRF middleware
|
|
71
|
+
*/
|
|
72
|
+
getCsrfMiddleware(): CsrfMiddleware {
|
|
73
|
+
if (!this.csrfMiddleware) {
|
|
74
|
+
this.csrfMiddleware = csrf(this.config?.csrf);
|
|
75
|
+
}
|
|
76
|
+
return this.csrfMiddleware;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get all enabled security middleware as an array
|
|
81
|
+
*/
|
|
82
|
+
getSecurityMiddleware(): MiddlewareHandler[] {
|
|
83
|
+
const middleware: MiddlewareHandler[] = [];
|
|
84
|
+
|
|
85
|
+
// Always add CORS (handles OPTIONS requests)
|
|
86
|
+
middleware.push(this.getCorsMiddleware());
|
|
87
|
+
|
|
88
|
+
// Add Helmet for security headers
|
|
89
|
+
middleware.push(this.getHelmetMiddleware());
|
|
90
|
+
|
|
91
|
+
// Add rate limiting if enabled
|
|
92
|
+
if (this.config?.rateLimit?.enabled !== false) {
|
|
93
|
+
middleware.push(this.getRateLimiterMiddleware());
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Add CSRF if enabled
|
|
97
|
+
if (this.config?.csrf?.enabled) {
|
|
98
|
+
middleware.push(this.getCsrfMiddleware());
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return middleware;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Configure security from a config object
|
|
106
|
+
*/
|
|
107
|
+
configure(config: Partial<SecurityConfig>): this {
|
|
108
|
+
this.config = { ...this.config, ...config } as SecurityConfig;
|
|
109
|
+
|
|
110
|
+
// Reset cached middleware so they get recreated with new config
|
|
111
|
+
this.corsMiddleware = null;
|
|
112
|
+
this.helmetMiddleware = null;
|
|
113
|
+
this.rateLimiterMiddleware = null;
|
|
114
|
+
this.sanitizerMiddleware = null;
|
|
115
|
+
this.csrfMiddleware = null;
|
|
116
|
+
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Stop any background tasks (for cleanup)
|
|
122
|
+
*/
|
|
123
|
+
shutdown(): void {
|
|
124
|
+
if (this.rateLimiterMiddleware) {
|
|
125
|
+
this.rateLimiterMiddleware.stopCleanup();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|