JSPM

  • Created
  • Published
  • Downloads 11
  • Score
    100M100P100Q106553F
  • License UNLICENSED

RODiT-based authentication system for Express.js applications

Package Exports

  • @rodit/rodit-auth-be

Readme

RODiT ID Authentication System

Using RODiT Configuration in Modules

When working with multiple modules that require RODiT configuration, you can easily import and use the configuration in any module without reinitializing it. Here's how:

1. Import the Required Utilities

In any module where you need to access the RODiT configuration or utilities, import them from the SDK:

const { roditManager, stateManager, logger } = require('@rodit/rodit-auth-be');

2. Accessing Configuration

Once the SDK is initialized in your main application (typically in app.js), you can access the configuration from any module:

// In any module
const { stateManager } = require('@rodit/rodit-auth-be');

async function someFunction() {
  try {
    // Get the current RODiT configuration
    const config = await stateManager.getConfigOwnRodit();
    
    // Access configuration properties
    const { own_rodit } = config;
    const roditId = own_rodit.rodit_id;
    const metadata = own_rodit.metadata || {};
    
    // Use the configuration
    logger.info(`Using RODiT ID: ${roditId}`, { module: 'your-module-name' });
    
    return { roditId, metadata };
  } catch (error) {
    logger.error('Failed to access RODiT configuration', { 
      error: error.message,
      module: 'your-module-name' 
    });
    throw error;
  }
}

3. Using Logger in Modules

The logger is automatically configured in the main application and can be used in any module:

const { logger } = require('@rodit/rodit-auth-be');

function processData(data) {
  try {
    logger.debug('Processing data', { 
      dataLength: data.length,
      module: 'data-processor'
    });
    
    // Your processing logic here
    
    logger.info('Data processed successfully', {
      module: 'data-processor',
      timestamp: new Date().toISOString()
    });
  } catch (error) {
    logger.error('Failed to process data', {
      error: error.message,
      stack: error.stack,
      module: 'data-processor'
    });
    throw error;
  }
}

4. Best Practices

  1. Single Initialization: The RODiT SDK should only be initialized once in your main application file (e.g., app.js).

  2. Module Context: Always include a module field in your log messages to identify which module generated the log.

  3. Error Handling: Always wrap RODiT configuration access in try-catch blocks and include meaningful error messages.

  4. Configuration Access: Use stateManager.getConfigOwnRodit() to access the current configuration. This is safe to call multiple times as it returns the cached configuration after the first call.

  5. Environment Variables: Remember that environment variables take precedence over configuration files. Use the canonical format (dots replaced with underscores and uppercased) when setting environment variables.

5. Example Module

Here's a complete example of a module that uses the RODiT configuration:

// services/rodit-service.js
const { stateManager, logger } = require('@rodit/rodit-auth-be');

class RoditService {
  constructor() {
    this.moduleName = 'rodit-service';
  }

  async getRoditInfo() {
    try {
      const config = await stateManager.getConfigOwnRodit();
      const { own_rodit } = config;
      
      logger.info('Retrieved RODiT configuration', {
        module: this.moduleName,
        roditId: own_rodit.rodit_id,
        environment: process.env.NODE_ENV || 'development'
      });
      
      return {
        roditId: own_rodit.rodit_id,
        metadata: own_rodit.metadata || {},
        timestamp: new Date().toISOString()
      };
    } catch (error) {
      logger.error('Failed to get RODiT info', {
        module: this.moduleName,
        error: error.message,
        stack: error.stack
      });
      throw new Error(`RODiT service error: ${error.message}`);
    }
  }
}

module.exports = new RoditService();

By following these patterns, you can ensure consistent and reliable access to the RODiT configuration throughout your application while maintaining clean separation of concerns and proper error handling.

This npm package provides the RODiT-based authentication system for Express.js applications. It exports the exact same authentication functionality without adding unnecessary abstraction layers.

Endpoint Determination

The authentication middleware determines the login and logout endpoints based on the following rules:

Login Endpoint

  • The login endpoint is constructed by appending /api/login to the base apiEndpoint provided during configuration.
  • The base apiEndpoint can be configured in the following ways (in order of precedence):
    1. Directly passed in the configuration object when initializing the SDK
    2. Set via the API_ENDPOINT environment variable
    3. Falls back to a default value if not specified

Logout Endpoint

  • The logout endpoint is constructed by appending /api/logout to the base apiEndpoint.
  • It uses the same base URL as the login endpoint.

Example Configuration

const auth = require('@rodit/rodit-auth-be');

// Configure with custom endpoint
auth.configure({
  apiEndpoint: 'https://your-api.example.com',
  logger: require('./config/logger')
});

// Login endpoint will be: https://your-api.example.com/api/login
// Logout endpoint will be: https://your-api.example.com/api/logout

Features

  • Exclusive RODiT-based authentication with blockchain verification
  • JWT-based token management through HTTP headers (no cookies)
  • Permission validation for protected routes based on RODiT token permissions
  • Session management with secure token handling
  • Comprehensive logging and error handling with Loki support
  • Automatic RODIT token information display during startup
  • Programmatic access to RODIT token metadata for dynamic configuration

Logging Setup

The SDK uses Winston for logging and supports Loki transport for centralized logging. Here's how to set it up:

Basic Console Logging

