Skip to content

Commit

Permalink
Add typescript support (#26)
Browse files Browse the repository at this point in the history
* feat: add typescript support

* test: use ts source for unit tests

Co-authored-by: Piotrek Witkowski <[email protected]>
  • Loading branch information
piotrekwitkowski and Piotrek Witkowski authored Jan 12, 2022
1 parent 022ddba commit c01ef99
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 30 deletions.
13 changes: 9 additions & 4 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@
"amd": true,
"jest": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 2018
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"rules": {
"indent": ["error", 2],
"linebreak-style": ["error", "unix"],
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ node_modules
package-lock.json
output-template.yml
*.tgz
coverage/
coverage/
dist/
.DS_Store
13 changes: 8 additions & 5 deletions __tests__/index.test.js → __tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
const axios = require('axios');
import axios from 'axios';

jest.mock('axios');

const { Authenticator } = require('../index');
import { Authenticator } from '../src/';

const DATE = new Date('2017');
// @ts-ignore
global.Date = class extends Date {
constructor() {
super();
Expand All @@ -28,7 +29,7 @@ describe('private functions', () => {
});

test('should fetch token', () => {
axios.request.mockResolvedValue({ data: tokenData });
axios.request = jest.fn().mockResolvedValue({ data: tokenData });

return authenticator._fetchTokensFromCode('htt://redirect', 'AUTH_CODE')
.then(res => {
Expand All @@ -37,7 +38,7 @@ describe('private functions', () => {
});

test('should throw if unable to fetch token', () => {
axios.request.mockRejectedValue(new Error('Unexpected error'));
axios.request = jest.fn().mockRejectedValue(new Error('Unexpected error'));
return expect(() => authenticator._fetchTokensFromCode('htt://redirect', 'AUTH_CODE')).rejects.toThrow();
});

Expand Down Expand Up @@ -152,7 +153,9 @@ describe('createAuthenticator', () => {
});

test('should fail when creating authenticator without params', () => {
expect(() => new Authenticator()).toThrow('Expected params');
// @ts-ignore
// ts-ignore is used here to override typescript's type check in the constructor
// this test is still useful when the library is imported to a js file
expect(() => new Authenticator()).toThrow('Expected params');
});

Expand Down
5 changes: 5 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
16 changes: 12 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
"description": "Cognito authentication made easy to protect your website with CloudFront and Lambda@Edge.",
"author": "AWS Builder Labs <[email protected]>",
"license": "Apache-2.0",
"main": "index.js",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"index.js"
"/dist"
],
"scripts": {
"build": "tsc",
"test": "jest --coverage"
},
"dependencies": {
Expand All @@ -21,8 +23,14 @@
"pino": "^6.10.0"
},
"devDependencies": {
"eslint": "^7.17.0",
"jest": "^26.6.3"
"@types/aws-lambda": "^8.10.89",
"@types/jest": "^27.4.0",
"@typescript-eslint/eslint-plugin": "^5.9.1",
"@typescript-eslint/parser": "^5.9.1",
"eslint": "^7.32.0",
"jest": "^27.4.7",
"ts-jest": "^27.1.2",
"typescript": "^4.5.4"
},
"engines": {
"node": ">=10.0.0"
Expand Down
53 changes: 37 additions & 16 deletions index.js → src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,33 @@
const axios = require('axios');
const querystring = require('querystring');
const pino = require('pino');
const awsJwtVerify = require('aws-jwt-verify');
import axios from 'axios';
import { parse, stringify } from 'querystring';
import pino from 'pino';
import { CognitoJwtVerifier } from 'aws-jwt-verify';
import { CloudFrontRequestEvent } from 'aws-lambda';

class Authenticator {
constructor(params) {
interface AuthenticatorParams {
region: string;
userPoolId: string;
userPoolAppId: string;
userPoolAppSecret?: string;
userPoolDomain: string;
cookieExpirationDays?: number;
disableCookieDomain?: boolean;
logLevel?: 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'silent';
}

export class Authenticator {
_region: string;
_userPoolId: string;
_userPoolAppId: string;
_userPoolAppSecret: string;
_userPoolDomain: string;
_cookieExpirationDays: number;
_disableCookieDomain: boolean;
_cookieBase: string;
_logger;
_jwtVerifier;

constructor(params: AuthenticatorParams) {
this._verifyParams(params);
this._region = params.region;
this._userPoolId = params.userPoolId;
Expand All @@ -18,7 +41,7 @@ class Authenticator {
level: params.logLevel || 'silent', // Default to silent
base: null, //Remove pid, hostname and name logging as not usefull for Lambda
});
this._jwtVerifier = awsJwtVerify.CognitoJwtVerifier.create({
this._jwtVerifier = CognitoJwtVerifier.create({
userPoolId: params.userPoolId,
clientId: params.userPoolAppId,
tokenUse: 'id',
Expand Down Expand Up @@ -57,18 +80,18 @@ class Authenticator {
const authorization = this._userPoolAppSecret && Buffer.from(`${this._userPoolAppId}:${this._userPoolAppSecret}`).toString('base64');
const request = {
url: `https://${this._userPoolDomain}/oauth2/token`,
method: 'post',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
...(authorization && {'Authorization': `Basic ${authorization}`}),
},
data: querystring.stringify({
data: stringify({
client_id: this._userPoolAppId,
code: code,
grant_type: 'authorization_code',
redirect_uri: redirectURI,
}),
};
} as const;
this._logger.debug({ msg: 'Fetching tokens from grant code...', request, code });
return axios.request(request)
.then(resp => {
Expand All @@ -93,8 +116,8 @@ class Authenticator {
const username = decoded['cognito:username'];
const usernameBase = `${this._cookieBase}.${username}`;
const directives = (!this._disableCookieDomain) ?
`Domain=${domain}; Expires=${new Date(new Date() * 1 + this._cookieExpirationDays * 864e+5)}; Secure` :
`Expires=${new Date(new Date() * 1 + this._cookieExpirationDays * 864e+5)}; Secure`;
`Domain=${domain}; Expires=${new Date(Date.now() + this._cookieExpirationDays * 864e+5)}; Secure` :
`Expires=${new Date(Date.now() + this._cookieExpirationDays * 864e+5)}; Secure`;
const response = {
status: '302' ,
headers: {
Expand Down Expand Up @@ -170,11 +193,11 @@ class Authenticator {
* @param {Object} event Lambda@Edge event.
* @return {Promise} CloudFront response.
*/
async handle(event) {
async handle(event: CloudFrontRequestEvent) {
this._logger.debug({ msg: 'Handling Lambda@Edge event', event });

const { request } = event.Records[0].cf;
const requestParams = querystring.parse(request.querystring);
const requestParams = parse(request.querystring);
const cfDomain = request.headers.host[0].value;
const redirectURI = `https://${cfDomain}`;

Expand Down Expand Up @@ -217,5 +240,3 @@ class Authenticator {
}
}
}

module.exports.Authenticator = Authenticator;
11 changes: 11 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "esnext",
"declaration": true,
"outDir": "./dist"
},
"include": [
"src/**/*"
]
}

0 comments on commit c01ef99

Please sign in to comment.