Skip to content
Snippets Groups Projects
Unverified Commit a98d179f authored by Kyle Doherty's avatar Kyle Doherty Committed by GitHub
Browse files

clean up UX on SSO login (#10023)

* clean up UX on SSO login

present users with a clearer choice of SSO vs email / password

* use link wip

* update sign in copy

* update tests

* lint fix
parent 8ff7dc2e
Branches
Tags
No related merge requests found
......@@ -12,8 +12,8 @@ class SSOLoginButton extends Component {
render() {
const { provider } = this.props;
return (
<div className="relative z2 bg-white p2 cursor-pointer shadow-hover text-centered sm-text-left rounded block sm-inline-block bordered shadowed">
<div className="flex align-center">
<div className="relative z2 bg-white p2 cursor-pointer shadow-hover text-centered sm-text-left rounded bordered shadowed flex">
<div className="flex align-center ml-auto mr-auto">
<Icon className="mr1" name={provider} />
<h4>{t`Sign in with ${capitalize(provider)}`}</h4>
</div>
......
......@@ -113,11 +113,13 @@ export default class LoginApp extends Component {
}
render() {
const { loginError } = this.props;
const { loginError, location } = this.props;
const ldapEnabled = Settings.ldapEnabled();
const preferUsernameAndPassword = location.query.useMBLogin;
return (
<div className="full bg-white flex flex-column flex-full md-layout-centered">
<div className="bg-white flex flex-column flex-full md-layout-centered">
<div className="Login-wrapper wrapper Grid Grid--full md-Grid--1of2 relative z2">
<div className="Grid-cell flex layout-centered text-brand">
<LogoIcon className="Logo my4 sm-my0" width={66} height={85} />
......@@ -130,108 +132,126 @@ export default class LoginApp extends Component {
>
<h3 className="Login-header Form-offset">{t`Sign in to Metabase`}</h3>
{Settings.ssoEnabled() && (
<div className="mx4 mb4 py3 border-bottom relative">
<SSOLoginButton provider="google" ref="ssoLoginButton" />
{/*<div className="g-signin2 ml1 relative z2" id="g-signin2"></div>*/}
<div
className="mx1 absolute text-centered left right"
style={{ bottom: -8 }}
>
<span className="text-bold px3 py2 text-medium bg-white">{t`OR`}</span>
{Settings.ssoEnabled() && !preferUsernameAndPassword && (
<div className="mx4 py3 relative my4">
<div className="relative border-bottom pb4">
<SSOLoginButton provider="google" ref="ssoLoginButton" />
{/*<div className="g-signin2 ml1 relative z2" id="g-signin2"></div>*/}
<div
className="mx1 absolute text-centered left right"
style={{ bottom: -8 }}
>
<span className="text-bold px3 py2 text-medium bg-white">{t`OR`}</span>
</div>
</div>
<div className="py3">
<Link to="/auth/login?useMBLogin=true">
<Button className="EmailSignIn full">
{t`Sign in with email`}
</Button>
</Link>
</div>
</div>
)}
<FormMessage
formError={
loginError && loginError.data.message ? loginError : null
}
/>
<FormField
key="username"
fieldName="username"
formError={loginError}
>
<FormLabel
title={
Settings.ldapEnabled()
? t`Username or email address`
: t`Email address`
}
fieldName={"username"}
formError={loginError}
/>
<input
className="Form-input Form-offset full py1"
name="username"
placeholder="youlooknicetoday@email.com"
type={
/*
* if a user has ldap enabled, use a text input to allow for
* ldap username && schemes. if not and they're using built
* in auth, set the input type to email so we get built in
* validation in modern browsers
* */
ldapEnabled ? "text" : "email"
}
onChange={e => this.onChange("username", e.target.value)}
autoFocus
/>
<span className="Form-charm" />
</FormField>
<FormField
key="password"
fieldName="password"
formError={loginError}
>
<FormLabel
title={t`Password`}
fieldName={"password"}
formError={loginError}
/>
<input
className="Form-input Form-offset full py1"
name="password"
placeholder="Shh..."
type="password"
onChange={e => this.onChange("password", e.target.value)}
/>
<span className="Form-charm" />
</FormField>
<div className="Form-field">
<div className="Form-offset flex align-center">
<CheckBox
name="remember"
checked={this.state.rememberMe}
onChange={() =>
this.setState({ rememberMe: !this.state.rememberMe })
{(!Settings.ssoEnabled() || preferUsernameAndPassword) && (
<div>
<FormMessage
formError={
loginError && loginError.data.message ? loginError : null
}
/>
<span className="ml1">{t`Remember Me`}</span>
<FormField
key="username"
fieldName="username"
formError={loginError}
>
<FormLabel
title={
Settings.ldapEnabled()
? t`Username or email address`
: t`Email address`
}
fieldName={"username"}
formError={loginError}
/>
<input
className="Form-input Form-offset full py1"
name="username"
placeholder="youlooknicetoday@email.com"
type={
/*
* if a user has ldap enabled, use a text input to allow for
* ldap username && schemes. if not and they're using built
* in auth, set the input type to email so we get built in
* validation in modern browsers
* */
ldapEnabled ? "text" : "email"
}
onChange={e => this.onChange("username", e.target.value)}
autoFocus
/>
<span className="Form-charm" />
</FormField>
<FormField
key="password"
fieldName="password"
formError={loginError}
>
<FormLabel
title={t`Password`}
fieldName={"password"}
formError={loginError}
/>
<input
className="Form-input Form-offset full py1"
name="password"
placeholder="Shh..."
type="password"
onChange={e => this.onChange("password", e.target.value)}
/>
<span className="Form-charm" />
</FormField>
<div className="Form-field">
<div className="Form-offset flex align-center">
<CheckBox
name="remember"
checked={this.state.rememberMe}
onChange={() =>
this.setState({
rememberMe: !this.state.rememberMe,
})
}
/>
<span className="ml1">{t`Remember Me`}</span>
</div>
</div>
<div className="Form-actions p4">
<Button
primary={this.state.valid}
disabled={!this.state.valid}
>
{t`Sign in`}
</Button>
<Link
to={
"/auth/forgot_password" +
(Utils.validEmail(this.state.credentials.username)
? "?email=" + this.state.credentials.username
: "")
}
className="Grid-cell py2 sm-py0 md-text-right text-centered flex-full link"
onClick={e => {
window.OSX ? window.OSX.resetPassword() : null;
}}
>{t`I seem to have forgotten my password`}</Link>
</div>
</div>
</div>
<div className="Form-actions p4">
<Button primary={this.state.valid} disabled={!this.state.valid}>
{t`Sign in`}
</Button>
<Link
to={
"/auth/forgot_password" +
(Utils.validEmail(this.state.credentials.username)
? "?email=" + this.state.credentials.username
: "")
}
className="Grid-cell py2 sm-py0 md-text-right text-centered flex-full link"
onClick={e => {
window.OSX ? window.OSX.resetPassword() : null;
}}
>{t`I seem to have forgotten my password`}</Link>
</div>
)}
</form>
</div>
</div>
......
import React from "react";
import LoginApp from "metabase/auth/containers/LoginApp";
import { mountWithStore } from "__support__/integration_tests";
jest.mock("metabase/lib/settings", () => ({
get: () => ({
tag: 1,
version: 1,
}),
ssoEnabled: jest.fn(),
ldapEnabled: jest.fn(),
}));
import Settings from "metabase/lib/settings";
describe("LoginApp", () => {
describe("initial state", () => {
describe("without SSO", () => {
it("should show the login form", () => {
const { wrapper } = mountWithStore(
<LoginApp location={{ query: {} }} />,
);
expect(wrapper.find("FormField").length).toBe(2);
});
});
describe("with SSO", () => {
beforeEach(() => {
Settings.ssoEnabled.mockReturnValue(true);
});
it("should show the SSO button", () => {
const { wrapper } = mountWithStore(
<LoginApp location={{ query: {} }} />,
);
expect(wrapper.find("SSOLoginButton").length).toBe(1);
expect(wrapper.find(".Button.EmailSignIn").length).toBe(1);
});
it("should hide the login form initially", () => {
const { wrapper } = mountWithStore(
<LoginApp location={{ query: {} }} />,
);
expect(wrapper.find("FormField").length).toBe(0);
});
it("should show the login form if the url param is set", () => {
const { wrapper } = mountWithStore(
<LoginApp location={{ query: { useMBLogin: true } }} />,
);
expect(wrapper.find("FormField").length).toBe(2);
expect(wrapper.find("SSOLoginButton").length).toBe(0);
});
});
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment