├── .github └── PULL_REQUEST_TEMPLATE.md ├── LICENSE ├── NOTICE ├── README.md ├── artifacts ├── edge-auth.zip ├── private.zip └── public.zip ├── images ├── access-denied-401.png ├── auth-at-edge-architecture.png ├── cloudformation-cloudfrontdistro.png ├── cloudformation-edgefunction.png ├── cloudformation-mainurl.png ├── cloudformation-outputs.png ├── cloudfront-behavior-edit.png ├── cloudfront-behavior-private.png ├── cloudfront-console.png ├── cloudfront-events-that-trigger-lambda-functions.png ├── cognito-custom-ui.png ├── lambda-arn.png ├── lambda-console.png ├── lambda-list.png ├── lambda-publish.png ├── launch-stack.png ├── private-content-retrieved.png ├── private-content-viewer-with-jwt.png └── private-content-viewer.png ├── node └── lambda-edge-function │ ├── index.js │ ├── template.yaml │ └── test-event.json ├── templates ├── cognito-user-pool.template ├── edge-auth.template ├── lambda-at-edge.template └── populate-s3-bucket.template ├── www-private └── top-secret.json └── www ├── index.html └── js ├── amazon-cognito-identity.min.js ├── aws-cognito-sdk.min.js └── config.js /.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 my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Authorization Lambda At Edge 2 | Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Authorization Lambda At Edge 2 | 3 | Code demonstrates authorization with Lambda@Edge and JSON Web Tokens (JWTs) 4 | 5 | ## License 6 | 7 | This library is licensed under the Apache 2.0 License. 8 | -------------------------------------------------------------------------------- /artifacts/edge-auth.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/artifacts/edge-auth.zip -------------------------------------------------------------------------------- /artifacts/private.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/artifacts/private.zip -------------------------------------------------------------------------------- /artifacts/public.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/artifacts/public.zip -------------------------------------------------------------------------------- /images/access-denied-401.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/images/access-denied-401.png -------------------------------------------------------------------------------- /images/auth-at-edge-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/images/auth-at-edge-architecture.png -------------------------------------------------------------------------------- /images/cloudformation-cloudfrontdistro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/images/cloudformation-cloudfrontdistro.png -------------------------------------------------------------------------------- /images/cloudformation-edgefunction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/images/cloudformation-edgefunction.png -------------------------------------------------------------------------------- /images/cloudformation-mainurl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/images/cloudformation-mainurl.png -------------------------------------------------------------------------------- /images/cloudformation-outputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/images/cloudformation-outputs.png -------------------------------------------------------------------------------- /images/cloudfront-behavior-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/images/cloudfront-behavior-edit.png -------------------------------------------------------------------------------- /images/cloudfront-behavior-private.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/images/cloudfront-behavior-private.png -------------------------------------------------------------------------------- /images/cloudfront-console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/images/cloudfront-console.png -------------------------------------------------------------------------------- /images/cloudfront-events-that-trigger-lambda-functions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/images/cloudfront-events-that-trigger-lambda-functions.png -------------------------------------------------------------------------------- /images/cognito-custom-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/images/cognito-custom-ui.png -------------------------------------------------------------------------------- /images/lambda-arn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/images/lambda-arn.png -------------------------------------------------------------------------------- /images/lambda-console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/images/lambda-console.png -------------------------------------------------------------------------------- /images/lambda-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/images/lambda-list.png -------------------------------------------------------------------------------- /images/lambda-publish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/images/lambda-publish.png -------------------------------------------------------------------------------- /images/launch-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/images/launch-stack.png -------------------------------------------------------------------------------- /images/private-content-retrieved.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/images/private-content-retrieved.png -------------------------------------------------------------------------------- /images/private-content-viewer-with-jwt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/images/private-content-viewer-with-jwt.png -------------------------------------------------------------------------------- /images/private-content-viewer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/authorization-lambda-at-edge/ea8a4254e08fc52bede2817cddd735815cb5596d/images/private-content-viewer.png -------------------------------------------------------------------------------- /node/lambda-edge-function/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var jwt = require('jsonwebtoken'); 3 | var jwkToPem = require('jwk-to-pem'); 4 | 5 | /* 6 | TO DO: 7 | copy values from CloudFormation outputs into USERPOOLID and JWKS variables 8 | */ 9 | 10 | var USERPOOLID = '##USERPOOLID##'; 11 | var JWKS = '##JWKS##'; 12 | 13 | /* 14 | verify values above 15 | */ 16 | 17 | 18 | 19 | var region = 'us-east-1'; 20 | var iss = 'https://cognito-idp.' + region + '.amazonaws.com/' + USERPOOLID; 21 | var pems; 22 | 23 | pems = {}; 24 | var keys = JSON.parse(JWKS).keys; 25 | for(var i = 0; i < keys.length; i++) { 26 | //Convert each key to PEM 27 | var key_id = keys[i].kid; 28 | var modulus = keys[i].n; 29 | var exponent = keys[i].e; 30 | var key_type = keys[i].kty; 31 | var jwk = { kty: key_type, n: modulus, e: exponent}; 32 | var pem = jwkToPem(jwk); 33 | pems[key_id] = pem; 34 | } 35 | 36 | const response401 = { 37 | status: '401', 38 | statusDescription: 'Unauthorized' 39 | }; 40 | 41 | exports.handler = (event, context, callback) => { 42 | const cfrequest = event.Records[0].cf.request; 43 | const headers = cfrequest.headers; 44 | console.log('getting started'); 45 | console.log('USERPOOLID=' + USERPOOLID); 46 | console.log('region=' + region); 47 | console.log('pems=' + pems); 48 | 49 | //Fail if no authorization header found 50 | if(!headers.authorization) { 51 | console.log("no auth header"); 52 | callback(null, response401); 53 | return false; 54 | } 55 | 56 | //strip out "Bearer " to extract JWT token only 57 | var jwtToken = headers.authorization[0].value.slice(7); 58 | console.log('jwtToken=' + jwtToken); 59 | 60 | //Fail if the token is not jwt 61 | var decodedJwt = jwt.decode(jwtToken, {complete: true}); 62 | if (!decodedJwt) { 63 | console.log("Not a valid JWT token"); 64 | callback(null, response401); 65 | return false; 66 | } 67 | 68 | //Fail if token is not from your UserPool 69 | if (decodedJwt.payload.iss != iss) { 70 | console.log("invalid issuer"); 71 | callback(null, response401); 72 | return false; 73 | } 74 | 75 | //Reject the jwt if it's not an 'Access Token' 76 | if (decodedJwt.payload.token_use != 'access') { 77 | console.log("Not an access token"); 78 | callback(null, response401); 79 | return false; 80 | } 81 | 82 | //Get the kid from the token and retrieve corresponding PEM 83 | var kid = decodedJwt.header.kid; 84 | var pem = pems[kid]; 85 | if (!pem) { 86 | console.log('Invalid access token'); 87 | callback(null, response401); 88 | return false; 89 | } 90 | 91 | //Verify the signature of the JWT token to ensure it's really coming from your User Pool 92 | jwt.verify(jwtToken, pem, { issuer: iss }, function(err, payload) { 93 | if(err) { 94 | console.log('Token failed verification'); 95 | callback(null, response401); 96 | return false; 97 | } else { 98 | //Valid token. 99 | console.log('Successful verification'); 100 | //remove authorization header 101 | delete cfrequest.headers.authorization; 102 | //CloudFront can proceed to fetch the content from origin 103 | callback(null, cfrequest); 104 | return true; 105 | } 106 | }); 107 | }; 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /node/lambda-edge-function/template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | Resources: 4 | EdgeAuthFunction: 5 | Type: AWS::Serverless::Function 6 | Properties: 7 | Handler: index.handler 8 | Runtime: nodejs14.x 9 | Environment: 10 | Variables: 11 | REGION: us-east-1 12 | USERPOOLID: us-east-1_jThCsaPez 13 | -------------------------------------------------------------------------------- /node/lambda-edge-function/test-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "Records": [ 3 | { 4 | "cf": { 5 | "request": { 6 | "headers": { 7 | "host": [ 8 | { 9 | "value": "d123.cf.net", 10 | "key": "Host" 11 | } 12 | ], 13 | "authorization": [ 14 | { 15 | "value":"Bearer eyJraWQiOiJzSFprWjhTN3k5ZVwvODg2MEVXeTFMVU1HaGZWUjlGNlNtYzN5N0o0dnRCdz0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiI0NTFhM2NlMi05Y2U0LTRlMzAtOWY2ZS1iNzJiOGEzNDE5NGUiLCJ0b2tlbl91c2UiOiJhY2Nlc3MiLCJzY29wZSI6ImF3cy5jb2duaXRvLnNpZ25pbi51c2VyLmFkbWluIiwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tXC91cy1lYXN0LTFfalRoQ3NhUGV6IiwiZXhwIjoxNTA1NjYwMTY2LCJpYXQiOjE1MDU2NTY1NjYsInZlcnNpb24iOjIsImp0aSI6ImI1Yzg5MmIyLTE0NDEtNDQ0OS1hYTRiLTdjYmJmNzg3OTMzNiIsImNsaWVudF9pZCI6IjVoN2VkNGRzdmdzdWdyNWwxZ2RsMzJlcGg3IiwidXNlcm5hbWUiOiJqb2UifQ.V5OqX61MA_hZ-DfxkK-dLTfusIde8X_joqzUmDiAnWnaYk5L2jKgEUNKdXjSnIXL7kgSFx1rXWIGQvF2x4CpCJ7D_u_Ux1aEj-GeM1MyYra8EPgDmt0Eu62UUYRtZ0uUi-EtDWImsZ4cfS4jCeMfBniph4I2GKfjgF2NJoSU9KtfWTaAk7XHv4yML6Q8w_iyBGZDmDYjb7vx6vCXJoc5KnAE87T1MBQByLNhdkhgF8_0YAuSJk9E0Gj6sEbdVoF7dsCO3UkcTjuxZCl3pABorhtbI1HQJk91GiK7Ca4Y_UwV2WqM_eW9qaNKFI6y1MgsRP612uLLhENb8BGc28QCNA", 16 | "key": "Authorization" 17 | } 18 | ] 19 | }, 20 | "clientIp": "2001:cdba::3257:9652", 21 | "uri": "/experiment-pixel.jpg", 22 | "method": "GET" 23 | }, 24 | "config": { 25 | "distributionId": "EXAMPLE" 26 | } 27 | } 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /templates/cognito-user-pool.template: -------------------------------------------------------------------------------- 1 | Description: > 2 | Amazon Cognito User Pool and User Pool Client deployment for Edge Authentication sample stack 3 | You will be billed for the AWS resources used if you create a stack from this template. **NOTICE** Copyright 2017 Amazon.com, Inc. or its affiliates. 4 | All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 5 | http://www.apache.org/licenses/LICENSE-2.0 or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 6 | See the License for the specific language governing permissions and limitations under the License. 7 | 8 | Parameters: 9 | 10 | BaseUrl: 11 | Type: String 12 | Environment: 13 | Type: String 14 | 15 | Resources: 16 | 17 | LambdaExecutionRole: 18 | Type: AWS::IAM::Role 19 | Properties: 20 | AssumeRolePolicyDocument: 21 | Version: '2012-10-17' 22 | Statement: 23 | - Effect: Allow 24 | Principal: 25 | Service: 26 | - lambda.amazonaws.com 27 | Action: 28 | - sts:AssumeRole 29 | Path: "/" 30 | Policies: 31 | - PolicyName: root 32 | PolicyDocument: 33 | Version: '2012-10-17' 34 | Statement: 35 | - Effect: Allow 36 | Action: 37 | - logs:* 38 | Resource: arn:aws:logs:*:*:* 39 | - Effect: Allow 40 | Action: 41 | - cognito-idp:CreateUserPool 42 | - cognito-idp:CreateUserPoolClient 43 | - cognito-idp:CreateUserPoolDomain 44 | Resource: 45 | - '*' 46 | 47 | 48 | CreateUserPoolAndClientFunction: 49 | Type: AWS::Lambda::Function 50 | Properties: 51 | Handler: index.handler 52 | Role: !GetAtt LambdaExecutionRole.Arn 53 | Runtime: python3.6 54 | Timeout: 60 55 | Code: 56 | ZipFile: | 57 | import boto3 58 | import cfnresponse 59 | def handler(event, context): 60 | responseData = {} 61 | print (str(event)) 62 | try: 63 | if event['RequestType'] == 'Create': 64 | Environment = event['ResourceProperties']['Environment'] 65 | BaseUrl = event['ResourceProperties']['BaseUrl'] 66 | client = boto3.client('cognito-idp') 67 | response = client.create_user_pool( 68 | PoolName=Environment+'-userpool', 69 | AutoVerifiedAttributes=['email'], 70 | Schema=[ 71 | { 72 | 'Name': 'email', 73 | 'Required': True 74 | } 75 | ] 76 | ) 77 | CreatedUserPoolId = response['UserPool']['Id'] 78 | response = client.create_user_pool_client( 79 | UserPoolId=CreatedUserPoolId, 80 | ClientName=Environment + '-client', 81 | ReadAttributes=[ 82 | 'address', 'birthdate', 'email', 'email_verified', 'family_name', 'gender', 'given_name', 'locale', 'middle_name', 'name', 'nickname', 'phone_number', 'phone_number_verified', 'picture', 'preferred_username', 'profile', 'updated_at', 'website', 'zoneinfo' 83 | ], 84 | WriteAttributes=[ 85 | 'address', 'birthdate', 'email', 'family_name', 'gender', 'given_name', 'locale', 'middle_name', 'name', 'nickname', 'phone_number', 'picture', 'preferred_username', 'profile', 'updated_at', 'website', 'zoneinfo' 86 | ], 87 | SupportedIdentityProviders=['COGNITO'], 88 | CallbackURLs=['https://' + BaseUrl + '/index.html'], 89 | LogoutURLs=['https://' + BaseUrl + '/index.html'], 90 | AllowedOAuthFlows=['implicit','code'], 91 | AllowedOAuthScopes=['aws.cognito.signin.user.admin','openid'], 92 | AllowedOAuthFlowsUserPoolClient=True 93 | ) 94 | CreatedClientId = response['UserPoolClient']['ClientId'] 95 | response = client.create_user_pool_domain( 96 | Domain = str(CreatedClientId), 97 | UserPoolId = CreatedUserPoolId 98 | ) 99 | responseData['UserPoolId'] = CreatedUserPoolId 100 | responseData['ClientId'] = CreatedClientId 101 | print("SUCCESS, ResponseData=" + str(responseData)) 102 | cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID") 103 | else: 104 | print("SUCCESS - operation not Create, ResponseData=" + str(responseData)) 105 | cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID") 106 | except Exception as e: 107 | responseData['Error'] = str(e) 108 | cfnresponse.send(event, context, cfnresponse.FAILED, responseData, "CustomResourcePhysicalID") 109 | print("FAILED ERROR: " + responseData['Error']) 110 | 111 | CreateUserPoolAndClient: 112 | Type: Custom::CreateUserPoolAndClient 113 | DeletionPolicy: Retain 114 | Properties: 115 | ServiceToken: !GetAtt CreateUserPoolAndClientFunction.Arn 116 | BaseUrl: !Ref BaseUrl 117 | Environment: !Ref Environment 118 | 119 | 120 | Outputs: 121 | UserPoolId: 122 | Description: generated ID for this UserPool 123 | Value: !GetAtt CreateUserPoolAndClient.UserPoolId 124 | ClientId: 125 | Description: Amazon Cognito user pool client ID 126 | Value: !GetAtt CreateUserPoolAndClient.ClientId 127 | -------------------------------------------------------------------------------- /templates/edge-auth.template: -------------------------------------------------------------------------------- 1 | Description: > 2 | Authorization@Edge - Using Lambda@Edge and JSON Web Tokens to Enhance Web Application Security sample template 3 | You will be billed for the AWS resources used if you create a stack from this template. 4 | **NOTICE** Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | Licensed under the Apache License, Version 2.0 (the "License"). 6 | You may not use this file except in compliance with the License. 7 | A copy of the License is located at http://www.apache.org/licenses/LICENSE-2.0 or in the "license" file accompanying this file. 8 | This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 9 | either express or implied. See the License for the specific language governing permissions and limitations under the License. 10 | 11 | Parameters: 12 | ArtifactsBucket: 13 | Description: S3 bucket with artifact files (Lambda functions, html files, JS code, etc.) 14 | Type: String 15 | Default: cloudfront-blog-resources 16 | AllowedPattern: ^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$ 17 | ConstraintDescription: ArtifactsBucket S3 bucket name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-). 18 | ArtifactsPrefix: 19 | Description: Path in the S3 bucket containing artifact files 20 | Type: String 21 | Default: authorization-lambda-at-edge/ 22 | AllowedPattern: ^[0-9a-zA-Z-/]*$ 23 | ConstraintDescription: TemplatesPrefix key prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slash (/). 24 | TemplatesBucket: 25 | Description: S3 bucket with CloudFormation templates for nested stacks 26 | Type: String 27 | Default: cloudfront-blog-resources 28 | AllowedPattern: ^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$ 29 | ConstraintDescription: TemplatesBucket S3 bucket name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-). 30 | TemplatesPrefix: 31 | Description: Path in the S3 bucket containing CloudFormation templates 32 | Type: String 33 | Default: authorization-lambda-at-edge/ 34 | AllowedPattern: ^[0-9a-zA-Z-/]*$ 35 | ConstraintDescription: TemplatesPrefix key prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slash (/). 36 | 37 | Resources: 38 | 39 | 40 | CFOriginAccessIdentity: 41 | Type: AWS::CloudFront::CloudFrontOriginAccessIdentity 42 | Properties: 43 | CloudFrontOriginAccessIdentityConfig: 44 | Comment: CloudFrontOAI 45 | 46 | S3BucketPrivate: 47 | Type: AWS::S3::Bucket 48 | DeletionPolicy: Retain 49 | Properties: 50 | BucketName: !Sub '${AWS::StackName}-origin-private-${AWS::AccountId}' 51 | CorsConfiguration: 52 | CorsRules: 53 | - 54 | AllowedOrigins: 55 | - 'http*' 56 | AllowedMethods: 57 | - HEAD 58 | - GET 59 | - PUT 60 | - POST 61 | - DELETE 62 | AllowedHeaders: 63 | - '*' 64 | ExposedHeaders: 65 | - ETag 66 | - x-amz-meta-custom-header 67 | 68 | S3BucketPolicyPrivate: 69 | Type: AWS::S3::BucketPolicy 70 | Properties: 71 | Bucket: !Ref S3BucketPrivate 72 | PolicyDocument: 73 | Statement: 74 | - 75 | Effect: Allow 76 | Action: s3:GetObject 77 | Principal: 78 | CanonicalUser: !GetAtt CFOriginAccessIdentity.S3CanonicalUserId 79 | Resource: !Sub 'arn:aws:s3:::${S3BucketPrivate}/*' 80 | 81 | 82 | S3BucketPublic: 83 | Type: AWS::S3::Bucket 84 | DeletionPolicy: Retain 85 | Properties: 86 | BucketName: !Sub '${AWS::StackName}-origin-public-${AWS::AccountId}' 87 | CorsConfiguration: 88 | CorsRules: 89 | - 90 | AllowedOrigins: 91 | - 'http*' 92 | AllowedMethods: 93 | - HEAD 94 | - GET 95 | - PUT 96 | - POST 97 | - DELETE 98 | AllowedHeaders: 99 | - '*' 100 | ExposedHeaders: 101 | - ETag 102 | - x-amz-meta-custom-header 103 | 104 | S3BucketPolicyPublic: 105 | Type: AWS::S3::BucketPolicy 106 | Properties: 107 | Bucket: !Ref S3BucketPublic 108 | PolicyDocument: 109 | Statement: 110 | - 111 | Effect: Allow 112 | Action: s3:GetObject 113 | Principal: 114 | CanonicalUser: !GetAtt CFOriginAccessIdentity.S3CanonicalUserId 115 | Resource: !Sub 'arn:aws:s3:::${S3BucketPublic}/*' 116 | 117 | CFDistribution: 118 | Type: AWS::CloudFront::Distribution 119 | Properties: 120 | DistributionConfig: 121 | Enabled: 'true' 122 | Comment : Auth at Edge Test Distro 123 | DefaultRootObject: index.html 124 | Origins: 125 | - 126 | Id: S3OriginPrivate 127 | DomainName: !GetAtt S3BucketPrivate.DomainName 128 | S3OriginConfig: 129 | OriginAccessIdentity: !Sub origin-access-identity/cloudfront/${CFOriginAccessIdentity} 130 | - 131 | Id: S3OriginPublic 132 | DomainName: !GetAtt S3BucketPublic.DomainName 133 | S3OriginConfig: 134 | OriginAccessIdentity: !Sub origin-access-identity/cloudfront/${CFOriginAccessIdentity} 135 | DefaultCacheBehavior: 136 | TargetOriginId: S3OriginPublic 137 | ForwardedValues: 138 | QueryString: 'false' 139 | Headers: 140 | - Origin 141 | Cookies: 142 | Forward: none 143 | ViewerProtocolPolicy: redirect-to-https 144 | CacheBehaviors: 145 | - PathPattern: 'private/*' 146 | TargetOriginId: S3OriginPrivate 147 | ForwardedValues: 148 | QueryString: 'false' 149 | Headers: 150 | - Origin 151 | Cookies: 152 | Forward: none 153 | ViewerProtocolPolicy: redirect-to-https 154 | - PathPattern: 'public/*' 155 | TargetOriginId: S3OriginPublic 156 | ForwardedValues: 157 | QueryString: 'false' 158 | Headers: 159 | - Origin 160 | Cookies: 161 | Forward: none 162 | ViewerProtocolPolicy: redirect-to-https 163 | 164 | CognitoUserPool: 165 | Type: AWS::CloudFormation::Stack 166 | Properties: 167 | TemplateURL: !Sub https://s3.amazonaws.com/${TemplatesBucket}/${TemplatesPrefix}cognito-user-pool.template 168 | Parameters: 169 | BaseUrl: !GetAtt CFDistribution.DomainName 170 | Environment: !Ref AWS::StackName 171 | 172 | PopulateS3Buckets: 173 | Type: AWS::CloudFormation::Stack 174 | Properties: 175 | TemplateURL: !Sub https://s3.amazonaws.com/${TemplatesBucket}/${TemplatesPrefix}populate-s3-bucket.template 176 | Parameters: 177 | UserPoolId: !GetAtt CognitoUserPool.Outputs.UserPoolId 178 | ClientId: !GetAtt CognitoUserPool.Outputs.ClientId 179 | BaseUrl: !GetAtt CFDistribution.DomainName 180 | PublicBucket: !Ref S3BucketPublic 181 | PrivateBucket: !Ref S3BucketPrivate 182 | PublicPrefix: '' 183 | PrivatePrefix: 'private/' 184 | PublicContentUrl: !Sub 'http://${ArtifactsBucket}.s3.amazonaws.com/${ArtifactsPrefix}public.zip' 185 | PrivateContentUrl: !Sub 'http://${ArtifactsBucket}.s3.amazonaws.com/${ArtifactsPrefix}private.zip' 186 | ConfigFile: 'js/config.js' 187 | 188 | LambdaAtEdge: 189 | Type: AWS::CloudFormation::Stack 190 | Properties: 191 | TemplateURL: !Sub https://s3.amazonaws.com/${TemplatesBucket}/${TemplatesPrefix}lambda-at-edge.template 192 | Parameters: 193 | UserPoolId: !GetAtt CognitoUserPool.Outputs.UserPoolId 194 | PublicBucket: !Ref S3BucketPublic 195 | PublicPrefix: 'lambda-at-edge/' 196 | EdgeAuthFunctionUrl: !Sub 'http://${ArtifactsBucket}.s3.amazonaws.com/${ArtifactsPrefix}edge-auth.zip' 197 | 198 | 199 | Outputs: 200 | PrivateS3BucketName: 201 | Description: Private S3 Bucket Name 202 | Value: !Ref S3BucketPrivate 203 | PublicS3BucketName: 204 | Description: Public S3 Bucket Name 205 | Value: !Ref S3BucketPublic 206 | CognitoUserPool: 207 | Description: User pool contains user credentials. For cleanup, delete Domain Name first, then delete the user pool. 208 | Value: !Sub https://console.aws.amazon.com/cognito/users/?region=${AWS::Region}#/pool/${CognitoUserPool.Outputs.UserPoolId} 209 | CloudFrontDistribution: 210 | Description: Edit CloudFront distribution settings 211 | Value: !Sub 'https://console.aws.amazon.com/cloudfront/home?region=${AWS::Region}#distribution-settings:${CFDistribution}' 212 | LambdaAtEdgeFunction: 213 | Description: Edit Lambda at Edge and publish 214 | Value: !Sub 'https://console.aws.amazon.com/lambda/home?region=${AWS::Region}#/functions/${LambdaAtEdge.Outputs.EdgeAuthFunction}' 215 | MAINURL: 216 | Description: Click here to test your Authorization @Edge stack 217 | Value: !Sub 'https://${CFDistribution.DomainName}/index.html' 218 | -------------------------------------------------------------------------------- /templates/lambda-at-edge.template: -------------------------------------------------------------------------------- 1 | Description: > 2 | Lambda at edge function and a custom resource that configures the function 3 | You will be billed for the AWS resources used if you create a stack from this template. **NOTICE** Copyright 2017 Amazon.com, Inc. or its affiliates. 4 | All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 5 | http://www.apache.org/licenses/LICENSE-2.0 or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 6 | See the License for the specific language governing permissions and limitations under the License. 7 | 8 | Parameters: 9 | 10 | UserPoolId: 11 | Type: String 12 | PublicBucket: 13 | Type: String 14 | PublicPrefix: 15 | Type: String 16 | EdgeAuthFunctionUrl: 17 | Type: String 18 | 19 | 20 | Resources: 21 | 22 | UpdateConfigExecutionRole: 23 | Type: AWS::IAM::Role 24 | Properties: 25 | AssumeRolePolicyDocument: 26 | Version: '2012-10-17' 27 | Statement: 28 | - Effect: Allow 29 | Principal: 30 | Service: 31 | - lambda.amazonaws.com 32 | Action: 33 | - sts:AssumeRole 34 | Path: "/" 35 | Policies: 36 | - PolicyName: root 37 | PolicyDocument: 38 | Version: '2012-10-17' 39 | Statement: 40 | - Effect: Allow 41 | Action: 42 | - logs:CreateLogGroup 43 | - logs:CreateLogStream 44 | - logs:PutLogEvents 45 | Resource: arn:aws:logs:*:*:* 46 | - Effect: Allow 47 | Action: 48 | - s3:PutObject 49 | - s3:PutObjectAcl 50 | Resource: 51 | - !Sub 'arn:aws:s3:::${PublicBucket}/*' 52 | 53 | UpdateConfigFunction: 54 | Type: AWS::Lambda::Function 55 | Properties: 56 | Handler: index.handler 57 | Role: !GetAtt UpdateConfigExecutionRole.Arn 58 | Runtime: python3.6 59 | Timeout: 60 60 | MemorySize: 1536 61 | Code: 62 | ZipFile: | 63 | import cfnresponse 64 | import os 65 | import boto3 66 | import json 67 | from io import BytesIO 68 | from urllib.request import urlopen 69 | import zipfile 70 | from pathlib import Path 71 | def handler(event, context): 72 | print (str(event)) 73 | responseData = {} 74 | try: 75 | if (event['RequestType'] == 'Create') or (event['RequestType'] == 'Update'): 76 | DestinationBucket = event['ResourceProperties']['DestinationBucket'] 77 | DestinationPrefix = event['ResourceProperties']['DestinationPrefix'] 78 | UserPoolId = event['ResourceProperties']['UserPoolId'] 79 | AWSRegion = event['ResourceProperties']['AWSRegion'] 80 | SourceUrl = event['ResourceProperties']['SourceUrl'] 81 | print("get jwks value") 82 | jwksUrl = 'https://cognito-idp.' + AWSRegion + '.amazonaws.com/' + UserPoolId + '/.well-known/jwks.json' 83 | with urlopen(jwksUrl) as httpresponse: 84 | jwks = str( httpresponse.read() ) 85 | jwks = jwks.replace('b\'{', '{') 86 | jwks = jwks.replace('}\'', '}') 87 | print("unzip source Zip to local directory") 88 | baseDir = '/tmp/' + DestinationBucket + '/' + DestinationPrefix 89 | print("baseDir=" + baseDir) 90 | with urlopen(SourceUrl) as zipresp: 91 | with zipfile.ZipFile(BytesIO(zipresp.read())) as zfile: 92 | zfile.extractall(baseDir) 93 | print("read index.js") 94 | indexjs = Path(baseDir + 'index.js').read_text() 95 | indexjs = indexjs.replace('##JWKS##', jwks) 96 | indexjs = indexjs.replace('##USERPOOLID##', UserPoolId) 97 | print("save index.js back to disk") 98 | with open(baseDir + 'index.js',"w") as w: 99 | w.write(indexjs) 100 | print("zip up the directory") 101 | zipHandle = zipfile.ZipFile('/tmp/edge-auth.zip', 'w', compression = zipfile.ZIP_DEFLATED) 102 | addDirToZip(zipHandle, baseDir, baseDir) 103 | zipHandle.close() 104 | print("upload to S3") 105 | s3 = boto3.resource('s3') 106 | s3.meta.client.upload_file('/tmp/edge-auth.zip', DestinationBucket, DestinationPrefix+'edge-auth.zip', ExtraArgs={'ACL': 'public-read'} ) 107 | responseData['Status'] = 'SUCCESS' 108 | cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID") 109 | print ('SUCCESS') 110 | else: 111 | print("SUCCESS - operation not Create or Update, ResponseData=" + str(responseData)) 112 | cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID") 113 | except Exception as e: 114 | responseData['Error'] = str(e) 115 | cfnresponse.send(event, context, cfnresponse.FAILED, responseData, "CustomResourcePhysicalID") 116 | print("FAILED ERROR: " + responseData['Error']) 117 | def addDirToZip(zipHandle, path, basePath=""): 118 | basePath = basePath.rstrip("\\/") + "" 119 | basePath = basePath.rstrip("\\/") 120 | for root, dirs, files in os.walk(path): 121 | zipHandle.write(os.path.join(root, ".")) 122 | for file in files: 123 | filePath = os.path.join(root, file) 124 | inZipPath = filePath.replace(basePath, "", 1).lstrip("\\/") 125 | zipHandle.write(filePath, inZipPath) 126 | 127 | 128 | UpdateConfigCustom: 129 | Type: Custom::UpdateConfigCustom 130 | DeletionPolicy: Retain 131 | Properties: 132 | ServiceToken: !GetAtt UpdateConfigFunction.Arn 133 | DestinationBucket: !Ref PublicBucket 134 | DestinationPrefix: !Ref PublicPrefix 135 | AWSRegion: !Ref "AWS::Region" 136 | UserPoolId: !Ref UserPoolId 137 | SourceUrl: !Ref EdgeAuthFunctionUrl 138 | 139 | EdgeAuthExecutionRole: 140 | Type: AWS::IAM::Role 141 | Properties: 142 | AssumeRolePolicyDocument: 143 | Version: '2012-10-17' 144 | Statement: 145 | - Effect: Allow 146 | Principal: 147 | Service: 148 | - lambda.amazonaws.com 149 | Action: 150 | - sts:AssumeRole 151 | - Effect: Allow 152 | Principal: 153 | Service: 154 | - edgelambda.amazonaws.com 155 | Action: 156 | - sts:AssumeRole 157 | Path: "/" 158 | Policies: 159 | - PolicyName: root 160 | PolicyDocument: 161 | Version: '2012-10-17' 162 | Statement: 163 | - Effect: Allow 164 | Action: 165 | - logs:CreateLogGroup 166 | - logs:CreateLogStream 167 | - logs:PutLogEvents 168 | Resource: arn:aws:logs:*:*:* 169 | 170 | 171 | EdgeAuthFunction: 172 | Type: AWS::Lambda::Function 173 | DependsOn: UpdateConfigCustom 174 | DeletionPolicy: Retain 175 | Properties: 176 | Handler: index.handler 177 | Role: !GetAtt EdgeAuthExecutionRole.Arn 178 | Runtime: nodejs14.x 179 | Timeout: 1 180 | MemorySize: 128 181 | Code: 182 | S3Bucket: !Ref PublicBucket 183 | S3Key: !Sub ${PublicPrefix}edge-auth.zip 184 | 185 | Outputs: 186 | EdgeAuthFunction: 187 | Description: Reference to the Lambda function 188 | Value: !Ref EdgeAuthFunction 189 | 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /templates/populate-s3-bucket.template: -------------------------------------------------------------------------------- 1 | Description: > 2 | Copies static content from zip files into S3 buckets. Writes out JavaScript configuration file. 3 | You will be billed for the AWS resources used if you create a stack from this template. **NOTICE** Copyright 2017 Amazon.com, Inc. or its affiliates. 4 | All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 5 | http://www.apache.org/licenses/LICENSE-2.0 or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 6 | See the License for the specific language governing permissions and limitations under the License. 7 | 8 | 9 | Parameters: 10 | 11 | UserPoolId: 12 | Type: String 13 | ClientId: 14 | Type: String 15 | BaseUrl: 16 | Type: String 17 | PublicBucket: 18 | Type: String 19 | PrivateBucket: 20 | Type: String 21 | PublicPrefix: 22 | Type: String 23 | PrivatePrefix: 24 | Type: String 25 | PrivateContentUrl: 26 | Type: String 27 | PublicContentUrl: 28 | Type: String 29 | ConfigFile: 30 | Type: String 31 | 32 | Resources: 33 | 34 | LambdaExecutionRole: 35 | Type: AWS::IAM::Role 36 | Properties: 37 | AssumeRolePolicyDocument: 38 | Version: '2012-10-17' 39 | Statement: 40 | - Effect: Allow 41 | Principal: 42 | Service: 43 | - lambda.amazonaws.com 44 | Action: 45 | - sts:AssumeRole 46 | Path: "/" 47 | Policies: 48 | - PolicyName: root 49 | PolicyDocument: 50 | Version: '2012-10-17' 51 | Statement: 52 | - Effect: Allow 53 | Action: 54 | - logs:CreateLogGroup 55 | - logs:CreateLogStream 56 | - logs:PutLogEvents 57 | Resource: arn:aws:logs:*:*:* 58 | - Effect: Allow 59 | Action: 60 | - s3:PutObject 61 | Resource: 62 | - !Sub 'arn:aws:s3:::${PublicBucket}/*' 63 | - !Sub 'arn:aws:s3:::${PrivateBucket}/*' 64 | 65 | PopulateS3BucketFunction: 66 | Type: AWS::Lambda::Function 67 | Properties: 68 | Handler: index.handler 69 | Role: !GetAtt LambdaExecutionRole.Arn 70 | Runtime: python3.6 71 | Timeout: 60 72 | MemorySize: 1536 73 | Code: 74 | ZipFile: | 75 | from io import BytesIO 76 | from urllib.request import urlopen 77 | from zipfile import ZipFile 78 | import os 79 | import boto3 80 | import json 81 | import cfnresponse 82 | def handler(event, context): 83 | print (str(event)) 84 | responseData = {} 85 | contenttype = {'html': 'text/html', 'js': 'application/javascript', 'css': 'text/css', 'json':'application/json'} 86 | try: 87 | SourceUrl = event['ResourceProperties']['SourceUrl'] 88 | DestinationBucket = event['ResourceProperties']['DestinationBucket'] 89 | DestinationPrefix = event['ResourceProperties']['DestinationPrefix'] 90 | baseDir = '/tmp/' + DestinationBucket + '/' + DestinationPrefix 91 | print("baseDir=" + baseDir) 92 | with urlopen(SourceUrl) as zipresp: 93 | with ZipFile(BytesIO(zipresp.read())) as zfile: 94 | zfile.extractall(baseDir) 95 | s3 = boto3.resource('s3') 96 | for path, subdirs, files in os.walk(baseDir): 97 | for filename in files: 98 | extension = os.path.splitext(filename)[1][1:] 99 | print("extension=" + extension + ", content type=" + contenttype[extension] ) 100 | f = os.path.join(path, filename) 101 | print("f=" + f) 102 | key = f.replace(baseDir, "", 1) 103 | print("key=" + key) 104 | s3.meta.client.upload_file(f, DestinationBucket, DestinationPrefix+key, ExtraArgs={ 'ContentType': contenttype[extension] } ) 105 | cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID") 106 | print ('SUCCESS') 107 | except Exception as e: 108 | responseData['Error'] = str(e) 109 | cfnresponse.send(event, context, cfnresponse.FAILED, responseData, "CustomResourcePhysicalID") 110 | print("FAILED ERROR: " + responseData['Error']) 111 | 112 | 113 | PopulatePublicBucket: 114 | Type: Custom::PopulatePublicBucket 115 | Properties: 116 | ServiceToken: !GetAtt PopulateS3BucketFunction.Arn 117 | DestinationBucket: !Ref PublicBucket 118 | DestinationPrefix: !Ref PublicPrefix 119 | SourceUrl: !Ref PublicContentUrl 120 | 121 | PopulatePrivateBucket: 122 | Type: Custom::PopulatePrivateBucket 123 | Properties: 124 | ServiceToken: !GetAtt PopulateS3BucketFunction.Arn 125 | DestinationBucket: !Ref PrivateBucket 126 | DestinationPrefix: !Ref PrivatePrefix 127 | SourceUrl: !Ref PrivateContentUrl 128 | 129 | WriteConfigFileFunction: 130 | Type: AWS::Lambda::Function 131 | Properties: 132 | Handler: index.handler 133 | Role: !GetAtt LambdaExecutionRole.Arn 134 | Runtime: python3.6 135 | Timeout: 30 136 | MemorySize: 1536 137 | Code: 138 | ZipFile: | 139 | from io import BytesIO 140 | from urllib.request import urlopen 141 | from zipfile import ZipFile 142 | import os 143 | import boto3 144 | import json 145 | import cfnresponse 146 | def handler(event, context): 147 | responseData = {} 148 | print (str(event)) 149 | try: 150 | DestinationBucket = event['ResourceProperties']['DestinationBucket'] 151 | DestinationPrefix = event['ResourceProperties']['DestinationPrefix'] 152 | UserPoolId = event['ResourceProperties']['UserPoolId'] 153 | ClientId = event['ResourceProperties']['ClientId'] 154 | AWSRegion = event['ResourceProperties']['AWSRegion'] 155 | BaseUrl = event['ResourceProperties']['BaseUrl'] 156 | ConfigFile = event['ResourceProperties']['ConfigFile'] 157 | config = "" 158 | config = config + "var UserPoolId = '" + UserPoolId + "';\n" 159 | config = config + "var ClientId = '" + ClientId + "';\n" 160 | config = config + "var AWSRegion = '" + AWSRegion + "';\n" 161 | config = config + "var BaseUrl = '" + BaseUrl + "';\n" 162 | print(config) 163 | s3 = boto3.client('s3') 164 | s3.put_object(Body=config, Bucket=DestinationBucket, Key=DestinationPrefix + ConfigFile, ContentType='application/javascript') 165 | cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID") 166 | print ('SUCCESS') 167 | except Exception as e: 168 | responseData['Error'] = str(e) 169 | cfnresponse.send(event, context, cfnresponse.FAILED, responseData, "CustomResourcePhysicalID") 170 | print("FAILED ERROR: " + responseData['Error']) 171 | 172 | WriteConfigFile: 173 | Type: Custom::WriteConfigFile 174 | DependsOn: PopulatePublicBucket 175 | Properties: 176 | ServiceToken: !GetAtt WriteConfigFileFunction.Arn 177 | DestinationBucket: !Ref PublicBucket 178 | DestinationPrefix: !Ref PublicPrefix 179 | AWSRegion: !Ref "AWS::Region" 180 | UserPoolId: !Ref UserPoolId 181 | ClientId: !Ref ClientId 182 | BaseUrl: !Ref BaseUrl 183 | ConfigFile: !Ref ConfigFile 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /www-private/top-secret.json: -------------------------------------------------------------------------------- 1 | { 2 | "title":"!!!Success - retrieved private content is below!!!", 3 | "content":"This content can only be seen after proper authentication via Lambda@Edge. Your JWT token was passed in the Authorization http request header enabling authorization process to take place. Lambda@Edge extracted the Authorization header, then performed a series of steps to validate the JWT token. That included matching it up against User Pool ID as well as cryptographic verification of JWT signature. Amazon cognito used a private RSA key and HMAC algorithm to create the JWT token signature. Lambda@Edge used the public RSA key to decrypt the JWT signature and verify its validity. If JWT signature is missing, or is expired or its signature does not pass validation tests, then Lambda@Edge returns 401 unauthorized error. For unauthorized requests, the function takes advantage of Lambda@Edge response-generating capabilities to send immediate response without causing any load on the origin server. For authorized requests, the function takes advantage of Lamba@Edge capability to manipulate headers sent to origin. In this case, the function removes Authorization header which cannot be handled by Amazon S3 as origin. After Lambda@Edge is finished with header manipulation, CloudFront processes requests and responses according to the distribution configuration." 4 | } -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Private Content Viewer - Lambda at Edge Authentication Demo 4 | 5 | 6 | 10 | 11 | 12 |

