├── .DS_Store ├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── config.js ├── entitlement_example.json ├── entitlements.js ├── index.js ├── login.js ├── package-lock.json ├── package.json ├── paywall.js ├── paywall_edge_function ├── .gitignore ├── config.js ├── package-lock.json ├── package.json └── paywall.js ├── public ├── AWS_logo_RGB.svg ├── articles.json ├── articlesexportall.json ├── bootstrap.min.css ├── bootstrap.min.js ├── dataTables.bootstrap4.min.css ├── dataTables.bootstrap4.min.js ├── favicon.ico ├── jquery-3.2.1.min.js ├── jquery.dataTables.min.js ├── login.css ├── mustache.min.js └── popper.min.js ├── screenshots ├── .DS_Store ├── architecture.png ├── attach_lambda_cf.jpg ├── cloudformation.jpg ├── create_cloudfront.jpg ├── create_lambda_fn.jpg ├── create_lambda_fn_2.jpg ├── domain_name_cf.jpg ├── example_app_view.jpg ├── example_app_view_2.jpg ├── login_screen.png └── upload.png ├── serverless.yml └── view ├── entitlements.html ├── index.html └── login.html /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-subscription-service-node/42f20f30977a5dc4641fd04ad24e565ce0afaf2d/.DS_Store -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and not Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # Stores VSCode versions used for testing VSCode extensions 107 | .vscode-test -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/aws-samples/aws-serverless-subscription-service-node/issues), or [recently closed](https://github.com/aws-samples/aws-serverless-subscription-service-node/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws-samples/aws-serverless-subscription-service-node/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/aws-samples/aws-serverless-subscription-service-node/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 10 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 11 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 12 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 13 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 14 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Subscription using Lambda At Edge 2 | Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building Subscription Service 2 | 3 | ## Summary 4 | 5 | This is the code example for the blog post [Building Serverless Subscription Service using Lambda@Edge](https://aws.amazon.com/blogs/networking-and-content-delivery/building-a-serverless-subscription-service-using-lambdaedge) 6 | Our example application supports providing a custom experience for website visitors who sign in to the site, so we start by authenticating users when they navigate to the website in their browser. 7 | 8 | ## Step-by-Step Setup Guide 9 | 10 | - First, install the serverless framework by following the instructions at this link: https://serverless.com/framework/docs/providers/aws/guide/quick-start/ 11 | 12 | ```sh 13 | $ npm install -g serverless 14 | $ git pull https://github.com/aws-samples/aws-serverless-subscription-service-node.git 15 | $ cd to the repository folder 16 | $ npm install package.json --save 17 | $ serverless deploy -v 18 | ``` 19 | 20 | - In the CloudFormation console, when the application is complete, click the output URL to verify the deployment. 21 | 22 | - In the paywall_edge_function folder, update the config.js file using the CloudFormation output. 23 | 24 | - Update the config.js by replacing cloudfront-distro-id with your CloudFront distribution ID. 25 | 26 | ```sh 27 | config.web.rootPath = 'https://cloudfront-distro-id.execute-api.us-east-1.amazonaws.com/dev'; 28 | config.web.hostName = 'cloudfront-distro-id.execute-api.us-east-1.amazonaws.com'; 29 | config.web.headlessCmsUrl = 'https://cloudfront-distro-id.execute-api.us-east-1.amazonaws.com/dev/articlesexportall'; 30 | ``` 31 | 32 | - After you update the config.js file, create the Lambda zip file by using following commands: 33 | 34 | ```sh 35 | npm install package.json --save 36 | zip paywall.zip * 37 | ``` 38 | - More information about how to create a Lambda deployment package can be found in our [documentation](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html) 39 | 40 | - In the AWS Lambda console, create a new Lambda function. Use paywall.zip as the codebase for the function. 41 | 42 | - In the CloudFront console, create a distribution following the steps in the blog post. 43 | 44 | - Under Add triggers, choose CloudFront, and then add viewer-request triggers for /login, /api/login, and /articles by adding cache behaviors for each one. You can find step-by-step instructions in our [documentation](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-add-triggers.html) 45 | 46 | - You can also refer the screenshots included in screenshots directory in the repository. 47 | 48 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The config-specific functionality of the platform. 3 | * 4 | * @since 1.0.0 5 | * 6 | * @package ServerlessSubscriptionPlatform 7 | * @subpackage ServerlessSubscriptionPlatform/config 8 | */ 9 | 10 | /** 11 | * The config-specific functionality of the platform. 12 | * 13 | * @description 14 | * 15 | * @package ServerlessSubscriptionPlatform 16 | * @subpackage ServerlessSubscriptionPlatform/config 17 | * @author Vasanth Kumararajan 18 | */ 19 | 20 | let config = {}; 21 | 22 | config.web = {}; 23 | 24 | //create a base64 signing key 25 | config.web.base64SigningKey = 'your_signing_key'; 26 | //This is from the CloudFromation output 27 | config.web.rootPath = 'https://api_gw_endpoint.execute-api.us-east-1.amazonaws.com/dev'; 28 | config.web.hostName = 'api_gw_endpoint.execute-api.us-east-1.amazonaws.com'; 29 | config.web.headlessCmsUrl = 'https://api_gw_endpoint.execute-api.us-east-1.amazonaws.com/dev/articlesexportall'; 30 | 31 | module.exports = config; 32 | -------------------------------------------------------------------------------- /entitlement_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "userId": "user1", 3 | "name": "full_name", 4 | "entitlement": {"compute":"2018-05-11T00:00:00Z", "security":"2018-05-18T00:00:00Z", "edge":"2018-05-09T00:00:00Z", "language":"hi"}, 5 | "validFrom": "2018-04-17T00:00:00Z", 6 | "validTo": "9999-12-31T23:59:59Z", 7 | "entered": "2018-04-17T00:00:00Z", 8 | "enteredBy": "yourname" 9 | } 10 | -------------------------------------------------------------------------------- /entitlements.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The entitlements-specific functionality of the platform. 3 | * 4 | * @since 1.0.0 5 | * 6 | * @package ServerlessSubscriptionPlatform 7 | * @subpackage ServerlessSubscriptionPlatform/entitlements 8 | */ 9 | 10 | /** 11 | * The entitlements-specific functionality of the platform. 12 | * 13 | * @description 14 | * 15 | * @package ServerlessSubscriptionPlatform 16 | * @subpackage ServerlessSubscriptionPlatform/entitlements 17 | * @author Vasanth Kumararajan 18 | */ 19 | 20 | const serverless = require('serverless-http'); 21 | const bodyParser = require('body-parser'); 22 | const express = require('express') 23 | const app = express() 24 | const AWS = require('aws-sdk'); 25 | const uuidv4 = require('uuid/v4'); 26 | const nJwt = require('njwt'); 27 | const config = require('./config'); 28 | 29 | // TODO: Retrieve signing key from a data store or KMS?? 30 | const secureRandom = require('secure-random'); 31 | const b64string = config.web.base64SigningKey; 32 | const signingKey = Buffer.from(b64string, 'base64') || secureRandom(256, {type: 'Buffer'}); // Create a highly random byte array of 256 bytes 33 | //console.log(signingKey); 34 | //const base64SigningKey = signingKey.toString('base64'); 35 | //console.log(base64SigningKey); 36 | 37 | const ENTITLEMENTS_TABLE = 'entitlements-table-dev'; 38 | const ROOT_PATH = config.web.rootPath; 39 | const ENTITLEMENTS_PATH = '/api/entitlements/'; 40 | const ENTITLEMENTS_SCOPE = 'entitlement-management'; 41 | const dynamoDb = new AWS.DynamoDB.DocumentClient({ 42 | convertEmptyValues: true, 43 | }); 44 | 45 | app.use(bodyParser.json({ strict: false })); 46 | 47 | // Get all active entitlements endpoint 48 | app.get(ENTITLEMENTS_PATH, function (req, res) { 49 | var d = new Date(); 50 | const params = { 51 | TableName: ENTITLEMENTS_TABLE, 52 | FilterExpression: 'validTo > :timestamp', 53 | ExpressionAttributeValues: {":timestamp":d.toISOString()}, 54 | Limit: 1000 55 | } 56 | 57 | dynamoDb.scan(params, (error, result) => { 58 | if (error) { 59 | console.log(error) 60 | res.status(400).json({ error: 'Could not get all entitlements' }) 61 | } else if (result) { 62 | res.json(result) 63 | } 64 | }) 65 | }) 66 | 67 | // Create Entitlement endpoint 68 | app.post(ENTITLEMENTS_PATH, function (req, res) { 69 | 70 | if(!req.body || req.body.length === 0) 71 | { 72 | res.status(400).json({ error: 'Request body not found' }) 73 | } 74 | else if(req.body.length > 4e5) 75 | { 76 | res.status(413).json({ error: 'Payload Too Large' }); 77 | } 78 | const { userId, name, entitlement, validFrom, validTo, entered, enteredBy } = req.body; 79 | validate(userId, res, name, entitlement, validFrom, validTo, entered, enteredBy); 80 | 81 | const params = { 82 | TableName: ENTITLEMENTS_TABLE, 83 | Item: { 84 | userId: userId, 85 | name: name, 86 | entitlement: entitlement, 87 | validFrom: validFrom, 88 | validTo: validTo, 89 | entered: entered, 90 | enteredBy: enteredBy, 91 | }, 92 | }; 93 | 94 | dynamoDb.put(params, (error) => { 95 | if (error) { 96 | console.log(error); 97 | res.status(400).json({ error: 'Could not create entitlement' }); 98 | } 99 | var uri = ROOT_PATH + ENTITLEMENTS_PATH + userId; 100 | res.status(201).json({ userId, uri, name, entitlement, validFrom, validTo, entered, enteredBy }); 101 | }); 102 | }) 103 | 104 | // Get Entitlement endpoint 105 | app.get(ENTITLEMENTS_PATH+':userId', function (req, res) { 106 | const params = { 107 | TableName: ENTITLEMENTS_TABLE, 108 | Key: { 109 | userId: req.params.userId, 110 | }, 111 | } 112 | 113 | dynamoDb.get(params, (error, result) => { 114 | if (error) { 115 | console.log(error); 116 | res.status(400).json({ error: 'Could not get entitlement' }); 117 | } 118 | if (result.Item) { 119 | const {userId, name, entitlement, validFrom, validTo, entered, enteredBy} = result.Item; 120 | res.json({ userId, name, entitlement, validFrom, validTo, entered, enteredBy }); 121 | } else { 122 | res.status(404).json({ error: "Entitlement not found" }); 123 | } 124 | }); 125 | }) 126 | 127 | // Update Entitlement endpoint 128 | app.put(ENTITLEMENTS_PATH+':userId', function (req, res) { 129 | 130 | if(!req.body || req.body.length === 0) 131 | { 132 | res.status(400).json({ error: 'Request body not found' }) 133 | } 134 | else if(req.body.length > 4e5) 135 | { 136 | res.status(413).json({ error: 'Payload Too Large' }); 137 | } 138 | const { name, entitlement, validFrom, validTo, entered, enteredBy } = req.body; 139 | validate(req.params.userId, res, name, entitlement, validFrom, validTo, entered, enteredBy); 140 | 141 | const params = { 142 | TableName: ENTITLEMENTS_TABLE, 143 | Key: { 144 | userId: req.params.userId, 145 | }, 146 | UpdateExpression: 'set #n = :name, entitlement = :entitlement, validFrom = :validFrom, validTo = :validTo, entered = :entered, enteredBy = :enteredBy', 147 | ExpressionAttributeNames: { 148 | '#n': 'name' 149 | }, 150 | ExpressionAttributeValues: { 151 | ':name': name, 152 | ':entitlement': entitlement, 153 | ':validFrom': validFrom, 154 | ':validTo': validTo, 155 | ':entered': entered, 156 | ':enteredBy': enteredBy, 157 | }, 158 | }; 159 | 160 | dynamoDb.update(params, (error, data) => { 161 | if (error) { 162 | console.log(error); 163 | res.status(400).json({ error: 'Could not update entitlement' }); 164 | } else { 165 | res.status(202).json(JSON.stringify(data, null, 2)); 166 | } 167 | }); 168 | }) 169 | 170 | // Update Entitlement endpoint 171 | app.delete(ENTITLEMENTS_PATH+':userId', function (req, res) { 172 | 173 | var d = new Date(); 174 | const params = { 175 | TableName: ENTITLEMENTS_TABLE, 176 | Key: { 177 | userId: req.params.userId, 178 | }, 179 | UpdateExpression: 'set validTo = :validTo', 180 | ExpressionAttributeValues: { 181 | ':validTo': d.toISOString(), 182 | }, 183 | }; 184 | 185 | dynamoDb.update(params, (error, data) => { 186 | if (error) { 187 | console.log(error); 188 | res.status(400).json({ error: 'Could not delete entitlement' }); 189 | } else { 190 | res.status(202).json(JSON.stringify(data, null, 2)); 191 | } 192 | }); 193 | }) 194 | 195 | function validate(userId, res, name, entitlement, validFrom, validTo, entered, enteredBy) { 196 | if (typeof userId !== 'string') { 197 | res.status(400).json({ error: '"userId" must be a string' }); 198 | } 199 | else if (typeof name !== 'string') { 200 | res.status(400).json({ error: '"name" must be a string' }); 201 | } 202 | else if (typeof entitlement !== 'object') { 203 | res.status(400).json({ error: '"entitlement" must be a object' }); 204 | } 205 | else if (isNaN(Date.parse(validFrom))) { 206 | res.status(400).json({ error: '"validFrom" must be a date' }); 207 | } 208 | else if (isNaN(Date.parse(validTo))) { 209 | res.status(400).json({ error: '"validTo" must be a date' }); 210 | } 211 | else if (isNaN(Date.parse(entered))) { 212 | res.status(400).json({ error: '"entered" must be a date' }); 213 | } 214 | else if (typeof enteredBy !== 'string') { 215 | res.status(400).json({ error: '"enteredBy" must be a string' }); 216 | } 217 | } 218 | 219 | module.exports.handler = serverless(app); 220 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The main entry-specific functionality of the platform. 3 | * 4 | * @since 1.0.0 5 | * 6 | * @package ServerlessSubscriptionPlatform 7 | * @subpackage ServerlessSubscriptionPlatform/index 8 | */ 9 | 10 | /** 11 | * The main entry-specific functionality of the platform. 12 | * 13 | * @description 14 | * 15 | * @package ServerlessSubscriptionPlatform 16 | * @subpackage ServerlessSubscriptionPlatform/index 17 | * @author Vasanth Kumararajan 18 | */ 19 | 20 | const serverless = require('serverless-http'); 21 | const bodyParser = require('body-parser'); 22 | const express = require('express'); 23 | const app = express(); 24 | var path = require('path'); 25 | var cookieParser = require('cookie-parser') 26 | 27 | 28 | app.use(bodyParser.json({ strict: false })); 29 | 30 | app.use(cookieParser()); 31 | 32 | app.use('/js', express.static(path.join(__dirname, 'public'))); 33 | app.use('/css', express.static(path.join(__dirname, 'public'))); 34 | app.use('/images', express.static(path.join(__dirname, 'public'))); 35 | 36 | app.get('/', (req, res) => { 37 | res.sendFile(path.join(__dirname + '/view/index.html')); 38 | }); 39 | 40 | app.get('/articles', (req, res) => { 41 | res.sendFile(path.join(__dirname + '/public/articles.json')); 42 | }); 43 | 44 | app.get('/articlesexportall', (req, res) => { 45 | res.sendFile(path.join(__dirname + '/public/articlesexportall.json')); 46 | }); 47 | 48 | app.get('/login', (req, res) => { 49 | res.sendFile(path.join(__dirname + '/view/login.html')); 50 | }); 51 | 52 | app.get('/entitlements', (req, res) => { 53 | res.sendFile(path.join(__dirname + '/view/entitlements.html')); 54 | }); 55 | 56 | app.get('/api/hello', function (req, res) { 57 | if (!req.cookies['londonsheriff-Token']) { 58 | res.status(401).json({ error: 'Unauthorized' }); 59 | } else { 60 | // ISO 639-2 Language Code List 61 | // https://www.loc.gov/standards/iso639-2/php/code_list.php 62 | let hello = { 63 | "en" : "hello", 64 | "es" : "hola", 65 | "fr" : "bonjour", 66 | "he" : "שלום", 67 | "hi" : "नमस्ते", 68 | "it" : "ciao", 69 | "ja" : "こんにちは", 70 | "pt" : "Oi", 71 | "si" : "හෙලෝ", 72 | "ta" : "வனக்கம்", 73 | "zh" : "你好" 74 | } 75 | res.json(hello); 76 | } 77 | }); 78 | 79 | module.exports.handler = serverless(app); 80 | -------------------------------------------------------------------------------- /login.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The login-specific functionality of the platform. 3 | * 4 | * @since 1.0.0 5 | * 6 | * @package ServerlessSubscriptionPlatform 7 | * @subpackage ServerlessSubscriptionPlatform/login 8 | */ 9 | 10 | /** 11 | * The login-specific functionality of the platform. 12 | * 13 | * @description 14 | * 15 | * @package ServerlessSubscriptionPlatform 16 | * @subpackage ServerlessSubscriptionPlatform/login 17 | * @author Vasanth Kumararajan 18 | */ 19 | 20 | const serverless = require('serverless-http'); 21 | const bodyParser = require('body-parser'); 22 | const express = require('express') 23 | const app = express() 24 | const AWS = require('aws-sdk'); 25 | const uuidv4 = require('uuid/v4'); 26 | const nJwt = require('njwt'); 27 | const config = require('./config'); 28 | 29 | // TODO: Retrieve signing key from a data store or KMS?? 30 | const secureRandom = require('secure-random'); 31 | const b64string = config.web.base64SigningKey; 32 | const signingKey = Buffer.from(b64string, 'base64') || secureRandom(256, {type: 'Buffer'}); // Create a highly random byte array of 256 bytes 33 | //console.log(signingKey); 34 | //const base64SigningKey = signingKey.toString('base64'); 35 | //console.log(base64SigningKey); 36 | 37 | let responseStatus = 404; 38 | let responseBody = {'error':{'code':responseStatus,'message':'Not Found'}}; 39 | 40 | const ENTITLEMENTS_TABLE = 'entitlements-table-dev'; 41 | const ROOT_PATH = config.web.rootPath; 42 | const LOGIN_PATH = '/api/login/'; 43 | const dynamoDb = new AWS.DynamoDB.DocumentClient(); 44 | 45 | app.use(function (req, res, next) { 46 | var d = new Date(); 47 | console.log(`${d.toISOString()}\t${req.method}\t${req.url}\t${req.header('authorization')}`); 48 | 49 | res.setHeader('Content-Type', 'application/json'); 50 | let requestId = uuidv4(); 51 | res.setHeader('x-londonsheriff-Request-Id', requestId); 52 | // HTTP-only cookies aren't accessible via JavaScript through the Document.cookie property. 53 | res.setHeader('Set-Cookie',`londonsheriff-Request-Id=${requestId}; HttpOnly; Path=/`); // Non-production 54 | // A secure cookie will only be sent to the server when a request is made using SSL and the HTTPS protocol. 55 | //res.setHeader('Set-Cookie',`londonsheriff-Request-Id=${requestId}; Secure; HttpOnly; Path=/`); // TODO: Production 56 | 57 | bodyParser.json({ strict: false }); 58 | next(); 59 | }); 60 | 61 | // Login endpoint 62 | app.post(LOGIN_PATH, function (req, res) { 63 | let encodedData = req.headers["authorization"].split(' '); 64 | 65 | if(encodedData[0].toLowerCase().indexOf('basic') !== -1) { 66 | let decodedData = Buffer.from(encodedData[1], 'base64').toString().split(':'); 67 | let user; 68 | try { 69 | console.log(`userId: ${decodedData[0]}`); 70 | 71 | // TODO: Authenticate the user credentials 72 | 73 | const params = { 74 | TableName: ENTITLEMENTS_TABLE, 75 | Key: { 76 | userId: decodedData[0], 77 | }, 78 | } 79 | // console.log(params); 80 | dynamoDb.get(params, (error, result) => { 81 | if (error) { 82 | responseStatus = 400; 83 | responseBody = {'error':{'code':responseStatus,'message':'Could not get entitlement','details':error.message}}; 84 | console.log(error); 85 | } 86 | if (result.Item) { 87 | var d = new Date(); 88 | var authenticated = false; 89 | // console.log(result.Item); 90 | const {userId, name, entitlement, validFrom, validTo, entered, enteredBy} = result.Item; 91 | // console.log(authenticated); 92 | if(validTo > d.toISOString()) 93 | { 94 | authenticated = true; 95 | } 96 | console.log(`authenticated: ${authenticated}`); 97 | user = { userId, authenticated, name, entitlement, validFrom, validTo, entered, enteredBy }; 98 | } else { 99 | responseStatus = 404; 100 | responseBody = {'error':{'code':responseStatus,'message':'User not found','details':error.message}}; 101 | } 102 | 103 | if(typeof user !== 'undefined') { 104 | if (!user.authenticated) { 105 | responseStatus = 401; 106 | responseBody = {'error':{'code':responseStatus,'message':'Unauthorized'}}; 107 | 108 | } else { 109 | let claims = { 110 | iss: ROOT_PATH, // The URL of your service 111 | sub: user.userId, // The UID of the user in your system 112 | scope: user.entitlement 113 | }; 114 | 115 | //console.log(base64SigningKey); 116 | 117 | try { 118 | let jwt = nJwt.create(claims,signingKey); 119 | // console.log(`jwt: ${jwt}`); 120 | let token = jwt.compact(); 121 | console.log(`token: ${token}`); 122 | let responseAuthorization = `Bearer ${token}`; 123 | res.setHeader('Authorization', responseAuthorization); 124 | // HTTP-only cookies aren't accessible via JavaScript through the Document.cookie property. 125 | res.setHeader('Set-Cookie',`londonsheriff-Token=${token}; HttpOnly; Path=/`); // Non-production 126 | // A secure cookie will only be sent to the server when a request is made using SSL and the HTTPS protocol. 127 | //res.setHeader('Set-Cookie',`londonsheriff-Token=${token}; Secure; HttpOnly; Path=/`); // TODO: Production 128 | responseStatus = 200; 129 | responseBody = claims; 130 | } catch (error) { 131 | responseStatus = 403; 132 | responseBody = {'error':{'code':responseStatus,'message':'Forbidden','details':error.message}}; 133 | console.log(error); 134 | } 135 | } 136 | } 137 | 138 | res.statusCode = responseStatus; 139 | res.write(JSON.stringify(responseBody)); 140 | res.send(); 141 | }); 142 | } catch (error) { 143 | responseStatus = 400; 144 | responseBody = {'error':{'code':responseStatus,'message':'Bad Request','details':error.message}}; 145 | console.log(error); 146 | res.statusCode = responseStatus; 147 | res.write(JSON.stringify(responseBody)); 148 | res.send(); 149 | } 150 | // }); 151 | } else { 152 | responseStatus = 405; 153 | responseBody = {'error':{'code':responseStatus,'message':'Method Not Allowed'}}; 154 | res.statusCode = responseStatus; 155 | res.write(JSON.stringify(responseBody)); 156 | res.send(); 157 | } 158 | }) 159 | 160 | module.exports.handler = serverless(app); 161 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverlesssubscriptionplatform-master", 3 | "version": "1.0.0", 4 | "description": "Serverless subscription platform using Serverless, Express.js and Node.js", 5 | "main": "index.js", 6 | "private": "true", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [ 11 | "REST", 12 | "API", 13 | "Serverless", 14 | "Express", 15 | "Express.js", 16 | "Dynomo", 17 | "DynomoDB", 18 | "CloudFormation", 19 | "CloudWatch", 20 | "API Gateway", 21 | "JSON Web Token", 22 | "JWT", 23 | "Node", 24 | "Node.js", 25 | "AWS", 26 | "Lambda", 27 | "AWS Lambda", 28 | "CloudFront", 29 | "Amazon CloudFront", 30 | "Lambda@Edge", 31 | "SSP", 32 | "Serverless Subscription Platform", 33 | "Serverless subscription platform" 34 | ], 35 | "author": "Vasanth Kumararajan ", 36 | "contributors": [ 37 | "Prateek Yadav " 38 | ], 39 | "license": "MIT-0", 40 | "dependencies": { 41 | "body-parser": "^1.20.3", 42 | "cookie-parser": "^1.4.7", 43 | "express": "^4.21.2", 44 | "njwt": "^2.0.1", 45 | "package.json": "^2.0.1", 46 | "secure-random": "^1.1.1", 47 | "serverless-http": "^1.5.3", 48 | "uuid": "^3.2.1" 49 | }, 50 | "devDependencies": { 51 | "aws-sdk": "^2.1354.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /paywall.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The paywall-specific functionality of the platform. 3 | * 4 | * @since 1.0.0 5 | * 6 | * @package ServerlessSubscriptionPlatform 7 | * @subpackage ServerlessSubscriptionPlatform/paywall 8 | */ 9 | 10 | /** 11 | * The paywall-specific functionality of the platform. 12 | * 13 | * This lambda function is associated with viewer-request event 14 | * type, example shows verifying the JWT, makes two separate call 15 | * to hello api and headless cms, combines the response and 16 | * personalized response is returned at edge. Also does the pass 17 | * through for remaining use cases, such as login and default 18 | * view. 19 | * 20 | * @package ServerlessSubscriptionPlatform 21 | * @subpackage ServerlessSubscriptionPlatform/paywall 22 | * @author Prateek Yadav 23 | * @author Vasanth Kumararajan 24 | */ 25 | 26 | 'use strict'; 27 | 28 | const http = require('http'); 29 | const https = require('https'); 30 | const nJwt = require('njwt'); 31 | const config = require('./config'); 32 | 33 | const HOST_NAME = config.web.hostName; 34 | const TEMPLATE_URL = config.web.headlessCmsUrl; 35 | 36 | function parseCookies(headers) { 37 | const parsedCookie = {}; 38 | if (headers.cookie) { 39 | headers.cookie[0].value.split(';').forEach((cookie) => { 40 | if (cookie) { 41 | const parts = cookie.split('='); 42 | if (parts[1]) { 43 | parsedCookie[parts[0].trim()] = parts[1].trim(); 44 | } 45 | } 46 | }); 47 | } 48 | return parsedCookie; 49 | } 50 | 51 | exports.handler = (event, context, callback) => { 52 | const request = event.Records[0].cf.request; 53 | console.log(JSON.stringify(request)); 54 | const parsedCookies = parseCookies(request.headers); 55 | 56 | if (parsedCookies && parsedCookies['londonsheriff-Token'] && request.uri == "/articles") { 57 | console.log('Cookie present'); 58 | 59 | const jwtToken = parsedCookies['londonsheriff-Token']; 60 | const b64string = config.web.base64SigningKey; 61 | const verifiedToken = nJwt.verify(jwtToken, b64string); 62 | 63 | // TODO: Decide what to do when the passed token is not valid or expired 64 | 65 | const userDetails = jwtToken.split('.')[1]; 66 | console.log(userDetails); 67 | console.log(Buffer.from(userDetails, 'base64').toString('ascii')); 68 | let userToken = JSON.parse(Buffer.from(userDetails, 'base64').toString('ascii')); 69 | const userName = userToken.sub; 70 | const scope = userToken.scope; 71 | 72 | let templateUrl = TEMPLATE_URL; 73 | 74 | http.get(templateUrl, (res) => { 75 | var content = ''; 76 | res.on('data', (chunk) => { content += chunk; }); 77 | res.on('end', () => { 78 | console.log(content); 79 | let responseBody = []; 80 | var responseWithTags = {}; 81 | let jsonBody = JSON.parse(content); 82 | // Go through the scope to find out what this user is intersted in 83 | for (var key in scope) { 84 | if (scope.hasOwnProperty(key) && key != 'language') { 85 | for (var i = 0; i < jsonBody.length; i++) { 86 | let article = jsonBody[i]; 87 | if (article['field_tags'].indexOf(key) > 0) { 88 | responseBody.push({'title': article['title'], 'body': article['body']}) 89 | } 90 | } 91 | responseWithTags[key] = responseBody; 92 | } 93 | responseBody = []; 94 | } 95 | const options = { 96 | hostname: HOST_NAME, 97 | port: 443, 98 | protocol: 'https:', 99 | path: '/dev/api/hello', 100 | method: 'GET', 101 | headers: { 102 | 'Cookie': request.headers.cookie[0].value 103 | } 104 | }; 105 | console.log("cookie " + request.headers.cookie) 106 | var req = https.request(options, function(res) { 107 | var content = ''; 108 | res.on('data', (chunk) => { content += chunk; }); 109 | res.on('end', () => { 110 | console.log("got hello: " + content); 111 | const jsonBody = JSON.parse(content); 112 | responseWithTags['hello'] = jsonBody[scope['language']]; 113 | const response = { 114 | status: '200', 115 | statusDescription: 'OK', 116 | body: JSON.stringify(responseWithTags), 117 | bodyEncoding: 'text', 118 | }; 119 | callback(null, response); 120 | }); 121 | }); 122 | req.end(); 123 | }); 124 | }); 125 | } else if (request.uri == '/login' || request.uri.startsWith('/api/login')) { 126 | console.log('login uri found'); 127 | if (!request.headers.authorization) { 128 | console.log('No auth header'); 129 | const options = { 130 | hostname: HOST_NAME, 131 | port: 443, 132 | protocol: 'https:', 133 | path: '/dev/login', 134 | method: 'GET' 135 | }; 136 | 137 | var req = https.request(options, function(res) { 138 | var content = ''; 139 | res.on('data', (chunk) => { content += chunk; }); 140 | res.on('end', () => { 141 | console.log(content); 142 | const response = { 143 | status: '200', 144 | statusDescription: 'OK', 145 | body: content, 146 | bodyEncoding: 'text', 147 | }; 148 | callback(null, response); 149 | }); 150 | }); 151 | req.end(); 152 | } else { 153 | const options = { 154 | hostname: HOST_NAME, 155 | port: 443, 156 | protocol: 'https:', 157 | path: '/dev/api/login', 158 | method: 'POST', 159 | headers: { 160 | 'Authorization': request.headers.authorization[0].value 161 | } 162 | }; 163 | 164 | var req = https.request(options, function(res) { 165 | var content = ''; 166 | res.on('data', (chunk) => { content += chunk; }); 167 | res.on('end', () => { 168 | const response = { 169 | status: res.statusCode, 170 | statusDescription: 'OK', 171 | body: content, 172 | bodyEncoding: 'text', 173 | headers: { 174 | 'set-cookie': [{ 175 | key: 'set-cookie', 176 | value: res.headers['set-cookie'], 177 | }] 178 | }, 179 | }; 180 | callback(null, response); 181 | }); 182 | }); 183 | req.end(); 184 | } 185 | } else { 186 | let templateUrl = TEMPLATE_URL; 187 | console.log('default case'); 188 | http.get(templateUrl, (res) => { 189 | var content = ''; 190 | res.on('data', (chunk) => { content += chunk; }); 191 | res.on('end', () => { 192 | console.log(content); 193 | let jsonBody = JSON.parse(content); 194 | let responseEdge = []; 195 | let responseSecurity = []; 196 | let responseCompute = []; 197 | for (var i = 0; i < 3; i++) { 198 | let article = jsonBody[i]; 199 | if (article['field_tags'].indexOf("edge") > 0) { 200 | responseEdge.push({'title': article['title'], 'body': article['body']}) 201 | } 202 | if (article['field_tags'].indexOf("security") > 0) { 203 | responseSecurity.push({'title': article['title'], 'body': article['body']}) 204 | } 205 | if (article['field_tags'].indexOf("compute") > 0) { 206 | responseCompute.push({'title': article['title'], 'body': article['body']}) 207 | } 208 | } 209 | 210 | let responseBody = {'hello': 'hello', 'edge': responseEdge, 'security': responseSecurity, 'compute': responseCompute }; 211 | console.log(JSON.stringify(responseBody)); 212 | const response = { 213 | status: '200', 214 | statusDescription: 'OK', 215 | body: JSON.stringify(responseBody), 216 | bodyEncoding: 'text', 217 | }; 218 | callback(null, response); 219 | }); 220 | }); 221 | } 222 | 223 | }; 224 | -------------------------------------------------------------------------------- /paywall_edge_function/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and not Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # Stores VSCode versions used for testing VSCode extensions 107 | .vscode-test -------------------------------------------------------------------------------- /paywall_edge_function/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The config-specific functionality of the platform. 3 | * 4 | * @link Link_to_the_blog_post 5 | * @since 1.0.0 6 | * 7 | * @package ServerlessSubscriptionPlatform 8 | * @subpackage ServerlessSubscriptionPlatform/config 9 | */ 10 | 11 | /** 12 | * The config-specific functionality of the platform. 13 | * 14 | * @description 15 | * 16 | * @package ServerlessSubscriptionPlatform 17 | * @subpackage ServerlessSubscriptionPlatform/config 18 | * @author Vasanth Kumararajan 19 | */ 20 | 21 | let config = {}; 22 | 23 | config.web = {}; 24 | 25 | //create a base64 signing key 26 | config.web.base64SigningKey = 'your_signing_key'; 27 | //This is from the CloudFromation output 28 | config.web.rootPath = 'https://api_gw_endpoint.execute-api.us-east-1.amazonaws.com/dev'; 29 | config.web.hostName = 'api_gw_endpoint.execute-api.us-east-1.amazonaws.com'; 30 | config.web.headlessCmsUrl = 'https://api_gw_endpoint.execute-api.us-east-1.amazonaws.com/dev/articlesexportall'; 31 | 32 | module.exports = config; 33 | -------------------------------------------------------------------------------- /paywall_edge_function/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverlesssubscriptionplatform-master", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "15.14.9", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-15.14.9.tgz", 10 | "integrity": "sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==" 11 | }, 12 | "abs": { 13 | "version": "1.3.13", 14 | "resolved": "https://registry.npmjs.org/abs/-/abs-1.3.13.tgz", 15 | "integrity": "sha512-VgsJF4AZDoxLwTRx+TlZ6gpHfSaRUcg1Vhyruqxzpr6lTmh3JMO9667AHAVUGHUD3Li9QqjX2WaTXR6pkGFU+Q==", 16 | "requires": { 17 | "ul": "^5.0.0" 18 | } 19 | }, 20 | "capture-stack-trace": { 21 | "version": "1.0.1", 22 | "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", 23 | "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==" 24 | }, 25 | "core-util-is": { 26 | "version": "1.0.2", 27 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 28 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 29 | }, 30 | "create-error-class": { 31 | "version": "3.0.2", 32 | "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", 33 | "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", 34 | "requires": { 35 | "capture-stack-trace": "^1.0.0" 36 | } 37 | }, 38 | "deep-extend": { 39 | "version": "0.6.0", 40 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 41 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" 42 | }, 43 | "deffy": { 44 | "version": "2.2.3", 45 | "resolved": "https://registry.npmjs.org/deffy/-/deffy-2.2.3.tgz", 46 | "integrity": "sha512-c5JD8Z6V1aBWVzn1+aELL97R1pHCwEjXeU3hZXdigkZkxb9vhgFP162kAxGXl992TtAg0btwQyx7d54CqcQaXQ==", 47 | "requires": { 48 | "typpy": "^2.0.0" 49 | } 50 | }, 51 | "duplexer2": { 52 | "version": "0.1.4", 53 | "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", 54 | "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", 55 | "requires": { 56 | "readable-stream": "^2.0.2" 57 | } 58 | }, 59 | "ecdsa-sig-formatter": { 60 | "version": "1.0.11", 61 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 62 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 63 | "requires": { 64 | "safe-buffer": "^5.0.1" 65 | } 66 | }, 67 | "err": { 68 | "version": "1.1.1", 69 | "resolved": "https://registry.npmjs.org/err/-/err-1.1.1.tgz", 70 | "integrity": "sha1-65KOLhGjFmSPeCgz0PlyWLpDwvg=", 71 | "requires": { 72 | "typpy": "^2.2.0" 73 | } 74 | }, 75 | "error-ex": { 76 | "version": "1.3.2", 77 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 78 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 79 | "requires": { 80 | "is-arrayish": "^0.2.1" 81 | } 82 | }, 83 | "exec-limiter": { 84 | "version": "3.2.12", 85 | "resolved": "https://registry.npmjs.org/exec-limiter/-/exec-limiter-3.2.12.tgz", 86 | "integrity": "sha512-2Bj2X3UmPQHIPtYkDW5epEHn1aTtGxP30x8Be6IzXzQzyuavlOdKI4wT56iEt9UUfvI421AHAHHnV+lBIvCcVA==", 87 | "requires": { 88 | "limit-it": "^3.0.0", 89 | "typpy": "^2.1.0" 90 | } 91 | }, 92 | "function.name": { 93 | "version": "1.0.12", 94 | "resolved": "https://registry.npmjs.org/function.name/-/function.name-1.0.12.tgz", 95 | "integrity": "sha512-C7Tu+rAFrWW5RjXqtKtXp2xOdCujq+4i8ZH3w0uz/xrYHBwXZrPt96x8cDAEHrIjeyEv/Jm6iDGyqupbaVQTlw==", 96 | "requires": { 97 | "noop6": "^1.0.1" 98 | } 99 | }, 100 | "git-package-json": { 101 | "version": "1.4.9", 102 | "resolved": "https://registry.npmjs.org/git-package-json/-/git-package-json-1.4.9.tgz", 103 | "integrity": "sha512-F88a40RBqCS6S7layrE4LIhX5TIVYyUJRYxZjAPPLfCZu9zf0R5B3l3wIY8A7hFb3xAU6Df/AHVMoBQ9SaR1Jw==", 104 | "requires": { 105 | "deffy": "^2.2.1", 106 | "err": "^1.1.1", 107 | "gry": "^5.0.0", 108 | "normalize-package-data": "^2.3.5", 109 | "oargv": "^3.4.1", 110 | "one-by-one": "^3.1.0", 111 | "r-json": "^1.2.1", 112 | "r-package-json": "^1.0.0", 113 | "tmp": "0.0.28" 114 | } 115 | }, 116 | "git-source": { 117 | "version": "1.1.9", 118 | "resolved": "https://registry.npmjs.org/git-source/-/git-source-1.1.9.tgz", 119 | "integrity": "sha512-LRWKxFrt1lIrEAdRMrCk9sGbEYQdf3TwDe9pEwR8DMau+2dljQjqqwITJqhYIbA0TkFaxatOXzLhBWW89ZMO7w==", 120 | "requires": { 121 | "git-url-parse": "^5.0.1" 122 | } 123 | }, 124 | "git-up": { 125 | "version": "1.2.1", 126 | "resolved": "https://registry.npmjs.org/git-up/-/git-up-1.2.1.tgz", 127 | "integrity": "sha1-JkSAoAax2EJhrB/gmjpRacV+oZ0=", 128 | "requires": { 129 | "is-ssh": "^1.0.0", 130 | "parse-url": "^1.0.0" 131 | } 132 | }, 133 | "git-url-parse": { 134 | "version": "5.0.1", 135 | "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-5.0.1.tgz", 136 | "integrity": "sha1-/j15xnRq4FBIz6UIyB553du6OEM=", 137 | "requires": { 138 | "git-up": "^1.0.0" 139 | } 140 | }, 141 | "got": { 142 | "version": "5.7.1", 143 | "resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz", 144 | "integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=", 145 | "requires": { 146 | "create-error-class": "^3.0.1", 147 | "duplexer2": "^0.1.4", 148 | "is-redirect": "^1.0.0", 149 | "is-retry-allowed": "^1.0.0", 150 | "is-stream": "^1.0.0", 151 | "lowercase-keys": "^1.0.0", 152 | "node-status-codes": "^1.0.0", 153 | "object-assign": "^4.0.1", 154 | "parse-json": "^2.1.0", 155 | "pinkie-promise": "^2.0.0", 156 | "read-all-stream": "^3.0.0", 157 | "readable-stream": "^2.0.5", 158 | "timed-out": "^3.0.0", 159 | "unzip-response": "^1.0.2", 160 | "url-parse-lax": "^1.0.0" 161 | } 162 | }, 163 | "gry": { 164 | "version": "5.0.8", 165 | "resolved": "https://registry.npmjs.org/gry/-/gry-5.0.8.tgz", 166 | "integrity": "sha512-meq9ZjYVpLzZh3ojhTg7IMad9grGsx6rUUKHLqPnhLXzJkRQvEL2U3tQpS5/WentYTtHtxkT3Ew/mb10D6F6/g==", 167 | "requires": { 168 | "abs": "^1.2.1", 169 | "exec-limiter": "^3.0.0", 170 | "one-by-one": "^3.0.0", 171 | "ul": "^5.0.0" 172 | } 173 | }, 174 | "hosted-git-info": { 175 | "version": "2.8.9", 176 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", 177 | "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" 178 | }, 179 | "inherits": { 180 | "version": "2.0.4", 181 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 182 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 183 | }, 184 | "ini": { 185 | "version": "1.3.8", 186 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", 187 | "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" 188 | }, 189 | "is-arrayish": { 190 | "version": "0.2.1", 191 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 192 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" 193 | }, 194 | "is-redirect": { 195 | "version": "1.0.0", 196 | "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", 197 | "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" 198 | }, 199 | "is-retry-allowed": { 200 | "version": "1.2.0", 201 | "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", 202 | "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" 203 | }, 204 | "is-ssh": { 205 | "version": "1.3.1", 206 | "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.1.tgz", 207 | "integrity": "sha512-0eRIASHZt1E68/ixClI8bp2YK2wmBPVWEismTs6M+M099jKgrzl/3E976zIbImSIob48N2/XGe9y7ZiYdImSlg==", 208 | "requires": { 209 | "protocols": "^1.1.0" 210 | } 211 | }, 212 | "is-stream": { 213 | "version": "1.1.0", 214 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 215 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" 216 | }, 217 | "isarray": { 218 | "version": "1.0.0", 219 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 220 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 221 | }, 222 | "iterate-object": { 223 | "version": "1.3.3", 224 | "resolved": "https://registry.npmjs.org/iterate-object/-/iterate-object-1.3.3.tgz", 225 | "integrity": "sha512-DximWbkke36cnrSfNJv6bgcB2QOMV9PRD2FiowwzCoMsh8RupFLdbNIzWe+cVDWT+NIMNJgGlB1dGxP6kpzGtA==" 226 | }, 227 | "limit-it": { 228 | "version": "3.2.9", 229 | "resolved": "https://registry.npmjs.org/limit-it/-/limit-it-3.2.9.tgz", 230 | "integrity": "sha512-3cAf+D47VdMrrzLpV3wIyEHoAACc7FonHMz+I8onocXdnWD2zBeicse851NZ9TUeCEyuBM35Cx82mpdx1WLm2A==", 231 | "requires": { 232 | "typpy": "^2.0.0" 233 | } 234 | }, 235 | "lowercase-keys": { 236 | "version": "1.0.1", 237 | "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", 238 | "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" 239 | }, 240 | "minimist": { 241 | "version": "1.2.6", 242 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", 243 | "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" 244 | }, 245 | "njwt": { 246 | "version": "2.0.1", 247 | "resolved": "https://registry.npmjs.org/njwt/-/njwt-2.0.1.tgz", 248 | "integrity": "sha512-HwFeZsPJ1aOhIjMjqT9Qv7BOsQbkxjRVPPSdFXNOTEkfKpr9+O6OX+dSN6TxxIErSYSqrmlDR4H2zOGOpEbZLA==", 249 | "requires": { 250 | "@types/node": "^15.0.1", 251 | "ecdsa-sig-formatter": "^1.0.5", 252 | "uuid": "^8.3.2" 253 | } 254 | }, 255 | "node-status-codes": { 256 | "version": "1.0.0", 257 | "resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz", 258 | "integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=" 259 | }, 260 | "noop6": { 261 | "version": "1.0.8", 262 | "resolved": "https://registry.npmjs.org/noop6/-/noop6-1.0.8.tgz", 263 | "integrity": "sha512-+Al5csMVc40I8xRfJsyBcN1IbpyvebOuQmMfxdw+AL6ECELey12ANgNTRhMfTwNIDU4W9W0g8EHLcsb3+3qPFA==" 264 | }, 265 | "normalize-package-data": { 266 | "version": "2.5.0", 267 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", 268 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", 269 | "requires": { 270 | "hosted-git-info": "^2.1.4", 271 | "resolve": "^1.10.0", 272 | "semver": "2 || 3 || 4 || 5", 273 | "validate-npm-package-license": "^3.0.1" 274 | } 275 | }, 276 | "oargv": { 277 | "version": "3.4.9", 278 | "resolved": "https://registry.npmjs.org/oargv/-/oargv-3.4.9.tgz", 279 | "integrity": "sha512-24Eatdf7OGezTAU0Yw3HaoO9x+GTFnmBkuFHfWEQtVsIKbD7VMHhyIlDMtxxUxfZKPBPHYsTo8UgGwKr4ySewA==", 280 | "requires": { 281 | "iterate-object": "^1.1.0", 282 | "ul": "^5.0.0" 283 | } 284 | }, 285 | "obj-def": { 286 | "version": "1.0.7", 287 | "resolved": "https://registry.npmjs.org/obj-def/-/obj-def-1.0.7.tgz", 288 | "integrity": "sha512-ahx1PnGDpovRglgczxsKtoYhPhrhYEG1rs3WklAHMTk29DyStqsrGDVISOIGZLF+ewK4m5CFZNuZXIXRQwZUMg==", 289 | "requires": { 290 | "deffy": "^2.2.2" 291 | } 292 | }, 293 | "object-assign": { 294 | "version": "4.1.1", 295 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 296 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 297 | }, 298 | "one-by-one": { 299 | "version": "3.2.7", 300 | "resolved": "https://registry.npmjs.org/one-by-one/-/one-by-one-3.2.7.tgz", 301 | "integrity": "sha512-EFE5hyHMGPcesACi1tT6HRmMK23Q74ujX2gjhfGD9qMkz7CxD1AJd5TmBHIEEzuL7h7hKwWh9n9hJ5ClQJnO/Q==", 302 | "requires": { 303 | "obj-def": "^1.0.0", 304 | "sliced": "^1.0.1" 305 | } 306 | }, 307 | "os-tmpdir": { 308 | "version": "1.0.2", 309 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 310 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" 311 | }, 312 | "package-json": { 313 | "version": "2.4.0", 314 | "resolved": "https://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz", 315 | "integrity": "sha1-DRW9Z9HLvduyyiIv8u24a8sxqLs=", 316 | "requires": { 317 | "got": "^5.0.0", 318 | "registry-auth-token": "^3.0.1", 319 | "registry-url": "^3.0.3", 320 | "semver": "^5.1.0" 321 | } 322 | }, 323 | "package-json-path": { 324 | "version": "1.0.8", 325 | "resolved": "https://registry.npmjs.org/package-json-path/-/package-json-path-1.0.8.tgz", 326 | "integrity": "sha512-8OCXvm2TmEYoWC7e9AswLC0eoKY3RGbkupbiWa2vaTFaH4vEE3Kr+oeefLVm/7N4me2gYh5SjQYsdwAZLkL87g==", 327 | "requires": { 328 | "abs": "^1.2.1" 329 | } 330 | }, 331 | "package.json": { 332 | "version": "2.0.1", 333 | "resolved": "https://registry.npmjs.org/package.json/-/package.json-2.0.1.tgz", 334 | "integrity": "sha1-+IYFnSpJ7QduZIg2ldc7K0bSHW0=", 335 | "requires": { 336 | "git-package-json": "^1.4.0", 337 | "git-source": "^1.1.0", 338 | "package-json": "^2.3.1" 339 | } 340 | }, 341 | "parse-json": { 342 | "version": "2.2.0", 343 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", 344 | "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", 345 | "requires": { 346 | "error-ex": "^1.2.0" 347 | } 348 | }, 349 | "parse-url": { 350 | "version": "1.3.11", 351 | "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-1.3.11.tgz", 352 | "integrity": "sha1-V8FUKKuKiSsfQ4aWRccR0OFEtVQ=", 353 | "requires": { 354 | "is-ssh": "^1.3.0", 355 | "protocols": "^1.4.0" 356 | } 357 | }, 358 | "path-parse": { 359 | "version": "1.0.7", 360 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 361 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" 362 | }, 363 | "pinkie": { 364 | "version": "2.0.4", 365 | "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", 366 | "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" 367 | }, 368 | "pinkie-promise": { 369 | "version": "2.0.1", 370 | "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", 371 | "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", 372 | "requires": { 373 | "pinkie": "^2.0.0" 374 | } 375 | }, 376 | "prepend-http": { 377 | "version": "1.0.4", 378 | "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", 379 | "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" 380 | }, 381 | "process-nextick-args": { 382 | "version": "2.0.1", 383 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 384 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 385 | }, 386 | "protocols": { 387 | "version": "1.4.7", 388 | "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.7.tgz", 389 | "integrity": "sha512-Fx65lf9/YDn3hUX08XUc0J8rSux36rEsyiv21ZGUC1mOyeM3lTRpZLcrm8aAolzS4itwVfm7TAPyxC2E5zd6xg==" 390 | }, 391 | "r-json": { 392 | "version": "1.2.9", 393 | "resolved": "https://registry.npmjs.org/r-json/-/r-json-1.2.9.tgz", 394 | "integrity": "sha512-E5u25XBE7PpZmH5XwtthAmNvSLMTygDQMpcPtCTUBdvwPaqgIYJrxlRQJhG55Sgz7uC0Tuyh5nqNrsDT3uiefA==" 395 | }, 396 | "r-package-json": { 397 | "version": "1.0.8", 398 | "resolved": "https://registry.npmjs.org/r-package-json/-/r-package-json-1.0.8.tgz", 399 | "integrity": "sha512-y+dKPLBYKcNMY8pNy+m8YLUqeGsEhhOu0wrqfu1yr8yGX+08CzMq2uUV5GSkGA21GcaIyt6lQAiSoD+DFf3/ag==", 400 | "requires": { 401 | "package-json-path": "^1.0.0", 402 | "r-json": "^1.2.1" 403 | } 404 | }, 405 | "rc": { 406 | "version": "1.2.8", 407 | "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", 408 | "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", 409 | "requires": { 410 | "deep-extend": "^0.6.0", 411 | "ini": "~1.3.0", 412 | "minimist": "^1.2.0", 413 | "strip-json-comments": "~2.0.1" 414 | } 415 | }, 416 | "read-all-stream": { 417 | "version": "3.1.0", 418 | "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", 419 | "integrity": "sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po=", 420 | "requires": { 421 | "pinkie-promise": "^2.0.0", 422 | "readable-stream": "^2.0.0" 423 | } 424 | }, 425 | "readable-stream": { 426 | "version": "2.3.7", 427 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 428 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 429 | "requires": { 430 | "core-util-is": "~1.0.0", 431 | "inherits": "~2.0.3", 432 | "isarray": "~1.0.0", 433 | "process-nextick-args": "~2.0.0", 434 | "safe-buffer": "~5.1.1", 435 | "string_decoder": "~1.1.1", 436 | "util-deprecate": "~1.0.1" 437 | }, 438 | "dependencies": { 439 | "safe-buffer": { 440 | "version": "5.1.2", 441 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 442 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 443 | } 444 | } 445 | }, 446 | "registry-auth-token": { 447 | "version": "3.4.0", 448 | "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", 449 | "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", 450 | "requires": { 451 | "rc": "^1.1.6", 452 | "safe-buffer": "^5.0.1" 453 | } 454 | }, 455 | "registry-url": { 456 | "version": "3.1.0", 457 | "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", 458 | "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", 459 | "requires": { 460 | "rc": "^1.0.1" 461 | } 462 | }, 463 | "resolve": { 464 | "version": "1.14.2", 465 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.14.2.tgz", 466 | "integrity": "sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ==", 467 | "requires": { 468 | "path-parse": "^1.0.6" 469 | } 470 | }, 471 | "safe-buffer": { 472 | "version": "5.2.0", 473 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", 474 | "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" 475 | }, 476 | "semver": { 477 | "version": "5.7.2", 478 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", 479 | "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" 480 | }, 481 | "sliced": { 482 | "version": "1.0.1", 483 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", 484 | "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" 485 | }, 486 | "spdx-correct": { 487 | "version": "3.1.0", 488 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", 489 | "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", 490 | "requires": { 491 | "spdx-expression-parse": "^3.0.0", 492 | "spdx-license-ids": "^3.0.0" 493 | } 494 | }, 495 | "spdx-exceptions": { 496 | "version": "2.2.0", 497 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", 498 | "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" 499 | }, 500 | "spdx-expression-parse": { 501 | "version": "3.0.0", 502 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", 503 | "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", 504 | "requires": { 505 | "spdx-exceptions": "^2.1.0", 506 | "spdx-license-ids": "^3.0.0" 507 | } 508 | }, 509 | "spdx-license-ids": { 510 | "version": "3.0.5", 511 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", 512 | "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" 513 | }, 514 | "string_decoder": { 515 | "version": "1.1.1", 516 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 517 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 518 | "requires": { 519 | "safe-buffer": "~5.1.0" 520 | }, 521 | "dependencies": { 522 | "safe-buffer": { 523 | "version": "5.1.2", 524 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 525 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 526 | } 527 | } 528 | }, 529 | "strip-json-comments": { 530 | "version": "2.0.1", 531 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 532 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" 533 | }, 534 | "timed-out": { 535 | "version": "3.1.3", 536 | "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-3.1.3.tgz", 537 | "integrity": "sha1-lYYL/MXHbCd/j4Mm/Q9bLiDrohc=" 538 | }, 539 | "tmp": { 540 | "version": "0.0.28", 541 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.28.tgz", 542 | "integrity": "sha1-Fyc1t/YU6nrzlmT6hM8N5OUV0SA=", 543 | "requires": { 544 | "os-tmpdir": "~1.0.1" 545 | } 546 | }, 547 | "typpy": { 548 | "version": "2.3.11", 549 | "resolved": "https://registry.npmjs.org/typpy/-/typpy-2.3.11.tgz", 550 | "integrity": "sha512-Jh/fykZSaxeKO0ceMAs6agki9T5TNA9kiIR6fzKbvafKpIw8UlNlHhzuqKyi5lfJJ5VojJOx9tooIbyy7vHV/g==", 551 | "requires": { 552 | "function.name": "^1.0.3" 553 | } 554 | }, 555 | "ul": { 556 | "version": "5.2.14", 557 | "resolved": "https://registry.npmjs.org/ul/-/ul-5.2.14.tgz", 558 | "integrity": "sha512-VaIRQZ5nkEd8VtI3OYo5qNbhHQuBtPtu5k5GrYaKCmcP1H+FkuWtS+XFTSU1oz5GiuAg2FJL5ka8ufr9zdm8eg==", 559 | "requires": { 560 | "deffy": "^2.2.2", 561 | "typpy": "^2.3.4" 562 | } 563 | }, 564 | "unzip-response": { 565 | "version": "1.0.2", 566 | "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz", 567 | "integrity": "sha1-uYTwh3/AqJwsdzzB73tbIytbBv4=" 568 | }, 569 | "url-parse-lax": { 570 | "version": "1.0.0", 571 | "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", 572 | "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", 573 | "requires": { 574 | "prepend-http": "^1.0.1" 575 | } 576 | }, 577 | "util-deprecate": { 578 | "version": "1.0.2", 579 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 580 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 581 | }, 582 | "uuid": { 583 | "version": "8.3.2", 584 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 585 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" 586 | }, 587 | "validate-npm-package-license": { 588 | "version": "3.0.4", 589 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", 590 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", 591 | "requires": { 592 | "spdx-correct": "^3.0.0", 593 | "spdx-expression-parse": "^3.0.0" 594 | } 595 | } 596 | } 597 | } 598 | -------------------------------------------------------------------------------- /paywall_edge_function/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverlesssubscriptionplatform-master", 3 | "version": "1.0.0", 4 | "description": "Serverless subscription platform using Serverless, Express.js and Node.js", 5 | "main": "paywall.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "REST", 11 | "API", 12 | "Serverless", 13 | "Express", 14 | "Express.js", 15 | "Dynomo", 16 | "DynomoDB", 17 | "CloudFormation", 18 | "CloudWatch", 19 | "API Gateway", 20 | "JSON Web Token", 21 | "JWT", 22 | "Node", 23 | "Node.js", 24 | "AWS", 25 | "Lambda", 26 | "AWS Lambda", 27 | "CloudFront", 28 | "Amazon CloudFront", 29 | "Lambda@Edge", 30 | "SSP", 31 | "Serverless Subscription Platform", 32 | "Serverless subscription platform" 33 | ], 34 | "author": "vkumarar@amazon.com", 35 | "license": "MIT-0", 36 | "dependencies": { 37 | "njwt": "^2.0.1", 38 | "package.json": "^2.0.1" 39 | }, 40 | "devDependencies": {} 41 | } 42 | -------------------------------------------------------------------------------- /paywall_edge_function/paywall.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The paywall-specific functionality of the platform. 3 | * 4 | * @since 1.0.0 5 | * 6 | * @package ServerlessSubscriptionPlatform 7 | * @subpackage ServerlessSubscriptionPlatform/paywall 8 | */ 9 | 10 | /** 11 | * The paywall-specific functionality of the platform. 12 | * 13 | * This lambda function is associated with viewer-request event 14 | * type, example shows verifying the JWT, makes two separate call 15 | * to hello api and headless cms, combines the response and 16 | * personalized response is returned at edge. Also does the pass 17 | * through for remaining use cases, such as login and default 18 | * view. 19 | * 20 | * @package ServerlessSubscriptionPlatform 21 | * @subpackage ServerlessSubscriptionPlatform/paywall 22 | * @author vkumarar@amazon.com 23 | */ 24 | 25 | 'use strict'; 26 | 27 | const https = require('https') 28 | ,nJwt = require('njwt') 29 | ,config = require('./config'); 30 | 31 | const HOST_NAME = config.web.hostName; 32 | const TEMPLATE_URL = config.web.headlessCmsUrl; 33 | 34 | function parseCookies(headers) { 35 | const parsedCookie = {}; 36 | if (headers.cookie) { 37 | headers.cookie[0].value.split(';').forEach((cookie) => { 38 | if (cookie) { 39 | const parts = cookie.split('='); 40 | if (parts[1]) { 41 | parsedCookie[parts[0].trim()] = parts[1].trim(); 42 | } 43 | } 44 | }); 45 | } 46 | return parsedCookie; 47 | } 48 | 49 | exports.handler = (event, context, callback) => { 50 | const request = event.Records[0].cf.request; 51 | console.log(JSON.stringify(request)); 52 | const parsedCookies = parseCookies(request.headers); 53 | 54 | if (parsedCookies && parsedCookies['londonsheriff-Token'] && request.uri == "/articles") { 55 | console.log('Cookie present'); 56 | 57 | const token = parsedCookies['londonsheriff-Token']; 58 | console.log(token); 59 | const b64string = config.web.base64SigningKey; 60 | const signingKey = Buffer.from(b64string, 'base64'); 61 | console.log(signingKey); 62 | try { 63 | let verifiedJwt = nJwt.verify(token,signingKey); 64 | console.log(verifiedJwt); 65 | } catch(e) { 66 | console.log(e); 67 | } 68 | 69 | // TODO: Decide what to do when the passed token is not valid or expired 70 | 71 | const userDetails = token.split('.')[1]; 72 | console.log(userDetails); 73 | console.log(Buffer.from(userDetails, 'base64').toString('ascii')); 74 | let userToken = JSON.parse(Buffer.from(userDetails, 'base64').toString('ascii')); 75 | const userName = userToken.sub; 76 | const scope = userToken.scope; 77 | 78 | let templateUrl = TEMPLATE_URL; 79 | 80 | https.get(templateUrl, (res) => { 81 | var content = ''; 82 | res.on('data', (chunk) => { content += chunk; }); 83 | res.on('end', () => { 84 | console.log(content); 85 | let responseBody = []; 86 | var responseWithTags = {}; 87 | let jsonBody = JSON.parse(content); 88 | // Go through the scope to find out what this user is intersted in 89 | for (var key in scope) { 90 | if (scope.hasOwnProperty(key) && key != 'language') { 91 | for (var i = 0; i < jsonBody.length; i++) { 92 | let article = jsonBody[i]; 93 | if (article['field_tags'].indexOf(key) > 0) { 94 | responseBody.push({'title': article['title'], 'body': article['body']}) 95 | } 96 | } 97 | responseWithTags[key] = responseBody; 98 | } 99 | responseBody = []; 100 | } 101 | const options = { 102 | hostname: HOST_NAME, 103 | port: 443, 104 | protocol: 'https:', 105 | path: '/dev/api/hello', 106 | method: 'GET', 107 | headers: { 108 | 'Cookie': request.headers.cookie[0].value 109 | } 110 | }; 111 | console.log("cookie " + request.headers.cookie) 112 | var req = https.request(options, function(res) { 113 | var content = ''; 114 | res.on('data', (chunk) => { content += chunk; }); 115 | res.on('end', () => { 116 | console.log("got hello: " + content); 117 | const jsonBody = JSON.parse(content); 118 | responseWithTags['hello'] = jsonBody[scope['language']]; 119 | const response = { 120 | status: '200', 121 | statusDescription: 'OK', 122 | body: JSON.stringify(responseWithTags), 123 | bodyEncoding: 'text', 124 | }; 125 | callback(null, response); 126 | }); 127 | }); 128 | req.end(); 129 | }); 130 | }); 131 | } else if (request.uri == '/login' || request.uri.startsWith('/api/login')) { 132 | console.log('login uri found'); 133 | if (!request.headers.authorization) { 134 | console.log('No auth header'); 135 | const options = { 136 | hostname: HOST_NAME, 137 | port: 443, 138 | protocol: 'https:', 139 | path: '/dev/login', 140 | method: 'GET' 141 | }; 142 | 143 | var req = https.request(options, function(res) { 144 | var content = ''; 145 | res.on('data', (chunk) => { content += chunk; }); 146 | res.on('end', () => { 147 | console.log(content); 148 | const response = { 149 | status: '200', 150 | statusDescription: 'OK', 151 | body: content, 152 | bodyEncoding: 'text', 153 | }; 154 | callback(null, response); 155 | }); 156 | }); 157 | req.end(); 158 | } else { 159 | const options = { 160 | hostname: HOST_NAME, 161 | port: 443, 162 | protocol: 'https:', 163 | path: '/dev/api/login', 164 | method: 'POST', 165 | headers: { 166 | 'Authorization': request.headers.authorization[0].value 167 | } 168 | }; 169 | 170 | var req = https.request(options, function(res) { 171 | var content = ''; 172 | res.on('data', (chunk) => { content += chunk; }); 173 | res.on('end', () => { 174 | const response = { 175 | status: res.statusCode, 176 | statusDescription: 'OK', 177 | body: content, 178 | bodyEncoding: 'text', 179 | headers: { 180 | 'set-cookie': [{ 181 | key: 'set-cookie', 182 | value: res.headers['set-cookie'], 183 | }] 184 | }, 185 | }; 186 | callback(null, response); 187 | }); 188 | }); 189 | req.end(); 190 | } 191 | } else { 192 | let templateUrl = TEMPLATE_URL; 193 | console.log('default case'); 194 | https.get(templateUrl, (res) => { 195 | var content = ''; 196 | res.on('data', (chunk) => { content += chunk; }); 197 | res.on('end', () => { 198 | console.log(content); 199 | let jsonBody = JSON.parse(content); 200 | let responseEdge = []; 201 | let responseSecurity = []; 202 | let responseCompute = []; 203 | for (var i = 0; i < 3; i++) { 204 | let article = jsonBody[i]; 205 | if (article['field_tags'].indexOf("edge") > 0) { 206 | responseEdge.push({'title': article['title'], 'body': article['body']}) 207 | } 208 | if (article['field_tags'].indexOf("security") > 0) { 209 | responseSecurity.push({'title': article['title'], 'body': article['body']}) 210 | } 211 | if (article['field_tags'].indexOf("compute") > 0) { 212 | responseCompute.push({'title': article['title'], 'body': article['body']}) 213 | } 214 | } 215 | 216 | let responseBody = {'hello': 'hello', 'edge': responseEdge, 'security': responseSecurity, 'compute': responseCompute }; 217 | console.log(JSON.stringify(responseBody)); 218 | const response = { 219 | status: '200', 220 | statusDescription: 'OK', 221 | body: JSON.stringify(responseBody), 222 | bodyEncoding: 'text', 223 | }; 224 | callback(null, response); 225 | }); 226 | }); 227 | } 228 | 229 | }; 230 | -------------------------------------------------------------------------------- /public/AWS_logo_RGB.svg: -------------------------------------------------------------------------------- 1 | AWS-Logo_Full-Color -------------------------------------------------------------------------------- /public/articles.json: -------------------------------------------------------------------------------- 1 | { 2 | "compute": [ 3 | { 4 | "title": "New Amazon EC2 Spot pricing model: Simplified purchasing without bidding and fewer interruptions", 5 | "body": "Amazon EC2 Spot Instances offer spare compute capacity in the AWS Cloud at steep discounts. Customers—including Yelp, NASA JPL, FINRA, and Autodesk—use Spot Instances to reduce costs and get faster results. Spot Instances provide acceleration, scale, and deep cost savings to big data workloads, containerized applications such as […]" 6 | }, 7 | { 8 | "title": "Migrating Your Amazon ECS Containers to AWS Fargate", 9 | "body": "AWS Fargate is a new technology that works with Amazon Elastic Container Service (ECS) to run containers without having to manage servers or clusters. What does this mean? With Fargate, you no longer need to provision or manage a single virtual machine; you can just create tasks and run them directly! Fargate uses the same API actions as ECS, so […]" 10 | }, 11 | { 12 | "title": "Invoking AWS Lambda from Amazon MQ", 13 | "body": "Architect Message brokers can be used to solve a number of needs in enterprise architectures, including managing workload queues and broadcasting messages to a number of subscribers. Amazon MQ is a managed message broker service for Apache ActiveMQ that makes it easy to set up and operate […]" 14 | } 15 | ], 16 | "security": [ 17 | { 18 | "title": "How we reduce complexity and rapidly iterate on Amazon GuardDuty: twelve new detections added", 19 | "body": "We’re relentlessly innovating on your behalf at AWS, especially when it comes to security. Last November, we launched Amazon GuardDuty, a continuous security monitoring and threat detection service that incorporates threat intelligence, anomaly detection, and machine learning to help protect your AWS resources, including your AWS accounts. Many large customers, including General Electric, Autodesk, and […]" 20 | }, 21 | { 22 | "title": "How to Delegate Administration of Your AWS Managed Microsoft AD Directory to Your On-Premises Active Directory Users", 23 | "body": "You can now enable your on-premises users administer your AWS Directory Service for Microsoft Active Directory, also known as AWS Managed Microsoft AD. Using an Active Directory (AD) trust and the new AWS delegated AD security groups, you can grant administrative permissions to your on-premises users by managing group membership in your on-premises AD directory. […]" 24 | }, 25 | { 26 | "title": "How to Use Bucket Policies and Apply Defense-in-Depth to Help Secure Your Amazon S3 Data", 27 | "body": "Amazon S3 provides comprehensive security and compliance capabilities that meet even the most stringent regulatory requirements. It gives you flexibility in the way you manage data for cost optimization, access control, and compliance. However, because the service is flexible, a user could accidentally configure buckets in a manner that is not secure. For example, let’s […]" 28 | } 29 | ], 30 | "edge": [ 31 | { 32 | "title": "Resizing Images with Amazon CloudFront & Lambda@Edge | AWS CDN Blog", 33 | "body": "Do you have lots of images that need to be modified before delivery? No problem with Amazon CloudFront and Lambda@Edge. Read more on how you can use our services to modify image dimensions, apply watermarks, or optimize formats based on browser support all on the fly. Optimizing your image delivery can lead to a better experience for your users while also reducing your bandwidth usage." 34 | }, 35 | { 36 | "title": "Adding HTTP Security Headers Using Lambda@Edge and Amazon CloudFront", 37 | "body": "Adding security response headers is often achievable by modifications to your application configuration. In this blog we will focus on how to achieve the same result when you have an application that can’t be modified at the origin (e.g., a web site hosted in Amazon S3)." 38 | }, 39 | { 40 | "title": "Dynamically Route Viewer Requests to Any Origin Using Lambda@Edge", 41 | "body": "Today Lambda@Edge announces a new feature that allows you to do content-based routing. Now you can programmatically define the origin based on logic in your Lambda function. This enables you to route requests to different origins based on request attributes such as headers, query strings, and cookies. Read our blog post to learn about different use cases and how you can set this up within your own architecture." 42 | } 43 | ], 44 | "hello": "hello" 45 | } -------------------------------------------------------------------------------- /public/articlesexportall.json: -------------------------------------------------------------------------------- 1 | [{"title":"New Amazon EC2 Spot pricing model: Simplified purchasing without bidding and fewer interruptions","created":"Wed, 03\/28\/2018 - 19:28","body":"Amazon EC2 Spot Instances offer spare compute capacity in the AWS Cloud at steep discounts. Customers\u2014including Yelp, NASA JPL, FINRA, and Autodesk\u2014use Spot Instances to reduce costs and get faster results. Spot Instances provide acceleration, scale, and deep cost savings to big data workloads, containerized applications such as [\u2026]","field_image":"","path":"\/node\/9","uid_1":"\u003Ca href=\u0022\/user\/1\u0022 hreflang=\u0022en\u0022\u003Euser\u003C\/a\u003E","field_tags":"\u003Ca href=\u0022\/taxonomy\/term\/9\u0022 hreflang=\u0022en\u0022\u003Ecompute\u003C\/a\u003E"},{"title":"Migrating Your Amazon ECS Containers to AWS Fargate","created":"Wed, 03\/28\/2018 - 19:28","body":"AWS Fargate\u00a0is a new technology that works with\u00a0Amazon Elastic Container Service\u00a0(ECS) to run containers without having to manage servers or clusters. What does this mean? With Fargate, you no longer need to provision or manage a single virtual machine;\u00a0you can just create tasks and run them directly! Fargate uses\u00a0the same API actions as ECS, so [\u2026]","field_image":"","path":"\/node\/8","uid_1":"\u003Ca href=\u0022\/user\/1\u0022 hreflang=\u0022en\u0022\u003Euser\u003C\/a\u003E","field_tags":"\u003Ca href=\u0022\/taxonomy\/term\/9\u0022 hreflang=\u0022en\u0022\u003Ecompute\u003C\/a\u003E"},{"title":"Invoking AWS Lambda from Amazon MQ","created":"Wed, 03\/28\/2018 - 19:27","body":"Architect Message brokers can be used to solve a number of needs in enterprise architectures, including managing workload queues and broadcasting messages to a number of subscribers. Amazon MQ is a managed message broker service for Apache ActiveMQ that makes it easy to set up and operate [\u2026]","field_image":"","path":"\/node\/7","uid_1":"\u003Ca href=\u0022\/user\/1\u0022 hreflang=\u0022en\u0022\u003Euser\u003C\/a\u003E","field_tags":"\u003Ca href=\u0022\/taxonomy\/term\/9\u0022 hreflang=\u0022en\u0022\u003Ecompute\u003C\/a\u003E"},{"title":"How we reduce complexity and rapidly iterate on Amazon GuardDuty: twelve new detections added","created":"Wed, 03\/28\/2018 - 19:27","body":"We\u2019re relentlessly innovating on your behalf at AWS, especially when it comes to security. Last November, we launched Amazon GuardDuty, a continuous security monitoring and threat detection service that incorporates threat intelligence, anomaly detection, and machine learning to help protect your AWS resources, including your AWS accounts. Many large customers, including General Electric, Autodesk, and [\u2026]","field_image":"","path":"\/node\/6","uid_1":"\u003Ca href=\u0022\/user\/1\u0022 hreflang=\u0022en\u0022\u003Euser\u003C\/a\u003E","field_tags":"\u003Ca href=\u0022\/taxonomy\/term\/8\u0022 hreflang=\u0022en\u0022\u003Esecurity\u003C\/a\u003E"},{"title":"How to Delegate Administration of Your AWS Managed Microsoft AD Directory to Your On-Premises Active Directory Users","created":"Wed, 03\/28\/2018 - 19:26","body":"You can now enable your on-premises users administer your AWS Directory Service for Microsoft Active Directory, also known as AWS Managed Microsoft AD. Using an Active Directory (AD) trust and the new AWS delegated AD security groups, you can grant administrative permissions to your on-premises users by managing group membership in your on-premises AD directory. [\u2026]","field_image":"","path":"\/node\/5","uid_1":"\u003Ca href=\u0022\/user\/1\u0022 hreflang=\u0022en\u0022\u003Euser\u003C\/a\u003E","field_tags":"\u003Ca href=\u0022\/taxonomy\/term\/8\u0022 hreflang=\u0022en\u0022\u003Esecurity\u003C\/a\u003E"},{"title":"How to Use Bucket Policies and Apply Defense-in-Depth to Help Secure Your Amazon S3 Data","created":"Wed, 03\/28\/2018 - 19:24","body":"Amazon S3 provides comprehensive security and compliance capabilities that meet even the most stringent regulatory requirements. It gives you flexibility in the way you manage data for cost optimization, access control, and compliance. However, because the service is flexible, a user could accidentally configure buckets in a manner that is not secure. For example, let\u2019s [\u2026]","field_image":"","path":"\/node\/4","uid_1":"\u003Ca href=\u0022\/user\/1\u0022 hreflang=\u0022en\u0022\u003Euser\u003C\/a\u003E","field_tags":"\u003Ca href=\u0022\/taxonomy\/term\/8\u0022 hreflang=\u0022en\u0022\u003Esecurity\u003C\/a\u003E"},{"title":"Resizing Images with Amazon CloudFront \u0026amp; Lambda@Edge | AWS CDN Blog","created":"Wed, 03\/21\/2018 - 22:50","body":"Do you have lots of images that need to be modified before delivery? No problem with Amazon CloudFront and Lambda@Edge. Read more on how you can use our services to modify image dimensions, apply watermarks, or optimize formats based on browser support all on the fly. Optimizing your image delivery can lead to a better experience for your users while also reducing your bandwidth usage.","field_image":"","path":"\/node\/3","uid_1":"\u003Ca href=\u0022\/user\/1\u0022 hreflang=\u0022en\u0022\u003Euser\u003C\/a\u003E","field_tags":"\u003Ca href=\u0022\/taxonomy\/term\/7\u0022 hreflang=\u0022en\u0022\u003Eedge\u003C\/a\u003E"},{"title":"Adding HTTP Security Headers Using Lambda@Edge and Amazon CloudFront","created":"Wed, 03\/21\/2018 - 22:48","body":"Adding security response headers is often achievable by modifications to your application configuration. In this blog we will focus on how to achieve the same result when you have an application that can\u2019t be modified at the origin (e.g., a web site hosted in Amazon S3).","field_image":"","path":"\/node\/2","uid_1":"\u003Ca href=\u0022\/user\/1\u0022 hreflang=\u0022en\u0022\u003Euser\u003C\/a\u003E","field_tags":"\u003Ca href=\u0022\/taxonomy\/term\/7\u0022 hreflang=\u0022en\u0022\u003Eedge\u003C\/a\u003E"},{"title":"Dynamically Route Viewer Requests to Any Origin Using Lambda@Edge","created":"Wed, 03\/21\/2018 - 22:43","body":"Today Lambda@Edge announces a new feature that allows you to do content-based routing. Now you can programmatically define the origin based on logic in your Lambda function. This enables you to route requests to different origins based on request attributes such as headers, query strings, and cookies. Read our blog post to learn about different use cases and how you can set this up within your own architecture.","field_image":"","path":"\/node\/1","uid_1":"\u003Ca href=\u0022\/user\/1\u0022 hreflang=\u0022en\u0022\u003Euser\u003C\/a\u003E","field_tags":"\u003Ca href=\u0022\/taxonomy\/term\/7\u0022 hreflang=\u0022en\u0022\u003Eedge\u003C\/a\u003E"}] -------------------------------------------------------------------------------- /public/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v4.0.0 (https://getbootstrap.com) 3 | * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e(t.bootstrap={},t.jQuery,t.Popper)}(this,function(t,e,n){"use strict";function i(t,e){for(var n=0;n0?i:null}catch(t){return null}},reflow:function(t){return t.offsetHeight},triggerTransitionEnd:function(n){t(n).trigger(e.end)},supportsTransitionEnd:function(){return Boolean(e)},isElement:function(t){return(t[0]||t).nodeType},typeCheckConfig:function(t,e,n){for(var s in n)if(Object.prototype.hasOwnProperty.call(n,s)){var r=n[s],o=e[s],a=o&&i.isElement(o)?"element":(l=o,{}.toString.call(l).match(/\s([a-zA-Z]+)/)[1].toLowerCase());if(!new RegExp(r).test(a))throw new Error(t.toUpperCase()+': Option "'+s+'" provided type "'+a+'" but expected type "'+r+'".')}var l}};return e=("undefined"==typeof window||!window.QUnit)&&{end:"transitionend"},t.fn.emulateTransitionEnd=n,i.supportsTransitionEnd()&&(t.event.special[i.TRANSITION_END]={bindType:e.end,delegateType:e.end,handle:function(e){if(t(e.target).is(this))return e.handleObj.handler.apply(this,arguments)}}),i}(e),L=(a="alert",h="."+(l="bs.alert"),c=(o=e).fn[a],u={CLOSE:"close"+h,CLOSED:"closed"+h,CLICK_DATA_API:"click"+h+".data-api"},f="alert",d="fade",_="show",g=function(){function t(t){this._element=t}var e=t.prototype;return e.close=function(t){t=t||this._element;var e=this._getRootElement(t);this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},e.dispose=function(){o.removeData(this._element,l),this._element=null},e._getRootElement=function(t){var e=P.getSelectorFromElement(t),n=!1;return e&&(n=o(e)[0]),n||(n=o(t).closest("."+f)[0]),n},e._triggerCloseEvent=function(t){var e=o.Event(u.CLOSE);return o(t).trigger(e),e},e._removeElement=function(t){var e=this;o(t).removeClass(_),P.supportsTransitionEnd()&&o(t).hasClass(d)?o(t).one(P.TRANSITION_END,function(n){return e._destroyElement(t,n)}).emulateTransitionEnd(150):this._destroyElement(t)},e._destroyElement=function(t){o(t).detach().trigger(u.CLOSED).remove()},t._jQueryInterface=function(e){return this.each(function(){var n=o(this),i=n.data(l);i||(i=new t(this),n.data(l,i)),"close"===e&&i[e](this)})},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},s(t,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),t}(),o(document).on(u.CLICK_DATA_API,'[data-dismiss="alert"]',g._handleDismiss(new g)),o.fn[a]=g._jQueryInterface,o.fn[a].Constructor=g,o.fn[a].noConflict=function(){return o.fn[a]=c,g._jQueryInterface},g),R=(m="button",E="."+(v="bs.button"),T=".data-api",y=(p=e).fn[m],C="active",I="btn",A="focus",b='[data-toggle^="button"]',D='[data-toggle="buttons"]',S="input",w=".active",N=".btn",O={CLICK_DATA_API:"click"+E+T,FOCUS_BLUR_DATA_API:"focus"+E+T+" blur"+E+T},k=function(){function t(t){this._element=t}var e=t.prototype;return e.toggle=function(){var t=!0,e=!0,n=p(this._element).closest(D)[0];if(n){var i=p(this._element).find(S)[0];if(i){if("radio"===i.type)if(i.checked&&p(this._element).hasClass(C))t=!1;else{var s=p(n).find(w)[0];s&&p(s).removeClass(C)}if(t){if(i.hasAttribute("disabled")||n.hasAttribute("disabled")||i.classList.contains("disabled")||n.classList.contains("disabled"))return;i.checked=!p(this._element).hasClass(C),p(i).trigger("change")}i.focus(),e=!1}}e&&this._element.setAttribute("aria-pressed",!p(this._element).hasClass(C)),t&&p(this._element).toggleClass(C)},e.dispose=function(){p.removeData(this._element,v),this._element=null},t._jQueryInterface=function(e){return this.each(function(){var n=p(this).data(v);n||(n=new t(this),p(this).data(v,n)),"toggle"===e&&n[e]()})},s(t,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),t}(),p(document).on(O.CLICK_DATA_API,b,function(t){t.preventDefault();var e=t.target;p(e).hasClass(I)||(e=p(e).closest(N)),k._jQueryInterface.call(p(e),"toggle")}).on(O.FOCUS_BLUR_DATA_API,b,function(t){var e=p(t.target).closest(N)[0];p(e).toggleClass(A,/^focus(in)?$/.test(t.type))}),p.fn[m]=k._jQueryInterface,p.fn[m].Constructor=k,p.fn[m].noConflict=function(){return p.fn[m]=y,k._jQueryInterface},k),j=function(t){var e="carousel",n="bs.carousel",i="."+n,o=t.fn[e],a={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0},l={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean"},h="next",c="prev",u="left",f="right",d={SLIDE:"slide"+i,SLID:"slid"+i,KEYDOWN:"keydown"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i,TOUCHEND:"touchend"+i,LOAD_DATA_API:"load"+i+".data-api",CLICK_DATA_API:"click"+i+".data-api"},_="carousel",g="active",p="slide",m="carousel-item-right",v="carousel-item-left",E="carousel-item-next",T="carousel-item-prev",y={ACTIVE:".active",ACTIVE_ITEM:".active.carousel-item",ITEM:".carousel-item",NEXT_PREV:".carousel-item-next, .carousel-item-prev",INDICATORS:".carousel-indicators",DATA_SLIDE:"[data-slide], [data-slide-to]",DATA_RIDE:'[data-ride="carousel"]'},C=function(){function o(e,n){this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this._config=this._getConfig(n),this._element=t(e)[0],this._indicatorsElement=t(this._element).find(y.INDICATORS)[0],this._addEventListeners()}var C=o.prototype;return C.next=function(){this._isSliding||this._slide(h)},C.nextWhenVisible=function(){!document.hidden&&t(this._element).is(":visible")&&"hidden"!==t(this._element).css("visibility")&&this.next()},C.prev=function(){this._isSliding||this._slide(c)},C.pause=function(e){e||(this._isPaused=!0),t(this._element).find(y.NEXT_PREV)[0]&&P.supportsTransitionEnd()&&(P.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},C.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},C.to=function(e){var n=this;this._activeElement=t(this._element).find(y.ACTIVE_ITEM)[0];var i=this._getItemIndex(this._activeElement);if(!(e>this._items.length-1||e<0))if(this._isSliding)t(this._element).one(d.SLID,function(){return n.to(e)});else{if(i===e)return this.pause(),void this.cycle();var s=e>i?h:c;this._slide(s,this._items[e])}},C.dispose=function(){t(this._element).off(i),t.removeData(this._element,n),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},C._getConfig=function(t){return t=r({},a,t),P.typeCheckConfig(e,t,l),t},C._addEventListeners=function(){var e=this;this._config.keyboard&&t(this._element).on(d.KEYDOWN,function(t){return e._keydown(t)}),"hover"===this._config.pause&&(t(this._element).on(d.MOUSEENTER,function(t){return e.pause(t)}).on(d.MOUSELEAVE,function(t){return e.cycle(t)}),"ontouchstart"in document.documentElement&&t(this._element).on(d.TOUCHEND,function(){e.pause(),e.touchTimeout&&clearTimeout(e.touchTimeout),e.touchTimeout=setTimeout(function(t){return e.cycle(t)},500+e._config.interval)}))},C._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},C._getItemIndex=function(e){return this._items=t.makeArray(t(e).parent().find(y.ITEM)),this._items.indexOf(e)},C._getItemByDirection=function(t,e){var n=t===h,i=t===c,s=this._getItemIndex(e),r=this._items.length-1;if((i&&0===s||n&&s===r)&&!this._config.wrap)return e;var o=(s+(t===c?-1:1))%this._items.length;return-1===o?this._items[this._items.length-1]:this._items[o]},C._triggerSlideEvent=function(e,n){var i=this._getItemIndex(e),s=this._getItemIndex(t(this._element).find(y.ACTIVE_ITEM)[0]),r=t.Event(d.SLIDE,{relatedTarget:e,direction:n,from:s,to:i});return t(this._element).trigger(r),r},C._setActiveIndicatorElement=function(e){if(this._indicatorsElement){t(this._indicatorsElement).find(y.ACTIVE).removeClass(g);var n=this._indicatorsElement.children[this._getItemIndex(e)];n&&t(n).addClass(g)}},C._slide=function(e,n){var i,s,r,o=this,a=t(this._element).find(y.ACTIVE_ITEM)[0],l=this._getItemIndex(a),c=n||a&&this._getItemByDirection(e,a),_=this._getItemIndex(c),C=Boolean(this._interval);if(e===h?(i=v,s=E,r=u):(i=m,s=T,r=f),c&&t(c).hasClass(g))this._isSliding=!1;else if(!this._triggerSlideEvent(c,r).isDefaultPrevented()&&a&&c){this._isSliding=!0,C&&this.pause(),this._setActiveIndicatorElement(c);var I=t.Event(d.SLID,{relatedTarget:c,direction:r,from:l,to:_});P.supportsTransitionEnd()&&t(this._element).hasClass(p)?(t(c).addClass(s),P.reflow(c),t(a).addClass(i),t(c).addClass(i),t(a).one(P.TRANSITION_END,function(){t(c).removeClass(i+" "+s).addClass(g),t(a).removeClass(g+" "+s+" "+i),o._isSliding=!1,setTimeout(function(){return t(o._element).trigger(I)},0)}).emulateTransitionEnd(600)):(t(a).removeClass(g),t(c).addClass(g),this._isSliding=!1,t(this._element).trigger(I)),C&&this.cycle()}},o._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),s=r({},a,t(this).data());"object"==typeof e&&(s=r({},s,e));var l="string"==typeof e?e:s.slide;if(i||(i=new o(this,s),t(this).data(n,i)),"number"==typeof e)i.to(e);else if("string"==typeof l){if("undefined"==typeof i[l])throw new TypeError('No method named "'+l+'"');i[l]()}else s.interval&&(i.pause(),i.cycle())})},o._dataApiClickHandler=function(e){var i=P.getSelectorFromElement(this);if(i){var s=t(i)[0];if(s&&t(s).hasClass(_)){var a=r({},t(s).data(),t(this).data()),l=this.getAttribute("data-slide-to");l&&(a.interval=!1),o._jQueryInterface.call(t(s),a),l&&t(s).data(n).to(l),e.preventDefault()}}},s(o,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),o}();return t(document).on(d.CLICK_DATA_API,y.DATA_SLIDE,C._dataApiClickHandler),t(window).on(d.LOAD_DATA_API,function(){t(y.DATA_RIDE).each(function(){var e=t(this);C._jQueryInterface.call(e,e.data())})}),t.fn[e]=C._jQueryInterface,t.fn[e].Constructor=C,t.fn[e].noConflict=function(){return t.fn[e]=o,C._jQueryInterface},C}(e),H=function(t){var e="collapse",n="bs.collapse",i="."+n,o=t.fn[e],a={toggle:!0,parent:""},l={toggle:"boolean",parent:"(string|element)"},h={SHOW:"show"+i,SHOWN:"shown"+i,HIDE:"hide"+i,HIDDEN:"hidden"+i,CLICK_DATA_API:"click"+i+".data-api"},c="show",u="collapse",f="collapsing",d="collapsed",_="width",g="height",p={ACTIVES:".show, .collapsing",DATA_TOGGLE:'[data-toggle="collapse"]'},m=function(){function i(e,n){this._isTransitioning=!1,this._element=e,this._config=this._getConfig(n),this._triggerArray=t.makeArray(t('[data-toggle="collapse"][href="#'+e.id+'"],[data-toggle="collapse"][data-target="#'+e.id+'"]'));for(var i=t(p.DATA_TOGGLE),s=0;s0&&(this._selector=o,this._triggerArray.push(r))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var o=i.prototype;return o.toggle=function(){t(this._element).hasClass(c)?this.hide():this.show()},o.show=function(){var e,s,r=this;if(!this._isTransitioning&&!t(this._element).hasClass(c)&&(this._parent&&0===(e=t.makeArray(t(this._parent).find(p.ACTIVES).filter('[data-parent="'+this._config.parent+'"]'))).length&&(e=null),!(e&&(s=t(e).not(this._selector).data(n))&&s._isTransitioning))){var o=t.Event(h.SHOW);if(t(this._element).trigger(o),!o.isDefaultPrevented()){e&&(i._jQueryInterface.call(t(e).not(this._selector),"hide"),s||t(e).data(n,null));var a=this._getDimension();t(this._element).removeClass(u).addClass(f),this._element.style[a]=0,this._triggerArray.length>0&&t(this._triggerArray).removeClass(d).attr("aria-expanded",!0),this.setTransitioning(!0);var l=function(){t(r._element).removeClass(f).addClass(u).addClass(c),r._element.style[a]="",r.setTransitioning(!1),t(r._element).trigger(h.SHOWN)};if(P.supportsTransitionEnd()){var _="scroll"+(a[0].toUpperCase()+a.slice(1));t(this._element).one(P.TRANSITION_END,l).emulateTransitionEnd(600),this._element.style[a]=this._element[_]+"px"}else l()}}},o.hide=function(){var e=this;if(!this._isTransitioning&&t(this._element).hasClass(c)){var n=t.Event(h.HIDE);if(t(this._element).trigger(n),!n.isDefaultPrevented()){var i=this._getDimension();if(this._element.style[i]=this._element.getBoundingClientRect()[i]+"px",P.reflow(this._element),t(this._element).addClass(f).removeClass(u).removeClass(c),this._triggerArray.length>0)for(var s=0;s0&&t(n).toggleClass(d,!i).attr("aria-expanded",i)}},i._getTargetFromElement=function(e){var n=P.getSelectorFromElement(e);return n?t(n)[0]:null},i._jQueryInterface=function(e){return this.each(function(){var s=t(this),o=s.data(n),l=r({},a,s.data(),"object"==typeof e&&e);if(!o&&l.toggle&&/show|hide/.test(e)&&(l.toggle=!1),o||(o=new i(this,l),s.data(n,o)),"string"==typeof e){if("undefined"==typeof o[e])throw new TypeError('No method named "'+e+'"');o[e]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),i}();return t(document).on(h.CLICK_DATA_API,p.DATA_TOGGLE,function(e){"A"===e.currentTarget.tagName&&e.preventDefault();var i=t(this),s=P.getSelectorFromElement(this);t(s).each(function(){var e=t(this),s=e.data(n)?"toggle":i.data();m._jQueryInterface.call(e,s)})}),t.fn[e]=m._jQueryInterface,t.fn[e].Constructor=m,t.fn[e].noConflict=function(){return t.fn[e]=o,m._jQueryInterface},m}(e),W=function(t){var e="dropdown",i="bs.dropdown",o="."+i,a=".data-api",l=t.fn[e],h=new RegExp("38|40|27"),c={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,CLICK:"click"+o,CLICK_DATA_API:"click"+o+a,KEYDOWN_DATA_API:"keydown"+o+a,KEYUP_DATA_API:"keyup"+o+a},u="disabled",f="show",d="dropup",_="dropright",g="dropleft",p="dropdown-menu-right",m="dropdown-menu-left",v="position-static",E='[data-toggle="dropdown"]',T=".dropdown form",y=".dropdown-menu",C=".navbar-nav",I=".dropdown-menu .dropdown-item:not(.disabled)",A="top-start",b="top-end",D="bottom-start",S="bottom-end",w="right-start",N="left-start",O={offset:0,flip:!0,boundary:"scrollParent"},k={offset:"(number|string|function)",flip:"boolean",boundary:"(string|element)"},L=function(){function a(t,e){this._element=t,this._popper=null,this._config=this._getConfig(e),this._menu=this._getMenuElement(),this._inNavbar=this._detectNavbar(),this._addEventListeners()}var l=a.prototype;return l.toggle=function(){if(!this._element.disabled&&!t(this._element).hasClass(u)){var e=a._getParentFromElement(this._element),i=t(this._menu).hasClass(f);if(a._clearMenus(),!i){var s={relatedTarget:this._element},r=t.Event(c.SHOW,s);if(t(e).trigger(r),!r.isDefaultPrevented()){if(!this._inNavbar){if("undefined"==typeof n)throw new TypeError("Bootstrap dropdown require Popper.js (https://popper.js.org)");var o=this._element;t(e).hasClass(d)&&(t(this._menu).hasClass(m)||t(this._menu).hasClass(p))&&(o=e),"scrollParent"!==this._config.boundary&&t(e).addClass(v),this._popper=new n(o,this._menu,this._getPopperConfig())}"ontouchstart"in document.documentElement&&0===t(e).closest(C).length&&t("body").children().on("mouseover",null,t.noop),this._element.focus(),this._element.setAttribute("aria-expanded",!0),t(this._menu).toggleClass(f),t(e).toggleClass(f).trigger(t.Event(c.SHOWN,s))}}}},l.dispose=function(){t.removeData(this._element,i),t(this._element).off(o),this._element=null,this._menu=null,null!==this._popper&&(this._popper.destroy(),this._popper=null)},l.update=function(){this._inNavbar=this._detectNavbar(),null!==this._popper&&this._popper.scheduleUpdate()},l._addEventListeners=function(){var e=this;t(this._element).on(c.CLICK,function(t){t.preventDefault(),t.stopPropagation(),e.toggle()})},l._getConfig=function(n){return n=r({},this.constructor.Default,t(this._element).data(),n),P.typeCheckConfig(e,n,this.constructor.DefaultType),n},l._getMenuElement=function(){if(!this._menu){var e=a._getParentFromElement(this._element);this._menu=t(e).find(y)[0]}return this._menu},l._getPlacement=function(){var e=t(this._element).parent(),n=D;return e.hasClass(d)?(n=A,t(this._menu).hasClass(p)&&(n=b)):e.hasClass(_)?n=w:e.hasClass(g)?n=N:t(this._menu).hasClass(p)&&(n=S),n},l._detectNavbar=function(){return t(this._element).closest(".navbar").length>0},l._getPopperConfig=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t._config.offset(e.offsets)||{}),e}:e.offset=this._config.offset,{placement:this._getPlacement(),modifiers:{offset:e,flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}}},a._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(i);if(n||(n=new a(this,"object"==typeof e?e:null),t(this).data(i,n)),"string"==typeof e){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}})},a._clearMenus=function(e){if(!e||3!==e.which&&("keyup"!==e.type||9===e.which))for(var n=t.makeArray(t(E)),s=0;s0&&r--,40===e.which&&rdocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},p._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},p._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent"},f="show",d="out",_={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,INSERTED:"inserted"+o,CLICK:"click"+o,FOCUSIN:"focusin"+o,FOCUSOUT:"focusout"+o,MOUSEENTER:"mouseenter"+o,MOUSELEAVE:"mouseleave"+o},g="fade",p="show",m=".tooltip-inner",v=".arrow",E="hover",T="focus",y="click",C="manual",I=function(){function a(t,e){if("undefined"==typeof n)throw new TypeError("Bootstrap tooltips require Popper.js (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var I=a.prototype;return I.enable=function(){this._isEnabled=!0},I.disable=function(){this._isEnabled=!1},I.toggleEnabled=function(){this._isEnabled=!this._isEnabled},I.toggle=function(e){if(this._isEnabled)if(e){var n=this.constructor.DATA_KEY,i=t(e.currentTarget).data(n);i||(i=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(n,i)),i._activeTrigger.click=!i._activeTrigger.click,i._isWithActiveTrigger()?i._enter(null,i):i._leave(null,i)}else{if(t(this.getTipElement()).hasClass(p))return void this._leave(null,this);this._enter(null,this)}},I.dispose=function(){clearTimeout(this._timeout),t.removeData(this.element,this.constructor.DATA_KEY),t(this.element).off(this.constructor.EVENT_KEY),t(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&t(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,null!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},I.show=function(){var e=this;if("none"===t(this.element).css("display"))throw new Error("Please use show on visible elements");var i=t.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){t(this.element).trigger(i);var s=t.contains(this.element.ownerDocument.documentElement,this.element);if(i.isDefaultPrevented()||!s)return;var r=this.getTipElement(),o=P.getUID(this.constructor.NAME);r.setAttribute("id",o),this.element.setAttribute("aria-describedby",o),this.setContent(),this.config.animation&&t(r).addClass(g);var l="function"==typeof this.config.placement?this.config.placement.call(this,r,this.element):this.config.placement,h=this._getAttachment(l);this.addAttachmentClass(h);var c=!1===this.config.container?document.body:t(this.config.container);t(r).data(this.constructor.DATA_KEY,this),t.contains(this.element.ownerDocument.documentElement,this.tip)||t(r).appendTo(c),t(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new n(this.element,r,{placement:h,modifiers:{offset:{offset:this.config.offset},flip:{behavior:this.config.fallbackPlacement},arrow:{element:v},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){e._handlePopperPlacementChange(t)}}),t(r).addClass(p),"ontouchstart"in document.documentElement&&t("body").children().on("mouseover",null,t.noop);var u=function(){e.config.animation&&e._fixTransition();var n=e._hoverState;e._hoverState=null,t(e.element).trigger(e.constructor.Event.SHOWN),n===d&&e._leave(null,e)};P.supportsTransitionEnd()&&t(this.tip).hasClass(g)?t(this.tip).one(P.TRANSITION_END,u).emulateTransitionEnd(a._TRANSITION_DURATION):u()}},I.hide=function(e){var n=this,i=this.getTipElement(),s=t.Event(this.constructor.Event.HIDE),r=function(){n._hoverState!==f&&i.parentNode&&i.parentNode.removeChild(i),n._cleanTipClass(),n.element.removeAttribute("aria-describedby"),t(n.element).trigger(n.constructor.Event.HIDDEN),null!==n._popper&&n._popper.destroy(),e&&e()};t(this.element).trigger(s),s.isDefaultPrevented()||(t(i).removeClass(p),"ontouchstart"in document.documentElement&&t("body").children().off("mouseover",null,t.noop),this._activeTrigger[y]=!1,this._activeTrigger[T]=!1,this._activeTrigger[E]=!1,P.supportsTransitionEnd()&&t(this.tip).hasClass(g)?t(i).one(P.TRANSITION_END,r).emulateTransitionEnd(150):r(),this._hoverState="")},I.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},I.isWithContent=function(){return Boolean(this.getTitle())},I.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-tooltip-"+e)},I.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},I.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(m),this.getTitle()),e.removeClass(g+" "+p)},I.setElementContent=function(e,n){var i=this.config.html;"object"==typeof n&&(n.nodeType||n.jquery)?i?t(n).parent().is(e)||e.empty().append(n):e.text(t(n).text()):e[i?"html":"text"](n)},I.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},I._getAttachment=function(t){return c[t.toUpperCase()]},I._setListeners=function(){var e=this;this.config.trigger.split(" ").forEach(function(n){if("click"===n)t(e.element).on(e.constructor.Event.CLICK,e.config.selector,function(t){return e.toggle(t)});else if(n!==C){var i=n===E?e.constructor.Event.MOUSEENTER:e.constructor.Event.FOCUSIN,s=n===E?e.constructor.Event.MOUSELEAVE:e.constructor.Event.FOCUSOUT;t(e.element).on(i,e.config.selector,function(t){return e._enter(t)}).on(s,e.config.selector,function(t){return e._leave(t)})}t(e.element).closest(".modal").on("hide.bs.modal",function(){return e.hide()})}),this.config.selector?this.config=r({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},I._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},I._enter=function(e,n){var i=this.constructor.DATA_KEY;(n=n||t(e.currentTarget).data(i))||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusin"===e.type?T:E]=!0),t(n.getTipElement()).hasClass(p)||n._hoverState===f?n._hoverState=f:(clearTimeout(n._timeout),n._hoverState=f,n.config.delay&&n.config.delay.show?n._timeout=setTimeout(function(){n._hoverState===f&&n.show()},n.config.delay.show):n.show())},I._leave=function(e,n){var i=this.constructor.DATA_KEY;(n=n||t(e.currentTarget).data(i))||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusout"===e.type?T:E]=!1),n._isWithActiveTrigger()||(clearTimeout(n._timeout),n._hoverState=d,n.config.delay&&n.config.delay.hide?n._timeout=setTimeout(function(){n._hoverState===d&&n.hide()},n.config.delay.hide):n.hide())},I._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},I._getConfig=function(n){return"number"==typeof(n=r({},this.constructor.Default,t(this.element).data(),n)).delay&&(n.delay={show:n.delay,hide:n.delay}),"number"==typeof n.title&&(n.title=n.title.toString()),"number"==typeof n.content&&(n.content=n.content.toString()),P.typeCheckConfig(e,n,this.constructor.DefaultType),n},I._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},I._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(l);null!==n&&n.length>0&&e.removeClass(n.join(""))},I._handlePopperPlacementChange=function(t){this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},I._fixTransition=function(){var e=this.getTipElement(),n=this.config.animation;null===e.getAttribute("x-placement")&&(t(e).removeClass(g),this.config.animation=!1,this.hide(),this.show(),this.config.animation=n)},a._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(i),s="object"==typeof e&&e;if((n||!/dispose|hide/.test(e))&&(n||(n=new a(this,s),t(this).data(i,n)),"string"==typeof e)){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}})},s(a,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return u}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return i}},{key:"Event",get:function(){return _}},{key:"EVENT_KEY",get:function(){return o}},{key:"DefaultType",get:function(){return h}}]),a}();return t.fn[e]=I._jQueryInterface,t.fn[e].Constructor=I,t.fn[e].noConflict=function(){return t.fn[e]=a,I._jQueryInterface},I}(e),x=function(t){var e="popover",n="bs.popover",i="."+n,o=t.fn[e],a=new RegExp("(^|\\s)bs-popover\\S+","g"),l=r({},U.Default,{placement:"right",trigger:"click",content:"",template:''}),h=r({},U.DefaultType,{content:"(string|element|function)"}),c="fade",u="show",f=".popover-header",d=".popover-body",_={HIDE:"hide"+i,HIDDEN:"hidden"+i,SHOW:"show"+i,SHOWN:"shown"+i,INSERTED:"inserted"+i,CLICK:"click"+i,FOCUSIN:"focusin"+i,FOCUSOUT:"focusout"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i},g=function(r){var o,g;function p(){return r.apply(this,arguments)||this}g=r,(o=p).prototype=Object.create(g.prototype),o.prototype.constructor=o,o.__proto__=g;var m=p.prototype;return m.isWithContent=function(){return this.getTitle()||this._getContent()},m.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-popover-"+e)},m.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},m.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(f),this.getTitle());var n=this._getContent();"function"==typeof n&&(n=n.call(this.element)),this.setElementContent(e.find(d),n),e.removeClass(c+" "+u)},m._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},m._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(a);null!==n&&n.length>0&&e.removeClass(n.join(""))},p._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),s="object"==typeof e?e:null;if((i||!/destroy|hide/.test(e))&&(i||(i=new p(this,s),t(this).data(n,i)),"string"==typeof e)){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}})},s(p,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return l}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return n}},{key:"Event",get:function(){return _}},{key:"EVENT_KEY",get:function(){return i}},{key:"DefaultType",get:function(){return h}}]),p}(U);return t.fn[e]=g._jQueryInterface,t.fn[e].Constructor=g,t.fn[e].noConflict=function(){return t.fn[e]=o,g._jQueryInterface},g}(e),K=function(t){var e="scrollspy",n="bs.scrollspy",i="."+n,o=t.fn[e],a={offset:10,method:"auto",target:""},l={offset:"number",method:"string",target:"(string|element)"},h={ACTIVATE:"activate"+i,SCROLL:"scroll"+i,LOAD_DATA_API:"load"+i+".data-api"},c="dropdown-item",u="active",f={DATA_SPY:'[data-spy="scroll"]',ACTIVE:".active",NAV_LIST_GROUP:".nav, .list-group",NAV_LINKS:".nav-link",NAV_ITEMS:".nav-item",LIST_ITEMS:".list-group-item",DROPDOWN:".dropdown",DROPDOWN_ITEMS:".dropdown-item",DROPDOWN_TOGGLE:".dropdown-toggle"},d="offset",_="position",g=function(){function o(e,n){var i=this;this._element=e,this._scrollElement="BODY"===e.tagName?window:e,this._config=this._getConfig(n),this._selector=this._config.target+" "+f.NAV_LINKS+","+this._config.target+" "+f.LIST_ITEMS+","+this._config.target+" "+f.DROPDOWN_ITEMS,this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,t(this._scrollElement).on(h.SCROLL,function(t){return i._process(t)}),this.refresh(),this._process()}var g=o.prototype;return g.refresh=function(){var e=this,n=this._scrollElement===this._scrollElement.window?d:_,i="auto"===this._config.method?n:this._config.method,s=i===_?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),t.makeArray(t(this._selector)).map(function(e){var n,r=P.getSelectorFromElement(e);if(r&&(n=t(r)[0]),n){var o=n.getBoundingClientRect();if(o.width||o.height)return[t(n)[i]().top+s,r]}return null}).filter(function(t){return t}).sort(function(t,e){return t[0]-e[0]}).forEach(function(t){e._offsets.push(t[0]),e._targets.push(t[1])})},g.dispose=function(){t.removeData(this._element,n),t(this._scrollElement).off(i),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},g._getConfig=function(n){if("string"!=typeof(n=r({},a,n)).target){var i=t(n.target).attr("id");i||(i=P.getUID(e),t(n.target).attr("id",i)),n.target="#"+i}return P.typeCheckConfig(e,n,l),n},g._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},g._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},g._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},g._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var s=this._offsets.length;s--;){this._activeTarget!==this._targets[s]&&t>=this._offsets[s]&&("undefined"==typeof this._offsets[s+1]||t=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}(e),t.Util=P,t.Alert=L,t.Button=R,t.Carousel=j,t.Collapse=H,t.Dropdown=W,t.Modal=M,t.Popover=x,t.Scrollspy=K,t.Tab=V,t.Tooltip=U,Object.defineProperty(t,"__esModule",{value:!0})}); 7 | //# sourceMappingURL=bootstrap.min.js.map -------------------------------------------------------------------------------- /public/dataTables.bootstrap4.min.css: -------------------------------------------------------------------------------- 1 | table.dataTable{clear:both;margin-top:6px !important;margin-bottom:6px !important;max-width:none !important;border-collapse:separate !important}table.dataTable td,table.dataTable th{-webkit-box-sizing:content-box;box-sizing:content-box}table.dataTable td.dataTables_empty,table.dataTable th.dataTables_empty{text-align:center}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}div.dataTables_wrapper div.dataTables_length label{font-weight:normal;text-align:left;white-space:nowrap}div.dataTables_wrapper div.dataTables_length select{width:75px;display:inline-block}div.dataTables_wrapper div.dataTables_filter{text-align:right}div.dataTables_wrapper div.dataTables_filter label{font-weight:normal;white-space:nowrap;text-align:left}div.dataTables_wrapper div.dataTables_filter input{margin-left:0.5em;display:inline-block;width:auto}div.dataTables_wrapper div.dataTables_info{padding-top:0.85em;white-space:nowrap}div.dataTables_wrapper div.dataTables_paginate{margin:0;white-space:nowrap;text-align:right}div.dataTables_wrapper div.dataTables_paginate ul.pagination{margin:2px 0;white-space:nowrap;justify-content:flex-end}div.dataTables_wrapper div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-26px;text-align:center;padding:1em 0}table.dataTable thead>tr>th.sorting_asc,table.dataTable thead>tr>th.sorting_desc,table.dataTable thead>tr>th.sorting,table.dataTable thead>tr>td.sorting_asc,table.dataTable thead>tr>td.sorting_desc,table.dataTable thead>tr>td.sorting{padding-right:30px}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;position:relative}table.dataTable thead .sorting:before,table.dataTable thead .sorting:after,table.dataTable thead .sorting_asc:before,table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:before,table.dataTable thead .sorting_desc:after,table.dataTable thead .sorting_asc_disabled:before,table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:before,table.dataTable thead .sorting_desc_disabled:after{position:absolute;bottom:0.9em;display:block;opacity:0.3}table.dataTable thead .sorting:before,table.dataTable thead .sorting_asc:before,table.dataTable thead .sorting_desc:before,table.dataTable thead .sorting_asc_disabled:before,table.dataTable thead .sorting_desc_disabled:before{right:1em;content:"\2191"}table.dataTable thead .sorting:after,table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:after,table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{right:0.5em;content:"\2193"}table.dataTable thead .sorting_asc:before,table.dataTable thead .sorting_desc:after{opacity:1}table.dataTable thead .sorting_asc_disabled:before,table.dataTable thead .sorting_desc_disabled:after{opacity:0}div.dataTables_scrollHead table.dataTable{margin-bottom:0 !important}div.dataTables_scrollBody table{border-top:none;margin-top:0 !important;margin-bottom:0 !important}div.dataTables_scrollBody table thead .sorting:after,div.dataTables_scrollBody table thead .sorting_asc:after,div.dataTables_scrollBody table thead .sorting_desc:after{display:none}div.dataTables_scrollBody table tbody tr:first-child th,div.dataTables_scrollBody table tbody tr:first-child td{border-top:none}div.dataTables_scrollFoot>.dataTables_scrollFootInner{box-sizing:content-box}div.dataTables_scrollFoot>.dataTables_scrollFootInner>table{margin-top:0 !important;border-top:none}@media screen and (max-width: 767px){div.dataTables_wrapper div.dataTables_length,div.dataTables_wrapper div.dataTables_filter,div.dataTables_wrapper div.dataTables_info,div.dataTables_wrapper div.dataTables_paginate{text-align:center}}table.dataTable.table-sm>thead>tr>th{padding-right:20px}table.dataTable.table-sm .sorting:before,table.dataTable.table-sm .sorting_asc:before,table.dataTable.table-sm .sorting_desc:before{top:5px;right:0.85em}table.dataTable.table-sm .sorting:after,table.dataTable.table-sm .sorting_asc:after,table.dataTable.table-sm .sorting_desc:after{top:5px}table.table-bordered.dataTable th,table.table-bordered.dataTable td{border-left-width:0}table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable td:last-child,table.table-bordered.dataTable td:last-child{border-right-width:0}table.table-bordered.dataTable tbody th,table.table-bordered.dataTable tbody td{border-bottom-width:0}div.dataTables_scrollHead table.table-bordered{border-bottom-width:0}div.table-responsive>div.dataTables_wrapper>div.row{margin:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:first-child{padding-left:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:last-child{padding-right:0} 2 | -------------------------------------------------------------------------------- /public/dataTables.bootstrap4.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | DataTables Bootstrap 3 integration 3 | ©2011-2015 SpryMedia Ltd - datatables.net/license 4 | */ 5 | (function(b){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(a){return b(a,window,document)}):"object"===typeof exports?module.exports=function(a,d){a||(a=window);if(!d||!d.fn.dataTable)d=require("datatables.net")(a,d).$;return b(d,a,a.document)}:b(jQuery,window,document)})(function(b,a,d,m){var f=b.fn.dataTable;b.extend(!0,f.defaults,{dom:"<'row'<'col-sm-12 col-md-6'l><'col-sm-12 col-md-6'f>><'row'<'col-sm-12'tr>><'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", 6 | renderer:"bootstrap"});b.extend(f.ext.classes,{sWrapper:"dataTables_wrapper container-fluid dt-bootstrap4",sFilterInput:"form-control form-control-sm",sLengthSelect:"form-control form-control-sm",sProcessing:"dataTables_processing card",sPageButton:"paginate_button page-item"});f.ext.renderer.pageButton.bootstrap=function(a,h,r,s,j,n){var o=new f.Api(a),t=a.oClasses,k=a.oLanguage.oPaginate,u=a.oLanguage.oAria.paginate||{},e,g,p=0,q=function(d,f){var l,h,i,c,m=function(a){a.preventDefault();!b(a.currentTarget).hasClass("disabled")&& 7 | o.page()!=a.data.action&&o.page(a.data.action).draw("page")};l=0;for(h=f.length;l",{"class":t.sPageButton+" "+g,id:0===r&& 8 | "string"===typeof c?a.sTableId+"_"+c:null}).append(b("",{href:"#","aria-controls":a.sTableId,"aria-label":u[c],"data-dt-idx":p,tabindex:a.iTabIndex,"class":"page-link"}).html(e)).appendTo(d),a.oApi._fnBindAction(i,{action:c},m),p++)}},i;try{i=b(h).find(d.activeElement).data("dt-idx")}catch(v){}q(b(h).empty().html('
    ').children("ul"),s);i!==m&&b(h).find("[data-dt-idx="+i+"]").focus()};return f}); 9 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-subscription-service-node/42f20f30977a5dc4641fd04ad24e565ce0afaf2d/public/favicon.ico -------------------------------------------------------------------------------- /public/login.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | 6 | body { 7 | display: -ms-flexbox; 8 | display: -webkit-box; 9 | display: flex; 10 | -ms-flex-align: center; 11 | -ms-flex-pack: center; 12 | -webkit-box-align: center; 13 | align-items: center; 14 | -webkit-box-pack: center; 15 | justify-content: center; 16 | padding-top: 40px; 17 | padding-bottom: 40px; 18 | background-color: #f5f5f5; 19 | } 20 | 21 | .form-login { 22 | width: 100%; 23 | max-width: 330px; 24 | padding: 15px; 25 | margin: 0 auto; 26 | } 27 | .form-login .checkbox { 28 | font-weight: 400; 29 | } 30 | .form-login .form-control { 31 | position: relative; 32 | box-sizing: border-box; 33 | height: auto; 34 | padding: 10px; 35 | font-size: 16px; 36 | } 37 | .form-login .form-control:focus { 38 | z-index: 2; 39 | } 40 | .form-login input[type="userId"] { 41 | margin-bottom: -1px; 42 | border-bottom-right-radius: 0; 43 | border-bottom-left-radius: 0; 44 | } 45 | .form-login input[type="password"] { 46 | margin-bottom: 10px; 47 | border-top-left-radius: 0; 48 | border-top-right-radius: 0; 49 | } -------------------------------------------------------------------------------- /public/mustache.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Apache-2.0 3 | */ 4 | (function defineMustache(global,factory){if(typeof exports==="object"&&exports&&typeof exports.nodeName!=="string"){factory(exports)}else if(typeof define==="function"&&define.amd){define(["exports"],factory)}else{global.Mustache={};factory(global.Mustache)}})(this,function mustacheFactory(mustache){var objectToString=Object.prototype.toString;var isArray=Array.isArray||function isArrayPolyfill(object){return objectToString.call(object)==="[object Array]"};function isFunction(object){return typeof object==="function"}function typeStr(obj){return isArray(obj)?"array":typeof obj}function escapeRegExp(string){return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}function hasProperty(obj,propName){return obj!=null&&typeof obj==="object"&&propName in obj}var regExpTest=RegExp.prototype.test;function testRegExp(re,string){return regExpTest.call(re,string)}var nonSpaceRe=/\S/;function isWhitespace(string){return!testRegExp(nonSpaceRe,string)}var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="};function escapeHtml(string){return String(string).replace(/[&<>"'`=\/]/g,function fromEntityMap(s){return entityMap[s]})}var whiteRe=/\s*/;var spaceRe=/\s+/;var equalsRe=/\s*=/;var curlyRe=/\s*\}/;var tagRe=/#|\^|\/|>|\{|&|=|!/;function parseTemplate(template,tags){if(!template)return[];var sections=[];var tokens=[];var spaces=[];var hasTag=false;var nonSpace=false;function stripSpace(){if(hasTag&&!nonSpace){while(spaces.length)delete tokens[spaces.pop()]}else{spaces=[]}hasTag=false;nonSpace=false}var openingTagRe,closingTagRe,closingCurlyRe;function compileTags(tagsToCompile){if(typeof tagsToCompile==="string")tagsToCompile=tagsToCompile.split(spaceRe,2);if(!isArray(tagsToCompile)||tagsToCompile.length!==2)throw new Error("Invalid tags: "+tagsToCompile);openingTagRe=new RegExp(escapeRegExp(tagsToCompile[0])+"\\s*");closingTagRe=new RegExp("\\s*"+escapeRegExp(tagsToCompile[1]));closingCurlyRe=new RegExp("\\s*"+escapeRegExp("}"+tagsToCompile[1]))}compileTags(tags||mustache.tags);var scanner=new Scanner(template);var start,type,value,chr,token,openSection;while(!scanner.eos()){start=scanner.pos;value=scanner.scanUntil(openingTagRe);if(value){for(var i=0,valueLength=value.length;i0?sections[sections.length-1][4]:nestedTokens;break;default:collector.push(token)}}return nestedTokens}function Scanner(string){this.string=string;this.tail=string;this.pos=0}Scanner.prototype.eos=function eos(){return this.tail===""};Scanner.prototype.scan=function scan(re){var match=this.tail.match(re);if(!match||match.index!==0)return"";var string=match[0];this.tail=this.tail.substring(string.length);this.pos+=string.length;return string};Scanner.prototype.scanUntil=function scanUntil(re){var index=this.tail.search(re),match;switch(index){case-1:match=this.tail;this.tail="";break;case 0:match="";break;default:match=this.tail.substring(0,index);this.tail=this.tail.substring(index)}this.pos+=match.length;return match};function Context(view,parentContext){this.view=view;this.cache={".":this.view};this.parent=parentContext}Context.prototype.push=function push(view){return new Context(view,this)};Context.prototype.lookup=function lookup(name){var cache=this.cache;var value;if(cache.hasOwnProperty(name)){value=cache[name]}else{var context=this,names,index,lookupHit=false;while(context){if(name.indexOf(".")>0){value=context.view;names=name.split(".");index=0;while(value!=null&&index")value=this.renderPartial(token,context,partials,originalTemplate);else if(symbol==="&")value=this.unescapedValue(token,context);else if(symbol==="name")value=this.escapedValue(token,context);else if(symbol==="text")value=this.rawValue(token);if(value!==undefined)buffer+=value}return buffer};Writer.prototype.renderSection=function renderSection(token,context,partials,originalTemplate){var self=this;var buffer="";var value=context.lookup(token[1]);function subRender(template){return self.render(template,context,partials)}if(!value)return;if(isArray(value)){for(var j=0,valueLength=value.length;j=o.clientWidth&&i>=o.clientHeight}),l=0i[e]&&!t.escapeWithReference&&(n=_(p[o],i[e]-('right'===e?p.width:p.height))),pe({},o,n)}};return n.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';p=se({},p,s[t](e))}),e.offsets.popper=p,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,i=t.reference,n=e.placement.split('-')[0],r=X,p=-1!==['top','bottom'].indexOf(n),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(i[s])&&(e.offsets.popper[d]=r(i[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var i;if(!F(e.instance.modifiers,'arrow','keepTogether'))return e;var n=o.element;if('string'==typeof n){if(n=e.instance.popper.querySelector(n),!n)return e;}else if(!e.instance.popper.contains(n))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',g=a?'bottom':'right',u=L(n)[l];d[g]-us[g]&&(e.offsets.popper[m]+=d[m]+u-s[g]),e.offsets.popper=c(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f],10),E=parseFloat(w['border'+f+'Width'],10),v=b-e.offsets.popper[m]-y-E;return v=J(_(s[l]-u,v),0),e.arrowElement=n,e.offsets.arrow=(i={},pe(i,m,Math.round(v)),pe(i,h,''),i),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(k(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=y(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement),i=e.placement.split('-')[0],n=x(i),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case le.FLIP:p=[i,n];break;case le.CLOCKWISE:p=q(i);break;case le.COUNTERCLOCKWISE:p=q(i,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(i!==s||p.length===d+1)return e;i=e.placement.split('-')[0],n=x(i);var a=e.offsets.popper,l=e.offsets.reference,f=X,m='left'===i&&f(a.right)>f(l.left)||'right'===i&&f(a.left)f(l.top)||'bottom'===i&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===i&&h||'right'===i&&c||'top'===i&&g||'bottom'===i&&u,w=-1!==['top','bottom'].indexOf(i),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u);(m||b||y)&&(e.flipped=!0,(m||b)&&(i=p[d+1]),y&&(r=K(r)),e.placement=i+(r?'-'+r:''),e.offsets.popper=se({},e.offsets.popper,S(e.instance.popper,e.offsets.reference,e.placement)),e=C(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport'},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],i=e.offsets,n=i.popper,r=i.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return n[p?'left':'top']=r[o]-(s?n[p?'width':'height']:0),e.placement=x(t),e.offsets.popper=c(n),e}},hide:{order:800,enabled:!0,fn:function(e){if(!F(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=T(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.right 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Entitlements 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
    User IdNameEntitlementvalidFromvalidToEnteredEntered By
    User IdNameEntitlementvalidFromvalidToEnteredEntered By
    45 | 46 | 47 | 48 | 49 | 94 | 95 | -------------------------------------------------------------------------------- /view/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Home 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
    Loading...
    21 | 60 | 61 | 62 | 63 | 64 | 65 | 85 | -------------------------------------------------------------------------------- /view/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Login 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 36 | 37 | 38 | 39 | 40 | 69 | 70 | --------------------------------------------------------------------------------