-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Kibana Login Selector #53010
Changes from 1 commit
f442437
d89ab88
5253fc7
d6d4c2d
a572c52
706748a
5b189c2
db2f4b3
aae6867
1204619
181c6db
37c6489
f6b1d4f
c197b9c
aed4521
ceba57b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,3 +27,7 @@ | |
max-width: 700px; | ||
} | ||
} | ||
|
||
.loginWelcome__selectorButton { | ||
margin-bottom: $euiSize; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI, instead of using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, I was torn on this and decided to go with CSS solution. But I don't have strong opinion here. Do we recommend using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes you can find a link to the docs in the comment above. Thanks! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, actually I use this margin for the elements that are rendered in the loop, but it seems we don't recommend using spacer in such case:
I'll see if I can turn the element I render into component. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah sorry that is an old warning. I actually have a branch going that will remove that message from our docs. It is ok to use EuiSpacer in repeatable loops. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it, replaced with |
||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -9,19 +9,17 @@ import ReactDOM from 'react-dom'; | |||||
import classNames from 'classnames'; | ||||||
import { BehaviorSubject } from 'rxjs'; | ||||||
import { parse } from 'url'; | ||||||
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; | ||||||
import { EuiButton, EuiIcon, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; | ||||||
import { i18n } from '@kbn/i18n'; | ||||||
import { FormattedMessage } from '@kbn/i18n/react'; | ||||||
import { CoreStart, FatalErrorsStart, HttpStart } from 'src/core/public'; | ||||||
import { LoginLayout } from '../../../common/licensing'; | ||||||
import { LoginState } from '../../../common/login_state'; | ||||||
import { BasicLoginForm, DisabledLoginForm } from './components'; | ||||||
import { LoginState } from './login_state'; | ||||||
|
||||||
interface Props { | ||||||
http: HttpStart; | ||||||
fatalErrors: FatalErrorsStart; | ||||||
loginAssistanceMessage: string; | ||||||
requiresSecureConnection: boolean; | ||||||
} | ||||||
|
||||||
interface State { | ||||||
|
@@ -67,12 +65,10 @@ export class LoginPage extends Component<Props, State> { | |||||
} | ||||||
|
||||||
const isSecureConnection = !!window.location.protocol.match(/^https/); | ||||||
const { allowLogin, layout } = loginState; | ||||||
const { allowLogin, layout, requiresSecureConnection } = loginState; | ||||||
|
||||||
const loginIsSupported = | ||||||
this.props.requiresSecureConnection && !isSecureConnection | ||||||
? false | ||||||
: allowLogin && layout === 'form'; | ||||||
requiresSecureConnection && !isSecureConnection ? false : allowLogin && layout === 'form'; | ||||||
|
||||||
const contentHeaderClasses = classNames('loginWelcome__content', 'eui-textCenter', { | ||||||
['loginWelcome__contentDisabledForm']: !loginIsSupported, | ||||||
|
@@ -110,22 +106,40 @@ export class LoginPage extends Component<Props, State> { | |||||
</div> | ||||||
</header> | ||||||
<div className={contentBodyClasses}> | ||||||
<EuiFlexGroup gutterSize="l"> | ||||||
<EuiFlexItem>{this.getLoginForm({ isSecureConnection, layout })}</EuiFlexItem> | ||||||
</EuiFlexGroup> | ||||||
{this.getLoginForm({ ...loginState, isSecureConnection })} | ||||||
</div> | ||||||
</div> | ||||||
); | ||||||
} | ||||||
|
||||||
private getLoginForm = ({ | ||||||
isSecureConnection, | ||||||
layout, | ||||||
}: { | ||||||
isSecureConnection: boolean; | ||||||
layout: LoginLayout; | ||||||
}) => { | ||||||
if (this.props.requiresSecureConnection && !isSecureConnection) { | ||||||
requiresSecureConnection, | ||||||
isSecureConnection, | ||||||
selector, | ||||||
showLoginForm, | ||||||
}: LoginState & { isSecureConnection: boolean }) => { | ||||||
const showLoginSelector = selector.providers.length > 0; | ||||||
if (!showLoginSelector && !showLoginForm) { | ||||||
return ( | ||||||
<DisabledLoginForm | ||||||
title={ | ||||||
<FormattedMessage | ||||||
id="xpack.security.loginPage.noLoginMethodsAvailableTitle" | ||||||
defaultMessage="Login is disabled." | ||||||
/> | ||||||
} | ||||||
message={ | ||||||
<FormattedMessage | ||||||
id="xpack.security.loginPage.noLoginMethodsAvailableMessage" | ||||||
defaultMessage="Contact your system administrator." | ||||||
/> | ||||||
} | ||||||
/> | ||||||
); | ||||||
} | ||||||
|
||||||
if (requiresSecureConnection && !isSecureConnection) { | ||||||
return ( | ||||||
<DisabledLoginForm | ||||||
title={ | ||||||
|
@@ -144,69 +158,116 @@ export class LoginPage extends Component<Props, State> { | |||||
); | ||||||
} | ||||||
|
||||||
switch (layout) { | ||||||
case 'form': | ||||||
return ( | ||||||
<BasicLoginForm | ||||||
http={this.props.http} | ||||||
infoMessage={infoMessageMap.get( | ||||||
parse(window.location.href, true).query.msg?.toString() | ||||||
)} | ||||||
loginAssistanceMessage={this.props.loginAssistanceMessage} | ||||||
/> | ||||||
); | ||||||
case 'error-es-unavailable': | ||||||
return ( | ||||||
<DisabledLoginForm | ||||||
title={ | ||||||
<FormattedMessage | ||||||
id="xpack.security.loginPage.esUnavailableTitle" | ||||||
defaultMessage="Cannot connect to the Elasticsearch cluster" | ||||||
/> | ||||||
} | ||||||
message={ | ||||||
<FormattedMessage | ||||||
id="xpack.security.loginPage.esUnavailableMessage" | ||||||
defaultMessage="See the Kibana logs for details and try reloading the page." | ||||||
/> | ||||||
} | ||||||
/> | ||||||
); | ||||||
case 'error-xpack-unavailable': | ||||||
return ( | ||||||
<DisabledLoginForm | ||||||
title={ | ||||||
<FormattedMessage | ||||||
id="xpack.security.loginPage.xpackUnavailableTitle" | ||||||
defaultMessage="Cannot connect to the Elasticsearch cluster currently configured for Kibana." | ||||||
/> | ||||||
} | ||||||
message={ | ||||||
<FormattedMessage | ||||||
id="xpack.security.loginPage.xpackUnavailableMessage" | ||||||
defaultMessage="To use the full set of free features in this distribution of Kibana, please update Elasticsearch to the default distribution." | ||||||
/> | ||||||
} | ||||||
/> | ||||||
); | ||||||
default: | ||||||
return ( | ||||||
<DisabledLoginForm | ||||||
title={ | ||||||
<FormattedMessage | ||||||
id="xpack.security.loginPage.unknownLayoutTitle" | ||||||
defaultMessage="Unsupported login form layout." | ||||||
/> | ||||||
} | ||||||
message={ | ||||||
<FormattedMessage | ||||||
id="xpack.security.loginPage.unknownLayoutMessage" | ||||||
defaultMessage="Refer to the Kibana logs for more details and refresh to try again." | ||||||
/> | ||||||
} | ||||||
/> | ||||||
); | ||||||
if (layout === 'error-es-unavailable') { | ||||||
return ( | ||||||
<DisabledLoginForm | ||||||
title={ | ||||||
<FormattedMessage | ||||||
id="xpack.security.loginPage.esUnavailableTitle" | ||||||
defaultMessage="Cannot connect to the Elasticsearch cluster" | ||||||
/> | ||||||
} | ||||||
message={ | ||||||
<FormattedMessage | ||||||
id="xpack.security.loginPage.esUnavailableMessage" | ||||||
defaultMessage="See the Kibana logs for details and try reloading the page." | ||||||
/> | ||||||
} | ||||||
/> | ||||||
); | ||||||
} | ||||||
|
||||||
if (layout === 'error-xpack-unavailable') { | ||||||
return ( | ||||||
<DisabledLoginForm | ||||||
title={ | ||||||
<FormattedMessage | ||||||
id="xpack.security.loginPage.xpackUnavailableTitle" | ||||||
defaultMessage="Cannot connect to the Elasticsearch cluster currently configured for Kibana." | ||||||
/> | ||||||
} | ||||||
message={ | ||||||
<FormattedMessage | ||||||
id="xpack.security.loginPage.xpackUnavailableMessage" | ||||||
defaultMessage="To use the full set of free features in this distribution of Kibana, please update Elasticsearch to the default distribution." | ||||||
/> | ||||||
} | ||||||
/> | ||||||
); | ||||||
} | ||||||
|
||||||
if (layout !== 'form') { | ||||||
return ( | ||||||
<DisabledLoginForm | ||||||
title={ | ||||||
<FormattedMessage | ||||||
id="xpack.security.loginPage.unknownLayoutTitle" | ||||||
defaultMessage="Unsupported login form layout." | ||||||
/> | ||||||
} | ||||||
message={ | ||||||
<FormattedMessage | ||||||
id="xpack.security.loginPage.unknownLayoutMessage" | ||||||
defaultMessage="Refer to the Kibana logs for more details and refresh to try again." | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: for consistency with the other messages
Suggested change
|
||||||
/> | ||||||
} | ||||||
/> | ||||||
); | ||||||
} | ||||||
|
||||||
const loginSelector = | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: we probably would need a separate component for that button, but it doesn't feel mandatory at this point. |
||||||
showLoginSelector && | ||||||
selector.providers.map((provider, index) => ( | ||||||
<EuiButton | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Feel free to defer this, but we might want to disable the button after clicking (and show a progress animation) like we do for the primary "Log in" button. Authentication usually happens very quickly when running locally on an untaxed cluster, but I've seen even
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, we would need to disable all buttons, including login form ones. The more I think about it the more it feels natural to just replace There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That sounds good to me. We can address this in a followup so as not to block merging. |
||||||
key={index} | ||||||
className="loginWelcome__selectorButton" | ||||||
iconType="user" | ||||||
fullWidth={true} | ||||||
onClick={() => this.login(provider.type, provider.name)} | ||||||
> | ||||||
{provider.options.description} | ||||||
</EuiButton> | ||||||
)); | ||||||
|
||||||
const loginSelectorAndLoginFormSeparator = showLoginSelector && showLoginForm && ( | ||||||
<> | ||||||
<EuiText textAlign="center" color="subdued"> | ||||||
――― | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: didn't have chance to find a better solution for this nasty |
||||||
<FormattedMessage id="xpack.security.loginPage.loginSelectorOR" defaultMessage="OR" /> | ||||||
――― | ||||||
</EuiText> | ||||||
<EuiSpacer size="m" /> | ||||||
</> | ||||||
); | ||||||
|
||||||
const loginForm = showLoginForm && ( | ||||||
<BasicLoginForm | ||||||
http={this.props.http} | ||||||
infoMessage={infoMessageMap.get(parse(window.location.href, true).query.msg?.toString())} | ||||||
loginAssistanceMessage={this.props.loginAssistanceMessage} | ||||||
/> | ||||||
); | ||||||
|
||||||
return ( | ||||||
<> | ||||||
{loginSelector} | ||||||
{loginSelectorAndLoginFormSeparator} | ||||||
{loginForm} | ||||||
</> | ||||||
); | ||||||
}; | ||||||
|
||||||
private login = (providerType: string, providerName: string) => { | ||||||
const query = parse(window.location.href, true).query; | ||||||
This comment was marked as resolved.
Sorry, something went wrong. |
||||||
const next = | ||||||
Array.isArray(query.next) && query.next.length > 0 ? query.next[0] : (query.next as string); | ||||||
const queryString = next ? `?next=${encodeURIComponent(next)}${window.location.hash}` : ''; | ||||||
|
||||||
window.location.href = `${ | ||||||
this.props.http.basePath.serverBasePath | ||||||
}/internal/security/login/${encodeURIComponent(providerType)}/${encodeURIComponent( | ||||||
providerName | ||||||
)}${queryString}`; | ||||||
}; | ||||||
} | ||||||
|
||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: This fixes the issue that's is a blocker for us (without this we can't have required fields within
schema.recordOf
value schema), I'll prepare a separate PR for kbn/config-schema next week or ask Platform team for a help if it turns out to be more complex than I think.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handled in #60406.