├── .gitignore ├── LICENSE ├── README.md ├── authn ├── github.index.js ├── openid.index.js └── pkce.index.js ├── authz ├── auth0.js ├── centrify.js ├── github.membership-lookup.js ├── google.groups-lookup.js ├── google.hosted-domain.js ├── google.json-email-lookup.js ├── microsoft.js ├── microsoft.json-username-lookup.js └── okta.js ├── build.sh ├── build ├── build.js ├── package-lock.json └── package.json ├── code-challenge.js ├── nonce.js ├── package-lock.json ├── package.json ├── template.yaml └── tests ├── package-lock.json ├── package.json └── tests.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | config.json 3 | *.DS_Store 4 | cloudfront-auth.zip 5 | id_rsa 6 | id_rsa.pub 7 | google-authz.json 8 | auth.js 9 | /index.js 10 | /.idea 11 | tests/logs/* 12 | !tests/logs/.gitkeep 13 | tests/config 14 | /distributions 15 | packaged.yaml 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Widen Enterprises 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, 4 | provided that the above copyright notice and this permission notice appear in all copies. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 7 | WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR 8 | CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 9 | NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This project has been archived! 2 | 3 | See https://github.com/Widen/cloudfront-auth/issues/104 for details about the status of this project. 4 | 5 | --- 6 | 7 | [Google Apps (G Suite)](https://developers.google.com/identity/protocols/OpenIDConnect), [Microsoft Azure AD](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-code), [GitHub](https://developer.github.com/apps/building-oauth-apps/authorization-options-for-oauth-apps/), [OKTA](https://www.okta.com/), [Auth0](https://auth0.com/), [Centrify](https://centrify.com) authentication for [CloudFront](https://aws.amazon.com/cloudfront/) using [Lambda@Edge](http://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html). The original use case for `cloudfront-auth` was to serve private S3 content over HTTPS without running a proxy server in EC2 to authenticate requests; but `cloudfront-auth` can be used authenticate requests of any Cloudfront origin configuration. 8 | 9 | ## Description 10 | Upon successful authentication, a cookie (named `TOKEN`) with the value of a signed JWT is set and the user redirected back to the originally requested path. Upon each request, Lambda@Edge checks the JWT for validity (signature, expiration date, audience and matching hosted domain) and will redirect the user to configured provider's login when their session has timed out. 11 | 12 | ## Usage 13 | If your CloudFront distribution is pointed at a S3 bucket, [configure origin access identity](http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#private-content-creating-oai-console) so S3 objects can be stored with private permissions. (Origin access identity requires the S3 ACL owner be the account owner. Use our [s3-object-owner-monitor](https://github.com/Widen/s3-object-owner-monitor) Lambda function if writing objects across multiple accounts.) 14 | 15 | Enable SSL/HTTPS on your CloudFront distribution; AWS Certificate Manager can be used to provision a no-cost certificate. 16 | 17 | Session duration is defined as the number of hours that the JWT is valid for. After session expiration, cloudfront-auth will redirect the user to the configured provider to re-authenticate. RSA keys are used to sign and validate the JWT. If the files `id_rsa` and `id_rsa.pub` do not exist they will be automatically generated by the build. To disable all issued JWTs upload a new ZIP using the Lambda Console after deleting the `id_rsa` and `id_rsa.pub` files (a new key will be automatically generated). 18 | 19 | ## Identity Provider Guides 20 | 21 | ### Github 22 | 23 | 1. Clone or download this repo 24 | 1. Navigate to your organization's [profile page](https://github.com/settings/profile), then choose OAuth Apps under Developer settings. 25 | 1. Select **New OAuth App** 26 | 1. For **Authorization callback URL** enter your Cloudfront hostname with your preferred path value for the authorization callback. Example: `https://my-cloudfront-site.example.com/_callback` 27 | 1. Execute `./build.sh` in the downloaded directory. NPM will run to download dependencies and a RSA key will be generated. 28 | 1. Choose `Github` as the authorization method and enter the values for Client ID, Client Secret, Redirect URI, Session Duration and Organization 29 | - cloudfront-auth will check that users are a member of the entered Organization. 30 | 1. Upload the resulting `zip` file found in your distribution folder using the AWS Lambda console and jump to the [configuration step](#configure-lambda-and-cloudfront) 31 | 32 | ### Google 33 | 34 | 1. Clone or download this repo 35 | 1. Go to the **Credentials** tab of your [Google developers console](https://console.developers.google.com) 36 | 1. Create a new Project 37 | 1. Create an **OAuth Client ID** from the **Create credentials** menu 38 | 1. Select **Web application** for the Application type 39 | 1. Under **Authorized redirect URIs**, enter your Cloudfront hostname with your preferred path value for the authorization callback. Example: `https://my-cloudfront-site.example.com/_callback` 40 | 1. Execute `./build.sh` in the downloaded directory. NPM will run to download dependencies and a RSA key will be generated. 41 | 1. Choose `Google` as the authorization method and enter the values for Client ID, Client Secret, Redirect URI, Hosted Domain and Session Duration 42 | 1. Select the preferred authentication method 43 | 1. Hosted Domain (verify email's domain matches that of the given hosted domain) 44 | 1. JSON Email Lookup 45 | 1. Enter your JSON Email Lookup URL (example below) that consists of a single JSON array of emails to search through 46 | 1. Google Groups Lookup 47 | 1. [Use Google Groups to authorize users](https://github.com/Widen/cloudfront-auth/wiki/Google-Groups-Setup) 48 | 1. Upload the resulting `zip` file found in your distribution folder using the AWS Lambda console and jump to the [configuration step](#configure-lambda-and-cloudfront) 49 | 50 | ### Microsoft Azure 51 | 52 | 1. Clone or download this repo 53 | 1. In your Azure portal, go to Azure Active Directory and select **App registrations** 54 | 1. Create a new application registration with an application type of **Web app / api** 55 | 1. Once created, go to your application `Settings -> Keys` and make a new key with your desired duration. Click save and copy the value. This will be your `client_secret` 56 | 1. Above where you selected `Keys`, go to `Reply URLs` and enter your Cloudfront hostname with your preferred path value for the authorization callback. Example: https://my-cloudfront-site.example.com/_callback 57 | 1. Execute `./build.sh` in the downloaded directory. NPM will run to download dependencies and a RSA key will be generated. 58 | 1. Choose `Microsoft` as the authorization method and enter the values for [Tenant](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-howto-tenant), Client ID (**Application ID**), Client Secret (**previously created key**), Redirect URI and Session Duration 59 | 1. Select the preferred authentication method 60 | 1. Azure AD Membership (default) 61 | 1. JSON Username Lookup 62 | 1. Enter your JSON Username Lookup URL (example below) that consists of a single JSON array of usernames to search through 63 | 1. Upload the resulting `zip` file found in your distribution folder using the AWS Lambda console and jump to the [configuration step](#configure-lambda-and-cloudfront) 64 | 65 | ### OKTA 66 | 67 | 1. Clone or download this repo 68 | 1. Sign in to OKTA with your administrator account and navigate to the `Applications` tab. 69 | 1. Add Application 70 | 1. Select the `Web` application type 71 | 1. Base URI: CloudFront distribution domain name (`https://{cf-endpoint}.cloudfront.net`) 72 | 1. Login Redirect URI: CloudFront distribution domain name with callback path (`https://{cf-endpoint}.cloudfront.net/_callback`) 73 | 1. Group Assignments: Optional 74 | 1. Grant Type Allowed: Authorization Code 75 | 1. Done 76 | 1. Gather the following information for Lambda configuration 77 | 1. Client Id and Client Secret from the application created in our previous step (can be found at the bottom of the general tab) 78 | 1. Base Url 79 | 1. This is named the 'Org URL' and can be found in the top right of the Dashboard tab. 80 | 1. Execute `./build.sh` in the downloaded directory. NPM will run to download dependencies and a RSA key will be generated. 81 | 1. Choose `OKTA` as the authorization method and enter the values for Base URL (Org URL), Client ID, Client Secret, Redirect URI, and Session Duration 82 | 1. Upload the resulting `zip` file found in your distribution folder using the AWS Lambda console and jump to the [configuration step](#configure-lambda-and-cloudfront) 83 | 84 | ### Auth0 85 | 86 | 1. Clone or download this repo 87 | 1. Go to the **Dashboard** of your Auth0 admin page 88 | 1. Click **New Application** 89 | 1. Select **Regular Web App** and click **Create**. 90 | 1. Now select an application type and follow the steps for 'Quick Start' or use your own app. 91 | 1. Go to application **Settings** and enter required details. In **Allowed Callback URLs** enter your Cloudfront hostname with your preferred path value for the authorization callback. Example: `https://my-cloudfront-site.example.com/_callback` 92 | 1. Execute `./build.sh` in the downloaded directory. NPM will run to download dependencies and a RSA key will be generated. 93 | 1. Choose `AUTH0` as the authorization method and enter the values for Base URL (Auth0 Domain), Client ID, Client Secret, Redirect URI, and Session Duration 94 | 1. Upload the resulting `zip` file found in your distribution folder using the AWS Lambda console and jump to the [configuration step](#configure-lambda-and-cloudfront) 95 | 96 | ### Centrify 97 | 98 | 1. Clone or download this repo 99 | 1. Go to the **Dashboard** of your Centrify admin page 100 | 1. Click **Web Apps** from the LHS. 101 | 1. Click **Add Web App** and select the **Custom Tab**. 102 | 1. Add an **OpenID Connect** webapp and click **Yes** to confirm. 103 | 1. Fill in naming and logo information and then switch to the **Trust** tab. 104 | 1. Enter service provider information. In **Authorized Redirect URIs** enter your Cloudfront hostname with your preferred path value for the authorization callback. Example: `https://my-cloudfront-site.example.com/_callback` 105 | 1. Execute `./build.sh` in the downloaded directory. NPM will run to download dependencies and a RSA key will be generated. 106 | 1. Choose `CENTRIFY` as the authorization method and enter the values for Base URL (Centrify Resource application URL), Client ID, Client Secret, Redirect URI, and Session Duration (which is available from the **Tokens** tab). 107 | 1. Upload the resulting `zip` file found in your distribution folder using the AWS Lambda console and jump to the [configuration step](#configure-lambda-and-cloudfront) 108 | 109 | ### OKTA Native 110 | 111 | 1. Clone or download this repo 112 | 1. Sign in to OKTA with your administrator account and navigate to the `Applications` tab. 113 | 1. Add Application 114 | 1. Select the `Native` application type 115 | 1. Base URI: CloudFront distribution domain name (`https://{cf-endpoint}.cloudfront.net`) 116 | 1. Login Redirect URI: CloudFront distribution domain name with callback path (`https://{cf-endpoint}.cloudfront.net/_callback`) 117 | 1. Group Assignments: Optional 118 | 1. Grant Type Allowed: Authorization Code 119 | 1. Done 120 | 1. Gather the following information for Lambda configuration 121 | 1. Client Id from the application created in our previous step (can be found at the bottom of the general tab) 122 | 1. Base Url 123 | 1. This is named the 'Org URL' and can be found in the top right of the Dashboard tab. 124 | 1. Execute `./build.sh` in the downloaded directory. NPM will run to download dependencies and a RSA key will be generated. 125 | 1. Choose `OKTA Native` as the authorization method and enter the values for Base URL (Org URL), Client ID, PKCE Code Verifier Length, Redirect URI, and Session Duration 126 | 1. Upload the resulting `zip` file found in your distribution folder using the AWS Lambda console and jump to the [configuration step](#configure-lambda-and-cloudfront) 127 | 128 | ## Configure Lambda and CloudFront 129 | 130 | [Manual Deployment](https://github.com/Widen/cloudfront-auth/wiki/Manual-Deployment) __*or*__ [AWS SAM Deployment](https://github.com/Widen/cloudfront-auth/wiki/AWS-SAM-Deployment) 131 | 132 | ## Authorization Method Examples 133 | 134 | - [Use Google Groups to authorize users](https://github.com/Widen/cloudfront-auth/wiki/Google-Groups-Setup) 135 | 136 | - JSON array of email addresses 137 | 138 | ``` 139 | [ "foo@gmail.com", "bar@gmail.com" ] 140 | ``` 141 | ## Testing 142 | Detailed instructions on testing your function can be found [in the Wiki](https://github.com/Widen/cloudfront-auth/wiki/Debug-&-Test). 143 | 144 | ## Build Requirements 145 | - [npm](https://www.npmjs.com/) ^5.6.0 146 | - [node](https://nodejs.org/en/) ^10.0 147 | - [openssl](https://www.openssl.org) 148 | 149 | ## Contributing 150 | All contributions are welcome. Please create an issue in order open up communication with the community. 151 | 152 | When implementing a new flow or using an already implemented flow, be sure to follow the same style used in `build.js`. The config.json file should have an object for each request made. For example, `openid.index.js` converts config.AUTH_REQUEST and config.TOKEN_REQUEST to querystrings for simplified requests (after adding dynamic variables such as state or nonce). For implementations that are not generic (most), endpoints are hardcoded in to the config (or discovery documents). 153 | 154 | Be considerate of our [limitations](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html#limits-lambda-at-edge). The zipped function can be no more than 1MB in size and execution cannot take longer than 5 seconds, so we must pay close attention to the size of our dependencies and complexity of operations. 155 | -------------------------------------------------------------------------------- /authn/github.index.js: -------------------------------------------------------------------------------- 1 | const qs = require('querystring'); 2 | const fs = require('fs'); 3 | const jwt = require('jsonwebtoken'); 4 | const cookie = require('cookie'); 5 | const jwkToPem = require('jwk-to-pem'); 6 | const auth = require('./auth.js'); 7 | const axios = require('axios'); 8 | var config; 9 | 10 | exports.handler = (event, context, callback) => { 11 | if (typeof config == 'undefined') { 12 | config = JSON.parse(fs.readFileSync('config.json', 'utf8')); 13 | } 14 | mainProcess(event, context, callback); 15 | }; 16 | 17 | function mainProcess(event, context, callback) { 18 | // Get request, request headers, and querystring dictionary 19 | const request = event.Records[0].cf.request; 20 | const headers = request.headers; 21 | const queryDict = qs.parse(request.querystring); 22 | if (event.Records[0].cf.config.hasOwnProperty('test')) { 23 | config.AUTH_REQUEST.redirect_uri = event.Records[0].cf.config.test + config.CALLBACK_PATH; 24 | config.TOKEN_REQUEST.redirect_uri = event.Records[0].cf.config.test + config.CALLBACK_PATH; 25 | } 26 | if (request.uri.startsWith(config.CALLBACK_PATH)) { 27 | console.log("Callback from GitHub received"); 28 | /** Verify code is in querystring */ 29 | if (!queryDict.code || !queryDict.state) { 30 | unauthorized("No code or state found.", callback); 31 | } 32 | config.TOKEN_REQUEST.code = queryDict.code; 33 | config.TOKEN_REQUEST.state = queryDict.state; 34 | /** Exchange code for authorization token */ 35 | const postData = qs.stringify(config.TOKEN_REQUEST); 36 | console.log("Requesting access token."); 37 | axios.post(config.TOKEN_ENDPOINT, postData) 38 | .then(function(response) { 39 | console.log(response); 40 | var responseQueryString = qs.parse(response.data); 41 | /** Get authenticated user's login */ 42 | if (responseQueryString.error) { 43 | internalServerError("Error while getting token: " + responseQueryString.error_description, callback); 44 | } else { 45 | const authorization = responseQueryString.token_type + ' ' + responseQueryString.access_token; 46 | axios.get('https://api.github.com/user', { headers: {'Authorization': authorization}}) 47 | .then(function(response) { 48 | console.log(response); 49 | /** Check if authenticated user's login is a member of given org */ 50 | if (!response.data.hasOwnProperty('login')) { 51 | internalServerError('Unable to find login', callback); 52 | } 53 | var username = response.data.login; 54 | var orgsGet = 'https://api.github.com/orgs/' + config.ORGANIZATION + '/members/' + username; 55 | console.log("Checking ORG membership."); 56 | axios.get(orgsGet, { headers: {'Authorization': authorization} }) 57 | .then(function(response) { 58 | console.log(response); 59 | /** Set cookie upon verified membership */ 60 | if (response.status == 204) { 61 | console.log("Setting cookie and redirecting."); 62 | const nextLocation = { 63 | "status": "302", 64 | "statusDescription": "Found", 65 | "body": "ID token retrieved.", 66 | "headers": { 67 | "location" : [{ 68 | "key": "Location", 69 | "value": event.Records[0].cf.config.hasOwnProperty('test') ? (config.AUTH_REQUEST.redirect_uri + queryDict.state) : queryDict.state 70 | }], 71 | "set-cookie" : [{ 72 | "key": "Set-Cookie", 73 | "value" : cookie.serialize('TOKEN', jwt.sign( 74 | { }, 75 | config.PRIVATE_KEY.trim(), 76 | { 77 | audience: headers.host[0].value, 78 | subject: auth.getSubject(username), 79 | expiresIn: config.SESSION_DURATION, 80 | algorithm: 'RS256' 81 | } // Options 82 | )) 83 | }], 84 | }, 85 | }; 86 | callback(null, nextLocation); 87 | } else { 88 | console.log("User not a member of required ORG. Unauthorized."); 89 | unauthorized('Unauthorized. User ' + response.login + ' is not a member of required organization.', callback); 90 | } 91 | }) 92 | .catch(function(error) { 93 | internalServerError('Error checking membership: ' + error.message, callback); 94 | }); 95 | }) 96 | .catch(function(error) { 97 | internalServerError('Error getting user: ' + error.message, callback); 98 | }); 99 | } 100 | }) 101 | .catch(function(error) { 102 | internalServerError('Error getting token: ' + error.message, callback); 103 | }); 104 | } else if ("cookie" in headers 105 | && "TOKEN" in cookie.parse(headers["cookie"][0].value)) { 106 | // Verify the JWT, the payload email, and that the email ends with configured hosted domain 107 | jwt.verify(cookie.parse(headers["cookie"][0].value).TOKEN, config.PUBLIC_KEY.trim(), { algorithms: ['RS256'] }, function(err, decoded) { 108 | if (err) { 109 | switch (err.name) { 110 | case 'TokenExpiredError': 111 | console.log("Token expired, redirecting to OIDC provider."); 112 | redirect(request, headers, callback); 113 | break; 114 | case 'JsonWebTokenError': 115 | console.log("JWT error, unauthorized."); 116 | unauthorized(err.message, callback); 117 | break; 118 | default: 119 | console.log("Unknown JWT error, unauthorized."); 120 | unauthorized('Unauthorized. User ' + decoded.sub + ' is not permitted.', callback); 121 | } 122 | } else { 123 | console.log("Authorizing user."); 124 | auth.isAuthorized(decoded, request, callback, unauthorized, internalServerError, config); 125 | } 126 | }); 127 | } else { 128 | console.log("Redirecting to GitHub."); 129 | redirect(request, headers, callback); 130 | } 131 | } 132 | 133 | function redirect(request, headers, callback) { 134 | config.AUTH_REQUEST.state = request.uri; 135 | // Redirect to Authorization Server 136 | var querystring = qs.stringify(config.AUTH_REQUEST); 137 | 138 | const response = { 139 | status: "302", 140 | statusDescription: "Found", 141 | body: "Redirecting to OAuth2 provider", 142 | headers: { 143 | "location" : [{ 144 | "key": "Location", 145 | "value": config.AUTHORIZATION_ENDPOINT + '?' + querystring 146 | }], 147 | "set-cookie" : [{ 148 | "key": "Set-Cookie", 149 | "value" : cookie.serialize('TOKEN', '', { path: '/', expires: new Date(1970, 1, 1, 0, 0, 0, 0) }) 150 | }], 151 | }, 152 | }; 153 | callback(null, response); 154 | } 155 | 156 | function unauthorized(body, callback) { 157 | const response = { 158 | "status": "401", 159 | "statusDescription": "Unauthorized", 160 | "body": body, 161 | "headers": { 162 | "set-cookie" : [{ 163 | "key": "Set-Cookie", 164 | "value" : cookie.serialize('TOKEN', '', { path: '/', expires: new Date(1970, 1, 1, 0, 0, 0, 0) }) 165 | }], 166 | }, 167 | }; 168 | callback(null, response); 169 | } 170 | 171 | function internalServerError(body, callback) { 172 | const response = { 173 | "status": "500", 174 | "statusDescription": "Internal Server Error", 175 | "body": body, 176 | }; 177 | callback(null, response); 178 | } 179 | -------------------------------------------------------------------------------- /authn/openid.index.js: -------------------------------------------------------------------------------- 1 | const qs = require('querystring'); 2 | const fs = require('fs'); 3 | const jwt = require('jsonwebtoken'); 4 | const cookie = require('cookie'); 5 | const jwkToPem = require('jwk-to-pem'); 6 | const auth = require('./auth.js'); 7 | const nonce = require('./nonce.js'); 8 | const axios = require('axios'); 9 | var discoveryDocument; 10 | var jwks; 11 | var config; 12 | 13 | exports.handler = (event, context, callback) => { 14 | if (typeof jwks == 'undefined' || typeof discoveryDocument == 'undefined' || typeof config == 'undefined') { 15 | config = JSON.parse(fs.readFileSync('config.json', 'utf8')); 16 | 17 | // Get Discovery Document data 18 | console.log("Get discovery document data"); 19 | axios.get(config.DISCOVERY_DOCUMENT) 20 | .then(function(response) { 21 | console.log(response); 22 | 23 | // Get jwks from discovery document url 24 | console.log("Get jwks from discovery document"); 25 | discoveryDocument = response.data; 26 | if (discoveryDocument.hasOwnProperty('jwks_uri')) { 27 | 28 | // Get public key and verify JWT 29 | axios.get(discoveryDocument.jwks_uri) 30 | .then(function(response) { 31 | console.log(response); 32 | jwks = response.data; 33 | 34 | // Callback to main function 35 | mainProcess(event, context, callback); 36 | }) 37 | .catch(function(error) { 38 | console.log("Internal server error: " + error.message); 39 | internalServerError(callback); 40 | }); 41 | } else { 42 | console.log("Internal server error: Unable to find JWK in discovery document"); 43 | internalServerError(callback); 44 | } 45 | }) 46 | .catch(function(error) { 47 | console.log("Internal server error: " + error.message); 48 | internalServerError(callback); 49 | }); 50 | } else { 51 | mainProcess(event, context, callback); 52 | } 53 | }; 54 | 55 | function mainProcess(event, context, callback) { 56 | 57 | // Get request, request headers, and querystring dictionary 58 | const request = event.Records[0].cf.request; 59 | const headers = request.headers; 60 | const queryDict = qs.parse(request.querystring); 61 | if (event.Records[0].cf.config.hasOwnProperty('test')) { 62 | config.AUTH_REQUEST.redirect_uri = event.Records[0].cf.config.test + config.CALLBACK_PATH; 63 | config.TOKEN_REQUEST.redirect_uri = event.Records[0].cf.config.test + config.CALLBACK_PATH; 64 | } 65 | if (request.uri.startsWith(config.CALLBACK_PATH)) { 66 | console.log("Callback from OIDC provider received"); 67 | 68 | // Check for error response (https://tools.ietf.org/html/rfc6749#section-4.2.2.1) 69 | if (queryDict.error) { 70 | const errors = { 71 | "invalid_request": "Invalid Request", 72 | "unauthorized_client": "Unauthorized Client", 73 | "access_denied": "Access Denied", 74 | "unsupported_response_type": "Unsupported Response Type", 75 | "invalid_scope": "Invalid Scope", 76 | "server_error": "Server Error", 77 | "temporarily_unavailable": "Temporarily Unavailable" 78 | } 79 | 80 | var error = ''; 81 | var error_description = ''; 82 | var error_uri = ''; 83 | 84 | if (errors[queryDict.error] != null) { 85 | // Replace with corresponding value 86 | error = errors[queryDict.error]; 87 | } else { 88 | // Replace with passed in value 89 | error = queryDict.error; 90 | } 91 | 92 | if (queryDict.error_description != null) { 93 | error_description = queryDict.error_description; 94 | } else { 95 | error_description = ''; 96 | } 97 | 98 | if (queryDict.error_uri != null) { 99 | error_uri = queryDict.error_uri; 100 | } else { 101 | error_uri = ''; 102 | } 103 | 104 | unauthorized(error, error_description, error_uri, callback); 105 | } 106 | 107 | // Verify code is in querystring 108 | if (!queryDict.code) { 109 | unauthorized('No Code Found', '', '', callback); 110 | } 111 | config.TOKEN_REQUEST.code = queryDict.code; 112 | 113 | // Exchange code for authorization token 114 | const postData = qs.stringify(config.TOKEN_REQUEST); 115 | console.log("Requesting access token."); 116 | axios.post(discoveryDocument.token_endpoint, postData) 117 | .then(function(response) { 118 | console.log(response); 119 | const decodedData = jwt.decode(response.data.id_token, {complete: true}); 120 | console.log(decodedData); 121 | try { 122 | console.log("Searching for JWK from discovery document"); 123 | 124 | // Search for correct JWK from discovery document and create PEM 125 | var pem = ""; 126 | for (var i = 0; i < jwks.keys.length; i++) { 127 | if (decodedData.header.kid === jwks.keys[i].kid) { 128 | pem = jwkToPem(jwks.keys[i]); 129 | } 130 | } 131 | console.log("Verifying JWT"); 132 | 133 | // Verify the JWT, the payload email, and that the email ends with configured hosted domain 134 | jwt.verify(response.data.id_token, pem, { algorithms: ['RS256'] }, function(err, decoded) { 135 | if (err) { 136 | switch (err.name) { 137 | case 'TokenExpiredError': 138 | console.log("Token expired, redirecting to OIDC provider."); 139 | redirect(request, headers, callback) 140 | break; 141 | case 'JsonWebTokenError': 142 | console.log("JWT error, unauthorized."); 143 | unauthorized('Json Web Token Error', err.message, '', callback); 144 | break; 145 | default: 146 | console.log("Unknown JWT error, unauthorized."); 147 | unauthorized('Unknown JWT', 'User ' + decodedData.payload.email + ' is not permitted.', '', callback); 148 | } 149 | } else { 150 | 151 | // Validate nonce 152 | if ("cookie" in headers 153 | && "NONCE" in cookie.parse(headers["cookie"][0].value) 154 | && nonce.validateNonce(decoded.nonce, cookie.parse(headers["cookie"][0].value).NONCE)) { 155 | console.log("Setting cookie and redirecting."); 156 | 157 | // Once verified, create new JWT for this server 158 | const response = { 159 | "status": "302", 160 | "statusDescription": "Found", 161 | "body": "ID token retrieved.", 162 | "headers": { 163 | "location" : [ 164 | { 165 | "key": "Location", 166 | "value": event.Records[0].cf.config.hasOwnProperty('test') ? (config.AUTH_REQUEST.redirect_uri + queryDict.state) : queryDict.state 167 | } 168 | ], 169 | "set-cookie" : [ 170 | { 171 | "key": "Set-Cookie", 172 | "value" : cookie.serialize('TOKEN', jwt.sign( 173 | { }, 174 | config.PRIVATE_KEY.trim(), 175 | { 176 | "audience": headers.host[0].value, 177 | "subject": auth.getSubject(decodedData), 178 | "expiresIn": config.SESSION_DURATION, 179 | "algorithm": "RS256" 180 | } // Options 181 | ), { 182 | path: '/', 183 | maxAge: config.SESSION_DURATION 184 | }) 185 | }, 186 | { 187 | "key": "Set-Cookie", 188 | "value" : cookie.serialize('NONCE', '', { 189 | path: '/', 190 | expires: new Date(1970, 1, 1, 0, 0, 0, 0) 191 | }) 192 | } 193 | ], 194 | }, 195 | }; 196 | callback(null, response); 197 | } else { 198 | unauthorized('Nonce Verification Failed', '', '', callback); 199 | } 200 | } 201 | }); 202 | } catch (error) { 203 | console.log("Internal server error: " + error.message); 204 | internalServerError(callback); 205 | } 206 | }) 207 | .catch(function(error) { 208 | console.log("Internal server error: " + error.message); 209 | internalServerError(callback); 210 | }); 211 | } else if ("cookie" in headers 212 | && "TOKEN" in cookie.parse(headers["cookie"][0].value)) { 213 | console.log("Request received with TOKEN cookie. Validating."); 214 | 215 | // Verify the JWT, the payload email, and that the email ends with configured hosted domain 216 | jwt.verify(cookie.parse(headers["cookie"][0].value).TOKEN, config.PUBLIC_KEY.trim(), { algorithms: ['RS256'] }, function(err, decoded) { 217 | if (err) { 218 | switch (err.name) { 219 | case 'TokenExpiredError': 220 | console.log("Token expired, redirecting to OIDC provider."); 221 | redirect(request, headers, callback) 222 | break; 223 | case 'JsonWebTokenError': 224 | console.log("JWT error, unauthorized."); 225 | unauthorized('Json Web Token Error', err.message, '', callback); 226 | break; 227 | default: 228 | console.log("Unknown JWT error, unauthorized."); 229 | unauthorized('Unauthorized.', 'User ' + decoded.sub + ' is not permitted.', '', callback); 230 | } 231 | } else { 232 | console.log("Authorizing user."); 233 | auth.isAuthorized(decoded, request, callback, unauthorized, internalServerError, config); 234 | } 235 | }); 236 | } else { 237 | console.log("Redirecting to OIDC provider."); 238 | redirect(request, headers, callback); 239 | } 240 | } 241 | 242 | function redirect(request, headers, callback) { 243 | const n = nonce.getNonce(); 244 | config.AUTH_REQUEST.nonce = n[0]; 245 | config.AUTH_REQUEST.state = request.uri; 246 | 247 | // Redirect to Authorization Server 248 | var querystring = qs.stringify(config.AUTH_REQUEST); 249 | 250 | const response = { 251 | "status": "302", 252 | "statusDescription": "Found", 253 | "body": "Redirecting to OIDC provider", 254 | "headers": { 255 | "location" : [{ 256 | "key": "Location", 257 | "value": discoveryDocument.authorization_endpoint + '?' + querystring 258 | }], 259 | "set-cookie" : [ 260 | { 261 | "key": "Set-Cookie", 262 | "value" : cookie.serialize('TOKEN', '', { 263 | path: '/', 264 | expires: new Date(1970, 1, 1, 0, 0, 0, 0) 265 | }) 266 | }, 267 | { 268 | "key": "Set-Cookie", 269 | "value" : cookie.serialize('NONCE', n[1], { 270 | path: '/', 271 | httpOnly: true 272 | }) 273 | } 274 | ], 275 | }, 276 | }; 277 | callback(null, response); 278 | } 279 | 280 | function unauthorized(error, error_description, error_uri, callback) { 281 | let page = ` 282 | 283 | 284 | 285 | 286 | 287 | We've got some trouble | 401 - Unauthorized 288 | 289 | 290 | 291 |

%error% Error 401

%error_description%

%error_uri%

292 | 293 | 294 | 295 | `; 296 | 297 | page = page.replace(/%error%/g, encodeURI(error).replace(/%20/g,' ')); 298 | page = page.replace(/%error_description%/g, encodeURI(error_description).replace(/%20/g,' ')); 299 | page = page.replace(/%error_uri%/g, encodeURI(error_uri)); 300 | 301 | // Unauthorized access attempt. Reset token and nonce cookies 302 | const response = { 303 | "status": "401", 304 | "statusDescription": "Unauthorized", 305 | "body": page, 306 | "headers": { 307 | "set-cookie" : [ 308 | { 309 | "key": "Set-Cookie", 310 | "value" : cookie.serialize('TOKEN', '', { 311 | path: '/', 312 | expires: new Date(1970, 1, 1, 0, 0, 0, 0) 313 | }) 314 | }, 315 | { 316 | "key": "Set-Cookie", 317 | "value" : cookie.serialize('NONCE', '', { 318 | path: '/', 319 | expires: new Date(1970, 1, 1, 0, 0, 0, 0) 320 | }) 321 | } 322 | ], 323 | }, 324 | }; 325 | callback(null, response); 326 | } 327 | 328 | function internalServerError(callback) { 329 | let page = ` 330 | 331 | 332 | 333 | 334 | 335 | We've got some trouble | 500 - Internal Server Error 336 | 337 | 338 | 339 |

Internal Server Error Error 500

340 | 341 | 342 | 343 | `; 344 | 345 | const response = { 346 | "status": "500", 347 | "statusDescription": "Internal Server Error", 348 | "body": page, 349 | }; 350 | callback(null, response); 351 | } 352 | -------------------------------------------------------------------------------- /authn/pkce.index.js: -------------------------------------------------------------------------------- 1 | const qs = require('querystring'); 2 | const fs = require('fs'); 3 | const jwt = require('jsonwebtoken'); 4 | const cookie = require('cookie'); 5 | const jwkToPem = require('jwk-to-pem'); 6 | const auth = require('./auth.js'); 7 | const nonce = require('./nonce.js'); 8 | const codeChallenge = require('./code-challenge.js'); 9 | const axios = require('axios'); 10 | var discoveryDocument; 11 | var jwks; 12 | var config; 13 | 14 | exports.handler = (event, context, callback) => { 15 | if (typeof jwks == 'undefined' || typeof discoveryDocument == 'undefined' || typeof config == 'undefined') { 16 | config = JSON.parse(fs.readFileSync('config.json', 'utf8')); 17 | 18 | // Get Discovery Document data 19 | console.log("Get discovery document data"); 20 | axios.get(config.DISCOVERY_DOCUMENT) 21 | .then(function(response) { 22 | console.log(response); 23 | 24 | // Get jwks from discovery document url 25 | console.log("Get jwks from discovery document"); 26 | discoveryDocument = response.data; 27 | if (discoveryDocument.hasOwnProperty('jwks_uri')) { 28 | 29 | // Get public key and verify JWT 30 | axios.get(discoveryDocument.jwks_uri) 31 | .then(function(response) { 32 | console.log(response); 33 | jwks = response.data; 34 | 35 | // Callback to main function 36 | mainProcess(event, context, callback); 37 | }) 38 | .catch(function(error) { 39 | console.log("Internal server error: " + error.message); 40 | internalServerError(callback); 41 | }); 42 | } else { 43 | console.log("Internal server error: Unable to find JWK in discovery document"); 44 | internalServerError(callback); 45 | } 46 | }) 47 | .catch(function(error) { 48 | console.log("Internal server error: " + error.message); 49 | internalServerError(callback); 50 | }); 51 | } else { 52 | mainProcess(event, context, callback); 53 | } 54 | }; 55 | 56 | function mainProcess(event, context, callback) { 57 | 58 | // Get request, request headers, and querystring dictionary 59 | const request = event.Records[0].cf.request; 60 | const headers = request.headers; 61 | const queryDict = qs.parse(request.querystring); 62 | if (event.Records[0].cf.config.hasOwnProperty('test')) { 63 | config.AUTH_REQUEST.redirect_uri = event.Records[0].cf.config.test + config.CALLBACK_PATH; 64 | config.TOKEN_REQUEST.redirect_uri = event.Records[0].cf.config.test + config.CALLBACK_PATH; 65 | } 66 | if (request.uri.startsWith(config.CALLBACK_PATH)) { 67 | console.log("Callback from OIDC provider received"); 68 | 69 | // Check for error response (https://tools.ietf.org/html/rfc6749#section-4.2.2.1) 70 | if (queryDict.error) { 71 | const errors = { 72 | "invalid_request": "Invalid Request", 73 | "unauthorized_client": "Unauthorized Client", 74 | "access_denied": "Access Denied", 75 | "unsupported_response_type": "Unsupported Response Type", 76 | "invalid_scope": "Invalid Scope", 77 | "server_error": "Server Error", 78 | "temporarily_unavailable": "Temporarily Unavailable" 79 | } 80 | 81 | var error = ''; 82 | var error_description = ''; 83 | var error_uri = ''; 84 | 85 | if (errors[queryDict.error] != null) { 86 | // Replace with corresponding value 87 | error = errors[queryDict.error]; 88 | } else { 89 | // Replace with passed in value 90 | error = queryDict.error; 91 | } 92 | 93 | if (queryDict.error_description != null) { 94 | error_description = queryDict.error_description; 95 | } else { 96 | error_description = ''; 97 | } 98 | 99 | if (queryDict.error_uri != null) { 100 | error_uri = queryDict.error_uri; 101 | } else { 102 | error_uri = ''; 103 | } 104 | 105 | unauthorized(error, error_description, error_uri, callback); 106 | } 107 | 108 | // Verify code is in querystring 109 | if (!queryDict.code) { 110 | unauthorized('No Code Found', '', '', callback); 111 | } 112 | config.TOKEN_REQUEST.code = queryDict.code; 113 | if ("cookie" in headers && "CV" in cookie.parse(headers["cookie"][0].value)) { 114 | config.TOKEN_REQUEST.code_verifier = cookie.parse(headers["cookie"][0].value).CV 115 | console.log("Code Verifier: " + config.TOKEN_REQUEST.code_verifier); 116 | } else { 117 | unauthorized('No Code Verifier Found', '', '', callback); 118 | } 119 | // Exchange code for authorization token 120 | const postData = qs.stringify(config.TOKEN_REQUEST); 121 | console.log("Requesting access token."); 122 | axios.post(discoveryDocument.token_endpoint, postData) 123 | .then(function(response) { 124 | console.log(response); 125 | const decodedData = jwt.decode(response.data.id_token, {complete: true}); 126 | console.log(decodedData); 127 | try { 128 | console.log("Searching for JWK from discovery document"); 129 | 130 | // Search for correct JWK from discovery document and create PEM 131 | var pem = ""; 132 | for (var i = 0; i < jwks.keys.length; i++) { 133 | if (decodedData.header.kid === jwks.keys[i].kid) { 134 | pem = jwkToPem(jwks.keys[i]); 135 | } 136 | } 137 | console.log("Verifying JWT"); 138 | 139 | // Verify the JWT, the payload email, and that the email ends with configured hosted domain 140 | jwt.verify(response.data.id_token, pem, { algorithms: ['RS256'] }, function(err, decoded) { 141 | if (err) { 142 | switch (err.name) { 143 | case 'TokenExpiredError': 144 | console.log("Token expired, redirecting to OIDC provider."); 145 | redirect(request, headers, callback) 146 | break; 147 | case 'JsonWebTokenError': 148 | console.log("JWT error, unauthorized."); 149 | unauthorized('Json Web Token Error', err.message, '', callback); 150 | break; 151 | default: 152 | console.log("Unknown JWT error, unauthorized."); 153 | unauthorized('Unknown JWT', 'User ' + decodedData.payload.email + ' is not permitted.', '', callback); 154 | } 155 | } else { 156 | 157 | // Validate nonce 158 | if ("cookie" in headers 159 | && "NONCE" in cookie.parse(headers["cookie"][0].value) 160 | && nonce.validateNonce(decoded.nonce, cookie.parse(headers["cookie"][0].value).NONCE)) { 161 | console.log("Setting cookie and redirecting."); 162 | 163 | // Once verified, create new JWT for this server 164 | const response = { 165 | "status": "302", 166 | "statusDescription": "Found", 167 | "body": "ID token retrieved.", 168 | "headers": { 169 | "location" : [ 170 | { 171 | "key": "Location", 172 | "value": event.Records[0].cf.config.hasOwnProperty('test') ? (config.AUTH_REQUEST.redirect_uri + queryDict.state) : queryDict.state 173 | } 174 | ], 175 | "set-cookie" : [ 176 | { 177 | "key": "Set-Cookie", 178 | "value" : cookie.serialize('TOKEN', jwt.sign( 179 | { }, 180 | config.PRIVATE_KEY.trim(), 181 | { 182 | "audience": headers.host[0].value, 183 | "subject": auth.getSubject(decodedData), 184 | "expiresIn": config.SESSION_DURATION, 185 | "algorithm": "RS256" 186 | } // Options 187 | ), { 188 | path: '/', 189 | maxAge: config.SESSION_DURATION 190 | }) 191 | }, 192 | { 193 | "key": "Set-Cookie", 194 | "value" : cookie.serialize('NONCE', '', { 195 | path: '/', 196 | expires: new Date(1970, 1, 1, 0, 0, 0, 0) 197 | }) 198 | } 199 | ], 200 | }, 201 | }; 202 | callback(null, response); 203 | } else { 204 | unauthorized('Nonce Verification Failed', '', '', callback); 205 | } 206 | } 207 | }); 208 | } catch (error) { 209 | console.log("Internal server error: " + error.message); 210 | internalServerError(callback); 211 | } 212 | }) 213 | .catch(function(error) { 214 | console.log("Internal server error: " + error.message); 215 | internalServerError(callback); 216 | }); 217 | } else if ("cookie" in headers 218 | && "TOKEN" in cookie.parse(headers["cookie"][0].value)) { 219 | console.log("Request received with TOKEN cookie. Validating."); 220 | 221 | // Verify the JWT, the payload email, and that the email ends with configured hosted domain 222 | jwt.verify(cookie.parse(headers["cookie"][0].value).TOKEN, config.PUBLIC_KEY.trim(), { algorithms: ['RS256'] }, function(err, decoded) { 223 | if (err) { 224 | switch (err.name) { 225 | case 'TokenExpiredError': 226 | console.log("Token expired, redirecting to OIDC provider."); 227 | redirect(request, headers, callback) 228 | break; 229 | case 'JsonWebTokenError': 230 | console.log("JWT error, unauthorized."); 231 | unauthorized('Json Web Token Error', err.message, '', callback); 232 | break; 233 | default: 234 | console.log("Unknown JWT error, unauthorized."); 235 | unauthorized('Unauthorized.', 'User ' + decoded.sub + ' is not permitted.', '', callback); 236 | } 237 | } else { 238 | console.log("Authorizing user."); 239 | auth.isAuthorized(decoded, request, callback, unauthorized, internalServerError, config); 240 | } 241 | }); 242 | } else { 243 | console.log("Redirecting to OIDC provider."); 244 | redirect(request, headers, callback); 245 | } 246 | } 247 | 248 | function redirect(request, headers, callback) { 249 | const n = nonce.getNonce(); 250 | const challenge = codeChallenge.get(parseInt(config.PKCE_CODE_VERIFIER_LENGTH)); 251 | config.AUTH_REQUEST.code_challenge=challenge[1]; 252 | config.AUTH_REQUEST.code_challenge_method="S256" 253 | config.AUTH_REQUEST.nonce = n[0]; 254 | config.AUTH_REQUEST.state = request.uri; 255 | 256 | // Redirect to Authorization Server 257 | var querystring = qs.stringify(config.AUTH_REQUEST); 258 | 259 | const response = { 260 | "status": "302", 261 | "statusDescription": "Found", 262 | "body": "Redirecting to OIDC provider", 263 | "headers": { 264 | "location" : [{ 265 | "key": "Location", 266 | "value": discoveryDocument.authorization_endpoint + '?' + querystring 267 | }], 268 | "set-cookie" : [ 269 | { 270 | "key": "Set-Cookie", 271 | "value" : cookie.serialize('TOKEN', '', { 272 | path: '/', 273 | expires: new Date(1970, 1, 1, 0, 0, 0, 0) 274 | }) 275 | }, 276 | { 277 | "key": "Set-Cookie", 278 | "value" : cookie.serialize('NONCE', n[1], { 279 | path: '/', 280 | httpOnly: true 281 | }) 282 | }, 283 | { 284 | "key": "Set-Cookie", 285 | "value" : cookie.serialize('CV', challenge[0], { 286 | path: '/', 287 | httpOnly: true 288 | }) 289 | } 290 | ], 291 | }, 292 | }; 293 | callback(null, response); 294 | } 295 | 296 | 297 | function unauthorized(error, error_description, error_uri, callback) { 298 | let page = ` 299 | 300 | 301 | 302 | 303 | 304 | We've got some trouble | 401 - Unauthorized 305 | 306 | 307 | 308 |

%error% Error 401

%error_description%

%error_uri%

309 | 310 | 311 | 312 | `; 313 | 314 | page = page.replace(/%error%/g, error); 315 | page = page.replace(/%error_description%/g, error_description); 316 | page = page.replace(/%error_uri%/g, error_uri); 317 | 318 | // Unauthorized access attempt. Reset token and nonce cookies 319 | const response = { 320 | "status": "401", 321 | "statusDescription": "Unauthorized", 322 | "body": page, 323 | "headers": { 324 | "set-cookie" : [ 325 | { 326 | "key": "Set-Cookie", 327 | "value" : cookie.serialize('TOKEN', '', { 328 | path: '/', 329 | expires: new Date(1970, 1, 1, 0, 0, 0, 0) 330 | }) 331 | }, 332 | { 333 | "key": "Set-Cookie", 334 | "value" : cookie.serialize('NONCE', '', { 335 | path: '/', 336 | expires: new Date(1970, 1, 1, 0, 0, 0, 0) 337 | }) 338 | } 339 | ], 340 | }, 341 | }; 342 | callback(null, response); 343 | } 344 | 345 | function internalServerError(callback) { 346 | let page = ` 347 | 348 | 349 | 350 | 351 | 352 | We've got some trouble | 500 - Internal Server Error 353 | 354 | 355 | 356 |

Internal Server Error Error 500

357 | 358 | 359 | 360 | `; 361 | 362 | const response = { 363 | "status": "500", 364 | "statusDescription": "Internal Server Error", 365 | "body": page, 366 | }; 367 | callback(null, response); 368 | } 369 | -------------------------------------------------------------------------------- /authz/auth0.js: -------------------------------------------------------------------------------- 1 | function isAuthorized(decoded, request, callback, unauthorized, internalServerError, config) { 2 | callback(null, request); 3 | } 4 | 5 | function getSubject(decoded) { 6 | return decoded.payload.email; 7 | } 8 | 9 | exports.isAuthorized = isAuthorized; 10 | exports.getSubject = getSubject; 11 | -------------------------------------------------------------------------------- /authz/centrify.js: -------------------------------------------------------------------------------- 1 | function isAuthorized(decoded, request, callback, unauthorized, internalServerError, config) { 2 | callback(null, request); 3 | } 4 | 5 | function getSubject(decoded) { 6 | return decoded.payload.email; 7 | } 8 | 9 | exports.isAuthorized = isAuthorized; 10 | exports.getSubject = getSubject; 11 | -------------------------------------------------------------------------------- /authz/github.membership-lookup.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | function isAuthorized(decoded, request, callback, unauthorized, internalServerError, config) { 4 | callback(null, request); 5 | } 6 | 7 | function getSubject(data) { return data; } 8 | 9 | exports.isAuthorized = isAuthorized; 10 | exports.getSubject = getSubject; 11 | -------------------------------------------------------------------------------- /authz/google.groups-lookup.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const axios = require('axios'); 3 | const jwt = require('jsonwebtoken'); 4 | const qs = require('querystring'); 5 | 6 | function isAuthorized(decoded, request, callback, unauthorized, internalServerError, config) { 7 | var googleAuthz = JSON.parse(fs.readFileSync('./google-authz.json')); 8 | var groupChecks = 0; 9 | var token = jwt.sign({ 10 | scope: 'https://www.googleapis.com/auth/admin.directory.group.member.readonly' 11 | }, 12 | googleAuthz.private_key, { 13 | issuer: googleAuthz.client_email, 14 | expiresIn: 3600, 15 | audience: googleAuthz.token_uri, 16 | subject: config.SERVICE_ACCOUNT_EMAIL, 17 | algorithm: 'RS256' 18 | }); 19 | const postData = qs.stringify({ 20 | grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', 21 | assertion: token 22 | }); 23 | axios.post(googleAuthz.token_uri, postData) 24 | .then(function(response) { 25 | for (var i = 0; i < googleAuthz.cloudfront_authz_groups.length; i++) { 26 | var authorization = response.data.token_type + ' ' + response.data.access_token; 27 | var membershipGet = 'https://www.googleapis.com/admin/directory/v1/groups/' + googleAuthz.cloudfront_authz_groups[i] + '/hasMember/' + decoded.sub; 28 | console.log(membershipGet + ': ' + authorization); 29 | axios.get(membershipGet, { headers: {'Authorization': authorization}}) 30 | .then(function(response) { 31 | groupChecks++; 32 | if (!response.data.error && response.data.isMember == true && decoded.aud === request.headers.host[0].value && decoded.sub.endsWith(config.HOSTED_DOMAIN)) { 33 | callback(null, request); 34 | } else if (groupChecks >= googleAuthz.cloudfront_authz_groups.length) { 35 | unauthorized('Unauthorized', 'User ' + decoded.sub + ' is not permitted.', '', callback); 36 | } 37 | }) 38 | .catch(function(error) { 39 | groupChecks++; 40 | if (groupChecks >= googleAuthz.cloudfront_authz_groups.length) { 41 | unauthorized('Unauthorized.', 'User ' + decoded.sub + ' is not permitted.', '', callback); 42 | } 43 | }); 44 | } 45 | }) 46 | .catch(function(error) { 47 | internalServerError(callback); 48 | }); 49 | } 50 | 51 | function getSubject(decoded) { return decoded.payload.email; } 52 | 53 | exports.isAuthorized = isAuthorized; 54 | exports.getSubject = getSubject; 55 | -------------------------------------------------------------------------------- /authz/google.hosted-domain.js: -------------------------------------------------------------------------------- 1 | function isAuthorized(decoded, request, callback, unauthorized, internalServerError, config) { 2 | if (decoded.sub.endsWith(config.HOSTED_DOMAIN)) { 3 | callback(null, request); 4 | } else { 5 | unauthorized('Unauthorized', 'User ' + decoded.sub + ' is not permitted.', '', callback); 6 | } 7 | } 8 | 9 | function getSubject(decoded) { return decoded.payload.email; } 10 | 11 | exports.isAuthorized = isAuthorized; 12 | exports.getSubject = getSubject; 13 | -------------------------------------------------------------------------------- /authz/google.json-email-lookup.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | function isAuthorized(decoded, request, callback, unauthorized, internalServerError, config) { 4 | axios.get(config.JSON_EMAIL_LOOKUP) 5 | .then(function(response) { 6 | if (Array.isArray(response.data) && response.data.indexOf(decoded.sub) > -1) { 7 | callback(null, request); 8 | } else { 9 | unauthorized('Unauthorized', 'User ' + decoded.sub + ' is not permitted.', '', callback); 10 | } 11 | }) 12 | .catch(function(error) { 13 | internalServerError(error.message, callback); 14 | }); 15 | } 16 | 17 | function getSubject(decoded) { return decoded.payload.email; } 18 | 19 | exports.isAuthorized = isAuthorized; 20 | exports.getSubject = getSubject; 21 | -------------------------------------------------------------------------------- /authz/microsoft.js: -------------------------------------------------------------------------------- 1 | function isAuthorized(decoded, request, callback, unauthorized, internalServerError, config) { 2 | callback(null, request); 3 | } 4 | 5 | function getSubject(decoded) { 6 | return decoded.payload.unique_name; 7 | } 8 | 9 | exports.isAuthorized = isAuthorized; 10 | exports.getSubject = getSubject; 11 | -------------------------------------------------------------------------------- /authz/microsoft.json-username-lookup.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | function isAuthorized(decoded, request, callback, unauthorized, internalServerError, config) { 4 | axios.get(config.JSON_USERNAME_LOOKUP) 5 | .then(function(response) { 6 | if (Array.isArray(response.data) && response.data.indexOf(decoded.sub) > -1) { 7 | callback(null, request); 8 | } else { 9 | unauthorized('Unauthorized', 'User ' + decoded.sub + ' is not permitted.', '', callback); 10 | } 11 | }) 12 | .catch(function(error) { 13 | internalServerError(error.message, callback); 14 | }); 15 | } 16 | 17 | function getSubject(decoded) { 18 | if (decoded.payload.hasOwnProperty('upn')) { 19 | return decoded.payload.upn; 20 | } else { 21 | return 'Username not found'; 22 | } 23 | } 24 | 25 | exports.isAuthorized = isAuthorized; 26 | exports.getSubject = getSubject; 27 | -------------------------------------------------------------------------------- /authz/okta.js: -------------------------------------------------------------------------------- 1 | function isAuthorized(decoded, request, callback, unauthorized, internalServerError, config) { 2 | callback(null, request); 3 | } 4 | 5 | function getSubject(decoded) { 6 | return decoded.payload.email; 7 | } 8 | 9 | exports.isAuthorized = isAuthorized; 10 | exports.getSubject = getSubject; 11 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | if [ ! -d "distributions" ]; then 5 | if [ ! -L "distributions" ]; then 6 | mkdir distributions 7 | fi 8 | fi 9 | 10 | npm run-script build 11 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | const shell = require('shelljs'); 2 | const prompt = require('prompt'); 3 | const fs = require('fs'); 4 | const axios = require('axios'); 5 | const colors = require('colors/safe'); 6 | const url = require('url'); 7 | const R = require('ramda'); 8 | 9 | var config = { AUTH_REQUEST: {}, TOKEN_REQUEST: {} }; 10 | var oldConfig; 11 | 12 | prompt.message = colors.blue(">"); 13 | prompt.start(); 14 | prompt.get({ 15 | properties: { 16 | distribution: { 17 | message: colors.red("Enter distribution name"), 18 | required: true 19 | }, 20 | method: { 21 | description: colors.red("Authentication methods:\n (1) Google\n (2) Microsoft\n (3) GitHub\n (4) OKTA\n (5) Auth0\n (6) Centrify\n (7) OKTA Native\n\n Select an authentication method") 22 | } 23 | } 24 | }, function (err, result) { 25 | config.DISTRIBUTION = result.distribution; 26 | shell.mkdir('-p', 'distributions/' + config.DISTRIBUTION); 27 | if (fs.existsSync('distributions/' + config.DISTRIBUTION + '/config.json')) { 28 | oldConfig = JSON.parse(fs.readFileSync('./distributions/' + config.DISTRIBUTION + '/config.json', 'utf8')); 29 | } 30 | if (!fs.existsSync('distributions/' + config.DISTRIBUTION + '/id_rsa') || !fs.existsSync('./distributions/' + config.DISTRIBUTION + '/id_rsa.pub')) { 31 | shell.exec("ssh-keygen -t rsa -m PEM -b 4096 -f ./distributions/" + config.DISTRIBUTION + "/id_rsa -N ''"); 32 | shell.exec("openssl rsa -in ./distributions/" + config.DISTRIBUTION + "/id_rsa -pubout -outform PEM -out ./distributions/" + config.DISTRIBUTION + "/id_rsa.pub"); 33 | } 34 | switch (result.method) { 35 | case '1': 36 | if (R.pathOr('', ['AUTHN'], oldConfig) != "GOOGLE") { 37 | oldConfig = undefined; 38 | } 39 | config.AUTHN = "GOOGLE"; 40 | googleConfiguration(); 41 | break; 42 | case '2': 43 | if (R.pathOr('', ['AUTHN'], oldConfig) != "MICROSOFT") { 44 | oldConfig = undefined; 45 | } 46 | config.AUTHN = "MICROSOFT"; 47 | microsoftConfiguration(); 48 | break; 49 | case '3': 50 | if (R.pathOr('', ['AUTHN'], oldConfig) != "GITHUB") { 51 | oldConfig = undefined; 52 | } 53 | config.AUTHN = "GITHUB"; 54 | githubConfiguration(); 55 | break; 56 | case '4': 57 | if (R.pathOr('', ['AUTHN'], oldConfig) != "OKTA") { 58 | oldConfig = undefined; 59 | } 60 | config.AUTHN = "OKTA"; 61 | oktaConfiguration(); 62 | break; 63 | case '5': 64 | if (R.pathOr('', ['AUTHN'], oldConfig) != "AUTH0") { 65 | oldConfig = undefined; 66 | } 67 | config.AUTHN = "AUTH0"; 68 | auth0Configuration(); 69 | break; 70 | case '6': 71 | if (R.pathOr('', ['AUTHN'], oldConfig) != "CENTRIFY") { 72 | oldConfig = undefined; 73 | } 74 | config.AUTHN = "CENTRIFY"; 75 | centrifyConfiguration(); 76 | break; 77 | case '7': 78 | if (R.pathOr('', ['AUTHN'], oldConfig) != "OKTA_NATIVE") { 79 | oldConfig = undefined; 80 | } 81 | config.AUTHN = "OKTA_NATIVE"; 82 | oktaConfiguration(); 83 | break; 84 | default: 85 | console.log("Method not recognized. Stopping build..."); 86 | process.exit(1); 87 | } 88 | }); 89 | 90 | function microsoftConfiguration() { 91 | prompt.message = colors.blue(">>"); 92 | prompt.start(); 93 | prompt.get({ 94 | properties: { 95 | TENANT: { 96 | message: colors.red("Tenant"), 97 | required: true, 98 | default: R.pathOr('', ['TENANT'], oldConfig) 99 | }, 100 | CLIENT_ID: { 101 | message: colors.red("Client ID"), 102 | required: true, 103 | default: R.pathOr('', ['AUTH_REQUEST', 'client_id'], oldConfig) 104 | }, 105 | CLIENT_SECRET: { 106 | message: colors.red("Client Secret"), 107 | required: true, 108 | default: R.pathOr('', ['TOKEN_REQUEST', 'client_secret'], oldConfig) 109 | }, 110 | REDIRECT_URI: { 111 | message: colors.red("Redirect URI"), 112 | required: true, 113 | default: R.pathOr('', ['AUTH_REQUEST', 'redirect_uri'], oldConfig) 114 | }, 115 | SESSION_DURATION: { 116 | message: colors.red("Session Duration (hours)"), 117 | required: true, 118 | default: R.pathOr('', ['SESSION_DURATION'], oldConfig)/60/60 119 | }, 120 | AUTHZ: { 121 | description: colors.red("Authorization methods:\n (1) Azure AD Login (default)\n (2) JSON Username Lookup\n\n Select an authorization method") 122 | } 123 | } 124 | }, function(err, result) { 125 | config.PRIVATE_KEY = fs.readFileSync('distributions/' + config.DISTRIBUTION + '/id_rsa', 'utf8'); 126 | config.PUBLIC_KEY = fs.readFileSync('distributions/' + config.DISTRIBUTION + '/id_rsa.pub', 'utf8'); 127 | config.TENANT = result.TENANT; 128 | config.DISCOVERY_DOCUMENT = 'https://login.microsoftonline.com/' + result.TENANT + '/.well-known/openid-configuration'; 129 | config.SESSION_DURATION = parseInt(result.SESSION_DURATION, 10) * 60 * 60; 130 | 131 | config.CALLBACK_PATH = url.parse(result.REDIRECT_URI).pathname; 132 | 133 | config.AUTH_REQUEST.client_id = result.CLIENT_ID; 134 | config.AUTH_REQUEST.redirect_uri = result.REDIRECT_URI; 135 | config.AUTH_REQUEST.response_type = 'code'; 136 | config.AUTH_REQUEST.response_mode = 'query'; 137 | config.AUTH_REQUEST.scope = 'openid'; 138 | 139 | config.TOKEN_REQUEST.client_id = result.CLIENT_ID; 140 | config.TOKEN_REQUEST.grant_type = 'authorization_code'; 141 | config.TOKEN_REQUEST.redirect_uri = result.REDIRECT_URI; 142 | config.TOKEN_REQUEST.client_secret = result.CLIENT_SECRET; 143 | 144 | config.AUTHZ = result.AUTHZ; 145 | 146 | shell.cp('./authz/microsoft.js', './distributions/' + config.DISTRIBUTION + '/auth.js'); 147 | shell.cp('./authn/openid.index.js', './distributions/' + config.DISTRIBUTION + '/index.js'); 148 | shell.cp('./nonce.js', './distributions/' + config.DISTRIBUTION + '/nonce.js'); 149 | 150 | fs.writeFileSync('distributions/' + config.DISTRIBUTION + '/config.json', JSON.stringify(result, null, 4)); 151 | 152 | switch (result.AUTHZ) { 153 | case '1': 154 | shell.cp('./authz/microsoft.js', './distributions/' + config.DISTRIBUTION + '/auth.js'); 155 | writeConfig(config, zip, ['config.json', 'index.js', 'auth.js', 'nonce.js']); 156 | break; 157 | case '2': 158 | shell.cp('./authz/microsoft.json-username-lookup.js', './distributions/' + config.DISTRIBUTION + '/auth.js'); 159 | prompt.start(); 160 | prompt.message = colors.blue(">>>"); 161 | prompt.get({ 162 | properties: { 163 | JSON_USERNAME_LOOKUP: { 164 | description: colors.red("JSON username lookup endpoint"), 165 | default: R.pathOr('', ['JSON_USERNAME_LOOKUP'], oldConfig) 166 | } 167 | } 168 | }, function (err, result) { 169 | config.JSON_USERNAME_LOOKUP = result.JSON_USERNAME_LOOKUP; 170 | writeConfig(config, zip, ['config.json', 'index.js', 'auth.js', 'nonce.js']); 171 | }); 172 | break; 173 | default: 174 | console.log("Method not recognized. Stopping build..."); 175 | } 176 | }); 177 | } 178 | 179 | function googleConfiguration() { 180 | prompt.message = colors.blue(">>"); 181 | prompt.start(); 182 | prompt.get({ 183 | properties: { 184 | CLIENT_ID: { 185 | message: colors.red("Client ID"), 186 | required: true, 187 | default: R.pathOr('', ['AUTH_REQUEST', 'client_id'], oldConfig) 188 | }, 189 | CLIENT_SECRET: { 190 | message: colors.red("Client Secret"), 191 | required: true, 192 | default: R.pathOr('', ['TOKEN_REQUEST', 'client_secret'], oldConfig) 193 | }, 194 | REDIRECT_URI: { 195 | message: colors.red("Redirect URI"), 196 | required: true, 197 | default: R.pathOr('', ['AUTH_REQUEST', 'redirect_uri'], oldConfig) 198 | }, 199 | HD: { 200 | message: colors.red("Hosted Domain"), 201 | required: true, 202 | default: R.pathOr('', ['AUTH_REQUEST', 'hd'], oldConfig) 203 | }, 204 | SESSION_DURATION: { 205 | pattern: /^[0-9]*$/, 206 | description: colors.red("Session Duration (hours)"), 207 | message: colors.green("Entry must only contain numbers"), 208 | required: true, 209 | default: R.pathOr('', ['SESSION_DURATION'], oldConfig)/60/60 210 | }, 211 | AUTHZ: { 212 | description: colors.red("Authorization methods:\n (1) Hosted Domain - verify email's domain matches that of the given hosted domain\n (2) HTTP Email Lookup - verify email exists in JSON array located at given HTTP endpoint\n (3) Google Groups Lookup - verify email exists in one of given Google Groups\n\n Select an authorization method") 213 | } 214 | } 215 | }, function(err, result) { 216 | config.PRIVATE_KEY = fs.readFileSync('distributions/' + config.DISTRIBUTION + '/id_rsa', 'utf8'); 217 | config.PUBLIC_KEY = fs.readFileSync('distributions/' + config.DISTRIBUTION + '/id_rsa.pub', 'utf8'); 218 | config.DISCOVERY_DOCUMENT = 'https://accounts.google.com/.well-known/openid-configuration'; 219 | config.SESSION_DURATION = parseInt(result.SESSION_DURATION, 10) * 60 * 60; 220 | 221 | config.CALLBACK_PATH = url.parse(result.REDIRECT_URI).pathname; 222 | config.HOSTED_DOMAIN = result.HD; 223 | 224 | config.AUTH_REQUEST.client_id = result.CLIENT_ID; 225 | config.AUTH_REQUEST.response_type = 'code'; 226 | config.AUTH_REQUEST.scope = 'openid email'; 227 | config.AUTH_REQUEST.redirect_uri = result.REDIRECT_URI; 228 | config.AUTH_REQUEST.hd = result.HD; 229 | 230 | config.TOKEN_REQUEST.client_id = result.CLIENT_ID; 231 | config.TOKEN_REQUEST.client_secret = result.CLIENT_SECRET; 232 | config.TOKEN_REQUEST.redirect_uri = result.REDIRECT_URI; 233 | config.TOKEN_REQUEST.grant_type = 'authorization_code'; 234 | 235 | config.AUTHZ = result.AUTHZ; 236 | 237 | shell.cp('./authn/openid.index.js', './distributions/' + config.DISTRIBUTION + '/index.js'); 238 | shell.cp('./nonce.js', './distributions/' + config.DISTRIBUTION + '/nonce.js'); 239 | 240 | fs.writeFileSync('distributions/' + config.DISTRIBUTION + '/config.json', JSON.stringify(result, null, 4)); 241 | 242 | switch (result.AUTHZ) { 243 | case '1': 244 | shell.cp('./authz/google.hosted-domain.js', './distributions/' + config.DISTRIBUTION + '/auth.js'); 245 | shell.cp('./nonce.js', './distributions/' + config.DISTRIBUTION + '/nonce.js'); 246 | writeConfig(config, zip, ['config.json', 'index.js', 'auth.js', 'nonce.js']); 247 | break; 248 | case '2': 249 | shell.cp('./authz/google.json-email-lookup.js', './distributions/' + config.DISTRIBUTION + '/auth.js'); 250 | prompt.start(); 251 | prompt.message = colors.blue(">>>"); 252 | prompt.get({ 253 | properties: { 254 | JSON_EMAIL_LOOKUP: { 255 | description: colors.red("JSON email lookup endpoint"), 256 | default: R.pathOr('', ['JSON_EMAIL_LOOKUP'], oldConfig) 257 | } 258 | } 259 | }, function (err, result) { 260 | config.JSON_EMAIL_LOOKUP = result.JSON_EMAIL_LOOKUP; 261 | writeConfig(config, zip, ['config.json', 'index.js', 'auth.js', 'nonce.js']); 262 | }); 263 | break; 264 | case '3': 265 | prompt.start(); 266 | prompt.message = colors.blue(">>>"); 267 | prompt.get({ 268 | properties: { 269 | MOVE: { 270 | message: colors.red("Place ") + colors.blue("google-authz.json") + colors.red(" file into ") + colors.blue("distributions/" + config.DISTRIBUTION) + colors.red(" folder. Press enter when done") 271 | } 272 | } 273 | }, function (err, result) { 274 | if (!shell.test('-f', 'distributions/' + config.DISTRIBUTION + '/google-authz.json')) { 275 | console.log('Need google-authz.json to use google groups authentication. Stopping build...'); 276 | } else { 277 | var googleAuthz = JSON.parse(fs.readFileSync('distributions/' + config.DISTRIBUTION + '/google-authz.json')); 278 | if (!googleAuthz.hasOwnProperty('cloudfront_authz_groups')) { 279 | console.log('google-authz.json is missing cloudfront_authz_groups. Stopping build...'); 280 | } else { 281 | shell.cp('./authz/google.groups-lookup.js', './distributions/' + config.DISTRIBUTION + '/auth.js'); 282 | googleGroupsConfiguration(); 283 | } 284 | } 285 | }); 286 | break; 287 | default: 288 | console.log("Method not recognized. Stopping build..."); 289 | } 290 | }); 291 | } 292 | 293 | function googleGroupsConfiguration() { 294 | prompt.start(); 295 | prompt.message = colors.blue(">>>"); 296 | prompt.get({ 297 | properties: { 298 | SERVICE_ACCOUNT_EMAIL: { 299 | description: colors.red("Service Account Email"), 300 | required: true, 301 | default: R.pathOr('', ['SERVICE_ACCOUNT_EMAIL'], oldConfig) 302 | } 303 | } 304 | }, function (err, result) { 305 | config.SERVICE_ACCOUNT_EMAIL = result.SERVICE_ACCOUNT_EMAIL; 306 | writeConfig(config, zip, ['config.json', 'index.js', 'auth.js', 'google-authz.json', 'nonce.js']); 307 | }); 308 | } 309 | 310 | function oktaConfiguration() { 311 | var properties = { 312 | BASE_URL: { 313 | message: colors.red("Base URL"), 314 | required: true, 315 | default: R.pathOr('', ['BASE_URL'], oldConfig) 316 | }, 317 | CLIENT_ID: { 318 | message: colors.red("Client ID"), 319 | required: true, 320 | default: R.pathOr('', ['AUTH_REQUEST', 'client_id'], oldConfig) 321 | }, 322 | REDIRECT_URI: { 323 | message: colors.red("Redirect URI"), 324 | required: true, 325 | default: R.pathOr('', ['AUTH_REQUEST', 'redirect_uri'], oldConfig) 326 | }, 327 | SESSION_DURATION: { 328 | pattern: /^[0-9]*$/, 329 | description: colors.red("Session Duration (hours)"), 330 | message: colors.green("Entry must only contain numbers"), 331 | required: true, 332 | default: R.pathOr('', ['SESSION_DURATION'], oldConfig)/60/60 333 | } 334 | }; 335 | 336 | if(config.AUTHN == 'OKTA') { 337 | properties['CLIENT_SECRET'] = { 338 | message: colors.red("Client Secret"), 339 | required: true, 340 | default: R.pathOr('', ['TOKEN_REQUEST', 'client_secret'], oldConfig) 341 | } 342 | } else if (config.AUTHN == 'OKTA_NATIVE') { 343 | properties['PKCE_CODE_VERIFIER_LENGTH'] = { 344 | message: colors.red("Length of random PKCE code_verifier"), 345 | required: true, 346 | default: R.pathOr('', ['PKCE_CODE_VERIFIER_LENGTH'], oldConfig) 347 | } 348 | } 349 | 350 | prompt.message = colors.blue(">>"); 351 | prompt.start(); 352 | prompt.get({ 353 | properties: properties 354 | }, function(err, result) { 355 | config.PRIVATE_KEY = fs.readFileSync('distributions/' + config.DISTRIBUTION + '/id_rsa', 'utf8'); 356 | config.PUBLIC_KEY = fs.readFileSync('distributions/' + config.DISTRIBUTION + '/id_rsa.pub', 'utf8'); 357 | config.DISCOVERY_DOCUMENT = result.BASE_URL + '/.well-known/openid-configuration'; 358 | config.SESSION_DURATION = parseInt(result.SESSION_DURATION, 10) * 60 * 60; 359 | 360 | config.BASE_URL = result.BASE_URL; 361 | config.CALLBACK_PATH = url.parse(result.REDIRECT_URI).pathname; 362 | 363 | config.AUTH_REQUEST.client_id = result.CLIENT_ID; 364 | config.AUTH_REQUEST.response_type = 'code'; 365 | config.AUTH_REQUEST.scope = 'openid email'; 366 | config.AUTH_REQUEST.redirect_uri = result.REDIRECT_URI; 367 | 368 | config.TOKEN_REQUEST.client_id = result.CLIENT_ID; 369 | config.TOKEN_REQUEST.redirect_uri = result.REDIRECT_URI; 370 | config.TOKEN_REQUEST.grant_type = 'authorization_code'; 371 | var files = ['config.json', 'index.js', 'auth.js', 'nonce.js']; 372 | if(result.CLIENT_SECRET) { 373 | config.TOKEN_REQUEST.client_secret = result.CLIENT_SECRET; 374 | shell.cp('./authn/openid.index.js', './distributions/' + config.DISTRIBUTION + '/index.js'); 375 | } else { 376 | config.PKCE_CODE_VERIFIER_LENGTH = result.PKCE_CODE_VERIFIER_LENGTH || "96"; 377 | shell.cp('./code-challenge.js', './distributions/' + config.DISTRIBUTION + '/code-challenge.js'); 378 | shell.cp('./authn/pkce.index.js', './distributions/' + config.DISTRIBUTION + '/index.js'); 379 | files.push('code-challenge.js'); 380 | } 381 | config.AUTHZ = "OKTA"; 382 | 383 | shell.cp('./nonce.js', './distributions/' + config.DISTRIBUTION + '/nonce.js'); 384 | fs.writeFileSync('distributions/' + config.DISTRIBUTION + '/config.json', JSON.stringify(result, null, 4)); 385 | shell.cp('./authz/okta.js', './distributions/' + config.DISTRIBUTION + '/auth.js'); 386 | writeConfig(config, zip, files); 387 | }); 388 | } 389 | 390 | function githubConfiguration() { 391 | prompt.message = colors.blue(">>"); 392 | prompt.start(); 393 | prompt.get({ 394 | properties: { 395 | CLIENT_ID: { 396 | message: colors.red("Client ID"), 397 | required: true, 398 | default: R.pathOr('', ['AUTH_REQUEST', 'client_id'], oldConfig) 399 | }, 400 | CLIENT_SECRET: { 401 | message: colors.red("Client Secret"), 402 | required: true, 403 | default: R.pathOr('', ['TOKEN_REQUEST', 'client_secret'], oldConfig) 404 | }, 405 | REDIRECT_URI: { 406 | message: colors.red("Redirect URI"), 407 | required: true, 408 | default: R.pathOr('', ['AUTH_REQUEST', 'redirect_uri'], oldConfig) 409 | }, 410 | SESSION_DURATION: { 411 | pattern: /^[0-9]*$/, 412 | description: colors.red("Session Duration (hours)"), 413 | message: colors.green("Entry must only contain numbers"), 414 | required: true, 415 | default: R.pathOr('', ['SESSION_DURATION'], oldConfig)/60/60 416 | }, 417 | ORGANIZATION: { 418 | description: colors.red("Organization"), 419 | required: true, 420 | default: R.pathOr('', ['ORGANIZATION'], oldConfig) 421 | } 422 | } 423 | }, function(err, result) { 424 | axios.get('https://api.github.com/orgs/' + result.ORGANIZATION) 425 | .then(function (response) { 426 | if (response.status == 200) { 427 | config.PRIVATE_KEY = fs.readFileSync('distributions/' + config.DISTRIBUTION + '/id_rsa', 'utf8'); 428 | config.PUBLIC_KEY = fs.readFileSync('distributions/' + config.DISTRIBUTION + '/id_rsa.pub', 'utf8'); 429 | config.SESSION_DURATION = parseInt(result.SESSION_DURATION, 10) * 60 * 60; 430 | config.CALLBACK_PATH = url.parse(result.REDIRECT_URI).pathname; 431 | config.ORGANIZATION = result.ORGANIZATION; 432 | config.AUTHORIZATION_ENDPOINT = 'https://github.com/login/oauth/authorize'; 433 | config.TOKEN_ENDPOINT = 'https://github.com/login/oauth/access_token'; 434 | 435 | config.AUTH_REQUEST.client_id = result.CLIENT_ID; 436 | config.AUTH_REQUEST.redirect_uri = result.REDIRECT_URI; 437 | config.AUTH_REQUEST.scope = 'read:org user:email'; 438 | 439 | config.TOKEN_REQUEST.client_id = result.CLIENT_ID; 440 | config.TOKEN_REQUEST.client_secret = result.CLIENT_SECRET; 441 | config.TOKEN_REQUEST.redirect_uri = result.REDIRECT_URI; 442 | 443 | shell.cp('./authz/github.membership-lookup.js', './distributions/' + config.DISTRIBUTION + '/auth.js'); 444 | shell.cp('./authn/github.index.js', './distributions/' + config.DISTRIBUTION + '/index.js'); 445 | writeConfig(config, zip, ['config.json', 'index.js', 'auth.js']); 446 | } else { 447 | console.log("Organization could not be verified (code " + response.status + "). Stopping build..."); 448 | } 449 | }) 450 | .catch(function(error) { 451 | console.log("Organization could not be verified. Stopping build... (" + error.message + ")"); 452 | }); 453 | }); 454 | } 455 | 456 | // Auth0 configuration 457 | function auth0Configuration() { 458 | prompt.message = colors.blue(">>"); 459 | prompt.start(); 460 | prompt.get({ 461 | properties: { 462 | BASE_URL: { 463 | message: colors.red("Base URL"), 464 | required: true, 465 | default: R.pathOr('', ['BASE_URL'], oldConfig) 466 | }, 467 | CLIENT_ID: { 468 | message: colors.red("Client ID"), 469 | required: true, 470 | default: R.pathOr('', ['AUTH_REQUEST', 'client_id'], oldConfig) 471 | }, 472 | CLIENT_SECRET: { 473 | message: colors.red("Client Secret"), 474 | required: true, 475 | default: R.pathOr('', ['TOKEN_REQUEST', 'client_secret'], oldConfig) 476 | }, 477 | REDIRECT_URI: { 478 | message: colors.red("Redirect URI"), 479 | required: true, 480 | default: R.pathOr('', ['AUTH_REQUEST', 'redirect_uri'], oldConfig) 481 | }, 482 | SESSION_DURATION: { 483 | pattern: /^[0-9]*$/, 484 | description: colors.red("Session Duration (hours)"), 485 | message: colors.green("Entry must only contain numbers"), 486 | required: true, 487 | default: R.pathOr('', ['SESSION_DURATION'], oldConfig)/60/60 488 | } 489 | } 490 | }, function(err, result) { 491 | config.PRIVATE_KEY = fs.readFileSync('distributions/' + config.DISTRIBUTION + '/id_rsa', 'utf8'); 492 | config.PUBLIC_KEY = fs.readFileSync('distributions/' + config.DISTRIBUTION + '/id_rsa.pub', 'utf8'); 493 | config.DISCOVERY_DOCUMENT = result.BASE_URL + '/.well-known/openid-configuration'; 494 | config.SESSION_DURATION = parseInt(result.SESSION_DURATION, 10) * 60 * 60; 495 | 496 | config.BASE_URL = result.BASE_URL; 497 | config.CALLBACK_PATH = url.parse(result.REDIRECT_URI).pathname; 498 | 499 | config.AUTH_REQUEST.client_id = result.CLIENT_ID; 500 | config.AUTH_REQUEST.response_type = 'code'; 501 | config.AUTH_REQUEST.scope = 'openid email'; 502 | config.AUTH_REQUEST.redirect_uri = result.REDIRECT_URI; 503 | 504 | config.TOKEN_REQUEST.client_id = result.CLIENT_ID; 505 | config.TOKEN_REQUEST.client_secret = result.CLIENT_SECRET; 506 | config.TOKEN_REQUEST.redirect_uri = result.REDIRECT_URI; 507 | config.TOKEN_REQUEST.grant_type = 'authorization_code'; 508 | 509 | config.AUTHZ = "AUTH0"; 510 | 511 | shell.cp('./authn/openid.index.js', './distributions/' + config.DISTRIBUTION + '/index.js'); 512 | shell.cp('./nonce.js', './distributions/' + config.DISTRIBUTION + '/nonce.js'); 513 | 514 | fs.writeFileSync('distributions/' + config.DISTRIBUTION + '/config.json', JSON.stringify(result, null, 4)); 515 | 516 | shell.cp('./authz/auth0.js', './distributions/' + config.DISTRIBUTION + '/auth.js'); 517 | writeConfig(config, zip, ['config.json', 'index.js', 'auth.js', 'nonce.js']); 518 | }); 519 | } 520 | 521 | // Centrify configuration 522 | function centrifyConfiguration() { 523 | prompt.message = colors.blue(">>"); 524 | prompt.start(); 525 | prompt.get({ 526 | properties: { 527 | BASE_URL: { 528 | message: colors.red("Base URL"), 529 | required: true, 530 | default: R.pathOr('', ['BASE_URL'], oldConfig) 531 | }, 532 | CLIENT_ID: { 533 | message: colors.red("Client ID"), 534 | required: true, 535 | default: R.pathOr('', ['AUTH_REQUEST', 'client_id'], oldConfig) 536 | }, 537 | CLIENT_SECRET: { 538 | message: colors.red("Client Secret"), 539 | required: true, 540 | default: R.pathOr('', ['TOKEN_REQUEST', 'client_secret'], oldConfig) 541 | }, 542 | REDIRECT_URI: { 543 | message: colors.red("Redirect URI"), 544 | required: true, 545 | default: R.pathOr('', ['AUTH_REQUEST', 'redirect_uri'], oldConfig) 546 | }, 547 | SESSION_DURATION: { 548 | pattern: /^[0-9]*$/, 549 | description: colors.red("Session Duration (hours)"), 550 | message: colors.green("Entry must only contain numbers"), 551 | required: true, 552 | default: R.pathOr('', ['SESSION_DURATION'], oldConfig)/60/60 553 | } 554 | } 555 | }, function(err, result) { 556 | config.PRIVATE_KEY = fs.readFileSync('distributions/' + config.DISTRIBUTION + '/id_rsa', 'utf8'); 557 | config.PUBLIC_KEY = fs.readFileSync('distributions/' + config.DISTRIBUTION + '/id_rsa.pub', 'utf8'); 558 | config.DISCOVERY_DOCUMENT = result.BASE_URL + '/.well-known/openid-configuration'; 559 | config.SESSION_DURATION = parseInt(result.SESSION_DURATION, 10) * 60 * 60; 560 | 561 | config.BASE_URL = result.BASE_URL; 562 | config.CALLBACK_PATH = url.parse(result.REDIRECT_URI).pathname; 563 | 564 | config.AUTH_REQUEST.client_id = result.CLIENT_ID; 565 | config.AUTH_REQUEST.response_type = 'code'; 566 | config.AUTH_REQUEST.scope = 'openid email'; 567 | config.AUTH_REQUEST.redirect_uri = result.REDIRECT_URI; 568 | 569 | config.TOKEN_REQUEST.client_id = result.CLIENT_ID; 570 | config.TOKEN_REQUEST.client_secret = result.CLIENT_SECRET; 571 | config.TOKEN_REQUEST.redirect_uri = result.REDIRECT_URI; 572 | config.TOKEN_REQUEST.grant_type = 'authorization_code'; 573 | 574 | config.AUTHZ = "CENTRIFY"; 575 | 576 | shell.cp('./authn/openid.index.js', './distributions/' + config.DISTRIBUTION + '/index.js'); 577 | shell.cp('./nonce.js', './distributions/' + config.DISTRIBUTION + '/nonce.js'); 578 | 579 | fs.writeFileSync('distributions/' + config.DISTRIBUTION + '/config.json', JSON.stringify(result, null, 4)); 580 | 581 | shell.cp('./authz/centrify.js', './distributions/' + config.DISTRIBUTION + '/auth.js'); 582 | writeConfig(config, zip, ['config.json', 'index.js', 'auth.js', 'nonce.js']); 583 | }); 584 | } 585 | 586 | function zip(files) { 587 | var filesString = ''; 588 | for (var i = 0; i < files.length; i++) { 589 | filesString += ' distributions/' + config.DISTRIBUTION + '/' + files[i] + ' '; 590 | } 591 | shell.exec('zip -q distributions/' + config.DISTRIBUTION + '/' + config.DISTRIBUTION + '.zip ' + 'package-lock.json package.json -r node_modules'); 592 | shell.exec('zip -q -r -j distributions/' + config.DISTRIBUTION + '/' + config.DISTRIBUTION + '.zip ' + filesString); 593 | console.log(colors.green("Done... created Lambda function distributions/" + config.DISTRIBUTION + "/" + config.DISTRIBUTION + ".zip")); 594 | } 595 | 596 | function writeConfig(result, callback, files) { 597 | fs.writeFile('distributions/' + config.DISTRIBUTION + '/config.json', JSON.stringify(result, null, 4), (err) => { 598 | if (err) throw err; 599 | callback(files); 600 | }); 601 | } 602 | -------------------------------------------------------------------------------- /build/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudfront-auth-build", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "asn1": { 8 | "version": "0.2.3", 9 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", 10 | "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" 11 | }, 12 | "async": { 13 | "version": "0.9.2", 14 | "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", 15 | "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" 16 | }, 17 | "axios": { 18 | "version": "0.19.0", 19 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", 20 | "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", 21 | "requires": { 22 | "follow-redirects": "1.5.10", 23 | "is-buffer": "^2.0.2" 24 | } 25 | }, 26 | "balanced-match": { 27 | "version": "1.0.0", 28 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 29 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 30 | }, 31 | "brace-expansion": { 32 | "version": "1.1.8", 33 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", 34 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", 35 | "requires": { 36 | "balanced-match": "^1.0.0", 37 | "concat-map": "0.0.1" 38 | } 39 | }, 40 | "colors": { 41 | "version": "1.1.2", 42 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", 43 | "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=" 44 | }, 45 | "concat-map": { 46 | "version": "0.0.1", 47 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 48 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 49 | }, 50 | "cycle": { 51 | "version": "1.0.3", 52 | "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", 53 | "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=" 54 | }, 55 | "debug": { 56 | "version": "3.1.0", 57 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 58 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 59 | "requires": { 60 | "ms": "2.0.0" 61 | } 62 | }, 63 | "deep-equal": { 64 | "version": "0.2.2", 65 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz", 66 | "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0=" 67 | }, 68 | "eyes": { 69 | "version": "0.1.8", 70 | "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", 71 | "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" 72 | }, 73 | "follow-redirects": { 74 | "version": "1.5.10", 75 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", 76 | "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", 77 | "requires": { 78 | "debug": "=3.1.0" 79 | } 80 | }, 81 | "fs.realpath": { 82 | "version": "1.0.0", 83 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 84 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 85 | }, 86 | "glob": { 87 | "version": "7.1.2", 88 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 89 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 90 | "requires": { 91 | "fs.realpath": "^1.0.0", 92 | "inflight": "^1.0.4", 93 | "inherits": "2", 94 | "minimatch": "^3.0.4", 95 | "once": "^1.3.0", 96 | "path-is-absolute": "^1.0.0" 97 | } 98 | }, 99 | "i": { 100 | "version": "0.3.6", 101 | "resolved": "https://registry.npmjs.org/i/-/i-0.3.6.tgz", 102 | "integrity": "sha1-2WyScyB28HJxG2sQ/X1PZa2O4j0=" 103 | }, 104 | "inflight": { 105 | "version": "1.0.6", 106 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 107 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 108 | "requires": { 109 | "once": "^1.3.0", 110 | "wrappy": "1" 111 | } 112 | }, 113 | "inherits": { 114 | "version": "2.0.3", 115 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 116 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 117 | }, 118 | "interpret": { 119 | "version": "1.1.0", 120 | "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", 121 | "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=" 122 | }, 123 | "is-buffer": { 124 | "version": "2.0.3", 125 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", 126 | "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" 127 | }, 128 | "isstream": { 129 | "version": "0.1.2", 130 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 131 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 132 | }, 133 | "minimatch": { 134 | "version": "3.0.4", 135 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 136 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 137 | "requires": { 138 | "brace-expansion": "^1.1.7" 139 | } 140 | }, 141 | "minimist": { 142 | "version": "0.0.8", 143 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 144 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 145 | }, 146 | "mkdirp": { 147 | "version": "0.5.1", 148 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 149 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 150 | "requires": { 151 | "minimist": "0.0.8" 152 | } 153 | }, 154 | "ms": { 155 | "version": "2.0.0", 156 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 157 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 158 | }, 159 | "mute-stream": { 160 | "version": "0.0.7", 161 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", 162 | "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" 163 | }, 164 | "ncp": { 165 | "version": "1.0.1", 166 | "resolved": "https://registry.npmjs.org/ncp/-/ncp-1.0.1.tgz", 167 | "integrity": "sha1-0VNn5cuHQyuhF9K/gP30Wuz7QkY=" 168 | }, 169 | "node-rsa": { 170 | "version": "0.4.2", 171 | "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-0.4.2.tgz", 172 | "integrity": "sha1-1jkXKewWqDDtWjgEKzFX0tXXJTA=", 173 | "requires": { 174 | "asn1": "0.2.3" 175 | } 176 | }, 177 | "once": { 178 | "version": "1.4.0", 179 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 180 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 181 | "requires": { 182 | "wrappy": "1" 183 | } 184 | }, 185 | "path-is-absolute": { 186 | "version": "1.0.1", 187 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 188 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 189 | }, 190 | "path-parse": { 191 | "version": "1.0.5", 192 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", 193 | "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" 194 | }, 195 | "pkginfo": { 196 | "version": "0.4.1", 197 | "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.4.1.tgz", 198 | "integrity": "sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8=" 199 | }, 200 | "prompt": { 201 | "version": "1.0.0", 202 | "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.0.0.tgz", 203 | "integrity": "sha1-jlcSPDlquYiJf7Mn/Trtw+c15P4=", 204 | "requires": { 205 | "colors": "^1.1.2", 206 | "pkginfo": "0.x.x", 207 | "read": "1.0.x", 208 | "revalidator": "0.1.x", 209 | "utile": "0.3.x", 210 | "winston": "2.1.x" 211 | } 212 | }, 213 | "punycode": { 214 | "version": "1.3.2", 215 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 216 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" 217 | }, 218 | "querystring": { 219 | "version": "0.2.0", 220 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 221 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" 222 | }, 223 | "ramda": { 224 | "version": "0.25.0", 225 | "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", 226 | "integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==" 227 | }, 228 | "read": { 229 | "version": "1.0.7", 230 | "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", 231 | "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", 232 | "requires": { 233 | "mute-stream": "~0.0.4" 234 | } 235 | }, 236 | "rechoir": { 237 | "version": "0.6.2", 238 | "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", 239 | "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", 240 | "requires": { 241 | "resolve": "^1.1.6" 242 | } 243 | }, 244 | "resolve": { 245 | "version": "1.5.0", 246 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", 247 | "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", 248 | "requires": { 249 | "path-parse": "^1.0.5" 250 | } 251 | }, 252 | "revalidator": { 253 | "version": "0.1.8", 254 | "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", 255 | "integrity": "sha1-/s5hv6DBtSoga9axgZgYS91SOjs=" 256 | }, 257 | "rimraf": { 258 | "version": "2.6.2", 259 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", 260 | "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", 261 | "requires": { 262 | "glob": "^7.0.5" 263 | } 264 | }, 265 | "shelljs": { 266 | "version": "0.8.0", 267 | "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.0.tgz", 268 | "integrity": "sha512-wb72o5SM27oFRq2mMeNSl70DVkkvwf3ZSgk9rvieRVz7xFMIQp02HYw0oxlZgeAWS+wzGB+jcJWQTF7hH9WIPg==", 269 | "requires": { 270 | "glob": "^7.0.0", 271 | "interpret": "^1.0.0", 272 | "rechoir": "^0.6.2" 273 | } 274 | }, 275 | "stack-trace": { 276 | "version": "0.0.10", 277 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", 278 | "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" 279 | }, 280 | "url": { 281 | "version": "0.11.0", 282 | "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", 283 | "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", 284 | "requires": { 285 | "punycode": "1.3.2", 286 | "querystring": "0.2.0" 287 | } 288 | }, 289 | "utile": { 290 | "version": "0.3.0", 291 | "resolved": "https://registry.npmjs.org/utile/-/utile-0.3.0.tgz", 292 | "integrity": "sha1-E1LDQOuCDk2N26A5pPv6oy7U7zo=", 293 | "requires": { 294 | "async": "~0.9.0", 295 | "deep-equal": "~0.2.1", 296 | "i": "0.3.x", 297 | "mkdirp": "0.x.x", 298 | "ncp": "1.0.x", 299 | "rimraf": "2.x.x" 300 | } 301 | }, 302 | "winston": { 303 | "version": "2.1.1", 304 | "resolved": "https://registry.npmjs.org/winston/-/winston-2.1.1.tgz", 305 | "integrity": "sha1-PJNJ0ZYgf9G9/51LxD73JRDjoS4=", 306 | "requires": { 307 | "async": "~1.0.0", 308 | "colors": "1.0.x", 309 | "cycle": "1.0.x", 310 | "eyes": "0.1.x", 311 | "isstream": "0.1.x", 312 | "pkginfo": "0.3.x", 313 | "stack-trace": "0.0.x" 314 | }, 315 | "dependencies": { 316 | "async": { 317 | "version": "1.0.0", 318 | "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", 319 | "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=" 320 | }, 321 | "colors": { 322 | "version": "1.0.3", 323 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", 324 | "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" 325 | }, 326 | "pkginfo": { 327 | "version": "0.3.1", 328 | "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", 329 | "integrity": "sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=" 330 | } 331 | } 332 | }, 333 | "wrappy": { 334 | "version": "1.0.2", 335 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 336 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 337 | } 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /build/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudfront-auth-build", 3 | "version": "1.0.0", 4 | "description": "Build files for cloudfront-auth", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "axios": "^0.19.0", 13 | "node-rsa": "^0.4.2", 14 | "prompt": "^1.0.0", 15 | "ramda": "^0.25.0", 16 | "shelljs": "^0.8.0", 17 | "url": "^0.11.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /code-challenge.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | 3 | module.exports.get = function (length) { 4 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'; 5 | const charactersLength = characters.length; 6 | 7 | var codeChallenge = ''; 8 | 9 | for ( var i = 0; i < length; i++ ) { 10 | codeChallenge += characters.charAt(Math.floor(Math.random() * charactersLength)); 11 | } 12 | const hash = crypto.createHash('sha256'); 13 | hash.update(codeChallenge); 14 | 15 | return [codeChallenge, hash.digest('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')]; 16 | } 17 | -------------------------------------------------------------------------------- /nonce.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | 3 | module.exports.getNonce = function () { 4 | const nonce = crypto.randomBytes(32) 5 | .toString('hex'); 6 | const hash = crypto.createHmac('sha256', nonce) 7 | .digest('hex'); 8 | return [nonce, hash]; 9 | } 10 | 11 | module.exports.validateNonce = function (nonce, hash) { 12 | const other = crypto.createHmac('sha256', nonce) 13 | .digest('hex'); 14 | return (other == hash); 15 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudfront-auth", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "asn1.js": { 8 | "version": "4.9.2", 9 | "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.2.tgz", 10 | "integrity": "sha512-b/OsSjvWEo8Pi8H0zsDd2P6Uqo2TK2pH8gNLSJtNLM2Db0v2QaAZ0pBQJXVjAn4gBuugeVDr7s63ZogpUIwWDg==", 11 | "requires": { 12 | "bn.js": "^4.0.0", 13 | "inherits": "^2.0.1", 14 | "minimalistic-assert": "^1.0.0" 15 | } 16 | }, 17 | "axios": { 18 | "version": "0.18.1", 19 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz", 20 | "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==", 21 | "requires": { 22 | "follow-redirects": "1.5.10", 23 | "is-buffer": "^2.0.2" 24 | } 25 | }, 26 | "bn.js": { 27 | "version": "4.11.8", 28 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", 29 | "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" 30 | }, 31 | "brorand": { 32 | "version": "1.1.0", 33 | "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", 34 | "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" 35 | }, 36 | "buffer-equal-constant-time": { 37 | "version": "1.0.1", 38 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 39 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" 40 | }, 41 | "cookie": { 42 | "version": "0.3.1", 43 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 44 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 45 | }, 46 | "crypto": { 47 | "version": "1.0.1", 48 | "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", 49 | "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==" 50 | }, 51 | "debug": { 52 | "version": "3.1.0", 53 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 54 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 55 | "requires": { 56 | "ms": "2.0.0" 57 | }, 58 | "dependencies": { 59 | "ms": { 60 | "version": "2.0.0", 61 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 62 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 63 | } 64 | } 65 | }, 66 | "ecdsa-sig-formatter": { 67 | "version": "1.0.10", 68 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", 69 | "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", 70 | "requires": { 71 | "safe-buffer": "^5.0.1" 72 | } 73 | }, 74 | "elliptic": { 75 | "version": "6.4.0", 76 | "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", 77 | "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", 78 | "requires": { 79 | "bn.js": "^4.4.0", 80 | "brorand": "^1.0.1", 81 | "hash.js": "^1.0.0", 82 | "hmac-drbg": "^1.0.0", 83 | "inherits": "^2.0.1", 84 | "minimalistic-assert": "^1.0.0", 85 | "minimalistic-crypto-utils": "^1.0.0" 86 | } 87 | }, 88 | "follow-redirects": { 89 | "version": "1.5.10", 90 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", 91 | "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", 92 | "requires": { 93 | "debug": "=3.1.0" 94 | } 95 | }, 96 | "hash.js": { 97 | "version": "1.1.3", 98 | "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", 99 | "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", 100 | "requires": { 101 | "inherits": "^2.0.3", 102 | "minimalistic-assert": "^1.0.0" 103 | } 104 | }, 105 | "hmac-drbg": { 106 | "version": "1.0.1", 107 | "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", 108 | "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", 109 | "requires": { 110 | "hash.js": "^1.0.3", 111 | "minimalistic-assert": "^1.0.0", 112 | "minimalistic-crypto-utils": "^1.0.1" 113 | } 114 | }, 115 | "inherits": { 116 | "version": "2.0.3", 117 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 118 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 119 | }, 120 | "is-buffer": { 121 | "version": "2.0.3", 122 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", 123 | "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" 124 | }, 125 | "jsonwebtoken": { 126 | "version": "8.1.0", 127 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.1.0.tgz", 128 | "integrity": "sha1-xjl80uX9WD1lwAeoPce7eOaYK4M=", 129 | "requires": { 130 | "jws": "^3.1.4", 131 | "lodash.includes": "^4.3.0", 132 | "lodash.isboolean": "^3.0.3", 133 | "lodash.isinteger": "^4.0.4", 134 | "lodash.isnumber": "^3.0.3", 135 | "lodash.isplainobject": "^4.0.6", 136 | "lodash.isstring": "^4.0.1", 137 | "lodash.once": "^4.0.0", 138 | "ms": "^2.0.0", 139 | "xtend": "^4.0.1" 140 | } 141 | }, 142 | "jwa": { 143 | "version": "1.1.6", 144 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", 145 | "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==", 146 | "requires": { 147 | "buffer-equal-constant-time": "1.0.1", 148 | "ecdsa-sig-formatter": "1.0.10", 149 | "safe-buffer": "^5.0.1" 150 | } 151 | }, 152 | "jwk-to-pem": { 153 | "version": "1.2.6", 154 | "resolved": "https://registry.npmjs.org/jwk-to-pem/-/jwk-to-pem-1.2.6.tgz", 155 | "integrity": "sha1-1QfOzkAInFJI4J7GgmaiAwqcYyU=", 156 | "requires": { 157 | "asn1.js": "^4.5.2", 158 | "elliptic": "^6.2.3", 159 | "safe-buffer": "^5.0.1" 160 | } 161 | }, 162 | "jws": { 163 | "version": "3.1.5", 164 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", 165 | "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", 166 | "requires": { 167 | "jwa": "^1.1.5", 168 | "safe-buffer": "^5.0.1" 169 | } 170 | }, 171 | "lodash.includes": { 172 | "version": "4.3.0", 173 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 174 | "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" 175 | }, 176 | "lodash.isboolean": { 177 | "version": "3.0.3", 178 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 179 | "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" 180 | }, 181 | "lodash.isinteger": { 182 | "version": "4.0.4", 183 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 184 | "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" 185 | }, 186 | "lodash.isnumber": { 187 | "version": "3.0.3", 188 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 189 | "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" 190 | }, 191 | "lodash.isplainobject": { 192 | "version": "4.0.6", 193 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 194 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" 195 | }, 196 | "lodash.isstring": { 197 | "version": "4.0.1", 198 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 199 | "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" 200 | }, 201 | "lodash.once": { 202 | "version": "4.1.1", 203 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 204 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" 205 | }, 206 | "minimalistic-assert": { 207 | "version": "1.0.0", 208 | "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", 209 | "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=" 210 | }, 211 | "minimalistic-crypto-utils": { 212 | "version": "1.0.1", 213 | "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", 214 | "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" 215 | }, 216 | "ms": { 217 | "version": "2.1.1", 218 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 219 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 220 | }, 221 | "nonce": { 222 | "version": "1.0.4", 223 | "resolved": "https://registry.npmjs.org/nonce/-/nonce-1.0.4.tgz", 224 | "integrity": "sha1-7nMCrejBvvR28wG4yR9cxRpIdhI=" 225 | }, 226 | "punycode": { 227 | "version": "1.3.2", 228 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 229 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" 230 | }, 231 | "querystring": { 232 | "version": "0.2.0", 233 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 234 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" 235 | }, 236 | "safe-buffer": { 237 | "version": "5.1.1", 238 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 239 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 240 | }, 241 | "url": { 242 | "version": "0.11.0", 243 | "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", 244 | "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", 245 | "requires": { 246 | "punycode": "1.3.2", 247 | "querystring": "0.2.0" 248 | } 249 | }, 250 | "xtend": { 251 | "version": "4.0.1", 252 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 253 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudfront-auth", 3 | "version": "1.0.0", 4 | "description": "An AWS Cloudfront Lambda@Edge function to authenticate requests using Google Apps, Microsoft, GitHub login, OKTA & Auth0", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "cd tests && npm install && cd .. && node tests/tests.js", 8 | "build": "npm install && cd build && npm install && cd .. && node build/build.js" 9 | }, 10 | "author": "Widen Enterprises", 11 | "repository": "github:widen/cloudfront-auth", 12 | "license": "ISC", 13 | "dependencies": { 14 | "axios": "^0.18.1", 15 | "cookie": "^0.3.1", 16 | "crypto": "^1.0.1", 17 | "jsonwebtoken": "^8.1.0", 18 | "jwk-to-pem": "^1.2.6", 19 | "nonce": "^1.0.4", 20 | "querystring": "^0.2.0", 21 | "url": "^0.11.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | Description: SAM configuration for cloudfront-auth function 4 | Resources: 5 | 6 | CloudFrontAuthFunction: 7 | Type: AWS::Serverless::Function 8 | Properties: 9 | CodeUri: distributions/{distribution_name}/{distribution_name}.zip 10 | Role: !GetAtt LambdaEdgeFunctionRole.Arn 11 | Runtime: nodejs10.x 12 | Handler: index.handler 13 | Timeout: 5 14 | AutoPublishAlias: LIVE 15 | 16 | LambdaEdgeFunctionRole: 17 | Type: "AWS::IAM::Role" 18 | Properties: 19 | Path: "/" 20 | ManagedPolicyArns: 21 | - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 22 | AssumeRolePolicyDocument: 23 | Version: "2012-10-17" 24 | Statement: 25 | - 26 | Sid: "AllowLambdaServiceToAssumeRole" 27 | Effect: "Allow" 28 | Action: 29 | - "sts:AssumeRole" 30 | Principal: 31 | Service: 32 | - "lambda.amazonaws.com" 33 | - "edgelambda.amazonaws.com" 34 | 35 | Outputs: 36 | 37 | CloudFrontAuthFunction: 38 | Description: Lambda@Edge CloudFront Auth Function ARN 39 | Value: !GetAtt CloudFrontAuthFunction.Arn 40 | 41 | CloudFrontAuthFunctionVersion: 42 | Description: Lambda@Edge CloudFront Auth Function ARN with Version 43 | Value: !Ref CloudFrontAuthFunction.Version 44 | -------------------------------------------------------------------------------- /tests/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudfront-auth-tests", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "8.9.5", 9 | "resolved": "http://registry.npmjs.org/@types/node/-/node-8.9.5.tgz", 10 | "integrity": "sha512-jRHfWsvyMtXdbhnz5CVHxaBgnV6duZnPlQuRSo/dm/GnmikNcmZhxIES4E9OZjUmQ8C+HCl4KJux+cXN/ErGDQ==" 11 | }, 12 | "abbrev": { 13 | "version": "1.1.1", 14 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 15 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" 16 | }, 17 | "ajv": { 18 | "version": "5.5.2", 19 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", 20 | "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", 21 | "requires": { 22 | "co": "^4.6.0", 23 | "fast-deep-equal": "^1.0.0", 24 | "fast-json-stable-stringify": "^2.0.0", 25 | "json-schema-traverse": "^0.3.0" 26 | } 27 | }, 28 | "asn1": { 29 | "version": "0.2.4", 30 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 31 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 32 | "requires": { 33 | "safer-buffer": "~2.1.0" 34 | } 35 | }, 36 | "assert-plus": { 37 | "version": "1.0.0", 38 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 39 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 40 | }, 41 | "async": { 42 | "version": "0.9.2", 43 | "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", 44 | "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" 45 | }, 46 | "asynckit": { 47 | "version": "0.4.0", 48 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 49 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 50 | }, 51 | "aws-sdk": { 52 | "version": "2.207.0", 53 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.207.0.tgz", 54 | "integrity": "sha1-wKMJd9jSeIMBhQsmX/Bet8n99/I=", 55 | "requires": { 56 | "buffer": "4.9.1", 57 | "events": "^1.1.1", 58 | "jmespath": "0.15.0", 59 | "querystring": "0.2.0", 60 | "sax": "1.2.1", 61 | "url": "0.10.3", 62 | "uuid": "3.1.0", 63 | "xml2js": "0.4.17", 64 | "xmlbuilder": "4.2.1" 65 | }, 66 | "dependencies": { 67 | "uuid": { 68 | "version": "3.1.0", 69 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", 70 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" 71 | } 72 | } 73 | }, 74 | "aws-sign2": { 75 | "version": "0.7.0", 76 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 77 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 78 | }, 79 | "aws4": { 80 | "version": "1.8.0", 81 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", 82 | "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" 83 | }, 84 | "balanced-match": { 85 | "version": "1.0.0", 86 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 87 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 88 | }, 89 | "base64-js": { 90 | "version": "1.2.3", 91 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.3.tgz", 92 | "integrity": "sha512-MsAhsUW1GxCdgYSO6tAfZrNapmUKk7mWx/k5mFY/A1gBtkaCaNapTg+FExCw1r9yeaZhqx/xPg43xgTFH6KL5w==" 93 | }, 94 | "bcrypt-pbkdf": { 95 | "version": "1.0.2", 96 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 97 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 98 | "requires": { 99 | "tweetnacl": "^0.14.3" 100 | } 101 | }, 102 | "binary": { 103 | "version": "0.3.0", 104 | "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", 105 | "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", 106 | "requires": { 107 | "buffers": "~0.1.1", 108 | "chainsaw": "~0.1.0" 109 | } 110 | }, 111 | "brace-expansion": { 112 | "version": "1.1.11", 113 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 114 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 115 | "requires": { 116 | "balanced-match": "^1.0.0", 117 | "concat-map": "0.0.1" 118 | } 119 | }, 120 | "buffer": { 121 | "version": "4.9.1", 122 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", 123 | "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", 124 | "requires": { 125 | "base64-js": "^1.0.2", 126 | "ieee754": "^1.1.4", 127 | "isarray": "^1.0.0" 128 | }, 129 | "dependencies": { 130 | "isarray": { 131 | "version": "1.0.0", 132 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 133 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 134 | } 135 | } 136 | }, 137 | "buffers": { 138 | "version": "0.1.1", 139 | "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", 140 | "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=" 141 | }, 142 | "caseless": { 143 | "version": "0.12.0", 144 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 145 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 146 | }, 147 | "chainsaw": { 148 | "version": "0.1.0", 149 | "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", 150 | "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", 151 | "requires": { 152 | "traverse": ">=0.3.0 <0.4" 153 | } 154 | }, 155 | "co": { 156 | "version": "4.6.0", 157 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 158 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" 159 | }, 160 | "colors": { 161 | "version": "1.1.2", 162 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", 163 | "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=" 164 | }, 165 | "combined-stream": { 166 | "version": "1.0.7", 167 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", 168 | "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", 169 | "requires": { 170 | "delayed-stream": "~1.0.0" 171 | } 172 | }, 173 | "concat-map": { 174 | "version": "0.0.1", 175 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 176 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 177 | }, 178 | "core-util-is": { 179 | "version": "1.0.2", 180 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 181 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 182 | }, 183 | "cycle": { 184 | "version": "1.0.3", 185 | "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", 186 | "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=" 187 | }, 188 | "dashdash": { 189 | "version": "1.14.1", 190 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 191 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 192 | "requires": { 193 | "assert-plus": "^1.0.0" 194 | } 195 | }, 196 | "dateformat": { 197 | "version": "3.0.3", 198 | "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", 199 | "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" 200 | }, 201 | "decompress-zip": { 202 | "version": "0.3.0", 203 | "resolved": "https://registry.npmjs.org/decompress-zip/-/decompress-zip-0.3.0.tgz", 204 | "integrity": "sha1-rjvLfjTGWHmt/nfhnDD4ZgK0vbA=", 205 | "requires": { 206 | "binary": "^0.3.0", 207 | "graceful-fs": "^4.1.3", 208 | "mkpath": "^0.1.0", 209 | "nopt": "^3.0.1", 210 | "q": "^1.1.2", 211 | "readable-stream": "^1.1.8", 212 | "touch": "0.0.3" 213 | } 214 | }, 215 | "deep-equal": { 216 | "version": "0.2.2", 217 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz", 218 | "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0=" 219 | }, 220 | "delayed-stream": { 221 | "version": "1.0.0", 222 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 223 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 224 | }, 225 | "ecc-jsbn": { 226 | "version": "0.1.2", 227 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 228 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 229 | "requires": { 230 | "jsbn": "~0.1.0", 231 | "safer-buffer": "^2.1.0" 232 | } 233 | }, 234 | "events": { 235 | "version": "1.1.1", 236 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 237 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" 238 | }, 239 | "extend": { 240 | "version": "3.0.2", 241 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 242 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 243 | }, 244 | "extsprintf": { 245 | "version": "1.3.0", 246 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 247 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 248 | }, 249 | "eyes": { 250 | "version": "0.1.8", 251 | "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", 252 | "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" 253 | }, 254 | "fast-deep-equal": { 255 | "version": "1.1.0", 256 | "resolved": "http://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", 257 | "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" 258 | }, 259 | "fast-json-stable-stringify": { 260 | "version": "2.0.0", 261 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 262 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 263 | }, 264 | "forever-agent": { 265 | "version": "0.6.1", 266 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 267 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 268 | }, 269 | "form-data": { 270 | "version": "2.3.3", 271 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 272 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 273 | "requires": { 274 | "asynckit": "^0.4.0", 275 | "combined-stream": "^1.0.6", 276 | "mime-types": "^2.1.12" 277 | } 278 | }, 279 | "fs.realpath": { 280 | "version": "1.0.0", 281 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 282 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 283 | }, 284 | "generate-password": { 285 | "version": "1.4.1", 286 | "resolved": "https://registry.npmjs.org/generate-password/-/generate-password-1.4.1.tgz", 287 | "integrity": "sha512-MwMSkOIKkgYBG3JrquF0m/Rky+pl5jZFNmoroE9bQU5VawFDKdJfxMx1qBthPusx8GQyNWSW0m+Jaw0mZnqApg==" 288 | }, 289 | "getpass": { 290 | "version": "0.1.7", 291 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 292 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 293 | "requires": { 294 | "assert-plus": "^1.0.0" 295 | } 296 | }, 297 | "glob": { 298 | "version": "7.1.2", 299 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 300 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 301 | "requires": { 302 | "fs.realpath": "^1.0.0", 303 | "inflight": "^1.0.4", 304 | "inherits": "2", 305 | "minimatch": "^3.0.4", 306 | "once": "^1.3.0", 307 | "path-is-absolute": "^1.0.0" 308 | } 309 | }, 310 | "graceful-fs": { 311 | "version": "4.1.11", 312 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", 313 | "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" 314 | }, 315 | "har-schema": { 316 | "version": "2.0.0", 317 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 318 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 319 | }, 320 | "har-validator": { 321 | "version": "5.1.0", 322 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", 323 | "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", 324 | "requires": { 325 | "ajv": "^5.3.0", 326 | "har-schema": "^2.0.0" 327 | } 328 | }, 329 | "http": { 330 | "version": "0.0.0", 331 | "resolved": "https://registry.npmjs.org/http/-/http-0.0.0.tgz", 332 | "integrity": "sha1-huYybSnF0Dnen6xYSkVon5KfT3I=" 333 | }, 334 | "http-signature": { 335 | "version": "1.2.0", 336 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 337 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 338 | "requires": { 339 | "assert-plus": "^1.0.0", 340 | "jsprim": "^1.2.2", 341 | "sshpk": "^1.7.0" 342 | } 343 | }, 344 | "i": { 345 | "version": "0.3.6", 346 | "resolved": "https://registry.npmjs.org/i/-/i-0.3.6.tgz", 347 | "integrity": "sha1-2WyScyB28HJxG2sQ/X1PZa2O4j0=" 348 | }, 349 | "ieee754": { 350 | "version": "1.1.8", 351 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", 352 | "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" 353 | }, 354 | "inflight": { 355 | "version": "1.0.6", 356 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 357 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 358 | "requires": { 359 | "once": "^1.3.0", 360 | "wrappy": "1" 361 | } 362 | }, 363 | "inherits": { 364 | "version": "2.0.3", 365 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 366 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 367 | }, 368 | "interpret": { 369 | "version": "1.1.0", 370 | "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", 371 | "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=" 372 | }, 373 | "is-typedarray": { 374 | "version": "1.0.0", 375 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 376 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 377 | }, 378 | "is-wsl": { 379 | "version": "1.1.0", 380 | "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", 381 | "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" 382 | }, 383 | "isarray": { 384 | "version": "0.0.1", 385 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 386 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" 387 | }, 388 | "isstream": { 389 | "version": "0.1.2", 390 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 391 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 392 | }, 393 | "jmespath": { 394 | "version": "0.15.0", 395 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 396 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" 397 | }, 398 | "jsbn": { 399 | "version": "0.1.1", 400 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 401 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" 402 | }, 403 | "json-beautify": { 404 | "version": "1.0.1", 405 | "resolved": "https://registry.npmjs.org/json-beautify/-/json-beautify-1.0.1.tgz", 406 | "integrity": "sha1-WYtQ1Mjqm4/KWru0C34svTrUwvw=" 407 | }, 408 | "json-schema": { 409 | "version": "0.2.3", 410 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 411 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 412 | }, 413 | "json-schema-traverse": { 414 | "version": "0.3.1", 415 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", 416 | "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" 417 | }, 418 | "json-stringify-safe": { 419 | "version": "5.0.1", 420 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 421 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 422 | }, 423 | "jsprim": { 424 | "version": "1.4.1", 425 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 426 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 427 | "requires": { 428 | "assert-plus": "1.0.0", 429 | "extsprintf": "1.3.0", 430 | "json-schema": "0.2.3", 431 | "verror": "1.10.0" 432 | } 433 | }, 434 | "lock": { 435 | "version": "0.1.4", 436 | "resolved": "https://registry.npmjs.org/lock/-/lock-0.1.4.tgz", 437 | "integrity": "sha1-/sfervF+fDoKVeHaBCgD4l2RdF0=" 438 | }, 439 | "mime-db": { 440 | "version": "1.37.0", 441 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", 442 | "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" 443 | }, 444 | "mime-types": { 445 | "version": "2.1.21", 446 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", 447 | "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", 448 | "requires": { 449 | "mime-db": "~1.37.0" 450 | } 451 | }, 452 | "minimatch": { 453 | "version": "3.0.4", 454 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 455 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 456 | "requires": { 457 | "brace-expansion": "^1.1.7" 458 | } 459 | }, 460 | "minimist": { 461 | "version": "0.0.8", 462 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 463 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 464 | }, 465 | "mkdirp": { 466 | "version": "0.5.1", 467 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 468 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 469 | "requires": { 470 | "minimist": "0.0.8" 471 | } 472 | }, 473 | "mkpath": { 474 | "version": "0.1.0", 475 | "resolved": "https://registry.npmjs.org/mkpath/-/mkpath-0.1.0.tgz", 476 | "integrity": "sha1-dVSm+Nhxg0zJe1RisSLEwSTW3pE=" 477 | }, 478 | "mute-stream": { 479 | "version": "0.0.7", 480 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", 481 | "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" 482 | }, 483 | "ncp": { 484 | "version": "1.0.1", 485 | "resolved": "https://registry.npmjs.org/ncp/-/ncp-1.0.1.tgz", 486 | "integrity": "sha1-0VNn5cuHQyuhF9K/gP30Wuz7QkY=" 487 | }, 488 | "ngrok": { 489 | "version": "2.3.0", 490 | "resolved": "https://registry.npmjs.org/ngrok/-/ngrok-2.3.0.tgz", 491 | "integrity": "sha512-zRzeTtdwx2tjeeT/GbOdZk8dUR3S3yOW3W+VwkRF4wpCajbP/8u3I3jlP0HyHoiPLb/zpT2PsgqFxM+K9cfclA==", 492 | "requires": { 493 | "@types/node": "^8.0.19", 494 | "async": "^2.3.0", 495 | "decompress-zip": "^0.3.0", 496 | "lock": "^0.1.2", 497 | "request": "^2.55.0", 498 | "uuid": "^3.0.0" 499 | }, 500 | "dependencies": { 501 | "async": { 502 | "version": "2.6.0", 503 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", 504 | "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", 505 | "requires": { 506 | "lodash": "^4.14.0" 507 | } 508 | }, 509 | "lodash": { 510 | "version": "4.17.11", 511 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", 512 | "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" 513 | } 514 | } 515 | }, 516 | "nopt": { 517 | "version": "3.0.6", 518 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", 519 | "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", 520 | "requires": { 521 | "abbrev": "1" 522 | } 523 | }, 524 | "oauth-sign": { 525 | "version": "0.9.0", 526 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 527 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" 528 | }, 529 | "once": { 530 | "version": "1.4.0", 531 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 532 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 533 | "requires": { 534 | "wrappy": "1" 535 | } 536 | }, 537 | "opn": { 538 | "version": "5.2.0", 539 | "resolved": "https://registry.npmjs.org/opn/-/opn-5.2.0.tgz", 540 | "integrity": "sha512-Jd/GpzPyHF4P2/aNOVmS3lfMSWV9J7cOhCG1s08XCEAsPkB7lp6ddiU0J7XzyQRDUh8BqJ7PchfINjR8jyofRQ==", 541 | "requires": { 542 | "is-wsl": "^1.1.0" 543 | } 544 | }, 545 | "path-is-absolute": { 546 | "version": "1.0.1", 547 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 548 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 549 | }, 550 | "path-parse": { 551 | "version": "1.0.5", 552 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", 553 | "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" 554 | }, 555 | "performance-now": { 556 | "version": "2.1.0", 557 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 558 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 559 | }, 560 | "pkginfo": { 561 | "version": "0.4.1", 562 | "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.4.1.tgz", 563 | "integrity": "sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8=" 564 | }, 565 | "prompt": { 566 | "version": "1.0.0", 567 | "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.0.0.tgz", 568 | "integrity": "sha1-jlcSPDlquYiJf7Mn/Trtw+c15P4=", 569 | "requires": { 570 | "colors": "^1.1.2", 571 | "pkginfo": "0.x.x", 572 | "read": "1.0.x", 573 | "revalidator": "0.1.x", 574 | "utile": "0.3.x", 575 | "winston": "2.1.x" 576 | } 577 | }, 578 | "psl": { 579 | "version": "1.1.29", 580 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", 581 | "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" 582 | }, 583 | "punycode": { 584 | "version": "1.4.1", 585 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 586 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 587 | }, 588 | "q": { 589 | "version": "1.5.1", 590 | "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", 591 | "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" 592 | }, 593 | "qs": { 594 | "version": "6.5.2", 595 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 596 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 597 | }, 598 | "querystring": { 599 | "version": "0.2.0", 600 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 601 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" 602 | }, 603 | "read": { 604 | "version": "1.0.7", 605 | "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", 606 | "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", 607 | "requires": { 608 | "mute-stream": "~0.0.4" 609 | } 610 | }, 611 | "readable-stream": { 612 | "version": "1.1.14", 613 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 614 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", 615 | "requires": { 616 | "core-util-is": "~1.0.0", 617 | "inherits": "~2.0.1", 618 | "isarray": "0.0.1", 619 | "string_decoder": "~0.10.x" 620 | } 621 | }, 622 | "rechoir": { 623 | "version": "0.6.2", 624 | "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", 625 | "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", 626 | "requires": { 627 | "resolve": "^1.1.6" 628 | } 629 | }, 630 | "request": { 631 | "version": "2.88.0", 632 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", 633 | "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", 634 | "requires": { 635 | "aws-sign2": "~0.7.0", 636 | "aws4": "^1.8.0", 637 | "caseless": "~0.12.0", 638 | "combined-stream": "~1.0.6", 639 | "extend": "~3.0.2", 640 | "forever-agent": "~0.6.1", 641 | "form-data": "~2.3.2", 642 | "har-validator": "~5.1.0", 643 | "http-signature": "~1.2.0", 644 | "is-typedarray": "~1.0.0", 645 | "isstream": "~0.1.2", 646 | "json-stringify-safe": "~5.0.1", 647 | "mime-types": "~2.1.19", 648 | "oauth-sign": "~0.9.0", 649 | "performance-now": "^2.1.0", 650 | "qs": "~6.5.2", 651 | "safe-buffer": "^5.1.2", 652 | "tough-cookie": "~2.4.3", 653 | "tunnel-agent": "^0.6.0", 654 | "uuid": "^3.3.2" 655 | }, 656 | "dependencies": { 657 | "uuid": { 658 | "version": "3.3.2", 659 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 660 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" 661 | } 662 | } 663 | }, 664 | "resolve": { 665 | "version": "1.5.0", 666 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", 667 | "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", 668 | "requires": { 669 | "path-parse": "^1.0.5" 670 | } 671 | }, 672 | "revalidator": { 673 | "version": "0.1.8", 674 | "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", 675 | "integrity": "sha1-/s5hv6DBtSoga9axgZgYS91SOjs=" 676 | }, 677 | "rimraf": { 678 | "version": "2.6.2", 679 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", 680 | "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", 681 | "requires": { 682 | "glob": "^7.0.5" 683 | } 684 | }, 685 | "safe-buffer": { 686 | "version": "5.1.2", 687 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 688 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 689 | }, 690 | "safer-buffer": { 691 | "version": "2.1.2", 692 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 693 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 694 | }, 695 | "sax": { 696 | "version": "1.2.1", 697 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 698 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" 699 | }, 700 | "shelljs": { 701 | "version": "0.8.1", 702 | "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.1.tgz", 703 | "integrity": "sha512-YA/iYtZpzFe5HyWVGrb02FjPxc4EMCfpoU/Phg9fQoyMC72u9598OUBrsU8IrtwAKG0tO8IYaqbaLIw+k3IRGA==", 704 | "requires": { 705 | "glob": "^7.0.0", 706 | "interpret": "^1.0.0", 707 | "rechoir": "^0.6.2" 708 | } 709 | }, 710 | "sshpk": { 711 | "version": "1.15.2", 712 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", 713 | "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", 714 | "requires": { 715 | "asn1": "~0.2.3", 716 | "assert-plus": "^1.0.0", 717 | "bcrypt-pbkdf": "^1.0.0", 718 | "dashdash": "^1.12.0", 719 | "ecc-jsbn": "~0.1.1", 720 | "getpass": "^0.1.1", 721 | "jsbn": "~0.1.0", 722 | "safer-buffer": "^2.0.2", 723 | "tweetnacl": "~0.14.0" 724 | } 725 | }, 726 | "stack-trace": { 727 | "version": "0.0.10", 728 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", 729 | "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" 730 | }, 731 | "string_decoder": { 732 | "version": "0.10.31", 733 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 734 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" 735 | }, 736 | "touch": { 737 | "version": "0.0.3", 738 | "resolved": "https://registry.npmjs.org/touch/-/touch-0.0.3.tgz", 739 | "integrity": "sha1-Ua7z1ElXHU8oel2Hyci0kYGg2x0=", 740 | "requires": { 741 | "nopt": "~1.0.10" 742 | }, 743 | "dependencies": { 744 | "nopt": { 745 | "version": "1.0.10", 746 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", 747 | "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", 748 | "requires": { 749 | "abbrev": "1" 750 | } 751 | } 752 | } 753 | }, 754 | "tough-cookie": { 755 | "version": "2.4.3", 756 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", 757 | "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", 758 | "requires": { 759 | "psl": "^1.1.24", 760 | "punycode": "^1.4.1" 761 | } 762 | }, 763 | "traverse": { 764 | "version": "0.3.9", 765 | "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", 766 | "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=" 767 | }, 768 | "tunnel-agent": { 769 | "version": "0.6.0", 770 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 771 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 772 | "requires": { 773 | "safe-buffer": "^5.0.1" 774 | } 775 | }, 776 | "tweetnacl": { 777 | "version": "0.14.5", 778 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 779 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" 780 | }, 781 | "url": { 782 | "version": "0.10.3", 783 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 784 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 785 | "requires": { 786 | "punycode": "1.3.2", 787 | "querystring": "0.2.0" 788 | }, 789 | "dependencies": { 790 | "punycode": { 791 | "version": "1.3.2", 792 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 793 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" 794 | } 795 | } 796 | }, 797 | "utile": { 798 | "version": "0.3.0", 799 | "resolved": "https://registry.npmjs.org/utile/-/utile-0.3.0.tgz", 800 | "integrity": "sha1-E1LDQOuCDk2N26A5pPv6oy7U7zo=", 801 | "requires": { 802 | "async": "~0.9.0", 803 | "deep-equal": "~0.2.1", 804 | "i": "0.3.x", 805 | "mkdirp": "0.x.x", 806 | "ncp": "1.0.x", 807 | "rimraf": "2.x.x" 808 | } 809 | }, 810 | "uuid": { 811 | "version": "3.2.1", 812 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", 813 | "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" 814 | }, 815 | "verror": { 816 | "version": "1.10.0", 817 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 818 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 819 | "requires": { 820 | "assert-plus": "^1.0.0", 821 | "core-util-is": "1.0.2", 822 | "extsprintf": "^1.2.0" 823 | } 824 | }, 825 | "winston": { 826 | "version": "2.1.1", 827 | "resolved": "https://registry.npmjs.org/winston/-/winston-2.1.1.tgz", 828 | "integrity": "sha1-PJNJ0ZYgf9G9/51LxD73JRDjoS4=", 829 | "requires": { 830 | "async": "~1.0.0", 831 | "colors": "1.0.x", 832 | "cycle": "1.0.x", 833 | "eyes": "0.1.x", 834 | "isstream": "0.1.x", 835 | "pkginfo": "0.3.x", 836 | "stack-trace": "0.0.x" 837 | }, 838 | "dependencies": { 839 | "async": { 840 | "version": "1.0.0", 841 | "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", 842 | "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=" 843 | }, 844 | "colors": { 845 | "version": "1.0.3", 846 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", 847 | "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" 848 | }, 849 | "pkginfo": { 850 | "version": "0.3.1", 851 | "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", 852 | "integrity": "sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=" 853 | } 854 | } 855 | }, 856 | "wrappy": { 857 | "version": "1.0.2", 858 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 859 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 860 | }, 861 | "xml2js": { 862 | "version": "0.4.17", 863 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", 864 | "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", 865 | "requires": { 866 | "sax": ">=0.6.0", 867 | "xmlbuilder": "^4.1.0" 868 | } 869 | }, 870 | "xmlbuilder": { 871 | "version": "4.2.1", 872 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", 873 | "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", 874 | "requires": { 875 | "lodash": "^4.0.0" 876 | }, 877 | "dependencies": { 878 | "lodash": { 879 | "version": "4.17.11", 880 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", 881 | "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" 882 | } 883 | } 884 | } 885 | } 886 | } 887 | -------------------------------------------------------------------------------- /tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudfront-auth-tests", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "init-tests.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "aws-sdk": "^2.207.0", 13 | "dateformat": "^3.0.3", 14 | "generate-password": "^1.4.1", 15 | "http": "0.0.0", 16 | "json-beautify": "^1.0.1", 17 | "ngrok": "^2.3.0", 18 | "opn": "^5.2.0", 19 | "prompt": "^1.0.0", 20 | "querystring": "^0.2.0", 21 | "shelljs": "^0.8.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const opn = require('opn'); 3 | const prompt = require('prompt'); 4 | const colors = require('colors/safe'); 5 | const http = require('http'); 6 | const url = require('url'); 7 | const qs = require('querystring'); 8 | const shell = require('shelljs'); 9 | var beautify = require('json-beautify'); 10 | var ngrok = require('ngrok'); 11 | var dateFormat = require('dateformat'); 12 | var AWS = require('aws-sdk'); 13 | var logName = dateFormat(Date.now(), "mm-dd-yyyy-hh:MM:ss"); 14 | let server; 15 | 16 | // Check for distribution argument 17 | const DISTRIBUTION = process.argv.slice(2)[0]; 18 | if(!fs.existsSync("distributions/" + DISTRIBUTION)) { 19 | console.log(colors.red("Distribution '" + DISTRIBUTION + "' does not exist. Stopping test...")); 20 | process.exit(); 21 | } 22 | 23 | shell.mkdir('-p', 'distributions/' + DISTRIBUTION + "/logs"); 24 | 25 | // Check for config-test.json 26 | if(!fs.existsSync("distributions/" + DISTRIBUTION + "/config-test.json")) { 27 | console.log(colors.red("config-test.json does not exist in '" + DISTRIBUTION + "'. Add config-test.json and restart. Stopping test...")); 28 | process.exit(); 29 | } 30 | 31 | // Load configs 32 | var config = JSON.parse(fs.readFileSync("distributions/" + DISTRIBUTION + "/config.json", 'utf8')); 33 | var testConfig = JSON.parse(fs.readFileSync("distributions/" + DISTRIBUTION + "/config-test.json")); 34 | 35 | // Update AWS config 36 | AWS.config.update({ 37 | accessKeyId: testConfig.aws.accessKeyId, 38 | secretAccessKey: testConfig.aws.secretAccessKey, 39 | region: testConfig.aws.region 40 | }); 41 | var lambda = new AWS.Lambda(); 42 | 43 | // Start local server 44 | server = http.createServer(); 45 | server.on('request', function(req, res){ 46 | var querystring = qs.parse(url.parse(req.url).query); 47 | res.writeHead(200, {'Content-Type': 'text/html'}); 48 | if (querystring.code != undefined) { 49 | // Write simple HTML page with copy button for code 50 | var codeHtml = '' + 51 | ' ' + 52 | ' cloudfront-auth testing' + 53 | ' ' + 60 | ' ' + 61 | ' ' + 62 | ' ' + 63 | ' ' + 64 | ' ' 65 | ''; 66 | res.write(codeHtml); 67 | } 68 | res.end(); 69 | }); 70 | server.listen(testConfig.port); 71 | 72 | // Toggle ngrok auth if necessary 73 | switch(testConfig.auth) { 74 | case("y"): 75 | // Generate password for ngrok 76 | var generator = require('generate-password'); 77 | var username = generator.generate({ 78 | length: 10, 79 | numbers: true 80 | }); 81 | var password = generator.generate({ 82 | length: 10, 83 | numbers: true 84 | }); 85 | console.log(colors.red("USERNAME: ") + colors.green(username)); 86 | console.log(colors.red("PASSWORD: ") + colors.green(password)); 87 | ngrok.connect({ 88 | addr: testConfig.port, 89 | auth: username + ':' + password 90 | }, function(err, url){ 91 | if (err) { 92 | console.log(err); 93 | exit(1); 94 | } 95 | setupRedirect(url, testConfig.lambdaFunction); 96 | }); 97 | break; 98 | case("n"): 99 | default: 100 | ngrok.connect(testConfig.port, function(err, url){ 101 | if (err) { 102 | console.log(err); 103 | exit(1); 104 | } 105 | setupRedirect(url, testConfig.lambdaFunction); 106 | }); 107 | } 108 | 109 | // Prompt user to enter temporary ngrok url as redirect uri 110 | function setupRedirect(url, lambdaFunction) { 111 | prompt.message = colors.blue(">"); 112 | prompt.start(); 113 | prompt.get({ 114 | properties: { 115 | wait: { 116 | description: colors.red("Add " + colors.green(url + config.CALLBACK_PATH) + " to your list of redirect URIs. Press enter when finished"), 117 | required: false, 118 | } 119 | } 120 | }, function (err, result) { 121 | // Setup initial lambda request 122 | var params = { 123 | FunctionName: lambdaFunction, 124 | Payload: initialRequestPayload(url), 125 | LogType: "Tail" 126 | } 127 | 128 | // Update log 129 | fs.writeFileSync('distributions/' + DISTRIBUTION + '/logs/' + logName + '.log', "*/ Initial Request Payload /*\n" + beautify(JSON.parse(params.Payload), null, 2, 80)); 130 | 131 | // Invoke lambda 132 | lambda.invoke(params, function(err, data) { 133 | if (err) { 134 | console.log(err, err.stack); 135 | } else { 136 | // Update log 137 | fs.appendFileSync('distributions/' + DISTRIBUTION + '/logs/' + logName + '.log', "\n*/ Initial Request Response /*\nStatus Code: " + data.StatusCode + "\nExecuted Version: " + data.ExecutedVersion + "\nLog Result:\n" + new Buffer(data.LogResult, 'base64').toString('ascii') + "\nPayload:\n" + beautify(JSON.parse(data.Payload), null, 2, 80)); 138 | var payload = JSON.parse(data.Payload); 139 | console.log(payload.headers.location[0].value); 140 | opn(payload.headers.location[0].value); 141 | 142 | // Prompt user for code on open page 143 | prompt.message = colors.blue(">"); 144 | prompt.start(); 145 | prompt.get({ 146 | properties: { 147 | code: { 148 | description: colors.red("Enter the code in your browser received from authentication provider"), 149 | required: false, 150 | } 151 | } 152 | }, function (err, result) { 153 | // Setup callback lambda request 154 | var params = { 155 | FunctionName: lambdaFunction, 156 | Payload: callbackPayload(url, result.code), 157 | LogType: "Tail" 158 | } 159 | 160 | // Update log 161 | fs.appendFileSync('distributions/' + DISTRIBUTION + '/logs/' + logName + '.log', "\n\n*/ Callback Payload /*\n" + beautify(JSON.parse(params.Payload), null, 2, 80)); 162 | 163 | // Invoke lambda 164 | lambda.invoke(params, function(err, data) { 165 | if (err) { 166 | console.log(err, err.stack); 167 | } else { 168 | // Update log 169 | fs.appendFileSync('distributions/' + DISTRIBUTION + '/logs/' + logName + '.log', "\n*/ Callback Response /*\nStatus Code: " + data.StatusCode + "\nExecuted Version: " + data.ExecutedVersion + "\nLog Result:\n" + new Buffer(data.LogResult, 'base64').toString('ascii') + "\nPayload:\n" + beautify(JSON.parse(data.Payload), null, 2, 80)); 170 | 171 | // Setup token lambda request 172 | var params = { 173 | FunctionName: lambdaFunction, 174 | Payload: tokenRequestPayload(url, JSON.parse(data.Payload).headers["set-cookie"][0].value), 175 | LogType: "Tail" 176 | } 177 | 178 | // Update log 179 | fs.appendFileSync('distributions/' + DISTRIBUTION + '/logs/' + logName + '.log', "\n\n*/ Token Payload /*\n" + beautify(JSON.parse(params.Payload), null, 2, 80)); 180 | 181 | // Invoke lambda 182 | lambda.invoke(params, function(err, data) { 183 | if (err) { 184 | console.log(err, err.stack); 185 | } else { 186 | // Update log 187 | fs.appendFileSync('distributions/' + DISTRIBUTION + '/logs/' + logName + '.log', "\n*/ Token Response /*\nStatus Code: " + data.StatusCode + "\nExecuted Version: " + data.ExecutedVersion + "\nLog Result:\n" + new Buffer(data.LogResult, 'base64').toString('ascii') + "\nPayload:\n" + beautify(JSON.parse(data.Payload), null, 2, 80)); 188 | } 189 | 190 | // Notify user of test end and kill ngrok/local server 191 | console.log(colors.green("Log created at /distributions/" + DISTRIBUTION + "/logs/" + logName + ".log")); 192 | server.close(); 193 | ngrok.disconnect(); 194 | ngrok.kill(); 195 | process.exit(); 196 | }); 197 | } 198 | }); 199 | }); 200 | } 201 | }); 202 | }); 203 | } 204 | 205 | /** Lambda test payloads **/ 206 | function initialRequestPayload(url) { 207 | var payload = { 208 | "Records": [ 209 | { 210 | "cf": { 211 | "request": { 212 | "headers": { 213 | "host": [ 214 | { 215 | "value": "example.com", 216 | "key": "Host" 217 | } 218 | ], 219 | "user-agent": [ 220 | { 221 | "value": "test-agent", 222 | "key": "User-Agent" 223 | } 224 | ] 225 | }, 226 | "clientIp": "1234:abcd::5678:1234", 227 | "uri": "/", 228 | "method": "GET", 229 | "querystring": "" 230 | }, 231 | "config": { 232 | "distributionId": "EXAMPLE", 233 | "test": url 234 | } 235 | } 236 | } 237 | ] 238 | }; 239 | return JSON.stringify(payload).replace(/\r?\n|\r/g, ""); 240 | } 241 | 242 | function callbackPayload(url, code) { 243 | var payload = { 244 | "Records": [ 245 | { 246 | "cf": { 247 | "request": { 248 | "headers": { 249 | "host": [ 250 | { 251 | "value": "example.com", 252 | "key": "Host" 253 | } 254 | ], 255 | "user-agent": [ 256 | { 257 | "value": "test-agent", 258 | "key": "User-Agent" 259 | } 260 | ] 261 | }, 262 | "clientIp": "2001:cdba::3257:9652", 263 | "uri": "/_callback", 264 | "method": "GET", 265 | "querystring": "code=" + code + "&state=%2f&session_state=fc350b67-673e-4ecd-98e7-3c2f5a875d0a" 266 | }, 267 | "config": { 268 | "distributionId": "EXAMPLE", 269 | "test": url 270 | } 271 | } 272 | } 273 | ] 274 | }; 275 | return JSON.stringify(payload).replace(/\r?\n|\r/g, ""); 276 | } 277 | 278 | function tokenRequestPayload(url, cookie) { 279 | var payload = { 280 | "Records": [ 281 | { 282 | "cf": { 283 | "request": { 284 | "headers": { 285 | "host": [ 286 | { 287 | "value": "example.com", 288 | "key": "Host" 289 | } 290 | ], 291 | "user-agent": [ 292 | { 293 | "value": "test-agent", 294 | "key": "User-Agent" 295 | } 296 | ], 297 | "cookie": [ 298 | { 299 | "value": cookie, 300 | "key": "Cookie" 301 | } 302 | ] 303 | }, 304 | "clientIp": "1234:abcd::5678:1234", 305 | "uri": "/", 306 | "method": "GET", 307 | "querystring": "" 308 | }, 309 | "config": { 310 | "distributionId": "EXAMPLE", 311 | "test": url 312 | } 313 | } 314 | } 315 | ] 316 | } 317 | return JSON.stringify(payload).replace(/\r?\n|\r/g, ""); 318 | } --------------------------------------------------------------------------------