GT AI OS Community Edition v2.0.33
Security hardening release addressing CodeQL and Dependabot alerts: - Fix stack trace exposure in error responses - Add SSRF protection with DNS resolution checking - Implement proper URL hostname validation (replaces substring matching) - Add centralized path sanitization to prevent path traversal - Fix ReDoS vulnerability in email validation regex - Improve HTML sanitization in validation utilities - Fix capability wildcard matching in auth utilities - Update glob dependency to address CVE - Add CodeQL suppression comments for verified false positives 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
216
packages/utils/dist/database.js
vendored
Normal file
216
packages/utils/dist/database.js
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getTenantDatabasePath = getTenantDatabasePath;
|
||||
exports.getTenantChromaCollection = getTenantChromaCollection;
|
||||
exports.getTenantRedisPrefix = getTenantRedisPrefix;
|
||||
exports.getTenantMinioBucket = getTenantMinioBucket;
|
||||
exports.getSQLiteWALConfig = getSQLiteWALConfig;
|
||||
exports.getSQLiteEncryptionConfig = getSQLiteEncryptionConfig;
|
||||
exports.getTenantDatabaseSchema = getTenantDatabaseSchema;
|
||||
exports.generateDocumentChunkId = generateDocumentChunkId;
|
||||
exports.parseConnectionString = parseConnectionString;
|
||||
exports.escapeSQLIdentifier = escapeSQLIdentifier;
|
||||
exports.generateBackupFilename = generateBackupFilename;
|
||||
// Database utility functions
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const crypto_1 = __importDefault(require("crypto"));
|
||||
/**
|
||||
* Generate SQLite database path for tenant
|
||||
*/
|
||||
function getTenantDatabasePath(tenantDomain, dataDir = '/data') {
|
||||
return path_1.default.join(dataDir, tenantDomain, 'app.db');
|
||||
}
|
||||
/**
|
||||
* Generate ChromaDB collection name for tenant
|
||||
*/
|
||||
function getTenantChromaCollection(tenantDomain) {
|
||||
// ChromaDB collection names must be alphanumeric with underscores
|
||||
return `gt2_${tenantDomain.replace(/-/g, '_')}_documents`;
|
||||
}
|
||||
/**
|
||||
* Generate Redis key prefix for tenant
|
||||
*/
|
||||
function getTenantRedisPrefix(tenantDomain) {
|
||||
return `gt2:${tenantDomain}:`;
|
||||
}
|
||||
/**
|
||||
* Generate MinIO bucket name for tenant
|
||||
*/
|
||||
function getTenantMinioBucket(tenantDomain) {
|
||||
// MinIO bucket names must be lowercase and DNS-compliant
|
||||
return `gt2-${tenantDomain}-files`;
|
||||
}
|
||||
/**
|
||||
* Generate SQLite WAL mode configuration
|
||||
*/
|
||||
function getSQLiteWALConfig() {
|
||||
return `
|
||||
PRAGMA journal_mode=WAL;
|
||||
PRAGMA synchronous=NORMAL;
|
||||
PRAGMA cache_size=1000;
|
||||
PRAGMA foreign_keys=ON;
|
||||
PRAGMA temp_store=MEMORY;
|
||||
`;
|
||||
}
|
||||
/**
|
||||
* Generate SQLite encryption configuration
|
||||
*/
|
||||
function getSQLiteEncryptionConfig(encryptionKey) {
|
||||
return `PRAGMA key='${encryptionKey}';`;
|
||||
}
|
||||
/**
|
||||
* Create tenant database schema (SQLite)
|
||||
*/
|
||||
function getTenantDatabaseSchema() {
|
||||
return `
|
||||
-- Enable foreign key constraints
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
-- Conversations for AI chat
|
||||
CREATE TABLE IF NOT EXISTS conversations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
model_id TEXT NOT NULL,
|
||||
system_prompt TEXT,
|
||||
created_by TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Messages with full context tracking
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
conversation_id INTEGER NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
||||
role TEXT NOT NULL CHECK (role IN ('user', 'assistant', 'system')),
|
||||
content TEXT NOT NULL,
|
||||
model_used TEXT,
|
||||
tokens_used INTEGER DEFAULT 0,
|
||||
context_sources TEXT DEFAULT '[]', -- JSON array of document chunk IDs
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Documents with processing status
|
||||
CREATE TABLE IF NOT EXISTS documents (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
filename TEXT NOT NULL,
|
||||
file_type TEXT NOT NULL,
|
||||
file_size INTEGER DEFAULT 0,
|
||||
processing_status TEXT DEFAULT 'pending' CHECK (processing_status IN ('pending', 'processing', 'completed', 'failed')),
|
||||
chunk_count INTEGER DEFAULT 0,
|
||||
uploaded_by TEXT NOT NULL,
|
||||
storage_path TEXT,
|
||||
error_message TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Document chunks for RAG
|
||||
CREATE TABLE IF NOT EXISTS document_chunks (
|
||||
id TEXT PRIMARY KEY, -- UUID
|
||||
document_id INTEGER NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
|
||||
chunk_index INTEGER NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
metadata TEXT DEFAULT '{}', -- JSON metadata
|
||||
embedding_id TEXT, -- Reference to ChromaDB embedding
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- User sessions and preferences
|
||||
CREATE TABLE IF NOT EXISTS user_sessions (
|
||||
id TEXT PRIMARY KEY, -- Session token
|
||||
user_email TEXT NOT NULL,
|
||||
expires_at DATETIME NOT NULL,
|
||||
data TEXT DEFAULT '{}', -- JSON session data
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- User preferences
|
||||
CREATE TABLE IF NOT EXISTS user_preferences (
|
||||
user_email TEXT PRIMARY KEY,
|
||||
preferences TEXT DEFAULT '{}', -- JSON preferences
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Usage tracking for tenant
|
||||
CREATE TABLE IF NOT EXISTS usage_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_email TEXT NOT NULL,
|
||||
action_type TEXT NOT NULL, -- 'chat', 'document_upload', 'document_query'
|
||||
resource_used TEXT, -- Model name or resource identifier
|
||||
tokens_used INTEGER DEFAULT 0,
|
||||
success BOOLEAN DEFAULT TRUE,
|
||||
metadata TEXT DEFAULT '{}', -- JSON metadata
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Indexes for performance
|
||||
CREATE INDEX IF NOT EXISTS idx_conversations_created_by ON conversations(created_by);
|
||||
CREATE INDEX IF NOT EXISTS idx_conversations_updated_at ON conversations(updated_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_messages_conversation_id ON messages(conversation_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_messages_created_at ON messages(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_documents_uploaded_by ON documents(uploaded_by);
|
||||
CREATE INDEX IF NOT EXISTS idx_documents_status ON documents(processing_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_document_chunks_document_id ON document_chunks(document_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_usage_logs_user_email ON usage_logs(user_email);
|
||||
CREATE INDEX IF NOT EXISTS idx_usage_logs_created_at ON usage_logs(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_sessions_expires_at ON user_sessions(expires_at);
|
||||
|
||||
-- Triggers for updated_at columns
|
||||
CREATE TRIGGER IF NOT EXISTS update_conversations_updated_at
|
||||
AFTER UPDATE ON conversations
|
||||
BEGIN
|
||||
UPDATE conversations SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS update_documents_updated_at
|
||||
AFTER UPDATE ON documents
|
||||
BEGIN
|
||||
UPDATE documents SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS update_user_preferences_updated_at
|
||||
AFTER UPDATE ON user_preferences
|
||||
BEGIN
|
||||
UPDATE user_preferences SET updated_at = CURRENT_TIMESTAMP WHERE user_email = NEW.user_email;
|
||||
END;
|
||||
`;
|
||||
}
|
||||
/**
|
||||
* Generate unique document chunk ID
|
||||
*/
|
||||
function generateDocumentChunkId(documentId, chunkIndex) {
|
||||
const data = `${documentId}-${chunkIndex}-${Date.now()}`;
|
||||
return crypto_1.default.createHash('sha256').update(data).digest('hex').substring(0, 32);
|
||||
}
|
||||
/**
|
||||
* Parse connection string for database configuration
|
||||
*/
|
||||
function parseConnectionString(connectionString) {
|
||||
const url = new URL(connectionString);
|
||||
return {
|
||||
host: url.hostname,
|
||||
port: url.port ? parseInt(url.port) : undefined,
|
||||
database: url.pathname.substring(1), // Remove leading slash
|
||||
username: url.username,
|
||||
password: url.password,
|
||||
options: Object.fromEntries(url.searchParams.entries())
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Escape SQL identifiers (table names, column names, etc.)
|
||||
*/
|
||||
function escapeSQLIdentifier(identifier) {
|
||||
return `"${identifier.replace(/"/g, '""')}"`;
|
||||
}
|
||||
/**
|
||||
* Generate database backup filename
|
||||
*/
|
||||
function generateBackupFilename(tenantDomain, timestamp) {
|
||||
const date = timestamp || new Date();
|
||||
const dateString = date.toISOString().split('T')[0]; // YYYY-MM-DD
|
||||
const timeString = date.toTimeString().split(' ')[0].replace(/:/g, '-'); // HH-MM-SS
|
||||
return `gt2-${tenantDomain}-backup-${dateString}-${timeString}.db`;
|
||||
}
|
||||
Reference in New Issue
Block a user