@hazeljs/core 0.2.0-beta.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.
- package/README.md +522 -0
- package/dist/__tests__/container.test.d.ts +2 -0
- package/dist/__tests__/container.test.d.ts.map +1 -0
- package/dist/__tests__/container.test.js +454 -0
- package/dist/__tests__/decorators.test.d.ts +2 -0
- package/dist/__tests__/decorators.test.d.ts.map +1 -0
- package/dist/__tests__/decorators.test.js +693 -0
- package/dist/__tests__/errors/http.error.test.d.ts +2 -0
- package/dist/__tests__/errors/http.error.test.d.ts.map +1 -0
- package/dist/__tests__/errors/http.error.test.js +117 -0
- package/dist/__tests__/filters/exception-filter.test.d.ts +2 -0
- package/dist/__tests__/filters/exception-filter.test.d.ts.map +1 -0
- package/dist/__tests__/filters/exception-filter.test.js +135 -0
- package/dist/__tests__/filters/http-exception.filter.test.d.ts +2 -0
- package/dist/__tests__/filters/http-exception.filter.test.d.ts.map +1 -0
- package/dist/__tests__/filters/http-exception.filter.test.js +119 -0
- package/dist/__tests__/hazel-app.test.d.ts +2 -0
- package/dist/__tests__/hazel-app.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-app.test.js +682 -0
- package/dist/__tests__/hazel-module.test.d.ts +2 -0
- package/dist/__tests__/hazel-module.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-module.test.js +408 -0
- package/dist/__tests__/hazel-response.test.d.ts +2 -0
- package/dist/__tests__/hazel-response.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-response.test.js +138 -0
- package/dist/__tests__/health.test.d.ts +2 -0
- package/dist/__tests__/health.test.d.ts.map +1 -0
- package/dist/__tests__/health.test.js +147 -0
- package/dist/__tests__/index.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts.map +1 -0
- package/dist/__tests__/index.test.js +239 -0
- package/dist/__tests__/interceptors/interceptor.test.d.ts +2 -0
- package/dist/__tests__/interceptors/interceptor.test.d.ts.map +1 -0
- package/dist/__tests__/interceptors/interceptor.test.js +166 -0
- package/dist/__tests__/logger.test.d.ts +2 -0
- package/dist/__tests__/logger.test.d.ts.map +1 -0
- package/dist/__tests__/logger.test.js +141 -0
- package/dist/__tests__/middleware/cors.test.d.ts +2 -0
- package/dist/__tests__/middleware/cors.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/cors.test.js +129 -0
- package/dist/__tests__/middleware/csrf.test.d.ts +2 -0
- package/dist/__tests__/middleware/csrf.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/csrf.test.js +247 -0
- package/dist/__tests__/middleware/global-middleware.test.d.ts +2 -0
- package/dist/__tests__/middleware/global-middleware.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/global-middleware.test.js +259 -0
- package/dist/__tests__/middleware/rate-limit.test.d.ts +2 -0
- package/dist/__tests__/middleware/rate-limit.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/rate-limit.test.js +264 -0
- package/dist/__tests__/middleware/security-headers.test.d.ts +2 -0
- package/dist/__tests__/middleware/security-headers.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/security-headers.test.js +229 -0
- package/dist/__tests__/middleware/timeout.test.d.ts +2 -0
- package/dist/__tests__/middleware/timeout.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/timeout.test.js +132 -0
- package/dist/__tests__/middleware.test.d.ts +2 -0
- package/dist/__tests__/middleware.test.d.ts.map +1 -0
- package/dist/__tests__/middleware.test.js +180 -0
- package/dist/__tests__/pipes/pipe.test.d.ts +2 -0
- package/dist/__tests__/pipes/pipe.test.d.ts.map +1 -0
- package/dist/__tests__/pipes/pipe.test.js +245 -0
- package/dist/__tests__/pipes/validation.pipe.test.d.ts +2 -0
- package/dist/__tests__/pipes/validation.pipe.test.d.ts.map +1 -0
- package/dist/__tests__/pipes/validation.pipe.test.js +297 -0
- package/dist/__tests__/request-parser.test.d.ts +2 -0
- package/dist/__tests__/request-parser.test.d.ts.map +1 -0
- package/dist/__tests__/request-parser.test.js +182 -0
- package/dist/__tests__/router.test.d.ts +2 -0
- package/dist/__tests__/router.test.d.ts.map +1 -0
- package/dist/__tests__/router.test.js +680 -0
- package/dist/__tests__/routing/route-matcher.test.d.ts +2 -0
- package/dist/__tests__/routing/route-matcher.test.d.ts.map +1 -0
- package/dist/__tests__/routing/route-matcher.test.js +219 -0
- package/dist/__tests__/routing/version.decorator.test.d.ts +2 -0
- package/dist/__tests__/routing/version.decorator.test.d.ts.map +1 -0
- package/dist/__tests__/routing/version.decorator.test.js +298 -0
- package/dist/__tests__/service.test.d.ts +2 -0
- package/dist/__tests__/service.test.d.ts.map +1 -0
- package/dist/__tests__/service.test.js +121 -0
- package/dist/__tests__/shutdown.test.d.ts +2 -0
- package/dist/__tests__/shutdown.test.d.ts.map +1 -0
- package/dist/__tests__/shutdown.test.js +250 -0
- package/dist/__tests__/testing/testing.module.test.d.ts +2 -0
- package/dist/__tests__/testing/testing.module.test.d.ts.map +1 -0
- package/dist/__tests__/testing/testing.module.test.js +370 -0
- package/dist/__tests__/upload/file-upload.test.d.ts +2 -0
- package/dist/__tests__/upload/file-upload.test.d.ts.map +1 -0
- package/dist/__tests__/upload/file-upload.test.js +498 -0
- package/dist/__tests__/utils/sanitize.test.d.ts +2 -0
- package/dist/__tests__/utils/sanitize.test.d.ts.map +1 -0
- package/dist/__tests__/utils/sanitize.test.js +291 -0
- package/dist/__tests__/validator.test.d.ts +2 -0
- package/dist/__tests__/validator.test.d.ts.map +1 -0
- package/dist/__tests__/validator.test.js +300 -0
- package/dist/container.d.ts +80 -0
- package/dist/container.d.ts.map +1 -0
- package/dist/container.js +271 -0
- package/dist/decorators.d.ts +92 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/decorators.js +343 -0
- package/dist/errors/http.error.d.ts +31 -0
- package/dist/errors/http.error.d.ts.map +1 -0
- package/dist/errors/http.error.js +62 -0
- package/dist/filters/exception-filter.d.ts +39 -0
- package/dist/filters/exception-filter.d.ts.map +1 -0
- package/dist/filters/exception-filter.js +38 -0
- package/dist/filters/http-exception.filter.d.ts +9 -0
- package/dist/filters/http-exception.filter.d.ts.map +1 -0
- package/dist/filters/http-exception.filter.js +42 -0
- package/dist/hazel-app.d.ts +78 -0
- package/dist/hazel-app.d.ts.map +1 -0
- package/dist/hazel-app.js +453 -0
- package/dist/hazel-module.d.ts +20 -0
- package/dist/hazel-module.d.ts.map +1 -0
- package/dist/hazel-module.js +109 -0
- package/dist/hazel-response.d.ts +20 -0
- package/dist/hazel-response.d.ts.map +1 -0
- package/dist/hazel-response.js +68 -0
- package/dist/health.d.ts +73 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +174 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +140 -0
- package/dist/interceptors/interceptor.d.ts +22 -0
- package/dist/interceptors/interceptor.d.ts.map +1 -0
- package/dist/interceptors/interceptor.js +46 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +238 -0
- package/dist/middleware/cors.middleware.d.ts +44 -0
- package/dist/middleware/cors.middleware.d.ts.map +1 -0
- package/dist/middleware/cors.middleware.js +118 -0
- package/dist/middleware/csrf.middleware.d.ts +82 -0
- package/dist/middleware/csrf.middleware.d.ts.map +1 -0
- package/dist/middleware/csrf.middleware.js +183 -0
- package/dist/middleware/global-middleware.d.ts +111 -0
- package/dist/middleware/global-middleware.d.ts.map +1 -0
- package/dist/middleware/global-middleware.js +179 -0
- package/dist/middleware/rate-limit.middleware.d.ts +73 -0
- package/dist/middleware/rate-limit.middleware.d.ts.map +1 -0
- package/dist/middleware/rate-limit.middleware.js +124 -0
- package/dist/middleware/security-headers.middleware.d.ts +76 -0
- package/dist/middleware/security-headers.middleware.d.ts.map +1 -0
- package/dist/middleware/security-headers.middleware.js +123 -0
- package/dist/middleware/timeout.middleware.d.ts +25 -0
- package/dist/middleware/timeout.middleware.d.ts.map +1 -0
- package/dist/middleware/timeout.middleware.js +74 -0
- package/dist/middleware.d.ts +13 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +47 -0
- package/dist/pipes/pipe.d.ts +50 -0
- package/dist/pipes/pipe.d.ts.map +1 -0
- package/dist/pipes/pipe.js +96 -0
- package/dist/pipes/validation.pipe.d.ts +6 -0
- package/dist/pipes/validation.pipe.d.ts.map +1 -0
- package/dist/pipes/validation.pipe.js +61 -0
- package/dist/request-context.d.ts +17 -0
- package/dist/request-context.d.ts.map +1 -0
- package/dist/request-context.js +2 -0
- package/dist/request-parser.d.ts +7 -0
- package/dist/request-parser.d.ts.map +1 -0
- package/dist/request-parser.js +60 -0
- package/dist/router.d.ts +33 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +426 -0
- package/dist/routing/route-matcher.d.ts +39 -0
- package/dist/routing/route-matcher.d.ts.map +1 -0
- package/dist/routing/route-matcher.js +93 -0
- package/dist/routing/version.decorator.d.ts +36 -0
- package/dist/routing/version.decorator.d.ts.map +1 -0
- package/dist/routing/version.decorator.js +89 -0
- package/dist/service.d.ts +9 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +39 -0
- package/dist/shutdown.d.ts +32 -0
- package/dist/shutdown.d.ts.map +1 -0
- package/dist/shutdown.js +109 -0
- package/dist/testing/testing.module.d.ts +83 -0
- package/dist/testing/testing.module.d.ts.map +1 -0
- package/dist/testing/testing.module.js +164 -0
- package/dist/types.d.ts +76 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/upload/file-upload.d.ts +75 -0
- package/dist/upload/file-upload.d.ts.map +1 -0
- package/dist/upload/file-upload.js +261 -0
- package/dist/utils/sanitize.d.ts +45 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +165 -0
- package/dist/validator.d.ts +7 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +119 -0
- package/package.json +65 -0
package/dist/logger.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.requestLogger = void 0;
|
|
7
|
+
const winston_1 = __importDefault(require("winston"));
|
|
8
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
12
|
+
// Load environment variables
|
|
13
|
+
dotenv_1.default.config();
|
|
14
|
+
const logLevel = process.env.LOG_LEVEL || 'info';
|
|
15
|
+
const logDir = process.env.LOG_DIR || 'logs';
|
|
16
|
+
// Ensure log directory exists
|
|
17
|
+
if (!fs_1.default.existsSync(logDir)) {
|
|
18
|
+
fs_1.default.mkdirSync(logDir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
// Professional color scheme for different log levels
|
|
21
|
+
const colors = {
|
|
22
|
+
error: chalk_1.default.red,
|
|
23
|
+
warn: chalk_1.default.yellow,
|
|
24
|
+
info: chalk_1.default.blue,
|
|
25
|
+
http: chalk_1.default.magenta,
|
|
26
|
+
debug: chalk_1.default.gray,
|
|
27
|
+
silly: chalk_1.default.gray,
|
|
28
|
+
};
|
|
29
|
+
// Professional category-based coloring
|
|
30
|
+
const categoryColors = {
|
|
31
|
+
// Module operations
|
|
32
|
+
'Initializing HazelModule': chalk_1.default.blue.bold,
|
|
33
|
+
'Initializing module': chalk_1.default.blue.bold,
|
|
34
|
+
'Initializing imported modules': chalk_1.default.blue,
|
|
35
|
+
'Initializing HazelApp': chalk_1.default.cyan.bold,
|
|
36
|
+
// Provider operations
|
|
37
|
+
'Registering providers': chalk_1.default.green.bold,
|
|
38
|
+
'Registering provider': chalk_1.default.green,
|
|
39
|
+
'Created instance of': chalk_1.default.green,
|
|
40
|
+
// Controller operations
|
|
41
|
+
'Registering controllers': chalk_1.default.yellow.bold,
|
|
42
|
+
'Registering controller': chalk_1.default.yellow,
|
|
43
|
+
// Route operations
|
|
44
|
+
'Registering route': chalk_1.default.magenta.bold,
|
|
45
|
+
// Server operations
|
|
46
|
+
'Server listening on': chalk_1.default.green.bold,
|
|
47
|
+
'Application started': chalk_1.default.cyan.bold,
|
|
48
|
+
'Connected to database': chalk_1.default.green.bold,
|
|
49
|
+
'Disconnected from database': chalk_1.default.yellow,
|
|
50
|
+
// Cache operations
|
|
51
|
+
'Cache service initialized': chalk_1.default.blue,
|
|
52
|
+
'Cache registered': chalk_1.default.green,
|
|
53
|
+
'Configuring cache module': chalk_1.default.blue,
|
|
54
|
+
// Configuration
|
|
55
|
+
'Configuration loaded': chalk_1.default.green,
|
|
56
|
+
Configuring: chalk_1.default.blue,
|
|
57
|
+
// Container
|
|
58
|
+
'Container initialized': chalk_1.default.cyan.bold,
|
|
59
|
+
};
|
|
60
|
+
// Helper to detect log category and apply appropriate color
|
|
61
|
+
const getCategoryColor = (message) => {
|
|
62
|
+
for (const [category, colorFn] of Object.entries(categoryColors)) {
|
|
63
|
+
if (message.includes(category)) {
|
|
64
|
+
return colorFn;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return chalk_1.default.white;
|
|
68
|
+
};
|
|
69
|
+
// Custom format for better readability with enhanced colors
|
|
70
|
+
const customFormat = winston_1.default.format.printf(({ level, message, timestamp, ...metadata }) => {
|
|
71
|
+
// Get the appropriate color for the log level
|
|
72
|
+
const levelColor = colors[level] || chalk_1.default.white;
|
|
73
|
+
// Format the timestamp with subtle color
|
|
74
|
+
const time = chalk_1.default.gray.dim(timestamp);
|
|
75
|
+
// Format the level with color and padding (no icons for professional look)
|
|
76
|
+
const levelStr = levelColor.bold(`[${level.toUpperCase()}]`.padEnd(9));
|
|
77
|
+
// Convert message to string
|
|
78
|
+
const messageStr = String(message);
|
|
79
|
+
// Detect category and apply appropriate color to message
|
|
80
|
+
const categoryColor = getCategoryColor(messageStr);
|
|
81
|
+
let msg = categoryColor(messageStr);
|
|
82
|
+
// Special formatting for important messages
|
|
83
|
+
if (messageStr.includes('Server listening on')) {
|
|
84
|
+
msg = chalk_1.default.green.bold(messageStr);
|
|
85
|
+
}
|
|
86
|
+
else if (messageStr.includes('Application started')) {
|
|
87
|
+
msg = chalk_1.default.cyan.bold(messageStr);
|
|
88
|
+
}
|
|
89
|
+
else if (messageStr.includes('→ Local:') || messageStr.includes('→ Network:')) {
|
|
90
|
+
msg = chalk_1.default.green(messageStr);
|
|
91
|
+
}
|
|
92
|
+
// Format metadata if present with enhanced colors
|
|
93
|
+
let metaStr = '';
|
|
94
|
+
if (Object.keys(metadata).length > 0) {
|
|
95
|
+
metaStr = Object.entries(metadata)
|
|
96
|
+
.map(([key, value]) => {
|
|
97
|
+
// Color keys based on their type
|
|
98
|
+
let keyColor = chalk_1.default.cyan;
|
|
99
|
+
if (key.includes('module') || key.includes('Module')) {
|
|
100
|
+
keyColor = chalk_1.default.blue;
|
|
101
|
+
}
|
|
102
|
+
else if (key.includes('provider') ||
|
|
103
|
+
key.includes('Provider') ||
|
|
104
|
+
key.includes('Service')) {
|
|
105
|
+
keyColor = chalk_1.default.green;
|
|
106
|
+
}
|
|
107
|
+
else if (key.includes('controller') || key.includes('Controller')) {
|
|
108
|
+
keyColor = chalk_1.default.yellow;
|
|
109
|
+
}
|
|
110
|
+
else if (key.includes('route') || key.includes('Route')) {
|
|
111
|
+
keyColor = chalk_1.default.magenta;
|
|
112
|
+
}
|
|
113
|
+
const formattedKey = keyColor.bold(key);
|
|
114
|
+
// Use a custom replacer function to handle circular references
|
|
115
|
+
const seen = new WeakSet();
|
|
116
|
+
const safeValue = JSON.stringify(value, (key, val) => {
|
|
117
|
+
// Skip known circular references
|
|
118
|
+
if (key === 'socket' || key === 'parser' || key === 'res' || key === 'req') {
|
|
119
|
+
return '[Circular]';
|
|
120
|
+
}
|
|
121
|
+
// Skip Node.js internal objects
|
|
122
|
+
if (key === '_idlePrev' || key === '_idleNext' || key === 'cleanupInterval') {
|
|
123
|
+
return '[Internal]';
|
|
124
|
+
}
|
|
125
|
+
// Handle circular references
|
|
126
|
+
if (typeof val === 'object' && val !== null) {
|
|
127
|
+
if (seen.has(val)) {
|
|
128
|
+
return '[Circular]';
|
|
129
|
+
}
|
|
130
|
+
seen.add(val);
|
|
131
|
+
}
|
|
132
|
+
return val;
|
|
133
|
+
});
|
|
134
|
+
// Color values based on their content
|
|
135
|
+
let valueColor = chalk_1.default.yellow;
|
|
136
|
+
if (typeof value === 'string') {
|
|
137
|
+
if (value.includes('Module') ||
|
|
138
|
+
value.includes('Service') ||
|
|
139
|
+
value.includes('Controller')) {
|
|
140
|
+
valueColor = chalk_1.default.cyan;
|
|
141
|
+
}
|
|
142
|
+
else if (value.includes('http://') || value.includes('localhost')) {
|
|
143
|
+
valueColor = chalk_1.default.green;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const formattedValue = valueColor(safeValue);
|
|
147
|
+
return `${formattedKey}=${formattedValue}`;
|
|
148
|
+
})
|
|
149
|
+
.join(' ');
|
|
150
|
+
metaStr = chalk_1.default.gray.dim(' | ') + metaStr;
|
|
151
|
+
}
|
|
152
|
+
return `${time} ${levelStr} ${msg}${metaStr}`;
|
|
153
|
+
});
|
|
154
|
+
// Create logger instance
|
|
155
|
+
const logger = winston_1.default.createLogger({
|
|
156
|
+
level: logLevel,
|
|
157
|
+
format: winston_1.default.format.combine(winston_1.default.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), customFormat),
|
|
158
|
+
transports: [
|
|
159
|
+
// Console transport with colors
|
|
160
|
+
new winston_1.default.transports.Console({
|
|
161
|
+
format: winston_1.default.format.combine(customFormat),
|
|
162
|
+
}),
|
|
163
|
+
// File transport for all logs (without colors)
|
|
164
|
+
new winston_1.default.transports.File({
|
|
165
|
+
filename: path_1.default.join(logDir, 'combined.log'),
|
|
166
|
+
format: winston_1.default.format.combine(winston_1.default.format.timestamp(), winston_1.default.format.printf(({ level, message, timestamp, ...metadata }) => {
|
|
167
|
+
let msg = `${timestamp} [${level.toUpperCase()}] ${message}`;
|
|
168
|
+
if (Object.keys(metadata).length > 0) {
|
|
169
|
+
msg += ` | ${JSON.stringify(metadata, (key, val) => {
|
|
170
|
+
if (key === 'socket' || key === 'parser' || key === 'res' || key === 'req') {
|
|
171
|
+
return '[Circular]';
|
|
172
|
+
}
|
|
173
|
+
return val;
|
|
174
|
+
})}`;
|
|
175
|
+
}
|
|
176
|
+
return msg;
|
|
177
|
+
})),
|
|
178
|
+
}),
|
|
179
|
+
// File transport for errors only (without colors)
|
|
180
|
+
new winston_1.default.transports.File({
|
|
181
|
+
filename: path_1.default.join(logDir, 'error.log'),
|
|
182
|
+
level: 'error',
|
|
183
|
+
format: winston_1.default.format.combine(winston_1.default.format.timestamp(), winston_1.default.format.printf(({ level, message, timestamp, ...metadata }) => {
|
|
184
|
+
let msg = `${timestamp} [${level.toUpperCase()}] ${message}`;
|
|
185
|
+
if (Object.keys(metadata).length > 0) {
|
|
186
|
+
msg += ` | ${JSON.stringify(metadata, (key, val) => {
|
|
187
|
+
if (key === 'socket' || key === 'parser' || key === 'res' || key === 'req') {
|
|
188
|
+
return '[Circular]';
|
|
189
|
+
}
|
|
190
|
+
return val;
|
|
191
|
+
})}`;
|
|
192
|
+
}
|
|
193
|
+
return msg;
|
|
194
|
+
})),
|
|
195
|
+
}),
|
|
196
|
+
],
|
|
197
|
+
});
|
|
198
|
+
// Log application info when starting
|
|
199
|
+
const appInfo = {
|
|
200
|
+
service: process.env.APP_NAME || 'hazeljs',
|
|
201
|
+
version: process.env.APP_VERSION || '0.1.0',
|
|
202
|
+
environment: process.env.NODE_ENV || 'development',
|
|
203
|
+
};
|
|
204
|
+
logger.info(chalk_1.default.cyan.bold('Application started'), appInfo);
|
|
205
|
+
// Add request logging middleware with beautiful formatting
|
|
206
|
+
const requestLogger = (req, res, next) => {
|
|
207
|
+
const start = Date.now();
|
|
208
|
+
res.on('finish', () => {
|
|
209
|
+
const duration = Date.now() - start;
|
|
210
|
+
const statusColor = res.statusCode >= 500
|
|
211
|
+
? chalk_1.default.red
|
|
212
|
+
: res.statusCode >= 400
|
|
213
|
+
? chalk_1.default.yellow
|
|
214
|
+
: res.statusCode >= 300
|
|
215
|
+
? chalk_1.default.cyan
|
|
216
|
+
: res.statusCode >= 200
|
|
217
|
+
? chalk_1.default.green
|
|
218
|
+
: chalk_1.default.white;
|
|
219
|
+
logger.info(`${chalk_1.default.bold(req.method)} ${req.url}`, {
|
|
220
|
+
status: statusColor(res.statusCode),
|
|
221
|
+
duration: chalk_1.default.yellow(`${duration}ms`),
|
|
222
|
+
userAgent: chalk_1.default.gray(req.headers['user-agent']),
|
|
223
|
+
ip: chalk_1.default.gray(req.socket.remoteAddress),
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
next();
|
|
227
|
+
};
|
|
228
|
+
exports.requestLogger = requestLogger;
|
|
229
|
+
// Add helper method to check if debug is enabled
|
|
230
|
+
const isDebugEnabled = () => {
|
|
231
|
+
return logger.isLevelEnabled('debug');
|
|
232
|
+
};
|
|
233
|
+
// Extend logger with helper method
|
|
234
|
+
const enhancedLogger = Object.assign(logger, {
|
|
235
|
+
isDebugEnabled,
|
|
236
|
+
});
|
|
237
|
+
// Export logger instance
|
|
238
|
+
exports.default = enhancedLogger;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CORS Middleware
|
|
3
|
+
* Handles Cross-Origin Resource Sharing
|
|
4
|
+
*/
|
|
5
|
+
import { Request, Response } from '../types';
|
|
6
|
+
export interface CorsOptions {
|
|
7
|
+
origin?: string | string[] | ((origin: string) => boolean);
|
|
8
|
+
methods?: string[];
|
|
9
|
+
allowedHeaders?: string[];
|
|
10
|
+
exposedHeaders?: string[];
|
|
11
|
+
credentials?: boolean;
|
|
12
|
+
maxAge?: number;
|
|
13
|
+
preflightContinue?: boolean;
|
|
14
|
+
optionsSuccessStatus?: number;
|
|
15
|
+
}
|
|
16
|
+
export declare class CorsMiddleware {
|
|
17
|
+
private options;
|
|
18
|
+
constructor(options?: CorsOptions);
|
|
19
|
+
/**
|
|
20
|
+
* Check if origin is allowed
|
|
21
|
+
*/
|
|
22
|
+
private isOriginAllowed;
|
|
23
|
+
/**
|
|
24
|
+
* Set CORS headers
|
|
25
|
+
*/
|
|
26
|
+
private setCorsHeaders;
|
|
27
|
+
/**
|
|
28
|
+
* Handle preflight request
|
|
29
|
+
*/
|
|
30
|
+
private handlePreflight;
|
|
31
|
+
/**
|
|
32
|
+
* Handle CORS request
|
|
33
|
+
*/
|
|
34
|
+
handle(req: Request, res: Response, next: () => void): void;
|
|
35
|
+
/**
|
|
36
|
+
* Create middleware function
|
|
37
|
+
*/
|
|
38
|
+
static create(options?: CorsOptions): (req: Request, res: Response, next: () => void) => void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Simple CORS helper for common use cases
|
|
42
|
+
*/
|
|
43
|
+
export declare function enableCors(options?: CorsOptions): (req: Request, res: Response, next: () => void) => void;
|
|
44
|
+
//# sourceMappingURL=cors.middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cors.middleware.d.ts","sourceRoot":"","sources":["../../src/middleware/cors.middleware.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAE7C,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;IAC3D,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAwB;gBAE3B,OAAO,GAAE,WAAgB;IAarC;;OAEG;IACH,OAAO,CAAC,eAAe;IAsBvB;;OAEG;IACH,OAAO,CAAC,cAAc;IAsBtB;;OAEG;IACH,OAAO,CAAC,eAAe;IA+BvB;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,IAAI,GAAG,IAAI;IAa3D;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,IAAI,KAAK,IAAI;CAI9F;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,IAAI,KAAK,IAAI,CAEzG"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CORS Middleware
|
|
4
|
+
* Handles Cross-Origin Resource Sharing
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.CorsMiddleware = void 0;
|
|
8
|
+
exports.enableCors = enableCors;
|
|
9
|
+
class CorsMiddleware {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.options = {
|
|
12
|
+
origin: options.origin || '*',
|
|
13
|
+
methods: options.methods || ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'],
|
|
14
|
+
allowedHeaders: options.allowedHeaders || ['Content-Type', 'Authorization'],
|
|
15
|
+
exposedHeaders: options.exposedHeaders || [],
|
|
16
|
+
credentials: options.credentials || false,
|
|
17
|
+
maxAge: options.maxAge || 86400, // 24 hours
|
|
18
|
+
preflightContinue: options.preflightContinue || false,
|
|
19
|
+
optionsSuccessStatus: options.optionsSuccessStatus || 204,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Check if origin is allowed
|
|
24
|
+
*/
|
|
25
|
+
isOriginAllowed(origin) {
|
|
26
|
+
const { origin: allowedOrigin } = this.options;
|
|
27
|
+
if (allowedOrigin === '*') {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
if (typeof allowedOrigin === 'string') {
|
|
31
|
+
return origin === allowedOrigin;
|
|
32
|
+
}
|
|
33
|
+
if (Array.isArray(allowedOrigin)) {
|
|
34
|
+
return allowedOrigin.includes(origin);
|
|
35
|
+
}
|
|
36
|
+
if (typeof allowedOrigin === 'function') {
|
|
37
|
+
return allowedOrigin(origin);
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Set CORS headers
|
|
43
|
+
*/
|
|
44
|
+
setCorsHeaders(req, res) {
|
|
45
|
+
const origin = req.headers?.origin || req.headers?.referer || '';
|
|
46
|
+
// Set Access-Control-Allow-Origin
|
|
47
|
+
if (this.options.origin === '*') {
|
|
48
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
49
|
+
}
|
|
50
|
+
else if (this.isOriginAllowed(origin)) {
|
|
51
|
+
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
52
|
+
res.setHeader('Vary', 'Origin');
|
|
53
|
+
}
|
|
54
|
+
// Set Access-Control-Allow-Credentials
|
|
55
|
+
if (this.options.credentials) {
|
|
56
|
+
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
57
|
+
}
|
|
58
|
+
// Set Access-Control-Expose-Headers
|
|
59
|
+
if (this.options.exposedHeaders.length > 0) {
|
|
60
|
+
res.setHeader('Access-Control-Expose-Headers', this.options.exposedHeaders.join(', '));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Handle preflight request
|
|
65
|
+
*/
|
|
66
|
+
handlePreflight(req, res) {
|
|
67
|
+
if (req.method !== 'OPTIONS') {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
// Set CORS headers
|
|
71
|
+
this.setCorsHeaders(req, res);
|
|
72
|
+
// Set Access-Control-Allow-Methods
|
|
73
|
+
res.setHeader('Access-Control-Allow-Methods', this.options.methods.join(', '));
|
|
74
|
+
// Set Access-Control-Allow-Headers
|
|
75
|
+
const requestHeaders = req.headers?.['access-control-request-headers'];
|
|
76
|
+
if (requestHeaders) {
|
|
77
|
+
res.setHeader('Access-Control-Allow-Headers', requestHeaders);
|
|
78
|
+
}
|
|
79
|
+
else if (this.options.allowedHeaders.length > 0) {
|
|
80
|
+
res.setHeader('Access-Control-Allow-Headers', this.options.allowedHeaders.join(', '));
|
|
81
|
+
}
|
|
82
|
+
// Set Access-Control-Max-Age
|
|
83
|
+
res.setHeader('Access-Control-Max-Age', this.options.maxAge.toString());
|
|
84
|
+
// Send response
|
|
85
|
+
if (!this.options.preflightContinue) {
|
|
86
|
+
res.status(this.options.optionsSuccessStatus).end();
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Handle CORS request
|
|
93
|
+
*/
|
|
94
|
+
handle(req, res, next) {
|
|
95
|
+
// Handle preflight request
|
|
96
|
+
if (this.handlePreflight(req, res)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Set CORS headers for actual request
|
|
100
|
+
this.setCorsHeaders(req, res);
|
|
101
|
+
// Continue to next middleware
|
|
102
|
+
next();
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Create middleware function
|
|
106
|
+
*/
|
|
107
|
+
static create(options) {
|
|
108
|
+
const middleware = new CorsMiddleware(options);
|
|
109
|
+
return (req, res, next) => middleware.handle(req, res, next);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
exports.CorsMiddleware = CorsMiddleware;
|
|
113
|
+
/**
|
|
114
|
+
* Simple CORS helper for common use cases
|
|
115
|
+
*/
|
|
116
|
+
function enableCors(options) {
|
|
117
|
+
return CorsMiddleware.create(options);
|
|
118
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Request, Response } from '../types';
|
|
2
|
+
import { MiddlewareClass, NextFunction } from './global-middleware';
|
|
3
|
+
/**
|
|
4
|
+
* CSRF protection options
|
|
5
|
+
*/
|
|
6
|
+
export interface CsrfOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Cookie name for CSRF token
|
|
9
|
+
*/
|
|
10
|
+
cookieName?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Header name for CSRF token
|
|
13
|
+
*/
|
|
14
|
+
headerName?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Secret key for token generation
|
|
17
|
+
*/
|
|
18
|
+
secret?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Cookie options
|
|
21
|
+
*/
|
|
22
|
+
cookieOptions?: {
|
|
23
|
+
httpOnly?: boolean;
|
|
24
|
+
secure?: boolean;
|
|
25
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
26
|
+
path?: string;
|
|
27
|
+
domain?: string;
|
|
28
|
+
maxAge?: number;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Methods to protect (default: POST, PUT, PATCH, DELETE)
|
|
32
|
+
*/
|
|
33
|
+
methods?: string[];
|
|
34
|
+
/**
|
|
35
|
+
* Paths to exclude from CSRF protection
|
|
36
|
+
*/
|
|
37
|
+
excludePaths?: string[];
|
|
38
|
+
/**
|
|
39
|
+
* Token length in bytes
|
|
40
|
+
*/
|
|
41
|
+
tokenLength?: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* CSRF Protection Middleware
|
|
45
|
+
* Protects against Cross-Site Request Forgery attacks
|
|
46
|
+
*/
|
|
47
|
+
export declare class CsrfMiddleware implements MiddlewareClass {
|
|
48
|
+
private options;
|
|
49
|
+
private secret;
|
|
50
|
+
private tokenStore;
|
|
51
|
+
constructor(options?: CsrfOptions);
|
|
52
|
+
use(req: Request, res: Response, next: NextFunction): void;
|
|
53
|
+
/**
|
|
54
|
+
* Get or create CSRF token
|
|
55
|
+
*/
|
|
56
|
+
private getOrCreateToken;
|
|
57
|
+
/**
|
|
58
|
+
* Generate CSRF token
|
|
59
|
+
*/
|
|
60
|
+
private generateToken;
|
|
61
|
+
/**
|
|
62
|
+
* Verify CSRF token
|
|
63
|
+
*/
|
|
64
|
+
private verifyToken;
|
|
65
|
+
/**
|
|
66
|
+
* Get token from request
|
|
67
|
+
*/
|
|
68
|
+
private getTokenFromRequest;
|
|
69
|
+
/**
|
|
70
|
+
* Get session ID from request
|
|
71
|
+
*/
|
|
72
|
+
private getSessionId;
|
|
73
|
+
/**
|
|
74
|
+
* Build cookie string
|
|
75
|
+
*/
|
|
76
|
+
private buildCookie;
|
|
77
|
+
/**
|
|
78
|
+
* Cleanup expired tokens
|
|
79
|
+
*/
|
|
80
|
+
private cleanupTokens;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=csrf.middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csrf.middleware.d.ts","sourceRoot":"","sources":["../../src/middleware/csrf.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAKpE;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,aAAa,CAAC,EAAE;QACd,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;QACrC,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IAEF;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAExB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,qBAAa,cAAe,YAAW,eAAe;IAIxC,OAAO,CAAC,OAAO;IAH3B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAkC;gBAEhC,OAAO,GAAE,WAAgB;IAyB7C,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI;IA4B1D;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAgBxB;;OAEG;IACH,OAAO,CAAC,aAAa;IAQrB;;OAEG;IACH,OAAO,CAAC,WAAW;IAenB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAwB3B;;OAEG;IACH,OAAO,CAAC,YAAY;IAsBpB;;OAEG;IACH,OAAO,CAAC,WAAW;IAcnB;;OAEG;IACH,OAAO,CAAC,aAAa;CAYtB"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CsrfMiddleware = void 0;
|
|
7
|
+
const http_error_1 = require("../errors/http.error");
|
|
8
|
+
const crypto_1 = require("crypto");
|
|
9
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
10
|
+
/**
|
|
11
|
+
* CSRF Protection Middleware
|
|
12
|
+
* Protects against Cross-Site Request Forgery attacks
|
|
13
|
+
*/
|
|
14
|
+
class CsrfMiddleware {
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.options = options;
|
|
17
|
+
this.tokenStore = new Map();
|
|
18
|
+
this.options = {
|
|
19
|
+
cookieName: '_csrf',
|
|
20
|
+
headerName: 'x-csrf-token',
|
|
21
|
+
secret: process.env.CSRF_SECRET || (0, crypto_1.randomBytes)(32).toString('hex'),
|
|
22
|
+
cookieOptions: {
|
|
23
|
+
httpOnly: true,
|
|
24
|
+
secure: process.env.NODE_ENV === 'production',
|
|
25
|
+
sameSite: 'strict',
|
|
26
|
+
path: '/',
|
|
27
|
+
maxAge: 3600, // 1 hour
|
|
28
|
+
},
|
|
29
|
+
methods: ['POST', 'PUT', 'PATCH', 'DELETE'],
|
|
30
|
+
tokenLength: 32,
|
|
31
|
+
...options,
|
|
32
|
+
};
|
|
33
|
+
this.secret = this.options.secret;
|
|
34
|
+
// Cleanup expired tokens every 5 minutes
|
|
35
|
+
setInterval(() => {
|
|
36
|
+
this.cleanupTokens();
|
|
37
|
+
}, 300000);
|
|
38
|
+
}
|
|
39
|
+
use(req, res, next) {
|
|
40
|
+
const path = req.url?.split('?')[0] || '/';
|
|
41
|
+
// Skip excluded paths
|
|
42
|
+
if (this.options.excludePaths?.some(excluded => path.startsWith(excluded))) {
|
|
43
|
+
next();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// Generate or retrieve token
|
|
47
|
+
const token = this.getOrCreateToken(req, res);
|
|
48
|
+
// Check if method requires CSRF protection
|
|
49
|
+
if (this.options.methods?.includes(req.method || '')) {
|
|
50
|
+
const providedToken = this.getTokenFromRequest(req);
|
|
51
|
+
if (!providedToken || !this.verifyToken(token, providedToken)) {
|
|
52
|
+
logger_1.default.warn(`CSRF token validation failed for ${req.method} ${path}`);
|
|
53
|
+
throw new http_error_1.HttpError(403, 'Invalid CSRF token');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Add token to response for forms
|
|
57
|
+
res.setHeader('X-CSRF-Token', token);
|
|
58
|
+
next();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get or create CSRF token
|
|
62
|
+
*/
|
|
63
|
+
getOrCreateToken(req, res) {
|
|
64
|
+
const sessionId = this.getSessionId(req);
|
|
65
|
+
let token = this.tokenStore.get(sessionId);
|
|
66
|
+
if (!token) {
|
|
67
|
+
token = this.generateToken();
|
|
68
|
+
this.tokenStore.set(sessionId, token);
|
|
69
|
+
}
|
|
70
|
+
// Set cookie
|
|
71
|
+
const cookie = this.buildCookie(token);
|
|
72
|
+
res.setHeader('Set-Cookie', cookie);
|
|
73
|
+
return token;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Generate CSRF token
|
|
77
|
+
*/
|
|
78
|
+
generateToken() {
|
|
79
|
+
const random = (0, crypto_1.randomBytes)(this.options.tokenLength || 32).toString('hex');
|
|
80
|
+
const hmac = (0, crypto_1.createHmac)('sha256', this.secret);
|
|
81
|
+
hmac.update(random);
|
|
82
|
+
const signature = hmac.digest('hex');
|
|
83
|
+
return `${random}.${signature}`;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Verify CSRF token
|
|
87
|
+
*/
|
|
88
|
+
verifyToken(storedToken, providedToken) {
|
|
89
|
+
if (!storedToken || !providedToken)
|
|
90
|
+
return false;
|
|
91
|
+
if (storedToken === providedToken)
|
|
92
|
+
return true;
|
|
93
|
+
// Verify signature
|
|
94
|
+
const [random, signature] = providedToken.split('.');
|
|
95
|
+
if (!random || !signature)
|
|
96
|
+
return false;
|
|
97
|
+
const hmac = (0, crypto_1.createHmac)('sha256', this.secret);
|
|
98
|
+
hmac.update(random);
|
|
99
|
+
const expectedSignature = hmac.digest('hex');
|
|
100
|
+
return signature === expectedSignature;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get token from request
|
|
104
|
+
*/
|
|
105
|
+
getTokenFromRequest(req) {
|
|
106
|
+
// Try header first
|
|
107
|
+
if (req.headers) {
|
|
108
|
+
const headerToken = req.headers[this.options.headerName.toLowerCase()];
|
|
109
|
+
if (headerToken) {
|
|
110
|
+
return Array.isArray(headerToken) ? headerToken[0] : headerToken;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Try body
|
|
114
|
+
if (req.body && typeof req.body === 'object' && this.options.cookieName in req.body) {
|
|
115
|
+
const bodyValue = req.body[this.options.cookieName];
|
|
116
|
+
return typeof bodyValue === 'string' ? bodyValue : null;
|
|
117
|
+
}
|
|
118
|
+
// Try query parameter
|
|
119
|
+
if (req.query && typeof req.query === 'object' && this.options.cookieName in req.query) {
|
|
120
|
+
const queryValue = req.query[this.options.cookieName];
|
|
121
|
+
return typeof queryValue === 'string' ? queryValue : null;
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get session ID from request
|
|
127
|
+
*/
|
|
128
|
+
getSessionId(req) {
|
|
129
|
+
// Try to get from cookie
|
|
130
|
+
if (req.headers?.cookie) {
|
|
131
|
+
const sessionCookie = req.headers.cookie
|
|
132
|
+
.split(';')
|
|
133
|
+
.find(c => c.trim().startsWith('sessionId='));
|
|
134
|
+
if (sessionCookie) {
|
|
135
|
+
return sessionCookie.split('=')[1].trim();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Fallback to IP + User-Agent hash
|
|
139
|
+
const ip = req.socket?.remoteAddress || 'unknown';
|
|
140
|
+
const ua = req.headers?.['user-agent'] || 'unknown';
|
|
141
|
+
const hash = (0, crypto_1.createHmac)('sha256', this.secret)
|
|
142
|
+
.update(`${ip}:${ua}`)
|
|
143
|
+
.digest('hex')
|
|
144
|
+
.substring(0, 16);
|
|
145
|
+
return hash;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Build cookie string
|
|
149
|
+
*/
|
|
150
|
+
buildCookie(token) {
|
|
151
|
+
const opts = this.options.cookieOptions;
|
|
152
|
+
const parts = [`${this.options.cookieName}=${token}`];
|
|
153
|
+
if (opts.path)
|
|
154
|
+
parts.push(`Path=${opts.path}`);
|
|
155
|
+
if (opts.domain)
|
|
156
|
+
parts.push(`Domain=${opts.domain}`);
|
|
157
|
+
if (opts.maxAge)
|
|
158
|
+
parts.push(`Max-Age=${opts.maxAge}`);
|
|
159
|
+
if (opts.httpOnly)
|
|
160
|
+
parts.push('HttpOnly');
|
|
161
|
+
if (opts.secure)
|
|
162
|
+
parts.push('Secure');
|
|
163
|
+
if (opts.sameSite)
|
|
164
|
+
parts.push(`SameSite=${opts.sameSite}`);
|
|
165
|
+
return parts.join('; ');
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Cleanup expired tokens
|
|
169
|
+
*/
|
|
170
|
+
cleanupTokens() {
|
|
171
|
+
// Simple cleanup - remove old entries
|
|
172
|
+
// In production, use a proper session store with TTL
|
|
173
|
+
if (this.tokenStore.size > 10000) {
|
|
174
|
+
const entries = Array.from(this.tokenStore.entries());
|
|
175
|
+
this.tokenStore.clear();
|
|
176
|
+
// Keep last 5000 entries
|
|
177
|
+
entries.slice(-5000).forEach(([key, value]) => {
|
|
178
|
+
this.tokenStore.set(key, value);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
exports.CsrfMiddleware = CsrfMiddleware;
|