├── .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 | ' Copy code ' +
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 | }
--------------------------------------------------------------------------------