By default, the SDK will log to the console. You can set the log level using the LOG_LEVEL environment variable:

export LOG_LEVEL=debug  # or info, warn, error

Loki Integration

To enable Loki logging, set these environment variables:

# Required
LOKI_URL=https://your-loki-instance:3100

# Optional
LOKI_BASIC_AUTH=username:password  # If Loki requires authentication
LOKI_TLS_SKIP_VERIFY=true          # Set to "true" to skip TLS verification (not recommended for production)
LOG_LEVEL=info                     # Default: info

Example Integration

const express = require('express');
const { logger } = require('@rodit/rodit-auth-be');

// Basic usage - logs to console
logger.info('Application starting...');

// With context
logger.info('User logged in', { 
  userId: '123',
  ip: '192.168.1.1' 
});

// Error logging
try {
  // Your code here
} catch (error) {
  logger.error('Operation failed', { 
    error: error.message,
    stack: error.stack 
  });
}

Log Levels

  • error: Error conditions that require immediate attention
  • warn: Warning conditions that might need attention
  • info: General operational information
  • debug: Detailed debug information
  • silly: Extremely detailed debugging

Environment Variables

Variable Description Default
LOG_LEVEL Minimum log level to output info
LOKI_URL Loki server URL -
LOKI_BASIC_AUTH Basic auth credentials for Loki -
LOKI_TLS_SKIP_VERIFY Skip TLS verification ("true" to enable) -
API_DEFAULT_OPTIONS_LOG_DIR Directory for log files /app/logs

Log Directory Permissions

When running in a containerized environment, ensure proper permissions are set for log directories:

  1. Directory Structure

    /app/logs/
    ├── application/  # Application logs
    └── nginx/        # Nginx access/error logs
  2. Required Permissions

    # Create log directories with correct ownership
    mkdir -p /app/logs/{application,nginx}
    
    # Set appropriate permissions (adjust user/group as needed)
    chown -R node:node /app/logs
    chmod -R 755 /app/logs
  3. Dockerfile Example

    # Create log directory and set permissions
    RUN mkdir -p /app/logs/{application,nginx} \
        && chown -R node:node /app/logs \
        && chmod -R 755 /app/logs
    
    # Ensure the application user has write access
    USER node
  4. Troubleshooting

    • If logs aren't being written, check:
      • Directory exists and is writable by the application user
      • No permission denied errors in container logs
      • Sufficient disk space is available

Installation

npm install @rodit/rodit-auth-be

Usage

Basic Setup

const express = require('express');
const app = express();
const auth = require('@rodit/rodit-auth-be');

// Configure authentication with default options
auth.configure({
  logger: require('./config/logger')
});

// Mount the authentication routes
app.post('/api/login', auth.login);
app.post('/api/logout', auth.authenticate_apicall, auth.logout);

// Protect routes with authentication
app.use('/api/protected', auth.authenticate_apicall, protectedRoutes);

// Add permission validation to routes that require specific permissions
app.use('/api/admin', auth.authenticate_apicall, auth.validatePermissions, adminRoutes);

// Example of a protected route with permission validation
app.get('/api/entities/:entityId', auth.authenticate_apicall, auth.validatePermissions, (req, res) => {
  // This route is protected and requires specific permissions from the RODiT token
  // The validatePermissions middleware will check if the user has permission to access this entity
  const entityId = req.params.entityId;
  // Process the request...
});

app.listen(3000, () => {
  console.log('Server started on port 3000');
});

Custom Configuration

const auth = require('@rodit/rodit-auth-be');

// Configure authentication with custom options
auth.configure({
  // Use a custom logger
  logger: myCustomLogger,
  
  // Set token expiry time to 1 hour (in seconds)
  tokenExpiry: 3600,
  
  // Customize header names
  headerName: 'X-Auth-Token',
  tokenPrefix: '',
  newTokenHeader: 'X-New-Token',
  
  // Customize user property name on request object
  userProperty: 'currentUser'
});

API Reference

Core Middleware Functions

  • authenticate_apicall(req, res, next) - Middleware to authenticate_apicall API calls
  • validatePermissions(req, res, next) - Middleware to validate permissions for protected routes

Authentication Handlers

  • login(req, res) - Handle client login
  • logout(req, res) - Handle client logout
  • loginWithNEP413(req, res) - Handle client login with NEP-413 standard

Token Functions

  • validateToken(token) - Validate a JWT token
  • generateToken(payload, options) - Generate a JWT token

Services

  • sessionManager - Session management service
  • blockchainService - Blockchain interaction service
  • stateManager - State management service

RODIT Token Information Access

The SDK provides access to RODIT token information that can be used for dynamic API configuration:

const { stateManager } = require('@rodit/rodit-auth-be');

// Get complete RODIT configuration during or after initialization
const roditClient = await stateManager.getConfigOwnRodit();
const roditToken = roditClient.own_rodit;

// Access token metadata for configuration
const metadata = roditToken.metadata;
const tokenId = roditToken.token_id;

// Example: Configure API behavior based on token metadata
const allowedRoutes = JSON.parse(metadata.permissioned_routes);
const jwtDuration = parseInt(metadata.jwt_duration);
const allowedCIDR = metadata.allowed_cidr;
const apiEndpoint = metadata.subjectuniqueidentifier_url;

