@aws-amplify/amplify-category-auth
Version:
amplify-cli authentication plugin
475 lines • 23.1 kB
JavaScript
;
const category = 'auth';
const _ = require('lodash');
const path = require('path');
const sequential = require('promise-sequential');
const { validateAddAuthRequest, validateUpdateAuthRequest, validateImportAuthRequest } = require('amplify-util-headless-input');
const { stateManager, AmplifySupportedService, JSONUtilities } = require('@aws-amplify/amplify-cli-core');
const { printer } = require('@aws-amplify/amplify-prompts');
const { ensureEnvParamManager } = require('@aws-amplify/amplify-environment-parameters');
const defaults = require('./provider-utils/awscloudformation/assets/cognito-defaults');
const { getAuthResourceName } = require('./utils/getAuthResourceName');
const { updateConfigOnEnvInit, migrate } = require('./provider-utils/awscloudformation');
const { removeDeprecatedProps } = require('./provider-utils/awscloudformation/utils/synthesize-resources');
const { ENV_SPECIFIC_PARAMS } = require('./provider-utils/awscloudformation/constants');
const { transformUserPoolGroupSchema } = require('./provider-utils/awscloudformation/utils/transform-user-pool-group');
const { uploadFiles } = require('./provider-utils/awscloudformation/utils/trigger-file-uploader');
const { getAddAuthRequestAdaptor, getUpdateAuthRequestAdaptor } = require('./provider-utils/awscloudformation/utils/auth-request-adaptors');
const { getAddAuthHandler, getUpdateAuthHandler } = require('./provider-utils/awscloudformation/handlers/resource-handlers');
const { projectHasAuth } = require('./provider-utils/awscloudformation/utils/project-has-auth');
const { printAuthExistsWarning } = require('./provider-utils/awscloudformation/utils/print-auth-exists-warning');
const { attachPrevParamsToContext } = require('./provider-utils/awscloudformation/utils/attach-prev-params-to-context');
const { headlessImport } = require('./provider-utils/awscloudformation/import');
const { getFrontendConfig } = require('./provider-utils/awscloudformation/utils/amplify-meta-updaters');
const { AuthParameters } = require('./provider-utils/awscloudformation/import/types');
const { getSupportedServices } = require('./provider-utils/supported-services');
const { generateAuthStackTemplate } = require('./provider-utils/awscloudformation/utils/generate-auth-stack-template');
const { AmplifyAuthTransform, AmplifyUserPoolGroupTransform } = require('./provider-utils/awscloudformation/auth-stack-builder');
const { doesConfigurationIncludeSMS, loadResourceParameters, loadImportedAuthParameters, } = require('./provider-utils/awscloudformation/utils/auth-sms-workflow-helper');
const { AuthInputState } = require('./provider-utils/awscloudformation/auth-inputs-manager/auth-input-state');
const { privateKeys } = require('./provider-utils/awscloudformation/constants');
const { checkAuthResourceMigration } = require('./provider-utils/awscloudformation/utils/check-for-auth-migration');
const { run: authRunPush } = require('./commands/auth/push');
const { getAuthTriggerStackCfnParameters } = require('./provider-utils/awscloudformation/utils/get-auth-trigger-stack-cfn-parameters');
const { updateAppClientWithGeneratedSecret } = require('./provider-utils/awscloudformation/utils/generate-cognito-app-client-secret');
const { prePushHandler } = require('./events/prePushHandler');
async function add(context, skipNextSteps = false) {
const { amplify } = context;
const servicesMetadata = getSupportedServices();
const existingAuth = amplify.getProjectDetails().amplifyMeta.auth || {};
if (Object.keys(existingAuth).length > 0) {
return context.print.warning('Auth has already been added to this project.');
}
let resultMetadata;
return amplify
.serviceSelectionPrompt(context, category, servicesMetadata)
.then((result) => {
resultMetadata = result;
const providerController = require(`${__dirname}/provider-utils/${result.providerName}/index`);
if (!providerController) {
context.print.error('Provider not configured for this category');
return;
}
return providerController.addResource(context, result.service, skipNextSteps);
})
.catch((err) => {
context.print.info(err.stack);
context.print.error('There was an error adding the auth resource');
context.usageData.emitError(err);
process.exitCode = 1;
});
}
async function transformCategoryStack(context, resource) {
if (resource.service === AmplifySupportedService.COGNITO) {
if (canResourceBeTransformed(context, resource.resourceName)) {
await generateAuthStackTemplate(context, resource.resourceName);
}
}
if (resource.service === AmplifySupportedService.COGNITOUSERPOOLGROUPS) {
const authResourceName = await getAuthResourceName(context);
if (canResourceBeTransformed(context, authResourceName)) {
await generateAuthStackTemplate(context, authResourceName);
}
}
}
function canResourceBeTransformed(context, resourceName) {
const resourceInputState = new AuthInputState(context, resourceName);
return resourceInputState.cliInputFileExists();
}
async function migrateAuthResource(context, resourceName) {
return checkAuthResourceMigration(context, resourceName, true);
}
async function externalAuthEnable(context, externalCategory, resourceName, requirements) {
const { amplify } = context;
const serviceMetadata = getSupportedServices();
const authExists = amplify.getProjectDetails().amplifyMeta.auth && Object.keys(amplify.getProjectDetails().amplifyMeta.auth).length > 0;
let currentAuthName;
const projectName = context.amplify
.getProjectConfig()
.projectName.toLowerCase()
.replace(/[^A-Za-z0-9_]+/g, '_');
let currentAuthParams;
const immutables = {};
if (authExists) {
if (authExists.serviceType === 'imported') {
throw new Error('Existing auth resource is imported and auth configuration update was requested.');
}
currentAuthName = await getAuthResourceName(context);
await checkAuthResourceMigration(context, currentAuthName, true);
const { provider } = serviceMetadata.Cognito;
const providerPlugin = context.amplify.getPluginInstance(context, provider);
currentAuthParams = providerPlugin.loadResourceParameters(context, 'auth', currentAuthName);
if (requirements.authSelections.includes('identityPoolOnly') && currentAuthParams.userPoolName) {
requirements.authSelections = 'identityPoolAndUserPool';
}
if (requirements.authSelections.includes('userPoolOnly') && currentAuthParams.identityPoolName) {
requirements.authSelections = 'identityPoolAndUserPool';
}
const defaultVals = defaults.getAllDefaults(currentAuthName);
serviceMetadata.Cognito.inputs.forEach((s) => {
if (!context.amplify.getWhen(s, defaultVals, currentAuthParams, context.amplify)()) {
if (currentAuthParams[s.key]) {
immutables[s.key] = currentAuthParams[s.key];
}
}
});
}
else {
currentAuthName = projectName;
}
const authPropsValues = authExists
? Object.assign(defaults.functionMap[requirements.authSelections](currentAuthName), currentAuthParams, immutables, requirements)
: Object.assign(defaults.functionMap[requirements.authSelections](currentAuthName), requirements, {
resourceName: currentAuthName,
sharedId: defaults.sharedId,
serviceName: 'Cognito',
useDefault: 'manual',
authSelections: requirements.authSelections,
});
const { roles } = defaults;
let authProps = {
...authPropsValues,
...roles,
};
try {
authProps = await removeDeprecatedProps(authProps);
let sharedParams = { ...authProps };
privateKeys.forEach((p) => delete sharedParams[p]);
sharedParams = removeDeprecatedProps(sharedParams);
const envSpecificParams = {};
const cliInputs = { ...sharedParams };
ENV_SPECIFIC_PARAMS.forEach((paramName) => {
if (paramName in authProps) {
envSpecificParams[paramName] = cliInputs[paramName];
delete cliInputs[paramName];
}
});
context.amplify.saveEnvResourceParameters(context, category, currentAuthName, envSpecificParams);
const cognitoCLIInputs = {
version: '1',
cognitoConfig: cliInputs,
};
const cliState = new AuthInputState(context, cognitoCLIInputs.cognitoConfig.resourceName);
await cliState.saveCLIInputPayload(cognitoCLIInputs);
await generateAuthStackTemplate(context, currentAuthName);
const resourceDirPath = path.join(amplify.pathManager.getBackendDirPath(), 'auth', authProps.resourceName, 'build', 'parameters.json');
const authParameters = await amplify.readJsonFile(resourceDirPath);
if (!authExists) {
const options = {
service: 'Cognito',
serviceType: 'managed',
providerPlugin: 'awscloudformation',
};
if (authParameters.dependsOn) {
options.dependsOn = authParameters.dependsOn;
}
await amplify.updateamplifyMetaAfterResourceAdd(category, authProps.resourceName, options);
}
const allResources = context.amplify.getProjectMeta();
if (allResources.auth && allResources.auth.userPoolGroups) {
const attributes = ['UserPoolId', 'AppClientIDWeb', 'AppClientID'];
if (authParameters.identityPoolName) {
attributes.push('IdentityPoolId');
}
const userPoolGroupDependsOn = [
{
category: 'auth',
resourceName: authProps.resourceName,
attributes,
},
];
amplify.updateamplifyMetaAfterResourceUpdate('auth', 'userPoolGroups', 'dependsOn', userPoolGroupDependsOn);
await transformUserPoolGroupSchema(context);
}
const action = authExists ? 'updated' : 'added';
printer.success(`Successfully ${action} auth resource locally.`);
return authProps.resourceName;
}
catch (e) {
printer.error('Error updating Cognito resource');
throw e;
}
}
async function checkRequirements(requirements, context, category, targetResourceName) {
if (!requirements || !requirements.authSelections) {
const error = `Your plugin has not properly defined it's Cognito requirements.`;
return {
errors: [error],
};
}
const existingAuth = context.amplify.getProjectDetails().amplifyMeta.auth;
let authParameters;
const result = {
errors: [],
};
if (existingAuth && Object.keys(existingAuth).length > 0) {
const authResourceName = await getAuthResourceName(context);
const authResource = existingAuth[authResourceName];
authParameters = stateManager.getResourceParametersJson(undefined, 'auth', authResourceName);
result.authEnabled = true;
result.authImported = false;
if (authResource.serviceType === 'imported') {
const envSpecificParams = context.amplify.loadEnvResourceParameters(context, 'auth', authResourceName);
if (envSpecificParams) {
authParameters.allowUnauthenticatedIdentities = envSpecificParams.allowUnauthenticatedIdentities;
}
result.authImported = true;
}
}
else {
return {
authEnabled: false,
};
}
if ((requirements.authSelections === 'userPoolOnly' &&
(authParameters.authSelections === 'userPoolOnly' || authParameters.authSelections === 'identityPoolAndUserPool')) ||
(requirements.authSelections === 'identityPoolOnly' && authParameters.authSelections === 'identityPoolOnly') ||
(requirements.authSelections === 'identityPoolOnly' && authParameters.authSelections === 'identityPoolAndUserPool') ||
(requirements.authSelections === 'identityPoolAndUserPool' && authParameters.authSelections === 'identityPoolAndUserPool')) {
result.authSelections = true;
}
else {
result.authSelections = false;
result.errors.push(`Current auth configuration is: ${authParameters.authSelections}, but ${requirements.authSelections} was required.`);
}
if ((requirements.allowUnauthenticatedIdentities === true && authParameters.allowUnauthenticatedIdentities === true) ||
!requirements.allowUnauthenticatedIdentities) {
result.allowUnauthenticatedIdentities = true;
}
else {
result.allowUnauthenticatedIdentities = false;
result.errors.push(`Specified resource configuration requires Cognito Identity Provider unauthenticated access but it is not enabled.`);
}
result.requirementsMet = result.authSelections && result.allowUnauthenticatedIdentities;
return result;
}
async function initEnv(context) {
const { amplify } = context;
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 = [];
let toBeSynced = [];
let toBeDeleted = [];
if (resourcesToBeCreated && resourcesToBeCreated.length > 0) {
toBeCreated = resourcesToBeCreated.filter((a) => a.category === 'auth');
}
if (resourcesToBeUpdated && resourcesToBeUpdated.length > 0) {
toBeUpdated = resourcesToBeUpdated.filter((c) => c.category === 'auth');
}
if (resourcesToBeSynced && resourcesToBeSynced.length > 0) {
toBeSynced = resourcesToBeSynced.filter((b) => b.category === 'auth');
}
if (resourcesToBeDeleted && resourcesToBeDeleted.length > 0) {
toBeDeleted = resourcesToBeDeleted.filter((b) => b.category === 'auth');
}
await ensureEnvParamManager();
toBeDeleted.forEach((authResource) => {
amplify.removeResourceParameters(context, 'auth', authResource.resourceName);
});
toBeSynced
.filter((authResource) => authResource.sync === 'unlink')
.forEach((authResource) => {
amplify.removeResourceParameters(context, 'auth', authResource.resourceName);
});
let tasks = toBeCreated.concat(toBeUpdated);
if (!isPulling) {
tasks = tasks.concat(toBeSynced);
}
if (isPulling && allResources.length > 0) {
tasks.push(...allResources);
}
const authTasks = tasks.map((authResource) => {
const { resourceName } = authResource;
return async () => {
const config = await updateConfigOnEnvInit(context, 'auth', resourceName);
context.amplify.saveEnvResourceParameters(context, 'auth', resourceName, config);
};
});
await sequential(authTasks);
}
async function authConsole(context) {
const { amplify } = context;
const amplifyMeta = amplify.getProjectMeta();
if (!amplifyMeta.auth || Object.keys(amplifyMeta.auth).length === 0) {
return context.print.error('Auth has NOT been added to this project.');
}
return amplify
.serviceSelectionPrompt(context, category, getSupportedServices())
.then((result) => {
const providerController = require(`${__dirname}/provider-utils/${result.providerName}/index`);
if (!providerController) {
context.print.error('Provider not configured for this category');
return;
}
return providerController.console(context, amplifyMeta);
})
.catch((err) => {
context.print.info(err.stack);
context.print.error('There was an error trying to open the auth web console.');
throw err;
});
}
async function getPermissionPolicies(context, resourceOpsMapping) {
const amplifyMetaFilePath = context.amplify.pathManager.getAmplifyMetaFilePath();
const amplifyMeta = context.amplify.readJsonFile(amplifyMetaFilePath);
const permissionPolicies = [];
const resourceAttributes = [];
Object.keys(resourceOpsMapping).forEach((resourceName) => {
try {
const providerName = amplifyMeta[category][resourceName].providerPlugin;
if (providerName) {
const providerController = require(`./provider-utils/${providerName}/index`);
const { policy, attributes } = providerController.getPermissionPolicies(context, amplifyMeta[category][resourceName].service, resourceName, resourceOpsMapping[resourceName]);
permissionPolicies.push(policy);
resourceAttributes.push({ resourceName, attributes, category });
}
else {
context.print.error(`Provider not configured for ${category}: ${resourceName}`);
}
}
catch (e) {
context.print.warning(`Could not get policies for ${category}: ${resourceName}`);
throw e;
}
});
return { permissionPolicies, resourceAttributes };
}
async function executeAmplifyCommand(context) {
let commandPath = path.normalize(path.join(__dirname, 'commands'));
if (context.input.command === 'help') {
commandPath = path.join(commandPath, category);
}
else {
commandPath = path.join(commandPath, category, context.input.command);
}
const commandModule = require(commandPath);
await commandModule.run(context);
}
const executeAmplifyHeadlessCommand = async (context, headlessPayload) => {
context.usageData.pushHeadlessFlow(headlessPayload, context.input);
switch (context.input.command) {
case 'add':
if (projectHasAuth()) {
printAuthExistsWarning(context);
return;
}
await validateAddAuthRequest(headlessPayload)
.then(getAddAuthRequestAdaptor(context.amplify.getProjectConfig().frontend))
.then(getAddAuthHandler(context));
return;
case 'update':
const authResourceName = await getAuthResourceName(context);
await checkAuthResourceMigration(context, authResourceName, true);
await attachPrevParamsToContext(context);
await validateUpdateAuthRequest(headlessPayload)
.then(getUpdateAuthRequestAdaptor(context.amplify.getProjectConfig().frontend, context.updatingAuth.requiredAttributes))
.then(getUpdateAuthHandler(context));
return;
case 'import':
if (projectHasAuth()) {
printAuthExistsWarning(context);
return;
}
await validateImportAuthRequest(headlessPayload);
const { provider } = getSupportedServices().Cognito;
const providerPlugin = context.amplify.getPluginInstance(context, provider);
const cognito = await providerPlugin.createCognitoUserPoolService(context);
const identity = await providerPlugin.createIdentityPoolService(context);
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 = {
authSelections: identityPoolId ? 'identityPoolAndUserPool' : 'userPoolOnly',
resourceName,
};
await headlessImport(context, cognito, identity, provider, resourceName, resourceParams, {
userPoolId,
identityPoolId,
nativeClientId,
webClientId,
});
return;
default:
context.print.error(`Headless mode for ${context.input.command} auth is not implemented yet`);
}
};
async function handleAmplifyEvent(context, args) {
switch (args.event) {
case 'PrePush':
await prePushHandler(context);
break;
default:
printer.info(`Event handler for ${args.event} not implemented by ${category} category`);
}
}
async function prePushAuthHook(context) {
}
async function importAuth(context) {
const { amplify } = context;
const servicesMetadata = getSupportedServices();
const existingAuth = amplify.getProjectDetails().amplifyMeta.auth || {};
if (Object.keys(existingAuth).length > 0) {
return context.print.warning('Auth has already been added to this project.');
}
const serviceSelection = await context.amplify.serviceSelectionPrompt(context, category, servicesMetadata);
const providerController = require(`./provider-utils/${serviceSelection.providerName}`);
return providerController.importResource(context, serviceSelection, undefined, undefined, false);
}
async function isSMSWorkflowEnabled(context, resourceName) {
const { imported, userPoolId } = context.amplify.getImportedAuthProperties(context);
let userNameAndMfaConfig;
if (imported) {
userNameAndMfaConfig = await loadImportedAuthParameters(context, userPoolId);
}
else {
userNameAndMfaConfig = loadResourceParameters(context, resourceName);
}
const result = doesConfigurationIncludeSMS(userNameAndMfaConfig);
return result;
}
const authPushYes = async (context) => {
const exeInfoClone = { ...context === null || context === void 0 ? void 0 : context.exeInfo };
const parametersClone = { ...context === null || context === void 0 ? void 0 : context.parameters };
try {
context.exeInfo = context.exeInfo || {};
context.exeInfo.inputParams = context.exeInfo.inputParams || {};
context.exeInfo.inputParams.yes = true;
context.parameters = context.parameters || {};
context.parameters.options.yes = true;
context.parameters.first = undefined;
await authRunPush(context);
}
finally {
context.exeInfo = exeInfoClone;
context.parameters = parametersClone;
}
};
module.exports = {
externalAuthEnable,
migrateAuthResource,
checkRequirements,
add,
migrate,
initEnv,
console: authConsole,
getPermissionPolicies,
executeAmplifyCommand,
executeAmplifyHeadlessCommand,
handleAmplifyEvent,
prePushAuthHook,
uploadFiles,
category,
importAuth,
isSMSWorkflowEnabled,
AuthParameters,
getFrontendConfig,
generateAuthStackTemplate,
AmplifyAuthTransform,
AmplifyUserPoolGroupTransform,
transformCategoryStack,
authPluginAPIPush: authPushYes,
getAuthTriggerStackCfnParameters,
updateAppClientWithGeneratedSecret,
};
//# sourceMappingURL=index.js.map