-- Authentication and Security Schema for Foxhunt Trading System -- Implements comprehensive security for financial trading platform -- Compliant with SOX, FINRA, and financial industry standards -- Users table for authentication CREATE TABLE IF NOT EXISTS users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), username VARCHAR(255) UNIQUE NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, salt VARCHAR(255) NOT NULL, first_name VARCHAR(255), last_name VARCHAR(255), phone VARCHAR(50), department VARCHAR(100), job_title VARCHAR(100), manager_id UUID REFERENCES users(id), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), last_login TIMESTAMP WITH TIME ZONE, failed_login_attempts INTEGER DEFAULT 0, account_locked_until TIMESTAMP WITH TIME ZONE, password_changed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), must_change_password BOOLEAN DEFAULT FALSE, two_factor_enabled BOOLEAN DEFAULT FALSE, two_factor_secret VARCHAR(255), active BOOLEAN DEFAULT TRUE, deleted_at TIMESTAMP WITH TIME ZONE, -- Audit fields created_by UUID REFERENCES users(id), updated_by UUID REFERENCES users(id) ); -- Roles table for RBAC CREATE TABLE IF NOT EXISTS roles ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(100) UNIQUE NOT NULL, description TEXT, permissions TEXT[], -- JSON array of permissions parent_role_id UUID REFERENCES roles(id), resource_constraints JSONB, -- Resource-based constraints created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), active BOOLEAN DEFAULT TRUE, -- Hierarchy depth for performance hierarchy_level INTEGER DEFAULT 0, -- Audit fields created_by UUID REFERENCES users(id), updated_by UUID REFERENCES users(id) ); -- User role assignments CREATE TABLE IF NOT EXISTS user_roles ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE, granted_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), expires_at TIMESTAMP WITH TIME ZONE, granted_by UUID NOT NULL REFERENCES users(id), resource_constraints JSONB, -- Additional constraints for this assignment active BOOLEAN DEFAULT TRUE, UNIQUE(user_id, role_id) ); -- Sessions table for session management CREATE TABLE IF NOT EXISTS sessions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), token_hash VARCHAR(255) UNIQUE NOT NULL, -- Hashed session token user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), last_activity TIMESTAMP WITH TIME ZONE DEFAULT NOW(), expires_at TIMESTAMP WITH TIME ZONE NOT NULL, client_ip INET, user_agent TEXT, device_fingerprint VARCHAR(255), active BOOLEAN DEFAULT TRUE, -- Session metadata login_method VARCHAR(50), -- password, api_key, certificate session_type VARCHAR(50) DEFAULT 'web' -- web, api, mobile ); -- API keys table CREATE TABLE IF NOT EXISTS api_keys ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(255) NOT NULL, key_hash VARCHAR(255) UNIQUE NOT NULL, -- Hashed API key user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, permissions TEXT[], -- JSON array of permissions created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), last_used TIMESTAMP WITH TIME ZONE, expires_at TIMESTAMP WITH TIME ZONE NOT NULL, usage_count BIGINT DEFAULT 0, rate_limit_override INTEGER, -- Custom rate limit for this key ip_whitelist INET[], -- Allowed IP addresses active BOOLEAN DEFAULT TRUE, revoked_at TIMESTAMP WITH TIME ZONE, revoked_by UUID REFERENCES users(id), revoke_reason TEXT, -- Key metadata key_type VARCHAR(50) DEFAULT 'standard', -- standard, trading, readonly scopes TEXT[] -- API scopes this key can access ); -- Audit log table for compliance CREATE TABLE IF NOT EXISTS audit_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), timestamp TIMESTAMP WITH TIME ZONE DEFAULT NOW(), event_type VARCHAR(100) NOT NULL, severity VARCHAR(20) NOT NULL CHECK (severity IN ('info', 'warning', 'error', 'critical')), user_id UUID REFERENCES users(id), session_id UUID REFERENCES sessions(id), api_key_id UUID REFERENCES api_keys(id), client_ip INET, user_agent TEXT, resource VARCHAR(255), action VARCHAR(100) NOT NULL, result VARCHAR(100) NOT NULL, details JSONB, correlation_id UUID, request_id UUID, service_name VARCHAR(100), service_version VARCHAR(50), -- Compliance fields compliance_category VARCHAR(100), -- SOX, FINRA, MiFID, etc. retention_until TIMESTAMP WITH TIME ZONE, -- Tamper detection checksum VARCHAR(255), previous_log_hash VARCHAR(255) ); -- Rate limiting buckets CREATE TABLE IF NOT EXISTS rate_limit_buckets ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), client_identifier VARCHAR(255) NOT NULL, bucket_type VARCHAR(50) NOT NULL, -- auth, api, trading, market_data requests JSONB NOT NULL DEFAULT '[]', -- Array of request timestamps total_requests BIGINT DEFAULT 0, last_request TIMESTAMP WITH TIME ZONE, violations INTEGER DEFAULT 0, blocked_until TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(client_identifier, bucket_type) ); -- TLS certificates table CREATE TABLE IF NOT EXISTS certificates ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(255) NOT NULL, certificate_type VARCHAR(50) NOT NULL, -- server, client, ca subject VARCHAR(500) NOT NULL, issuer VARCHAR(500) NOT NULL, serial_number VARCHAR(100) NOT NULL, fingerprint VARCHAR(255) UNIQUE NOT NULL, not_before TIMESTAMP WITH TIME ZONE NOT NULL, not_after TIMESTAMP WITH TIME ZONE NOT NULL, key_algorithm VARCHAR(50) NOT NULL, key_size INTEGER NOT NULL, signature_algorithm VARCHAR(100) NOT NULL, san_dns_names TEXT[], san_ip_addresses INET[], certificate_pem TEXT NOT NULL, private_key_encrypted TEXT, -- Encrypted private key (if stored) created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), active BOOLEAN DEFAULT TRUE, -- Certificate chain relationships parent_certificate_id UUID REFERENCES certificates(id), root_ca_id UUID REFERENCES certificates(id) ); -- Permission cache table for performance CREATE TABLE IF NOT EXISTS permission_cache ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, permissions JSONB NOT NULL, cached_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), expires_at TIMESTAMP WITH TIME ZONE NOT NULL, cache_version INTEGER DEFAULT 1, UNIQUE(user_id, cache_version) ); -- Compliance violations table CREATE TABLE IF NOT EXISTS compliance_violations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), violation_type VARCHAR(100) NOT NULL, severity VARCHAR(20) NOT NULL CHECK (severity IN ('low', 'medium', 'high', 'critical')), user_id UUID REFERENCES users(id), description TEXT NOT NULL, detected_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), resolved_at TIMESTAMP WITH TIME ZONE, resolved_by UUID REFERENCES users(id), resolution_notes TEXT, compliance_framework VARCHAR(100), -- SOX, FINRA, MiFID II, etc. rule_violated VARCHAR(255), evidence JSONB, status VARCHAR(50) DEFAULT 'open' CHECK (status IN ('open', 'investigating', 'resolved', 'false_positive')), -- Regulatory reporting reported_to_regulator BOOLEAN DEFAULT FALSE, regulator_reference VARCHAR(255), reporting_deadline TIMESTAMP WITH TIME ZONE ); -- Indexes for performance CREATE INDEX IF NOT EXISTS idx_users_username ON users(username); CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); CREATE INDEX IF NOT EXISTS idx_users_active ON users(active) WHERE active = TRUE; CREATE INDEX IF NOT EXISTS idx_users_last_login ON users(last_login); CREATE INDEX IF NOT EXISTS idx_roles_name ON roles(name); CREATE INDEX IF NOT EXISTS idx_roles_active ON roles(active) WHERE active = TRUE; CREATE INDEX IF NOT EXISTS idx_roles_parent ON roles(parent_role_id); CREATE INDEX IF NOT EXISTS idx_user_roles_user_id ON user_roles(user_id); CREATE INDEX IF NOT EXISTS idx_user_roles_role_id ON user_roles(role_id); CREATE INDEX IF NOT EXISTS idx_user_roles_active ON user_roles(active) WHERE active = TRUE; CREATE INDEX IF NOT EXISTS idx_user_roles_expires ON user_roles(expires_at); CREATE INDEX IF NOT EXISTS idx_sessions_token_hash ON sessions(token_hash); CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id); CREATE INDEX IF NOT EXISTS idx_sessions_active ON sessions(active) WHERE active = TRUE; CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at); CREATE INDEX IF NOT EXISTS idx_sessions_last_activity ON sessions(last_activity); CREATE INDEX IF NOT EXISTS idx_api_keys_key_hash ON api_keys(key_hash); CREATE INDEX IF NOT EXISTS idx_api_keys_user_id ON api_keys(user_id); CREATE INDEX IF NOT EXISTS idx_api_keys_active ON api_keys(active) WHERE active = TRUE; CREATE INDEX IF NOT EXISTS idx_api_keys_expires ON api_keys(expires_at); CREATE INDEX IF NOT EXISTS idx_audit_logs_timestamp ON audit_logs(timestamp); CREATE INDEX IF NOT EXISTS idx_audit_logs_user_id ON audit_logs(user_id); CREATE INDEX IF NOT EXISTS idx_audit_logs_event_type ON audit_logs(event_type); CREATE INDEX IF NOT EXISTS idx_audit_logs_severity ON audit_logs(severity); CREATE INDEX IF NOT EXISTS idx_audit_logs_correlation_id ON audit_logs(correlation_id); CREATE INDEX IF NOT EXISTS idx_rate_limit_buckets_client ON rate_limit_buckets(client_identifier, bucket_type); CREATE INDEX IF NOT EXISTS idx_rate_limit_buckets_updated ON rate_limit_buckets(updated_at); CREATE INDEX IF NOT EXISTS idx_certificates_fingerprint ON certificates(fingerprint); CREATE INDEX IF NOT EXISTS idx_certificates_not_after ON certificates(not_after); CREATE INDEX IF NOT EXISTS idx_certificates_active ON certificates(active) WHERE active = TRUE; CREATE INDEX IF NOT EXISTS idx_permission_cache_user_id ON permission_cache(user_id); CREATE INDEX IF NOT EXISTS idx_permission_cache_expires ON permission_cache(expires_at); CREATE INDEX IF NOT EXISTS idx_compliance_violations_user_id ON compliance_violations(user_id); CREATE INDEX IF NOT EXISTS idx_compliance_violations_status ON compliance_violations(status); CREATE INDEX IF NOT EXISTS idx_compliance_violations_detected ON compliance_violations(detected_at); CREATE INDEX IF NOT EXISTS idx_compliance_violations_framework ON compliance_violations(compliance_framework); -- Triggers for updated_at timestamps CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ language 'plpgsql'; CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_roles_updated_at BEFORE UPDATE ON roles FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_certificates_updated_at BEFORE UPDATE ON certificates FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_rate_limit_buckets_updated_at BEFORE UPDATE ON rate_limit_buckets FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- Function to clean up expired sessions CREATE OR REPLACE FUNCTION cleanup_expired_sessions() RETURNS INTEGER AS $$ DECLARE deleted_count INTEGER; BEGIN DELETE FROM sessions WHERE expires_at < NOW() - INTERVAL '7 days'; GET DIAGNOSTICS deleted_count = ROW_COUNT; RETURN deleted_count; END; $$ LANGUAGE plpgsql; -- Function to clean up old audit logs (based on retention policy) CREATE OR REPLACE FUNCTION cleanup_audit_logs(retention_days INTEGER DEFAULT 2555) RETURNS INTEGER AS $$ DECLARE deleted_count INTEGER; BEGIN DELETE FROM audit_logs WHERE timestamp < NOW() - INTERVAL '1 day' * retention_days; GET DIAGNOSTICS deleted_count = ROW_COUNT; RETURN deleted_count; END; $$ LANGUAGE plpgsql; -- Function to check password complexity CREATE OR REPLACE FUNCTION check_password_complexity(password_hash TEXT) RETURNS BOOLEAN AS $$ BEGIN -- In production, implement proper password complexity checking -- This is a placeholder that assumes passwords are already validated RETURN LENGTH(password_hash) >= 60; -- Assuming bcrypt hash length END; $$ LANGUAGE plpgsql; -- Function to log security events CREATE OR REPLACE FUNCTION log_security_event( event_type VARCHAR(100), user_id UUID, session_id UUID, client_ip INET, details JSONB ) RETURNS UUID AS $$ DECLARE log_id UUID; BEGIN INSERT INTO audit_logs ( event_type, severity, user_id, session_id, client_ip, action, result, details, service_name ) VALUES ( event_type, 'info', user_id, session_id, client_ip, 'security_event', 'logged', details, 'tli' ) RETURNING id INTO log_id; RETURN log_id; END; $$ LANGUAGE plpgsql; -- Insert default roles INSERT INTO roles (id, name, description, permissions, hierarchy_level) VALUES ('00000000-0000-0000-0000-000000000001', 'system_admin', 'System Administrator - Full Access', ARRAY['system:admin', 'system:config', 'system:user_management', 'system:role_management'], 0), ('00000000-0000-0000-0000-000000000002', 'trader', 'Senior Trader - Full Trading Access', ARRAY['trade:execute', 'trade:view', 'trade:cancel', 'trade:modify', 'order:place', 'order:cancel', 'order:modify', 'order:view', 'position:view', 'position:close', 'position:modify', 'risk:view', 'market_data:view', 'market_data:subscribe', 'portfolio:view', 'report:view', 'analytics:view', 'api:access', 'ml:signal_view'], 1), ('00000000-0000-0000-0000-000000000003', 'junior_trader', 'Junior Trader - Limited Trading Access', ARRAY['order:place', 'order:view', 'position:view', 'trade:view', 'risk:view', 'market_data:view', 'portfolio:view', 'report:view', 'ml:signal_view'], 2), ('00000000-0000-0000-0000-000000000004', 'risk_manager', 'Risk Manager - Risk Oversight', ARRAY['risk:view', 'risk:config', 'risk:override', 'risk:limits', 'risk:drawdown_monitor', 'position:view', 'position:limit', 'trade:view', 'order:view', 'portfolio:view', 'report:view', 'report:generate', 'analytics:view', 'compliance:view', 'audit:view'], 1), ('00000000-0000-0000-0000-000000000005', 'viewer', 'Viewer - Read-only Access', ARRAY['trade:view', 'order:view', 'position:view', 'risk:view', 'market_data:view', 'portfolio:view', 'report:view', 'analytics:view', 'ml:signal_view'], 3), ('00000000-0000-0000-0000-000000000006', 'api_user', 'API User - Programmatic Access', ARRAY['api:access', 'market_data:view', 'market_data:subscribe', 'order:place', 'order:view', 'order:cancel', 'position:view', 'trade:view', 'ml:signal_view'], 2) ON CONFLICT (id) DO NOTHING; -- Insert default admin user (password should be changed on first login) -- Default password hash is for 'DefaultAdmin123!' - MUST be changed in production INSERT INTO users ( id, username, email, password_hash, salt, first_name, last_name, must_change_password ) VALUES ( '00000000-0000-0000-0000-000000000001', 'admin', 'admin@foxhunt.local', '$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/lewZJYHCB7vMNZJdK', -- DefaultAdmin123! 'default_salt_change_in_production', 'System', 'Administrator', TRUE ) ON CONFLICT (id) DO NOTHING; -- Assign admin role to default admin user INSERT INTO user_roles (user_id, role_id, granted_by) VALUES ('00000000-0000-0000-0000-000000000001', '00000000-0000-0000-0000-000000000001', '00000000-0000-0000-0000-000000000001') ON CONFLICT (user_id, role_id) DO NOTHING; -- Create view for active user permissions CREATE OR REPLACE VIEW user_permissions AS SELECT DISTINCT u.id as user_id, u.username, unnest(r.permissions) as permission, ur.expires_at as permission_expires_at FROM users u JOIN user_roles ur ON u.id = ur.user_id JOIN roles r ON ur.role_id = r.id WHERE u.active = TRUE AND ur.active = TRUE AND r.active = TRUE AND (ur.expires_at IS NULL OR ur.expires_at > NOW()); -- Create view for session summary CREATE OR REPLACE VIEW active_sessions AS SELECT s.id, s.user_id, u.username, s.created_at, s.last_activity, s.expires_at, s.client_ip, s.session_type, EXTRACT(EPOCH FROM (s.expires_at - NOW())) as seconds_until_expiry FROM sessions s JOIN users u ON s.user_id = u.id WHERE s.active = TRUE AND s.expires_at > NOW(); -- Grant permissions to application role (adjust as needed) -- GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO foxhunt_app; -- GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO foxhunt_app; -- Add comments for documentation COMMENT ON TABLE users IS 'User accounts for authentication and authorization'; COMMENT ON TABLE roles IS 'Role definitions for RBAC system'; COMMENT ON TABLE user_roles IS 'User to role assignments with optional expiration'; COMMENT ON TABLE sessions IS 'Active user sessions for session management'; COMMENT ON TABLE api_keys IS 'API keys for programmatic access'; COMMENT ON TABLE audit_logs IS 'Comprehensive audit trail for compliance'; COMMENT ON TABLE rate_limit_buckets IS 'Rate limiting state tracking'; COMMENT ON TABLE certificates IS 'TLS certificate management'; COMMENT ON TABLE permission_cache IS 'Cached user permissions for performance'; COMMENT ON TABLE compliance_violations IS 'Compliance violations tracking and reporting'; COMMENT ON COLUMN users.password_hash IS 'Bcrypt hash of user password'; COMMENT ON COLUMN users.salt IS 'Salt used for password hashing'; COMMENT ON COLUMN users.failed_login_attempts IS 'Count of consecutive failed login attempts'; COMMENT ON COLUMN users.account_locked_until IS 'Account lockout expiration timestamp'; COMMENT ON COLUMN users.two_factor_secret IS 'TOTP secret for 2FA (encrypted)'; COMMENT ON COLUMN audit_logs.checksum IS 'Tamper detection checksum for audit integrity'; COMMENT ON COLUMN audit_logs.previous_log_hash IS 'Hash of previous log entry for chain verification'; COMMENT ON COLUMN audit_logs.retention_until IS 'Data retention deadline for compliance'; COMMENT ON COLUMN api_keys.key_hash IS 'SHA-256 hash of the API key for secure storage'; COMMENT ON COLUMN api_keys.scopes IS 'API scopes this key can access'; COMMENT ON COLUMN api_keys.ip_whitelist IS 'Allowed source IP addresses for this key'; COMMENT ON COLUMN certificates.certificate_pem IS 'PEM-encoded certificate'; COMMENT ON COLUMN certificates.private_key_encrypted IS 'Encrypted private key (if stored)'; COMMENT ON COLUMN certificates.san_dns_names IS 'Subject Alternative Names - DNS names'; COMMENT ON COLUMN certificates.san_ip_addresses IS 'Subject Alternative Names - IP addresses';