Files
metabuilder/docs/implementation/SECURE_DB_MIGRATION_GUIDE.md
2025-12-25 11:42:22 +00:00

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

  1. Complete the migration - Work through each component systematically
  2. Add tests - Ensure security rules are enforced
  3. Monitor audit logs - Watch for unusual patterns
  4. Extend as needed - Add new resources and methods
  5. Review regularly - Security is an ongoing process

For questions or issues, refer to:

  • docs/SECURE_DATABASE_LAYER.md - Full documentation
  • src/lib/secure-db-layer.ts - Implementation
  • src/lib/level-types.ts - Type definitions