mastercontroller 1.3.0 → 1.3.2

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 CHANGED
@@ -14,6 +14,7 @@ MasterController is a lightweight MVC-style server framework for Node.js with mi
14
14
  - [CORS](#cors)
15
15
  - [Sessions](#sessions)
16
16
  - [Security](#security)
17
+ - [File Conversion & Binary Data](#file-conversion--binary-data)
17
18
  - [Components](#components)
18
19
  - [Timeout System](#timeout-system)
19
20
  - [Error Handling](#error-handling)
@@ -55,7 +56,13 @@ const cors = require('./cors.json');
55
56
  master.cors.init(cors);
56
57
 
57
58
  // Initialize sessions (auto-registers with pipeline)
58
- master.sessions.init();
59
+ master.session.init({
60
+ cookieName: 'mc_session',
61
+ maxAge: 3600000,
62
+ httpOnly: true,
63
+ secure: true,
64
+ sameSite: 'strict'
65
+ });
59
66
 
60
67
  // Auto-discover custom middleware from middleware/ folder
61
68
  master.pipeline.discoverMiddleware('middleware');
@@ -790,94 +797,129 @@ master.cors.init({
790
797
 
791
798
  ## Sessions
792
799
 
793
- ### `master.sessions.init(options)`
800
+ MasterController provides secure, Rails/Django-style sessions with automatic regeneration and protection.
801
+
802
+ ### Secure Sessions
803
+
804
+ #### `master.session.init(options)`
794
805
 
795
- Initialize sessions (auto-registers with middleware pipeline).
806
+ Initialize secure sessions with Rails/Django-style `req.session` object (auto-registers with middleware pipeline).
796
807
 
797
808
  ```javascript
798
- master.sessions.init({
799
- secret: 'your-secret-key',
800
- maxAge: 900000, // 15 minutes
801
- httpOnly: true,
802
- secure: true, // HTTPS only
803
- sameSite: true,
804
- path: '/'
809
+ // Environment-specific configuration
810
+ const isProduction = master.environmentType === 'production';
811
+
812
+ master.session.init({
813
+ cookieName: 'mc_session',
814
+ maxAge: isProduction ? 3600000 : 86400000, // Production: 1 hour, Dev: 24 hours
815
+ httpOnly: true, // Prevent JavaScript access (XSS protection)
816
+ secure: isProduction, // HTTPS only in production
817
+ sameSite: isProduction ? 'strict' : 'lax', // CSRF protection
818
+ rolling: true, // Extend session on each request
819
+ regenerateInterval: 900000, // Regenerate session ID every 15 minutes
820
+ useFingerprint: false // Session hijacking detection (opt-in)
805
821
  });
806
822
  ```
807
823
 
808
- ### Session API
824
+ **Security Features:**
825
+ - ✅ 32-byte (256-bit) session IDs (cryptographically secure)
826
+ - ✅ Automatic session regeneration (prevents fixation attacks)
827
+ - ✅ HttpOnly cookies (prevents XSS cookie theft)
828
+ - ✅ Secure flag for HTTPS (prevents MITM attacks)
829
+ - ✅ SameSite CSRF protection
830
+ - ✅ Rolling sessions (extends expiry on activity)
831
+ - ✅ Automatic cleanup of expired sessions
832
+ - ✅ Optional fingerprinting (detects hijacking)
833
+
834
+ #### Using Sessions in Controllers
809
835
 
810
- #### `master.sessions.set(name, data, response, secret, options)`
811
- Create a session.
836
+ Sessions are accessed via `obj.request.session` object:
812
837
 
813
838
  ```javascript
814
839
  class AuthController {
815
840
  login(obj) {
816
841
  const user = authenticateUser(obj.params.formData);
817
842
 
818
- master.sessions.set('user', user, obj.response);
843
+ // Set session data (Rails/Express style)
844
+ obj.request.session.userId = user.id;
845
+ obj.request.session.username = user.name;
846
+ obj.request.session.loggedInAt = Date.now();
819
847
 
820
848
  this.redirect('/dashboard');
821
849
  }
850
+
851
+ logout(obj) {
852
+ // Destroy entire session
853
+ master.session.destroy(obj.request, obj.response);
854
+ this.redirect('/');
855
+ }
822
856
  }
823
857
  ```
824
858
 
825
- #### `master.sessions.get(name, request, secret)`
826
- Retrieve session data.
827
-
828
859
  ```javascript
829
860
  class DashboardController {
830
861
  index(obj) {
831
- const user = master.sessions.get('user', obj.request);
862
+ // Read session data
863
+ const userId = obj.request.session.userId;
832
864
 
833
- if (!user) {
865
+ if (!userId) {
834
866
  this.redirect('/login');
835
867
  return;
836
868
  }
837
869
 
838
- this.render('dashboard', { user });
870
+ this.render('dashboard', { userId });
839
871
  }
840
872
  }
841
873
  ```
842
874
 
843
- #### `master.sessions.delete(name, response)`
844
- Delete a session.
875
+ #### Session Management API
876
+
877
+ **`master.session.destroy(req, res)`** - Destroy session completely
845
878
 
846
879
  ```javascript
847
- class AuthController {
848
- logout(obj) {
849
- master.sessions.delete('user', obj.response);
850
- this.redirect('/');
851
- }
852
- }
880
+ master.session.destroy(obj.request, obj.response);
853
881
  ```
854
882
 
855
- #### `master.sessions.reset()`
856
- Clear all sessions (useful for testing).
883
+ **`master.session.touch(sessionId)`** - Extend session expiry
857
884
 
858
885
  ```javascript
859
- master.sessions.reset();
886
+ master.session.touch(obj.request.sessionId);
860
887
  ```
861
888
 
862
- ### Cookie Methods
889
+ **`master.session.getSessionCount()`** - Get active session count (monitoring)
863
890
 
864
- Direct cookie access:
865
-
866
- #### `master.sessions.setCookie(name, value, response, options)`
867
891
  ```javascript
868
- master.sessions.setCookie('theme', 'dark', obj.response);
892
+ const count = master.session.getSessionCount();
893
+ console.log(`Active sessions: ${count}`);
869
894
  ```
870
895
 
871
- #### `master.sessions.getCookie(name, request, secret)`
896
+ **`master.session.clearAllSessions()`** - Clear all sessions (testing only)
897
+
872
898
  ```javascript
873
- const theme = master.sessions.getCookie('theme', obj.request);
899
+ master.session.clearAllSessions();
874
900
  ```
875
901
 
876
- #### `master.sessions.deleteCookie(name, response, options)`
902
+ #### Environment-Specific Best Practices
903
+
877
904
  ```javascript
878
- master.sessions.deleteCookie('theme', obj.response);
905
+ // Get recommended settings
906
+ const settings = master.session.getBestPractices('production');
907
+ master.session.init(settings);
879
908
  ```
880
909
 
910
+ **Production Settings:**
911
+ - Secure: true (HTTPS only)
912
+ - SameSite: 'strict' (maximum CSRF protection)
913
+ - MaxAge: 1 hour (short-lived sessions)
914
+ - RegenerateInterval: 15 minutes
915
+
916
+ **Development Settings:**
917
+ - Secure: false (allow HTTP)
918
+ - SameSite: 'lax' (easier testing)
919
+ - MaxAge: 24 hours (convenient for development)
920
+ - RegenerateInterval: 1 hour
921
+
922
+
881
923
  ---
882
924
 
883
925
  ## Security
@@ -987,350 +1029,2173 @@ class UsersController {
987
1029
  - `detectSQLInjection(input)` - Detect SQL injection
988
1030
  - `detectCommandInjection(input)` - Detect command injection
989
1031
 
990
- ---
991
-
992
- ## Components
1032
+ ### File Upload Security
993
1033
 
994
- Components are self-contained modules with their own routes, controllers, and views.
1034
+ MasterController v1.3.1 includes built-in protection against file upload attacks and DoS.
995
1035
 
996
- ### Structure
1036
+ #### Request Body Size Limits
997
1037
 
998
- ```
999
- components/
1000
- user/
1001
- config/
1002
- initializers/
1003
- config.js
1004
- routes.js
1005
- app/
1006
- controllers/
1007
- authController.js
1008
- views/
1009
- auth/
1010
- login.html
1011
- models/
1012
- userContext.js
1038
+ **config/initializers/request.json:**
1039
+ ```json
1040
+ {
1041
+ "disableFormidableMultipartFormData": false,
1042
+ "formidable": {
1043
+ "multiples": true,
1044
+ "keepExtensions": true,
1045
+ "maxFileSize": 10485760, // 10MB per file
1046
+ "maxFieldsSize": 2097152, // 2MB total form fields
1047
+ "maxFields": 1000, // Max number of fields
1048
+ "allowEmptyFiles": false, // Reject empty files
1049
+ "minFileSize": 1 // Reject 0-byte files
1050
+ },
1051
+ "maxBodySize": 10485760, // 10MB for form-urlencoded
1052
+ "maxJsonSize": 1048576, // 1MB for JSON payloads
1053
+ "maxTextSize": 1048576 // 1MB for text/plain
1054
+ }
1013
1055
  ```
1014
1056
 
1015
- ### Register Component
1057
+ **DoS Protection:**
1058
+ - All request bodies are size-limited (prevents memory exhaustion)
1059
+ - Connections destroyed if limits exceeded
1060
+ - Configurable per content-type
1016
1061
 
1017
- ```javascript
1018
- // In config/initializers/config.js
1019
- master.component('components', 'user');
1020
- master.component('components', 'mail');
1021
- ```
1062
+ #### File Type Validation
1022
1063
 
1023
- ### Absolute Path Components
1064
+ **Always validate file types in your controllers:**
1024
1065
 
1025
1066
  ```javascript
1026
- // Load component from absolute path
1027
- master.component('/var/www/shared-components', 'analytics');
1028
- ```
1067
+ class UploadController {
1068
+ uploadImage(obj) {
1069
+ const file = obj.params.formData.files.avatar[0];
1070
+
1071
+ // 1. Validate MIME type
1072
+ const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
1073
+ if (!allowedTypes.includes(file.mimetype)) {
1074
+ this.json({ error: 'Only images allowed (JPEG, PNG, GIF, WebP)' });
1075
+ return;
1076
+ }
1029
1077
 
1030
- Components are isolated and can be reused across projects.
1078
+ // 2. Validate file extension
1079
+ const allowedExts = ['.jpg', '.jpeg', '.png', '.gif', '.webp'];
1080
+ if (!allowedExts.includes(file.extension.toLowerCase())) {
1081
+ this.json({ error: 'Invalid file extension' });
1082
+ return;
1083
+ }
1031
1084
 
1032
- ---
1085
+ // 3. Validate file size (additional check)
1086
+ const maxSize = 5 * 1024 * 1024; // 5MB
1087
+ if (file.size > maxSize) {
1088
+ this.json({ error: 'File too large (max 5MB)' });
1089
+ return;
1090
+ }
1033
1091
 
1034
- ## Timeout System
1092
+ // 4. Generate safe filename (prevent path traversal)
1093
+ const crypto = require('crypto');
1094
+ const safeFilename = crypto.randomBytes(16).toString('hex') + file.extension;
1095
+ const uploadPath = path.join(master.root, 'uploads', safeFilename);
1035
1096
 
1036
- MasterController v2.0 includes a professional timeout system with per-request tracking (Rails/Django style).
1097
+ // 5. Move file
1098
+ fs.renameSync(file.filepath, uploadPath);
1037
1099
 
1038
- ### Configuration
1100
+ this.json({ success: true, filename: safeFilename });
1101
+ }
1039
1102
 
1040
- ```javascript
1041
- // config/initializers/config.js
1042
- master.timeout.init({
1043
- globalTimeout: 120000, // 120 seconds (2 minutes) default
1044
- enabled: true,
1045
- onTimeout: (ctx, timeoutInfo) => {
1046
- // Optional custom timeout handler
1047
- console.log(`Request timeout: ${timeoutInfo.path}`);
1103
+ uploadDocument(obj) {
1104
+ const file = obj.params.formData.files.document[0];
1105
+
1106
+ // Allow PDF, DOC, DOCX only
1107
+ const allowedTypes = [
1108
+ 'application/pdf',
1109
+ 'application/msword',
1110
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
1111
+ ];
1112
+
1113
+ if (!allowedTypes.includes(file.mimetype)) {
1114
+ this.json({ error: 'Only PDF and Word documents allowed' });
1115
+ return;
1116
+ }
1117
+
1118
+ // Process upload...
1048
1119
  }
1049
- });
1120
+ }
1121
+ ```
1050
1122
 
1051
- // Register timeout middleware
1052
- master.pipeline.use(master.timeout.middleware());
1123
+ #### Formidable Custom Filter
1124
+
1125
+ **Add file filter in request.json (formidable v3+):**
1126
+
1127
+ ```json
1128
+ {
1129
+ "formidable": {
1130
+ "filter": "function({ name, originalFilename, mimetype }) { return mimetype && mimetype.startsWith('image/'); }"
1131
+ }
1132
+ }
1053
1133
  ```
1054
1134
 
1055
- ### Route-Specific Timeouts
1135
+ **Note:** JSON doesn't support functions, so filters must be configured in code:
1056
1136
 
1057
1137
  ```javascript
1058
- // Short timeout for API endpoints
1059
- master.timeout.setRouteTimeout('/api/*', 30000); // 30 seconds
1138
+ // config/initializers/config.js
1139
+ const formidableOptions = master.env.request.formidable;
1060
1140
 
1061
- // Long timeout for reports
1062
- master.timeout.setRouteTimeout('/admin/reports', 300000); // 5 minutes
1141
+ // Add runtime filter for images only
1142
+ formidableOptions.filter = function({ name, originalFilename, mimetype }) {
1143
+ return mimetype && mimetype.startsWith('image/');
1144
+ };
1063
1145
 
1064
- // Very long timeout for batch operations
1065
- master.timeout.setRouteTimeout('/batch/process', 600000); // 10 minutes
1146
+ master.request.init({
1147
+ ...master.env.request,
1148
+ formidable: formidableOptions
1149
+ });
1066
1150
  ```
1067
1151
 
1068
- ### Timeout Statistics
1152
+ #### Security Best Practices
1153
+
1154
+ 1. **Always validate both MIME type AND file extension** (double check)
1155
+ 2. **Generate random filenames** (prevents overwriting and path traversal)
1156
+ 3. **Store uploads outside public directory** (prevent direct execution)
1157
+ 4. **Scan files for viruses** (use ClamAV or similar)
1158
+ 5. **Set proper file permissions** (chmod 644 for files, 755 for dirs)
1159
+ 6. **Never trust user-provided filenames** (can contain `../` or null bytes)
1160
+ 7. **Limit file sizes** (prevent disk space exhaustion)
1161
+ 8. **Delete temporary files** after processing
1162
+
1163
+ #### Delete Temporary Files
1069
1164
 
1070
1165
  ```javascript
1071
- const stats = master.timeout.getStats();
1166
+ class UploadController {
1167
+ upload(obj) {
1168
+ const file = obj.params.formData.files.upload[0];
1072
1169
 
1073
- console.log(stats);
1074
- // {
1075
- // enabled: true,
1076
- // globalTimeout: 120000,
1077
- // routeTimeouts: [
1078
- // { pattern: '/api/*', timeout: 30000 }
1079
- // ],
1080
- // activeRequests: 5,
1081
- // requests: [
1082
- // {
1083
- // requestId: 'req_1234567890_abc123',
1084
- // path: 'api/users',
1085
- // method: 'get',
1086
- // timeout: 30000,
1087
- // elapsed: 15000,
1088
- // remaining: 15000
1089
- // }
1090
- // ]
1091
- // }
1170
+ try {
1171
+ // Validate and process...
1172
+
1173
+ // Delete temp file after processing
1174
+ master.request.deleteFileBuffer(file.filepath);
1175
+
1176
+ this.json({ success: true });
1177
+ } catch (error) {
1178
+ // Always cleanup on error
1179
+ master.request.deleteFileBuffer(file.filepath);
1180
+ this.json({ error: error.message });
1181
+ }
1182
+ }
1183
+ }
1092
1184
  ```
1093
1185
 
1094
- ### Disable/Enable Timeouts
1186
+ ---
1187
+
1188
+ ## File Conversion & Binary Data
1189
+
1190
+ MasterController v1.3.1 includes production-grade utilities for converting between files, base64, and binary data. These are essential for working with uploaded files, API responses, and data storage.
1191
+
1192
+ ### Quick Start
1095
1193
 
1096
1194
  ```javascript
1097
- // Disable for debugging
1098
- master.timeout.disable();
1195
+ // Convert uploaded file to base64 for API response
1196
+ class UploadController {
1197
+ uploadImage(obj) {
1198
+ const file = obj.params.formData.files.image[0];
1199
+
1200
+ // Convert to base64 (with data URI for <img> src)
1201
+ const base64 = master.tools.fileToBase64(file, {
1202
+ includeDataURI: true, // Adds "data:image/jpeg;base64," prefix
1203
+ maxSize: 5 * 1024 * 1024 // 5MB limit
1204
+ });
1099
1205
 
1100
- // Re-enable
1101
- master.timeout.enable();
1206
+ this.json({
1207
+ success: true,
1208
+ imageData: base64 // Can be used directly in <img src="">
1209
+ });
1210
+ }
1211
+ }
1102
1212
  ```
1103
1213
 
1104
- ---
1214
+ ### File to Base64
1105
1215
 
1106
- ## Error Handling
1216
+ #### `master.tools.fileToBase64(filePathOrFile, options)`
1217
+
1218
+ Convert a file to base64 string (binary-safe for all file types).
1107
1219
 
1108
- MasterController v2.0 includes a professional error template system inspired by Rails and Django.
1220
+ **Parameters:**
1221
+ - `filePathOrFile`: File path string OR formidable file object
1222
+ - `options`:
1223
+ - `includeDataURI` (boolean) - Prepend data URI (e.g., `data:image/jpeg;base64,`)
1224
+ - `maxSize` (number) - Maximum file size in bytes (default: 10MB)
1109
1225
 
1110
- ### Error Renderer Configuration
1226
+ **Returns:** Base64 string
1227
+
1228
+ **Examples:**
1111
1229
 
1112
1230
  ```javascript
1113
- // config/initializers/config.js
1114
- master.errorRenderer.init({
1115
- templateDir: 'public/errors', // Error templates directory
1116
- environment: master.environmentType,
1117
- showStackTrace: master.environmentType === 'development' // Dev only
1231
+ // Convert file from file path
1232
+ const base64 = master.tools.fileToBase64('/path/to/image.jpg');
1233
+
1234
+ // Convert uploaded file with data URI
1235
+ const file = obj.params.formData.files.avatar[0];
1236
+ const dataURI = master.tools.fileToBase64(file, {
1237
+ includeDataURI: true,
1238
+ maxSize: 5 * 1024 * 1024 // 5MB
1118
1239
  });
1240
+
1241
+ // Use in HTML email or response
1242
+ const html = `<img src="${dataURI}" alt="Avatar">`;
1243
+
1244
+ // Store in database
1245
+ await db.query('UPDATE users SET avatar = ? WHERE id = ?', [base64, userId]);
1119
1246
  ```
1120
1247
 
1121
- ### Using Error Renderer
1248
+ **Error Handling:**
1122
1249
 
1123
1250
  ```javascript
1124
- // In middleware
1125
- master.pipeline.use(async (ctx, next) => {
1126
- if (!isAuthenticated(ctx)) {
1127
- master.errorRenderer.send(ctx, 401, {
1128
- message: 'Please log in to access this resource',
1129
- suggestions: [
1130
- 'Sign in with your credentials',
1131
- 'Request a password reset if forgotten'
1132
- ]
1133
- });
1134
- return;
1251
+ try {
1252
+ const base64 = master.tools.fileToBase64(file);
1253
+ } catch (error) {
1254
+ if (error.message.includes('not found')) {
1255
+ console.error('File does not exist');
1256
+ } else if (error.message.includes('exceeds maximum')) {
1257
+ console.error('File too large');
1258
+ } else if (error.message.includes('directory')) {
1259
+ console.error('Path is a directory, not a file');
1135
1260
  }
1136
- await next();
1137
- });
1261
+ }
1262
+ ```
1138
1263
 
1139
- // In controllers
1140
- class UsersController {
1141
- async show(obj) {
1142
- const userId = obj.params.userId;
1143
- const user = await this.db.query('SELECT * FROM users WHERE id = ?', [userId]);
1264
+ ---
1144
1265
 
1145
- if (!user) {
1146
- master.errorRenderer.send(obj, 404, {
1147
- message: `User #${userId} not found`,
1148
- suggestions: [
1149
- 'Check the user ID',
1150
- 'Browse all users',
1151
- 'Search for the user by name'
1152
- ]
1153
- });
1154
- return;
1155
- }
1266
+ ### Base64 to File
1156
1267
 
1157
- this.render('show', { user });
1268
+ #### `master.tools.base64ToFile(base64String, outputPath, options)`
1269
+
1270
+ Convert base64 string to a file on disk (binary-safe).
1271
+
1272
+ **Parameters:**
1273
+ - `base64String`: Base64 encoded string (with or without data URI prefix)
1274
+ - `outputPath`: Destination file path
1275
+ - `options`:
1276
+ - `overwrite` (boolean) - Allow overwriting existing files (default: false)
1277
+ - `createDir` (boolean) - Create parent directories if needed (default: true)
1278
+
1279
+ **Returns:** `{ success: true, filePath: outputPath, size: number }`
1280
+
1281
+ **Examples:**
1282
+
1283
+ ```javascript
1284
+ // Save base64 from API to file
1285
+ class ApiController {
1286
+ async saveImage(obj) {
1287
+ const base64Data = obj.params.formData.imageData;
1288
+
1289
+ // Save to disk
1290
+ const result = master.tools.base64ToFile(
1291
+ base64Data,
1292
+ './uploads/images/photo.jpg',
1293
+ { overwrite: false, createDir: true }
1294
+ );
1295
+
1296
+ this.json({
1297
+ success: true,
1298
+ path: result.filePath,
1299
+ size: result.size
1300
+ });
1158
1301
  }
1159
1302
  }
1160
- ```
1161
-
1162
- ### Error Templates
1163
1303
 
1164
- Create templates in `public/errors/`:
1304
+ // Data URI with prefix (automatically handled)
1305
+ const dataURI = 'data:image/png;base64,iVBORw0KGgoAAAANS...';
1306
+ master.tools.base64ToFile(dataURI, './output.png');
1165
1307
 
1166
- ```
1167
- public/errors/
1168
- ├── 400.html # Bad Request
1169
- ├── 401.html # Unauthorized
1170
- ├── 403.html # Forbidden
1171
- ├── 404.html # Not Found
1172
- ├── 405.html # Method Not Allowed
1173
- ├── 422.html # Unprocessable Entity
1174
- ├── 429.html # Too Many Requests
1175
- ├── 500.html # Internal Server Error
1176
- ├── 502.html # Bad Gateway
1177
- ├── 503.html # Service Unavailable
1178
- └── 504.html # Gateway Timeout
1308
+ // Pure base64 without prefix
1309
+ const pureBase64 = 'iVBORw0KGgoAAAANS...';
1310
+ master.tools.base64ToFile(pureBase64, './output.png');
1179
1311
  ```
1180
1312
 
1181
- **Template Variables:**
1313
+ ---
1182
1314
 
1183
- ```html
1184
- <!DOCTYPE html>
1185
- <html>
1186
- <head>
1187
- <title>{{title}} ({{statusCode}})</title>
1188
- </head>
1189
- <body>
1190
- <h1>{{statusCode}} - {{title}}</h1>
1191
- <p>{{message}}</p>
1315
+ ### Buffer Operations
1192
1316
 
1193
- <!-- Conditionals (dev only) -->
1194
- {{#if showStackTrace}}
1195
- <pre>{{stack}}</pre>
1196
- {{/if}}
1317
+ #### `master.tools.fileToBuffer(filePathOrFile, options)`
1197
1318
 
1198
- <!-- Loops -->
1199
- {{#each suggestions}}
1200
- <li>{{this}}</li>
1201
- {{/each}}
1202
- </body>
1203
- </html>
1204
- ```
1319
+ Convert file to Node.js Buffer (for in-memory processing).
1205
1320
 
1206
- **Available Variables:**
1207
- - `{{statusCode}}` - HTTP status code (404, 500, etc.)
1208
- - `{{title}}` - Error title
1209
- - `{{message}}` - Error message
1210
- - `{{code}}` - Error code
1211
- - `{{stack}}` - Stack trace (development only)
1212
- - `{{suggestions}}` - Array of suggestions
1213
- - `{{environment}}` - Current environment
1321
+ **Parameters:**
1322
+ - `filePathOrFile`: File path string OR formidable file object
1323
+ - `options`:
1324
+ - `maxSize` (number) - Maximum file size (default: 10MB)
1214
1325
 
1215
- ### Custom Error Handlers
1326
+ **Returns:** Node.js Buffer
1327
+
1328
+ **Examples:**
1216
1329
 
1217
1330
  ```javascript
1218
- // Register custom handler for specific status code
1219
- master.errorRenderer.registerHandler(503, (ctx, errorData) => {
1220
- return `
1221
- <!DOCTYPE html>
1222
- <html>
1223
- <body>
1224
- <h1>Maintenance Mode</h1>
1225
- <p>We'll be back soon! Expected completion: 2:00 PM EST</p>
1226
- </body>
1227
- </html>
1228
- `;
1229
- });
1331
+ // Read file into buffer
1332
+ const buffer = master.tools.fileToBuffer('./image.jpg');
1333
+
1334
+ // Process image with sharp library
1335
+ const sharp = require('sharp');
1336
+ const resized = await sharp(buffer)
1337
+ .resize(800, 600)
1338
+ .toBuffer();
1339
+
1340
+ // Convert buffer back to base64
1341
+ const base64 = master.tools.bytesToBase64(resized);
1230
1342
  ```
1231
1343
 
1232
- ### Content Negotiation
1344
+ ---
1233
1345
 
1234
- The error renderer automatically detects API requests and returns JSON:
1346
+ #### `master.tools.fileToBytes(filePathOrFile, options)`
1235
1347
 
1236
- ```javascript
1237
- // Browser request → HTML
1238
- GET /users/999
1239
- Accept: text/html
1240
- → Returns beautiful HTML error page
1348
+ Convert file to Uint8Array (for Web APIs and TypedArrays).
1241
1349
 
1242
- // API request → JSON
1243
- GET /api/users/999
1350
+ **Parameters:**
1351
+ - `filePathOrFile`: File path string OR formidable file object
1352
+ - `options`:
1353
+ - `maxSize` (number) - Maximum file size (default: 10MB)
1354
+
1355
+ **Returns:** Uint8Array
1356
+
1357
+ **Examples:**
1358
+
1359
+ ```javascript
1360
+ // Get raw bytes
1361
+ const bytes = master.tools.fileToBytes('./document.pdf');
1362
+
1363
+ // Send over WebSocket as binary
1364
+ websocket.send(bytes);
1365
+
1366
+ // Use with crypto
1367
+ const crypto = require('crypto');
1368
+ const hash = crypto.createHash('sha256').update(bytes).digest('hex');
1369
+ ```
1370
+
1371
+ ---
1372
+
1373
+ #### `master.tools.bytesToBase64(bufferOrBytes, options)`
1374
+
1375
+ Convert Buffer or Uint8Array to base64 string.
1376
+
1377
+ **Parameters:**
1378
+ - `bufferOrBytes`: Node.js Buffer OR Uint8Array
1379
+ - `options`:
1380
+ - `includeDataURI` (boolean) - Prepend data URI
1381
+ - `mimetype` (string) - MIME type for data URI (required if includeDataURI=true)
1382
+
1383
+ **Returns:** Base64 string
1384
+
1385
+ **Examples:**
1386
+
1387
+ ```javascript
1388
+ const buffer = Buffer.from('Hello World');
1389
+ const base64 = master.tools.bytesToBase64(buffer);
1390
+ // → 'SGVsbG8gV29ybGQ='
1391
+
1392
+ // With data URI
1393
+ const base64WithURI = master.tools.bytesToBase64(buffer, {
1394
+ includeDataURI: true,
1395
+ mimetype: 'text/plain'
1396
+ });
1397
+ // → 'data:text/plain;base64,SGVsbG8gV29ybGQ='
1398
+ ```
1399
+
1400
+ ---
1401
+
1402
+ #### `master.tools.base64ToBytes(base64String)`
1403
+
1404
+ Convert base64 string to Node.js Buffer.
1405
+
1406
+ **Parameters:**
1407
+ - `base64String`: Base64 string (with or without data URI prefix)
1408
+
1409
+ **Returns:** Node.js Buffer
1410
+
1411
+ **Examples:**
1412
+
1413
+ ```javascript
1414
+ const base64 = 'SGVsbG8gV29ybGQ=';
1415
+ const buffer = master.tools.base64ToBytes(base64);
1416
+ console.log(buffer.toString('utf8')); // → 'Hello World'
1417
+
1418
+ // Handles data URIs automatically
1419
+ const dataURI = 'data:text/plain;base64,SGVsbG8gV29ybGQ=';
1420
+ const buffer2 = master.tools.base64ToBytes(dataURI);
1421
+ ```
1422
+
1423
+ ---
1424
+
1425
+ ### Streaming Large Files
1426
+
1427
+ #### `master.tools.streamFileToBase64(filePathOrFile, options)`
1428
+
1429
+ Stream large files to base64 without loading into memory (async).
1430
+
1431
+ **Parameters:**
1432
+ - `filePathOrFile`: File path string OR formidable file object
1433
+ - `options`:
1434
+ - `includeDataURI` (boolean) - Prepend data URI
1435
+ - `chunkSize` (number) - Read chunk size (default: 64KB)
1436
+ - `onProgress` (function) - Progress callback: `(bytesRead, totalBytes, percent) => {}`
1437
+
1438
+ **Returns:** Promise<base64 string>
1439
+
1440
+ **Examples:**
1441
+
1442
+ ```javascript
1443
+ // Stream large video file to base64
1444
+ class VideoController {
1445
+ async processVideo(obj) {
1446
+ const file = obj.params.formData.files.video[0];
1447
+
1448
+ // Stream with progress tracking
1449
+ const base64 = await master.tools.streamFileToBase64(file, {
1450
+ includeDataURI: true,
1451
+ chunkSize: 128 * 1024, // 128KB chunks
1452
+ onProgress: (bytesRead, total, percent) => {
1453
+ console.log(`Processing: ${percent.toFixed(1)}% (${bytesRead}/${total} bytes)`);
1454
+
1455
+ // Send progress to client via WebSocket
1456
+ master.socket.emit('upload-progress', { percent });
1457
+ }
1458
+ });
1459
+
1460
+ this.json({ success: true, videoData: base64 });
1461
+ }
1462
+ }
1463
+
1464
+ // Process 500MB file without memory issues
1465
+ const largeFile = '/path/to/500mb-video.mp4';
1466
+ const base64 = await master.tools.streamFileToBase64(largeFile, {
1467
+ onProgress: (read, total, percent) => {
1468
+ console.log(`${percent.toFixed(1)}% complete`);
1469
+ }
1470
+ });
1471
+ ```
1472
+
1473
+ ---
1474
+
1475
+ ### Common Use Cases
1476
+
1477
+ #### Use Case 1: API Response with Embedded Image
1478
+
1479
+ ```javascript
1480
+ class ProductController {
1481
+ show(obj) {
1482
+ const product = db.getProduct(obj.params.id);
1483
+ const imagePath = `./uploads/products/${product.imageFilename}`;
1484
+
1485
+ // Convert image to base64 for API
1486
+ const imageData = master.tools.fileToBase64(imagePath, {
1487
+ includeDataURI: true,
1488
+ maxSize: 2 * 1024 * 1024 // 2MB limit
1489
+ });
1490
+
1491
+ this.json({
1492
+ id: product.id,
1493
+ name: product.name,
1494
+ image: imageData // Client can use directly in <img src="">
1495
+ });
1496
+ }
1497
+ }
1498
+ ```
1499
+
1500
+ #### Use Case 2: Store File in Database
1501
+
1502
+ ```javascript
1503
+ class DocumentController {
1504
+ async upload(obj) {
1505
+ const file = obj.params.formData.files.document[0];
1506
+
1507
+ // Validate file type
1508
+ const allowedTypes = ['application/pdf', 'application/msword'];
1509
+ if (!allowedTypes.includes(file.mimetype)) {
1510
+ this.json({ error: 'Only PDF and Word documents allowed' });
1511
+ return;
1512
+ }
1513
+
1514
+ // Convert to base64 for database storage
1515
+ const base64 = master.tools.fileToBase64(file, {
1516
+ maxSize: 10 * 1024 * 1024 // 10MB
1517
+ });
1518
+
1519
+ // Store in database
1520
+ await this.db.query(
1521
+ 'INSERT INTO documents (filename, mimetype, data) VALUES (?, ?, ?)',
1522
+ [file.originalFilename, file.mimetype, base64]
1523
+ );
1524
+
1525
+ // Delete temp file
1526
+ master.request.deleteFileBuffer(file.filepath);
1527
+
1528
+ this.json({ success: true });
1529
+ }
1530
+ }
1531
+ ```
1532
+
1533
+ #### Use Case 3: Retrieve File from Database
1534
+
1535
+ ```javascript
1536
+ class DocumentController {
1537
+ async download(obj) {
1538
+ const docId = obj.params.id;
1539
+
1540
+ // Get from database
1541
+ const doc = await this.db.query(
1542
+ 'SELECT filename, mimetype, data FROM documents WHERE id = ?',
1543
+ [docId]
1544
+ );
1545
+
1546
+ if (!doc) {
1547
+ master.errorRenderer.send(obj, 404, {
1548
+ message: 'Document not found'
1549
+ });
1550
+ return;
1551
+ }
1552
+
1553
+ // Convert base64 back to file
1554
+ const tempPath = `./temp/${Date.now()}-${doc.filename}`;
1555
+ master.tools.base64ToFile(doc.data, tempPath);
1556
+
1557
+ // Send file to client
1558
+ obj.response.setHeader('Content-Type', doc.mimetype);
1559
+ obj.response.setHeader('Content-Disposition', `attachment; filename="${doc.filename}"`);
1560
+
1561
+ const fs = require('fs');
1562
+ const fileStream = fs.createReadStream(tempPath);
1563
+ fileStream.pipe(obj.response);
1564
+
1565
+ // Cleanup after sending
1566
+ fileStream.on('end', () => {
1567
+ fs.unlinkSync(tempPath);
1568
+ });
1569
+ }
1570
+ }
1571
+ ```
1572
+
1573
+ #### Use Case 4: Image Processing Pipeline
1574
+
1575
+ ```javascript
1576
+ const sharp = require('sharp');
1577
+
1578
+ class ImageController {
1579
+ async processThumbnail(obj) {
1580
+ const file = obj.params.formData.files.image[0];
1581
+
1582
+ // Read file to buffer
1583
+ const buffer = master.tools.fileToBuffer(file, {
1584
+ maxSize: 10 * 1024 * 1024
1585
+ });
1586
+
1587
+ // Process with sharp
1588
+ const thumbnail = await sharp(buffer)
1589
+ .resize(200, 200, { fit: 'cover' })
1590
+ .jpeg({ quality: 80 })
1591
+ .toBuffer();
1592
+
1593
+ // Convert thumbnail to base64
1594
+ const base64 = master.tools.bytesToBase64(thumbnail, {
1595
+ includeDataURI: true,
1596
+ mimetype: 'image/jpeg'
1597
+ });
1598
+
1599
+ // Cleanup temp file
1600
+ master.request.deleteFileBuffer(file.filepath);
1601
+
1602
+ this.json({
1603
+ success: true,
1604
+ thumbnail: base64
1605
+ });
1606
+ }
1607
+ }
1608
+ ```
1609
+
1610
+ #### Use Case 5: Email with Embedded Images
1611
+
1612
+ ```javascript
1613
+ const nodemailer = require('nodemailer');
1614
+
1615
+ class EmailController {
1616
+ async sendWithImage(obj) {
1617
+ const file = obj.params.formData.files.logo[0];
1618
+
1619
+ // Convert to base64 data URI
1620
+ const logoData = master.tools.fileToBase64(file, {
1621
+ includeDataURI: true
1622
+ });
1623
+
1624
+ // Send email with embedded image
1625
+ const transporter = nodemailer.createTransport({/* config */});
1626
+ await transporter.sendMail({
1627
+ to: 'user@example.com',
1628
+ subject: 'Welcome!',
1629
+ html: `
1630
+ <h1>Welcome to our platform!</h1>
1631
+ <img src="${logoData}" alt="Logo">
1632
+ <p>Thanks for joining.</p>
1633
+ `
1634
+ });
1635
+
1636
+ // Cleanup
1637
+ master.request.deleteFileBuffer(file.filepath);
1638
+
1639
+ this.json({ success: true });
1640
+ }
1641
+ }
1642
+ ```
1643
+
1644
+ ---
1645
+
1646
+ ### Security Best Practices
1647
+
1648
+ 1. **Always set size limits:**
1649
+ ```javascript
1650
+ const base64 = master.tools.fileToBase64(file, {
1651
+ maxSize: 5 * 1024 * 1024 // Prevent DoS
1652
+ });
1653
+ ```
1654
+
1655
+ 2. **Validate file types before conversion:**
1656
+ ```javascript
1657
+ const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
1658
+ if (!allowedTypes.includes(file.mimetype)) {
1659
+ throw new Error('Invalid file type');
1660
+ }
1661
+ const base64 = master.tools.fileToBase64(file);
1662
+ ```
1663
+
1664
+ 3. **Delete temporary files after processing:**
1665
+ ```javascript
1666
+ try {
1667
+ const base64 = master.tools.fileToBase64(file);
1668
+ // ... process ...
1669
+ } finally {
1670
+ master.request.deleteFileBuffer(file.filepath);
1671
+ }
1672
+ ```
1673
+
1674
+ 4. **Use streaming for large files:**
1675
+ ```javascript
1676
+ // ❌ Bad: Loads entire 500MB file into memory
1677
+ const base64 = master.tools.fileToBase64(largeFile);
1678
+
1679
+ // ✅ Good: Streams in chunks
1680
+ const base64 = await master.tools.streamFileToBase64(largeFile, {
1681
+ chunkSize: 128 * 1024
1682
+ });
1683
+ ```
1684
+
1685
+ 5. **Validate base64 before decoding:**
1686
+ ```javascript
1687
+ try {
1688
+ const buffer = master.tools.base64ToBytes(untrustedBase64);
1689
+ } catch (error) {
1690
+ console.error('Invalid base64 data');
1691
+ }
1692
+ ```
1693
+
1694
+ ---
1695
+
1696
+ ### Deprecated Methods
1697
+
1698
+ #### `master.tools.base64()` - DEPRECATED
1699
+
1700
+ **⚠️ WARNING:** The original `base64()` method is **BROKEN for binary files** and should not be used. It uses `charCodeAt()` which only works correctly for text files (UTF-8). Binary files like images, PDFs, and videos will be corrupted.
1701
+
1702
+ **Do NOT use:**
1703
+ ```javascript
1704
+ // ❌ BROKEN - Corrupts binary files
1705
+ const broken = master.tools.base64(file.filepath);
1706
+ ```
1707
+
1708
+ **Use instead:**
1709
+ ```javascript
1710
+ // ✅ CORRECT - Binary-safe
1711
+ const correct = master.tools.fileToBase64(file);
1712
+ ```
1713
+
1714
+ The old method is kept for backward compatibility with text-only use cases, but all new code should use the production-grade methods documented above.
1715
+
1716
+ ---
1717
+
1718
+ ## Components
1719
+
1720
+ Components are self-contained modules with their own routes, controllers, and views.
1721
+
1722
+ ### Structure
1723
+
1724
+ ```
1725
+ components/
1726
+ user/
1727
+ config/
1728
+ initializers/
1729
+ config.js
1730
+ routes.js
1731
+ app/
1732
+ controllers/
1733
+ authController.js
1734
+ views/
1735
+ auth/
1736
+ login.html
1737
+ models/
1738
+ userContext.js
1739
+ ```
1740
+
1741
+ ### Register Component
1742
+
1743
+ ```javascript
1744
+ // In config/initializers/config.js
1745
+ master.component('components', 'user');
1746
+ master.component('components', 'mail');
1747
+ ```
1748
+
1749
+ ### Absolute Path Components
1750
+
1751
+ ```javascript
1752
+ // Load component from absolute path
1753
+ master.component('/var/www/shared-components', 'analytics');
1754
+ ```
1755
+
1756
+ Components are isolated and can be reused across projects.
1757
+
1758
+ ---
1759
+
1760
+ ## Timeout System
1761
+
1762
+ MasterController includes a production-ready timeout system with per-request tracking (Rails/Django style).
1763
+
1764
+ ### Quick Start
1765
+
1766
+ ```javascript
1767
+ // config/initializers/config.js
1768
+ const master = require('mastercontroller');
1769
+
1770
+ // Initialize timeout system
1771
+ master.timeout.init({
1772
+ globalTimeout: 120000, // 120 seconds (2 minutes) default
1773
+ enabled: true,
1774
+ onTimeout: (ctx, timeoutInfo) => {
1775
+ // Optional custom timeout handler
1776
+ console.log(`Request timeout: ${timeoutInfo.path}`);
1777
+ }
1778
+ });
1779
+
1780
+ // Register timeout middleware
1781
+ master.pipeline.use(master.timeout.middleware());
1782
+ ```
1783
+
1784
+ ### Route-Specific Timeouts
1785
+
1786
+ Configure different timeouts for different routes:
1787
+
1788
+ ```javascript
1789
+ // Short timeout for API endpoints (30 seconds)
1790
+ master.timeout.setRouteTimeout('/api/*', 30000);
1791
+
1792
+ // Long timeout for reports (5 minutes)
1793
+ master.timeout.setRouteTimeout('/admin/reports', 300000);
1794
+
1795
+ // Very long timeout for batch operations (10 minutes)
1796
+ master.timeout.setRouteTimeout('/batch/process', 600000);
1797
+
1798
+ // Critical operations (1 minute)
1799
+ master.timeout.setRouteTimeout('/checkout/*', 60000);
1800
+ ```
1801
+
1802
+ ### Environment-Specific Configuration
1803
+
1804
+ **config/environments/env.development.json:**
1805
+ ```json
1806
+ {
1807
+ "server": {
1808
+ "requestTimeout": 300000
1809
+ }
1810
+ }
1811
+ ```
1812
+
1813
+ **config/environments/env.production.json:**
1814
+ ```json
1815
+ {
1816
+ "server": {
1817
+ "requestTimeout": 120000
1818
+ }
1819
+ }
1820
+ ```
1821
+
1822
+ ### Timeout Response
1823
+
1824
+ When a request times out, the client receives:
1825
+
1826
+ ```json
1827
+ {
1828
+ "error": "Request Timeout",
1829
+ "message": "The server did not receive a complete request within the allowed time",
1830
+ "code": "MC_REQUEST_TIMEOUT",
1831
+ "timeout": 120000
1832
+ }
1833
+ ```
1834
+
1835
+ ### Monitoring Active Requests
1836
+
1837
+ ```javascript
1838
+ const stats = master.timeout.getStats();
1839
+
1840
+ console.log(stats);
1841
+ // {
1842
+ // enabled: true,
1843
+ // globalTimeout: 120000,
1844
+ // routeTimeouts: [
1845
+ // { pattern: '/api/*', timeout: 30000 },
1846
+ // { pattern: '/admin/reports', timeout: 300000 }
1847
+ // ],
1848
+ // activeRequests: 5,
1849
+ // requests: [
1850
+ // {
1851
+ // requestId: 'req_1234567890_abc123',
1852
+ // path: 'api/users',
1853
+ // method: 'get',
1854
+ // timeout: 30000,
1855
+ // elapsed: 15000,
1856
+ // remaining: 15000
1857
+ // }
1858
+ // ]
1859
+ // }
1860
+
1861
+ // Check for slow requests
1862
+ stats.requests.forEach(req => {
1863
+ if (req.elapsed > req.timeout * 0.8) {
1864
+ console.warn(`Request close to timeout: ${req.path} (${req.elapsed}ms/${req.timeout}ms)`);
1865
+ }
1866
+ });
1867
+ ```
1868
+
1869
+ ### Disable/Enable Timeouts
1870
+
1871
+ ```javascript
1872
+ // Disable for debugging
1873
+ master.timeout.disable();
1874
+
1875
+ // Re-enable
1876
+ master.timeout.enable();
1877
+
1878
+ // Check status
1879
+ console.log(master.timeout.getStats().enabled); // true/false
1880
+ ```
1881
+
1882
+ ### Complete Setup Example
1883
+
1884
+ ```javascript
1885
+ // config/initializers/config.js
1886
+ const master = require('mastercontroller');
1887
+
1888
+ // Initialize timeout system
1889
+ master.timeout.init({
1890
+ globalTimeout: master.env.server.requestTimeout || 120000,
1891
+ enabled: true,
1892
+ onTimeout: (ctx, timeoutInfo) => {
1893
+ // Log timeout
1894
+ console.error(`Request timeout: ${timeoutInfo.path} (${timeoutInfo.duration}ms)`);
1895
+
1896
+ // Send to monitoring service
1897
+ sendToMonitoring('timeout', timeoutInfo);
1898
+ }
1899
+ });
1900
+
1901
+ // Configure route-specific timeouts
1902
+ master.timeout.setRouteTimeout('/api/*', 30000); // API: 30s
1903
+ master.timeout.setRouteTimeout('/admin/reports', 300000); // Reports: 5m
1904
+ master.timeout.setRouteTimeout('/batch/*', 600000); // Batch: 10m
1905
+
1906
+ // Register middleware
1907
+ master.pipeline.use(master.timeout.middleware());
1908
+
1909
+ // Monitor timeouts periodically
1910
+ setInterval(() => {
1911
+ const stats = master.timeout.getStats();
1912
+
1913
+ if (stats.activeRequests > 100) {
1914
+ console.warn(`High number of active requests: ${stats.activeRequests}`);
1915
+ }
1916
+ }, 60000); // Every minute
1917
+ ```
1918
+
1919
+ ### Best Practices
1920
+
1921
+ 1. **Set appropriate global timeout**: 120 seconds (2 minutes) is a good default
1922
+ 2. **Use route-specific timeouts**: APIs should have shorter timeouts (30s)
1923
+ 3. **Long operations**: Use background jobs instead of long timeouts
1924
+ 4. **Disable in development**: For debugging, temporarily disable timeouts
1925
+ 5. **Monitor statistics**: Regularly check active requests and slow requests
1926
+
1927
+ ---
1928
+
1929
+ ## Error Handling
1930
+
1931
+ MasterController includes a professional error template system inspired by Rails and Django.
1932
+
1933
+ ### Quick Start
1934
+
1935
+ ```javascript
1936
+ // config/initializers/config.js
1937
+ const master = require('mastercontroller');
1938
+
1939
+ // Initialize error renderer
1940
+ master.errorRenderer.init({
1941
+ templateDir: 'public/errors', // Error templates directory
1942
+ environment: master.environmentType,
1943
+ showStackTrace: master.environmentType === 'development' // Dev only
1944
+ });
1945
+ ```
1946
+
1947
+ ### Using Error Renderer
1948
+
1949
+ **In Middleware:**
1950
+ ```javascript
1951
+ master.pipeline.use(async (ctx, next) => {
1952
+ if (!isAuthenticated(ctx)) {
1953
+ master.errorRenderer.send(ctx, 401, {
1954
+ message: 'Please log in to access this resource',
1955
+ suggestions: [
1956
+ 'Sign in with your credentials',
1957
+ 'Request a password reset if forgotten',
1958
+ 'Contact support for account issues'
1959
+ ]
1960
+ });
1961
+ return;
1962
+ }
1963
+ await next();
1964
+ });
1965
+ ```
1966
+
1967
+ **In Controllers:**
1968
+ ```javascript
1969
+ class UsersController {
1970
+ async show(obj) {
1971
+ const userId = obj.params.userId;
1972
+ const user = await this.db.query('SELECT * FROM users WHERE id = ?', [userId]);
1973
+
1974
+ if (!user) {
1975
+ master.errorRenderer.send(obj, 404, {
1976
+ message: `User #${userId} not found`,
1977
+ suggestions: [
1978
+ 'Check the user ID',
1979
+ 'Browse all users',
1980
+ 'Search for the user by name'
1981
+ ]
1982
+ });
1983
+ return;
1984
+ }
1985
+
1986
+ this.render('show', { user });
1987
+ }
1988
+
1989
+ async update(obj) {
1990
+ try {
1991
+ const userId = obj.params.id;
1992
+ const updates = obj.params.formData;
1993
+
1994
+ await this.db.query('UPDATE users SET ? WHERE id = ?', [updates, userId]);
1995
+ this.redirect(`/users/${userId}`);
1996
+ } catch (error) {
1997
+ console.error('Update failed:', error);
1998
+
1999
+ master.errorRenderer.send(obj, 500, {
2000
+ message: 'Failed to update user',
2001
+ code: 'DB_ERROR',
2002
+ stack: error.stack
2003
+ });
2004
+ }
2005
+ }
2006
+ }
2007
+ ```
2008
+
2009
+ ### Error Templates
2010
+
2011
+ Create templates in `public/errors/`:
2012
+
2013
+ ```
2014
+ public/errors/
2015
+ ├── 400.html # Bad Request
2016
+ ├── 401.html # Unauthorized
2017
+ ├── 403.html # Forbidden
2018
+ ├── 404.html # Not Found
2019
+ ├── 405.html # Method Not Allowed
2020
+ ├── 422.html # Unprocessable Entity
2021
+ ├── 429.html # Too Many Requests
2022
+ ├── 500.html # Internal Server Error
2023
+ ├── 502.html # Bad Gateway
2024
+ ├── 503.html # Service Unavailable
2025
+ └── 504.html # Gateway Timeout
2026
+ ```
2027
+
2028
+ **Template Variables:**
2029
+
2030
+ ```html
2031
+ <!DOCTYPE html>
2032
+ <html>
2033
+ <head>
2034
+ <title>{{title}} ({{statusCode}})</title>
2035
+ </head>
2036
+ <body>
2037
+ <h1>{{statusCode}} - {{title}}</h1>
2038
+ <p>{{message}}</p>
2039
+
2040
+ <!-- Conditionals (dev only) -->
2041
+ {{#if showStackTrace}}
2042
+ <pre>{{stack}}</pre>
2043
+ {{/if}}
2044
+
2045
+ <!-- Loops -->
2046
+ {{#each suggestions}}
2047
+ <li>{{this}}</li>
2048
+ {{/each}}
2049
+ </body>
2050
+ </html>
2051
+ ```
2052
+
2053
+ **Available Variables:**
2054
+ - `{{statusCode}}` - HTTP status code (404, 500, etc.)
2055
+ - `{{title}}` - Error title
2056
+ - `{{message}}` - Error message
2057
+ - `{{code}}` - Error code
2058
+ - `{{stack}}` - Stack trace (development only)
2059
+ - `{{suggestions}}` - Array of suggestions
2060
+ - `{{environment}}` - Current environment
2061
+
2062
+ ### Custom Error Handlers
2063
+
2064
+ Register custom error handlers for specific status codes:
2065
+
2066
+ ```javascript
2067
+ // Custom 404 handler
2068
+ master.errorRenderer.registerHandler(404, (ctx, errorData) => {
2069
+ return `
2070
+ <!DOCTYPE html>
2071
+ <html>
2072
+ <head>
2073
+ <title>Page Not Found</title>
2074
+ <style>
2075
+ body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
2076
+ .icon { font-size: 100px; }
2077
+ h1 { color: #333; }
2078
+ </style>
2079
+ </head>
2080
+ <body>
2081
+ <div class="icon">🔍</div>
2082
+ <h1>Page Not Found</h1>
2083
+ <p>${errorData.message}</p>
2084
+ <a href="/">Go Home</a>
2085
+ </body>
2086
+ </html>
2087
+ `;
2088
+ });
2089
+
2090
+ // Custom 500 handler with logging
2091
+ master.errorRenderer.registerHandler(500, (ctx, errorData) => {
2092
+ // Log to external service
2093
+ logToSentry(errorData);
2094
+
2095
+ return `
2096
+ <!DOCTYPE html>
2097
+ <html>
2098
+ <body>
2099
+ <h1>Oops! Something went wrong</h1>
2100
+ <p>Our team has been notified.</p>
2101
+ <p>Reference: ${errorData.code}</p>
2102
+ </body>
2103
+ </html>
2104
+ `;
2105
+ });
2106
+
2107
+ // Custom 503 handler (maintenance mode)
2108
+ master.errorRenderer.registerHandler(503, (ctx, errorData) => {
2109
+ return `
2110
+ <!DOCTYPE html>
2111
+ <html>
2112
+ <head>
2113
+ <title>Maintenance Mode</title>
2114
+ <style>
2115
+ body {
2116
+ font-family: Arial, sans-serif;
2117
+ text-align: center;
2118
+ padding: 50px;
2119
+ }
2120
+ .icon { font-size: 100px; }
2121
+ h1 { color: #333; }
2122
+ </style>
2123
+ </head>
2124
+ <body>
2125
+ <div class="icon">🔧</div>
2126
+ <h1>We'll be back soon!</h1>
2127
+ <p>We're performing scheduled maintenance.</p>
2128
+ <p>Expected completion: 2:00 PM EST</p>
2129
+ </body>
2130
+ </html>
2131
+ `;
2132
+ });
2133
+ ```
2134
+
2135
+ ### Content Negotiation
2136
+
2137
+ The error renderer automatically detects API requests and returns JSON:
2138
+
2139
+ ```javascript
2140
+ // Browser request → HTML
2141
+ GET /users/999
2142
+ Accept: text/html
2143
+ → Returns beautiful HTML error page
2144
+
2145
+ // API request → JSON
2146
+ GET /api/users/999
1244
2147
  Accept: application/json
1245
2148
  → Returns JSON error response
1246
2149
  {
1247
- "error": "Page Not Found",
1248
- "statusCode": 404,
1249
- "code": "MC_HTTP_ERROR",
1250
- "message": "The user you're looking for doesn't exist."
2150
+ "error": "Page Not Found",
2151
+ "statusCode": 404,
2152
+ "code": "MC_HTTP_ERROR",
2153
+ "message": "The user you're looking for doesn't exist."
2154
+ }
2155
+ ```
2156
+
2157
+ ### Global Error Handler (Pipeline)
2158
+
2159
+ ```javascript
2160
+ master.pipeline.useError(async (error, ctx, next) => {
2161
+ console.error('Pipeline error:', error);
2162
+
2163
+ // Use error renderer for HTTP errors
2164
+ master.errorRenderer.send(ctx, 500, {
2165
+ message: error.message,
2166
+ code: error.code,
2167
+ stack: error.stack
2168
+ });
2169
+ });
2170
+ ```
2171
+
2172
+ ### Controller Error Handling
2173
+
2174
+ ```javascript
2175
+ class UsersController {
2176
+ async index(obj) {
2177
+ try {
2178
+ const users = await this.db.query('SELECT * FROM users');
2179
+ this.render('index', { users });
2180
+ } catch (error) {
2181
+ console.error('Database error:', error);
2182
+
2183
+ master.errorRenderer.send(obj, 500, {
2184
+ message: 'Failed to load users',
2185
+ code: 'DB_ERROR',
2186
+ stack: error.stack
2187
+ });
2188
+ }
2189
+ }
2190
+ }
2191
+ ```
2192
+
2193
+ ### Logging
2194
+
2195
+ ```javascript
2196
+ const { logger } = require('./error/MasterErrorLogger');
2197
+
2198
+ // In controllers or middleware
2199
+ logger.info({
2200
+ code: 'USER_LOGIN',
2201
+ message: 'User logged in',
2202
+ userId: user.id
2203
+ });
2204
+
2205
+ logger.warn({
2206
+ code: 'INVALID_INPUT',
2207
+ message: 'Invalid email format',
2208
+ email: input
2209
+ });
2210
+
2211
+ logger.error({
2212
+ code: 'DB_ERROR',
2213
+ message: 'Database query failed',
2214
+ error: error.message,
2215
+ stack: error.stack
2216
+ });
2217
+ ```
2218
+
2219
+ ### Common Use Cases
2220
+
2221
+ **Rate Limiting with Custom 429 Page:**
2222
+ ```javascript
2223
+ const rateLimit = new Map();
2224
+
2225
+ master.pipeline.map('/api/*', (api) => {
2226
+ api.use(async (ctx, next) => {
2227
+ const clientId = ctx.request.connection.remoteAddress;
2228
+ const requests = rateLimit.get(clientId) || [];
2229
+ const now = Date.now();
2230
+
2231
+ // Remove requests older than 1 minute
2232
+ const recent = requests.filter(time => now - time < 60000);
2233
+
2234
+ if (recent.length >= 100) {
2235
+ master.errorRenderer.send(ctx, 429, {
2236
+ message: 'Rate limit exceeded (100 requests per minute)',
2237
+ suggestions: [
2238
+ 'Wait 60 seconds and try again',
2239
+ 'Upgrade to a higher tier plan',
2240
+ 'Contact support for increased limits'
2241
+ ]
2242
+ });
2243
+ return;
2244
+ }
2245
+
2246
+ recent.push(now);
2247
+ rateLimit.set(clientId, recent);
2248
+ await next();
2249
+ });
2250
+ });
2251
+ ```
2252
+
2253
+ **Protected Admin Section:**
2254
+ ```javascript
2255
+ master.pipeline.map('/admin/*', (admin) => {
2256
+ admin.use(async (ctx, next) => {
2257
+ if (!ctx.state.user || !ctx.state.user.isAdmin) {
2258
+ master.errorRenderer.send(ctx, 403, {
2259
+ message: 'Admin access required',
2260
+ suggestions: [
2261
+ 'Sign in with an admin account',
2262
+ 'Contact an administrator for access'
2263
+ ]
2264
+ });
2265
+ return;
2266
+ }
2267
+ await next();
2268
+ });
2269
+ });
2270
+ ```
2271
+
2272
+ **Maintenance Mode:**
2273
+ ```javascript
2274
+ const maintenanceMode = process.env.MAINTENANCE === 'true';
2275
+
2276
+ if (maintenanceMode) {
2277
+ master.pipeline.use(async (ctx, next) => {
2278
+ master.errorRenderer.send(ctx, 503, {
2279
+ message: 'Service temporarily unavailable'
2280
+ });
2281
+ });
2282
+ }
2283
+ ```
2284
+
2285
+ ### Complete Setup Example
2286
+
2287
+ ```javascript
2288
+ // config/initializers/config.js
2289
+ const master = require('mastercontroller');
2290
+
2291
+ // Initialize error renderer
2292
+ master.errorRenderer.init({
2293
+ templateDir: 'public/errors',
2294
+ environment: master.environmentType,
2295
+ showStackTrace: master.environmentType === 'development'
2296
+ });
2297
+
2298
+ // Register custom handlers
2299
+ master.errorRenderer.registerHandler(404, (ctx, errorData) => {
2300
+ return `
2301
+ <!DOCTYPE html>
2302
+ <html>
2303
+ <body>
2304
+ <h1>404 - Page Not Found</h1>
2305
+ <p>${errorData.message}</p>
2306
+ <a href="/">Go Home</a>
2307
+ </body>
2308
+ </html>
2309
+ `;
2310
+ });
2311
+
2312
+ // Global error handler
2313
+ master.pipeline.useError(async (error, ctx, next) => {
2314
+ console.error('Pipeline error:', error);
2315
+
2316
+ master.errorRenderer.send(ctx, 500, {
2317
+ message: error.message,
2318
+ code: error.code,
2319
+ stack: error.stack
2320
+ });
2321
+ });
2322
+ ```
2323
+
2324
+ ### Best Practices
2325
+
2326
+ 1. **Keep error messages user-friendly**: Don't expose technical details in production
2327
+ 2. **Show stack traces in development only**: Use `showStackTrace` conditional
2328
+ 3. **Provide actionable suggestions**: Help users resolve the issue
2329
+ 4. **Consistent design**: Match your application's design
2330
+ 5. **Test all error codes**: Ensure templates render correctly
2331
+ 6. **Log errors**: Use `logger` for error tracking
2332
+ 7. **Monitor errors**: Track error rates and patterns
2333
+
2334
+ ---
2335
+
2336
+ ## HTTPS Setup
2337
+
2338
+ MasterController v1.3.2 includes **production-grade HTTPS/TLS security** with automatic secure defaults.
2339
+
2340
+ ### 🔒 Security Features (Automatic)
2341
+
2342
+ When you setup HTTPS, MasterController automatically configures:
2343
+ - ✅ **TLS 1.3** by default (2026 security standard)
2344
+ - ✅ **Secure cipher suites** (Mozilla Intermediate configuration)
2345
+ - ✅ **Path traversal protection** for static files
2346
+ - ✅ **Open redirect protection** for HTTP→HTTPS redirects
2347
+ - ✅ **SNI support** for multiple domains
2348
+ - ✅ **Certificate live reload** (zero-downtime updates)
2349
+ - ✅ **HSTS support** with preload option
2350
+
2351
+ ---
2352
+
2353
+ ## Quick Start: HTTPS in 5 Minutes
2354
+
2355
+ ### Development (Self-Signed Certificate)
2356
+
2357
+ **Step 1: Generate Self-Signed Certificate**
2358
+ ```bash
2359
+ # Create certificates directory
2360
+ mkdir -p certs
2361
+ cd certs
2362
+
2363
+ # Generate self-signed certificate (valid for 365 days)
2364
+ openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem \
2365
+ -days 365 -nodes -subj "/CN=localhost"
2366
+
2367
+ # Combine for convenience
2368
+ cat key.pem > localhost.pem
2369
+ cat cert.pem >> localhost.pem
2370
+
2371
+ cd ..
2372
+ ```
2373
+
2374
+ **Step 2: Update `server.js`**
2375
+ ```javascript
2376
+ const fs = require('fs');
2377
+ const master = require('mastercontroller');
2378
+
2379
+ master.environmentType = process.env.NODE_ENV || 'development';
2380
+ master.root = __dirname;
2381
+
2382
+ // Setup HTTPS for development
2383
+ const server = master.setupServer('https', {
2384
+ key: fs.readFileSync('./certs/key.pem'),
2385
+ cert: fs.readFileSync('./certs/cert.pem')
2386
+ });
2387
+
2388
+ require('./config/initializers/config');
2389
+
2390
+ master.start(server);
2391
+ master.serverSettings({ httpPort: 3000 }); // Use 3000 for development
2392
+
2393
+ console.log('✅ HTTPS server running on https://localhost:3000');
2394
+ console.log('⚠️ Self-signed certificate - browser will show warning (this is normal)');
2395
+ ```
2396
+
2397
+ **Step 3: Visit `https://localhost:3000`**
2398
+ - Browser will show "Not Secure" warning
2399
+ - Click "Advanced" → "Proceed to localhost" (safe for development)
2400
+
2401
+ ---
2402
+
2403
+ ### Production (Let's Encrypt - FREE)
2404
+
2405
+ **Step 1: Install Certbot**
2406
+ ```bash
2407
+ # Ubuntu/Debian
2408
+ sudo apt update
2409
+ sudo apt install certbot
2410
+
2411
+ # CentOS/RHEL
2412
+ sudo yum install certbot
2413
+
2414
+ # macOS
2415
+ brew install certbot
2416
+ ```
2417
+
2418
+ **Step 2: Get FREE SSL Certificate**
2419
+ ```bash
2420
+ # Stop any web server on port 80
2421
+ sudo systemctl stop nginx
2422
+
2423
+ # Get certificate (replace with your domain)
2424
+ sudo certbot certonly --standalone -d yourapp.com -d www.yourapp.com
2425
+
2426
+ # Certificates will be saved to:
2427
+ # /etc/letsencrypt/live/yourapp.com/privkey.pem
2428
+ # /etc/letsencrypt/live/yourapp.com/fullchain.pem
2429
+ ```
2430
+
2431
+ **Step 3: Update `server.js` for Production**
2432
+ ```javascript
2433
+ const fs = require('fs');
2434
+ const master = require('mastercontroller');
2435
+
2436
+ master.environmentType = process.env.NODE_ENV || 'production';
2437
+ master.root = __dirname;
2438
+
2439
+ // Setup HTTPS with Let's Encrypt certificates
2440
+ const server = master.setupServer('https', {
2441
+ key: fs.readFileSync('/etc/letsencrypt/live/yourapp.com/privkey.pem'),
2442
+ cert: fs.readFileSync('/etc/letsencrypt/live/yourapp.com/fullchain.pem')
2443
+ });
2444
+
2445
+ // Enable HSTS (strongly recommended)
2446
+ master.enableHSTS({
2447
+ maxAge: 31536000, // 1 year
2448
+ includeSubDomains: true,
2449
+ preload: true
2450
+ });
2451
+
2452
+ require('./config/initializers/config');
2453
+
2454
+ // Start HTTPS on port 443
2455
+ master.start(server);
2456
+ master.serverSettings({ httpPort: 443 });
2457
+
2458
+ // Redirect HTTP to HTTPS (port 80 → 443)
2459
+ const redirectServer = master.startHttpToHttpsRedirect(80, '0.0.0.0', [
2460
+ 'yourapp.com',
2461
+ 'www.yourapp.com'
2462
+ ]);
2463
+
2464
+ console.log('========================================');
2465
+ console.log('🚀 Production Server Started');
2466
+ console.log('========================================');
2467
+ console.log('✅ HTTPS: https://yourapp.com (port 443)');
2468
+ console.log('✅ HTTP redirect: http://yourapp.com → https://yourapp.com');
2469
+ console.log('✅ TLS 1.3 enabled');
2470
+ console.log('✅ HSTS enabled (1 year)');
2471
+ console.log('✅ Secure ciphers configured');
2472
+ console.log('========================================');
2473
+ ```
2474
+
2475
+ **Step 4: Set Permissions (if needed)**
2476
+ ```bash
2477
+ # Option 1: Allow Node.js to bind to ports 80/443 (Linux)
2478
+ sudo setcap 'cap_net_bind_service=+ep' $(which node)
2479
+
2480
+ # Option 2: Run with sudo (not recommended)
2481
+ sudo node server.js
2482
+
2483
+ # Option 3: Use reverse proxy (recommended - see below)
2484
+ ```
2485
+
2486
+ **Step 5: Auto-Renew Certificates**
2487
+ ```bash
2488
+ # Certbot automatically renews certificates
2489
+ # Test renewal process:
2490
+ sudo certbot renew --dry-run
2491
+
2492
+ # Add to crontab for auto-renewal (runs daily)
2493
+ sudo crontab -e
2494
+ # Add this line:
2495
+ 0 0 * * * certbot renew --quiet --post-hook "systemctl restart myapp"
2496
+ ```
2497
+
2498
+ ---
2499
+
2500
+ ### Production (Custom Certificate)
2501
+
2502
+ If you have a certificate from a commercial CA (GoDaddy, Namecheap, etc.):
2503
+
2504
+ ```javascript
2505
+ const fs = require('fs');
2506
+ const master = require('mastercontroller');
2507
+
2508
+ master.environmentType = 'production';
2509
+ master.root = __dirname;
2510
+
2511
+ const server = master.setupServer('https', {
2512
+ key: fs.readFileSync('/path/to/your-domain.key'),
2513
+ cert: fs.readFileSync('/path/to/your-domain.crt'),
2514
+ ca: fs.readFileSync('/path/to/ca-bundle.crt') // Intermediate certificates
2515
+ });
2516
+
2517
+ master.enableHSTS({
2518
+ maxAge: 31536000,
2519
+ includeSubDomains: true,
2520
+ preload: true
2521
+ });
2522
+
2523
+ require('./config/initializers/config');
2524
+ master.start(server);
2525
+ master.serverSettings({ httpPort: 443 });
2526
+
2527
+ const redirectServer = master.startHttpToHttpsRedirect(80, '0.0.0.0', [
2528
+ 'yourapp.com',
2529
+ 'www.yourapp.com'
2530
+ ]);
2531
+ ```
2532
+
2533
+ ---
2534
+
2535
+ ---
2536
+
2537
+ ## Production Deployment Options
2538
+
2539
+ ### Option 1: Direct HTTPS (Simple, Good for Small Apps)
2540
+
2541
+ Run MasterController directly on ports 80/443:
2542
+
2543
+ ```javascript
2544
+ const fs = require('fs');
2545
+ const master = require('mastercontroller');
2546
+
2547
+ master.environmentType = 'production';
2548
+ master.root = __dirname;
2549
+
2550
+ const server = master.setupServer('https', {
2551
+ key: fs.readFileSync('/etc/letsencrypt/live/yourapp.com/privkey.pem'),
2552
+ cert: fs.readFileSync('/etc/letsencrypt/live/yourapp.com/fullchain.pem')
2553
+ });
2554
+
2555
+ master.enableHSTS({
2556
+ maxAge: 31536000,
2557
+ includeSubDomains: true,
2558
+ preload: true
2559
+ });
2560
+
2561
+ require('./config/initializers/config');
2562
+ master.start(server);
2563
+ master.serverSettings({ httpPort: 443 });
2564
+
2565
+ // HTTP redirect
2566
+ const redirectServer = master.startHttpToHttpsRedirect(80, '0.0.0.0', [
2567
+ 'yourapp.com',
2568
+ 'www.yourapp.com'
2569
+ ]);
2570
+ ```
2571
+
2572
+ **Pros:**
2573
+ - ✅ Simple setup
2574
+ - ✅ No extra software needed
2575
+ - ✅ Full control over TLS
2576
+
2577
+ **Cons:**
2578
+ - ❌ Requires root/sudo for ports 80/443
2579
+ - ❌ No load balancing
2580
+ - ❌ No static file caching
2581
+
2582
+ ---
2583
+
2584
+ ### Option 2: Nginx Reverse Proxy (Recommended for Production)
2585
+
2586
+ Run MasterController on high port (3000) behind Nginx on ports 80/443:
2587
+
2588
+ **Step 1: Install Nginx**
2589
+ ```bash
2590
+ # Ubuntu/Debian
2591
+ sudo apt update
2592
+ sudo apt install nginx
2593
+
2594
+ # CentOS/RHEL
2595
+ sudo yum install nginx
2596
+
2597
+ # macOS
2598
+ brew install nginx
2599
+ ```
2600
+
2601
+ **Step 2: Configure MasterController (High Port)**
2602
+ ```javascript
2603
+ // server.js - Run on port 3000
2604
+ const master = require('mastercontroller');
2605
+
2606
+ master.environmentType = 'production';
2607
+ master.root = __dirname;
2608
+
2609
+ // HTTP only (Nginx handles HTTPS)
2610
+ const server = master.setupServer('http');
2611
+
2612
+ require('./config/initializers/config');
2613
+ master.start(server);
2614
+ master.serverSettings({
2615
+ httpPort: 3000,
2616
+ hostname: '127.0.0.1' // Only accept local connections
2617
+ });
2618
+
2619
+ console.log('✅ Server running on http://127.0.0.1:3000');
2620
+ console.log('⚠️ Behind Nginx reverse proxy');
2621
+ ```
2622
+
2623
+ **Step 3: Configure Nginx**
2624
+ ```nginx
2625
+ # /etc/nginx/sites-available/yourapp.com
2626
+
2627
+ # HTTP redirect to HTTPS
2628
+ server {
2629
+ listen 80;
2630
+ listen [::]:80;
2631
+ server_name yourapp.com www.yourapp.com;
2632
+
2633
+ return 301 https://$server_name$request_uri;
2634
+ }
2635
+
2636
+ # HTTPS server
2637
+ server {
2638
+ listen 443 ssl http2;
2639
+ listen [::]:443 ssl http2;
2640
+ server_name yourapp.com www.yourapp.com;
2641
+
2642
+ # SSL Configuration (Let's Encrypt)
2643
+ ssl_certificate /etc/letsencrypt/live/yourapp.com/fullchain.pem;
2644
+ ssl_certificate_key /etc/letsencrypt/live/yourapp.com/privkey.pem;
2645
+ ssl_trusted_certificate /etc/letsencrypt/live/yourapp.com/chain.pem;
2646
+
2647
+ # Modern TLS configuration (matches MasterController defaults)
2648
+ ssl_protocols TLSv1.3 TLSv1.2;
2649
+ ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-RSA-AES256-GCM-SHA384';
2650
+ ssl_prefer_server_ciphers on;
2651
+
2652
+ # HSTS
2653
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
2654
+
2655
+ # Security headers
2656
+ add_header X-Frame-Options "SAMEORIGIN" always;
2657
+ add_header X-Content-Type-Options "nosniff" always;
2658
+ add_header X-XSS-Protection "1; mode=block" always;
2659
+
2660
+ # Proxy to MasterController
2661
+ location / {
2662
+ proxy_pass http://127.0.0.1:3000;
2663
+ proxy_http_version 1.1;
2664
+
2665
+ # WebSocket support
2666
+ proxy_set_header Upgrade $http_upgrade;
2667
+ proxy_set_header Connection 'upgrade';
2668
+
2669
+ # Forward real client IP
2670
+ proxy_set_header Host $host;
2671
+ proxy_set_header X-Real-IP $remote_addr;
2672
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
2673
+ proxy_set_header X-Forwarded-Proto $scheme;
2674
+
2675
+ # Timeouts
2676
+ proxy_connect_timeout 60s;
2677
+ proxy_send_timeout 60s;
2678
+ proxy_read_timeout 60s;
2679
+
2680
+ proxy_cache_bypass $http_upgrade;
2681
+ }
2682
+
2683
+ # Static file caching (optional)
2684
+ location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
2685
+ proxy_pass http://127.0.0.1:3000;
2686
+ expires 1y;
2687
+ add_header Cache-Control "public, immutable";
2688
+ }
2689
+
2690
+ # Gzip compression
2691
+ gzip on;
2692
+ gzip_vary on;
2693
+ gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss;
2694
+ }
2695
+ ```
2696
+
2697
+ **Step 4: Enable Nginx Configuration**
2698
+ ```bash
2699
+ # Create symlink to enable site
2700
+ sudo ln -s /etc/nginx/sites-available/yourapp.com /etc/nginx/sites-enabled/
2701
+
2702
+ # Test configuration
2703
+ sudo nginx -t
2704
+
2705
+ # Reload Nginx
2706
+ sudo systemctl reload nginx
2707
+
2708
+ # Enable Nginx on boot
2709
+ sudo systemctl enable nginx
2710
+ ```
2711
+
2712
+ **Step 5: Configure MasterController to Trust Proxy**
2713
+ ```javascript
2714
+ // config/initializers/config.js
2715
+ const master = require('mastercontroller');
2716
+
2717
+ // Trust X-Forwarded-* headers from Nginx
2718
+ master.pipeline.use(async (ctx, next) => {
2719
+ // Get real client IP from X-Forwarded-For
2720
+ const forwardedFor = ctx.request.headers['x-forwarded-for'];
2721
+ if (forwardedFor) {
2722
+ ctx.request.clientIp = forwardedFor.split(',')[0].trim();
2723
+ }
2724
+
2725
+ // Trust X-Forwarded-Proto for HTTPS detection
2726
+ if (ctx.request.headers['x-forwarded-proto'] === 'https') {
2727
+ ctx.request.isHttps = true;
2728
+ }
2729
+
2730
+ await next();
2731
+ });
2732
+
2733
+ // ... rest of config
2734
+ ```
2735
+
2736
+ **Pros:**
2737
+ - ✅ No root/sudo needed for Node.js
2738
+ - ✅ Static file caching
2739
+ - ✅ Load balancing support
2740
+ - ✅ Better performance
2741
+ - ✅ Easier certificate management
2742
+ - ✅ Industry standard
2743
+
2744
+ **Cons:**
2745
+ - ❌ Extra complexity
2746
+ - ❌ Another service to maintain
2747
+
2748
+ ---
2749
+
2750
+ ### Option 3: PM2 with Nginx (Best for Production)
2751
+
2752
+ Combine PM2 process manager with Nginx:
2753
+
2754
+ **Step 1: Install PM2**
2755
+ ```bash
2756
+ npm install -g pm2
2757
+ ```
2758
+
2759
+ **Step 2: Create PM2 Ecosystem File**
2760
+ ```javascript
2761
+ // ecosystem.config.js
2762
+ module.exports = {
2763
+ apps: [{
2764
+ name: 'myapp',
2765
+ script: './server.js',
2766
+ instances: 'max', // Use all CPU cores
2767
+ exec_mode: 'cluster',
2768
+ env: {
2769
+ NODE_ENV: 'production'
2770
+ },
2771
+ error_file: './logs/err.log',
2772
+ out_file: './logs/out.log',
2773
+ log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
2774
+ merge_logs: true
2775
+ }]
2776
+ };
2777
+ ```
2778
+
2779
+ **Step 3: Start with PM2**
2780
+ ```bash
2781
+ # Start application
2782
+ pm2 start ecosystem.config.js
2783
+
2784
+ # Save PM2 configuration
2785
+ pm2 save
2786
+
2787
+ # Setup PM2 to start on system boot
2788
+ pm2 startup
2789
+
2790
+ # Monitor application
2791
+ pm2 monit
2792
+
2793
+ # View logs
2794
+ pm2 logs
2795
+
2796
+ # Restart application
2797
+ pm2 restart myapp
2798
+
2799
+ # Reload with zero downtime
2800
+ pm2 reload myapp
2801
+ ```
2802
+
2803
+ **Step 4: Configure Nginx** (same as Option 2)
2804
+
2805
+ **Pros:**
2806
+ - ✅ Auto-restart on crash
2807
+ - ✅ Zero-downtime deployments
2808
+ - ✅ Cluster mode (use all CPU cores)
2809
+ - ✅ Log management
2810
+ - ✅ Monitoring
2811
+ - ✅ Auto-start on boot
2812
+
2813
+ ---
2814
+
2815
+ ---
2816
+
2817
+ ## Advanced HTTPS Configuration
2818
+
2819
+ ### Multiple Domains (SNI - Server Name Indication)
2820
+
2821
+ MasterController supports serving multiple domains with different certificates:
2822
+
2823
+ **Method 1: Using Environment Configuration**
2824
+ ```json
2825
+ // config/environments/env.production.json
2826
+ {
2827
+ "server": {
2828
+ "httpPort": 443,
2829
+ "tls": {
2830
+ "default": {
2831
+ "keyPath": "/etc/letsencrypt/live/example.com/privkey.pem",
2832
+ "certPath": "/etc/letsencrypt/live/example.com/fullchain.pem"
2833
+ },
2834
+ "sni": {
2835
+ "api.example.com": {
2836
+ "keyPath": "/etc/letsencrypt/live/api.example.com/privkey.pem",
2837
+ "certPath": "/etc/letsencrypt/live/api.example.com/fullchain.pem"
2838
+ },
2839
+ "admin.example.com": {
2840
+ "keyPath": "/etc/letsencrypt/live/admin.example.com/privkey.pem",
2841
+ "certPath": "/etc/letsencrypt/live/admin.example.com/fullchain.pem"
2842
+ }
2843
+ },
2844
+ "hsts": true,
2845
+ "hstsMaxAge": 31536000
2846
+ }
2847
+ }
1251
2848
  }
1252
2849
  ```
1253
2850
 
1254
- ### Global Error Handler (Pipeline)
2851
+ ```javascript
2852
+ // server.js
2853
+ const master = require('mastercontroller');
2854
+
2855
+ master.environmentType = 'production';
2856
+ master.root = __dirname;
2857
+
2858
+ // Loads TLS config from environment file (including SNI)
2859
+ const server = master.setupServer('https');
1255
2860
 
2861
+ require('./config/initializers/config');
2862
+ master.start(server);
2863
+ master.serverSettings(master.env.server);
2864
+
2865
+ console.log('✅ HTTPS with SNI enabled');
2866
+ console.log(' • example.com');
2867
+ console.log(' • api.example.com');
2868
+ console.log(' • admin.example.com');
2869
+ ```
2870
+
2871
+ **Method 2: Programmatic SNI**
1256
2872
  ```javascript
1257
- master.pipeline.useError(async (error, ctx, next) => {
1258
- console.error('Pipeline error:', error);
2873
+ const fs = require('fs');
2874
+ const tls = require('tls');
2875
+ const master = require('mastercontroller');
1259
2876
 
1260
- // Use error renderer for HTTP errors
1261
- master.errorRenderer.send(ctx, 500, {
1262
- message: error.message,
1263
- code: error.code,
1264
- stack: error.stack
1265
- });
2877
+ master.environmentType = 'production';
2878
+ master.root = __dirname;
2879
+
2880
+ // Default certificate
2881
+ const server = master.setupServer('https', {
2882
+ key: fs.readFileSync('/etc/letsencrypt/live/example.com/privkey.pem'),
2883
+ cert: fs.readFileSync('/etc/letsencrypt/live/example.com/fullchain.pem'),
2884
+
2885
+ // SNI callback for different domains
2886
+ SNICallback: (servername, cb) => {
2887
+ let ctx;
2888
+
2889
+ switch(servername) {
2890
+ case 'api.example.com':
2891
+ ctx = tls.createSecureContext({
2892
+ key: fs.readFileSync('/etc/letsencrypt/live/api.example.com/privkey.pem'),
2893
+ cert: fs.readFileSync('/etc/letsencrypt/live/api.example.com/fullchain.pem')
2894
+ });
2895
+ break;
2896
+
2897
+ case 'admin.example.com':
2898
+ ctx = tls.createSecureContext({
2899
+ key: fs.readFileSync('/etc/letsencrypt/live/admin.example.com/privkey.pem'),
2900
+ cert: fs.readFileSync('/etc/letsencrypt/live/admin.example.com/fullchain.pem')
2901
+ });
2902
+ break;
2903
+
2904
+ default:
2905
+ // Use default certificate
2906
+ ctx = null;
2907
+ }
2908
+
2909
+ cb(null, ctx);
2910
+ }
1266
2911
  });
2912
+
2913
+ require('./config/initializers/config');
2914
+ master.start(server);
2915
+ master.serverSettings({ httpPort: 443 });
1267
2916
  ```
1268
2917
 
1269
- ### Controller Error Handling
2918
+ ---
2919
+
2920
+ ### HTTP to HTTPS Redirect (Secure)
2921
+
2922
+ **⚠️ SECURITY:** Always specify allowed hosts to prevent open redirect attacks!
1270
2923
 
1271
2924
  ```javascript
1272
- class UsersController {
1273
- async index(obj) {
1274
- try {
1275
- const users = await this.db.query('SELECT * FROM users');
1276
- this.render('index', { users });
1277
- } catch (error) {
1278
- console.error('Database error:', error);
2925
+ // SECURE: Validate host header against whitelist
2926
+ const redirectServer = master.startHttpToHttpsRedirect(80, '0.0.0.0', [
2927
+ 'example.com',
2928
+ 'www.example.com',
2929
+ 'api.example.com'
2930
+ ]);
1279
2931
 
1280
- master.errorRenderer.send(obj, 500, {
1281
- message: 'Failed to load users',
1282
- code: 'DB_ERROR',
1283
- stack: error.stack
1284
- });
1285
- }
1286
- }
1287
- }
2932
+ console.log('✅ HTTP redirect server running on port 80');
1288
2933
  ```
1289
2934
 
1290
- ### Logging
2935
+ **Why host validation?** Without it, attackers can redirect users to malicious domains:
2936
+ ```bash
2937
+ # Attack without validation:
2938
+ curl -H "Host: evil.com" http://example.com
2939
+ # Redirects to: https://evil.com (phishing!)
2940
+
2941
+ # With validation: Returns 400 Bad Request ✅
2942
+ ```
1291
2943
 
2944
+ **For Multiple Domains:**
1292
2945
  ```javascript
1293
- const { logger } = require('./error/MasterErrorLogger');
2946
+ // Redirect all domains
2947
+ const redirectServer = master.startHttpToHttpsRedirect(80, '0.0.0.0', [
2948
+ 'example.com',
2949
+ 'www.example.com',
2950
+ 'api.example.com',
2951
+ 'admin.example.com',
2952
+ 'blog.example.com'
2953
+ ]);
2954
+ ```
1294
2955
 
1295
- // In controllers or middleware
1296
- logger.info({
1297
- code: 'USER_LOGIN',
1298
- message: 'User logged in',
1299
- userId: user.id
1300
- });
2956
+ ---
1301
2957
 
1302
- logger.warn({
1303
- code: 'INVALID_INPUT',
1304
- message: 'Invalid email format',
1305
- email: input
2958
+ ### Certificate Renewal (Let's Encrypt)
2959
+
2960
+ **Automatic Renewal (Recommended)**
2961
+
2962
+ Let's Encrypt certificates expire after 90 days. Setup automatic renewal:
2963
+
2964
+ ```bash
2965
+ # Method 1: Systemd timer (Ubuntu/Debian - already configured)
2966
+ sudo systemctl status certbot.timer
2967
+
2968
+ # Method 2: Crontab (manual setup)
2969
+ sudo crontab -e
2970
+ # Add this line (runs twice daily):
2971
+ 0 0,12 * * * certbot renew --quiet --post-hook "systemctl restart myapp"
2972
+
2973
+ # Method 3: PM2 with reload hook
2974
+ sudo crontab -e
2975
+ # Add this line:
2976
+ 0 0 * * * certbot renew --quiet --post-hook "pm2 reload myapp"
2977
+ ```
2978
+
2979
+ **Manual Renewal**
2980
+ ```bash
2981
+ # Test renewal (dry run)
2982
+ sudo certbot renew --dry-run
2983
+
2984
+ # Actually renew certificates
2985
+ sudo certbot renew
2986
+
2987
+ # Restart your application
2988
+ pm2 restart myapp
2989
+ # or
2990
+ sudo systemctl restart myapp
2991
+ ```
2992
+
2993
+ **Certificate Live Reload (Zero Downtime)**
2994
+
2995
+ MasterController supports certificate live reload with `fs.watchFile()`:
2996
+
2997
+ ```javascript
2998
+ const fs = require('fs');
2999
+ const master = require('mastercontroller');
3000
+
3001
+ const certPath = '/etc/letsencrypt/live/example.com/fullchain.pem';
3002
+ const keyPath = '/etc/letsencrypt/live/example.com/privkey.pem';
3003
+
3004
+ let server = master.setupServer('https', {
3005
+ key: fs.readFileSync(keyPath),
3006
+ cert: fs.readFileSync(certPath)
1306
3007
  });
1307
3008
 
1308
- logger.error({
1309
- code: 'DB_ERROR',
1310
- message: 'Database query failed',
1311
- error: error.message,
1312
- stack: error.stack
3009
+ // Watch for certificate changes
3010
+ fs.watchFile(certPath, (curr, prev) => {
3011
+ console.log('📝 Certificate changed, reloading...');
3012
+
3013
+ try {
3014
+ // Reload certificates without restarting server
3015
+ const newCert = fs.readFileSync(certPath);
3016
+ const newKey = fs.readFileSync(keyPath);
3017
+
3018
+ // Update server context
3019
+ server.setSecureContext({
3020
+ key: newKey,
3021
+ cert: newCert
3022
+ });
3023
+
3024
+ console.log('✅ Certificate reloaded successfully (zero downtime)');
3025
+ } catch (error) {
3026
+ console.error('❌ Failed to reload certificate:', error);
3027
+ }
1313
3028
  });
3029
+
3030
+ require('./config/initializers/config');
3031
+ master.start(server);
3032
+ master.serverSettings({ httpPort: 443 });
1314
3033
  ```
1315
3034
 
1316
3035
  ---
1317
3036
 
1318
- ## HTTPS Setup
3037
+ ### Common Errors and Solutions
3038
+
3039
+ #### Error: "EACCES: permission denied, bind 80" or "bind 443"
3040
+
3041
+ **Problem:** Node.js doesn't have permission to bind to ports 80/443.
3042
+
3043
+ **Solutions:**
3044
+
3045
+ **Option 1: Use setcap (Linux - Recommended)**
3046
+ ```bash
3047
+ # Give Node.js permission to bind to privileged ports
3048
+ sudo setcap 'cap_net_bind_service=+ep' $(which node)
3049
+
3050
+ # Verify
3051
+ getcap $(which node)
3052
+ # Output: /usr/bin/node = cap_net_bind_service+ep
3053
+ ```
3054
+
3055
+ **Option 2: Run as root (Not Recommended)**
3056
+ ```bash
3057
+ sudo node server.js
3058
+ ```
3059
+
3060
+ **Option 3: Use reverse proxy (Best)**
3061
+ ```bash
3062
+ # Run Node.js on port 3000 (no permissions needed)
3063
+ # Use Nginx on ports 80/443
3064
+ ```
3065
+
3066
+ **Option 4: Use authbind (Linux)**
3067
+ ```bash
3068
+ sudo apt install authbind
3069
+ sudo touch /etc/authbind/byport/80
3070
+ sudo touch /etc/authbind/byport/443
3071
+ sudo chmod 500 /etc/authbind/byport/80
3072
+ sudo chmod 500 /etc/authbind/byport/443
3073
+ sudo chown $USER /etc/authbind/byport/80
3074
+ sudo chown $USER /etc/authbind/byport/443
3075
+
3076
+ # Run with authbind
3077
+ authbind --deep node server.js
3078
+ ```
3079
+
3080
+ ---
3081
+
3082
+ #### Error: "ENOENT: no such file or directory, open '/path/to/cert.pem'"
3083
+
3084
+ **Problem:** Certificate files don't exist or path is wrong.
3085
+
3086
+ **Solutions:**
3087
+
3088
+ ```bash
3089
+ # Check if files exist
3090
+ ls -l /etc/letsencrypt/live/yourapp.com/
1319
3091
 
1320
- ### Basic HTTPS
3092
+ # Check permissions
3093
+ sudo ls -l /etc/letsencrypt/live/yourapp.com/
3094
+ # If you see permission denied, you need to run as root or copy certs
3095
+
3096
+ # Copy certificates to accessible location (if needed)
3097
+ sudo cp /etc/letsencrypt/live/yourapp.com/privkey.pem ~/certs/
3098
+ sudo cp /etc/letsencrypt/live/yourapp.com/fullchain.pem ~/certs/
3099
+ sudo chown $USER:$USER ~/certs/*.pem
3100
+ ```
3101
+
3102
+ ---
3103
+
3104
+ #### Error: "unable to verify the first certificate"
3105
+
3106
+ **Problem:** Missing intermediate certificates (chain).
3107
+
3108
+ **Solution:**
1321
3109
 
1322
3110
  ```javascript
1323
- const fs = require('fs');
3111
+ // Use fullchain.pem instead of cert.pem
3112
+ const server = master.setupServer('https', {
3113
+ key: fs.readFileSync('/path/to/privkey.pem'),
3114
+ cert: fs.readFileSync('/path/to/fullchain.pem'), // NOT cert.pem!
3115
+ ca: fs.readFileSync('/path/to/chain.pem') // Optional: explicit chain
3116
+ });
3117
+ ```
3118
+
3119
+ ---
3120
+
3121
+ #### Error: "cert has expired"
3122
+
3123
+ **Problem:** SSL certificate has expired.
3124
+
3125
+ **Solutions:**
3126
+
3127
+ ```bash
3128
+ # Check expiration date
3129
+ openssl x509 -in /etc/letsencrypt/live/yourapp.com/fullchain.pem -noout -dates
3130
+
3131
+ # Renew certificate
3132
+ sudo certbot renew
3133
+
3134
+ # Restart application
3135
+ pm2 restart myapp
3136
+ ```
3137
+
3138
+ ---
3139
+
3140
+ #### Error: Browser shows "Not Secure" or "NET::ERR_CERT_AUTHORITY_INVALID"
3141
+
3142
+ **Problem:** Self-signed certificate (development) or certificate not trusted.
3143
+
3144
+ **Solutions:**
3145
+
3146
+ **For Development (Self-Signed):**
3147
+ 1. Click "Advanced" in browser
3148
+ 2. Click "Proceed to localhost" (safe for development)
3149
+ 3. Or add certificate to system trust store
3150
+
3151
+ **For Production:**
3152
+ 1. Use Let's Encrypt or commercial CA certificate
3153
+ 2. Ensure fullchain.pem includes intermediate certificates
3154
+ 3. Check certificate matches domain name
3155
+
3156
+ ---
3157
+
3158
+ #### Browser keeps redirecting between HTTP and HTTPS (Loop)
3159
+
3160
+ **Problem:** Redirect loop, usually caused by incorrect proxy configuration.
3161
+
3162
+ **Solution:**
1324
3163
 
1325
- const credentials = {
1326
- key: fs.readFileSync('path/to/key.pem'),
1327
- cert: fs.readFileSync('path/to/cert.pem')
3164
+ ```javascript
3165
+ // config/initializers/config.js
3166
+ // Configure hostname in environment file
3167
+ master.env = {
3168
+ server: {
3169
+ hostname: 'yourapp.com', // Set this!
3170
+ httpsPort: 443
3171
+ }
1328
3172
  };
1329
3173
 
1330
- const server = master.setupServer('https', credentials);
3174
+ // For Nginx proxy, make sure X-Forwarded-Proto is set correctly
3175
+ ```
3176
+
3177
+ ---
3178
+
3179
+ ### HSTS (HTTP Strict Transport Security)
3180
+
3181
+ HSTS tells browsers to always use HTTPS for your domain (prevents downgrade attacks).
3182
+
3183
+ ```javascript
3184
+ // Basic usage (1 year, includeSubDomains)
3185
+ master.enableHSTS();
3186
+
3187
+ // Custom configuration
3188
+ master.enableHSTS({
3189
+ maxAge: 15552000, // 180 days
3190
+ includeSubDomains: true, // Cover *.example.com
3191
+ preload: false // Don't submit to preload list yet
3192
+ });
1331
3193
  ```
1332
3194
 
1333
- ### Environment-based TLS
3195
+ **HSTS Preload List:**
3196
+ After running HSTS for 30+ days, submit to [hstspreload.org](https://hstspreload.org/) for browser built-in enforcement.
3197
+
3198
+ ### Environment-based TLS Configuration
1334
3199
 
1335
3200
  Configure TLS in `config/environments/env.production.json`:
1336
3201
 
@@ -1352,33 +3217,206 @@ Configure TLS in `config/environments/env.production.json`:
1352
3217
  "keyPath": "/path/to/app.key",
1353
3218
  "certPath": "/path/to/app.crt"
1354
3219
  }
1355
- }
3220
+ },
3221
+ "hsts": true,
3222
+ "hstsMaxAge": 31536000
1356
3223
  }
1357
3224
  }
1358
3225
  }
1359
3226
  ```
1360
3227
 
1361
3228
  ```javascript
3229
+ // Loads TLS config from environment file
1362
3230
  const server = master.setupServer('https');
1363
3231
  master.serverSettings(master.env.server);
1364
3232
  ```
1365
3233
 
1366
- ### HTTP to HTTPS Redirect
3234
+ **Features:**
3235
+ - ✅ **SNI Support** - Different certificates for different domains
3236
+ - ✅ **Live Reload** - Update certificates without restarting server
3237
+ - ✅ **HSTS Configuration** - Automatic HSTS from config
3238
+
3239
+ ### Advanced TLS Configuration
3240
+
3241
+ #### Custom TLS Version
3242
+ ```javascript
3243
+ const server = master.setupServer('https', {
3244
+ key: fs.readFileSync('/path/to/key.pem'),
3245
+ cert: fs.readFileSync('/path/to/cert.pem'),
3246
+ minVersion: 'TLSv1.2', // Override default TLS 1.3 (for compatibility)
3247
+ maxVersion: 'TLSv1.3'
3248
+ });
3249
+ ```
3250
+
3251
+ #### Custom Cipher Suites
3252
+ ```javascript
3253
+ const server = master.setupServer('https', {
3254
+ key: fs.readFileSync('/path/to/key.pem'),
3255
+ cert: fs.readFileSync('/path/to/cert.pem'),
3256
+ ciphers: [
3257
+ 'TLS_AES_256_GCM_SHA384',
3258
+ 'TLS_CHACHA20_POLY1305_SHA256',
3259
+ 'ECDHE-RSA-AES256-GCM-SHA384'
3260
+ ].join(':')
3261
+ });
3262
+ ```
3263
+
3264
+ **Note:** MasterController uses secure defaults. Only customize if you have specific requirements.
3265
+
3266
+ ### Let's Encrypt Example
1367
3267
 
1368
3268
  ```javascript
1369
- // Start HTTPS server on 443
1370
- const httpsServer = master.setupServer('https');
1371
- master.start(httpsServer);
3269
+ // Let's Encrypt certificates (auto-renewed by certbot)
3270
+ const server = master.setupServer('https', {
3271
+ key: fs.readFileSync('/etc/letsencrypt/live/example.com/privkey.pem'),
3272
+ cert: fs.readFileSync('/etc/letsencrypt/live/example.com/fullchain.pem')
3273
+ });
3274
+
3275
+ master.enableHSTS({
3276
+ maxAge: 31536000,
3277
+ includeSubDomains: true,
3278
+ preload: true
3279
+ });
3280
+
3281
+ master.start(server);
1372
3282
  master.serverSettings({ httpPort: 443 });
1373
3283
 
1374
- // Start redirect server on 80
1375
- const redirectServer = master.startHttpToHttpsRedirect(80);
3284
+ // Secure HTTP redirect
3285
+ const redirectServer = master.startHttpToHttpsRedirect(80, '0.0.0.0', [
3286
+ 'example.com',
3287
+ 'www.example.com'
3288
+ ]);
1376
3289
  ```
1377
3290
 
1378
- ### HSTS (HTTP Strict Transport Security)
3291
+ ### Testing Your HTTPS Setup
3292
+
3293
+ #### 1. SSL Labs Test
3294
+ ```bash
3295
+ # Test your HTTPS configuration (should get A or A+)
3296
+ https://www.ssllabs.com/ssltest/analyze.html?d=yourdomain.com
3297
+ ```
3298
+
3299
+ #### 2. Local Testing
3300
+ ```bash
3301
+ # Test TLS 1.3
3302
+ curl -v --tlsv1.3 https://localhost
3303
+
3304
+ # Test HSTS header
3305
+ curl -I https://localhost | grep Strict-Transport-Security
3306
+
3307
+ # Test HTTP redirect
3308
+ curl -I http://localhost
3309
+
3310
+ # Test path traversal protection (should return 403)
3311
+ curl http://localhost/../../../etc/passwd
3312
+ ```
3313
+
3314
+ #### 3. Cipher Suite Testing
3315
+ ```bash
3316
+ # Use testssl.sh for comprehensive testing
3317
+ ./testssl.sh --full https://yourdomain.com
3318
+
3319
+ # Or nmap
3320
+ nmap --script ssl-enum-ciphers -p 443 yourdomain.com
3321
+ ```
3322
+
3323
+ ### Security Comparison
3324
+
3325
+ MasterController's HTTPS implementation **exceeds industry standards**:
3326
+
3327
+ | Feature | MasterController v1.3.1 | Express | ASP.NET Core | Rails |
3328
+ |---------|-------------------------|---------|--------------|-------|
3329
+ | **TLS 1.3 Default** | ✅ | ❌ | ❌ | ❌ |
3330
+ | **Secure Ciphers** | ✅ Auto | ❌ Manual | ⚠️ Partial | ❌ Manual |
3331
+ | **Path Traversal Protection** | ✅ | ✅ | ✅ | ✅ |
3332
+ | **Open Redirect Protection** | ✅ | ✅ | ✅ | ✅ |
3333
+ | **SNI Support** | ✅ Built-in | ❌ Manual | ✅ | ❌ Manual |
3334
+ | **Certificate Live Reload** | ✅ **Unique!** | ❌ | ❌ | ❌ |
3335
+ | **HSTS Built-in** | ✅ | Via helmet | ✅ | ✅ |
3336
+
3337
+ ### Complete Production Example
1379
3338
 
1380
3339
  ```javascript
1381
- master.enableHSTS(); // In production HTTPS
3340
+ // server.js - Production HTTPS setup
3341
+ const master = require('mastercontroller');
3342
+ const fs = require('fs');
3343
+
3344
+ // Set environment
3345
+ master.environmentType = process.env.NODE_ENV || 'production';
3346
+ master.root = __dirname;
3347
+
3348
+ // Setup HTTPS with Let's Encrypt certificates
3349
+ const server = master.setupServer('https', {
3350
+ key: fs.readFileSync('/etc/letsencrypt/live/example.com/privkey.pem'),
3351
+ cert: fs.readFileSync('/etc/letsencrypt/live/example.com/fullchain.pem')
3352
+ });
3353
+
3354
+ // Enable HSTS with preload
3355
+ master.enableHSTS({
3356
+ maxAge: 31536000, // 1 year
3357
+ includeSubDomains: true,
3358
+ preload: true // Submit to hstspreload.org after 30 days
3359
+ });
3360
+
3361
+ // Load application configuration
3362
+ require('./config/initializers/config');
3363
+
3364
+ // Start HTTPS server on port 443
3365
+ master.start(server);
3366
+ master.serverSettings({ httpPort: 443 });
3367
+
3368
+ // Start HTTP to HTTPS redirect with host validation
3369
+ const redirectServer = master.startHttpToHttpsRedirect(80, '0.0.0.0', [
3370
+ 'example.com',
3371
+ 'www.example.com',
3372
+ 'api.example.com',
3373
+ 'admin.example.com'
3374
+ ]);
3375
+
3376
+ console.log('========================================');
3377
+ console.log('🚀 MasterController Production Server');
3378
+ console.log('========================================');
3379
+ console.log('✅ HTTPS on port 443');
3380
+ console.log('✅ HTTP redirect on port 80');
3381
+ console.log('✅ TLS 1.3 enabled');
3382
+ console.log('✅ Secure cipher suites');
3383
+ console.log('✅ HSTS enabled (max-age: 1 year)');
3384
+ console.log('✅ Path traversal protection');
3385
+ console.log('✅ Open redirect protection');
3386
+ console.log('========================================');
3387
+ ```
3388
+
3389
+ ### Troubleshooting
3390
+
3391
+ **Certificate Errors:**
3392
+ ```bash
3393
+ # Check certificate expiration
3394
+ openssl x509 -in /path/to/cert.pem -noout -dates
3395
+
3396
+ # Verify certificate chain
3397
+ openssl verify -CAfile /path/to/ca.pem /path/to/cert.pem
3398
+ ```
3399
+
3400
+ **Port Permission Errors (ports 80/443):**
3401
+ ```bash
3402
+ # Option 1: Use setcap (Linux)
3403
+ sudo setcap 'cap_net_bind_service=+ep' $(which node)
3404
+
3405
+ # Option 2: Run as root (not recommended)
3406
+ sudo node server.js
3407
+
3408
+ # Option 3: Use reverse proxy (recommended)
3409
+ # Run Node.js on high port (3000) behind nginx/Apache
3410
+ ```
3411
+
3412
+ **HSTS Testing:**
3413
+ ```bash
3414
+ # Check if HSTS header is present
3415
+ curl -I https://yourdomain.com | grep -i strict
3416
+
3417
+ # Check HSTS status in browser
3418
+ # Chrome: chrome://net-internals/#hsts
3419
+ # Firefox: about:networking#hsts
1382
3420
  ```
1383
3421
 
1384
3422
  ---
@@ -1444,16 +3482,15 @@ master.enableHSTS(); // In production HTTPS
1444
3482
 
1445
3483
  ### Sessions
1446
3484
 
1447
- - `master.sessions.init(options)` - Initialize sessions
1448
- - `master.sessions.set(name, data, response, secret, options)` - Create session
1449
- - `master.sessions.get(name, request, secret)` - Get session data
1450
- - `master.sessions.delete(name, response)` - Delete session
1451
- - `master.sessions.reset()` - Clear all sessions
1452
- - `master.sessions.setCookie(name, value, response, options)` - Set cookie
1453
- - `master.sessions.getCookie(name, request, secret)` - Get cookie
1454
- - `master.sessions.deleteCookie(name, response, options)` - Delete cookie
1455
- - `master.sessions.createSessionID()` - Generate random session ID
1456
- - `master.sessions.middleware()` - Get pipeline middleware
3485
+ - `master.session.init(options)` - Initialize secure sessions
3486
+ - `master.session.destroy(req, res)` - Destroy session completely
3487
+ - `master.session.touch(sessionId)` - Extend session expiry
3488
+ - `master.session.getSessionCount()` - Get active session count
3489
+ - `master.session.clearAllSessions()` - Clear all sessions (testing only)
3490
+ - `master.session.getBestPractices(environment)` - Get recommended settings
3491
+ - `master.session.middleware()` - Get pipeline middleware
3492
+
3493
+ **Session Data Access:** Use `obj.request.session` object directly (Rails/Express style)
1457
3494
 
1458
3495
  ### Request
1459
3496
 
@@ -1466,11 +3503,26 @@ master.enableHSTS(); // In production HTTPS
1466
3503
 
1467
3504
  ### Tools
1468
3505
 
1469
- - `master.tools.encrypt(data, secret)` - Encrypt data
1470
- - `master.tools.decrypt(data, secret)` - Decrypt data
3506
+ **Encryption:**
3507
+ - `master.tools.encrypt(data, secret)` - Encrypt data with AES-256-CBC
3508
+ - `master.tools.decrypt(data, secret)` - Decrypt data with AES-256-CBC
3509
+
3510
+ **File Conversion (NEW in v1.3.1):**
3511
+ - `master.tools.fileToBase64(filePathOrFile, options)` - Convert file to base64 (binary-safe)
3512
+ - `master.tools.base64ToFile(base64String, outputPath, options)` - Convert base64 to file
3513
+ - `master.tools.fileToBuffer(filePathOrFile, options)` - Convert file to Node.js Buffer
3514
+ - `master.tools.fileToBytes(filePathOrFile, options)` - Convert file to Uint8Array
3515
+ - `master.tools.bytesToBase64(bufferOrBytes, options)` - Convert Buffer/Uint8Array to base64
3516
+ - `master.tools.base64ToBytes(base64String)` - Convert base64 to Buffer
3517
+ - `master.tools.streamFileToBase64(filePathOrFile, options)` - Stream large files to base64 (async)
3518
+
3519
+ **Utilities:**
1471
3520
  - `master.tools.combineObjects(target, source)` - Merge objects
1472
3521
  - `master.tools.makeWordId(length)` - Generate random ID
1473
3522
 
3523
+ **Deprecated:**
3524
+ - `master.tools.base64(path)` - ⚠️ DEPRECATED - Broken for binary files, use `fileToBase64()` instead
3525
+
1474
3526
  ---
1475
3527
 
1476
3528
  ## Production Tips
@@ -1490,14 +3542,17 @@ master.enableHSTS(); // In production HTTPS
1490
3542
 
1491
3543
  ## Documentation
1492
3544
 
1493
- Detailed guides:
3545
+ ### Security Documentation
3546
+
3547
+ - [Security Fixes v1.3.2](SECURITY-FIXES-v1.3.2.md) - All security fixes and migration guide
3548
+ - [Security Quick Start](docs/SECURITY-QUICKSTART.md) - 5-minute security setup guide
3549
+ - [Security Audit - Action System](docs/SECURITY-AUDIT-ACTION-SYSTEM.md) - Complete security audit of controllers and filters
3550
+ - [Security Audit - HTTPS](docs/SECURITY-AUDIT-HTTPS.md) - HTTPS/TLS security audit
3551
+
3552
+ ### Feature Documentation
1494
3553
 
1495
- - [HTTP Server Setup](docs/server-setup-http.md)
1496
- - [HTTPS with Credentials](docs/server-setup-https-credentials.md)
1497
- - [HTTPS with Environment TLS & SNI](docs/server-setup-https-env-tls-sni.md)
1498
- - [Hostname Binding](docs/server-setup-hostname-binding.md)
1499
- - [Nginx Reverse Proxy](docs/server-setup-nginx-reverse-proxy.md)
1500
- - [Environment TLS Reference](docs/environment-tls-reference.md)
3554
+ - [Timeout and Error Handling](docs/timeout-and-error-handling.md) - Professional timeout tracking and error rendering
3555
+ - [Environment TLS Reference](docs/environment-tls-reference.md) - TLS/SNI configuration reference
1501
3556
 
1502
3557
  ---
1503
3558