// Example: Dynamic rate limiting from token
if (metadata.max_requests && metadata.maxrq_window) {
  const maxRequests = parseInt(metadata.max_requests);
  const windowSeconds = parseInt(metadata.maxrq_window);
  // Apply rate limiting configuration
}

Available RODIT Metadata Fields

  • token_id: Unique RODIT token identifier
  • allowed_cidr: Permitted IP address ranges (CIDR format)
  • allowed_iso3166list: Geographic restrictions (JSON string)
  • jwt_duration: JWT token lifetime in seconds
  • max_requests: Rate limit - maximum requests per window
  • maxrq_window: Rate limit - time window in seconds
  • not_before/not_after: Token validity period
  • openapijson_url: OpenAPI specification URL
  • permissioned_routes: Allowed API routes and methods (JSON string)
  • serviceprovider_id: Blockchain contract and service provider info
  • serviceprovider_signature: Cryptographic signature for verification
  • subjectuniqueidentifier_url: Primary API service endpoint
  • userselected_dn: Distinguished name for the service
  • webhook_cidr: Allowed IP ranges for webhooks
  • webhook_url: Webhook endpoint URL

Startup Information Display

When using this SDK in your application, it automatically displays comprehensive RODIT token information during startup, similar to the roditwallet.sh command:

=== RODiT Authentication System ===
Version 1.0.26 running on testnet at Smart Contract 20251001-rodit-org.testnet
Get help with: npm run help

RODiT Contents
▹▸▹▹▹ Authentication token information loaded...

{
  "token_id": "01K43ASJTMA4V81C46WCRADGVD",
  "metadata": {
    // ... complete token metadata
  }
}

This information helps verify proper token configuration and provides visibility into the authentication system's capabilities.

Configuration

  • configure(config) - Configure the authentication system

Configuration Options

  • logger - Logger instance (optional, will use default if not provided)
  • tokenExpiry - JWT token expiry time in seconds (default: 3600)
  • headerName - Name of the header to use for the token (default: 'Authorization')
  • tokenPrefix - Prefix to use for the token in the header (default: 'Bearer ')
  • newTokenHeader - Name of the header to use for the new token (default: 'New-Token')
  • userProperty - Name of the property to attach the user object to on the request (default: 'user')

RODiT ID Authentication

RODiT-based authentication uses the RODiT system to authenticate_apicall users. It requires the following parameters in the login request:

  • roditid - RODiT ID
  • timestamp - Timestamp (optional, defaults to current time)
  • roditid_base64url_signature - Signature

The system also supports NEP-413 standard authentication with the loginWithNEP413 function, which requires:

  • message - Message to sign
  • nonce - Nonce value
  • recipient - Recipient identifier
  • callbackUrl - Callback URL
  • signature - Signature

Permission System

The permission system allows you to control access to protected routes based on the user's permissions. The permissions are stored in the JWT token and are checked by the validatePermissions middleware.

RODiT-based Permissions

RODiT-based permissions use the permissioned_routes property in the JWT token. This property contains an array of objects with the following structure:

[
  {
    "route": "^/api/admin/.*$",
    "methods": ["get", "post", "put", "delete"]
  },
  {
    "route": "^/api/users/.*$",
    "methods": ["get"]
  }
]

The permission validator checks if the requested route matches any of the patterns in the permissioned_routes array and if the HTTP method is allowed for that route.

Error Handling

All methods in this module handle errors gracefully and return appropriate HTTP status codes and error messages. Errors are also logged using the provided logger.

Logging

This module uses the provided logger to log important events. If no logger is provided, it uses the default logger from the SDK.

Security Considerations

  • Tokens are transmitted via HTTP headers only, not cookies
  • Tokens are validated on every request
  • Permissions are checked on protected routes
  • Session invalidation is supported via logout
  • Token expiry is configurable

Backward Compatibility

For backward compatibility, the package also exports the original function names:

  • authenticate_apicall - Original name for authenticate_apicall
  • login_client - Original name for login
  • logout_client - Original name for logout
  • login_client_withnep413 - Original name for loginWithNEP413

License

Copyright (c) 2025 Discernible Inc. All rights reserved.

Full SDK API Reference (Consolidated)

This section consolidates the full SDK reference originally maintained in sdk/API-REFERENCE.md. Going forward, this README is the single source of truth.

Last Updated: Jun 29, 2025 — Streamlined logging section added and project structure updated.

Table of Contents

RoditClient

The main client class for interacting with RODiT services.

Constructor

const client = new RoditClient(options);

Parameters:

  • options (Object, optional): Configuration options
    • credentialsFilePath (string, optional): Path to credentials file

Methods

init(options)

Initialize the client with credentials.

await client.init(options);

Parameters:

  • options (Object, optional): Initialization options
    • credentialsPath (string, optional): Path to credentials file
    • token (string, optional): RODiT token
    • apiEndpoint (string, optional): API endpoint

Returns: Promise

login(options)

Authenticate and obtain a session token.

const loginResult = await client.login(options);

Parameters:

  • options (Object, optional): Login options
    • roditId (string, optional): RODiT ID to use for login

Returns: Promise — Login result with token and expiration information

logout()

Invalidate the current session token.

await client.logout();

Returns: Promise — True if logout was successful

request(method, path, data, options)

Make an authenticated API request.

const response = await client.request(method, path, data, options);

