add configurable club login domains in RMS software
This commit is contained in:
@@ -71,6 +71,7 @@ const config = {
|
||||
autoDisableTxBeforeActivation: String(process.env.AUTO_DISABLE_TX_BEFORE_ACTIVATION || "").trim().toLowerCase(),
|
||||
stationMaxUsageSec: Number(process.env.STATION_MAX_USAGE_SEC || 3600),
|
||||
primaryEmailDomain: String(process.env.PRIMARY_EMAIL_DOMAIN || "arcg.at").toLowerCase(),
|
||||
loginAllowedDomains: parseDomainList(process.env.ALLOWED_LOGIN_DOMAINS || "arcg.at,oevsv.at"),
|
||||
publicBaseUrl: String(process.env.PUBLIC_BASE_URL || ""),
|
||||
approverEmails: new Set(
|
||||
String(process.env.APPROVER_EMAILS || "")
|
||||
@@ -162,6 +163,7 @@ const runtime = {
|
||||
logoLightUrl: null,
|
||||
logoDarkUrl: null
|
||||
},
|
||||
loginAllowedDomains: config.loginAllowedDomains,
|
||||
updatedAt: null
|
||||
},
|
||||
pluginState: {
|
||||
@@ -301,6 +303,9 @@ async function routeRequest(req, res) {
|
||||
maintenanceMode: Boolean(runtime.systemState.maintenanceMode),
|
||||
maintenanceMessage: runtime.systemState.maintenanceMessage,
|
||||
branding: runtime.systemState.branding,
|
||||
allowedLoginDomains: Array.isArray(runtime.systemState.loginAllowedDomains)
|
||||
? runtime.systemState.loginAllowedDomains
|
||||
: config.loginAllowedDomains,
|
||||
updatedAt: runtime.systemState.updatedAt
|
||||
});
|
||||
}
|
||||
@@ -849,6 +854,31 @@ async function routeRequest(req, res) {
|
||||
return handleMaintenanceUpdate(res, auth.user, body);
|
||||
}
|
||||
|
||||
if (method === "GET" && url.pathname === "/v1/admin/login-domains") {
|
||||
const auth = requireAuth(req, res);
|
||||
if (!auth) return;
|
||||
if (!hasRole(auth.user, ["admin"])) {
|
||||
return sendError(res, 403, "auth.forbidden", "Nur Admin");
|
||||
}
|
||||
return sendJson(res, 200, {
|
||||
ok: true,
|
||||
domains: Array.isArray(runtime.systemState.loginAllowedDomains)
|
||||
? runtime.systemState.loginAllowedDomains
|
||||
: config.loginAllowedDomains,
|
||||
updatedAt: runtime.systemState.updatedAt
|
||||
});
|
||||
}
|
||||
|
||||
if (method === "PUT" && url.pathname === "/v1/admin/login-domains") {
|
||||
const auth = requireAuth(req, res);
|
||||
if (!auth) return;
|
||||
if (!hasRole(auth.user, ["admin"])) {
|
||||
return sendError(res, 403, "auth.forbidden", "Nur Admin");
|
||||
}
|
||||
const body = await readJsonBody(req);
|
||||
return handleLoginDomainsUpdate(res, auth.user, body);
|
||||
}
|
||||
|
||||
if (method === "PUT" && url.pathname === "/v1/admin/branding/logo") {
|
||||
const auth = requireAuth(req, res);
|
||||
if (!auth) return;
|
||||
@@ -940,6 +970,9 @@ async function handleRequestAccess(req, res, body) {
|
||||
if (!isValidEmail(email)) {
|
||||
return sendError(res, 400, "auth.invalid_email", "Bitte gueltige E-Mail angeben");
|
||||
}
|
||||
if (!isLoginDomainAllowed(email)) {
|
||||
return sendError(res, 403, "auth.email.domain_forbidden", "Nur Club-Mailadressen (@arcg.at oder @oevsv.at) sind zum Anmelden moeglich");
|
||||
}
|
||||
|
||||
let user = runtime.users.find((entry) => entry.email === email);
|
||||
if (runtime.systemState.maintenanceMode) {
|
||||
@@ -1573,6 +1606,22 @@ async function handleMaintenanceUpdate(res, actor, body) {
|
||||
});
|
||||
}
|
||||
|
||||
async function handleLoginDomainsUpdate(res, actor, body) {
|
||||
const domains = normalizeLoginDomainList(body && body.domains);
|
||||
if (domains.length === 0) {
|
||||
return sendError(res, 400, "auth.login_domains.invalid", "Mindestens eine gueltige Domain ist erforderlich");
|
||||
}
|
||||
runtime.systemState.loginAllowedDomains = domains;
|
||||
runtime.systemState.updatedAt = new Date().toISOString();
|
||||
await saveSystemState();
|
||||
await appendAudit("admin.login_domains.update", actor, { domains });
|
||||
return sendJson(res, 200, {
|
||||
ok: true,
|
||||
domains,
|
||||
updatedAt: runtime.systemState.updatedAt
|
||||
});
|
||||
}
|
||||
|
||||
async function handleAdminBrandingLogoUpdate(res, actor, body) {
|
||||
const theme = typeof (body && body.theme) === "string" ? body.theme.trim().toLowerCase() : "";
|
||||
if (theme !== "light" && theme !== "dark") {
|
||||
@@ -1743,6 +1792,7 @@ function parseImageDataUrl(value) {
|
||||
function normalizeSystemState(state) {
|
||||
const input = state && typeof state === "object" ? state : {};
|
||||
const branding = input.branding && typeof input.branding === "object" ? input.branding : {};
|
||||
const loginAllowedDomains = normalizeLoginDomainList(input.loginAllowedDomains);
|
||||
return {
|
||||
maintenanceMode: Boolean(input.maintenanceMode),
|
||||
maintenanceMessage: typeof input.maintenanceMessage === "string" && input.maintenanceMessage.trim()
|
||||
@@ -1752,6 +1802,7 @@ function normalizeSystemState(state) {
|
||||
logoLightUrl: typeof branding.logoLightUrl === "string" && branding.logoLightUrl.trim() ? branding.logoLightUrl : null,
|
||||
logoDarkUrl: typeof branding.logoDarkUrl === "string" && branding.logoDarkUrl.trim() ? branding.logoDarkUrl : null
|
||||
},
|
||||
loginAllowedDomains: loginAllowedDomains.length > 0 ? loginAllowedDomains : config.loginAllowedDomains,
|
||||
updatedAt: input.updatedAt || null
|
||||
};
|
||||
}
|
||||
@@ -2495,10 +2546,41 @@ function domainForEmail(email) {
|
||||
return at === -1 ? "" : email.slice(at + 1).toLowerCase();
|
||||
}
|
||||
|
||||
function parseDomainList(value) {
|
||||
return normalizeLoginDomainList(String(value || ""));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function isPrimaryDomainEmail(email) {
|
||||
return domainForEmail(email) === config.primaryEmailDomain;
|
||||
}
|
||||
|
||||
function isLoginDomainAllowed(email) {
|
||||
const allowed = Array.isArray(runtime.systemState && runtime.systemState.loginAllowedDomains)
|
||||
? runtime.systemState.loginAllowedDomains
|
||||
: config.loginAllowedDomains;
|
||||
const domain = domainForEmail(email);
|
||||
return allowed.includes(domain);
|
||||
}
|
||||
|
||||
function stationUsageDurationMs() {
|
||||
return Math.max(1, Number(config.stationMaxUsageSec || 3600)) * 1000;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user