@govish/shared-services

v1.6.0

Published

Govish shared services package - officer, device, station, penal code, station-duty block, and rank leave days cache services; API key service; audit logging; authentication middleware with support for API key + officer combined authentication

Downloads

56

Readme

Govish Shared Services Package

This package contains shared services, middleware, and utilities that can be used across multiple microservices.

Quick Start

import { 
  AuditService,
  createAuthenticateDeviceOrOfficer,
  SharedServicesDependencies 
} from '@govish/shared-services';

const dependencies: SharedServicesDependencies = {
  redisClient,
  logger,
  kafkaBrokers: process.env.KAFKA_BROKER || 'localhost:9092',
  auditTopic: 'audit-log',
  enableDebugLogs: false, // Set to true for verbose logging
};

const auditService = new AuditService(dependencies);
const authenticateDeviceOrOfficer = createAuthenticateDeviceOrOfficer(dependencies);

Services Included

  • OfficerCacheService: Redis-based caching service for officers with indexing and efficient queries
  • PenalCodeCacheService: Redis-based caching service for penal codes
  • DeviceCacheService: Redis-based caching service for devices with indexing
  • StationCacheService: Redis-based caching service for police stations (JSON list, set of IDs, and per-station hashes)
  • StationDutyBlockCacheService: Read-only cache for station-duty blocks and sectors — fetch only (getBlocks, getBlocksByStationId, getBlock, getSectors, getSector)
  • RankLeaveDaysCacheService: Read-only cache for rank leave days (getLeaveDays, getLeaveDay)
  • ApiKeyService: Service for managing and validating API keys with Redis caching
  • AuditService: Service for logging audit events to Kafka with comprehensive event tracking
  • authenticateDeviceOrOfficer: Express middleware for authenticating devices, officers, or microservices via API keys

Utilities Included

  • createRollingCache: List/item rolling cache (buildKey, get, set, getOrSet, invalidate, invalidateByPrefix, recordAccess) with day-based TTL from CACHE_DAYS_LIST / CACHE_DAYS_ITEM; enable with ROLLING_CACHE_ENABLED=true
  • createSafeRedisGet: Factory function to create a safe Redis GET operation with automatic reconnection
  • createSafeRedisSet: Factory function to create a safe Redis SET operation with automatic reconnection
  • createSafeRedisUtils: Factory function that creates both safeRedisGet and safeRedisSet functions

Installation

This is a local package. To use it in your project:

cd packages/ob-shared-services
npm install
npm run build

Usage

Initialization

All services require dependencies to be injected. Create a factory file in your project:

import { 
  OfficerCacheService, 
  PenalCodeCacheService, 
  DeviceCacheService, 
  ApiKeyService,
  createAuthenticateDeviceOrOfficer,
  SharedServicesDependencies 
} from '@govish/shared-services';
import { redisClient } from './app';
import { logger } from './utils/logger';
// ... other dependencies

const dependencies: SharedServicesDependencies = {
  redisClient,
  logger,
  authHost: process.env.AUTH_HOST,
  authPort: process.env.AUTH_PORT,
  serverName: process.env.SERVER_NAME,
  auditTopic: process.env.AUDIT_TOPIC || 'audit-log',
  enableDebugLogs: process.env.AUDIT_DEBUG_LOGS === 'true',
  jwtConfig,
  
  // Option 1: Use Kafka brokers (recommended - audit service manages its own connection)
  kafkaBrokers: process.env.KAFKA_BROKER || process.env.KAFKA_BROKERS,
  kafkaClientId: process.env.KAFKA_CLIENT_ID || 'audit-service',
  
  // Option 2: Use existing producer (backward compatibility)
  // kafkaProducer: producer,
  // isProducerConnected: () => kafkaProducerService?.getIsConnected() || false,
  
  ...createSafeRedisUtils(redisClient),  // Spreads safeRedisGet and safeRedisSet
};

// Initialize services
export const officerCacheService = new OfficerCacheService(dependencies);
export const penalCodeCacheService = new PenalCodeCacheService(dependencies);
export const deviceCacheService = new DeviceCacheService(dependencies);
export const apiKeyService = new ApiKeyService(dependencies);
export const auditService = new AuditService(dependencies);

// Create middleware
export const authenticateDeviceOrOfficer = createAuthenticateDeviceOrOfficer(dependencies);

// Export Redis utilities (optional - can also use createSafeRedisUtils directly)
export const { safeRedisGet, safeRedisSet } = createSafeRedisUtils(redisClient);

Using Services

import { 
  officerCacheService, 
  penalCodeCacheService,
  deviceCacheService,
  apiKeyService,
  auditService,
  safeRedisGet,
  safeRedisSet
} from './services/sharedServicesFactory';

// Get officer by ID
const officer = await officerCacheService.getOfficerById(123);

// Get officers by station
const officers = await officerCacheService.getOfficersByStation(1);

// Store officer
await officerCacheService.storeOfficer(officerData);

// Get penal codes
const penalCodes = await penalCodeCacheService.getPenalCodes();

// Validate API key
const apiKey = await apiKeyService.validateApiKey('ak_xxx');

// Log audit event
await auditService.logAuditEvent(req, {
  event_type: AuditEventType.ENDPOINT_ACCESSED
});

// Log audit event with custom keys
await auditService.logAuditEvent(req, {
  event_type: AuditEventType.ENDPOINT_ACCESSED,
  custom_field: 'custom_value',
  metadata: {
    action: 'special_action',
    reason: 'user_requested'
  }
});

// Use Redis utilities
const value = await safeRedisGet('some-key');
await safeRedisSet('some-key', 'some-value');

Using Middleware

import { authenticateDeviceOrOfficer } from './services/sharedServicesFactory';
import { Router } from 'express';

const router = Router();
router.get('/api/v1/officers', authenticateDeviceOrOfficer, getOfficers);

Dependencies

The package requires the following dependencies to be provided:

Required:

  • redisClient: Redis client instance
  • logger: Winston logger instance

Optional:

  • authHost: Auth service host (defaults to process.env.AUTH_HOST)
  • authPort: Auth service port (defaults to process.env.AUTH_PORT)
  • serverName: Server/microservice name (defaults to process.env.SERVER_NAME)
  • auditTopic: Kafka topic for audit logs (defaults to process.env.AUDIT_TOPIC or 'audit-log')
  • jwtConfig: JWT configuration object (for authentication middleware)
  • enableDebugLogs: Enable verbose debug logging (defaults to false, can be set via AUDIT_DEBUG_LOGS=true)
  • Kafka Configuration (choose one):
    • kafkaBrokers: Kafka broker URLs as string or array (e.g., 'localhost:9092' or ['broker1:9092', 'broker2:9092']) - Recommended: Audit service manages its own connection
    • kafkaClientId: Kafka client ID (defaults to 'audit-service')
    • OR (for backward compatibility):
      • kafkaProducer: Existing Kafka producer instance
      • isProducerConnected: Function that returns boolean indicating Kafka connection status
  • safeRedisGet: Safe Redis get function (created via createSafeRedisGet)
  • safeRedisSet: Safe Redis set function (created via createSafeRedisSet)

Audit Service

Features

Automatic Event Type Detection

The audit service automatically determines event types based on request path and method:

  • Login events: LOGIN_SUCCESS, LOGIN_FAILED, LOGIN_NOT_PERMITTED
  • Officer events: OFFICER_RETRIEVED, OFFICERS_LISTED
  • Device events: DEVICE_RETRIEVED, DEVICES_LISTED
  • Arrest events: ARREST_CREATED, ARREST_UPDATED, ARREST_DELETED, ARREST_RETRIEVED, ARRESTS_LISTED
  • General: ENDPOINT_ACCESSED, UNAUTHORIZED_ACCESS, FORBIDDEN_ACCESS

Custom Keys Support

You can add custom keys to audit events - they will be included in the Kafka message:

await auditService.logAuditEvent(req, {
  custom_field: 'custom_value',
  metadata: { action: 'special_action' },
  any_other_key: 'any_value'
});

IP Address Detection

The audit service automatically detects client IP addresses from multiple sources (in order of priority):

  1. x-forwarded-for (most common behind proxies/load balancers)
  2. cf-connecting-ip (Cloudflare)
  3. x-real-ip (nginx)
  4. true-client-ip (Cloudflare Enterprise)
  5. x-client-ip
  6. req.ip (requires trust proxy)
  7. req.socket.remoteAddress
  8. req.connection.remoteAddress

