mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-25 22:34:56 +00:00
feat(security): implement secure database operations with access control and logging
This commit is contained in:
40
frontends/nextjs/src/lib/security/secure-db/execute-query.ts
Normal file
40
frontends/nextjs/src/lib/security/secure-db/execute-query.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { SecurityContext, OperationType, ResourceType } from './types'
|
||||
import { checkRateLimit } from './check-rate-limit'
|
||||
import { checkAccess } from './check-access'
|
||||
import { logOperation } from './log-operation'
|
||||
|
||||
/**
|
||||
* Execute a secure database query with rate limiting, access control, and audit logging
|
||||
*/
|
||||
export async function executeQuery<T>(
|
||||
ctx: SecurityContext,
|
||||
resource: ResourceType,
|
||||
operation: OperationType,
|
||||
queryFn: () => Promise<T>,
|
||||
resourceId: string = 'unknown'
|
||||
): Promise<T> {
|
||||
// Check rate limit
|
||||
const canProceed = checkRateLimit(ctx.user.id)
|
||||
if (!canProceed) {
|
||||
await logOperation(ctx, operation, resource, resourceId, false, 'Rate limit exceeded')
|
||||
throw new Error('Rate limit exceeded. Please try again later.')
|
||||
}
|
||||
|
||||
// Check access permissions
|
||||
const hasAccess = await checkAccess(ctx, resource, operation, resourceId)
|
||||
if (!hasAccess) {
|
||||
await logOperation(ctx, operation, resource, resourceId, false, 'Access denied')
|
||||
throw new Error('Access denied. Insufficient permissions.')
|
||||
}
|
||||
|
||||
// Execute query
|
||||
try {
|
||||
const result = await queryFn()
|
||||
await logOperation(ctx, operation, resource, resourceId, true)
|
||||
return result
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
||||
await logOperation(ctx, operation, resource, resourceId, false, errorMessage)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
61
frontends/nextjs/src/lib/security/secure-db/index.ts
Normal file
61
frontends/nextjs/src/lib/security/secure-db/index.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
// Types
|
||||
export type {
|
||||
OperationType,
|
||||
ResourceType,
|
||||
AuditLog,
|
||||
SecurityContext,
|
||||
AccessRule
|
||||
} from './types'
|
||||
|
||||
// Core functions
|
||||
export { ACCESS_RULES } from './access-rules'
|
||||
export { checkAccess } from './check-access'
|
||||
export { checkRateLimit, clearRateLimit, clearAllRateLimits } from './check-rate-limit'
|
||||
export { sanitizeInput } from './sanitize-input'
|
||||
export { logOperation } from './log-operation'
|
||||
export { executeQuery } from './execute-query'
|
||||
|
||||
// Operations
|
||||
export { getUsers } from './operations/get-users'
|
||||
export { getUserById } from './operations/get-user-by-id'
|
||||
export { createUser } from './operations/create-user'
|
||||
export { updateUser } from './operations/update-user'
|
||||
export { deleteUser } from './operations/delete-user'
|
||||
export { verifyCredentials } from './operations/verify-credentials'
|
||||
|
||||
// Import all for namespace class
|
||||
import { ACCESS_RULES } from './access-rules'
|
||||
import { checkAccess } from './check-access'
|
||||
import { checkRateLimit, clearRateLimit, clearAllRateLimits } from './check-rate-limit'
|
||||
import { sanitizeInput } from './sanitize-input'
|
||||
import { logOperation } from './log-operation'
|
||||
import { executeQuery } from './execute-query'
|
||||
import { getUsers } from './operations/get-users'
|
||||
import { getUserById } from './operations/get-user-by-id'
|
||||
import { createUser } from './operations/create-user'
|
||||
import { updateUser } from './operations/update-user'
|
||||
import { deleteUser } from './operations/delete-user'
|
||||
import { verifyCredentials } from './operations/verify-credentials'
|
||||
|
||||
/**
|
||||
* SecureDatabase namespace class - groups all secure DB operations as static methods
|
||||
*/
|
||||
export class SecureDatabase {
|
||||
// Core
|
||||
static ACCESS_RULES = ACCESS_RULES
|
||||
static checkAccess = checkAccess
|
||||
static checkRateLimit = checkRateLimit
|
||||
static clearRateLimit = clearRateLimit
|
||||
static clearAllRateLimits = clearAllRateLimits
|
||||
static sanitizeInput = sanitizeInput
|
||||
static logOperation = logOperation
|
||||
static executeQuery = executeQuery
|
||||
|
||||
// Operations
|
||||
static getUsers = getUsers
|
||||
static getUserById = getUserById
|
||||
static createUser = createUser
|
||||
static updateUser = updateUser
|
||||
static deleteUser = deleteUser
|
||||
static verifyCredentials = verifyCredentials
|
||||
}
|
||||
38
frontends/nextjs/src/lib/security/secure-db/log-operation.ts
Normal file
38
frontends/nextjs/src/lib/security/secure-db/log-operation.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { SecurityContext, OperationType, ResourceType, AuditLog } from './types'
|
||||
|
||||
/**
|
||||
* Log an operation for audit trail
|
||||
*/
|
||||
export async function logOperation(
|
||||
ctx: SecurityContext,
|
||||
operation: OperationType,
|
||||
resource: ResourceType,
|
||||
resourceId: string,
|
||||
success: boolean,
|
||||
errorMessage?: string
|
||||
): Promise<void> {
|
||||
const log: AuditLog = {
|
||||
id: `audit_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
timestamp: Date.now(),
|
||||
userId: ctx.user.id,
|
||||
username: ctx.user.username,
|
||||
operation,
|
||||
resource,
|
||||
resourceId,
|
||||
success,
|
||||
errorMessage,
|
||||
ipAddress: ctx.ipAddress,
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: Replace with proper audit log storage
|
||||
// For now, just log to console in development
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('[AUDIT]', log)
|
||||
}
|
||||
// In production, this would write to a persistent audit log table
|
||||
// await Database.addAuditLog(log)
|
||||
} catch (error) {
|
||||
console.error('Failed to log operation:', error)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { prisma } from '../../db/prisma'
|
||||
import type { User } from '../../types/level-types'
|
||||
import type { SecurityContext } from './types'
|
||||
import { executeQuery } from './execute-query'
|
||||
import { sanitizeInput } from './sanitize-input'
|
||||
|
||||
/**
|
||||
* Create a new user with security checks
|
||||
*/
|
||||
export async function createUser(
|
||||
ctx: SecurityContext,
|
||||
userData: Omit<User, 'id' | 'createdAt'>
|
||||
): Promise<User> {
|
||||
const sanitized = sanitizeInput(userData)
|
||||
|
||||
return executeQuery(
|
||||
ctx,
|
||||
'user',
|
||||
'CREATE',
|
||||
async () => {
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
id: `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
username: sanitized.username,
|
||||
email: sanitized.email,
|
||||
role: sanitized.role,
|
||||
profilePicture: sanitized.profilePicture,
|
||||
bio: sanitized.bio,
|
||||
createdAt: BigInt(Date.now()),
|
||||
tenantId: sanitized.tenantId,
|
||||
isInstanceOwner: sanitized.isInstanceOwner || false,
|
||||
},
|
||||
})
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
role: user.role as User['role'],
|
||||
profilePicture: user.profilePicture || undefined,
|
||||
bio: user.bio || undefined,
|
||||
createdAt: Number(user.createdAt),
|
||||
tenantId: user.tenantId || undefined,
|
||||
isInstanceOwner: user.isInstanceOwner,
|
||||
}
|
||||
},
|
||||
'new_user'
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { prisma } from '../../db/prisma'
|
||||
import type { SecurityContext } from './types'
|
||||
import { executeQuery } from './execute-query'
|
||||
|
||||
/**
|
||||
* Delete a user with security checks
|
||||
*/
|
||||
export async function deleteUser(ctx: SecurityContext, userId: string): Promise<void> {
|
||||
return executeQuery(
|
||||
ctx,
|
||||
'user',
|
||||
'DELETE',
|
||||
async () => {
|
||||
await prisma.user.delete({ where: { id: userId } })
|
||||
},
|
||||
userId
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { prisma } from '../../db/prisma'
|
||||
import type { User } from '../../types/level-types'
|
||||
import type { SecurityContext } from './types'
|
||||
import { executeQuery } from './execute-query'
|
||||
|
||||
/**
|
||||
* Get a user by ID with security checks
|
||||
*/
|
||||
export async function getUserById(ctx: SecurityContext, userId: string): Promise<User | null> {
|
||||
return executeQuery(
|
||||
ctx,
|
||||
'user',
|
||||
'READ',
|
||||
async () => {
|
||||
const user = await prisma.user.findUnique({ where: { id: userId } })
|
||||
if (!user) return null
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
role: user.role as User['role'],
|
||||
profilePicture: user.profilePicture || undefined,
|
||||
bio: user.bio || undefined,
|
||||
createdAt: Number(user.createdAt),
|
||||
tenantId: user.tenantId || undefined,
|
||||
isInstanceOwner: user.isInstanceOwner,
|
||||
}
|
||||
},
|
||||
userId
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { prisma } from '../../db/prisma'
|
||||
import type { User } from '../../types/level-types'
|
||||
import type { SecurityContext } from './types'
|
||||
import { executeQuery } from './execute-query'
|
||||
|
||||
/**
|
||||
* Get all users with security checks
|
||||
*/
|
||||
export async function getUsers(ctx: SecurityContext): Promise<User[]> {
|
||||
return executeQuery(
|
||||
ctx,
|
||||
'user',
|
||||
'READ',
|
||||
async () => {
|
||||
const users = await prisma.user.findMany()
|
||||
return users.map(u => ({
|
||||
id: u.id,
|
||||
username: u.username,
|
||||
email: u.email,
|
||||
role: u.role as User['role'],
|
||||
profilePicture: u.profilePicture || undefined,
|
||||
bio: u.bio || undefined,
|
||||
createdAt: Number(u.createdAt),
|
||||
tenantId: u.tenantId || undefined,
|
||||
isInstanceOwner: u.isInstanceOwner,
|
||||
}))
|
||||
},
|
||||
'all_users'
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { prisma } from '../../db/prisma'
|
||||
import type { User } from '../../types/level-types'
|
||||
import type { SecurityContext } from './types'
|
||||
import { executeQuery } from './execute-query'
|
||||
import { sanitizeInput } from './sanitize-input'
|
||||
|
||||
/**
|
||||
* Update a user with security checks
|
||||
*/
|
||||
export async function updateUser(
|
||||
ctx: SecurityContext,
|
||||
userId: string,
|
||||
updates: Partial<User>
|
||||
): Promise<User> {
|
||||
const sanitized = sanitizeInput(updates)
|
||||
|
||||
return executeQuery(
|
||||
ctx,
|
||||
'user',
|
||||
'UPDATE',
|
||||
async () => {
|
||||
const user = await prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
username: sanitized.username,
|
||||
email: sanitized.email,
|
||||
role: sanitized.role,
|
||||
profilePicture: sanitized.profilePicture,
|
||||
bio: sanitized.bio,
|
||||
tenantId: sanitized.tenantId,
|
||||
},
|
||||
})
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
role: user.role as User['role'],
|
||||
profilePicture: user.profilePicture || undefined,
|
||||
bio: user.bio || undefined,
|
||||
createdAt: Number(user.createdAt),
|
||||
tenantId: user.tenantId || undefined,
|
||||
isInstanceOwner: user.isInstanceOwner,
|
||||
}
|
||||
},
|
||||
userId
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { prisma } from '../../db/prisma'
|
||||
import type { SecurityContext } from './types'
|
||||
import { executeQuery } from './execute-query'
|
||||
import { sanitizeInput } from './sanitize-input'
|
||||
|
||||
/**
|
||||
* Verify user credentials with security checks
|
||||
*/
|
||||
export async function verifyCredentials(
|
||||
ctx: SecurityContext,
|
||||
username: string,
|
||||
password: string
|
||||
): Promise<boolean> {
|
||||
const sanitizedUsername = sanitizeInput(username)
|
||||
|
||||
return executeQuery(
|
||||
ctx,
|
||||
'credential',
|
||||
'READ',
|
||||
async () => {
|
||||
const credential = await prisma.credential.findUnique({
|
||||
where: { username: sanitizedUsername },
|
||||
})
|
||||
|
||||
if (!credential) return false
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
const data = encoder.encode(password)
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-512', data)
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
||||
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
|
||||
|
||||
return hashHex === credential.passwordHash
|
||||
},
|
||||
sanitizedUsername
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user