Parameters:

  • method (string): HTTP method (POST, PUT, DELETE, etc.)
  • path (string): API path
  • data (any, optional): Request body data
  • options (Object, optional): Request options
    • headers (Object, optional): Additional headers
    • timeout (number, optional): Request timeout in milliseconds

Returns: Promise — API response

isTokenValid()

Check if the token is valid.

const isValid = client.isTokenValid();

Returns: boolean — True if token is valid

isSubscriptionActive()

Check if the subscription is active.

const isActive = client.isSubscriptionActive();

Returns: boolean — True if subscription is active

getRoditMetadata()

Get the token metadata.

const metadata = client.getRoditMetadata();

Returns: Object — Token metadata

isOperationPermitted(method, path)

Check if an operation is permitted.

const isPermitted = client.isOperationPermitted(method, path);

Parameters:

  • method (string): HTTP method
  • path (string): API path

Returns: boolean — True if operation is permitted

refreshToken()

Refresh the session token.

await client.refreshToken();

Returns: Promise — Refresh result

getAvailableEndpoints()

Get available API endpoints from OpenAPI spec and include the RODiT configured endpoint (subjectuniqueidentifier_url).

const endpoints = await client.getAvailableEndpoints();

Returns: Promise — Available endpoints including the RODiT configured endpoint


Authentication

Authentication-related classes and functions.

authentication.js

Core authentication functionality.

verifyToken(token, options)

Verify a JWT token.

const payload = authentication.verifyToken(token, options);

Parameters:

  • token (string): JWT token
  • options (Object, optional): Verification options

Returns: Object — Token payload if valid

tokenservice.js

Token management service.

createToken(subject, claims)

Create a new token.

const token = tokenService.createToken(subject, claims);

Parameters:

  • subject (string): Token subject
  • claims (Object): Token claims

Returns: string — JWT token

validateToken(token)

Validate a token.

const isValid = tokenService.validateToken(token);

Parameters:

  • token (string): JWT token

Returns: boolean — True if token is valid


Session Management

Session management functionality.

sessionmanager.js

Session management service.

createSession(userId, metadata)

Create a new session.

const session = sessionManager.createSession(userId, metadata);

Parameters:

  • userId (string): User ID
  • metadata (Object, optional): Session metadata

Returns: Object — Session information

validateSession(sessionId, token)

Validate a session.

const isValid = sessionManager.validateSession(sessionId, token);

Parameters:

  • sessionId (string): Session ID
  • token (string): Session token

Returns: boolean — True if session is valid

terminateSession(sessionId)

Terminate a session.

sessionManager.terminateSession(sessionId);

Parameters:

  • sessionId (string): Session ID

Returns: boolean — True if session was terminated

removeExpiredSessions()

Remove expired sessions.

const count = sessionManager.removeExpiredSessions();

Returns: number — Number of sessions removed

sessioncleanup.js

Session cleanup utilities.

startAutomaticCleanup(options)

Start automatic session cleanup.

sessionCleanup.startCleanupJob(options);

Parameters:

  • options (Object, optional): Cleanup options
    • interval (number, optional): Cleanup interval in milliseconds

Returns: void

stopAutomaticCleanup()

Stop automatic session cleanup.

sessionCleanup.stopCleanupJob();

Returns: void

cleanupExpiredSessions()

Clean up expired sessions.

const result = await sessionCleanup.runManualCleanup();

Returns: Promise — Cleanup results

Environment variables and configuration mapping

This project follows a canonical environment variable naming scheme for config keys:

  • Replace dots with underscores and uppercase everything.
  • Example: API_DEFAULT_OPTIONS.LOG_DIRAPI_DEFAULT_OPTIONS_LOG_DIR.

Key mappings:

  • API_DEFAULT_OPTIONS.LOG_DIRAPI_DEFAULT_OPTIONS_LOG_DIR
  • API_DEFAULT_OPTIONS.DB_PATHAPI_DEFAULT_OPTIONS_DB_PATH

Not mapped:

  • RODIT_NEAR_CREDENTIALS_SOURCE
  • VAULT_ENDPOINT
  • VAULT_RODIT_KEYVALUE_PATH
  • VAULT_ROLE_ID (secret)
  • VAULT_SECRET_ID (secret)
  • VAULT_TOKEN_TTL
  • SERVICE_NAME
  • NEAR_RPC_URL
  • NEAR_CONTRACT_ID (SDK testing default: rodit-org.near; override in production)

Critical Deployment Requirements

When deploying applications using this SDK, ensure proper environment variable configuration:

Required for vault-based credentials:

  • RODIT_NEAR_CREDENTIALS_SOURCE=vault must be set to use vault instead of file-based credentials
  • All vault-related environment variables must be provided as secrets
  • Missing environment variables will cause authentication failures

Container deployment example:

podman run -d \
  -e RODIT_NEAR_CREDENTIALS_SOURCE=vault \
  -e VAULT_ENDPOINT=$VAULT_ENDPOINT \
  -e VAULT_ROLE_ID=$VAULT_ROLE_ID \
  -e VAULT_SECRET_ID=$VAULT_SECRET_ID \
  -e VAULT_RODIT_KEYVALUE_PATH=$VAULT_RODIT_KEYVALUE_PATH \
  -e SERVICE_NAME=your-service-name \
  -e NEAR_CONTRACT_ID=$NEAR_CONTRACT_ID \
  your-api-image