Kafka Connection Management

The audit service can manage its own Kafka connection when kafkaBrokers is provided:

  • Creates its own Kafka client and producer
  • Handles connection lifecycle automatically
  • Connects on first use (lazy connection)
  • Gracefully handles connection failures
  • Falls back silently if Kafka is unavailable (doesn't break requests)

User Information Tracking

Automatically extracts and includes:

  • Officer information (id, name, service_number, email)
  • Device information (id, device_id)
  • Microservice information (for API key authentication)
  • Authentication type (officer, device, both, api_key, microservice)

Usage Examples

import { auditService, AuditEventType } from './services/sharedServicesFactory';

// Basic usage - event type determined automatically
await auditService.logAuditEvent(req, {});

// Explicit event type
await auditService.logAuditEvent(req, {
  event_type: AuditEventType.ARREST_CREATED
});

// With custom keys
await auditService.logAuditEvent(req, {
  event_type: AuditEventType.ARREST_CREATED,
  custom_action: 'manual_review',
  review_reason: 'suspicious_pattern'
});

// With response information
await auditService.logAuditEvent(req, {
  response_status: 200,
  duration_ms: 150
});

// Log endpoint access (helper method)
await auditService.logEndpointAccess(req);

// Log with response status (helper method)
const startTime = Date.now();
// ... handle request ...
await auditService.logEndpointAccessWithResponse(req, res, startTime);

Environment Variables

Audit Service Configuration

# Kafka brokers (required for internal connection)
KAFKA_BROKER=localhost:9092
# or
KAFKA_BROKERS=broker1:9092,broker2:9092

# Kafka client ID (optional)
KAFKA_CLIENT_ID=audit-service

# Audit topic (optional, defaults to 'audit-log')
AUDIT_TOPIC=audit-log

# Enable debug logs (optional, defaults to false)
AUDIT_DEBUG_LOGS=true

Other Configuration

# Auth service (for device/officer service fallback)
AUTH_HOST=localhost
AUTH_PORT=3000

# Server name
SERVER_NAME=ob-main

Environment Modes

The package respects the NODE_ENV environment variable to control logging behavior:

Development Mode (NODE_ENV=development or NODE_ENV=dev)

  • All console logs are shown (debug, info, warnings, errors)
  • Detailed error information is displayed
  • Debug logging is enabled
  • Full stack traces are shown

Production Mode (NODE_ENV=production or NODE_ENV=prod)

  • Console logs are suppressed (only errors with minimal info)
  • Only warnings and errors are logged via Winston logger
  • Debug and info logs are hidden
  • Reduced logging for better performance

Usage

The package provides utility functions for conditional logging:

import { devLog, devError, devWarn, devDebug, isDevelopmentMode } from '@govish/shared-services';

// Only logs in development mode
devLog('This will only show in dev mode');
devDebug('Debug information');
devWarn('Warning message');

// Errors always log, but with less detail in production
devError('Error occurred', errorDetails);

// Check mode programmatically
if (isDevelopmentMode()) {
  // Development-only code
}

Logger Configuration

The Winston logger automatically adjusts based on environment:

  • Development: Logs all levels (debug, info, warn, error) to console and files
  • Production: Only logs warnings and errors to files (console disabled unless LOG_TO_CONSOLE=true)

To enable console logging in production, set:

LOG_TO_CONSOLE=true

Debug Logging Control

The audit service has its own debug logging control via enableDebugLogs:

// Enable all debug logs (console.log, console.error for audit service)
const dependencies: SharedServicesDependencies = {
  // ...
  enableDebugLogs: true, // or set AUDIT_DEBUG_LOGS=true
};

When enableDebugLogs is false (default):

  • No [AUDIT KAFKA] debug logs
  • No [AUDIT] user access logs
  • Only errors are logged via Winston logger

When enableDebugLogs is true:

  • All [AUDIT KAFKA] debug logs are shown
  • [AUDIT] user access logs are shown
  • Detailed Kafka connection and send logs

Building

npm run build

This will compile TypeScript to JavaScript in the dist/ directory.