@valink-solutions-ltd/ozhi
v0.1.2
Published
Simple pluggable audit middleware for Svelte 5 and SvelteKit
Downloads
3
Maintainers
Readme
Ozhi - Audit System
A comprehensive pluggable audit system for SvelteKit applications with seamless integration for Drizzle ORM, Better-Auth, Resend, and Stripe.
Features
- 🔌 Pluggable Architecture - Easy to integrate without modifying existing code
- 🔐 Automatic Context Capture - User, session, IP, and request details via AsyncLocalStorage
- 📝 Type-Safe Events - Full TypeScript support with categorized events
- 🗄️ Optimized Database Schema - Efficient Drizzle schema with proper indexing
- 🪝 Multiple Integration Points - Middleware, wrapper functions and decorators (future)
- 🔔 Built-in Plugins - Notifications, Stripe enrichment, and security monitoring
- 🎯 Flexible Targeting - Track changes to any entity with before/after states
Installation & Usage
1. Install
pnpm add @valink-solutions-ltd/ozhi2. Setup drizzle schema
In your src/lib/server/db/index.ts:
import { drizzle } from 'drizzle-orm/postgres-js';
import { postgres } from 'drizzle-orm/postgres-js';
import * as schema from "./schema";
import * as ohziSchema from '@valink-solutions-ltd/ozhi/db/schema';
const client = postgres(process.env.DATABASE_URL!);
export const db = drizzle(client, {{ ...schema, ...ohziSchema }});3. Configure Hooks
In your src/hooks.server.ts:
import { createAuditMiddleware } from '@valink-solutions-ltd/ozhi';
import { auditor } from '@valink-solutions-ltd/ozhi';
import {
createNotificationPlugin,
createStripePlugin,
createSecurityPlugin
} from '@valink-solutions-ltd/ozhi/plugins';
import { resend } from 'resend';
import { stripe } from 'stripe';
// Configure plugins (optional)
auditor.registerPlugin(
createSecurityPlugin({
maxFailedAttempts: 5,
timeWindowMs: 300000 // 5 minutes
})
);
auditor.registerPlugin(
createNotificationPlugin({
resend,
notifyEmail: '[email protected]',
severityThreshold: AuditSeverity.HIGH
})
);
auditor.registerPlugin(createStripePlugin({ stripe }));
// Initialize auditor
await auditor.initialize();
// Apply middleware
export const handle = createAuditMiddleware();4. Start Auditing
Manual Audit Logging
import { audit } from '@valink-solutions-ltd/ozhi';
import { AuditCategory, AuditResult } from '@valink-solutions-ltd/ozhi/types';
// Log a successful action
await audit({
action: 'user.profile.updated',
category: AuditCategory.DATA_MODIFICATION,
result: AuditResult.SUCCESS,
target: {
type: 'user',
id: userId,
name: userEmail
},
changes: {
before: { role: 'user' },
after: { role: 'admin' },
fields: ['role']
}
});Wrapped Server Endpoints
import { withAudit } from '@valink-solutions-ltd/ozhi/middleware';
import type { RequestHandler } from './$types';
export const POST: RequestHandler = withAudit(
'invoice.complete',
AuditCategory.INVOICE,
async ({ request }) => {
const { invoiceId } = await request.json();
// Your business logic here
return json({ success: true });
}
);Form Actions with Decorators
import { auditFormAction } from '@valink-solutions-ltd/ozhi/helpers';
import type { Actions } from './$types';
export const actions = {
delete: auditFormAction(
'user.delete',
AuditCategory.USER_MANAGEMENT
)(async ({ params }) => {
// Delete user logic
})
} satisfies Actions;Better-Auth Integration
Automatically audit authentication events:
import { betterAuth } from 'better-auth';
import { audit } from '@valink-solutions-ltd/ozhi';
import { AuditCategory, AuditResult } from '@valink-solutions-ltd/ozhi/types';
export const auth = betterAuth({
// ... your config
hooks: {
after: {
signIn: async ({ user, session }) => {
await audit({
action: 'auth.signin',
category: AuditCategory.AUTH,
result: AuditResult.SUCCESS,
target: {
type: 'user',
id: user.id,
name: user.email
}
});
},
signOut: async ({ user }) => {
await audit({
action: 'auth.signout',
category: AuditCategory.AUTH,
result: AuditResult.SUCCESS,
target: {
type: 'user',
id: user.id,
name: user.email
}
});
}
}
}
});Audit Categories
AUTH- Authentication events (login, logout, password changes)PAYMENT- Payment processing eventsDATA_ACCESS- Read operations on sensitive dataDATA_MODIFICATION- Create, update, delete operationsSYSTEM- System-level eventsINVOICE- Invoice-related operationsUSER_MANAGEMENT- User administration events
Severity Levels
LOW- Routine operationsMEDIUM- Important business operationsHIGH- Security-relevant or critical operationsCRITICAL- High-risk operations requiring immediate attention
Plugin Development
Create custom plugins by implementing the AuditPlugin interface:
import type { AuditPlugin, AuditEvent } from '@valink-solutions-ltd/ozhi/types';
export function createCustomPlugin(): AuditPlugin {
return {
name: 'custom-plugin',
initialize: async () => {
// Setup code
},
beforeAudit: async (event: AuditEvent) => {
// Modify or cancel events
// Return null to cancel, or modified event
return event;
},
afterAudit: async (event: AuditEvent) => {
// React to audit events
}
};
}Built-in Plugins
Notification Plugin
Sends email alerts for high-severity events:
createNotificationPlugin({
resend: resendClient,
notifyEmail: '[email protected]',
severityThreshold: AuditSeverity.HIGH // Only alert for high and critical
});Security Plugin
Monitors and alerts on suspicious activity:
createSecurityPlugin({
maxFailedAttempts: 5,
timeWindowMs: 300000 // 5 minutes
});Stripe Plugin
Enriches payment events with Stripe metadata:
createStripePlugin({
stripe: stripeClient
});Querying Audit Logs
Use the built-in query builder:
import { queryAuditLogs } from '@valink-solutions-ltd/ozhi/query-builder';
const logs = await queryAuditLogs({
userId: 'user_123',
category: AuditCategory.PAYMENT,
severity: [AuditSeverity.HIGH, AuditSeverity.CRITICAL],
startDate: new Date('2024-01-01'),
endDate: new Date('2024-12-31'),
limit: 50,
offset: 0
});Best Practices
- Use Consistent Action Names: Follow a dot-notation pattern (e.g.,
entity.action.detail) - Include Context: Always provide meaningful target information
- Track Changes: Use the changes object for data modifications
- Set Appropriate Severity: Let critical events trigger notifications
- Leverage Metadata: Store additional context in metadata fields
Security Considerations
- Audit logs are tamper-evident once written
- Consider encrypting sensitive data in metadata fields
- Implement retention policies for compliance
- Use read-only database users for audit queries
- Monitor for unusual patterns using the security plugin
TypeScript Support
Full TypeScript support with exported types:
import type {
AuditEvent,
AuditContext,
AuditCategory,
AuditSeverity,
AuditResult,
AuditTarget,
AuditChanges,
AuditPlugin
} from '@valink-solutions-ltd/ozhi/types';Architecture
The system uses:
- AsyncLocalStorage for request-scoped context
- Drizzle ORM for database operations
- Plugin Architecture for extensibility
- Middleware Pattern for automatic context injection
License
MIT
Contributing
Contributions are welcome! Please ensure all changes maintain backward compatibility and include appropriate tests.
