Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add isAuthenticated to check if user is authenticated #22

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 57 additions & 19 deletions __tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ describe('private functions', () => {
},
});
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`},
{ 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._jwtVerifier.verify).toHaveBeenCalled();
});
Expand All @@ -79,13 +79,13 @@ describe('private functions', () => {
logLevel: 'error',
});
authenticatorWithNoCookieDomain._jwtVerifier.cacheJwks(jwksData);

const username = 'toto';
const domain = 'example.com';
const path = '/test';
jest.spyOn(authenticatorWithNoCookieDomain._jwtVerifier, 'verify');
authenticatorWithNoCookieDomain._jwtVerifier.verify.mockReturnValueOnce(Promise.resolve({ token_use: 'id', 'cognito:username': username }));

const response = await authenticatorWithNoCookieDomain._getRedirectResponse(tokenData, domain, path);
expect(response).toMatchObject({
status: '302',
Expand All @@ -97,11 +97,11 @@ describe('private functions', () => {
},
});
expect(response.headers['set-cookie']).toEqual(expect.arrayContaining([
{key: 'Set-Cookie', value: `CognitoIdentityServiceProvider.123456789qwertyuiop987abcd.${username}.accessToken=${tokenData.access_token}; Expires=${DATE}; Secure`},
{key: 'Set-Cookie', value: `CognitoIdentityServiceProvider.123456789qwertyuiop987abcd.${username}.refreshToken=${tokenData.refresh_token}; Expires=${DATE}; Secure`},
{key: 'Set-Cookie', value: `CognitoIdentityServiceProvider.123456789qwertyuiop987abcd.${username}.tokenScopesString=phone email profile openid aws.cognito.signin.user.admin; Expires=${DATE}; Secure`},
{key: 'Set-Cookie', value: `CognitoIdentityServiceProvider.123456789qwertyuiop987abcd.${username}.idToken=${tokenData.id_token}; Expires=${DATE}; Secure`},
{key: 'Set-Cookie', value: `CognitoIdentityServiceProvider.123456789qwertyuiop987abcd.LastAuthUser=${username}; Expires=${DATE}; Secure`},
{ key: 'Set-Cookie', value: `CognitoIdentityServiceProvider.123456789qwertyuiop987abcd.${username}.accessToken=${tokenData.access_token}; Expires=${DATE}; Secure` },
{ key: 'Set-Cookie', value: `CognitoIdentityServiceProvider.123456789qwertyuiop987abcd.${username}.refreshToken=${tokenData.refresh_token}; Expires=${DATE}; Secure` },
{ key: 'Set-Cookie', value: `CognitoIdentityServiceProvider.123456789qwertyuiop987abcd.${username}.tokenScopesString=phone email profile openid aws.cognito.signin.user.admin; Expires=${DATE}; Secure` },
{ key: 'Set-Cookie', value: `CognitoIdentityServiceProvider.123456789qwertyuiop987abcd.${username}.idToken=${tokenData.id_token}; Expires=${DATE}; Secure` },
{ key: 'Set-Cookie', value: `CognitoIdentityServiceProvider.123456789qwertyuiop987abcd.LastAuthUser=${username}; Expires=${DATE}; Secure` },
]));
expect(authenticatorWithNoCookieDomain._jwtVerifier.verify).toHaveBeenCalled();
});
Expand Down Expand Up @@ -236,7 +236,7 @@ describe('handle', () => {
});

test('should fetch and set token if code is present', () => {
authenticator._jwtVerifier.verify.mockImplementationOnce(async () => { throw new Error();});
authenticator._jwtVerifier.verify.mockImplementationOnce(async () => { throw new Error(); });
authenticator._fetchTokensFromCode.mockResolvedValueOnce(tokenData);
authenticator._getRedirectResponse.mockReturnValueOnce({ response: 'toto' });
const request = getCloudfrontRequest();
Expand All @@ -250,7 +250,7 @@ describe('handle', () => {
});

test('should redirect to auth domain if unauthenticated and no code', () => {
authenticator._jwtVerifier.verify.mockImplementationOnce(async () => { throw new Error();});
authenticator._jwtVerifier.verify.mockImplementationOnce(async () => { throw new Error(); });
return expect(authenticator.handle(getCloudfrontRequest())).resolves.toEqual(
{
status: 302,
Expand All @@ -268,6 +268,44 @@ describe('handle', () => {
});
});

describe('isAuthenticated', () => {
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._jwtVerifier.cacheJwks(jwksData);
jest.spyOn(authenticator, '_getIdTokenFromCookie');
jest.spyOn(authenticator, '_fetchTokensFromCode');
jest.spyOn(authenticator, '_getRedirectResponse');
jest.spyOn(authenticator._jwtVerifier, 'verify');
});

test('return true if the user is authenticated', () => {
authenticator._jwtVerifier.verify.mockReturnValueOnce(Promise.resolve({}));
return expect(authenticator.isAuthenticated(getCloudfrontRequest())).resolves.toEqual(true)
.then(() => {
expect(authenticator._getIdTokenFromCookie).toHaveBeenCalled();
expect(authenticator._jwtVerifier.verify).toHaveBeenCalled();
});
});

test('return false if the user is not authenticated', () => {
authenticator._jwtVerifier.verify.mockImplementationOnce(async () => { throw new Error(); });
return expect(authenticator.isAuthenticated(getCloudfrontRequest())).resolves.toEqual(false)
.then(() => {
expect(authenticator._jwtVerifier.verify).toHaveBeenCalled();
});
});
});


/* eslint-disable quotes, comma-dangle */

const jwksData = {
Expand All @@ -278,11 +316,11 @@ const jwksData = {
};

const tokenData = {
"access_token":"eyJz9sdfsdfsdfsd",
"refresh_token":"dn43ud8uj32nk2je",
"id_token":"dmcxd329ujdmkemkd349r",
"token_type":"Bearer",
'expires_in':3600,
"access_token": "eyJz9sdfsdfsdfsd",
"refresh_token": "dn43ud8uj32nk2je",
"id_token": "dmcxd329ujdmkemkd349r",
"token_type": "Bearer",
'expires_in': 3600,
};

const getCloudfrontRequest = () => ({
Expand Down
41 changes: 32 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class Authenticator {
if (typeof params !== 'object') {
throw new Error('Expected params to be an object');
}
[ 'region', 'userPoolId', 'userPoolAppId', 'userPoolDomain' ].forEach(param => {
['region', 'userPoolId', 'userPoolAppId', 'userPoolDomain'].forEach(param => {
if (typeof params[param] !== 'string') {
throw new Error(`Expected params.${param} to be a string`);
}
Expand All @@ -60,13 +60,13 @@ class Authenticator {
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
...(authorization && {'Authorization': `Basic ${authorization}`}),
...(authorization && { 'Authorization': `Basic ${authorization}` }),
},
data: querystring.stringify({
client_id: this._userPoolAppId,
code: code,
grant_type: 'authorization_code',
redirect_uri: redirectURI,
client_id: this._userPoolAppId,
code: code,
grant_type: 'authorization_code',
redirect_uri: redirectURI,
}),
};
this._logger.debug({ msg: 'Fetching tokens from grant code...', request, code });
Expand All @@ -92,11 +92,11 @@ class Authenticator {
const decoded = await this._jwtVerifier.verify(tokens.id_token);
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` :
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`;
const response = {
status: '302' ,
status: '302',
headers: {
'location': [{ key: 'Location', 'value': location }],
'set-cookie': [
Expand Down Expand Up @@ -197,6 +197,29 @@ class Authenticator {
}
}
}

/**
* Check if user is authenticated:
* * if authentication cookie is present and valid: return true
* * else return false
* @param {Object} event Lambda@Edge event.
* @return {Boolean} True if user is authenticated.
*/
async isAuthenticated(event) {
this._logger.debug({ msg: 'Checking if Lambda@Edge event is authenticated', event });

const { request } = event.Records[0].cf;

try {
const token = this._getIdTokenFromCookie(request.headers.cookie);
this._logger.debug({ msg: 'Verifying token...', token });
await this._jwtVerifier.verify(token);

return true;
} catch (err) {
return false;
}
}
}

module.exports.Authenticator = Authenticator;