Skip to content

Commit

Permalink
feat: add support for importing userpool with no appclient secret (#6404
Browse files Browse the repository at this point in the history
)

Update auth import support importing cognito user pool app client (for native) with or without secret. The CLI has been updated (in #6333) to support generation of App client for native not to generate App Secret. By updating the import path, customers can import the auth provisioned by the CLI in a different project.
  • Loading branch information
yuth committed Jan 20, 2021
1 parent 52d33f8 commit 4ce4138
Show file tree
Hide file tree
Showing 7 changed files with 315 additions and 115 deletions.
@@ -1,17 +1,4 @@
import Enquirer from 'enquirer';
import _ from 'lodash';
import uuid from 'uuid';
import {
IdentityProviderType,
UserPoolClientType,
UserPoolDescriptionType,
UserPoolType,
} from 'aws-sdk/clients/cognitoidentityserviceprovider';
import { CognitoIdentityProvider, IdentityPool } from 'aws-sdk/clients/cognitoidentity';

import { $TSContext, ServiceSelection, stateManager } from 'amplify-cli-core';
import { ICognitoUserPoolService, IIdentityPoolService } from 'amplify-util-import';
import { importMessages } from './messages';
import {
AuthSelections,
BackendConfiguration,
Expand All @@ -25,6 +12,19 @@ import {
ProviderUtils,
ResourceParameters,
} from './types';
import { CognitoIdentityProvider, IdentityPool } from 'aws-sdk/clients/cognitoidentity';
import { ICognitoUserPoolService, IIdentityPoolService } from 'amplify-util-import';
import {
IdentityProviderType,
UserPoolClientType,
UserPoolDescriptionType,
UserPoolType,
} from 'aws-sdk/clients/cognitoidentityserviceprovider';

import Enquirer from 'enquirer';
import _ from 'lodash';
import { importMessages } from './messages';
import uuid from 'uuid';

// Currently the CLI only supports the output generation of these providers
const supportedIdentityProviders = ['COGNITO', 'Facebook', 'Google', 'LoginWithAmazon'];
Expand Down Expand Up @@ -355,17 +355,13 @@ const validateUserPool = async (
): Promise<boolean | string> => {
const userPoolClients = await cognito.listUserPoolClients(userPoolId);
const webClients = userPoolClients.filter(c => !c.ClientSecret);
const nativeClients = userPoolClients.filter(c => c.ClientSecret !== undefined);
const nativeClients = userPoolClients;

// Check if the selected user pool has at least 1 native and 1 web app client configured.
// Check if the selected user pool has at least 1 web app client configured.
if (webClients?.length < 1) {
return importMessages.NoAtLeastOneAppClient('Web');
}

if (nativeClients?.length < 1) {
return importMessages.NoAtLeastOneAppClient('Native');
}

// If authSelections involves the selection of an Identity Pool as well then we have to look for an
// IdentityPool that has the selected UserPool configured. This is an upfront validation for better DX
// We can't validate until fully until AppClients are selected later.
Expand Down Expand Up @@ -419,68 +415,77 @@ const selectAppClients = async (
answers: ImportAnswers,
): Promise<void> => {
let autoSelected = 0;
let changeAppClientSelction = false;
do {
// Select web application clients
if (questionParameters.webClients!.length === 1) {
answers.appClientWeb = questionParameters.webClients![0];

// Select web application clients
if (questionParameters.webClients!.length === 1) {
answers.appClientWeb = questionParameters.webClients![0];

context.print.info(importMessages.SingleAppClientSelected('Web', answers.appClientWeb.ClientName!));

autoSelected++;
} else {
const appClientChoices = questionParameters
.webClients!.map(c => ({
message: `${c.ClientName!} (${c.ClientId})`,
value: c.ClientId,
}))
.sort((a, b) => a.message.localeCompare(b.message));
context.print.info(importMessages.SingleAppClientSelected('Web', answers.appClientWeb.ClientName!));

const appClientSelectQuestion = {
type: 'select',
name: 'appClientWebId',
message: importMessages.Questions.SelectAppClient('Web'),
required: true,
choices: appClientChoices,
};
autoSelected++;
} else {
const appClientChoices = questionParameters
.webClients!.map(c => ({
message: `${c.ClientName!} (${c.ClientId})`,
value: c.ClientId,
}))
.sort((a, b) => a.message.localeCompare(b.message));

context.print.info(importMessages.MultipleAppClients('Web'));
const appClientSelectQuestion = {
type: 'autocomplete',
name: 'appClientWebId',
message: importMessages.Questions.SelectAppClient('Web'),
required: true,
choices: appClientChoices,
limit: 5,
footer: importMessages.Questions.AutoCompleteFooter,
};

const { appClientWebId } = await enquirer.prompt(appClientSelectQuestion);
answers.appClientWeb = questionParameters.webClients!.find(c => c.ClientId! === appClientWebId);
answers.appClientWebId = undefined; // Only to be used by enquirer
}
context.print.info(importMessages.MultipleAppClients('Web'));

// Select Native application client
if (questionParameters.nativeClients!.length === 1) {
answers.appClientNative = questionParameters.nativeClients![0];
const { appClientWebId } = await enquirer.prompt(appClientSelectQuestion);
answers.appClientWeb = questionParameters.webClients!.find(c => c.ClientId! === appClientWebId);
answers.appClientWebId = undefined; // Only to be used by enquirer
}

context.print.info(importMessages.SingleAppClientSelected('Native', answers.appClientNative.ClientName!));
// Select Native application client
if (questionParameters.nativeClients!.length === 1) {
answers.appClientNative = questionParameters.nativeClients![0];

autoSelected++;
} else {
const appClientChoices = questionParameters
.nativeClients!.map(c => ({
message: `${c.ClientName!} (${c.ClientId})`,
value: c.ClientId,
}))
.sort((a, b) => a.message.localeCompare(b.message));
context.print.info(importMessages.SingleAppClientSelected('Native', answers.appClientNative.ClientName!));
context.print.warning(importMessages.WarnAppClientReuse);
autoSelected++;
} else {
const appClientChoices = questionParameters
.nativeClients!.map(c => ({
message: `${c.ClientName!} (${c.ClientId}) ${c.ClientSecret ? '(has app client secret)' : ''}`,
value: c.ClientId,
}))
.sort((a, b) => a.message.localeCompare(b.message));

const appClientSelectQuestion = {
type: 'select',
name: 'appClientNativeId',
message: importMessages.Questions.SelectAppClient('Native'),
required: true,
choices: appClientChoices,
};
const appClientSelectQuestion = {
type: 'autocomplete',
name: 'appClientNativeId',
message: importMessages.Questions.SelectAppClient('Native'),
required: true,
choices: appClientChoices,
limit: 5,
footer: importMessages.Questions.AutoCompleteFooter,
};

context.print.info(importMessages.MultipleAppClients('Native'));
context.print.info(importMessages.MultipleAppClients('Native'));

const { appClientNativeId } = await enquirer.prompt(appClientSelectQuestion);
answers.appClientNative = questionParameters.nativeClients!.find(c => c.ClientId! === appClientNativeId);
answers.appClientNativeId = undefined; // Only to be used by enquirer
}
const { appClientNativeId } = await enquirer.prompt(appClientSelectQuestion);
answers.appClientNative = questionParameters.nativeClients!.find(c => c.ClientId! === appClientNativeId);
answers.appClientNativeId = undefined; // Only to be used by enquirer

questionParameters.bothAppClientsWereAutoSelected = autoSelected === 2;
if (answers.appClientNative === answers.appClientWeb) {
changeAppClientSelction = await context.prompt.confirm(importMessages.ConfirmUseDifferentAppClient);
}
}
questionParameters.bothAppClientsWereAutoSelected = autoSelected === 2;
} while (changeAppClientSelction);
};

const appClientsOAuthPropertiesMatching = async (
Expand Down Expand Up @@ -726,7 +731,7 @@ const createMetaOutput = (answers: ImportAnswers, hasOAuthConfig: boolean): Meta
UserPoolId: userPool.Id!,
UserPoolName: userPool.Name!,
AppClientID: answers.appClientNative!.ClientId,
AppClientSecret: answers.appClientNative!.ClientSecret,
...(answers.appClientNative!.ClientSecret ? { AppClientSecret: answers.appClientNative!.ClientSecret } : {}),
AppClientIDWeb: answers.appClientWeb!.ClientId,
HostedUIDomain: userPool.Domain,
};
Expand Down
Expand Up @@ -25,7 +25,6 @@ export const importMessages = {
`The selected Cognito User Pool does not have at least 1 ${type} app client configured. ${type} app clients are app clients ${
type === 'Web' ? 'without' : 'with'
} a client secret.`,

OneIdentityPoolValid: (identityPoolName: string, identityPoolId: string) =>
`${greenCheck} Only one Identity Pool resource found: '${identityPoolName}' (${identityPoolId}) was automatically selected.`,
MultipleIdentityPools: ` Multiple Identity Pools are configured for the selected Cognito User Pool.`,
Expand All @@ -40,7 +39,9 @@ export const importMessages = {
ImportPreviousResourceFooter: `If you choose No, then an import walkthrough will run to import a different resource into the new environment.`,
ImportNewResourceRequired: (resourceName: string) =>
`Imported resource: '${resourceName}' found, parameters are required for environment creation.`,

ConfirmUseDifferentAppClient:
'It is recommended to use different app clients for web and native application, You have chosen the same app client for both. Do you want to change this?',
WarnAppClientReuse: '⚠️ It is recommended to use different app client for web and native application.',
Questions: {
UserPoolSelection: 'Select the User Pool you want to import:',
IdentityPoolSelection: `Select the Identity Pool you want to import:`,
Expand Down

0 comments on commit 4ce4138

Please sign in to comment.