Fix authenticated BIM share promotion
This commit is contained in:
parent
33d2f03624
commit
48400c7d8c
|
|
@ -3220,11 +3220,17 @@ header {
|
||||||
}
|
}
|
||||||
|
|
||||||
body[data-viewer-mode="guest"] .tb-btn:not([data-action="measure"]):not([data-action="section"]):not([data-action="projection"]),
|
body[data-viewer-mode="guest"] .tb-btn:not([data-action="measure"]):not([data-action="section"]):not([data-action="projection"]),
|
||||||
|
html[data-share-startup="guest"] body:not([data-viewer-mode="standard"]) .tb-btn:not([data-action="measure"]):not([data-action="section"]):not([data-action="projection"]),
|
||||||
body[data-viewer-mode="guest"] #settingsPanel,
|
body[data-viewer-mode="guest"] #settingsPanel,
|
||||||
|
html[data-share-startup="guest"] body:not([data-viewer-mode="standard"]) #settingsPanel,
|
||||||
body[data-viewer-mode="guest"] #templatesPanel,
|
body[data-viewer-mode="guest"] #templatesPanel,
|
||||||
|
html[data-share-startup="guest"] body:not([data-viewer-mode="standard"]) #templatesPanel,
|
||||||
body[data-viewer-mode="guest"] #projectNameSection,
|
body[data-viewer-mode="guest"] #projectNameSection,
|
||||||
|
html[data-share-startup="guest"] body:not([data-viewer-mode="standard"]) #projectNameSection,
|
||||||
body[data-viewer-mode="guest"] #saveProjectSection,
|
body[data-viewer-mode="guest"] #saveProjectSection,
|
||||||
body[data-viewer-mode="guest"] #modelVersionUploadButton {
|
html[data-share-startup="guest"] body:not([data-viewer-mode="standard"]) #saveProjectSection,
|
||||||
|
body[data-viewer-mode="guest"] #modelVersionUploadButton,
|
||||||
|
html[data-share-startup="guest"] body:not([data-viewer-mode="standard"]) #modelVersionUploadButton {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,7 @@ const AUTH_SESSION_API = "/api/auth/session";
|
||||||
const SHARES_API = "/api/shares";
|
const SHARES_API = "/api/shares";
|
||||||
const COMMENTS_API = "/api/comments";
|
const COMMENTS_API = "/api/comments";
|
||||||
const UPLOAD_VERSIONS_API = "/api/uploads/versions";
|
const UPLOAD_VERSIONS_API = "/api/uploads/versions";
|
||||||
|
const SHARE_AUTH_GUEST_PARAM = "ndc_bim_guest";
|
||||||
const SHARE_PROMOTION_TIMEOUT_MS = 4500;
|
const SHARE_PROMOTION_TIMEOUT_MS = 4500;
|
||||||
|
|
||||||
const acceptByType = {
|
const acceptByType = {
|
||||||
|
|
@ -624,13 +625,39 @@ const getShareTokenFromPath = () => {
|
||||||
const getCurrentReturnPath = () => `${window.location.pathname}${window.location.search || ""}${window.location.hash || ""}`;
|
const getCurrentReturnPath = () => `${window.location.pathname}${window.location.search || ""}${window.location.hash || ""}`;
|
||||||
|
|
||||||
const getShareTopLevelPromotionUrl = () => {
|
const getShareTopLevelPromotionUrl = () => {
|
||||||
const promoteUrl = startupSharePayload?.promoteUrl;
|
const promoteUrl = startupSharePayload?.optionalPromoteUrl || startupSharePayload?.promoteUrl;
|
||||||
return typeof promoteUrl === "string" && promoteUrl.trim() ? promoteUrl : "";
|
return typeof promoteUrl === "string" && promoteUrl.trim() ? promoteUrl : "";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasShareGuestAuthMarker = () => {
|
||||||
|
try {
|
||||||
|
return new URLSearchParams(window.location.search || "").get(SHARE_AUTH_GUEST_PARAM) === "1";
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearShareGuestAuthMarker = () => {
|
||||||
|
if (!hasShareGuestAuthMarker()) return;
|
||||||
|
try {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
url.searchParams.delete(SHARE_AUTH_GUEST_PARAM);
|
||||||
|
window.history.replaceState(window.history.state, "", `${url.pathname}${url.search}${url.hash}`);
|
||||||
|
} catch (_) {
|
||||||
|
// keep current URL if the History API is unavailable
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const scheduleShareSessionPromotion = (shareToken) => {
|
const scheduleShareSessionPromotion = (shareToken) => {
|
||||||
const silentPromoteUrl = startupSharePayload?.silentPromoteUrl;
|
const silentPromoteUrl = startupSharePayload?.silentPromoteUrl;
|
||||||
if (!shareToken || viewerMode !== "guest" || runtimeSessionInfo.authenticated || typeof silentPromoteUrl !== "string" || !silentPromoteUrl.trim()) {
|
if (
|
||||||
|
!shareToken ||
|
||||||
|
hasShareGuestAuthMarker() ||
|
||||||
|
viewerMode !== "guest" ||
|
||||||
|
runtimeSessionInfo.authenticated ||
|
||||||
|
typeof silentPromoteUrl !== "string" ||
|
||||||
|
!silentPromoteUrl.trim()
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let storageKey = "";
|
let storageKey = "";
|
||||||
|
|
@ -1810,6 +1837,7 @@ const applyRuntimeMode = () => {
|
||||||
|
|
||||||
const initializeRuntimeMode = async () => {
|
const initializeRuntimeMode = async () => {
|
||||||
const shareToken = getShareTokenFromPath();
|
const shareToken = getShareTokenFromPath();
|
||||||
|
const shareGuestAuthMarker = hasShareGuestAuthMarker();
|
||||||
if (shareToken) {
|
if (shareToken) {
|
||||||
viewerMode = "guest";
|
viewerMode = "guest";
|
||||||
inspectorCollapsed = true;
|
inspectorCollapsed = true;
|
||||||
|
|
@ -1849,7 +1877,8 @@ const initializeRuntimeMode = async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
applyRuntimeMode();
|
applyRuntimeMode();
|
||||||
if (shareToken && viewerMode === "guest") {
|
clearShareGuestAuthMarker();
|
||||||
|
if (shareToken && viewerMode === "guest" && !shareGuestAuthMarker) {
|
||||||
scheduleShareSessionPromotion(shareToken);
|
scheduleShareSessionPromotion(shareToken);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -5182,13 +5211,13 @@ const applyViewerState = (state) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const initViewer = async () => {
|
const initViewer = async () => {
|
||||||
// remove preload hidden style ASAP
|
await initializeRuntimeMode();
|
||||||
|
|
||||||
const preloadHidden = document.getElementById("preload-hidden");
|
const preloadHidden = document.getElementById("preload-hidden");
|
||||||
if (preloadHidden) {
|
if (preloadHidden) {
|
||||||
preloadHidden.remove();
|
preloadHidden.remove();
|
||||||
}
|
}
|
||||||
document.body.style.opacity = "1";
|
document.body.style.opacity = "1";
|
||||||
await initializeRuntimeMode();
|
|
||||||
|
|
||||||
const sdk = await import(SDK_URL);
|
const sdk = await import(SDK_URL);
|
||||||
sdkRef = sdk;
|
sdkRef = sdk;
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,13 @@
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Manrope:wght@500;600;700&display=swap">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Manrope:wght@500;600;700&display=swap">
|
||||||
<script src="/theme.js?v=3"></script>
|
<script src="/theme.js?v=3"></script>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
if (/^\/share\/[^/]+\/?$/.test(window.location.pathname || "")) {
|
||||||
|
document.documentElement.dataset.shareStartup = "guest";
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
</script>
|
||||||
<script>
|
<script>
|
||||||
// Apply persisted theme ASAP to avoid initial flash of defaults
|
// Apply persisted theme ASAP to avoid initial flash of defaults
|
||||||
(function () {
|
(function () {
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ const BIM_SESSION_COOKIE = process.env.NODEDC_BIM_SESSION_COOKIE || "nodedc_bim_
|
||||||
const BIM_SESSION_TTL_MS = Number(process.env.NODEDC_BIM_SESSION_TTL_MS || 12 * 60 * 60 * 1000);
|
const BIM_SESSION_TTL_MS = Number(process.env.NODEDC_BIM_SESSION_TTL_MS || 12 * 60 * 60 * 1000);
|
||||||
const BIM_COOKIE_SECURE = parseBooleanEnv(process.env.NODEDC_BIM_COOKIE_SECURE, false);
|
const BIM_COOKIE_SECURE = parseBooleanEnv(process.env.NODEDC_BIM_COOKIE_SECURE, false);
|
||||||
const BIM_COOKIE_SAMESITE = process.env.NODEDC_BIM_COOKIE_SAMESITE || (BIM_COOKIE_SECURE ? "None" : "Lax");
|
const BIM_COOKIE_SAMESITE = process.env.NODEDC_BIM_COOKIE_SAMESITE || (BIM_COOKIE_SECURE ? "None" : "Lax");
|
||||||
|
const SHARE_AUTH_GUEST_PARAM = "ndc_bim_guest";
|
||||||
const CORS_ALLOWED_ORIGINS = String(process.env.NODEDC_BIM_ALLOWED_ORIGINS || "")
|
const CORS_ALLOWED_ORIGINS = String(process.env.NODEDC_BIM_ALLOWED_ORIGINS || "")
|
||||||
.split(",")
|
.split(",")
|
||||||
.map((origin) => origin.trim())
|
.map((origin) => origin.trim())
|
||||||
|
|
@ -812,6 +813,14 @@ const buildLauncherLaunchUrl = (nextPath = "/") => {
|
||||||
return launchUrl.toString();
|
return launchUrl.toString();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buildLauncherOptionalLaunchUrl = (nextPath = "/", fallbackPath = nextPath) => {
|
||||||
|
const launchPath = `/api/services/${encodeURIComponent(BIM_SERVICE_SLUG)}/optional-launch`;
|
||||||
|
const launchUrl = new URL(launchPath, LAUNCHER_BASE_URL);
|
||||||
|
launchUrl.searchParams.set("returnTo", sanitizeReturnTo(nextPath));
|
||||||
|
launchUrl.searchParams.set("fallbackTo", sanitizeReturnTo(fallbackPath, nextPath));
|
||||||
|
return launchUrl.toString();
|
||||||
|
};
|
||||||
|
|
||||||
const buildLauncherLoginUrl = (req, nextPath = "/") => {
|
const buildLauncherLoginUrl = (req, nextPath = "/") => {
|
||||||
const loginUrl = new URL("/auth/login", LAUNCHER_BASE_URL);
|
const loginUrl = new URL("/auth/login", LAUNCHER_BASE_URL);
|
||||||
const launchUrl = new URL(buildLauncherLaunchUrl(nextPath));
|
const launchUrl = new URL(buildLauncherLaunchUrl(nextPath));
|
||||||
|
|
@ -822,16 +831,47 @@ const buildLauncherLoginUrl = (req, nextPath = "/") => {
|
||||||
const buildSharePromotionUrls = (token) => {
|
const buildSharePromotionUrls = (token) => {
|
||||||
const safeToken = safeShareToken(token);
|
const safeToken = safeShareToken(token);
|
||||||
if (!safeToken) {
|
if (!safeToken) {
|
||||||
return {promoteUrl: null, silentPromoteUrl: null};
|
return {promoteUrl: null, silentPromoteUrl: null, optionalPromoteUrl: null};
|
||||||
}
|
}
|
||||||
const sharePath = `/share/${encodeURIComponent(safeToken)}`;
|
const sharePath = `/share/${encodeURIComponent(safeToken)}`;
|
||||||
|
const guestPath = `${sharePath}?${SHARE_AUTH_GUEST_PARAM}=1`;
|
||||||
const bridgePath = `/auth/share-promoted?next=${encodeURIComponent(sharePath)}`;
|
const bridgePath = `/auth/share-promoted?next=${encodeURIComponent(sharePath)}`;
|
||||||
return {
|
return {
|
||||||
promoteUrl: buildLauncherLaunchUrl(sharePath),
|
promoteUrl: buildLauncherLaunchUrl(sharePath),
|
||||||
silentPromoteUrl: buildLauncherLaunchUrl(bridgePath)
|
silentPromoteUrl: buildLauncherLaunchUrl(bridgePath),
|
||||||
|
optionalPromoteUrl: buildLauncherOptionalLaunchUrl(sharePath, guestPath)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const shouldPromoteShareNavigation = (req, url) => {
|
||||||
|
if (!BIM_AUTH_REQUIRED || !INTERNAL_ACCESS_TOKEN || !isPublicSharePath(url)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (req.method !== "GET" && req.method !== "HEAD") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (url.searchParams.get(SHARE_AUTH_GUEST_PARAM) === "1") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getCurrentBimSession(req)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const accept = typeof req.headers.accept === "string" ? req.headers.accept : "";
|
||||||
|
return accept.includes("text/html") || accept.includes("*/*") || !accept;
|
||||||
|
};
|
||||||
|
|
||||||
|
const redirectShareNavigationToPromotion = (res, token) => {
|
||||||
|
const urls = buildSharePromotionUrls(token);
|
||||||
|
if (!urls.optionalPromoteUrl) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
res.statusCode = 302;
|
||||||
|
res.setHeader("Location", urls.optionalPromoteUrl);
|
||||||
|
res.setHeader("Cache-Control", "no-store");
|
||||||
|
res.end();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
const sendAuthRequired = (req, res, url) => {
|
const sendAuthRequired = (req, res, url) => {
|
||||||
const nextPath = sanitizeReturnTo(`${url.pathname}${url.search || ""}`);
|
const nextPath = sanitizeReturnTo(`${url.pathname}${url.search || ""}`);
|
||||||
if (!INTERNAL_ACCESS_TOKEN) {
|
if (!INTERNAL_ACCESS_TOKEN) {
|
||||||
|
|
@ -1777,6 +1817,7 @@ const handleGetShare = async (req, res, token) => {
|
||||||
canShare: !BIM_AUTH_REQUIRED || authenticated,
|
canShare: !BIM_AUTH_REQUIRED || authenticated,
|
||||||
promoteUrl: promotionUrls.promoteUrl,
|
promoteUrl: promotionUrls.promoteUrl,
|
||||||
silentPromoteUrl: promotionUrls.silentPromoteUrl,
|
silentPromoteUrl: promotionUrls.silentPromoteUrl,
|
||||||
|
optionalPromoteUrl: promotionUrls.optionalPromoteUrl,
|
||||||
user: session?.user || null,
|
user: session?.user || null,
|
||||||
viewer: {
|
viewer: {
|
||||||
src: publicUrlForUploadSrc(req, share.src),
|
src: publicUrlForUploadSrc(req, share.src),
|
||||||
|
|
@ -3267,6 +3308,10 @@ const requestHandler = async (req, res) => {
|
||||||
return handleGetShare(req, res, shareTokenFromPath(url.pathname));
|
return handleGetShare(req, res, shareTokenFromPath(url.pathname));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldPromoteShareNavigation(req, url) && redirectShareNavigationToPromotion(res, shareTokenFromPath(url.pathname))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (ensureBimSessionForRequest(req, res, url)) {
|
if (ensureBimSessionForRequest(req, res, url)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue