@@ -0,0 +1,106 @@
import React from 'react';
import InputWrap from './input_wrap';

const inputIconSVG =
'<svg class="auth0-lock-icon auth0-lock-icon-box" width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="icon/key"><path id="Shape" fill-rule="evenodd" clip-rule="evenodd" d="M16.3884 11.0145C14.4508 12.9522 11.3948 13.0447 9.32094 11.3401L7.20872 13.2642L9.60904 15.6697L8.83354 16.4466L6.39631 14.0048L4.88251 15.3832L7.39496 17.89L6.61586 18.6666L3.5 15.5587L3.53914 15.5198C3.47446 15.3363 3.51554 15.1287 3.67514 14.983L8.54211 10.5496C6.89425 8.47691 7.005 5.46384 8.92209 3.54636C10.9841 1.48455 14.3267 1.48455 16.3884 3.54636C18.4501 5.60846 18.4501 8.95239 16.3884 11.0145ZM15.6048 4.33856C13.98 2.71309 11.3454 2.71309 9.72062 4.33856C8.0958 5.9632 8.0958 8.59793 9.72062 10.2226C11.3454 11.8478 13.98 11.8478 15.6048 10.2226C17.2297 8.59793 17.2297 5.96292 15.6048 4.33856Z" fill="#888888"/></g></svg>';

const refreshIconSVG = `<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M17.5496 8.77262C17.007 5.01122 13.8021 2.11002 9.88989 2.11002C6.79596 2.11002 4.14611 3.93257 2.896 6.55065H7.11154C7.41852 6.55065 7.66749 6.79935 7.66749 7.10579C7.66749 7.41251 7.41879 7.66205 7.11154 7.66205H2.47938H1.30282C1.30781 7.64318 1.3092 7.62291 1.3142 7.60404C1.23426 7.56296 1.16987 7.50217 1.11824 7.42889C1.10325 7.40918 1.09048 7.39086 1.07827 7.36977C1.04746 7.3112 1.02998 7.24791 1.02193 7.17935C1.01776 7.15326 1 7.133 1 7.10552C1 7.08997 1.00722 7.0772 1.00888 7.06166V1.5582C1.00888 1.25176 1.25757 1.0025 1.56427 1.0025C1.87125 1.0025 2.11994 1.25148 2.11994 1.5582V5.61994C3.6293 2.87224 6.53311 1 9.88989 1C14.4182 1 18.1481 4.39195 18.6951 8.77235H17.5496V8.77262ZM9.88989 17.655C12.9841 17.655 15.6337 15.833 16.8841 13.2152H12.6685C12.3615 13.2152 12.1128 12.967 12.1128 12.6595C12.1128 12.3525 12.3615 12.1043 12.6685 12.1043H17.3004C17.3004 12.1041 17.3007 12.1035 17.3007 12.1032H18.4775C18.4725 12.1221 18.4709 12.1424 18.4659 12.161C18.5461 12.2021 18.6105 12.2631 18.6621 12.3367C18.6774 12.3564 18.6893 12.3744 18.7018 12.3955C18.7326 12.4541 18.7504 12.5174 18.7584 12.5859C18.7626 12.612 18.7803 12.632 18.7803 12.6598C18.7803 12.675 18.7728 12.6881 18.7717 12.7031V18.2076C18.7717 18.5144 18.523 18.7628 18.2161 18.7628C17.9091 18.7628 17.6604 18.5144 17.6604 18.2076V14.1451C16.151 16.8928 13.2469 18.765 9.89017 18.765C5.36209 18.765 1.63255 15.3739 1.08493 10.9935H2.23041C2.77331 14.7549 5.97799 17.655 9.88989 17.655Z" fill="black"/><mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="1" y="1" width="18" height="18"><path fill-rule="evenodd" clip-rule="evenodd" d="M17.5496 8.77262C17.007 5.01122 13.8021 2.11002 9.88989 2.11002C6.79596 2.11002 4.14611 3.93257 2.896 6.55065H7.11154C7.41852 6.55065 7.66749 6.79935 7.66749 7.10579C7.66749 7.41251 7.41879 7.66205 7.11154 7.66205H2.47938H1.30282C1.30781 7.64318 1.3092 7.62291 1.3142 7.60404C1.23426 7.56296 1.16987 7.50217 1.11824 7.42889C1.10325 7.40918 1.09048 7.39086 1.07827 7.36977C1.04746 7.3112 1.02998 7.24791 1.02193 7.17935C1.01776 7.15326 1 7.133 1 7.10552C1 7.08997 1.00722 7.0772 1.00888 7.06166V1.5582C1.00888 1.25176 1.25757 1.0025 1.56427 1.0025C1.87125 1.0025 2.11994 1.25148 2.11994 1.5582V5.61994C3.6293 2.87224 6.53311 1 9.88989 1C14.4182 1 18.1481 4.39195 18.6951 8.77235H17.5496V8.77262ZM9.88989 17.655C12.9841 17.655 15.6337 15.833 16.8841 13.2152H12.6685C12.3615 13.2152 12.1128 12.967 12.1128 12.6595C12.1128 12.3525 12.3615 12.1043 12.6685 12.1043H17.3004C17.3004 12.1041 17.3007 12.1035 17.3007 12.1032H18.4775C18.4725 12.1221 18.4709 12.1424 18.4659 12.161C18.5461 12.2021 18.6105 12.2631 18.6621 12.3367C18.6774 12.3564 18.6893 12.3744 18.7018 12.3955C18.7326 12.4541 18.7504 12.5174 18.7584 12.5859C18.7626 12.612 18.7803 12.632 18.7803 12.6598C18.7803 12.675 18.7728 12.6881 18.7717 12.7031V18.2076C18.7717 18.5144 18.523 18.7628 18.2161 18.7628C17.9091 18.7628 17.6604 18.5144 17.6604 18.2076V14.1451C16.151 16.8928 13.2469 18.765 9.89017 18.765C5.36209 18.765 1.63255 15.3739 1.08493 10.9935H2.23041C2.77331 14.7549 5.97799 17.655 9.88989 17.655Z" fill="white"/></mask><g mask="url(#mask0)"></g></svg>`;