Common deployment issues:

  • Missing RODIT_NEAR_CREDENTIALS_SOURCE environment variable causes fallback to file-based credentials
  • Vault credentials not passed to container runtime cause authentication errors
  • Missing API_DEFAULT_OPTIONS.DB_PATH configuration causes startup failures

Testing default for NEAR_CONTRACT_ID

For developer convenience, the host app may define a testing default for NEAR_CONTRACT_ID in its config files. At present, the testing default is:

{
  "NEAR_CONTRACT_ID": "rodit-org.near"
}

This is intended for local/testing use only. In production, you must explicitly set NEAR_CONTRACT_ID via your deployment environment (see options below). Do not rely on the SDK default in production.

Logging-related environment variables used by src/app.js when injecting winston-loki:

  • LOKI_URL – Loki push URL
  • LOKI_BASIC_AUTH – optional basic auth (secret)
  • LOKI_TLS_SKIP_VERIFYtrue to skip TLS verification (testing only)
  • LOG_LEVELdebug, info, warn, error (default: info)

Fallback behavior

The SDK wrapper at sdk/services/config.js provides safe fallbacks for several keys so the SDK can run in development without external configuration:

  • Has fallbacks: RODIT_NEAR_CREDENTIALS_SOURCE, SERVICE_NAME, NEAR_RPC_URL, NEAR_CONTRACT_ID (testing default rodit-org.near), API_DEFAULT_OPTIONS.*, SECURITY_OPTIONS, and others noted in FALLBACK_DEFAULTS.
  • Recommendation: Override NEAR_CONTRACT_ID in production via environment or configuration.
  • Exclusions by design: Vault credentials (VAULT_*) and METHOD_PERMISSION_MAP are not supplied by SDK fallbacks.

METHOD_PERMISSION_MAP is read by sdk/lib/middleware/validatepermissions.js via config.get('METHOD_PERMISSION_MAP') and is used to decide which permission scopes (e.g., entityAndProperties, propertiesOnly, entityOnly) are allowed for each endpoint operation in a decoded JWT token. Provide this mapping via your configuration files or environment (see below).

Supplying configuration

You have three ergonomic options for providing configuration to the app/SDK:

  1. Config files (default behavior)

    • Place values in config/default.json, config/production.json, etc.
    • Best for local development or when committing non-secret defaults.
  2. Environment variables (recommended for CI/CD)

    • Set environment variables using the exact names listed above in “Environment variables and configuration mapping”. No mapping file is used.

    • Examples:

      export RODIT_NEAR_CREDENTIALS_SOURCE=vault
      export SERVICE_NAME=signportal-api
      export API_DEFAULT_OPTIONS_LOG_DIR=/app/logs
      export NEAR_RPC_URL=https://rpc.testnet.fastnear.com/
      export NEAR_CONTRACT_ID=your-contract.testnet
  3. NODE_CONFIG environment variable (for complex or nested values)

    • Set NODE_CONFIG to a JSON string that contains your configuration (including nested objects like METHOD_PERMISSION_MAP).

    • Example:

      export NODE_CONFIG='{
        "NEAR_CONTRACT_ID": "your-contract.testnet",
        "METHOD_PERMISSION_MAP": { "list_agents": ["entityAndProperties", "entityOnly"] }
      }'

Notes:

  • Secrets such as VAULT_ROLE_ID, VAULT_SECRET_ID, and LOKI_BASIC_AUTH should be stored in your CI/CD secret store and injected at runtime.
  • Although NEAR_CONTRACT_ID has a testing default in sdk/services/config.js, you should always provide it explicitly via one of the options above in production deployments.

Token Management

Token management functionality.

roditmanager.js

RODiT token management.

getInstance()

Get the RODiT manager singleton instance.

const roditManager = roditManager.getInstance();

Returns: Object — RODiT manager instance

getRoditId()

Get the current RODiT ID.

const roditId = roditManager.getRoditId();

Returns: string — RODiT ID

getRoditMetadata()

Get the token metadata.

const metadata = roditManager.getRoditMetadata();

Returns: Object — Token metadata


Configuration

Configuration management functionality.

Configuration System (Consolidated)

The SDK provides a configuration wrapper that behaves like the config package while offering safe defaults and a security-first fallback strategy. This allows applications to run with minimal setup in development, while ensuring production deployments explicitly provide sensitive settings.

Architecture
  • Wrapper module: sdk/services/config.js
  • Responsibilities:
    • Load the host application's config package if present
    • Provide fallback defaults for many non-sensitive keys
    • Exclude sensitive keys from defaults so they must be provided by the host
    • Preserve the standard config API (get, has), including nested keys
Source Priority (highest to lowest)
  1. Host App Config (config package, env, NODE_CONFIG)
  2. SDK Fallback Defaults (sdk/config/default.json baked-in)
  3. Error (for missing required keys without fallbacks)
