Apply Composer changes: comprehensive API updates, migrations, middleware, and infrastructure improvements

- Add comprehensive database migrations (001-024) for schema evolution
- Enhance API schema with expanded type definitions and resolvers
- Add new middleware: audit logging, rate limiting, MFA enforcement, security, tenant auth
- Implement new services: AI optimization, billing, blockchain, compliance, marketplace
- Add adapter layer for cloud integrations (Cloudflare, Kubernetes, Proxmox, storage)
- Update Crossplane provider with enhanced VM management capabilities
- Add comprehensive test suite for API endpoints and services
- Update frontend components with improved GraphQL subscriptions and real-time updates
- Enhance security configurations and headers (CSP, CORS, etc.)
- Update documentation and configuration files
- Add new CI/CD workflows and validation scripts
- Implement design system improvements and UI enhancements
This commit is contained in:
defiQUG
2025-12-12 18:01:35 -08:00
parent e01131efaf
commit 9daf1fd378
968 changed files with 160890 additions and 1092 deletions

View File

@@ -0,0 +1,237 @@
/**
* Tenant-Aware Authentication Middleware
* Enforces tenant isolation in all queries
* Superior to Azure with more flexible permission model
*/
import { FastifyRequest, FastifyReply } from 'fastify'
import { identityService, TokenValidationResult } from '../services/identity.js'
import { getDb } from '../db/index.js'
import { logger } from '../lib/logger.js'
export interface TenantContext {
tenantId?: string
userId: string
email: string
role: string
tenantRole?: string
permissions: Record<string, any>
isSystemAdmin: boolean
}
declare module 'fastify' {
interface FastifyRequest {
tenantContext?: TenantContext
}
}
/**
* Extract tenant context from request
*/
export async function extractTenantContext(
request: FastifyRequest
): Promise<TenantContext | null> {
// Get token from Authorization header
const authHeader = request.headers.authorization
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return null
}
const token = authHeader.substring(7)
// Validate token
const validation = await identityService.validateToken(token)
if (!validation.valid || !validation.userId) {
return null
}
// Get user from database
const db = getDb()
const userResult = await db.query('SELECT id, email, role FROM users WHERE id = $1', [
validation.userId,
])
if (userResult.rows.length === 0) {
return null
}
const user = userResult.rows[0]
const isSystemAdmin = user.role === 'ADMIN'
// Get tenant information if tenant ID is present
let tenantRole: string | undefined
let tenantPermissions: Record<string, any> = {}
if (validation.tenantId) {
const tenantUserResult = await db.query(
`SELECT role, permissions FROM tenant_users
WHERE tenant_id = $1 AND user_id = $2`,
[validation.tenantId, validation.userId]
)
if (tenantUserResult.rows.length > 0) {
tenantRole = tenantUserResult.rows[0].role
tenantPermissions = tenantUserResult.rows[0].permissions || {}
}
}
return {
tenantId: validation.tenantId,
userId: validation.userId,
email: validation.email || user.email,
role: user.role,
tenantRole,
permissions: { ...tenantPermissions, ...(validation.permissions || {}) },
isSystemAdmin,
}
}
/**
* Tenant-aware authentication middleware
*/
export async function tenantAuthMiddleware(
request: FastifyRequest,
reply: FastifyReply
): Promise<void> {
// Skip auth for health check and GraphQL introspection
if (request.url === '/health' || request.method === 'OPTIONS') {
return
}
const context = await extractTenantContext(request)
if (!context) {
// Allow unauthenticated requests - GraphQL will handle auth per query/mutation
return
}
// Attach tenant context to request
request.tenantContext = context
// Set tenant context in database session for RLS policies
const db = getDb()
if (context.userId) {
await db.query(`SET LOCAL app.current_user_id = $1`, [context.userId])
}
}
/**
* Require authentication middleware
*/
export function requireAuth(
request: FastifyRequest,
reply: FastifyReply
): TenantContext {
const context = request.tenantContext
if (!context) {
reply.code(401).send({
error: 'Authentication required',
code: 'UNAUTHENTICATED',
})
throw new Error('Authentication required')
}
return context
}
/**
* Require tenant membership middleware
*/
export function requireTenant(
request: FastifyRequest,
reply: FastifyReply
): TenantContext {
const context = requireAuth(request, reply)
if (!context.tenantId && !context.isSystemAdmin) {
reply.code(403).send({
error: 'Tenant membership required',
code: 'TENANT_REQUIRED',
})
throw new Error('Tenant membership required')
}
return context
}
/**
* Require specific tenant role
*/
export function requireTenantRole(
allowedRoles: string[]
) {
return (request: FastifyRequest, reply: FastifyReply): TenantContext => {
const context = requireTenant(request, reply)
if (context.isSystemAdmin) {
return context
}
if (!context.tenantRole || !allowedRoles.includes(context.tenantRole)) {
reply.code(403).send({
error: 'Insufficient permissions',
code: 'FORBIDDEN',
required: allowedRoles,
current: context.tenantRole,
})
throw new Error('Insufficient tenant permissions')
}
return context
}
}
/**
* Require system admin middleware
*/
export function requireSystemAdmin(
request: FastifyRequest,
reply: FastifyReply
): TenantContext {
const context = requireAuth(request, reply)
if (!context.isSystemAdmin) {
reply.code(403).send({
error: 'System administrator access required',
code: 'FORBIDDEN',
})
throw new Error('System administrator access required')
}
return context
}
/**
* Filter resources by tenant automatically
*/
export function filterByTenant(
query: string,
params: any[],
context: TenantContext
): { query: string; params: any[] } {
// If system admin, don't filter
if (context.isSystemAdmin) {
return { query, params }
}
// If no tenant context, filter to show only system resources (tenant_id IS NULL)
if (!context.tenantId) {
const whereClause = query.includes('WHERE') ? 'AND tenant_id IS NULL' : 'WHERE tenant_id IS NULL'
return {
query: `${query} ${whereClause}`,
params,
}
}
// Filter by tenant_id
const whereClause = query.includes('WHERE')
? `AND tenant_id = $${params.length + 1}`
: `WHERE tenant_id = $${params.length + 1}`
return {
query: `${query} ${whereClause}`,
params: [...params, context.tenantId],
}
}