wulf-pulse/lib/services/audit.ts
root 9f912aed24 feat: add authentication, user management, and admin features
Added comprehensive authentication and authorization system:

Authentication System:
- Better Auth integration with session management
- Login/logout pages and API routes
- Middleware for route protection
- Auth utilities and client libraries

User Management:
- User list, detail, and invite pages
- User API endpoints (CRUD operations)
- Session management for users
- Profile settings page

Role-Based Access Control:
- Role management pages (list, create, edit)
- Permission system with granular controls
- Role assignment to users
- Role API endpoints

Admin Features:
- Audit log page for tracking system events
- Admin settings page
- Audit service for logging user actions

Additional Features:
- Quotes management pages and components
- SalesBldr API integration
- Email service for notifications

Configuration & Documentation:
- Updated docker-compose.yml
- MCP server configuration (mcp.json)
- CVE-2025-55182 security review documentation
- Standards guide and PRD documents
- Re-enabling authentication documentation

Database Migrations:
- 012: Auth tables (users, sessions, accounts, verifications)
- 013: Role tables (roles, permissions, role_permissions, user_roles)
- 014: Admin settings table

UI Updates:
- Updated dashboard layout
- Enhanced app layout with auth integration
2026-01-31 12:43:14 -05:00

197 lines
5.1 KiB
TypeScript

import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
export interface AuditLogEntry {
userId?: string;
userEmail?: string;
action: string;
resource: string;
resourceId?: string;
details?: Record<string, unknown>;
ipAddress?: string;
userAgent?: string;
}
/**
* Log an audit event
*/
export async function log(entry: AuditLogEntry): Promise<void> {
try {
const id = crypto.randomUUID();
await pool.query(
`INSERT INTO "audit_log"
(id, user_id, user_email, action, resource, resource_id, details, ip_address, user_agent)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
[
id,
entry.userId || null,
entry.userEmail || null,
entry.action,
entry.resource,
entry.resourceId || null,
entry.details ? JSON.stringify(entry.details) : null,
entry.ipAddress || null,
entry.userAgent || null,
]
);
} catch (error) {
console.error("Failed to write audit log:", error);
// Don't throw - audit logging should not break the main flow
}
}
// Convenience functions for common audit events
export const audit = {
log,
// Auth events
signIn: (userId: string, email: string, ipAddress?: string, userAgent?: string) =>
log({
userId,
userEmail: email,
action: "sign_in",
resource: "auth",
ipAddress,
userAgent,
}),
signOut: (userId: string, email: string, ipAddress?: string) =>
log({
userId,
userEmail: email,
action: "sign_out",
resource: "auth",
ipAddress,
}),
signInFailed: (email: string, reason: string, ipAddress?: string) =>
log({
userEmail: email,
action: "sign_in_failed",
resource: "auth",
details: { reason },
ipAddress,
}),
// User management events
userCreated: (actorId: string, actorEmail: string, targetUserId: string, targetEmail: string) =>
log({
userId: actorId,
userEmail: actorEmail,
action: "create",
resource: "user",
resourceId: targetUserId,
details: { targetEmail },
}),
userUpdated: (actorId: string, actorEmail: string, targetUserId: string, changes: Record<string, unknown>) =>
log({
userId: actorId,
userEmail: actorEmail,
action: "update",
resource: "user",
resourceId: targetUserId,
details: { changes },
}),
userDeleted: (actorId: string, actorEmail: string, targetUserId: string, targetEmail: string) =>
log({
userId: actorId,
userEmail: actorEmail,
action: "delete",
resource: "user",
resourceId: targetUserId,
details: { targetEmail },
}),
userBanned: (actorId: string, actorEmail: string, targetUserId: string, reason?: string) =>
log({
userId: actorId,
userEmail: actorEmail,
action: "ban",
resource: "user",
resourceId: targetUserId,
details: { reason },
}),
userUnbanned: (actorId: string, actorEmail: string, targetUserId: string) =>
log({
userId: actorId,
userEmail: actorEmail,
action: "unban",
resource: "user",
resourceId: targetUserId,
}),
userRoleChanged: (actorId: string, actorEmail: string, targetUserId: string, oldRole: string, newRole: string) =>
log({
userId: actorId,
userEmail: actorEmail,
action: "role_change",
resource: "user",
resourceId: targetUserId,
details: { oldRole, newRole },
}),
// Role management events
roleCreated: (actorId: string, actorEmail: string, roleId: string, roleName: string) =>
log({
userId: actorId,
userEmail: actorEmail,
action: "create",
resource: "role",
resourceId: roleId,
details: { roleName },
}),
roleUpdated: (actorId: string, actorEmail: string, roleId: string, changes: Record<string, unknown>) =>
log({
userId: actorId,
userEmail: actorEmail,
action: "update",
resource: "role",
resourceId: roleId,
details: { changes },
}),
roleDeleted: (actorId: string, actorEmail: string, roleId: string, roleName: string) =>
log({
userId: actorId,
userEmail: actorEmail,
action: "delete",
resource: "role",
resourceId: roleId,
details: { roleName },
}),
// Settings events
settingsUpdated: (actorId: string, actorEmail: string, setting: string, oldValue: unknown, newValue: unknown) =>
log({
userId: actorId,
userEmail: actorEmail,
action: "update",
resource: "settings",
resourceId: setting,
details: { oldValue, newValue },
}),
};
/**
* Purge old audit logs based on retention policy
*/
export async function purgeOldLogs(retentionDays?: number): Promise<number> {
const days = retentionDays || parseInt(process.env.AUDIT_LOG_RETENTION_DAYS || "90");
try {
const result = await pool.query(
`DELETE FROM "audit_log" WHERE timestamp < NOW() - INTERVAL '${days} days'`
);
return result.rowCount || 0;
} catch (error) {
console.error("Failed to purge audit logs:", error);
return 0;
}
}