diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough.ts index 823b02f622e..dde284315c9 100644 --- a/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough.ts +++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough.ts @@ -12,7 +12,15 @@ import { authConfigToAppSyncAuthType } from '../utils/auth-config-to-app-sync-au import { resolverConfigToConflictResolution } from '../utils/resolver-config-to-conflict-resolution-bi-di-mapper'; import _ from 'lodash'; import { getAppSyncAuthConfig, checkIfAuthExists, authConfigHasApiKey } from '../utils/amplify-meta-utils'; -import { ResourceAlreadyExistsError, ResourceDoesNotExistError, UnknownResourceTypeError, exitOnNextTick } from 'amplify-cli-core'; +import { + ResourceAlreadyExistsError, + ResourceDoesNotExistError, + UnknownResourceTypeError, + exitOnNextTick, + stateManager, + $TSContext, +} from 'amplify-cli-core'; +const { doAdminCredentialsExist } = require('amplify-provider-awscloudformation'); const serviceName = 'AppSync'; const providerName = 'awscloudformation'; @@ -37,8 +45,8 @@ const authProviderChoices = [ }, ]; -export const openConsole = context => { - const amplifyMeta = context.amplify.getProjectMeta(); +export const openConsole = (context: $TSContext) => { + const amplifyMeta = stateManager.getMeta(); const categoryAmplifyMeta = amplifyMeta[category]; let appSyncMeta; Object.keys(categoryAmplifyMeta).forEach(resourceName => { @@ -49,16 +57,21 @@ export const openConsole = context => { if (appSyncMeta) { const { GraphQLAPIIdOutput } = appSyncMeta; + const appId = amplifyMeta.providers[providerName].AmplifyAppId; const { Region } = amplifyMeta.providers[providerName]; + let consoleUrl = `https://console.aws.amazon.com/appsync/home?region=${Region}#/${GraphQLAPIIdOutput}/v1/queries`; - const consoleUrl = `https://console.aws.amazon.com/appsync/home?region=${Region}#/${GraphQLAPIIdOutput}/v1/queries`; + if (doAdminCredentialsExist(appId)) { + const { envName } = context.amplify.getEnvInfo(); + consoleUrl = `https://www.dracarys.app/admin/${appId}/${envName}/datastore`; + } open(consoleUrl, { wait: false }); } else { context.print.error('AppSync API is not pushed in the cloud.'); } }; -export const serviceWalkthrough = async (context, defaultValuesFilename, serviceMetadata) => { +export const serviceWalkthrough = async (context: $TSContext, defaultValuesFilename, serviceMetadata) => { const resourceName = resourceAlreadyExists(context); let authConfig; let defaultAuthType; diff --git a/packages/amplify-category-auth/src/provider-utils/awscloudformation/constants.ts b/packages/amplify-category-auth/src/provider-utils/awscloudformation/constants.ts index 762308a0dd3..75ca7594c10 100644 --- a/packages/amplify-category-auth/src/provider-utils/awscloudformation/constants.ts +++ b/packages/amplify-category-auth/src/provider-utils/awscloudformation/constants.ts @@ -62,3 +62,9 @@ export const privateKeys = [ 'addLogoutOnUpdate', 'additionalQuestions', ]; + +// amplify console auth options +export const UserPool = 'User Pool'; +export const IdentityPool = 'Identity Pool'; +export const BothPools = `${UserPool} and ${IdentityPool}`; +export const AmplifyAdmin = 'Amplify admin UI'; diff --git a/packages/amplify-category-auth/src/provider-utils/awscloudformation/index.js b/packages/amplify-category-auth/src/provider-utils/awscloudformation/index.js index ed6a312e355..3b4e82cba1c 100644 --- a/packages/amplify-category-auth/src/provider-utils/awscloudformation/index.js +++ b/packages/amplify-category-auth/src/provider-utils/awscloudformation/index.js @@ -4,10 +4,11 @@ const _ = require('lodash'); const { stateManager } = require('amplify-cli-core'); const { getAuthResourceName } = require('../../utils/getAuthResourceName'); const { copyCfnTemplate, saveResourceParameters } = require('./utils/synthesize-resources'); -const { ENV_SPECIFIC_PARAMS, privateKeys } = require('./constants'); +const { ENV_SPECIFIC_PARAMS, AmplifyAdmin, UserPool, IdentityPool, BothPools, privateKeys } = require('./constants'); const { getAddAuthHandler, getUpdateAuthHandler } = require('./handlers/resource-handlers'); const { supportedServices } = require('../supported-services'); const { importResource, importedAuthEnvInit } = require('./import'); +const { doAdminCredentialsExist } = require('amplify-provider-awscloudformation'); function serviceQuestions(context, defaultValuesFilename, stringMapsFilename, serviceWalkthroughFilename, serviceMetadata) { const serviceWalkthroughSrc = `${__dirname}/service-walkthroughs/${serviceWalkthroughFilename}`; @@ -295,23 +296,32 @@ function getRequiredParamsForHeadlessInit(projectType, previousValues) { async function console(context, amplifyMeta) { const cognitoOutput = getCognitoOutput(amplifyMeta); if (cognitoOutput) { - const { Region } = amplifyMeta.providers.awscloudformation; + const { AmplifyAppId, Region } = amplifyMeta.providers.awscloudformation; if (cognitoOutput.UserPoolId && cognitoOutput.IdentityPoolId) { + let choices = [UserPool, IdentityPool, BothPools]; + let adminOption = doAdminCredentialsExist(AmplifyAppId); + if (adminOption) { + choices = [AmplifyAdmin, ...choices]; + } const answer = await inquirer.prompt({ name: 'selection', type: 'list', message: 'Which console', - choices: ['User Pool', 'Identity Pool', 'Both'], - default: 'Both', + choices, + default: adminOption ? AmplifyAdmin : BothPools, }); switch (answer.selection) { - case 'User Pool': + case AmplifyAdmin: + await openAdminUI(context, AmplifyAppId, Region); + break; + case UserPool: await openUserPoolConsole(context, Region, cognitoOutput.UserPoolId); break; - case 'Identity Pool': + case IdentityPool: await openIdentityPoolConsole(context, Region, cognitoOutput.IdentityPoolId); break; + case BothPools: default: await openUserPoolConsole(context, Region, cognitoOutput.UserPoolId); await openIdentityPoolConsole(context, Region, cognitoOutput.IdentityPoolId); @@ -342,6 +352,14 @@ function getCognitoOutput(amplifyMeta) { return cognitoOutput; } +async function openAdminUI(context, appId, region) { + // region will be needed in prod + const { envName } = context.amplify.getEnvInfo(); + const adminUrl = `https://www.dracarys.app/admin/${appId}/${envName}/auth`; + await open(adminUrl, { wait: false }); + context.print.success(adminUrl); +} + async function openUserPoolConsole(context, region, userPoolId) { const userPoolConsoleUrl = `https://${region}.console.aws.amazon.com/cognito/users/?region=${region}#/pool/${userPoolId}/details`; await open(userPoolConsoleUrl, { wait: false }); diff --git a/packages/amplify-cli-core/src/index.ts b/packages/amplify-cli-core/src/index.ts index afa200b1941..70105e338a3 100644 --- a/packages/amplify-cli-core/src/index.ts +++ b/packages/amplify-cli-core/src/index.ts @@ -128,7 +128,7 @@ interface AmplifyToolkit { getResourceStatus: (category?: $TSAny, resourceName?: $TSAny, providerName?: $TSAny, filteredResources?: $TSAny) => $TSAny; getResourceOutputs: () => $TSAny; getWhen: () => $TSAny; - inputValidation: () => $TSAny; + inputValidation: (input: $TSAny) => $TSAny; listCategories: () => $TSAny; makeId: () => $TSAny; openEditor: () => $TSAny; diff --git a/packages/amplify-cli-core/src/state-manager/stateManager.ts b/packages/amplify-cli-core/src/state-manager/stateManager.ts index ae747d02b21..81a34446bd0 100644 --- a/packages/amplify-cli-core/src/state-manager/stateManager.ts +++ b/packages/amplify-cli-core/src/state-manager/stateManager.ts @@ -12,7 +12,7 @@ export type GetOptions = { }; export class StateManager { - metaFileExists = (projectPath?: string): boolean => fs.existsSync(pathManager.getAmplifyMetaFilePath(projectPath)); + metaFileExists = (projectPath?: string): boolean => this.doesExist(pathManager.getAmplifyMetaFilePath, projectPath); getMeta = (projectPath?: string, options?: GetOptions<$TSMeta>): $TSMeta => { const filePath = pathManager.getAmplifyMetaFilePath(projectPath); @@ -26,7 +26,7 @@ export class StateManager { return data; }; - currentMetaFileExists = (projectPath?: string): boolean => fs.existsSync(pathManager.getCurrentAmplifyMetaFilePath(projectPath)); + currentMetaFileExists = (projectPath?: string): boolean => this.doesExist(pathManager.getCurrentAmplifyMetaFilePath, projectPath); setDeploymentSecrets = (deploymentSecrets: DeploymentSecrets): void => { const path = pathManager.getDeploymentSecrets(); @@ -57,7 +57,7 @@ export class StateManager { getCurrentProjectTags = (projectPath?: string): Tag[] => ReadValidateTags(pathManager.getCurrentTagFilePath(projectPath)); - teamProviderInfoExists = (projectPath?: string): boolean => fs.existsSync(pathManager.getTeamProviderInfoFilePath(projectPath)); + teamProviderInfoExists = (projectPath?: string): boolean => this.doesExist(pathManager.getTeamProviderInfoFilePath, projectPath); getTeamProviderInfo = (projectPath?: string, options?: GetOptions<$TSTeamProviderInfo>): $TSTeamProviderInfo => { const filePath = pathManager.getTeamProviderInfoFilePath(projectPath); @@ -69,7 +69,7 @@ export class StateManager { return this.getData<$TSTeamProviderInfo>(filePath, mergedOptions); }; - localEnvInfoExists = (projectPath?: string): boolean => fs.existsSync(pathManager.getLocalEnvFilePath(projectPath)); + localEnvInfoExists = (projectPath?: string): boolean => this.doesExist(pathManager.getLocalEnvFilePath, projectPath); getLocalEnvInfo = (projectPath?: string, options?: GetOptions<$TSAny>): $TSAny => { const filePath = pathManager.getLocalEnvFilePath(projectPath); @@ -91,7 +91,7 @@ export class StateManager { return this.getData<$TSAny>(filePath, mergedOptions); }; - projectConfigExists = (projectPath?: string): boolean => fs.existsSync(pathManager.getProjectConfigFilePath(projectPath)); + projectConfigExists = (projectPath?: string): boolean => this.doesExist(pathManager.getProjectConfigFilePath, projectPath); getProjectConfig = (projectPath?: string, options?: GetOptions<$TSAny>): $TSAny => { const filePath = pathManager.getProjectConfigFilePath(projectPath); @@ -103,7 +103,7 @@ export class StateManager { return this.getData<$TSAny>(filePath, mergedOptions); }; - backendConfigFileExists = (projectPath?: string): boolean => fs.existsSync(pathManager.getBackendConfigFilePath(projectPath)); + backendConfigFileExists = (projectPath?: string): boolean => this.doesExist(pathManager.getBackendConfigFilePath, projectPath); getBackendConfig = (projectPath?: string, options?: GetOptions<$TSAny>): $TSAny => { const filePath = pathManager.getBackendConfigFilePath(projectPath); @@ -200,7 +200,13 @@ export class StateManager { JSONUtilities.writeJson(filePath, parameters); }; - cliJSONFileExists = (projectPath: string, env?: string): boolean => fs.existsSync(pathManager.getCLIJSONFilePath(projectPath, env)); + cliJSONFileExists = (projectPath: string, env?: string): boolean => { + try { + return fs.existsSync(pathManager.getCLIJSONFilePath(projectPath, env)); + } catch (e) { + return false; + } + }; getCLIJSON = (projectPath: string, env?: string, options?: GetOptions<$TSAny>): $TSAny => { const filePath = pathManager.getCLIJSONFilePath(projectPath, env); @@ -220,6 +226,17 @@ export class StateManager { }); }; + private doesExist = (filePathGetter: (projPath?: string) => string, projectPath?: string): boolean => { + let path; + try { + // getting the file path can fail if we are not in a valid project + path = filePathGetter(projectPath); + return fs.existsSync(path); + } catch (e) { + return false; + } + }; + private getData = (filePath: string, options?: GetOptions): T | undefined => { const data = JSONUtilities.readJson(filePath, { throwIfNotExist: options?.throwIfNotExist ?? true, diff --git a/packages/amplify-cli/src/commands/console.ts b/packages/amplify-cli/src/commands/console.ts index 53b5d17e004..637f0dfbafd 100644 --- a/packages/amplify-cli/src/commands/console.ts +++ b/packages/amplify-cli/src/commands/console.ts @@ -1,5 +1,7 @@ import open from 'open'; +import { prompt } from 'enquirer'; import { stateManager } from 'amplify-cli-core'; +const { doAdminCredentialsExist } = require('amplify-provider-awscloudformation'); const providerName = 'awscloudformation'; @@ -22,6 +24,20 @@ export const run = async context => { if (envName && AmplifyAppId) { consoleUrl = constructStatusURL(Region, AmplifyAppId, envName); + if (doAdminCredentialsExist(AmplifyAppId)) { + const { choice } = await prompt<{ choice: string }>({ + type: 'select', + name: 'choice', + message: 'Which site do you want to open?', + choices: [ + { name: 'admin', message: 'Amplify admin UI', value: 'admin' }, + { name: 'console', message: 'Amplify console', value: 'console' }, + ], + }); + if (choice === 'admin') { + consoleUrl = constructAdminURL(Region, AmplifyAppId, envName); + } + } } } catch (e) { context.print.error(e.message); @@ -31,6 +47,10 @@ export const run = async context => { open(consoleUrl, { wait: false }); }; +function constructAdminURL(region: string, appId: string, envName: string) { + return `https://www.dracarys.app/admin/${appId}/${envName}/home`; +} + function constructStatusURL(region, appId, envName) { const prodURL = `https://${region}.console.aws.amazon.com/amplify/home?region=${region}#/${appId}/YmFja2VuZA/${envName}`; // eslint-disable-line return prodURL; diff --git a/packages/amplify-cli/src/pre-deployment-pull.ts b/packages/amplify-cli/src/pre-deployment-pull.ts index 8ae242038ce..0a9617d4f7e 100644 --- a/packages/amplify-cli/src/pre-deployment-pull.ts +++ b/packages/amplify-cli/src/pre-deployment-pull.ts @@ -37,8 +37,8 @@ export async function preDeployPullBackend(context: $TSContext, sandboxId: strin // Replace base schema with the schema configured in Backend-manager app let schema = resJson.schema; - schema = schema.replace(/\@auth\(rules\: \[\{allow\: private\, provider\: iam\}\]\)/g, ""); - + schema = schema.replace(/\@auth\(rules\: \[\{allow\: private\, provider\: iam\}\]\)/g, ''); + replaceSchema(schema); // Generate models diff --git a/packages/amplify-provider-awscloudformation/src/index.ts b/packages/amplify-provider-awscloudformation/src/index.ts index 6c33294084d..5ef2560511c 100644 --- a/packages/amplify-provider-awscloudformation/src/index.ts +++ b/packages/amplify-provider-awscloudformation/src/index.ts @@ -19,6 +19,7 @@ const { formUserAgentParam } = require('./aws-utils/user-agent'); const predictionsRegionMap = require('./aws-predictions-regions'); import { adminLoginFlow } from './admin-login'; +import { doAdminCredentialsExist, isAmplifyAdminApp } from './utils/admin-helpers'; import { CognitoUserPoolService, createCognitoUserPoolService } from './aws-utils/CognitoUserPoolService'; import { IdentityPoolService, createIdentityPoolService } from './aws-utils/IdentityPoolService'; import { S3Service, createS3Service } from './aws-utils/S3Service'; @@ -103,6 +104,7 @@ module.exports = { attachBackend, init, initEnv, + isAmplifyAdminApp, onInitSuccessful, configure, configureNewUser, @@ -119,6 +121,7 @@ module.exports = { getConfiguredAmplifyClient, showHelpfulLinks, deleteEnv, + doAdminCredentialsExist, loadResourceParameters, saveResourceParameters, predictionsRegionMap, diff --git a/packages/amplify-provider-awscloudformation/src/utils/admin-helpers.ts b/packages/amplify-provider-awscloudformation/src/utils/admin-helpers.ts index 08a744543ec..7924ebfcd53 100644 --- a/packages/amplify-provider-awscloudformation/src/utils/admin-helpers.ts +++ b/packages/amplify-provider-awscloudformation/src/utils/admin-helpers.ts @@ -12,6 +12,9 @@ export function doAdminCredentialsExist(appId: string): boolean { } export async function isAmplifyAdminApp(appId: string): Promise { + if (doAdminCredentialsExist(appId)) { + return true; + } const url = `https://rh2kdo2x79.execute-api.us-east-1.amazonaws.com/gamma/AppState/?appId=${appId}`; const res = await fetch(`${url}`); const resJson = await res.json();