@dotenvx/dotenvx 1.71.2 → 1.72.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/README.md +2 -2
  3. package/package.json +8 -3
  4. package/src/cli/actions/decrypt.js +7 -3
  5. package/src/cli/actions/encrypt.js +9 -3
  6. package/src/cli/actions/get.js +3 -1
  7. package/src/cli/actions/keypair.js +3 -1
  8. package/src/cli/actions/login.js +63 -0
  9. package/src/cli/actions/logout.js +36 -0
  10. package/src/cli/actions/normalizeArmorOptions.js +1 -2
  11. package/src/cli/actions/rotate.js +6 -2
  12. package/src/cli/actions/run.js +2 -1
  13. package/src/cli/actions/set.js +4 -2
  14. package/src/cli/dotenvx.js +14 -19
  15. package/src/db/device.js +73 -0
  16. package/src/db/session.js +186 -0
  17. package/src/lib/api/postLogout.js +34 -0
  18. package/src/lib/api/postOauthDeviceCode.js +38 -0
  19. package/src/lib/api/postOauthToken.js +35 -0
  20. package/src/lib/extensions/armor.js +13 -0
  21. package/src/lib/helpers/buildApiError.js +16 -0
  22. package/src/lib/helpers/buildOauthError.js +14 -0
  23. package/src/lib/helpers/createSpinner.js +1 -1
  24. package/src/lib/helpers/cryptography/armorKeypair.js +2 -1
  25. package/src/lib/helpers/cryptography/provision.js +2 -2
  26. package/src/lib/helpers/cryptography/provisionSync.js +2 -2
  27. package/src/lib/helpers/decryptDeviceValue.js +10 -0
  28. package/src/lib/helpers/encryptDeviceValue.js +9 -0
  29. package/src/lib/helpers/formatCode.js +11 -0
  30. package/src/lib/helpers/http.js +7 -0
  31. package/src/lib/helpers/jsonToEnv.js +7 -0
  32. package/src/lib/helpers/keyResolution/keyValues.js +3 -0
  33. package/src/lib/helpers/keyResolution/keyValuesFromEnvSrc.js +3 -0
  34. package/src/lib/helpers/keyResolution/keyValuesSync.js +3 -0
  35. package/src/lib/helpers/listenForOpenKey.js +46 -0
  36. package/src/lib/helpers/normalizeToken.js +5 -0
  37. package/src/lib/helpers/openUrl.js +7 -0
  38. package/src/lib/main.d.ts +0 -24
  39. package/src/lib/main.js +1 -1
  40. package/src/lib/services/decrypt.js +7 -2
  41. package/src/lib/services/encrypt.js +5 -2
  42. package/src/lib/services/get.js +8 -3
  43. package/src/lib/services/keypair.js +12 -3
  44. package/src/lib/services/login.js +26 -0
  45. package/src/lib/services/loginPoll.js +35 -0
  46. package/src/lib/services/logout.js +26 -0
  47. package/src/lib/services/rotate.js +10 -3
  48. package/src/lib/services/run.js +12 -3
  49. package/src/lib/services/sets.js +26 -5
