ФУНКЦИИ - NODEDC LAUNCHER: direct auth redirect
This commit is contained in:
parent
a1fb2d0f83
commit
a1e8cacd88
|
|
@ -9,3 +9,4 @@ yarn-debug.log*
|
|||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
*.tsbuildinfo
|
||||
public/storage/backups/
|
||||
|
|
|
|||
|
|
@ -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 {};
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,6 @@ export function LauncherApp() {
|
|||
const [adminOpen, setAdminOpen] = useState(false);
|
||||
const [authSession, setAuthSession] = useState<AuthSession | null>(null);
|
||||
const [authApps, setAuthApps] = useState<LauncherAuthApp[] | null>(null);
|
||||
const [authError, setAuthError] = useState<string | null>(null);
|
||||
const [profileSettingsOpen, setProfileSettingsOpen] = useState(false);
|
||||
const [pendingAccessAssignments, setPendingAccessAssignments] = useState<Record<string, AccessAssignmentValue>>({});
|
||||
|
||||
|
|
@ -160,7 +159,6 @@ export function LauncherApp() {
|
|||
if (!isMounted) return;
|
||||
|
||||
setAuthSession(session);
|
||||
setAuthError(null);
|
||||
|
||||
if (!session.authenticated) {
|
||||
setAuthApps([]);
|
||||
|
|
@ -178,7 +176,7 @@ export function LauncherApp() {
|
|||
|
||||
setAuthSession({ authenticated: false, loginUrl: "/auth/login" });
|
||||
setAuthApps([]);
|
||||
setAuthError(error instanceof Error ? error.message : "Не удалось проверить сессию платформы");
|
||||
console.warn(error instanceof Error ? error.message : "Не удалось проверить сессию платформы");
|
||||
});
|
||||
|
||||
return () => {
|
||||
|
|
@ -186,6 +184,12 @@ export function LauncherApp() {
|
|||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!authSession || authSession.authenticated) return;
|
||||
|
||||
window.location.replace(authSession.loginUrl || "/auth/login");
|
||||
}, [authSession]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!authSession?.authenticated) return;
|
||||
|
||||
|
|
@ -247,7 +251,6 @@ export function LauncherApp() {
|
|||
if (!isMounted) return;
|
||||
|
||||
setAuthSession(nextSession);
|
||||
setAuthError(null);
|
||||
|
||||
if (!nextSession.authenticated) {
|
||||
setAuthApps([]);
|
||||
|
|
@ -473,7 +476,7 @@ export function LauncherApp() {
|
|||
}
|
||||
|
||||
if (!authSession.authenticated) {
|
||||
return <AuthStateScreen title="Вход на платформу NODE.DC" description="Войдите, чтобы открыть рабочую область и доступы." error={authError} loginUrl={authSession.loginUrl} />;
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
Loading…
Reference in New Issue