UNPKG

@azure/static-web-apps-cli

Version:
144 lines 7 kB
import chalk from "chalk"; import dotenv from "dotenv"; import { existsSync, promises as fs } from "node:fs"; import path from "node:path"; import { logger, logGitHubIssueMessageAndExit } from "../../../core/utils/logger.js"; import { authenticateWithAzureIdentity, listSubscriptions, listTenants } from "../../../core/account.js"; import { AZURE_LOGIN_CONFIG, ENV_FILENAME } from "../../../core/constants.js"; import { pathExists, safeReadJson } from "../../../core/utils/file.js"; import { updateGitIgnore } from "../../../core/git.js"; import { chooseSubscription, chooseTenant } from "../../../core/prompts.js"; import { Environment } from "../../../core/swa-cli-persistence-plugin/impl/azure-environment.js"; const defaultScope = `${Environment.AzureCloud.resourceManagerEndpointUrl}/.default`; export async function loginCommand(options) { try { const { credentialChain, subscriptionId } = await login(options); if (credentialChain && subscriptionId) { logger.log(chalk.green(`✔ Successfully setup project!`)); } else { logger.log(chalk.red(`✘ Failed to setup project!`)); logGitHubIssueMessageAndExit(); } } catch (error) { logger.error(`Failed to setup project: ${error.message}`); logGitHubIssueMessageAndExit(); } } export async function login(options) { let credentialChain = undefined; logger.log(`Checking Azure session...`); let tenantId = options.tenantId; let clientId = options.clientId; let clientSecret = options.clientSecret; credentialChain = await authenticateWithAzureIdentity({ tenantId, clientId, clientSecret }, options.useKeychain, options.clearCredentials); if (await credentialChain.getToken(defaultScope)) { logger.log(chalk.green(`✔ Successfully logged into Azure!`)); } options = await tryGetAzTenantAndSubscription(options); return await setupProjectCredentials(options, credentialChain); } async function setupProjectCredentials(options, credentialChain) { let { subscriptionId, tenantId, clientId, clientSecret } = options; // If the user has not specified a tenantId, we will prompt them to choose one if (!tenantId) { const tenants = await listTenants(credentialChain); if (tenants.length === 0) { throw new Error(`No Azure tenants found in your account.\n Please read https://docs.microsoft.com/azure/cost-management-billing/manage/troubleshoot-sign-in-issue`); } else if (tenants.length === 1) { logger.silly(`Found 1 tenant: ${tenants[0].tenantId}`); tenantId = tenants[0].tenantId; } else { const tenant = await chooseTenant(tenants, options.tenantId); tenantId = tenant?.tenantId; // login again with the new tenant // TODO: can we silently authenticate the user with the new tenant? credentialChain = await authenticateWithAzureIdentity({ tenantId, clientId, clientSecret }, options.useKeychain, true); if (await credentialChain.getToken(defaultScope)) { logger.log(chalk.green(`✔ Successfully logged into Azure tenant: ${tenantId}`)); } } } logger.silly(`Selected tenant: ${tenantId}`); // If the user has not specified a subscriptionId, we will prompt them to choose one if (!subscriptionId) { const subscriptions = await listSubscriptions(credentialChain); if (subscriptions.length === 0) { throw new Error(`No valid subscription found for tenant ${tenantId}.\n Please read https://docs.microsoft.com/azure/cost-management-billing/manage/no-subscriptions-found`); } else if (subscriptions.length === 1) { logger.silly(`Found 1 subscription: ${subscriptions[0].subscriptionId}`); subscriptionId = subscriptions[0].subscriptionId; } else { const subscription = await chooseSubscription(subscriptions, subscriptionId); subscriptionId = subscription?.subscriptionId; } } logger.silly(`Selected subscription: ${subscriptionId}`); logger.silly(`Project credentials:`); logger.silly({ subscriptionId, tenantId, clientId, clientSecret }); await storeProjectCredentialsInEnvFile(subscriptionId, tenantId, clientId, clientSecret); return { credentialChain, subscriptionId: subscriptionId, }; } async function storeProjectCredentialsInEnvFile(subscriptionId, tenantId, clientId, clientSecret) { const envFile = path.join(process.cwd(), ENV_FILENAME); const envFileExists = existsSync(envFile); const envFileContent = envFileExists ? await fs.readFile(envFile, "utf8") : ""; const buf = Buffer.from(envFileContent); // in case the .env file format changes in the future, we can use the following to parse the file const config = dotenv.parse(buf); const oldEnvFileLines = Object.keys(config).map((key) => `${key}=${config[key]}`); const newEnvFileLines = []; let entry = `AZURE_SUBSCRIPTION_ID=${subscriptionId}`; if (subscriptionId && !envFileContent.includes(entry)) { newEnvFileLines.push(entry); } entry = `AZURE_TENANT_ID=${tenantId}`; if (tenantId && !envFileContent.includes(entry)) { newEnvFileLines.push(entry); } entry = `AZURE_CLIENT_ID=${clientId}`; if (clientId && !envFileContent.includes(entry)) { newEnvFileLines.push(entry); } entry = `AZURE_CLIENT_SECRET=${clientSecret}`; if (clientSecret && !envFileContent.includes(entry)) { newEnvFileLines.push(entry); } // write file if we have at least one new env line if (newEnvFileLines.length > 0) { const envFileContentWithProjectDetails = [...oldEnvFileLines, ...newEnvFileLines].join("\n"); await fs.writeFile(envFile, envFileContentWithProjectDetails); logger.log(chalk.green(`✔ Saved project credentials in ${ENV_FILENAME} file.`)); await updateGitIgnore(ENV_FILENAME); } } async function tryGetAzTenantAndSubscription(options) { const doesAzureConfigExist = await pathExists(AZURE_LOGIN_CONFIG); if (!doesAzureConfigExist) { return options; } else { logger.silly(`Found an existing Azure config file, getting Tenant and Subscription Id from ${AZURE_LOGIN_CONFIG}`); const azureProfile = await safeReadJson(AZURE_LOGIN_CONFIG); if (azureProfile) { const allSubscriptions = azureProfile.subscriptions; if (allSubscriptions) { const defaultAzureInfo = allSubscriptions.find((subscription) => subscription.isDefault == true); if (defaultAzureInfo) { options.tenantId = defaultAzureInfo.tenantId; options.subscriptionId = defaultAzureInfo.id; } } } return options; } } //# sourceMappingURL=login.js.map