ФУНКЦИИ - NODEDC LAUNCHER: direct auth redirect
This commit is contained in:
+38
-5
@@ -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 {};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user