Fallback Configuration Keys (examples)
  • SECURITY_OPTIONS
    • LAPSED_LIFETIME_PROPORTION_4RENEWAL_ELIGIBILITY: "0.99"
    • THRESHOLD_VALIDATION_TYPE: "0.10"
    • DURATIONRAMP: "0.7"
    • SERVERORCLIENT: "SERVER-INITIATED"
    • SILENT_LOGIN_FAILURES: false
  • API_DEFAULT_OPTIONS
    • ISO639: "es"
    • ISO3166: "ES"
    • ISO15924: "215"
    • TIMESTAMP_MAX_AGE: 300
    • TIMEOPTIONS: { tzname: "Europe/Madrid", tzoffset: "+01:00", datetimeformat: "2023-04-15T14:30:00-05:00" }
    • LOG_DIR: "./logs"
  • PERFORMANCE
    • LOAD_LEVELS: { LOW: 'low', MEDIUM: 'medium', HIGH: 'high', CRITICAL: 'critical' }
    • LOAD_THRESHOLDS: { MEDIUM: 500, HIGH: 1000, CRITICAL: 2000 }
  • Service-level
    • SERVERPORT: 3000
    • NEAR_RPC_URL: "https://rpc.testnet.near.org"
    • NEAR_CONTRACT_ID: "dev-1234567890123-1234567890123"
    • SERVICE_NAME: "signportal-sdk"
Excluded Keys (no fallbacks; must be provided by host)
  • Vault: VAULT_ENDPOINT, VAULT_ROLE_ID, VAULT_SECRET_ID, VAULT_RODIT_KEYVALUE_PATH, and any VAULT_*
  • Permissions: METHOD_PERMISSION_MAP

These are excluded for security and correctness to avoid accidental defaults for secrets or authorization policies.

Usage Examples
const config = require('./services/config');

// Get with fallback
const port = config.get('SERVERPORT'); // 3000 if not configured

// Check and get
if (config.has('CUSTOM_SETTING')) {
  const value = config.get('CUSTOM_SETTING');
}

// Nested values
const logDir = config.get('API_DEFAULT_OPTIONS.LOG_DIR');

// Performance monitoring configuration
const loadLevels = config.get('PERFORMANCE.LOAD_LEVELS');
const loadThresholds = config.get('PERFORMANCE.LOAD_THRESHOLDS');

// Override performance thresholds for high-traffic environments
const highTrafficThresholds = config.get('PERFORMANCE.LOAD_THRESHOLDS', {
  MEDIUM: 1000,  // Custom threshold for medium load
  HIGH: 2500,    // Custom threshold for high load  
  CRITICAL: 5000 // Custom threshold for critical load
});

// Provide a runtime default
const timeout = config.get('REQUEST_TIMEOUT', 30000);

To get a merged view of all configuration:

const all = config.getAllMerged();
Host Application Integration
  • Minimal production config should include sensitive and environment-specific values: Vault credentials and METHOD_PERMISSION_MAP.

Example:

{
  "VAULT_ENDPOINT": "https://vault.example.com",
  "VAULT_ROLE_ID": "...",
  "VAULT_SECRET_ID": "...",
  "METHOD_PERMISSION_MAP": {
    "GET:/api/public": ["anonymous"],
    "POST:/api/secure": ["authenticated"]
  }
}

Override any fallback defaults as needed:

{
  "SERVERPORT": 8080,
  "SERVICE_NAME": "my-custom-service",
  "SECURITY_OPTIONS": { "SILENT_LOGIN_FAILURES": true },
  "PERFORMANCE": {
    "LOAD_LEVELS": {
      "LOW": "low",
      "MEDIUM": "medium", 
      "HIGH": "high",
      "CRITICAL": "critical"
    },
    "LOAD_THRESHOLDS": {
      "MEDIUM": 1000,
      "HIGH": 2500,
      "CRITICAL": 5000
    }
  }
}
Environment variables and canonical mapping

Use canonical env var names by uppercasing and replacing dots with underscores:

  • API_DEFAULT_OPTIONS.LOG_DIRAPI_DEFAULT_OPTIONS_LOG_DIR

Common variables used by the SDK and host app include:

  • RODIT_NEAR_CREDENTIALS_SOURCE
  • SERVICE_NAME
  • API_DEFAULT_OPTIONS_LOG_DIR
  • NEAR_RPC_URL
  • NEAR_CONTRACT_ID (testing default exists; override in production)
  • SECURITY_OPTIONS_SILENT_LOGIN_FAILURES (controls whether login failures are logged silently)
  • Performance: PERFORMANCE_LOAD_LEVELS_MEDIUM, PERFORMANCE_LOAD_THRESHOLDS_HIGH, etc.
  • Vault: VAULT_ENDPOINT, VAULT_ROLE_ID, VAULT_SECRET_ID, VAULT_RODIT_KEYVALUE_PATH

You can also set complex values via NODE_CONFIG (JSON string).

Migration Guide

Replace direct config imports with the SDK wrapper to benefit from fallbacks:

// Before
const config = require('config');

// After
const config = require('./services/config');

The wrapper preserves config.get(key), config.has(key), and default values via config.get(key, defaultValue).

Error Handling

Accessing excluded keys without providing them will throw. Use explicit defaults where appropriate:

try {
  const vaultEndpoint = config.get('VAULT_ENDPOINT');
} catch (err) {
  console.error('Vault endpoint not configured:', err.message);
}

const serverPort = config.get('SERVERPORT');            // has fallback
const customTimeout = config.get('CUSTOM_TIMEOUT', 5000); // explicit default
Best Practices
  • Provide sensitive keys via env/secret stores; never hardcode
  • Override defaults per environment (development, staging, production)
  • Validate critical settings at startup
  • Document any custom keys your app introduces
