Add login and register dialogs, user dropdown menu with profile link and logout option, and integrate with session management hook. Prevent hydration mismatches by conditionally rendering auth elements after mount. Update package-lock.json with new @esbuild dependencies for build tooling.
144 lines
3.3 KiB
TypeScript
144 lines
3.3 KiB
TypeScript
/**
|
|
* Sistema de sessão usando Redis
|
|
* Armazena informações de sessão do usuário
|
|
*/
|
|
|
|
import { getRedisClient } from "./index";
|
|
|
|
const SESSION_PREFIX = "session:";
|
|
const USER_SESSION_PREFIX = "user_session:";
|
|
const SESSION_TTL = 60 * 60 * 24 * 7; // 7 dias (mesmo que refresh token)
|
|
|
|
export interface SessionData {
|
|
userId: number;
|
|
username: string;
|
|
email: string;
|
|
accessToken: string;
|
|
createdAt: number;
|
|
}
|
|
|
|
/**
|
|
* Cria ou atualiza uma sessão
|
|
*/
|
|
export async function setSession(
|
|
sessionId: string,
|
|
data: SessionData
|
|
): Promise<void> {
|
|
try {
|
|
const client = getRedisClient();
|
|
if (!client.isOpen) {
|
|
await client.connect();
|
|
}
|
|
|
|
const key = `${SESSION_PREFIX}${sessionId}`;
|
|
await client.setEx(key, SESSION_TTL, JSON.stringify(data));
|
|
|
|
// Também armazenar referência por userId para invalidar todas as sessões
|
|
const userKey = `${USER_SESSION_PREFIX}${data.userId}`;
|
|
await client.sAdd(userKey, sessionId);
|
|
await client.expire(userKey, SESSION_TTL);
|
|
} catch (error) {
|
|
console.error("❌ Erro ao criar sessão:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtém dados de uma sessão
|
|
*/
|
|
export async function getSession(
|
|
sessionId: string
|
|
): Promise<SessionData | null> {
|
|
try {
|
|
const client = getRedisClient();
|
|
if (!client.isOpen) {
|
|
await client.connect();
|
|
}
|
|
|
|
const key = `${SESSION_PREFIX}${sessionId}`;
|
|
const data = await client.get(key);
|
|
|
|
if (!data) {
|
|
return null;
|
|
}
|
|
|
|
return JSON.parse(data) as SessionData;
|
|
} catch (error) {
|
|
console.error("❌ Erro ao obter sessão:", error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove uma sessão
|
|
*/
|
|
export async function deleteSession(sessionId: string): Promise<void> {
|
|
try {
|
|
const client = getRedisClient();
|
|
if (!client.isOpen) {
|
|
await client.connect();
|
|
}
|
|
|
|
const key = `${SESSION_PREFIX}${sessionId}`;
|
|
const data = await client.get(key);
|
|
|
|
if (data) {
|
|
const sessionData = JSON.parse(data) as SessionData;
|
|
const userKey = `${USER_SESSION_PREFIX}${sessionData.userId}`;
|
|
await client.sRem(userKey, sessionId);
|
|
}
|
|
|
|
await client.del(key);
|
|
} catch (error) {
|
|
console.error("❌ Erro ao remover sessão:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove todas as sessões de um usuário
|
|
*/
|
|
export async function deleteAllUserSessions(userId: number): Promise<void> {
|
|
try {
|
|
const client = getRedisClient();
|
|
if (!client.isOpen) {
|
|
await client.connect();
|
|
}
|
|
|
|
const userKey = `${USER_SESSION_PREFIX}${userId}`;
|
|
const sessionIds = await client.sMembers(userKey);
|
|
|
|
if (sessionIds.length > 0) {
|
|
const keys = sessionIds.map((id) => `${SESSION_PREFIX}${id}`);
|
|
await client.del(keys);
|
|
await client.del(userKey);
|
|
}
|
|
} catch (error) {
|
|
console.error("❌ Erro ao remover todas as sessões do usuário:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Atualiza TTL de uma sessão
|
|
*/
|
|
export async function refreshSession(sessionId: string): Promise<void> {
|
|
try {
|
|
const client = getRedisClient();
|
|
if (!client.isOpen) {
|
|
await client.connect();
|
|
}
|
|
|
|
const key = `${SESSION_PREFIX}${sessionId}`;
|
|
const exists = await client.exists(key);
|
|
|
|
if (exists) {
|
|
await client.expire(key, SESSION_TTL);
|
|
}
|
|
} catch (error) {
|
|
console.error("❌ Erro ao atualizar TTL da sessão:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|