From f48dc1357d77c3b6e62e67844cca0f31e662508c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Tron=C3=AD=C4=8Dek?= Date: Fri, 26 Apr 2024 19:39:04 +0200 Subject: [PATCH] Fix super long unauthed contexts (#19637) --- .../dashboard/src/components/QuickStart.tsx | 46 ++++++++++++++++++- components/dashboard/src/utils.ts | 32 +++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/components/dashboard/src/components/QuickStart.tsx b/components/dashboard/src/components/QuickStart.tsx index e885c23566cb52..86e50468eb9ac9 100644 --- a/components/dashboard/src/components/QuickStart.tsx +++ b/components/dashboard/src/components/QuickStart.tsx @@ -10,6 +10,7 @@ import { useHistory, useLocation } from "react-router"; import { AppLoading } from "../app/AppLoading"; import { Link } from "react-router-dom"; import { authProviderClient, userClient } from "../service/public-api"; +import { storageAvailable } from "../utils"; const parseErrorFromSearch = (search: string): string => { const searchParams = new URLSearchParams(search); @@ -22,6 +23,14 @@ const parseErrorFromSearch = (search: string): string => { return ""; }; +const generateLocalStorageItemName = async (hash: string) => { + const id = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(hash)); + return `quickstart-${Array.from(new Uint8Array(id)) + .map((b) => b.toString(16).padStart(2, "0")) + .join("") + .slice(0, 10)}` as const; +}; + const QuickStart: FC = () => { const [error, setError] = useState(parseErrorFromSearch(window.location.search)); const history = useHistory(); @@ -40,10 +49,33 @@ const QuickStart: FC = () => { .then((r) => r?.descriptions); const hashValue = hash.slice(1); + + // The browser will reject cookies larger than 4096 bytes, so we store the hash in local storage if it's too long and restore it later. + if (hashValue.length > 2048) { + const isLocalStorageAvailable = storageAvailable("localStorage"); + if (isLocalStorageAvailable) { + const localStorageItemName = await generateLocalStorageItemName(hashValue); + + console.log(`Hash value too long, storing in local storage as ${localStorageItemName}`); + localStorage.setItem(localStorageItemName, hashValue); + window.location.hash = `#${localStorageItemName}`; + return; + } + + setError("Context URL value is too long."); + return; + } + let contextUrl: URL; try { + const value = hashValue.startsWith("quickstart-") ? localStorage.getItem(hashValue) : hashValue; + if (!value) { + setError("Invalid hash value"); + return; + } + // We have to account for the case where environment variables are provided through the hash, so we search it for the URL. - const toParse = hashValue.match(/^https?:/) ? hashValue : hashValue.slice(hashValue.indexOf("/") + 1); + const toParse = value.match(/^https?:/) ? value : value.slice(value.indexOf("/") + 1); contextUrl = new URL(toParse); } catch { setError("Invalid context URL"); @@ -87,6 +119,18 @@ const QuickStart: FC = () => { const searchParams = new URLSearchParams(window.location.search); searchParams.delete("message"); + if (hashValue.startsWith("quickstart-")) { + const storedHash = localStorage.getItem(hashValue); + if (!storedHash) { + setError("Invalid hash value"); + return; + } + + localStorage.removeItem(hashValue); + history.push(`/new/?${searchParams}#${storedHash}`); + return; + } + history.push(`/new/?${searchParams}${window.location.hash}`); return; diff --git a/components/dashboard/src/utils.ts b/components/dashboard/src/utils.ts index e94cf03ee9b17b..8c2994ce3b4ad5 100644 --- a/components/dashboard/src/utils.ts +++ b/components/dashboard/src/utils.ts @@ -117,3 +117,35 @@ export function isWebsiteSlug(pathName: string) { ]; return slugs.some((slug) => pathName.startsWith("/" + slug + "/") || pathName === "/" + slug); } + +// https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API#testing_for_availability +export function storageAvailable(type: "localStorage" | "sessionStorage"): boolean { + let storage; + try { + storage = window[type]; + const x = "__storage_test__"; + storage.setItem(x, x); + storage.removeItem(x); + return true; + } catch (e) { + if (!storage) { + return false; + } + + return ( + e instanceof DOMException && + // everything except Firefox + (e.code === 22 || + // Firefox + e.code === 1014 || + // test name field too, because code might not be present + // everything except Firefox + e.name === "QuotaExceededError" || + // Firefox + e.name === "NS_ERROR_DOM_QUOTA_REACHED") && + // acknowledge QuotaExceededError only if there's something already stored + storage && + storage.length !== 0 + ); + } +}