-- ================================================================================================ -- 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;