UNPKG

@aws-amplify/amplify-category-api

Version:
190 lines 10.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.notifySecurityEnhancement = exports.notifyListQuerySecurityChange = exports.notifyFieldAuthSecurityChange = exports.hasFieldAuthDirectives = exports.displayAuthNotification = void 0; const path_1 = __importDefault(require("path")); const amplify_cli_core_1 = require("@aws-amplify/amplify-cli-core"); const amplify_prompts_1 = require("@aws-amplify/amplify-prompts"); const graphql_1 = require("graphql"); const graphql_transformer_core_1 = require("graphql-transformer-core"); const fs_extra_1 = __importDefault(require("fs-extra")); const api_resource_paths_1 = require("./api-resource-paths"); const force_refresh_schema_1 = require("./force-refresh-schema"); const setNotificationFlag = async (projectPath, flagName, value) => { await amplify_cli_core_1.FeatureFlags.ensureFeatureFlag('graphqltransformer', flagName); const config = amplify_cli_core_1.stateManager.getCLIJSON(projectPath, undefined, { throwIfNotExist: false, preserveComments: true, }); if (config) { config.features.graphqltransformer[flagName] = value; amplify_cli_core_1.stateManager.setCLIJSON(projectPath, config); await amplify_cli_core_1.FeatureFlags.reloadValues(); } }; const loadResolvers = async (apiResourceDirectory) => { const resolvers = {}; const resolverDirectory = path_1.default.join(apiResourceDirectory, 'build', 'resolvers'); const resolverDirExists = fs_extra_1.default.existsSync(resolverDirectory); if (resolverDirExists) { const resolverFiles = await fs_extra_1.default.readdir(resolverDirectory); for (const resolverFile of resolverFiles) { if (resolverFile.indexOf('.') === 0) { continue; } const resolverFilePath = path_1.default.join(resolverDirectory, resolverFile); resolvers[resolverFile] = await fs_extra_1.default.readFile(resolverFilePath, 'utf8'); } } return resolvers; }; const displayAuthNotification = (directiveMap, fieldDirectives) => { const usesTransformerV2 = amplify_cli_core_1.FeatureFlags.getNumber('graphqltransformer.transformerVersion') === 2; const schemaHasValues = Object.keys(directiveMap).some((typeName) => { const typeObj = directiveMap[typeName]; const modelDirective = typeObj.find((dir) => dir.name.value === 'model'); const subscriptionOff = ((modelDirective === null || modelDirective === void 0 ? void 0 : modelDirective.arguments) || []).some((arg) => { var _a, _b; if (arg.name.value === 'subscriptions') { const subscriptionNull = arg.value.kind === 'NullValue'; const levelFieldOffOrNull = (_b = (_a = arg.value) === null || _a === void 0 ? void 0 : _a.fields) === null || _b === void 0 ? void 0 : _b.some(({ name, value }) => name.value === 'level' && (value.value === 'off' || value.kind === 'NullValue')); return levelFieldOffOrNull || subscriptionNull; } }); return subscriptionOff && fieldDirectives.has(typeName); }); return schemaHasValues && usesTransformerV2; }; exports.displayAuthNotification = displayAuthNotification; const hasFieldAuthDirectives = (doc) => { var _a; const haveFieldAuthDir = new Set(); (_a = doc.definitions) === null || _a === void 0 ? void 0 : _a.forEach((def) => { const withAuth = (def.fields || []).filter((field) => { var _a; const nonNullable = field.type.kind === 'NonNullType'; const hasAuth = (_a = field.directives) === null || _a === void 0 ? void 0 : _a.some((dir) => dir.name.value === 'auth'); return hasAuth && nonNullable; }); if (withAuth.length > 0) { haveFieldAuthDir.add(def.name.value); } }); return haveFieldAuthDir; }; exports.hasFieldAuthDirectives = hasFieldAuthDirectives; const notifyFieldAuthSecurityChange = async (context) => { var _a; const flagName = 'showFieldAuthNotification'; const dontShowNotification = !amplify_cli_core_1.FeatureFlags.getBoolean(`graphqltransformer.${flagName}`); if (dontShowNotification) return false; const projectPath = (_a = amplify_cli_core_1.pathManager.findProjectRoot()) !== null && _a !== void 0 ? _a : process.cwd(); const apiResourceDir = await (0, api_resource_paths_1.getApiResourceDir)(); if (!apiResourceDir) { await setNotificationFlag(projectPath, flagName, false); return false; } const project = await (0, graphql_transformer_core_1.readProjectConfiguration)(apiResourceDir); const directiveMap = (0, graphql_transformer_core_1.collectDirectivesByType)(project.schema); const doc = (0, graphql_1.parse)(project.schema); const fieldDirectives = (0, exports.hasFieldAuthDirectives)(doc); let schemaModified = false; if ((0, exports.displayAuthNotification)(directiveMap, fieldDirectives)) { amplify_prompts_1.printer.blankLine(); const continueChange = await amplify_prompts_1.prompter.yesOrNo('This version of Amplify CLI introduces additional security enhancements for your GraphQL API. ' + "The changes are applied automatically with this deployment. This change won't impact your client code. Continue?"); if (!continueChange) { await context.usageData.emitSuccess(); (0, amplify_cli_core_1.exitOnNextTick)(0); } (0, force_refresh_schema_1.forceRefreshSchema)(); schemaModified = true; } await setNotificationFlag(projectPath, flagName, false); return schemaModified; }; exports.notifyFieldAuthSecurityChange = notifyFieldAuthSecurityChange; const hasV2AuthDirectives = (doc) => { var _a; let containsAuthDir = false; const usesTransformerV2 = amplify_cli_core_1.FeatureFlags.getNumber('graphqltransformer.transformerVersion') === 2; (_a = doc.definitions) === null || _a === void 0 ? void 0 : _a.forEach((def) => { var _a; if ((_a = def.directives) === null || _a === void 0 ? void 0 : _a.some((dir) => dir.name.value === 'auth')) { containsAuthDir = true; } }); return containsAuthDir && usesTransformerV2; }; const notifyListQuerySecurityChange = async (context) => { const apiResourceDir = await (0, api_resource_paths_1.getApiResourceDir)(); if (!apiResourceDir) { return false; } const project = await (0, graphql_transformer_core_1.readProjectConfiguration)(apiResourceDir); const resolvers = await loadResolvers(apiResourceDir); const resolversToCheck = Object.entries(resolvers) .filter(([resolverFileName, _]) => resolverFileName.startsWith('Query.list') && resolverFileName.endsWith('.req.vtl')) .map(([_, resolverCode]) => resolverCode); const listQueryPattern = /#set\( \$filterExpression = \$util\.parseJson\(\$util\.transform\.toDynamoDBFilterExpression\(\$filter\)\) \)\s*(?!\s*#if\( \$util\.isNullOrEmpty\(\$filterExpression\) \))/gm; const resolversToSecure = resolversToCheck.filter((resolver) => listQueryPattern.test(resolver)); if (resolversToSecure.length === 0) { return false; } const doc = (0, graphql_1.parse)(project.schema); let schemaModified = false; if (hasV2AuthDirectives(doc)) { amplify_prompts_1.printer.blankLine(); const continueChange = await amplify_prompts_1.prompter.yesOrNo('This version of Amplify CLI introduces additional security enhancements for your GraphQL API. ' + "The changes are applied automatically with this deployment. This change won't impact your client code. Continue?"); if (!continueChange) { await context.usageData.emitSuccess(); (0, amplify_cli_core_1.exitOnNextTick)(0); } (0, force_refresh_schema_1.forceRefreshSchema)(); schemaModified = true; } return schemaModified; }; exports.notifyListQuerySecurityChange = notifyListQuerySecurityChange; const notifySecurityEnhancement = async (context) => { var _a; if (amplify_cli_core_1.FeatureFlags.getBoolean('graphqltransformer.securityEnhancementNotification')) { const projectPath = (_a = amplify_cli_core_1.pathManager.findProjectRoot()) !== null && _a !== void 0 ? _a : process.cwd(); const meta = amplify_cli_core_1.stateManager.getMeta(); const apiNames = Object.entries((meta === null || meta === void 0 ? void 0 : meta.api) || {}) .filter(([_, apiResource]) => apiResource.service === 'AppSync') .map(([name]) => name); if (apiNames.length !== 1) { await setNotificationFlag(projectPath, 'securityEnhancementNotification', false); return; } const apiName = apiNames[0]; const apiResourceDir = amplify_cli_core_1.pathManager.getResourceDirectoryPath(projectPath, 'api', apiName); if (!fs_extra_1.default.existsSync(apiResourceDir)) { await setNotificationFlag(projectPath, 'securityEnhancementNotification', false); return; } const project = await (0, graphql_transformer_core_1.readProjectConfiguration)(apiResourceDir); const directiveMap = (0, graphql_transformer_core_1.collectDirectivesByTypeNames)(project.schema); const notifyAuthWithKey = Object.keys(directiveMap.types).some((type) => directiveMap.types[type].includes('auth') && directiveMap.types[type].includes('primaryKey')); if ((meta === null || meta === void 0 ? void 0 : meta.auth) && notifyAuthWithKey) { amplify_prompts_1.printer.blankLine(); const shouldContinue = await amplify_prompts_1.prompter.yesOrNo("This version of Amplify CLI introduces additional security enhancements for your GraphQL API. @auth authorization rules applied on primary keys and indexes are scoped down further. The changes are applied automatically with this deployment. This change won't impact your client code. Continue"); if (!shouldContinue) { await context.usageData.emitSuccess(); (0, amplify_cli_core_1.exitOnNextTick)(0); } (0, force_refresh_schema_1.forceRefreshSchema)(); await setNotificationFlag(projectPath, 'securityEnhancementNotification', false); } else { await setNotificationFlag(projectPath, 'securityEnhancementNotification', false); } } }; exports.notifySecurityEnhancement = notifySecurityEnhancement; //# sourceMappingURL=auth-notifications.js.map