add configurable club login domains in RMS software

This commit is contained in:
2026-04-02 22:06:19 +02:00
parent 1cc2014034
commit 66b08693b9
4 changed files with 278 additions and 20 deletions

View File

@@ -4,6 +4,7 @@ const SWR_DETAIL_REFRESH_MS = 20000;
const DEFAULT_UI_LANGUAGE = "de";
const SUPPORTED_UI_LANGUAGES = ["de", "en"];
const LANGUAGE_STORAGE_KEY = "rms-lang";
const DEFAULT_ALLOWED_LOGIN_DOMAINS = ["arcg.at", "oevsv.at"];
let activationWatchTimer = null;
let activationWatchInFlight = false;
@@ -21,7 +22,8 @@ const state = {
branding: {
logoLightUrl: null,
logoDarkUrl: null
}
},
allowedLoginDomains: DEFAULT_ALLOWED_LOGIN_DOMAINS.slice()
},
accessToken: localStorage.getItem("rms-access-token") || "",
refreshToken: localStorage.getItem("rms-refresh-token") || "",
@@ -251,6 +253,8 @@ const els = {
maintenanceMessageInput: document.getElementById("maintenanceMessageInput"),
maintenanceEnableBtn: document.getElementById("maintenanceEnableBtn"),
maintenanceDisableBtn: document.getElementById("maintenanceDisableBtn"),
loginDomainsInput: document.getElementById("loginDomainsInput"),
saveLoginDomainsBtn: document.getElementById("saveLoginDomainsBtn"),
logoLightFile: document.getElementById("logoLightFile"),
logoDarkFile: document.getElementById("logoDarkFile"),
uploadLogoLightBtn: document.getElementById("uploadLogoLightBtn"),
@@ -817,6 +821,11 @@ function bindEvents() {
await setMaintenanceMode(false);
});
}
if (els.saveLoginDomainsBtn) {
els.saveLoginDomainsBtn.addEventListener("click", async () => {
await saveLoginDomains();
});
}
if (els.uploadLogoLightBtn) {
els.uploadLogoLightBtn.addEventListener("click", async () => {
await uploadBrandLogo("light", els.logoLightFile);
@@ -902,6 +911,10 @@ async function refreshFrontendOnResume() {
async function requestAccess() {
clearMessages("auth");
const email = els.email.value.trim();
if (!isLoginEmailAllowed(email)) {
renderMessage(els.authMessage, `Nur Club-Mailadressen (${formatAllowedDomainsHint()}) sind zum Anmelden moeglich.`, true);
return;
}
const method = els.authMethodSelect.value;
try {
const result = await api("/v1/auth/request-access", {
@@ -971,6 +984,30 @@ async function verifyOtpCode() {
}
}
function isLoginEmailAllowed(email) {
const normalized = String(email || "").trim().toLowerCase();
if (!normalized || !normalized.includes("@")) {
return false;
}
const atIndex = normalized.lastIndexOf("@");
if (atIndex < 0) {
return false;
}
const domain = normalized.slice(atIndex + 1);
return getAllowedLoginDomains().includes(domain);
}
function getAllowedLoginDomains() {
const list = state && state.system && Array.isArray(state.system.allowedLoginDomains)
? state.system.allowedLoginDomains
: [];
return list.length > 0 ? list : DEFAULT_ALLOWED_LOGIN_DOMAINS.slice();
}
function formatAllowedDomainsHint() {
return getAllowedLoginDomains().map((domain) => `@${domain}`).join(" oder ");
}
async function handleEmailTokenFromUrl() {
const url = new URL(window.location.href);
if (url.searchParams.get("requestApproval") === "1") {
@@ -1036,14 +1073,16 @@ async function refreshPublicSystemStatus() {
maintenanceMode: Boolean(result.maintenanceMode),
maintenanceMessage: result.maintenanceMessage || "",
updatedAt: result.updatedAt || null,
branding: normalizeBranding(result.branding)
branding: normalizeBranding(result.branding),
allowedLoginDomains: normalizeLoginDomainList(result.allowedLoginDomains)
};
} catch {
state.system = {
maintenanceMode: false,
maintenanceMessage: "",
updatedAt: null,
branding: normalizeBranding(null)
branding: normalizeBranding(null),
allowedLoginDomains: DEFAULT_ALLOWED_LOGIN_DOMAINS.slice()
};
}
renderMaintenanceBanner();
@@ -1165,6 +1204,43 @@ function renderMaintenanceBanner() {
if (els.maintenanceMessageInput && !els.maintenanceMessageInput.value) {
els.maintenanceMessageInput.value = state.system.maintenanceMessage || "";
}
if (els.loginDomainsInput && document.activeElement !== els.loginDomainsInput) {
els.loginDomainsInput.value = getAllowedLoginDomains().join("\n");
}
}
function normalizeLoginDomainList(value) {
const list = Array.isArray(value)
? value
: String(value || "").split(/[\n,;\s]+/);
const unique = [];
const seen = new Set();
for (const entry of list) {
const domain = String(entry || "").trim().toLowerCase();
if (!domain) continue;
if (!/^[a-z0-9.-]+$/.test(domain)) continue;
if (!domain.includes(".")) continue;
if (seen.has(domain)) continue;
seen.add(domain);
unique.push(domain);
}
return unique.length > 0 ? unique : DEFAULT_ALLOWED_LOGIN_DOMAINS.slice();
}
async function saveLoginDomains() {
clearMessages("admin");
const domains = normalizeLoginDomainList(els.loginDomainsInput ? els.loginDomainsInput.value : "");
try {
const result = await api("/v1/admin/login-domains", {
method: "PUT",
body: { domains }
});
state.system.allowedLoginDomains = normalizeLoginDomainList(result && result.domains ? result.domains : domains);
renderMaintenanceBanner();
renderMessage(els.adminMessage, `Login-Domains gespeichert (${formatAllowedDomainsHint()}).`, false, true);
} catch (error) {
renderMessage(els.adminMessage, error.message, true);
}
}
function renderBranding() {