Skip to content

Commit

Permalink
Merge pull request #38 from elastic-analytics/priscilla_poc
Browse files Browse the repository at this point in the history
Basic and OIDC Authentication POC, Refactor configuration
  • Loading branch information
aoguan1990 authored Feb 6, 2023
2 parents 4929bb7 + 0a9b37e commit 48cd5ee
Show file tree
Hide file tree
Showing 33 changed files with 2,578 additions and 0 deletions.
36 changes: 36 additions & 0 deletions src/plugins/dashboards_security/common/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export const PLUGIN_ID = 'opensearchDashboardsSecurity';
export const PLUGIN_NAME = 'security-dashboards-plugin';

export const APP_ID_LOGIN = 'login';

export const API_PREFIX = '/api/v1';
export const CONFIGURATION_API_PREFIX = 'configuration';
export const API_ENDPOINT_AUTHINFO = API_PREFIX + '/auth/authinfo';
export const API_ENDPOINT_AUTHTYPE = API_PREFIX + '/auth/type';
export const LOGIN_PAGE_URI = '/app/' + APP_ID_LOGIN;
export const API_AUTH_LOGIN = '/auth/login';
export const API_AUTH_LOGOUT = '/auth/logout';
export const ANONYMOUS_AUTH_LOGIN = '/auth/anonymous';
export const SAML_AUTH_LOGIN_WITH_FRAGMENT = '/auth/saml/captureUrlFragment?nextUrl=%2F';

export const OPENID_AUTH_LOGOUT = '/auth/openid/logout';
export const SAML_AUTH_LOGOUT = '/auth/saml/logout';
export const ANONYMOUS_AUTH_LOGOUT = '/auth/anonymous/logout';

export const AUTH_HEADER_NAME = 'authorization';
export const AUTH_GRANT_TYPE = 'authorization_code';
export const AUTH_RESPONSE_TYPE = 'code';

export enum AuthType {
BASIC = 'basicauth',
OIDC = 'oidc',
JWT = 'jwt',
SAML = 'saml',
PROXY = 'proxy',
ANONYMOUS = 'anonymous',
}
8 changes: 8 additions & 0 deletions src/plugins/dashboards_security/opensearch_dashboards.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "dashboardsSecurity",
"version": "opensearchDashboards",
"configPath": ["dashboards_security"],
"requiredPlugins": ["navigation"],
"server": true,
"ui": true
}
25 changes: 25 additions & 0 deletions src/plugins/dashboards_security/public/apps/login/_index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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.
*/

.login-wrapper {
margin: 10% auto;
width: 350px;
padding: 1rem;
position: relative;
}

.btn-login {
width: 100%;
}
21 changes: 21 additions & 0 deletions src/plugins/dashboards_security/public/apps/login/login-app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import './_index.scss';
// @ts-ignore : Component not used
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { AppMountParameters, CoreStart } from '../../../../../core/public';
import { ClientConfigType } from '../../types';
import { LoginPage } from './login-page';

export function renderApp(
coreStart: CoreStart,
params: AppMountParameters,
config: ClientConfigType
) {
ReactDOM.render(<LoginPage http={coreStart.http} config={config} />, params.element);
return () => ReactDOM.unmountComponentAtNode(params.element);
}
184 changes: 184 additions & 0 deletions src/plugins/dashboards_security/public/apps/login/login-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useState } from 'react';
import {
EuiText,
EuiFieldText,
EuiIcon,
EuiSpacer,
EuiButton,
EuiImage,
EuiListGroup,
EuiForm,
EuiFormRow,
} from '@elastic/eui';
import { AuthType } from 'src/plugins/dashboards_security/common';
import { ESMap } from 'typescript';
import { map } from 'bluebird';
import { CoreStart } from '../../../../../core/public';
import { ClientConfigType } from '../../types';
import defaultBrandImage from '../../assets/opensearch_logo_h.svg';
import { validateCurrentPassword } from '../../utils/auth_utils';

interface LoginButtonConfig {
buttonname: string;
showbrandimage: boolean;
brandimage: string;
buttonstyle: string;
}

interface LoginPageDeps {
http: CoreStart['http'];
config: ClientConfigType;
}

