@heroku-cli/command
Version:
base class for Heroku CLI commands
183 lines (182 loc) • 7.66 kB
JavaScript
import { ux } from '@oclif/core';
import debug from 'debug';
import tsheredoc from 'tsheredoc';
import { LinuxHandler } from './credential-handlers/linux-handler.js';
import { MacOSHandler } from './credential-handlers/macos-handler.js';
import { NetrcHandler } from './credential-handlers/netrc-handler.js';
import { WindowsHandler } from './credential-handlers/windows-handler.js';
import { reportCredentialStoreError } from './lib/cli-command-telemetry.js';
import { CredentialStore, getNativeCredentialStore, getStorageConfig } from './lib/credential-storage-selector.js';
const credDebug = debug('heroku-credential-manager');
const heredoc = tsheredoc.default;
const SERVICE_NAME = 'heroku-cli';
/**
* Saves authentication credentials to the native credential store (if available) and .netrc file.
*
* @param account - User's account (email)
* @param token - Authentication token
* @param hosts - Hostname(s) for netrc storage (e.g., ['api.heroku.com'])
* @param service - Service name (defaults to 'heroku-cli')
* @returns Promise that resolves when credentials are saved
*/
export async function saveAuth(account, token, hosts, service = SERVICE_NAME) {
const config = getStorageConfig();
const netrcHandler = new NetrcHandler();
if (config.credentialStore) {
try {
const handler = getCredentialHandler(config.credentialStore);
handler.saveAuth({ account, service, token });
}
catch (error) {
const { message } = error;
credDebug(message);
if (process.env.HEROKU_KEYCHAIN_WARNINGS !== 'off') {
ux.warn(heredoc(`
We can't save the Heroku token to your computer's keychain.
We'll save the token to the .netrc file instead.
To turn off this warning, set HEROKU_KEYCHAIN_WARNINGS to "off".`));
}
await reportCredentialStoreError(error, {
credentialStore: config.credentialStore,
operation: 'saveAuth',
});
}
}
if (config.useNetrc && hosts.length > 0) {
const netrcAuth = {
login: account,
password: token,
};
await netrcHandler.saveAuthForHosts(netrcAuth, hosts);
}
}
/**
* Retrieves authentication credentials from the native credential store (if available) or .netrc file.
*
* @param account - User's account (email), or undefined to search for account
* @param host - Hostname for netrc lookup (e.g., 'api.heroku.com')
* @param service - Service name (defaults to 'heroku-cli')
* @returns Promise that resolves with the authentication account and token.
* @throws Error if no credentials are found in either location.
*/
export async function getAuth(account, host, service = SERVICE_NAME) {
const config = getStorageConfig();
const netrcHandler = new NetrcHandler();
if (config.credentialStore && account) {
try {
const handler = getCredentialHandler(config.credentialStore);
const token = handler.getAuth(account, service);
return { account, token };
}
catch (error) {
const { message } = error;
credDebug(message);
if (process.env.HEROKU_KEYCHAIN_WARNINGS !== 'off') {
ux.warn(heredoc(`
We can't retrieve the Heroku token from your computer's keychain.
We'll try to retrieve the token from the .netrc file instead.
To turn off this warning, set HEROKU_KEYCHAIN_WARNINGS to "off".`));
}
await reportCredentialStoreError(error, {
credentialStore: config.credentialStore,
operation: 'getAuth',
});
}
}
if (config.useNetrc) {
const auth = await netrcHandler.getAuth(host);
if (!auth.password) {
throw new Error('No auth found');
}
return { account: auth.login, token: auth.password };
}
throw new Error('No auth found');
}
/**
* Lists all accounts stored in the native credential store for a given service.
*
* @param service - Service name (defaults to 'heroku-cli')
* @returns Array of account names, or empty array if no native credential store is available
*/
export async function listKeychainAccounts(service = SERVICE_NAME) {
const config = getStorageConfig();
if (config.credentialStore) {
try {
const handler = getCredentialHandler(config.credentialStore);
return handler.listAccounts(service);
}
catch (error) {
const { message } = error;
credDebug(message);
await reportCredentialStoreError(error, {
credentialStore: config.credentialStore,
operation: 'listKeychainAccounts',
});
return [];
}
}
return [];
}
/**
* Removes authentication credentials from the platform native store (when present) and .netrc.
* Uses {@link getNativeCredentialStore} so legacy HEROKU_NETRC_WRITE-only mode does not skip Keychain/vault cleanup after a mixed login.
*
* @param account - User's account (email), or undefined when using HEROKU_API_KEY only (native removal is skipped)
* @param hosts - Hostname(s) for netrc storage (e.g., ['api.heroku.com'])
* @param service - Service name (defaults to 'heroku-cli')
* @returns Promise that resolves when credentials are removed
*/
export async function removeAuth(account, hosts, service = SERVICE_NAME) {
const config = getStorageConfig();
const netrcHandler = new NetrcHandler();
const nativeStore = getNativeCredentialStore();
if (nativeStore && account) {
try {
const handler = getCredentialHandler(nativeStore);
handler.removeAuth(account, service);
}
catch (error) {
const { message } = error;
credDebug(message);
if (process.env.HEROKU_KEYCHAIN_WARNINGS !== 'off') {
ux.warn(heredoc(`
We can't remove the Heroku token from your computer's keychain.
We'll remove the token from the .netrc file instead.
To turn off this warning, set HEROKU_KEYCHAIN_WARNINGS to "off".`));
}
await reportCredentialStoreError(error, {
credentialStore: nativeStore,
operation: 'removeAuth',
});
}
}
if (config.useNetrc && hosts.length > 0) {
await netrcHandler.removeAuthForHosts(hosts);
}
}
/**
* Factory function to create the appropriate credential handler based on platform.
* @private
* @param store - The type of credential store to use
* @returns A handler instance for the specified store
*/
export function getCredentialHandler(store) {
switch (store) {
case CredentialStore.LinuxSecretService: {
return new LinuxHandler();
}
case CredentialStore.MacOSKeychain: {
return new MacOSHandler();
}
case CredentialStore.WindowsCredentialManager: {
return new WindowsHandler();
}
}
}
export { LinuxHandler } from './credential-handlers/linux-handler.js';
export { MacOSHandler } from './credential-handlers/macos-handler.js';
export { NetrcHandler } from './credential-handlers/netrc-handler.js';
export { WindowsHandler } from './credential-handlers/windows-handler.js';
export { CredentialStore, getNativeCredentialStore, getStorageConfig } from './lib/credential-storage-selector.js';
export { deleteLoginState, readLoginState, writeLoginState } from './lib/login-state.js';
export { Netrc, parse } from './lib/netrc-parser.js';