UNPKG

@aminya/dotenv-vault

Version:

A secrets manager for .env files – from the same people that pioneered dotenv.

171 lines (170 loc) 6.44 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LocalBuildService = void 0; const tslib_1 = require("tslib"); const crypto = tslib_1.__importStar(require("crypto")); const dotenv = tslib_1.__importStar(require("dotenv")); const chalk_1 = tslib_1.__importDefault(require("chalk")); const vars_1 = require("../../vars"); const fs_1 = require("fs"); const core_1 = require("@oclif/core"); const append_to_ignore_service_1 = require("../../services/append-to-ignore-service"); const log_service_1 = require("../../services/log-service"); class LocalBuildService { constructor(attrs = {}) { this.cmd = attrs.cmd; this.log = new log_service_1.LogService({ cmd: attrs.cmd }); } async run() { this.log.deprecated(); new append_to_ignore_service_1.AppendToIgnoreService().run(); const buildMsg = 'Building .env.vault from files on your machine'; core_1.CliUx.ux.action.start(`${chalk_1.default.dim(this.log.pretextLocal)}${buildMsg}`); await this.build(); } async build() { (0, fs_1.writeFileSync)(this.keysName, this.keysData); (0, fs_1.writeFileSync)(this.vaultName, this.vaultData); core_1.CliUx.ux.action.stop(); this.log.local(`Built ${this.vaultName}`); this.log.plain(''); this.log.plain('Next:'); this.log.plain(`1. Commit ${this.vaultName} to code`); this.log.plain('2. Set DOTENV_KEY on server'); this.log.plain('3. Deploy your code'); this.log.plain(''); this.log.plain(`(Find your DOTENV_KEY in the ${chalk_1.default.bold(this.keysName)} file)`); core_1.CliUx.ux.action.stop(); } get vaultData() { let s = `${vars_1.vars.vaultFileHeaderComment}\n\n`; for (const file in this.envLookups) { if (Object.prototype.hasOwnProperty.call(this.envLookups, file)) { const environment = this.envLookups[file]; const dotenvKey = this.keys[`DOTENV_KEY_${environment.toUpperCase()}`]; const message = (0, fs_1.readFileSync)(file, 'utf8'); const key = this._parseEncryptionKeyFromDotenvKey(dotenvKey); const ciphertext = this._encrypt(key, message); s += `# ${environment}\n`; s += `DOTENV_VAULT_${environment.toUpperCase()}="${ciphertext}"\n\n`; } } return s; } get keys() { const keys = {}; // grab current .env.keys const parsed = (dotenv.configDotenv({ path: this.keysName }).parsed || {}); for (const file in this.envLookups) { if (Object.prototype.hasOwnProperty.call(this.envLookups, file)) { const environment = this.envLookups[file]; const key = `DOTENV_KEY_${environment.toUpperCase()}`; let value = parsed[key]; // prevent overwriting current .env.keys data if (!value || value.length === 0) { value = this._generateDotenvKey(environment); } keys[key] = value; } } return keys; } get keysData() { let keysData = `${vars_1.vars.keysFileHeaderComment}\n`; for (const key in this.keys) { if (Object.prototype.hasOwnProperty.call(this.keys, key)) { const value = this.keys[key]; keysData += `${key}="${value}"\n`; } } return keysData; } get vaultName() { return '.env.vault'; } get keysName() { return '.env.keys'; } get envLookups() { const dir = './'; const lookups = {}; const files = (0, fs_1.readdirSync)(dir); for (const file of files) { // must be a .env* file if (!file.startsWith('.env')) { continue; } // must not be .env.vault.something, or .env.me.something, etc. if (this._reservedEnvFilePath(file)) { continue; } // must not end with .previous if (file.endsWith('.previous')) { continue; } const environment = this._determineLikelyEnvironment(file); lookups[file] = environment; } return lookups; } _reservedEnvFilePath(file) { const reservedEnvFiles = ['.env.vault', '.env.keys', '.env.me']; let result = false; for (const reservedFile of reservedEnvFiles) { if (file.startsWith(reservedFile)) { result = true; } } return result; } _determineLikelyEnvironment(file) { const splitFile = file.split('.'); const possibleEnvironment = splitFile[2]; // ['', 'env', environment'] if (!possibleEnvironment || possibleEnvironment.length === 0) { return 'development'; } return possibleEnvironment; } _generateDotenvKey(environment) { const rand = crypto.randomBytes(32).toString('hex'); return `dotenv://:key_${rand}@dotenv.local/vault/.env.vault?environment=${environment}`; } _parseEncryptionKeyFromDotenvKey(dotenvKey) { // Parse DOTENV_KEY. Format is a URI const uri = new URL(dotenvKey); // Get decrypt key const key = uri.password; if (!key) { throw new Error('INVALID_DOTENV_KEY: Missing key part'); } return Buffer.from(key.slice(-64), 'hex'); } _encrypt(key, message) { // set up nonce const nonce = this._generateNonce(); // set up cipher const cipher = crypto.createCipheriv('aes-256-gcm', key, nonce); // generate ciphertext let ciphertext = ''; ciphertext += cipher.update(message, 'utf8', 'hex'); ciphertext += cipher.final('hex'); ciphertext += cipher.getAuthTag().toString('hex'); // prepend nonce ciphertext = nonce.toString('hex') + ciphertext; // base64 encode output return Buffer.from(ciphertext, 'hex').toString('base64'); } _generateNonce() { return crypto.randomBytes(this._nonceBytes()); } _keyBytes() { return 32; } _authTagBytes() { return 16; } _nonceBytes() { return 12; } } exports.LocalBuildService = LocalBuildService;