npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@rytass/secret-adapter-vault

v0.4.4

Published

Rytass Secret Vault adapter

Readme

Rytass Utils - HashiCorp Vault Secret Adapter

Comprehensive secret management adapter for HashiCorp Vault, providing secure storage and retrieval of sensitive configuration data. Features both online and offline modes with automatic token management, caching mechanisms, and support for Key-Value V2 secret engines.

Features

Authentication

  • [x] Username/Password authentication (userpass)
  • [x] Token-based authentication
  • [x] Automatic token renewal
  • [x] Configurable token TTL

Secret Engines

  • [x] Key-Value V2 engine support
  • [x] Online mode (real-time operations)
  • [x] Offline mode (cached operations)
  • [x] Version control and conflict resolution
  • [x] Batch synchronization

Advanced Features

  • [x] Automatic caching for offline operations
  • [x] Event-driven architecture
  • [x] Error handling and retry mechanisms
  • [x] TypeScript type safety
  • [x] Connection health monitoring

Installation

npm install @rytass/secret-adapter-vault
# or
yarn add @rytass/secret-adapter-vault

Basic Usage

Online Mode (Real-time Operations)

import { VaultSecret } from '@rytass/secret-adapter-vault';

// Online mode - all operations hit Vault directly
const onlineVault = new VaultSecret('apps/myapp/config', {
  host: 'https://vault.company.com',
  online: true, // Enable online mode
  auth: {
    account: 'myapp-service',
    password: 'secure-password',
  },
});

// Wait for connection to be ready
await new Promise(resolve => {
  onlineVault.onReady(() => resolve(undefined));
});

// Get secret (async operation)
const dbPassword = await onlineVault.get<string>('DATABASE_PASSWORD');
console.log('DB Password:', dbPassword);

// Set new secret (async operation)
await onlineVault.set<string>('API_KEY', 'new-api-key-value');

// Delete secret (async operation)
await onlineVault.delete('OLD_CONFIG_KEY');

Offline Mode (Cached Operations)

import { VaultSecret } from '@rytass/secret-adapter-vault';

// Offline mode - operations use local cache
const offlineVault = new VaultSecret('apps/myapp/config', {
  host: 'https://vault.company.com',
  online: false, // Default offline mode
  auth: {
    account: 'myapp-service',
    password: 'secure-password',
  },
  onReady: async () => {
    console.log('Vault cache ready for offline operations');

    // All operations are synchronous and use cache
    const dbHost = offlineVault.get<string>('DATABASE_HOST');
    const dbPort = offlineVault.get<number>('DATABASE_PORT');

    // Modify cached values
    offlineVault.set<string>('NEW_FEATURE_FLAG', 'enabled');

    // Delete from cache
    offlineVault.delete('DEPRECATED_CONFIG');

    // Sync changes to Vault server
    try {
      await offlineVault.sync();
      console.log('Successfully synced changes to Vault');
    } catch (error) {
      console.error('Sync failed:', error);
    }
  },
  onError: error => {
    console.error('Vault connection error:', error);
  },
});

Authentication Methods

Username/Password Authentication

const vault = new VaultSecret('secret/myapp', {
  host: 'https://vault.example.com',
  auth: {
    account: 'service-account',
    password: 'strong-password',
  },
  tokenTTL: 3600000, // 1 hour in milliseconds
});

Token-based Authentication

// Using existing token
const vault = new VaultSecret('secret/myapp', {
  host: 'https://vault.example.com',
  auth: {
    token: 'hvs.1234567890abcdef...', // Pre-existing token
  },
});

Configuration Options

VaultSecretOptions

| Property | Type | Required | Default | Description | | ---------- | ------------------ | -------- | --------- | ------------------------------------------- | | host | string | Yes | - | Vault server URL | | auth | VaultAuthMethods | Yes | - | Authentication configuration | | online | boolean | No | false | Enable online mode for real-time operations | | tokenTTL | number | No | 2764724 | Token TTL in milliseconds | | onReady | function | No | - | Callback when vault is ready | | onError | function | No | - | Error callback |

Authentication Types

// Username/Password auth
interface UsernamePasswordAuth {
  account: string;
  password: string;
}

// Token auth
interface TokenAuth {
  token: string;
}

type VaultAuthMethods = UsernamePasswordAuth | TokenAuth;

Advanced Usage

Environment-specific Configuration

// Development environment
const devVault = new VaultSecret('apps/myapp/dev', {
  host: 'https://vault-dev.company.com',
  auth: {
    account: process.env.VAULT_USERNAME!,
    password: process.env.VAULT_PASSWORD!,
  },
  online: true, // Real-time for development
  onReady: () => {
    console.log('Development vault ready');
  },
});

