@govish/shared-services 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +2 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -1
- package/dist/middleware/authenticateDevice.d.ts +13 -2
- package/dist/middleware/authenticateDevice.js +551 -69
- package/dist/services/apiKeyService.d.ts +2 -1
- package/dist/services/apiKeyService.js +237 -45
- package/dist/services/auditService.d.ts +14 -2
- package/dist/services/auditService.js +289 -12
- package/dist/utils/rollingCache.d.ts +34 -0
- package/dist/utils/rollingCache.js +203 -0
- package/package.json +7 -2
|
@@ -20,7 +20,7 @@ const deviceService_1 = require("../services/deviceService");
|
|
|
20
20
|
const officerService_1 = require("../services/officerService");
|
|
21
21
|
const logMode_1 = require("../utils/logMode");
|
|
22
22
|
/**
|
|
23
|
-
* Middleware to authenticate either a device, an officer,
|
|
23
|
+
* Middleware to authenticate either a device, an officer, a microservice (via API key), or combinations thereof
|
|
24
24
|
* Checks JWT token and validates against Device or Officer table
|
|
25
25
|
* Also checks for API keys for microservice authentication
|
|
26
26
|
*
|
|
@@ -29,7 +29,18 @@ const logMode_1 = require("../utils/logMode");
|
|
|
29
29
|
* - Device-Token: <device_jwt_token> (for device authentication)
|
|
30
30
|
* - Authorization: Bearer <user_jwt_token> (for officer/user authentication)
|
|
31
31
|
*
|
|
32
|
-
*
|
|
32
|
+
* Supports combined authentication:
|
|
33
|
+
* - API Key + Officer: X-API-Key + Authorization header (sets authType: 'api_key_and_officer')
|
|
34
|
+
* - Device + Officer: Device-Token + Authorization header (sets authType: 'both')
|
|
35
|
+
*
|
|
36
|
+
* Authentication Priority/Combination Logic:
|
|
37
|
+
* 1. API Key + Officer (if both headers present) -> authType: 'api_key_and_officer'
|
|
38
|
+
* 2. API Key only (if only X-API-Key present) -> authType: 'api_key'
|
|
39
|
+
* 3. Device + Officer (if both headers present) -> authType: 'both'
|
|
40
|
+
* 4. Device only (if only Device-Token present) -> authType: 'device'
|
|
41
|
+
* 5. Officer only (if only Authorization present) -> authType: 'officer'
|
|
42
|
+
*
|
|
43
|
+
* At least one authentication method must succeed for the request to proceed.
|
|
33
44
|
*/
|
|
34
45
|
const createAuthenticateDeviceOrOfficer = (deps) => {
|
|
35
46
|
const apiKeyService = new apiKeyService_1.ApiKeyService(deps);
|
|
@@ -69,32 +80,122 @@ const createAuthenticateDeviceOrOfficer = (deps) => {
|
|
|
69
80
|
// Always initialize so we can log to console/logger even if Kafka is not available
|
|
70
81
|
const auditService = new auditService_1.AuditService(deps);
|
|
71
82
|
return (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
|
|
83
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
72
84
|
try {
|
|
73
|
-
//
|
|
74
|
-
const
|
|
85
|
+
// Track authentication attempts and failures
|
|
86
|
+
const authAttempts = [];
|
|
87
|
+
const authFailures = [];
|
|
88
|
+
// Step 1: Check for API key (for microservice authentication)
|
|
89
|
+
// Check both normalized headers and rawHeaders to handle case variations
|
|
90
|
+
let apiKeyHeader;
|
|
91
|
+
// Check normalized headers first (Express normalizes to lowercase)
|
|
92
|
+
apiKeyHeader = req.headers['x-api-key'];
|
|
93
|
+
// If not found, check rawHeaders (case-insensitive search)
|
|
94
|
+
if (!apiKeyHeader && req.rawHeaders) {
|
|
95
|
+
const index = req.rawHeaders.findIndex((h) => h.toLowerCase() === 'x-api-key');
|
|
96
|
+
if (index >= 0 && index < req.rawHeaders.length - 1) {
|
|
97
|
+
apiKeyHeader = req.rawHeaders[index + 1];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
let apiKeyAuthenticated = false;
|
|
101
|
+
let apiKeyFailureReason = null;
|
|
75
102
|
if (apiKeyHeader) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
req.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
103
|
+
authAttempts.push('api_key');
|
|
104
|
+
debugLog('[Authentication Flow] Step 1: X-API-Key header detected, starting API key validation');
|
|
105
|
+
console.log('🔑 [Authentication Flow] Step 1: X-API-Key header detected, starting API key validation', {
|
|
106
|
+
headerLength: apiKeyHeader.length,
|
|
107
|
+
headerPreview: apiKeyHeader.substring(0, 15) + '...',
|
|
108
|
+
endpoint: req.originalUrl || req.url
|
|
109
|
+
});
|
|
110
|
+
logger.info('[Authentication Flow] Step 1: X-API-Key header detected', {
|
|
111
|
+
endpoint: req.originalUrl || req.url,
|
|
112
|
+
method: req.method,
|
|
113
|
+
apiKeyPreview: apiKeyHeader.substring(0, 10) + '...',
|
|
114
|
+
headerLength: apiKeyHeader.length
|
|
115
|
+
});
|
|
116
|
+
try {
|
|
117
|
+
const apiKey = yield apiKeyService.validateApiKey(apiKeyHeader);
|
|
118
|
+
if (apiKey) {
|
|
119
|
+
apiKeyAuthenticated = true;
|
|
120
|
+
console.log('✅ [Authentication Flow] Step 1a: API key validation successful');
|
|
121
|
+
console.log(' 📋 API Key Details:', {
|
|
122
|
+
id: apiKey.id,
|
|
123
|
+
microservice: apiKey.microservice,
|
|
124
|
+
keyPreview: apiKey.key ? apiKey.key.substring(0, 15) + '...' : 'N/A',
|
|
125
|
+
isActive: apiKey.is_active,
|
|
126
|
+
expiresAt: apiKey.expires_at ? new Date(apiKey.expires_at).toISOString() : 'Never'
|
|
127
|
+
});
|
|
128
|
+
logger.info('[Authentication Flow] Step 1a: API key validation successful', {
|
|
129
|
+
apiKeyId: apiKey.id,
|
|
130
|
+
microservice: apiKey.microservice,
|
|
131
|
+
keyPreview: apiKey.key ? apiKey.key.substring(0, 15) + '...' : 'N/A',
|
|
132
|
+
isActive: apiKey.is_active,
|
|
133
|
+
expiresAt: apiKey.expires_at,
|
|
134
|
+
endpoint: req.originalUrl || req.url
|
|
88
135
|
});
|
|
136
|
+
// Attach API key info to request
|
|
137
|
+
req.apiKey = apiKey;
|
|
138
|
+
req.microservice = apiKey.microservice;
|
|
139
|
+
// Don't set authType yet - wait to see if officer is also authenticated
|
|
140
|
+
// This allows API key + officer authentication (similar to device + officer)
|
|
141
|
+
// Log debug info
|
|
142
|
+
debugLog('API Key authentication successful', {
|
|
143
|
+
apiKeyId: apiKey.id,
|
|
144
|
+
microservice: apiKey.microservice,
|
|
145
|
+
endpoint: req.originalUrl || req.url,
|
|
146
|
+
method: req.method
|
|
147
|
+
});
|
|
148
|
+
console.log('[Authentication Flow] Step 1b: API key attached to request, continuing to check for officer authentication');
|
|
149
|
+
console.log(' 🔍 Checking for Bearer token (Authorization header) to authenticate officer...');
|
|
150
|
+
logger.info('[Authentication Flow] Step 1b: API key attached to request, continuing to check for officer authentication');
|
|
151
|
+
// Continue to check for officer authentication (similar to device authentication)
|
|
152
|
+
// Don't return early - let it go through the same flow as device + officer
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
apiKeyFailureReason = 'Invalid, expired, inactive, or not found';
|
|
156
|
+
authFailures.push({ method: 'api_key', reason: apiKeyFailureReason });
|
|
157
|
+
console.error('❌ [Authentication Flow] Step 1a: API key validation failed', {
|
|
158
|
+
reason: apiKeyFailureReason,
|
|
159
|
+
endpoint: req.originalUrl || req.url,
|
|
160
|
+
method: req.method,
|
|
161
|
+
apiKeyPreview: apiKeyHeader.substring(0, 10) + '...'
|
|
162
|
+
});
|
|
163
|
+
logger.warn('[Authentication Flow] Step 1a: API key validation failed', {
|
|
164
|
+
reason: apiKeyFailureReason,
|
|
165
|
+
endpoint: req.originalUrl || req.url,
|
|
166
|
+
method: req.method,
|
|
167
|
+
apiKeyPreview: apiKeyHeader.substring(0, 10) + '...'
|
|
168
|
+
});
|
|
169
|
+
// Don't return 401 yet - continue to check Bearer token
|
|
89
170
|
}
|
|
90
|
-
return next();
|
|
91
171
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
172
|
+
catch (apiKeyError) {
|
|
173
|
+
const errorMessage = apiKeyError instanceof Error ? apiKeyError.message : 'Unknown error';
|
|
174
|
+
const errorStack = apiKeyError instanceof Error ? apiKeyError.stack : undefined;
|
|
175
|
+
apiKeyFailureReason = `Network or validation error: ${errorMessage}`;
|
|
176
|
+
authFailures.push({ method: 'api_key', reason: apiKeyFailureReason });
|
|
177
|
+
console.error('❌ [Authentication Flow] Step 1a: API key validation error', {
|
|
178
|
+
error: errorMessage,
|
|
179
|
+
errorStack,
|
|
180
|
+
endpoint: req.originalUrl || req.url,
|
|
181
|
+
apiKeyPreview: apiKeyHeader.substring(0, 10) + '...'
|
|
182
|
+
});
|
|
183
|
+
logger.error('[Authentication Flow] Step 1a: API key validation error', {
|
|
184
|
+
error: errorMessage,
|
|
185
|
+
errorStack,
|
|
186
|
+
endpoint: req.originalUrl || req.url,
|
|
187
|
+
apiKeyPreview: apiKeyHeader.substring(0, 10) + '...'
|
|
95
188
|
});
|
|
189
|
+
// Don't return 401 yet - continue to check Bearer token
|
|
96
190
|
}
|
|
97
191
|
}
|
|
192
|
+
else {
|
|
193
|
+
// Log when API key header is not present (for debugging)
|
|
194
|
+
debugLog('[Authentication Flow] Step 1: No X-API-Key header found', {
|
|
195
|
+
availableHeaders: Object.keys(req.headers).filter(h => h.toLowerCase().includes('api') || h.toLowerCase().includes('key')),
|
|
196
|
+
endpoint: req.originalUrl || req.url
|
|
197
|
+
});
|
|
198
|
+
}
|
|
98
199
|
// Check for device-token header (case-insensitive)
|
|
99
200
|
// Express normalizes headers to lowercase, but check multiple variations
|
|
100
201
|
// Also check rawHeaders in case Express hasn't normalized it
|
|
@@ -110,7 +211,6 @@ const createAuthenticateDeviceOrOfficer = (deps) => {
|
|
|
110
211
|
deviceTokenHeader = req.rawHeaders[index + 1];
|
|
111
212
|
}
|
|
112
213
|
}
|
|
113
|
-
const authHeader = req.headers.authorization;
|
|
114
214
|
let deviceAuthenticated = false;
|
|
115
215
|
let officerAuthenticated = false;
|
|
116
216
|
// Authenticate device if Device-Token header is present
|
|
@@ -156,6 +256,14 @@ const createAuthenticateDeviceOrOfficer = (deps) => {
|
|
|
156
256
|
// Attach device to request
|
|
157
257
|
req.device = device;
|
|
158
258
|
deviceAuthenticated = true;
|
|
259
|
+
// Log debug info
|
|
260
|
+
debugLog('Device authentication successful', {
|
|
261
|
+
deviceId: device.id,
|
|
262
|
+
deviceDeviceId: device.device_id,
|
|
263
|
+
deviceName: device.device_name,
|
|
264
|
+
endpoint: req.originalUrl || req.url,
|
|
265
|
+
method: req.method
|
|
266
|
+
});
|
|
159
267
|
}
|
|
160
268
|
catch (error) {
|
|
161
269
|
// Log audit event for invalid device token
|
|
@@ -173,101 +281,471 @@ const createAuthenticateDeviceOrOfficer = (deps) => {
|
|
|
173
281
|
return res.status(401).json({ message: 'Invalid or expired device token' });
|
|
174
282
|
}
|
|
175
283
|
}
|
|
176
|
-
// Authenticate officer if Authorization header is present
|
|
284
|
+
// Step 2: Authenticate officer if Authorization header is present
|
|
285
|
+
let bearerTokenFailureReason = null;
|
|
286
|
+
const authHeader = req.headers.authorization;
|
|
177
287
|
if (authHeader && authHeader.startsWith('Bearer ')) {
|
|
288
|
+
authAttempts.push('bearer_token');
|
|
289
|
+
console.log('🔐 [Authentication Flow] Step 2: Authorization (Bearer) header detected, starting officer authentication');
|
|
290
|
+
logger.info('[Authentication Flow] Step 2: Authorization header detected, starting officer authentication', {
|
|
291
|
+
endpoint: req.originalUrl || req.url,
|
|
292
|
+
method: req.method,
|
|
293
|
+
hasApiKey: apiKeyAuthenticated,
|
|
294
|
+
hasDevice: deviceAuthenticated,
|
|
295
|
+
apiKeyFailed: !!apiKeyFailureReason
|
|
296
|
+
});
|
|
178
297
|
const token = authHeader.substring(7); // Remove 'Bearer ' prefix
|
|
179
298
|
try {
|
|
299
|
+
console.log('[Authentication Flow] Step 2a: Verifying JWT token');
|
|
300
|
+
logger.info('[Authentication Flow] Step 2a: Verifying JWT token');
|
|
180
301
|
const decoded = jsonwebtoken_1.default.verify(token, jwtConfig.secret, {
|
|
181
302
|
issuer: jwtConfig.issuer,
|
|
182
303
|
audience: jwtConfig.audience,
|
|
183
304
|
});
|
|
305
|
+
console.log('✅ [Authentication Flow] Step 2b: JWT token verified successfully', {
|
|
306
|
+
tokenType: decoded.type,
|
|
307
|
+
hasId: !!decoded.id,
|
|
308
|
+
hasSub: !!decoded.sub
|
|
309
|
+
});
|
|
310
|
+
logger.info('[Authentication Flow] Step 2b: JWT token verified successfully', {
|
|
311
|
+
tokenType: decoded.type,
|
|
312
|
+
hasId: !!decoded.id,
|
|
313
|
+
hasSub: !!decoded.sub
|
|
314
|
+
});
|
|
184
315
|
// Skip if this is a device token (should use Device-Token header instead)
|
|
185
316
|
if (decoded.type === 'device') {
|
|
317
|
+
bearerTokenFailureReason = 'Token is a device token, should use Device-Token header';
|
|
318
|
+
authFailures.push({ method: 'bearer_token', reason: bearerTokenFailureReason });
|
|
319
|
+
console.warn('⚠️ [Authentication Flow] Step 2c: Token is a device token, should use Device-Token header');
|
|
320
|
+
logger.warn('[Authentication Flow] Step 2c: Token is a device token, should use Device-Token header');
|
|
186
321
|
if (!deviceAuthenticated) {
|
|
187
|
-
return
|
|
322
|
+
// Don't return 401 yet - check if we have other valid auth
|
|
323
|
+
// Will be handled in final check
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
// If device already authenticated via Device-Token, continue
|
|
327
|
+
console.log('[Authentication Flow] Step 2c: Device already authenticated via Device-Token header, continuing');
|
|
328
|
+
logger.info('[Authentication Flow] Step 2c: Device already authenticated via Device-Token header, continuing');
|
|
188
329
|
}
|
|
189
|
-
// If device already authenticated via Device-Token, continue
|
|
190
330
|
}
|
|
191
331
|
else {
|
|
192
|
-
// Authenticate as officer
|
|
332
|
+
// Step 2d: Authenticate as officer
|
|
333
|
+
console.log('[Authentication Flow] Step 2d: Processing as officer token');
|
|
334
|
+
logger.info('[Authentication Flow] Step 2d: Processing as officer token');
|
|
193
335
|
const officerId = decoded.id || decoded.sub;
|
|
194
336
|
if (!officerId) {
|
|
195
|
-
|
|
337
|
+
bearerTokenFailureReason = 'Invalid token payload - no officer ID found';
|
|
338
|
+
authFailures.push({ method: 'bearer_token', reason: bearerTokenFailureReason });
|
|
339
|
+
console.error('❌ [Authentication Flow] Step 2d: Invalid token payload - no officer ID found');
|
|
340
|
+
logger.warn('[Authentication Flow] Step 2d: Invalid token payload - no officer ID found');
|
|
341
|
+
// Don't return 401 yet - will be handled in final check
|
|
196
342
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
343
|
+
else {
|
|
344
|
+
console.log('[Authentication Flow] Step 2e: Fetching officer from service', {
|
|
345
|
+
officerId
|
|
346
|
+
});
|
|
347
|
+
logger.info('[Authentication Flow] Step 2e: Fetching officer from service', {
|
|
348
|
+
officerId
|
|
349
|
+
});
|
|
350
|
+
// Get officer from service (uses Redis cache with auth service fallback)
|
|
351
|
+
const officer = yield officerService.getOfficerById(officerId);
|
|
352
|
+
if (!officer) {
|
|
353
|
+
bearerTokenFailureReason = 'Officer not found';
|
|
354
|
+
authFailures.push({ method: 'bearer_token', reason: bearerTokenFailureReason });
|
|
355
|
+
console.error('❌ [Authentication Flow] Step 2e: Officer not found', { officerId });
|
|
356
|
+
logger.warn('[Authentication Flow] Step 2e: Officer not found', { officerId });
|
|
357
|
+
// Don't return 401 yet - will be handled in final check
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
console.log('✅ [Authentication Flow] Step 2f: Officer found and authenticated', {
|
|
361
|
+
officerId: officer.id,
|
|
362
|
+
officerName: officer.name,
|
|
363
|
+
serviceNumber: officer.service_number
|
|
364
|
+
});
|
|
365
|
+
logger.info('[Authentication Flow] Step 2f: Officer found and authenticated', {
|
|
366
|
+
officerId: officer.id,
|
|
367
|
+
officerName: officer.name,
|
|
368
|
+
serviceNumber: officer.service_number
|
|
369
|
+
});
|
|
370
|
+
// Attach officer to request
|
|
371
|
+
req.officer = officer;
|
|
372
|
+
req.user = officer;
|
|
373
|
+
officerAuthenticated = true;
|
|
374
|
+
// Log debug info
|
|
375
|
+
const hasApiKey = !!req.apiKey;
|
|
376
|
+
debugLog('Officer authentication successful', {
|
|
377
|
+
officerId: officer.id,
|
|
378
|
+
officerName: officer.name,
|
|
379
|
+
serviceNumber: officer.service_number,
|
|
380
|
+
endpoint: req.originalUrl || req.url,
|
|
381
|
+
method: req.method,
|
|
382
|
+
combinedWithApiKey: hasApiKey,
|
|
383
|
+
apiKeyMicroservice: hasApiKey ? req.apiKey.microservice : undefined
|
|
384
|
+
});
|
|
385
|
+
console.log('[Authentication Flow] Step 2g: Officer attached to request', {
|
|
386
|
+
officerId: officer.id,
|
|
387
|
+
officerName: officer.name,
|
|
388
|
+
serviceNumber: officer.service_number,
|
|
389
|
+
combinedWithApiKey: hasApiKey,
|
|
390
|
+
combinedWithDevice: deviceAuthenticated
|
|
391
|
+
});
|
|
392
|
+
// Enhanced logging when officer is authenticated with API key
|
|
393
|
+
if (hasApiKey) {
|
|
394
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
395
|
+
console.log('✅ API KEY + OFFICER AUTHENTICATION SUCCESSFUL:');
|
|
396
|
+
console.log(' 🔑 API Key:', {
|
|
397
|
+
id: req.apiKey.id,
|
|
398
|
+
microservice: req.apiKey.microservice,
|
|
399
|
+
keyPreview: req.apiKey.key ? req.apiKey.key.substring(0, 15) + '...' : 'N/A'
|
|
400
|
+
});
|
|
401
|
+
console.log(' 👤 Officer:', {
|
|
402
|
+
id: officer.id,
|
|
403
|
+
name: officer.name,
|
|
404
|
+
serviceNumber: officer.service_number,
|
|
405
|
+
email: officer.email
|
|
406
|
+
});
|
|
407
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
408
|
+
}
|
|
409
|
+
logger.info('[Authentication Flow] Step 2g: Officer attached to request', {
|
|
410
|
+
officerId: officer.id,
|
|
411
|
+
officerName: officer.name,
|
|
412
|
+
serviceNumber: officer.service_number,
|
|
413
|
+
combinedWithApiKey: hasApiKey,
|
|
414
|
+
combinedWithDevice: deviceAuthenticated
|
|
210
415
|
});
|
|
211
416
|
}
|
|
212
|
-
return res.status(401).json({ message: 'Officer not found' });
|
|
213
417
|
}
|
|
214
|
-
// Attach officer to request
|
|
215
|
-
req.officer = officer;
|
|
216
|
-
req.user = officer;
|
|
217
|
-
officerAuthenticated = true;
|
|
218
418
|
}
|
|
219
419
|
}
|
|
220
420
|
catch (error) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
return
|
|
421
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
422
|
+
bearerTokenFailureReason = `Invalid or expired token: ${errorMessage}`;
|
|
423
|
+
authFailures.push({ method: 'bearer_token', reason: bearerTokenFailureReason });
|
|
424
|
+
console.error('❌ [Authentication Flow] Step 2: Bearer token validation error', {
|
|
425
|
+
error: errorMessage,
|
|
426
|
+
errorStack: error instanceof Error ? error.stack : undefined,
|
|
427
|
+
errorName: error instanceof Error ? error.name : undefined
|
|
428
|
+
});
|
|
429
|
+
logger.error('[Authentication Flow] Step 2: Bearer token validation error', {
|
|
430
|
+
error: errorMessage,
|
|
431
|
+
errorStack: error instanceof Error ? error.stack : undefined
|
|
432
|
+
});
|
|
433
|
+
// Don't return 401 yet - will be handled in final check
|
|
234
434
|
}
|
|
235
435
|
}
|
|
436
|
+
// Step 3: Determine final authentication status
|
|
437
|
+
const finalApiKeyAuthenticated = !!req.apiKey;
|
|
438
|
+
console.log('[Authentication Flow] Step 3: Determining final authentication status', {
|
|
439
|
+
apiKeyAuthenticated: finalApiKeyAuthenticated,
|
|
440
|
+
deviceAuthenticated,
|
|
441
|
+
officerAuthenticated,
|
|
442
|
+
authAttempts,
|
|
443
|
+
authFailures: authFailures.length > 0 ? authFailures : 'None'
|
|
444
|
+
});
|
|
445
|
+
logger.info('[Authentication Flow] Step 3: Determining final authentication status', {
|
|
446
|
+
apiKeyAuthenticated: finalApiKeyAuthenticated,
|
|
447
|
+
deviceAuthenticated,
|
|
448
|
+
officerAuthenticated,
|
|
449
|
+
authAttempts,
|
|
450
|
+
authFailures
|
|
451
|
+
});
|
|
236
452
|
// At least one authentication method must succeed
|
|
237
|
-
if (!deviceAuthenticated && !officerAuthenticated) {
|
|
453
|
+
if (!finalApiKeyAuthenticated && !deviceAuthenticated && !officerAuthenticated) {
|
|
454
|
+
// Build detailed error message
|
|
455
|
+
let errorMessage = 'Authentication failed';
|
|
456
|
+
const failedMethods = [];
|
|
457
|
+
if (apiKeyFailureReason) {
|
|
458
|
+
failedMethods.push(`API key (${apiKeyFailureReason})`);
|
|
459
|
+
}
|
|
460
|
+
if (bearerTokenFailureReason) {
|
|
461
|
+
failedMethods.push(`Bearer token (${bearerTokenFailureReason})`);
|
|
462
|
+
}
|
|
463
|
+
if (failedMethods.length > 0) {
|
|
464
|
+
if (failedMethods.length === 1) {
|
|
465
|
+
errorMessage = `Authentication failed: ${failedMethods[0]}`;
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
errorMessage = `Authentication failed: Both ${failedMethods.join(' and ')}`;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
else if (authAttempts.length === 0) {
|
|
472
|
+
errorMessage = 'No valid authentication provided. Provide X-API-Key, Device-Token, or Authorization header';
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
errorMessage = 'All provided authentication methods failed';
|
|
476
|
+
}
|
|
477
|
+
console.error('❌ [Authentication Flow] Step 3a: No authentication method succeeded', {
|
|
478
|
+
attemptedMethods: authAttempts,
|
|
479
|
+
failedMethods: authFailures,
|
|
480
|
+
errorMessage
|
|
481
|
+
});
|
|
482
|
+
logger.warn('[Authentication Flow] Step 3a: No authentication method succeeded', {
|
|
483
|
+
attemptedMethods: authAttempts,
|
|
484
|
+
failedMethods: authFailures
|
|
485
|
+
});
|
|
238
486
|
// Log audit event for missing authentication
|
|
239
487
|
if (auditService) {
|
|
240
488
|
auditService.logAuditEvent(req, {
|
|
241
489
|
event_type: undefined, // Will be determined automatically
|
|
242
490
|
response_status: 401,
|
|
243
|
-
error_message:
|
|
244
|
-
error_code: 'NO_AUTH_TOKEN',
|
|
491
|
+
error_message: errorMessage,
|
|
492
|
+
error_code: authAttempts.length === 0 ? 'NO_AUTH_TOKEN' : 'ALL_AUTH_FAILED',
|
|
245
493
|
}).catch((err) => {
|
|
246
494
|
logger.debug('Failed to log audit event', { error: err.message });
|
|
247
495
|
});
|
|
248
496
|
}
|
|
249
|
-
return res.status(401).json({
|
|
497
|
+
return res.status(401).json({
|
|
498
|
+
message: errorMessage,
|
|
499
|
+
failedMethods: authFailures.map(f => f.method),
|
|
500
|
+
attemptedMethods: authAttempts
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
// Step 3b: Set auth type based on what was authenticated
|
|
504
|
+
// Priority: API Key + Device + Officer > API Key + Officer > API Key + Device > API Key > Device + Officer > Device > Officer
|
|
505
|
+
console.log('[Authentication Flow] Step 3b: Setting authentication type');
|
|
506
|
+
logger.info('[Authentication Flow] Step 3b: Setting authentication type');
|
|
507
|
+
// Check for all combinations including device + API key
|
|
508
|
+
if (finalApiKeyAuthenticated && deviceAuthenticated && officerAuthenticated) {
|
|
509
|
+
// All three: API Key + Device + Officer
|
|
510
|
+
req.authType = 'api_key_and_officer'; // Use existing type, device info will be preserved
|
|
511
|
+
const apiKey = req.apiKey;
|
|
512
|
+
const device = req.device;
|
|
513
|
+
const officer = req.officer;
|
|
514
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
515
|
+
console.log('✅ [Authentication Flow] Step 3c: Authentication type set to api_key_and_officer (with device)');
|
|
516
|
+
console.log(' 🔑 API Key Being Used:', {
|
|
517
|
+
id: apiKey === null || apiKey === void 0 ? void 0 : apiKey.id,
|
|
518
|
+
microservice: apiKey === null || apiKey === void 0 ? void 0 : apiKey.microservice,
|
|
519
|
+
keyPreview: (apiKey === null || apiKey === void 0 ? void 0 : apiKey.key) ? apiKey.key.substring(0, 15) + '...' : 'N/A',
|
|
520
|
+
isActive: apiKey === null || apiKey === void 0 ? void 0 : apiKey.is_active,
|
|
521
|
+
expiresAt: (apiKey === null || apiKey === void 0 ? void 0 : apiKey.expires_at) ? new Date(apiKey.expires_at).toISOString() : 'Never'
|
|
522
|
+
});
|
|
523
|
+
console.log(' 📱 Device Being Used:', {
|
|
524
|
+
id: device === null || device === void 0 ? void 0 : device.id,
|
|
525
|
+
device_id: device === null || device === void 0 ? void 0 : device.device_id,
|
|
526
|
+
device_name: device === null || device === void 0 ? void 0 : device.device_name
|
|
527
|
+
});
|
|
528
|
+
console.log(' 👤 Officer Using This API Key:', {
|
|
529
|
+
id: officer === null || officer === void 0 ? void 0 : officer.id,
|
|
530
|
+
name: officer === null || officer === void 0 ? void 0 : officer.name,
|
|
531
|
+
serviceNumber: officer === null || officer === void 0 ? void 0 : officer.service_number,
|
|
532
|
+
email: officer === null || officer === void 0 ? void 0 : officer.email
|
|
533
|
+
});
|
|
534
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
535
|
+
logger.info('[Authentication Flow] Step 3c: Authentication type set to api_key_and_officer (with device)', {
|
|
536
|
+
apiKeyId: apiKey === null || apiKey === void 0 ? void 0 : apiKey.id,
|
|
537
|
+
microservice: apiKey === null || apiKey === void 0 ? void 0 : apiKey.microservice,
|
|
538
|
+
deviceId: device === null || device === void 0 ? void 0 : device.id,
|
|
539
|
+
deviceDeviceId: device === null || device === void 0 ? void 0 : device.device_id,
|
|
540
|
+
officerId: officer === null || officer === void 0 ? void 0 : officer.id,
|
|
541
|
+
officerName: officer === null || officer === void 0 ? void 0 : officer.name,
|
|
542
|
+
officerServiceNumber: officer === null || officer === void 0 ? void 0 : officer.service_number
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
else if (finalApiKeyAuthenticated && deviceAuthenticated) {
|
|
546
|
+
// API Key + Device (no officer)
|
|
547
|
+
req.authType = 'api_key'; // Use api_key type, device info will be preserved
|
|
548
|
+
const apiKey = req.apiKey;
|
|
549
|
+
const device = req.device;
|
|
550
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
551
|
+
console.log('✅ [Authentication Flow] Step 3c: Authentication type set to api_key (with device)');
|
|
552
|
+
console.log(' 🔑 API Key Being Used:', {
|
|
553
|
+
id: apiKey === null || apiKey === void 0 ? void 0 : apiKey.id,
|
|
554
|
+
microservice: apiKey === null || apiKey === void 0 ? void 0 : apiKey.microservice,
|
|
555
|
+
keyPreview: (apiKey === null || apiKey === void 0 ? void 0 : apiKey.key) ? apiKey.key.substring(0, 15) + '...' : 'N/A',
|
|
556
|
+
isActive: apiKey === null || apiKey === void 0 ? void 0 : apiKey.is_active,
|
|
557
|
+
expiresAt: (apiKey === null || apiKey === void 0 ? void 0 : apiKey.expires_at) ? new Date(apiKey.expires_at).toISOString() : 'Never'
|
|
558
|
+
});
|
|
559
|
+
console.log(' 📱 Device Being Used:', {
|
|
560
|
+
id: device === null || device === void 0 ? void 0 : device.id,
|
|
561
|
+
device_id: device === null || device === void 0 ? void 0 : device.device_id,
|
|
562
|
+
device_name: device === null || device === void 0 ? void 0 : device.device_name
|
|
563
|
+
});
|
|
564
|
+
console.log(' ⚠️ No Officer Authenticated (API key + device only)');
|
|
565
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
566
|
+
logger.info('[Authentication Flow] Step 3c: Authentication type set to api_key (with device)', {
|
|
567
|
+
apiKeyId: apiKey === null || apiKey === void 0 ? void 0 : apiKey.id,
|
|
568
|
+
microservice: apiKey === null || apiKey === void 0 ? void 0 : apiKey.microservice,
|
|
569
|
+
deviceId: device === null || device === void 0 ? void 0 : device.id,
|
|
570
|
+
deviceDeviceId: device === null || device === void 0 ? void 0 : device.device_id
|
|
571
|
+
});
|
|
572
|
+
// Clear officer if not authenticated
|
|
573
|
+
req.officer = null;
|
|
574
|
+
req.user = null;
|
|
575
|
+
}
|
|
576
|
+
else if (finalApiKeyAuthenticated && officerAuthenticated) {
|
|
577
|
+
req.authType = 'api_key_and_officer';
|
|
578
|
+
const apiKey = req.apiKey;
|
|
579
|
+
const officer = req.officer;
|
|
580
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
581
|
+
console.log('✅ [Authentication Flow] Step 3c: Authentication type set to api_key_and_officer');
|
|
582
|
+
console.log(' 🔑 API Key Being Used:', {
|
|
583
|
+
id: apiKey === null || apiKey === void 0 ? void 0 : apiKey.id,
|
|
584
|
+
microservice: apiKey === null || apiKey === void 0 ? void 0 : apiKey.microservice,
|
|
585
|
+
keyPreview: (apiKey === null || apiKey === void 0 ? void 0 : apiKey.key) ? apiKey.key.substring(0, 15) + '...' : 'N/A',
|
|
586
|
+
isActive: apiKey === null || apiKey === void 0 ? void 0 : apiKey.is_active,
|
|
587
|
+
expiresAt: (apiKey === null || apiKey === void 0 ? void 0 : apiKey.expires_at) ? new Date(apiKey.expires_at).toISOString() : 'Never'
|
|
588
|
+
});
|
|
589
|
+
console.log(' 👤 Officer Using This API Key:', {
|
|
590
|
+
id: officer === null || officer === void 0 ? void 0 : officer.id,
|
|
591
|
+
name: officer === null || officer === void 0 ? void 0 : officer.name,
|
|
592
|
+
serviceNumber: officer === null || officer === void 0 ? void 0 : officer.service_number,
|
|
593
|
+
email: officer === null || officer === void 0 ? void 0 : officer.email
|
|
594
|
+
});
|
|
595
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
596
|
+
logger.info('[Authentication Flow] Step 3c: Authentication type set to api_key_and_officer', {
|
|
597
|
+
apiKeyId: apiKey === null || apiKey === void 0 ? void 0 : apiKey.id,
|
|
598
|
+
microservice: apiKey === null || apiKey === void 0 ? void 0 : apiKey.microservice,
|
|
599
|
+
keyPreview: (apiKey === null || apiKey === void 0 ? void 0 : apiKey.key) ? apiKey.key.substring(0, 15) + '...' : 'N/A',
|
|
600
|
+
officerId: officer === null || officer === void 0 ? void 0 : officer.id,
|
|
601
|
+
officerName: officer === null || officer === void 0 ? void 0 : officer.name,
|
|
602
|
+
officerServiceNumber: officer === null || officer === void 0 ? void 0 : officer.service_number
|
|
603
|
+
});
|
|
604
|
+
// Officer info is already set from officer authentication above
|
|
250
605
|
}
|
|
251
|
-
|
|
252
|
-
|
|
606
|
+
else if (finalApiKeyAuthenticated) {
|
|
607
|
+
req.authType = 'api_key';
|
|
608
|
+
const apiKey = req.apiKey;
|
|
609
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
610
|
+
console.log('✅ [Authentication Flow] Step 3c: Authentication type set to api_key');
|
|
611
|
+
console.log(' 🔑 API Key Being Used:', {
|
|
612
|
+
id: apiKey === null || apiKey === void 0 ? void 0 : apiKey.id,
|
|
613
|
+
microservice: apiKey === null || apiKey === void 0 ? void 0 : apiKey.microservice,
|
|
614
|
+
keyPreview: (apiKey === null || apiKey === void 0 ? void 0 : apiKey.key) ? apiKey.key.substring(0, 15) + '...' : 'N/A',
|
|
615
|
+
isActive: apiKey === null || apiKey === void 0 ? void 0 : apiKey.is_active,
|
|
616
|
+
expiresAt: (apiKey === null || apiKey === void 0 ? void 0 : apiKey.expires_at) ? new Date(apiKey.expires_at).toISOString() : 'Never'
|
|
617
|
+
});
|
|
618
|
+
console.log(' ⚠️ No Officer Authenticated (API key only - service-to-service)');
|
|
619
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
620
|
+
logger.info('[Authentication Flow] Step 3c: Authentication type set to api_key', {
|
|
621
|
+
apiKeyId: apiKey === null || apiKey === void 0 ? void 0 : apiKey.id,
|
|
622
|
+
microservice: apiKey === null || apiKey === void 0 ? void 0 : apiKey.microservice,
|
|
623
|
+
keyPreview: (apiKey === null || apiKey === void 0 ? void 0 : apiKey.key) ? apiKey.key.substring(0, 15) + '...' : 'N/A'
|
|
624
|
+
});
|
|
625
|
+
// Clear officer if only API key is authenticated
|
|
626
|
+
req.officer = null;
|
|
627
|
+
req.user = null;
|
|
628
|
+
}
|
|
629
|
+
else if (deviceAuthenticated && officerAuthenticated) {
|
|
253
630
|
req.authType = 'both';
|
|
631
|
+
console.log('[Authentication Flow] Step 3c: Authentication type set to both (device + officer)', {
|
|
632
|
+
deviceId: (_a = req.device) === null || _a === void 0 ? void 0 : _a.id,
|
|
633
|
+
officerId: (_b = req.officer) === null || _b === void 0 ? void 0 : _b.id
|
|
634
|
+
});
|
|
635
|
+
logger.info('[Authentication Flow] Step 3c: Authentication type set to both (device + officer)', {
|
|
636
|
+
deviceId: (_c = req.device) === null || _c === void 0 ? void 0 : _c.id,
|
|
637
|
+
officerId: (_d = req.officer) === null || _d === void 0 ? void 0 : _d.id
|
|
638
|
+
});
|
|
254
639
|
}
|
|
255
640
|
else if (deviceAuthenticated) {
|
|
256
641
|
req.authType = 'device';
|
|
642
|
+
console.log('[Authentication Flow] Step 3c: Authentication type set to device', {
|
|
643
|
+
deviceId: (_e = req.device) === null || _e === void 0 ? void 0 : _e.id
|
|
644
|
+
});
|
|
645
|
+
logger.info('[Authentication Flow] Step 3c: Authentication type set to device', {
|
|
646
|
+
deviceId: (_f = req.device) === null || _f === void 0 ? void 0 : _f.id
|
|
647
|
+
});
|
|
257
648
|
req.user = null;
|
|
258
649
|
}
|
|
259
650
|
else {
|
|
260
651
|
req.authType = 'officer';
|
|
652
|
+
console.log('[Authentication Flow] Step 3c: Authentication type set to officer', {
|
|
653
|
+
officerId: (_g = req.officer) === null || _g === void 0 ? void 0 : _g.id
|
|
654
|
+
});
|
|
655
|
+
logger.info('[Authentication Flow] Step 3c: Authentication type set to officer', {
|
|
656
|
+
officerId: (_h = req.officer) === null || _h === void 0 ? void 0 : _h.id
|
|
657
|
+
});
|
|
261
658
|
req.device = null;
|
|
262
659
|
}
|
|
263
|
-
|
|
660
|
+
// Log authentication success with consistent format for all auth types
|
|
661
|
+
const authInfo = {
|
|
264
662
|
authType: req.authType,
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
663
|
+
};
|
|
664
|
+
// Enhanced API key logging
|
|
665
|
+
if (req.apiKey) {
|
|
666
|
+
const apiKey = req.apiKey;
|
|
667
|
+
authInfo.apiKey = {
|
|
668
|
+
id: apiKey.id,
|
|
669
|
+
microservice: apiKey.microservice,
|
|
670
|
+
keyPreview: apiKey.key ? apiKey.key.substring(0, 15) + '...' : 'N/A',
|
|
671
|
+
isActive: apiKey.is_active,
|
|
672
|
+
expiresAt: apiKey.expires_at || 'Never'
|
|
673
|
+
};
|
|
674
|
+
authInfo.sourceMicroservice = apiKey.microservice;
|
|
675
|
+
// Prominent console log for API key usage
|
|
676
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
677
|
+
console.log('🔑 API KEY AUTHENTICATION DETAILS:');
|
|
678
|
+
console.log(' API Key ID:', apiKey.id);
|
|
679
|
+
console.log(' Microservice:', apiKey.microservice);
|
|
680
|
+
console.log(' API Key Preview:', apiKey.key ? apiKey.key.substring(0, 15) + '...' : 'N/A');
|
|
681
|
+
console.log(' Is Active:', apiKey.is_active);
|
|
682
|
+
console.log(' Expires At:', apiKey.expires_at ? new Date(apiKey.expires_at).toISOString() : 'Never');
|
|
683
|
+
if (req.device) {
|
|
684
|
+
console.log(' 📱 Device Also Authenticated:');
|
|
685
|
+
console.log(' Device ID:', req.device.id);
|
|
686
|
+
console.log(' Device Device ID:', req.device.device_id);
|
|
687
|
+
console.log(' Device Name:', req.device.device_name);
|
|
688
|
+
}
|
|
689
|
+
if (req.officer) {
|
|
690
|
+
console.log(' ✅ Officer Also Authenticated:');
|
|
691
|
+
console.log(' Officer ID:', req.officer.id);
|
|
692
|
+
console.log(' Officer Name:', req.officer.name);
|
|
693
|
+
console.log(' Service Number:', req.officer.service_number);
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
console.log(' ⚠️ No Officer Authenticated (API key only)');
|
|
697
|
+
}
|
|
698
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
699
|
+
}
|
|
700
|
+
if (req.device) {
|
|
701
|
+
authInfo.device = {
|
|
702
|
+
id: req.device.id,
|
|
703
|
+
device_id: req.device.device_id,
|
|
704
|
+
device_name: req.device.device_name,
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
if (req.officer) {
|
|
708
|
+
authInfo.officer = {
|
|
709
|
+
id: req.officer.id,
|
|
710
|
+
name: req.officer.name,
|
|
711
|
+
service_number: req.officer.service_number,
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
(0, logMode_1.devLog)('Authentication successful', authInfo);
|
|
715
|
+
// Enhanced console logging for authentication summary
|
|
716
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
717
|
+
console.log('✅ AUTHENTICATION SUMMARY:');
|
|
718
|
+
console.log(' Auth Type:', req.authType);
|
|
719
|
+
console.log(' Endpoint:', req.originalUrl || req.url);
|
|
720
|
+
console.log(' Method:', req.method);
|
|
721
|
+
if (req.apiKey) {
|
|
722
|
+
console.log(' 🔑 API Key:', {
|
|
723
|
+
id: req.apiKey.id,
|
|
724
|
+
microservice: req.apiKey.microservice,
|
|
725
|
+
keyPreview: req.apiKey.key ? req.apiKey.key.substring(0, 15) + '...' : 'N/A'
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
if (req.device) {
|
|
729
|
+
console.log(' 📱 Device:', {
|
|
730
|
+
id: req.device.id,
|
|
731
|
+
device_id: req.device.device_id,
|
|
732
|
+
device_name: req.device.device_name
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
if (req.officer) {
|
|
736
|
+
console.log(' 👤 Officer:', {
|
|
737
|
+
id: req.officer.id,
|
|
738
|
+
name: req.officer.name,
|
|
739
|
+
service_number: req.officer.service_number,
|
|
740
|
+
email: req.officer.email
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
else if (req.authType === 'api_key' || req.authType === 'api_key_and_officer') {
|
|
744
|
+
console.log(' ⚠️ No Officer Information (API key authentication only)');
|
|
745
|
+
}
|
|
746
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
270
747
|
// Log audit event for endpoint access (non-blocking)
|
|
748
|
+
// This now handles ALL authentication types consistently: API Key, Device, Officer, or Both
|
|
271
749
|
if (auditService) {
|
|
272
750
|
(0, logMode_1.devLog)('Calling audit service for endpoint access', {
|
|
273
751
|
path: req.path,
|
|
@@ -275,6 +753,9 @@ const createAuthenticateDeviceOrOfficer = (deps) => {
|
|
|
275
753
|
baseUrl: req.baseUrl,
|
|
276
754
|
method: req.method,
|
|
277
755
|
authType: req.authType,
|
|
756
|
+
hasApiKey: !!req.apiKey,
|
|
757
|
+
hasDevice: !!req.device,
|
|
758
|
+
hasOfficer: !!req.officer,
|
|
278
759
|
});
|
|
279
760
|
auditService.logAuditEvent(req, {
|
|
280
761
|
event_type: undefined, // Will be determined automatically
|
|
@@ -285,6 +766,7 @@ const createAuthenticateDeviceOrOfficer = (deps) => {
|
|
|
285
766
|
stack: err.stack,
|
|
286
767
|
path: req.path,
|
|
287
768
|
originalUrl: req.originalUrl,
|
|
769
|
+
authType: req.authType,
|
|
288
770
|
});
|
|
289
771
|
(0, logMode_1.devError)('Audit logging failed', err);
|
|
290
772
|
});
|