diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..b91696c --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,21 @@ +{ + "env": { + "es6": true, + "node": true, + "amd": true, + "jest": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 2018 + }, + "rules": { + "indent": ["error", 2], + "linebreak-style": ["error", "unix"], + "quotes": ["error", "single", { "avoidEscape": true }], + "camelcase": [2, { "properties": "never" }], + "semi": ["error", "always"], + "comma-dangle": ["error", "always-multiline"], + "no-console": "off" + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..200aa91 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +package-lock.json +output-template.yml diff --git a/README.md b/README.md index 8f46b7c..735343f 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,69 @@ -## My Project +## cognito-at-edge +*Serverless authentication solution to protect your website or Amplify application.* -TODO: Fill this README out! +![Architecture](./doc/architecture.png) +This NodeJS library authenticate CloudFront requests with Lambda@Edge based and a Cognito UserPool. -Be sure to: +### Requirements +* NodeJS v10+ (install with [NVM](https://github.com/nvm-sh/nvm)) +* aws-cli installed and configured ([installation guide](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html)) -* Change the title in this README -* Edit your repository description on GitHub +### Usage -## License +Install the `cognito-at-edge` package: +``` +npm install --save cognito-at-edge +``` -This project is licensed under the Apache-2.0 License. +Create the a Lambda@Edge function with the following content and modify the parameters based on your configuration: +``` +const { Authenticator } = require('cognito-at-edge'); + +const authenticator = new Authenticator({ + region: 'us-east-1', // user pool region + userPoolId: 'us-east-1_tyo1a1FHH', + userPoolAppId: '63gcbm2jmskokurt5ku9fhejc6', + userPoolDomain: 'domain.auth.us-east-1.amazoncognito.com', + logLevel: 'error', +}); + +exports.handler = async (request) => authenticator.handle(request); +``` + +**Every `request` will be authenticated by the `Authenticator.handle` function.** + +### Getting started + +Based on your requirements you can use of the solution below. They all provide the complete infrastructure leveraging `cognito-at-edge` to protect a website or an Amplify application. + +*WIP* + +### Reference +#### Authenticator Class +##### Authenticator(params) +* `params` *Object* Authenticator parameters: + * `region` *string* Cognito UserPool region (eg: `us-east-1`) + * `userPoolId` *string* Cognito UserPool ID (eg: `us-east-1_tyo1a1FHH`) + * `userPoolAppId` *string* Cognito UserPool Application ID (eg: `63gcbm2jmskokurt5ku9fhejc6`) + * `userPoolDomain` *string* Cognito UserPool domain (eg: `your-domain.auth.us-east-1.amazoncognito.com`) + * `cookieExpirationDays` *number* (Optional) Number of day to set cookies expiration date, default to 365 days (eg: `365`) + * `logLevel` *string* (Optional) Logging level. Default: `'silent'`. One of `'fatal'`, `'error'`, `'warn'`, `'info'`, `'debug'`, `'trace'` or `'silent'`. + +*This is the class constructor.* + +##### handle(request) +* `request` *Object* Lambda@Edge request Object + * cf AWS doc for details: [Lambda@Edge events](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html) + +Use it as your Lambda Handler. It will authenticate each query. +``` +const authenticator = new Authenticator( ... ); +exports.handler = async (request) => authenticator.handle(request); +``` + +### Contact +Please fill an issue in the Github repository ([Open issues](https://github.com/awslabs/cognito-at-edge/issues)). + +## License +This project is licensed under the Apache-2.0 License. diff --git a/__tests__/index.test.js b/__tests__/index.test.js new file mode 100644 index 0000000..d299f39 --- /dev/null +++ b/__tests__/index.test.js @@ -0,0 +1,346 @@ +const axios = require('axios'); +const jwt = require('jsonwebtoken'); + +jest.mock('axios'); +jest.mock('jwk-to-pem'); +jest.mock('jsonwebtoken'); + +const { Authenticator } = require('../index'); + +const DATE = new Date('2017'); +global.Date = class extends Date { + constructor() { + return DATE; + } +}; + +describe('private functions', () => { + let authenticator; + + beforeEach(() => { + authenticator = new Authenticator({ + region: 'us-east-1', + userPoolId: 'us-east-1_abcdef123', + userPoolAppId: '123456789qwertyuiop987abcd', + userPoolDomain: 'my-cognito-domain.auth.us-east-1.amazoncognito.com', + cookieExpirationDays: 365, + logLevel: 'error', + }); + }); + + test('JWKS should be false by default', () => { + expect(authenticator._jwks).toBeFalsy(); + }); + + test('should fetch JWKS', () => { + axios.get.mockResolvedValue({ data: jwksData }); + return authenticator._fetchJWKS('http://something') + .then(() => { + expect(authenticator._jwks).toEqual({ + '1234example=': { 'kid': '1234example=', 'alg': 'RS256', 'kty': 'RSA', 'e': 'AQAB', 'n': '1234567890', 'use': 'sig' }, + '5678example=': { 'kid': '5678example=', 'alg': 'RS256', 'kty': 'RSA', 'e': 'AQAB', 'n': '987654321', 'use': 'sig' }, + }); + }); + }); + + test('should get valid decoded token', () => { + authenticator._jwks = {}; + jwt.decode.mockReturnValueOnce({ header: { kid: 'kid' } }); + jwt.verify.mockReturnValueOnce({ token_use: 'id', attribute: 'valid' }); + expect(authenticator._getVerifiedToken('valid-token')).toEqual({ token_use: 'id', attribute: 'valid' }); + }); + + test('should fetch token', () => { + axios.request.mockResolvedValue({ data: tokenData }); + + return authenticator._fetchTokensFromCode('htt://redirect', 'AUTH_CODE') + .then(res => { + expect(res).toEqual(tokenData); + }); + }); + + test('should getRedirectResponse', () => { + const username = 'toto'; + const domain = 'example.com'; + const path = '/test'; + jest.spyOn(authenticator, '_getVerifiedToken'); + authenticator._getVerifiedToken.mockReturnValueOnce({ token_use: 'id', 'cognito:username': username }); + + const response = authenticator._getRedirectResponse(tokenData, domain, path); + expect(response).toMatchObject({ + status: '302', + headers: { + location: [{ + key: 'Location', + value: path, + }], + }, + }); + expect(response.headers['set-cookie']).toEqual(expect.arrayContaining([ + {key: 'Set-Cookie', value: `CognitoIdentityServiceProvider.123456789qwertyuiop987abcd.${username}.accessToken=${tokenData.access_token}; Domain=${domain}; Expires=${DATE}; Secure`}, + {key: 'Set-Cookie', value: `CognitoIdentityServiceProvider.123456789qwertyuiop987abcd.${username}.refreshToken=${tokenData.refresh_token}; Domain=${domain}; Expires=${DATE}; Secure`}, + {key: 'Set-Cookie', value: `CognitoIdentityServiceProvider.123456789qwertyuiop987abcd.${username}.tokenScopesString=phone email profile openid aws.cognito.signin.user.admin; Domain=${domain}; Expires=${DATE}; Secure`}, + {key: 'Set-Cookie', value: `CognitoIdentityServiceProvider.123456789qwertyuiop987abcd.${username}.idToken=${tokenData.id_token}; Domain=${domain}; Expires=${DATE}; Secure`}, + {key: 'Set-Cookie', value: `CognitoIdentityServiceProvider.123456789qwertyuiop987abcd.LastAuthUser=${username}; Domain=${domain}; Expires=${DATE}; Secure`}, + ])); + expect(authenticator._getVerifiedToken).toHaveBeenCalled(); + }); + + test('should getIdTokenFromCookie', () => { + expect( + authenticator._getIdTokenFromCookie([{ + key: 'Cookie', + value: `CognitoIdentityServiceProvider.5uka3k8840tap1g1i1617jh8pi.MyFederation_toto123.idToken=wrong; CognitoIdentityServiceProvider.123456789qwertyuiop987abcd.MyFederation_toto123.idToken=${tokenData.id_token}; CognitoIdentityServiceProvider.123456789qwertyuiop987abcd.MyFederation_toto123.idToken=${tokenData.id_token}; CognitoIdentityServiceProvider.5ukasw8840tap1g1i1617jh8pi.MyFederation_toto123.idToken=wrong;`, + }]), + ).toBe(tokenData.id_token); + }); + + test('should getIdTokenFromCookie throw on cookies', () => { + expect(() => authenticator._getIdTokenFromCookie()).toThrow('Id token'); + expect(() => authenticator._getIdTokenFromCookie('')).toThrow('Id token'); + expect(() => authenticator._getIdTokenFromCookie([])).toThrow('Id token'); + }); +}); + +describe('createAuthenticator', () => { + let params; + + beforeEach(() => { + params = { + region: 'us-east-1', + userPoolId: 'us-east-1_abcdef123', + userPoolAppId: '123456789qwertyuiop987abcd', + userPoolDomain: 'my-cognito-domain.auth.us-east-1.amazoncognito.com', + cookieExpirationDays: 365, + }; + }); + + test('should create authenticator', () => { + expect(typeof new Authenticator(params)).toBe('object'); + }); + + test('should create authenticator without cookieExpirationDay', () => { + delete params.cookieExpirationDays; + expect(typeof new Authenticator(params)).toBe('object'); + }); + + test('should fail when creating authenticator without params', () => { + expect(() => new Authenticator()).toThrow('Expected params'); + expect(() => new Authenticator()).toThrow('Expected params'); + }); + + test('should fail when creating authenticator without region', () => { + delete params.region; + expect(() => new Authenticator(params)).toThrow('region'); + }); + + test('should fail when creating authenticator without userPoolId', () => { + delete params.userPoolId; + expect(() => new Authenticator(params)).toThrow('userPoolId'); + }); + + test('should fail when creating authenticator without userPoolAppId', () => { + delete params.userPoolAppId; + expect(() => new Authenticator(params)).toThrow('userPoolAppId'); + }); + + test('should fail when creating authenticator without userPoolDomain', () => { + delete params.userPoolDomain; + expect(() => new Authenticator(params)).toThrow('userPoolDomain'); + }); + + test('should fail when creating authenticator with invalid region', () => { + params.region = 123; + expect(() => new Authenticator(params)).toThrow('region'); + }); + + test('should fail when creating authenticator with invalid userPoolId', () => { + params.userPoolId = 123; + expect(() => new Authenticator(params)).toThrow('userPoolId'); + }); + + test('should fail when creating authenticator with invalid userPoolAppId', () => { + params.userPoolAppId = 123; + expect(() => new Authenticator(params)).toThrow('userPoolAppId'); + }); + + test('should fail when creating authenticator with invalid userPoolDomain', () => { + params.userPoolDomain = 123; + expect(() => new Authenticator(params)).toThrow('userPoolDomain'); + }); + + test('should fail when creating authenticator with invalid cookieExpirationDay', () => { + params.cookieExpirationDays = '123'; + expect(() => new Authenticator(params)).toThrow('cookieExpirationDays'); + }); +}); + +describe('handle', () => { + let authenticator; + + beforeEach(() => { + authenticator = new Authenticator({ + region: 'us-east-1', + userPoolId: 'us-east-1_abcdef123', + userPoolAppId: '123456789qwertyuiop987abcd', + userPoolDomain: 'my-cognito-domain.auth.us-east-1.amazoncognito.com', + cookieExpirationDays: 365, + logLevel: 'debug', + }); + authenticator._jwks = jwksData; + jest.spyOn(authenticator, '_fetchJWKS'); + jest.spyOn(authenticator, '_getVerifiedToken'); + jest.spyOn(authenticator, '_getIdTokenFromCookie'); + jest.spyOn(authenticator, '_fetchTokensFromCode'); + jest.spyOn(authenticator, '_getRedirectResponse'); + }); + + test('should fetch JWKS if not present', () => { + authenticator._jwks = undefined; + authenticator._fetchJWKS.mockResolvedValueOnce(jwksData); + return authenticator.handle(getCloudfrontRequest()) + .catch(err => err) + .finally(() => expect(authenticator._fetchJWKS).toHaveBeenCalled()); + }); + + test('should forward request if authenticated', () => { + authenticator._getVerifiedToken.mockReturnValueOnce({}); + return expect(authenticator.handle(getCloudfrontRequest())).resolves.toEqual(getCloudfrontRequest().Records[0].cf.request) + .then(() => { + expect(authenticator._getIdTokenFromCookie).toHaveBeenCalled(); + expect(authenticator._getVerifiedToken).toHaveBeenCalled(); + }); + }); + + test('should fetch and set token if code is present', () => { + authenticator._getVerifiedToken.mockImplementationOnce(() => { throw new Error();}); + authenticator._fetchTokensFromCode.mockResolvedValueOnce(tokenData); + authenticator._getRedirectResponse.mockReturnValueOnce({ response: 'toto' }); + const request = getCloudfrontRequest(); + request.Records[0].cf.request.querystring = 'code=54fe5f4e&state=/lol'; + return expect(authenticator.handle(request)).resolves.toEqual({ response: 'toto' }) + .then(() => { + expect(authenticator._getVerifiedToken).toHaveBeenCalled(); + expect(authenticator._fetchTokensFromCode).toHaveBeenCalled(); + expect(authenticator._getRedirectResponse).toHaveBeenCalledWith(tokenData, 'd111111abcdef8.cloudfront.net', '/lol'); + }); + }); + + test('should redirect to auth domain if unauthenticated and no code', () => { + authenticator._getVerifiedToken.mockImplementationOnce(() => { throw new Error();}); + return expect(authenticator.handle(getCloudfrontRequest())).resolves.toEqual( + { + status: 302, + headers: { + location: [{ + key: 'Location', + value: 'https://my-cognito-domain.auth.us-east-1.amazoncognito.com/authorize?redirect_uri=https://d111111abcdef8.cloudfront.net&response_type=code&client_id=123456789qwertyuiop987abcd&state=/lol', + }], + }, + }, + ) + .then(() => { + expect(authenticator._getVerifiedToken).toHaveBeenCalled(); + }); + }); +}); + +/* eslint-disable quotes, comma-dangle */ + +const jwksData = { + "keys": [ + { "kid": "1234example=", "alg": "RS256", "kty": "RSA", "e": "AQAB", "n": "1234567890", "use": "sig" }, + { "kid": "5678example=", "alg": "RS256", "kty": "RSA", "e": "AQAB", "n": "987654321", "use": "sig" }, + ] +}; + +const tokenData = { + "access_token":"eyJz9sdfsdfsdfsd", + "refresh_token":"dn43ud8uj32nk2je", + "id_token":"dmcxd329ujdmkemkd349r", + "token_type":"Bearer", + 'expires_in':3600, +}; + +const getCloudfrontRequest = () => ({ + "Records": [ + { + "cf": { + "config": { + "distributionDomainName": "d123.cloudfront.net", + "distributionId": "EDFDVBD6EXAMPLE", + "eventType": "viewer-request", + "requestId": "MRVMF7KydIvxMWfJIglgwHQwZsbG2IhRJ07sn9AkKUFSHS9EXAMPLE==" + }, + "request": { + "body": { + "action": "read-only", + "data": "eyJ1c2VybmFtZSI6IkxhbWJkYUBFZGdlIiwiY29tbWVudCI6IlRoaXMgaXMgcmVxdWVzdCBib2R5In0=", + "encoding": "base64", + "inputTruncated": false + }, + "clientIp": "2001:0db8:85a3:0:0:8a2e:0370:7334", + "querystring": "", + "uri": "/lol", + "method": "GET", + "headers": { + "host": [ + { + "key": "Host", + "value": "d111111abcdef8.cloudfront.net" + } + ], + "user-agent": [ + { + "key": "User-Agent", + "value": "curl/7.51.0" + }, + ], + "cookie": [ + { + key: 'cookie', + value: `CognitoIdentityServiceProvider.123456789qwertyuiop987abcd.toto.idToken=${tokenData.access_token};` + } + ] + }, + "origin": { + "custom": { + "customHeaders": { + "my-origin-custom-header": [ + { + "key": "My-Origin-Custom-Header", + "value": "Test" + } + ] + }, + "domainName": "example.com", + "keepaliveTimeout": 5, + "path": "/custom_path", + "port": 443, + "protocol": "https", + "readTimeout": 5, + "sslProtocols": [ + "TLSv1", + "TLSv1.1" + ] + }, + "s3": { + "authMethod": "origin-access-identity", + "customHeaders": { + "my-origin-custom-header": [ + { + "key": "My-Origin-Custom-Header", + "value": "Test" + } + ] + }, + "domainName": "my-bucket.s3.amazonaws.com", + "path": "/s3_path", + "region": "us-east-1" + } + } + } + } + } + ] +}); diff --git a/doc/architecture.png b/doc/architecture.png new file mode 100644 index 0000000..d539346 Binary files /dev/null and b/doc/architecture.png differ diff --git a/index.js b/index.js new file mode 100644 index 0000000..6c70b69 --- /dev/null +++ b/index.js @@ -0,0 +1,227 @@ +const jwt = require('jsonwebtoken'); +const jwkToPem = require('jwk-to-pem'); +const assert = require('assert'); +const axios = require('axios'); +const querystring = require('querystring'); +const pino = require('pino'); + +class Authenticator { + constructor(params) { + this._verifyParams(params); + this._region = params.region; + this._userPoolId = params.userPoolId; + this._userPoolAppId = params.userPoolAppId; + this._userPoolDomain = params.userPoolDomain; + this._cookieExpirationDays = params.cookieExpirationDays || 365; + + this._issuer = `https://cognito-idp.${params.region}.amazonaws.com/${params.userPoolId}`; + this._cookieBase = `CognitoIdentityServiceProvider.${params.userPoolAppId}`; + this._logger = pino({ + level: params.logLevel || 'silent', // Default to silent + base: null, //Remove pid, hostname and name logging as not usefull for Lambda + }); + } + + /** + * Verify that constructor parameters are corrects. + * @param {object} params constructor params + * @return {void} throw an exception if params are incorects. + */ + _verifyParams(params) { + if (typeof params !== 'object') { + throw new Error('Expected params to be an object'); + } + [ 'region', 'userPoolId', 'userPoolAppId', 'userPoolDomain' ].forEach(param => { + if (typeof params[param] !== 'string') { + throw new Error(`Expected params.${param} to be a string`); + } + }); + if (params.cookieExpirationDays && typeof params.cookieExpirationDays !== 'number') { + throw new Error('Expected params.cookieExpirationDays to be a number'); + } + } + + /** + * Download JSON Web Key Set (JWKS) from the UserPool. + * @param {String} issuer URI of the UserPool. + * @return {Promise} Request. + */ + _fetchJWKS() { + this._jwks = {}; + const URL = `${this._issuer}/.well-known/jwks.json`; + this._logger.debug(`Fetching JWKS from ${URL}`); + return axios.get(URL) + .then(resp => { + resp.data.keys.forEach(key => this._jwks[key.kid] = key); + }) + .catch(err => { + this._logger.error(`Unable to fetch JWKS from ${URL}`); + throw err; + }); + } + + /** + * Verify that the current token is valid. Throw an error if not. + * @param {String} token Token to verify. + * @return {Object} Decoded token. + */ + _getVerifiedToken(token) { + this._logger.debug({ msg: 'Verifying token...', token }); + const decoded = jwt.decode(token, {complete: true}); + const kid = decoded.header.kid; + const verified = jwt.verify(token, jwkToPem(this._jwks[kid]), { audience: this._userPoolAppId, issuer: this._issuer }); + assert.strictEqual(verified.token_use, 'id'); + return verified; + } + + /** + * Exchange authorization code for tokens. + * @param {String} redirectURI Redirection URI. + * @param {String} code Authorization code. + * @return {Promise} Authenticated user tokens. + */ + _fetchTokensFromCode(redirectURI, code) { + const request = { + url: `https://${this._userPoolDomain}/oauth2/token`, + method: 'post', + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + data: querystring.stringify({ + client_id: this._userPoolAppId, + code: code, + grant_type: 'authorization_code', + redirect_uri: redirectURI, + }), + }; + this._logger.debug({ msg: 'Fetching tokens from grant code...', request, code }); + return axios.request(request) + .then(resp => { + this._logger.debug({ msg: 'Fetched tokens', tokens: resp.data }); + return resp.data; + }) + .catch(err => { + this._logger.error({ msg: 'Unable to fetch tokens from grant code', request, code }); + throw err; + }); + } + + /** + * Create a Lambda@Edge redirection response to set the tokens on the user's browser cookies. + * @param {Object} tokens Cognito User Pool tokens. + * @param {String} domain Website domain. + * @param {String} location Path to redirection. + * @return {Object} Lambda@Edge response. + */ + _getRedirectResponse(tokens, domain, location) { + const decoded = this._getVerifiedToken(tokens.id_token); + const username = decoded['cognito:username']; + const usernameBase = `${this._cookieBase}.${username}`; + const directives = `Domain=${domain}; Expires=${new Date(new Date() * 1 + this._cookieExpirationDays * 864e+5)}; Secure`; + + const response = { + status: '302' , + headers: { + 'location': [{ key: 'Location', 'value': location }], + 'set-cookie': [ + { + key: 'Set-Cookie', + value: `${usernameBase}.accessToken=${tokens.access_token}; ${directives}`, + }, + { + key: 'Set-Cookie', + value: `${usernameBase}.idToken=${tokens.id_token}; ${directives}`, + }, + { + key: 'Set-Cookie', + value: `${usernameBase}.refreshToken=${tokens.refresh_token}; ${directives}`, + }, + { + key: 'Set-Cookie', + value: `${usernameBase}.tokenScopesString=phone email profile openid aws.cognito.signin.user.admin; ${directives}`, + }, + { + key: 'Set-Cookie', + value: `${this._cookieBase}.LastAuthUser=${username}; ${directives}`, + }, + ], + }, + }; + + this._logger.debug({ msg: 'Generated set-cookie response', response }); + + return response; + } + + /** + * Extract value of the authentication token from the request cookies. + * @param {Array} cookies Request cookies. + * @return {String} Extracted access token. Throw if not found. + */ + _getIdTokenFromCookie(cookies) { + this._logger.debug({ msg: 'Extracting authentication token from request cookie', cookies }); + // eslint-disable-next-line no-useless-escape + const regex = new RegExp(`${this._userPoolAppId}\.[A-z0-9_]*\.idToken=(.*?);`); + if (cookies) { + for (let i = 0; i < cookies.length; i++) { + const matches = cookies[i].value.match(regex); + if (matches && matches.length > 1) { + this._logger.debug({ msg: ' Found token in cookie', token: matches[1] }); + return matches[1]; + } + } + } + this._logger.debug(" idToken wasn't present in request cookies"); + throw new Error("Id token isn't present in the request cookies"); + } + + /** + * Handle Lambda@Edge event: + * * if authentication cookie is present and valid: forward the request + * * if ?code= is present: set cookies with new tokens + * * else redirect to the Cognito UserPool to authenticate the user + * @param {Object} event Lambda@Edge event. + * @return {Promise} CloudFront response. + */ + async handle(event) { + this._logger.debug({ msg: 'Handling Lambda@Edge event', event }); + + if (!this._jwks) { + await this._fetchJWKS(); + } + + const { request } = event.Records[0].cf; + const requestParams = querystring.parse(request.querystring); + const cfDomain = request.headers.host[0].value; + const redirectURI = `https://${cfDomain}`; + + try { + const token = this._getIdTokenFromCookie(request.headers.cookie); + const user = this._getVerifiedToken(token); + this._logger.info({ msg: 'Forwading request', path: request.uri, user }); + return request; + } catch (err) { + this._logger.debug("User isn't authenticated"); + if (requestParams.code) { + return this._fetchTokensFromCode(redirectURI, requestParams.code) + .then(tokens => this._getRedirectResponse(tokens, cfDomain, decodeURIComponent(requestParams.state))); + } else { + let redirectPath = request.uri; + if (request.querystring && request.querystring !== '') { + redirectPath += encodeURIComponent('?' + request.querystring); + } + const userPoolUrl = `https://${this._userPoolDomain}/authorize?redirect_uri=${redirectURI}&response_type=code&client_id=${this._userPoolAppId}&state=${redirectPath}`; + this._logger.debug(`Redirecting user to Cognito User Pool URL ${userPoolUrl}`); + return { + status: 302, + headers: { + location: [{ + key: 'Location', + value: userPoolUrl, + }], + }, + }; + } + } + } +} + +module.exports.Authenticator = Authenticator; diff --git a/package.json b/package.json new file mode 100644 index 0000000..ee829be --- /dev/null +++ b/package.json @@ -0,0 +1,41 @@ +{ + "name": "cognito-at-edge", + "version": "1.0.0", + "repository": { + "type": "git", + "url": "https://github.com/awslabs/cognito-at-edge" + }, + "description": "Serverless authentication solution to protect your website or Amplify application.", + "author": "Hugo David-Boyet ", + "license": "Apache-2.0", + "main": "index.js", + "files": [ + "index.js" + ], + "scripts": { + "test": "jest" + }, + "dependencies": { + "axios": "^0.18.0", + "jsonwebtoken": "^8.2.1", + "jwk-to-pem": "^2.0.0", + "pino": "^5.13.2" + }, + "devDependencies": { + "eslint": "^6.2.2", + "jest": "^24.8.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "keywords": [ + "aws", + "cognito", + "userpool", + "cloudfront", + "lambda", + "edge", + "private", + "website" + ] +}