@heroku/heroku-fetch 0.1.6 → 0.2.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/README.md +19 -21
- package/dist/browser.d.ts +1 -2
- package/dist/browser.js +1 -2
- package/dist/client/http-error-handler.js +2 -1
- package/dist/client/http-request-hooks.js +2 -25
- package/dist/client/node-environment-defaults.d.ts +10 -2
- package/dist/client/node-environment-defaults.js +13 -11
- package/dist/client/two-factor-authentication-handler.js +2 -1
- package/dist/debug-loggers.d.ts +0 -1
- package/dist/debug-loggers.js +0 -1
- package/dist/host.d.ts +33 -0
- package/dist/host.js +38 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/redact.d.ts +26 -0
- package/dist/redact.js +158 -0
- package/dist/types.d.ts +8 -1
- package/package.json +3 -4
- package/dist/auth/auth.d.ts +0 -66
- package/dist/auth/auth.js +0 -93
- package/dist/cli/cli-login.d.ts +0 -7
- package/dist/cli/cli-login.js +0 -7
- package/dist/cli/login/browser-login.d.ts +0 -8
- package/dist/cli/login/browser-login.js +0 -115
- package/dist/cli/login/index.d.ts +0 -34
- package/dist/cli/login/index.js +0 -193
- package/dist/cli/login/interactive-login.d.ts +0 -8
- package/dist/cli/login/interactive-login.js +0 -59
- package/dist/cli/login/netrc-utils.d.ts +0 -22
- package/dist/cli/login/netrc-utils.js +0 -85
- package/dist/cli/login/oauth.d.ts +0 -20
- package/dist/cli/login/oauth.js +0 -142
- package/dist/cli/login/sso-login.d.ts +0 -8
- package/dist/cli/login/sso-login.js +0 -41
- package/dist/cli/login/types.d.ts +0 -42
- package/dist/cli/login/types.js +0 -11
package/dist/auth/auth.js
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Authentication utilities for Heroku API
|
|
3
|
-
* Handles token retrieval from environment variables and netrc file
|
|
4
|
-
*/
|
|
5
|
-
import { Netrc } from 'netrc-parser';
|
|
6
|
-
// Defer netrc instantiation to avoid eager .netrc file operations at module load time
|
|
7
|
-
let _netrc;
|
|
8
|
-
function getNetrc() {
|
|
9
|
-
if (!_netrc) {
|
|
10
|
-
_netrc = new Netrc();
|
|
11
|
-
}
|
|
12
|
-
return _netrc;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Get the Heroku API token from environment variable or netrc file
|
|
16
|
-
*
|
|
17
|
-
* Priority:
|
|
18
|
-
* 1. HEROKU_API_KEY environment variable
|
|
19
|
-
* 2. ~/.netrc file (uses api.heroku.com or HEROKU_HOST if set)
|
|
20
|
-
*
|
|
21
|
-
* @returns The API token or undefined if not found
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```typescript
|
|
25
|
-
* import { getAuthToken, HerokuApiClient } from '@heroku/heroku-fetch';
|
|
26
|
-
*
|
|
27
|
-
* const token = getAuthToken();
|
|
28
|
-
* const client = new HerokuApiClient({
|
|
29
|
-
* service: 'platform',
|
|
30
|
-
* token,
|
|
31
|
-
* });
|
|
32
|
-
* ```
|
|
33
|
-
*/
|
|
34
|
-
export function getAuthToken() {
|
|
35
|
-
// Check environment variable first
|
|
36
|
-
let token = process.env.HEROKU_API_KEY;
|
|
37
|
-
if (!token) {
|
|
38
|
-
// Fall back to netrc
|
|
39
|
-
const netrc = getNetrc();
|
|
40
|
-
netrc.loadSync();
|
|
41
|
-
const apiHost = getApiHost();
|
|
42
|
-
token = netrc.machines[apiHost]?.password;
|
|
43
|
-
}
|
|
44
|
-
return token;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Get the API host from environment or default
|
|
48
|
-
*
|
|
49
|
-
* @returns The API host (default: 'api.heroku.com')
|
|
50
|
-
*
|
|
51
|
-
* @example
|
|
52
|
-
* ```typescript
|
|
53
|
-
* const host = getApiHost();
|
|
54
|
-
* // Returns 'api.heroku.com' or value of HEROKU_HOST env var
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
export function getApiHost() {
|
|
58
|
-
return process.env.HEROKU_HOST || 'api.heroku.com';
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Get the full API URL from environment or default
|
|
62
|
-
*
|
|
63
|
-
* @returns The API URL (default: 'https://api.heroku.com')
|
|
64
|
-
*
|
|
65
|
-
* @example
|
|
66
|
-
* ```typescript
|
|
67
|
-
* const url = getApiUrl();
|
|
68
|
-
* // Returns 'https://api.heroku.com' or https://{HEROKU_HOST}
|
|
69
|
-
* ```
|
|
70
|
-
*/
|
|
71
|
-
export function getApiUrl() {
|
|
72
|
-
const host = getApiHost();
|
|
73
|
-
return `https://${host}`;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Create a token provider function for use with HerokuApiClient
|
|
77
|
-
* This is useful when you want the token to be fetched dynamically
|
|
78
|
-
*
|
|
79
|
-
* @returns A function that returns the current auth token
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* ```typescript
|
|
83
|
-
* import { getAuthTokenProvider, HerokuApiClient } from '@heroku/heroku-fetch';
|
|
84
|
-
*
|
|
85
|
-
* const client = new HerokuApiClient({
|
|
86
|
-
* service: 'platform',
|
|
87
|
-
* token: getAuthTokenProvider(),
|
|
88
|
-
* });
|
|
89
|
-
* ```
|
|
90
|
-
*/
|
|
91
|
-
export function getAuthTokenProvider() {
|
|
92
|
-
return () => getAuthToken();
|
|
93
|
-
}
|
package/dist/cli/cli-login.d.ts
DELETED
package/dist/cli/cli-login.js
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser-based OAuth login flow
|
|
3
|
-
*/
|
|
4
|
-
import os from 'node:os';
|
|
5
|
-
import { debugCliLogin } from '../../debug-loggers.js';
|
|
6
|
-
import { headers } from './types.js';
|
|
7
|
-
const hostname = os.hostname();
|
|
8
|
-
/**
|
|
9
|
-
* Show the manual browser login URL to the user
|
|
10
|
-
*/
|
|
11
|
-
function showManualBrowserLoginUrl(url) {
|
|
12
|
-
console.warn('If browser does not open, visit:');
|
|
13
|
-
console.error(`\u001B[32m${url}\u001B[0m`); // Green color
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Poll the auth server for the access token with retry logic
|
|
17
|
-
*/
|
|
18
|
-
async function fetchAuth(loginHost, cliUrl, token, retries = 3) {
|
|
19
|
-
try {
|
|
20
|
-
debugCliLogin('Polling auth server: %s%s (retries left: %d)', loginHost, cliUrl, retries);
|
|
21
|
-
const response = await fetch(`${loginHost}${cliUrl}`, {
|
|
22
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
23
|
-
});
|
|
24
|
-
if (!response.ok && retries > 0 && response.status >= 500) {
|
|
25
|
-
debugCliLogin('Auth server returned %d, retrying...', response.status);
|
|
26
|
-
return fetchAuth(loginHost, cliUrl, token, retries - 1);
|
|
27
|
-
}
|
|
28
|
-
if (!response.ok) {
|
|
29
|
-
debugCliLogin('Auth polling failed with status: %d', response.status);
|
|
30
|
-
throw new Error(`Login failed: ${response.statusText}`);
|
|
31
|
-
}
|
|
32
|
-
debugCliLogin('Access token received from auth server');
|
|
33
|
-
return (await response.json());
|
|
34
|
-
}
|
|
35
|
-
catch (error) {
|
|
36
|
-
if (retries > 0
|
|
37
|
-
&& error instanceof Error
|
|
38
|
-
&& error.message.includes('500')) {
|
|
39
|
-
debugCliLogin('Caught 500 error, retrying...');
|
|
40
|
-
return fetchAuth(loginHost, cliUrl, token, retries - 1);
|
|
41
|
-
}
|
|
42
|
-
debugCliLogin('Auth polling failed: %O', error);
|
|
43
|
-
throw error;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Perform browser-based OAuth login
|
|
48
|
-
*/
|
|
49
|
-
export async function browserLogin(config, browser) {
|
|
50
|
-
debugCliLogin('Starting browser login flow');
|
|
51
|
-
debugCliLogin('Creating auth session at %s/auth', config.loginHost);
|
|
52
|
-
// Create auth session
|
|
53
|
-
const response = await fetch(`${config.loginHost}/auth`, {
|
|
54
|
-
body: JSON.stringify({
|
|
55
|
-
description: `Heroku CLI login from ${hostname}`,
|
|
56
|
-
}),
|
|
57
|
-
headers: { 'Content-Type': 'application/json' },
|
|
58
|
-
method: 'POST',
|
|
59
|
-
});
|
|
60
|
-
if (!response.ok) {
|
|
61
|
-
debugCliLogin('Failed to create auth session: %d %s', response.status, response.statusText);
|
|
62
|
-
throw new Error(`Failed to initiate browser login: ${response.statusText}`);
|
|
63
|
-
}
|
|
64
|
-
const urls = (await response.json());
|
|
65
|
-
const url = `${config.loginHost}${urls.browser_url}`;
|
|
66
|
-
debugCliLogin('Auth session created, browser URL: %s', url);
|
|
67
|
-
console.error(`Opening browser to ${url}\n`);
|
|
68
|
-
let urlDisplayed = false;
|
|
69
|
-
const showUrl = () => {
|
|
70
|
-
if (!urlDisplayed)
|
|
71
|
-
console.warn('Cannot open browser.');
|
|
72
|
-
urlDisplayed = true;
|
|
73
|
-
};
|
|
74
|
-
showManualBrowserLoginUrl(url);
|
|
75
|
-
const open = (await import('open')).default;
|
|
76
|
-
debugCliLogin('Opening browser with %s', browser ? `browser: ${browser}` : 'default browser');
|
|
77
|
-
const cp = await open(url, {
|
|
78
|
-
wait: false,
|
|
79
|
-
...(browser ? { app: { name: browser } } : {}),
|
|
80
|
-
});
|
|
81
|
-
cp.on('error', err => {
|
|
82
|
-
debugCliLogin('Browser open error: %O', err);
|
|
83
|
-
console.warn(err);
|
|
84
|
-
showUrl();
|
|
85
|
-
});
|
|
86
|
-
if (process.env.HEROKU_TESTING_HEADLESS_LOGIN === '1')
|
|
87
|
-
showUrl();
|
|
88
|
-
cp.on('close', code => {
|
|
89
|
-
if (code !== 0) {
|
|
90
|
-
debugCliLogin('Browser process exited with non-zero code: %d', code);
|
|
91
|
-
showUrl();
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
console.error('heroku: Waiting for login...');
|
|
95
|
-
const auth = await fetchAuth(config.loginHost, urls.cli_url, urls.token);
|
|
96
|
-
if (auth.error) {
|
|
97
|
-
debugCliLogin('Auth response contained error: %s', auth.error);
|
|
98
|
-
throw new Error(auth.error);
|
|
99
|
-
}
|
|
100
|
-
console.error('Logging in...');
|
|
101
|
-
debugCliLogin('Fetching account information from %s/account', config.apiUrl);
|
|
102
|
-
const accountResponse = await fetch(`${config.apiUrl}/account`, {
|
|
103
|
-
headers: headers(auth.access_token),
|
|
104
|
-
});
|
|
105
|
-
if (!accountResponse.ok) {
|
|
106
|
-
debugCliLogin('Failed to fetch account: %d %s', accountResponse.status, accountResponse.statusText);
|
|
107
|
-
throw new Error(`Failed to fetch account: ${accountResponse.statusText}`);
|
|
108
|
-
}
|
|
109
|
-
const account = (await accountResponse.json());
|
|
110
|
-
debugCliLogin('Browser login successful for user: %s', account.email);
|
|
111
|
-
return {
|
|
112
|
-
login: account.email,
|
|
113
|
-
password: auth.access_token,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI Login Module
|
|
3
|
-
*
|
|
4
|
-
* Provides login/logout functionality for Heroku CLI tools.
|
|
5
|
-
* Supports browser-based, interactive (username/password), and SSO login methods.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```typescript
|
|
9
|
-
* import { Login } from '@heroku/heroku-fetch/cli-login';
|
|
10
|
-
* import { HerokuApiClient } from '@heroku/heroku-fetch';
|
|
11
|
-
*
|
|
12
|
-
* const client = new HerokuApiClient({ service: 'platform' });
|
|
13
|
-
* const login = new Login(client);
|
|
14
|
-
*
|
|
15
|
-
* // Browser-based login (default)
|
|
16
|
-
* await login.login({ method: 'browser' });
|
|
17
|
-
*
|
|
18
|
-
* // Interactive login with username/password
|
|
19
|
-
* await login.login({ method: 'interactive' });
|
|
20
|
-
*
|
|
21
|
-
* // Logout
|
|
22
|
-
* await login.logout();
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
|
-
import type { HerokuApiClient } from '../../client/index.js';
|
|
26
|
-
import type { LoginOptions } from './types.js';
|
|
27
|
-
export declare class Login {
|
|
28
|
-
private readonly heroku;
|
|
29
|
-
private config;
|
|
30
|
-
constructor(heroku: HerokuApiClient);
|
|
31
|
-
login(opts?: LoginOptions): Promise<void>;
|
|
32
|
-
logout(token?: string): Promise<void>;
|
|
33
|
-
}
|
|
34
|
-
export { type LoginOptions } from './types.js';
|
package/dist/cli/login/index.js
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI Login Module
|
|
3
|
-
*
|
|
4
|
-
* Provides login/logout functionality for Heroku CLI tools.
|
|
5
|
-
* Supports browser-based, interactive (username/password), and SSO login methods.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```typescript
|
|
9
|
-
* import { Login } from '@heroku/heroku-fetch/cli-login';
|
|
10
|
-
* import { HerokuApiClient } from '@heroku/heroku-fetch';
|
|
11
|
-
*
|
|
12
|
-
* const client = new HerokuApiClient({ service: 'platform' });
|
|
13
|
-
* const login = new Login(client);
|
|
14
|
-
*
|
|
15
|
-
* // Browser-based login (default)
|
|
16
|
-
* await login.login({ method: 'browser' });
|
|
17
|
-
*
|
|
18
|
-
* // Interactive login with username/password
|
|
19
|
-
* await login.login({ method: 'interactive' });
|
|
20
|
-
*
|
|
21
|
-
* // Logout
|
|
22
|
-
* await login.logout();
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
|
-
import * as readline from 'node:readline';
|
|
26
|
-
import { debugCliLogin } from '../../debug-loggers.js';
|
|
27
|
-
import { browserLogin } from './browser-login.js';
|
|
28
|
-
import { interactiveLogin } from './interactive-login.js';
|
|
29
|
-
import { clearTokens, getNetrc, getStoredLogin, getStoredToken, saveToken, } from './netrc-utils.js';
|
|
30
|
-
import { deleteOAuthTokens } from './oauth.js';
|
|
31
|
-
import { ssoLogin } from './sso-login.js';
|
|
32
|
-
import { LOGIN_TIMEOUT, THIRTY_DAYS } from './types.js';
|
|
33
|
-
// Re-export types for convenience
|
|
34
|
-
/**
|
|
35
|
-
* Prompt user for login method selection
|
|
36
|
-
*/
|
|
37
|
-
async function promptForLoginMethod() {
|
|
38
|
-
console.error('heroku: Press any key to open up the browser to login or \'q\' to exit');
|
|
39
|
-
const rl = readline.createInterface({
|
|
40
|
-
input: process.stdin,
|
|
41
|
-
output: process.stdout,
|
|
42
|
-
});
|
|
43
|
-
// Set raw mode to get immediate keypresses
|
|
44
|
-
process.stdin.setRawMode(true);
|
|
45
|
-
process.stdin.resume();
|
|
46
|
-
const key = await new Promise(resolve => {
|
|
47
|
-
process.stdin.once('data', data => {
|
|
48
|
-
const key = data.toString();
|
|
49
|
-
resolve(key);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
// Restore normal terminal settings
|
|
53
|
-
process.stdin.setRawMode(false);
|
|
54
|
-
rl.close();
|
|
55
|
-
console.error('');
|
|
56
|
-
return key;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Convert keypress to login method
|
|
60
|
-
*/
|
|
61
|
-
function getLoginMethodFromPromptKey(key) {
|
|
62
|
-
if (key === '\u0003') {
|
|
63
|
-
// Ctrl+C
|
|
64
|
-
throw new Error('Login cancelled by user');
|
|
65
|
-
}
|
|
66
|
-
if (key.toLowerCase() === 'q') {
|
|
67
|
-
throw new Error('Login cancelled by user');
|
|
68
|
-
}
|
|
69
|
-
return 'browser';
|
|
70
|
-
}
|
|
71
|
-
export class Login {
|
|
72
|
-
constructor(heroku) {
|
|
73
|
-
this.heroku = heroku;
|
|
74
|
-
this.config = {
|
|
75
|
-
apiHost: 'api.heroku.com',
|
|
76
|
-
apiUrl: process.env.HEROKU_API_URL || 'https://api.heroku.com',
|
|
77
|
-
httpGitHost: 'git.heroku.com',
|
|
78
|
-
loginHost: process.env.HEROKU_LOGIN_HOST || 'https://cli-auth.heroku.com',
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
async login(opts = {}) {
|
|
82
|
-
debugCliLogin('Login initiated with options: %O', opts);
|
|
83
|
-
let loggedIn = false;
|
|
84
|
-
try {
|
|
85
|
-
// timeout after 10 minutes
|
|
86
|
-
setTimeout(() => {
|
|
87
|
-
if (!loggedIn)
|
|
88
|
-
throw new Error('Login timed out');
|
|
89
|
-
}, LOGIN_TIMEOUT).unref();
|
|
90
|
-
if (process.env.HEROKU_API_KEY) {
|
|
91
|
-
debugCliLogin('Login blocked: HEROKU_API_KEY environment variable is set');
|
|
92
|
-
throw new Error('Cannot log in with HEROKU_API_KEY set');
|
|
93
|
-
}
|
|
94
|
-
if (opts.expiresIn && opts.expiresIn > THIRTY_DAYS) {
|
|
95
|
-
debugCliLogin('Login blocked: expiresIn (%d) exceeds 30 days', opts.expiresIn);
|
|
96
|
-
throw new Error('Cannot set an expiration longer than thirty days');
|
|
97
|
-
}
|
|
98
|
-
const netrc = getNetrc();
|
|
99
|
-
await netrc.load();
|
|
100
|
-
const previousEntry = netrc.machines[this.config.apiHost];
|
|
101
|
-
if (previousEntry) {
|
|
102
|
-
debugCliLogin('Found existing credentials in netrc for: %s', previousEntry.login);
|
|
103
|
-
}
|
|
104
|
-
let input = opts.method;
|
|
105
|
-
// Determine login method
|
|
106
|
-
if (input) {
|
|
107
|
-
debugCliLogin('Using specified login method: %s', input);
|
|
108
|
-
}
|
|
109
|
-
else if (opts.expiresIn) {
|
|
110
|
-
// can't use browser with --expires-in
|
|
111
|
-
input = 'interactive';
|
|
112
|
-
debugCliLogin('Selected interactive login (expiresIn specified)');
|
|
113
|
-
}
|
|
114
|
-
else if (process.env.HEROKU_LEGACY_SSO === '1') {
|
|
115
|
-
input = 'sso';
|
|
116
|
-
debugCliLogin('Selected SSO login (HEROKU_LEGACY_SSO=1)');
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
const key = await promptForLoginMethod();
|
|
120
|
-
input = getLoginMethodFromPromptKey(key);
|
|
121
|
-
debugCliLogin('Selected %s login (from user prompt)', input);
|
|
122
|
-
}
|
|
123
|
-
// Logout previous session
|
|
124
|
-
try {
|
|
125
|
-
if (previousEntry && previousEntry.password) {
|
|
126
|
-
debugCliLogin('Logging out previous session');
|
|
127
|
-
await this.logout(previousEntry.password);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
catch (error) {
|
|
131
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
132
|
-
debugCliLogin('Previous session logout failed: %s', message);
|
|
133
|
-
console.warn(message);
|
|
134
|
-
}
|
|
135
|
-
// Perform login based on selected method
|
|
136
|
-
let auth;
|
|
137
|
-
switch (input) {
|
|
138
|
-
case 'b':
|
|
139
|
-
case 'browser': {
|
|
140
|
-
debugCliLogin('Executing browser login');
|
|
141
|
-
auth = await browserLogin(this.config, opts.browser);
|
|
142
|
-
break;
|
|
143
|
-
}
|
|
144
|
-
case 'i':
|
|
145
|
-
case 'interactive': {
|
|
146
|
-
debugCliLogin('Executing interactive login');
|
|
147
|
-
const previousLogin = await getStoredLogin(this.config.apiHost);
|
|
148
|
-
auth = await interactiveLogin(this.config, previousLogin, opts.expiresIn);
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
|
-
case 's':
|
|
152
|
-
case 'sso': {
|
|
153
|
-
debugCliLogin('Executing SSO login');
|
|
154
|
-
auth = await ssoLogin(this.config);
|
|
155
|
-
break;
|
|
156
|
-
}
|
|
157
|
-
default: {
|
|
158
|
-
debugCliLogin('Unknown login method: %s, retrying', input);
|
|
159
|
-
return this.login(opts);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
await saveToken(auth, this.config.apiHost, this.config.httpGitHost);
|
|
163
|
-
debugCliLogin('Login completed successfully for user: %s', auth.login);
|
|
164
|
-
console.error(`Logged in as ${auth.login}`);
|
|
165
|
-
}
|
|
166
|
-
finally {
|
|
167
|
-
loggedIn = true;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
async logout(token) {
|
|
171
|
-
debugCliLogin('Logout initiated');
|
|
172
|
-
// Get token from netrc if not provided
|
|
173
|
-
if (!token) {
|
|
174
|
-
token = await getStoredToken(this.config.apiHost);
|
|
175
|
-
}
|
|
176
|
-
if (!token) {
|
|
177
|
-
debugCliLogin('no credentials to logout');
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
// Delete OAuth tokens and sessions from API
|
|
181
|
-
// If this fails (e.g., token not found), we still want to clear local netrc
|
|
182
|
-
try {
|
|
183
|
-
await deleteOAuthTokens(this.heroku, token);
|
|
184
|
-
}
|
|
185
|
-
catch (error) {
|
|
186
|
-
debugCliLogin('Failed to delete OAuth tokens from API: %O', error);
|
|
187
|
-
// Continue to clear netrc even if API call fails
|
|
188
|
-
}
|
|
189
|
-
// Clear netrc entries
|
|
190
|
-
await clearTokens(this.config.apiHost, this.config.httpGitHost);
|
|
191
|
-
console.error('Logged out');
|
|
192
|
-
}
|
|
193
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Interactive username/password login flow
|
|
3
|
-
*/
|
|
4
|
-
import type { LoginConfig, NetrcEntry } from './types.js';
|
|
5
|
-
/**
|
|
6
|
-
* Perform interactive username/password login
|
|
7
|
-
*/
|
|
8
|
-
export declare function interactiveLogin(config: LoginConfig, previousLogin?: string, expiresIn?: number): Promise<NetrcEntry>;
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Interactive username/password login flow
|
|
3
|
-
*/
|
|
4
|
-
import { prompt } from '@heroku/heroku-cli-util/hux';
|
|
5
|
-
import { debugCliLogin } from '../../debug-loggers.js';
|
|
6
|
-
import { createOAuthToken } from './oauth.js';
|
|
7
|
-
/**
|
|
8
|
-
* Perform interactive username/password login
|
|
9
|
-
*/
|
|
10
|
-
export async function interactiveLogin(config, previousLogin, expiresIn) {
|
|
11
|
-
debugCliLogin('Starting interactive login flow');
|
|
12
|
-
console.error('heroku: Enter your login credentials\n');
|
|
13
|
-
const email = await prompt('Email', {
|
|
14
|
-
default: previousLogin,
|
|
15
|
-
type: 'input',
|
|
16
|
-
});
|
|
17
|
-
const login = email;
|
|
18
|
-
debugCliLogin('Email provided: %s', login);
|
|
19
|
-
const password = await prompt('Password', {
|
|
20
|
-
type: 'password',
|
|
21
|
-
});
|
|
22
|
-
let auth;
|
|
23
|
-
try {
|
|
24
|
-
debugCliLogin('Creating OAuth token at %s', config.apiUrl);
|
|
25
|
-
auth = await createOAuthToken(config.apiUrl, login, password, { expiresIn });
|
|
26
|
-
debugCliLogin('OAuth token created successfully');
|
|
27
|
-
}
|
|
28
|
-
catch (error) {
|
|
29
|
-
if (error instanceof Error) {
|
|
30
|
-
const errBody = error.body;
|
|
31
|
-
if (errBody?.id === 'device_trust_required') {
|
|
32
|
-
debugCliLogin('Device trust required error - 2FA must be enabled');
|
|
33
|
-
throw new Error('The interactive flag requires Two-Factor Authentication to be enabled on your account. Please use browser login.');
|
|
34
|
-
}
|
|
35
|
-
if (errBody?.id === 'two_factor') {
|
|
36
|
-
debugCliLogin('2FA challenge required, prompting for code');
|
|
37
|
-
const secondFactor = await prompt('Two-factor code', {
|
|
38
|
-
type: 'password',
|
|
39
|
-
});
|
|
40
|
-
debugCliLogin('Retrying OAuth token creation with 2FA code');
|
|
41
|
-
auth = await createOAuthToken(config.apiUrl, login, password, {
|
|
42
|
-
expiresIn,
|
|
43
|
-
secondFactor,
|
|
44
|
-
});
|
|
45
|
-
debugCliLogin('OAuth token created successfully with 2FA');
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
debugCliLogin('OAuth token creation failed: %O', error);
|
|
49
|
-
throw error;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
debugCliLogin('Unexpected error during OAuth token creation: %O', error);
|
|
54
|
-
throw error;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
debugCliLogin('Interactive login successful for user: %s', auth.login);
|
|
58
|
-
return auth;
|
|
59
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Netrc file management utilities
|
|
3
|
-
*/
|
|
4
|
-
import { Netrc } from 'netrc-parser';
|
|
5
|
-
import type { NetrcEntry } from './types.js';
|
|
6
|
-
export declare function getNetrc(): Netrc;
|
|
7
|
-
/**
|
|
8
|
-
* Save authentication token to netrc file
|
|
9
|
-
*/
|
|
10
|
-
export declare function saveToken(entry: NetrcEntry, apiHost: string, httpGitHost: string): Promise<void>;
|
|
11
|
-
/**
|
|
12
|
-
* Clear authentication tokens from netrc file
|
|
13
|
-
*/
|
|
14
|
-
export declare function clearTokens(apiHost: string, httpGitHost: string): Promise<void>;
|
|
15
|
-
/**
|
|
16
|
-
* Get stored token from netrc file
|
|
17
|
-
*/
|
|
18
|
-
export declare function getStoredToken(apiHost: string): Promise<string | undefined>;
|
|
19
|
-
/**
|
|
20
|
-
* Get stored login from netrc file
|
|
21
|
-
*/
|
|
22
|
-
export declare function getStoredLogin(apiHost: string): Promise<string | undefined>;
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Netrc file management utilities
|
|
3
|
-
*/
|
|
4
|
-
import { Netrc } from 'netrc-parser';
|
|
5
|
-
import { debugCliLogin } from '../../debug-loggers.js';
|
|
6
|
-
// Defer netrc instantiation to avoid eager file operations
|
|
7
|
-
let _netrc;
|
|
8
|
-
export function getNetrc() {
|
|
9
|
-
if (!_netrc) {
|
|
10
|
-
_netrc = new Netrc();
|
|
11
|
-
}
|
|
12
|
-
return _netrc;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Save authentication token to netrc file
|
|
16
|
-
*/
|
|
17
|
-
export async function saveToken(entry, apiHost, httpGitHost) {
|
|
18
|
-
debugCliLogin('Saving credentials to netrc for hosts: %s, %s', apiHost, httpGitHost);
|
|
19
|
-
const netrc = getNetrc();
|
|
20
|
-
await netrc.load();
|
|
21
|
-
const hosts = [apiHost, httpGitHost];
|
|
22
|
-
for (const host of hosts) {
|
|
23
|
-
if (!netrc.machines[host])
|
|
24
|
-
netrc.machines[host] = {};
|
|
25
|
-
netrc.machines[host].login = entry.login;
|
|
26
|
-
netrc.machines[host].password = entry.password;
|
|
27
|
-
delete netrc.machines[host].method;
|
|
28
|
-
delete netrc.machines[host].org;
|
|
29
|
-
debugCliLogin('Set credentials for host: %s (login: %s)', host, entry.login);
|
|
30
|
-
}
|
|
31
|
-
if (netrc.machines._tokens) {
|
|
32
|
-
// eslint-disable-next-line unicorn/no-array-for-each
|
|
33
|
-
netrc.machines._tokens.forEach((token) => {
|
|
34
|
-
if (hosts.includes(token.host)) {
|
|
35
|
-
token.internalWhitespace = '\n ';
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
await netrc.save();
|
|
40
|
-
debugCliLogin('Netrc file saved successfully');
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Clear authentication tokens from netrc file
|
|
44
|
-
*/
|
|
45
|
-
export async function clearTokens(apiHost, httpGitHost) {
|
|
46
|
-
debugCliLogin('Clearing credentials from netrc for hosts: %s, %s', apiHost, httpGitHost);
|
|
47
|
-
const netrc = getNetrc();
|
|
48
|
-
await netrc.load();
|
|
49
|
-
delete netrc.machines[apiHost];
|
|
50
|
-
delete netrc.machines[httpGitHost];
|
|
51
|
-
await netrc.save();
|
|
52
|
-
debugCliLogin('Credentials cleared from netrc successfully');
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Get stored token from netrc file
|
|
56
|
-
*/
|
|
57
|
-
export async function getStoredToken(apiHost) {
|
|
58
|
-
debugCliLogin('Reading stored token from netrc for host: %s', apiHost);
|
|
59
|
-
const netrc = getNetrc();
|
|
60
|
-
await netrc.load();
|
|
61
|
-
const entry = netrc.machines[apiHost];
|
|
62
|
-
if (entry?.password) {
|
|
63
|
-
debugCliLogin('Token found in netrc for host: %s', apiHost);
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
debugCliLogin('No token found in netrc for host: %s', apiHost);
|
|
67
|
-
}
|
|
68
|
-
return entry?.password;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Get stored login from netrc file
|
|
72
|
-
*/
|
|
73
|
-
export async function getStoredLogin(apiHost) {
|
|
74
|
-
debugCliLogin('Reading stored login from netrc for host: %s', apiHost);
|
|
75
|
-
const netrc = getNetrc();
|
|
76
|
-
await netrc.load();
|
|
77
|
-
const entry = netrc.machines[apiHost];
|
|
78
|
-
if (entry?.login) {
|
|
79
|
-
debugCliLogin('Login found in netrc for host: %s (login: %s)', apiHost, entry.login);
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
debugCliLogin('No login found in netrc for host: %s', apiHost);
|
|
83
|
-
}
|
|
84
|
-
return entry?.login;
|
|
85
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OAuth token operations
|
|
3
|
-
*/
|
|
4
|
-
import type { HerokuApiClient } from '../../client/index.js';
|
|
5
|
-
import type { NetrcEntry } from './types.js';
|
|
6
|
-
/**
|
|
7
|
-
* Create an OAuth token via the Heroku API
|
|
8
|
-
*/
|
|
9
|
-
export declare function createOAuthToken(apiUrl: string, username: string, password: string, opts?: {
|
|
10
|
-
expiresIn?: number;
|
|
11
|
-
secondFactor?: string;
|
|
12
|
-
}): Promise<NetrcEntry>;
|
|
13
|
-
/**
|
|
14
|
-
* Get the default OAuth token for the current user
|
|
15
|
-
*/
|
|
16
|
-
export declare function getDefaultToken(client: HerokuApiClient): Promise<string | undefined>;
|
|
17
|
-
/**
|
|
18
|
-
* Delete OAuth session and authorizations for the given token
|
|
19
|
-
*/
|
|
20
|
-
export declare function deleteOAuthTokens(client: HerokuApiClient, token: string): Promise<void>;
|