diff --git a/.circleci/config.yml b/.circleci/config.yml index 6cda4ef5..e92f487c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -104,6 +104,9 @@ jobs: - set_aws_prefix - checkout - node/install-packages + - run: + name: Run unit tests + command: npm run unit-test - run: name: Run infra tests command: npm run infra-test diff --git a/examples/simple-authenticated-api/.gitignore b/examples/simple-authenticated-api/.gitignore index 1807d587..aec09340 100644 --- a/examples/simple-authenticated-api/.gitignore +++ b/examples/simple-authenticated-api/.gitignore @@ -10,3 +10,5 @@ cdk.context.json !src/lambda/route1.js !src/lambda/route2.js +!src/lambda/route3.js +!src/lambda/route4.js diff --git a/examples/simple-authenticated-api/lib/simple-authenticated-api-stack.ts b/examples/simple-authenticated-api/lib/simple-authenticated-api-stack.ts index 75c94099..70c12685 100644 --- a/examples/simple-authenticated-api/lib/simple-authenticated-api-stack.ts +++ b/examples/simple-authenticated-api/lib/simple-authenticated-api-stack.ts @@ -85,6 +85,32 @@ export class SimpleAuthenticatedApiStack extends cdk.Stack { } ); + const route3Handler = new AuthenticatedApiFunction( + this, + `${prefix}simple-authenticated-api-route3-handler`, + { + name: `${prefix}route3-handler`, + entry: "src/lambda/route3.js", + environment: {}, + handler: "route", + timeout: cdk.Duration.seconds(30), + securityGroups: lambdaSecurityGroups, + } + ); + + const route4Handler = new AuthenticatedApiFunction( + this, + `${prefix}simple-authenticated-api-route4-handler`, + { + name: `${prefix}route4-handler`, + entry: "src/lambda/route4.js", + environment: {}, + handler: "route", + timeout: cdk.Duration.seconds(30), + securityGroups: lambdaSecurityGroups, + } + ); + const api = new AuthenticatedApi( this, `${prefix}simple-authenticated-api`, @@ -127,6 +153,20 @@ export class SimpleAuthenticatedApiStack extends cdk.Stack { lambda: route2Handler, isPublic: true, }, + { + name: "route3", + path: "/1/route3/{id}", + method: apigatewayv2.HttpMethod.GET, + lambda: route3Handler, + requiredScope: "analytics:admin", + }, + { + name: "route4", + path: "/1/route4/{id}/route4", + method: apigatewayv2.HttpMethod.GET, + lambda: route4Handler, + requiredScope: "analytics:admin", + }, ], } ); diff --git a/examples/simple-authenticated-api/src/lambda/route3.js b/examples/simple-authenticated-api/src/lambda/route3.js new file mode 100644 index 00000000..b61f7647 --- /dev/null +++ b/examples/simple-authenticated-api/src/lambda/route3.js @@ -0,0 +1,20 @@ +class Route { + constructor(event) { + this.event = event; + } + + async handle() { + console.log("Route 3 processing event."); + + return { + statusCode: 200, + headers: {}, + body: "route 3", + }; + } +} + +module.exports.route = async (event) => { + const route = new Route(event); + return await route.handle(); +}; diff --git a/examples/simple-authenticated-api/src/lambda/route4.js b/examples/simple-authenticated-api/src/lambda/route4.js new file mode 100644 index 00000000..70d35d38 --- /dev/null +++ b/examples/simple-authenticated-api/src/lambda/route4.js @@ -0,0 +1,20 @@ +class Route { + constructor(event) { + this.event = event; + } + + async handle() { + console.log("Route 4 processing event."); + + return { + statusCode: 200, + headers: {}, + body: "route 4", + }; + } +} + +module.exports.route = async (event) => { + const route = new Route(event); + return await route.handle(); +}; diff --git a/lib/authenticated-api/authenticated-api.ts b/lib/authenticated-api/authenticated-api.ts index 46678879..c36c5a16 100644 --- a/lib/authenticated-api/authenticated-api.ts +++ b/lib/authenticated-api/authenticated-api.ts @@ -80,7 +80,7 @@ export class AuthenticatedApi extends cdk.Construct { if (props.lambdaRoutes) { for (const routeProps of props.lambdaRoutes) { if (routeProps.requiredScope) { - scopeConfig[`^${routeProps.path}$`] = routeProps.requiredScope; + scopeConfig[routeProps.path] = routeProps.requiredScope; } } } diff --git a/package.json b/package.json index d447f1e6..b7f4bea9 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "scripts": { "build": "tsc", "watch": "tsc -w", + "unit-test": "npm run build && jest test/unit", "infra-test": "npm run build && jest test/infra", "integration-test": "npm run build && npm run jest-integration-test", "jest-integration-test": "jest test/integration/", diff --git a/src/lambda/api/authorizer.d.ts b/src/lambda/api/authorizer.d.ts index cb0ff5c3..e3b5ba76 100644 --- a/src/lambda/api/authorizer.d.ts +++ b/src/lambda/api/authorizer.d.ts @@ -1 +1,46 @@ +import { PersonaClient } from "talis-node"; +declare type ParsedArn = { + method: string; + resourcePath: string; + apiOptions: { + region: string; + restApiId: string; + stage: string; + }; + awsAccountId: string; +}; +export declare class PersonaAuthorizer { + event: any; + context: any; + personaClient: PersonaClient | undefined; + constructor(event: any, context: any); + handle(): Promise; + validateToken(validationOpts: any): Promise>; + /** + * Break down an API gateway method ARN into it's constituent parts. + * Method ARNs take the following format: + * + * arn:aws:execute-api:::/// + * + * e.g: + * + * arn:aws:execute-api:eu-west-1:123:abc/development/GET/2/works + * + * @param methodArn {string} The method ARN provided by the event handed to a Lambda function + * @returns {{ + * method: string, + * resourcePath: string, + * apiOptions: { + * region: string, + * restApiId: string, + * stage: string + * }, + * awsAccountId: string + * }} + */ + parseMethodArn(methodArn: string): ParsedArn; + getScope(parsedMethodArn: ParsedArn): any; + getPersonaClient(): PersonaClient; + pathMatch(pathDefinition: string, path: string): boolean; +} export {}; diff --git a/src/lambda/api/authorizer.js b/src/lambda/api/authorizer.js index 01563891..2976de3e 100644 --- a/src/lambda/api/authorizer.js +++ b/src/lambda/api/authorizer.js @@ -1,5 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); +exports.PersonaAuthorizer = void 0; const _ = require("lodash"); const talis_node_1 = require("talis-node"); // Constants used by parseMethodArn: @@ -164,9 +165,9 @@ class PersonaAuthorizer { const scopeConfig = process.env["SCOPE_CONFIG"]; if (scopeConfig != undefined) { const conf = JSON.parse(scopeConfig); - for (const pathRegEx of Object.keys(conf)) { - if (parsedMethodArn.resourcePath.match(pathRegEx)) { - return conf[pathRegEx]; + for (const path of Object.keys(conf)) { + if (this.pathMatch(path, parsedMethodArn.resourcePath)) { + return conf[path]; } } } @@ -184,9 +185,32 @@ class PersonaAuthorizer { } return this.personaClient; } + pathMatch(pathDefinition, path) { + const pathDefinitionParts = pathDefinition.split("/"); + const pathParts = path.split("/"); + if (pathDefinitionParts.length !== pathParts.length) { + return false; + } + for (let i = 0; i < pathDefinitionParts.length; i++) { + const pathDefinitionSegment = pathDefinitionParts[i]; + const pathSegment = pathParts[i]; + if (pathDefinitionSegment.startsWith("{") && + pathDefinitionSegment.endsWith("}")) { + // Matches path argument + } + else { + // Should match directly + if (pathDefinitionSegment !== pathSegment) { + return false; + } + } + } + return true; + } } +exports.PersonaAuthorizer = PersonaAuthorizer; module.exports.validateToken = async (event, context) => { const route = new PersonaAuthorizer(event, context); return await route.handle(); }; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aG9yaXplci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImF1dGhvcml6ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSw0QkFBNEI7QUFDNUIsMkNBQW9EO0FBYXBELG9DQUFvQztBQUNwQyxFQUFFO0FBQ0YscUJBQXFCO0FBQ3JCLDZGQUE2RjtBQUM3RixvRUFBb0U7QUFDcEUsOEZBQThGO0FBQzlGLEVBQUU7QUFDRixFQUFFO0FBQ0YsTUFBTSxTQUFTLEdBQUcsQ0FBQyxDQUFDO0FBQ3BCLE1BQU0sU0FBUyxHQUFHLENBQUMsQ0FBQztBQUNwQixNQUFNLGFBQWEsR0FBRyxDQUFDLENBQUM7QUFDeEIsTUFBTSxZQUFZLEdBQUcsQ0FBQyxDQUFDO0FBQ3ZCLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxDQUFDO0FBQzNCLE1BQU0scUJBQXFCLEdBQUcsQ0FBQyxDQUFDO0FBRWhDLE1BQU0sa0JBQWtCLEdBQUc7SUFDekIsU0FBUztJQUNULFNBQVM7SUFDVCxhQUFhO0lBQ2IsWUFBWTtJQUNaLGdCQUFnQjtJQUNoQixxQkFBcUI7Q0FDdEIsQ0FBQztBQUVGLE1BQU0sWUFBWSxHQUFHLENBQUMsQ0FBQztBQUN2QixNQUFNLFdBQVcsR0FBRyxDQUFDLENBQUM7QUFDdEIsTUFBTSxZQUFZLEdBQUcsQ0FBQyxDQUFDO0FBQ3ZCLE1BQU0sbUJBQW1CLEdBQUcsQ0FBQyxDQUFDO0FBRTlCLE1BQU0sdUJBQXVCLEdBQUc7SUFDOUIsWUFBWTtJQUNaLFdBQVc7SUFDWCxZQUFZO0lBQ1osbUJBQW1CO0NBQ3BCLENBQUM7QUFFRixNQUFNLGlCQUFpQjtJQUtyQixZQUFZLEtBQVUsRUFBRSxPQUFZO1FBQ2xDLElBQUksQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDO1FBQ25CLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDO1FBRXZCLElBQUksQ0FBQyxhQUFhLEdBQUcsU0FBUyxDQUFDO0lBQ2pDLENBQUM7SUFFRCxLQUFLLENBQUMsTUFBTTs7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LEtBQVUsRUFBRSxPQUFZLEVBQUUsRUFBRTtJQUNoRSxNQUFNLEtBQUssR0FBRyxJQUFJLGlCQUFpQixDQUFDLEtBQUssRUFBRSxPQUFPLENBQUMsQ0FBQztJQUNwRCxPQUFPLE1BQU0sS0FBSyxDQUFDLE1BQU0sRUFBRSxDQUFDO0FBQzlCLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIF8gZnJvbSBcImxvZGFzaFwiO1xuaW1wb3J0IHsgcGVyc29uYSwgUGVyc29uYUNsaWVudCB9IGZyb20gXCJ0YWxpcy1ub2RlXCI7XG5cbnR5cGUgUGFyc2VkQXJuID0ge1xuICBtZXRob2Q6IHN0cmluZztcbiAgcmVzb3VyY2VQYXRoOiBzdHJpbmc7XG4gIGFwaU9wdGlvbnM6IHtcbiAgICByZWdpb246IHN0cmluZztcbiAgICByZXN0QXBpSWQ6IHN0cmluZztcbiAgICBzdGFnZTogc3RyaW5nO1xuICB9O1xuICBhd3NBY2NvdW50SWQ6IHN0cmluZztcbn07XG5cbi8vIENvbnN0YW50cyB1c2VkIGJ5IHBhcnNlTWV0aG9kQXJuOlxuLy9cbi8vIEV4YW1wbGUgTWV0aG9kQVJOOlxuLy8gICBcImFybjphd3M6ZXhlY3V0ZS1hcGk6PFJlZ2lvbiBpZD46PEFjY291bnQgaWQ+OjxBUEkgaWQ+LzxTdGFnZT4vPE1ldGhvZD4vPFJlc291cmNlIHBhdGg+XCJcbi8vIE1ldGhvZCBBUk4gSW5kZXg6ICAwICAgMSAgIDIgICAgICAgICAgIDMgICAgICAgICAgIDQgICAgICAgICAgICA1XG4vLyBBUEkgR2F0ZXdheSBBUk4gSW5kZXg6ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMCAgICAgICAgMSAgICAgICAyICAgICAgICAzXG4vL1xuLy9cbmNvbnN0IEFSTl9JTkRFWCA9IDA7XG5jb25zdCBBV1NfSU5ERVggPSAxO1xuY29uc3QgRVhFQ1VURV9JTkRFWCA9IDI7XG5jb25zdCBSRUdJT05fSU5ERVggPSAzO1xuY29uc3QgQUNDT1VOVF9JRF9JTkRFWCA9IDQ7XG5jb25zdCBBUElfR0FURVdBWV9BUk5fSU5ERVggPSA1O1xuXG5jb25zdCBNRVRIT0RfQVJOX0lOREVYRVMgPSBbXG4gIEFSTl9JTkRFWCxcbiAgQVdTX0lOREVYLFxuICBFWEVDVVRFX0lOREVYLFxuICBSRUdJT05fSU5ERVgsXG4gIEFDQ09VTlRfSURfSU5ERVgsXG4gIEFQSV9HQVRFV0FZX0FSTl9JTkRFWCxcbl07XG5cbmNvbnN0IEFQSV9JRF9JTkRFWCA9IDA7XG5jb25zdCBTVEFHRV9JTkRFWCA9IDE7XG5jb25zdCBNRVRIT0RfSU5ERVggPSAyO1xuY29uc3QgUkVTT1VSQ0VfUEFUSF9JTkRFWCA9IDM7XG5cbmNvbnN0IEFQSV9HQVRFV0FZX0FSTl9JTkRFWEVTID0gW1xuICBBUElfSURfSU5ERVgsXG4gIFNUQUdFX0lOREVYLFxuICBNRVRIT0RfSU5ERVgsXG4gIFJFU09VUkNFX1BBVEhfSU5ERVgsXG5dO1xuXG5jbGFzcyBQZXJzb25hQXV0aG9yaXplciB7XG4gIGV2ZW50OiBhbnk7XG4gIGNvbnRleHQ6IGFueTtcbiAgcGVyc29uYUNsaWVudDogUGVyc29uYUNsaWVudCB8IHVuZGVmaW5lZDtcblxuICBjb25zdHJ1Y3RvcihldmVudDogYW55LCBjb250ZXh0OiBhbnkpIHtcbiAgICB0aGlzLmV2ZW50ID0gZXZlbnQ7XG4gICAgdGhpcy5jb250ZXh0ID0gY29udGV4dDtcblxuICAgIHRoaXMucGVyc29uYUNsaWVudCA9IHVuZGVmaW5lZDtcbiAgfVxuXG4gIGFzeW5jIGhhbmRsZSgpIHtcbiAgICBjb25zb2xlLmxvZyhcIlJlY2VpdmVkIGV2ZW50XCIsIHRoaXMuZXZlbnQpO1xuXG4gICAgaWYgKCF0aGlzLmV2ZW50Py5oZWFkZXJzIHx8IHRoaXMuZXZlbnQuaGVhZGVyc1tcImF1dGhvcml6YXRpb25cIl0gPT0gbnVsbCkge1xuICAgICAgY29uc29sZS5sb2coXCJNaXNzaW5nIGF1dGggdG9rZW5cIik7XG4gICAgICByZXR1cm4gdGhpcy5jb250ZXh0LmZhaWwoXCJVbmF1dGhvcml6ZWRcIik7XG4gICAgfVxuXG4gICAgY29uc3QgcGFyc2VkTWV0aG9kQXJuID0gdGhpcy5wYXJzZU1ldGhvZEFybih0aGlzLmV2ZW50LnJvdXRlQXJuKTtcbiAgICBjb25zb2xlLmxvZyhgUGFyc2VkIE1ldGhvZCBBcm46ICR7SlNPTi5zdHJpbmdpZnkocGFyc2VkTWV0aG9kQXJuKX1gKTtcblxuICAgIGNvbnN0IHNjb3BlID0gdGhpcy5nZXRTY29wZShwYXJzZWRNZXRob2RBcm4pO1xuICAgIGNvbnNvbGUubG9nKGBNZXRob2QgaGFzIHNjb3BlOiAke3Njb3BlfWApO1xuXG4gICAgbGV0IHZhbGlkYXRpb25PcHRzID0ge1xuICAgICAgdG9rZW46IF8ucmVwbGFjZShcbiAgICAgICAgdGhpcy5ldmVudC5oZWFkZXJzW1wiYXV0aG9yaXphdGlvblwiXSxcbiAgICAgICAgXCJCZWFyZXJcIixcbiAgICAgICAgXCJcIlxuICAgICAgKS50cmltKCksXG4gICAgfTtcbiAgICBpZiAoc2NvcGUgIT0gbnVsbCkge1xuICAgICAgdmFsaWRhdGlvbk9wdHMgPSBfLm1lcmdlKHZhbGlkYXRpb25PcHRzLCB7IHNjb3BlIH0pO1xuICAgIH1cbiAgICBjb25zb2xlLmxvZyhgVmFsaWRhdGlvbiBvcHM6ICR7SlNPTi5zdHJpbmdpZnkodmFsaWRhdGlvbk9wdHMpfWApO1xuXG4gICAgY29uc29sZS5sb2coXG4gICAgICBcInZhbGlkYXRpbmcgdG9rZW4gYWdhaW5zdCByZXF1ZXN0XCIsXG4gICAgICBgJHtwYXJzZWRNZXRob2RBcm4ucmVzb3VyY2VQYXRofWBcbiAgICApO1xuXG4gICAgaWYgKCF2YWxpZGF0aW9uT3B0cy50b2tlbiB8fCB2YWxpZGF0aW9uT3B0cy50b2tlbi5sZW5ndGggPT09IDApIHtcbiAgICAgIGNvbnNvbGUubG9nKFwidG9rZW4gbWlzc2luZ1wiKTtcbiAgICAgIHJldHVybiB0aGlzLmNvbnRleHQuZmFpbChcIlVuYXV0aG9yaXplZFwiKTtcbiAgICB9XG5cbiAgICB0cnkge1xuICAgICAgY29uc3QgdG9rZW4gPSBhd2FpdCB0aGlzLnZhbGlkYXRlVG9rZW4odmFsaWRhdGlvbk9wdHMpO1xuICAgICAgY29uc3Qgc3VjY2VzcyA9IHtcbiAgICAgICAgaXNBdXRob3JpemVkOiB0cnVlLFxuICAgICAgICBjb250ZXh0OiB7XG4gICAgICAgICAgY2xpZW50SWQ6IHRva2VuW1wic3ViXCJdLFxuICAgICAgICB9LFxuICAgICAgfTtcbiAgICAgIHJldHVybiB0aGlzLmNvbnRleHQuc3VjY2VlZChzdWNjZXNzKTtcbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIGNvbnNvbGUubG9nKFwidG9rZW4gdmFsaWRhdGlvbiBmYWlsZWRcIiwgZXJyKTtcblxuICAgICAgY29uc3QgZXJyb3IgPSBlcnIgYXMgeyBlcnJvcjogYW55OyB0b2tlbjogUmVjb3JkPHN0cmluZywgYW55PiB9O1xuXG4gICAgICBpZiAoZXJyb3IuZXJyb3IgPT09IHBlcnNvbmEuZXJyb3JUeXBlcy5JTlNVRkZJQ0lFTlRfU0NPUEUpIHtcbiAgICAgICAgY29uc3QgaW5zdWZmaWNpZW50U2NvcGUgPSB7XG4gICAgICAgICAgaXNBdXRob3JpemVkOiBmYWxzZSxcbiAgICAgICAgICBjb250ZXh0OiB7XG4gICAgICAgICAgICBkZXNjcmlwdGlvbjogXCJJbnN1ZmZpY2llbnQgU2NvcGVcIixcbiAgICAgICAgICAgIGNsaWVudElkOiBlcnJvcj8udG9rZW4gPyBlcnJvci50b2tlbltcInN1YlwiXSA6IFwiXCIsXG4gICAgICAgICAgfSxcbiAgICAgICAgfTtcbiAgICAgICAgcmV0dXJuIHRoaXMuY29udGV4dC5zdWNjZWVkKGluc3VmZmljaWVudFNjb3BlKTtcbiAgICAgIH1cblxuICAgICAgY29uc3QgZmFpbHVyZSA9IHtcbiAgICAgICAgaXNBdXRob3JpemVkOiBmYWxzZSxcbiAgICAgICAgY29udGV4dDoge1xuICAgICAgICAgIGNsaWVudElkOiBlcnJvcj8udG9rZW4gPyBlcnJvci50b2tlbltcInN1YlwiXSA6IFwiXCIsXG4gICAgICAgIH0sXG4gICAgICB9O1xuICAgICAgcmV0dXJuIHRoaXMuY29udGV4dC5zdWNjZWVkKGZhaWx1cmUpO1xuICAgIH1cbiAgfVxuXG4gIHZhbGlkYXRlVG9rZW4odmFsaWRhdGlvbk9wdHM6IGFueSk6IFByb21pc2U8UmVjb3JkPHN0cmluZywgYW55Pj4ge1xuICAgIGNvbnN0IGNsaWVudCA9IHRoaXMuZ2V0UGVyc29uYUNsaWVudCgpO1xuICAgIHJldHVybiBuZXcgUHJvbWlzZShmdW5jdGlvbiAocmVzb2x2ZSwgcmVqZWN0KSB7XG4gICAgICBjbGllbnQudmFsaWRhdGVUb2tlbihcbiAgICAgICAgdmFsaWRhdGlvbk9wdHMsXG4gICAgICAgIChlcnJvcjogYW55LCBvazogYW55LCBkZWNvZGVkVG9rZW46IGFueSkgPT4ge1xuICAgICAgICAgIGlmIChlcnJvcikge1xuICAgICAgICAgICAgcmVqZWN0KHtcbiAgICAgICAgICAgICAgZXJyb3I6IGVycm9yLFxuICAgICAgICAgICAgICB0b2tlbjogZGVjb2RlZFRva2VuLFxuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgfVxuICAgICAgICAgIHJlc29sdmUoZGVjb2RlZFRva2VuKTtcbiAgICAgICAgfVxuICAgICAgKTtcbiAgICB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBCcmVhayBkb3duIGFuIEFQSSBnYXRld2F5IG1ldGhvZCBBUk4gaW50byBpdCdzIGNvbnN0aXR1ZW50IHBhcnRzLlxuICAgKiBNZXRob2QgQVJOcyB0YWtlIHRoZSBmb2xsb3dpbmcgZm9ybWF0OlxuICAgKlxuICAgKiAgIGFybjphd3M6ZXhlY3V0ZS1hcGk6PFJlZ2lvbiBpZD46PEFjY291bnQgaWQ+OjxBUEkgaWQ+LzxTdGFnZT4vPE1ldGhvZD4vPFJlc291cmNlIHBhdGg+XG4gICAqXG4gICAqIGUuZzpcbiAgICpcbiAgICogICBhcm46YXdzOmV4ZWN1dGUtYXBpOmV1LXdlc3QtMToxMjM6YWJjL2RldmVsb3BtZW50L0dFVC8yL3dvcmtzXG4gICAqXG4gICAqIEBwYXJhbSBtZXRob2RBcm4ge3N0cmluZ30gVGhlIG1ldGhvZCBBUk4gcHJvdmlkZWQgYnkgdGhlIGV2ZW50IGhhbmRlZCB0byBhIExhbWJkYSBmdW5jdGlvblxuICAgKiBAcmV0dXJucyB7e1xuICAgKiAgIG1ldGhvZDogc3RyaW5nLFxuICAgKiAgIHJlc291cmNlUGF0aDogc3RyaW5nLFxuICAgKiAgIGFwaU9wdGlvbnM6IHtcbiAgICogICAgIHJlZ2lvbjogc3RyaW5nLFxuICAgKiAgICAgcmVzdEFwaUlkOiBzdHJpbmcsXG4gICAqICAgICBzdGFnZTogc3RyaW5nXG4gICAqICAgfSxcbiAgICogICBhd3NBY2NvdW50SWQ6IHN0cmluZ1xuICAgKiAgIH19XG4gICAqL1xuICBwYXJzZU1ldGhvZEFybihtZXRob2RBcm46IHN0cmluZyk6IFBhcnNlZEFybiB7XG4gICAgY29uc3QgbWV0aG9kQXJuUGFydHMgPSBtZXRob2RBcm4uc3BsaXQoXCI6XCIpO1xuICAgIGNvbnNvbGUubG9nKGBNZXRob2QgQVJOIFBhcnRzOiAke0pTT04uc3RyaW5naWZ5KG1ldGhvZEFyblBhcnRzKX1gKTtcbiAgICBsZXQgYXBpR2F0ZXdheUFybiA9IG1ldGhvZEFyblBhcnRzW0FQSV9HQVRFV0FZX0FSTl9JTkRFWF07XG4gICAgLy8gSWYgdGhlIHNwbGl0IGNyZWF0ZWQgbW9yZSB0aGFuIHRoZSBleHBlY3RlZCBudW1iZXIgb2YgcGFydHMsIHRoZW4gdGhlXG4gICAgLy8gYXBpR2F0ZXdheUFybiBtdXN0IGhhdmUgaGFkIG9uZSBvciBtb3JlIDoncyBpbiBpdC4gUmVjcmVhdGUgdGhlIGFwaUdhdGV3YXkgYXJuLlxuICAgIGZvciAoXG4gICAgICBsZXQgaW5kZXggPSBNRVRIT0RfQVJOX0lOREVYRVMubGVuZ3RoO1xuICAgICAgaW5kZXggPCBtZXRob2RBcm5QYXJ0cy5sZW5ndGg7XG4gICAgICBpbmRleCArPSAxXG4gICAgKSB7XG4gICAgICBhcGlHYXRld2F5QXJuICs9IGA6JHttZXRob2RBcm5QYXJ0c1tpbmRleF19YDtcbiAgICB9XG5cbiAgICBjb25zdCBhcGlHYXRld2F5QXJuUGFydHMgPSBhcGlHYXRld2F5QXJuLnNwbGl0KFwiL1wiKTtcbiAgICBjb25zb2xlLmxvZyhgYXBpIGdhdGV3YXkgYXJuIHBhcnRzOiAke0pTT04uc3RyaW5naWZ5KGFwaUdhdGV3YXlBcm5QYXJ0cyl9YCk7XG5cbiAgICAvLyBJZiB0aGUgc3BsaXQgY3JlYXRlZCBtb3JlIHRoYW4gdGhlIGV4cGVjdGVkIG51bWJlciBvZiBwYXJ0cywgdGhlbiB0aGVcbiAgICAvLyByZXNvdXJjZSBwYXRoIG11c3QgaGF2ZSBoYWQgb25lIG9yIG1vcmUgLydzIGluIGl0LiBSZWNyZWF0ZSB0aGUgcmVzb3VyY2UgcGF0aC5cbiAgICBsZXQgcmVzb3VyY2VQYXRoID0gXCJcIjtcbiAgICBmb3IgKFxuICAgICAgbGV0IGkgPSBBUElfR0FURVdBWV9BUk5fSU5ERVhFUy5sZW5ndGggLSAxO1xuICAgICAgaSA8IGFwaUdhdGV3YXlBcm5QYXJ0cy5sZW5ndGg7XG4gICAgICBpICs9IDFcbiAgICApIHtcbiAgICAgIHJlc291cmNlUGF0aCArPSBgLyR7YXBpR2F0ZXdheUFyblBhcnRzW2ldfWA7XG4gICAgfVxuICAgIGNvbnNvbGUubG9nKGByZXNvdXJjZSBwYXRoOiAke0pTT04uc3RyaW5naWZ5KHJlc291cmNlUGF0aCl9YCk7XG4gICAgcmV0dXJuIHtcbiAgICAgIG1ldGhvZDogYXBpR2F0ZXdheUFyblBhcnRzW01FVEhPRF9JTkRFWF0sXG4gICAgICByZXNvdXJjZVBhdGgsXG4gICAgICBhcGlPcHRpb25zOiB7XG4gICAgICAgIHJlZ2lvbjogbWV0aG9kQXJuUGFydHNbUkVHSU9OX0lOREVYXSxcbiAgICAgICAgcmVzdEFwaUlkOiBhcGlHYXRld2F5QXJuUGFydHNbQVBJX0lEX0lOREVYXSxcbiAgICAgICAgc3RhZ2U6IGFwaUdhdGV3YXlBcm5QYXJ0c1tTVEFHRV9JTkRFWF0sXG4gICAgICB9LFxuICAgICAgYXdzQWNjb3VudElkOiBtZXRob2RBcm5QYXJ0c1tBQ0NPVU5UX0lEX0lOREVYXSxcbiAgICB9O1xuICB9XG5cbiAgZ2V0U2NvcGUocGFyc2VkTWV0aG9kQXJuOiBQYXJzZWRBcm4pIHtcbiAgICBjb25zdCBzY29wZUNvbmZpZyA9IHByb2Nlc3MuZW52W1wiU0NPUEVfQ09ORklHXCJdO1xuICAgIGlmIChzY29wZUNvbmZpZyAhPSB1bmRlZmluZWQpIHtcbiAgICAgIGNvbnN0IGNvbmYgPSBKU09OLnBhcnNlKHNjb3BlQ29uZmlnKTtcbiAgICAgIGZvciAoY29uc3QgcGF0aFJlZ0V4IG9mIE9iamVjdC5rZXlzKGNvbmYpKSB7XG4gICAgICAgIGlmIChwYXJzZWRNZXRob2RBcm4ucmVzb3VyY2VQYXRoLm1hdGNoKHBhdGhSZWdFeCkpIHtcbiAgICAgICAgICByZXR1cm4gY29uZltwYXRoUmVnRXhdO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgZ2V0UGVyc29uYUNsaWVudCgpIHtcbiAgICBpZiAodGhpcy5wZXJzb25hQ2xpZW50ID09IG51bGwpIHtcbiAgICAgIGNvbnN0IHBlcnNvbmFDb25maWcgPSB7XG4gICAgICAgIHBlcnNvbmFfaG9zdDogcHJvY2Vzcy5lbnZbXCJQRVJTT05BX0hPU1RcIl0sXG4gICAgICAgIHBlcnNvbmFfc2NoZW1lOiBwcm9jZXNzLmVudltcIlBFUlNPTkFfU0NIRU1FXCJdLFxuICAgICAgICBwZXJzb25hX3BvcnQ6IHByb2Nlc3MuZW52W1wiUEVSU09OQV9QT1JUXCJdLFxuICAgICAgICBwZXJzb25hX29hdXRoX3JvdXRlOiBwcm9jZXNzLmVudltcIlBFUlNPTkFfT0FVVEhfUk9VVEVcIl0sXG4gICAgICB9O1xuXG4gICAgICB0aGlzLnBlcnNvbmFDbGllbnQgPSBwZXJzb25hLmNyZWF0ZUNsaWVudChcbiAgICAgICAgYCR7cHJvY2Vzcy5lbnZbXCJQRVJTT05BX0NMSUVOVF9OQU1FXCJdfSAobGFtYmRhOyBOT0RFX0VOVj0ke3Byb2Nlc3MuZW52W1wiTk9ERV9FTlZcIl19KWAsXG4gICAgICAgIF8ubWVyZ2UocGVyc29uYUNvbmZpZywge30pXG4gICAgICApO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzLnBlcnNvbmFDbGllbnQ7XG4gIH1cbn1cblxubW9kdWxlLmV4cG9ydHMudmFsaWRhdGVUb2tlbiA9IGFzeW5jIChldmVudDogYW55LCBjb250ZXh0OiBhbnkpID0+IHtcbiAgY29uc3Qgcm91dGUgPSBuZXcgUGVyc29uYUF1dGhvcml6ZXIoZXZlbnQsIGNvbnRleHQpO1xuICByZXR1cm4gYXdhaXQgcm91dGUuaGFuZGxlKCk7XG59O1xuIl19 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aG9yaXplci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImF1dGhvcml6ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsNEJBQTRCO0FBQzVCLDJDQUFvRDtBQWFwRCxvQ0FBb0M7QUFDcEMsRUFBRTtBQUNGLHFCQUFxQjtBQUNyQiw2RkFBNkY7QUFDN0Ysb0VBQW9FO0FBQ3BFLDhGQUE4RjtBQUM5RixFQUFFO0FBQ0YsRUFBRTtBQUNGLE1BQU0sU0FBUyxHQUFHLENBQUMsQ0FBQztBQUNwQixNQUFNLFNBQVMsR0FBRyxDQUFDLENBQUM7QUFDcEIsTUFBTSxhQUFhLEdBQUcsQ0FBQyxDQUFDO0FBQ3hCLE1BQU0sWUFBWSxHQUFHLENBQUMsQ0FBQztBQUN2QixNQUFNLGdCQUFnQixHQUFHLENBQUMsQ0FBQztBQUMzQixNQUFNLHFCQUFxQixHQUFHLENBQUMsQ0FBQztBQUVoQyxNQUFNLGtCQUFrQixHQUFHO0lBQ3pCLFNBQVM7SUFDVCxTQUFTO0lBQ1QsYUFBYTtJQUNiLFlBQVk7SUFDWixnQkFBZ0I7SUFDaEIscUJBQXFCO0NBQ3RCLENBQUM7QUFFRixNQUFNLFlBQVksR0FBRyxDQUFDLENBQUM7QUFDdkIsTUFBTSxXQUFXLEdBQUcsQ0FBQyxDQUFDO0FBQ3RCLE1BQU0sWUFBWSxHQUFHLENBQUMsQ0FBQztBQUN2QixNQUFNLG1CQUFtQixHQUFHLENBQUMsQ0FBQztBQUU5QixNQUFNLHVCQUF1QixHQUFHO0lBQzlCLFlBQVk7SUFDWixXQUFXO0lBQ1gsWUFBWTtJQUNaLG1CQUFtQjtDQUNwQixDQUFDO0FBRUYsTUFBYSxpQkFBaUI7SUFLNUIsWUFBWSxLQUFVLEVBQUUsT0FBWTtRQUNsQyxJQUFJLENBQUMsS0FBSyxHQUFHLEtBQUssQ0FBQztRQUNuQixJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQztRQUV2QixJQUFJLENBQUMsYUFBYSxHQUFHLFNBQVMsQ0FBQztJQUNqQyxDQUFDO0lBRUQsS0FBSyxDQUFDLE1BQU07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QUFNLG1CQUFtQixHQUFHLGNBQWMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDdEQsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUVsQyxJQUFJLG1CQUFtQixDQUFDLE1BQU0sS0FBSyxTQUFTLENBQUMsTUFBTSxFQUFFO1lBQ25ELE9BQU8sS0FBSyxDQUFDO1NBQ2Q7UUFFRCxLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsbUJBQW1CLENBQUMsTUFBTSxFQUFFLENBQUMsRUFBRSxFQUFFO1lBQ25ELE1BQU0scUJBQXFCLEdBQUcsbUJBQW1CLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDckQsTUFBTSxXQUFXLEdBQUcsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBRWpDLElBQ0UscUJBQXFCLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQztnQkFDckMscUJBQXFCLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxFQUNuQztnQkFDQSx3QkFBd0I7YUFDekI7aUJBQU07Z0JBQ0wsd0JBQXdCO2dCQUN4QixJQUFJLHFCQUFxQixLQUFLLFdBQVcsRUFBRTtvQkFDekMsT0FBTyxLQUFLLENBQUM7aUJBQ2Q7YUFDRjtTQUNGO1FBRUQsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0NBQ0Y7QUE3TkQsOENBNk5DO0FBRUQsTUFBTSxDQUFDLE9BQU8sQ0FBQyxhQUFhLEdBQUcsS0FBSyxFQUFFLEtBQVUsRUFBRSxPQUFZLEVBQUUsRUFBRTtJQUNoRSxNQUFNLEtBQUssR0FBRyxJQUFJLGlCQUFpQixDQUFDLEtBQUssRUFBRSxPQUFPLENBQUMsQ0FBQztJQUNwRCxPQUFPLE1BQU0sS0FBSyxDQUFDLE1BQU0sRUFBRSxDQUFDO0FBQzlCLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIF8gZnJvbSBcImxvZGFzaFwiO1xuaW1wb3J0IHsgcGVyc29uYSwgUGVyc29uYUNsaWVudCB9IGZyb20gXCJ0YWxpcy1ub2RlXCI7XG5cbnR5cGUgUGFyc2VkQXJuID0ge1xuICBtZXRob2Q6IHN0cmluZztcbiAgcmVzb3VyY2VQYXRoOiBzdHJpbmc7XG4gIGFwaU9wdGlvbnM6IHtcbiAgICByZWdpb246IHN0cmluZztcbiAgICByZXN0QXBpSWQ6IHN0cmluZztcbiAgICBzdGFnZTogc3RyaW5nO1xuICB9O1xuICBhd3NBY2NvdW50SWQ6IHN0cmluZztcbn07XG5cbi8vIENvbnN0YW50cyB1c2VkIGJ5IHBhcnNlTWV0aG9kQXJuOlxuLy9cbi8vIEV4YW1wbGUgTWV0aG9kQVJOOlxuLy8gICBcImFybjphd3M6ZXhlY3V0ZS1hcGk6PFJlZ2lvbiBpZD46PEFjY291bnQgaWQ+OjxBUEkgaWQ+LzxTdGFnZT4vPE1ldGhvZD4vPFJlc291cmNlIHBhdGg+XCJcbi8vIE1ldGhvZCBBUk4gSW5kZXg6ICAwICAgMSAgIDIgICAgICAgICAgIDMgICAgICAgICAgIDQgICAgICAgICAgICA1XG4vLyBBUEkgR2F0ZXdheSBBUk4gSW5kZXg6ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMCAgICAgICAgMSAgICAgICAyICAgICAgICAzXG4vL1xuLy9cbmNvbnN0IEFSTl9JTkRFWCA9IDA7XG5jb25zdCBBV1NfSU5ERVggPSAxO1xuY29uc3QgRVhFQ1VURV9JTkRFWCA9IDI7XG5jb25zdCBSRUdJT05fSU5ERVggPSAzO1xuY29uc3QgQUNDT1VOVF9JRF9JTkRFWCA9IDQ7XG5jb25zdCBBUElfR0FURVdBWV9BUk5fSU5ERVggPSA1O1xuXG5jb25zdCBNRVRIT0RfQVJOX0lOREVYRVMgPSBbXG4gIEFSTl9JTkRFWCxcbiAgQVdTX0lOREVYLFxuICBFWEVDVVRFX0lOREVYLFxuICBSRUdJT05fSU5ERVgsXG4gIEFDQ09VTlRfSURfSU5ERVgsXG4gIEFQSV9HQVRFV0FZX0FSTl9JTkRFWCxcbl07XG5cbmNvbnN0IEFQSV9JRF9JTkRFWCA9IDA7XG5jb25zdCBTVEFHRV9JTkRFWCA9IDE7XG5jb25zdCBNRVRIT0RfSU5ERVggPSAyO1xuY29uc3QgUkVTT1VSQ0VfUEFUSF9JTkRFWCA9IDM7XG5cbmNvbnN0IEFQSV9HQVRFV0FZX0FSTl9JTkRFWEVTID0gW1xuICBBUElfSURfSU5ERVgsXG4gIFNUQUdFX0lOREVYLFxuICBNRVRIT0RfSU5ERVgsXG4gIFJFU09VUkNFX1BBVEhfSU5ERVgsXG5dO1xuXG5leHBvcnQgY2xhc3MgUGVyc29uYUF1dGhvcml6ZXIge1xuICBldmVudDogYW55O1xuICBjb250ZXh0OiBhbnk7XG4gIHBlcnNvbmFDbGllbnQ6IFBlcnNvbmFDbGllbnQgfCB1bmRlZmluZWQ7XG5cbiAgY29uc3RydWN0b3IoZXZlbnQ6IGFueSwgY29udGV4dDogYW55KSB7XG4gICAgdGhpcy5ldmVudCA9IGV2ZW50O1xuICAgIHRoaXMuY29udGV4dCA9IGNvbnRleHQ7XG5cbiAgICB0aGlzLnBlcnNvbmFDbGllbnQgPSB1bmRlZmluZWQ7XG4gIH1cblxuICBhc3luYyBoYW5kbGUoKSB7XG4gICAgY29uc29sZS5sb2coXCJSZWNlaXZlZCBldmVudFwiLCB0aGlzLmV2ZW50KTtcblxuICAgIGlmICghdGhpcy5ldmVudD8uaGVhZGVycyB8fCB0aGlzLmV2ZW50LmhlYWRlcnNbXCJhdXRob3JpemF0aW9uXCJdID09IG51bGwpIHtcbiAgICAgIGNvbnNvbGUubG9nKFwiTWlzc2luZyBhdXRoIHRva2VuXCIpO1xuICAgICAgcmV0dXJuIHRoaXMuY29udGV4dC5mYWlsKFwiVW5hdXRob3JpemVkXCIpO1xuICAgIH1cblxuICAgIGNvbnN0IHBhcnNlZE1ldGhvZEFybiA9IHRoaXMucGFyc2VNZXRob2RBcm4odGhpcy5ldmVudC5yb3V0ZUFybik7XG4gICAgY29uc29sZS5sb2coYFBhcnNlZCBNZXRob2QgQXJuOiAke0pTT04uc3RyaW5naWZ5KHBhcnNlZE1ldGhvZEFybil9YCk7XG5cbiAgICBjb25zdCBzY29wZSA9IHRoaXMuZ2V0U2NvcGUocGFyc2VkTWV0aG9kQXJuKTtcbiAgICBjb25zb2xlLmxvZyhgTWV0aG9kIGhhcyBzY29wZTogJHtzY29wZX1gKTtcblxuICAgIGxldCB2YWxpZGF0aW9uT3B0cyA9IHtcbiAgICAgIHRva2VuOiBfLnJlcGxhY2UoXG4gICAgICAgIHRoaXMuZXZlbnQuaGVhZGVyc1tcImF1dGhvcml6YXRpb25cIl0sXG4gICAgICAgIFwiQmVhcmVyXCIsXG4gICAgICAgIFwiXCJcbiAgICAgICkudHJpbSgpLFxuICAgIH07XG4gICAgaWYgKHNjb3BlICE9IG51bGwpIHtcbiAgICAgIHZhbGlkYXRpb25PcHRzID0gXy5tZXJnZSh2YWxpZGF0aW9uT3B0cywgeyBzY29wZSB9KTtcbiAgICB9XG4gICAgY29uc29sZS5sb2coYFZhbGlkYXRpb24gb3BzOiAke0pTT04uc3RyaW5naWZ5KHZhbGlkYXRpb25PcHRzKX1gKTtcblxuICAgIGNvbnNvbGUubG9nKFxuICAgICAgXCJ2YWxpZGF0aW5nIHRva2VuIGFnYWluc3QgcmVxdWVzdFwiLFxuICAgICAgYCR7cGFyc2VkTWV0aG9kQXJuLnJlc291cmNlUGF0aH1gXG4gICAgKTtcblxuICAgIGlmICghdmFsaWRhdGlvbk9wdHMudG9rZW4gfHwgdmFsaWRhdGlvbk9wdHMudG9rZW4ubGVuZ3RoID09PSAwKSB7XG4gICAgICBjb25zb2xlLmxvZyhcInRva2VuIG1pc3NpbmdcIik7XG4gICAgICByZXR1cm4gdGhpcy5jb250ZXh0LmZhaWwoXCJVbmF1dGhvcml6ZWRcIik7XG4gICAgfVxuXG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHRva2VuID0gYXdhaXQgdGhpcy52YWxpZGF0ZVRva2VuKHZhbGlkYXRpb25PcHRzKTtcbiAgICAgIGNvbnN0IHN1Y2Nlc3MgPSB7XG4gICAgICAgIGlzQXV0aG9yaXplZDogdHJ1ZSxcbiAgICAgICAgY29udGV4dDoge1xuICAgICAgICAgIGNsaWVudElkOiB0b2tlbltcInN1YlwiXSxcbiAgICAgICAgfSxcbiAgICAgIH07XG4gICAgICByZXR1cm4gdGhpcy5jb250ZXh0LnN1Y2NlZWQoc3VjY2Vzcyk7XG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICBjb25zb2xlLmxvZyhcInRva2VuIHZhbGlkYXRpb24gZmFpbGVkXCIsIGVycik7XG5cbiAgICAgIGNvbnN0IGVycm9yID0gZXJyIGFzIHsgZXJyb3I6IGFueTsgdG9rZW46IFJlY29yZDxzdHJpbmcsIGFueT4gfTtcblxuICAgICAgaWYgKGVycm9yLmVycm9yID09PSBwZXJzb25hLmVycm9yVHlwZXMuSU5TVUZGSUNJRU5UX1NDT1BFKSB7XG4gICAgICAgIGNvbnN0IGluc3VmZmljaWVudFNjb3BlID0ge1xuICAgICAgICAgIGlzQXV0aG9yaXplZDogZmFsc2UsXG4gICAgICAgICAgY29udGV4dDoge1xuICAgICAgICAgICAgZGVzY3JpcHRpb246IFwiSW5zdWZmaWNpZW50IFNjb3BlXCIsXG4gICAgICAgICAgICBjbGllbnRJZDogZXJyb3I/LnRva2VuID8gZXJyb3IudG9rZW5bXCJzdWJcIl0gOiBcIlwiLFxuICAgICAgICAgIH0sXG4gICAgICAgIH07XG4gICAgICAgIHJldHVybiB0aGlzLmNvbnRleHQuc3VjY2VlZChpbnN1ZmZpY2llbnRTY29wZSk7XG4gICAgICB9XG5cbiAgICAgIGNvbnN0IGZhaWx1cmUgPSB7XG4gICAgICAgIGlzQXV0aG9yaXplZDogZmFsc2UsXG4gICAgICAgIGNvbnRleHQ6IHtcbiAgICAgICAgICBjbGllbnRJZDogZXJyb3I/LnRva2VuID8gZXJyb3IudG9rZW5bXCJzdWJcIl0gOiBcIlwiLFxuICAgICAgICB9LFxuICAgICAgfTtcbiAgICAgIHJldHVybiB0aGlzLmNvbnRleHQuc3VjY2VlZChmYWlsdXJlKTtcbiAgICB9XG4gIH1cblxuICB2YWxpZGF0ZVRva2VuKHZhbGlkYXRpb25PcHRzOiBhbnkpOiBQcm9taXNlPFJlY29yZDxzdHJpbmcsIGFueT4+IHtcbiAgICBjb25zdCBjbGllbnQgPSB0aGlzLmdldFBlcnNvbmFDbGllbnQoKTtcbiAgICByZXR1cm4gbmV3IFByb21pc2UoZnVuY3Rpb24gKHJlc29sdmUsIHJlamVjdCkge1xuICAgICAgY2xpZW50LnZhbGlkYXRlVG9rZW4oXG4gICAgICAgIHZhbGlkYXRpb25PcHRzLFxuICAgICAgICAoZXJyb3I6IGFueSwgb2s6IGFueSwgZGVjb2RlZFRva2VuOiBhbnkpID0+IHtcbiAgICAgICAgICBpZiAoZXJyb3IpIHtcbiAgICAgICAgICAgIHJlamVjdCh7XG4gICAgICAgICAgICAgIGVycm9yOiBlcnJvcixcbiAgICAgICAgICAgICAgdG9rZW46IGRlY29kZWRUb2tlbixcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgIH1cbiAgICAgICAgICByZXNvbHZlKGRlY29kZWRUb2tlbik7XG4gICAgICAgIH1cbiAgICAgICk7XG4gICAgfSk7XG4gIH1cblxuICAvKipcbiAgICogQnJlYWsgZG93biBhbiBBUEkgZ2F0ZXdheSBtZXRob2QgQVJOIGludG8gaXQncyBjb25zdGl0dWVudCBwYXJ0cy5cbiAgICogTWV0aG9kIEFSTnMgdGFrZSB0aGUgZm9sbG93aW5nIGZvcm1hdDpcbiAgICpcbiAgICogICBhcm46YXdzOmV4ZWN1dGUtYXBpOjxSZWdpb24gaWQ+OjxBY2NvdW50IGlkPjo8QVBJIGlkPi88U3RhZ2U+LzxNZXRob2Q+LzxSZXNvdXJjZSBwYXRoPlxuICAgKlxuICAgKiBlLmc6XG4gICAqXG4gICAqICAgYXJuOmF3czpleGVjdXRlLWFwaTpldS13ZXN0LTE6MTIzOmFiYy9kZXZlbG9wbWVudC9HRVQvMi93b3Jrc1xuICAgKlxuICAgKiBAcGFyYW0gbWV0aG9kQXJuIHtzdHJpbmd9IFRoZSBtZXRob2QgQVJOIHByb3ZpZGVkIGJ5IHRoZSBldmVudCBoYW5kZWQgdG8gYSBMYW1iZGEgZnVuY3Rpb25cbiAgICogQHJldHVybnMge3tcbiAgICogICBtZXRob2Q6IHN0cmluZyxcbiAgICogICByZXNvdXJjZVBhdGg6IHN0cmluZyxcbiAgICogICBhcGlPcHRpb25zOiB7XG4gICAqICAgICByZWdpb246IHN0cmluZyxcbiAgICogICAgIHJlc3RBcGlJZDogc3RyaW5nLFxuICAgKiAgICAgc3RhZ2U6IHN0cmluZ1xuICAgKiAgIH0sXG4gICAqICAgYXdzQWNjb3VudElkOiBzdHJpbmdcbiAgICogICB9fVxuICAgKi9cbiAgcGFyc2VNZXRob2RBcm4obWV0aG9kQXJuOiBzdHJpbmcpOiBQYXJzZWRBcm4ge1xuICAgIGNvbnN0IG1ldGhvZEFyblBhcnRzID0gbWV0aG9kQXJuLnNwbGl0KFwiOlwiKTtcbiAgICBjb25zb2xlLmxvZyhgTWV0aG9kIEFSTiBQYXJ0czogJHtKU09OLnN0cmluZ2lmeShtZXRob2RBcm5QYXJ0cyl9YCk7XG4gICAgbGV0IGFwaUdhdGV3YXlBcm4gPSBtZXRob2RBcm5QYXJ0c1tBUElfR0FURVdBWV9BUk5fSU5ERVhdO1xuICAgIC8vIElmIHRoZSBzcGxpdCBjcmVhdGVkIG1vcmUgdGhhbiB0aGUgZXhwZWN0ZWQgbnVtYmVyIG9mIHBhcnRzLCB0aGVuIHRoZVxuICAgIC8vIGFwaUdhdGV3YXlBcm4gbXVzdCBoYXZlIGhhZCBvbmUgb3IgbW9yZSA6J3MgaW4gaXQuIFJlY3JlYXRlIHRoZSBhcGlHYXRld2F5IGFybi5cbiAgICBmb3IgKFxuICAgICAgbGV0IGluZGV4ID0gTUVUSE9EX0FSTl9JTkRFWEVTLmxlbmd0aDtcbiAgICAgIGluZGV4IDwgbWV0aG9kQXJuUGFydHMubGVuZ3RoO1xuICAgICAgaW5kZXggKz0gMVxuICAgICkge1xuICAgICAgYXBpR2F0ZXdheUFybiArPSBgOiR7bWV0aG9kQXJuUGFydHNbaW5kZXhdfWA7XG4gICAgfVxuXG4gICAgY29uc3QgYXBpR2F0ZXdheUFyblBhcnRzID0gYXBpR2F0ZXdheUFybi5zcGxpdChcIi9cIik7XG4gICAgY29uc29sZS5sb2coYGFwaSBnYXRld2F5IGFybiBwYXJ0czogJHtKU09OLnN0cmluZ2lmeShhcGlHYXRld2F5QXJuUGFydHMpfWApO1xuXG4gICAgLy8gSWYgdGhlIHNwbGl0IGNyZWF0ZWQgbW9yZSB0aGFuIHRoZSBleHBlY3RlZCBudW1iZXIgb2YgcGFydHMsIHRoZW4gdGhlXG4gICAgLy8gcmVzb3VyY2UgcGF0aCBtdXN0IGhhdmUgaGFkIG9uZSBvciBtb3JlIC8ncyBpbiBpdC4gUmVjcmVhdGUgdGhlIHJlc291cmNlIHBhdGguXG4gICAgbGV0IHJlc291cmNlUGF0aCA9IFwiXCI7XG4gICAgZm9yIChcbiAgICAgIGxldCBpID0gQVBJX0dBVEVXQVlfQVJOX0lOREVYRVMubGVuZ3RoIC0gMTtcbiAgICAgIGkgPCBhcGlHYXRld2F5QXJuUGFydHMubGVuZ3RoO1xuICAgICAgaSArPSAxXG4gICAgKSB7XG4gICAgICByZXNvdXJjZVBhdGggKz0gYC8ke2FwaUdhdGV3YXlBcm5QYXJ0c1tpXX1gO1xuICAgIH1cbiAgICBjb25zb2xlLmxvZyhgcmVzb3VyY2UgcGF0aDogJHtKU09OLnN0cmluZ2lmeShyZXNvdXJjZVBhdGgpfWApO1xuICAgIHJldHVybiB7XG4gICAgICBtZXRob2Q6IGFwaUdhdGV3YXlBcm5QYXJ0c1tNRVRIT0RfSU5ERVhdLFxuICAgICAgcmVzb3VyY2VQYXRoLFxuICAgICAgYXBpT3B0aW9uczoge1xuICAgICAgICByZWdpb246IG1ldGhvZEFyblBhcnRzW1JFR0lPTl9JTkRFWF0sXG4gICAgICAgIHJlc3RBcGlJZDogYXBpR2F0ZXdheUFyblBhcnRzW0FQSV9JRF9JTkRFWF0sXG4gICAgICAgIHN0YWdlOiBhcGlHYXRld2F5QXJuUGFydHNbU1RBR0VfSU5ERVhdLFxuICAgICAgfSxcbiAgICAgIGF3c0FjY291bnRJZDogbWV0aG9kQXJuUGFydHNbQUNDT1VOVF9JRF9JTkRFWF0sXG4gICAgfTtcbiAgfVxuXG4gIGdldFNjb3BlKHBhcnNlZE1ldGhvZEFybjogUGFyc2VkQXJuKSB7XG4gICAgY29uc3Qgc2NvcGVDb25maWcgPSBwcm9jZXNzLmVudltcIlNDT1BFX0NPTkZJR1wiXTtcbiAgICBpZiAoc2NvcGVDb25maWcgIT0gdW5kZWZpbmVkKSB7XG4gICAgICBjb25zdCBjb25mID0gSlNPTi5wYXJzZShzY29wZUNvbmZpZyk7XG4gICAgICBmb3IgKGNvbnN0IHBhdGggb2YgT2JqZWN0LmtleXMoY29uZikpIHtcbiAgICAgICAgaWYgKHRoaXMucGF0aE1hdGNoKHBhdGgsIHBhcnNlZE1ldGhvZEFybi5yZXNvdXJjZVBhdGgpKSB7XG4gICAgICAgICAgcmV0dXJuIGNvbmZbcGF0aF07XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICBnZXRQZXJzb25hQ2xpZW50KCkge1xuICAgIGlmICh0aGlzLnBlcnNvbmFDbGllbnQgPT0gbnVsbCkge1xuICAgICAgY29uc3QgcGVyc29uYUNvbmZpZyA9IHtcbiAgICAgICAgcGVyc29uYV9ob3N0OiBwcm9jZXNzLmVudltcIlBFUlNPTkFfSE9TVFwiXSxcbiAgICAgICAgcGVyc29uYV9zY2hlbWU6IHByb2Nlc3MuZW52W1wiUEVSU09OQV9TQ0hFTUVcIl0sXG4gICAgICAgIHBlcnNvbmFfcG9ydDogcHJvY2Vzcy5lbnZbXCJQRVJTT05BX1BPUlRcIl0sXG4gICAgICAgIHBlcnNvbmFfb2F1dGhfcm91dGU6IHByb2Nlc3MuZW52W1wiUEVSU09OQV9PQVVUSF9ST1VURVwiXSxcbiAgICAgIH07XG5cbiAgICAgIHRoaXMucGVyc29uYUNsaWVudCA9IHBlcnNvbmEuY3JlYXRlQ2xpZW50KFxuICAgICAgICBgJHtwcm9jZXNzLmVudltcIlBFUlNPTkFfQ0xJRU5UX05BTUVcIl19IChsYW1iZGE7IE5PREVfRU5WPSR7cHJvY2Vzcy5lbnZbXCJOT0RFX0VOVlwiXX0pYCxcbiAgICAgICAgXy5tZXJnZShwZXJzb25hQ29uZmlnLCB7fSlcbiAgICAgICk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXMucGVyc29uYUNsaWVudDtcbiAgfVxuXG4gIHBhdGhNYXRjaChwYXRoRGVmaW5pdGlvbjogc3RyaW5nLCBwYXRoOiBzdHJpbmcpOiBib29sZWFuIHtcbiAgICBjb25zdCBwYXRoRGVmaW5pdGlvblBhcnRzID0gcGF0aERlZmluaXRpb24uc3BsaXQoXCIvXCIpO1xuICAgIGNvbnN0IHBhdGhQYXJ0cyA9IHBhdGguc3BsaXQoXCIvXCIpO1xuXG4gICAgaWYgKHBhdGhEZWZpbml0aW9uUGFydHMubGVuZ3RoICE9PSBwYXRoUGFydHMubGVuZ3RoKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBwYXRoRGVmaW5pdGlvblBhcnRzLmxlbmd0aDsgaSsrKSB7XG4gICAgICBjb25zdCBwYXRoRGVmaW5pdGlvblNlZ21lbnQgPSBwYXRoRGVmaW5pdGlvblBhcnRzW2ldO1xuICAgICAgY29uc3QgcGF0aFNlZ21lbnQgPSBwYXRoUGFydHNbaV07XG5cbiAgICAgIGlmIChcbiAgICAgICAgcGF0aERlZmluaXRpb25TZWdtZW50LnN0YXJ0c1dpdGgoXCJ7XCIpICYmXG4gICAgICAgIHBhdGhEZWZpbml0aW9uU2VnbWVudC5lbmRzV2l0aChcIn1cIilcbiAgICAgICkge1xuICAgICAgICAvLyBNYXRjaGVzIHBhdGggYXJndW1lbnRcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIC8vIFNob3VsZCBtYXRjaCBkaXJlY3RseVxuICAgICAgICBpZiAocGF0aERlZmluaXRpb25TZWdtZW50ICE9PSBwYXRoU2VnbWVudCkge1xuICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiB0cnVlO1xuICB9XG59XG5cbm1vZHVsZS5leHBvcnRzLnZhbGlkYXRlVG9rZW4gPSBhc3luYyAoZXZlbnQ6IGFueSwgY29udGV4dDogYW55KSA9PiB7XG4gIGNvbnN0IHJvdXRlID0gbmV3IFBlcnNvbmFBdXRob3JpemVyKGV2ZW50LCBjb250ZXh0KTtcbiAgcmV0dXJuIGF3YWl0IHJvdXRlLmhhbmRsZSgpO1xufTtcbiJdfQ== \ No newline at end of file diff --git a/src/lambda/api/authorizer.ts b/src/lambda/api/authorizer.ts index 31d7260f..83e1b2d3 100644 --- a/src/lambda/api/authorizer.ts +++ b/src/lambda/api/authorizer.ts @@ -48,7 +48,7 @@ const API_GATEWAY_ARN_INDEXES = [ RESOURCE_PATH_INDEX, ]; -class PersonaAuthorizer { +export class PersonaAuthorizer { event: any; context: any; personaClient: PersonaClient | undefined; @@ -215,9 +215,9 @@ class PersonaAuthorizer { const scopeConfig = process.env["SCOPE_CONFIG"]; if (scopeConfig != undefined) { const conf = JSON.parse(scopeConfig); - for (const pathRegEx of Object.keys(conf)) { - if (parsedMethodArn.resourcePath.match(pathRegEx)) { - return conf[pathRegEx]; + for (const path of Object.keys(conf)) { + if (this.pathMatch(path, parsedMethodArn.resourcePath)) { + return conf[path]; } } } @@ -241,6 +241,34 @@ class PersonaAuthorizer { return this.personaClient; } + + pathMatch(pathDefinition: string, path: string): boolean { + const pathDefinitionParts = pathDefinition.split("/"); + const pathParts = path.split("/"); + + if (pathDefinitionParts.length !== pathParts.length) { + return false; + } + + for (let i = 0; i < pathDefinitionParts.length; i++) { + const pathDefinitionSegment = pathDefinitionParts[i]; + const pathSegment = pathParts[i]; + + if ( + pathDefinitionSegment.startsWith("{") && + pathDefinitionSegment.endsWith("}") + ) { + // Matches path argument + } else { + // Should match directly + if (pathDefinitionSegment !== pathSegment) { + return false; + } + } + } + + return true; + } } module.exports.validateToken = async (event: any, context: any) => { diff --git a/test/integration/authenticated-api/authenticated-api.test.ts b/test/integration/authenticated-api/authenticated-api.test.ts index 384587b8..f1242b90 100644 --- a/test/integration/authenticated-api/authenticated-api.test.ts +++ b/test/integration/authenticated-api/authenticated-api.test.ts @@ -156,4 +156,66 @@ describe("AuthenticatedApi", () => { expect(response.status).toBe(200); expect(response.data).toBe("Simple Authenticated Api Documentation"); }); + + test("returns 200 when routing to a url ending in a path argument", async () => { + const token = await getOAuthToken( + TALIS_CDK_AUTH_API_VALID_CLIENT, + TALIS_CDK_AUTH_API_VALID_SECRET + ); + const axiosAuthInstance = axios.create({ + headers: { Authorization: `Bearer ${token}` }, + baseURL: `https://${apiGatewayId}.execute-api.eu-west-1.amazonaws.com/1/`, + }); + const response = await axiosAuthInstance.get("route3/1234"); + expect(response.status).toBe(200); + expect(response.data).toBe("route 3"); + }); + + test("returns 403 when routing to a url ending in a path argument", async () => { + const token = await getOAuthToken( + TALIS_CDK_AUTH_API_MISSING_SCOPE_CLIENT, + TALIS_CDK_AUTH_API_MISSING_SCOPE_SECRET + ); + try { + const axiosBadAuthInstance = axios.create({ + headers: { Authorization: `Bearer ${token}` }, + baseURL: `https://${apiGatewayId}.execute-api.eu-west-1.amazonaws.com/1/`, + }); + await axiosBadAuthInstance.get("route3/1234"); + throw Error("Expected a 403 response"); + } catch (err) { + expect(err.message).toBe("Request failed with status code 403"); + } + }); + + test("returns 200 when routing to a url containing a path argument", async () => { + const token = await getOAuthToken( + TALIS_CDK_AUTH_API_VALID_CLIENT, + TALIS_CDK_AUTH_API_VALID_SECRET + ); + const axiosAuthInstance = axios.create({ + headers: { Authorization: `Bearer ${token}` }, + baseURL: `https://${apiGatewayId}.execute-api.eu-west-1.amazonaws.com/1/`, + }); + const response = await axiosAuthInstance.get("route4/1234/route4"); + expect(response.status).toBe(200); + expect(response.data).toBe("route 4"); + }); + + test("returns 403 when routing to a url containing a path argument", async () => { + const token = await getOAuthToken( + TALIS_CDK_AUTH_API_MISSING_SCOPE_CLIENT, + TALIS_CDK_AUTH_API_MISSING_SCOPE_SECRET + ); + try { + const axiosBadAuthInstance = axios.create({ + headers: { Authorization: `Bearer ${token}` }, + baseURL: `https://${apiGatewayId}.execute-api.eu-west-1.amazonaws.com/1/`, + }); + await axiosBadAuthInstance.get("route4/1234/route4"); + throw Error("Expected a 403 response"); + } catch (err) { + expect(err.message).toBe("Request failed with status code 403"); + } + }); }); diff --git a/test/unit/lambda/api/authorizer.test.ts b/test/unit/lambda/api/authorizer.test.ts new file mode 100644 index 00000000..bfed901e --- /dev/null +++ b/test/unit/lambda/api/authorizer.test.ts @@ -0,0 +1,58 @@ +import { PersonaAuthorizer } from "../../../../src/lambda/api/authorizer"; + +describe("authorizer", () => { + describe("pathMatch", () => { + const pathMatchTests = [ + { + title: "matches simple paths", + pathDefinition: "/1/route1", + path: "/1/route1", + expectedResult: true, + }, + { + title: "does not match different simple paths", + pathDefinition: "/1/route1", + path: "/1/route2", + expectedResult: false, + }, + { + title: "matches long paths", + pathDefinition: "/1/a/b/route1", + path: "/1/a/b/route1", + expectedResult: true, + }, + { + title: "matches paths terminated by argument", + pathDefinition: "/1/route1/{id}", + path: "/1/route1/test_id", + expectedResult: true, + }, + { + title: "does not matche paths when argument incorrect syntax", + pathDefinition: "/1/route1/:id", + path: "/1/route1/test_id", + expectedResult: false, + }, + { + title: "matches paths containing an argument", + pathDefinition: "/1/a/{id}/route1", + path: "/1/a/test_id/route1", + expectedResult: true, + }, + { + title: "does not match when number of segments don't match", + pathDefinition: "/a/b/route1", + path: "/a/b/c/route1", + expectedResult: false, + }, + ]; + pathMatchTests.forEach((testSpec) => { + test(`${testSpec.title}`, async () => { + const authorizer = new PersonaAuthorizer(null, null); + expect( + authorizer.pathMatch(testSpec.pathDefinition, testSpec.path) + ).toBe(testSpec.expectedResult); + }); + }); + }); +});