feat: add user authentication UI to header component
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.
This commit is contained in:
1 parent
e641138887
commit
55eebde0ba
55 files changed
+7353
-7
No files matched your search
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Módulo de conexão com Redis
|
||||
* Gerencia conexão e fornece helpers para cache e sessões
|
||||
*/
|
||||
|
||||
import { createClient, RedisClientType } from "redis";
|
||||
|
||||
let client: RedisClientType | null = null;
|
||||
|
||||
/**
|
||||
* Valida variáveis de ambiente necessárias
|
||||
*/
|
||||
function validateEnvVars() {
|
||||
if (!process.env.REDIS_DB) {
|
||||
throw new Error(
|
||||
"REDIS_DB não está definido. Configure a variável de ambiente REDIS_DB com a connection string do Redis."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtém ou cria o cliente Redis
|
||||
*/
|
||||
export function getRedisClient(): RedisClientType {
|
||||
if (!client) {
|
||||
validateEnvVars();
|
||||
|
||||
const connectionString = process.env.REDIS_DB;
|
||||
|
||||
client = createClient({
|
||||
url: connectionString,
|
||||
});
|
||||
|
||||
// Event listeners para debug
|
||||
client.on("error", (err) => {
|
||||
console.error("❌ Erro no cliente Redis:", err);
|
||||
});
|
||||
|
||||
client.on("connect", () => {
|
||||
console.log("✅ Conectado ao Redis");
|
||||
});
|
||||
|
||||
client.on("ready", () => {
|
||||
console.log("✅ Redis está pronto para uso");
|
||||
});
|
||||
|
||||
// Conectar
|
||||
client.connect().catch((err) => {
|
||||
console.error("❌ Erro ao conectar com Redis:", err);
|
||||
});
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica se Redis está conectado
|
||||
*/
|
||||
export async function isRedisConnected(): Promise<boolean> {
|
||||
try {
|
||||
const redisClient = getRedisClient();
|
||||
if (!redisClient.isOpen) {
|
||||
await redisClient.connect();
|
||||
}
|
||||
await redisClient.ping();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("❌ Redis não está conectado:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fecha conexão com Redis
|
||||
*/
|
||||
export async function closeRedis(): Promise<void> {
|
||||
if (client && client.isOpen) {
|
||||
await client.quit();
|
||||
client = null;
|
||||
console.log("🔌 Conexão Redis fechada");
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup ao encerrar o processo
|
||||
if (typeof process !== "undefined") {
|
||||
process.on("SIGINT", async () => {
|
||||
await closeRedis();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on("SIGTERM", async () => {
|
||||
await closeRedis();
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user