Skip to content
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
12 changes: 0 additions & 12 deletions .babelrc

This file was deleted.

22 changes: 22 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
esmodules: false
Comment on lines +6 to +7

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security & Maintenance Risk: No explicit Node.js target specified

By only setting esmodules: false without specifying a concrete Node.js version target, the transpilation behavior becomes less predictable and depends on Babel's default browserslist configuration. This can lead to:

  1. Security risk: No explicit lower bound means the code may not transpile newer syntax that could cause runtime errors on older environments
  2. Inconsistent builds: Different developers/CI environments might produce different output depending on their browserslist database version

While I understand from the review comments that you're keeping this approach to maintain ES2017 compatibility for the es-check validation, I recommend:

  1. Either add an explicit node: '8.0' target (as discussed in comments) OR
  2. Update the project to drop Node 8 support and set a modern target like node: '14', then update the es-check script accordingly

The current configuration creates an implicit dependency on browserslist defaults which reduces build reproducibility.

},
useBuiltIns: 'entry',
corejs: '3.26.1'
}
],
'@babel/preset-react'
],
plugins: [
'@babel/plugin-proposal-function-bind',
"babel-plugin-stylus-compiler",
"version-inline",
"transform-css-import-to-string",
'@babel/plugin-proposal-object-rest-spread'
]
};
6,885 changes: 4,253 additions & 2,632 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 10 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@
"i18n:prettier": "prettier --write src/i18n/*",
"i18n:validate": "node -r esm scripts/lang-audit.js"
},
"overrides": {
"jest-environment-jsdom": "^30.2.0",
Comment on lines +42 to +43

This comment was marked as outdated.

"enzyme": {
"cheerio": "0.22.0"
}
Comment on lines +44 to +46

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pinning Cheerio Version: Why 0.22.0?

Cheerio 0.22.0 was released in 2017 and has known security vulnerabilities. Pinning to this specific old version raises concerns:

  1. Security risk: Very old version likely contains unpatched vulnerabilities
  2. Why this version?: The comment mentions "maintain compatibility" but doesn't specify what breaks with newer versions
  3. Enzyme limitation: If this is required for [email protected] compatibility, it indicates enzyme itself may be outdated

Recommendation:

  1. Document in a comment WHY this specific version is required
  2. Investigate upgrading to React Testing Library instead of Enzyme (Enzyme is no longer actively maintained)
  3. If keeping Enzyme, verify if newer cheerio versions work with enzyme 3.11.0

},
"devDependencies": {
"@auth0/component-cdn-uploader": "^2.2.2",
"@babel/core": "^7.0.0",
Expand All @@ -54,6 +60,7 @@
"@babel/plugin-proposal-logical-assignment-operators": "^7.0.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
"@babel/plugin-proposal-numeric-separator": "^7.0.0",
"@babel/plugin-proposal-object-rest-spread": "^7.20.7",
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
"@babel/plugin-proposal-pipeline-operator": "^7.0.0",
"@babel/plugin-proposal-throw-expressions": "^7.0.0",
Expand All @@ -75,7 +82,7 @@
"cross-env": "^7.0.3",
"css-loader": "^0.28.11",
"emojic": "^1.1.17",
"enzyme": "^3.1.1",
"enzyme": "^3.11.0",
"es-check": "^6.0.0",
"eslint": "^8.45.0",
"eslint-config-prettier": "^8.8.0",
Expand All @@ -94,7 +101,7 @@
"grunt-webpack": "^5.0.0",
"husky": "^7.0.2",
"jest": "^29.3.1",
"jest-environment-jsdom": "^29.3.1",
"jest-environment-jsdom": "^30.2.0",
"jest-environment-jsdom-global": "^4.0.0",
"karma": "^6.4.1",
"karma-babel-preprocessor": "^8.0.2",
Expand All @@ -119,7 +126,7 @@
"webpack-dev-server": "^4.11.1"
},
"dependencies": {
"auth0-js": "^9.27.0",
"auth0-js": "^9.29.0",
"auth0-password-policies": "^1.0.2",
"blueimp-md5": "^2.19.0",
"classnames": "^2.3.2",
Expand Down
40 changes: 22 additions & 18 deletions src/__tests__/core/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,41 @@ const setResolvedConnection = (...params) => require('core/index').setResolvedCo
const setup = (...params) => require('core/index').setup(...params);

const mockLock = 'm';
let mockSet;
let mockInit;

jest.mock('i18n', () => ({
initI18n: jest.fn(),
html: (...keys) => keys.join()
}));

jest.mock('utils/data_utils', () => ({
dataFns: () => ({
get: jest.fn(),
set: mockSet,
init: mockInit
})
}));

jest.mock('utils/data_utils', () => {
const mockSet = jest.fn();
const mockInit = jest.fn();

return {
mockSet,
mockInit,
dataFns: () => ({
get: jest.fn(),
set: mockSet,
init: mockInit
})
};
});

const { mockSet, mockInit } = require('utils/data_utils');

describe('setup', () => {
beforeEach(() => {
mockInit = jest.fn();
jest.resetModules();
jest.clearAllMocks();
});

it('default redirectUrl should not include location.hash', () => {
setURL('https://test.com/path/#not-this-part');
const options = {};
setup('id', 'clientID', 'domain', options, 'hookRunner', 'emitEventFn');
const { mock } = mockInit;
expect(mock.calls.length).toBe(1);
const model = mock.calls[0][1].toJS();
expect(mockInit.mock.calls.length).toBe(1);
const model = mockInit.mock.calls[0][1].toJS();
expect(model.auth.redirectUrl).toBe('https://test.com/path/');
});

Expand All @@ -47,9 +53,8 @@ describe('setup', () => {

const options = {};
setup('id', 'clientID', 'domain', options, 'hookRunner', 'emitEventFn');
const { mock } = mockInit;
expect(mock.calls.length).toBe(1);
const model = mock.calls[0][1].toJS();
expect(mockInit.mock.calls.length).toBe(1);
const model = mockInit.mock.calls[0][1].toJS();
expect(model.auth.redirectUrl).toBe('https://test.com/path/');
});

Expand Down Expand Up @@ -249,7 +254,6 @@ describe('setup', () => {

describe('setResolvedConnection', () => {
beforeEach(() => {
mockSet = jest.fn();
jest.resetModules();
});
it('sets undefined when is called with undefined', () => {
Expand Down
98 changes: 66 additions & 32 deletions src/__tests__/core/web_api.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,38 @@
import Auth0WebApi from '../../core/web_api';
import { JSDOM } from 'jsdom';

jest.mock('../../core/web_api/p2_api', () => {
return jest.fn().mockImplementation((lockID, clientID, domain, opts) => {
const redirect = Boolean(opts.redirect);
return {
authOpt: {
popup: typeof opts.popup !== 'undefined' ? opts.popup : !redirect,
sso: opts.sso,
redirect: opts.redirect,
},
lockID: lockID,
clientID: clientID,
domain: domain,
logIn: jest.fn(),
logout: jest.fn(),
signUp: jest.fn(),
resetPassword: jest.fn(),
passwordlessStart: jest.fn(),
passwordlessVerify: jest.fn(),
parseHash: jest.fn(),
getUserInfo: jest.fn(),
getProfile: jest.fn(),
getChallenge: jest.fn(),
getSignupChallenge: jest.fn(),
getPasswordlessChallenge: jest.fn(),
getPasswordResetChallenge: jest.fn(),
getSSOData: jest.fn(),
getUserCountry: jest.fn(),
checkSession: jest.fn(),
isUniversalLogin: lockID === 'lock-id' && domain === 'test.com', // Simplified condition for testing
};
});
});

describe('Auth0WebApi', () => {
let originalWindow;
Expand All @@ -9,64 +43,64 @@ describe('Auth0WebApi', () => {
const client = () => Auth0WebApi.clients[LOCK_ID];

beforeEach(() => {
originalWindow = window.window;
originalWindow = global.window;
});

afterEach(() => {
window.window = originalWindow;
global.window = originalWindow;
delete window.cordova;

This comment was marked as outdated.

delete window.electron;
Auth0WebApi.clients = {};
});

const setWindowLocation = (url) => {
const dom = new JSDOM('', { url });
global.window = dom.window;
global.document = dom.window.document;
};
Comment on lines +56 to +60

This comment was marked as outdated.


describe('setupClient', () => {
it('sets the correct options when is on the hosted login page', () => {
delete window.location;
window.location = { ...originalWindow.location, host: DEFAULT_DOMAIN, search: '' };
Auth0WebApi.setupClient(LOCK_ID, CLIENT_ID, DEFAULT_DOMAIN, { redirect: true });
it('sets the correct options when on the hosted login page', () => {
setWindowLocation(`https://${DEFAULT_DOMAIN}/`);

expect(client()).toEqual(
expect.objectContaining({
isUniversalLogin: true,
domain: DEFAULT_DOMAIN,
authOpt: {
popup: false
}
})
);
});
Auth0WebApi.setupClient(LOCK_ID, CLIENT_ID, DEFAULT_DOMAIN, { redirect: true });

it('sets redirect: true when on the same origin as the specified domain', () => {
delete window.location;
window.location = { ...originalWindow.location, host: DEFAULT_DOMAIN, search: '' };
expect(client()).toMatchObject({
domain: DEFAULT_DOMAIN,
authOpt: expect.objectContaining({
popup: false,
}),
});

Auth0WebApi.setupClient(LOCK_ID, CLIENT_ID, DEFAULT_DOMAIN, {});
expect(client().authOpt.popup).toBe(false);
expect(client().isUniversalLogin).toBe(true);
});

it('sets redirect: false when on a different origin as the specified domain', () => {
delete window.location;
window.location = { ...originalWindow.location, host: 'test-other.com', search: '' };

setWindowLocation('https://different-origin.com/');
Auth0WebApi.setupClient(LOCK_ID, CLIENT_ID, DEFAULT_DOMAIN, {});

expect(client().authOpt.popup).toBe(true);
});

it('forces popup and sso mode for cordova, only when not running in the hosted environment', () => {
delete window.location;
window.location = { ...originalWindow.location, host: DEFAULT_DOMAIN, search: '' };
setWindowLocation(`https://${DEFAULT_DOMAIN}/`);
window.cordova = true;

Auth0WebApi.setupClient(LOCK_ID, CLIENT_ID, DEFAULT_DOMAIN, {});
expect(client().authOpt.popup).toBe(false);
expect(client().authOpt.sso).toBeUndefined();

expect(client().authOpt.popup).toBe(true);
expect(client().authOpt.sso).toBe(false);
});

it('forces popup and sso mode for electron, only when not running in the hosted environment', () => {
delete window.location;
window.location = { ...originalWindow.location, host: DEFAULT_DOMAIN, search: '' };
setWindowLocation(`https://${DEFAULT_DOMAIN}/`);
window.electron = true;

Auth0WebApi.setupClient(LOCK_ID, CLIENT_ID, DEFAULT_DOMAIN, {});
expect(client().authOpt.popup).toBe(false);
expect(client().authOpt.sso).toBeUndefined();

expect(client().authOpt.popup).toBe(true);
expect(client().authOpt.sso).toBe(false);
});

});
});
11 changes: 7 additions & 4 deletions src/__tests__/engine/classic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ describe('Classic Engine', () => {
});
test('willShow calls `resolveAdditionalSignUpFields`', () => {
const classic = getClassic();
const model = classic.willShow(model, {});
expect(model.resolveAdditionalSignUpFields).toBe(true);
const inputModel = {};
const resultModel = classic.willShow(inputModel, {});
expect(resultModel.resolveAdditionalSignUpFields).toBe(true);

});
test('willShow calls `overrideDatabaseOptions`', () => {
const classic = getClassic();
const model = classic.willShow(model, {});
expect(model.overrideDatabaseOptions).toBe(true);
const inputModel = {};
const resultModel = classic.willShow(inputModel, {});
expect(resultModel.overrideDatabaseOptions).toBe(true);
});
});
11 changes: 9 additions & 2 deletions src/__tests__/i18n.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,15 @@ describe('i18n', () => {
};
const m = Immutable.fromJS({ i18n: { strings } });
const html = i18n.html(m, 'test');
expect(html.props.dangerouslySetInnerHTML.__html).not.toMatch(/javascript:alert/);
expect(html.props.dangerouslySetInnerHTML.__html).toEqual('<img href="1" src="1">');

const sanitized = html.props.dangerouslySetInnerHTML.__html;

// Security check
expect(sanitized).not.toMatch(/javascript:alert/);

// Attribute presence (order-independent)
expect(sanitized).toMatch(/<img[^>]+src="1"/);
expect(sanitized).toMatch(/<img[^>]+href="1"/);
});

it('should allow target=_blank with noopener noreferrer attributes', () => {
Expand Down
11 changes: 11 additions & 0 deletions src/__tests__/setup-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@
import { configure } from 'enzyme';
import Adapter from '@cfaester/enzyme-adapter-react-18';

import { TextEncoder, TextDecoder } from 'util';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Node.js Specific Import in Browser Test Environment

Importing TextEncoder and TextDecoder from Node.js's util module in a browser-focused test environment (jsdom) is a workaround for missing globals.

Concerns:

  1. Semantic mismatch: You're polyfilling browser APIs with Node.js implementations
  2. Behavior differences: Node.js TextEncoder/TextDecoder may have subtle behavioral differences from browser implementations
  3. Better alternatives: Consider using a proper polyfill package like text-encoding or fast-text-encoding that provides browser-compliant implementations

Recommendation:

// Instead of Node.js util, use a proper polyfill:
import { TextEncoder, TextDecoder } from 'text-encoding';

However, if this is working in practice and jest-environment-jsdom 30.x requires it, this is acceptable with a comment explaining why.


if (typeof global.TextEncoder === 'undefined') {

This comment was marked as outdated.

global.TextEncoder = TextEncoder;
}

if (typeof global.TextDecoder === 'undefined') {
global.TextDecoder = TextDecoder;
}


configure({ adapter: new Adapter() });

//jest polyfills
Expand Down
4 changes: 0 additions & 4 deletions src/i18n/.babelrc

This file was deleted.

2 changes: 1 addition & 1 deletion webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ module.exports = {
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: path.join(__dirname, 'node_modules')
exclude: /node_modules\/(?!(auth0-password-policies)\/)/
},
{
test: /\.styl$/,
Expand Down