Skip to content

Commit

Permalink
fix(amplify-category-auth): switching to social providers with user p…
Browse files Browse the repository at this point in the history
…ools instead of identity pools (#8308)
  • Loading branch information
lazpavel committed Oct 26, 2021
1 parent a35352d commit 0c82fe3
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 65 deletions.
Expand Up @@ -116,8 +116,7 @@ const emailRegistration = [

const authSelectionMap = [
{
name:
'User Sign-Up, Sign-In, connected with AWS IAM controls (Enables per-user Storage features for images or other content, Analytics, and more)',
name: 'User Sign-Up, Sign-In, connected with AWS IAM controls (Enables per-user Storage features for images or other content, Analytics, and more)',
value: 'identityPoolAndUserPool',
},
{
Expand Down Expand Up @@ -449,18 +448,22 @@ const hostedUIProviders = [
{
name: 'Facebook',
value: 'Facebook',
key: 'FACEBOOK',
},
{
name: 'Google',
value: 'Google',
key: 'GOOGLE',
},
{
name: 'Login With Amazon',
value: 'LoginWithAmazon',
key: 'AMAZON',
},
{
name: 'Sign in with Apple',
value: 'SignInWithApple',
key: 'APPLE',
},
];

Expand Down
@@ -1,5 +1,6 @@
import { $TSContext, ServiceSelection, stateManager } from 'amplify-cli-core';
import {
AuthParameters,
AuthSelections,
BackendConfiguration,
EnvSpecificResourceParameters,
Expand All @@ -25,6 +26,7 @@ import Enquirer from 'enquirer';
import _ from 'lodash';
import { importMessages } from './messages';
import uuid from 'uuid';
import { hostedUIProviders, coreAttributes } from '../assets/string-maps';

// Currently the CLI only supports the output generation of these providers
const supportedIdentityProviders = ['COGNITO', 'Facebook', 'Google', 'LoginWithAmazon', 'SignInWithApple'];
Expand Down Expand Up @@ -701,7 +703,29 @@ const updateStateFiles = async (
region: questionParameters.region!,
};

stateManager.setResourceParametersJson(undefined, 'auth', answers.resourceName!, resourceParameters);
const authResourceParameters: AuthParameters = {
aliasAttributes: answers.userPool?.AliasAttributes,
usernameAttributes: answers.userPool?.UsernameAttributes,
authProvidersUserPool: answers.oauthProviders?.filter(provider => !!hostedUIProviders.find(it => it.value === provider)),
requiredAttributes: (answers.userPool?.SchemaAttributes ?? [])
.filter(att => att.Required && !!coreAttributes.find(it => it.value === att.Name))
.map(att => att.Name!),
passwordPolicyMinLength: answers.userPool?.Policies?.PasswordPolicy?.MinimumLength ?? 8,
passwordPolicyCharacters: [
...(answers.userPool?.Policies?.PasswordPolicy?.RequireLowercase ? ['Requires Lowercase'] : []),
...(answers.userPool?.Policies?.PasswordPolicy?.RequireUppercase ? ['Requires Uppercase'] : []),
...(answers.userPool?.Policies?.PasswordPolicy?.RequireNumbers ? ['Requires Numbers'] : []),
...(answers.userPool?.Policies?.PasswordPolicy?.RequireSymbols ? ['Requires Symbols'] : []),
],
mfaConfiguration: answers.userPool?.MfaConfiguration,
autoVerifiedAttributes: answers.userPool?.AutoVerifiedAttributes,
mfaTypes: [
...(answers.mfaConfiguration?.SmsMfaConfiguration ? ['SMS Text Message'] : []),
...(answers.mfaConfiguration?.SoftwareTokenMfaConfiguration ? ['TOTP'] : []),
],
};

stateManager.setResourceParametersJson(undefined, 'auth', answers.resourceName!, { ...resourceParameters, ...authResourceParameters });

// Add resource data to amplify-meta file and backend-config, since backend-config requires less information
// we have to do a separate update to it without duplicating the methods
Expand Down
Expand Up @@ -57,8 +57,9 @@ export type AuthParameters = {
aliasAttributes?: string[];
usernameAttributes?: string[];
authProviders?: string[];
authProvidersUserPool?: string[];
requiredAttributes?: string[];
passwordPolicyMinLength?: string;
passwordPolicyMinLength?: number;
passwordPolicyCharacters?: string[];
mfaConfiguration?: string;
mfaTypes?: string[];
Expand Down
@@ -1,7 +1,7 @@
import * as path from 'path';
import { JSONUtilities, pathManager } from 'amplify-cli-core';
import { transformUserPoolGroupSchema } from './transform-user-pool-group';
import { authProviders as authProviderList } from '../assets/string-maps';
import { hostedUIProviders } from '../assets/string-maps';
import { AuthParameters } from '../import/types';

/**
Expand Down Expand Up @@ -108,27 +108,20 @@ export const getPostUpdateAuthMetaUpdater = (context: any) => async (resourceNam

export function getFrontendConfig(authParameters: AuthParameters) {
const verificationMechanisms = (authParameters?.autoVerifiedAttributes || []).map((att: string) => att.toUpperCase());
const loginMechanisms = new Set<string>();
(authParameters?.aliasAttributes ?? []).forEach(it => loginMechanisms.add(it.toUpperCase()));
const usernameAttributes: string[] = [];

// backwards compatibility
if (authParameters?.usernameAttributes && authParameters.usernameAttributes.length > 0) {
authParameters.usernameAttributes[0].split(',').forEach(it => loginMechanisms.add(it.trim().toUpperCase()));
authParameters.usernameAttributes[0].split(',').forEach(it => usernameAttributes.push(it.trim().toUpperCase()));
}

if (authParameters.authProviders) {
authParameters.authProviders.forEach((provider: string) => {
let name = authProviderList.find(it => it.value === provider)?.name;
const socialProviders: string[] = [];
(authParameters?.authProvidersUserPool ?? []).forEach((provider: string) => {
const key = hostedUIProviders.find(it => it.value === provider)?.key;

if (name) {
loginMechanisms.add(name.toUpperCase());
}
});
}

if (loginMechanisms.size === 0) {
loginMechanisms.add('PREFERRED_USERNAME');
}
if (key) {
socialProviders.push(key);
}
});

const signupAttributes = (authParameters?.requiredAttributes || []).map((att: string) => att.toUpperCase());

Expand All @@ -149,7 +142,8 @@ export function getFrontendConfig(authParameters: AuthParameters) {
}

return {
loginMechanisms: Array.from(loginMechanisms),
socialProviders: socialProviders,
usernameAttributes: usernameAttributes,
signupAttributes: signupAttributes,
passwordProtectionSettings: passwordProtectionSettings,
mfaConfiguration: authParameters?.mfaConfiguration,
Expand Down
Expand Up @@ -6,34 +6,28 @@ jest.mock('amplify-cli-core');
const stateManager_mock = stateManager as jest.Mocked<typeof stateManager>;
stateManager_mock.getMeta.mockReturnValue({ auth: { authResource: { service: 'Cognito' } } });
stateManager_mock.getResourceParametersJson.mockReturnValue({
aliasAttributes: ['EMAIL'],
usernameAttributes: ['EMAIL'],
requiredAttributes: ['EMAIL'],
passwordPolicyMinLength: '10',
passwordPolicyMinLength: 10,
mfaConfiguration: 'ON',
mfaTypes: ['SMS Text Message'],
authProvidersUserPool: ['Google', 'Facebook', 'LoginWithAmazon', 'SignInWithApple'],
});

stateManager_mock.setMeta.mockImplementation(jest.fn());

describe('ensureAmplifyMetaFrontendConfig', () => {
const mockContext = {
amplify: {
pathManager: {
getAmplifyMetaFilePath: jest.fn(() => 'amplifyDirPath'),
},
},
};

it('should add front end config to amplify meta', () => {
ensureAmplifyMetaFrontendConfig();
expect(stateManager_mock.setMeta).lastCalledWith(undefined, {
auth: {
authResource: {
frontendAuthConfig: {
loginMechanisms: ['EMAIL'],
usernameAttributes: ['EMAIL'],
socialProviders: ['GOOGLE', 'FACEBOOK', 'AMAZON', 'APPLE'],
mfaConfiguration: 'ON',
mfaTypes: ['SMS'],
passwordProtectionSettings: { passwordPolicyCharacters: [], passwordPolicyMinLength: '10' },
passwordProtectionSettings: { passwordPolicyCharacters: [], passwordPolicyMinLength: 10 },
signupAttributes: ['EMAIL'],
verificationMechanisms: [],
},
Expand Down
Expand Up @@ -87,9 +87,7 @@ export function ensureAmplifyMetaFrontendConfig(amplifyMeta?) {
amplifyMeta.auth[authResourceName].frontendAuthConfig ??= {};
const metaFrontendAuthConfig = amplifyMeta.auth[authResourceName].frontendAuthConfig;
Object.keys(frontendAuthConfig).forEach(key => {
if (!metaFrontendAuthConfig.hasOwnProperty(key)) {
metaFrontendAuthConfig[key] = frontendAuthConfig[key];
}
metaFrontendAuthConfig[key] = frontendAuthConfig[key];
});

stateManager.setMeta(undefined, amplifyMeta);
Expand Down
13 changes: 7 additions & 6 deletions packages/amplify-e2e-tests/src/__tests__/auth_6.test.ts
Expand Up @@ -34,12 +34,6 @@ describe('zero config auth ', () => {

expect(authMeta.frontendAuthConfig).toMatchInlineSnapshot(`
Object {
"loginMechanisms": Array [
"FACEBOOK",
"GOOGLE",
"AMAZON",
"APPLE",
],
"mfaConfiguration": "ON",
"mfaTypes": Array [
"SMS",
Expand All @@ -57,6 +51,13 @@ describe('zero config auth ', () => {
"signupAttributes": Array [
"EMAIL",
],
"socialProviders": Array [
"FACEBOOK",
"GOOGLE",
"AMAZON",
"APPLE",
],
"usernameAttributes": Array [],
"verificationMechanisms": Array [
"EMAIL",
],
Expand Down
49 changes: 28 additions & 21 deletions packages/amplify-e2e-tests/src/__tests__/import_auth_1.test.ts
@@ -1,52 +1,38 @@
import * as fs from 'fs-extra';
import * as path from 'path';

import { $TSObject, JSONUtilities } from 'amplify-cli-core';
import { $TSObject, JSONUtilities, stateManager } from 'amplify-cli-core';
import {
AddAuthUserPoolOnlyWithOAuthSettings,
addApi,
addApiWithCognitoUserPoolAuthTypeWhenAuthExists,
addAuthUserPoolOnlyWithOAuth,
AddAuthUserPoolOnlyWithOAuthSettings,
addFunction,
amplifyPull,
amplifyPush,
amplifyPushAuth,
amplifyStatus,
createNewProjectDir,
deleteProject,
deleteProjectDir,
getAppId,
getEnvVars,
getTeamProviderInfo,
initJSProjectWithProfile,
initProjectWithAccessKey,
addApi,
updateApiSchema,
} from 'amplify-e2e-core';
import * as fs from 'fs-extra';
import * as path from 'path';
import {
AppClientSettings,
AuthProjectDetails,
addAppClientWithSecret,
addAppClientWithoutSecret,
addS3WithAuthConfigurationMismatchErrorExit,
AuthProjectDetails,
createUserPoolOnlyWithOAuthSettings,
deleteAppClient,
expectApiHasCorrectAuthConfig,
expectAuthLocalAndOGMetaFilesOutputMatching,
expectAuthParametersMatch,
expectAuthProjectDetailsMatch,
expectLocalAndCloudMetaFilesMatching,
expectLocalAndPulledBackendConfigMatching,
expectLocalTeamInfoHasNoCategories,
expectNoAuthInMeta,
getAuthProjectDetails,
getOGAuthProjectDetails,
getShortId,
importIdentityPoolAndUserPool,
importUserPoolOnly,
readRootStack,
removeImportedAuthWithDefault,
} from '../import-helpers';
import { addEnvironmentWithImportedAuth, checkoutEnvironment, removeEnvironment } from '../environment/env';

import { getCognitoResourceName } from '../schema-api-directives/authHelper';
import { randomizedFunctionName } from '../schema-api-directives/functionTester';

Expand Down Expand Up @@ -244,4 +230,25 @@ describe('auth import userpool only', () => {
await amplifyPush(projectRoot);
// successful push indicates iam auth works when only importing user pool
});

it('should update parameters.json with auth configuration', async () => {
await initJSProjectWithProfile(projectRoot, projectSettings);
await importUserPoolOnly(projectRoot, ogSettings.userPoolName, { native: '_app_client ', web: '_app_clientWeb' });

const ogProjectAuthParameters = stateManager.getResourceParametersJson(ogProjectRoot, 'auth', ogProjectDetails.authResourceName);

let projectDetails = getAuthProjectDetails(projectRoot);
let projectAuthParameters = stateManager.getResourceParametersJson(projectRoot, 'auth', projectDetails.authResourceName);
expectAuthParametersMatch(projectAuthParameters, ogProjectAuthParameters);

await amplifyStatus(projectRoot, 'Import');
await amplifyPushAuth(projectRoot);
await amplifyStatus(projectRoot, 'No Change');

expectLocalAndCloudMetaFilesMatching(projectRoot);

projectDetails = getAuthProjectDetails(projectRoot);
projectAuthParameters = stateManager.getResourceParametersJson(projectRoot, 'auth', projectDetails.authResourceName);
expectAuthParametersMatch(projectAuthParameters, ogProjectAuthParameters);
});
});
10 changes: 10 additions & 0 deletions packages/amplify-e2e-tests/src/import-helpers/expects.ts
@@ -1,6 +1,7 @@
import _ from 'lodash';
import { getProjectMeta, getBackendAmplifyMeta, getTeamProviderInfo, getBackendConfig } from 'amplify-e2e-core';
import { AuthProjectDetails, DynamoDBProjectDetails, readRootStack, StorageProjectDetails } from '.';
import { AuthParameters } from 'amplify-category-auth';

export const expectAuthProjectDetailsMatch = (projectDetails: AuthProjectDetails, ogProjectDetails: AuthProjectDetails) => {
expect(projectDetails.parameters.authSelections).toEqual(ogProjectDetails.parameters.authSelections);
Expand Down Expand Up @@ -168,3 +169,12 @@ export const expectDynamoDBLocalAndOGMetaFilesOutputMatching = (projectRoot: str
expect(storageMeta.output.Arn).toEqual(ogStorageMeta.output.Arn);
expect(storageMeta.output.StreamArn).toEqual(ogStorageMeta.output.StreamArn);
};

export const expectAuthParametersMatch = (authParameters: AuthParameters, ogAuthParameters: AuthParameters) => {
expect(authParameters.authProvidersUserPool).toEqual(ogAuthParameters.authProvidersUserPool);
expect(authParameters.requiredAttributes).toEqual(ogAuthParameters.requiredAttributes);
expect(authParameters.passwordPolicyMinLength).toEqual(ogAuthParameters.passwordPolicyMinLength);
expect(authParameters.passwordPolicyCharacters).toEqual(ogAuthParameters.passwordPolicyCharacters);
expect(authParameters.mfaConfiguration).toEqual(ogAuthParameters.mfaConfiguration);
expect(authParameters.autoVerifiedAttributes).toEqual(ogAuthParameters.autoVerifiedAttributes);
};
Expand Up @@ -309,7 +309,8 @@ function getCognitoConfig(cognitoResources, projectRegion) {

const frontendAuthConfig = {};
if (cognitoResource.frontendAuthConfig) {
frontendAuthConfig.aws_cognito_login_mechanisms = cognitoResource.frontendAuthConfig.loginMechanisms;
frontendAuthConfig.aws_cognito_username_attributes = cognitoResource.frontendAuthConfig.usernameAttributes;
frontendAuthConfig.aws_cognito_social_providers = cognitoResource.frontendAuthConfig.socialProviders;
frontendAuthConfig.aws_cognito_signup_attributes = cognitoResource.frontendAuthConfig.signupAttributes;
frontendAuthConfig.aws_cognito_mfa_configuration = cognitoResource.frontendAuthConfig.mfaConfiguration;
frontendAuthConfig.aws_cognito_mfa_types = cognitoResource.frontendAuthConfig.mfaTypes;
Expand Down

0 comments on commit 0c82fe3

Please sign in to comment.