GT AI OS Community v2.0.33 - Add NVIDIA NIM and Nemotron agents

- Updated python_coding_microproject.csv to use NVIDIA NIM Kimi K2
- Updated kali_linux_shell_simulator.csv to use NVIDIA NIM Kimi K2
  - Made more general-purpose (flexible targets, expanded tools)
- Added nemotron-mini-agent.csv for fast local inference via Ollama
- Added nemotron-agent.csv for advanced reasoning via Ollama
- Added wiki page: Projects for NVIDIA NIMs and Nemotron
This commit is contained in:
HackWeasel
2025-12-12 17:47:14 -05:00
commit 310491a557
750 changed files with 232701 additions and 0 deletions

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,212 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Unit tests for authentication utilities
*/
const auth_1 = require("../auth");
describe('Authentication Utilities', () => {
describe('Capability Hash Functions', () => {
const testCapabilities = [
{
resource: 'tenant:test:*',
actions: ['read', 'write'],
constraints: {}
}
];
test('generateCapabilityHash creates consistent hash', () => {
const hash1 = (0, auth_1.generateCapabilityHash)(testCapabilities);
const hash2 = (0, auth_1.generateCapabilityHash)(testCapabilities);
expect(hash1).toBe(hash2);
expect(typeof hash1).toBe('string');
expect(hash1.length).toBeGreaterThan(0);
});
test('verifyCapabilityHash validates correct hash', () => {
const hash = (0, auth_1.generateCapabilityHash)(testCapabilities);
const isValid = (0, auth_1.verifyCapabilityHash)(testCapabilities, hash);
expect(isValid).toBe(true);
});
test('verifyCapabilityHash rejects incorrect hash', () => {
const isValid = (0, auth_1.verifyCapabilityHash)(testCapabilities, 'incorrect-hash');
expect(isValid).toBe(false);
});
test('capability hash changes with different capabilities', () => {
const capabilities1 = [
{ resource: 'tenant:test1:*', actions: ['read'], constraints: {} }
];
const capabilities2 = [
{ resource: 'tenant:test2:*', actions: ['write'], constraints: {} }
];
const hash1 = (0, auth_1.generateCapabilityHash)(capabilities1);
const hash2 = (0, auth_1.generateCapabilityHash)(capabilities2);
expect(hash1).not.toBe(hash2);
});
});
describe('JWT Functions', () => {
const testPayload = {
sub: 'test@example.com',
tenant_id: '123',
user_type: 'tenant_user',
capabilities: [
{
resource: 'tenant:test:*',
actions: ['read', 'write'],
constraints: {}
}
]
};
test('createJWT generates valid token', () => {
const token = (0, auth_1.createJWT)(testPayload);
expect(typeof token).toBe('string');
expect(token.split('.')).toHaveLength(3); // JWT has 3 parts
});
test('verifyJWT validates correct token', () => {
const token = (0, auth_1.createJWT)(testPayload);
const decoded = (0, auth_1.verifyJWT)(token);
expect(decoded).toBeTruthy();
expect(decoded?.sub).toBe(testPayload.sub);
expect(decoded?.tenant_id).toBe(testPayload.tenant_id);
expect(decoded?.user_type).toBe(testPayload.user_type);
});
test('verifyJWT rejects invalid token', () => {
const decoded = (0, auth_1.verifyJWT)('invalid.token.here');
expect(decoded).toBeNull();
});
test('verifyJWT rejects tampered token', () => {
const token = (0, auth_1.createJWT)(testPayload);
const tamperedToken = token.slice(0, -10) + 'tampered123';
const decoded = (0, auth_1.verifyJWT)(tamperedToken);
expect(decoded).toBeNull();
});
test('isTokenExpired detects expired tokens', () => {
const expiredPayload = {
...testPayload,
exp: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago
iat: Math.floor(Date.now() / 1000) - 7200 // 2 hours ago
};
expect((0, auth_1.isTokenExpired)(expiredPayload)).toBe(true);
});
test('isTokenExpired allows valid tokens', () => {
const validPayload = {
...testPayload,
exp: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
iat: Math.floor(Date.now() / 1000) // Now
};
expect((0, auth_1.isTokenExpired)(validPayload)).toBe(false);
});
});
describe('Capability Authorization', () => {
const userCapabilities = [
{
resource: 'tenant:acme:*',
actions: ['read', 'write'],
constraints: {}
},
{
resource: 'ai_resource:*',
actions: ['use'],
constraints: {
usage_limits: {
max_requests_per_hour: 100
}
}
}
];
test('hasCapability grants access for exact match', () => {
const hasAccess = (0, auth_1.hasCapability)(userCapabilities, 'tenant:acme:conversations', 'read');
expect(hasAccess).toBe(true);
});
test('hasCapability grants access for wildcard match', () => {
const hasAccess = (0, auth_1.hasCapability)(userCapabilities, 'ai_resource:groq', 'use');
expect(hasAccess).toBe(true);
});
test('hasCapability denies access for unauthorized resource', () => {
const hasAccess = (0, auth_1.hasCapability)(userCapabilities, 'tenant:other:*', 'read');
expect(hasAccess).toBe(false);
});
test('hasCapability denies access for unauthorized action', () => {
const hasAccess = (0, auth_1.hasCapability)(userCapabilities, 'tenant:acme:*', 'admin');
expect(hasAccess).toBe(false);
});
test('hasCapability respects time constraints', () => {
const expiredCapabilities = [
{
resource: 'tenant:test:*',
actions: ['read'],
constraints: {
valid_until: new Date(Date.now() - 3600000).toISOString() // 1 hour ago
}
}
];
const hasAccess = (0, auth_1.hasCapability)(expiredCapabilities, 'tenant:test:*', 'read');
expect(hasAccess).toBe(false);
});
});
describe('Password Functions', () => {
const testPassword = 'TestPassword123!';
test('hashPassword creates valid hash', async () => {
const hash = await (0, auth_1.hashPassword)(testPassword);
expect(typeof hash).toBe('string');
expect(hash).not.toBe(testPassword);
expect(hash.startsWith('$2b$')).toBe(true); // bcrypt hash format
});
test('verifyPassword validates correct password', async () => {
const hash = await (0, auth_1.hashPassword)(testPassword);
const isValid = await (0, auth_1.verifyPassword)(testPassword, hash);
expect(isValid).toBe(true);
});
test('verifyPassword rejects incorrect password', async () => {
const hash = await (0, auth_1.hashPassword)(testPassword);
const isValid = await (0, auth_1.verifyPassword)('WrongPassword', hash);
expect(isValid).toBe(false);
});
test('different passwords create different hashes', async () => {
const hash1 = await (0, auth_1.hashPassword)('Password1');
const hash2 = await (0, auth_1.hashPassword)('Password2');
expect(hash1).not.toBe(hash2);
});
});
describe('Utility Functions', () => {
test('generateSecureToken creates token of correct length', () => {
const token = (0, auth_1.generateSecureToken)(16);
expect(typeof token).toBe('string');
expect(token.length).toBe(32); // Hex encoding doubles the length
});
test('generateSecureToken creates different tokens', () => {
const token1 = (0, auth_1.generateSecureToken)();
const token2 = (0, auth_1.generateSecureToken)();
expect(token1).not.toBe(token2);
});
test('extractBearerToken extracts token correctly', () => {
const token = (0, auth_1.extractBearerToken)('Bearer abc123token');
expect(token).toBe('abc123token');
});
test('extractBearerToken returns null for invalid format', () => {
expect((0, auth_1.extractBearerToken)('Invalid format')).toBeNull();
expect((0, auth_1.extractBearerToken)('Bearer')).toBeNull();
expect((0, auth_1.extractBearerToken)('')).toBeNull();
expect((0, auth_1.extractBearerToken)(undefined)).toBeNull();
});
});
describe('Capability Template Functions', () => {
test('createTenantCapabilities for admin user', () => {
const capabilities = (0, auth_1.createTenantCapabilities)('acme', 'tenant_admin');
expect(capabilities).toHaveLength(2);
expect(capabilities[0].resource).toBe('tenant:acme:*');
expect(capabilities[0].actions).toContain('admin');
expect(capabilities[1].resource).toBe('ai_resource:*');
});
test('createTenantCapabilities for regular user', () => {
const capabilities = (0, auth_1.createTenantCapabilities)('acme', 'tenant_user');
expect(capabilities).toHaveLength(3);
expect(capabilities[0].resource).toBe('tenant:acme:conversations');
expect(capabilities[0].actions).not.toContain('admin');
expect(capabilities[1].resource).toBe('tenant:acme:documents');
});
test('createSuperAdminCapabilities grants full access', () => {
const capabilities = (0, auth_1.createSuperAdminCapabilities)();
expect(capabilities).toHaveLength(1);
expect(capabilities[0].resource).toBe('*');
expect(capabilities[0].actions).toEqual(['*']);
});
});
});

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,204 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Unit tests for cryptographic utilities
*/
const crypto_1 = require("../crypto");
describe('Cryptographic Utilities', () => {
describe('Key Generation', () => {
test('generateEncryptionKey creates valid key', () => {
const key = (0, crypto_1.generateEncryptionKey)();
expect(typeof key).toBe('string');
expect(key.length).toBe(64); // 32 bytes * 2 for hex encoding
expect(/^[a-f0-9]+$/i.test(key)).toBe(true); // Valid hex string
});
test('generateEncryptionKey creates different keys', () => {
const key1 = (0, crypto_1.generateEncryptionKey)();
const key2 = (0, crypto_1.generateEncryptionKey)();
expect(key1).not.toBe(key2);
});
});
describe('Encryption/Decryption', () => {
const testData = 'This is test data to encrypt';
const testKey = 'a'.repeat(64); // 32-byte key in hex
test('encrypt returns encrypted data with IV and tag', () => {
const result = (0, crypto_1.encrypt)(testData, testKey);
expect(result).toHaveProperty('encrypted');
expect(result).toHaveProperty('iv');
expect(result).toHaveProperty('tag');
expect(typeof result.encrypted).toBe('string');
expect(typeof result.iv).toBe('string');
expect(typeof result.tag).toBe('string');
expect(result.encrypted).not.toBe(testData);
});
test('decrypt successfully recovers original data', () => {
const { encrypted, iv, tag } = (0, crypto_1.encrypt)(testData, testKey);
const decrypted = (0, crypto_1.decrypt)(encrypted, testKey, iv, tag);
expect(decrypted).toBe(testData);
});
test('decrypt fails with wrong key', () => {
const { encrypted, iv, tag } = (0, crypto_1.encrypt)(testData, testKey);
const wrongKey = 'b'.repeat(64);
expect(() => {
(0, crypto_1.decrypt)(encrypted, wrongKey, iv, tag);
}).toThrow();
});
test('decrypt fails with tampered data', () => {
const { encrypted, iv, tag } = (0, crypto_1.encrypt)(testData, testKey);
const tamperedData = encrypted.slice(0, -2) + 'XX';
expect(() => {
(0, crypto_1.decrypt)(tamperedData, testKey, iv, tag);
}).toThrow();
});
test('encryption produces different results for same data', () => {
const result1 = (0, crypto_1.encrypt)(testData, testKey);
const result2 = (0, crypto_1.encrypt)(testData, testKey);
// Different IVs should produce different encrypted data
expect(result1.encrypted).not.toBe(result2.encrypted);
expect(result1.iv).not.toBe(result2.iv);
// But both should decrypt to same original data
const decrypted1 = (0, crypto_1.decrypt)(result1.encrypted, testKey, result1.iv, result1.tag);
const decrypted2 = (0, crypto_1.decrypt)(result2.encrypted, testKey, result2.iv, result2.tag);
expect(decrypted1).toBe(testData);
expect(decrypted2).toBe(testData);
});
});
describe('Hashing', () => {
test('sha256Hash creates consistent hash', () => {
const data = 'test data';
const hash1 = (0, crypto_1.sha256Hash)(data);
const hash2 = (0, crypto_1.sha256Hash)(data);
expect(hash1).toBe(hash2);
expect(typeof hash1).toBe('string');
expect(hash1.length).toBe(64); // SHA-256 produces 32 bytes = 64 hex chars
});
test('sha256Hash creates different hashes for different data', () => {
const hash1 = (0, crypto_1.sha256Hash)('data 1');
const hash2 = (0, crypto_1.sha256Hash)('data 2');
expect(hash1).not.toBe(hash2);
});
});
describe('HMAC', () => {
const testData = 'test data';
const testSecret = 'test secret';
test('generateHMAC creates valid signature', () => {
const signature = (0, crypto_1.generateHMAC)(testData, testSecret);
expect(typeof signature).toBe('string');
expect(signature.length).toBe(64); // HMAC-SHA256 = 64 hex chars
expect(/^[a-f0-9]+$/i.test(signature)).toBe(true);
});
test('verifyHMAC validates correct signature', () => {
const signature = (0, crypto_1.generateHMAC)(testData, testSecret);
const isValid = (0, crypto_1.verifyHMAC)(testData, signature, testSecret);
expect(isValid).toBe(true);
});
test('verifyHMAC rejects incorrect signature', () => {
const signature = (0, crypto_1.generateHMAC)(testData, testSecret);
const isValid = (0, crypto_1.verifyHMAC)(testData, signature + 'tampered', testSecret);
expect(isValid).toBe(false);
});
test('verifyHMAC rejects signature with wrong secret', () => {
const signature = (0, crypto_1.generateHMAC)(testData, testSecret);
const isValid = (0, crypto_1.verifyHMAC)(testData, signature, 'wrong secret');
expect(isValid).toBe(false);
});
test('HMAC is consistent for same inputs', () => {
const signature1 = (0, crypto_1.generateHMAC)(testData, testSecret);
const signature2 = (0, crypto_1.generateHMAC)(testData, testSecret);
expect(signature1).toBe(signature2);
});
});
describe('Key Derivation', () => {
const masterKey = 'a'.repeat(64); // 32-byte master key
const tenantId = 'tenant-123';
test('deriveTenantKey creates consistent key for tenant', () => {
const key1 = (0, crypto_1.deriveTenantKey)(masterKey, tenantId);
const key2 = (0, crypto_1.deriveTenantKey)(masterKey, tenantId);
expect(key1).toBe(key2);
expect(typeof key1).toBe('string');
expect(key1.length).toBe(64); // 32 bytes in hex
});
test('deriveTenantKey creates different keys for different tenants', () => {
const key1 = (0, crypto_1.deriveTenantKey)(masterKey, 'tenant-1');
const key2 = (0, crypto_1.deriveTenantKey)(masterKey, 'tenant-2');
expect(key1).not.toBe(key2);
});
test('deriveTenantKey creates different keys for different master keys', () => {
const masterKey2 = 'b'.repeat(64);
const key1 = (0, crypto_1.deriveTenantKey)(masterKey, tenantId);
const key2 = (0, crypto_1.deriveTenantKey)(masterKey2, tenantId);
expect(key1).not.toBe(key2);
});
});
describe('Database Encryption', () => {
const testData = { id: 1, name: 'test', data: [1, 2, 3] };
const testKey = 'a'.repeat(64);
test('encryptForDatabase encrypts JSON data', () => {
const encrypted = (0, crypto_1.encryptForDatabase)(testData, testKey);
expect(typeof encrypted).toBe('string');
expect(encrypted.split(':')).toHaveLength(3); // iv:tag:encrypted format
expect(encrypted).not.toContain('test'); // Should not contain original data
});
test('decryptFromDatabase recovers original JSON data', () => {
const encrypted = (0, crypto_1.encryptForDatabase)(testData, testKey);
const decrypted = (0, crypto_1.decryptFromDatabase)(encrypted, testKey);
expect(decrypted).toEqual(testData);
});
test('decryptFromDatabase fails with wrong key', () => {
const encrypted = (0, crypto_1.encryptForDatabase)(testData, testKey);
const wrongKey = 'b'.repeat(64);
expect(() => {
(0, crypto_1.decryptFromDatabase)(encrypted, wrongKey);
}).toThrow();
});
test('decryptFromDatabase fails with invalid format', () => {
expect(() => {
(0, crypto_1.decryptFromDatabase)('invalid-format', testKey);
}).toThrow('Invalid encrypted data format');
});
test('database encryption handles complex objects', () => {
const complexData = {
user: { id: 1, name: 'John Doe' },
preferences: { theme: 'dark', lang: 'en' },
timestamps: { created: new Date().toISOString() },
numbers: [1, 2.5, -3],
boolean: true,
null_value: null
};
const encrypted = (0, crypto_1.encryptForDatabase)(complexData, testKey);
const decrypted = (0, crypto_1.decryptFromDatabase)(encrypted, testKey);
expect(decrypted).toEqual(complexData);
});
});
describe('Password Generation', () => {
test('generateSecurePassword creates password of correct length', () => {
const password = (0, crypto_1.generateSecurePassword)(16);
expect(typeof password).toBe('string');
expect(password.length).toBe(16);
});
test('generateSecurePassword uses default length', () => {
const password = (0, crypto_1.generateSecurePassword)();
expect(password.length).toBe(16); // Default length
});
test('generateSecurePassword creates different passwords', () => {
const password1 = (0, crypto_1.generateSecurePassword)();
const password2 = (0, crypto_1.generateSecurePassword)();
expect(password1).not.toBe(password2);
});
test('generateSecurePassword includes variety of characters', () => {
const password = (0, crypto_1.generateSecurePassword)(50); // Longer for better test
expect(/[a-z]/.test(password)).toBe(true); // Lowercase
expect(/[A-Z]/.test(password)).toBe(true); // Uppercase
expect(/[0-9]/.test(password)).toBe(true); // Numbers
expect(/[!@#$%^&*]/.test(password)).toBe(true); // Special chars
});
test('generateSecurePassword creates strong passwords', () => {
// Test multiple passwords to ensure consistency
for (let i = 0; i < 10; i++) {
const password = (0, crypto_1.generateSecurePassword)(12);
expect(password.length).toBe(12);
expect(/[a-zA-Z0-9!@#$%^&*]/.test(password)).toBe(true);
}
});
});
});

View File

@@ -0,0 +1,3 @@
/**
* Test setup for utility functions
*/

20
packages/utils/dist/__tests__/setup.js vendored Normal file
View File

@@ -0,0 +1,20 @@
"use strict";
/**
* Test setup for utility functions
*/
// Mock environment variables for testing
process.env.JWT_SECRET = 'test-jwt-secret-for-testing-only';
process.env.MASTER_ENCRYPTION_KEY = 'test-master-key-32-bytes-long-test';
// Mock crypto for consistent testing
jest.mock('crypto', () => {
const originalCrypto = jest.requireActual('crypto');
return {
...originalCrypto,
randomBytes: jest.fn().mockImplementation((size) => {
return Buffer.alloc(size, 'a'); // Return consistent fake random bytes
}),
randomInt: jest.fn().mockReturnValue(5), // Return consistent fake random int
};
});
// Global test timeout
jest.setTimeout(10000);

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,279 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Unit tests for validation utilities
*/
const validation_1 = require("../validation");
describe('Validation Utilities', () => {
describe('Email Validation', () => {
test('validates correct email formats', () => {
const validEmails = [
'test@example.com',
'user.name@domain.co.uk',
'user+tag@example.org',
'user123@sub.domain.com'
];
validEmails.forEach(email => {
expect((0, validation_1.isValidEmail)(email)).toBe(true);
});
});
test('rejects invalid email formats', () => {
const invalidEmails = [
'invalid-email',
'@domain.com',
'user@',
'user@domain',
'user space@domain.com',
'',
'user@@domain.com'
];
invalidEmails.forEach(email => {
expect((0, validation_1.isValidEmail)(email)).toBe(false);
});
});
});
describe('Domain Validation', () => {
test('validates correct domain formats', () => {
const validDomains = [
'acme',
'test-company',
'company123',
'a1b2c3',
'long-domain-name-with-dashes'
];
validDomains.forEach(domain => {
expect((0, validation_1.isValidDomain)(domain)).toBe(true);
});
});
test('rejects invalid domain formats', () => {
const invalidDomains = [
'AB', // Too short
'a', // Too short
'domain-', // Ends with dash
'-domain', // Starts with dash
'domain.com', // Contains dot
'domain_name', // Contains underscore
'UPPERCASE', // Contains uppercase
'domain with spaces', // Contains spaces
'a'.repeat(51), // Too long
'' // Empty
];
invalidDomains.forEach(domain => {
expect((0, validation_1.isValidDomain)(domain)).toBe(false);
});
});
});
describe('Password Validation', () => {
test('validates strong passwords', () => {
const strongPasswords = [
'StrongPass123!',
'MySecure#Password1',
'Complex$Password99'
];
strongPasswords.forEach(password => {
const result = (0, validation_1.isValidPassword)(password);
expect(result.valid).toBe(true);
expect(result.errors).toHaveLength(0);
});
});
test('rejects weak passwords with specific errors', () => {
const weakPasswords = [
{ password: 'short', expectedErrors: 5 }, // All criteria failed
{ password: 'toolongbutnothing', expectedErrors: 4 }, // No upper, digit, special
{ password: 'NoNumbers!', expectedErrors: 1 }, // No numbers
{ password: 'nonumbers123', expectedErrors: 2 }, // No upper, special
{ password: 'NOLOWER123!', expectedErrors: 1 }, // No lower
];
weakPasswords.forEach(({ password, expectedErrors }) => {
const result = (0, validation_1.isValidPassword)(password);
expect(result.valid).toBe(false);
expect(result.errors.length).toBeGreaterThanOrEqual(1);
});
});
});
describe('Tenant Create Request Validation', () => {
const validTenantRequest = {
name: 'Test Company',
domain: 'test-company',
template: 'basic',
max_users: 50,
resource_limits: {
cpu: '1000m',
memory: '2Gi',
storage: '10Gi'
}
};
test('validates correct tenant request', () => {
const result = (0, validation_1.validateTenantCreateRequest)(validTenantRequest);
expect(result.valid).toBe(true);
expect(result.errors).toHaveLength(0);
});
test('rejects request with missing name', () => {
const request = { ...validTenantRequest, name: '' };
const result = (0, validation_1.validateTenantCreateRequest)(request);
expect(result.valid).toBe(false);
expect(result.errors).toContain('Tenant name is required');
});
test('rejects request with invalid domain', () => {
const request = { ...validTenantRequest, domain: 'invalid_domain' };
const result = (0, validation_1.validateTenantCreateRequest)(request);
expect(result.valid).toBe(false);
expect(result.errors[0]).toContain('Domain must be');
});
test('rejects request with invalid template', () => {
const request = { ...validTenantRequest, template: 'invalid' };
const result = (0, validation_1.validateTenantCreateRequest)(request);
expect(result.valid).toBe(false);
expect(result.errors[0]).toContain('Template must be one of');
});
test('rejects request with invalid max_users', () => {
const request = { ...validTenantRequest, max_users: -1 };
const result = (0, validation_1.validateTenantCreateRequest)(request);
expect(result.valid).toBe(false);
expect(result.errors[0]).toContain('Max users must be between');
});
test('validates resource limits format', () => {
const invalidRequests = [
{ ...validTenantRequest, resource_limits: { cpu: 'invalid' } },
{ ...validTenantRequest, resource_limits: { memory: '2Tb' } }, // Invalid unit
{ ...validTenantRequest, resource_limits: { storage: '10' } } // Missing unit
];
invalidRequests.forEach(request => {
const result = (0, validation_1.validateTenantCreateRequest)(request);
expect(result.valid).toBe(false);
expect(result.errors.length).toBeGreaterThan(0);
});
});
});
describe('Chat Request Validation', () => {
const validChatRequest = {
message: 'Hello, how can I help you?',
conversation_id: 1,
model_id: 'gpt-4',
system_prompt: 'You are a helpful assistant.',
context_sources: ['doc1', 'doc2']
};
test('validates correct chat request', () => {
const result = (0, validation_1.validateChatRequest)(validChatRequest);
expect(result.valid).toBe(true);
expect(result.errors).toHaveLength(0);
});
test('rejects request with empty message', () => {
const request = { ...validChatRequest, message: '' };
const result = (0, validation_1.validateChatRequest)(request);
expect(result.valid).toBe(false);
expect(result.errors).toContain('Message is required');
});
test('rejects request with too long message', () => {
const request = { ...validChatRequest, message: 'a'.repeat(10001) };
const result = (0, validation_1.validateChatRequest)(request);
expect(result.valid).toBe(false);
expect(result.errors[0]).toContain('10000 characters or less');
});
test('rejects request with invalid conversation_id', () => {
const request = { ...validChatRequest, conversation_id: 0 };
const result = (0, validation_1.validateChatRequest)(request);
expect(result.valid).toBe(false);
expect(result.errors).toContain('Invalid conversation ID');
});
test('rejects request with too long system_prompt', () => {
const request = { ...validChatRequest, system_prompt: 'a'.repeat(2001) };
const result = (0, validation_1.validateChatRequest)(request);
expect(result.valid).toBe(false);
expect(result.errors[0]).toContain('2000 characters or less');
});
});
describe('Document Upload Validation', () => {
const createMockFile = (size, type, name) => ({
file: Buffer.alloc(size),
filename: name,
file_type: type
});
test('validates correct document upload', () => {
const upload = createMockFile(1000, 'text/plain', 'test.txt');
const result = (0, validation_1.validateDocumentUpload)(upload);
expect(result.valid).toBe(true);
expect(result.errors).toHaveLength(0);
});
test('rejects upload with empty filename', () => {
const upload = createMockFile(1000, 'text/plain', '');
const result = (0, validation_1.validateDocumentUpload)(upload);
expect(result.valid).toBe(false);
expect(result.errors).toContain('Filename is required');
});
test('rejects upload with unsupported file type', () => {
const upload = createMockFile(1000, 'image/jpeg', 'image.jpg');
const result = (0, validation_1.validateDocumentUpload)(upload);
expect(result.valid).toBe(false);
expect(result.errors[0]).toContain('not supported');
});
test('rejects upload with file too large', () => {
const upload = createMockFile(51 * 1024 * 1024, 'text/plain', 'large.txt');
const result = (0, validation_1.validateDocumentUpload)(upload);
expect(result.valid).toBe(false);
expect(result.errors).toContain('File size must be 50MB or less');
});
test('validates supported file types', () => {
const supportedTypes = [
'text/plain',
'text/markdown',
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'text/csv'
];
supportedTypes.forEach(type => {
const upload = createMockFile(1000, type, 'test.file');
const result = (0, validation_1.validateDocumentUpload)(upload);
expect(result.valid).toBe(true);
});
});
});
describe('Utility Validations', () => {
test('sanitizeString removes dangerous content', () => {
const dangerous = '<script>alert("xss")</script><p onclick="alert()">Click me</p>';
const sanitized = (0, validation_1.sanitizeString)(dangerous);
expect(sanitized).not.toContain('<script>');
expect(sanitized).not.toContain('onclick');
expect(sanitized).not.toContain('javascript:');
});
test('isValidUUID validates correct UUIDs', () => {
const validUUIDs = [
'123e4567-e89b-12d3-a456-426614174000',
'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11',
'6ba7b810-9dad-11d1-80b4-00c04fd430c8'
];
validUUIDs.forEach(uuid => {
expect((0, validation_1.isValidUUID)(uuid)).toBe(true);
});
});
test('isValidUUID rejects invalid UUIDs', () => {
const invalidUUIDs = [
'not-a-uuid',
'123e4567-e89b-12d3-a456', // Too short
'123e4567-e89b-12d3-a456-426614174000-extra', // Too long
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', // Invalid characters
''
];
invalidUUIDs.forEach(uuid => {
expect((0, validation_1.isValidUUID)(uuid)).toBe(false);
});
});
test('validatePagination normalizes and validates parameters', () => {
// Test valid parameters
const result1 = (0, validation_1.validatePagination)(2, 50);
expect(result1.page).toBe(2);
expect(result1.limit).toBe(50);
expect(result1.errors).toHaveLength(0);
// Test defaults
const result2 = (0, validation_1.validatePagination)();
expect(result2.page).toBe(1);
expect(result2.limit).toBe(20);
// Test invalid parameters
const result3 = (0, validation_1.validatePagination)(-1, 150);
expect(result3.page).toBe(1); // Corrected
expect(result3.limit).toBe(100); // Corrected to max
expect(result3.errors.length).toBeGreaterThan(0);
});
});
});

49
packages/utils/dist/auth.d.ts vendored Normal file
View File

@@ -0,0 +1,49 @@
import { JWTPayload, Capability } from '@gt2/types';
/**
* Generate a cryptographic hash for capability verification
*/
export declare function generateCapabilityHash(capabilities: Capability[]): string;
/**
* Verify capability hash to ensure JWT hasn't been tampered with
*/
export declare function verifyCapabilityHash(capabilities: Capability[], hash: string): boolean;
/**
* Create a capability-based JWT token
*/
export declare function createJWT(payload: Omit<JWTPayload, 'capability_hash' | 'exp' | 'iat'>): string;
/**
* Verify and decode a JWT token
*/
export declare function verifyJWT(token: string): JWTPayload | null;
/**
* Check if user has required capability
*/
export declare function hasCapability(userCapabilities: Capability[], resource: string, action: string): boolean;
/**
* Hash password for storage
*/
export declare function hashPassword(password: string): Promise<string>;
/**
* Verify password against hash
*/
export declare function verifyPassword(password: string, hash: string): Promise<boolean>;
/**
* Generate secure random token
*/
export declare function generateSecureToken(length?: number): string;
/**
* Create tenant-scoped capabilities
*/
export declare function createTenantCapabilities(tenantDomain: string, userType: 'tenant_admin' | 'tenant_user'): Capability[];
/**
* Create super admin capabilities
*/
export declare function createSuperAdminCapabilities(): Capability[];
/**
* Extract Bearer token from Authorization header
*/
export declare function extractBearerToken(authHeader?: string): string | null;
/**
* Check if JWT token is expired
*/
export declare function isTokenExpired(token: JWTPayload): boolean;

187
packages/utils/dist/auth.js vendored Normal file
View File

@@ -0,0 +1,187 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateCapabilityHash = generateCapabilityHash;
exports.verifyCapabilityHash = verifyCapabilityHash;
exports.createJWT = createJWT;
exports.verifyJWT = verifyJWT;
exports.hasCapability = hasCapability;
exports.hashPassword = hashPassword;
exports.verifyPassword = verifyPassword;
exports.generateSecureToken = generateSecureToken;
exports.createTenantCapabilities = createTenantCapabilities;
exports.createSuperAdminCapabilities = createSuperAdminCapabilities;
exports.extractBearerToken = extractBearerToken;
exports.isTokenExpired = isTokenExpired;
// Authentication and Authorization Utilities
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
const bcryptjs_1 = __importDefault(require("bcryptjs"));
const crypto_1 = __importDefault(require("crypto"));
// JWT Configuration
const JWT_SECRET = process.env.JWT_SECRET || 'dev-secret-change-in-production';
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '24h';
/**
* Generate a cryptographic hash for capability verification
*/
function generateCapabilityHash(capabilities) {
const capabilityString = JSON.stringify(capabilities, Object.keys(capabilities).sort());
return crypto_1.default.createHmac('sha256', JWT_SECRET).update(capabilityString).digest('hex');
}
/**
* Verify capability hash to ensure JWT hasn't been tampered with
*/
function verifyCapabilityHash(capabilities, hash) {
const expectedHash = generateCapabilityHash(capabilities);
return crypto_1.default.timingSafeEqual(Buffer.from(hash), Buffer.from(expectedHash));
}
/**
* Create a capability-based JWT token
*/
function createJWT(payload) {
const capability_hash = generateCapabilityHash(payload.capabilities);
const fullPayload = {
...payload,
capability_hash,
exp: Math.floor(Date.now() / 1000) + (24 * 60 * 60), // 24 hours
iat: Math.floor(Date.now() / 1000)
};
return jsonwebtoken_1.default.sign(fullPayload, JWT_SECRET, { algorithm: 'HS256' });
}
/**
* Verify and decode a JWT token
*/
function verifyJWT(token) {
try {
const decoded = jsonwebtoken_1.default.verify(token, JWT_SECRET);
// Verify capability hash to ensure token hasn't been tampered with
if (!verifyCapabilityHash(decoded.capabilities, decoded.capability_hash)) {
throw new Error('Invalid capability hash');
}
return decoded;
}
catch (error) {
return null;
}
}
/**
* Check if user has required capability
*/
function hasCapability(userCapabilities, resource, action) {
return userCapabilities.some(cap => {
// Check if capability matches resource (support wildcards)
const resourceMatch = cap.resource === '*' ||
cap.resource === resource ||
resource.startsWith(cap.resource.replace('*', ''));
// Check if capability includes required action
const actionMatch = cap.actions.includes('*') || cap.actions.includes(action);
// Check constraints if present
if (cap.constraints) {
// Check validity period
if (cap.constraints.valid_until) {
const validUntil = new Date(cap.constraints.valid_until);
if (new Date() > validUntil) {
return false;
}
}
// Additional constraint checks can be added here
}
return resourceMatch && actionMatch;
});
}
/**
* Hash password for storage
*/
async function hashPassword(password) {
const salt = await bcryptjs_1.default.genSalt(12);
return bcryptjs_1.default.hash(password, salt);
}
/**
* Verify password against hash
*/
async function verifyPassword(password, hash) {
return bcryptjs_1.default.compare(password, hash);
}
/**
* Generate secure random token
*/
function generateSecureToken(length = 32) {
return crypto_1.default.randomBytes(length).toString('hex');
}
/**
* Create tenant-scoped capabilities
*/
function createTenantCapabilities(tenantDomain, userType) {
const baseResource = `tenant:${tenantDomain}`;
if (userType === 'tenant_admin') {
return [
{
resource: `${baseResource}:*`,
actions: ['read', 'write', 'admin'],
constraints: {}
},
{
resource: 'ai_resource:*',
actions: ['use'],
constraints: {
usage_limits: {
max_requests_per_hour: 1000,
max_tokens_per_request: 4000
}
}
}
];
}
else {
return [
{
resource: `${baseResource}:conversations`,
actions: ['read', 'write'],
constraints: {}
},
{
resource: `${baseResource}:documents`,
actions: ['read', 'write'],
constraints: {}
},
{
resource: 'ai_resource:*',
actions: ['use'],
constraints: {
usage_limits: {
max_requests_per_hour: 100,
max_tokens_per_request: 4000
}
}
}
];
}
}
/**
* Create super admin capabilities
*/
function createSuperAdminCapabilities() {
return [
{
resource: '*',
actions: ['*'],
constraints: {}
}
];
}
/**
* Extract Bearer token from Authorization header
*/
function extractBearerToken(authHeader) {
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return null;
}
return authHeader.substring(7);
}
/**
* Check if JWT token is expired
*/
function isTokenExpired(token) {
return Date.now() >= token.exp * 1000;
}

44
packages/utils/dist/crypto.d.ts vendored Normal file
View File

@@ -0,0 +1,44 @@
/**
* Generate a random encryption key
*/
export declare function generateEncryptionKey(): string;
/**
* Encrypt data using AES-256-GCM
*/
export declare function encrypt(data: string, keyHex: string): {
encrypted: string;
iv: string;
tag: string;
};
/**
* Decrypt data using AES-256-GCM
*/
export declare function decrypt(encryptedData: string, keyHex: string, ivHex: string, tagHex: string): string;
/**
* Hash data using SHA-256
*/
export declare function sha256Hash(data: string): string;
/**
* Generate HMAC signature
*/
export declare function generateHMAC(data: string, secret: string): string;
/**
* Verify HMAC signature
*/
export declare function verifyHMAC(data: string, signature: string, secret: string): boolean;
/**
* Generate tenant-specific encryption key from master key and tenant ID
*/
export declare function deriveTenantKey(masterKey: string, tenantId: string): string;
/**
* Encrypt JSON data for database storage
*/
export declare function encryptForDatabase(data: any, encryptionKey: string): string;
/**
* Decrypt JSON data from database storage
*/
export declare function decryptFromDatabase(encryptedData: string, encryptionKey: string): any;
/**
* Generate a secure random password
*/
export declare function generateSecurePassword(length?: number): string;

118
packages/utils/dist/crypto.js vendored Normal file
View File

@@ -0,0 +1,118 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateEncryptionKey = generateEncryptionKey;
exports.encrypt = encrypt;
exports.decrypt = decrypt;
exports.sha256Hash = sha256Hash;
exports.generateHMAC = generateHMAC;
exports.verifyHMAC = verifyHMAC;
exports.deriveTenantKey = deriveTenantKey;
exports.encryptForDatabase = encryptForDatabase;
exports.decryptFromDatabase = decryptFromDatabase;
exports.generateSecurePassword = generateSecurePassword;
// Cryptographic utilities for GT 2.0
const crypto_1 = __importDefault(require("crypto"));
// Encryption configuration
const ALGORITHM = 'aes-256-gcm';
const KEY_LENGTH = 32; // 256 bits
const IV_LENGTH = 16; // 128 bits
const TAG_LENGTH = 16; // 128 bits
/**
* Generate a random encryption key
*/
function generateEncryptionKey() {
return crypto_1.default.randomBytes(KEY_LENGTH).toString('hex');
}
/**
* Encrypt data using AES-256-GCM
*/
function encrypt(data, keyHex) {
const key = Buffer.from(keyHex, 'hex');
const iv = crypto_1.default.randomBytes(IV_LENGTH);
const cipher = crypto_1.default.createCipher(ALGORITHM, key);
cipher.setAAD(Buffer.from('GT2-TENANT-DATA'));
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
const tag = cipher.getAuthTag();
return {
encrypted,
iv: iv.toString('hex'),
tag: tag.toString('hex')
};
}
/**
* Decrypt data using AES-256-GCM
*/
function decrypt(encryptedData, keyHex, ivHex, tagHex) {
const key = Buffer.from(keyHex, 'hex');
const iv = Buffer.from(ivHex, 'hex');
const tag = Buffer.from(tagHex, 'hex');
const decipher = crypto_1.default.createDecipher(ALGORITHM, key);
decipher.setAuthTag(tag);
decipher.setAAD(Buffer.from('GT2-TENANT-DATA'));
let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
/**
* Hash data using SHA-256
*/
function sha256Hash(data) {
return crypto_1.default.createHash('sha256').update(data).digest('hex');
}
/**
* Generate HMAC signature
*/
function generateHMAC(data, secret) {
return crypto_1.default.createHmac('sha256', secret).update(data).digest('hex');
}
/**
* Verify HMAC signature
*/
function verifyHMAC(data, signature, secret) {
const expectedSignature = generateHMAC(data, secret);
return crypto_1.default.timingSafeEqual(Buffer.from(signature, 'hex'), Buffer.from(expectedSignature, 'hex'));
}
/**
* Generate tenant-specific encryption key from master key and tenant ID
*/
function deriveTenantKey(masterKey, tenantId) {
const key = crypto_1.default.pbkdf2Sync(tenantId, Buffer.from(masterKey, 'hex'), 100000, // iterations
KEY_LENGTH, 'sha256');
return key.toString('hex');
}
/**
* Encrypt JSON data for database storage
*/
function encryptForDatabase(data, encryptionKey) {
const jsonString = JSON.stringify(data);
const { encrypted, iv, tag } = encrypt(jsonString, encryptionKey);
// Combine all components into a single string
return `${iv}:${tag}:${encrypted}`;
}
/**
* Decrypt JSON data from database storage
*/
function decryptFromDatabase(encryptedData, encryptionKey) {
const [iv, tag, encrypted] = encryptedData.split(':');
if (!iv || !tag || !encrypted) {
throw new Error('Invalid encrypted data format');
}
const jsonString = decrypt(encrypted, encryptionKey, iv, tag);
return JSON.parse(jsonString);
}
/**
* Generate a secure random password
*/
function generateSecurePassword(length = 16) {
const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
let password = '';
for (let i = 0; i < length; i++) {
const randomIndex = crypto_1.default.randomInt(0, charset.length);
password += charset[randomIndex];
}
return password;
}

51
packages/utils/dist/database.d.ts vendored Normal file
View File

@@ -0,0 +1,51 @@
/**
* Generate SQLite database path for tenant
*/
export declare function getTenantDatabasePath(tenantDomain: string, dataDir?: string): string;
/**
* Generate ChromaDB collection name for tenant
*/
export declare function getTenantChromaCollection(tenantDomain: string): string;
/**
* Generate Redis key prefix for tenant
*/
export declare function getTenantRedisPrefix(tenantDomain: string): string;
/**
* Generate MinIO bucket name for tenant
*/
export declare function getTenantMinioBucket(tenantDomain: string): string;
/**
* Generate SQLite WAL mode configuration
*/
export declare function getSQLiteWALConfig(): string;
/**
* Generate SQLite encryption configuration
*/
export declare function getSQLiteEncryptionConfig(encryptionKey: string): string;
/**
* Create tenant database schema (SQLite)
*/
export declare function getTenantDatabaseSchema(): string;
/**
* Generate unique document chunk ID
*/
export declare function generateDocumentChunkId(documentId: number, chunkIndex: number): string;
/**
* Parse connection string for database configuration
*/
export declare function parseConnectionString(connectionString: string): {
host?: string;
port?: number;
database?: string;
username?: string;
password?: string;
options?: Record<string, string>;
};
/**
* Escape SQL identifiers (table names, column names, etc.)
*/
export declare function escapeSQLIdentifier(identifier: string): string;
/**
* Generate database backup filename
*/
export declare function generateBackupFilename(tenantDomain: string, timestamp?: Date): string;

216
packages/utils/dist/database.js vendored Normal file
View 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`;
}

1
packages/utils/dist/index.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export {};

1
packages/utils/dist/index.js vendored Normal file
View File

@@ -0,0 +1 @@
export {};

58
packages/utils/dist/tenant.d.ts vendored Normal file
View File

@@ -0,0 +1,58 @@
import { Tenant, TenantCreateRequest } from '@gt2/types';
/**
* Generate Kubernetes namespace name for tenant
*/
export declare function generateTenantNamespace(domain: string): string;
/**
* Generate tenant subdomain
*/
export declare function generateTenantSubdomain(domain: string): string;
/**
* Generate OS user ID for tenant isolation
*/
export declare function generateTenantUserId(tenantId: number): number;
/**
* Generate OS group ID for tenant isolation
*/
export declare function generateTenantGroupId(tenantId: number): number;
/**
* Get tenant data directory path
*/
export declare function getTenantDataPath(domain: string, baseDataDir?: string): string;
/**
* Get default resource limits based on template
*/
export declare function getTemplateResourceLimits(template: string): {
cpu: string;
memory: string;
storage: string;
};
/**
* Get default max users based on template
*/
export declare function getTemplateMaxUsers(template: string): number;
/**
* Validate tenant domain availability (placeholder - would check database in real implementation)
*/
export declare function isDomainAvailable(domain: string): boolean;
/**
* Generate complete tenant configuration from create request
*/
export declare function generateTenantConfig(request: TenantCreateRequest, masterEncryptionKey: string): Partial<Tenant>;
/**
* Generate Kubernetes deployment YAML for tenant
*/
export declare function generateTenantDeploymentYAML(tenant: Tenant, tenantUserId: number): string;
/**
* Calculate tenant usage costs
*/
export declare function calculateTenantCosts(cpuUsage: number, // CPU hours
memoryUsage: number, // Memory GB-hours
storageUsage: number, // Storage GB-hours
aiTokens: number): {
cpu_cost_cents: number;
memory_cost_cents: number;
storage_cost_cents: number;
ai_cost_cents: number;
total_cost_cents: number;
};

338
packages/utils/dist/tenant.js vendored Normal file
View File

@@ -0,0 +1,338 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateTenantNamespace = generateTenantNamespace;
exports.generateTenantSubdomain = generateTenantSubdomain;
exports.generateTenantUserId = generateTenantUserId;
exports.generateTenantGroupId = generateTenantGroupId;
exports.getTenantDataPath = getTenantDataPath;
exports.getTemplateResourceLimits = getTemplateResourceLimits;
exports.getTemplateMaxUsers = getTemplateMaxUsers;
exports.isDomainAvailable = isDomainAvailable;
exports.generateTenantConfig = generateTenantConfig;
exports.generateTenantDeploymentYAML = generateTenantDeploymentYAML;
exports.calculateTenantCosts = calculateTenantCosts;
/**
* Generate Kubernetes namespace name for tenant
*/
function generateTenantNamespace(domain) {
return `gt-${domain}`;
}
/**
* Generate tenant subdomain
*/
function generateTenantSubdomain(domain) {
return domain; // For now, subdomain matches domain
}
/**
* Generate OS user ID for tenant isolation
*/
function generateTenantUserId(tenantId) {
const baseUserId = 10000; // Start user IDs from 10000
return baseUserId + tenantId;
}
/**
* Generate OS group ID for tenant isolation
*/
function generateTenantGroupId(tenantId) {
return generateTenantUserId(tenantId); // Use same ID for group
}
/**
* Get tenant data directory path
*/
function getTenantDataPath(domain, baseDataDir = '/data') {
return `${baseDataDir}/${domain}`;
}
/**
* Get default resource limits based on template
*/
function getTemplateResourceLimits(template) {
switch (template) {
case 'basic':
return {
cpu: '500m',
memory: '1Gi',
storage: '5Gi'
};
case 'professional':
return {
cpu: '1000m',
memory: '2Gi',
storage: '20Gi'
};
case 'enterprise':
return {
cpu: '2000m',
memory: '4Gi',
storage: '100Gi'
};
default:
return {
cpu: '500m',
memory: '1Gi',
storage: '5Gi'
};
}
}
/**
* Get default max users based on template
*/
function getTemplateMaxUsers(template) {
switch (template) {
case 'basic':
return 10;
case 'professional':
return 100;
case 'enterprise':
return 1000;
default:
return 10;
}
}
/**
* Validate tenant domain availability (placeholder - would check database in real implementation)
*/
function isDomainAvailable(domain) {
// In real implementation, this would check the database
// For now, just check format
const reservedDomains = ['admin', 'api', 'www', 'mail', 'ftp', 'localhost', 'gt2'];
return !reservedDomains.includes(domain.toLowerCase());
}
/**
* Generate complete tenant configuration from create request
*/
function generateTenantConfig(request, masterEncryptionKey) {
const template = request.template || 'basic';
const resourceLimits = request.resource_limits || getTemplateResourceLimits(template);
const maxUsers = request.max_users || getTemplateMaxUsers(template);
return {
name: request.name.trim(),
domain: request.domain.toLowerCase(),
template,
max_users: maxUsers,
resource_limits: resourceLimits,
namespace: generateTenantNamespace(request.domain),
subdomain: generateTenantSubdomain(request.domain),
status: 'pending'
};
}
/**
* Generate Kubernetes deployment YAML for tenant
*/
function generateTenantDeploymentYAML(tenant, tenantUserId) {
return `
apiVersion: v1
kind: Namespace
metadata:
name: ${tenant.namespace}
labels:
gt.tenant: ${tenant.domain}
gt.template: ${tenant.template}
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ${tenant.domain}-isolation
namespace: ${tenant.namespace}
spec:
podSelector: {}
policyTypes: ["Ingress", "Egress"]
ingress:
- from:
- namespaceSelector:
matchLabels:
name: gt-admin
egress:
- to:
- namespaceSelector:
matchLabels:
name: gt-resource
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ${tenant.domain}-config
namespace: ${tenant.namespace}
data:
TENANT_ID: "${tenant.id}"
TENANT_DOMAIN: "${tenant.domain}"
TENANT_NAME: "${tenant.name}"
DATABASE_PATH: "/data/${tenant.domain}/app.db"
CHROMA_COLLECTION: "gt2_${tenant.domain.replace(/-/g, '_')}_documents"
REDIS_PREFIX: "gt2:${tenant.domain}:"
MINIO_BUCKET: "gt2-${tenant.domain}-files"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${tenant.domain}-app
namespace: ${tenant.namespace}
labels:
app: ${tenant.domain}-app
tenant: ${tenant.domain}
spec:
replicas: 1
selector:
matchLabels:
app: ${tenant.domain}-app
template:
metadata:
labels:
app: ${tenant.domain}-app
tenant: ${tenant.domain}
spec:
securityContext:
runAsUser: ${tenantUserId}
runAsGroup: ${tenantUserId}
fsGroup: ${tenantUserId}
containers:
- name: frontend
image: gt2/tenant-frontend:latest
ports:
- containerPort: 3000
name: frontend
env:
- name: NEXT_PUBLIC_API_URL
value: "http://localhost:8000"
- name: NEXT_PUBLIC_WS_URL
value: "ws://localhost:8000"
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "${tenant.resource_limits.cpu}"
memory: "${tenant.resource_limits.memory}"
volumeMounts:
- name: tenant-data
mountPath: /data/${tenant.domain}
- name: backend
image: gt2/tenant-backend:latest
ports:
- containerPort: 8000
name: backend
envFrom:
- configMapRef:
name: ${tenant.domain}-config
env:
- name: ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: ${tenant.domain}-secrets
key: encryption-key
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "${tenant.resource_limits.cpu}"
memory: "${tenant.resource_limits.memory}"
volumeMounts:
- name: tenant-data
mountPath: /data/${tenant.domain}
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8000
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: tenant-data
persistentVolumeClaim:
claimName: ${tenant.domain}-data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ${tenant.domain}-data
namespace: ${tenant.namespace}
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: ${tenant.resource_limits.storage}
---
apiVersion: v1
kind: Secret
metadata:
name: ${tenant.domain}-secrets
namespace: ${tenant.namespace}
type: Opaque
data:
encryption-key: ${Buffer.from(tenant.encryption_key || '').toString('base64')}
---
apiVersion: v1
kind: Service
metadata:
name: ${tenant.domain}-service
namespace: ${tenant.namespace}
spec:
selector:
app: ${tenant.domain}-app
ports:
- name: frontend
port: 3000
targetPort: 3000
- name: backend
port: 8000
targetPort: 8000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ${tenant.domain}-ingress
namespace: ${tenant.namespace}
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: ${tenant.subdomain}.gt2.local
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: ${tenant.domain}-service
port:
number: 8000
- path: /
pathType: Prefix
backend:
service:
name: ${tenant.domain}-service
port:
number: 3000
`.trim();
}
/**
* Calculate tenant usage costs
*/
function calculateTenantCosts(cpuUsage, // CPU hours
memoryUsage, // Memory GB-hours
storageUsage, // Storage GB-hours
aiTokens // AI tokens used
) {
// Pricing (example rates)
const CPU_COST_PER_HOUR = 5; // 5 cents per CPU hour
const MEMORY_COST_PER_GB_HOUR = 1; // 1 cent per GB-hour
const STORAGE_COST_PER_GB_HOUR = 0.1; // 0.1 cents per GB-hour
const AI_COST_PER_1K_TOKENS = 0.5; // 0.5 cents per 1K tokens
const cpu_cost_cents = Math.round(cpuUsage * CPU_COST_PER_HOUR);
const memory_cost_cents = Math.round(memoryUsage * MEMORY_COST_PER_GB_HOUR);
const storage_cost_cents = Math.round(storageUsage * STORAGE_COST_PER_GB_HOUR);
const ai_cost_cents = Math.round((aiTokens / 1000) * AI_COST_PER_1K_TOKENS);
return {
cpu_cost_cents,
memory_cost_cents,
storage_cost_cents,
ai_cost_cents,
total_cost_cents: cpu_cost_cents + memory_cost_cents + storage_cost_cents + ai_cost_cents
};
}

53
packages/utils/dist/validation.d.ts vendored Normal file
View File

@@ -0,0 +1,53 @@
import { TenantCreateRequest, ChatRequest, DocumentUploadRequest } from '@gt2/types';
/**
* Validate email format
*/
export declare function isValidEmail(email: string): boolean;
/**
* Validate domain name format
*/
export declare function isValidDomain(domain: string): boolean;
/**
* Validate password strength
*/
export declare function isValidPassword(password: string): {
valid: boolean;
errors: string[];
};
/**
* Validate tenant creation request
*/
export declare function validateTenantCreateRequest(request: TenantCreateRequest): {
valid: boolean;
errors: string[];
};
/**
* Validate chat request
*/
export declare function validateChatRequest(request: ChatRequest): {
valid: boolean;
errors: string[];
};
/**
* Validate file upload request
*/
export declare function validateDocumentUpload(request: DocumentUploadRequest): {
valid: boolean;
errors: string[];
};
/**
* Sanitize string input to prevent injection attacks
*/
export declare function sanitizeString(input: string): string;
/**
* Validate UUID format
*/
export declare function isValidUUID(uuid: string): boolean;
/**
* Validate pagination parameters
*/
export declare function validatePagination(page?: number, limit?: number): {
page: number;
limit: number;
errors: string[];
};

205
packages/utils/dist/validation.js vendored Normal file
View File

@@ -0,0 +1,205 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isValidEmail = isValidEmail;
exports.isValidDomain = isValidDomain;
exports.isValidPassword = isValidPassword;
exports.validateTenantCreateRequest = validateTenantCreateRequest;
exports.validateChatRequest = validateChatRequest;
exports.validateDocumentUpload = validateDocumentUpload;
exports.sanitizeString = sanitizeString;
exports.isValidUUID = isValidUUID;
exports.validatePagination = validatePagination;
/**
* Validate email format
*/
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
/**
* Validate domain name format
*/
function isValidDomain(domain) {
// Must be lowercase alphanumeric with hyphens, 3-50 characters
const domainRegex = /^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$/;
return domainRegex.test(domain);
}
/**
* Validate password strength
*/
function isValidPassword(password) {
const errors = [];
if (password.length < 8) {
errors.push('Password must be at least 8 characters long');
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain at least one uppercase letter');
}
if (!/[a-z]/.test(password)) {
errors.push('Password must contain at least one lowercase letter');
}
if (!/[0-9]/.test(password)) {
errors.push('Password must contain at least one number');
}
if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
errors.push('Password must contain at least one special character');
}
return {
valid: errors.length === 0,
errors
};
}
/**
* Validate tenant creation request
*/
function validateTenantCreateRequest(request) {
const errors = [];
// Validate name
if (!request.name || request.name.trim().length === 0) {
errors.push('Tenant name is required');
}
else if (request.name.length > 100) {
errors.push('Tenant name must be 100 characters or less');
}
// Validate domain
if (!request.domain) {
errors.push('Domain is required');
}
else if (!isValidDomain(request.domain)) {
errors.push('Domain must be 3-50 characters, lowercase alphanumeric with hyphens');
}
// Validate template
const validTemplates = ['basic', 'professional', 'enterprise'];
if (request.template && !validTemplates.includes(request.template)) {
errors.push(`Template must be one of: ${validTemplates.join(', ')}`);
}
// Validate max_users
if (request.max_users !== undefined) {
if (request.max_users < 1 || request.max_users > 10000) {
errors.push('Max users must be between 1 and 10000');
}
}
// Validate resource limits
if (request.resource_limits) {
if (request.resource_limits.cpu) {
if (!/^\d+m?$/.test(request.resource_limits.cpu)) {
errors.push('CPU limit must be in format like "1000m" or "2"');
}
}
if (request.resource_limits.memory) {
if (!/^\d+(Mi|Gi)$/.test(request.resource_limits.memory)) {
errors.push('Memory limit must be in format like "2Gi" or "512Mi"');
}
}
if (request.resource_limits.storage) {
if (!/^\d+(Mi|Gi|Ti)$/.test(request.resource_limits.storage)) {
errors.push('Storage limit must be in format like "10Gi" or "100Mi"');
}
}
}
return {
valid: errors.length === 0,
errors
};
}
/**
* Validate chat request
*/
function validateChatRequest(request) {
const errors = [];
if (!request.message || request.message.trim().length === 0) {
errors.push('Message is required');
}
else if (request.message.length > 10000) {
errors.push('Message must be 10000 characters or less');
}
if (request.conversation_id !== undefined && request.conversation_id < 1) {
errors.push('Invalid conversation ID');
}
if (request.system_prompt && request.system_prompt.length > 2000) {
errors.push('System prompt must be 2000 characters or less');
}
return {
valid: errors.length === 0,
errors
};
}
/**
* Validate file upload request
*/
function validateDocumentUpload(request) {
const errors = [];
if (!request.filename || request.filename.trim().length === 0) {
errors.push('Filename is required');
}
else if (request.filename.length > 255) {
errors.push('Filename must be 255 characters or less');
}
// Validate file type
const allowedTypes = [
'text/plain',
'text/markdown',
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'text/csv'
];
if (!allowedTypes.includes(request.file_type)) {
errors.push(`File type ${request.file_type} is not supported`);
}
// Check file size (assuming file is Buffer with length property)
if (Buffer.isBuffer(request.file)) {
const maxSize = 50 * 1024 * 1024; // 50MB
if (request.file.length > maxSize) {
errors.push('File size must be 50MB or less');
}
}
else if (request.file instanceof File) {
const maxSize = 50 * 1024 * 1024; // 50MB
if (request.file.size > maxSize) {
errors.push('File size must be 50MB or less');
}
}
return {
valid: errors.length === 0,
errors
};
}
/**
* Sanitize string input to prevent injection attacks
*/
function sanitizeString(input) {
return input
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') // Remove script tags
.replace(/javascript:/gi, '') // Remove javascript: protocol
.replace(/on\w+\s*=\s*['"][^'"]*['"]?/gi, '') // Remove event handlers
.trim();
}
/**
* Validate UUID format
*/
function isValidUUID(uuid) {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
return uuidRegex.test(uuid);
}
/**
* Validate pagination parameters
*/
function validatePagination(page, limit) {
const errors = [];
let validatedPage = page || 1;
let validatedLimit = limit || 20;
if (validatedPage < 1) {
errors.push('Page must be 1 or greater');
validatedPage = 1;
}
if (validatedLimit < 1 || validatedLimit > 100) {
errors.push('Limit must be between 1 and 100');
validatedLimit = Math.min(Math.max(validatedLimit, 1), 100);
}
return {
page: validatedPage,
limit: validatedLimit,
errors
};
}