export default class CaptchaInput extends React.Component {
constructor(props) {
super(props);
this.state = {};
}

shouldComponentUpdate(nextProps, nextState) {
const { isValid, value, image, placeholder } = this.props;
const { focused } = this.state;

return (
isValid != nextProps.isValid ||
value != nextProps.value ||
focused != nextState.focused ||
image != nextState.image ||
placeholder != nextState.placeholder
);
}

render() {
const {
lockId,
image,
value,
placeholder,
onReload,
invalidHint,
isValid,
...props
} = this.props;
const { focused } = this.state;

return (
<div>
<div className="auth0-lock-captcha">
<div
className="auth0-lock-captcha-image"
style={{ backgroundImage: `url(${image})` }}
></div>
<button
type="button"
onClick={::this.handleReload}
className="auth0-lock-captcha-refresh"
dangerouslySetInnerHTML={{ __html: refreshIconSVG }}
></button>
</div>
<InputWrap
focused={focused}
invalidHint={invalidHint}
isValid={isValid}
name="captcha"
icon={inputIconSVG}
>
<input
id={`${lockId}-captcha`}
ref="input"
type="text"
inputMode="text"
name="captcha"
className="auth0-lock-input"
placeholder={placeholder}
autoComplete="off"
autoCapitalize="off"
onChange={::this.handleOnChange}
onFocus={::this.handleFocus}
onBlur={::this.handleBlur}
aria-label="Email"
aria-invalid={!isValid}
aria-describedby={!isValid && invalidHint ? `auth0-lock-error-msg-email` : undefined}
value={value}
{...props}
/>
</InputWrap>
</div>
);
}

handleOnChange(e) {
if (this.props.onChange) {
this.props.onChange(e);
}
}

handleReload(e) {
if (this.props.onReload) {
e.preventDefault();
this.props.onReload(e);
}
}

handleFocus() {
this.setState({ focused: true });
}

handleBlur() {
this.setState({ focused: false });
}
}
@@ -0,0 +1,87 @@
import expect from 'expect.js';
import * as h from './helper/ui';

const lockOpts = {
allowedConnections: ['db'],
rememberLastLogin: false
};

const requiredResponse1 = {
required: true,
image: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
type: 'code'
};

const requiredResponse2 = {
required: true,
image: 'data:image/gif;base64,blibli',
type: 'code'
};

