Compare commits

..

1 Commits

Author SHA1 Message Date
01d18902d7 more updates to allow process to be manually ran 2023-09-20 19:52:13 -05:00
7 changed files with 52 additions and 135 deletions

View File

@ -6,7 +6,7 @@
"@auth0/auth0-react": "1.10.2", "@auth0/auth0-react": "1.10.2",
"@emotion/react": "11.7.1", "@emotion/react": "11.7.1",
"@emotion/styled": "11.6.0", "@emotion/styled": "11.6.0",
"@kingsrook/qqq-frontend-core": "1.0.81", "@kingsrook/qqq-frontend-core": "1.0.80",
"@mui/icons-material": "5.4.1", "@mui/icons-material": "5.4.1",
"@mui/material": "5.11.1", "@mui/material": "5.11.1",
"@mui/styles": "5.11.1", "@mui/styles": "5.11.1",

View File

@ -33,7 +33,7 @@ import CssBaseline from "@mui/material/CssBaseline";
import Icon from "@mui/material/Icon"; import Icon from "@mui/material/Icon";
import {ThemeProvider} from "@mui/material/styles"; import {ThemeProvider} from "@mui/material/styles";
import {LicenseInfo} from "@mui/x-license-pro"; import {LicenseInfo} from "@mui/x-license-pro";
import React, {JSXElementConstructor, Key, ReactElement, useEffect, useState,} from "react"; import React, {JSXElementConstructor, Key, ReactElement, useContext, useEffect, useState,} from "react";
import {useCookies} from "react-cookie"; import {useCookies} from "react-cookie";
import {Navigate, Route, Routes, useLocation,} from "react-router-dom"; import {Navigate, Route, Routes, useLocation,} from "react-router-dom";
import {Md5} from "ts-md5/dist/md5"; import {Md5} from "ts-md5/dist/md5";
@ -57,11 +57,11 @@ import ProcessUtils from "qqq/utils/qqq/ProcessUtils";
const qController = Client.getInstance(); const qController = Client.getInstance();
export const SESSION_UUID_COOKIE_NAME = "sessionUUID"; export const SESSION_ID_COOKIE_NAME = "sessionId";
export default function App() export default function App()
{ {
const [, setCookie, removeCookie] = useCookies([SESSION_UUID_COOKIE_NAME]); const [, setCookie, removeCookie] = useCookies([SESSION_ID_COOKIE_NAME]);
const {user, getAccessTokenSilently, logout} = useAuth0(); const {user, getAccessTokenSilently, logout} = useAuth0();
const [loadingToken, setLoadingToken] = useState(false); const [loadingToken, setLoadingToken] = useState(false);
const [isFullyAuthenticated, setIsFullyAuthenticated] = useState(false); const [isFullyAuthenticated, setIsFullyAuthenticated] = useState(false);
@ -69,67 +69,8 @@ export default function App()
const [branding, setBranding] = useState({} as QBrandingMetaData); const [branding, setBranding] = useState({} as QBrandingMetaData);
const [metaData, setMetaData] = useState({} as QInstance); const [metaData, setMetaData] = useState({} as QInstance);
const [needLicenseKey, setNeedLicenseKey] = useState(true); const [needLicenseKey, setNeedLicenseKey] = useState(true);
const [loggedInUser, setLoggedInUser] = useState({} as { name?: string, email?: string });
const [defaultRoute, setDefaultRoute] = useState("/no-apps"); const [defaultRoute, setDefaultRoute] = useState("/no-apps");
const decodeJWT = (jwt: string): any =>
{
const base64Url = jwt.split(".")[1];
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
const jsonPayload = decodeURIComponent(window.atob(base64).split("").map(function (c)
{
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
}).join(""));
return JSON.parse(jsonPayload);
};
const shouldStoreNewToken = (newToken: string, oldToken: string): boolean =>
{
if (!oldToken)
{
return (true);
}
try
{
const oldJSON = decodeJWT(oldToken);
const newJSON = decodeJWT(newToken);
////////////////////////////////////////////////////////////////////////////////////
// if the old (local storage) token is expired, then we need to store the new one //
////////////////////////////////////////////////////////////////////////////////////
const oldExp = oldJSON["exp"];
if(oldExp * 1000 < (new Date().getTime()))
{
console.log("Access token in local storage was expired.");
return (true);
}
////////////////////////////////////////////////////////////////////////////////////////////////
// remove the exp & iat values from what we compare - as they are always different from auth0 //
// note, this is only deleting them from what we compare, not from what we'd store. //
////////////////////////////////////////////////////////////////////////////////////////////////
delete newJSON["exp"]
delete newJSON["iat"]
delete oldJSON["exp"]
delete oldJSON["iat"]
const different = JSON.stringify(newJSON) !== JSON.stringify(oldJSON);
if(different)
{
console.log("Latest access token from auth0 has changed vs localStorage.");
}
return (different);
}
catch(e)
{
console.log("Caught in shouldStoreNewToken: " + e)
}
return (true);
};
useEffect(() => useEffect(() =>
{ {
if (loadingToken) if (loadingToken)
@ -151,38 +92,20 @@ export default function App()
{ {
console.log("Loading token from auth0..."); console.log("Loading token from auth0...");
const accessToken = await getAccessTokenSilently(); const accessToken = await getAccessTokenSilently();
const lsAccessToken = localStorage.getItem("accessToken");
if (shouldStoreNewToken(accessToken, lsAccessToken))
{
console.log("Sending accessToken to backend, requesting a sessionUUID...");
const newSessionUuid = await qController.manageSession(accessToken, null);
setCookie(SESSION_UUID_COOKIE_NAME, newSessionUuid, {path: "/"});
localStorage.setItem("accessToken", accessToken);
}
/*
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// todo#authHeader - this is our quick rollback plan - if we feel the need to stop using the cookie approach. //
// we turn off the shouldStoreNewToken block above, and turn on these 2 lines. //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"});
localStorage.removeItem("accessToken");
*/
setIsFullyAuthenticated(true);
qController.setGotAuthentication();
qController.setAuthorizationHeaderValue("Bearer " + accessToken); qController.setAuthorizationHeaderValue("Bearer " + accessToken);
setLoggedInUser(user); /////////////////////////////////////////////////////////////////////////////////
// we've stopped using session id cook with auth0, so make sure it is not set. //
/////////////////////////////////////////////////////////////////////////////////
removeCookie(SESSION_ID_COOKIE_NAME);
setIsFullyAuthenticated(true);
console.log("Token load complete."); console.log("Token load complete.");
} }
catch (e) catch (e)
{ {
console.log(`Error loading token: ${JSON.stringify(e)}`); console.log(`Error loading token: ${JSON.stringify(e)}`);
qController.clearAuthenticationMetaDataLocalStorage(); qController.clearAuthenticationMetaDataLocalStorage();
localStorage.removeItem("accessToken")
removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"});
logout(); logout();
return; return;
} }
@ -193,9 +116,9 @@ export default function App()
// use a random token if anonymous or mock // // use a random token if anonymous or mock //
///////////////////////////////////////////// /////////////////////////////////////////////
console.log("Generating random token..."); console.log("Generating random token...");
qController.setAuthorizationHeaderValue(Md5.hashStr(`${new Date()}`)); qController.setAuthorizationHeaderValue(null);
setIsFullyAuthenticated(true); setIsFullyAuthenticated(true);
setCookie(SESSION_UUID_COOKIE_NAME, Md5.hashStr(`${new Date()}`), {path: "/"}); setCookie(SESSION_ID_COOKIE_NAME, Md5.hashStr(`${new Date()}`), {path: "/"});
console.log("Token generation complete."); console.log("Token generation complete.");
return; return;
} }
@ -506,11 +429,11 @@ export default function App()
let profileRoutes = {}; let profileRoutes = {};
const gravatarBase = "https://www.gravatar.com/avatar/"; const gravatarBase = "https://www.gravatar.com/avatar/";
const hash = Md5.hashStr(loggedInUser?.email || "user"); const hash = Md5.hashStr(user?.email || "user");
const profilePicture = `${gravatarBase}${hash}`; const profilePicture = `${gravatarBase}${hash}`;
profileRoutes = { profileRoutes = {
type: "collapse", type: "collapse",
name: loggedInUser?.name ?? "Anonymous", name: user?.name,
key: "username", key: "username",
noCollapse: true, noCollapse: true,
icon: <Avatar src={profilePicture} alt="{user?.name}" />, icon: <Avatar src={profilePicture} alt="{user?.name}" />,
@ -572,10 +495,7 @@ export default function App()
{ {
if ((e as QException).status === "401") if ((e as QException).status === "401")
{ {
console.log("Exception is a QException with status = 401. Clearing some of localStorage & cookies");
qController.clearAuthenticationMetaDataLocalStorage(); qController.clearAuthenticationMetaDataLocalStorage();
localStorage.removeItem("accessToken")
removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"});
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
// todo - this is auth0 logout... make more generic // // todo - this is auth0 logout... make more generic //

View File

@ -22,7 +22,7 @@
import {useAuth0} from "@auth0/auth0-react"; import {useAuth0} from "@auth0/auth0-react";
import React, {useEffect} from "react"; import React, {useEffect} from "react";
import {useCookies} from "react-cookie"; import {useCookies} from "react-cookie";
import {SESSION_UUID_COOKIE_NAME} from "App"; import {SESSION_ID_COOKIE_NAME} from "App";
interface Props interface Props
{ {
@ -33,13 +33,13 @@ interface Props
function HandleAuthorizationError({errorMessage}: Props) function HandleAuthorizationError({errorMessage}: Props)
{ {
const [, , removeCookie] = useCookies([SESSION_UUID_COOKIE_NAME]); const [, , removeCookie] = useCookies([SESSION_ID_COOKIE_NAME]);
const {logout} = useAuth0(); const {logout} = useAuth0();
useEffect(() => useEffect(() =>
{ {
logout(); logout();
removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"}); removeCookie(SESSION_ID_COOKIE_NAME, {path: "/"});
}); });
return ( return (

View File

@ -62,10 +62,12 @@ import {GoogleDriveFolderPickerWrapper} from "qqq/components/processes/GoogleDri
import ProcessSummaryResults from "qqq/components/processes/ProcessSummaryResults"; import ProcessSummaryResults from "qqq/components/processes/ProcessSummaryResults";
import ValidationReview from "qqq/components/processes/ValidationReview"; import ValidationReview from "qqq/components/processes/ValidationReview";
import BaseLayout from "qqq/layouts/BaseLayout"; import BaseLayout from "qqq/layouts/BaseLayout";
import {TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT} from "qqq/pages/records/query/RecordQuery";
import Client from "qqq/utils/qqq/Client"; import Client from "qqq/utils/qqq/Client";
import TableUtils from "qqq/utils/qqq/TableUtils"; import TableUtils from "qqq/utils/qqq/TableUtils";
import ValueUtils from "qqq/utils/qqq/ValueUtils"; import ValueUtils from "qqq/utils/qqq/ValueUtils";
interface Props interface Props
{ {
process?: QProcessMetaData; process?: QProcessMetaData;
@ -88,6 +90,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
{ {
const processNameParam = useParams().processName; const processNameParam = useParams().processName;
const processName = process === null ? processNameParam : process.name; const processName = process === null ? processNameParam : process.name;
const tableVariantLocalStorageKey = `${TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT}.${table.name}`;
/////////////////// ///////////////////
// process state // // process state //
@ -221,19 +224,12 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
const download = (url: string, fileName: string) => const download = (url: string, fileName: string) =>
{ {
///////////////////////////////////////////////////////////////////////////////////////////// const qController = Client.getInstance();
// todo - this could be simplified. //
// it was originally built like this when we had to submit full access token to backend... //
/////////////////////////////////////////////////////////////////////////////////////////////
let xhr = new XMLHttpRequest(); let xhr = new XMLHttpRequest();
xhr.open("POST", url); xhr.open("POST", url);
xhr.responseType = "blob"; xhr.responseType = "blob";
let formData = new FormData(); let formData = new FormData();
////////////////////////////////////
// todo#authHeader - delete this. //
////////////////////////////////////
const qController = Client.getInstance();
formData.append("Authorization", qController.getAuthorizationHeaderValue()); formData.append("Authorization", qController.getAuthorizationHeaderValue());
// @ts-ignore // @ts-ignore
@ -383,7 +379,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
{ {
if (processValues[key]) if (processValues[key])
{ {
formFields[key].possibleValueProps.initialDisplayValue = processValues[key] formFields[key].possibleValueProps.initialDisplayValue = processValues[key];
} }
formFields[key].possibleValueProps.otherValues = formFields[key].possibleValueProps.otherValues ?? new Map<string, any>(); formFields[key].possibleValueProps.otherValues = formFields[key].possibleValueProps.otherValues ?? new Map<string, any>();
@ -392,7 +388,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
formFields[key].possibleValueProps.otherValues.set(otherKey, processValues[otherKey]); formFields[key].possibleValueProps.otherValues.set(otherKey, processValues[otherKey]);
}); });
} }
}) });
} }
return ( return (
@ -807,9 +803,9 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
Object.keys(initialValues).forEach((ivKey: any) => Object.keys(initialValues).forEach((ivKey: any) =>
{ {
dynamicFormFields[key].possibleValueProps.otherValues.set(ivKey, initialValues[ivKey]); dynamicFormFields[key].possibleValueProps.otherValues.set(ivKey, initialValues[ivKey]);
}) });
} }
}) });
//////////////////////////////////////////////////// ////////////////////////////////////////////////////
// disable all fields if this is a bulk edit form // // disable all fields if this is a bulk edit form //
@ -1109,6 +1105,12 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
} }
} }
if (localStorage.getItem(tableVariantLocalStorageKey))
{
let tableVariant = JSON.parse(localStorage.getItem(tableVariantLocalStorageKey));
queryStringPairsForInit.push(`tableVariant=${JSON.stringify(tableVariant)}`);
}
try try
{ {
const qInstance = await Client.getInstance().loadMetaData(); const qInstance = await Client.getInstance().loadMetaData();
@ -1156,7 +1158,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
if (tableMetaData) if (tableMetaData)
{ {
queryStringPairsForInit.push(`tableName=${tableMetaData.name}`) queryStringPairsForInit.push(`tableName=${tableMetaData.name}`);
} }
try try
@ -1194,6 +1196,12 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
formData.append(key, values[key]); formData.append(key, values[key]);
}); });
if (localStorage.getItem(tableVariantLocalStorageKey))
{
let tableVariant = JSON.parse(localStorage.getItem(tableVariantLocalStorageKey));
formData.append("tableVariant", JSON.stringify(tableVariant));
}
if (doesStepHaveComponent(activeStep, QComponentType.BULK_EDIT_FORM)) if (doesStepHaveComponent(activeStep, QComponentType.BULK_EDIT_FORM))
{ {
const bulkEditEnabledFields: string[] = []; const bulkEditEnabledFields: string[] = [];

View File

@ -82,7 +82,8 @@ const ROWS_PER_PAGE_LOCAL_STORAGE_KEY_ROOT = "qqq.rowsPerPage";
const PINNED_COLUMNS_LOCAL_STORAGE_KEY_ROOT = "qqq.pinnedColumns"; const PINNED_COLUMNS_LOCAL_STORAGE_KEY_ROOT = "qqq.pinnedColumns";
const SEEN_JOIN_TABLES_LOCAL_STORAGE_KEY_ROOT = "qqq.seenJoinTables"; const SEEN_JOIN_TABLES_LOCAL_STORAGE_KEY_ROOT = "qqq.seenJoinTables";
const DENSITY_LOCAL_STORAGE_KEY_ROOT = "qqq.density"; const DENSITY_LOCAL_STORAGE_KEY_ROOT = "qqq.density";
const TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT = "qqq.tableVariant";
export const TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT = "qqq.tableVariant";
interface Props interface Props
{ {
@ -1139,7 +1140,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
<body> <body>
Generating file <u>${filename}</u>${totalRecords ? " with " + totalRecords.toLocaleString() + " record" + (totalRecords == 1 ? "" : "s") : ""}... Generating file <u>${filename}</u>${totalRecords ? " with " + totalRecords.toLocaleString() + " record" + (totalRecords == 1 ? "" : "s") : ""}...
<form id="exportForm" method="post" action="${url}" > <form id="exportForm" method="post" action="${url}" >
<!-- todo#authHeader - remove this. -->
<input type="hidden" name="Authorization" value="${qController.getAuthorizationHeaderValue()}"> <input type="hidden" name="Authorization" value="${qController.getAuthorizationHeaderValue()}">
<input type="hidden" name="fields" value="${visibleFields.join(",")}"> <input type="hidden" name="fields" value="${visibleFields.join(",")}">
<input type="hidden" name="filter" id="filter"> <input type="hidden" name="filter" id="filter">

View File

@ -95,11 +95,6 @@ export default class HtmlUtils
form.setAttribute("target", "downloadIframe"); form.setAttribute("target", "downloadIframe");
iframe.appendChild(form); iframe.appendChild(form);
/////////////////////////////////////////////////////////////////////////////////////////////
// todo#authHeader - remove after comfortable with sessionUUID //
// todo - this could be simplified (i think?) //
// it was originally built like this when we had to submit full access token to backend... //
/////////////////////////////////////////////////////////////////////////////////////////////
const authorizationInput = document.createElement("input"); const authorizationInput = document.createElement("input");
authorizationInput.setAttribute("type", "hidden"); authorizationInput.setAttribute("type", "hidden");
authorizationInput.setAttribute("id", "authorizationInput"); authorizationInput.setAttribute("id", "authorizationInput");
@ -123,11 +118,6 @@ export default class HtmlUtils
{ {
if(url.startsWith("data:")) if(url.startsWith("data:"))
{ {
/////////////////////////////////////////////////////////////////////////////////////////////
// todo#authHeader - remove the Authorization input after comfortable with sessionUUID //
// todo - this could be simplified (i think?) //
// it was originally built like this when we had to submit full access token to backend... //
/////////////////////////////////////////////////////////////////////////////////////////////
const openInWindow = window.open("", "_blank"); const openInWindow = window.open("", "_blank");
openInWindow.document.write(`<html lang="en"> openInWindow.document.write(`<html lang="en">
<body style="margin: 0"> <body style="margin: 0">

View File

@ -49,7 +49,6 @@ module.exports = function (app)
app.use("/data/*", getRequestHandler()); app.use("/data/*", getRequestHandler());
app.use("/widget/*", getRequestHandler()); app.use("/widget/*", getRequestHandler());
app.use("/serverInfo", getRequestHandler()); app.use("/serverInfo", getRequestHandler());
app.use("/manageSession", getRequestHandler());
app.use("/processes", getRequestHandler()); app.use("/processes", getRequestHandler());
app.use("/reports", getRequestHandler()); app.use("/reports", getRequestHandler());
app.use("/images", getRequestHandler()); app.use("/images", getRequestHandler());