- Deprecated/broken migration files archived - New auth_schema migration (015) - Trading service events migration (016) - Migration renumbering utility - Migration test suite
450 lines
15 KiB
PL/PgSQL
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;
|