@heroku/heroku-cli-util 8.0.12
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/LICENSE +3 -0
- package/README.md +269 -0
- package/index.js +40 -0
- package/lib/action.js +54 -0
- package/lib/auth.js +207 -0
- package/lib/command.js +171 -0
- package/lib/console.js +105 -0
- package/lib/date.js +18 -0
- package/lib/errors.js +122 -0
- package/lib/exit.js +42 -0
- package/lib/got.js +153 -0
- package/lib/linewrap.js +783 -0
- package/lib/mutex.js +41 -0
- package/lib/open.js +22 -0
- package/lib/preauth.js +26 -0
- package/lib/process.js +14 -0
- package/lib/prompt.js +150 -0
- package/lib/spinner.js +147 -0
- package/lib/spinners.json +739 -0
- package/lib/styled.js +131 -0
- package/lib/table.js +132 -0
- package/lib/util.js +38 -0
- package/lib/vars.js +29 -0
- package/lib/yubikey.js +14 -0
- package/package.json +80 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
Copyright (c) 2016, Heroku
|
|
2
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
3
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# Heroku CLI Utilities
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
[](http://badge.fury.io/js/heroku-cli-util)
|
|
5
|
+
[](https://github.com/heroku/heroku-cli-util/blob/master/LICENSE)
|
|
6
|
+
|
|
7
|
+
Set of helpful CLI utilities
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install heroku-cli-util --save
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Action
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
let cli = require('heroku-cli-util');
|
|
19
|
+
await cli.action('restarting dynos', async function() {
|
|
20
|
+
let app = await heroku.get(`/apps/${context.app}`);
|
|
21
|
+
await heroku.request({method: 'DELETE', path: `/apps/${app.name}/dynos`});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// restarting dynos... done
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Prompt
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
let cli = require('heroku-cli-util');
|
|
31
|
+
let email = await cli.prompt('email', {});
|
|
32
|
+
console.log(`your email is: ${email}`);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**cli.prompt options**
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
cli.prompt('email', {
|
|
39
|
+
mask: true, // mask input field after submitting
|
|
40
|
+
hide: true // mask characters while entering
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Confirm App
|
|
45
|
+
|
|
46
|
+
Supports the same async styles as `prompt()`. Errors if not confirmed.
|
|
47
|
+
|
|
48
|
+
Basic
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
let cli = require('heroku-cli-util');
|
|
52
|
+
await cli.confirmApp('appname', context.flags.confirm);
|
|
53
|
+
|
|
54
|
+
// ! WARNING: Destructive Action
|
|
55
|
+
// ! This command will affect the app appname
|
|
56
|
+
// ! To proceed, type appname or re-run this command with --confirm appname
|
|
57
|
+
|
|
58
|
+
> appname
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Custom message
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
let cli = require('heroku-cli-util');
|
|
65
|
+
await cli.confirmApp('appname', context.flags.confirm, 'foo');
|
|
66
|
+
|
|
67
|
+
// ! foo
|
|
68
|
+
// ! To proceed, type appname or re-run this command with --confirm appname
|
|
69
|
+
|
|
70
|
+
> appname
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Note that you will still need to define a `confirm` flag for your command.
|
|
74
|
+
|
|
75
|
+
## Errors
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
let cli = require('heroku-cli-util');
|
|
79
|
+
cli.error("App not found");
|
|
80
|
+
// ! App not found
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Warnings
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
let cli = require('heroku-cli-util');
|
|
87
|
+
cli.warn("App not found");
|
|
88
|
+
// ! App not found
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Dates
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
let cli = require('heroku-cli-util');
|
|
95
|
+
let d = new Date();
|
|
96
|
+
console.log(cli.formatDate(d));
|
|
97
|
+
// 2001-01-01T08:00:00.000Z
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Hush
|
|
101
|
+
|
|
102
|
+
Use hush for verbose logging when `HEROKU_DEBUG=1`.
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
let cli = require('heroku-cli-util');
|
|
106
|
+
cli.hush('foo');
|
|
107
|
+
// only prints if HEROKU_DEBUG is set
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Debug
|
|
111
|
+
|
|
112
|
+
Pretty print an object.
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
let cli = require('heroku-cli-util');
|
|
116
|
+
cli.debug({foo: [1,2,3]});
|
|
117
|
+
// { foo: [ 1, 2, 3 ] }
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Stylized output
|
|
121
|
+
|
|
122
|
+
Pretty print a header, hash, and JSON
|
|
123
|
+
```js
|
|
124
|
+
let cli = require('heroku-cli-util');
|
|
125
|
+
cli.styledHeader("MyApp");
|
|
126
|
+
cli.styledHash({name: "myapp", collaborators: ["user1@example.com", "user2@example.com"]});
|
|
127
|
+
cli.styledJSON({name: "myapp"});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Produces
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
=== MyApp
|
|
134
|
+
Collaborators: user1@example.com
|
|
135
|
+
user1@example.com
|
|
136
|
+
Name: myapp
|
|
137
|
+
|
|
138
|
+
{
|
|
139
|
+
"name": "myapp"
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Table
|
|
144
|
+
|
|
145
|
+
```js
|
|
146
|
+
cli.table([
|
|
147
|
+
{app: 'first-app', language: 'ruby', dyno_count: 3},
|
|
148
|
+
{app: 'second-app', language: 'node', dyno_count: 2},
|
|
149
|
+
], {
|
|
150
|
+
columns: [
|
|
151
|
+
{key: 'app'},
|
|
152
|
+
{key: 'dyno_count', label: 'Dyno Count'},
|
|
153
|
+
{key: 'language', format: language => cli.color.red(language)},
|
|
154
|
+
]
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Produces:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
app Dyno Count language
|
|
162
|
+
────────── ────────── ────────
|
|
163
|
+
first-app 3 ruby
|
|
164
|
+
second-app 2 node
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Linewrap
|
|
168
|
+
|
|
169
|
+
Used to indent output with wrapping around words:
|
|
170
|
+
|
|
171
|
+
```js
|
|
172
|
+
cli.log(cli.linewrap(2, 10, 'this is text is longer than 10 characters'));
|
|
173
|
+
// Outputs:
|
|
174
|
+
//
|
|
175
|
+
// this
|
|
176
|
+
// text is
|
|
177
|
+
// longer
|
|
178
|
+
// than 10
|
|
179
|
+
// characters`);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Useful with `process.stdout.columns || 80`.
|
|
183
|
+
|
|
184
|
+
## Open Web Browser
|
|
185
|
+
|
|
186
|
+
```js
|
|
187
|
+
await cli.open('https://github.com');
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## HTTP calls
|
|
191
|
+
|
|
192
|
+
`heroku-cli-util` includes an instance of [got](https://www.npmjs.com/package/got) that will correctly use HTTP proxies.
|
|
193
|
+
|
|
194
|
+
```js
|
|
195
|
+
let cli = require('heroku-cli-util');
|
|
196
|
+
let rsp = await cli.got('https://google.com');
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Mocking
|
|
200
|
+
|
|
201
|
+
Mock stdout and stderr by using `cli.log()` and `cli.error()`.
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
let cli = require('heroku-cli-util');
|
|
205
|
+
cli.log('message 1'); // prints 'message 1'
|
|
206
|
+
cli.mockConsole();
|
|
207
|
+
cli.log('message 2'); // prints nothing
|
|
208
|
+
cli.stdout.should.eq('message 2\n');
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Command
|
|
212
|
+
|
|
213
|
+
Used for initializing a plugin command.
|
|
214
|
+
give you an auth'ed instance of `heroku-client` and cleanly handle API exceptions.
|
|
215
|
+
|
|
216
|
+
It expects you to return a promise chain. This is usually done with [co](https://github.com/tj/co).
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
let cli = require('heroku-cli-util');
|
|
220
|
+
let co = require('co');
|
|
221
|
+
module.exports.commands = [
|
|
222
|
+
{
|
|
223
|
+
topic: 'apps',
|
|
224
|
+
command: 'info',
|
|
225
|
+
needsAuth: true,
|
|
226
|
+
needsApp: true,
|
|
227
|
+
run: cli.command(async function (context, heroku) {
|
|
228
|
+
let app = await heroku.get(`/apps/${context.app}`);
|
|
229
|
+
console.dir(app);
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
];
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
With options:
|
|
236
|
+
|
|
237
|
+
```js
|
|
238
|
+
let cli = require('heroku-cli-util');
|
|
239
|
+
let co = require('co');
|
|
240
|
+
module.exports.commands = [
|
|
241
|
+
{
|
|
242
|
+
topic: 'apps',
|
|
243
|
+
command: 'info',
|
|
244
|
+
needsAuth: true,
|
|
245
|
+
needsApp: true,
|
|
246
|
+
run: cli.command(
|
|
247
|
+
{preauth: true},
|
|
248
|
+
async function (context, heroku) {
|
|
249
|
+
let app = await heroku.get(`/apps/${context.app}`);
|
|
250
|
+
console.dir(app);
|
|
251
|
+
}
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
];
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
If the command has a `two_factor` API error, it will ask the user for a 2fa code and retry.
|
|
258
|
+
If you set `preauth: true` it will preauth against the current app instead of just setting the header on an app. (This is necessary if you need to do more than 1 API call that will require 2fa)
|
|
259
|
+
|
|
260
|
+
## Tests
|
|
261
|
+
|
|
262
|
+
```sh
|
|
263
|
+
npm install
|
|
264
|
+
npm test
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## License
|
|
268
|
+
|
|
269
|
+
ISC
|
package/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
exports.color = require('@heroku-cli/color').default
|
|
4
|
+
|
|
5
|
+
var console = require('./lib/console')
|
|
6
|
+
var errors = require('./lib/errors')
|
|
7
|
+
var prompt = require('./lib/prompt')
|
|
8
|
+
var styled = require('./lib/styled')
|
|
9
|
+
|
|
10
|
+
exports.hush = console.hush
|
|
11
|
+
exports.log = console.log.bind(console)
|
|
12
|
+
exports.formatDate = require('./lib/date').formatDate
|
|
13
|
+
exports.error = errors.error
|
|
14
|
+
exports.action = require('./lib/action')
|
|
15
|
+
exports.warn = exports.action.warn
|
|
16
|
+
exports.errorHandler = errors.errorHandler
|
|
17
|
+
exports.console = console
|
|
18
|
+
exports.yubikey = require('./lib/yubikey')
|
|
19
|
+
exports.prompt = prompt.prompt
|
|
20
|
+
exports.confirmApp = prompt.confirmApp
|
|
21
|
+
exports.preauth = require('./lib/preauth')
|
|
22
|
+
exports.command = require('./lib/command')
|
|
23
|
+
exports.debug = console.debug
|
|
24
|
+
exports.mockConsole = console.mock
|
|
25
|
+
exports.table = require('./lib/table')
|
|
26
|
+
exports.stdout = ''
|
|
27
|
+
exports.stderr = ''
|
|
28
|
+
exports.styledHeader = styled.styledHeader
|
|
29
|
+
exports.styledObject = styled.styledObject
|
|
30
|
+
exports.styledHash = styled.styledObject
|
|
31
|
+
exports.styledNameValues = styled.styledNameValues
|
|
32
|
+
exports.styledJSON = styled.styledJSON
|
|
33
|
+
exports.open = require('./lib/open')
|
|
34
|
+
exports.got = require('./lib/got')
|
|
35
|
+
exports.linewrap = require('./lib/linewrap')
|
|
36
|
+
exports.Spinner = require('./lib/spinner')
|
|
37
|
+
exports.exit = require('./lib/exit').exit
|
|
38
|
+
exports.auth = require('./lib/auth')
|
|
39
|
+
exports.login = exports.auth.login
|
|
40
|
+
exports.logout = exports.auth.logout
|
package/lib/action.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
let cli = require('..')
|
|
4
|
+
let errors = require('./errors')
|
|
5
|
+
|
|
6
|
+
function start (message, options) {
|
|
7
|
+
if (!options) options = {}
|
|
8
|
+
module.exports.task = {
|
|
9
|
+
spinner: new cli.Spinner({ spinner: options.spinner, text: `${message}...` }),
|
|
10
|
+
stream: options.stream
|
|
11
|
+
}
|
|
12
|
+
module.exports.task.spinner.start()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function action (message, options, promise) {
|
|
16
|
+
if (options.then) [options, promise] = [{}, options]
|
|
17
|
+
start(message, options)
|
|
18
|
+
return promise.then(function (result) {
|
|
19
|
+
if (options.success !== false) done(options.success || 'done', options)
|
|
20
|
+
else done(null, options)
|
|
21
|
+
return result
|
|
22
|
+
}).catch(function (err) {
|
|
23
|
+
if (err.body && err.body.id === 'two_factor') done(cli.color.yellow.bold('!'), options)
|
|
24
|
+
else done(cli.color.red.bold('!'), options)
|
|
25
|
+
throw err
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function warn (msg) {
|
|
30
|
+
let task = module.exports.task
|
|
31
|
+
if (task) task.spinner.warn(msg)
|
|
32
|
+
else errors.warn(msg)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function status (status) {
|
|
36
|
+
let task = module.exports.task
|
|
37
|
+
if (task) task.spinner.status = status
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function done (msg, options) {
|
|
41
|
+
options = options || {}
|
|
42
|
+
let task = module.exports.task
|
|
43
|
+
if (task) {
|
|
44
|
+
task.spinner.stop(msg)
|
|
45
|
+
module.exports.task = null
|
|
46
|
+
if (options.clear) task.spinner.clear()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = action
|
|
51
|
+
module.exports.start = start
|
|
52
|
+
module.exports.warn = warn
|
|
53
|
+
module.exports.status = status
|
|
54
|
+
module.exports.done = done
|
package/lib/auth.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const cli = require('..')
|
|
4
|
+
const vars = require('./vars')
|
|
5
|
+
|
|
6
|
+
function basicAuth (username, password) {
|
|
7
|
+
let auth = [username, password].join(':')
|
|
8
|
+
auth = Buffer.from(auth).toString('base64')
|
|
9
|
+
return `Basic ${auth}`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function createOAuthToken (username, password, expiresIn, secondFactor) {
|
|
13
|
+
const os = require('os')
|
|
14
|
+
|
|
15
|
+
let headers = {
|
|
16
|
+
Authorization: basicAuth(username, password)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (secondFactor) headers['Heroku-Two-Factor-Code'] = secondFactor
|
|
20
|
+
|
|
21
|
+
return cli.heroku.post('/oauth/authorizations', {
|
|
22
|
+
headers,
|
|
23
|
+
body: {
|
|
24
|
+
scope: ['global'],
|
|
25
|
+
description: `Heroku CLI login from ${os.hostname()} at ${new Date()}`,
|
|
26
|
+
expires_in: expiresIn || 60 * 60 * 24 * 365 // 1 year
|
|
27
|
+
}
|
|
28
|
+
}).then(function (auth) {
|
|
29
|
+
return { token: auth.access_token.token, email: auth.user.email, expires_in: auth.access_token.expires_in }
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function saveToken ({ email, token }) {
|
|
34
|
+
const netrc = require('netrc-parser').default
|
|
35
|
+
netrc.loadSync()
|
|
36
|
+
const hosts = [vars.apiHost, vars.httpGitHost]
|
|
37
|
+
hosts.forEach(host => {
|
|
38
|
+
if (!netrc.machines[host]) netrc.machines[host] = {}
|
|
39
|
+
netrc.machines[host].login = email
|
|
40
|
+
netrc.machines[host].password = token
|
|
41
|
+
})
|
|
42
|
+
if (netrc.machines._tokens) {
|
|
43
|
+
netrc.machines._tokens.forEach(token => {
|
|
44
|
+
if (hosts.includes(token.host)) {
|
|
45
|
+
token.internalWhitespace = '\n '
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
netrc.saveSync()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function loginUserPass ({ save, expires_in: expiresIn }) {
|
|
53
|
+
const { prompt } = require('./prompt')
|
|
54
|
+
|
|
55
|
+
cli.log('Enter your Heroku credentials:')
|
|
56
|
+
let email = await prompt('Email')
|
|
57
|
+
let password = await prompt('Password', { hide: true })
|
|
58
|
+
|
|
59
|
+
let auth
|
|
60
|
+
try {
|
|
61
|
+
auth = await createOAuthToken(email, password, expiresIn)
|
|
62
|
+
} catch (err) {
|
|
63
|
+
if (!err.body || err.body.id !== 'two_factor') throw err
|
|
64
|
+
let secondFactor = await prompt('Two-factor code', { mask: true })
|
|
65
|
+
auth = await createOAuthToken(email, password, expiresIn, secondFactor)
|
|
66
|
+
}
|
|
67
|
+
if (save) saveToken(auth)
|
|
68
|
+
return auth
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function loginSSO ({ save, browser }) {
|
|
72
|
+
const { prompt } = require('./prompt')
|
|
73
|
+
|
|
74
|
+
let url = process.env['SSO_URL']
|
|
75
|
+
if (!url) {
|
|
76
|
+
let org = process.env['HEROKU_ORGANIZATION']
|
|
77
|
+
if (!org) {
|
|
78
|
+
org = await prompt('Enter your organization name')
|
|
79
|
+
}
|
|
80
|
+
url = `https://sso.heroku.com/saml/${encodeURIComponent(org)}/init?cli=true`
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const open = require('./open')
|
|
84
|
+
|
|
85
|
+
let openError
|
|
86
|
+
await cli.action('Opening browser for login', open(url, browser)
|
|
87
|
+
.catch(function (err) {
|
|
88
|
+
openError = err
|
|
89
|
+
})
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if (openError) {
|
|
93
|
+
cli.console.error(openError.message)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let token = await prompt('Enter your access token (typing will be hidden)', { hide: true })
|
|
97
|
+
|
|
98
|
+
let account = await cli.heroku.get('/account', {
|
|
99
|
+
headers: {
|
|
100
|
+
Authorization: `Bearer ${token}`
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
if (save) saveToken({ token, email: account.email })
|
|
105
|
+
return { token: token, email: account.email }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function logout () {
|
|
109
|
+
let token = cli.heroku.options.token
|
|
110
|
+
if (token) {
|
|
111
|
+
// for SSO logins we delete the session since those do not show up in
|
|
112
|
+
// authorizations because they are created a trusted client
|
|
113
|
+
let sessionsP = cli.heroku.delete('/oauth/sessions/~')
|
|
114
|
+
.catch(err => {
|
|
115
|
+
if (err.statusCode === 404 && err.body && err.body.id === 'not_found' && err.body.resource === 'session') {
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
if (err.statusCode === 401 && err.body && err.body.id === 'unauthorized') {
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
throw err
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// grab the default authorization because that is the token shown in the
|
|
125
|
+
// dashboard as API Key and they may be using it for something else and we
|
|
126
|
+
// would unwittingly break an integration that they are depending on
|
|
127
|
+
let defaultAuthorizationP = cli.heroku.get('/oauth/authorizations/~')
|
|
128
|
+
.catch(err => {
|
|
129
|
+
if (err.statusCode === 404 && err.body && err.body.id === 'not_found' && err.body.resource === 'authorization') {
|
|
130
|
+
return null
|
|
131
|
+
}
|
|
132
|
+
if (err.statusCode === 401 && err.body && err.body.id === 'unauthorized') {
|
|
133
|
+
return null
|
|
134
|
+
}
|
|
135
|
+
throw err
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
// grab all the authorizations so that we can delete the token they are
|
|
139
|
+
// using in the CLI. we have to do this rather than delete ~ because
|
|
140
|
+
// the ~ is the API Key, not the authorization that is currently requesting
|
|
141
|
+
let authorizationsP = cli.heroku.get('/oauth/authorizations')
|
|
142
|
+
.catch(err => {
|
|
143
|
+
if (err.statusCode === 401 && err.body && err.body.id === 'unauthorized') {
|
|
144
|
+
return []
|
|
145
|
+
}
|
|
146
|
+
throw err
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
let [, defaultAuthorization, authorizations] = await Promise.all([sessionsP, defaultAuthorizationP, authorizationsP])
|
|
150
|
+
|
|
151
|
+
if (accessToken(defaultAuthorization) !== token) {
|
|
152
|
+
for (let authorization of authorizations) {
|
|
153
|
+
if (accessToken(authorization) === token) {
|
|
154
|
+
// remove the matching access token from core services
|
|
155
|
+
await cli.heroku.delete(`/oauth/authorizations/${authorization.id}`)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const netrc = require('netrc-parser').default
|
|
162
|
+
netrc.loadSync()
|
|
163
|
+
if (netrc.machines[vars.apiHost]) {
|
|
164
|
+
netrc.machines[vars.apiHost] = undefined
|
|
165
|
+
}
|
|
166
|
+
if (netrc.machines[vars.httpGitHost]) {
|
|
167
|
+
netrc.machines[vars.httpGitHost] = undefined
|
|
168
|
+
}
|
|
169
|
+
netrc.saveSync()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function accessToken (authorization) {
|
|
173
|
+
return authorization && authorization.access_token && authorization.access_token.token
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function login (options = {}) {
|
|
177
|
+
if (!options.skipLogout) await logout()
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
if (options['sso']) {
|
|
181
|
+
return await loginSSO(options)
|
|
182
|
+
} else {
|
|
183
|
+
return await loginUserPass(options)
|
|
184
|
+
}
|
|
185
|
+
} catch (e) {
|
|
186
|
+
const { PromptMaskError } = require('./prompt')
|
|
187
|
+
const os = require('os')
|
|
188
|
+
if (e instanceof PromptMaskError && os.platform() === 'win32') {
|
|
189
|
+
throw new PromptMaskError('Login is currently incompatible with git bash/Cygwin/MinGW')
|
|
190
|
+
} else {
|
|
191
|
+
throw e
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function token () {
|
|
197
|
+
const netrc = require('netrc-parser').default
|
|
198
|
+
netrc.loadSync()
|
|
199
|
+
if (process.env.HEROKU_API_KEY) return process.env.HEROKU_API_KEY
|
|
200
|
+
return netrc.machines[vars.apiHost] && netrc.machines[vars.apiHost].password
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = {
|
|
204
|
+
login: login,
|
|
205
|
+
logout: logout,
|
|
206
|
+
token
|
|
207
|
+
}
|