@dotenvx/dotenvx 2.4.1 → 2.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/CHANGELOG.md +7 -1
- package/README.md +3 -3
- package/package.json +1 -1
- package/src/cli/commands/native.js +1 -1
- package/src/cli/dotenvx.js +1 -1
- package/src/lib/helpers/windowsCredentialManager.js +161 -0
- package/src/lib/providers/index.js +7 -7
- package/src/lib/providers/native/index.js +28 -0
- package/src/lib/services/keychainDown.js +25 -2
- package/src/lib/services/keychainPull.js +13 -2
- package/src/lib/services/keychainPush.js +15 -1
- package/src/lib/services/keychainUp.js +15 -1
- package/src/lib/providers/keychain/index.js +0 -20
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,13 @@
|
|
|
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.5.0...main)
|
|
6
|
+
|
|
7
|
+
## [2.5.0](https://github.com/dotenvx/dotenvx/compare/v2.4.2...v2.5.0) (2026-07-11)
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
* Support `native up|down|pull|push` for windows ([#833](https://github.com/dotenvx/dotenvx/pull/883))
|
|
6
12
|
|
|
7
13
|
## [2.4.1](https://github.com/dotenvx/dotenvx/compare/v2.4.0...v2.4.1) (2026-07-10)
|
|
8
14
|
|
package/README.md
CHANGED
|
@@ -2482,9 +2482,9 @@ $ dotenvx lock down
|
|
|
2482
2482
|
</details>
|
|
2483
2483
|
<details><summary>`native`</summary><br>
|
|
2484
2484
|
|
|
2485
|
-
Move private keys into your OS secret store
|
|
2485
|
+
Move private keys into your OS secret store.
|
|
2486
2486
|
|
|
2487
|
-
|
|
2487
|
+
Native commands support macOS Keychain and Windows Credential Manager.
|
|
2488
2488
|
|
|
2489
2489
|
</details>
|
|
2490
2490
|
<details><summary>`native up`</summary><br>
|
|
@@ -2646,7 +2646,7 @@ Commands:
|
|
|
2646
2646
|
|
|
2647
2647
|
Professional Security:
|
|
2648
2648
|
lock ⊡ lock private keys with a local passphrase
|
|
2649
|
-
native ⌥ move private keys into your OS secret store (macOS
|
|
2649
|
+
native ⌥ move private keys into your OS secret store (macOS and Windows supported)
|
|
2650
2650
|
armor ⛨ move private keys into Dotenvx Armor [www.dotenvx.com/armor]
|
|
2651
2651
|
```
|
|
2652
2652
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
function configureNativeCommand (native) {
|
|
2
2
|
native
|
|
3
|
-
.description('move private keys into your OS secret store (macOS
|
|
3
|
+
.description('move private keys into your OS secret store (macOS and Windows supported)')
|
|
4
4
|
.action(function () {
|
|
5
5
|
this.help()
|
|
6
6
|
})
|
package/src/cli/dotenvx.js
CHANGED
|
@@ -260,7 +260,7 @@ program.command('help [command]')
|
|
|
260
260
|
program.addHelpText('after', ' ')
|
|
261
261
|
program.addHelpText('after', 'Professional Security: ')
|
|
262
262
|
program.addHelpText('after', ' lock ⊡ lock private keys with a local passphrase')
|
|
263
|
-
program.addHelpText('after', ' native ⌥ move private keys into your OS secret store (macOS
|
|
263
|
+
program.addHelpText('after', ' native ⌥ move private keys into your OS secret store (macOS and Windows supported)')
|
|
264
264
|
program.addHelpText('after', ' armor ⛨ move private keys into Dotenvx Armor [www.dotenvx.com/armor]')
|
|
265
265
|
|
|
266
266
|
// dotenvx native
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const { execFileSync } = require('child_process')
|
|
2
|
+
|
|
3
|
+
const POWERSHELL_BIN = 'powershell.exe'
|
|
4
|
+
const SERVICE = 'dotenvx'
|
|
5
|
+
|
|
6
|
+
const script = `
|
|
7
|
+
$ErrorActionPreference = 'Stop'
|
|
8
|
+
|
|
9
|
+
Add-Type -TypeDefinition @'
|
|
10
|
+
using System;
|
|
11
|
+
using System.ComponentModel;
|
|
12
|
+
using System.Runtime.InteropServices;
|
|
13
|
+
using System.Text;
|
|
14
|
+
|
|
15
|
+
public static class DotenvxCredentialManager
|
|
16
|
+
{
|
|
17
|
+
private const uint CRED_TYPE_GENERIC = 1;
|
|
18
|
+
private const uint CRED_PERSIST_LOCAL_MACHINE = 2;
|
|
19
|
+
private const int ERROR_NOT_FOUND = 1168;
|
|
20
|
+
|
|
21
|
+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
22
|
+
private struct Credential
|
|
23
|
+
{
|
|
24
|
+
public uint Flags;
|
|
25
|
+
public uint Type;
|
|
26
|
+
public string TargetName;
|
|
27
|
+
public string Comment;
|
|
28
|
+
public long LastWritten;
|
|
29
|
+
public uint CredentialBlobSize;
|
|
30
|
+
public IntPtr CredentialBlob;
|
|
31
|
+
public uint Persist;
|
|
32
|
+
public uint AttributeCount;
|
|
33
|
+
public IntPtr Attributes;
|
|
34
|
+
public string TargetAlias;
|
|
35
|
+
public string UserName;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
[DllImport("Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
39
|
+
private static extern bool CredWrite(ref Credential credential, uint flags);
|
|
40
|
+
|
|
41
|
+
[DllImport("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
42
|
+
private static extern bool CredRead(string target, uint type, uint flags, out IntPtr credentialPtr);
|
|
43
|
+
|
|
44
|
+
[DllImport("Advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
45
|
+
private static extern bool CredDelete(string target, uint type, uint flags);
|
|
46
|
+
|
|
47
|
+
[DllImport("Advapi32.dll", SetLastError = true)]
|
|
48
|
+
private static extern void CredFree(IntPtr credentialPtr);
|
|
49
|
+
|
|
50
|
+
public static string Read(string target)
|
|
51
|
+
{
|
|
52
|
+
IntPtr credentialPtr;
|
|
53
|
+
if (!CredRead(target, CRED_TYPE_GENERIC, 0, out credentialPtr))
|
|
54
|
+
{
|
|
55
|
+
int error = Marshal.GetLastWin32Error();
|
|
56
|
+
if (error == ERROR_NOT_FOUND) return null;
|
|
57
|
+
throw new Win32Exception(error);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try
|
|
61
|
+
{
|
|
62
|
+
Credential credential = (Credential)Marshal.PtrToStructure(credentialPtr, typeof(Credential));
|
|
63
|
+
if (credential.CredentialBlob == IntPtr.Zero || credential.CredentialBlobSize == 0) return null;
|
|
64
|
+
return Marshal.PtrToStringUni(credential.CredentialBlob, (int)credential.CredentialBlobSize / 2);
|
|
65
|
+
}
|
|
66
|
+
finally
|
|
67
|
+
{
|
|
68
|
+
CredFree(credentialPtr);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public static void Write(string target, string username, string secret)
|
|
73
|
+
{
|
|
74
|
+
IntPtr secretPtr = Marshal.StringToCoTaskMemUni(secret);
|
|
75
|
+
try
|
|
76
|
+
{
|
|
77
|
+
Credential credential = new Credential();
|
|
78
|
+
credential.Type = CRED_TYPE_GENERIC;
|
|
79
|
+
credential.TargetName = target;
|
|
80
|
+
credential.CredentialBlobSize = (uint)Encoding.Unicode.GetByteCount(secret);
|
|
81
|
+
credential.CredentialBlob = secretPtr;
|
|
82
|
+
credential.Persist = CRED_PERSIST_LOCAL_MACHINE;
|
|
83
|
+
credential.UserName = username;
|
|
84
|
+
|
|
85
|
+
if (!CredWrite(ref credential, 0))
|
|
86
|
+
{
|
|
87
|
+
throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
finally
|
|
91
|
+
{
|
|
92
|
+
Marshal.ZeroFreeCoTaskMemUnicode(secretPtr);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public static void Delete(string target)
|
|
97
|
+
{
|
|
98
|
+
if (!CredDelete(target, CRED_TYPE_GENERIC, 0))
|
|
99
|
+
{
|
|
100
|
+
throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
'@
|
|
105
|
+
|
|
106
|
+
$payload = [Console]::In.ReadToEnd() | ConvertFrom-Json
|
|
107
|
+
|
|
108
|
+
if ($payload.action -eq 'read') {
|
|
109
|
+
$secret = [DotenvxCredentialManager]::Read([string]$payload.target)
|
|
110
|
+
if ($null -ne $secret) {
|
|
111
|
+
[Console]::Out.Write($secret)
|
|
112
|
+
}
|
|
113
|
+
} elseif ($payload.action -eq 'write') {
|
|
114
|
+
[DotenvxCredentialManager]::Write([string]$payload.target, [string]$payload.username, [string]$payload.secret)
|
|
115
|
+
} elseif ($payload.action -eq 'delete') {
|
|
116
|
+
[DotenvxCredentialManager]::Delete([string]$payload.target)
|
|
117
|
+
} else {
|
|
118
|
+
throw "unsupported credential action"
|
|
119
|
+
}
|
|
120
|
+
`
|
|
121
|
+
|
|
122
|
+
const encodedScript = Buffer.from(script, 'utf16le').toString('base64')
|
|
123
|
+
|
|
124
|
+
function target (publicKey) {
|
|
125
|
+
return `${SERVICE}:${publicKey}`
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function run (payload) {
|
|
129
|
+
return execFileSync(POWERSHELL_BIN, ['-NoProfile', '-NonInteractive', '-EncodedCommand', encodedScript], {
|
|
130
|
+
input: JSON.stringify(payload),
|
|
131
|
+
encoding: 'utf8',
|
|
132
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
133
|
+
windowsHide: true
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function findGenericPassword (publicKey) {
|
|
138
|
+
try {
|
|
139
|
+
return run({ action: 'read', target: target(publicKey) }).trim() || null
|
|
140
|
+
} catch {
|
|
141
|
+
throw new Error('failed to read private key from Windows Credential Manager')
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function addGenericPassword (publicKey, privateKey) {
|
|
146
|
+
try {
|
|
147
|
+
run({ action: 'write', target: target(publicKey), username: publicKey, secret: privateKey })
|
|
148
|
+
} catch {
|
|
149
|
+
throw new Error('failed to save private key to Windows Credential Manager')
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function deleteGenericPassword (publicKey) {
|
|
154
|
+
try {
|
|
155
|
+
run({ action: 'delete', target: target(publicKey) })
|
|
156
|
+
} catch {
|
|
157
|
+
throw new Error('failed to delete private key from Windows Credential Manager')
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = { findGenericPassword, addGenericPassword, deleteGenericPassword }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const Session = require('./../../db/session')
|
|
2
2
|
|
|
3
3
|
const armorProvider = require('./armor/index')
|
|
4
|
-
const
|
|
4
|
+
const nativeProvider = require('./native/index')
|
|
5
5
|
|
|
6
6
|
function syncArmorProvider (publicKeyHex) {
|
|
7
7
|
const { createSyncFn } = require('@dotenvx/tooling')
|
|
@@ -43,8 +43,8 @@ function armorProviderForOptions (options) {
|
|
|
43
43
|
})
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
function
|
|
47
|
-
if (process.platform !== 'darwin') return false
|
|
46
|
+
function useNative (options) {
|
|
47
|
+
if (process.platform !== 'darwin' && process.platform !== 'win32') return false
|
|
48
48
|
if (process.env.CI) return false
|
|
49
49
|
return options.noNative !== true && options.native !== false && options.noKeychain !== true
|
|
50
50
|
}
|
|
@@ -67,8 +67,8 @@ async function providers (options = {}) {
|
|
|
67
67
|
|
|
68
68
|
const providerFns = []
|
|
69
69
|
|
|
70
|
-
if (
|
|
71
|
-
providerFns.push(
|
|
70
|
+
if (useNative(options)) {
|
|
71
|
+
providerFns.push(nativeProvider)
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
if (options.noArmor !== true && options.armor !== false) {
|
|
@@ -89,8 +89,8 @@ providers.sync = function providersSync (options = {}) {
|
|
|
89
89
|
|
|
90
90
|
const providerFns = []
|
|
91
91
|
|
|
92
|
-
if (
|
|
93
|
-
providerFns.push(
|
|
92
|
+
if (useNative(options)) {
|
|
93
|
+
providerFns.push(nativeProvider)
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
if (options.noArmor !== true && options.armor !== false) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const { execFileSync } = require('child_process')
|
|
2
|
+
|
|
3
|
+
const windowsCredentialManager = require('../../helpers/windowsCredentialManager')
|
|
4
|
+
|
|
5
|
+
const SECURITY_BIN = '/usr/bin/security'
|
|
6
|
+
const SERVICE = 'dotenvx'
|
|
7
|
+
|
|
8
|
+
function findMacosPrivateKey (publicKeyHex) {
|
|
9
|
+
return execFileSync(SECURITY_BIN, ['find-generic-password', '-s', SERVICE, '-a', publicKeyHex, '-w'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function index (publicKeyHex) {
|
|
13
|
+
if (process.platform !== 'darwin' && process.platform !== 'win32') return {}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const privateKeyHex = process.platform === 'win32'
|
|
17
|
+
? windowsCredentialManager.findGenericPassword(publicKeyHex)
|
|
18
|
+
: findMacosPrivateKey(publicKeyHex)
|
|
19
|
+
|
|
20
|
+
if (!privateKeyHex) return {}
|
|
21
|
+
|
|
22
|
+
return { [publicKeyHex]: privateKeyHex }
|
|
23
|
+
} catch {
|
|
24
|
+
return {}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = index
|
|
@@ -3,10 +3,29 @@ const { execFileSync } = require('child_process')
|
|
|
3
3
|
const keynames = require('../conventions/keynames')
|
|
4
4
|
const readEnvKey = require('../helpers/readEnvKey')
|
|
5
5
|
const upsertEnvKey = require('../helpers/upsertEnvKey')
|
|
6
|
+
const armoredKeyDisplay = require('../helpers/armoredKeyDisplay')
|
|
7
|
+
const windowsCredentialManager = require('../helpers/windowsCredentialManager')
|
|
6
8
|
|
|
7
9
|
const SECURITY_BIN = '/usr/bin/security'
|
|
8
10
|
const SERVICE = 'dotenvx'
|
|
9
11
|
|
|
12
|
+
function findGenericPassword (publicKey) {
|
|
13
|
+
if (process.platform === 'win32') {
|
|
14
|
+
return windowsCredentialManager.findGenericPassword(publicKey)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return execFileSync(SECURITY_BIN, ['find-generic-password', '-s', SERVICE, '-a', publicKey, '-w'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function deleteGenericPassword (publicKey) {
|
|
21
|
+
if (process.platform === 'win32') {
|
|
22
|
+
windowsCredentialManager.deleteGenericPassword(publicKey)
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
execFileSync(SECURITY_BIN, ['delete-generic-password', '-s', SERVICE, '-a', publicKey], { stdio: 'ignore' })
|
|
27
|
+
}
|
|
28
|
+
|
|
10
29
|
class KeychainDown {
|
|
11
30
|
constructor (envFile = '.env', envKeysFile = '.env.keys') {
|
|
12
31
|
this.envFile = envFile
|
|
@@ -26,7 +45,11 @@ class KeychainDown {
|
|
|
26
45
|
let privateKey
|
|
27
46
|
|
|
28
47
|
try {
|
|
29
|
-
privateKey =
|
|
48
|
+
privateKey = findGenericPassword(publicKey)
|
|
49
|
+
if (!privateKey) {
|
|
50
|
+
const secretStore = process.platform === 'win32' ? 'Windows Credential Manager' : 'macOS Keychain'
|
|
51
|
+
throw new Error(`[NOT_FOUND] private key not found in ${secretStore} (${armoredKeyDisplay(publicKey)}). fix: [dotenvx native up]`)
|
|
52
|
+
}
|
|
30
53
|
} catch (error) {
|
|
31
54
|
privateKey = readEnvKey(privateKeyName, envKeysFile, { strict: false })
|
|
32
55
|
|
|
@@ -43,7 +66,7 @@ class KeychainDown {
|
|
|
43
66
|
}
|
|
44
67
|
|
|
45
68
|
upsertEnvKey(privateKeyName, privateKey, envKeysFile)
|
|
46
|
-
|
|
69
|
+
deleteGenericPassword(publicKey)
|
|
47
70
|
|
|
48
71
|
return {
|
|
49
72
|
changed: true,
|
|
@@ -4,10 +4,19 @@ const keynames = require('../conventions/keynames')
|
|
|
4
4
|
const readEnvKey = require('../helpers/readEnvKey')
|
|
5
5
|
const upsertEnvKey = require('../helpers/upsertEnvKey')
|
|
6
6
|
const armoredKeyDisplay = require('../helpers/armoredKeyDisplay')
|
|
7
|
+
const windowsCredentialManager = require('../helpers/windowsCredentialManager')
|
|
7
8
|
|
|
8
9
|
const SECURITY_BIN = '/usr/bin/security'
|
|
9
10
|
const SERVICE = 'dotenvx'
|
|
10
11
|
|
|
12
|
+
function findGenericPassword (publicKey) {
|
|
13
|
+
if (process.platform === 'win32') {
|
|
14
|
+
return windowsCredentialManager.findGenericPassword(publicKey)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return execFileSync(SECURITY_BIN, ['find-generic-password', '-s', SERVICE, '-a', publicKey, '-w'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
|
|
18
|
+
}
|
|
19
|
+
|
|
11
20
|
class KeychainPull {
|
|
12
21
|
constructor (envFile = '.env', envKeysFile = '.env.keys') {
|
|
13
22
|
this.envFile = envFile
|
|
@@ -27,9 +36,11 @@ class KeychainPull {
|
|
|
27
36
|
let privateKey
|
|
28
37
|
|
|
29
38
|
try {
|
|
30
|
-
privateKey =
|
|
39
|
+
privateKey = findGenericPassword(publicKey)
|
|
40
|
+
if (!privateKey) throw new Error('not found')
|
|
31
41
|
} catch {
|
|
32
|
-
|
|
42
|
+
const secretStore = process.platform === 'win32' ? 'Windows Credential Manager' : 'macOS Keychain'
|
|
43
|
+
throw new Error(`[NOT_FOUND] private key not found in ${secretStore} (${armoredKeyDisplay(publicKey)}). fix: [dotenvx native up]`)
|
|
33
44
|
}
|
|
34
45
|
|
|
35
46
|
const result = upsertEnvKey(privateKeyName, privateKey, envKeysFile)
|
|
@@ -3,11 +3,17 @@ const { execFileSync } = require('child_process')
|
|
|
3
3
|
const keynames = require('../conventions/keynames')
|
|
4
4
|
const readEnvKey = require('../helpers/readEnvKey')
|
|
5
5
|
const armoredKeyDisplay = require('../helpers/armoredKeyDisplay')
|
|
6
|
+
const windowsCredentialManager = require('../helpers/windowsCredentialManager')
|
|
6
7
|
|
|
7
8
|
const SECURITY_BIN = '/usr/bin/security'
|
|
8
9
|
const SERVICE = 'dotenvx'
|
|
9
10
|
|
|
10
11
|
function addGenericPassword (publicKey, label, privateKey) {
|
|
12
|
+
if (process.platform === 'win32') {
|
|
13
|
+
windowsCredentialManager.addGenericPassword(publicKey, privateKey)
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
try {
|
|
12
18
|
execFileSync(SECURITY_BIN, ['add-generic-password', '-U', '-s', SERVICE, '-a', publicKey, '-l', label, '-w', privateKey], { stdio: 'ignore' })
|
|
13
19
|
} catch {
|
|
@@ -15,6 +21,14 @@ function addGenericPassword (publicKey, label, privateKey) {
|
|
|
15
21
|
}
|
|
16
22
|
}
|
|
17
23
|
|
|
24
|
+
function findGenericPassword (publicKey) {
|
|
25
|
+
if (process.platform === 'win32') {
|
|
26
|
+
return windowsCredentialManager.findGenericPassword(publicKey)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return execFileSync(SECURITY_BIN, ['find-generic-password', '-s', SERVICE, '-a', publicKey, '-w'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
|
|
30
|
+
}
|
|
31
|
+
|
|
18
32
|
class KeychainPush {
|
|
19
33
|
constructor (envFile = '.env', envKeysFile = '.env.keys') {
|
|
20
34
|
this.envFile = envFile
|
|
@@ -35,7 +49,7 @@ class KeychainPush {
|
|
|
35
49
|
const label = `dotenvx (${armoredKeyDisplay(publicKey)})`
|
|
36
50
|
|
|
37
51
|
try {
|
|
38
|
-
const existingPrivateKey =
|
|
52
|
+
const existingPrivateKey = findGenericPassword(publicKey)
|
|
39
53
|
|
|
40
54
|
if (existingPrivateKey === privateKey) {
|
|
41
55
|
return {
|
|
@@ -4,11 +4,17 @@ const keynames = require('../conventions/keynames')
|
|
|
4
4
|
const readEnvKey = require('../helpers/readEnvKey')
|
|
5
5
|
const removeEnvKey = require('../helpers/removeEnvKey')
|
|
6
6
|
const armoredKeyDisplay = require('../helpers/armoredKeyDisplay')
|
|
7
|
+
const windowsCredentialManager = require('../helpers/windowsCredentialManager')
|
|
7
8
|
|
|
8
9
|
const SECURITY_BIN = '/usr/bin/security'
|
|
9
10
|
const SERVICE = 'dotenvx'
|
|
10
11
|
|
|
11
12
|
function addGenericPassword (publicKey, label, privateKey) {
|
|
13
|
+
if (process.platform === 'win32') {
|
|
14
|
+
windowsCredentialManager.addGenericPassword(publicKey, privateKey)
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
try {
|
|
13
19
|
execFileSync(SECURITY_BIN, ['add-generic-password', '-U', '-s', SERVICE, '-a', publicKey, '-l', label, '-w', privateKey], { stdio: 'ignore' })
|
|
14
20
|
} catch {
|
|
@@ -16,6 +22,14 @@ function addGenericPassword (publicKey, label, privateKey) {
|
|
|
16
22
|
}
|
|
17
23
|
}
|
|
18
24
|
|
|
25
|
+
function findGenericPassword (publicKey) {
|
|
26
|
+
if (process.platform === 'win32') {
|
|
27
|
+
return windowsCredentialManager.findGenericPassword(publicKey)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return execFileSync(SECURITY_BIN, ['find-generic-password', '-s', SERVICE, '-a', publicKey, '-w'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
class KeychainUp {
|
|
20
34
|
constructor (envFile = '.env', envKeysFile = '.env.keys') {
|
|
21
35
|
this.envFile = envFile
|
|
@@ -36,7 +50,7 @@ class KeychainUp {
|
|
|
36
50
|
let existingPrivateKey
|
|
37
51
|
|
|
38
52
|
try {
|
|
39
|
-
existingPrivateKey =
|
|
53
|
+
existingPrivateKey = findGenericPassword(publicKey)
|
|
40
54
|
} catch {}
|
|
41
55
|
|
|
42
56
|
const privateKey = readEnvKey(privateKeyName, envKeysFile, { strict: !existingPrivateKey })
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
const { execFileSync } = require('child_process')
|
|
2
|
-
|
|
3
|
-
const SECURITY_BIN = '/usr/bin/security'
|
|
4
|
-
const SERVICE = 'dotenvx'
|
|
5
|
-
|
|
6
|
-
function index (publicKeyHex) {
|
|
7
|
-
if (process.platform !== 'darwin') return {}
|
|
8
|
-
|
|
9
|
-
try {
|
|
10
|
-
const stdout = execFileSync(SECURITY_BIN, ['find-generic-password', '-s', SERVICE, '-a', publicKeyHex, '-w'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
|
|
11
|
-
const privateKeyHex = stdout.trim()
|
|
12
|
-
if (!privateKeyHex) return {}
|
|
13
|
-
|
|
14
|
-
return { [publicKeyHex]: privateKeyHex }
|
|
15
|
-
} catch {
|
|
16
|
-
return {}
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
module.exports = index
|