describe('captcha', function() {
before(h.stubWebApis);
after(h.restoreWebApis);

describe('when the api returns a new challenge', function() {
beforeEach(function(done) {
this.stub = h.stubGetChallenge([requiredResponse1, requiredResponse2]);
this.lock = h.displayLock('', lockOpts, done);
});

afterEach(function() {
this.lock.hide();
});

it('should show the captcha input', function() {
expect(h.qInput(this.lock, 'captcha', false)).to.be.ok();
});

it('should require another challenge when clicking the refresh button', function(done) {
h.clickRefreshCaptchaButton(this.lock);
setTimeout(() => {
expect(h.q(this.lock, '.auth0-lock-captcha-image').style.backgroundImage)
.to.equal(`url("${requiredResponse2.image}")`);
done();
}, 200);
});

it('should submit the captcha provided by the user', function() {
h.logInWithEmailPasswordAndCaptcha(this.lock);
expect(h.wasLoginAttemptedWith({ captcha: 'captchaValue' })).to.be.ok();
});
});

describe('when the challenge api returns required: false', function() {
beforeEach(function(done) {
h.stubGetChallenge({
required: false
});
this.lock = h.displayLock('', lockOpts, done);
});

afterEach(function() {
this.lock.hide();
});

it('should not show the captcha input', function() {
expect(h.qInput(this.lock, 'captcha', false)).to.not.be.ok();
});

describe('when the form submission fails and the transaction starts requiring a challenge', function() {
beforeEach(function(done) {
h.assertAuthorizeRedirection((lockID, options, authParams, cb) => {
cb(new Error('bad request'));
setTimeout(done, 300);
});
h.stubGetChallenge(requiredResponse1);
h.fillEmailInput(this.lock, 'someone@example.com');
h.fillPasswordInput(this.lock, 'mypass');
h.submitForm(this.lock);
});

it('should call the challenge api again and show the input', function() {
expect(h.qInput(this.lock, 'captcha', false)).to.be.ok();
});
});
});
});
@@ -25,6 +25,7 @@ export const stubWebApis = () => {
stub(SSOData, 'fetchSSOData', (id, adInfo, cb) => {
cb(null, ssoData);
});
stubGetChallenge();
};

