feat: Restaurar y mejorar la lógica de manejo de sesiones y estado, incluyendo la verificación de SID y el almacenamiento de estado
This commit is contained in:
@@ -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) {
|
function setSessionCookie(res: ServerResponse, sid: string) {
|
||||||
// Sign the SID to prevent tampering
|
// Sign the SID to prevent tampering
|
||||||
const secret = getSessionSecret();
|
const secret = getSessionSecret();
|
||||||
@@ -92,51 +139,10 @@ function clearSessionCookie(res: ServerResponse) {
|
|||||||
const secure = isProd ? "; Secure" : "";
|
const secure = isProd ? "; Secure" : "";
|
||||||
res.setHeader(
|
res.setHeader(
|
||||||
"Set-Cookie",
|
"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) {
|
function createSession(data: any) {
|
||||||
// Evict oldest if over cap
|
// Evict oldest if over cap
|
||||||
if (SESSIONS.size >= MAX_SESSIONS) {
|
if (SESSIONS.size >= MAX_SESSIONS) {
|
||||||
|
|||||||
@@ -43,9 +43,9 @@
|
|||||||
|
|
||||||
<div id="cardList" class="max-h-72 overflow-auto rounded">
|
<div id="cardList" class="max-h-72 overflow-auto rounded">
|
||||||
<% if (guilds && guilds.length) { %>
|
<% 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 => { %>
|
<% withBot.forEach(g => { %>
|
||||||
<div class="p-3 cursor-pointer hover:bg-white/5 flex items-center justify-between text-white" data-id="<%= g.id %>" data-bot="1" data-invite="https://discord.com/oauth2/authorize?client_id=991062751633883136&permissions=2416176272&integration_type=0&scope=bot&guild_id=<%= g.id %>">
|
<div onclick="location.href='/dashboard/<%= g.id %>/overview'" role="button" tabindex="0" class="p-3 cursor-pointer hover:bg-white/5 flex items-center justify-between text-white" data-id="<%= g.id %>" data-bot="1" data-invite="https://discord.com/oauth2/authorize?client_id=991062751633883136&permissions=2416176272&integration_type=0&scope=bot&guild_id=<%= g.id %>">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<% if (g.icon) { %>
|
<% if (g.icon) { %>
|
||||||
<img src="https://cdn.discordapp.com/icons/<%= g.id %>/<%= g.icon %>.webp" class="w-8 h-8 rounded-full" alt="icon">
|
<img src="https://cdn.discordapp.com/icons/<%= g.id %>/<%= g.icon %>.webp" class="w-8 h-8 rounded-full" alt="icon">
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
<div class="w-8 h-8 rounded-full bg-white/8 flex items-center justify-center text-xs text-white">S</div>
|
<div class="w-8 h-8 rounded-full bg-white/8 flex items-center justify-center text-xs text-white">S</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
<div class="text-left">
|
<div class="text-left">
|
||||||
<div class="font-medium"><%= g.name %></div>
|
<div class="font-medium"><%= g.name %> <% if (g.botInGuild === true) { %><span class="text-xs text-emerald-300">(Bot)</span><% } else { %><span class="text-xs text-sky-300">(Invitar)</span><% } %></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
<% if (withoutBot && withoutBot.length) { %>
|
<% if (withoutBot && withoutBot.length) { %>
|
||||||
<div class="border-t border-white/6 my-2"></div>
|
<div class="border-t border-white/6 my-2"></div>
|
||||||
<% withoutBot.forEach(g => { %>
|
<% withoutBot.forEach(g => { %>
|
||||||
<div class="p-3 cursor-pointer hover:bg-white/5 flex items-center justify-between text-white opacity-60" data-id="<%= g.id %>" data-bot="0" data-invite="https://discord.com/oauth2/authorize?client_id=991062751633883136&permissions=2416176272&integration_type=0&scope=bot&guild_id=<%= g.id %>">
|
<div onclick="window.open('https://discord.com/oauth2/authorize?client_id=991062751633883136&permissions=2416176272&integration_type=0&scope=bot&guild_id=<%= g.id %>', '_blank', 'noopener')" role="button" tabindex="0" class="p-3 cursor-pointer hover:bg-white/5 flex items-center justify-between text-white opacity-60" data-id="<%= g.id %>" data-bot="0" data-invite="https://discord.com/oauth2/authorize?client_id=991062751633883136&permissions=2416176272&integration_type=0&scope=bot&guild_id=<%= g.id %>">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<% if (g.icon) { %>
|
<% if (g.icon) { %>
|
||||||
<img src="https://cdn.discordapp.com/icons/<%= g.id %>/<%= g.icon %>.webp" class="w-8 h-8 rounded-full" alt="icon">
|
<img src="https://cdn.discordapp.com/icons/<%= g.id %>/<%= g.icon %>.webp" class="w-8 h-8 rounded-full" alt="icon">
|
||||||
@@ -110,11 +110,15 @@
|
|||||||
const id = el.dataset.id;
|
const id = el.dataset.id;
|
||||||
const bot = el.getAttribute('data-bot');
|
const bot = el.getAttribute('data-bot');
|
||||||
const invite = el.getAttribute('data-invite');
|
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');
|
window.open(invite, '_blank', 'noopener');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window.location.href = `/dashboard/${id}/overview`;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -23,22 +23,29 @@
|
|||||||
</button>
|
</button>
|
||||||
<div id="miniGuildList" class="origin-top-left absolute top-full left-0 mt-2 w-56 bg-white/80 backdrop-blur-md rounded-lg p-1 hidden transition-transform duration-150 z-50" style="max-height:18rem; overflow:auto; background-color: rgba(12,15,18,0.92);">
|
<div id="miniGuildList" class="origin-top-left absolute top-full left-0 mt-2 w-56 bg-white/80 backdrop-blur-md rounded-lg p-1 hidden transition-transform duration-150 z-50" style="max-height:18rem; overflow:auto; background-color: rgba(12,15,18,0.92);">
|
||||||
<% if (guilds && guilds.length) { %>
|
<% if (guilds && guilds.length) { %>
|
||||||
<% const withBot = guilds.filter(g=> g.botInGuild !== false); const withoutBot = guilds.filter(g=> g.botInGuild === false); %>
|
<% const withBot = guilds.filter(g=> g.botInGuild === true); const withoutBot = guilds.filter(g=> g.botInGuild !== true); %>
|
||||||
<% withBot.forEach(g => { %>
|
<% withBot.forEach(g => { %>
|
||||||
<div class="p-1 rounded-md hover:bg-white/5 cursor-pointer text-white guild-item flex items-center gap-3" data-id="<%= g.id %>" data-bot="1" data-invite="https://discord.com/oauth2/authorize?client_id=991062751633883136&permissions=2416176272&integration_type=0&scope=bot&guild_id=<%= g.id %>">
|
<div onclick="location.href='/dashboard/<%= g.id %>/overview'" role="button" tabindex="0" class="p-1 rounded-md hover:bg-white/5 cursor-pointer text-white guild-item flex items-center gap-3" data-id="<%= g.id %>" data-bot="1" data-invite="https://discord.com/oauth2/authorize?client_id=991062751633883136&permissions=2416176272&integration_type=0&scope=bot&guild_id=<%= g.id %>">
|
||||||
<% if (g.icon) { %>
|
<% if (g.icon) { %>
|
||||||
<img src="https://cdn.discordapp.com/icons/<%= g.id %>/<%= g.icon %>.webp" class="w-5 h-5 rounded-full" alt="icon">
|
<img src="https://cdn.discordapp.com/icons/<%= g.id %>/<%= g.icon %>.webp" class="w-5 h-5 rounded-full" alt="icon">
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
<div class="w-5 h-5 rounded-full bg-white/8 flex items-center justify-center text-xs text-white">S</div>
|
<div class="w-5 h-5 rounded-full bg-white/8 flex items-center justify-center text-xs text-white">S</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
<div class="flex-1 text-sm truncate pl-1"><%= g.name %></div>
|
<div class="flex-1 text-sm truncate pl-1">
|
||||||
|
<%= g.name %>
|
||||||
|
<% if (g.botInGuild === true) { %>
|
||||||
|
<span class="ml-2 text-xs text-emerald-300">(Bot)</span>
|
||||||
|
<% } else { %>
|
||||||
|
<span class="ml-2 text-xs text-sky-300">(Invitar)</span>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
<!-- invite button removed from inline list; rows without bot are dimmed and clicking them opens the invite -->
|
<!-- invite button removed from inline list; rows without bot are dimmed and clicking them opens the invite -->
|
||||||
</div>
|
</div>
|
||||||
<% }) %>
|
<% }) %>
|
||||||
<% if (withoutBot && withoutBot.length) { %>
|
<% if (withoutBot && withoutBot.length) { %>
|
||||||
<div class="border-t border-white/6 my-1"></div>
|
<div class="border-t border-white/6 my-1"></div>
|
||||||
<% withoutBot.forEach(g => { %>
|
<% withoutBot.forEach(g => { %>
|
||||||
<div class="p-1 rounded-md hover:bg-white/5 cursor-pointer text-white guild-item flex items-center gap-3 opacity-60" data-id="<%= g.id %>" data-bot="0" data-invite="https://discord.com/oauth2/authorize?client_id=991062751633883136&permissions=2416176272&integration_type=0&scope=bot&guild_id=<%= g.id %>">
|
<div onclick="window.open('https://discord.com/oauth2/authorize?client_id=991062751633883136&permissions=2416176272&integration_type=0&scope=bot&guild_id=<%= g.id %>', '_blank', 'noopener')" role="button" tabindex="0" class="p-1 rounded-md hover:bg-white/5 cursor-pointer text-white guild-item flex items-center gap-3 opacity-60" data-id="<%= g.id %>" data-bot="0" data-invite="https://discord.com/oauth2/authorize?client_id=991062751633883136&permissions=2416176272&integration_type=0&scope=bot&guild_id=<%= g.id %>">
|
||||||
<% if (g.icon) { %>
|
<% if (g.icon) { %>
|
||||||
<img src="https://cdn.discordapp.com/icons/<%= g.id %>/<%= g.icon %>.webp" class="w-5 h-5 rounded-full" alt="icon">
|
<img src="https://cdn.discordapp.com/icons/<%= g.id %>/<%= g.icon %>.webp" class="w-5 h-5 rounded-full" alt="icon">
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
@@ -130,26 +137,27 @@
|
|||||||
openDropdown(btn, list);
|
openDropdown(btn, list);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Array.from(list.querySelectorAll('.guild-item')).forEach(it=>{
|
// Use event delegation on the list so clicks on children are handled reliably
|
||||||
it.addEventListener('click', ()=>{
|
list.addEventListener('click', (e)=>{
|
||||||
const id = it.getAttribute('data-id');
|
let el = e.target;
|
||||||
const bot = it.getAttribute('data-bot');
|
while (el && !el.dataset?.id && el !== list) el = el.parentElement;
|
||||||
const invite = it.getAttribute('data-invite');
|
if (!el || el === list) return;
|
||||||
if (bot === '0' && invite) {
|
const id = el.getAttribute('data-id');
|
||||||
// open invite in a new tab
|
const bot = el.getAttribute('data-bot');
|
||||||
window.open(invite, '_blank', 'noopener');
|
const invite = el.getAttribute('data-invite');
|
||||||
|
const isDim = el.classList && el.classList.contains('opacity-60');
|
||||||
|
if (bot === '1') {
|
||||||
|
closeDropdown(btn, list);
|
||||||
|
window.location.href = `/dashboard/${id}/overview`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (id) window.location.href = `/dashboard/${id}/overview`;
|
// if not explicitly '1', treat as missing and open invite if available
|
||||||
});
|
if ((bot === '0' || isDim) && invite) {
|
||||||
// keyboard activation (Enter / Space)
|
window.open(invite, '_blank', 'noopener');
|
||||||
it.addEventListener('keydown', (ev)=>{
|
closeDropdown(btn, list);
|
||||||
if (ev.key === 'Enter' || ev.key === ' ') {
|
return;
|
||||||
ev.preventDefault();
|
|
||||||
it.click();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (userBtn && userMenu) {
|
if (userBtn && userMenu) {
|
||||||
userBtn.addEventListener('click', (e)=>{
|
userBtn.addEventListener('click', (e)=>{
|
||||||
@@ -211,21 +219,27 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="mobileGuildList" class="space-y-2">
|
<div id="mobileGuildList" class="space-y-2">
|
||||||
<% if (guilds && guilds.length) { %>
|
<% if (guilds && guilds.length) { %>
|
||||||
<% const withBot = guilds.filter(g=> g.botInGuild !== false); const withoutBot = guilds.filter(g=> g.botInGuild === false); %>
|
<% const withBot = guilds.filter(g=> g.botInGuild === true); const withoutBot = guilds.filter(g=> g.botInGuild !== true); %>
|
||||||
<% withBot.forEach(g => { %>
|
<% withBot.forEach(g => { %>
|
||||||
<div class="flex items-center gap-3 p-2 rounded-md hover:bg-white/5 cursor-pointer" data-id="<%= g.id %>" data-bot="1" data-invite="https://discord.com/oauth2/authorize?client_id=991062751633883136&permissions=2416176272&integration_type=0&scope=bot&guild_id=<%= g.id %>">
|
<div onclick="location.href='/dashboard/<%= g.id %>/overview'" role="button" tabindex="0" class="flex items-center gap-3 p-2 rounded-md hover:bg-white/5 cursor-pointer" data-id="<%= g.id %>" data-bot="1" data-invite="https://discord.com/oauth2/authorize?client_id=991062751633883136&permissions=2416176272&integration_type=0&scope=bot&guild_id=<%= g.id %>">
|
||||||
<% if (g.icon) { %>
|
<% if (g.icon) { %>
|
||||||
<img src="https://cdn.discordapp.com/icons/<%= g.id %>/<%= g.icon %>.webp" class="w-8 h-8 rounded-full" alt="icon">
|
<img src="https://cdn.discordapp.com/icons/<%= g.id %>/<%= g.icon %>.webp" class="w-8 h-8 rounded-full" alt="icon">
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
<div class="w-8 h-8 rounded-full bg-white/8"></div>
|
<div class="w-8 h-8 rounded-full bg-white/8"></div>
|
||||||
<% } %>
|
<% } %>
|
||||||
<div class="text-white"><%= g.name %></div>
|
<div class="text-white"><%= g.name %>
|
||||||
|
<% if (g.botInGuild === true) { %>
|
||||||
|
<span class="ml-2 text-xs text-emerald-300">(Bot)</span>
|
||||||
|
<% } else { %>
|
||||||
|
<span class="ml-2 text-xs text-sky-300">(Invitar)</span>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% }) %>
|
<% }) %>
|
||||||
<% if (withoutBot && withoutBot.length) { %>
|
<% if (withoutBot && withoutBot.length) { %>
|
||||||
<div class="border-t border-white/6 mt-2 pt-2"></div>
|
<div class="border-t border-white/6 mt-2 pt-2"></div>
|
||||||
<% withoutBot.forEach(g => { %>
|
<% withoutBot.forEach(g => { %>
|
||||||
<div class="flex items-center gap-3 p-2 rounded-md hover:bg-white/5 cursor-pointer opacity-60" data-id="<%= g.id %>" data-bot="0" data-invite="https://discord.com/oauth2/authorize?client_id=991062751633883136&permissions=2416176272&integration_type=0&scope=bot&guild_id=<%= g.id %>">
|
<div onclick="window.open('https://discord.com/oauth2/authorize?client_id=991062751633883136&permissions=2416176272&integration_type=0&scope=bot&guild_id=<%= g.id %>', '_blank', 'noopener')" role="button" tabindex="0" class="flex items-center gap-3 p-2 rounded-md hover:bg-white/5 cursor-pointer opacity-60" data-id="<%= g.id %>" data-bot="0" data-invite="https://discord.com/oauth2/authorize?client_id=991062751633883136&permissions=2416176272&integration_type=0&scope=bot&guild_id=<%= g.id %>">
|
||||||
<% if (g.icon) { %>
|
<% if (g.icon) { %>
|
||||||
<img src="https://cdn.discordapp.com/icons/<%= g.id %>/<%= g.icon %>.webp" class="w-8 h-8 rounded-full" alt="icon">
|
<img src="https://cdn.discordapp.com/icons/<%= g.id %>/<%= g.icon %>.webp" class="w-8 h-8 rounded-full" alt="icon">
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
@@ -291,8 +305,9 @@
|
|||||||
if (el && el.dataset && el.dataset.id) {
|
if (el && el.dataset && el.dataset.id) {
|
||||||
const bot = el.getAttribute('data-bot');
|
const bot = el.getAttribute('data-bot');
|
||||||
const invite = el.getAttribute('data-invite');
|
const invite = el.getAttribute('data-invite');
|
||||||
if (bot === '0' && invite) { window.open(invite, '_blank', 'noopener'); return; }
|
const isDim = el.classList && el.classList.contains('opacity-60');
|
||||||
window.location.href = `/dashboard/${el.dataset.id}/overview`;
|
if (bot === '1') { window.location.href = `/dashboard/${el.dataset.id}/overview`; return; }
|
||||||
|
if ((bot === '0' || isDim) && invite) { window.open(invite, '_blank', 'noopener'); return; }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user