ФУНКЦИИ - NODEDC LAUNCHER: direct auth redirect

This commit is contained in:
DCCONSTRUCTIONS
2026-05-04 21:39:56 +03:00
parent a1fb2d0f83
commit a1e8cacd88
3 changed files with 47 additions and 10 deletions
+38 -5
View File
@@ -16,6 +16,7 @@ const maxStorageJsonBodyBytes = "260mb";
const pendingLoginTtlMs = 10 * 60 * 1000;
const sessionTtlMs = 12 * 60 * 60 * 1000;
const oidcStateCookieName = "nodedc_oidc_state";
const maxOidcStateCookieEntries = 8;
const sessionCookieName = "nodedc_session";
loadEnvFiles([
@@ -66,7 +67,7 @@ app.get("/auth/login", asyncRoute(async (req, res) => {
expiresAt: Date.now() + pendingLoginTtlMs,
});
res.cookie(oidcStateCookieName, state, cookieOptions(pendingLoginTtlMs));
setOidcStateCookie(res, [state, ...getValidOidcCookieStates(req)].slice(0, maxOidcStateCookieEntries));
const authorizationUrl = new URL(discovery.authorization_endpoint);
authorizationUrl.searchParams.set("response_type", "code");
@@ -97,15 +98,17 @@ app.get("/auth/callback", asyncRoute(async (req, res) => {
const code = typeof req.query.code === "string" ? req.query.code : null;
const state = typeof req.query.state === "string" ? req.query.state : null;
const cookieState = parseCookies(req.headers.cookie)[oidcStateCookieName];
const cookieStates = getValidOidcCookieStates(req);
if (!code || !state || state !== cookieState) {
throw new Error("OIDC callback state validation failed");
if (!code || !state || !cookieStates.includes(state)) {
res.clearCookie(oidcStateCookieName, clearCookieOptions());
res.redirect("/auth/login?returnTo=/");
return;
}
const pendingLogin = pendingLogins.get(state);
pendingLogins.delete(state);
res.clearCookie(oidcStateCookieName, clearCookieOptions());
setOidcStateCookie(res, cookieStates.filter((cookieState) => cookieState !== state));
if (!pendingLogin || pendingLogin.expiresAt < Date.now()) {
throw new Error("OIDC login state expired");
@@ -1073,6 +1076,36 @@ function pruneExpiredState() {
}
}
function getValidOidcCookieStates(req) {
const rawValue = parseCookies(req.headers.cookie)[oidcStateCookieName];
if (!rawValue) return [];
const seen = new Set();
return rawValue
.split(".")
.filter((state) => /^[A-Za-z0-9_-]{32,256}$/.test(state))
.filter((state) => {
if (seen.has(state)) return false;
seen.add(state);
return true;
})
.filter((state) => {
const pendingLogin = pendingLogins.get(state);
return Boolean(pendingLogin && pendingLogin.expiresAt >= Date.now());
});
}
function setOidcStateCookie(res, states) {
if (!states.length) {
res.clearCookie(oidcStateCookieName, clearCookieOptions());
return;
}
res.cookie(oidcStateCookieName, states.join("."), cookieOptions(pendingLoginTtlMs));
}
function parseCookies(cookieHeader) {
if (!cookieHeader) return {};