From 38dab980fd7a80962d028fe54abcfb6cbaea8de3 Mon Sep 17 00:00:00 2001 From: John Hockett Date: Tue, 16 Feb 2021 12:47:00 -0800 Subject: [PATCH] fix(amplify-provider-awscloudformation): admin token refresh, configure project (#6629) --- .../package.json | 1 - .../src/admin-login.ts | 6 +-- .../src/configuration-manager.ts | 37 ++++++++------ .../src/utils/admin-helpers.ts | 50 ++++++++++--------- 4 files changed, 52 insertions(+), 42 deletions(-) diff --git a/packages/amplify-provider-awscloudformation/package.json b/packages/amplify-provider-awscloudformation/package.json index 632f22d9ed1..6a7fe9feb48 100644 --- a/packages/amplify-provider-awscloudformation/package.json +++ b/packages/amplify-provider-awscloudformation/package.json @@ -106,7 +106,6 @@ "moment": "^2.24.0", "netmask": "^1.0.6", "node-fetch": "^2.6.1", - "open": "^7.0.0", "ora": "^4.0.3", "promise-sequential": "^1.1.1", "proxy-agent": "^3.1.1", diff --git a/packages/amplify-provider-awscloudformation/src/admin-login.ts b/packages/amplify-provider-awscloudformation/src/admin-login.ts index b2cde07dbf8..58c53e11d08 100644 --- a/packages/amplify-provider-awscloudformation/src/admin-login.ts +++ b/packages/amplify-provider-awscloudformation/src/admin-login.ts @@ -1,11 +1,11 @@ -import open from 'open'; import ora from 'ora'; -import { $TSContext } from 'amplify-cli-core'; +import { $TSContext, open } from 'amplify-cli-core'; import { adminVerifyUrl, adminBackendMap, isAmplifyAdminApp } from './utils/admin-helpers'; import { AdminLoginServer } from './utils/admin-login-server'; -export async function adminLoginFlow(context: $TSContext, appId: string, envName: string, region?: string) { +export async function adminLoginFlow(context: $TSContext, appId: string, envName?: string, region?: string) { + envName = envName || context.amplify.getEnvInfo().envName; if (!region) { const { isAdminApp, region: _region } = await isAmplifyAdminApp(appId); if (!isAdminApp) { diff --git a/packages/amplify-provider-awscloudformation/src/configuration-manager.ts b/packages/amplify-provider-awscloudformation/src/configuration-manager.ts index 32c0b5fe9d3..ad08d52dc30 100644 --- a/packages/amplify-provider-awscloudformation/src/configuration-manager.ts +++ b/packages/amplify-provider-awscloudformation/src/configuration-manager.ts @@ -5,7 +5,6 @@ import { prompt } from 'inquirer'; import _ from 'lodash'; import path from 'path'; import proxyAgent from 'proxy-agent'; -import { adminLoginFlow } from './admin-login'; import awsRegions from './aws-regions'; import constants from './constants'; import setupNewUser from './setup-new-user'; @@ -346,13 +345,29 @@ async function promptForAuthConfig(context: $TSContext, authConfig?: AuthFlowCon let answers: $TSAny; if (availableProfiles && availableProfiles.length > 0) { - const authType = authConfig.type ?? (await askAuthType()); + let authType: AuthFlow; + let isAdminApp = false; + if (authConfig?.type) { + authType = authConfig.type; + } else { + try { + const appId = resolveAppId(context); + isAdminApp = (await isAmplifyAdminApp(appId))?.isAdminApp || false; + } catch { + isAdminApp = false; + } + authType = await askAuthType(isAdminApp); + } if (authType === 'profile') { printProfileInfo(context); awsConfigInfo.config.useProfile = true; answers = await prompt(profileNameQuestion(availableProfiles, awsConfigInfo.config.profileName)); awsConfigInfo.config.profileName = answers.profileName; return; + } else if (authType === 'admin') { + awsConfigInfo.configLevel = 'amplifyAdmin'; + awsConfigInfo.config.useProfile = false; + return; } } else { awsConfigInfo.config.useProfile = false; @@ -547,20 +562,16 @@ export async function loadConfigurationForEnv(context: $TSContext, env: string, const projectConfigInfo = getConfigForEnv(context, env); const authType = await determineAuthFlow(context, projectConfigInfo); - const { print, usageData } = context; let awsConfig: AwsSdkConfig; if (authType.type === 'admin') { projectConfigInfo.configLevel = 'amplifyAdmin'; appId = appId || authType.appId; - if (!doAdminTokensExist(appId)) { - adminLoginFlow(context, appId, env, authType.region); - } try { - awsConfig = await getTempCredsWithAdminTokens(appId, print); - } catch (error) { - print.error(`Failed to get credentials: ${error.message || error}`); - await usageData.emitError(error); + awsConfig = await getTempCredsWithAdminTokens(context, appId); + } catch (e) { + context.print.error(`Failed to get credentials: ${e.message || e}`); + await context.usageData.emitError(e); exitOnNextTick(1); } } else if (authType.type === 'profile') { @@ -698,12 +709,8 @@ export async function getAwsConfig(context: $TSContext): Promise { } } else if (awsConfigInfo.configLevel === 'amplifyAdmin') { const appId = resolveAppId(context); - - if (!doAdminTokensExist(appId)) { - await adminLoginFlow(context, appId, context.amplify.getEnvInfo().envName); - } try { - awsConfig = await getTempCredsWithAdminTokens(appId, context.print); + awsConfig = await getTempCredsWithAdminTokens(context, appId); } catch (err) { context.print.error('Failed to fetch Amplify Admin credentials'); throw new Error(err); diff --git a/packages/amplify-provider-awscloudformation/src/utils/admin-helpers.ts b/packages/amplify-provider-awscloudformation/src/utils/admin-helpers.ts index ac46f1c3cde..466972ba8c8 100644 --- a/packages/amplify-provider-awscloudformation/src/utils/admin-helpers.ts +++ b/packages/amplify-provider-awscloudformation/src/utils/admin-helpers.ts @@ -2,6 +2,7 @@ import { stateManager, $TSContext } from 'amplify-cli-core'; import aws from 'aws-sdk'; import _ from 'lodash'; import fetch from 'node-fetch'; +import { adminLoginFlow } from '../admin-login'; import { AdminAuthConfig, AwsSdkConfig, CognitoAccessToken, CognitoIdToken } from './auth-types'; export const adminVerifyUrl = (appId: string, envName: string, region: string): string => { @@ -27,8 +28,11 @@ export async function isAmplifyAdminApp(appId: string): Promise<{ isAdminApp: bo return { isAdminApp: !!appState.appId, region: appState.region }; } -export async function getTempCredsWithAdminTokens(appId: string, print: $TSContext['print']): Promise { - const authConfig = await getRefreshedTokens(appId, print); +export async function getTempCredsWithAdminTokens(context: $TSContext, appId: string): Promise { + if (!doAdminTokensExist(appId)) { + await adminLoginFlow(context, appId); + } + const authConfig = await getRefreshedTokens(context, appId); const { idToken, IdentityId, region } = authConfig; // use tokens to get creds and assign to config const awsConfig = await getAdminCognitoCredentials(idToken, IdentityId, region); @@ -82,17 +86,23 @@ async function getAdminStsCredentials(idToken: CognitoIdToken, region: string): }; } -export async function getRefreshedTokens(appId: string, print: $TSContext['print']) { +async function getRefreshedTokens(context: $TSContext, appId: string) { // load token, check expiry, refresh if needed const authConfig: AdminAuthConfig = stateManager.getAmplifyAdminConfigEntry(appId); if (isJwtExpired(authConfig.idToken)) { - const refreshedTokens = await refreshJWTs(authConfig, print); - // Refresh stored tokens - authConfig.accessToken.jwtToken = refreshedTokens.AccessToken; - authConfig.idToken.jwtToken = refreshedTokens.IdToken; - authConfig.refreshToken.token = refreshedTokens.RefreshToken; - stateManager.setAmplifyAdminConfigEntry(appId, authConfig); + let refreshedTokens: aws.CognitoIdentityServiceProvider.AuthenticationResultType; + try { + refreshedTokens = (await refreshJWTs(authConfig)).AuthenticationResult; + // Refresh stored tokens + authConfig.accessToken.jwtToken = refreshedTokens.AccessToken; + authConfig.idToken.jwtToken = refreshedTokens.IdToken; + authConfig.refreshToken.token = refreshedTokens.RefreshToken; + stateManager.setAmplifyAdminConfigEntry(appId, authConfig); + } catch { + // Refresh failed, fall back to login + await adminLoginFlow(context, appId, null, authConfig.region); + } } return authConfig; } @@ -103,21 +113,15 @@ function isJwtExpired(token: CognitoAccessToken | CognitoIdToken) { return secSinceEpoch >= expiration - 60; } -async function refreshJWTs(authConfig: AdminAuthConfig, print: $TSContext['print']) { +async function refreshJWTs(authConfig: AdminAuthConfig) { const CognitoISP = new aws.CognitoIdentityServiceProvider({ region: authConfig.region }); - try { - const result = await CognitoISP.initiateAuth({ - AuthFlow: 'REFRESH_TOKEN', - AuthParameters: { - REFRESH_TOKEN: authConfig.refreshToken.token, - }, - ClientId: authConfig.accessToken.payload.client_id, // App client id from identityPool - }).promise(); - return result.AuthenticationResult; - } catch (e) { - print.error(`Failed to refresh tokens: ${e.message || 'Unknown error occurred'}`); - throw e; - } + return await CognitoISP.initiateAuth({ + AuthFlow: 'REFRESH_TOKEN', + AuthParameters: { + REFRESH_TOKEN: authConfig.refreshToken.token, + }, + ClientId: authConfig.accessToken.payload.client_id, // App client id from identityPool + }).promise(); } export const adminBackendMap: {