Skip to content

Commit

Permalink
disable submit button when the email does not match with any connection
Browse files Browse the repository at this point in the history
  • Loading branch information
glena committed Jan 23, 2017
1 parent c3c6503 commit 45d3846
Show file tree
Hide file tree
Showing 15 changed files with 91 additions and 31 deletions.
1 change: 1 addition & 0 deletions src/__tests__/field/__snapshots__/login_pane.test.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ exports[`LoginPane shows email pane when user usernameStyle === email 1`] = `
<div>
<div
data-__type="email_pane"
data-forceInvalidVisibility={false}
data-i18n={Object {}}
data-lock={Object {}}
data-placeholder="emailInputPlaceholder" />
Expand Down
8 changes: 8 additions & 0 deletions src/__tests__/field/email_pane.test.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import Immutable from 'immutable';
import { mount } from 'enzyme';

import { expectComponent, extractPropsFromWrapper, mockComponent } from 'testUtils';
Expand All @@ -20,8 +21,12 @@ describe('EmailPane', () => {
jest.resetModules();

const mockEmail = '[email protected]';
const mockEmailField = Immutable.fromJS({
value: mockEmail
});
jest.mock('field/index', () => ({
email: () => mockEmail,
getField: () => mockEmailField,
getFieldValue: () => mockEmail,
isFieldVisiblyInvalid: () => true
}));
Expand Down Expand Up @@ -60,6 +65,9 @@ describe('EmailPane', () => {
const fieldIndexMock = require('field/index');
fieldIndexMock.username = () => undefined;
fieldIndexMock.getFieldValue = () => undefined;
fieldIndexMock.getField = () => Immutable.fromJS({
value: undefined
});
const EmailPane = getComponent();

expectComponent(
Expand Down
1 change: 1 addition & 0 deletions src/connection/database/login_pane.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export default class LoginPane extends React.Component {
? <EmailPane
i18n={i18n}
lock={lock}
forceInvalidVisibility={!showPassword}
placeholder={emailInputPlaceholder}
/>
: <UsernamePane
Expand Down
18 changes: 18 additions & 0 deletions src/connection/enterprise.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ import { dataFns } from '../utils/data_utils';
import { emailDomain, emailLocalPart } from '../field/email';
import { setUsername } from '../field/username';
import { getFieldValue } from '../field/index';
import { isEmail } from '../field/email';
import { isSSOEnabled, matchesEnterpriseConnection } from '../engine/classic';
import { databaseUsernameValue } from './database/index';

import { swap, updateEntity } from '../store/index';

const { get, initNS, tget, tremove, tset } = dataFns(["enterprise"]);
const { tremove: tremoveCore, tset: tsetCore, tget: tgetCore } = dataFns(["core"]);

// TODO: Android version also has "google-opendid" in the list, but we
// consider it to be a social connection. See
Expand Down Expand Up @@ -147,3 +153,15 @@ export function toggleHRD(m, email) {
export function isHRDActive(m) {
return tget(m, "hrd", isSingleHRDConnection(m));
}

export function isHRDEmailValid(m, str) {
if(isEmail(str)
&& !l.hasSomeConnections(m, "database")
&& !findADConnectionWithoutDomain(m)
&& !matchesEnterpriseConnection(m, str) ) {

return false;
}

return true;
}
4 changes: 1 addition & 3 deletions src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
setupLock,
updateLock
} from './core/actions';
import { termsAccepted } from './connection/database/index';
import * as l from './core/index';
import * as c from './field/index';
import * as idu from './utils/id_utils';
Expand Down Expand Up @@ -81,8 +80,7 @@ export default class Base extends EventEmitter {
? i18n.str(m, "welcome", m.getIn(["avatar", "transient", "displayName"]))
: screen.getTitle(m);

const disableSubmitButton = screen.name === "main.signUp"
&& !termsAccepted(m);
const disableSubmitButton = screen.isSubmitDisabled(m);

const i18nProp = {
group: keyPath => i18n.group(m, keyPath),
Expand Down
4 changes: 4 additions & 0 deletions src/core/screen.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ export default class Screen {
return null;
}

isSubmitDisabled(m) {
return false;
}

renderAuxiliaryPane() {
return null;
}
Expand Down
6 changes: 5 additions & 1 deletion src/engine/classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ import { getFieldValue } from '../field/index';
import { swap, updateEntity } from '../store/index';

export function isSSOEnabled(m) {
return isEnterpriseDomain(m, databaseUsernameValue(m));
return matchesEnterpriseConnection(m, databaseUsernameValue(m));
}

export function matchesEnterpriseConnection(m, usernameValue) {
return isEnterpriseDomain(m, usernameValue);
}

export function usernameStyle(m) {
Expand Down
12 changes: 10 additions & 2 deletions src/engine/classic/login.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import LoginPane from '../../connection/database/login_pane';
import PaneSeparator from '../../core/pane_separator';
import {
databaseConnection,
databaseUsernameValue,
databaseUsernameStyle,
databaseUsernameValue,
defaultDatabaseConnection,
hasInitialScreen,
hasScreen,
Expand Down Expand Up @@ -37,7 +37,6 @@ import {
} from '../classic';
import * as i18n from '../../i18n';


function shouldRenderTabs(m) {
if (isSSOEnabled(m)) return false;
if (l.hasSomeConnections(m, "database")) return hasScreen(m, "signUp");
Expand Down Expand Up @@ -132,6 +131,15 @@ export default class Login extends Screen {
return i18n.str(m, ["loginSubmitLabel"]);
}

isSubmitDisabled(m) {
// it should disable the submit button if there is any connection that
// requires username/password and there is no enterprise with domain
// that matches with the email domain entered for HRD
return !l.hasSomeConnections(m, "database") // no database connection
&& !findADConnectionWithoutDomain(m) // no enterprise without domain
&& !isSSOEnabled(m); // no matching domain
}

submitHandler(model) {
if (hasOnlyClassicConnections(model, "social")) {
return null;
Expand Down
4 changes: 4 additions & 0 deletions src/engine/classic/sign_up_screen.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ export default class SignUp extends Screen {
return signUp;
}

isSubmitDisabled(m) {
return !termsAccepted(m)
}

renderAuxiliaryPane(lock) {
return renderSignedInConfirmation(lock)
|| renderSignedUpConfirmation(lock)
Expand Down
17 changes: 15 additions & 2 deletions src/field/email.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
import trim from 'trim';
import { setField } from './index';
import { endsWith } from '../utils/string_utils';
import { isHRDEmailValid } from '../connection/enterprise';
import * as i18n from '../i18n';

const regExp = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

export function validateEmail(str) {
return isEmail(str);
}

export function isEmail(str) {
const result = regExp.exec(trim(str.toLowerCase()));
return result && result[0];
return !!result && result[0] !== null;
}

export function setEmail(m, str) {
return setField(m, "email", str, validateEmail);
return setField(m, "email", str, (str) => {
const validHRDEMail = isHRDEmailValid(m, str);

return {
valid: trim(str) === '' || (validateEmail(str) && validHRDEMail),
hint: !validHRDEMail ? i18n.str(m, ["error", "login", "hrd.not_matching_email"]) : undefined
};
});
}

export function emailDomain(str) {
Expand Down
14 changes: 10 additions & 4 deletions src/field/email/email_pane.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,20 @@ export default class EmailPane extends React.Component {
}

render() {
const { i18n, lock, placeholder } = this.props;
const value = c.getFieldValue(lock, "email");
const { i18n, lock, placeholder, forceInvalidVisibility = false } = this.props;

const field = c.getField(lock, "email");
const value = field.get('value', "");
const valid = field.get('valid', true);
const invalidHint = field.get('invalidHint', i18n.str(value ? "invalidErrorHint": "blankErrorHint"));

const isValid = (!forceInvalidVisibility || valid) && !c.isFieldVisiblyInvalid(lock, "email");

return (
<EmailInput
value={value}
invalidHint={i18n.str(value ? "invalidErrorHint": "blankErrorHint")}
isValid={!c.isFieldVisiblyInvalid(lock, "email")}
invalidHint={invalidHint}
isValid={isValid}
onChange={::this.handleChange}
placeholder={placeholder}
/>
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export default {
"password_change_required": "You need to update your password because this is the first time you are logging in, or because your password has expired.", // TODO: verify error code
"password_leaked": "This login has been blocked because your password has been leaked in another website. We’ve sent you an email with instructions on how to unblock it.",
"too_many_attempts": "Your account has been blocked after multiple consecutive login attempts.",
"session_missing": "Couldn't complete your authentication request. Please try again after closing all open dialogs"
"session_missing": "Couldn't complete your authentication request. Please try again after closing all open dialogs",
"hrd.not_matching_email": "Please, use your corporate email to login."
},
passwordless: {
"bad.email": "The email is invalid",
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export default {
"too_many_attempts": "Su cuenta ha sido bloqueada luego de múltiples intentos de inicio de sesión consecutivos.",
"lock.mfa_registration_required": "Por favor enrole su dispositivo antes de continuar con el segundo factor.",
"lock.mfa_invalid_code": "Código incorrecto. Por favor vuelva a intentarlo.",
"session_missing": "No es posible completar el proceso de Autenticación. Por favor, cierre todas las ventanas e intente nuevamente."
"session_missing": "No es posible completar el proceso de Autenticación. Por favor, cierre todas las ventanas e intente nuevamente.",
"hrd.not_matching_email": "Por favor, use sus credenciales corporativas."
},
passwordless: {
"bad.email": "Correo inválido",
Expand Down
11 changes: 2 additions & 9 deletions support/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@
const cid = "ixeOHFhD7NSPxEQK6CFcswjUsa5YkcXS";
const domain = "auth0-tests-lock.auth0.com";
const options = {
auth: {
responseType: 'id_token'
},
oidcConformant: true
rememberLastLogin:false
};

const lock = new Auth0Lock(cid, domain, options);
Expand All @@ -38,11 +35,7 @@
console.log('authorization_error', error);
});

lock.show({
languageDictionary: {
title: "sarlanga"
}
});
lock.show();

</script>
</body>
Expand Down
16 changes: 8 additions & 8 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ var path = require('path');
module.exports = {
entry: './src/browser.js',
output: {
path: path.join(__dirname, "../build"),
path: path.join(__dirname, '../build'),
filename: 'lock.js'
},
resolve: {
extensions: ["", ".webpack.js", ".web.js", ".js", ".jsx", ".styl"]
extensions: ['', '.webpack.js', '.web.js', '.js', '.jsx', '.styl']
},
progress: true,
watch: true,
Expand All @@ -25,23 +25,23 @@ module.exports = {
reasons: true
},
stylus: {
preferPathResolver: 'webpack',
preferPathResolver: 'webpack'
},
module: {
loaders: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: __dirname + './node_modules',
exclude: path.join(__dirname, 'node_modules'),
query: {
plugins: ["version-inline", "transform-css-import-to-string"],
presets: ["es2015-loose", "stage-0", "react"]
plugins: ['version-inline', 'transform-css-import-to-string'],
presets: ['es2015-loose', 'stage-0', 'react']
}
},
{
test: /\.styl$/,
loader: 'css-loader!stylus-loader?paths=node_modules/bootstrap-stylus/stylus/'
}
]
},
};
}
};

0 comments on commit 45d3846

Please sign in to comment.