/*
* Copyright OpenSearch Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
import React, { useState } from 'react';
import {
EuiText,
EuiFieldText,
EuiIcon,
EuiSpacer,
EuiButton,
EuiImage,
EuiListGroup,
EuiForm,
EuiFormRow,
EuiHorizontalRule,
} from '@elastic/eui';
import { CoreStart } from '../../../../../src/core/public';
import { ClientConfigType } from '../../types';
import defaultBrandImage from '../../assets/opensearch_logo_h.svg';
import { validateCurrentPassword } from '../../utils/login-utils';
import {
ANONYMOUS_AUTH_LOGIN,
AuthType,
OPENID_AUTH_LOGIN,
SAML_AUTH_LOGIN_WITH_FRAGMENT,
} from '../../../common';
interface LoginPageDeps {
http: CoreStart['http'];
config: ClientConfigType;
}
interface LoginButtonConfig {
buttonname: string;
showbrandimage: boolean;
brandimage: string;
buttonstyle: string;
}
function redirect(serverBasePath: string) {
// navigate to nextUrl
const urlParams = new URLSearchParams(window.location.search);
let nextUrl = urlParams.get('nextUrl');
if (!nextUrl || nextUrl.toLowerCase().includes('//')) {
// Appending the next url with trailing slash. We do so because in case the serverBasePath is empty, we can simply
// redirect to '/'.
nextUrl = serverBasePath + '/';
}
window.location.href = nextUrl + window.location.hash;
}
export function LoginPage(props: LoginPageDeps) {
const [username, setUsername] = React.useState('');
const [password, setPassword] = React.useState('');
const [loginFailed, setloginFailed] = useState(false);
const [loginError, setloginError] = useState('');
const [usernameValidationFailed, setUsernameValidationFailed] = useState(false);
const [passwordValidationFailed, setPasswordValidationFailed] = useState(false);
let errorLabel: any = null;
if (loginFailed) {
errorLabel = (
{loginError}
);
}
// @ts-ignore : Parameter 'e' implicitly has an 'any' type.
const handleSubmit = async (e) => {
e.preventDefault();
// Clear errors
setloginFailed(false);
setUsernameValidationFailed(false);
setPasswordValidationFailed(false);
// Form validation
if (username === '') {
setUsernameValidationFailed(true);
return;
}
if (password === '') {
setPasswordValidationFailed(true);
return;
}
try {
await validateCurrentPassword(props.http, username, password);
redirect(props.http.basePath.serverBasePath);
} catch (error) {
console.log(error);
setloginFailed(true);
setloginError('Invalid username or password. Please try again.');
return;
}
};
const renderLoginButton = (
authType: string,
loginEndPoint: string,
buttonConfig: LoginButtonConfig
) => {
const buttonId = `${authType}_login_button`;
const loginEndPointWithPath = `${props.http.basePath.serverBasePath}${loginEndPoint}`;
return (
{buttonConfig.buttonname}
);
};
const formOptions = (options: string | string[]) => {
let formBody = [];
const formBodyOp = [];
let authOpts = [];
if (typeof options === 'string') {
if (options === '') {
authOpts.push(AuthType.BASIC);
} else {
authOpts.push(options.toLowerCase());
}
} else {
if (options && options.length === 1 && options[0] === '') {
authOpts.push(AuthType.BASIC);
} else {
authOpts = [...options];
}
}
for (let i = 0; i < authOpts.length; i++) {
switch (authOpts[i].toLowerCase()) {
case AuthType.BASIC: {
formBody.push(
}
onChange={(e) => setUsername(e.target.value)}
value={username}
isInvalid={usernameValidationFailed}
/>
);
formBody.push(
}
type="password"
onChange={(e) => setPassword(e.target.value)}
value={password}
isInvalid={usernameValidationFailed}
/>
);
const buttonId = `${AuthType.BASIC}_login_button`;
formBody.push(
Log in
);
if (authOpts.length > 1) {
if (props.config.auth.anonymous_auth_enabled) {
const anonymousConfig = props.config.ui[AuthType.ANONYMOUS].login;
formBody.push(
renderLoginButton(AuthType.ANONYMOUS, ANONYMOUS_AUTH_LOGIN, anonymousConfig)
);
}
formBody.push();
formBody.push();
formBody.push();
}
break;
}
case AuthType.OPEN_ID: {
const oidcConfig = props.config.ui[AuthType.OPEN_ID].login;
formBodyOp.push(renderLoginButton(AuthType.OPEN_ID, OPENID_AUTH_LOGIN, oidcConfig));
break;
}
case AuthType.SAML: {
const samlConfig = props.config.ui[AuthType.SAML].login;
formBodyOp.push(
renderLoginButton(AuthType.SAML, SAML_AUTH_LOGIN_WITH_FRAGMENT, samlConfig)
);
break;
}
default: {
setloginFailed(true);
setloginError(
`Authentication Type: ${authOpts[i]} is not supported for multiple authentication.`
);
break;
}
}
}
formBody = formBody.concat(formBodyOp);
return formBody;
};
// TODO: Get brand image from server config
return (
{props.config.ui.basicauth.login.showbrandimage && (
)}
{props.config.ui.basicauth.login.title || 'Log in to OpenSearch Dashboards'}
{props.config.ui.basicauth.login.subtitle ||
'If you have forgotten your username or password, contact your system administrator.'}
{formOptions(props.config.auth.type)}
{errorLabel}
);
}