@dotenvx/dotenvx 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -158,7 +158,7 @@ $ dotenvx run --env-file=.env.local --env-file=.env -- node index.js
158
158
  ## Encrypt Your Env Files
159
159
 
160
160
  ```
161
- dotenvx encrypt
161
+ $ dotenvx encrypt
162
162
  ```
163
163
 
164
164
  > This will encrypt your `.env` file to a `.env.vault` file. Commit your `.env.vault` file safely to code.
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.4.0",
2
+ "version": "0.5.0",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a better dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -2,7 +2,6 @@
2
2
 
3
3
  const fs = require('fs')
4
4
  const { Command } = require('commander')
5
- const dotenv = require('dotenv')
6
5
  const program = new Command()
7
6
 
8
7
  // constants
@@ -59,40 +58,97 @@ program.command('run')
59
58
  logger.debug('configuring options')
60
59
  logger.debug(options)
61
60
 
62
- // convert to array if needed
63
- let optionEnvFile = options.envFile
64
- if (!Array.isArray(optionEnvFile)) {
65
- optionEnvFile = [optionEnvFile]
66
- }
61
+ // load from .env.vault file
62
+ if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {
63
+ const filepath = helpers.resolvePath('.env.vault')
64
+
65
+ if (!fs.existsSync(filepath)) {
66
+ logger.error(`you set DOTENV_KEY but your .env.vault file is missing: ${filepath}`)
67
+ } else {
68
+ logger.verbose(`injecting encrypted env from ${filepath}`)
69
+
70
+ try {
71
+ logger.debug(`reading encrypted env from ${filepath}`)
72
+ const src = fs.readFileSync(filepath, { encoding: ENCODING })
73
+
74
+ logger.debug(`parsing encrypted env from ${filepath}`)
75
+ const parsedVault = main.parse(src)
76
+
77
+ logger.debug(`decrypting encrypted env from ${filepath}`)
78
+ // handle scenario for comma separated keys - for use with key rotation
79
+ // example: DOTENV_KEY="dotenv://:key_1234@dotenv.org/vault/.env.vault?environment=prod,dotenv://:key_7890@dotenv.org/vault/.env.vault?environment=prod"
80
+ const dotenvKeys = process.env.DOTENV_KEY.split(',')
81
+ const length = dotenvKeys.length
82
+
83
+ let decrypted
84
+ for (let i = 0; i < length; i++) {
85
+ try {
86
+ // Get full dotenvKey
87
+ const dotenvKey = dotenvKeys[i].trim()
88
+
89
+ const key = helpers._parseEncryptionKeyFromDotenvKey(dotenvKey)
90
+ const ciphertext = helpers._parseCipherTextFromDotenvKeyAndParsedVault(dotenvKey, parsedVault)
91
+
92
+ // Decrypt
93
+ decrypted = main.decrypt(ciphertext, key)
94
+
95
+ break
96
+ } catch (error) {
97
+ // last key
98
+ if (i + 1 >= length) {
99
+ throw error
100
+ }
101
+ // try next key
102
+ }
103
+ }
104
+ logger.debug(decrypted)
105
+
106
+ logger.debug(`parsing decrypted env from ${filepath}`)
107
+ const parsed = main.parse(decrypted)
108
+
109
+ logger.debug(`writing decrypted env from ${filepath}`)
110
+ const result = main.write(process.env, parsed, options.overload)
111
+
112
+ logger.info(`injecting ${result.written.size} environment ${helpers.pluralize('variable', result.written.size)} from encrypted .env.vault`)
113
+ } catch (e) {
114
+ logger.error(e)
115
+ }
116
+ }
117
+ } else {
118
+ // convert to array if needed
119
+ let optionEnvFile = options.envFile
120
+ if (!Array.isArray(optionEnvFile)) {
121
+ optionEnvFile = [optionEnvFile]
122
+ }
67
123
 
68
- const env = {}
69
- const readableFilepaths = new Set()
70
- const written = new Set()
124
+ const readableFilepaths = new Set()
125
+ const written = new Set()
71
126
 
72
- for (const envFilepath of optionEnvFile) {
73
- const filepath = helpers.resolvePath(envFilepath)
127
+ for (const envFilepath of optionEnvFile) {
128
+ const filepath = helpers.resolvePath(envFilepath)
74
129
 
75
- logger.verbose(`injecting env from ${filepath}`)
130
+ logger.verbose(`injecting env from ${filepath}`)
76
131
 
77
- try {
78
- logger.debug(`reading env from ${filepath}`)
79
- const src = fs.readFileSync(filepath, { encoding: ENCODING })
132
+ try {
133
+ logger.debug(`reading env from ${filepath}`)
134
+ const src = fs.readFileSync(filepath, { encoding: ENCODING })
80
135
 
81
- logger.debug(`parsing env from ${filepath}`)
82
- const parsed = main.parse(src)
136
+ logger.debug(`parsing env from ${filepath}`)
137
+ const parsed = main.parse(src)
83
138
 
84
- logger.debug(`writing env from ${filepath}`)
85
- const result = main.write(process.env, parsed, options.overload)
139
+ logger.debug(`writing env from ${filepath}`)
140
+ const result = main.write(process.env, parsed, options.overload)
86
141
 
87
- readableFilepaths.add(envFilepath)
88
- result.written.forEach(key => written.add(key))
89
- } catch (e) {
90
- logger.warn(e)
142
+ readableFilepaths.add(envFilepath)
143
+ result.written.forEach(key => written.add(key))
144
+ } catch (e) {
145
+ logger.warn(e)
146
+ }
91
147
  }
92
- }
93
148
 
94
- if (readableFilepaths.size > 0) {
95
- logger.info(`injecting ${written.size} environment ${helpers.pluralize('variable', written.size)} from ${[...readableFilepaths]}`)
149
+ if (readableFilepaths.size > 0) {
150
+ logger.info(`injecting ${written.size} environment ${helpers.pluralize('variable', written.size)} from ${[...readableFilepaths]}`)
151
+ }
96
152
  }
97
153
 
98
154
  // Extract command and arguments after '--'
@@ -103,7 +159,7 @@ program.command('run')
103
159
  } else {
104
160
  const subCommand = process.argv.slice(commandIndex + 1)
105
161
 
106
- helpers.executeCommand(subCommand, env)
162
+ helpers.executeCommand(subCommand, process.env)
107
163
  }
108
164
  })