export const stubWebApisForKerberos = () => {
@@ -49,6 +50,7 @@ export const assertAuthorizeRedirection = cb => {

export const restoreWebApis = () => {
webApi.logIn.restore();
webApi.getChallenge.restore();
gravatarProvider.displayName.restore();
gravatarProvider.url.restore();
ClientSettings.fetchClientSettings.restore();
@@ -103,7 +105,7 @@ export const displayLock = (name, opts = {}, done = () => {}, show_ops = {}) =>

// queries

const q = (lock, query, all = false) => {
export const q = (lock, query, all = false) => {
query = `#auth0-lock-container-${lock.id} ${query}`;
const method = all ? 'querySelectorAll' : 'querySelector';
return global.document[method](query);
@@ -217,6 +219,10 @@ const clickFn = (lock, query) => click(lock, query);
export const clickTermsCheckbox = checkFn(
".auth0-lock-sign-up-terms-agreement label input[type='checkbox']"
);

export const clickRefreshCaptchaButton = (lock, connection) =>
clickFn(lock, `.auth0-lock-captcha-refresh`);

export const clickSocialConnectionButton = (lock, connection) =>
clickFn(lock, `.auth0-lock-social-button[data-provider='${connection}']`);
const fillInput = (lock, name, str) => {
@@ -226,6 +232,7 @@ const fillInputFn = name => (lock, str) => fillInput(lock, name, str);

export const fillEmailInput = fillInputFn('email');
export const fillPasswordInput = fillInputFn('password');
export const fillCaptchaInput = fillInputFn('captcha');
export const fillUsernameInput = fillInputFn('username');
export const fillMFACodeInput = fillInputFn('mfa_code');

@@ -278,6 +285,13 @@ export const logInWithEmailAndPassword = lock => {
submit(lock);
};

export const logInWithEmailPasswordAndCaptcha = lock => {
fillEmailInput(lock, 'someone@example.com');
fillPasswordInput(lock, 'mypass');
fillCaptchaInput(lock, 'captchaValue');
submit(lock);
};

export const logInWithUsernameAndPassword = lock => {
fillUsernameInput(lock, 'someone');
fillPasswordInput(lock, 'mypass');
@@ -295,3 +309,15 @@ export const testAsync = (fn, done) => {
done(e);
}
};

export const stubGetChallenge = (result = { required: false }) => {
if (typeof webApi.getChallenge.restore === 'function') {
webApi.getChallenge.restore();
}
return stub(webApi, 'getChallenge', (lockID, callback) => {
if(Array.isArray(result)) {
return callback(null, result.shift());
}
callback(null, result);
});
};
@@ -609,18 +609,18 @@ atob@^2.1.1:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==

auth0-js@^9.11.2:
version "9.11.2"
resolved "https://registry.yarnpkg.com/auth0-js/-/auth0-js-9.11.2.tgz#8fa0324318ce3b427cfc1732a255c3049337092a"
integrity sha512-DP70OnHWapQ5gCwA1kgLpj9henNhwBZUKIR4Bi1pZcSD+1z5oosHvqNkRIreHaq4SGvZd9Mgus5WYsw8vTTMXg==
auth0-js@^9.12.0:
version "9.12.0"
resolved "https://registry.yarnpkg.com/auth0-js/-/auth0-js-9.12.0.tgz#6b8ac52767382366b1f81d85e394329174c54dc3"
integrity sha512-OnI04ISKF7SGOlP8MFqnVUNPwVaceynwkjA6f55z2CsZaUXynTTiTtGRhyU2c88kR4skPx1si0SKowzzy38+aw==
dependencies:
base64-js "^1.2.0"
idtoken-verifier "^1.4.1"
base64-js "^1.3.0"
idtoken-verifier "^2.0.0"
js-cookie "^2.2.0"
qs "^6.4.0"
qs "^6.7.0"
superagent "^3.8.3"
url-join "^4.0.0"
winchan "^0.2.1"
url-join "^4.0.1"
winchan "^0.2.2"

auth0-password-policies@^1.0.2:
version "1.0.2"
@@ -1539,11 +1539,16 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=

base64-js@^1.0.2, base64-js@^1.2.0:
base64-js@^1.0.2:
version "1.3.0"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3"
integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==

base64-js@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==

base@^0.11.1:
version "0.11.2"
resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
@@ -3616,7 +3621,7 @@ es-to-primitive@^1.2.0:
is-date-object "^1.0.1"
is-symbol "^1.0.2"

es6-promise@^4.0.3:
es6-promise@^4.0.3, es6-promise@^4.2.8:
version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
@@ -5268,17 +5273,17 @@ icss-replace-symbols@^1.1.0:
resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
integrity sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=

idtoken-verifier@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/idtoken-verifier/-/idtoken-verifier-1.4.1.tgz#032d7720fdd091830d22f694eb94b5f6644b0d3b"
integrity sha512-BoJc00Gj37hrNlx7NYmd8uJFvvC9/FiWDKugDluP4JmgOGT/AfNlPfnRmi9fHEEqSatnIIr3WTyf0dlhHfSHnA==
idtoken-verifier@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/idtoken-verifier/-/idtoken-verifier-2.0.0.tgz#3c4334b16ddce111eba9a2d5632ee3f7684525b7"
integrity sha512-yI6mETqFm3xAaNv8A0fazh4zlDEuM81sq1QbsfJENUEGJ3FRMGiwp//Lf4N/MD5gOlOTyNeCe7mAiaE3cU52rA==
dependencies:
base64-js "^1.2.0"
base64-js "^1.3.0"
crypto-js "^3.1.9-1"
jsbn "^0.1.0"
promise-polyfill "^8.1.3"
es6-promise "^4.2.8"
jsbn "^1.1.0"
unfetch "^4.1.0"
url-join "^1.1.0"
url-join "^4.0.1"

ieee754@1.1.8:
version "1.1.8"
@@ -6278,7 +6283,12 @@ js-yaml@~3.7.0:
argparse "^1.0.7"
esprima "^2.6.0"

jsbn@^0.1.0, jsbn@~0.1.0:
jsbn@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040"
integrity sha1-sBMHyym2GKHtJux56RH4A8TaAEA=

jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
@@ -8613,11 +8623,6 @@ progress@^2.0.0, progress@^2.0.1:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==

promise-polyfill@^8.1.3:
version "8.1.3"
resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.1.3.tgz#8c99b3cf53f3a91c68226ffde7bde81d7f904116"
integrity sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==

promise@^7.1.1:
version "7.3.1"
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
@@ -8754,7 +8759,7 @@ qs@2.4.2:
resolved "https://registry.yarnpkg.com/qs/-/qs-2.4.2.tgz#f7ce788e5777df0b5010da7f7c4e73ba32470f5a"
integrity sha1-9854jld33wtQENp/fE5zujJHD1o=

qs@6.7.0, qs@^6.4.0, qs@^6.5.1, qs@^6.7.0:
qs@6.7.0, qs@^6.5.1, qs@^6.7.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
@@ -10772,10 +10777,10 @@ url-join@^1.1.0:
resolved "https://registry.yarnpkg.com/url-join/-/url-join-1.1.0.tgz#741c6c2f4596c4830d6718460920d0c92202dc78"
integrity sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg=

url-join@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.0.tgz#4d3340e807d3773bda9991f8305acdcc2a665d2a"
integrity sha1-TTNA6AfTdzvamZH4MFrNzCpmXSo=
url-join@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7"
integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==

url-parse@^1.1.8, url-parse@^1.4.3:
version "1.4.7"
@@ -11162,10 +11167,10 @@ wide-align@^1.1.0:
dependencies:
string-width "^1.0.2 || 2"

winchan@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/winchan/-/winchan-0.2.1.tgz#19b334e49f7c07c0849f921f405fad87dfc8a1da"
integrity sha512-QrG9q+ObfmZBxScv0HSCqFm/owcgyR5Sgpiy1NlCZPpFXhbsmNHhTiLWoogItdBUi0fnU7Io/5ABEqRta5/6Dw==
winchan@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/winchan/-/winchan-0.2.2.tgz#6766917b88e5e1cb75f455ffc7cc13f51e5c834e"
integrity sha512-pvN+IFAbRP74n/6mc6phNyCH8oVkzXsto4KCHPJ2AScniAnA1AmeLI03I2BzjePpaClGSI4GUMowzsD3qz5PRQ==

window-size@0.1.0:
version "0.1.0"