MBO-Tech-IT-Webseite/modules/02-admin-auth/files/lib/audit-log.ts

155 lines
4.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { createServiceClient } from "@/lib/supabase";
export interface AuditLogEntry {
id: string;
email: string;
ip_addr: string;
user_agent: string;
success: boolean;
timestamp: string;
reason?: string; // z.B. "invalid_password", "user_not_found", "account_inactive"
}
/**
* Protokolliert einen Login-Versuch (erfolgreich oder fehlgeschlagen)
*/
export async function logLoginAttempt(
email: string,
ipAddr: string,
success: boolean,
userAgent: string,
reason?: string
): Promise<void> {
try {
const db = createServiceClient();
await db.from("admin_audit_logs").insert({
email: email.toLowerCase().trim(),
ip_addr: ipAddr,
user_agent: userAgent,
success,
reason,
timestamp: new Date().toISOString(),
});
} catch (error) {
console.error("Fehler beim Audit-Logging:", error);
// Nicht werfen Login sollte nicht scheitern wenn Logging scheitert
}
}
/**
* Prüft auf verdächtige Aktivitäten (zu viele fehlgeschlagene Versuche)
* Gibt die Anzahl fehlgeschlagener Versuche in der letzten Stunde zurück
*/
export async function getFailedLoginCount(
emailOrIp: string,
type: "email" | "ip" = "email",
timeWindowMinutes = 60
): Promise<number> {
try {
const db = createServiceClient();
const since = new Date(Date.now() - timeWindowMinutes * 60 * 1000).toISOString();
const column = type === "email" ? "email" : "ip_addr";
const { count } = await db
.from("admin_audit_logs")
.select("id", { count: "exact" })
.eq(column, emailOrIp)
.eq("success", false)
.gt("timestamp", since);
return count || 0;
} catch (error) {
console.error("Fehler beim Abrufen fehlgeschlagener Logins:", error);
return 0;
}
}
/**
* Holt die Audit-Log-Einträge mit optionalen Filtern
*/
export async function getAuditLogs(options?: {
email?: string;
ipAddr?: string;
successOnly?: boolean;
limit?: number;
offset?: number;
}): Promise<AuditLogEntry[]> {
try {
const db = createServiceClient();
const limit = options?.limit ?? 100;
const offset = options?.offset ?? 0;
let query = db
.from("admin_audit_logs")
.select("*")
.order("timestamp", { ascending: false })
.range(offset, offset + limit - 1);
if (options?.email) {
query = query.eq("email", options.email.toLowerCase().trim());
}
if (options?.ipAddr) {
query = query.eq("ip_addr", options.ipAddr);
}
if (options?.successOnly !== undefined) {
query = query.eq("success", options.successOnly);
}
const { data, error } = await query;
if (error) {
console.error("Fehler beim Abrufen von Audit-Logs:", error);
return [];
}
return data || [];
} catch (error) {
console.error("Fehler beim Abrufen von Audit-Logs:", error);
return [];
}
}
/**
* Löscht alte Audit-Logs (älter als X Tage)
* Sollte als Cron-Job regelmäßig ausgeführt werden
*/
export async function deleteOldAuditLogs(daysToKeep = 90): Promise<void> {
try {
const db = createServiceClient();
const cutoffDate = new Date(Date.now() - daysToKeep * 24 * 60 * 60 * 1000).toISOString();
await db
.from("admin_audit_logs")
.delete()
.lt("timestamp", cutoffDate);
console.log(`Audit-Logs älter als ${daysToKeep} Tage gelöscht.`);
} catch (error) {
console.error("Fehler beim Löschen alter Audit-Logs:", error);
}
}
/**
* Sendet eine Alert-Email bei verdächtiger Aktivität
* TODO: Implementieren wenn Email-System erweitert ist
*/
export async function sendSecurityAlert(
subject: string,
message: string,
adminEmail: string = process.env.SMTP_TO || ""
): Promise<void> {
if (!adminEmail) {
console.warn("SMTP_TO nicht konfiguriert keine Alert-Email gesendet");
return;
}
try {
// TODO: Nodemailer/Email-Integration wenn nötig
console.log(`⚠️ SECURITY ALERT: ${subject}\n${message}`);
} catch (error) {
console.error("Fehler beim Senden der Security-Alert:", error);
}
}