TypeScript 97.6%
SQL 1.4%
JavaScript 0.5%
1import { db, securityEvents } from "@spinza/database";2import type { FastifyRequest } from "fastify";34export type SecurityEventType =5 | "login.success"6 | "login.failed"7 | "register"8 | "logout"9 | "password.changed"10 | "recovery.used"11 | "recovery.failed"12 | "rate_limit"13 | "spin.suspicious"14 | "admin.login.success"15 | "admin.login.failed"16 | "admin.wallet.adjust"17 | "admin.user.status"18 | "admin.game.lifecycle"19 | "admin.game.flags"20 | "admin.flag"21 | "admin.setting"22 | "admin.maintenance"23 | "csrf.rejected";2425export function clientIp(req: FastifyRequest): string {26 const xff = req.headers["x-forwarded-for"];27 if (typeof xff === "string" && xff.length) return xff.split(",").pop()!.trim();28 return req.ip;29}3031export async function logSecurity(32 req: FastifyRequest | null,33 type: SecurityEventType,34 opts: { userId?: string | null; adminId?: string | null; severity?: "info" | "warn" | "high"; meta?: Record<string, unknown> } = {},35): Promise<void> {36 try {37 await db.insert(securityEvents).values({38 userId: opts.userId ?? null,39 adminId: opts.adminId ?? null,40 type,41 severity: opts.severity ?? (type.includes("failed") || type === "rate_limit" || type === "csrf.rejected" ? "warn" : "info"),42 ip: req ? clientIp(req) : null,43 userAgent: req ? (req.headers["user-agent"] ?? "").slice(0, 300) : null,44 meta: opts.meta ?? null,45 });46 } catch (e) {47 req?.log.error({ err: e }, "security event write failed");48 }49}50