diff --git a/dashboard/package.json b/dashboard/package.json
index 887e399cc5..4e78c1b249 100644
--- a/dashboard/package.json
+++ b/dashboard/package.json
@@ -9,6 +9,7 @@
"@patternfly/patternfly": "^4.183.1",
"@patternfly/react-core": "^4.198.19",
"@patternfly/react-table": "^4.75.2",
+ "@react-keycloak/web": "^3.4.0",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0",
@@ -21,6 +22,7 @@
"gulp": "^4.0.2",
"jest": "^27.5.1",
"js-cookie": "^3.0.1",
+ "keycloak-js": "^21.0.1",
"less-watch-compiler": "^1.16.3",
"patternfly": "^3.9.0",
"react": "^17.0.2",
diff --git a/dashboard/src/App.js b/dashboard/src/App.js
index d55e79b3d6..b4755fade3 100644
--- a/dashboard/src/App.js
+++ b/dashboard/src/App.js
@@ -16,21 +16,19 @@ import { AuthForm } from "modules/components/AuthComponent/common-components";
import AuthLayout from "modules/containers/AuthLayout";
import ComingSoonPage from "modules/components/EmptyPageComponent/ComingSoon";
import Cookies from "js-cookie";
-import LoginForm from "modules/components/AuthComponent/LoginForm";
import MainLayout from "modules/containers/MainLayout";
import NoMatchingPage from "modules/components/EmptyPageComponent/NoMatchingPage";
import OverviewComponent from "modules/components/OverviewComponent";
import ProfileComponent from "modules/components/ProfileComponent";
-import SignupForm from "modules/components/AuthComponent/SignupForm";
import TableOfContent from "modules/components/TableOfContent";
import TableWithFavorite from "modules/components/TableComponent";
import favicon from "./assets/logo/favicon.ico";
import { fetchEndpoints } from "./actions/endpointAction";
-import { getUserDetails } from "actions/authActions";
import { showToast } from "actions/toastActions";
-import { useDispatch } from "react-redux";
+import { useDispatch, useSelector } from "react-redux";
+import { ReactKeycloakProvider } from "@react-keycloak/web";
-const ProtectedRoute = ({ redirectPath = APP_ROUTES.AUTH_LOGIN, children }) => {
+const ProtectedRoute = ({ redirectPath = APP_ROUTES.AUTH, children }) => {
const loggedIn = Cookies.get("isLoggedIn");
const dispatch = useDispatch();
@@ -47,56 +45,68 @@ const HomeRoute = ({ redirectPath = APP_ROUTES.HOME }) => {
const App = () => {
const dispatch = useDispatch();
+ const { keycloak } = useSelector((state) => state.apiEndpoint);
useEffect(() => {
const faviconLogo = document.getElementById("favicon");
faviconLogo?.setAttribute("href", favicon);
dispatch(fetchEndpoints);
- dispatch(getUserDetails());
}, [dispatch]);
return (
-
-
- }>
-
- }>
- } />
- } />
- } />
-
- }>
- } />
- }>
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
+ {keycloak && (
+
+
+
+ }>
+
+ }>
+ } />
+
+ }>
+ } />
+ }>
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+
+ }
+ />
+
+ } />
- } />
-
- } />
-
-
-
+
+
+
+ )}
);
};
diff --git a/dashboard/src/actions/authActions.js b/dashboard/src/actions/authActions.js
index 94138224ae..458bfcddf3 100644
--- a/dashboard/src/actions/authActions.js
+++ b/dashboard/src/actions/authActions.js
@@ -2,203 +2,70 @@ import * as APP_ROUTES from "utils/routeConstants";
import * as CONSTANTS from "../assets/constants/authConstants";
import * as TYPES from "./types";
-import API from "../utils/axiosInstance";
import Cookies from "js-cookie";
import { SUCCESS } from "assets/constants/overviewConstants";
-import { showToast } from "actions/toastActions";
-import { uid } from "../utils/helper";
+import { showToast, clearToast } from "actions/toastActions";
-
-// Create an Authentication Request
-export const authenticationRequest = () => async (dispatch, getState) => {
- try {
- const endpoints = getState().apiEndpoint.endpoints;
- const oidcServer = endpoints.openid.server;
- const oidcRealm = endpoints.openid.realm;
- const oidcClient = endpoints.openid.client;
- // URI parameters ref: https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint
- // Refer Step 3 of pbench/docs/user_authentication/third_party_token_management.md
- const uri = `${oidcServer}/realms/${oidcRealm}/protocol/openid-connect/auth`;
- const queryParams = [
- 'client_id=' + oidcClient,
- 'response_type=code',
- 'redirect_uri=' + window.location.href.split('?')[0],
- 'scope=profile',
- 'prompt=login',
- 'max_age=120'
- ];
- window.location.href = uri + '?' + queryParams.join('&');
- } catch (error) {
- const alerts = getState().userAuth.alerts;
- dispatch(error?.response
- ? toggleLoginBtn(true)
- : { type: TYPES.OPENID_ERROR }
- );
- const alert = {
- title: error?.response ? error.response.data?.message : error?.message,
- key: uid(),
- };
- dispatch({
- type: TYPES.USER_NOTION_ALERTS,
- payload: [...alerts, alert],
- });
- dispatch({ type: TYPES.COMPLETED });
+/**
+ * Wait for the Pbench Server endpoints to be loaded.
+ * @param {getState} getState object.
+ * @return {promise} promise object
+ */
+export function waitForEndpoints(getState) {
+ const waitStart = Date.now();
+ /**
+ * Settle the wait-for-endpoints promise.
+ * @param {resolve} resolve object.
+ * @param {reject} reject object
+ */
+ function check(resolve, reject) {
+ if (Object.keys(getState().apiEndpoint.endpoints).length !== 0) {
+ resolve("Endpoints loaded");
+ } else if (Date.now() - waitStart > CONSTANTS.MAX_ENDPOINTS_WAIT_MS) {
+ reject(new Error("Timed out waiting for endpoints request"));
+ } else {
+ setTimeout(check, 250, resolve, reject);
}
-};
-
-export const makeLoginRequest =
- (details, navigate) => async (dispatch, getState) => {
- try {
- dispatch({ type: TYPES.LOADING });
- // empty the alerts
- dispatch({
- type: TYPES.USER_NOTION_ALERTS,
- payload: [],
- });
- const endpoints = getState().apiEndpoint.endpoints;
- const response = await API.post(endpoints?.api?.login, {
- ...details,
- });
- if (response.status === 200 && Object.keys(response.data).length > 0) {
- const keepUser = getState().userAuth.keepLoggedIn;
- const expiryTime = keepUser
- ? CONSTANTS.EXPIRY_KEEPUSER_DAYS
- : CONSTANTS.EXPIRY_DEFAULT_DAYS;
- Cookies.set("isLoggedIn", true, { expires: expiryTime });
- Cookies.set("token", response.data?.auth_token, {
- expires: expiryTime,
- });
- Cookies.set("username", response.data?.username, {
- expires: expiryTime,
- });
- const loginDetails = {
- isLoggedIn: true,
- token: response.data?.auth_token,
- username: response.data?.username,
- };
- await dispatch({
- type: TYPES.SET_LOGIN_DETAILS,
- payload: loginDetails,
- });
-
- navigate(APP_ROUTES.OVERVIEW);
+ }
+ return new Promise((resolve, reject) => check(resolve, reject));
+}
- dispatch(showToast(SUCCESS, "Logged in successfully!"));
- }
- dispatch({ type: TYPES.COMPLETED });
- } catch (error) {
- const alerts = getState().userAuth.alerts;
- let alert = {};
- if (error?.response) {
- alert = {
- title: error?.response?.data?.message,
- key: uid(),
- };
- dispatch(toggleLoginBtn(true));
- } else {
- alert = {
- title: error?.message,
- key: uid(),
- };
- dispatch({ type: TYPES.NETWORK_ERROR });
- }
- alerts.push(alert);
- dispatch({
- type: TYPES.USER_NOTION_ALERTS,
- payload: alerts,
- });
- dispatch({ type: TYPES.COMPLETED });
- }
- };
+// Perform some house keeping when the user logs in
+export const authCookies = async (dispatch, getState) => {
+ await waitForEndpoints(getState);
+ const keycloak = getState().apiEndpoint.keycloak;
+ if (keycloak.authenticated) {
+ // Set the isLoggedIn cookie with an expiry of OIDC refresh token.
+ // We have to convert the UNIX epoch seconds returned by the refresh token
+ // expiry to milliseconds before we can use it for creating a Date object.
+ Cookies.set("isLoggedIn", true, {
+ expires: new Date(keycloak.refreshTokenParsed.exp * 1000),
+ });
+ dispatch(showToast(SUCCESS, "Logged in successfully!"));
+ }
+};
export const movePage = (toPage, navigate) => async (dispatch) => {
- // empty the alerts
- dispatch({
- type: TYPES.USER_NOTION_ALERTS,
- payload: [],
- });
+ // clear all the toasts before navigating to another page
+ dispatch(clearToast());
navigate(toPage);
};
-export const setUserLoggedInState = (value) => async (dispatch) => {
- dispatch({
- type: TYPES.KEEP_USER_LOGGED_IN,
- payload: value,
- });
-};
-
-export const registerUser =
- (details, navigate) => async (dispatch, getState) => {
- try {
- dispatch({ type: TYPES.LOADING });
- // empty the alerts
- dispatch({
- type: TYPES.USER_NOTION_ALERTS,
- payload: [],
- });
- const endpoints = getState().apiEndpoint.endpoints;
- const response = await API.post(endpoints?.api?.register, {
- ...details,
- });
- if (response.status === 201) {
- dispatch(showToast(SUCCESS, "Account created!", "Login to continue"));
- navigate(APP_ROUTES.AUTH_LOGIN);
- }
- dispatch({ type: TYPES.COMPLETED });
- } catch (error) {
- const alerts = getState().userAuth.alerts;
- let amsg = {};
- document.querySelector(".signup-card").scrollTo(0, 0);
- if (error?.response) {
- amsg = error?.response?.data?.message;
- dispatch(toggleSignupBtn(true));
- } else {
- amsg = error?.message;
- dispatch({ type: TYPES.NETWORK_ERROR });
- }
- const alert = { title: amsg, key: uid() };
- alerts.push(alert);
- dispatch({
- type: TYPES.USER_NOTION_ALERTS,
- payload: alerts,
- });
- dispatch({ type: TYPES.COMPLETED });
- }
- };
-
-export const toggleSignupBtn = (isDisabled) => async (dispatch) => {
- dispatch({
- type: TYPES.SET_SIGNUP_BUTTON,
- payload: isDisabled,
- });
-};
-
-export const toggleLoginBtn = (isDisabled) => async (dispatch) => {
- dispatch({
- type: TYPES.SET_LOGIN_BUTTON,
- payload: isDisabled,
- });
-};
-
-export const getUserDetails = () => async (dispatch) => {
- const loginDetails = {
- isLoggedIn: Cookies.get("isLoggedIn"),
- token: Cookies.get("token"),
- username: Cookies.get("username"),
- };
- dispatch({
- type: TYPES.SET_LOGIN_DETAILS,
- payload: loginDetails,
- });
-};
-export const logout = () => async (dispatch) => {
+/**
+ * Clear the local cookies and re-direct to the auth page.
+ * @param {dispatch} dispatch object.
+ */
+export function clearCachedSession(dispatch) {
dispatch({ type: TYPES.LOADING });
- const keys = ["username", "token", "isLoggedIn"];
- for (const key of keys) {
- Cookies.remove(key);
- }
+ Cookies.remove("isLoggedIn");
dispatch({ type: TYPES.COMPLETED });
setTimeout(() => {
window.location.href = APP_ROUTES.AUTH;
}, CONSTANTS.LOGOUT_DELAY_MS);
+}
+
+export const sessionLogout = () => async (dispatch, getState) => {
+ const keycloak = getState().apiEndpoint.keycloak;
+ keycloak.logout();
+ clearCachedSession(dispatch);
};
diff --git a/dashboard/src/actions/endpointAction.js b/dashboard/src/actions/endpointAction.js
index c7020d1995..4b107049d7 100644
--- a/dashboard/src/actions/endpointAction.js
+++ b/dashboard/src/actions/endpointAction.js
@@ -1,4 +1,5 @@
import * as TYPES from "./types";
+import Keycloak from "keycloak-js";
export const fetchEndpoints = async (dispatch) => {
try {
@@ -12,6 +13,15 @@ export const fetchEndpoints = async (dispatch) => {
type: TYPES.SET_ENDPOINTS,
payload: data,
});
+ const keycloak = new Keycloak({
+ url: data.openid.server,
+ realm: data.openid.realm,
+ clientId: data.openid.client,
+ });
+ dispatch({
+ type: TYPES.SET_KEYCLOAK,
+ payload: keycloak,
+ });
} catch (error) {
dispatch({
type: TYPES.SHOW_TOAST,
diff --git a/dashboard/src/actions/overviewActions.js b/dashboard/src/actions/overviewActions.js
index 48cd0e0e30..e91bf89335 100644
--- a/dashboard/src/actions/overviewActions.js
+++ b/dashboard/src/actions/overviewActions.js
@@ -7,6 +7,7 @@ import API from "../utils/axiosInstance";
import { expandUriTemplate } from "../utils/helper";
import { findNoOfDays } from "utils/dateFunctions";
import { showToast } from "./toastActions";
+import { clearCachedSession } from "./authActions";
export const getDatasets = () => async (dispatch, getState) => {
const alreadyRendered = getState().overview.loadingDone;
@@ -42,8 +43,17 @@ export const getDatasets = () => async (dispatch, getState) => {
}
}
} catch (error) {
- dispatch(showToast(DANGER, error?.response?.data?.message ?? ERROR_MSG));
- dispatch({ type: TYPES.NETWORK_ERROR });
+ if (!error?.response) {
+ dispatch(showToast(DANGER, "Not Authenticated"));
+ dispatch({ type: TYPES.OPENID_ERROR });
+ clearCachedSession(dispatch);
+ } else {
+ const msg = error.response?.data?.message;
+ dispatch(
+ showToast(DANGER, msg ? msg : `Error response: ERROR_MSG`)
+ );
+ dispatch({ type: TYPES.NETWORK_ERROR });
+ }
}
if (alreadyRendered) {
dispatch({ type: TYPES.COMPLETED });
diff --git a/dashboard/src/actions/profileActions.js b/dashboard/src/actions/profileActions.js
deleted file mode 100644
index 14ae1a6c13..0000000000
--- a/dashboard/src/actions/profileActions.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import * as TYPES from "./types";
-
-import { showFailureToast, showToast } from "./toastActions";
-
-import API from "../utils/axiosInstance";
-
-export const getProfileDetails = () => async (dispatch, getState) => {
- try {
- dispatch({ type: TYPES.LOADING });
-
- const username = getState().userAuth.loginDetails.username;
- const endpoints = getState().apiEndpoint.endpoints;
-
- const response = await API.get(`${endpoints?.api?.user}/${username}`);
-
- if (response.status === 200 && Object.keys(response.data).length > 0) {
- dispatch({
- type: TYPES.GET_USER_DETAILS,
- payload: response?.data,
- });
- } else {
- dispatch(showFailureToast());
- }
- dispatch({ type: TYPES.COMPLETED });
- } catch (error) {
- dispatch(showToast("danger", error?.response?.data?.message));
- dispatch({ type: TYPES.NETWORK_ERROR });
- dispatch({ type: TYPES.COMPLETED });
- }
-};
-
-export const updateUserDetails =
- (value, fieldName) => async (dispatch, getState) => {
- const userDetails = { ...getState().userProfile.userDetails };
- const updatedUserDetails = { ...getState().userProfile.updatedUserDetails };
-
- userDetails[fieldName] = value;
- updatedUserDetails[fieldName] = value;
- const payload = {
- userDetails,
- updatedUserDetails,
- };
- dispatch({
- type: TYPES.UPDATE_USER_DETAILS,
- payload,
- });
- };
-
-export const sendForUpdate = () => async (dispatch, getState) => {
- try {
- dispatch({ type: TYPES.LOADING });
-
- const username = getState().userAuth.loginDetails.username;
- const endpoints = getState().apiEndpoint.endpoints;
-
- if (username) {
- const response = await API.put(`${endpoints?.api?.user}/${username}`, {
- ...getState().userProfile.updatedUserDetails,
- });
- if (response.status === 200) {
- dispatch(showToast("success", "Updated!"));
- dispatch({
- type: TYPES.GET_USER_DETAILS,
- payload: response?.data,
- });
- dispatch({ type: TYPES.RESET_DATA });
- } else {
- dispatch(showFailureToast());
- }
- dispatch({ type: TYPES.COMPLETED });
- }
- } catch (error) {
- dispatch(showToast("danger", error?.response?.data?.message));
- dispatch({ type: TYPES.NETWORK_ERROR });
- dispatch({ type: TYPES.COMPLETED });
- }
-};
-
-export const resetUserDetails = () => async (dispatch, getState) => {
- dispatch({
- type: TYPES.SET_USER_DETAILS,
- payload: getState().userProfile.userDetails_copy,
- });
- dispatch({ type: TYPES.RESET_DATA });
-};
diff --git a/dashboard/src/actions/toastActions.js b/dashboard/src/actions/toastActions.js
index 6e22673280..93eb4c4fef 100644
--- a/dashboard/src/actions/toastActions.js
+++ b/dashboard/src/actions/toastActions.js
@@ -1,7 +1,7 @@
import * as TYPES from "./types";
-import { logout } from "./authActions";
import { uid } from "utils/helper";
+import { clearCachedSession } from "./authActions";
export const showSessionExpired = () => async (dispatch) => {
const toast = {
@@ -10,7 +10,7 @@ export const showSessionExpired = () => async (dispatch) => {
message: "Please login to continue",
};
dispatch(showToast(toast.variant, toast.title, toast.message));
- dispatch(logout());
+ clearCachedSession(dispatch);
};
export const showFailureToast = () => async (dispatch) => {
diff --git a/dashboard/src/actions/types.js b/dashboard/src/actions/types.js
index 625af5b4fe..ee559da137 100644
--- a/dashboard/src/actions/types.js
+++ b/dashboard/src/actions/types.js
@@ -1,5 +1,6 @@
/* ENDPOINTS */
export const SET_ENDPOINTS = "SET_ENDPOINTS";
+export const SET_KEYCLOAK = "SET_KEYCLOAK";
/* TOAST */
export const SHOW_TOAST = "SHOW_TOAST";
@@ -14,10 +15,7 @@ export const OPENID_ERROR = "OPENID_ERROR";
export const DASHBOARD_LOADING = "DASHBOARD_LOADING";
/* USER AUTHENTICATION */
-export const KEEP_USER_LOGGED_IN = "KEEP_USER_LOGGED_IN";
export const USER_NOTION_ALERTS = "USER_NOTION_ALERTS";
-export const SET_LOGIN_BUTTON = "SET_LOGIN_BUTTON";
-export const SET_SIGNUP_BUTTON = "SET_SIGNUP_BUTTON";
/* NAVBAR OPEN/CLOSE */
export const NAVBAR_OPEN = "NAVBAR_OPEN";
@@ -27,13 +25,6 @@ export const NAVBAR_CLOSE = "NAVBAR_CLOSE";
export const GET_PUBLIC_DATASETS = "GET_PUBLIC_DATASETS";
export const FAVORITED_DATASETS = "GET_FAVORITE_DATASETS";
export const UPDATE_PUBLIC_DATASETS = "UPDATE_PUBLIC_DATASETS";
-export const SET_LOGIN_DETAILS = "SET_LOGIN_DETAILS";
-
-/* USER DETAILS */
-export const GET_USER_DETAILS = "GET_USER_DETAILS";
-export const UPDATE_USER_DETAILS = "UPDATE_USER_DETAILS";
-export const RESET_DATA = "RESET_DATA";
-export const SET_USER_DETAILS = "SET_USER_DETAILS";
/* DASHBOARD OVERVIEW */
export const USER_RUNS = "USER_RUNS";
diff --git a/dashboard/src/assets/constants/authConstants.js b/dashboard/src/assets/constants/authConstants.js
index dd06176980..569d6ffac1 100644
--- a/dashboard/src/assets/constants/authConstants.js
+++ b/dashboard/src/assets/constants/authConstants.js
@@ -1,3 +1,2 @@
export const LOGOUT_DELAY_MS = 2000;
-export const EXPIRY_KEEPUSER_DAYS = 7;
-export const EXPIRY_DEFAULT_DAYS = 0.5;
+export const MAX_ENDPOINTS_WAIT_MS = 10000;
diff --git a/dashboard/src/modules/components/AuthComponent/LoginForm.jsx b/dashboard/src/modules/components/AuthComponent/LoginForm.jsx
deleted file mode 100644
index 587778a89e..0000000000
--- a/dashboard/src/modules/components/AuthComponent/LoginForm.jsx
+++ /dev/null
@@ -1,170 +0,0 @@
-import * as APP_ROUTES from "utils/routeConstants";
-
-import {
- Alert,
- AlertGroup,
- Button,
- Card,
- CardBody,
- CardFooter,
- CardTitle,
- Checkbox,
- Form,
- FormGroup,
- TextInput,
-} from "@patternfly/react-core";
-import {
- Back,
- LoginHeader,
- NoLoginComponent,
- PasswordTextInput,
-} from "./common-components";
-import { EyeIcon, EyeSlashIcon } from "@patternfly/react-icons";
-import React, { useCallback, useEffect, useState } from "react";
-import { makeLoginRequest, setUserLoggedInState } from "actions/authActions";
-import { useDispatch, useSelector } from "react-redux";
-
-import { useOutletContext } from "react-router-dom";
-
-const LoginForm = () => {
- const dispatch = useDispatch();
- const navigate = useOutletContext();
- const alerts = useSelector((state) => state.userAuth.alerts);
- const [details, setDetails] = useState({
- password: "",
- username: "",
- });
- const [btnDisabled, setBtnDisabled] = useState(true);
- const [showPassword, setShowPassword] = useState(false);
-
- const { endpoints } = useSelector((state) => state.apiEndpoint);
- const isLoading = useSelector((state) => state.loading.isLoading);
-
- const primaryLoadingProps = {};
- if (isLoading) {
- primaryLoadingProps.spinnerAriaValueText = "Loading";
- primaryLoadingProps.spinnerAriaLabelledBy = "primary-loading-button";
- primaryLoadingProps.isLoading = true;
- }
- const handleUsernameChange = (value) => {
- setDetails({
- ...details,
- username: value,
- });
- };
- const handlePasswordChange = (value) => {
- setDetails({
- ...details,
- password: value,
- });
- };
- const sendLoginDetails = () => {
- dispatch(makeLoginRequest(details, navigate));
- };
- const checkOkButton = useCallback(() => {
- if (
- details.username?.length > 0 &&
- details.password?.length > 0 &&
- Object.keys(endpoints).length > 0
- ) {
- setBtnDisabled(false);
- } else {
- setBtnDisabled(true);
- }
- }, [details, endpoints]);
-
- const keepUser = useSelector((state) => state.userAuth.keepLoggedIn);
- const checkBoxChangeHander = (value) => {
- dispatch(setUserLoggedInState(value));
- };
- const onShowPassword = () => {
- setShowPassword(!showPassword);
- };
- useEffect(() => {
- checkOkButton();
- }, [checkOkButton, details]);
-
- const handleKeypress = (e) => {
- // it triggers by pressing the enter key
- if (e.charCode === 13) {
- sendLoginDetails();
- }
- };
- return (
-
-
-
-
-
- {alerts.map(({ title, key }) => (
-
- ))}
-
-
-
-
-
-
-
-
-
-
- -or-
-
-
-
-
-
- );
-};
-
-export default LoginForm;
diff --git a/dashboard/src/modules/components/AuthComponent/SignupForm.jsx b/dashboard/src/modules/components/AuthComponent/SignupForm.jsx
deleted file mode 100644
index 4cbf0333d1..0000000000
--- a/dashboard/src/modules/components/AuthComponent/SignupForm.jsx
+++ /dev/null
@@ -1,223 +0,0 @@
-import * as APP_ROUTES from "utils/routeConstants";
-
-import {
- Alert,
- AlertGroup,
- Button,
- Card,
- CardBody,
- CardFooter,
- CardTitle,
- Form,
- FormGroup,
- TextInput,
-} from "@patternfly/react-core";
-import {
- Back,
- LoginHeader,
- NoLoginComponent,
- PasswordConstraints,
- PasswordTextInput,
-} from "./common-components";
-import { EyeIcon, EyeSlashIcon } from "@patternfly/react-icons";
-import React, { useCallback, useEffect, useState } from "react";
-import { registerUser, toggleSignupBtn } from "actions/authActions";
-import { useDispatch, useSelector } from "react-redux";
-import { validateEmail, validatePassword } from "utils/helper.js";
-
-import { signupFormData } from "./signupFormData";
-import { useOutletContext } from "react-router-dom";
-
-const SignupForm = () => {
- const dispatch = useDispatch();
- const navigate = useOutletContext();
- const { endpoints } = useSelector((state) => state.apiEndpoint);
- const { alerts, isSignupBtnDisabled, passwordLength } = useSelector(
- (state) => state.userAuth
- );
- const [showPassword, setShowPassword] = useState(false);
- const [userDetails, setUserDetails] = useState({
- firstName: "",
- lastName: "",
- userName: "",
- email: "",
- password: "",
- passwordConfirm: "",
- });
- const [errors, setErrors] = useState({
- firstName: "",
- lastName: "",
- userName: "",
- email: "",
- passwordConstraints: "",
- passwordConfirm: "",
- });
- const [constraints, setConstraints] = useState({
- passwordLength: "indeterminate",
- passwordSpecialChars: "indeterminate",
- passwordContainsNumber: "indeterminate",
- passwordBlockLetter: "indeterminate",
- });
-
- const validateForm = useCallback(() => {
- if (
- userDetails.firstName?.trim() === "" ||
- userDetails.lastName?.trim() === "" ||
- userDetails.userName?.trim() === "" ||
- userDetails.email?.trim() === "" ||
- userDetails.password?.trim() === "" ||
- userDetails.passwordConfirm?.trim() === ""
- ) {
- return false;
- }
- // check if no errors.
- for (const dep of Object.entries(errors)) {
- if (dep[1].length > 0) {
- return false;
- }
- }
- // check if all constraints are met.
- for (const ct of Object.entries(constraints)) {
- if (ct[1] !== "success") {
- return false;
- }
- }
- // if we reach here, it means
- // we have covered all of the edge cases.
- return true;
- }, [constraints, errors, userDetails]);
-
- const onShowPassword = () => {
- setShowPassword(!showPassword);
- };
-
- useEffect(() => {
- if (validateForm() && Object.keys(endpoints).length > 0) {
- dispatch(toggleSignupBtn(false));
- } else {
- dispatch(toggleSignupBtn(true));
- }
- }, [validateForm, userDetails, dispatch, endpoints]);
-
- const checkPasswordError = (password, cnfPassword) => {
- if (cnfPassword.length > 1 && password !== cnfPassword) {
- setErrors({
- ...errors,
- passwordConfirm: "The above passwords do not match!",
- });
- } else {
- setErrors({ ...errors, passwordConfirm: "" });
- }
- };
- const changeHandler = (value, fieldName) => {
- setUserDetails({
- ...userDetails,
- [fieldName]: value,
- });
- if (fieldName === "email") {
- const isEmailValid = validateEmail(value);
- setErrors({
- ...errors,
- ...isEmailValid,
- });
- } else if (fieldName === "password") {
- const validPassword = validatePassword(value, passwordLength);
- setConstraints({
- ...constraints,
- ...validPassword,
- });
- // edge case where user deliberately
- // edits the password field, even when
- // confirm password is not empty.
- checkPasswordError(value, userDetails.confirmPassword);
- } else if (fieldName === "passwordConfirm") {
- checkPasswordError(userDetails.password, value);
- }
- };
- const sendForRegistration = () => {
- const details = {
- email: userDetails.email,
- password: userDetails.password,
- first_name: userDetails.firstName,
- last_name: userDetails.lastName,
- username: userDetails.userName,
- };
- dispatch(registerUser({ ...details }, navigate));
- };
- return (
-
-
-
-
-
- {alerts.map(({ title, key }) => (
-
- ))}
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default SignupForm;
diff --git a/dashboard/src/modules/components/AuthComponent/common-components.jsx b/dashboard/src/modules/components/AuthComponent/common-components.jsx
index 3520a00935..a7698d50ac 100644
--- a/dashboard/src/modules/components/AuthComponent/common-components.jsx
+++ b/dashboard/src/modules/components/AuthComponent/common-components.jsx
@@ -5,26 +5,21 @@ import * as APP_ROUTES from "utils/routeConstants";
import {
Button,
Card,
- CardBody,
CardFooter,
CardTitle,
Flex,
FlexItem,
- HelperText,
- HelperTextItem,
- TextInput,
Title,
} from "@patternfly/react-core";
-import { CheckIcon, CloseIcon, TimesIcon } from "@patternfly/react-icons";
-import { useDispatch, useSelector } from "react-redux";
+import { useDispatch } from "react-redux";
import { useNavigate, useOutletContext } from "react-router-dom";
-import { authenticationRequest } from "actions/authActions";
+import { authCookies } from "actions/authActions";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import PBenchLogo from "assets/logo/pbench_logo.svg";
-import React from "react";
+import React, { useEffect } from "react";
import { faAngleLeft } from "@fortawesome/free-solid-svg-icons";
import { movePage } from "actions/authActions";
-import { passwordConstraintsText } from "./signupFormData";
+import { useKeycloak } from "@react-keycloak/web";
export const LoginHeader = (props) => {
return {props?.title};
@@ -80,46 +75,27 @@ export const LoginRightComponent = () => {
export const AuthForm = () => {
const navigate = useOutletContext();
+ const { keycloak } = useKeycloak();
const dispatch = useDispatch();
const navigatePage = (toPage) => {
dispatch(movePage(toPage, navigate));
};
+ useEffect(() => {
+ dispatch(authCookies);
+ });
return (
-
-
-
-
-
-
- Need an account?
-
-
-
-
-
-
-
- Or log in with...
@@ -130,38 +106,6 @@ export const AuthForm = () => {
);
};
-export const PasswordConstraints = (props) => {
- const { checkConstraints } = props;
- const iconList = {
- indeterminate:
,
- success:
,
- error:
,
- };
- const passwordLength = useSelector((state) => state.userAuth.passwordLength);
- return (
- <>
-
Passwords must contain at least:
-
- {passwordConstraintsText.map((constraint, index) => {
- const variant = checkConstraints[constraint.name];
- return (
-
-
- {constraint.name === "passwordLength" && passwordLength}{" "}
- {constraint.label}
-
-
- );
- })}
-
- >
- );
-};
-
export const NoLoginComponent = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
@@ -181,19 +125,3 @@ export const NoLoginComponent = () => {
);
};
-
-export const PasswordTextInput = (props) => {
- const { isRequired, id, name, onChangeMethod, value, isShowPassword } = props;
- return (
- onChangeMethod(val, name)}
- onKeyPress={props.onKeyPress}
- />
- );
-};
diff --git a/dashboard/src/modules/components/AuthComponent/signupFormData.js b/dashboard/src/modules/components/AuthComponent/signupFormData.js
deleted file mode 100644
index 665dc701f7..0000000000
--- a/dashboard/src/modules/components/AuthComponent/signupFormData.js
+++ /dev/null
@@ -1,69 +0,0 @@
-export const signupFormData = [
- {
- key: 1,
- label: "First name",
- id: "horizontal-form-first-name",
- name: "firstName",
- isRequired: true,
- type: "text",
- },
- {
- key: 2,
- label: "Last name",
- id: "horizontal-form-last-name",
- name: "lastName",
- isRequired: true,
- type: "text",
- },
- {
- key: 3,
- label: "User name",
- id: "horizontal-form-user-name",
- name: "userName",
- isRequired: true,
- type: "text",
- },
- {
- key: 4,
- label: "Email address",
- id: "horizontal-form-email-address",
- name: "email",
- isRequired: true,
- type: "text",
- },
- {
- key: 5,
- label: "Password",
- id: "horizontal-form-password",
- name: "password",
- isRequired: true,
- type: "password",
- },
- {
- key: 6,
- label: "Confirm password",
- id: "horizontal-form-confirm-password",
- name: "passwordConfirm",
- isRequired: true,
- type: "password",
- },
-];
-
-export const passwordConstraintsText = [
- {
- label: "characters",
- name: "passwordLength",
- },
- {
- label: "1 special character",
- name: "passwordSpecialChars",
- },
- {
- label: "1 number",
- name: "passwordContainsNumber",
- },
- {
- label: "1 uppercase letter",
- name: "passwordBlockLetter",
- },
-];
diff --git a/dashboard/src/modules/components/HeaderComponent/index.jsx b/dashboard/src/modules/components/HeaderComponent/index.jsx
index ea34080021..841477f94d 100644
--- a/dashboard/src/modules/components/HeaderComponent/index.jsx
+++ b/dashboard/src/modules/components/HeaderComponent/index.jsx
@@ -31,23 +31,26 @@ import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useLocation, useNavigate } from "react-router-dom";
-import Cookies from "js-cookie";
-import { logout } from "actions/authActions";
+import { sessionLogout } from "actions/authActions";
import pbenchLogo from "assets/logo/pbench_logo.svg";
+import { useKeycloak } from "@react-keycloak/web";
+import { movePage } from "actions/authActions";
const HeaderToolbar = () => {
const dispatch = useDispatch();
- const loginDetails = useSelector((state) => state.userAuth.loginDetails);
+ const { keycloak } = useKeycloak();
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const navigate = useNavigate();
const { pathname } = useLocation();
- const isLoggedIn = Cookies.get("isLoggedIn");
+ const navigatePage = (toPage) => {
+ dispatch(movePage(toPage, navigate));
+ };
const onDropdownSelect = (event) => {
const type = event.target.name;
const menuOptions = {
profile: () => navigate(APP_ROUTES.USER_PROFILE),
- logout: () => dispatch(logout()),
+ logout: () => dispatch(sessionLogout()),
};
const action = menuOptions[type];
if (action) {
@@ -91,14 +94,14 @@ const HeaderToolbar = () => {
- {isLoggedIn ? (
+ {keycloak.authenticated ? (
- {loginDetails?.username}
+ {keycloak.tokenParsed.preferred_username}
}
dropdownItems={userDropdownItems}
@@ -108,7 +111,7 @@ const HeaderToolbar = () => {
aria-label="Login"
className="header-login-button"
variant={ButtonVariant.plain}
- onClick={() => navigate(APP_ROUTES.AUTH_LOGIN)}
+ onClick={() => navigatePage(APP_ROUTES.AUTH)}
>
Login
diff --git a/dashboard/src/modules/components/OverviewComponent/index.jsx b/dashboard/src/modules/components/OverviewComponent/index.jsx
index 079e552c3c..613a4b3153 100644
--- a/dashboard/src/modules/components/OverviewComponent/index.jsx
+++ b/dashboard/src/modules/components/OverviewComponent/index.jsx
@@ -28,7 +28,6 @@ import { getDatasets } from "actions/overviewActions";
const OverviewComponent = () => {
const dispatch = useDispatch();
const { endpoints } = useSelector((state) => state.apiEndpoint);
- const { loginDetails } = useSelector((state) => state.userAuth);
const { expiringRuns, savedRuns, newRuns, loadingDone } = useSelector(
(state) => state.overview
);
@@ -39,7 +38,7 @@ const OverviewComponent = () => {
if (Object.keys(endpoints).length > 0) {
dispatch(getDatasets());
}
- }, [dispatch, endpoints, loginDetails]);
+ }, [dispatch, endpoints]);
const onToggle = (id) => {
if (expanded.has(id)) {
diff --git a/dashboard/src/modules/components/ProfileComponent/index.jsx b/dashboard/src/modules/components/ProfileComponent/index.jsx
index 6f1fc6bf2e..076c97b669 100644
--- a/dashboard/src/modules/components/ProfileComponent/index.jsx
+++ b/dashboard/src/modules/components/ProfileComponent/index.jsx
@@ -1,7 +1,5 @@
-import React, { useEffect, useState } from "react";
-import { useDispatch, useSelector } from "react-redux";
+import React from "react";
import {
- Button,
Card,
CardBody,
Grid,
@@ -10,47 +8,16 @@ import {
LevelItem,
Text,
TextContent,
- TextInput,
TextVariants,
isValidDate,
} from "@patternfly/react-core";
-import { KeyIcon, PencilAltIcon, UserAltIcon } from "@patternfly/react-icons";
+import { KeyIcon, UserAltIcon } from "@patternfly/react-icons";
import "./index.less";
import avatar from "assets/images/avatar.jpg";
-import {
- getProfileDetails,
- resetUserDetails,
- sendForUpdate,
- updateUserDetails,
-} from "actions/profileActions";
+import { useKeycloak } from "@react-keycloak/web";
const ProfileComponent = () => {
- const [editView, setEditView] = useState(false);
- const dispatch = useDispatch();
-
- const user = useSelector((state) => state.userProfile.userDetails);
- const loginDetails = useSelector((state) => state.userAuth.loginDetails);
- const { endpoints } = useSelector((state) => state.apiEndpoint);
-
- const isUserDetailsUpdated = useSelector(
- (state) => state.userProfile.isUserDetailsUpdated
- );
-
- const edit = () => {
- if (editView) {
- dispatch(resetUserDetails());
- }
- setEditView(!editView);
- };
- const saveEdit = () => {
- dispatch(sendForUpdate());
- setEditView(false);
- };
- useEffect(() => {
- if (loginDetails?.username && Object.keys(endpoints).length > 0) {
- dispatch(getProfileDetails());
- }
- }, [dispatch, loginDetails?.username, endpoints]);
+ const { keycloak } = useKeycloak();
const formatDate = (date) => {
const registerDate = new Date(date);
@@ -58,9 +25,6 @@ const ProfileComponent = () => {
? registerDate.toLocaleDateString()
: "----";
};
- const handleInputChange = (value, name) => {
- dispatch(updateUserDetails(value, name));
- };
return (
@@ -78,14 +42,6 @@ const ProfileComponent = () => {
-
}
- onClick={edit}
- isDisabled={editView}
- >
- Edit
-
Profile Picture
@@ -95,110 +51,56 @@ const ProfileComponent = () => {
First Name
- {editView ? (
-
- handleInputChange(val, "first_name")
- }
- />
- ) : (
+ {
- {user?.first_name}
+ {keycloak.tokenParsed?.given_name
+ ? keycloak.tokenParsed.given_name
+ : ""}
- )}
+ }
Last Name
- {editView ? (
-
- handleInputChange(val, "last_name")
- }
- />
- ) : (
+ {
- {user?.last_name}
+ {keycloak.tokenParsed?.family_name
+ ? keycloak.tokenParsed.family_name
+ : ""}
- )}
+ }
User Name
- {editView ? (
-
- ) : (
+ {
- {user?.username}
+ {keycloak.tokenParsed?.preferred_username
+ ? keycloak.tokenParsed.preferred_username
+ : ""}
- )}
+ }
Email
- {editView ? (
- handleInputChange(val, "email")}
- />
- ) : (
+ {
- {user?.email}
+
+ {keycloak.tokenParsed?.email
+ ? keycloak.tokenParsed.email
+ : ""}
+
- )}
+ }
- {editView ? (
-
-
- {" "}
-
-
-
- ) : (
- <>>
- )}
+ {<>>}
@@ -213,9 +115,10 @@ const ProfileComponent = () => {
+ {/* TODO: How to handle account creation date */}
Account creation Date
- {formatDate(user?.registered_on)}
+ {formatDate("MM/DD/YYYY")}
diff --git a/dashboard/src/modules/components/SidebarComponent/index.jsx b/dashboard/src/modules/components/SidebarComponent/index.jsx
index 7f1a86e904..cebeabe86e 100644
--- a/dashboard/src/modules/components/SidebarComponent/index.jsx
+++ b/dashboard/src/modules/components/SidebarComponent/index.jsx
@@ -9,8 +9,7 @@ import React, { useEffect } from "react";
import { menuOptions, menuOptionsNonLoggedIn } from "./sideMenuOptions";
import { useDispatch, useSelector } from "react-redux";
import { useLocation, useNavigate } from "react-router-dom";
-
-import Cookies from "js-cookie";
+import { useKeycloak } from "@react-keycloak/web";
import { setActiveItem } from "actions/sideBarActions";
const MenuItem = ({ data, activeItem }) => {
@@ -34,7 +33,7 @@ const Menu = () => {
const { pathname } = useLocation();
const activeItem = useSelector((state) => state.sidebar.activeMenuItem);
- const isLoggedIn = Cookies.get("isLoggedIn");
+ const { keycloak } = useKeycloak();
const onSelect = (result) => {
dispatch(setActiveItem(result.itemId));
};
@@ -49,7 +48,7 @@ const Menu = () => {
return (