Files
foxhunt/migrations/tests/test_auth_schema.sql
jgrusewski 0d9f890aa6 🗄️ Wave 112: Migration cleanup and new schemas
- Deprecated/broken migration files archived
- New auth_schema migration (015)
- Trading service events migration (016)
- Migration renumbering utility
- Migration test suite
2025-10-05 22:23:37 +02:00

450 lines
15 KiB
PL/PgSQL

-- ================================================================================================
-- Authentication & Authorization Schema Test Suite (Migration 015)
-- PostgreSQL 16.10 + TimescaleDB 2.22.1
-- Tests RBAC, JWT revocation, rate limiting, and security features
-- ================================================================================================
BEGIN;
-- ================================================================================================
-- TEST 1: Authentication Tables Existence
-- Verify all auth-related tables are created
-- ================================================================================================
DO $$
DECLARE
v_required_tables TEXT[] := ARRAY[
'users',
'roles',
'permissions',
'user_roles',
'role_permissions',
'api_keys',
'jwt_revocations',
'rate_limits',
'auth_sessions',
'mfa_tokens',
'password_history'
];
v_table TEXT;
v_missing_tables TEXT[] := ARRAY[]::TEXT[];
v_found_count INTEGER := 0;
BEGIN
FOREACH v_table IN ARRAY v_required_tables
LOOP
IF EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = v_table
) THEN
v_found_count := v_found_count + 1;
ELSE
v_missing_tables := array_append(v_missing_tables, v_table);
END IF;
END LOOP;
IF array_length(v_missing_tables, 1) IS NULL THEN
RAISE NOTICE 'TEST 1 PASS: All % auth tables exist', array_length(v_required_tables, 1);
ELSE
RAISE WARNING 'TEST 1 WARNING: Missing tables (% of %): %',
array_length(v_missing_tables, 1),
array_length(v_required_tables, 1),
array_to_string(v_missing_tables, ', ');
END IF;
END $$;
-- ================================================================================================
-- TEST 2: User Creation and Validation
-- Test user table constraints and validation
-- ================================================================================================
DO $$
DECLARE
v_user_id UUID := uuid_generate_v4();
v_password_hash VARCHAR(255) := encode(sha256('SecureP@ssw0rd'::bytea), 'hex');
BEGIN
-- Insert valid user
INSERT INTO users (
id, username, email, password_hash, is_active, is_verified
) VALUES (
v_user_id, 'test_user_auth', 'test@example.com', v_password_hash, true, true
);
RAISE NOTICE 'TEST 2 PASS: User created successfully';
-- Test duplicate username constraint
BEGIN
INSERT INTO users (
username, email, password_hash
) VALUES (
'test_user_auth', 'another@example.com', v_password_hash
);
RAISE EXCEPTION 'TEST 2 FAIL: Should have rejected duplicate username';
EXCEPTION
WHEN unique_violation THEN
RAISE NOTICE 'TEST 2 PASS: Correctly rejected duplicate username';
END;
-- Test duplicate email constraint
BEGIN
INSERT INTO users (
username, email, password_hash
) VALUES (
'another_user', 'test@example.com', v_password_hash
);
RAISE EXCEPTION 'TEST 2 FAIL: Should have rejected duplicate email';
EXCEPTION
WHEN unique_violation THEN
RAISE NOTICE 'TEST 2 PASS: Correctly rejected duplicate email';
END;
END $$;
-- ================================================================================================
-- TEST 3: RBAC - Role and Permission Management
-- Test role-based access control setup
-- ================================================================================================
DO $$
DECLARE
v_role_id UUID := uuid_generate_v4();
v_perm_id UUID := uuid_generate_v4();
v_user_id UUID;
BEGIN
-- Create role
INSERT INTO roles (id, name, description)
VALUES (v_role_id, 'test_trader', 'Test trading role');
-- Create permission
INSERT INTO permissions (id, name, resource, action, description)
VALUES (v_perm_id, 'trade:execute', 'orders', 'execute', 'Execute trading orders');
-- Link permission to role
INSERT INTO role_permissions (role_id, permission_id)
VALUES (v_role_id, v_perm_id);
-- Get test user
SELECT id INTO v_user_id FROM users WHERE username = 'test_user_auth';
-- Assign role to user
INSERT INTO user_roles (user_id, role_id)
VALUES (v_user_id, v_role_id);
-- Verify RBAC chain
IF EXISTS (
SELECT 1
FROM users u
JOIN user_roles ur ON u.id = ur.user_id
JOIN roles r ON ur.role_id = r.id
JOIN role_permissions rp ON r.id = rp.role_id
JOIN permissions p ON rp.permission_id = p.id
WHERE u.id = v_user_id
AND p.name = 'trade:execute'
) THEN
RAISE NOTICE 'TEST 3 PASS: RBAC chain (user -> role -> permission) validated';
ELSE
RAISE EXCEPTION 'TEST 3 FAIL: RBAC chain validation failed';
END IF;
END $$;
-- ================================================================================================
-- TEST 4: API Key Management
-- Test API key generation and validation
-- ================================================================================================
DO $$
DECLARE
v_user_id UUID;
v_api_key_id UUID := uuid_generate_v4();
v_key_hash VARCHAR(255) := encode(sha256('test_api_key_12345'::bytea), 'hex');
BEGIN
SELECT id INTO v_user_id FROM users WHERE username = 'test_user_auth';
-- Create API key
INSERT INTO api_keys (
id, user_id, key_hash, name, expires_at
) VALUES (
v_api_key_id, v_user_id, v_key_hash, 'Test API Key',
NOW() + INTERVAL '30 days'
);
-- Verify API key
IF EXISTS (
SELECT 1 FROM api_keys
WHERE id = v_api_key_id
AND is_active = true
AND expires_at > NOW()
) THEN
RAISE NOTICE 'TEST 4 PASS: API key created and validated';
ELSE
RAISE EXCEPTION 'TEST 4 FAIL: API key validation failed';
END IF;
-- Test revocation
UPDATE api_keys SET is_active = false WHERE id = v_api_key_id;
IF EXISTS (
SELECT 1 FROM api_keys
WHERE id = v_api_key_id AND is_active = false
) THEN
RAISE NOTICE 'TEST 4 PASS: API key revocation successful';
ELSE
RAISE EXCEPTION 'TEST 4 FAIL: API key revocation failed';
END IF;
END $$;
-- ================================================================================================
-- TEST 5: JWT Revocation System
-- Test JWT token revocation
-- ================================================================================================
DO $$
DECLARE
v_jti UUID := uuid_generate_v4();
v_user_id UUID;
BEGIN
SELECT id INTO v_user_id FROM users WHERE username = 'test_user_auth';
-- Revoke JWT token
INSERT INTO jwt_revocations (
jti, user_id, expires_at, reason
) VALUES (
v_jti, v_user_id, NOW() + INTERVAL '1 hour', 'User logout'
);
-- Verify revocation
IF EXISTS (
SELECT 1 FROM jwt_revocations
WHERE jti = v_jti
AND expires_at > NOW()
) THEN
RAISE NOTICE 'TEST 5 PASS: JWT revocation recorded';
ELSE
RAISE EXCEPTION 'TEST 5 FAIL: JWT revocation failed';
END IF;
-- Test duplicate revocation (should fail)
BEGIN
INSERT INTO jwt_revocations (jti, user_id, expires_at)
VALUES (v_jti, v_user_id, NOW() + INTERVAL '1 hour');
RAISE EXCEPTION 'TEST 5 FAIL: Should have rejected duplicate JTI';
EXCEPTION
WHEN unique_violation THEN
RAISE NOTICE 'TEST 5 PASS: Correctly rejected duplicate JWT revocation';
END;
END $$;
-- ================================================================================================
-- TEST 6: Rate Limiting
-- Test rate limit tracking
-- ================================================================================================
DO $$
DECLARE
v_user_id UUID;
v_endpoint TEXT := '/api/v1/orders';
v_window_start BIGINT := EXTRACT(EPOCH FROM NOW())::BIGINT;
v_request_count INTEGER;
BEGIN
SELECT id INTO v_user_id FROM users WHERE username = 'test_user_auth';
-- Simulate multiple requests
FOR i IN 1..5 LOOP
INSERT INTO rate_limits (
user_id, endpoint, window_start, request_count
) VALUES (
v_user_id, v_endpoint, v_window_start, 1
)
ON CONFLICT (user_id, endpoint, window_start)
DO UPDATE SET request_count = rate_limits.request_count + 1;
END LOOP;
-- Check rate limit counter
SELECT request_count INTO v_request_count
FROM rate_limits
WHERE user_id = v_user_id
AND endpoint = v_endpoint
AND window_start = v_window_start;
IF v_request_count = 5 THEN
RAISE NOTICE 'TEST 6 PASS: Rate limiting counter accurate (% requests)', v_request_count;
ELSE
RAISE EXCEPTION 'TEST 6 FAIL: Expected 5 requests, found %', v_request_count;
END IF;
-- Test rate limit breach detection
IF v_request_count > 3 THEN
RAISE NOTICE 'TEST 6 INFO: Rate limit breach detected (limit: 3, actual: %)', v_request_count;
END IF;
END $$;
-- ================================================================================================
-- TEST 7: MFA Token Management
-- Test multi-factor authentication tokens
-- ================================================================================================
DO $$
DECLARE
v_user_id UUID;
v_token_id UUID := uuid_generate_v4();
v_secret VARCHAR(255) := encode(gen_random_bytes(20), 'base32');
BEGIN
SELECT id INTO v_user_id FROM users WHERE username = 'test_user_auth';
-- Create MFA token
INSERT INTO mfa_tokens (
id, user_id, token_type, secret, is_verified
) VALUES (
v_token_id, v_user_id, 'totp', v_secret, true
);
-- Verify MFA token
IF EXISTS (
SELECT 1 FROM mfa_tokens
WHERE id = v_token_id
AND user_id = v_user_id
AND is_verified = true
) THEN
RAISE NOTICE 'TEST 7 PASS: MFA token created and verified';
ELSE
RAISE EXCEPTION 'TEST 7 FAIL: MFA token verification failed';
END IF;
-- Test MFA requirement
UPDATE users SET mfa_enabled = true WHERE id = v_user_id;
IF EXISTS (
SELECT 1 FROM users u
JOIN mfa_tokens m ON u.id = m.user_id
WHERE u.id = v_user_id
AND u.mfa_enabled = true
AND m.is_verified = true
) THEN
RAISE NOTICE 'TEST 7 PASS: MFA requirement enforced';
ELSE
RAISE EXCEPTION 'TEST 7 FAIL: MFA enforcement failed';
END IF;
END $$;
-- ================================================================================================
-- TEST 8: Password History
-- Test password reuse prevention
-- ================================================================================================
DO $$
DECLARE
v_user_id UUID;
v_old_hash VARCHAR(255) := encode(sha256('OldP@ssw0rd'::bytea), 'hex');
v_new_hash VARCHAR(255) := encode(sha256('NewP@ssw0rd'::bytea), 'hex');
BEGIN
SELECT id INTO v_user_id FROM users WHERE username = 'test_user_auth';
-- Store old password in history
INSERT INTO password_history (user_id, password_hash)
VALUES (v_user_id, v_old_hash);
-- Verify history stored
IF EXISTS (
SELECT 1 FROM password_history
WHERE user_id = v_user_id
AND password_hash = v_old_hash
) THEN
RAISE NOTICE 'TEST 8 PASS: Password history recorded';
ELSE
RAISE EXCEPTION 'TEST 8 FAIL: Password history storage failed';
END IF;
-- Test password reuse check
IF EXISTS (
SELECT 1 FROM password_history
WHERE user_id = v_user_id
AND password_hash = v_new_hash
) THEN
RAISE EXCEPTION 'TEST 8 FAIL: Should prevent password reuse';
ELSE
RAISE NOTICE 'TEST 8 PASS: Password reuse prevention validated';
END IF;
END $$;
-- ================================================================================================
-- TEST 9: Session Management
-- Test authentication session lifecycle
-- ================================================================================================
DO $$
DECLARE
v_user_id UUID;
v_session_id UUID := uuid_generate_v4();
v_token VARCHAR(255) := encode(gen_random_bytes(32), 'hex');
BEGIN
SELECT id INTO v_user_id FROM users WHERE username = 'test_user_auth';
-- Create session
INSERT INTO auth_sessions (
id, user_id, token_hash, expires_at, ip_address, user_agent
) VALUES (
v_session_id, v_user_id, encode(sha256(v_token::bytea), 'hex'),
NOW() + INTERVAL '24 hours', '192.168.1.100', 'Test User Agent'
);
-- Verify active session
IF EXISTS (
SELECT 1 FROM auth_sessions
WHERE id = v_session_id
AND user_id = v_user_id
AND expires_at > NOW()
) THEN
RAISE NOTICE 'TEST 9 PASS: Session created successfully';
ELSE
RAISE EXCEPTION 'TEST 9 FAIL: Session creation failed';
END IF;
-- Test session invalidation
UPDATE auth_sessions SET expires_at = NOW() - INTERVAL '1 hour'
WHERE id = v_session_id;
IF NOT EXISTS (
SELECT 1 FROM auth_sessions
WHERE id = v_session_id AND expires_at > NOW()
) THEN
RAISE NOTICE 'TEST 9 PASS: Session invalidation successful';
ELSE
RAISE EXCEPTION 'TEST 9 FAIL: Session invalidation failed';
END IF;
END $$;
-- ================================================================================================
-- TEST 10: Security Audit Trail
-- Verify authentication events are logged
-- ================================================================================================
DO $$
DECLARE
v_user_id UUID;
v_current_ns BIGINT := EXTRACT(EPOCH FROM NOW())::BIGINT * 1000000000;
v_auth_events INTEGER;
BEGIN
SELECT id INTO v_user_id FROM users WHERE username = 'test_user_auth';
-- Log authentication events
INSERT INTO audit_events (
correlation_id, event_timestamp, event_type, severity, component,
user_id, event_data, event_hash, node_id, process_id
) VALUES
(uuid_generate_v4(), v_current_ns, 'authentication_attempt', 'info', 'authentication',
v_user_id::text, '{"method": "password", "ip": "192.168.1.100"}'::jsonb,
encode(sha256('auth1'::bytea), 'hex'), 'node-01', 12345),
(uuid_generate_v4(), v_current_ns + 1000000, 'authentication_success', 'info', 'authentication',
v_user_id::text, '{"method": "password", "ip": "192.168.1.100"}'::jsonb,
encode(sha256('auth2'::bytea), 'hex'), 'node-01', 12345);
-- Count auth events
SELECT COUNT(*) INTO v_auth_events
FROM audit_events
WHERE user_id = v_user_id::text
AND event_type IN ('authentication_attempt', 'authentication_success');
IF v_auth_events >= 2 THEN
RAISE NOTICE 'TEST 10 PASS: Authentication audit trail complete (% events logged)', v_auth_events;
ELSE
RAISE EXCEPTION 'TEST 10 FAIL: Authentication audit trail incomplete';
END IF;
END $$;
-- ================================================================================================
-- Cleanup: Rollback all test data
-- ================================================================================================
ROLLBACK;
-- Test Summary
SELECT 'Authentication & Authorization Test Suite Completed - Review NOTICE/WARNING messages above' AS test_summary;