@aws-amplify/cli-internal
Version:
Amplify CLI
347 lines • 26.1 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RestApiRenderer = void 0;
const typescript_1 = __importDefault(require("typescript"));
const ts_1 = require("../../ts");
const factory = typescript_1.default.factory;
class RestApiRenderer {
constructor(hasAuth) {
this.hasAuth = hasAuth;
}
render(restApi) {
return factory.createNodeArray([
...this.renderImports(),
this.renderBackendTypeImport(),
ts_1.newLineIdentifier,
ts_1.TS.createBranchNameDeclaration(),
ts_1.newLineIdentifier,
this.renderDefineApi(restApi),
]);
}
renderImports() {
return [
ts_1.TS.namedImport('aws-cdk-lib/aws-apigateway', 'RestApi', 'LambdaIntegration', 'AuthorizationType', 'Cors', 'ResponseType'),
ts_1.TS.namedImport('aws-cdk-lib/aws-iam', 'Policy', 'PolicyStatement'),
ts_1.TS.namedImport('aws-cdk-lib', 'Stack'),
];
}
renderBackendTypeImport() {
return ts_1.TS.typeImport('../../backend', 'Backend');
}
renderDefineApi(restApi) {
const statements = this.renderApi(restApi);
return ts_1.TS.exportedFunction(restApi.exportedFunctionName, statements);
}
renderApi(restApi) {
const statements = [];
const sanitizedName = restApi.apiName.replace(/[^a-zA-Z0-9]/g, '');
const apiVarName = `${sanitizedName}Api`;
const gen1ApiVarName = `gen1${sanitizedName}Api`;
const gen1PolicyVarName = `gen1${sanitizedName}Policy`;
statements.push(this.renderStack(restApi, 'stack'));
statements.push(this.renderRestApiConstruct(restApi, 'stack', apiVarName));
statements.push(...this.renderGatewayResponses(apiVarName));
const integrations = this.renderLambdaIntegrations(restApi);
statements.push(...integrations.statements);
statements.push(this.renderGen1ApiReference(restApi, 'stack', gen1ApiVarName));
statements.push(this.renderGen1Policy(restApi, 'stack', gen1ApiVarName, gen1PolicyVarName));
if (this.hasPathAuth(restApi) && this.hasAuth) {
statements.push(this.renderGen1PolicyAttachment(gen1PolicyVarName));
}
statements.push(...this.renderPaths(restApi, apiVarName, integrations.map));
statements.push(...this.renderPathPolicies(restApi, apiVarName, 'stack', gen1ApiVarName));
statements.push(this.renderOutput(apiVarName));
return statements;
}
extractMethods(pathConfig) {
var _a, _b, _c;
if (((_b = (_a = pathConfig.permissions) === null || _a === void 0 ? void 0 : _a.auth) === null || _b === void 0 ? void 0 : _b.length) > 0) {
return this.mapPermissionsToMethods(pathConfig.permissions.auth);
}
if ((_c = pathConfig.permissions) === null || _c === void 0 ? void 0 : _c.groups) {
const allPermissions = new Set();
for (const permissions of Object.values(pathConfig.permissions.groups)) {
for (const permission of permissions) {
allPermissions.add(permission);
}
}
return this.mapPermissionsToMethods(Array.from(allPermissions));
}
return ['GET'];
}
mapPermissionsToMethods(permissions) {
const methodMap = {
read: 'GET',
create: 'POST',
update: 'PUT',
delete: 'DELETE',
};
const methods = permissions.map((p) => methodMap[p]).filter((m) => m !== undefined);
return methods.length > 0 ? methods : ['GET'];
}
renderStack(restApi, stackVarName) {
return factory.createVariableStatement([], factory.createVariableDeclarationList([
factory.createVariableDeclaration(stackVarName, undefined, undefined, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('backend'), factory.createIdentifier('createStack')), undefined, [factory.createStringLiteral(`rest-api-stack-${restApi.apiName}`)])),
], typescript_1.default.NodeFlags.Const));
}
renderRestApiConstruct(restApi, stackVarName, apiVarName) {
return factory.createVariableStatement([], factory.createVariableDeclarationList([
factory.createVariableDeclaration(apiVarName, undefined, undefined, factory.createNewExpression(factory.createIdentifier('RestApi'), undefined, [
factory.createIdentifier(stackVarName),
factory.createStringLiteral('RestApi'),
factory.createObjectLiteralExpression([
factory.createPropertyAssignment('restApiName', factory.createTemplateExpression(factory.createTemplateHead(`${restApi.apiName}-`), [
factory.createTemplateSpan(factory.createIdentifier('branchName'), factory.createTemplateTail('')),
])),
], true),
])),
], typescript_1.default.NodeFlags.Const));
}
renderGatewayResponses(apiVarName) {
return [
this.renderGatewayResponse(apiVarName, 'Default4XX', 'DEFAULT_4XX'),
this.renderGatewayResponse(apiVarName, 'Default5XX', 'DEFAULT_5XX'),
];
}
renderGatewayResponse(apiVarName, name, responseType) {
return factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(apiVarName), factory.createIdentifier('addGatewayResponse')), undefined, [
factory.createStringLiteral(name),
factory.createObjectLiteralExpression([
ts_1.TS.enumProp('type', 'ResponseType', responseType),
factory.createPropertyAssignment('responseHeaders', factory.createObjectLiteralExpression([
factory.createPropertyAssignment(factory.createStringLiteral('Access-Control-Allow-Origin'), factory.createStringLiteral("'*'")),
factory.createPropertyAssignment(factory.createStringLiteral('Access-Control-Allow-Headers'), factory.createStringLiteral("'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'")),
factory.createPropertyAssignment(factory.createStringLiteral('Access-Control-Allow-Methods'), factory.createStringLiteral("'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'")),
factory.createPropertyAssignment(factory.createStringLiteral('Access-Control-Expose-Headers'), factory.createStringLiteral("'Date,X-Amzn-ErrorType'")),
], true)),
], true),
]));
}
renderLambdaIntegrations(restApi) {
const statements = [];
const map = new Map();
const uniqueFunctions = new Set();
for (const pathConfig of Object.values(restApi.paths)) {
uniqueFunctions.add(pathConfig.lambdaFunction);
}
for (const funcName of uniqueFunctions) {
const integrationVarName = `${funcName}Integration`;
map.set(funcName, integrationVarName);
statements.push(factory.createVariableStatement([], factory.createVariableDeclarationList([
factory.createVariableDeclaration(integrationVarName, undefined, undefined, factory.createNewExpression(factory.createIdentifier('LambdaIntegration'), undefined, [
factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('backend'), factory.createIdentifier(funcName)), factory.createIdentifier('resources')), factory.createIdentifier('lambda')),
])),
], typescript_1.default.NodeFlags.Const)));
}
return { statements, map };
}
renderGen1ApiReference(restApi, stackVarName, gen1ApiVarName) {
return factory.createVariableStatement([], factory.createVariableDeclarationList([
factory.createVariableDeclaration(gen1ApiVarName, undefined, undefined, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('RestApi'), factory.createIdentifier('fromRestApiAttributes')), undefined, [
factory.createIdentifier(stackVarName),
factory.createStringLiteral(`Gen1${restApi.apiName}Api`),
factory.createObjectLiteralExpression([
factory.createPropertyAssignment('restApiId', factory.createStringLiteral(restApi.gen1ApiId)),
factory.createPropertyAssignment('rootResourceId', factory.createStringLiteral(restApi.gen1RootResourceId)),
], true),
])),
], typescript_1.default.NodeFlags.Const));
}
renderGen1Policy(restApi, stackVarName, gen1ApiVarName, gen1PolicyVarName) {
return factory.createVariableStatement([], factory.createVariableDeclarationList([
factory.createVariableDeclaration(gen1PolicyVarName, undefined, undefined, factory.createNewExpression(factory.createIdentifier('Policy'), undefined, [
factory.createIdentifier(stackVarName),
factory.createStringLiteral(`Gen1${restApi.apiName}Policy`),
factory.createObjectLiteralExpression([
factory.createPropertyAssignment('statements', factory.createArrayLiteralExpression([
factory.createNewExpression(factory.createIdentifier('PolicyStatement'), undefined, [
factory.createObjectLiteralExpression([
factory.createPropertyAssignment('actions', factory.createArrayLiteralExpression([factory.createStringLiteral('execute-api:Invoke')])),
factory.createPropertyAssignment('resources', factory.createArrayLiteralExpression([
...Object.entries(restApi.paths).flatMap(([, pathConfig]) => this.extractMethods(pathConfig).map((method) => factory.createTemplateExpression(factory.createTemplateHead(''), [
factory.createTemplateSpan(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(gen1ApiVarName), factory.createIdentifier('arnForExecuteApi')), undefined, [factory.createStringLiteral(method), factory.createStringLiteral('/*')]), factory.createTemplateTail('')),
]))),
])),
], true),
]),
])),
], true),
])),
], typescript_1.default.NodeFlags.Const));
}
renderGen1PolicyAttachment(gen1PolicyVarName) {
return factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('backend.auth.resources'), factory.createIdentifier('authenticatedUserIamRole')), factory.createIdentifier('attachInlinePolicy')), undefined, [factory.createIdentifier(gen1PolicyVarName)]));
}
renderPaths(restApi, apiVarName, integrations) {
var _a, _b;
const statements = [];
for (const [pathName, pathConfig] of Object.entries(restApi.paths)) {
const pathSegments = pathName.split('/').filter((segment) => segment && segment !== '{proxy+}');
const resourceName = pathSegments.join('').replace(/[^a-zA-Z0-9]/g, '') || 'root';
let resourceExpression = factory.createPropertyAccessExpression(factory.createIdentifier(apiVarName), factory.createIdentifier('root'));
for (let i = 0; i < pathSegments.length; i++) {
const isLastSegment = i === pathSegments.length - 1;
const resourceArgs = [factory.createStringLiteral(pathSegments[i])];
if (isLastSegment) {
const resourceOptions = [];
if (((_a = pathConfig.permissions) === null || _a === void 0 ? void 0 : _a.setting) === 'private') {
resourceOptions.push(factory.createPropertyAssignment('defaultMethodOptions', factory.createObjectLiteralExpression([ts_1.TS.enumProp('authorizationType', 'AuthorizationType', 'IAM')], true)));
}
resourceOptions.push(this.renderCorsPreflightOptions());
resourceArgs.push(factory.createObjectLiteralExpression(resourceOptions, true));
}
resourceExpression = factory.createCallExpression(factory.createPropertyAccessExpression(resourceExpression, factory.createIdentifier('addResource')), undefined, resourceArgs);
}
statements.push(factory.createVariableStatement([], factory.createVariableDeclarationList([factory.createVariableDeclaration(resourceName, undefined, undefined, resourceExpression)], typescript_1.default.NodeFlags.Const)));
const integrationVar = (_b = integrations.get(pathConfig.lambdaFunction)) !== null && _b !== void 0 ? _b : `${pathConfig.lambdaFunction}Integration`;
statements.push(factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(resourceName), factory.createIdentifier('addMethod')), undefined, [factory.createStringLiteral('ANY'), factory.createIdentifier(integrationVar)])));
statements.push(factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(resourceName), factory.createIdentifier('addProxy')), undefined, [
factory.createObjectLiteralExpression([
factory.createPropertyAssignment('anyMethod', factory.createTrue()),
factory.createPropertyAssignment('defaultIntegration', factory.createIdentifier(integrationVar)),
], true),
])));
}
return statements;
}
renderCorsPreflightOptions() {
return factory.createPropertyAssignment('defaultCorsPreflightOptions', factory.createObjectLiteralExpression([
ts_1.TS.enumProp('allowOrigins', 'Cors', 'ALL_ORIGINS'),
ts_1.TS.enumProp('allowMethods', 'Cors', 'ALL_METHODS'),
factory.createPropertyAssignment('allowHeaders', factory.createArrayLiteralExpression([
factory.createStringLiteral('Content-Type'),
factory.createStringLiteral('X-Amz-Date'),
factory.createStringLiteral('Authorization'),
factory.createStringLiteral('X-Api-Key'),
factory.createStringLiteral('X-Amz-Security-Token'),
factory.createStringLiteral('X-Amz-User-Agent'),
])),
factory.createPropertyAssignment('statusCode', factory.createNumericLiteral('200')),
], true));
}
renderPathPolicies(restApi, apiVarName, stackVarName, gen1ApiVarName) {
var _a, _b, _c;
const statements = [];
for (const [pathName, pathConfig] of Object.entries(restApi.paths)) {
if (((_b = (_a = pathConfig.permissions) === null || _a === void 0 ? void 0 : _a.auth) === null || _b === void 0 ? void 0 : _b.length) > 0) {
statements.push(...this.renderAuthPathPolicy(pathName, pathConfig, apiVarName, stackVarName));
}
if ((_c = pathConfig.permissions) === null || _c === void 0 ? void 0 : _c.groups) {
for (const groupName of Object.keys(pathConfig.permissions.groups)) {
statements.push(...this.renderGroupPathPolicy(pathName, pathConfig, apiVarName, stackVarName, groupName, gen1ApiVarName));
}
}
}
return statements;
}
renderAuthPathPolicy(pathName, pathConfig, apiVarName, stackVarName) {
const comment = factory.createNotEmittedStatement(factory.createStringLiteral(''));
typescript_1.default.addSyntheticLeadingComment(comment, typescript_1.default.SyntaxKind.SingleLineCommentTrivia, ` ${pathName} - all authenticated users`, true);
const policyName = `${pathName.replace(/[^a-zA-Z0-9]/g, '')}AuthPolicy`;
const attachCall = factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('backend.auth.resources'), factory.createIdentifier('authenticatedUserIamRole')), factory.createIdentifier('attachInlinePolicy')), undefined, [
factory.createNewExpression(factory.createIdentifier('Policy'), undefined, [
factory.createIdentifier(stackVarName),
factory.createStringLiteral(policyName),
factory.createObjectLiteralExpression([
factory.createPropertyAssignment('statements', factory.createArrayLiteralExpression([
factory.createNewExpression(factory.createIdentifier('PolicyStatement'), undefined, [
factory.createObjectLiteralExpression([
factory.createPropertyAssignment('actions', factory.createArrayLiteralExpression([factory.createStringLiteral('execute-api:Invoke')])),
factory.createPropertyAssignment('resources', factory.createArrayLiteralExpression([
...this.extractMethods(pathConfig).flatMap((method) => [
factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(apiVarName), factory.createIdentifier('arnForExecuteApi')), undefined, [factory.createStringLiteral(method), factory.createStringLiteral(pathName)]),
factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(apiVarName), factory.createIdentifier('arnForExecuteApi')), undefined, [factory.createStringLiteral(method), factory.createStringLiteral(`${pathName}/*`)]),
]),
])),
], true),
]),
])),
], true),
]),
]));
return [comment, attachCall];
}
renderGroupPathPolicy(pathName, pathConfig, apiVarName, stackVarName, groupName, gen1ApiVarName) {
const comment = factory.createNotEmittedStatement(factory.createStringLiteral(''));
typescript_1.default.addSyntheticLeadingComment(comment, typescript_1.default.SyntaxKind.SingleLineCommentTrivia, ` ${pathName} - ${groupName} group only`, true);
const policyName = `${pathName.replace(/[^a-zA-Z0-9]/g, '')}${groupName}Policy`;
const attachCall = factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createElementAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('backend.auth.resources'), factory.createIdentifier('groups')), factory.createStringLiteral(groupName)), factory.createIdentifier('role')), factory.createIdentifier('attachInlinePolicy')), undefined, [
factory.createNewExpression(factory.createIdentifier('Policy'), undefined, [
factory.createIdentifier(stackVarName),
factory.createStringLiteral(policyName),
factory.createObjectLiteralExpression([
factory.createPropertyAssignment('statements', factory.createArrayLiteralExpression([
factory.createNewExpression(factory.createIdentifier('PolicyStatement'), undefined, [
factory.createObjectLiteralExpression([
factory.createPropertyAssignment('actions', factory.createArrayLiteralExpression([factory.createStringLiteral('execute-api:Invoke')])),
factory.createPropertyAssignment('resources', factory.createArrayLiteralExpression([
...this.extractMethods(pathConfig).flatMap((method) => [
factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(apiVarName), factory.createIdentifier('arnForExecuteApi')), undefined, [factory.createStringLiteral(method), factory.createStringLiteral(pathName)]),
factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(apiVarName), factory.createIdentifier('arnForExecuteApi')), undefined, [factory.createStringLiteral(method), factory.createStringLiteral(`${pathName}/*`)]),
]),
])),
], true),
]),
])),
], true),
]),
]));
const pathPart = pathName.replace(/[^a-zA-Z0-9]/g, '');
const capitalizedPathPart = pathPart.charAt(0).toUpperCase() + pathPart.slice(1);
const gen1PolicyName = `gen1${capitalizedPathPart}${groupName}Policy`;
return [
comment,
attachCall,
this.renderGen1GroupPathPolicy(pathName, pathConfig, gen1ApiVarName, stackVarName, groupName, gen1PolicyName),
];
}
renderGen1GroupPathPolicy(pathName, pathConfig, gen1ApiVarName, stackVarName, groupName, policyName) {
return factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createElementAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('backend.auth.resources'), factory.createIdentifier('groups')), factory.createStringLiteral(groupName)), factory.createIdentifier('role')), factory.createIdentifier('attachInlinePolicy')), undefined, [
factory.createNewExpression(factory.createIdentifier('Policy'), undefined, [
factory.createIdentifier(stackVarName),
factory.createStringLiteral(policyName),
factory.createObjectLiteralExpression([
factory.createPropertyAssignment('statements', factory.createArrayLiteralExpression([
factory.createNewExpression(factory.createIdentifier('PolicyStatement'), undefined, [
factory.createObjectLiteralExpression([
factory.createPropertyAssignment('actions', factory.createArrayLiteralExpression([factory.createStringLiteral('execute-api:Invoke')])),
factory.createPropertyAssignment('resources', factory.createArrayLiteralExpression([
...this.extractMethods(pathConfig).flatMap((method) => [
factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(gen1ApiVarName), factory.createIdentifier('arnForExecuteApi')), undefined, [factory.createStringLiteral(method), factory.createStringLiteral(pathName)]),
factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(gen1ApiVarName), factory.createIdentifier('arnForExecuteApi')), undefined, [factory.createStringLiteral(method), factory.createStringLiteral(`${pathName}/*`)]),
]),
])),
], true),
]),
])),
], true),
]),
]));
}
renderOutput(apiVarName) {
return factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('backend'), factory.createIdentifier('addOutput')), undefined, [
factory.createObjectLiteralExpression([
factory.createPropertyAssignment('custom', factory.createObjectLiteralExpression([
factory.createPropertyAssignment('API', factory.createObjectLiteralExpression([
factory.createPropertyAssignment(factory.createComputedPropertyName(factory.createPropertyAccessExpression(factory.createIdentifier(apiVarName), factory.createIdentifier('restApiName'))), factory.createObjectLiteralExpression([
factory.createPropertyAssignment('endpoint', factory.createCallExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier(apiVarName), factory.createIdentifier('url')), factory.createIdentifier('slice')), undefined, [
factory.createNumericLiteral('0'),
factory.createPrefixUnaryExpression(typescript_1.default.SyntaxKind.MinusToken, factory.createNumericLiteral('1')),
])),
factory.createPropertyAssignment('region', factory.createPropertyAccessExpression(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('Stack'), factory.createIdentifier('of')), undefined, [factory.createIdentifier(apiVarName)]), factory.createIdentifier('region'))),
factory.createPropertyAssignment('apiName', factory.createPropertyAccessExpression(factory.createIdentifier(apiVarName), factory.createIdentifier('restApiName'))),
], true)),
], true)),
], true)),
], true),
]));
}
hasPathAuth(restApi) {
return Object.values(restApi.paths).some((p) => { var _a, _b; return ((_a = p.permissions) === null || _a === void 0 ? void 0 : _a.setting) === 'private' || ((_b = p.permissions) === null || _b === void 0 ? void 0 : _b.setting) === 'protected'; });
}
}
exports.RestApiRenderer = RestApiRenderer;
//# sourceMappingURL=rest-api.renderer.js.map