@@ -2,10 +2,12 @@ import color from '@heroku-cli/color'
2
2
import * as Heroku from '@heroku-cli/schema'
3
3
import * as Config from '@oclif/config'
4
4
import ux from 'cli-ux'
5
+ import * as fs from 'fs-extra'
5
6
import HTTP from 'http-call'
6
7
import Netrc from 'netrc-parser'
7
8
import opn = require( 'opn' )
8
9
import { hostname } from 'os'
10
+ import * as path from 'path'
9
11
10
12
import { APIClient , HerokuAPIError } from './api_client'
11
13
import { vars } from './vars'
@@ -23,19 +25,21 @@ export namespace Login {
23
25
interface NetrcEntry {
24
26
login : string
25
27
password : string
26
- method ?: 'interactive' | 'sso' | 'browser'
27
- org ?: string
28
- refresh ?: string
29
28
}
30
29
31
30
const headers = ( token : string ) => ( { headers : { accept : 'application/vnd.heroku+json; version=3' , authorization : `Bearer ${ token } ` } } )
32
31
33
32
export class Login {
34
33
loginHost = process . env . HEROKU_LOGIN_HOST || 'https://cli-login.heroku.com'
34
+ settings ! : {
35
+ method ?: string
36
+ org ?: string
37
+ }
35
38
36
39
constructor ( private readonly config : Config . IConfig , private readonly heroku : APIClient ) { }
37
40
38
41
async login ( opts : Login . Options = { } ) : Promise < void > {
42
+ await this . loadSettings ( )
39
43
let loggedIn = false
40
44
try {
41
45
// timeout after 10 minutes
@@ -47,7 +51,7 @@ export class Login {
47
51
await Netrc . load ( )
48
52
const previousEntry = Netrc . machines [ 'api.heroku.com' ]
49
53
let input : string | undefined = opts . method
50
- const defaultMethod = ( previousEntry && previousEntry . method ) || 'interactive'
54
+ const defaultMethod = this . settings . method || 'interactive'
51
55
if ( ! input ) {
52
56
if ( opts . expiresIn ) {
53
57
// can't use browser with --expires-in
@@ -64,6 +68,7 @@ export class Login {
64
68
ux . warn ( err )
65
69
}
66
70
let auth
71
+ delete this . settings . method
67
72
switch ( input ) {
68
73
case 'b' :
69
74
case 'browser' :
@@ -75,12 +80,13 @@ export class Login {
75
80
break
76
81
case 's' :
77
82
case 'sso' :
78
- auth = await this . sso ( previousEntry && previousEntry . org )
83
+ auth = await this . sso ( )
79
84
break
80
85
default :
81
86
return this . login ( opts )
82
87
}
83
88
await this . saveToken ( auth )
89
+ await this . saveSettings ( )
84
90
} catch ( err ) {
85
91
throw new HerokuAPIError ( err )
86
92
} finally {
@@ -158,7 +164,6 @@ export class Login {
158
164
return {
159
165
login : account . email ! ,
160
166
password : auth . access_token ,
161
- method : 'browser' ,
162
167
}
163
168
}
164
169
@@ -176,7 +181,6 @@ export class Login {
176
181
auth = await this . createOAuthToken ( login ! , password , { expiresIn, secondFactor} )
177
182
}
178
183
this . heroku . auth = auth . password
179
- auth . method = 'interactive'
180
184
return auth
181
185
}
182
186
@@ -212,9 +216,6 @@ export class Login {
212
216
Netrc . machines [ host ] . login = entry . login
213
217
Netrc . machines [ host ] . password = entry . password
214
218
} )
215
- Netrc . machines [ vars . apiHost ] . refresh = entry . refresh
216
- Netrc . machines [ vars . apiHost ] . method = entry . method
217
- Netrc . machines [ vars . apiHost ] . org = entry . org
218
219
if ( Netrc . machines . _tokens ) {
219
220
( Netrc . machines . _tokens as any ) . forEach ( ( token : any ) => {
220
221
if ( hosts . includes ( token . host ) ) {
@@ -243,10 +244,10 @@ export class Login {
243
244
return this . config . channel !== 'stable'
244
245
}
245
246
246
- private async sso ( org ?: string ) : Promise < NetrcEntry > {
247
+ private async sso ( ) : Promise < NetrcEntry > {
247
248
let url = process . env . SSO_URL
249
+ let org = process . env . HEROKU_ORGANIZATION || this . settings . org
248
250
if ( ! url ) {
249
- org = process . env . HEROKU_ORGANIZATION || org
250
251
if ( org ) {
251
252
org = await ux . prompt ( 'Organization name' , { default : org } )
252
253
} else {
@@ -265,6 +266,32 @@ export class Login {
265
266
this . heroku . auth = password
266
267
const { body : account } = await HTTP . get < Heroku . Account > ( `${ vars . apiUrl } /account` , headers ( password ) )
267
268
268
- return { password, login : account . email ! , method : 'sso' , org}
269
+ this . settings . method = 'sso'
270
+ this . settings . org = org
271
+ return { password, login : account . email ! }
272
+ }
273
+
274
+ private async loadSettings ( ) {
275
+ try {
276
+ this . settings = await fs . readJSON ( this . settingsPath )
277
+ } catch ( err ) {
278
+ if ( err . code !== 'ENOENT' ) ux . warn ( err )
279
+ else debug ( err )
280
+ this . settings = { }
281
+ }
282
+ }
283
+
284
+ private async saveSettings ( ) {
285
+ try {
286
+ if ( Object . keys ( this . settings ) . length === 0 ) {
287
+ await fs . remove ( this . settingsPath )
288
+ } else {
289
+ await fs . outputJSON ( this . settingsPath , this . settings )
290
+ }
291
+ } catch ( err ) {
292
+ ux . warn ( err )
293
+ }
269
294
}
295
+
296
+ private get settingsPath ( ) { return path . join ( this . config . dataDir , 'login.json' ) }
270
297
}
0 commit comments