mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-25 14:25:02 +00:00
13 KiB
13 KiB
Migration Guide: Integrating Secure Database Layer
This guide walks you through migrating your existing codebase to use the secure database abstraction layer.
Quick Start
1. Import the Secure Layer
import { SecureDatabase, SecurityContext } from '@/lib/secure-db-layer'
2. Create Security Context
In your component or function, create a security context with the current user:
const ctx: SecurityContext = {
user: currentUser,
ipAddress: '192.168.1.1', // Optional
requestId: 'unique-request-id' // Optional
}
3. Replace Database Calls
Replace direct Prisma or database calls with SecureDatabase methods.
Common Migration Patterns
User Operations
Before (Direct Prisma):
import { prisma } from '@/lib/prisma'
const users = await prisma.user.findMany()
const user = await prisma.user.findUnique({ where: { id: userId } })
await prisma.user.create({ data: { ... } })
await prisma.user.update({ where: { id: userId }, data: { ... } })
await prisma.user.delete({ where: { id: userId } })
After (Secure Layer):
import { SecureDatabase, SecurityContext } from '@/lib/secure-db-layer'
const ctx: SecurityContext = { user: currentUser }
const users = await SecureDatabase.getUsers(ctx)
const user = await SecureDatabase.getUserById(ctx, userId)
await SecureDatabase.createUser(ctx, userData)
await SecureDatabase.updateUser(ctx, userId, updates)
await SecureDatabase.deleteUser(ctx, userId)
Credential Operations
Before:
const credential = await prisma.credential.findUnique({
where: { username }
})
const isValid = credential?.passwordHash === providedHash
await prisma.credential.upsert({
where: { username },
update: { passwordHash },
create: { username, passwordHash }
})
After:
const ctx: SecurityContext = { user: systemUser }
const isValid = await SecureDatabase.verifyCredentials(ctx, username, password)
await SecureDatabase.setCredential(ctx, username, passwordHash)
Comment Operations
Before:
const comments = await prisma.comment.findMany()
await prisma.comment.create({
data: {
id: `comment_${Date.now()}`,
userId: currentUser.id,
content: commentText,
createdAt: BigInt(Date.now())
}
})
After:
const ctx: SecurityContext = { user: currentUser }
const comments = await SecureDatabase.getComments(ctx)
await SecureDatabase.createComment(ctx, {
userId: currentUser.id,
content: commentText
})
Workflow Operations
Before:
const workflows = await prisma.workflow.findMany()
After:
const ctx: SecurityContext = { user: currentUser }
const workflows = await SecureDatabase.getWorkflows(ctx)
Component Integration Examples
Level 2 Component (User Area)
import { useState, useEffect } from 'react'
import { SecureDatabase, SecurityContext } from '@/lib/secure-db-layer'
import type { User, Comment } from '@/lib/level-types'
interface Level2Props {
user: User
onLogout: () => void
}
export function Level2({ user, onLogout }: Level2Props) {
const [comments, setComments] = useState<Comment[]>([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const ctx: SecurityContext = { user }
useEffect(() => {
loadComments()
}, [])
const loadComments = async () => {
setLoading(true)
setError(null)
try {
const data = await SecureDatabase.getComments(ctx)
setComments(data)
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load comments')
} finally {
setLoading(false)
}
}
const handleAddComment = async (content: string) => {
try {
await SecureDatabase.createComment(ctx, {
userId: user.id,
content
})
await loadComments() // Reload
toast.success('Comment added!')
} catch (err) {
toast.error(err instanceof Error ? err.message : 'Failed to add comment')
}
}
return (
<div>
{/* Your UI here */}
</div>
)
}
Level 3 Component (Admin Panel)
import { useState, useEffect } from 'react'
import { SecureDatabase, SecurityContext } from '@/lib/secure-db-layer'
import type { User } from '@/lib/level-types'
interface Level3Props {
user: User
onLogout: () => void
}
export function Level3({ user, onLogout }: Level3Props) {
const [users, setUsers] = useState<User[]>([])
const ctx: SecurityContext = { user }
useEffect(() => {
loadUsers()
}, [])
const loadUsers = async () => {
try {
const data = await SecureDatabase.getUsers(ctx)
setUsers(data)
} catch (err) {
toast.error('Failed to load users')
}
}
const handleUpdateUser = async (userId: string, updates: Partial<User>) => {
try {
await SecureDatabase.updateUser(ctx, userId, updates)
await loadUsers() // Reload
toast.success('User updated!')
} catch (err) {
toast.error(err instanceof Error ? err.message : 'Failed to update user')
}
}
return (
<div>
{/* Admin UI here */}
</div>
)
}
Level 4 Component (God Tier)
import { useState, useEffect } from 'react'
import { SecureDatabase, SecurityContext, AuditLog } from '@/lib/secure-db-layer'
import type { User, Workflow, LuaScript, PageConfig } from '@/lib/level-types'
interface Level4Props {
user: User
onLogout: () => void
}
export function Level4({ user, onLogout }: Level4Props) {
const [workflows, setWorkflows] = useState<Workflow[]>([])
const [luaScripts, setLuaScripts] = useState<LuaScript[]>([])
const [pageConfigs, setPageConfigs] = useState<PageConfig[]>([])
const [auditLogs, setAuditLogs] = useState<AuditLog[]>([])
const ctx: SecurityContext = { user }
useEffect(() => {
loadAllData()
}, [])
const loadAllData = async () => {
try {
const [wf, lua, pages, logs] = await Promise.all([
SecureDatabase.getWorkflows(ctx),
SecureDatabase.getLuaScripts(ctx),
SecureDatabase.getPageConfigs(ctx),
SecureDatabase.getAuditLogs(ctx, 100)
])
setWorkflows(wf)
setLuaScripts(lua)
setPageConfigs(pages)
setAuditLogs(logs)
} catch (err) {
toast.error('Failed to load data')
}
}
const handleCreateUser = async (userData: any) => {
try {
await SecureDatabase.createUser(ctx, userData)
toast.success('User created!')
} catch (err) {
toast.error(err instanceof Error ? err.message : 'Failed to create user')
}
}
return (
<div>
{/* God tier UI here */}
</div>
)
}
Error Handling Best Practices
Always Use Try-Catch
try {
const result = await SecureDatabase.getUsers(ctx)
// Handle success
} catch (err) {
if (err instanceof Error) {
if (err.message.includes('Rate limit')) {
toast.error('Too many requests. Please wait a moment.')
} else if (err.message.includes('Access denied')) {
toast.error('You do not have permission to perform this action.')
} else {
toast.error(err.message)
}
}
}
Handle Loading States
const [loading, setLoading] = useState(false)
const loadData = async () => {
setLoading(true)
try {
const data = await SecureDatabase.getUsers(ctx)
setUsers(data)
} catch (err) {
// Handle error
} finally {
setLoading(false)
}
}
Testing with Secure Layer
Mock the Secure Layer
import { vi } from 'vitest'
import * as SecureDB from '@/lib/secure-db-layer'
vi.mock('@/lib/secure-db-layer', () => ({
SecureDatabase: {
getUsers: vi.fn().mockResolvedValue([
{ id: '1', username: 'test', email: 'test@example.com', role: 'user' }
]),
createUser: vi.fn().mockResolvedValue({ id: '2', username: 'new' }),
// ... other methods
}
}))
describe('MyComponent', () => {
it('should load users', async () => {
render(<MyComponent user={testUser} />)
await waitFor(() => {
expect(screen.getByText('test')).toBeInTheDocument()
})
})
})
Test Access Control
it('should deny access for insufficient permissions', async () => {
const userCtx: SecurityContext = {
user: { role: 'user', id: '1', username: 'test' }
}
await expect(
SecureDatabase.createUser(userCtx, userData)
).rejects.toThrow('Access denied')
})
it('should allow access for god role', async () => {
const godCtx: SecurityContext = {
user: { role: 'god', id: '1', username: 'god' }
}
const user = await SecureDatabase.createUser(godCtx, userData)
expect(user).toBeDefined()
})
Performance Tips
Batch Operations
When loading multiple resources, use Promise.all:
const [users, workflows, scripts] = await Promise.all([
SecureDatabase.getUsers(ctx),
SecureDatabase.getWorkflows(ctx),
SecureDatabase.getLuaScripts(ctx)
])
Cache Results
For data that doesn't change often:
const [users, setUsers] = useState<User[]>([])
const usersLoadedRef = useRef(false)
useEffect(() => {
if (!usersLoadedRef.current) {
loadUsers()
usersLoadedRef.current = true
}
}, [])
Debounce Frequent Operations
For search or filter operations:
import { debounce } from 'lodash'
const debouncedSearch = useCallback(
debounce(async (query: string) => {
const results = await SecureDatabase.getUsers(ctx)
// Filter locally or add search method to SecureDatabase
}, 300),
[]
)
Monitoring Integration
Track Operations
const trackOperation = async <T,>(
operationName: string,
operation: () => Promise<T>
): Promise<T> => {
const startTime = Date.now()
try {
const result = await operation()
const duration = Date.now() - startTime
console.log(`${operationName} completed in ${duration}ms`)
return result
} catch (err) {
const duration = Date.now() - startTime
console.error(`${operationName} failed after ${duration}ms`, err)
throw err
}
}
// Usage
const users = await trackOperation('getUsers', () =>
SecureDatabase.getUsers(ctx)
)
Review Audit Logs
Add a monitoring dashboard in Level 5:
const [auditLogs, setAuditLogs] = useState<AuditLog[]>([])
const loadAuditLogs = async () => {
const logs = await SecureDatabase.getAuditLogs(ctx, 100)
setAuditLogs(logs)
// Check for suspicious activity
const failedAttempts = logs.filter(log =>
!log.success && log.operation === 'READ' && log.resource === 'credential'
)
if (failedAttempts.length > 10) {
toast.warning('High number of failed login attempts detected')
}
}
Common Pitfalls
❌ Don't: Bypass the Secure Layer
// DON'T DO THIS
import { prisma } from '@/lib/prisma'
const users = await prisma.user.findMany()
✅ Do: Always Use SecureDatabase
// DO THIS
import { SecureDatabase } from '@/lib/secure-db-layer'
const users = await SecureDatabase.getUsers(ctx)
❌ Don't: Forget Security Context
// DON'T DO THIS - Missing context
await SecureDatabase.getUsers()
✅ Do: Always Provide Context
// DO THIS
const ctx: SecurityContext = { user: currentUser }
await SecureDatabase.getUsers(ctx)
❌ Don't: Ignore Errors
// DON'T DO THIS
const users = await SecureDatabase.getUsers(ctx)
✅ Do: Handle Errors Properly
// DO THIS
try {
const users = await SecureDatabase.getUsers(ctx)
// Use users
} catch (err) {
console.error('Failed to load users:', err)
toast.error('Failed to load users')
}
Checklist for Migration
- Import SecureDatabase in all components that access database
- Create SecurityContext with current user
- Replace all direct Prisma calls with SecureDatabase methods
- Add try-catch error handling around all database operations
- Test each role's permissions (user, admin, god, supergod)
- Verify rate limiting works (100 requests/minute)
- Check audit logs are being created
- Update tests to use mocked SecureDatabase
- Add loading states for async operations
- Document any custom access rules needed
- Review and test all edge cases
- Monitor performance impact
- Set up audit log review process
Next Steps
- Complete the migration - Work through each component systematically
- Add tests - Ensure security rules are enforced
- Monitor audit logs - Watch for unusual patterns
- Extend as needed - Add new resources and methods
- Review regularly - Security is an ongoing process
For questions or issues, refer to:
docs/SECURE_DATABASE_LAYER.md- Full documentationsrc/lib/secure-db-layer.ts- Implementationsrc/lib/level-types.ts- Type definitions