subsafe
v1.0.1
Published
MOSIP WebSub event listener with message decryption capabilities
Maintainers
Readme
SubSafe
A flexible MOSIP WebSub event listener with message decryption capabilities.
Features
- WebSub subscription management for MOSIP events
- Automatic handling of webhook verification challenges
- Data Share URI resolution and data retrieval
- MOSIP partner credential decryption using P12 certificates
- Configurable through environment variables or constructor options
- Event-based notification processing
- Advanced logging with configurable log levels
Installation
npm install subsafeUsage as an npm Package
You can use SubSafe in two ways:
- As a standalone project (clone and run)
- As a dependency in your own project (npm install)
Using as a Dependency
To use SubSafe as an npm package in your project:
# Install the package
npm install subsafeThen in your application:
// Import the SubSafe package
const SubSafe = require('subsafe');
// Create an instance with your configuration
const subSafe = new SubSafe({
// Your configuration here
port: 3000,
// MOSIP configuration
baseUrl: 'https://your-mosip-instance.com',
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
// WebSub configuration
hubUrl: 'https://your-websub-hub.com',
topic: 'your-websub-topic',
// DataShare configuration
dataSourceMode: 'datashare', // 'datashare' or 'direct'
p12Path: './your-cert.p12',
p12Password: 'your-p12-password'
});
// Register a notification handler
subSafe.onNotification((decryptedData, rawNotification) => {
console.log('Received decrypted data:', decryptedData);
// Your business logic here
});
// Start the server and subscribe to events
async function startListener() {
// Start the server
const callbackUrl = await subSafe.start();
console.log(`Server started with callback URL: ${callbackUrl}`);
// Subscribe to the WebSub topic
const subscriptionResult = await subSafe.subscribe();
console.log('Subscription result:', subscriptionResult);
}
startListener();For more examples, see the examples directory.
Publishing Your Changes
If you've made improvements to SubSafe and want to publish your own version:
- Update the version in package.json
- Run tests to make sure everything works:
npm run test:package - Publish to npm:
npm publish
Quick Start
const SubSafe = require('subsafe');
// The logger is directly available from the SubSafe package
const { logger } = SubSafe;
// Create a new SubSafe instance
const subsafe = new SubSafe({
// Optional: Override environment variables with constructor options
hubUrl: process.env.MOSIP_WEBSUB_URL,
topic: process.env.MOSIP_TOPIC,
callbackPath: process.env.CALLBACK_PATH,
// Log level configuration
logLevel: process.env.LOG_LEVEL || 'info',
// Auth credentials
baseUrl: process.env.MOSIP_BASE_URL,
clientId: process.env.MOSIP_CLIENT_ID,
clientSecret: process.env.MOSIP_SECRET_KEY,
tokenUrl: process.env.MOSIP_TOKEN_URL,
authEndpoint: process.env.MOSIP_AUTH_ENDPOINT,
appId: process.env.MOSIP_APP_ID,
// P12 configuration for decryption
p12Path: process.env.P12_PATH,
p12Password: process.env.P12_PASSWORD,
// Data source configuration
dataSourceMode: process.env.DATA_SOURCE_MODE || 'datashare', // 'direct' or 'datashare'
directMessagePath: process.env.DIRECT_MESSAGE_PATH || 'event.data.credential',
registrationIdField: process.env.REGISTRATION_ID_FIELD || 'event.data.registrationId',
// Auth configuration
baseUrl: process.env.MOSIP_BASE_URL,
clientId: process.env.MOSIP_CLIENT_ID,
clientSecret: process.env.MOSIP_SECRET_KEY,
tokenUrl: process.env.MOSIP_TOKEN_URL,
authEndpoint: process.env.MOSIP_AUTH_ENDPOINT,
appId: process.env.MOSIP_APP_ID,
// Data share configuration
dataShareBaseUrl: process.env.DATASHARE_BASE_URL,
// Decryption configuration
p12Path: process.env.P12_PATH,
p12Password: process.env.P12_PASSWORD,
tempEncryptedFile: process.env.TEMP_ENCRYPTED_FILE,
tempDecryptedFile: process.env.TEMP_DECRYPTED_FILE
});
// Register notification handler for incoming messages
subsafe.onNotification((data) => {
logger.info('Registration ID:', { registrationId: data.registrationId });
logger.debug('Decrypted data:', { data });
// Your business logic here
});
// Start the server
subsafe.start(process.env.CALLBACK_URL)
.then(async (server) => {
logger.info('Server started!');
// Optional: Subscribe to the WebSub hub
await subsafe.subscribe();
})
.catch(error => {
logger.error('Failed to start server:', error);
});Configuration
SubSafe can be configured using environment variables (recommended) or constructor options.
Environment Variables
Create a .env file in your project root:
# Server Configuration
PORT=3000
CALLBACK_URL=
CALLBACK_PATH=
# Logging Configuration
# Levels: error, warn, info, http, verbose, debug, silly
LOG_LEVEL=info
# MOSIP Authentication Configuration
MOSIP_BASE_URL=
MOSIP_CLIENT_ID=
MOSIP_SECRET_KEY=
MOSIP_TOKEN_URL=
MOSIP_AUTH_ENDPOINT=
MOSIP_APP_ID=
# MOSIP WebSub Configuration
MOSIP_WEBSUB_URL=
MOSIP_TOPIC=
MOSIP_WEBSUB_SECRET=
# Data Source Configuration
# Options: 'direct' or 'datashare'
DATA_SOURCE_MODE=datashare
# Path to find the encrypted data in direct messages (dot notation)
DIRECT_MESSAGE_PATH=event.data.credential
# Path to find the registration ID in messages (dot notation)
REGISTRATION_ID_FIELD=event.data.registrationId
# Data Share Configuration
DATASHARE_BASE_URL=
# Decryption Configuration
P12_PATH=
P12_PASSWORD=
TEMP_ENCRYPTED_FILE=
TEMP_DECRYPTED_FILE=
# Auto Subscription
AUTO_SUBSCRIBE=falseConstructor Options
Constructor options take precedence over environment variables.
const options = {
// Server configuration
port: process.env.PORT || 3000,
// WebSub configuration
hubUrl: process.env.MOSIP_WEBSUB_URL,
topic: process.env.MOSIP_TOPIC,
callbackPath: process.env.CALLBACK_PATH,
secret: process.env.MOSIP_WEBSUB_SECRET,
// Data source configuration
dataSourceMode: process.env.DATA_SOURCE_MODE || 'datashare', // 'direct' or 'datashare'
directMessagePath: process.env.DIRECT_MESSAGE_PATH || 'event.data.credential',
registrationIdField: process.env.REGISTRATION_ID_FIELD || 'event.data.registrationId',
// Auth configuration
baseUrl: process.env.MOSIP_BASE_URL,
clientId: process.env.MOSIP_CLIENT_ID,
clientSecret: process.env.MOSIP_SECRET_KEY,
tokenUrl: process.env.MOSIP_TOKEN_URL,
authEndpoint: process.env.MOSIP_AUTH_ENDPOINT,
appId: process.env.MOSIP_APP_ID,
// Data share configuration
dataShareBaseUrl: process.env.DATASHARE_BASE_URL,
// Decryption configuration
p12Path: process.env.P12_PATH,
p12Password: process.env.P12_PASSWORD,
tempEncryptedFile: process.env.TEMP_ENCRYPTED_FILE,
tempDecryptedFile: process.env.TEMP_DECRYPTED_FILE
};
const subsafe = new SubSafe(options);API Reference
SubSafe
Constructor
const subsafe = new SubSafe(options);Methods
onNotification(handler)- Add an event handler for notification eventsstart(callbackUrl)- Start the server and set the callback URLsubscribe()- Subscribe to the WebSub hubunsubscribe()- Unsubscribe from the WebSub hub
Event Handler
The notification handler receives two parameters:
subsafe.onNotification((data, rawEvent) => {
// data: Decrypted and parsed data (if successful)
// rawEvent: Original webhook payload from MOSIP
});Data Source Modes
SubSafe supports two modes for receiving encrypted data:
1. DataShare Mode (Default)
In this mode, the notification contains a dataShareUri that points to an endpoint where the encrypted data can be retrieved. SubSafe fetches the data from this URI using the configured authentication.
Example notification in DataShare mode:
{
"event": {
"dataShareUri": "https://mosip.example.com/datashare/abc123",
"timestamp": "2023-01-01T12:00:00Z"
}
}2. Direct Mode
In this mode, the encrypted data is included directly within the notification payload. The path to the encrypted data in the notification body is configurable via the directMessagePath option.
Example notification in Direct mode:
{
"publisher": "CREDENTIAL_SERVICE",
"topic": "patner-opencrvs-i1/CREDENTIAL_ISSUED",
"publishedOn": "2025-03-28T11:33:26.355Z",
"event": {
"id": "1a61e56f-70c5-4369-9cb4-e602f0af328d",
"transactionId": "adb5f231-cb59-487a-8895-dac71d584927",
"type": {
"namespace": "mosip",
"name": "mosip"
},
"timestamp": "2025-03-28T11:33:26.355Z",
"dataShareUri": null,
"data": {
"registrationId": "10007100070013220250319095337",
"credential": "ENCRYPTED_DATA_STRING",
"credentialType": "euin"
}
}
}With the default directMessagePath of event.data.credential, SubSafe would extract and decrypt the value of ENCRYPTED_DATA_STRING in the above example.
Registration ID Tracking
SubSafe can extract and track registration IDs from incoming messages, allowing you to associate encrypted data with its source registration. The registration ID is extracted from the notification using a configurable field path and is included in the decrypted result.
To configure the registration ID field:
// Using constructor options
const subsafe = new SubSafe({
registrationIdField: 'event.data.registrationId'
});
// Or using environment variables in .env file
// REGISTRATION_ID_FIELD=event.data.registrationIdIn the notification handler, the registration ID will be included in the decrypted data:
subsafe.onNotification((data, rawEvent) => {
logger.info('Registration ID:', { registrationId: data.registrationId });
logger.debug('Decrypted data:', { data });
// Your business logic here
});The dashboard interface also displays the configured registration ID field path and the last received registration ID.
To configure the data source mode:
// Using constructor options
const subsafe = new SubSafe({
dataSourceMode: 'direct', // or 'datashare'
directMessagePath: 'event.data.credential'
});
// Or using environment variables in .env file
// DATA_SOURCE_MODE=direct
// DIRECT_MESSAGE_PATH=event.data.credentialRunning Standalone
SubSafe can also be run as a standalone application:
# Clone the repository
git clone https://github.com/abdulbathish/subsafe.git
cd subsafe
# Install dependencies
npm install
# Configure environment variables
cp .env.example .env
# Edit the .env file with your configuration
# Start the server
npm startTesting
SubSafe includes test scripts for different modes (subscription, datashare, direct). You can run these tests to verify the functionality:
# Run the default subscription test
npm test
# Run specific mode tests
npm run test:subscription # Test subscription mode
npm run test:datashare # Test datashare mode
npm run test:direct # Test direct credential mode
# Run tests with debug logging level
npm run test:debug # Subscription test with debug logging
npm run test:debug:datashare # Datashare test with debug logging
npm run test:debug:direct # Direct test with debug loggingTest Configuration
The tests use a separate .env.test file for configuration. Copy the .env.example file to .env.test and update the values:
cp .env.example .env.test
# Edit .env.test with your test configurationMake sure to set the appropriate log level for your tests:
# In .env.test
LOG_LEVEL=info # or debug, error, warn, etc.License
MIT
Logging
SubSafe includes a comprehensive logging system using Winston. Logs are captured at different levels and can be configured according to your needs.
Log Levels
SubSafe supports the following log levels (from highest to lowest priority):
- error: Error events that might still allow the application to continue running
- warn: Warning events that might indicate potential issues
- info: Informational messages highlighting the progress of the application (default)
- http: HTTP request-specific messages
- verbose: More detailed informational messages
- debug: Detailed debugging information
- silly: Extremely detailed debugging information
Configuration
You can configure the log level in two ways:
- Environment variable:
LOG_LEVEL=debug- Constructor option:
const subsafe = new SubSafe({
logLevel: 'debug'
});Log Output
Logs are written to:
- Console (with colors)
logs/combined.log- Contains all logs of the configured level and abovelogs/error.log- Contains only error-level logs
Additionally, logs are captured for display in the dashboard interface.
Usage in Your Code
When using SubSafe as a module in your application, you can access the logger:
const SubSafe = require('subsafe');
// The logger is directly available from the SubSafe package
const { logger } = SubSafe;
const subsafe = new SubSafe();
logger.info('Application started');
logger.debug('Detailed debugging information', {
additionalData: 'Any JavaScript object'
});
subsafe.onNotification((data) => {
logger.info('Notification received', { registrationId: data.registrationId });
// Process data...
});Examples
SubSafe includes several examples to help you get started:
Basic Usage Example
See the examples/basic-usage.js file for a simple example of how to use SubSafe in your project.
DataShare Mode Example
The examples/datashare-mode.js example demonstrates how to use SubSafe in DataShare mode, where:
- The notification contains a dataShareUri pointing to encrypted data
- SubSafe resolves the URI, downloads the encrypted data, and decrypts it
Run this example with:
node examples/datashare-mode.jsDirect Mode Example
The examples/direct-mode.js example demonstrates how to use SubSafe in Direct mode, where:
- The notification contains the encrypted data directly in the message body
- SubSafe extracts and decrypts the data
Run this example with:
node examples/direct-mode.js