109
165
 
@@ -124,7 +180,7 @@ program.command('encrypt')
124
180
  try {
125
181
  logger.verbose(`generating .env.keys from ${optionEnvFile}`)
126
182
 
127
- const dotenvKeys = (dotenv.configDotenv({ path: '.env.keys' }).parsed || {})
183
+ const dotenvKeys = (main.configDotenv({ path: '.env.keys' }).parsed || {})
128
184
 
129
185
  for (const envFilepath of optionEnvFile) {
130
186
  const filepath = helpers.resolvePath(envFilepath)
@@ -169,8 +225,8 @@ program.command('encrypt')
169
225
  try {
170
226
  logger.verbose(`generating .env.vault from ${optionEnvFile}`)
171
227
 
172
- const dotenvKeys = (dotenv.configDotenv({ path: '.env.keys' }).parsed || {})
173
- const dotenvVaults = (dotenv.configDotenv({ path: '.env.vault' }).parsed || {})
228
+ const dotenvKeys = (main.configDotenv({ path: '.env.keys' }).parsed || {})
229
+ const dotenvVaults = (main.configDotenv({ path: '.env.vault' }).parsed || {})
174
230
 
175
231
  for (const envFilepath of optionEnvFile) {
176
232
  const filepath = helpers.resolvePath(envFilepath)
@@ -211,8 +267,6 @@ program.command('encrypt')
211
267
  }
212
268
 
213
269
  logger.info(`encrypted ${optionEnvFile} to .env.vault`)
214
-
215
- // logger.info(`encrypting`)
216
270
  })
217
271
 
218
272
  program.parse(process.argv)
@@ -4,6 +4,7 @@ const crypto = require('crypto')
4
4
  const { spawn } = require('child_process')
5
5
  const xxhash = require('xxhashjs')
6
6
  const XXHASH_SEED = 0xABCD
7
+ const NONCE_BYTES = 12
7
8
 
8
9
  const main = require('./../lib/main')
9
10
 
@@ -69,17 +70,17 @@ const generateDotenvKey = function (environment) {
69
70
  }
70
71
 
71
72
  const encryptFile = function (filepath, dotenvKey, encoding) {
72
- const key = this._parseEncryptionKeyFromDotenvKey(dotenvKey)
73
+ const key = _parseEncryptionKeyFromDotenvKey(dotenvKey)
73
74
  const message = fs.readFileSync(filepath, encoding)
74
75
 
75
- const ciphertext = this.encrypt(key, message)
76
+ const ciphertext = encrypt(key, message)
76
77
 
77
78
  return ciphertext
78
79
  }
79
80
 
80
81
  const encrypt = function (key, message) {
81
82
  // set up nonce
82
- const nonce = this._generateNonce()
83
+ const nonce = crypto.randomBytes(NONCE_BYTES)
83
84
 
84
85
  // set up cipher
85
86
  const cipher = crypto.createCipheriv('aes-256-gcm', key, nonce)
@@ -98,11 +99,11 @@ const encrypt = function (key, message) {
98
99
  }
99
100
 
100
101
  const changed = function (ciphertext, dotenvKey, filepath, encoding) {
101
- const key = this._parseEncryptionKeyFromDotenvKey(dotenvKey)
102
+ const key = _parseEncryptionKeyFromDotenvKey(dotenvKey)
102
103
  const decrypted = main.decrypt(ciphertext, key)
103
104
  const raw = fs.readFileSync(filepath, encoding)
104
105
 
105
- return this.hash(decrypted) !== this.hash(raw)
106
+ return hash(decrypted) !== hash(raw)
106
107
  }
107
108
 
108
109
  const hash = function (str) {
@@ -111,7 +112,12 @@ const hash = function (str) {
111
112
 
112
113
  const _parseEncryptionKeyFromDotenvKey = function (dotenvKey) {
113
114
  // Parse DOTENV_KEY. Format is a URI
114
- const uri = new URL(dotenvKey)
115
+ let uri
116
+ try {
117
+ uri = new URL(dotenvKey)
118
+ } catch (e) {
119
+ throw new Error(`INVALID_DOTENV_KEY: ${e.message}`)
120
+ }
115
121
 
116
122
  // Get decrypt key
117
123
  const key = uri.password
@@ -122,12 +128,29 @@ const _parseEncryptionKeyFromDotenvKey = function (dotenvKey) {
122
128
  return Buffer.from(key.slice(-64), 'hex')
123
129
  }
124
130
 
125
- const _generateNonce = function () {
126
- return crypto.randomBytes(this._nonceBytes())
127
- }
131
+ const _parseCipherTextFromDotenvKeyAndParsedVault = function (dotenvKey, parsedVault) {
132
+ // Parse DOTENV_KEY. Format is a URI
133
+ let uri
134
+ try {
135
+ uri = new URL(dotenvKey)
136
+ } catch (e) {
137
+ throw new Error(`INVALID_DOTENV_KEY: ${e.message}`)
138
+ }
128
139
 
129
- const _nonceBytes = function () {
130
- return 12
140
+ // Get environment
141
+ const environment = uri.searchParams.get('environment')
142
+ if (!environment) {
143
+ throw new Error('INVALID_DOTENV_KEY: Missing environment part')
144
+ }
145
+
146
+ // Get ciphertext payload
147
+ const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`
148
+ const ciphertext = parsedVault[environmentKey] // DOTENV_VAULT_PRODUCTION
149
+ if (!ciphertext) {
150
+ throw new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: cannot locate environment ${environmentKey} in your .env.vault file`)
151
+ }
152
+
153
+ return ciphertext
131
154
  }
132
155
 
133
156
  module.exports = {
@@ -142,6 +165,5 @@ module.exports = {
142
165
  changed,
143
166
  hash,
144
167
  _parseEncryptionKeyFromDotenvKey,
145
- _generateNonce,
146
- _nonceBytes
168
+ _parseCipherTextFromDotenvKeyAndParsedVault
147
169
  }
package/src/lib/main.js CHANGED
@@ -9,6 +9,10 @@ const decrypt = function (encrypted, keyStr) {
9
9
  return dotenv.decrypt(encrypted, keyStr)
10
10
  }
11
11
 
12
+ const configDotenv = function (options) {
13
+ return dotenv.configDotenv(options)
14
+ }
15
+
12
16
  const parse = function (src) {
13
17
  const result = dotenv.parse(src)
14
18
 
@@ -57,6 +61,7 @@ const write = function (processEnv = {}, parsed = {}, overload = false) {
57
61
 
58
62
  module.exports = {
59
63
  config,
64
+ configDotenv,
60
65
  decrypt,
61
66
  parse,
62
67
  write