diff --git a/README.md b/README.md index c0931c4..e4c9c37 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ For an explanation of the interactions between CloudFront, Cognito and Lambda@Ed * `userPoolAppSecret` *string* (Optional) Cognito UserPool Application Secret (eg: `oh470px2i0uvy4i2ha6sju0vxe4ata9ol3m63ufhs2t8yytwjn7p`) * `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`) + * `disableCookieDomain` *boolean* (Optional) Sets domain attribute in cookies, defaults to false (eg: `false`) * `logLevel` *string* (Optional) Logging level. Default: `'silent'`. One of `'fatal'`, `'error'`, `'warn'`, `'info'`, `'debug'`, `'trace'` or `'silent'`. *This is the class constructor.* diff --git a/__tests__/index.test.js b/__tests__/index.test.js index 24bdaf8..b465cbf 100644 --- a/__tests__/index.test.js +++ b/__tests__/index.test.js @@ -25,6 +25,7 @@ describe('private functions', () => { userPoolAppId: '123456789qwertyuiop987abcd', userPoolDomain: 'my-cognito-domain.auth.us-east-1.amazoncognito.com', cookieExpirationDays: 365, + disableCookieDomain: false, logLevel: 'error', }); }); @@ -97,6 +98,43 @@ describe('private functions', () => { expect(authenticator._getVerifiedToken).toHaveBeenCalled(); }); + test('should not return cookie domain', () => { + const authenticatorWithNoCookieDomain = 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, + disableCookieDomain: true, + logLevel: 'error', + }); + + const username = 'toto'; + const domain = 'example.com'; + const path = '/test'; + jest.spyOn(authenticatorWithNoCookieDomain, '_getVerifiedToken'); + authenticatorWithNoCookieDomain._getVerifiedToken.mockReturnValueOnce({ token_use: 'id', 'cognito:username': username }); + + const response = authenticatorWithNoCookieDomain._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}; 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._getVerifiedToken).toHaveBeenCalled(); + }); + test('should getIdTokenFromCookie', () => { const appClientName = 'toto,./;;..-_lol123'; expect( @@ -124,6 +162,7 @@ describe('createAuthenticator', () => { userPoolAppId: '123456789qwertyuiop987abcd', userPoolDomain: 'my-cognito-domain.auth.us-east-1.amazoncognito.com', cookieExpirationDays: 365, + disableCookieDomain: true }; }); @@ -136,6 +175,11 @@ describe('createAuthenticator', () => { expect(typeof new Authenticator(params)).toBe('object'); }); + test('should create authenticator without disableCookieDomain', () => { + delete params.disableCookieDomain; + 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'); @@ -185,6 +229,11 @@ describe('createAuthenticator', () => { params.cookieExpirationDays = '123'; expect(() => new Authenticator(params)).toThrow('cookieExpirationDays'); }); + + test('should fail when creating authenticator with invalid disableCookieDomain', () => { + params.disableCookieDomain = '123'; + expect(() => new Authenticator(params)).toThrow('disableCookieDomain'); + }); }); describe('handle', () => { diff --git a/index.js b/index.js index 3f99c5b..970661a 100644 --- a/index.js +++ b/index.js @@ -14,7 +14,7 @@ class Authenticator { this._userPoolAppSecret = params.userPoolAppSecret; this._userPoolDomain = params.userPoolDomain; this._cookieExpirationDays = params.cookieExpirationDays || 365; - + this._disableCookieDomain = ('disableCookieDomain' in params && params.disableCookieDomain === true) ? true : false; this._issuer = `https://cognito-idp.${params.region}.amazonaws.com/${params.userPoolId}`; this._cookieBase = `CognitoIdentityServiceProvider.${params.userPoolAppId}`; this._logger = pino({ @@ -40,6 +40,9 @@ class Authenticator { if (params.cookieExpirationDays && typeof params.cookieExpirationDays !== 'number') { throw new Error('Expected params.cookieExpirationDays to be a number'); } + if ('disableCookieDomain' in params && typeof params.disableCookieDomain !== 'boolean') { + throw new Error('Expected params.disableCookieDomain to be a boolean'); + } } /** @@ -120,8 +123,9 @@ class Authenticator { 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 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' , headers: {