UNPKG

webpods

Version:

Append-only log service with OAuth authentication

235 lines 6.52 kB
/** * Session management routes */ import { Router } from "express"; import { createLogger } from "../logger.js"; import { authenticateHybrid as authenticate } from "../middleware/hybrid-auth.js"; import { getUserSessions, revokeSession, revokeUserSessions, } from "./session-store.js"; const logger = createLogger("webpods:auth:sessions"); const router = Router(); /** * Get current session info * GET /auth/session */ router.get("/session", (req, res) => { const session = req.session; if (!session || !session.user) { res.status(401).json({ error: { code: "NO_SESSION", message: "No active session", }, }); return; } res.json({ user: session.user, sessionId: session.id, createdAt: new Date(session.cookie.originalMaxAge ? Date.now() - (session.cookie.maxAge - session.cookie.originalMaxAge) : Date.now()), expiresAt: session.cookie.expires || new Date(Date.now() + session.cookie.maxAge), }); }); /** * List all active sessions for the authenticated user * GET /auth/sessions */ router.get("/sessions", authenticate, async (req, res) => { if (!req.auth) { res.status(401).json({ error: { code: "UNAUTHORIZED", message: "Authentication required", }, }); return; } try { const sessions = await getUserSessions(req.auth.user_id); res.json({ sessions, count: sessions.length, }); } catch (error) { logger.error("Failed to list sessions", { error, userId: req.auth.user_id, }); res.status(500).json({ error: { code: "INTERNAL_ERROR", message: "Failed to retrieve sessions", }, }); } }); /** * Revoke a specific session * DELETE /auth/sessions/:sessionId */ router.delete("/sessions/:sessionId", authenticate, async (req, res) => { if (!req.auth) { res.status(401).json({ error: { code: "UNAUTHORIZED", message: "Authentication required", }, }); return; } const sessionId = req.params.sessionId; if (!sessionId) { res.status(400).json({ error: { code: "INVALID_REQUEST", message: "Session ID is required", }, }); return; } try { // Verify the session belongs to the user const sessions = await getUserSessions(req.auth.user_id); const session = sessions.find((s) => s.id === sessionId); if (!session) { res.status(404).json({ error: { code: "SESSION_NOT_FOUND", message: "Session not found or does not belong to user", }, }); return; } const revoked = await revokeSession(sessionId); if (revoked) { logger.info("Session revoked", { sessionId, userId: req.auth.user_id, }); res.json({ success: true, message: "Session revoked successfully", }); } else { res.status(404).json({ error: { code: "SESSION_NOT_FOUND", message: "Session not found", }, }); } } catch (error) { logger.error("Failed to revoke session", { error, sessionId, userId: req.auth.user_id, }); res.status(500).json({ error: { code: "INTERNAL_ERROR", message: "Failed to revoke session", }, }); } }); /** * Revoke all sessions for the authenticated user * DELETE /auth/sessions */ router.delete("/sessions", authenticate, async (req, res) => { if (!req.auth) { res.status(401).json({ error: { code: "UNAUTHORIZED", message: "Authentication required", }, }); return; } try { const count = await revokeUserSessions(req.auth.user_id); logger.info("All sessions revoked", { userId: req.auth.user_id, count }); res.json({ success: true, message: `Revoked ${count} session(s)`, count, }); } catch (error) { logger.error("Failed to revoke all sessions", { error, userId: req.auth.user_id, }); res.status(500).json({ error: { code: "INTERNAL_ERROR", message: "Failed to revoke sessions", }, }); } }); /** * Logout current session * POST /auth/logout */ router.post("/logout", (req, res) => { const session = req.session; if (!session) { res.json({ success: true, message: "No active session", }); return; } session.destroy((err) => { if (err) { logger.error("Failed to destroy session", { error: err?.message || err, sessionId: session?.id, }); // Even if destroy fails, clear the cookie res.clearCookie("webpods.sid"); res.status(500).json({ error: { code: "LOGOUT_ERROR", message: "Failed to logout completely", }, }); } else { res.clearCookie("webpods.sid"); res.json({ success: true, message: "Logged out successfully", }); } }); }); /** * Logout current session (GET for browser convenience) * GET /auth/logout */ router.get("/logout", (req, res) => { const session = req.session; const redirect = req.query.redirect || "/"; if (!session) { res.redirect(redirect); return; } session.destroy((err) => { if (err) { logger.error("Failed to destroy session", { error: err?.message || err, sessionId: session?.id, }); } res.clearCookie("webpods.sid"); res.redirect(redirect); }); }); export default router; //# sourceMappingURL=session-routes.js.map