Skip to content

Instantly share code, notes, and snippets.

@malliapi
Created October 15, 2020 15:05
Show Gist options
  • Save malliapi/07eacdeb2869e71198bc2c5fe8ee0045 to your computer and use it in GitHub Desktop.
Save malliapi/07eacdeb2869e71198bc2c5fe8ee0045 to your computer and use it in GitHub Desktop.
Auth0 Authentication
const { WebAuth } = require('auth0-js');
const url = require('url');
const qs = require('qs');
const exenv = require('exenv');
const config = require('../../config/config.shared');
const helpers = require('./helpers');
const createSession = market => {
const {
auth0: { audience, domain, clientID, responseType, redirectUri },
} = config;
return new WebAuth({
audience,
domain,
clientID,
responseType,
redirectUri: `${redirectUri}/${market}/${config.pageUrls.authenticationCallback}`,
});
};
const authorise = ({ session, locale, mode }, conf = config) => {
const {
auth0: { connection },
} = conf;
helpers.setCookie(
config.continueAsGuestCookie.name,
qs.parse(url.parse(window.location.href).query),
config.continueAsGuestCookie.options
);
const returnUrl = helpers.getNewPageUrl(window.location.href, conf.pageUrls.checkout);
session.authorize({ ui_locales: locale, mode, connection, state: returnUrl });
};
const login = auth0Config => authorise({ ...auth0Config, mode: 'booking' });
const getStateFromUrlHash = () => {
try {
const parsedHash = qs.parse(url.parse(window.location.href).hash);
return decodeURIComponent(parsedHash.state);
} catch (err) {
return false;
}
};
const handleAuthentication = (session, conf = config) => {
return new Promise((resolve, reject) => {
session.parseHash((err, authResult) => {
if (err || !authResult || !authResult.accessToken || !authResult.idToken || !authResult.idTokenPayload) {
// eslint-disable-next-line prefer-promise-reject-errors
return reject({ ...err, redirectUri: getStateFromUrlHash() });
}
const {
auth0: { nameSpace },
} = conf;
return resolve({ ...authResult, redirectUri: authResult.state, nameSpace });
});
});
};
const checkSession = (session, conf = config) => {
return new Promise((resolve, reject) => {
session.checkSession({}, (err, authResult) => {
if (
err ||
!authResult ||
!authResult.accessToken ||
!authResult.idToken ||
!authResult.idTokenPayload ||
!authResult.state
) {
return reject(err);
}
const {
auth0: { nameSpace },
} = conf;
return resolve({ ...authResult, nameSpace });
});
});
};
const getAccountCookieDetailsFromAuth0UserInfo = userInfo => {
const {
auth0: { esUserIdNameSpace },
} = config;
const {
[userInfo.nameSpace]: { accessToken, expiresAt, refreshToken, emailHash },
} = userInfo.idTokenPayload;
const cid = userInfo.idTokenPayload[esUserIdNameSpace];
return {
accessToken,
cid,
expiresAt,
refreshToken,
emailHash,
};
};
const logout = ({ session }) => {
if (!exenv.canUseDOM) {
return;
}
const {
auth0: { clientID },
} = config;
session.logout({ clientID, returnTo: window.location.href });
};
module.exports = {
getAccountCookieDetailsFromAuth0UserInfo,
checkSession,
handleAuthentication,
getStateFromUrlHash,
login,
authorise,
createSession,
logout,
};
const expect = require('chai').expect;
const sinon = require('sinon');
const auth0 = require('./auth0');
const conf = require('../../config/config.shared');
describe('auth0', function testAuth0() {
describe('authorise', () => {
beforeEach(() => {
this.setItemSpy = sinon.spy();
this.auth0AuthoriseSpy = sinon.spy();
this.config = {
auth0: {
connection: 'authConnection',
},
pageUrls: {
checkout: 'checkout',
},
};
this.auth0Config = {
session: {
authorize: this.auth0AuthoriseSpy,
},
locale: 'en',
mode: 'authMode',
};
global.window = {
location: {
href: 'http://localhost:3000/uk-en/login',
},
};
});
it('should call Auth0 authorize method with configured options', () => {
auth0.authorise(this.auth0Config, this.config);
expect(
this.auth0AuthoriseSpy.calledWith({
ui_locales: this.auth0Config.locale,
mode: this.auth0Config.mode,
connection: this.config.auth0.connection,
state: 'http://localhost:3000/uk-en/checkout',
})
).to.equal(true);
});
});
describe('login', () => {
beforeEach(() => {
this.authoriseSpy = sinon.stub();
this.auth0Config = {
session: {
authorize: this.authoriseSpy,
},
locale: 'en',
mode: 'booking',
};
});
it('should call authorise with passed auth0 config', () => {
auth0.login(this.auth0Config);
expect(this.authoriseSpy.firstCall.args[0]).to.include({ mode: this.auth0Config.mode });
});
});
describe('handleAuthentication', () => {
beforeEach(() => {
this.redirectUri = 'http://localhost:3000/uk-en/checkout';
global.window = {
location: {
href:
'https://localhost:3000/rw-en/auth/callback#access_token=kaEWDe-GM2ddXALwGMefpxp1VaZsOlnm&expires_in=7200&token_type=Bearer&state=http%3A%2F%2Flocalhost%3A3000%2Fuk-en%2Fcheckout',
},
};
});
it('should call Auth0 parseHash method to extract the authentication result', () => {
const parseHashSpy = sinon.spy();
auth0.handleAuthentication({ parseHash: parseHashSpy });
expect(parseHashSpy.calledOnce).to.equal(true);
});
it('should return rejected promise with the authentication result error', async () => {
const error = { message: 'Error text' };
const parseHashStub = sinon.stub().callsFake(cb => cb(error));
try {
await auth0.handleAuthentication({ parseHash: parseHashStub });
} catch (e) {
expect(e).to.include(error);
}
});
it('should return rejected promise if there are no authentication result', async () => {
const authResult = null;
const parseHashStub = sinon.stub().callsFake(cb => cb(null, authResult));
try {
await auth0.handleAuthentication({ parseHash: parseHashStub });
} catch (e) {
expect(e).to.not.equal(null);
}
});
it('should return redirectUri in rejected promise result', async () => {
const authResult = null;
const parseHashStub = sinon.stub().callsFake(cb => cb(null, authResult));
try {
await auth0.handleAuthentication({ parseHash: parseHashStub });
} catch (e) {
expect(e).to.include({ redirectUri: this.redirectUri });
}
});
it('should return authResult in resolved promise', async () => {
const authResult = {
accessToken: 'accessToken',
idToken: 'idToken',
idTokenPayload: 'idTokenPayload',
state: this.redirectUri,
};
const nameSpace = 'nameSpace';
const config = {
auth0: {
nameSpace,
},
};
const parseHashStub = sinon.stub().callsFake(cb => cb(null, authResult));
const result = await auth0.handleAuthentication({ parseHash: parseHashStub }, config);
expect(result).to.deep.equal({ nameSpace, redirectUri: this.redirectUri, ...authResult });
});
});
describe('checkSession', () => {
it('should call Auth0 checkSession method to acquire a new token', () => {
const checkSessionSpy = sinon.spy();
auth0.checkSession({ checkSession: checkSessionSpy });
expect(checkSessionSpy.calledOnce).to.equal(true);
});
it('should return rejected promise with the checkout error', async () => {
const error = { message: 'Error text' };
const checkSessionStub = sinon.stub().callsFake((options, cb) => cb(error));
try {
await auth0.checkSession({ checkSession: checkSessionStub });
} catch (e) {
expect(e).to.include(error);
}
});
it('should return authResult in resolved promise', async () => {
const authResult = {
accessToken: 'accessToken',
idToken: 'idToken',
idTokenPayload: 'idTokenPayload',
state: 'state',
};
const nameSpace = 'nameSpace';
const config = {
auth0: {
nameSpace,
},
};
const checkSessionStub = sinon.stub().callsFake((options, cb) => cb(null, authResult));
const result = await auth0.checkSession({ checkSession: checkSessionStub }, config);
expect(result).to.deep.equal({ nameSpace, ...authResult });
});
});
describe('getAccountCookieDetailsFromAuth0UserInfo', () => {
it('should parse userInfo object', () => {
const nameSpace = 'nameSpace';
const { esUserIdNameSpace } = conf.auth0;
const nameSpacePayload = {
accessToken: 'accessToken',
expiresAt: 'expiresAt',
refreshToken: 'refreshToken',
emailHash: 'emailHash',
};
const cid = 'clientCid';
const userInfo = {
nameSpace,
idTokenPayload: {
[nameSpace]: nameSpacePayload,
[esUserIdNameSpace]: cid,
},
};
const result = auth0.getAccountCookieDetailsFromAuth0UserInfo(userInfo);
expect(result).to.deep.equal({ cid, ...nameSpacePayload });
});
});
});
import { useEffect, useCallback } from 'react';
import { checkSession, createSession, getAccountCookieDetailsFromAuth0UserInfo } from '../actions/auth0';
import { setCookie } from '../actions/helpers';
import config from '../../config/config.shared';
function useAuth0(isAuth0LoginInPathEnabled, storeAccount, loadAccountIfNeeded, logOutClear, market, screen) {
const getAuth0Session = useCallback(() => (isAuth0LoginInPathEnabled ? createSession(market) : null), []);
const auth0Session = getAuth0Session();
useEffect(() => {
const checkAuth0Session = async () => {
try {
const userInfo = await checkSession(auth0Session);
const accountCookie = getAccountCookieDetailsFromAuth0UserInfo(userInfo);
const accountCookieOptions = { ...config.accountCookie.options, expiresAt: accountCookie.expiresAt };
setCookie(config.accountCookie.name, accountCookie, accountCookieOptions);
storeAccount({ id: accountCookie.cid });
loadAccountIfNeeded();
} catch (err) {
logOutClear();
}
};
if (isAuth0LoginInPathEnabled) {
checkAuth0Session();
}
}, [screen]);
return {
auth0Session,
};
}
export default useAuth0;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment