UNPKG

@buildappolis/env-manager

Version:

Enterprise-grade environment variable management system with encryption, snapshots, and validation

174 lines (145 loc) 5.14 kB
import crypto from 'crypto' import fs from 'fs' import path from 'path' import os from 'os' interface Credentials { passwordHash: string salt: string encryptionKey: string createdAt: string lastModified: string recoveryPhrase?: string // Optional recovery phrase } export class CredentialsManager { private credentialsPath: string private credentials: Credentials | null = null constructor() { const configDir = path.join(os.homedir(), '.env-manager') this.credentialsPath = path.join(configDir, 'credentials.enc') // Ensure directory exists if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }) } this.loadCredentials() } private loadCredentials(): void { try { if (fs.existsSync(this.credentialsPath)) { const encryptedData = fs.readFileSync(this.credentialsPath, 'utf-8') // For now, store as JSON - in production, this should be encrypted this.credentials = JSON.parse(encryptedData) } } catch (error) { console.error('Failed to load credentials:', error) this.credentials = null } } private saveCredentials(): void { if (!this.credentials) return try { // In production, encrypt this data before saving const data = JSON.stringify(this.credentials, null, 2) fs.writeFileSync(this.credentialsPath, data, 'utf-8') // Set restrictive permissions (owner read/write only) fs.chmodSync(this.credentialsPath, 0o600) } catch (error) { console.error('Failed to save credentials:', error) throw error } } hasCredentials(): boolean { return this.credentials !== null } generateRecoveryPhrase(): string { // Generate a memorable recovery phrase const words = [ 'apple', 'banana', 'cherry', 'dragon', 'eagle', 'falcon', 'galaxy', 'harbor', 'island', 'jungle', 'kingdom', 'lantern', 'mountain', 'nebula', 'ocean', 'phoenix', 'quantum', 'rainbow', 'sunset', 'thunder', 'universe', 'volcano', 'warrior', 'zenith' ] const phrase: string[] = [] for (let i = 0; i < 4; i++) { phrase.push(words[Math.floor(Math.random() * words.length)]) } phrase.push(Math.floor(Math.random() * 9999).toString().padStart(4, '0')) return phrase.join('-') } setPassword(password: string, generateRecovery: boolean = true): { success: boolean; recoveryPhrase?: string } { const salt = crypto.randomBytes(32).toString('hex') const passwordHash = crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha512').toString('hex') const encryptionKey = crypto.randomBytes(32).toString('hex') let recoveryPhrase: string | undefined if (generateRecovery) { recoveryPhrase = this.generateRecoveryPhrase() } this.credentials = { passwordHash, salt, encryptionKey, createdAt: new Date().toISOString(), lastModified: new Date().toISOString(), recoveryPhrase: recoveryPhrase ? crypto.createHash('sha256').update(recoveryPhrase).digest('hex') : undefined } this.saveCredentials() return { success: true, recoveryPhrase } } verifyPassword(password: string): boolean { if (!this.credentials) return false const hash = crypto.pbkdf2Sync(password, this.credentials.salt, 100000, 64, 'sha512').toString('hex') return hash === this.credentials.passwordHash } verifyRecoveryPhrase(phrase: string): boolean { if (!this.credentials || !this.credentials.recoveryPhrase) return false const phraseHash = crypto.createHash('sha256').update(phrase).digest('hex') return phraseHash === this.credentials.recoveryPhrase } resetPassword(recoveryPhrase: string, newPassword: string): { success: boolean; message: string } { if (!this.verifyRecoveryPhrase(recoveryPhrase)) { return { success: false, message: 'Invalid recovery phrase' } } const result = this.setPassword(newPassword, true) return { success: result.success, message: result.success ? 'Password reset successfully' : 'Failed to reset password' } } getEncryptionKey(): string | null { return this.credentials?.encryptionKey || null } deleteCredentials(): void { try { if (fs.existsSync(this.credentialsPath)) { fs.unlinkSync(this.credentialsPath) } this.credentials = null } catch (error) { console.error('Failed to delete credentials:', error) throw error } } getCredentialsInfo(): { exists: boolean; createdAt?: string; lastModified?: string } { if (!this.credentials) { return { exists: false } } return { exists: true, createdAt: this.credentials.createdAt, lastModified: this.credentials.lastModified } } } // Singleton instance let credentialsInstance: CredentialsManager | null = null export function getCredentialsManager(): CredentialsManager { if (!credentialsInstance) { credentialsInstance = new CredentialsManager() } return credentialsInstance }