Private Content Viewer

13 |
14 | Below are contents of /private/top-secret.json file served from private content S3 bucket: 15 |
16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
jwt token...
private content will be shown here...
27 | 28 | 112 | 113 | -------------------------------------------------------------------------------- /www/js/amazon-cognito-identity.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2016 Amazon.com, 3 | * Inc. or its affiliates. All Rights Reserved. 4 | * 5 | * Licensed under the Amazon Software License (the "License"). 6 | * You may not use this file except in compliance with the 7 | * License. A copy of the License is located at 8 | * 9 | * http://aws.amazon.com/asl/ 10 | * 11 | * or in the "license" file accompanying this file. This file is 12 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, express or implied. See the License 14 | * for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("aws-sdk/global"),require("aws-sdk/clients/cognitoidentityserviceprovider")):"function"==typeof define&&define.amd?define(["aws-sdk/global","aws-sdk/clients/cognitoidentityserviceprovider"],t):"object"==typeof exports?exports.AmazonCognitoIdentity=t(require("aws-sdk/global"),require("aws-sdk/clients/cognitoidentityserviceprovider")):e.AmazonCognitoIdentity=t(e.AWSCognito,e.AWSCognito.CognitoIdentityServiceProvider)}(this,function(e,t){return function(e){function t(i){if(n[i])return n[i].exports;var s=n[i]={exports:{},id:i,loaded:!1};return e[i].call(s.exports,s,s.exports,t),s.loaded=!0,s.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function i(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t}function s(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(15);Object.keys(o).forEach(function(e){"default"!==e&&"__esModule"!==e&&Object.defineProperty(t,e,{enumerable:!0,get:function(){return o[e]}})});var r=n(12),a=s(r),u=i(o);Object.keys(u).forEach(function(e){a.default[e]=u[e]}),"undefined"!=typeof window&&!window.crypto&&window.msCrypto&&(window.crypto=window.msCrypto)},function(t,n){t.exports=e},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function s(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}t.__esModule=!0;var o=n(1),r=n(3),a=i(r),u="FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF",c="userAttributes.",l=function(){function e(t){s(this,e),this.N=new a.default(u,16),this.g=new a.default("2",16),this.k=new a.default(this.hexHash("00"+this.N.toString(16)+"0"+this.g.toString(16)),16),this.smallAValue=this.generateRandomSmallA(),this.largeAValue=this.calculateA(this.smallAValue),this.infoBits=new o.util.Buffer("Caldera Derived Key","utf8"),this.poolName=t}return e.prototype.getSmallAValue=function(){return this.smallAValue},e.prototype.getLargeAValue=function(){return this.largeAValue},e.prototype.generateRandomSmallA=function(){var e=o.util.crypto.lib.randomBytes(128).toString("hex"),t=new a.default(e,16),n=t.mod(this.N);return n},e.prototype.generateRandomString=function(){return o.util.crypto.lib.randomBytes(40).toString("base64")},e.prototype.getRandomPassword=function(){return this.randomPassword},e.prototype.getSaltDevices=function(){return this.SaltToHashDevices},e.prototype.getVerifierDevices=function(){return this.verifierDevices},e.prototype.generateHashDevice=function(e,t){this.randomPassword=this.generateRandomString();var n=""+e+t+":"+this.randomPassword,i=this.hash(n),s=o.util.crypto.lib.randomBytes(16).toString("hex");this.SaltToHashDevices=this.padHex(new a.default(s,16));var r=this.g.modPow(new a.default(this.hexHash(this.SaltToHashDevices+i),16),this.N);this.verifierDevices=this.padHex(r)},e.prototype.calculateA=function(e){var t=this.g.modPow(e,this.N);if(t.mod(this.N).equals(a.default.ZERO))throw new Error("Illegal paramater. A mod N cannot be 0.");return t},e.prototype.calculateU=function(e,t){this.UHexHash=this.hexHash(this.padHex(e)+this.padHex(t));var n=new a.default(this.UHexHash,16);return n},e.prototype.hash=function(e){var t=o.util.crypto.sha256(e,"hex");return new Array(64-t.length).join("0")+t},e.prototype.hexHash=function(e){return this.hash(new o.util.Buffer(e,"hex"))},e.prototype.computehkdf=function(e,t){var n=o.util.crypto.hmac(t,e,"buffer","sha256"),i=o.util.buffer.concat([this.infoBits,new o.util.Buffer(String.fromCharCode(1),"utf8")]),s=o.util.crypto.hmac(n,i,"buffer","sha256");return s.slice(0,16)},e.prototype.getPasswordAuthenticationKey=function(e,t,n,i){if(n.mod(this.N).equals(a.default.ZERO))throw new Error("B cannot be zero.");if(this.UValue=this.calculateU(this.largeAValue,n),this.UValue.equals(a.default.ZERO))throw new Error("U cannot be zero.");var s=""+this.poolName+e+":"+t,r=this.hash(s),u=new a.default(this.hexHash(this.padHex(i)+r),16),c=this.g.modPow(u,this.N),l=n.subtract(this.k.multiply(c)),h=l.modPow(this.smallAValue.add(this.UValue.multiply(u)),this.N).mod(this.N),f=this.computehkdf(new o.util.Buffer(this.padHex(h),"hex"),new o.util.Buffer(this.padHex(this.UValue.toString(16)),"hex"));return f},e.prototype.getNewPasswordRequiredChallengeUserAttributePrefix=function(){return c},e.prototype.padHex=function(e){var t=e.toString(16);return t.length%2===1?t="0"+t:"89ABCDEFabcdef".indexOf(t[0])!==-1&&(t="00"+t),t},e}();t.default=l},function(e,t){"use strict";function n(e,t){null!=e&&this.fromString(e,t)}function i(){return new n(null)}function s(e,t,n,i,s,o){for(;--o>=0;){var r=t*this[e++]+n[i]+s;s=Math.floor(r/67108864),n[i++]=67108863&r}return s}function o(e,t,n,i,s,o){for(var r=32767&t,a=t>>15;--o>=0;){var u=32767&this[e],c=this[e++]>>15,l=a*u+c*r;u=r*u+((32767&l)<<15)+n[i]+(1073741823&s),s=(u>>>30)+(l>>>15)+a*c+(s>>>30),n[i++]=1073741823&u}return s}function r(e,t,n,i,s,o){for(var r=16383&t,a=t>>14;--o>=0;){var u=16383&this[e],c=this[e++]>>14,l=a*u+c*r;u=r*u+((16383&l)<<14)+n[i]+s,s=(u>>28)+(l>>14)+a*c,n[i++]=268435455&u}return s}function a(e){return z.charAt(e)}function u(e,t){var n=Q[e.charCodeAt(t)];return null==n?-1:n}function c(e){for(var t=this.t-1;t>=0;--t)e[t]=this[t];e.t=this.t,e.s=this.s}function l(e){this.t=1,this.s=e<0?-1:0,e>0?this[0]=e:e<-1?this[0]=e+this.DV:this.t=0}function h(e){var t=i();return t.fromInt(e),t}function f(e,t){var i;if(16==t)i=4;else if(8==t)i=3;else if(2==t)i=1;else if(32==t)i=5;else{if(4!=t)throw new Error("Only radix 2, 4, 8, 16, 32 are supported");i=2}this.t=0,this.s=0;for(var s=e.length,o=!1,r=0;--s>=0;){var a=u(e,s);a<0?"-"==e.charAt(s)&&(o=!0):(o=!1,0==r?this[this.t++]=a:r+i>this.DB?(this[this.t-1]|=(a&(1<>this.DB-r):this[this.t-1]|=a<=this.DB&&(r-=this.DB))}this.clamp(),o&&n.ZERO.subTo(this,this)}function d(){for(var e=this.s&this.DM;this.t>0&&this[this.t-1]==e;)--this.t}function p(e){if(this.s<0)return"-"+this.negate().toString();var t;if(16==e)t=4;else if(8==e)t=3;else if(2==e)t=1;else if(32==e)t=5;else{if(4!=e)throw new Error("Only radix 2, 4, 8, 16, 32 are supported");t=2}var n,i=(1<0)for(u>u)>0&&(s=!0,o=a(n));r>=0;)u>(u+=this.DB-t)):(n=this[r]>>(u-=t)&i,u<=0&&(u+=this.DB,--r)),n>0&&(s=!0),s&&(o+=a(n));return s?o:"0"}function g(){var e=i();return n.ZERO.subTo(this,e),e}function v(){return this.s<0?this.negate():this}function m(e){var t=this.s-e.s;if(0!=t)return t;var n=this.t;if(t=n-e.t,0!=t)return this.s<0?-t:t;for(;--n>=0;)if(0!=(t=this[n]-e[n]))return t;return 0}function S(e){var t,n=1;return 0!=(t=e>>>16)&&(e=t,n+=16),0!=(t=e>>8)&&(e=t,n+=8),0!=(t=e>>4)&&(e=t,n+=4),0!=(t=e>>2)&&(e=t,n+=2),0!=(t=e>>1)&&(e=t,n+=1),n}function y(){return this.t<=0?0:this.DB*(this.t-1)+S(this[this.t-1]^this.s&this.DM)}function C(e,t){var n;for(n=this.t-1;n>=0;--n)t[n+e]=this[n];for(n=e-1;n>=0;--n)t[n]=0;t.t=this.t+e,t.s=this.s}function A(e,t){for(var n=e;n=0;--n)t[n+r+1]=this[n]>>s|a,a=(this[n]&o)<=0;--n)t[n]=0;t[r]=a,t.t=this.t+r+1,t.s=this.s,t.clamp()}function U(e,t){t.s=this.s;var n=Math.floor(e/this.DB);if(n>=this.t)return void(t.t=0);var i=e%this.DB,s=this.DB-i,o=(1<>i;for(var r=n+1;r>i;i>0&&(t[this.t-n-1]|=(this.s&o)<>=this.DB;if(e.t>=this.DB;i+=this.s}else{for(i+=this.s;n>=this.DB;i-=e.s}t.s=i<0?-1:0,i<-1?t[n++]=this.DV+i:i>0&&(t[n++]=i),t.t=n,t.clamp()}function E(e,t){var i=this.abs(),s=e.abs(),o=i.t;for(t.t=o+s.t;--o>=0;)t[o]=0;for(o=0;o=0;)e[n]=0;for(n=0;n=t.DV&&(e[n+t.t]-=t.DV,e[n+t.t+1]=1)}e.t>0&&(e[e.t-1]+=t.am(n,t[n],e,2*n,0,1)),e.s=0,e.clamp()}function D(e,t,s){var o=e.abs();if(!(o.t<=0)){var r=this.abs();if(r.t0?(o.lShiftTo(l,a),r.lShiftTo(l,s)):(o.copyTo(a),r.copyTo(s));var h=a.t,f=a[h-1];if(0!=f){var d=f*(1<1?a[h-2]>>this.F2:0),p=this.FV/d,g=(1<=0&&(s[s.t++]=1,s.subTo(C,s)),n.ONE.dlShiftTo(h,C),C.subTo(a,a);a.t=0;){var A=s[--m]==f?this.DM:Math.floor(s[m]*p+(s[m-1]+v)*g);if((s[m]+=a.am(0,A,s,y,0,h))0&&s.rShiftTo(l,s),u<0&&n.ZERO.subTo(s,s)}}}function k(e){var t=i();return this.abs().divRemTo(e,null,t),this.s<0&&t.compareTo(n.ZERO)>0&&e.subTo(t,t),t}function R(){if(this.t<1)return 0;var e=this[0];if(0==(1&e))return 0;var t=3&e;return t=t*(2-(15&e)*t)&15,t=t*(2-(255&e)*t)&255,t=t*(2-((65535&e)*t&65535))&65535,t=t*(2-e*t%this.DV)%this.DV,t>0?this.DV-t:-t}function F(e){return 0==this.compareTo(e)}function P(e,t){for(var n=0,i=0,s=Math.min(e.t,this.t);n>=this.DB;if(e.t>=this.DB;i+=this.s}else{for(i+=this.s;n>=this.DB;i+=e.s}t.s=i<0?-1:0,i>0?t[n++]=i:i<-1&&(t[n++]=this.DV+i),t.t=n,t.clamp()}function b(e){var t=i();return this.addTo(e,t),t}function _(e){var t=i();return this.subTo(e,t),t}function B(e){var t=i();return this.multiplyTo(e,t),t}function N(e){var t=i();return this.divRemTo(e,t,null),t}function M(e){this.m=e,this.mp=e.invDigit(),this.mpl=32767&this.mp,this.mph=this.mp>>15,this.um=(1<0&&this.m.subTo(t,t),t}function K(e){var t=i();return e.copyTo(t),this.reduce(t),t}function O(e){for(;e.t<=this.mt2;)e[e.t++]=0;for(var t=0;t>15)*this.mpl&this.um)<<15)&e.DM;for(n=t+this.m.t,e[n]+=this.m.am(0,i,e,t,0,this.m.t);e[n]>=e.DV;)e[n]-=e.DV,e[++n]++}e.clamp(),e.drShiftTo(this.m.t,e),e.compareTo(this.m)>=0&&e.subTo(this.m,e)}function q(e,t){e.squareTo(t),this.reduce(t)}function x(e,t,n){e.multiplyTo(t,n),this.reduce(n)}function H(e,t){var n,s=e.bitLength(),o=h(1),r=new M(t);if(s<=0)return o;n=s<18?1:s<48?3:s<144?4:s<768?5:6;var a=new Array,u=3,c=n-1,l=(1<1){var f=i();for(r.sqrTo(a[1],f);u<=l;)a[u]=i(),r.mulTo(f,a[u-2],a[u]),u+=2}var d,p,g=e.t-1,v=!0,m=i();for(s=S(e[g])-1;g>=0;){for(s>=c?d=e[g]>>s-c&l:(d=(e[g]&(1<0&&(d|=e[g-1]>>this.DB+s-c)),u=n;0==(1&d);)d>>=1,--u;if((s-=u)<0&&(s+=this.DB,--g),v)a[d].copyTo(o),v=!1;else{for(;u>1;)r.sqrTo(o,m),r.sqrTo(m,o),u-=2;u>0?r.sqrTo(o,m):(p=o,o=m,m=p),r.mulTo(m,a[d],o)}for(;g>=0&&0==(e[g]&1<0&&void 0!==arguments[0]?arguments[0]:{},n=t.AccessToken;i(this,e),this.jwtToken=n||""}return e.prototype.getJwtToken=function(){return this.jwtToken},e.prototype.getExpiration=function(){var e=this.jwtToken.split(".")[1],t=JSON.parse(s.util.base64.decode(e).toString("utf8"));return t.exp},e}();t.default=o},function(e,t,n){"use strict";function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}t.__esModule=!0;var s=n(1),o=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=t.IdToken;i(this,e),this.jwtToken=n||""}return e.prototype.getJwtToken=function(){return this.jwtToken},e.prototype.getExpiration=function(){var e=this.jwtToken.split(".")[1],t=JSON.parse(s.util.base64.decode(e).toString("utf8"));return t.exp},e}();t.default=o},function(e,t){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}t.__esModule=!0;/*! 18 | * Copyright 2016 Amazon.com, 19 | * Inc. or its affiliates. All Rights Reserved. 20 | * 21 | * Licensed under the Amazon Software License (the "License"). 22 | * You may not use this file except in compliance with the 23 | * License. A copy of the License is located at 24 | * 25 | * http://aws.amazon.com/asl/ 26 | * 27 | * or in the "license" file accompanying this file. This file is 28 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 29 | * CONDITIONS OF ANY KIND, express or implied. See the License 30 | * for the specific language governing permissions and 31 | * limitations under the License. 32 | */ 33 | var i=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},i=t.RefreshToken;n(this,e),this.token=i||""}return e.prototype.getToken=function(){return this.token},e}();t.default=i},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function s(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}t.__esModule=!0;var o=n(1),r=n(3),a=i(r),u=n(2),c=i(u),l=n(4),h=i(l),f=n(5),d=i(f),p=n(6),g=i(p),v=n(9),m=i(v),S=n(10),y=i(S),C=n(8),A=i(C),w=n(11),U=i(w),T=function(){function e(t){if(s(this,e),null==t||null==t.Username||null==t.Pool)throw new Error("Username and pool information are required.");this.username=t.Username||"",this.pool=t.Pool,this.Session=null,this.client=t.Pool.client,this.signInUserSession=null,this.authenticationFlowType="USER_SRP_AUTH",this.storage=t.Storage||(new U.default).getStorage()}return e.prototype.getSignInUserSession=function(){return this.signInUserSession},e.prototype.getUsername=function(){return this.username},e.prototype.getAuthenticationFlowType=function(){return this.authenticationFlowType},e.prototype.setAuthenticationFlowType=function(e){this.authenticationFlowType=e},e.prototype.initiateAuth=function(e,t){var n=this,i=e.getAuthParameters();i.USERNAME=this.username,this.client.makeUnauthenticatedRequest("initiateAuth",{AuthFlow:"CUSTOM_AUTH",ClientId:this.pool.getClientId(),AuthParameters:i,ClientMetadata:e.getValidationData()},function(e,i){if(e)return t.onFailure(e);var s=i.ChallengeName,o=i.ChallengeParameters;return"CUSTOM_CHALLENGE"===s?(n.Session=i.Session,t.customChallenge(o)):(n.signInUserSession=n.getCognitoUserSession(i.AuthenticationResult),n.cacheTokens(),t.onSuccess(n.signInUserSession))})},e.prototype.authenticateUser=function(e,t){var n=this,i=new c.default(this.pool.getUserPoolId().split("_")[1]),s=new y.default,r=void 0,u=void 0,l={};null!=this.deviceKey&&(l.DEVICE_KEY=this.deviceKey),l.USERNAME=this.username,l.SRP_A=i.getLargeAValue().toString(16),"CUSTOM_AUTH"===this.authenticationFlowType&&(l.CHALLENGE_NAME="SRP_A"),this.client.makeUnauthenticatedRequest("initiateAuth",{AuthFlow:this.authenticationFlowType,ClientId:this.pool.getClientId(),AuthParameters:l,ClientMetadata:e.getValidationData()},function(c,l){if(c)return t.onFailure(c);var h=l.ChallengeParameters;n.username=h.USER_ID_FOR_SRP,r=new a.default(h.SRP_B,16),u=new a.default(h.SALT,16),n.getCachedDeviceKeyAndPassword();var f=i.getPasswordAuthenticationKey(n.username,e.getPassword(),r,u),d=s.getNowString(),p=o.util.crypto.hmac(f,o.util.buffer.concat([new o.util.Buffer(n.pool.getUserPoolId().split("_")[1],"utf8"),new o.util.Buffer(n.username,"utf8"),new o.util.Buffer(h.SECRET_BLOCK,"base64"),new o.util.Buffer(d,"utf8")]),"base64","sha256"),g={};g.USERNAME=n.username,g.PASSWORD_CLAIM_SECRET_BLOCK=h.SECRET_BLOCK,g.TIMESTAMP=d,g.PASSWORD_CLAIM_SIGNATURE=p,null!=n.deviceKey&&(g.DEVICE_KEY=n.deviceKey);var v=function e(t,i){return n.client.makeUnauthenticatedRequest("respondToAuthChallenge",t,function(s,o){return s&&"ResourceNotFoundException"===s.code&&s.message.toLowerCase().indexOf("device")!==-1?(g.DEVICE_KEY=null,n.deviceKey=null,n.randomPassword=null,n.deviceGroupKey=null,n.clearCachedDeviceKeyAndPassword(),e(t,i)):i(s,o)})};v({ChallengeName:"PASSWORD_VERIFIER",ClientId:n.pool.getClientId(),ChallengeResponses:g,Session:l.Session},function(e,s){if(e)return t.onFailure(e);var o=s.ChallengeName;if("NEW_PASSWORD_REQUIRED"===o){n.Session=s.Session;var r=null,a=null,u=[],c=i.getNewPasswordRequiredChallengeUserAttributePrefix();if(s.ChallengeParameters&&(r=JSON.parse(s.ChallengeParameters.userAttributes),a=JSON.parse(s.ChallengeParameters.requiredAttributes)),a)for(var l=0;l0&&void 0!==arguments[0]?arguments[0]:{},i=t.Name,s=t.Value;n(this,e),this.Name=i||"",this.Value=s||""}return e.prototype.getValue=function(){return this.Value},e.prototype.setValue=function(e){return this.Value=e,this},e.prototype.getName=function(){return this.Name},e.prototype.setName=function(e){return this.Name=e,this},e.prototype.toString=function(){return JSON.stringify(this)},e.prototype.toJSON=function(){return{Name:this.Name,Value:this.Value}},e}();t.default=i},function(e,t){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}t.__esModule=!0;/*! 50 | * Copyright 2016 Amazon.com, 51 | * Inc. or its affiliates. All Rights Reserved. 52 | * 53 | * Licensed under the Amazon Software License (the "License"). 54 | * You may not use this file except in compliance with the 55 | * License. A copy of the License is located at 56 | * 57 | * http://aws.amazon.com/asl/ 58 | * 59 | * or in the "license" file accompanying this file. This file is 60 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 61 | * CONDITIONS OF ANY KIND, express or implied. See the License 62 | * for the specific language governing permissions and 63 | * limitations under the License. 64 | */ 65 | var i=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},i=t.IdToken,s=t.RefreshToken,o=t.AccessToken;if(n(this,e),null==o||null==i)throw new Error("Id token and Access Token must be present.");this.idToken=i,this.refreshToken=s,this.accessToken=o}return e.prototype.getIdToken=function(){return this.idToken},e.prototype.getRefreshToken=function(){return this.refreshToken},e.prototype.getAccessToken=function(){return this.accessToken},e.prototype.isValid=function(){var e=Math.floor(new Date/1e3);return e