diff --git a/CHANGELOG.md b/CHANGELOG.md index 5628a37d..7f953b26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. See [standa ## [Unreleased](https://github.com/motdotla/dotenv/compare/v16.3.1...master) +- Add `error.code` to error messages around `.env.vault` decryption [#795](https://github.com/motdotla/dotenv/pull/795) + ## [16.3.2](https://github.com/motdotla/dotenv/compare/v16.3.1...v16.3.2) (2024-01-18) ### Added diff --git a/lib/main.js b/lib/main.js index f5b4ffa6..7da182bd 100644 --- a/lib/main.js +++ b/lib/main.js @@ -53,7 +53,9 @@ function _parseVault (options) { // Parse .env.vault const result = DotenvModule.configDotenv({ path: vaultPath }) if (!result.parsed) { - throw new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`) + const err = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`) + err.code = 'MISSING_DATA' + throw err } // handle scenario for comma separated keys - for use with key rotation @@ -121,7 +123,9 @@ function _instructions (result, dotenvKey) { uri = new URL(dotenvKey) } catch (error) { if (error.code === 'ERR_INVALID_URL') { - throw new Error('INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenv.org/vault/.env.vault?environment=development') + const err = new Error('INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenv.org/vault/.env.vault?environment=development') + err.code = 'INVALID_DOTENV_KEY' + throw err } throw error @@ -130,20 +134,26 @@ function _instructions (result, dotenvKey) { // Get decrypt key const key = uri.password if (!key) { - throw new Error('INVALID_DOTENV_KEY: Missing key part') + const err = new Error('INVALID_DOTENV_KEY: Missing key part') + err.code = 'INVALID_DOTENV_KEY' + throw err } // Get environment const environment = uri.searchParams.get('environment') if (!environment) { - throw new Error('INVALID_DOTENV_KEY: Missing environment part') + const err = new Error('INVALID_DOTENV_KEY: Missing environment part') + err.code = 'INVALID_DOTENV_KEY' + throw err } // Get ciphertext payload const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}` const ciphertext = result.parsed[environmentKey] // DOTENV_VAULT_PRODUCTION if (!ciphertext) { - throw new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`) + const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`) + err.code = 'NOT_FOUND_DOTENV_ENVIRONMENT' + throw err } return { ciphertext, key } @@ -255,14 +265,14 @@ function decrypt (encrypted, keyStr) { const decryptionFailed = error.message === 'Unsupported state or unable to authenticate data' if (isRange || invalidKeyLength) { - const msg = 'INVALID_DOTENV_KEY: It must be 64 characters long (or more)' - throw new Error(msg) + const err = new Error('INVALID_DOTENV_KEY: It must be 64 characters long (or more)') + err.code = 'INVALID_DOTENV_KEY' + throw err } else if (decryptionFailed) { - const msg = 'DECRYPTION_FAILED: Please check your DOTENV_KEY' - throw new Error(msg) + const err = new Error('DECRYPTION_FAILED: Please check your DOTENV_KEY') + err.code = 'DECRYPTION_FAILED' + throw err } else { - console.error('Error: ', error.code) - console.error('Error: ', error.message) throw error } } @@ -274,7 +284,9 @@ function populate (processEnv, parsed, options = {}) { const override = Boolean(options && options.override) if (typeof parsed !== 'object') { - throw new Error('OBJECT_REQUIRED: Please check the processEnv argument being passed to populate') + const err = new Error('OBJECT_REQUIRED: Please check the processEnv argument being passed to populate') + err.code = 'OBJECT_REQUIRED' + throw err } // Set process.env diff --git a/tests/test-config-vault.js b/tests/test-config-vault.js index 160ffa58..2142c763 100644 --- a/tests/test-config-vault.js +++ b/tests/test-config-vault.js @@ -66,7 +66,7 @@ t.test('returns parsed object', ct => { }) t.test('throws not found if .env.vault is empty', ct => { - ct.plan(1) + ct.plan(2) const readFileSync = sinon.stub(fs, 'readFileSync').returns('') // empty file @@ -74,6 +74,7 @@ t.test('throws not found if .env.vault is empty', ct => { dotenv.config({ path: testPath }) } catch (e) { ct.equal(e.message, 'NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment DOTENV_VAULT_DEVELOPMENT in your .env.vault file.') + ct.equal(e.code, 'NOT_FOUND_DOTENV_ENVIRONMENT') } readFileSync.restore() @@ -81,7 +82,7 @@ t.test('throws not found if .env.vault is empty', ct => { }) t.test('throws missing data when somehow parsed badly', ct => { - ct.plan(1) + ct.plan(2) const configDotenvStub = sinon.stub(dotenv, 'configDotenv').returns({ parsed: undefined }) @@ -89,6 +90,7 @@ t.test('throws missing data when somehow parsed badly', ct => { dotenv.config({ path: testPath }) } catch (e) { ct.equal(e.message, 'MISSING_DATA: Cannot parse tests/.env.vault for an unknown reason') + ct.equal(e.code, 'MISSING_DATA') } configDotenvStub.restore() @@ -99,12 +101,13 @@ t.test('throws error when invalid formed DOTENV_KEY', ct => { envStub.restore() envStub = sinon.stub(process.env, 'DOTENV_KEY').value('invalid-format-non-uri-format') - ct.plan(1) + ct.plan(2) try { dotenv.config({ path: testPath }) } catch (e) { ct.equal(e.message, 'INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenv.org/vault/.env.vault?environment=development') + ct.equal(e.code, 'INVALID_DOTENV_KEY') } ct.end() @@ -132,12 +135,13 @@ t.test('throws error when DOTENV_KEY missing password', ct => { envStub.restore() envStub = sinon.stub(process.env, 'DOTENV_KEY').value('dotenv://username@dotenv.org/vault/.env.vault?environment=development') - ct.plan(1) + ct.plan(2) try { dotenv.config({ path: testPath }) } catch (e) { ct.equal(e.message, 'INVALID_DOTENV_KEY: Missing key part') + ct.equal(e.code, 'INVALID_DOTENV_KEY') } ct.end() @@ -147,12 +151,13 @@ t.test('throws error when DOTENV_KEY missing environment', ct => { envStub.restore() envStub = sinon.stub(process.env, 'DOTENV_KEY').value('dotenv://:key_ddcaa26504cd70a6fef9801901c3981538563a1767c297cb8416e8a38c62fe00@dotenv.org/vault/.env.vault') - ct.plan(1) + ct.plan(2) try { dotenv.config({ path: testPath }) } catch (e) { ct.equal(e.message, 'INVALID_DOTENV_KEY: Missing environment part') + ct.equal(e.code, 'INVALID_DOTENV_KEY') } ct.end() @@ -247,12 +252,13 @@ t.test('raises an INVALID_DOTENV_KEY if key RangeError', ct => { envStub.restore() envStub = sinon.stub(process.env, 'DOTENV_KEY').value('dotenv://:key_ddcaa26504cd70a@dotenv.org/vault/.env.vault?environment=development') - ct.plan(1) + ct.plan(2) try { dotenv.config({ path: testPath }) } catch (e) { ct.equal(e.message, 'INVALID_DOTENV_KEY: It must be 64 characters long (or more)') + ct.equal(e.code, 'INVALID_DOTENV_KEY') } ct.end() @@ -262,12 +268,13 @@ t.test('raises an DECRYPTION_FAILED if key fails to decrypt payload', ct => { envStub.restore() envStub = sinon.stub(process.env, 'DOTENV_KEY').value('dotenv://:key_2c4d267b8c3865f921311612e69273666cc76c008acb577d3e22bc3046fba386@dotenv.org/vault/.env.vault?environment=development') - ct.plan(1) + ct.plan(2) try { dotenv.config({ path: testPath }) } catch (e) { ct.equal(e.message, 'DECRYPTION_FAILED: Please check your DOTENV_KEY') + ct.equal(e.code, 'DECRYPTION_FAILED') } ct.end() @@ -277,12 +284,13 @@ t.test('raises an DECRYPTION_FAILED if both (comma separated) keys fail to decry envStub.restore() envStub = sinon.stub(process.env, 'DOTENV_KEY').value('dotenv://:key_2c4d267b8c3865f921311612e69273666cc76c008acb577d3e22bc3046fba386@dotenv.org/vault/.env.vault?environment=development,dotenv://:key_c04959b64473e43dd60c56a536ef8481388528b16759736d89515c25eec69247@dotenv.org/vault/.env.vault?environment=development') - ct.plan(1) + ct.plan(2) try { dotenv.config({ path: testPath }) } catch (e) { ct.equal(e.message, 'DECRYPTION_FAILED: Please check your DOTENV_KEY') + ct.equal(e.code, 'DECRYPTION_FAILED') } ct.end()