SPB Git
2.2 KB · 29 lines typescript
1import { z } from "zod";2import { and, desc, eq, gt, isNull } from "drizzle-orm";3import { handler, parseBody, uuid, HttpError } from "@/lib/api";4import { db, schema } from "@/db";5import { revokeSession, revokeAllSessions } from "@/lib/auth/session";6import { audit } from "@/lib/audit";78function device(ua: string | null) {9  if (!ua) return "Unknown device";10  const os = /iPhone|iPad/.test(ua) ? "iOS" : /Android/.test(ua) ? "Android" : /Mac OS X/.test(ua) ? "macOS" : /Windows/.test(ua) ? "Windows" : /Linux/.test(ua) ? "Linux" : "Unknown OS";11  const br = /Edg\//.test(ua) ? "Edge" : /OPR\//.test(ua) ? "Opera" : /Chrome\//.test(ua) ? "Chrome" : /Firefox\//.test(ua) ? "Firefox" : /Safari\//.test(ua) ? "Safari" : /curl/.test(ua) ? "curl" : "Browser";12  return `${br} · ${os}`;13}14/** GET → active sessions of the current user (current one flagged). */15export const GET = handler(async ({ user }) => {16  const rows = await db.select().from(schema.sessions).where(and(eq(schema.sessions.userId, user.id), isNull(schema.sessions.revokedAt), gt(schema.sessions.expiresAt, new Date()))).orderBy(desc(schema.sessions.lastSeenAt)).limit(50);17  return Response.json(rows.map((s) => ({ id: s.id, device: device(s.userAgent), userAgent: s.userAgent, ip: s.ip, createdAt: s.createdAt, lastSeenAt: s.lastSeenAt, expiresAt: s.expiresAt, mfaPassed: s.mfaPassed, current: s.id === user.sessionId })));18});19/** DELETE { id } → revoke one session; DELETE { all: true } → revoke every other session ("sign out everywhere"). */20export const DELETE = handler(async ({ req, user }) => {21  const b = await parseBody(req, z.object({ id: uuid.optional(), all: z.boolean().optional() }));22  if (b.all) { await revokeAllSessions(user.id, user.sessionId); await audit(user.id, "auth.sessions_revoked_all"); return Response.json({ ok: true }); }23  if (!b.id) throw new HttpError(400, "id or all required");24  const [s] = await db.select({ id: schema.sessions.id }).from(schema.sessions).where(and(eq(schema.sessions.id, b.id), eq(schema.sessions.userId, user.id)));25  if (!s) throw new HttpError(404, "Session not found");26  await revokeSession(b.id); await audit(user.id, "auth.session_revoked", { type: "session", id: b.id });27  return Response.json({ ok: true, current: b.id === user.sessionId });28});29
Files · main
Menu

Theme