Troubleshooting
  • Ensure the host app has a config package installed
  • Confirm you import sdk/services/config (not config directly) when using the wrapper from within the SDK
  • Verify key names and canonical env var mapping
  • Ensure Vault credentials and permission map are provided (no fallbacks)
Credentials Store Selection

The SDK supports file-based and Vault-backed credential stores, selected at runtime (env var takes precedence over config):

  • Default: RODIT_NEAR_CREDENTIALS_SOURCE = "file"
  • Env override: RODIT_NEAR_CREDENTIALS_SOURCE=vault or file

Config keys:

{
  "RODIT_NEAR_CREDENTIALS_SOURCE": "file",
  "credentials": { "filePath": "/app/.near-credentials/testnet/your-credentials.json" }
}

For vault:

{
  "RODIT_NEAR_CREDENTIALS_SOURCE": "vault",
  "VAULT_ENDPOINT": "https://vault.example.com:8200",
  "VAULT_ROLE_ID": "...",
  "VAULT_SECRET_ID": "...",
  "VAULT_RODIT_KEYVALUE_PATH": "signing-keys"
}

statemanager.js

State management service.

getInstance()

Get the state manager singleton instance.

const stateManager = stateManager.getInstance();

Returns: Object — State manager instance

getConfig(key)

Get a configuration value.

const value = stateManager.getConfig(key);

Parameters:

  • key (string): Configuration key

Returns: any — Configuration value

setConfig(key, value)

Set a configuration value.

stateManager.setConfig(key, value);

Parameters:

  • key (string): Configuration key
  • value (any): Configuration value

Returns: void


Utility Functions

Utility functions for common tasks.

utils.js

General utility functions.

isValidIpRange(cidr)

Validate an IP range in CIDR format.

const isValid = utils.isValidIpRange(cidr);

Parameters:

  • cidr (string): CIDR notation IP range

Returns: boolean — True if valid

isClientIpAuthorized(ip, cidr)

Check if a client IP is authorized.

const isAuthorized = utils.isClientIpAuthorized(ip, cidr);

Parameters:

  • ip (string): Client IP address
  • cidr (string): CIDR notation IP range

Returns: boolean — True if authorized

parseMetadataJson(jsonString, defaultValue)

Parse a JSON string from RODiT token metadata. RODiT tokens contain specific metadata fields that may be JSON-encoded strings, such as permissioned_routes which contains the allowed API routes and methods.

// Example: Parse permissioned routes from token metadata
const metadata = client.getRoditMetadata();
const permissionedRoutes = utils.parseMetadataJson(metadata.permissioned_routes, {});

// RODiT permissioned_routes structure example:
// {
//   "/api/health": ["POST"],
//   "/api/data": ["POST"],
//   "/api/users": ["POST"]
// }

Parameters:

  • jsonString (string): JSON string from RODiT token metadata
  • defaultValue (any): Default value if parsing fails

Returns: Object — Parsed RODiT metadata structure or default value


Middleware

Middleware for Express applications.

authenticationmw.js

Authentication middleware.

authenticate_apicall(options)

Middleware to authenticate_apicall requests.

app.use(authenticationmw.authenticate_apicall(options));

Parameters:

  • options (Object, optional): Authentication options
    • required (boolean, optional): Whether authentication is required

Returns: Function — Express middleware

filestore-credentials.js

File-based credential storage.

getCredentials(filePath)

Load credentials from a file.

let credentials = filestoreCredentials.getCredentials(filePath);

Parameters:

  • filePath (string): Path to credentials file

Returns: Object — Credentials


Logging

All SDK logs are emitted to stdout in structured JSON. There are no file transports in the SDK.

  • Output: JSON to stdout (suitable for Docker/K8s/systemd and log shippers like Promtail/Fluent Bit).
  • Verbosity: Controlled by LOG_LEVEL env var (debug, info, warn, error). Defaults to debug.
  • Helpers: Context helpers (createLogContext, infoWithContext, etc.) and lightweight metrics via logger.metric(name, value, labels).
  • Injection: You can inject your own logger (Winston/Pino/Bunyan/etc.) using setLogger(customLogger). Your logger must implement { error, warn, info, debug, log }.

Defaults (no injection)

const logger = require('../services/logger');
logger.infoWithContext('Client initialised', logger.createLogContext('RoditClient', 'init'));

Inject your own logger

You can provide a preconfigured logger that ships logs to your sinks. The SDK will use it and still provide the helper methods.

const logger = require('../services/logger');

// Example: inject a Pino instance
const pino = require('pino');
const pinoLogger = pino({ level: process.env.LOG_LEVEL || 'info' });
logger.setLogger(pinoLogger);

// or: inject a Winston instance with your transports (Datadog/Splunk/CloudWatch)
// const winston = require('winston');
// const custom = winston.createLogger({
//   level: 'info',
//   transports: [new winston.transports.Http({ /* ... */ })]
// });
// logger.setLogger(custom);

logger.info('Using injected logger');
logger.infoWithContext('Event', logger.createLogContext('RoditClient', 'init'));

Notes:

  • There is no LOG_DIR or file output in the SDK. If you need files, configure that in your injected logger.
  • The helper APIs remain the same whether you use the default or an injected logger.

Logging Guide

