fix: Update view path in config and add layout authentication page
This commit is contained in:
@@ -31,6 +31,6 @@ $config = array(
|
||||
'latest_news_url' => 'http://xibo.org.uk/feed/',
|
||||
'client_sendCurrentLayoutAsStatusUpdate_enabled' => false,
|
||||
'client_screenShotRequestInterval_enabled' => false,
|
||||
"view_path" => "../web/theme/custom/otssigns-beta/views",
|
||||
"view_path" => "../web/theme/custom/ots-signs/views",
|
||||
'product_support_url' => 'https://community.xibo.org.uk/c/support'
|
||||
);
|
||||
|
||||
202
ots-signs/layoutauth.html
Normal file
202
ots-signs/layoutauth.html
Normal file
@@ -0,0 +1,202 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user