// Production environment
const prodVault = new VaultSecret('apps/myapp/prod', {
  host: 'https://vault-prod.company.com',
  auth: {
    account: process.env.VAULT_PROD_USERNAME!,
    password: process.env.VAULT_PROD_PASSWORD!,
  },
  online: false, // Cached for production performance
  tokenTTL: 7200000, // 2 hours
  onReady: () => {
    console.log('Production vault cache loaded');
  },
  onError: error => {
    console.error('Production vault error:', error);
    // Implement alerting/monitoring
  },
});

Type-safe Secret Management

// Define secret schema
interface AppSecrets {
  DATABASE_URL: string;
  REDIS_URL: string;
  JWT_SECRET: string;
  API_TIMEOUT: number;
  FEATURE_FLAGS: {
    newFeature: boolean;
    betaAccess: boolean;
  };
  EMAIL_CONFIG: {
    smtp_host: string;
    smtp_port: number;
    username: string;
    password: string;
  };
}

class SecretManager {
  constructor(private vault: VaultSecret<any>) {}

  async getDatabaseUrl(): Promise<string> {
    return await this.vault.get<string>('DATABASE_URL');
  }

  async getRedisConfig(): Promise<{ url: string; timeout: number }> {
    const url = await this.vault.get<string>('REDIS_URL');
    const timeout = (await this.vault.get<number>('REDIS_TIMEOUT')) || 5000;

    return { url, timeout };
  }

  async getEmailConfig(): Promise<AppSecrets['EMAIL_CONFIG']> {
    return await this.vault.get<AppSecrets['EMAIL_CONFIG']>('EMAIL_CONFIG');
  }

  async updateFeatureFlag(flag: keyof AppSecrets['FEATURE_FLAGS'], enabled: boolean): Promise<void> {
    const currentFlags = (await this.vault.get<AppSecrets['FEATURE_FLAGS']>('FEATURE_FLAGS')) || {};

    await this.vault.set<AppSecrets['FEATURE_FLAGS']>('FEATURE_FLAGS', {
      ...currentFlags,
      [flag]: enabled,
    });
  }
}

Offline Mode with Synchronization Strategies

class VaultManager {
  private vault: VaultSecret<any>;
  private syncInterval?: NodeJS.Timeout;

  constructor(secretPath: string, vaultConfig: any) {
    this.vault = new VaultSecret(secretPath, {
      ...vaultConfig,
      online: false,
      onReady: () => {
        this.startPeriodicSync();
      },
    });
  }

  // Periodic synchronization
  private startPeriodicSync() {
    this.syncInterval = setInterval(async () => {
      try {
        await this.vault.sync();
        console.log('Periodic sync completed');
      } catch (error) {
        console.error('Periodic sync failed:', error);
      }
    }, 300000); // Sync every 5 minutes
  }

  // Force sync with conflict resolution
  async forceSyncWithBackup(): Promise<void> {
    try {
      // Create backup of current cache
      const currentCache = { ...this.vault.getCacheData() };

      // Force sync (ignore version conflicts)
      await this.vault.sync(true);

      console.log('Force sync completed successfully');
    } catch (error) {
      console.error('Force sync failed:', error);
      // Could implement rollback logic here
      throw error;
    }
  }

  // Conditional sync based on last update
  async smartSync(maxCacheAge: number = 600000): Promise<boolean> {
    const lastSync = this.getLastSyncTime();
    const now = Date.now();

    if (now - lastSync > maxCacheAge) {
      try {
        await this.vault.sync();
        this.setLastSyncTime(now);
        return true;
      } catch (error) {
        console.error('Smart sync failed:', error);
        return false;
      }
    }

    return false; // No sync needed
  }

  private getLastSyncTime(): number {
    // Implementation depends on storage mechanism
    return parseInt(localStorage.getItem('vault_last_sync') || '0');
  }

  private setLastSyncTime(timestamp: number): void {
    localStorage.setItem('vault_last_sync', timestamp.toString());
  }

  destroy(): void {
    if (this.syncInterval) {
      clearInterval(this.syncInterval);
    }
  }
}

Integration Examples

Express.js Application

import express from 'express';
import { VaultSecret } from '@rytass/secret-adapter-vault';

class AppConfig {
  private vault: VaultSecret<any>;
  private ready = false;

  constructor() {
    this.vault = new VaultSecret('apps/express-api/config', {
      host: process.env.VAULT_HOST!,
      auth: {
        account: process.env.VAULT_USERNAME!,
        password: process.env.VAULT_PASSWORD!,
      },
      online: false, // Use cache for better performance
      onReady: () => {
        this.ready = true;
        console.log('Application configuration loaded from Vault');
      },
      onError: error => {
        console.error('Vault configuration error:', error);
        process.exit(1);
      },
    });
  }

  async waitForReady(): Promise<void> {
    return new Promise(resolve => {
      if (this.ready) {
        resolve();
      } else {
        this.vault.onReady(() => resolve());
      }
    });
  }

  getDatabaseConfig() {
    return {
      host: this.vault.get<string>('DB_HOST'),
      port: this.vault.get<number>('DB_PORT'),
      database: this.vault.get<string>('DB_NAME'),
      username: this.vault.get<string>('DB_USER'),
      password: this.vault.get<string>('DB_PASSWORD'),
    };
  }

  getJwtSecret(): string {
    return this.vault.get<string>('JWT_SECRET');
  }

  getRedisUrl(): string {
    return this.vault.get<string>('REDIS_URL');
  }
}

// Usage
const config = new AppConfig();

async function startServer() {
  // Wait for configuration to load
  await config.waitForReady();

  const app = express();

  // Use configuration
  const dbConfig = config.getDatabaseConfig();
  const jwtSecret = config.getJwtSecret();

  // Setup database connection
  // Setup JWT middleware
  // ... other setup

  const port = process.env.PORT || 3000;
  app.listen(port, () => {
    console.log(`Server running on port ${port}`);
  });
}

startServer().catch(console.error);

NestJS Integration

// Use with @rytass/secret-adapter-vault-nestjs for full NestJS integration
import { Injectable, OnModuleInit } from '@nestjs/common';
import { VaultSecret } from '@rytass/secret-adapter-vault';

@Injectable()
export class ConfigService implements OnModuleInit {
  private vault: VaultSecret<any>;
  private configLoaded = false;

  async onModuleInit() {
    this.vault = new VaultSecret('apps/nestjs-api/config', {
      host: process.env.VAULT_HOST!,
      auth: {
        account: process.env.VAULT_USERNAME!,
        password: process.env.VAULT_PASSWORD!,
      },
      online: true, // Real-time for NestJS
      onReady: () => {
        this.configLoaded = true;
      },
    });

    // Wait for vault to be ready
    await new Promise(resolve => {
      if (this.configLoaded) {
        resolve(undefined);
      } else {
        this.vault.onReady(() => resolve(undefined));
      }
    });
  }

  async getDatabaseUrl(): Promise<string> {
    return await this.vault.get<string>('DATABASE_URL');
  }

  async getSecret(key: string): Promise<any> {
    return await this.vault.get(key);
  }

  async updateSecret(key: string, value: any): Promise<void> {
    await this.vault.set(key, value);
  }
}

Microservices Configuration

class MicroserviceConfig {
  private vault: VaultSecret<any>;
  private serviceName: string;

  constructor(serviceName: string) {
    this.serviceName = serviceName;
    this.vault = new VaultSecret(`microservices/${serviceName}`, {
      host: process.env.VAULT_HOST!,
      auth: {
        account: `${serviceName}-service`,
        password: process.env[`${serviceName.toUpperCase()}_VAULT_PASSWORD`]!,
      },
      online: false, // Offline for microservice performance
      tokenTTL: 1800000, // 30 minutes
      onReady: () => {
        console.log(`${serviceName} configuration ready`);
        this.registerHealthCheck();
      },
    });
  }

  private registerHealthCheck() {
    // Register with service discovery/health check system
    setInterval(() => {
      this.healthCheck();
    }, 30000);
  }

  private async healthCheck() {
    try {
      // Test vault connectivity
      await this.vault.get<string>('HEALTH_CHECK');
      console.log(`${this.serviceName}: Vault health check passed`);
    } catch (error) {
      console.error(`${this.serviceName}: Vault health check failed`, error);
      // Could trigger alerts or restart procedures
    }
  }

  getServiceConfig() {
    return {
      port: this.vault.get<number>('PORT') || 3000,
      logLevel: this.vault.get<string>('LOG_LEVEL') || 'info',
      timeout: this.vault.get<number>('TIMEOUT') || 30000,
      retries: this.vault.get<number>('RETRIES') || 3,
    };
  }

  getDatabaseConfig() {
    return {
      url: this.vault.get<string>('DATABASE_URL'),
      maxConnections: this.vault.get<number>('DB_MAX_CONNECTIONS') || 10,
    };
  }

  async rotateSecret(key: string, newValue: any) {
    // Store old value for rollback
    const oldValue = this.vault.get(key);

    try {
      this.vault.set(key, newValue);
      await this.vault.sync();
      console.log(`Secret ${key} rotated successfully`);
    } catch (error) {
      // Rollback on failure
      this.vault.set(key, oldValue);
      throw new Error(`Secret rotation failed for ${key}: ${error.message}`);
    }
  }
}

Error Handling

import { VaultSecret } from '@rytass/secret-adapter-vault';