function redirect(serverBasePath: string) {
// navigate to nextUrl
const urlParams = new URLSearchParams(window.location.search);
let nextUrl = urlParams.get('nextUrl');
if (!nextUrl || nextUrl.toLowerCase().includes('//')) {
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 = (
<EuiText id="error" color="danger" textAlign="center">
<b>{loginError}</b>
</EuiText>
);
}

// @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) {
setloginFailed(true);
setloginError('Invalid username or password. Please try again.');
return;
}
};

// TODO: Get brand image from server config
return (
<EuiListGroup className="login-wrapper">
{props.config.ui.basicauth.login.showbrandimage && (
<EuiImage
size="fullWidth"
alt=""
url={props.config.ui.basicauth.login.brandimage || defaultBrandImage}
/>
)}
<EuiSpacer size="s" />
<EuiText size="m" textAlign="center">
{props.config.ui.basicauth.login.title || 'Please login to OpenSearch Dashboards'}
</EuiText>
<EuiSpacer size="s" />
<EuiText size="s" textAlign="center">
{props.config.ui.basicauth.login.subtitle ||
'If you have forgotten your username or password, please ask your system administrator'}
</EuiText>
<EuiSpacer size="s" />
<EuiForm component="form">
<EuiFormRow>
<EuiFieldText
data-test-subj="user-name"
placeholder="Username"
prepend={<EuiIcon type="user" />}
onChange={(e) => setUsername(e.target.value)}
value={username}
/>
</EuiFormRow>
<EuiFormRow>
<EuiFieldText
data-test-subj="password"
placeholder="Password"
prepend={<EuiIcon type="lock" />}
type="password"
onChange={(e) => setPassword(e.target.value)}
value={password}
/>
</EuiFormRow>
<EuiFormRow>
<EuiButton
data-test-subj="submit"
fill
size="s"
type="submit"
className={props.config.ui.basicauth.login.buttonstyle || 'btn-login'}
onClick={handleSubmit}
>
Log In
</EuiButton>
</EuiFormRow>
</EuiForm>
<EuiSpacer size="s" />
<EuiFormRow>
<EuiButton
data-test-subj="submit"
size="s"
type="prime"
className={props.config.ui.openid.login.buttonstyle || 'btn-login'}
href="/auth/oidc/okta/login"
iconType={
props.config.ui.openid.login.showbrandimage
? props.config.ui.openid.login.brandimage
: ''
}
>
Login with OKTA (OIDC)
</EuiButton>
</EuiFormRow>
<EuiSpacer size="s" />
<EuiFormRow>
<EuiButton
data-test-subj="submit"
size="s"
type="prime"
className={props.config.ui.openid.login.buttonstyle || 'btn-login'}
href="/auth/oidc/google/login"
iconType={
props.config.ui.openid.login.showbrandimage
? props.config.ui.openid.login.brandimage
: ''
}
>
Login with Google (OIDC)
</EuiButton>
</EuiFormRow>
{errorLabel}
</EuiListGroup>
);
}
38 changes: 38 additions & 0 deletions src/plugins/dashboards_security/public/apps/logout/logout-app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

/*
* 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 from 'react';
import ReactDOM from 'react-dom';
import { CoreStart } from 'opensearch-dashboards/public';
import { ClientConfigType } from '../../types';
import { LogoutPage } from './logout-page';

export async function setupLogoutButton(coreStart: CoreStart, config: ClientConfigType) {
coreStart.chrome.navControls.registerRight({
order: 2000,
mount: (element: HTMLElement) => {
ReactDOM.render(
<LogoutPage http={coreStart.http} logoutUrl={config.auth.logout_url} />,
element
);
return () => ReactDOM.unmountComponentAtNode(element);
},
});
}
34 changes: 34 additions & 0 deletions src/plugins/dashboards_security/public/apps/logout/logout-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

/*
* 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 from 'react';
import { EuiButtonEmpty } from '@elastic/eui';
import { HttpStart } from 'opensearch-dashboards/public';
import { logout } from '../../utils/auth_utils';

export function LogoutPage(props: { http: HttpStart; logoutUrl?: string }) {
return (
<div>
<EuiButtonEmpty size="xs" onClick={() => logout(props.http, props.logoutUrl)}>
Log out
</EuiButtonEmpty>
</div>
);
}
Loading

0 comments on commit 48cd5ee

Please sign in to comment.