From b8949b2b519f6b6a26bcab3596c051acec51e077 Mon Sep 17 00:00:00 2001 From: Pavel Lazar <85319655+lazpavel@users.noreply.github.com> Date: Fri, 10 Sep 2021 15:48:04 -0400 Subject: [PATCH] feat(amplify-category-auth): add auth verification mechanisms to frontend config (#8037) (#8093) --- packages/amplify-category-auth/src/index.js | 20 +++----- .../awscloudformation/import/types.ts | 1 + .../utils/amplify-meta-updaters.ts | 18 ++++++-- ...nsure-amplify-meta-frontend-config.test.ts | 46 +++++++++++++++++++ .../on-category-outputs-change.ts | 36 ++++++++++++++- 5 files changed, 102 insertions(+), 19 deletions(-) create mode 100644 packages/amplify-cli/src/__tests__/extensions/amplify-helpers/ensure-amplify-meta-frontend-config.test.ts diff --git a/packages/amplify-category-auth/src/index.js b/packages/amplify-category-auth/src/index.js index 5253887f6f5..bec59ccb6f4 100644 --- a/packages/amplify-category-auth/src/index.js +++ b/packages/amplify-category-auth/src/index.js @@ -21,8 +21,10 @@ const { getAddAuthRequestAdaptor, getUpdateAuthRequestAdaptor } = require('./pro const { getAddAuthHandler, getUpdateAuthHandler } = require('./provider-utils/awscloudformation/handlers/resource-handlers'); const { projectHasAuth } = require('./provider-utils/awscloudformation/utils/project-has-auth'); const { attachPrevParamsToContext } = require('./provider-utils/awscloudformation/utils/attach-prev-params-to-context'); +const { getFrontendConfig } = require('./provider-utils/awscloudformation/utils/amplify-meta-updaters'); const { stateManager } = require('amplify-cli-core'); const { headlessImport } = require('./provider-utils/awscloudformation/import'); +const { AuthParameters } = require('./provider-utils/awscloudformation/import/types'); const { doesConfigurationIncludeSMS, @@ -250,13 +252,8 @@ async function checkRequirements(requirements, context, category, targetResource async function initEnv(context) { const { amplify } = context; - const { - resourcesToBeCreated, - resourcesToBeUpdated, - resourcesToBeSynced, - resourcesToBeDeleted, - allResources, - } = await amplify.getResourceStatus('auth'); + const { resourcesToBeCreated, resourcesToBeUpdated, resourcesToBeSynced, resourcesToBeDeleted, allResources } = + await amplify.getResourceStatus('auth'); const isPulling = context.input.command === 'pull' || (context.input.command === 'env' && context.input.subCommands[0] === 'pull'); let toBeCreated = []; let toBeUpdated = []; @@ -410,12 +407,7 @@ const executeAmplifyHeadlessCommand = async (context, headlessPayload) => { const cognito = await providerPlugin.createCognitoUserPoolService(context); const identity = await providerPlugin.createIdentityPoolService(context); const { JSONUtilities } = require('amplify-cli-core/lib/jsonUtilities'); - const { - userPoolId, - identityPoolId, - nativeClientId, - webClientId, - } = JSONUtilities.parse(headlessPayload); + const { userPoolId, identityPoolId, nativeClientId, webClientId } = JSONUtilities.parse(headlessPayload); const projectConfig = context.amplify.getProjectConfig(); const resourceName = projectConfig.projectName.toLowerCase().replace(/[^A-Za-z0-9_]+/g, '_'); const resourceParams = { @@ -487,4 +479,6 @@ module.exports = { category, importAuth, isSMSWorkflowEnabled, + AuthParameters, + getFrontendConfig, }; diff --git a/packages/amplify-category-auth/src/provider-utils/awscloudformation/import/types.ts b/packages/amplify-category-auth/src/provider-utils/awscloudformation/import/types.ts index 21d3a413745..6ca2ff1e4b6 100644 --- a/packages/amplify-category-auth/src/provider-utils/awscloudformation/import/types.ts +++ b/packages/amplify-category-auth/src/provider-utils/awscloudformation/import/types.ts @@ -55,6 +55,7 @@ export type AuthParameters = { triggers?: string; identityPoolName?: string; aliasAttributes?: string[]; + usernameAttributes?: string[]; authProviders?: string[]; requiredAttributes?: string[]; passwordPolicyMinLength?: string; diff --git a/packages/amplify-category-auth/src/provider-utils/awscloudformation/utils/amplify-meta-updaters.ts b/packages/amplify-category-auth/src/provider-utils/awscloudformation/utils/amplify-meta-updaters.ts index 1e69dfbda2b..1953ab7318f 100644 --- a/packages/amplify-category-auth/src/provider-utils/awscloudformation/utils/amplify-meta-updaters.ts +++ b/packages/amplify-category-auth/src/provider-utils/awscloudformation/utils/amplify-meta-updaters.ts @@ -106,20 +106,30 @@ export const getPostUpdateAuthMetaUpdater = (context: any) => async (resourceNam return resourceName; }; -function getFrontendConfig(authParameters: AuthParameters) { - const loginMechanisms = (authParameters?.aliasAttributes || []).map((att: string) => att.toUpperCase()); +export function getFrontendConfig(authParameters: AuthParameters) { const verificationMechanisms = (authParameters?.autoVerifiedAttributes || []).map((att: string) => att.toUpperCase()); + const loginMechanisms: Set = new Set(); + (authParameters?.aliasAttributes ?? []).forEach(it => loginMechanisms.add(it.toUpperCase())); + + // backwards compatibility + if (authParameters?.usernameAttributes && authParameters?.usernameAttributes.length > 0) { + authParameters.usernameAttributes[0].split(',').forEach(it => loginMechanisms.add(it.trim().toUpperCase())); + } if (authParameters.authProviders) { authParameters.authProviders.forEach((provider: string) => { let name = authProviderList.find(it => it.value === provider)?.name; if (name) { - loginMechanisms.push(name.toUpperCase()); + loginMechanisms.add(name.toUpperCase()); } }); } + if (loginMechanisms.size == 0) { + loginMechanisms.add('PREFERRED_USERNAME'); + } + const signupAttributes = (authParameters?.requiredAttributes || []).map((att: string) => att.toUpperCase()); const passwordProtectionSettings = { @@ -139,7 +149,7 @@ function getFrontendConfig(authParameters: AuthParameters) { } return { - loginMechanisms: loginMechanisms, + loginMechanisms: Array.from(loginMechanisms), signupAttributes: signupAttributes, passwordProtectionSettings: passwordProtectionSettings, mfaConfiguration: authParameters?.mfaConfiguration, diff --git a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/ensure-amplify-meta-frontend-config.test.ts b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/ensure-amplify-meta-frontend-config.test.ts new file mode 100644 index 00000000000..4370103c9b8 --- /dev/null +++ b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/ensure-amplify-meta-frontend-config.test.ts @@ -0,0 +1,46 @@ +import { JSONUtilities, stateManager } from 'amplify-cli-core'; +import { ensureAmplifyMetaFrontendConfig } from '../../../extensions/amplify-helpers/on-category-outputs-change'; + +jest.mock('amplify-cli-core'); + +const stateManager_mock = stateManager as jest.Mocked; +stateManager_mock.getMeta.mockReturnValue({ auth: { authResource: { service: 'Cognito' } } }); +stateManager_mock.getResourceParametersJson.mockReturnValue({ + aliasAttributes: ['EMAIL'], + requiredAttributes: ['EMAIL'], + passwordPolicyMinLength: '10', + mfaConfiguration: 'ON', + mfaTypes: ['SMS Text Message'], +}); + +const jsonUtilities_mock = JSONUtilities as jest.Mocked; +jsonUtilities_mock.writeJson.mockImplementation(jest.fn()); + +describe('ensureAmplifyMetaFrontendConfig', () => { + const mockContext = { + amplify: { + pathManager: { + getAmplifyMetaFilePath: jest.fn(() => 'amplifyDirPath'), + }, + }, + }; + + it('should add front end config to amplify meta', () => { + ensureAmplifyMetaFrontendConfig(mockContext); + expect(jsonUtilities_mock.writeJson).lastCalledWith(expect.anything(), { + auth: { + authResource: { + frontendAuthConfig: { + loginMechanisms: ['EMAIL'], + mfaConfiguration: 'ON', + mfaTypes: ['SMS'], + passwordProtectionSettings: { passwordPolicyCharacters: [], passwordPolicyMinLength: '10' }, + signupAttributes: ['EMAIL'], + verificationMechanisms: [], + }, + service: 'Cognito', + }, + }, + }); + }); +}); diff --git a/packages/amplify-cli/src/extensions/amplify-helpers/on-category-outputs-change.ts b/packages/amplify-cli/src/extensions/amplify-helpers/on-category-outputs-change.ts index 134967f62be..1131e1684da 100644 --- a/packages/amplify-cli/src/extensions/amplify-helpers/on-category-outputs-change.ts +++ b/packages/amplify-cli/src/extensions/amplify-helpers/on-category-outputs-change.ts @@ -2,7 +2,9 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import { getResourceOutputs } from './get-resource-outputs'; import sequential from 'promise-sequential'; -import { stateManager } from 'amplify-cli-core'; +import { JSONUtilities, stateManager } from 'amplify-cli-core'; +import { AuthParameters } from 'amplify-category-auth'; +import { getFrontendConfig } from 'amplify-category-auth'; export async function onCategoryOutputsChange(context, cloudAmplifyMeta?, localMeta?) { if (!cloudAmplifyMeta) { @@ -13,8 +15,8 @@ export async function onCategoryOutputsChange(context, cloudAmplifyMeta?, localM } const projectConfig = stateManager.getProjectConfig(); - if (projectConfig.frontend) { + ensureAmplifyMetaFrontendConfig(context, localMeta); const frontendPlugins = context.amplify.getFrontendPlugins(context); const frontendHandlerModule = require(frontendPlugins[projectConfig.frontend]); await frontendHandlerModule.createFrontendConfigs(context, getResourceOutputs(localMeta), getResourceOutputs(cloudAmplifyMeta)); @@ -63,3 +65,33 @@ function attachContextExtensions(context, packageLocation) { } } } + +// projects created before 5.2.0 didn't populate frontend config in amplify-meta.json +// this method ensures frontend config settings are added to amplify meta on pull as they exist in parameters.json +// https://app.asana.com/0/1200585422384147/1200740448709567/f +export function ensureAmplifyMetaFrontendConfig(context, amplifyMeta?) { + if (!amplifyMeta) { + amplifyMeta = stateManager.getMeta(); + } + + if (!amplifyMeta.auth) return; + + const authResourceName = Object.keys(amplifyMeta.auth).find((key: any) => { + return amplifyMeta.auth[key].service === 'Cognito'; + }); + + if (!authResourceName) return; + + const authParameters: AuthParameters = stateManager.getResourceParametersJson(undefined, 'auth', authResourceName); + const frontendAuthConfig = getFrontendConfig(authParameters); + + amplifyMeta.auth[authResourceName].frontendAuthConfig = amplifyMeta.auth[authResourceName].frontendAuthConfig ?? {}; + const metaFrontendAuthConfig = amplifyMeta.auth[authResourceName].frontendAuthConfig; + Object.keys(frontendAuthConfig).forEach(key => { + if (!metaFrontendAuthConfig.hasOwnProperty(key)) { + metaFrontendAuthConfig[key] = frontendAuthConfig[key]; + } + }); + + JSONUtilities.writeJson(context.amplify.pathManager.getAmplifyMetaFilePath(), amplifyMeta); +}