155 lines
4.0 KiB
TypeScript
155 lines
4.0 KiB
TypeScript
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);
|
||
}
|
||
}
|