secure-comms-hybrid 1.0.4 â 1.0.5
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/lib/backend/middleware.js +134 -103
- package/package.json +1 -1
|
@@ -9,14 +9,16 @@ function createEncryptionMiddleware(hybridCrypto, options = {}) {
|
|
|
9
9
|
logLevel = 'info'
|
|
10
10
|
} = options;
|
|
11
11
|
|
|
12
|
-
// Decryption middleware
|
|
13
|
-
const decryptRequest = (req, res, next) => {
|
|
12
|
+
// Decryption middleware - FIXED to handle raw body
|
|
13
|
+
const decryptRequest = async (req, res, next) => {
|
|
14
14
|
// Check if path is excluded
|
|
15
15
|
if (excludedPaths.includes(req.path)) {
|
|
16
|
+
console.log(`Path ${req.path} excluded from encryption`);
|
|
16
17
|
return next();
|
|
17
18
|
}
|
|
18
19
|
const isEncrypted = req.headers[CRYPTO_CONFIG.HEADERS.ENCRYPTED] === 'true';
|
|
19
20
|
const clientId = req.headers[CRYPTO_CONFIG.HEADERS.CLIENT_ID];
|
|
21
|
+
console.log(`đ Middleware check: Encrypted=${isEncrypted}, ClientID=${clientId}, Path=${req.path}`);
|
|
20
22
|
if (!isEncrypted) {
|
|
21
23
|
if (requireEncryption) {
|
|
22
24
|
return res.status(400).json({
|
|
@@ -25,92 +27,153 @@ function createEncryptionMiddleware(hybridCrypto, options = {}) {
|
|
|
25
27
|
timestamp: new Date().toISOString()
|
|
26
28
|
});
|
|
27
29
|
}
|
|
30
|
+
console.log('âšī¸ No encryption requested, proceeding with plain request');
|
|
28
31
|
return next();
|
|
29
32
|
}
|
|
30
33
|
if (!clientId) {
|
|
34
|
+
console.error('â Missing client ID for encrypted request');
|
|
31
35
|
return res.status(400).json({
|
|
32
36
|
success: false,
|
|
33
37
|
error: ERRORS.MISSING_CLIENT_ID,
|
|
34
38
|
timestamp: new Date().toISOString()
|
|
35
39
|
});
|
|
36
40
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
console.log(`đ Processing encrypted request for client: ${clientId}`);
|
|
42
|
+
try {
|
|
43
|
+
// IMPORTANT: Read raw body BEFORE express.json() consumes it
|
|
44
|
+
// We need to handle this differently
|
|
45
|
+
|
|
46
|
+
// Create a buffer to collect the raw body
|
|
47
|
+
const chunks = [];
|
|
48
|
+
|
|
49
|
+
// Listen for data chunks
|
|
50
|
+
req.on('data', chunk => {
|
|
51
|
+
chunks.push(chunk);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Wait for the entire body
|
|
55
|
+
await new Promise((resolve, reject) => {
|
|
56
|
+
req.on('end', resolve);
|
|
57
|
+
req.on('error', reject);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Combine chunks
|
|
61
|
+
const rawBody = Buffer.concat(chunks).toString();
|
|
62
|
+
console.log(`đĨ Raw body received (${rawBody.length} chars)`);
|
|
63
|
+
if (!rawBody) {
|
|
64
|
+
console.error('â Empty request body for encrypted request');
|
|
65
|
+
return res.status(400).json({
|
|
66
|
+
success: false,
|
|
67
|
+
error: 'Empty request body',
|
|
68
|
+
timestamp: new Date().toISOString()
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
let parsedBody;
|
|
42
72
|
try {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
success: false,
|
|
46
|
-
error: 'Empty request body',
|
|
47
|
-
timestamp: new Date().toISOString()
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
let parsedBody;
|
|
51
|
-
try {
|
|
52
|
-
parsedBody = JSON.parse(rawBody);
|
|
53
|
-
} catch (error) {
|
|
54
|
-
return res.status(400).json({
|
|
55
|
-
success: false,
|
|
56
|
-
error: 'Invalid JSON',
|
|
57
|
-
timestamp: new Date().toISOString()
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
if (!parsedBody.ciphertext || !parsedBody.iv || !parsedBody.authTag) {
|
|
61
|
-
return res.status(400).json({
|
|
62
|
-
success: false,
|
|
63
|
-
error: ERRORS.INVALID_REQUEST,
|
|
64
|
-
required: ['ciphertext', 'iv', 'authTag'],
|
|
65
|
-
timestamp: new Date().toISOString()
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
const decrypted = await hybridCrypto.decryptData(parsedBody.ciphertext, parsedBody.iv, parsedBody.authTag, clientId);
|
|
69
|
-
if (!decrypted.success) {
|
|
70
|
-
return res.status(400).json({
|
|
71
|
-
success: false,
|
|
72
|
-
error: ERRORS.DECRYPTION_FAILED,
|
|
73
|
-
details: decrypted.error,
|
|
74
|
-
timestamp: new Date().toISOString()
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
req.originalEncryptedBody = parsedBody;
|
|
78
|
-
req.body = decrypted.data;
|
|
79
|
-
req.encryptionMetadata = {
|
|
80
|
-
type: 'hybrid',
|
|
81
|
-
clientId,
|
|
82
|
-
decryptedAt: decrypted.timestamp,
|
|
83
|
-
algorithm: 'RSA-AES-GCM'
|
|
84
|
-
};
|
|
85
|
-
next();
|
|
73
|
+
parsedBody = JSON.parse(rawBody);
|
|
74
|
+
console.log(`â
Body parsed successfully, keys: ${Object.keys(parsedBody).join(', ')}`);
|
|
86
75
|
} catch (error) {
|
|
87
|
-
console.error('â
|
|
88
|
-
return res.status(
|
|
76
|
+
console.error('â Invalid JSON in request body:', error.message);
|
|
77
|
+
return res.status(400).json({
|
|
78
|
+
success: false,
|
|
79
|
+
error: 'Invalid JSON',
|
|
80
|
+
timestamp: new Date().toISOString()
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check for required encryption fields
|
|
85
|
+
if (!parsedBody.ciphertext || !parsedBody.iv || !parsedBody.authTag) {
|
|
86
|
+
console.error('â Missing encryption fields in request');
|
|
87
|
+
console.log('Received:', {
|
|
88
|
+
hasCiphertext: !!parsedBody.ciphertext,
|
|
89
|
+
hasIV: !!parsedBody.iv,
|
|
90
|
+
hasAuthTag: !!parsedBody.authTag,
|
|
91
|
+
allKeys: Object.keys(parsedBody)
|
|
92
|
+
});
|
|
93
|
+
return res.status(400).json({
|
|
94
|
+
success: false,
|
|
95
|
+
error: ERRORS.INVALID_REQUEST,
|
|
96
|
+
required: ['ciphertext', 'iv', 'authTag'],
|
|
97
|
+
received: Object.keys(parsedBody),
|
|
98
|
+
timestamp: new Date().toISOString()
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
console.log(`đ Attempting to decrypt data for client ${clientId}`);
|
|
102
|
+
console.log(` Ciphertext length: ${parsedBody.ciphertext.length}`);
|
|
103
|
+
console.log(` IV: ${parsedBody.iv}`);
|
|
104
|
+
console.log(` Auth Tag: ${parsedBody.authTag.substring(0, 20)}...`);
|
|
105
|
+
const decrypted = await hybridCrypto.decryptData(parsedBody.ciphertext, parsedBody.iv, parsedBody.authTag, clientId);
|
|
106
|
+
console.log(`đ Decryption result:`, {
|
|
107
|
+
success: decrypted.success,
|
|
108
|
+
hasData: !!decrypted.data,
|
|
109
|
+
error: decrypted.error
|
|
110
|
+
});
|
|
111
|
+
if (!decrypted.success) {
|
|
112
|
+
console.error(`â Decryption failed: ${decrypted.error}`);
|
|
113
|
+
return res.status(400).json({
|
|
89
114
|
success: false,
|
|
90
|
-
error:
|
|
115
|
+
error: ERRORS.DECRYPTION_FAILED,
|
|
116
|
+
details: decrypted.error,
|
|
91
117
|
timestamp: new Date().toISOString()
|
|
92
118
|
});
|
|
93
119
|
}
|
|
94
|
-
|
|
120
|
+
console.log(`â
Successfully decrypted data for client ${clientId}`);
|
|
121
|
+
|
|
122
|
+
// Store original encrypted body for debugging
|
|
123
|
+
req.originalEncryptedBody = parsedBody;
|
|
124
|
+
req.body = decrypted.data;
|
|
125
|
+
req.encryptionMetadata = {
|
|
126
|
+
type: 'hybrid',
|
|
127
|
+
clientId,
|
|
128
|
+
decryptedAt: decrypted.timestamp,
|
|
129
|
+
algorithm: 'RSA-AES-GCM',
|
|
130
|
+
originalBodySize: rawBody.length,
|
|
131
|
+
decryptedBodySize: JSON.stringify(decrypted.data).length
|
|
132
|
+
};
|
|
133
|
+
console.log(`đ¤ Decrypted body:`, {
|
|
134
|
+
type: typeof decrypted.data,
|
|
135
|
+
keys: Object.keys(decrypted.data || {}),
|
|
136
|
+
sample: JSON.stringify(decrypted.data).substring(0, 100)
|
|
137
|
+
});
|
|
138
|
+
next();
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error('â Error in decryption middleware:', error);
|
|
141
|
+
return res.status(500).json({
|
|
142
|
+
success: false,
|
|
143
|
+
error: 'Internal server error',
|
|
144
|
+
message: error.message,
|
|
145
|
+
timestamp: new Date().toISOString()
|
|
146
|
+
});
|
|
147
|
+
}
|
|
95
148
|
};
|
|
96
149
|
|
|
97
|
-
// Encryption middleware
|
|
150
|
+
// Encryption middleware - FIXED
|
|
98
151
|
const encryptResponse = (req, res, next) => {
|
|
99
152
|
const wantsEncryption = req.headers[CRYPTO_CONFIG.HEADERS.ENCRYPTED] === 'true';
|
|
100
153
|
const clientId = req.headers[CRYPTO_CONFIG.HEADERS.CLIENT_ID];
|
|
154
|
+
console.log(`đ Response encryption check: Encrypt=${wantsEncryption}, ClientID=${clientId}`);
|
|
101
155
|
if (!wantsEncryption || !clientId || excludedPaths.includes(req.path)) {
|
|
156
|
+
console.log('âšī¸ Response encryption not requested');
|
|
102
157
|
return next();
|
|
103
158
|
}
|
|
104
159
|
const originalJson = res.json;
|
|
105
160
|
res.json = async function (data) {
|
|
106
161
|
try {
|
|
162
|
+
console.log(`đ Attempting to encrypt response for client ${clientId}`);
|
|
163
|
+
|
|
164
|
+
// Don't encrypt error responses
|
|
107
165
|
if (res.statusCode >= 400) {
|
|
166
|
+
console.log('âšī¸ Not encrypting error response');
|
|
108
167
|
return originalJson.call(this, data);
|
|
109
168
|
}
|
|
110
169
|
const encrypted = await hybridCrypto.encryptData(data, clientId);
|
|
111
170
|
if (!encrypted.success) {
|
|
171
|
+
console.error(`â Response encryption failed: ${encrypted.error}`);
|
|
112
172
|
throw new Error(`${ERRORS.ENCRYPTION_FAILED}: ${encrypted.error}`);
|
|
113
173
|
}
|
|
174
|
+
console.log(`â
Response encrypted successfully`);
|
|
175
|
+
console.log(` Ciphertext length: ${encrypted.ciphertext.length}`);
|
|
176
|
+
console.log(` IV: ${encrypted.iv.substring(0, 20)}...`);
|
|
114
177
|
const encryptedResponse = {
|
|
115
178
|
success: true,
|
|
116
179
|
encrypted: true,
|
|
@@ -121,17 +184,24 @@ function createEncryptionMiddleware(hybridCrypto, options = {}) {
|
|
|
121
184
|
timestamp: encrypted.timestamp,
|
|
122
185
|
metadata: {
|
|
123
186
|
clientId,
|
|
124
|
-
originalDataType: typeof data
|
|
187
|
+
originalDataType: typeof data,
|
|
188
|
+
encryptedAt: encrypted.timestamp
|
|
125
189
|
}
|
|
126
190
|
};
|
|
191
|
+
|
|
192
|
+
// Set encryption headers
|
|
127
193
|
res.setHeader(CRYPTO_CONFIG.HEADERS.ENCRYPTED, 'true');
|
|
128
194
|
res.setHeader(CRYPTO_CONFIG.HEADERS.ENCRYPTION_ALGORITHM, 'RSA-AES-GCM');
|
|
129
195
|
res.setHeader(CRYPTO_CONFIG.HEADERS.CLIENT_ID, clientId);
|
|
196
|
+
console.log(`đ¤ Sending encrypted response`);
|
|
130
197
|
return originalJson.call(this, encryptedResponse);
|
|
131
198
|
} catch (error) {
|
|
132
199
|
console.error('â Response encryption failed:', error);
|
|
200
|
+
|
|
201
|
+
// Remove encryption headers
|
|
133
202
|
res.removeHeader(CRYPTO_CONFIG.HEADERS.ENCRYPTED);
|
|
134
203
|
res.removeHeader(CRYPTO_CONFIG.HEADERS.ENCRYPTION_ALGORITHM);
|
|
204
|
+
console.log('â ī¸ Falling back to plain response');
|
|
135
205
|
return originalJson.call(this, {
|
|
136
206
|
success: false,
|
|
137
207
|
error: ERRORS.ENCRYPTION_FAILED,
|
|
@@ -151,64 +221,25 @@ function createEncryptionMiddleware(hybridCrypto, options = {}) {
|
|
|
151
221
|
};
|
|
152
222
|
}
|
|
153
223
|
|
|
154
|
-
// Express.js specific middleware factory
|
|
224
|
+
// Express.js specific middleware factory - FIXED
|
|
155
225
|
function createExpressMiddleware(hybridCrypto, options = {}) {
|
|
156
226
|
const {
|
|
157
227
|
decryptRequest,
|
|
158
228
|
encryptResponse
|
|
159
229
|
} = createEncryptionMiddleware(hybridCrypto, options);
|
|
160
|
-
return function (req, res, next) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// Apply encryption
|
|
230
|
+
return async function (req, res, next) {
|
|
231
|
+
console.log(`đ Express middleware triggered for: ${req.method} ${req.path}`);
|
|
232
|
+
try {
|
|
233
|
+
await decryptRequest(req, res, next);
|
|
165
234
|
encryptResponse(req, res, next);
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
// Koa.js middleware
|
|
171
|
-
function createKoaMiddleware(hybridCrypto, options = {}) {
|
|
172
|
-
const {
|
|
173
|
-
decryptRequest,
|
|
174
|
-
encryptResponse
|
|
175
|
-
} = createEncryptionMiddleware(hybridCrypto, options);
|
|
176
|
-
return async (ctx, next) => {
|
|
177
|
-
// Convert Koa context to Express-like req/res
|
|
178
|
-
const req = ctx.request;
|
|
179
|
-
const res = {
|
|
180
|
-
json: data => {
|
|
181
|
-
ctx.body = data;
|
|
182
|
-
ctx.type = 'application/json';
|
|
183
|
-
},
|
|
184
|
-
status: code => {
|
|
185
|
-
ctx.status = code;
|
|
186
|
-
return res;
|
|
187
|
-
},
|
|
188
|
-
setHeader: (name, value) => {
|
|
189
|
-
ctx.set(name, value);
|
|
190
|
-
},
|
|
191
|
-
removeHeader: name => {
|
|
192
|
-
ctx.remove(name);
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
await new Promise(resolve => {
|
|
196
|
-
decryptRequest(req, res, err => {
|
|
197
|
-
if (err) throw err;
|
|
198
|
-
resolve();
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
await next();
|
|
202
|
-
if (ctx.headers[CRYPTO_CONFIG.HEADERS.ENCRYPTED] === 'true') {
|
|
203
|
-
await new Promise(resolve => {
|
|
204
|
-
encryptResponse(req, res, () => resolve());
|
|
205
|
-
});
|
|
235
|
+
} catch (error) {
|
|
236
|
+
console.error('Middleware error:', error);
|
|
237
|
+
next(error);
|
|
206
238
|
}
|
|
207
239
|
};
|
|
208
240
|
}
|
|
209
241
|
module.exports = {
|
|
210
242
|
createEncryptionMiddleware,
|
|
211
243
|
createExpressMiddleware,
|
|
212
|
-
createKoaMiddleware,
|
|
213
244
|
HybridCrypto: require('./HybridCrypto')
|
|
214
245
|
};
|
package/package.json
CHANGED