The SDK is cloud-native by default and emits structured JSON to stdout/stderr. This works out-of-the-box on all platforms (Docker, Kubernetes, ECS, Cloud Run, Heroku, systemd), where platform agents collect container logs.

If you need to route SDK logs to a specific backend (Grafana Loki, Datadog, CloudWatch, etc.), inject your own logger using logger.setLogger(customLogger).

Use winston with the winston-loki transport in your host application and inject it into the SDK.

// app bootstrap
const winston = require('winston');
const LokiTransport = require('winston-loki');
const { logger } = require('your-sdk');

const custom = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.Console({ format: winston.format.json() }),
    new LokiTransport({
      host: process.env.LOKI_URL,               // e.g. https://loki.example.com
      basicAuth: process.env.LOKI_BASIC_AUTH,   // optional: "user:password"
      ssl: process.env.LOKI_TLS_SKIP_VERIFY === 'true' ? { rejectUnauthorized: false } : undefined,
      labels: { app: 'my-app', component: 'sdk' },
      json: true,
      batching: true,
      gracefulShutdown: true,
      timeout: 5000,
    })
  ]
});

logger.setLogger(custom);

Environment variables (canonical style):

  • LOKI_URL — Loki push URL (e.g., https://loki.example.com)
  • LOKI_BASIC_AUTH — optional basic auth in the form user:password
  • LOKI_TLS_SKIP_VERIFY — set to true to skip certificate verification (testing only)
  • LOG_LEVEL — log verbosity (debug, info, warn, error), default info
Option 2: File + Agent (Promtail/Fluent Bit)

If you prefer using a log agent, write SDK logs to a file and have the agent ship them.

const fs = require('fs');
const path = require('path');
const winston = require('winston');
const { logger } = require('your-sdk');

const logDir = process.env.API_DEFAULT_OPTIONS_LOG_DIR || '/app/logs';
fs.mkdirSync(logDir, { recursive: true });

const custom = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.Console({ format: winston.format.json() }),
    new winston.transports.File({ filename: path.join(logDir, 'sdk.log'), maxsize: 10 * 1024 * 1024, maxFiles: 3 })
  ]
});

logger.setLogger(custom);

Then configure Promtail/Fluent Bit to scrape /app/logs/*.log and push to your backend. This approach is provider-agnostic and works with CloudWatch, Stackdriver, etc.


Error Handling

The SDK implements internal error handling for various operations. As a user of the SDK, you should handle errors that may be thrown from SDK methods.

SDK Error Handling

The SDK internally handles various error types:

  • Authentication errors (token validation, login failures)
  • Network errors (connection issues, timeouts)
  • Configuration errors (missing required values)
  • Permission errors (unauthorized operations)

Security Features

Timestamp Validation

The SDK implements timestamp validation to prevent replay attacks. Authentication requests include a timestamp that must be within a configurable time window (default: 5 minutes) of the server's current time.

// Example of timestamp validation in authentication requests
const timestamp = Math.floor(Date.now() / 1000); // Current Unix timestamp in seconds
const authData = {
  roditid: "your-rodit-id",
  timestamp: timestamp,
  signature: signData(`${roditid}${timestamp}`)
};

JWT-Based Authentication

The SDK uses modern JWT (JSON Web Token) for authentication, providing a stateless, secure method for transmitting information between parties.

// Example of JWT token verification
const tokenData = decodeJwt(token);
const isValid = await verifyJwtSignature(token);

Webhook Security

Webhooks are secured using cryptographic signatures to ensure the authenticity of webhook deliveries.

// Example of webhook signature verification
const isValid = await authenticate_webhook(
  payload,
  signature_hex_ofpayload,
  timestamp,
  server_public_key_base64url
);

Project Structure

The RODiT SDK is organized into the following directory structure:

sdk/
├── lib/                  # Core library files
│   ├── auth/             # Authentication modules
│   │   ├── authentication.js
│   │   ├── roditmanager.js
│   │   ├── sessionmanager.js
│   │   ├── tokenservice.js
│   │   └── sessioncleanup.js
│   ├── blockchain/       # Blockchain interaction
│   │   ├── blockchainservice.js
│   │   └── statemanager.js
│   └── middleware/       # Express middleware
│       ├── authenticationmw.js
│       ├── loggingmw.js
│       ├── performancemw.js
│       ├── ratelimit.js
│       └── validatepermissions.js
├── utils.js              # Utility functions
├── README.md             # SDK documentation
└── API-REFERENCE.md      # API reference

Module Dependencies

The SDK is designed to minimize circular dependencies by using direct function imports and careful module organization.

// Example of importing specific functions to avoid circular dependencies
const { login_server } = require("../middleware/authenticationmw");

Client-Side Error Handling

When using the SDK, implement error handling for SDK method calls:

try {
  // Initialize the client
  const client = await createClient({
    credentialsPath: './credentials.json'
  });
  
  // Make an API request
  const response = await client.request('POST', '/api/endpoint');
  console.log('Response:', response);
} catch (error) {
  console.error('Error:', error.message);
  
  // Check for specific error messages if needed
  if (error.message.includes('authentication')) {
    console.error('Authentication error - please check your credentials');
  } else if (error.message.includes('network')) {
    console.error('Network error - please check your connection');
  }
}

The SDK handles token refresh and other internal operations automatically.