@govish/shared-services 1.4.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,6 +13,7 @@ export interface ApiKeyData {
13
13
  }
14
14
  export declare class ApiKeyService {
15
15
  private logger;
16
+ private redisClient;
16
17
  private safeRedisGet?;
17
18
  private safeRedisSet?;
18
19
  constructor(deps: SharedServicesDependencies);
@@ -27,7 +28,7 @@ export declare class ApiKeyService {
27
28
  createApiKey(microservice: string, description?: string, expiresInDays?: number, createdBy?: number): Promise<ApiKeyData>;
28
29
  /**
29
30
  * Validate an API key
30
- * Uses Redis cache with auth service fallback (similar to officer service pattern)
31
+ * Uses Redis cache only (matches auth microservice cache format)
31
32
  */
32
33
  validateApiKey(apiKey: string): Promise<ApiKeyData | null>;
33
34
  /**
@@ -17,6 +17,7 @@ const crypto_1 = __importDefault(require("crypto"));
17
17
  class ApiKeyService {
18
18
  constructor(deps) {
19
19
  this.logger = deps.logger;
20
+ this.redisClient = deps.redisClient;
20
21
  this.safeRedisGet = deps.safeRedisGet;
21
22
  this.safeRedisSet = deps.safeRedisSet;
22
23
  }
@@ -74,72 +75,263 @@ class ApiKeyService {
74
75
  }
75
76
  /**
76
77
  * Validate an API key
77
- * Uses Redis cache with auth service fallback (similar to officer service pattern)
78
+ * Uses Redis cache only (matches auth microservice cache format)
78
79
  */
79
80
  validateApiKey(apiKey) {
80
81
  return __awaiter(this, void 0, void 0, function* () {
82
+ const apiKeyPreview = apiKey.substring(0, 10) + '...'; // Show first 10 chars for logging
83
+ console.log('🔑 [API Key Validation] Step 1: Starting API key validation', {
84
+ apiKeyPreview,
85
+ timestamp: new Date().toISOString()
86
+ });
87
+ this.logger.info('[API Key Validation] Step 1: Starting API key validation', {
88
+ apiKeyPreview
89
+ });
81
90
  try {
82
- // Try to get from Redis cache first
83
- const cacheKey = `api_key:${apiKey}`;
84
- const cachedData = this.safeRedisGet ? yield this.safeRedisGet(cacheKey) : null;
91
+ // Ensure Redis is connected
92
+ if (!this.redisClient.isOpen) {
93
+ yield this.redisClient.connect();
94
+ }
95
+ // Step 2: Get API key ID from lookup key (matches auth microservice format)
96
+ console.log('🔍 [API Key Validation] Step 2: Checking Redis cache for API key');
97
+ this.logger.info('[API Key Validation] Step 2: Checking Redis cache for API key');
98
+ const lookupKey = `apikey:by_key:${apiKey}`;
99
+ const apiKeyIdStr = yield this.redisClient.get(lookupKey);
85
100
  let key = null;
86
- if (cachedData) {
87
- try {
88
- key = JSON.parse(cachedData);
89
- this.logger.debug('API key found in cache', { apiKeyId: key.id });
101
+ let cacheHit = false;
102
+ if (apiKeyIdStr) {
103
+ // Step 3: Get API key hash data using the ID
104
+ const apiKeyId = parseInt(apiKeyIdStr, 10);
105
+ if (!isNaN(apiKeyId)) {
106
+ cacheHit = true;
107
+ console.log('✅ [API Key Validation] Step 2a: API key ID found in cache (CACHE HIT)', {
108
+ apiKeyId,
109
+ lookupKey
110
+ });
111
+ this.logger.info('[API Key Validation] Step 2a: API key ID found in cache (CACHE HIT)', {
112
+ apiKeyId,
113
+ lookupKey
114
+ });
115
+ // Get hash data from apikey:${id}
116
+ const hashKey = `apikey:${apiKeyId}`;
117
+ console.log('[API Key Validation] Step 2b: Retrieving API key hash data from cache');
118
+ this.logger.info('[API Key Validation] Step 2b: Retrieving API key hash data from cache', {
119
+ hashKey
120
+ });
121
+ try {
122
+ const hashData = yield this.redisClient.hGetAll(hashKey);
123
+ if (hashData && hashData.id) {
124
+ // Parse hash data (matches auth microservice format)
125
+ // Parse creator field safely (it's stored as JSON string)
126
+ let creator = undefined;
127
+ if (hashData.creator) {
128
+ try {
129
+ creator = JSON.parse(hashData.creator);
130
+ }
131
+ catch (parseError) {
132
+ // Ignore parse errors for creator field
133
+ console.warn('[API Key Validation] Failed to parse creator field', {
134
+ error: parseError instanceof Error ? parseError.message : 'Unknown error'
135
+ });
136
+ }
137
+ }
138
+ key = {
139
+ id: parseInt(hashData.id),
140
+ key: hashData.full_key || hashData.key || apiKey, // Use full_key if available (matches auth microservice behavior)
141
+ microservice: hashData.microservice || '',
142
+ description: hashData.description || undefined,
143
+ is_active: hashData.is_active === 'true', // Stored as 'true'/'false' string
144
+ last_used_at: hashData.last_used_at ? new Date(hashData.last_used_at) : undefined,
145
+ expires_at: hashData.expires_at ? new Date(hashData.expires_at) : undefined,
146
+ created_at: hashData.created_at ? new Date(hashData.created_at) : new Date(),
147
+ updated_at: hashData.updated_at ? new Date(hashData.updated_at) : new Date(),
148
+ created_by: hashData.created_by ? parseInt(hashData.created_by) : undefined,
149
+ creator: creator
150
+ };
151
+ console.log('✅ [API Key Validation] Step 2c: Successfully retrieved cached API key', {
152
+ apiKeyId: key.id,
153
+ microservice: key.microservice,
154
+ isActive: key.is_active,
155
+ expiresAt: key.expires_at || 'Never',
156
+ expiresAtFormatted: key.expires_at ? new Date(key.expires_at).toISOString() : 'N/A'
157
+ });
158
+ this.logger.info('[API Key Validation] Step 2c: Successfully retrieved cached API key', {
159
+ apiKeyId: key.id,
160
+ microservice: key.microservice,
161
+ isActive: key.is_active,
162
+ expiresAt: key.expires_at
163
+ });
164
+ }
165
+ else {
166
+ console.warn('âš ī¸ [API Key Validation] Step 2c: API key hash data not found', {
167
+ hashKey,
168
+ apiKeyId
169
+ });
170
+ this.logger.warn('[API Key Validation] Step 2c: API key hash data not found', {
171
+ hashKey,
172
+ apiKeyId
173
+ });
174
+ }
175
+ }
176
+ catch (hashError) {
177
+ console.error('❌ [API Key Validation] Step 2c: Failed to retrieve API key hash data', {
178
+ error: hashError instanceof Error ? hashError.message : 'Unknown error',
179
+ errorStack: hashError instanceof Error ? hashError.stack : undefined,
180
+ hashKey
181
+ });
182
+ this.logger.warn('[API Key Validation] Step 2c: Failed to retrieve API key hash data', {
183
+ error: hashError instanceof Error ? hashError.message : 'Unknown error',
184
+ hashKey
185
+ });
186
+ }
90
187
  }
91
- catch (parseError) {
92
- this.logger.warn('Failed to parse cached API key', {
93
- error: parseError instanceof Error ? parseError.message : 'Unknown error'
188
+ else {
189
+ console.warn('âš ī¸ [API Key Validation] Step 2a: Invalid API key ID in cache', {
190
+ apiKeyIdStr,
191
+ lookupKey
192
+ });
193
+ this.logger.warn('[API Key Validation] Step 2a: Invalid API key ID in cache', {
194
+ apiKeyIdStr,
195
+ lookupKey
94
196
  });
95
197
  }
96
198
  }
97
- // If not in cache, fetch from auth service
98
- if (!key) {
99
- let authHost = process.env.AUTH_HOST || 'localhost:3000';
100
- if (!authHost.includes(':')) {
101
- authHost = `${authHost}:3000`;
102
- }
103
- const response = yield fetch(`http://${authHost}/api/v1/api-key/validate`, {
104
- method: 'POST',
105
- headers: { 'Content-Type': 'application/json' },
106
- body: JSON.stringify({ apiKey })
199
+ else {
200
+ cacheHit = false;
201
+ console.log('âš ī¸ [API Key Validation] Step 2a: API key NOT found in cache (CACHE MISS)');
202
+ this.logger.info('[API Key Validation] Step 2a: API key not found in cache (CACHE MISS)', {
203
+ lookupKey
107
204
  });
108
- if (!response.ok) {
109
- if (response.status === 404 || response.status === 401) {
110
- return null;
111
- }
112
- this.logger.warn('Failed to validate API key from auth service', {
113
- status: response.status
114
- });
115
- return null;
116
- }
117
- const result = yield response.json();
118
- key = result.data || result;
119
- // Cache the result if found (15 minutes TTL for API keys)
120
- if (key && this.safeRedisSet) {
121
- yield this.safeRedisSet(cacheKey, JSON.stringify(key));
122
- this.logger.debug('API key cached', { apiKeyId: key.id });
123
- }
124
205
  }
206
+ // Step 3: Validate key exists (cache-only, no auth service fallback)
125
207
  if (!key) {
208
+ console.error('❌ [API Key Validation] Step 3: API key not found in cache', {
209
+ cacheChecked: true,
210
+ cacheHit,
211
+ apiKeyPreview,
212
+ lookupKey
213
+ });
214
+ this.logger.warn('[API Key Validation] Step 3: API key not found in cache', {
215
+ cacheChecked: true,
216
+ cacheHit,
217
+ lookupKey
218
+ });
126
219
  return null;
127
220
  }
128
- // Check if key is active
221
+ console.log('✅ [API Key Validation] Step 3: API key found, validating status', {
222
+ apiKeyId: key.id,
223
+ microservice: key.microservice,
224
+ cacheHit,
225
+ expiresAt: key.expires_at || 'Never',
226
+ expiresAtFormatted: key.expires_at ? new Date(key.expires_at).toISOString() : 'N/A'
227
+ });
228
+ this.logger.info('[API Key Validation] Step 3: API key found, validating status', {
229
+ apiKeyId: key.id,
230
+ microservice: key.microservice,
231
+ cacheHit
232
+ });
233
+ // Step 4: Check if key is active
234
+ console.log('[API Key Validation] Step 4: Checking if API key is active', {
235
+ apiKeyId: key.id,
236
+ isActive: key.is_active
237
+ });
238
+ this.logger.info('[API Key Validation] Step 4: Checking if API key is active', {
239
+ apiKeyId: key.id,
240
+ isActive: key.is_active
241
+ });
129
242
  if (!key.is_active) {
130
- this.logger.warn('Inactive API key used', { apiKeyId: key.id });
243
+ console.error('❌ [API Key Validation] Step 4a: API key is INACTIVE', {
244
+ apiKeyId: key.id,
245
+ microservice: key.microservice,
246
+ cacheHit
247
+ });
248
+ this.logger.warn('[API Key Validation] Step 4a: API key is inactive', {
249
+ apiKeyId: key.id
250
+ });
131
251
  return null;
132
252
  }
133
- // Check if key has expired
134
- if (key.expires_at && new Date(key.expires_at) < new Date()) {
135
- this.logger.warn('Expired API key used', { apiKeyId: key.id });
136
- return null;
253
+ console.log('✅ [API Key Validation] Step 4a: API key is active ✓');
254
+ // Step 5: Check if key has expired
255
+ if (key.expires_at) {
256
+ const expiresAt = new Date(key.expires_at);
257
+ const now = new Date();
258
+ const isExpired = expiresAt < now;
259
+ const timeUntilExpiry = expiresAt.getTime() - now.getTime();
260
+ const daysUntilExpiry = Math.floor(timeUntilExpiry / (1000 * 60 * 60 * 24));
261
+ console.log('[API Key Validation] Step 5: Checking API key expiration', {
262
+ apiKeyId: key.id,
263
+ expiresAt: key.expires_at,
264
+ expiresAtFormatted: expiresAt.toISOString(),
265
+ currentTime: now.toISOString(),
266
+ isExpired,
267
+ daysUntilExpiry: isExpired ? 'EXPIRED' : daysUntilExpiry
268
+ });
269
+ this.logger.info('[API Key Validation] Step 5: Checking API key expiration', {
270
+ apiKeyId: key.id,
271
+ expiresAt: key.expires_at,
272
+ currentTime: new Date().toISOString(),
273
+ isExpired
274
+ });
275
+ if (isExpired) {
276
+ console.error('❌ [API Key Validation] Step 5a: API key has EXPIRED', {
277
+ apiKeyId: key.id,
278
+ expiresAt: expiresAt.toISOString(),
279
+ expiredAt: expiresAt.toISOString(),
280
+ currentTime: now.toISOString(),
281
+ daysExpired: Math.abs(daysUntilExpiry)
282
+ });
283
+ this.logger.warn('[API Key Validation] Step 5a: API key has expired', {
284
+ apiKeyId: key.id,
285
+ expiresAt: key.expires_at,
286
+ currentTime: now.toISOString()
287
+ });
288
+ return null;
289
+ }
290
+ console.log('✅ [API Key Validation] Step 5a: API key has not expired ✓', {
291
+ daysUntilExpiry,
292
+ expiresAtFormatted: expiresAt.toISOString()
293
+ });
294
+ }
295
+ else {
296
+ console.log('â„šī¸ [API Key Validation] Step 5: API key has no expiration date (permanent)');
297
+ this.logger.info('[API Key Validation] Step 5: API key has no expiration date (permanent)');
137
298
  }
299
+ console.log('✅ [API Key Validation] Step 6: API key validation SUCCESSFUL', {
300
+ apiKeyId: key.id,
301
+ microservice: key.microservice,
302
+ isActive: key.is_active,
303
+ expiresAt: key.expires_at || 'Never',
304
+ expiresAtFormatted: key.expires_at ? new Date(key.expires_at).toISOString() : 'N/A',
305
+ cacheHit,
306
+ timestamp: new Date().toISOString()
307
+ });
308
+ this.logger.info('[API Key Validation] Step 6: API key validation successful', {
309
+ apiKeyId: key.id,
310
+ microservice: key.microservice,
311
+ isActive: key.is_active,
312
+ expiresAt: key.expires_at || 'Never',
313
+ cacheHit
314
+ });
138
315
  return key;
139
316
  }
140
317
  catch (error) {
141
- this.logger.error('Error validating API key', {
142
- error: error instanceof Error ? error.message : 'Unknown error'
318
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
319
+ const errorStack = error instanceof Error ? error.stack : undefined;
320
+ const errorName = error instanceof Error ? error.name : 'Unknown';
321
+ console.error('❌ [API Key Validation] ERROR during validation', {
322
+ step: 'Unknown',
323
+ error: errorMessage,
324
+ errorName,
325
+ errorStack,
326
+ apiKeyPreview,
327
+ timestamp: new Date().toISOString()
328
+ });
329
+ this.logger.error('[API Key Validation] Error during validation', {
330
+ step: 'Unknown',
331
+ error: errorMessage,
332
+ errorStack,
333
+ errorName,
334
+ apiKeyPreview
143
335
  });
144
336
  return null;
145
337
  }
@@ -21,7 +21,15 @@ export declare enum AuditEventType {
21
21
  ARREST_OB_GENERATED = "arrest_ob_generated",
22
22
  ENDPOINT_ACCESSED = "endpoint_accessed",
23
23
  UNAUTHORIZED_ACCESS = "unauthorized_access",
24
- FORBIDDEN_ACCESS = "forbidden_access"
24
+ FORBIDDEN_ACCESS = "forbidden_access",
25
+ IPRS_PERSON_SEARCHED = "iprs_person_searched",
26
+ IPRS_PERSON_FETCHED_FROM_VPS = "iprs_person_fetched_from_vps",
27
+ IPRS_PERSON_STORED = "iprs_person_stored",
28
+ IPRS_PERSON_UPDATED = "iprs_person_updated",
29
+ VEHICLE_SEARCHED = "vehicle_searched",
30
+ VEHICLE_FETCHED_FROM_IPRS = "vehicle_fetched_from_iprs",
31
+ VEHICLE_STORED = "vehicle_stored",
32
+ VEHICLE_UPDATED = "vehicle_updated"
25
33
  }
26
34
  export interface AuditEventPayload {
27
35
  timestamp: string;
@@ -36,7 +44,7 @@ export interface AuditEventPayload {
36
44
  ip_address?: string;
37
45
  user_agent?: string;
38
46
  user_id?: number;
39
- user_type?: 'officer' | 'device' | 'both' | 'api_key' | 'microservice';
47
+ user_type?: 'officer' | 'device' | 'both' | 'api_key' | 'microservice' | 'api_key_and_officer';
40
48
  officer_id?: number;
41
49
  officer_name?: string;
42
50
  officer_service_number?: string;
@@ -44,7 +52,11 @@ export interface AuditEventPayload {
44
52
  device_id?: number;
45
53
  device_device_id?: string;
46
54
  api_key_id?: number;
55
+ api_key_name?: string;
56
+ api_key_description?: string;
57
+ api_key_preview?: string;
47
58
  authenticated_microservice?: string;
59
+ source_microservice?: string;
48
60
  arrest_id?: number;
49
61
  ob_number?: string;
50
62
  sync_id?: any;