package/CHANGELOG.md CHANGED
@@ -2,7 +2,21 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
- [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.71.2...main)
5
+ [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.72.0...main)
6
+
7
+ ## [1.72.0](https://github.com/dotenvx/dotenvx/compare/v1.71.3...v1.72.0) (2026-06-15)
8
+
9
+ ### Added
10
+
11
+ * Move `armor login` command to first class `dotenvx login` command. ([#836](https://github.com/dotenvx/dotenvx/pull/836))
12
+ * `dotenvx` remains local-first: login is optional and only needed when you want Armor features like moving keys off-device, team sharing, and audit access.
13
+ * Patch 2 high severity dependencies ([#837](https://github.com/dotenvx/dotenvx/pull/837))
14
+
15
+ ## [1.71.3](https://github.com/dotenvx/dotenvx/compare/v1.71.2...v1.71.3) (2026-06-11)
16
+
17
+ ### Changed
18
+
19
+ * Forward command to Dotenvx Armor Key Guard approvals ([#835](https://github.com/dotenvx/dotenvx/pull/835))
6
20
 
7
21
  ## [1.71.2](https://github.com/dotenvx/dotenvx/compare/v1.71.1...v1.71.2) (2026-06-08)
8
22
 
package/README.md CHANGED
@@ -2119,8 +2119,8 @@ Commands:
2119
2119
  run inject env at runtime [dotenvx run -- yourcommand]
2120
2120
  get [KEY] return a single environment variable
2121
2121
  set <KEY> <value> set a single environment variable
2122
- encrypt convert .env file(s) to encrypted .env file(s)
2123
- decrypt convert encrypted .env file(s) to plain .env file(s)
2122
+ encrypt encrypt .env file(s)
2123
+ decrypt decrypt .env file(s)
2124
2124
  keypair [KEY] print public/private keys for .env file(s)
2125
2125
  ls [directory] print all .env files in a tree structure
2126
2126
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.71.2",
2
+ "version": "1.72.0",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a secure dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -45,21 +45,26 @@
45
45
  "funding": "https://dotenvx.com",
46
46
  "dependencies": {
47
47
  "commander": "^11.1.0",
48
+ "conf": "^10.2.0",
48
49
  "dotenv": "^17.2.1",
49
50
  "eciesjs": "^0.4.10",
50
51
  "enquirer": "^2.4.1",
52
+ "env-paths": "^2.2.1",
51
53
  "execa": "^5.1.1",
52
54
  "fdir": "^6.2.0",
53
55
  "ignore": "^5.3.0",
54
56
  "object-treeify": "1.1.33",
57
+ "open": "^8.4.2",
55
58
  "picomatch": "^4.0.4",
59
+ "systeminformation": "^5.22.11",
60
+ "undici": "^7.11.0",
56
61
  "which": "^4.0.0",
57
62
  "yocto-spinner": "^1.1.0"
58
63
  },
59
64
  "devDependencies": {
60
- "@yao-pkg/pkg": "^6.14.2",
65
+ "@yao-pkg/pkg": "^6.12.0",
61
66
  "capture-console": "^1.0.2",
62
- "esbuild": "^0.25.8",
67
+ "esbuild": "^0.28.1",
63
68
  "proxyquire": "^2.1.3",
64
69
  "sinon": "^14.0.1",
65
70
  "standard": "^17.1.0",
@@ -13,8 +13,8 @@ async function decrypt () {
13
13
 
14
14
  logger.debug(`options: ${JSON.stringify(options)}`)
15
15
 
16
- const sesh = new Session()
17
16
  const envs = this.envs
17
+ const sesh = new Session()
18
18
  const noArmor = options.armor === false || (await sesh.noArmor())
19
19
 
20
20
  let errorCount = 0
@@ -24,7 +24,9 @@ async function decrypt () {
24
24
  if (spinner) spinner.stop()
25
25
  const {
26
26
  processedEnvs
27
- } = await new Decrypt(envs, options.key, options.excludeKey, options.envKeysFile, noArmor).run()
27
+ } = await new Decrypt(envs, options.key, options.excludeKey, options.envKeysFile, noArmor, {
28
+ command: process.argv.slice(2)
29
+ }).run()
28
30
  if (spinner) spinner.stop()
29
31
  for (const processedEnv of processedEnvs) {
30
32
  if (processedEnv.error) {
@@ -47,7 +49,9 @@ async function decrypt () {
47
49
  processedEnvs,
48
50
  changedFilepaths,
49
51
  unchangedFilepaths
50
- } = await new Decrypt(envs, options.key, options.excludeKey, options.envKeysFile, noArmor).run()
52
+ } = await new Decrypt(envs, options.key, options.excludeKey, options.envKeysFile, noArmor, {
53
+ command: process.argv.slice(2)
54
+ }).run()
51
55
 
52
56
  for (const processedEnv of processedEnvs) {
53
57
  logger.verbose(`decrypting ${processedEnv.envFilepath} (${processedEnv.filepath})`)
@@ -46,8 +46,8 @@ async function encrypt () {
46
46
 
47
47
  logger.debug(`options: ${JSON.stringify(options)}`)
48
48
 
49
- const sesh = new Session()
50
49
  const envs = this.envs
50
+ const sesh = new Session()
51
51
  const noArmor = options.armor === false || (!options.token && (await sesh.noArmor()))
52
52
  const noCreate = options.create === false
53
53
 
@@ -56,7 +56,10 @@ async function encrypt () {
56
56
  if (spinner) spinner.stop()
57
57
  const {
58
58
  processedEnvs
59
- } = await new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, noArmor, noCreate, options.token, encryptOptions(noArmor)).run()
59
+ } = await new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, noArmor, noCreate, options.token, {
60
+ ...encryptOptions(noArmor),
61
+ command: process.argv.slice(2)
62
+ }).run()
60
63
  if (spinner) spinner.stop()
61
64
  for (const processedEnv of processedEnvs) {
62
65
  console.log(processedEnv.envSrc)
@@ -69,7 +72,10 @@ async function encrypt () {
69
72
  processedEnvs,
70
73
  changedFilepaths,
71
74
  unchangedFilepaths
72
- } = await new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, noArmor, noCreate, options.token, encryptOptions(noArmor)).run()
75
+ } = await new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, noArmor, noCreate, options.token, {
76
+ ...encryptOptions(noArmor),
77
+ command: process.argv.slice(2)
78
+ }).run()
73
79
 
74
80
  for (const processedEnv of processedEnvs) {
75
81
  logger.verbose(`encrypting ${processedEnv.envFilepath} (${processedEnv.filepath})`)
@@ -32,7 +32,9 @@ async function get (key) {
32
32
  const sesh = new Session()
33
33
  const noArmor = options.armor === false || (await sesh.noArmor())
34
34
  if (spinner) spinner.stop()
35
- const { parsed, errors } = await new Get(key, envs, options.overload, options.all, options.envKeysFile, noArmor).run()
35
+ const { parsed, errors } = await new Get(key, envs, options.overload, options.all, options.envKeysFile, noArmor, {
36
+ command: process.argv.slice(2)
37
+ }).run()
36
38
 
37
39
  for (const error of errors || []) {
38
40
  if (options.strict) throw error // throw immediately if strict
@@ -19,7 +19,9 @@ async function keypair (key) {
19
19
  const sesh = new Session()
20
20
  const noArmor = options.armor === false || await sesh.noArmor()
21
21
  if (spinner) spinner.stop()
22
- const keypairs = await new Keypair(options.envFile, options.envKeysFile, noArmor).run()
22
+ const keypairs = await new Keypair(options.envFile, options.envKeysFile, noArmor, {
23
+ command: process.argv.slice(2)
24
+ }).run()
23
25
  const results = key ? keypairs[key] : keypairs
24
26
 
25
27
  if (spinner) spinner.stop()
@@ -0,0 +1,63 @@
1
+ const { logger } = require('../../shared/logger')
2
+
3
+ const Session = require('../../db/session')
4
+ const Login = require('../../lib/services/login')
5
+ const LoginPoll = require('../../lib/services/loginPoll')
6
+
7
+ const createSpinner = require('../../lib/helpers/createSpinner')
8
+ const formatCode = require('../../lib/helpers/formatCode')
9
+ const listenForOpenKey = require('../../lib/helpers/listenForOpenKey')
10
+ const openUrl = require('../../lib/helpers/openUrl')
11
+
12
+ const FRAMES = ['◐', '◓', '◑', '◒']
13
+
14
+ async function login () {
15
+ const options = this.opts()
16
+ const spinner = await createSpinner({ ...options, text: 'logging in', frames: FRAMES })
17
+
18
+ logger.debug(`options: ${JSON.stringify(options)}`)
19
+
20
+ const sesh = new Session()
21
+ await sesh.notifyUpdate()
22
+ const hostname = options.hostname || sesh.hostname()
23
+ let cleanupOpenKeyListener = () => {}
24
+
25
+ try {
26
+ const {
27
+ deviceCode,
28
+ userCode,
29
+ verificationUri,
30
+ verificationUriComplete,
31
+ interval
32
+ } = await new Login(hostname).run()
33
+
34
+ const promptMessage = `◌ press Enter to open [${verificationUri}] and enter code [${formatCode(userCode)}]...`
35
+
36
+ logger.debug(`POST ${hostname} with deviceCode ${deviceCode} at interval ${interval}`)
37
+ logger.info(promptMessage)
38
+
39
+ // begin polling
40
+ const pollPromise = new LoginPoll(hostname, deviceCode, interval).run()
41
+ cleanupOpenKeyListener = listenForOpenKey(() => openUrl(verificationUriComplete))
42
+ const data = await pollPromise
43
+
44
+ cleanupOpenKeyListener()
45
+ if (spinner) spinner.stop()
46
+ logger.success(`◉ logged in (${data.username})`)
47
+ process.exit(0)
48
+ } catch (error) {
49
+ cleanupOpenKeyListener()
50
+ if (spinner) spinner.stop()
51
+ if (error.message) {
52
+ logger.error(error.message)
53
+ } else {
54
+ logger.error(error)
55
+ }
56
+ if (error.stack) {
57
+ logger.debug(error.stack)
58
+ }
59
+ process.exit(1)
60
+ }
61
+ }
62
+
63
+ module.exports = login
@@ -0,0 +1,36 @@
1
+ const { logger } = require('../../shared/logger')
2
+
3
+ const Session = require('../../db/session')
4
+ const Logout = require('../../lib/services/logout')
5
+ const createSpinner = require('../../lib/helpers/createSpinner')
6
+
7
+ const FRAMES = ['◐', '◓', '◑', '◒']
8
+
9
+ async function logout () {
10
+ const options = this.opts()
11
+ const spinner = await createSpinner({ ...options, text: 'logging out', frames: FRAMES })
12
+
13
+ logger.debug(`options: ${JSON.stringify(options)}`)
14
+
15
+ const sesh = new Session()
16
+ const hostname = options.hostname || sesh.hostname()
17
+
18
+ try {
19
+ const data = await new Logout(hostname).run()
20
+ if (spinner) spinner.stop()
21
+ logger.success(`◌ logged out (${data.username})`)
22
+ } catch (error) {
23
+ if (spinner) spinner.stop()
24
+ if (error.message) {
25
+ logger.error(error.message)
26
+ } else {
27
+ logger.error(error)
28
+ }
29
+ if (error.stack) {
30
+ logger.debug(error.stack)
31
+ }
32
+ process.exit(1)
33
+ }
34
+ }
35
+
36
+ module.exports = logout
@@ -1,8 +1,7 @@
1
1
  function normalizeArmorOptions (options) {
2
- if (options.armor === false || options.ops === false || options.vlt === false) {
2
+ if (options.armor === false || options.ops === false) {
3
3
  options.armor = false
4
4
  options.ops = false
5
- options.vlt = false
6
5
  }
7
6
 
8
7
  return options
@@ -23,7 +23,9 @@ async function rotate () {
23
23
  if (spinner) spinner.stop()
24
24
  const {
25
25
  processedEnvs
26
- } = await new Rotate(envs, options.key, options.excludeKey, options.envKeysFile, noArmor).run()
26
+ } = await new Rotate(envs, options.key, options.excludeKey, options.envKeysFile, noArmor, {
27
+ command: process.argv.slice(2)
28
+ }).run()
27
29
  if (spinner) spinner.stop()
28
30
  for (const processedEnv of processedEnvs) {
29
31
  console.log(processedEnv.envSrc)
@@ -40,7 +42,9 @@ async function rotate () {
40
42
  processedEnvs,
41
43
  changedFilepaths,
42
44
  unchangedFilepaths
43
- } = await new Rotate(envs, options.key, options.excludeKey, options.envKeysFile, noArmor).run()
45
+ } = await new Rotate(envs, options.key, options.excludeKey, options.envKeysFile, noArmor, {
46
+ command: process.argv.slice(2)
47
+ }).run()
44
48
 
45
49
  for (const processedEnv of processedEnvs) {
46
50
  logger.verbose(`rotating ${processedEnv.envFilepath} (${processedEnv.filepath})`)
@@ -56,7 +56,8 @@ async function run () {
56
56
  readableFilepaths,
57
57
  uniqueInjectedKeys
58
58
  } = await new Run(envs, options.overload, process.env, options.envKeysFile, noArmor, {
59
- token: options.token
59
+ token: options.token,
60
+ command: commandArgs
60
61
  }).run()
61
62
 
62
63
  for (const processedEnv of processedEnvs) {
@@ -24,9 +24,9 @@ async function set (key, value) {
24
24
  logger.debug(`options: ${JSON.stringify(options)}`)
25
25
 
26
26
  try {
27
- const sesh = new Session()
28
27
  const envs = this.envs
29
28
  const envKeysFilepath = options.envKeysFile
29
+ const sesh = new Session()
30
30
  const noArmor = options.armor === false || (await sesh.noArmor())
31
31
  const noCreate = options.create === false
32
32
 
@@ -35,7 +35,9 @@ async function set (key, value) {
35
35
  processedEnvs,
36
36
  changedFilepaths,
37
37
  unchangedFilepaths
38
- } = await new Sets(key, value, envs, encrypt, envKeysFilepath, noArmor, noCreate).run()
38
+ } = await new Sets(key, value, envs, encrypt, envKeysFilepath, noArmor, noCreate, {
39
+ command: process.argv.slice(2)
40
+ }).run()
39
41
 
40
42
  let withEncryption = ''
41
43
 
@@ -72,7 +72,6 @@ program.command('run')
72
72
  .option('--ignore <errorCodes...>', 'error code(s) to ignore (example: --ignore=MISSING_ENV_FILE)')
73
73
  .option('--token <token>', 'set Armor ⛨ token')
74
74
  .option('--no-armor', 'disable dotenvx-armor features')
75
- .addOption(new Option('--no-vlt', 'disable dotenvx-vlt features (deprecated. use --no-armor)').hideHelp())
76
75
  .addOption(new Option('--no-ops', 'disable dotenvx-ops features (deprecated. use --no-armor)').hideHelp())
77
76
  .action(function (...args) {
78
77
  this.envs = envs
@@ -96,7 +95,6 @@ program.command('get')
96
95
  .option('--pp', 'pretty print output (alias)')
97
96
  .option('--format <type>', 'format of the output (json, shell, colon, eval)', 'json')
98
97
  .option('--no-armor', 'disable dotenvx-armor features')
99
- .addOption(new Option('--no-vlt', 'disable dotenvx-vlt features (deprecated. use --no-armor)').hideHelp())
100
98
  .addOption(new Option('--no-ops', 'disable dotenvx-ops features (deprecated. use --no-armor)').hideHelp())
101
99
  .action(function (...args) {
102
100
  this.envs = envs
@@ -117,7 +115,6 @@ program.command('set')
117
115
  .option('-p, --plain', 'store value as plain text', false)
118
116
  .option('--no-create', 'do not create .env file(s) when missing')
119
117
  .option('--no-armor', 'disable dotenvx-armor features')
120
- .addOption(new Option('--no-vlt', 'disable dotenvx-vlt features (deprecated. use --no-armor)').hideHelp())
121
118
  .addOption(new Option('--no-ops', 'disable dotenvx-ops features (deprecated. use --no-armor)').hideHelp())
122
119
  .action(function (...args) {
123
120
  this.envs = envs
@@ -126,7 +123,7 @@ program.command('set')
126
123
 
127
124
  // dotenvx encrypt
128
125
  program.command('encrypt')
129
- .description('convert .env file(s) to encrypted .env file(s)')
126
+ .description('encrypt .env file(s)')
130
127
  .option('-f, --env-file <paths...>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
131
128
  .option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
132
129
  .option('-k, --key <keys...>', 'keys(s) to encrypt (default: all keys in file)')
@@ -135,7 +132,6 @@ program.command('encrypt')
135
132
  .option('--token <token>', 'set Armor ⛨ token')
136
133
  .option('--no-create', 'do not create .env file(s) when missing')
137
134
  .option('--no-armor', 'disable dotenvx-armor features')
138
- .addOption(new Option('--no-vlt', 'disable dotenvx-vlt features (deprecated. use --no-armor)').hideHelp())
139
135
  .addOption(new Option('--no-ops', 'disable dotenvx-ops features (deprecated. use --no-armor)').hideHelp())
140
136
  .action(function (...args) {
141
137
  this.envs = envs
@@ -144,13 +140,12 @@ program.command('encrypt')
144
140
 
145
141
  // dotenvx decrypt
146
142
  program.command('decrypt')
147
- .description('convert encrypted .env file(s) to plain .env file(s)')
143
+ .description('decrypt .env file(s)')
148
144
  .option('-f, --env-file <paths...>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
149
145
  .option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
150
146
  .option('-k, --key <keys...>', 'keys(s) to decrypt (default: all keys in file)')
151
147
  .option('-ek, --exclude-key <excludeKeys...>', 'keys(s) to exclude from decryption (default: none)')
152
148
  .option('--no-armor', 'disable dotenvx-armor features')
153
- .addOption(new Option('--no-vlt', 'disable dotenvx-vlt features (deprecated. use --no-armor)').hideHelp())
154
149
  .addOption(new Option('--no-ops', 'disable dotenvx-ops features (deprecated. use --no-armor)').hideHelp())
155
150
  .option('--stdout', 'send to stdout')
156
151
  .action(function (...args) {
@@ -166,7 +161,6 @@ program.command('rotate')
166
161
  .option('-k, --key <keys...>', 'keys(s) to encrypt (default: all keys in file)')
167
162
  .option('-ek, --exclude-key <excludeKeys...>', 'keys(s) to exclude from encryption (default: none)')
168
163
  .option('--no-armor', 'disable dotenvx-armor features')
169
- .addOption(new Option('--no-vlt', 'disable dotenvx-vlt features (deprecated. use --no-armor)').hideHelp())
170
164
  .addOption(new Option('--no-ops', 'disable dotenvx-ops features (deprecated. use --no-armor)').hideHelp())
171
165
  .option('--stdout', 'send to stdout')
172
166
  .action(function (...args) {
@@ -182,7 +176,6 @@ program.command('keypair')
182
176
  .option('-f, --env-file <paths...>', 'path(s) to your env file(s)')
183
177
  .option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
184
178
  .option('--no-armor', 'disable dotenvx-armor features')
185
- .addOption(new Option('--no-vlt', 'disable dotenvx-vlt features (deprecated. use --no-armor)').hideHelp())
186
179
  .addOption(new Option('--no-ops', 'disable dotenvx-ops features (deprecated. use --no-armor)').hideHelp())
187
180
  .option('-pp, --pretty-print', 'pretty print output')
188
181
  .option('--pp', 'pretty print output (alias)')
@@ -211,20 +204,20 @@ program.command('doctor', { hidden: true })
211
204
 
212
205
  // dotenvx login
213
206
  program.command('login', { hidden: true })
214
- .description('')
207
+ .description('log in to move keys off-device, share with your team, and audit access')
215
208
  .allowUnknownOption()
216
- .action(() => {
217
- const rawArgs = ['armor', 'login', ...process.argv.slice(3)]
218
- executeDynamic(program, 'armor', rawArgs)
209
+ .option('--hostname <hostname>', 'set Armor ⛨ hostname')
210
+ .action(function (...args) {
211
+ return require('./actions/login').apply(this, args)
219
212
  })
220
213
 
221
214
  // dotenvx logout
222
215
  program.command('logout', { hidden: true })
223
- .description('log out of your dotenvx account')
216
+ .description('log out of connected security features')
224
217
  .allowUnknownOption()
225
- .action(() => {
226
- const rawArgs = ['armor', 'logout', ...process.argv.slice(3)]
227
- executeDynamic(program, 'armor', rawArgs)
218
+ .option('--hostname <hostname>', 'set Armor ⛨ hostname')
219
+ .action(function (...args) {
220
+ return require('./actions/logout').apply(this, args)
228
221
  })
229
222
 
230
223
  // dotenvx help
@@ -245,8 +238,10 @@ program.command('help [command]')
245
238
 
246
239
  // dotenvx armor
247
240
  program.addHelpText('after', ' ')
248
- program.addHelpText('after', 'Advanced: ')
249
- program.addHelpText('after', ' armor ARMORED KEYS [www.dotenvx.com/armor]')
241
+ program.addHelpText('after', 'Professional Security: ')
242
+ program.addHelpText('after', ' login log in to move keys off-device, share with your team, and audit access')
243
+ program.addHelpText('after', ' logout log out of connected security features')
244
+ program.addHelpText('after', ' armor ⛨ move private keys off-device [www.dotenvx.com/armor]')
250
245
  program.addHelpText('after', ' ext ⊕ extensions')
251
246
 
252
247
  // dotenvx ext
@@ -0,0 +1,73 @@
1
+ const Conf = require('conf')
2
+ const { PrivateKey } = require('eciesjs')
3
+
4
+ const encryptDeviceValue = require('./../lib/helpers/encryptDeviceValue')
5
+ const decryptDeviceValue = require('./../lib/helpers/decryptDeviceValue')
6
+
7
+ class Device {
8
+ constructor () {
9
+ this.store = new Conf({
10
+ cwd: process.env.DOTENVX_CONFIG || undefined,
11
+ projectName: 'dotenvx',
12
+ configName: '.device-key',
13
+ projectSuffix: '',
14
+ fileExtension: '',
15
+ encryptionKey: 'dotenvxpro dotenvxpro dotenvxpro' // backwards compatible
16
+ })
17
+ }
18
+
19
+ touch () {
20
+ const _privateKey = this.privateKey()
21
+ const _publicKey = this.publicKey()
22
+
23
+ return {
24
+ privateKey: _privateKey,
25
+ publicKey: _publicKey
26
+ }
27
+ }
28
+
29
+ configPath () {
30
+ return this.store.path
31
+ }
32
+
33
+ privateKey () {
34
+ const currentPrivateKey = this.store.get('private_key/1')
35
+ if (currentPrivateKey && currentPrivateKey.length > 0) {
36
+ this.store.set('private_key/1', currentPrivateKey)
37
+
38
+ return currentPrivateKey
39
+ }
40
+
41
+ // generate privateKey for the first time
42
+ const kp = new PrivateKey()
43
+ const _privateKey = kp.secret.toString('hex')
44
+
45
+ this.store.set('private_key/1', _privateKey)
46
+
47
+ return _privateKey
48
+ }
49
+
50
+ publicKey () {
51
+ // must have private key to try and get public key
52
+ const privateKeyHex = this.privateKey()
53
+ if (!privateKeyHex || privateKeyHex.length < 1) {
54
+ return ''
55
+ }
56
+
57
+ // create keyPair object from hex string
58
+ const _privateKey = new PrivateKey(Buffer.from(privateKeyHex, 'hex'))
59
+
60
+ // compute publicKey from privateKey
61
+ return _privateKey.publicKey.toHex()
62
+ }
63
+
64
+ encrypt (value) {
65
+ return encryptDeviceValue(value, this.publicKey())
66
+ }
67
+
68
+ decrypt (value) {
69
+ return decryptDeviceValue(value, this.privateKey())
70
+ }
71
+ }
72
+
73
+ module.exports = Device