@dotenvx/dotenvx 2.6.0 → 2.7.1
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/CHANGELOG.md +15 -3
- package/README.md +92 -0
- package/package.json +1 -1
- package/src/cli/actions/decrypt.js +11 -1
- package/src/cli/actions/get.js +12 -0
- package/src/cli/actions/run.js +30 -3
- package/src/cli/actions/settings/device.js +23 -0
- package/src/cli/actions/settings/hostname.js +20 -0
- package/src/cli/actions/settings/off.js +14 -0
- package/src/cli/actions/settings/on.js +14 -0
- package/src/cli/actions/settings/path.js +20 -0
- package/src/cli/actions/settings/token.js +23 -0
- package/src/cli/actions/settings/username.js +20 -0
- package/src/cli/commands/armor.js +3 -0
- package/src/cli/commands/lock.js +6 -0
- package/src/cli/commands/native.js +6 -0
- package/src/cli/commands/settings.js +62 -0
- package/src/cli/dotenvx.js +3 -0
- package/src/db/session.js +60 -7
- package/src/lib/helpers/linuxSecretService.js +13 -11
- package/src/lib/helpers/macosKeychain.js +26 -0
- package/src/lib/helpers/mask.js +22 -0
- package/src/lib/helpers/maskEnvSrc.js +15 -0
- package/src/lib/helpers/maskProcessedEnvs.js +29 -0
- package/src/lib/helpers/windowsCredentialManager.js +11 -9
- package/src/lib/main.d.ts +16 -0
- package/src/lib/main.js +14 -0
- package/src/lib/providers/native/index.js +42 -14
- package/src/lib/services/keychainDown.js +3 -35
- package/src/lib/services/keychainPull.js +2 -20
- package/src/lib/services/keychainPush.js +3 -39
- package/src/lib/services/keychainUp.js +3 -39
package/CHANGELOG.md
CHANGED
|
@@ -2,19 +2,31 @@
|
|
|
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/v2.
|
|
5
|
+
[Unreleased](https://github.com/dotenvx/dotenvx/compare/v2.7.1...main)
|
|
6
|
+
|
|
7
|
+
## [2.7.1](https://github.com/dotenvx/dotenvx/compare/v2.7.0...v2.7.1) (2026-07-13)
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
* Security Improvement: `dotenvx armor login` attempts to set the `DOTENVX_ARMOR_TOKEN` in your native OS secret store going forward. For current Armor users run `dotenvx armor logout` and then `dotenvx armor login` to make use of this immediately. ([#886](https://github.com/dotenvx/dotenvx/pull/886))
|
|
12
|
+
|
|
13
|
+
## [2.7.0](https://github.com/dotenvx/dotenvx/compare/v2.6.0...v2.7.0) (2026-07-13)
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
* Added support for `--mask` on `get, run, decrypt` ([#885](https://github.com/dotenvx/dotenvx/pull/885))
|
|
6
18
|
|
|
7
19
|
## [2.6.0](https://github.com/dotenvx/dotenvx/compare/v2.5.0...v2.6.0) (2026-07-11)
|
|
8
20
|
|
|
9
21
|
### Added
|
|
10
22
|
|
|
11
|
-
* Support `native up|down|pull|push` for windows ([#
|
|
23
|
+
* Support `native up|down|pull|push` for windows ([#884](https://github.com/dotenvx/dotenvx/pull/884))
|
|
12
24
|
|
|
13
25
|
## [2.5.0](https://github.com/dotenvx/dotenvx/compare/v2.4.2...v2.5.0) (2026-07-11)
|
|
14
26
|
|
|
15
27
|
### Added
|
|
16
28
|
|
|
17
|
-
* Support `native up|down|pull|push` for windows ([#
|
|
29
|
+
* Support `native up|down|pull|push` for windows ([#883](https://github.com/dotenvx/dotenvx/pull/883))
|
|
18
30
|
|
|
19
31
|
## [2.4.1](https://github.com/dotenvx/dotenvx/compare/v2.4.0...v2.4.1) (2026-07-10)
|
|
20
32
|
|
package/README.md
CHANGED
|
@@ -1471,6 +1471,21 @@ $ dotenvx set HELLO Dotenvx -fk .env.keys -f apps/app1/.env
|
|
|
1471
1471
|
$ dotenvx run -fk .env.keys -f apps/app1/.env -- yourcommand
|
|
1472
1472
|
```
|
|
1473
1473
|
|
|
1474
|
+
</details>
|
|
1475
|
+
<details><summary>`run --mask`</summary><br>
|
|
1476
|
+
|
|
1477
|
+
Inject masked values into the command. By default, up to the first six characters are visible.
|
|
1478
|
+
|
|
1479
|
+
```sh
|
|
1480
|
+
$ echo "SECRET=abcdefghijkl" > .env
|
|
1481
|
+
$ echo "console.log(process.env.SECRET)" > index.js
|
|
1482
|
+
|
|
1483
|
+
$ dotenvx run --mask --quiet -- node index.js
|
|
1484
|
+
abcdef******
|
|
1485
|
+
```
|
|
1486
|
+
|
|
1487
|
+
Pass a number to control how many characters are visible, such as `--mask 0` to fully mask values.
|
|
1488
|
+
|
|
1474
1489
|
</details>
|
|
1475
1490
|
<details><summary>`run --token`</summary><br>
|
|
1476
1491
|
|
|
@@ -1510,6 +1525,20 @@ $ dotenvx get HELLO
|
|
|
1510
1525
|
World
|
|
1511
1526
|
```
|
|
1512
1527
|
|
|
1528
|
+
</details>
|
|
1529
|
+
<details><summary>`get KEY --mask`</summary><br>
|
|
1530
|
+
|
|
1531
|
+
Return a masked environment variable value. By default, up to the first six characters are visible.
|
|
1532
|
+
|
|
1533
|
+
```sh
|
|
1534
|
+
$ echo "SECRET=abcdefghijkl" > .env
|
|
1535
|
+
|
|
1536
|
+
$ dotenvx get SECRET --mask
|
|
1537
|
+
abcdef******
|
|
1538
|
+
```
|
|
1539
|
+
|
|
1540
|
+
Pass a number to control how many characters are visible, such as `--mask 0` to fully mask values.
|
|
1541
|
+
|
|
1513
1542
|
</details>
|
|
1514
1543
|
<details><summary>`get KEY --no-native`</summary><br>
|
|
1515
1544
|
|
|
@@ -2141,6 +2170,23 @@ or send to a file:
|
|
|
2141
2170
|
$ dotenvx decrypt --stdout > somefile.txt
|
|
2142
2171
|
```
|
|
2143
2172
|
|
|
2173
|
+
</details>
|
|
2174
|
+
<details><summary>`decrypt --stdout --mask`</summary><br>
|
|
2175
|
+
|
|
2176
|
+
Decrypt the contents of an encrypted `.env` file to stdout with its values masked. The encrypted `.env` file remains unchanged.
|
|
2177
|
+
|
|
2178
|
+
```sh
|
|
2179
|
+
$ echo "SECRET=abcdefghijkl" > .env
|
|
2180
|
+
$ dotenvx encrypt
|
|
2181
|
+
◈ encrypted (.env)
|
|
2182
|
+
|
|
2183
|
+
$ dotenvx decrypt --stdout --mask
|
|
2184
|
+
...
|
|
2185
|
+
SECRET="abcdef******"
|
|
2186
|
+
```
|
|
2187
|
+
|
|
2188
|
+
Pass a number to control how many characters are visible, such as `--mask 0` to fully mask values.
|
|
2189
|
+
|
|
2144
2190
|
</details>
|
|
2145
2191
|
<details><summary>`keypair`</summary><br>
|
|
2146
2192
|
|
|
@@ -2725,6 +2771,33 @@ Hello Dotenvx
|
|
|
2725
2771
|
|
|
2726
2772
|
It defaults to looking for a `.env` file.
|
|
2727
2773
|
|
|
2774
|
+
</details>
|
|
2775
|
+
<details><summary>`config(mask: true)` - mask</summary><br>
|
|
2776
|
+
|
|
2777
|
+
Inject and return masked values. By default, up to the first six characters are visible.
|
|
2778
|
+
|
|
2779
|
+
```ini
|
|
2780
|
+
# .env
|
|
2781
|
+
SECRET="abcdefghijkl"
|
|
2782
|
+
```
|
|
2783
|
+
|
|
2784
|
+
```js
|
|
2785
|
+
// index.js
|
|
2786
|
+
const dotenvx = require('@dotenvx/dotenvx')
|
|
2787
|
+
const result = dotenvx.config({ mask: true, quiet: true })
|
|
2788
|
+
|
|
2789
|
+
console.log(process.env.SECRET)
|
|
2790
|
+
console.log(result.parsed.SECRET)
|
|
2791
|
+
```
|
|
2792
|
+
|
|
2793
|
+
```sh
|
|
2794
|
+
$ node index.js
|
|
2795
|
+
abcdef******
|
|
2796
|
+
abcdef******
|
|
2797
|
+
```
|
|
2798
|
+
|
|
2799
|
+
Set `mask: 0` to fully mask values.
|
|
2800
|
+
|
|
2728
2801
|
</details>
|
|
2729
2802
|
<details><summary>`config(path: ['.env.local', '.env'])` - multiple files</summary><br>
|
|
2730
2803
|
|
|
@@ -3051,6 +3124,25 @@ console.log(decryptedValue)
|
|
|
3051
3124
|
|
|
3052
3125
|
This is known as *Decryption at Access* and is written about in [the whitepaper](https://dotenvx.com/dotenvx.pdf).
|
|
3053
3126
|
|
|
3127
|
+
</details>
|
|
3128
|
+
<details><summary>`get(KEY, {mask:})`</summary><br>
|
|
3129
|
+
|
|
3130
|
+
Programmatically return a masked environment variable value.
|
|
3131
|
+
|
|
3132
|
+
```js
|
|
3133
|
+
// index.js
|
|
3134
|
+
const dotenvx = require('@dotenvx/dotenvx')
|
|
3135
|
+
const maskedValue = await dotenvx.get('SECRET', { mask: true })
|
|
3136
|
+
console.log(maskedValue)
|
|
3137
|
+
```
|
|
3138
|
+
|
|
3139
|
+
```sh
|
|
3140
|
+
$ node index.js
|
|
3141
|
+
abcdef******
|
|
3142
|
+
```
|
|
3143
|
+
|
|
3144
|
+
Set `mask: 0` to fully mask values.
|
|
3145
|
+
|
|
3054
3146
|
</details>
|
|
3055
3147
|
|
|
3056
3148
|
|
package/package.json
CHANGED
|
@@ -6,6 +6,7 @@ const createSpinner = require('../../lib/helpers/createSpinner')
|
|
|
6
6
|
const Session = require('../../db/session')
|
|
7
7
|
|
|
8
8
|
const decryptTransform = require('./../../lib/transforms/decrypt')
|
|
9
|
+
const maskEnvSrc = require('../../lib/helpers/maskEnvSrc')
|
|
9
10
|
|
|
10
11
|
async function decrypt () {
|
|
11
12
|
const options = this.opts()
|
|
@@ -44,7 +45,16 @@ async function decrypt () {
|
|
|
44
45
|
errorCount += 1
|
|
45
46
|
logger.error(processedEnv.error.messageWithHelp || processedEnv.error.message)
|
|
46
47
|
} else {
|
|
47
|
-
|
|
48
|
+
let showChar = options.mask
|
|
49
|
+
if (options.mask === true) {
|
|
50
|
+
showChar = 6
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let envSrc = processedEnv.envSrc
|
|
54
|
+
if (options.mask !== undefined) {
|
|
55
|
+
envSrc = maskEnvSrc(processedEnv.envSrc, showChar)
|
|
56
|
+
}
|
|
57
|
+
console.log(envSrc)
|
|
48
58
|
}
|
|
49
59
|
}
|
|
50
60
|
|
package/src/cli/actions/get.js
CHANGED
|
@@ -8,6 +8,7 @@ const getResolver = require('./../../lib/resolvers/get')
|
|
|
8
8
|
const normalizeDotenvConfigConvention = require('../../lib/helpers/normalizeDotenvConfigConvention')
|
|
9
9
|
const buildCommandEnvs = require('../../lib/helpers/buildCommandEnvs')
|
|
10
10
|
const resolveEnvKeysFile = require('../../lib/helpers/resolveEnvKeysFile')
|
|
11
|
+
const mask = require('../../lib/helpers/mask')
|
|
11
12
|
|
|
12
13
|
async function get (key) {
|
|
13
14
|
const options = normalizeDotenvConfigConvention(this.opts())
|
|
@@ -55,6 +56,17 @@ async function get (key) {
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
if (spinner) spinner.stop()
|
|
59
|
+
if (options.mask !== undefined) {
|
|
60
|
+
let showChar = options.mask
|
|
61
|
+
if (options.mask === true) {
|
|
62
|
+
showChar = 6
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (const key of Object.keys(parsed)) {
|
|
66
|
+
parsed[key] = mask(parsed[key], showChar)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
58
70
|
if (key) {
|
|
59
71
|
const single = parsed[key]
|
|
60
72
|
if (single === undefined) {
|
package/src/cli/actions/run.js
CHANGED
|
@@ -10,6 +10,9 @@ const normalizeDotenvConfigQuiet = require('../../lib/helpers/normalizeDotenvCon
|
|
|
10
10
|
const normalizeDotenvConfigConvention = require('../../lib/helpers/normalizeDotenvConfigConvention')
|
|
11
11
|
const buildCommandEnvs = require('../../lib/helpers/buildCommandEnvs')
|
|
12
12
|
const resolveEnvKeysFile = require('../../lib/helpers/resolveEnvKeysFile')
|
|
13
|
+
const mask = require('../../lib/helpers/mask')
|
|
14
|
+
const maskEnvSrc = require('../../lib/helpers/maskEnvSrc')
|
|
15
|
+
const maskProcessedEnvs = require('../../lib/helpers/maskProcessedEnvs')
|
|
13
16
|
|
|
14
17
|
const { determine } = require('./../../lib/helpers/envResolution')
|
|
15
18
|
|
|
@@ -48,6 +51,12 @@ function uniqueInjectedKeys (processedEnvs) {
|
|
|
48
51
|
|
|
49
52
|
async function run () {
|
|
50
53
|
const options = normalizeDotenvConfigConvention(normalizeDotenvConfigQuiet(this.opts()))
|
|
54
|
+
const maskEnabled = options.mask !== undefined
|
|
55
|
+
let showChar = options.mask
|
|
56
|
+
if (options.mask === true) {
|
|
57
|
+
showChar = 6
|
|
58
|
+
}
|
|
59
|
+
let commandEnv = process.env
|
|
51
60
|
|
|
52
61
|
let commandArgs = this.args
|
|
53
62
|
if (commandArgs.length < 1) {
|
|
@@ -56,7 +65,16 @@ async function run () {
|
|
|
56
65
|
|
|
57
66
|
const spinner = await createSpinner({ ...options, text: 'injecting' })
|
|
58
67
|
|
|
59
|
-
|
|
68
|
+
let debugOptions = options
|
|
69
|
+
if (maskEnabled) {
|
|
70
|
+
let token = options.token
|
|
71
|
+
if (options.token) {
|
|
72
|
+
token = mask(options.token, showChar)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
debugOptions = { ...options, env: (options.env || []).map(envSrc => maskEnvSrc(envSrc, showChar)), token }
|
|
76
|
+
}
|
|
77
|
+
logger.debug(`options: ${JSON.stringify(debugOptions)}`)
|
|
60
78
|
logger.debug(`process command [${commandArgs.join(' ')}]`)
|
|
61
79
|
|
|
62
80
|
const ignore = options.ignore || []
|
|
@@ -103,13 +121,22 @@ async function run () {
|
|
|
103
121
|
}
|
|
104
122
|
})
|
|
105
123
|
|
|
124
|
+
if (maskEnabled) {
|
|
125
|
+
commandEnv = { ...process.env }
|
|
126
|
+
maskProcessedEnvs(processedEnvs, commandEnv, showChar)
|
|
127
|
+
}
|
|
128
|
+
|
|
106
129
|
for (const processedEnv of processedEnvs) {
|
|
107
130
|
if (processedEnv.type === 'envFile') {
|
|
108
131
|
logger.verbose(`loading env from ${processedEnv.filepath} (${path.resolve(processedEnv.filepath)})`)
|
|
109
132
|
}
|
|
110
133
|
|
|
111
134
|
if (processedEnv.type === 'env') {
|
|
112
|
-
|
|
135
|
+
let envString = processedEnv.string
|
|
136
|
+
if (maskEnabled) {
|
|
137
|
+
envString = maskEnvSrc(processedEnv.string, showChar)
|
|
138
|
+
}
|
|
139
|
+
logger.verbose(`loading env from string (${envString})`)
|
|
113
140
|
}
|
|
114
141
|
|
|
115
142
|
for (const error of processedEnv.errors || []) {
|
|
@@ -161,7 +188,7 @@ async function run () {
|
|
|
161
188
|
process.exit(1)
|
|
162
189
|
}
|
|
163
190
|
|
|
164
|
-
await executeCommand(commandArgs,
|
|
191
|
+
await executeCommand(commandArgs, commandEnv)
|
|
165
192
|
}
|
|
166
193
|
|
|
167
194
|
module.exports = run
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const { logger } = require('../../../shared/logger')
|
|
2
|
+
const Session = require('../../../db/session')
|
|
3
|
+
const mask = require('../../../lib/helpers/mask')
|
|
4
|
+
|
|
5
|
+
function device () {
|
|
6
|
+
const options = this.opts()
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const value = new Session().devicePublicKey()
|
|
10
|
+
if (value && value.length > 1) {
|
|
11
|
+
console.log(options.unmask ? value : mask(value, 6))
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
logger.error('missing device. Try generating one with [dotenvx armor login].')
|
|
16
|
+
process.exit(1)
|
|
17
|
+
} catch (error) {
|
|
18
|
+
logger.error(error.message)
|
|
19
|
+
process.exit(1)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = device
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const { logger } = require('../../../shared/logger')
|
|
2
|
+
const Session = require('../../../db/session')
|
|
3
|
+
|
|
4
|
+
function hostname () {
|
|
5
|
+
try {
|
|
6
|
+
const value = new Session().hostname()
|
|
7
|
+
if (value && value.length > 1) {
|
|
8
|
+
console.log(value)
|
|
9
|
+
return
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
logger.error('missing hostname. Try running [dotenvx armor login].')
|
|
13
|
+
process.exit(1)
|
|
14
|
+
} catch (error) {
|
|
15
|
+
logger.error(error.message)
|
|
16
|
+
process.exit(1)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = hostname
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const { logger } = require('../../../shared/logger')
|
|
2
|
+
const Session = require('../../../db/session')
|
|
3
|
+
|
|
4
|
+
function off () {
|
|
5
|
+
try {
|
|
6
|
+
new Session().turnOff()
|
|
7
|
+
logger.success('✔ armor: off')
|
|
8
|
+
} catch (error) {
|
|
9
|
+
logger.error(error.message)
|
|
10
|
+
process.exit(1)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = off
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const { logger } = require('../../../shared/logger')
|
|
2
|
+
const Session = require('../../../db/session')
|
|
3
|
+
|
|
4
|
+
function on () {
|
|
5
|
+
try {
|
|
6
|
+
new Session().turnOn()
|
|
7
|
+
logger.success('✔ armor: on')
|
|
8
|
+
} catch (error) {
|
|
9
|
+
logger.error(error.message)
|
|
10
|
+
process.exit(1)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = on
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const { logger } = require('../../../shared/logger')
|
|
2
|
+
const Session = require('../../../db/session')
|
|
3
|
+
|
|
4
|
+
function path () {
|
|
5
|
+
try {
|
|
6
|
+
const value = new Session().path()
|
|
7
|
+
if (value && value.length > 1) {
|
|
8
|
+
console.log(value)
|
|
9
|
+
return
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
logger.error('missing path. Try running [dotenvx armor login].')
|
|
13
|
+
process.exit(1)
|
|
14
|
+
} catch (error) {
|
|
15
|
+
logger.error(error.message)
|
|
16
|
+
process.exit(1)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = path
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const { logger } = require('../../../shared/logger')
|
|
2
|
+
const Session = require('../../../db/session')
|
|
3
|
+
const mask = require('../../../lib/helpers/mask')
|
|
4
|
+
|
|
5
|
+
function token () {
|
|
6
|
+
const options = this.opts()
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const value = new Session().token()
|
|
10
|
+
if (value && value.length > 1) {
|
|
11
|
+
console.log(options.unmask ? value : mask(value))
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
logger.error('missing token. Try generating one with [dotenvx armor login].')
|
|
16
|
+
process.exit(1)
|
|
17
|
+
} catch (error) {
|
|
18
|
+
logger.error(error.message)
|
|
19
|
+
process.exit(1)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = token
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const { logger } = require('../../../shared/logger')
|
|
2
|
+
const Session = require('../../../db/session')
|
|
3
|
+
|
|
4
|
+
function username () {
|
|
5
|
+
try {
|
|
6
|
+
const value = new Session().username()
|
|
7
|
+
if (value) {
|
|
8
|
+
console.log(value)
|
|
9
|
+
return
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
logger.error('login required. Try running [dotenvx armor login].')
|
|
13
|
+
process.exit(1)
|
|
14
|
+
} catch (error) {
|
|
15
|
+
logger.error(error.message)
|
|
16
|
+
process.exit(1)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = username
|
package/src/cli/commands/lock.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
function configureLockCommand (lock) {
|
|
2
|
+
lock.hook('preAction', async () => {
|
|
3
|
+
const Session = require('./../../db/session')
|
|
4
|
+
const sesh = new Session()
|
|
5
|
+
await sesh.notifyUpdate()
|
|
6
|
+
})
|
|
7
|
+
|
|
2
8
|
lock
|
|
3
9
|
.description('lock private keys with a local passphrase')
|
|
4
10
|
.action(function () {
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
function configureNativeCommand (native) {
|
|
2
|
+
native.hook('preAction', async () => {
|
|
3
|
+
const Session = require('./../../db/session')
|
|
4
|
+
const sesh = new Session()
|
|
5
|
+
await sesh.notifyUpdate()
|
|
6
|
+
})
|
|
7
|
+
|
|
2
8
|
native
|
|
3
9
|
.description('move private keys into your OS secret store')
|
|
4
10
|
.action(function () {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
function configureSettingsCommand (settings) {
|
|
2
|
+
settings
|
|
3
|
+
.description('settings')
|
|
4
|
+
.action(function () {
|
|
5
|
+
this.help()
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
settings
|
|
9
|
+
.command('username')
|
|
10
|
+
.description('print your username')
|
|
11
|
+
.action(function (...args) {
|
|
12
|
+
return require('./../actions/settings/username').apply(this, args)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
settings
|
|
16
|
+
.command('token')
|
|
17
|
+
.description('print your access token (--unmask)')
|
|
18
|
+
.option('--unmask', 'unmask access token')
|
|
19
|
+
.action(function (...args) {
|
|
20
|
+
return require('./../actions/settings/token').apply(this, args)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
settings
|
|
24
|
+
.command('device')
|
|
25
|
+
.description('print your device pubkey (--unmask)')
|
|
26
|
+
.option('--unmask', 'unmask device pubkey')
|
|
27
|
+
.action(function (...args) {
|
|
28
|
+
return require('./../actions/settings/device').apply(this, args)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
settings
|
|
32
|
+
.command('hostname')
|
|
33
|
+
.description('print hostname')
|
|
34
|
+
.action(function (...args) {
|
|
35
|
+
return require('./../actions/settings/hostname').apply(this, args)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
settings
|
|
39
|
+
.command('path')
|
|
40
|
+
.description('print path to settings file')
|
|
41
|
+
.action(function (...args) {
|
|
42
|
+
return require('./../actions/settings/path').apply(this, args)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
settings
|
|
46
|
+
.command('on')
|
|
47
|
+
.description('turn armor on')
|
|
48
|
+
.action(function (...args) {
|
|
49
|
+
return require('./../actions/settings/on').apply(this, args)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
settings
|
|
53
|
+
.command('off')
|
|
54
|
+
.description('turn armor off')
|
|
55
|
+
.action(function (...args) {
|
|
56
|
+
return require('./../actions/settings/off').apply(this, args)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
return settings
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = configureSettingsCommand
|
package/src/cli/dotenvx.js
CHANGED
|
@@ -69,6 +69,7 @@ program.command('run')
|
|
|
69
69
|
.option('--convention <name>', 'load a .env convention (available conventions: [\'nextjs\', \'flow\'])')
|
|
70
70
|
.option('--ignore <errorCodes...>', 'error code(s) to ignore (example: --ignore=MISSING_ENV_FILE)')
|
|
71
71
|
.option('--token <token>', 'set Armor ⛨ token')
|
|
72
|
+
.option('--mask [characters]', 'inject masked values, optionally setting visible characters')
|
|
72
73
|
.option('--no-armor', 'disable Dotenvx Armor features')
|
|
73
74
|
.option('--no-native', 'disable OS secret store features')
|
|
74
75
|
.action(function (...args) {
|
|
@@ -89,6 +90,7 @@ program.command('get')
|
|
|
89
90
|
.option('--convention <name>', 'load a .env convention (available conventions: [\'nextjs\', \'flow\'])')
|
|
90
91
|
.option('--ignore <errorCodes...>', 'error code(s) to ignore (example: --ignore=MISSING_ENV_FILE)')
|
|
91
92
|
.option('-a, --all', 'include all machine envs as well')
|
|
93
|
+
.option('--mask [characters]', 'mask values, optionally setting visible characters')
|
|
92
94
|
.option('-pp, --pretty-print', 'pretty print output')
|
|
93
95
|
.option('--pp', 'pretty print output (alias)')
|
|
94
96
|
.option('--format <type>', 'format of the output (json, shell, colon, eval)', 'json')
|
|
@@ -146,6 +148,7 @@ program.command('decrypt')
|
|
|
146
148
|
.option('--no-armor', 'disable Dotenvx Armor features')
|
|
147
149
|
.option('--no-native', 'disable OS secret store features')
|
|
148
150
|
.option('--stdout', 'send to stdout')
|
|
151
|
+
.option('--mask [characters]', 'mask stdout values, optionally setting visible characters')
|
|
149
152
|
.action(function (...args) {
|
|
150
153
|
this.envs = envs
|
|
151
154
|
return require('./actions/decrypt').apply(this, args)
|
package/src/db/session.js
CHANGED
|
@@ -5,6 +5,7 @@ const { Conf, dotenv, envPaths } = require('@dotenvx/tooling')
|
|
|
5
5
|
const jsonToEnv = require('./../lib/helpers/jsonToEnv')
|
|
6
6
|
const packageJson = require('./../lib/helpers/packageJson')
|
|
7
7
|
const { http } = require('./../lib/helpers/http')
|
|
8
|
+
const nativeProvider = require('./../lib/providers/native')
|
|
8
9
|
const { logger } = require('./../shared/logger')
|
|
9
10
|
|
|
10
11
|
const HOURS_24 = 60 * 60 * 24 * 1000
|
|
@@ -14,7 +15,8 @@ const ARMOR = {
|
|
|
14
15
|
HOSTNAME: 'DOTENVX_ARMOR_HOSTNAME',
|
|
15
16
|
USER: 'DOTENVX_ARMOR_USER',
|
|
16
17
|
USERNAME: 'DOTENVX_ARMOR_USERNAME',
|
|
17
|
-
TOKEN: 'DOTENVX_ARMOR_TOKEN'
|
|
18
|
+
TOKEN: 'DOTENVX_ARMOR_TOKEN',
|
|
19
|
+
ON: 'DOTENVX_ARMOR_ON'
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
const DOTENVX = {
|
|
@@ -100,7 +102,7 @@ class Session {
|
|
|
100
102
|
|
|
101
103
|
status () {
|
|
102
104
|
// if logged in
|
|
103
|
-
if (this.username() && this.token()) {
|
|
105
|
+
if (this.username() && this.token() && this.on()) {
|
|
104
106
|
return 'on'
|
|
105
107
|
}
|
|
106
108
|
|
|
@@ -133,7 +135,15 @@ class Session {
|
|
|
133
135
|
}
|
|
134
136
|
|
|
135
137
|
token () {
|
|
136
|
-
return this.
|
|
138
|
+
return this.getFromSecretStore(ARMOR.TOKEN) || undefined
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
on () {
|
|
142
|
+
return (this.readSetting('ON') || 'true') === 'true'
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
off () {
|
|
146
|
+
return (this.readSetting('ON') || 'true') === 'false'
|
|
137
147
|
}
|
|
138
148
|
|
|
139
149
|
devicePublicKey () {
|
|
@@ -187,7 +197,7 @@ class Session {
|
|
|
187
197
|
store.set(DOTENVX.VERSION_LAST_CHECK, now)
|
|
188
198
|
|
|
189
199
|
if (versionGreaterThan(remote, packageJson.version)) {
|
|
190
|
-
console.error('⛆ update available [
|
|
200
|
+
console.error('⛆ update available [curl -sfS https://dotenvx.sh | sh]')
|
|
191
201
|
}
|
|
192
202
|
} catch (error) {
|
|
193
203
|
logger.debug(error.message)
|
|
@@ -222,6 +232,47 @@ class Session {
|
|
|
222
232
|
//
|
|
223
233
|
// Set/Delete
|
|
224
234
|
//
|
|
235
|
+
getFromSecretStore (key) {
|
|
236
|
+
try {
|
|
237
|
+
const value = nativeProvider.get(key)
|
|
238
|
+
if (value) return value
|
|
239
|
+
} catch {}
|
|
240
|
+
|
|
241
|
+
const store = this.openStore()
|
|
242
|
+
if (!store) return undefined
|
|
243
|
+
|
|
244
|
+
return store.get(key)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
setInSecretStore (key, value) {
|
|
248
|
+
try {
|
|
249
|
+
nativeProvider.set(key, value)
|
|
250
|
+
return value
|
|
251
|
+
} catch {
|
|
252
|
+
this.createStore().set(key, value)
|
|
253
|
+
return value
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
deleteFromSecretStore (key) {
|
|
258
|
+
try {
|
|
259
|
+
nativeProvider.delete(key)
|
|
260
|
+
} catch {}
|
|
261
|
+
|
|
262
|
+
const store = this.openStore()
|
|
263
|
+
if (store) store.delete(key)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
turnOn () {
|
|
267
|
+
this.createStore().set(ARMOR.ON, 'true')
|
|
268
|
+
return 'true'
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
turnOff () {
|
|
272
|
+
this.createStore().set(ARMOR.ON, 'false')
|
|
273
|
+
return 'false'
|
|
274
|
+
}
|
|
275
|
+
|
|
225
276
|
login (hostname, id, username, accessToken) {
|
|
226
277
|
if (!hostname) {
|
|
227
278
|
throw new Error('DOTENVX_ARMOR_HOSTNAME not set. Run [dotenvx armor login]')
|
|
@@ -242,8 +293,9 @@ class Session {
|
|
|
242
293
|
const store = this.createStore()
|
|
243
294
|
store.set(ARMOR.USER, id)
|
|
244
295
|
store.set(ARMOR.USERNAME, username)
|
|
245
|
-
|
|
296
|
+
this.setInSecretStore(ARMOR.TOKEN, accessToken)
|
|
246
297
|
store.set(ARMOR.HOSTNAME, hostname)
|
|
298
|
+
store.set(ARMOR.ON, 'true')
|
|
247
299
|
|
|
248
300
|
return accessToken
|
|
249
301
|
}
|
|
@@ -261,13 +313,14 @@ class Session {
|
|
|
261
313
|
throw new Error('DOTENVX_ARMOR_TOKEN not set. Run [dotenvx armor login]')
|
|
262
314
|
}
|
|
263
315
|
|
|
316
|
+
this.deleteFromSecretStore(ARMOR.TOKEN)
|
|
317
|
+
|
|
264
318
|
const store = this.openStore()
|
|
265
319
|
if (!store) return true
|
|
266
|
-
|
|
267
320
|
store.delete(ARMOR.USER)
|
|
268
321
|
store.delete(ARMOR.USERNAME)
|
|
269
|
-
store.delete(ARMOR.TOKEN)
|
|
270
322
|
store.delete(ARMOR.HOSTNAME)
|
|
323
|
+
store.delete(ARMOR.ON)
|
|
271
324
|
store.delete(DOTENVX.VERSION)
|
|
272
325
|
store.delete(DOTENVX.VERSION_LAST_CHECK)
|
|
273
326
|
return true
|
|
@@ -7,7 +7,7 @@ function attributes (publicKey) {
|
|
|
7
7
|
return ['service', SERVICE, 'public-key', publicKey]
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
function
|
|
10
|
+
function get (publicKey) {
|
|
11
11
|
try {
|
|
12
12
|
return execFileSync(SECRET_TOOL_BIN, ['lookup', ...attributes(publicKey)], {
|
|
13
13
|
encoding: 'utf8',
|
|
@@ -18,7 +18,7 @@ function findGenericPassword (publicKey) {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
function
|
|
21
|
+
function set (publicKey, privateKey, label) {
|
|
22
22
|
try {
|
|
23
23
|
execFileSync(SECRET_TOOL_BIN, ['store', `--label=${label}`, ...attributes(publicKey)], {
|
|
24
24
|
input: privateKey,
|
|
@@ -30,14 +30,16 @@ function addGenericPassword (publicKey, label, privateKey) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
module.exports = {
|
|
34
|
+
get,
|
|
35
|
+
set,
|
|
36
|
+
delete (publicKey) {
|
|
37
|
+
try {
|
|
38
|
+
execFileSync(SECRET_TOOL_BIN, ['clear', ...attributes(publicKey)], {
|
|
39
|
+
stdio: ['ignore', 'ignore', 'pipe']
|
|
40
|
+
})
|
|
41
|
+
} catch {
|
|
42
|
+
throw new Error('failed to delete private key from Linux Secret Service')
|
|
43
|
+
}
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
|
-
|
|
43
|
-
module.exports = { findGenericPassword, addGenericPassword, deleteGenericPassword }
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const { execFileSync } = require('child_process')
|
|
2
|
+
|
|
3
|
+
const SECURITY_BIN = '/usr/bin/security'
|
|
4
|
+
const SERVICE = 'dotenvx'
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
get (key) {
|
|
8
|
+
return execFileSync(SECURITY_BIN, ['find-generic-password', '-s', SERVICE, '-a', key, '-w'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
set (key, value, label) {
|
|
12
|
+
try {
|
|
13
|
+
execFileSync(SECURITY_BIN, ['add-generic-password', '-U', '-s', SERVICE, '-a', key, '-l', label, '-w', value], { stdio: 'ignore' })
|
|
14
|
+
} catch {
|
|
15
|
+
throw new Error('failed to save private key to macOS Keychain')
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
delete (key) {
|
|
20
|
+
try {
|
|
21
|
+
execFileSync(SECURITY_BIN, ['delete-generic-password', '-s', SERVICE, '-a', key], { stdio: 'ignore' })
|
|
22
|
+
} catch {
|
|
23
|
+
throw new Error('failed to delete private key from macOS Keychain')
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
function mask (str, showChar = 6) {
|
|
2
|
+
if (!str || str.length < 1) {
|
|
3
|
+
return ''
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const number = Number(showChar)
|
|
7
|
+
showChar = 0
|
|
8
|
+
if (Number.isFinite(number)) {
|
|
9
|
+
showChar = Math.abs(Math.trunc(number))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let visibleChars = Math.min(showChar, Math.ceil(str.length / 2))
|
|
13
|
+
if (str.length === 1) {
|
|
14
|
+
visibleChars = 0
|
|
15
|
+
}
|
|
16
|
+
const visiblePart = str.slice(0, visibleChars)
|
|
17
|
+
const maskedPart = '*'.repeat(str.length - visibleChars)
|
|
18
|
+
|
|
19
|
+
return visiblePart + maskedPart
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = mask
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const { scan, upsert } = require('@dotenvx/primitives')
|
|
2
|
+
|
|
3
|
+
const mask = require('./mask')
|
|
4
|
+
|
|
5
|
+
function maskEnvSrc (envSrc, showChar) {
|
|
6
|
+
const { parsed } = scan(envSrc)
|
|
7
|
+
|
|
8
|
+
for (const [key, values] of Object.entries(parsed)) {
|
|
9
|
+
envSrc = upsert(envSrc, key, values.map(value => mask(value, showChar)))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return envSrc
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = maskEnvSrc
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const mask = require('./mask')
|
|
2
|
+
|
|
3
|
+
function maskProcessedEnvs (processedEnvs, processEnv, showChar) {
|
|
4
|
+
// Track every key resolved from the requested env sources so the same keys
|
|
5
|
+
// can also be masked in the environment receiving the resolved values.
|
|
6
|
+
const resolvedKeys = new Set()
|
|
7
|
+
|
|
8
|
+
for (const processedEnv of processedEnvs) {
|
|
9
|
+
for (const key of Object.keys(processedEnv.parsed || {})) {
|
|
10
|
+
resolvedKeys.add(key)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// These values may be logged, so mask each resolver reporting bucket.
|
|
14
|
+
for (const values of [processedEnv.parsed, processedEnv.injected, processedEnv.existed]) {
|
|
15
|
+
for (const key of Object.keys(values || {})) {
|
|
16
|
+
values[key] = mask(values[key], showChar)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Leave unrelated inherited environment variables unchanged.
|
|
22
|
+
for (const key of resolvedKeys) {
|
|
23
|
+
if (processEnv[key] !== undefined) {
|
|
24
|
+
processEnv[key] = mask(processEnv[key], showChar)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = maskProcessedEnvs
|
|
@@ -134,7 +134,7 @@ function run (payload) {
|
|
|
134
134
|
})
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
function
|
|
137
|
+
function get (publicKey) {
|
|
138
138
|
try {
|
|
139
139
|
return run({ action: 'read', target: target(publicKey) }).trim() || null
|
|
140
140
|
} catch {
|
|
@@ -142,7 +142,7 @@ function findGenericPassword (publicKey) {
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
function
|
|
145
|
+
function set (publicKey, privateKey) {
|
|
146
146
|
try {
|
|
147
147
|
run({ action: 'write', target: target(publicKey), username: publicKey, secret: privateKey })
|
|
148
148
|
} catch {
|
|
@@ -150,12 +150,14 @@ function addGenericPassword (publicKey, privateKey) {
|
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
module.exports = {
|
|
154
|
+
get,
|
|
155
|
+
set,
|
|
156
|
+
delete (publicKey) {
|
|
157
|
+
try {
|
|
158
|
+
run({ action: 'delete', target: target(publicKey) })
|
|
159
|
+
} catch {
|
|
160
|
+
throw new Error('failed to delete private key from Windows Credential Manager')
|
|
161
|
+
}
|
|
158
162
|
}
|
|
159
163
|
}
|
|
160
|
-
|
|
161
|
-
module.exports = { findGenericPassword, addGenericPassword, deleteGenericPassword }
|
package/src/lib/main.d.ts
CHANGED
|
@@ -114,6 +114,14 @@ export interface DotenvConfigOptions {
|
|
|
114
114
|
*/
|
|
115
115
|
processEnv?: DotenvPopulateInput;
|
|
116
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Mask resolved values, optionally setting the number of visible characters.
|
|
119
|
+
* @default false
|
|
120
|
+
* @example require('@dotenvx/dotenvx').config({ mask: true })
|
|
121
|
+
* @example require('@dotenvx/dotenvx').config({ mask: 0 })
|
|
122
|
+
*/
|
|
123
|
+
mask?: boolean | number;
|
|
124
|
+
|
|
117
125
|
/**
|
|
118
126
|
* Customize the path to your .env.keys file. This is useful with monorepos.
|
|
119
127
|
* @default []
|
|
@@ -309,6 +317,14 @@ export function set(
|
|
|
309
317
|
): Promise<SetOutput>;
|
|
310
318
|
|
|
311
319
|
export interface GetOptions {
|
|
320
|
+
/**
|
|
321
|
+
* Mask returned values, optionally setting the number of visible characters.
|
|
322
|
+
* @default false
|
|
323
|
+
* @example require('@dotenvx/dotenvx').get('KEY', { mask: true })
|
|
324
|
+
* @example require('@dotenvx/dotenvx').get('KEY', { mask: 0 })
|
|
325
|
+
*/
|
|
326
|
+
mask?: boolean | number;
|
|
327
|
+
|
|
312
328
|
/**
|
|
313
329
|
* Suppress specific errors like MISSING_ENV_FILE. The error keys can be found
|
|
314
330
|
* in src/lib/helpers/errors.js
|
package/src/lib/main.js
CHANGED
|
@@ -25,6 +25,8 @@ const decryptKeyValue = require('./helpers/cryptography/decryptKeyValue')
|
|
|
25
25
|
const Errors = require('./helpers/errors')
|
|
26
26
|
const normalizeDotenvConfigQuiet = require('./helpers/normalizeDotenvConfigQuiet')
|
|
27
27
|
const normalizeDotenvConfigConvention = require('./helpers/normalizeDotenvConfigConvention')
|
|
28
|
+
const mask = require('./helpers/mask')
|
|
29
|
+
const maskProcessedEnvs = require('./helpers/maskProcessedEnvs')
|
|
28
30
|
|
|
29
31
|
function uniqueInjectedKeys (processedEnvs) {
|
|
30
32
|
const result = new Set()
|
|
@@ -88,6 +90,11 @@ const config = function (options = {}) {
|
|
|
88
90
|
token: options.token
|
|
89
91
|
})
|
|
90
92
|
|
|
93
|
+
if (options.mask !== undefined) {
|
|
94
|
+
const showChar = options.mask === true ? 6 : options.mask
|
|
95
|
+
maskProcessedEnvs(processedEnvs, processEnv, showChar)
|
|
96
|
+
}
|
|
97
|
+
|
|
91
98
|
let lastError
|
|
92
99
|
/** @type {Record<string, string>} */
|
|
93
100
|
const parsedAll = {}
|
|
@@ -326,6 +333,13 @@ const get = async function (key, options = {}) {
|
|
|
326
333
|
noKeychain
|
|
327
334
|
})
|
|
328
335
|
|
|
336
|
+
if (options.mask !== undefined) {
|
|
337
|
+
const showChar = options.mask === true ? 6 : options.mask
|
|
338
|
+
for (const key of Object.keys(parsed)) {
|
|
339
|
+
parsed[key] = mask(parsed[key], showChar)
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
329
343
|
for (const error of errors || []) {
|
|
330
344
|
if (ignore.includes(error.code)) {
|
|
331
345
|
continue // ignore error
|
|
@@ -1,27 +1,52 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
1
|
+
const macosKeychain = require('../../helpers/macosKeychain')
|
|
3
2
|
const windowsCredentialManager = require('../../helpers/windowsCredentialManager')
|
|
4
3
|
const linuxSecretService = require('../../helpers/linuxSecretService')
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
function get (key) {
|
|
6
|
+
if (process.platform === 'win32') {
|
|
7
|
+
return windowsCredentialManager.get(key)
|
|
8
|
+
}
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
if (process.platform === 'linux') {
|
|
11
|
+
return linuxSecretService.get(key)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return macosKeychain.get(key)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function set (key, value, label = key) {
|
|
18
|
+
if (process.platform === 'win32') {
|
|
19
|
+
windowsCredentialManager.set(key, value, label)
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (process.platform === 'linux') {
|
|
24
|
+
linuxSecretService.set(key, value, label)
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
macosKeychain.set(key, value, label)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
index.delete = function (key) {
|
|
32
|
+
if (process.platform === 'win32') {
|
|
33
|
+
windowsCredentialManager.delete(key)
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (process.platform === 'linux') {
|
|
38
|
+
linuxSecretService.delete(key)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
macosKeychain.delete(key)
|
|
11
43
|
}
|
|
12
44
|
|
|
13
45
|
function index (publicKeyHex) {
|
|
14
46
|
if (!['darwin', 'linux', 'win32'].includes(process.platform)) return {}
|
|
15
47
|
|
|
16
48
|
try {
|
|
17
|
-
|
|
18
|
-
if (process.platform === 'win32') {
|
|
19
|
-
privateKeyHex = windowsCredentialManager.findGenericPassword(publicKeyHex)
|
|
20
|
-
} else if (process.platform === 'linux') {
|
|
21
|
-
privateKeyHex = linuxSecretService.findGenericPassword(publicKeyHex)
|
|
22
|
-
} else {
|
|
23
|
-
privateKeyHex = findMacosPrivateKey(publicKeyHex)
|
|
24
|
-
}
|
|
49
|
+
const privateKeyHex = get(publicKeyHex)
|
|
25
50
|
|
|
26
51
|
if (!privateKeyHex) return {}
|
|
27
52
|
|
|
@@ -31,4 +56,7 @@ function index (publicKeyHex) {
|
|
|
31
56
|
}
|
|
32
57
|
}
|
|
33
58
|
|
|
59
|
+
index.set = set
|
|
60
|
+
index.get = get
|
|
61
|
+
|
|
34
62
|
module.exports = index
|
|
@@ -1,40 +1,8 @@
|
|
|
1
|
-
const { execFileSync } = require('child_process')
|
|
2
|
-
|
|
3
1
|
const keynames = require('../conventions/keynames')
|
|
4
2
|
const readEnvKey = require('../helpers/readEnvKey')
|
|
5
3
|
const upsertEnvKey = require('../helpers/upsertEnvKey')
|
|
6
4
|
const armoredKeyDisplay = require('../helpers/armoredKeyDisplay')
|
|
7
|
-
const
|
|
8
|
-
const linuxSecretService = require('../helpers/linuxSecretService')
|
|
9
|
-
|
|
10
|
-
const SECURITY_BIN = '/usr/bin/security'
|
|
11
|
-
const SERVICE = 'dotenvx'
|
|
12
|
-
|
|
13
|
-
function findGenericPassword (publicKey) {
|
|
14
|
-
if (process.platform === 'win32') {
|
|
15
|
-
return windowsCredentialManager.findGenericPassword(publicKey)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (process.platform === 'linux') {
|
|
19
|
-
return linuxSecretService.findGenericPassword(publicKey)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return execFileSync(SECURITY_BIN, ['find-generic-password', '-s', SERVICE, '-a', publicKey, '-w'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function deleteGenericPassword (publicKey) {
|
|
26
|
-
if (process.platform === 'win32') {
|
|
27
|
-
windowsCredentialManager.deleteGenericPassword(publicKey)
|
|
28
|
-
return
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (process.platform === 'linux') {
|
|
32
|
-
linuxSecretService.deleteGenericPassword(publicKey)
|
|
33
|
-
return
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
execFileSync(SECURITY_BIN, ['delete-generic-password', '-s', SERVICE, '-a', publicKey], { stdio: 'ignore' })
|
|
37
|
-
}
|
|
5
|
+
const nativeProvider = require('../providers/native')
|
|
38
6
|
|
|
39
7
|
class KeychainDown {
|
|
40
8
|
constructor (envFile = '.env', envKeysFile = '.env.keys') {
|
|
@@ -55,7 +23,7 @@ class KeychainDown {
|
|
|
55
23
|
let privateKey
|
|
56
24
|
|
|
57
25
|
try {
|
|
58
|
-
privateKey =
|
|
26
|
+
privateKey = nativeProvider.get(publicKey)
|
|
59
27
|
if (!privateKey) {
|
|
60
28
|
const secretStore = process.platform === 'win32'
|
|
61
29
|
? 'Windows Credential Manager'
|
|
@@ -78,7 +46,7 @@ class KeychainDown {
|
|
|
78
46
|
}
|
|
79
47
|
|
|
80
48
|
upsertEnvKey(privateKeyName, privateKey, envKeysFile)
|
|
81
|
-
|
|
49
|
+
nativeProvider.delete(publicKey)
|
|
82
50
|
|
|
83
51
|
return {
|
|
84
52
|
changed: true,
|
|
@@ -1,26 +1,8 @@
|
|
|
1
|
-
const { execFileSync } = require('child_process')
|
|
2
|
-
|
|
3
1
|
const keynames = require('../conventions/keynames')
|
|
4
2
|
const readEnvKey = require('../helpers/readEnvKey')
|
|
5
3
|
const upsertEnvKey = require('../helpers/upsertEnvKey')
|
|
6
4
|
const armoredKeyDisplay = require('../helpers/armoredKeyDisplay')
|
|
7
|
-
const
|
|
8
|
-
const linuxSecretService = require('../helpers/linuxSecretService')
|
|
9
|
-
|
|
10
|
-
const SECURITY_BIN = '/usr/bin/security'
|
|
11
|
-
const SERVICE = 'dotenvx'
|
|
12
|
-
|
|
13
|
-
function findGenericPassword (publicKey) {
|
|
14
|
-
if (process.platform === 'win32') {
|
|
15
|
-
return windowsCredentialManager.findGenericPassword(publicKey)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (process.platform === 'linux') {
|
|
19
|
-
return linuxSecretService.findGenericPassword(publicKey)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return execFileSync(SECURITY_BIN, ['find-generic-password', '-s', SERVICE, '-a', publicKey, '-w'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
|
|
23
|
-
}
|
|
5
|
+
const nativeProvider = require('../providers/native')
|
|
24
6
|
|
|
25
7
|
class KeychainPull {
|
|
26
8
|
constructor (envFile = '.env', envKeysFile = '.env.keys') {
|
|
@@ -41,7 +23,7 @@ class KeychainPull {
|
|
|
41
23
|
let privateKey
|
|
42
24
|
|
|
43
25
|
try {
|
|
44
|
-
privateKey =
|
|
26
|
+
privateKey = nativeProvider.get(publicKey)
|
|
45
27
|
if (!privateKey) throw new Error('not found')
|
|
46
28
|
} catch {
|
|
47
29
|
const secretStore = process.platform === 'win32'
|
|
@@ -1,43 +1,7 @@
|
|
|
1
|
-
const { execFileSync } = require('child_process')
|
|
2
|
-
|
|
3
1
|
const keynames = require('../conventions/keynames')
|
|
4
2
|
const readEnvKey = require('../helpers/readEnvKey')
|
|
5
3
|
const armoredKeyDisplay = require('../helpers/armoredKeyDisplay')
|
|
6
|
-
const
|
|
7
|
-
const linuxSecretService = require('../helpers/linuxSecretService')
|
|
8
|
-
|
|
9
|
-
const SECURITY_BIN = '/usr/bin/security'
|
|
10
|
-
const SERVICE = 'dotenvx'
|
|
11
|
-
|
|
12
|
-
function addGenericPassword (publicKey, label, privateKey) {
|
|
13
|
-
if (process.platform === 'win32') {
|
|
14
|
-
windowsCredentialManager.addGenericPassword(publicKey, privateKey)
|
|
15
|
-
return
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (process.platform === 'linux') {
|
|
19
|
-
linuxSecretService.addGenericPassword(publicKey, label, privateKey)
|
|
20
|
-
return
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
execFileSync(SECURITY_BIN, ['add-generic-password', '-U', '-s', SERVICE, '-a', publicKey, '-l', label, '-w', privateKey], { stdio: 'ignore' })
|
|
25
|
-
} catch {
|
|
26
|
-
throw new Error('failed to save private key to macOS Keychain')
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function findGenericPassword (publicKey) {
|
|
31
|
-
if (process.platform === 'win32') {
|
|
32
|
-
return windowsCredentialManager.findGenericPassword(publicKey)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (process.platform === 'linux') {
|
|
36
|
-
return linuxSecretService.findGenericPassword(publicKey)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return execFileSync(SECURITY_BIN, ['find-generic-password', '-s', SERVICE, '-a', publicKey, '-w'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
|
|
40
|
-
}
|
|
4
|
+
const nativeProvider = require('../providers/native')
|
|
41
5
|
|
|
42
6
|
class KeychainPush {
|
|
43
7
|
constructor (envFile = '.env', envKeysFile = '.env.keys') {
|
|
@@ -59,7 +23,7 @@ class KeychainPush {
|
|
|
59
23
|
const label = `dotenvx (${armoredKeyDisplay(publicKey)})`
|
|
60
24
|
|
|
61
25
|
try {
|
|
62
|
-
const existingPrivateKey =
|
|
26
|
+
const existingPrivateKey = nativeProvider.get(publicKey)
|
|
63
27
|
|
|
64
28
|
if (existingPrivateKey === privateKey) {
|
|
65
29
|
return {
|
|
@@ -72,7 +36,7 @@ class KeychainPush {
|
|
|
72
36
|
}
|
|
73
37
|
} catch {}
|
|
74
38
|
|
|
75
|
-
|
|
39
|
+
nativeProvider.set(publicKey, privateKey, label)
|
|
76
40
|
|
|
77
41
|
return {
|
|
78
42
|
changed: true,
|
|
@@ -1,44 +1,8 @@
|
|
|
1
|
-
const { execFileSync } = require('child_process')
|
|
2
|
-
|
|
3
1
|
const keynames = require('../conventions/keynames')
|
|
4
2
|
const readEnvKey = require('../helpers/readEnvKey')
|
|
5
3
|
const removeEnvKey = require('../helpers/removeEnvKey')
|
|
6
4
|
const armoredKeyDisplay = require('../helpers/armoredKeyDisplay')
|
|
7
|
-
const
|
|
8
|
-
const linuxSecretService = require('../helpers/linuxSecretService')
|
|
9
|
-
|
|
10
|
-
const SECURITY_BIN = '/usr/bin/security'
|
|
11
|
-
const SERVICE = 'dotenvx'
|
|
12
|
-
|
|
13
|
-
function addGenericPassword (publicKey, label, privateKey) {
|
|
14
|
-
if (process.platform === 'win32') {
|
|
15
|
-
windowsCredentialManager.addGenericPassword(publicKey, privateKey)
|
|
16
|
-
return
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (process.platform === 'linux') {
|
|
20
|
-
linuxSecretService.addGenericPassword(publicKey, label, privateKey)
|
|
21
|
-
return
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
execFileSync(SECURITY_BIN, ['add-generic-password', '-U', '-s', SERVICE, '-a', publicKey, '-l', label, '-w', privateKey], { stdio: 'ignore' })
|
|
26
|
-
} catch {
|
|
27
|
-
throw new Error('failed to save private key to macOS Keychain')
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function findGenericPassword (publicKey) {
|
|
32
|
-
if (process.platform === 'win32') {
|
|
33
|
-
return windowsCredentialManager.findGenericPassword(publicKey)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (process.platform === 'linux') {
|
|
37
|
-
return linuxSecretService.findGenericPassword(publicKey)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return execFileSync(SECURITY_BIN, ['find-generic-password', '-s', SERVICE, '-a', publicKey, '-w'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
|
|
41
|
-
}
|
|
5
|
+
const nativeProvider = require('../providers/native')
|
|
42
6
|
|
|
43
7
|
class KeychainUp {
|
|
44
8
|
constructor (envFile = '.env', envKeysFile = '.env.keys') {
|
|
@@ -60,7 +24,7 @@ class KeychainUp {
|
|
|
60
24
|
let existingPrivateKey
|
|
61
25
|
|
|
62
26
|
try {
|
|
63
|
-
existingPrivateKey =
|
|
27
|
+
existingPrivateKey = nativeProvider.get(publicKey)
|
|
64
28
|
} catch {}
|
|
65
29
|
|
|
66
30
|
const privateKey = readEnvKey(privateKeyName, envKeysFile, { strict: !existingPrivateKey })
|
|
@@ -89,7 +53,7 @@ class KeychainUp {
|
|
|
89
53
|
}
|
|
90
54
|
}
|
|
91
55
|
|
|
92
|
-
|
|
56
|
+
nativeProvider.set(publicKey, privateKey, label)
|
|
93
57
|
removeEnvKey(privateKeyName, envKeysFile)
|
|
94
58
|
|
|
95
59
|
return {
|