203 lines
6.8 KiB
HTML
203 lines
6.8 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Auth to CMS — OTS Signs</title>
|
|
<style>
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
html, body {
|
|
height: 100%;
|
|
background: #0f172a;
|
|
color: #e2e8f0;
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
.container {
|
|
text-align: center;
|
|
padding: 2rem;
|
|
}
|
|
.logo {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
.logo svg {
|
|
width: 48px;
|
|
height: 48px;
|
|
}
|
|
.spinner {
|
|
display: inline-block;
|
|
width: 40px;
|
|
height: 40px;
|
|
border: 3px solid #1e293b;
|
|
border-top-color: #e87800;
|
|
border-radius: 50%;
|
|
animation: spin 0.7s linear infinite;
|
|
margin-bottom: 1rem;
|
|
}
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
.checkmark {
|
|
display: none;
|
|
margin-bottom: 1rem;
|
|
animation: pop 0.3s ease-out;
|
|
}
|
|
.checkmark.visible {
|
|
display: inline-block;
|
|
}
|
|
@keyframes pop {
|
|
0% { transform: scale(0.6); opacity: 0; }
|
|
70% { transform: scale(1.15); }
|
|
100% { transform: scale(1); opacity: 1; }
|
|
}
|
|
.message {
|
|
font-size: 1rem;
|
|
color: #94a3b8;
|
|
}
|
|
.link {
|
|
display: inline-block;
|
|
margin-top: 1rem;
|
|
color: #e87800;
|
|
text-decoration: none;
|
|
font-size: 0.875rem;
|
|
}
|
|
.link:hover { text-decoration: underline; }
|
|
#fallback-link { display: none; }
|
|
noscript .message {
|
|
color: #e2e8f0;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="logo">
|
|
<!-- OTS Signs wordmark placeholder -->
|
|
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
<rect width="48" height="48" rx="10" fill="#1e293b"/>
|
|
<path d="M10 24c0-7.732 6.268-14 14-14s14 6.268 14 14-6.268 14-14 14S10 31.732 10 24z"
|
|
stroke="#e87800" stroke-width="3" fill="none"/>
|
|
<path d="M19 24l3.5 3.5L30 20" stroke="#e87800" stroke-width="2.5"
|
|
stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
</div>
|
|
<div id="spinner" class="spinner" role="status" aria-label="Checking authentication"></div>
|
|
<div id="checkmark" class="checkmark" aria-label="Authenticated">
|
|
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
<circle cx="28" cy="28" r="26" fill="#14532d" stroke="#22c55e" stroke-width="2"/>
|
|
<path d="M17 28l8 8 14-16" stroke="#22c55e" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
</div>
|
|
<p id="message" class="message">Checking authentication…</p>
|
|
<noscript>
|
|
<p class="message">JavaScript is required for the automatic redirect.</p>
|
|
</noscript>
|
|
<a id="fallback-link" class="link" href="https://app.ots-signs.com">
|
|
Click here if you are not redirected automatically
|
|
</a>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
var TARGET_HOST = "https://app.ots-signs.com";
|
|
|
|
/**
|
|
* Validate that `to` is a safe relative path:
|
|
* - must be a non-empty string
|
|
* - must start with a single "/"
|
|
* - must NOT start with "//" (protocol-relative — would escape the host)
|
|
* - must NOT contain "@" (prevents https://app.ots-signs.com@evil.com)
|
|
* - must NOT contain "\" (prevents path traversal tricks in some parsers)
|
|
*/
|
|
function isValidPath(to) {
|
|
return (
|
|
typeof to === "string" &&
|
|
to.length > 0 &&
|
|
to.charAt(0) === "/" &&
|
|
to.charAt(1) !== "/" &&
|
|
to.indexOf("@") === -1 &&
|
|
to.indexOf("\\") === -1
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Extract the customer slug from the CMS base path.
|
|
* The CMS always runs at /{customerslug}/cms/…
|
|
* so pathname.split('/')[1] gives the slug.
|
|
*/
|
|
function getSlug() {
|
|
var parts = window.location.pathname.split("/");
|
|
// parts[0] = "" (before leading /), parts[1] = customerslug
|
|
return parts[1] || "";
|
|
}
|
|
|
|
function getQueryParam(name) {
|
|
try {
|
|
var params = new URLSearchParams(window.location.search);
|
|
return params.get(name);
|
|
} catch (e) {
|
|
var match = window.location.search.match(
|
|
new RegExp("[?&]" + name + "=([^&]*)")
|
|
);
|
|
return match ? decodeURIComponent(match[1].replace(/\+/g, " ")) : null;
|
|
}
|
|
}
|
|
|
|
var to = getQueryParam("to");
|
|
var slug = getSlug();
|
|
|
|
var destination = isValidPath(to)
|
|
? TARGET_HOST + to
|
|
: TARGET_HOST + (slug ? "/" + slug : "");
|
|
|
|
// Update the visible fallback link
|
|
var link = document.getElementById("fallback-link");
|
|
if (link) link.href = destination;
|
|
|
|
var spinner = document.getElementById("spinner");
|
|
var checkmark = document.getElementById("checkmark");
|
|
var message = document.getElementById("message");
|
|
|
|
// Check CMS web session auth by fetching the CMS root and following redirects.
|
|
// The CMS always runs at /{slug}/cms/:
|
|
// - Unauthenticated: 302 → /cms/login (final response.url contains "/login")
|
|
// - Authenticated: 302 → /cms/dashboard (final response.url does NOT contain "/login")
|
|
// Both cases produce an opaqueredirect with redirect:'manual', so we instead let
|
|
// the browser follow redirects and inspect where it ultimately lands.
|
|
var cmsRootUrl = window.location.origin + "/" + slug + "/cms/";
|
|
|
|
fetch(cmsRootUrl, {
|
|
method: "GET",
|
|
credentials: "include" // sends the CMS session cookie; follow redirects (default)
|
|
})
|
|
.then(function (response) {
|
|
// If the browser ended up on the login page, the session is not authenticated
|
|
if (!response.url || response.url.indexOf("/login") !== -1) {
|
|
throw new Error("unauthenticated");
|
|
}
|
|
return response;
|
|
})
|
|
.then(function () {
|
|
// Authenticated — show the green checkmark for 2 seconds then redirect
|
|
spinner.style.display = "none";
|
|
checkmark.classList.add("visible");
|
|
message.textContent = "Auth to CMS";
|
|
if (link) link.style.display = "inline";
|
|
|
|
setTimeout(function () {
|
|
window.location.replace(destination);
|
|
}, 2000);
|
|
})
|
|
.catch(function () {
|
|
// Not authenticated — send to the CMS login page, preserving the return URL
|
|
var returnUrl = encodeURIComponent(window.location.href);
|
|
var loginUrl = window.location.origin + "/" + slug + "/cms/login?redirect=" + returnUrl;
|
|
window.location.replace(loginUrl);
|
|
});
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|