// Comprehensive error handling
const vault = new VaultSecret('apps/myapp/config', {
  host: 'https://vault.company.com',
  auth: {
    account: 'service-account',
    password: 'secure-password',
  },
  online: true,
  onReady: () => {
    console.log('Vault connection established');
  },
  onError: error => {
    console.error('Vault error occurred:', error);

    // Handle different error types
    if (error.message.includes('authentication')) {
      console.error('Authentication failed - check credentials');
      // Could trigger credential rotation
    } else if (error.message.includes('network')) {
      console.error('Network error - vault unreachable');
      // Could trigger fallback to cached values
    } else if (error.message.includes('permission')) {
      console.error('Permission denied - check vault policies');
    }

    // Implement alerting/monitoring
    sendAlert(`Vault error: ${error.message}`);
  },
});

// Graceful error handling in operations
async function safeGetSecret(key: string, defaultValue?: any) {
  try {
    const value = await vault.get(key);
    return value !== undefined ? value : defaultValue;
  } catch (error) {
    console.error(`Failed to get secret ${key}:`, error);
    return defaultValue;
  }
}

async function safeSetSecret(key: string, value: any, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      await vault.set(key, value);
      return true;
    } catch (error) {
      console.error(`Attempt ${attempt} failed for setting ${key}:`, error);

      if (attempt === maxRetries) {
        throw new Error(`Failed to set secret ${key} after ${maxRetries} attempts`);
      }

      // Wait before retry (exponential backoff)
      await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
    }
  }

  return false;
}

function sendAlert(message: string) {
  // Implement your alerting mechanism
  // Could be email, Slack, PagerDuty, etc.
}

Security Best Practices

Credential Management

# Environment variables for credentials
export VAULT_HOST="https://vault.company.com"
export VAULT_USERNAME="myapp-service"
export VAULT_PASSWORD="$(cat /run/secrets/vault-password)"
export VAULT_TOKEN_TTL="3600000"

Access Policies

# Example Vault policy for application
path "apps/myapp/*" {
  capabilities = ["create", "read", "update", "delete", "list"]
}

path "shared/database/*" {
  capabilities = ["read"]
}

path "auth/token/renew-self" {
  capabilities = ["update"]
}

Network Security

// Use HTTPS and validate certificates
const vault = new VaultSecret('apps/myapp/config', {
  host: 'https://vault.company.com', // Always use HTTPS
  auth: {
    account: process.env.VAULT_USERNAME!,
    password: process.env.VAULT_PASSWORD!,
  },
  // Additional security options could be added
});

// Implement network timeouts
const VAULT_TIMEOUT = 30000; // 30 seconds

Performance Optimization

Caching Strategies

class OptimizedVaultManager {
  private vault: VaultSecret<any>;
  private localCache = new Map<string, { value: any; expiry: number }>();
  private cacheTimeout = 300000; // 5 minutes

  constructor(secretPath: string, vaultConfig: any) {
    this.vault = new VaultSecret(secretPath, {
      ...vaultConfig,
      online: false, // Use offline mode for performance
    });
  }

  async getCached<T>(key: string): Promise<T> {
    const cached = this.localCache.get(key);

    if (cached && cached.expiry > Date.now()) {
      return cached.value;
    }

    // Cache miss or expired
    const value = await this.vault.get<T>(key);

    this.localCache.set(key, {
      value,
      expiry: Date.now() + this.cacheTimeout,
    });

    return value;
  }

  clearCache(): void {
    this.localCache.clear();
  }

  // Batch operations for efficiency
  async getBatch<T>(keys: string[]): Promise<Record<string, T>> {
    const results: Record<string, T> = {};

    for (const key of keys) {
      results[key] = await this.vault.get<T>(key);
    }

    return results;
  }
}

Testing

// Mock Vault for testing
class MockVaultSecret {
  private data = new Map<string, any>();
  private ready = true;

  constructor(
    private path: string,
    private options: any,
  ) {
    // Populate with test data
    this.data.set('TEST_SECRET', 'test-value');
    this.data.set('DB_HOST', 'localhost');
    this.data.set('DB_PORT', 5432);
  }

  get<T>(key: string): T {
    return this.data.get(key);
  }

  set<T>(key: string, value: T): void {
    this.data.set(key, value);
  }

  delete(key: string): void {
    this.data.delete(key);
  }

  async sync(): Promise<void> {
    // Mock sync operation
  }

  onReady(callback: () => void): void {
    if (this.ready) {
      callback();
    }
  }
}

// Test example
describe('VaultSecret', () => {
  let vault: MockVaultSecret;

  beforeEach(() => {
    vault = new MockVaultSecret('test/secrets', {});
  });

  it('should retrieve secrets', () => {
    const secret = vault.get<string>('TEST_SECRET');
    expect(secret).toBe('test-value');
  });

  it('should store secrets', () => {
    vault.set('NEW_SECRET', 'new-value');
    expect(vault.get('NEW_SECRET')).toBe('new-value');
  });
});

License

MIT