From 02db391bfeeee3c335864e1b5b629c7a5f6efc3c Mon Sep 17 00:00:00 2001 From: Shni Date: Wed, 15 Oct 2025 03:53:35 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20Restaurar=20y=20mejorar=20la=20l=C3=B3g?= =?UTF-8?q?ica=20de=20manejo=20de=20sesiones=20y=20estado,=20incluyendo=20?= =?UTF-8?q?la=20verificaci=C3=B3n=20de=20SID=20y=20el=20almacenamiento=20d?= =?UTF-8?q?e=20estado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/server/server.ts | 90 +++++++++++---------- src/server/views/pages/dashboard.ejs | 16 ++-- src/server/views/partials/dashboard_nav.ejs | 79 ++++++++++-------- 3 files changed, 105 insertions(+), 80 deletions(-) diff --git a/src/server/server.ts b/src/server/server.ts index ae47568..bcd4c5d 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -72,6 +72,53 @@ function parseCookies(req: IncomingMessage) { }, {}); } +// --- Session and state helpers (restored) --- +function getSessionSecret(): string { + // Prefer explicit env var; fallback to package name + version for a deterministic but non-secret default. + if (process.env.SESSION_SECRET && process.env.SESSION_SECRET.length > 8) + return process.env.SESSION_SECRET; + const name = pkg?.name || "amayo"; + const version = pkg?.version || "0"; + return createHmac("sha256", "fallback") + .update(name + "@" + version) + .digest("hex"); +} + +function unsignSid(signed: string | undefined): string | null { + if (!signed) return null; + const parts = String(signed).split("."); + if (parts.length !== 2) return null; + const sid = parts[0]; + const sig = parts[1]; + try { + const expected = createHmac("sha256", getSessionSecret()) + .update(sid) + .digest("base64url"); + // timing-safe compare + const a = Buffer.from(sig); + const b = Buffer.from(expected); + if (a.length !== b.length) return null; + if (!timingSafeEqual(a, b)) return null; + return sid; + } catch { + return null; + } +} + +function storeState(key: string) { + STATE_STORE.set(key, { ts: Date.now() }); +} + +function hasState(key: string) { + const v = STATE_STORE.get(key); + if (!v) return false; + if (Date.now() - v.ts > STATE_TTL_MS) { + STATE_STORE.delete(key); + return false; + } + return true; +} + function setSessionCookie(res: ServerResponse, sid: string) { // Sign the SID to prevent tampering const secret = getSessionSecret(); @@ -92,51 +139,10 @@ function clearSessionCookie(res: ServerResponse) { const secure = isProd ? "; Secure" : ""; res.setHeader( "Set-Cookie", - `amayo_sid=; HttpOnly; Path=/; Max-Age=0; SameSite=Lax${secure}` + `amayo_sid=; HttpOnly; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT${secure}` ); } -function getSessionSecret() { - return ( - process.env.SESSION_SECRET || - process.env.DISCORD_CLIENT_SECRET || - "dev-session-secret" - ); -} - -function unsignSid(signed: string | undefined): string | null { - if (!signed) return null; - const decoded = decodeURIComponent(signed); - const parts = decoded.split("."); - if (parts.length !== 2) return null; - const [sid, sig] = parts; - try { - const secret = getSessionSecret(); - const expected = createHmac("sha256", secret).update(sid).digest(); - const got = Buffer.from(sig, "base64url"); - // timing safe compare - if (expected.length !== got.length) return null; - if (!timingSafeEqual(expected, got)) return null; - return sid; - } catch { - return null; - } -} - -function storeState(state: string) { - STATE_STORE.set(state, { ts: Date.now() }); -} - -function hasState(state: string) { - const v = STATE_STORE.get(state); - if (!v) return false; - if (Date.now() - v.ts > STATE_TTL_MS) { - STATE_STORE.delete(state); - return false; - } - return true; -} - function createSession(data: any) { // Evict oldest if over cap if (SESSIONS.size >= MAX_SESSIONS) { diff --git a/src/server/views/pages/dashboard.ejs b/src/server/views/pages/dashboard.ejs index 656c11a..b6bba89 100644 --- a/src/server/views/pages/dashboard.ejs +++ b/src/server/views/pages/dashboard.ejs @@ -43,9 +43,9 @@
<% if (guilds && guilds.length) { %> - <% const sorted = guilds.slice().sort((a,b)=> a.name.localeCompare(b.name)); const withBot = sorted.filter(g=> g.botInGuild !== false); const withoutBot = sorted.filter(g=> g.botInGuild === false); %> + <% const sorted = guilds.slice().sort((a,b)=> a.name.localeCompare(b.name)); const withBot = sorted.filter(g=> g.botInGuild === true); const withoutBot = sorted.filter(g=> g.botInGuild !== true); %> <% withBot.forEach(g => { %> -
+
<% if (g.icon) { %> icon @@ -53,7 +53,7 @@
S
<% } %>
-
<%= g.name %>
+
<%= g.name %> <% if (g.botInGuild === true) { %>(Bot)<% } else { %>(Invitar)<% } %>
@@ -64,7 +64,7 @@ <% if (withoutBot && withoutBot.length) { %>
<% withoutBot.forEach(g => { %> -
+
<% if (g.icon) { %> icon @@ -110,11 +110,15 @@ const id = el.dataset.id; const bot = el.getAttribute('data-bot'); const invite = el.getAttribute('data-invite'); - if (bot === '0' && invite) { + const isDim = el.classList && el.classList.contains('opacity-60'); + if (bot === '1') { + window.location.href = `/dashboard/${id}/overview`; + return; + } + if ((bot === '0' || isDim) && invite) { window.open(invite, '_blank', 'noopener'); return; } - window.location.href = `/dashboard/${id}/overview`; } }); })(); diff --git a/src/server/views/partials/dashboard_nav.ejs b/src/server/views/partials/dashboard_nav.ejs index 3d3d568..8f28192 100644 --- a/src/server/views/partials/dashboard_nav.ejs +++ b/src/server/views/partials/dashboard_nav.ejs @@ -22,23 +22,30 @@