@dotenvx/dotenvx 1.71.3 → 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.
- package/CHANGELOG.md +9 -1
- package/README.md +2 -2
- package/package.json +8 -3
- package/src/cli/actions/decrypt.js +1 -1
- package/src/cli/actions/encrypt.js +1 -1
- package/src/cli/actions/login.js +63 -0
- package/src/cli/actions/logout.js +36 -0
- package/src/cli/actions/normalizeArmorOptions.js +1 -2
- package/src/cli/actions/set.js +1 -1
- package/src/cli/dotenvx.js +14 -19
- package/src/db/device.js +73 -0
- package/src/db/session.js +186 -0
- package/src/lib/api/postLogout.js +34 -0
- package/src/lib/api/postOauthDeviceCode.js +38 -0
- package/src/lib/api/postOauthToken.js +35 -0
- package/src/lib/helpers/buildApiError.js +16 -0
- package/src/lib/helpers/buildOauthError.js +14 -0
- package/src/lib/helpers/createSpinner.js +1 -1
- package/src/lib/helpers/decryptDeviceValue.js +10 -0
- package/src/lib/helpers/encryptDeviceValue.js +9 -0
- package/src/lib/helpers/formatCode.js +11 -0
- package/src/lib/helpers/http.js +7 -0
- package/src/lib/helpers/jsonToEnv.js +7 -0
- package/src/lib/helpers/listenForOpenKey.js +46 -0
- package/src/lib/helpers/normalizeToken.js +5 -0
- package/src/lib/helpers/openUrl.js +7 -0
- package/src/lib/main.d.ts +0 -24
- package/src/lib/main.js +1 -1
- package/src/lib/services/login.js +26 -0
- package/src/lib/services/loginPoll.js +35 -0
- package/src/lib/services/logout.js +26 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,15 @@
|
|
|
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.
|
|
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))
|
|
6
14
|
|
|
7
15
|
## [1.71.3](https://github.com/dotenvx/dotenvx/compare/v1.71.2...v1.71.3) (2026-06-11)
|
|
8
16
|
|
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
|
|
2123
|
-
decrypt
|
|
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.
|
|
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.
|
|
65
|
+
"@yao-pkg/pkg": "^6.12.0",
|
|
61
66
|
"capture-console": "^1.0.2",
|
|
62
|
-
"esbuild": "^0.
|
|
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
|
|
@@ -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
|
|
|
@@ -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
|
|
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
|
package/src/cli/actions/set.js
CHANGED
|
@@ -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
|
|
package/src/cli/dotenvx.js
CHANGED
|
@@ -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('
|
|
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('
|
|
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
|
-
.
|
|
217
|
-
|
|
218
|
-
|
|
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
|
|
216
|
+
.description('log out of connected security features')
|
|
224
217
|
.allowUnknownOption()
|
|
225
|
-
.
|
|
226
|
-
|
|
227
|
-
|
|
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', '
|
|
249
|
-
program.addHelpText('after', '
|
|
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
|
package/src/db/device.js
ADDED
|
@@ -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
|
package/src/db/session.js
CHANGED
|
@@ -1,9 +1,139 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const Conf = require('conf')
|
|
4
|
+
const dotenv = require('dotenv')
|
|
5
|
+
const envPaths = require('env-paths')
|
|
6
|
+
|
|
1
7
|
const Armor = require('./../lib/extensions/armor')
|
|
8
|
+
const jsonToEnv = require('./../lib/helpers/jsonToEnv')
|
|
2
9
|
const { logger } = require('./../shared/logger')
|
|
3
10
|
|
|
11
|
+
const ARMOR = {
|
|
12
|
+
HOSTNAME: 'DOTENVX_ARMOR_HOSTNAME',
|
|
13
|
+
USER: 'DOTENVX_ARMOR_USER',
|
|
14
|
+
USERNAME: 'DOTENVX_ARMOR_USERNAME',
|
|
15
|
+
TOKEN: 'DOTENVX_ARMOR_TOKEN',
|
|
16
|
+
ON: 'DOTENVX_ARMOR_ON',
|
|
17
|
+
VERSION: 'DOTENVX_ARMOR_VERSION',
|
|
18
|
+
VERSION_LAST_CHECK: 'DOTENVX_ARMOR_VERSION_LAST_CHECK'
|
|
19
|
+
}
|
|
20
|
+
|
|
4
21
|
class Session {
|
|
5
22
|
constructor () {
|
|
6
23
|
this.armor = new Armor()
|
|
24
|
+
this._store = null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
_configPath () {
|
|
28
|
+
const cwd = process.env.DOTENVX_CONFIG || this._defaultConfigCwd()
|
|
29
|
+
return path.resolve(cwd, '.env')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_defaultConfigCwd () {
|
|
33
|
+
return envPaths('dotenvx', { suffix: '' }).config
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_configExists () {
|
|
37
|
+
return fs.existsSync(this._configPath())
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_newStore () {
|
|
41
|
+
return new Conf({
|
|
42
|
+
cwd: process.env.DOTENVX_CONFIG || undefined,
|
|
43
|
+
projectName: 'dotenvx',
|
|
44
|
+
configName: '.env',
|
|
45
|
+
projectSuffix: '',
|
|
46
|
+
fileExtension: '',
|
|
47
|
+
serialize: function (json) {
|
|
48
|
+
return jsonToEnv(json)
|
|
49
|
+
},
|
|
50
|
+
// Convert .env format to an object
|
|
51
|
+
deserialize: function (env) {
|
|
52
|
+
return dotenv.parse(env)
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
createStore () {
|
|
58
|
+
if (!this._store) this._store = this._newStore()
|
|
59
|
+
return this._store
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
openStore () {
|
|
63
|
+
if (!this._store && !this._configExists()) {
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!this._store) this._store = this._newStore()
|
|
68
|
+
return this._store
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get store () {
|
|
72
|
+
return this.openStore()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
status () {
|
|
76
|
+
// if logged in
|
|
77
|
+
if (this.username() && this.token() && this.on()) {
|
|
78
|
+
return 'on'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return 'off'
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
//
|
|
85
|
+
// Get
|
|
86
|
+
//
|
|
87
|
+
readSetting (key) {
|
|
88
|
+
const store = this.openStore()
|
|
89
|
+
if (!store) return undefined
|
|
90
|
+
|
|
91
|
+
return store.get(ARMOR[key])
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
hostname () {
|
|
95
|
+
return this.readSetting('HOSTNAME') || 'https://armor.dotenvx.com'
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
username () {
|
|
99
|
+
return this.readSetting('USERNAME') || undefined
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
token () {
|
|
103
|
+
return this.readSetting('TOKEN') || undefined
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
devicePublicKey () {
|
|
107
|
+
const Device = require('./device')
|
|
108
|
+
return new Device().publicKey()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
path () {
|
|
112
|
+
return this._store ? this._store.path : this._configPath()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
on () {
|
|
116
|
+
return (this.readSetting('ON') || 'true') === 'true'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
off () {
|
|
120
|
+
return (this.readSetting('ON') || 'true') === 'false'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async systemInformation () {
|
|
124
|
+
const si = require('systeminformation')
|
|
125
|
+
const system = await si.system()
|
|
126
|
+
const osInfo = await si.osInfo()
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
system_uuid: system.uuid,
|
|
130
|
+
os_platform: osInfo.platform,
|
|
131
|
+
os_arch: osInfo.arch
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async notifyUpdate () {
|
|
136
|
+
// native login keeps this lightweight; sidecar commands still handle full update messaging
|
|
7
137
|
}
|
|
8
138
|
|
|
9
139
|
//
|
|
@@ -20,6 +150,62 @@ class Session {
|
|
|
20
150
|
logger.debug(`armor: ${status}`)
|
|
21
151
|
return status === 'off'
|
|
22
152
|
}
|
|
153
|
+
|
|
154
|
+
//
|
|
155
|
+
// Set/Delete
|
|
156
|
+
//
|
|
157
|
+
login (hostname, id, username, accessToken) {
|
|
158
|
+
if (!hostname) {
|
|
159
|
+
throw new Error('DOTENVX_ARMOR_HOSTNAME not set. Run [dotenvx login]')
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!id) {
|
|
163
|
+
throw new Error('DOTENVX_ARMOR_USER not set. Run [dotenvx login]')
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!username) {
|
|
167
|
+
throw new Error('DOTENVX_ARMOR_USERNAME not set. Run [dotenvx login]')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!accessToken) {
|
|
171
|
+
throw new Error('DOTENVX_ARMOR_TOKEN not set. Run [dotenvx login]')
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const store = this.createStore()
|
|
175
|
+
store.set(ARMOR.USER, id)
|
|
176
|
+
store.set(ARMOR.USERNAME, username)
|
|
177
|
+
store.set(ARMOR.TOKEN, accessToken)
|
|
178
|
+
store.set(ARMOR.HOSTNAME, hostname)
|
|
179
|
+
store.set(ARMOR.ON, 'true')
|
|
180
|
+
|
|
181
|
+
return accessToken
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
logout (hostname, id, accessToken) {
|
|
185
|
+
if (!hostname) {
|
|
186
|
+
throw new Error('DOTENVX_ARMOR_HOSTNAME not set. Run [dotenvx login]')
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!id) {
|
|
190
|
+
throw new Error('DOTENVX_ARMOR_USER not set. Run [dotenvx login]')
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!accessToken) {
|
|
194
|
+
throw new Error('DOTENVX_ARMOR_TOKEN not set. Run [dotenvx login]')
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const store = this.openStore()
|
|
198
|
+
if (!store) return true
|
|
199
|
+
|
|
200
|
+
store.delete(ARMOR.USER)
|
|
201
|
+
store.delete(ARMOR.USERNAME)
|
|
202
|
+
store.delete(ARMOR.TOKEN)
|
|
203
|
+
store.delete(ARMOR.HOSTNAME)
|
|
204
|
+
store.delete(ARMOR.ON)
|
|
205
|
+
store.delete(ARMOR.VERSION)
|
|
206
|
+
store.delete(ARMOR.VERSION_LAST_CHECK)
|
|
207
|
+
return true
|
|
208
|
+
}
|
|
23
209
|
}
|
|
24
210
|
|
|
25
211
|
module.exports = Session
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const { http } = require('../helpers/http')
|
|
2
|
+
const buildApiError = require('../helpers/buildApiError')
|
|
3
|
+
const normalizeToken = require('../helpers/normalizeToken')
|
|
4
|
+
|
|
5
|
+
class PostLogout {
|
|
6
|
+
constructor (hostname, token) {
|
|
7
|
+
this.hostname = hostname
|
|
8
|
+
this.token = token
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async run () {
|
|
12
|
+
const token = normalizeToken(this.token)
|
|
13
|
+
const url = `${this.hostname}/api/logout`
|
|
14
|
+
|
|
15
|
+
const resp = await http(url, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: {
|
|
18
|
+
Authorization: `Bearer ${token}`,
|
|
19
|
+
'Content-Type': 'application/json'
|
|
20
|
+
},
|
|
21
|
+
body: JSON.stringify({})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const json = await resp.body.json()
|
|
25
|
+
|
|
26
|
+
if (resp.statusCode >= 400) {
|
|
27
|
+
throw buildApiError(resp.statusCode, json)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return json
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = PostLogout
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const { http } = require('../helpers/http')
|
|
2
|
+
const buildOauthError = require('../helpers/buildOauthError')
|
|
3
|
+
|
|
4
|
+
const OAUTH_CLIENT_ID = 'oac_dotenvxcli'
|
|
5
|
+
|
|
6
|
+
class PostOauthDeviceCode {
|
|
7
|
+
constructor (hostname, devicePublicKey, systemInformation, dotenvxProjectId = null) {
|
|
8
|
+
this.hostname = hostname
|
|
9
|
+
this.devicePublicKey = devicePublicKey
|
|
10
|
+
this.systemInformation = systemInformation
|
|
11
|
+
this.dotenvxProjectId = dotenvxProjectId
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async run () {
|
|
15
|
+
const resp = await http(`${this.hostname}/oauth/device/code`, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: {
|
|
18
|
+
'Content-Type': 'application/json'
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify({
|
|
21
|
+
client_id: OAUTH_CLIENT_ID,
|
|
22
|
+
device_public_key: this.devicePublicKey,
|
|
23
|
+
system_information: this.systemInformation,
|
|
24
|
+
dotenvx_project_id: this.dotenvxProjectId
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const json = await resp.body.json()
|
|
29
|
+
|
|
30
|
+
if (resp.statusCode >= 400) {
|
|
31
|
+
throw buildOauthError(resp.statusCode, json)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return json
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = PostOauthDeviceCode
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const { http } = require('../helpers/http')
|
|
2
|
+
const buildOauthError = require('../helpers/buildOauthError')
|
|
3
|
+
|
|
4
|
+
const OAUTH_CLIENT_ID = 'oac_dotenvxcli'
|
|
5
|
+
|
|
6
|
+
class PostOauthToken {
|
|
7
|
+
constructor (hostname, deviceCode) {
|
|
8
|
+
this.hostname = hostname
|
|
9
|
+
this.deviceCode = deviceCode
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async run () {
|
|
13
|
+
const resp = await http(`${this.hostname}/oauth/token`, {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
headers: {
|
|
16
|
+
'Content-Type': 'application/json'
|
|
17
|
+
},
|
|
18
|
+
body: JSON.stringify({
|
|
19
|
+
client_id: OAUTH_CLIENT_ID,
|
|
20
|
+
device_code: this.deviceCode,
|
|
21
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code'
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const json = await resp.body.json()
|
|
26
|
+
|
|
27
|
+
if (resp.statusCode >= 400) {
|
|
28
|
+
throw buildOauthError(resp.statusCode, json)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return json
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = PostOauthToken
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function buildApiError (statusCode, json) {
|
|
2
|
+
const code = json.error.code || statusCode.toString()
|
|
3
|
+
const message = `[${code}] ${json.error.message}`
|
|
4
|
+
const help = `[${code}] ${json.error.help || JSON.stringify(json)}`
|
|
5
|
+
const meta = json.error.meta
|
|
6
|
+
|
|
7
|
+
const error = new Error(message)
|
|
8
|
+
error.code = code
|
|
9
|
+
error.help = help
|
|
10
|
+
error.meta = meta
|
|
11
|
+
error.json = json
|
|
12
|
+
|
|
13
|
+
return error
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = buildApiError
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
function buildOauthError (statusCode, json) {
|
|
2
|
+
const code = json.error
|
|
3
|
+
const message = `[${code}] ${json.error_description}`
|
|
4
|
+
const help = `[${code}] ${JSON.stringify(json)}`
|
|
5
|
+
|
|
6
|
+
const error = new Error(message)
|
|
7
|
+
error.code = code
|
|
8
|
+
error.help = help
|
|
9
|
+
error.statusCode = statusCode
|
|
10
|
+
|
|
11
|
+
return error
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = buildOauthError
|
|
@@ -4,7 +4,7 @@ const FRAME_INTERVAL_MS = 80
|
|
|
4
4
|
async function createSpinner (options = {}) {
|
|
5
5
|
const stream = process.stderr
|
|
6
6
|
const hasCursorControls = typeof stream.cursorTo === 'function' && typeof stream.clearLine === 'function'
|
|
7
|
-
const enabled = Boolean(stream.isTTY && hasCursorControls && !options.quiet && !options.verbose && !options.debug)
|
|
7
|
+
const enabled = Boolean(stream.isTTY && hasCursorControls && options.spinner !== false && !options.quiet && !options.verbose && !options.debug)
|
|
8
8
|
if (!enabled) return null
|
|
9
9
|
|
|
10
10
|
const text = options.text || 'thinking'
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const { decrypt } = require('eciesjs')
|
|
2
|
+
|
|
3
|
+
function decryptDeviceValue (value, privateKey) {
|
|
4
|
+
const secret = Buffer.from(privateKey, 'hex')
|
|
5
|
+
const ciphertext = Buffer.from(value, 'base64')
|
|
6
|
+
|
|
7
|
+
return decrypt(secret, ciphertext).toString()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = decryptDeviceValue
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
function listenForOpenKey (onOpen) {
|
|
2
|
+
const stdin = process.stdin
|
|
3
|
+
if (!stdin.isTTY) return () => {}
|
|
4
|
+
|
|
5
|
+
const canSetRawMode = typeof stdin.setRawMode === 'function'
|
|
6
|
+
const wasRawMode = Boolean(stdin.isRaw)
|
|
7
|
+
let didHandleOpenChoice = false
|
|
8
|
+
|
|
9
|
+
const cleanup = () => {
|
|
10
|
+
stdin.off('data', onData)
|
|
11
|
+
if (canSetRawMode) stdin.setRawMode(wasRawMode)
|
|
12
|
+
stdin.pause()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const onData = (chunk) => {
|
|
16
|
+
const key = String(chunk)
|
|
17
|
+
const lower = key.toLowerCase()
|
|
18
|
+
|
|
19
|
+
if (key === '\u0003') {
|
|
20
|
+
cleanup()
|
|
21
|
+
process.kill(process.pid, 'SIGINT')
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (key === '\r' || key === '\n' || lower === 'y') {
|
|
26
|
+
if (!didHandleOpenChoice) {
|
|
27
|
+
didHandleOpenChoice = true
|
|
28
|
+
Promise.resolve(onOpen()).catch(() => {})
|
|
29
|
+
}
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (lower === 'n') {
|
|
34
|
+
cleanup()
|
|
35
|
+
process.kill(process.pid, 'SIGINT')
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (canSetRawMode) stdin.setRawMode(true)
|
|
40
|
+
stdin.resume()
|
|
41
|
+
stdin.on('data', onData)
|
|
42
|
+
|
|
43
|
+
return cleanup
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = listenForOpenKey
|
package/src/lib/main.d.ts
CHANGED
|
@@ -179,14 +179,6 @@ export interface DotenvConfigOptions {
|
|
|
179
179
|
*/
|
|
180
180
|
noArmor?: boolean;
|
|
181
181
|
|
|
182
|
-
/**
|
|
183
|
-
* Turn off Dotenvx Armor features. Alias for `noArmor`.
|
|
184
|
-
*
|
|
185
|
-
* @default false
|
|
186
|
-
* @example require('@dotenvx/dotenvx').config({ noVlt: true })
|
|
187
|
-
*/
|
|
188
|
-
noVlt?: boolean;
|
|
189
|
-
|
|
190
182
|
/**
|
|
191
183
|
* Turn off Dotenvx Armor features. Alias for `noArmor`.
|
|
192
184
|
*
|
|
@@ -270,14 +262,6 @@ export interface SetOptions {
|
|
|
270
262
|
*/
|
|
271
263
|
noArmor?: boolean;
|
|
272
264
|
|
|
273
|
-
/**
|
|
274
|
-
* Turn off Dotenvx Armor features. Alias for `noArmor`.
|
|
275
|
-
*
|
|
276
|
-
* @default false
|
|
277
|
-
* @example require('@dotenvx/dotenvx').set(key, value, { noVlt: true })
|
|
278
|
-
*/
|
|
279
|
-
noVlt?: boolean;
|
|
280
|
-
|
|
281
265
|
/**
|
|
282
266
|
* Turn off Dotenvx Armor features. Alias for `noArmor`.
|
|
283
267
|
*
|
|
@@ -363,14 +347,6 @@ export interface GetOptions {
|
|
|
363
347
|
*/
|
|
364
348
|
noArmor?: boolean;
|
|
365
349
|
|
|
366
|
-
/**
|
|
367
|
-
* Turn off Dotenvx Armor features. Alias for `noArmor`.
|
|
368
|
-
*
|
|
369
|
-
* @default false
|
|
370
|
-
* @example require('@dotenvx/dotenvx').get('KEY', { noVlt: true })
|
|
371
|
-
*/
|
|
372
|
-
noVlt?: boolean;
|
|
373
|
-
|
|
374
350
|
/**
|
|
375
351
|
* Turn off Dotenvx Armor features. Alias for `noArmor`.
|
|
376
352
|
*
|
package/src/lib/main.js
CHANGED
|
@@ -325,7 +325,7 @@ const keypair = function (envFile, key, envKeysFile = null, noArmor = false) {
|
|
|
325
325
|
|
|
326
326
|
function resolveNoArmor (options = {}) {
|
|
327
327
|
const sesh = new Session()
|
|
328
|
-
return options.noArmor === true || options.
|
|
328
|
+
return options.noArmor === true || options.noOps === true || (!options.token && sesh.noArmorSync())
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
module.exports = {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const Session = require('../../db/session')
|
|
2
|
+
const PostOauthDeviceCode = require('../api/postOauthDeviceCode')
|
|
3
|
+
|
|
4
|
+
class Login {
|
|
5
|
+
constructor (hostname) {
|
|
6
|
+
this.hostname = hostname
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async run () {
|
|
10
|
+
const sesh = new Session()
|
|
11
|
+
const devicePublicKey = sesh.devicePublicKey()
|
|
12
|
+
const systemInformation = await sesh.systemInformation()
|
|
13
|
+
|
|
14
|
+
const data = await new PostOauthDeviceCode(this.hostname, devicePublicKey, systemInformation).run()
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
deviceCode: data.device_code,
|
|
18
|
+
userCode: data.user_code,
|
|
19
|
+
verificationUri: data.verification_uri,
|
|
20
|
+
verificationUriComplete: data.verification_uri_complete,
|
|
21
|
+
interval: data.interval
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = Login
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const Session = require('../../db/session')
|
|
2
|
+
const PostOauthToken = require('../api/postOauthToken')
|
|
3
|
+
|
|
4
|
+
class LoginPoll {
|
|
5
|
+
constructor (hostname, deviceCode, interval) {
|
|
6
|
+
this.hostname = hostname
|
|
7
|
+
this.deviceCode = deviceCode
|
|
8
|
+
this.interval = interval
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async run () {
|
|
12
|
+
const sesh = new Session()
|
|
13
|
+
|
|
14
|
+
while (true) {
|
|
15
|
+
try {
|
|
16
|
+
const data = await new PostOauthToken(this.hostname, this.deviceCode).run()
|
|
17
|
+
|
|
18
|
+
if (data.access_token) {
|
|
19
|
+
sesh.login(this.hostname, data.id, data.username, data.access_token)
|
|
20
|
+
return data
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
await new Promise(resolve => setTimeout(resolve, this.interval * 1000))
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (error.code === 'authorization_pending') {
|
|
26
|
+
await new Promise(resolve => setTimeout(resolve, (this.interval + 1) * 1000))
|
|
27
|
+
} else {
|
|
28
|
+
throw error
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = LoginPoll
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const Session = require('../../db/session')
|
|
2
|
+
const PostLogout = require('../api/postLogout')
|
|
3
|
+
|
|
4
|
+
class Logout {
|
|
5
|
+
constructor (hostname) {
|
|
6
|
+
this.hostname = hostname
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async run () {
|
|
10
|
+
const sesh = new Session()
|
|
11
|
+
const token = sesh.token()
|
|
12
|
+
|
|
13
|
+
const data = await new PostLogout(this.hostname, token).run()
|
|
14
|
+
|
|
15
|
+
const id = data.id
|
|
16
|
+
const username = data.username
|
|
17
|
+
const accessToken = data.access_token
|
|
18
|
+
const settingsDevicesUrl = `${this.hostname}/settings/devices`
|
|
19
|
+
|
|
20
|
+
sesh.logout(this.hostname, id, accessToken)
|
|
21
|
+
|
|
22
|
+
return { username, accessToken, settingsDevicesUrl }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = Logout
|