#!/usr/bin/env node /** * Test script to demonstrate the composable security architecture * * This script simulates loading and using the composable security system * with different configurations. It can be run from either the project root * or from within the test/ directory. * * Usage: * node test/composable-security.test.js (from project root) * node composable-security.test.js (from test/ directory) */ const fs = require('fs'); const path = require('path'); // Mock the require function first, before any other requires const originalRequire = require; // Mock base security plugin (friends) const mockFriendsPlugin = (log, loga, argv) => ({ retrieveOwner: (cb) => { console.log('[FRIENDS] Retrieving owner...'); cb(); }, getOwner: () => 'test-owner', setOwner: (id, cb) => { console.log(`[FRIENDS] Setting owner to: ${id}`); cb(); }, getUser: (req) => { if (req.session && req.session.friend) { return 'test-user'; } return ''; }, isAuthorized: (req) => { return req.session && req.session.friend === 'test-secret'; }, isAdmin: (req) => { return req.session && req.session.friend === 'admin-secret'; }, defineRoutes: (app, cors, updateOwner) => { app.post('/login', 'friends-login-handler'); app.get('/logout', 'friends-logout-handler'); }, _debug: { authProvider: 'wiki-security-friends' } }); // Mock plugin with security enhancer (new approach) const mockPluginWithSecurityEnhancer = { securityEnhancer: (log, loga, argv, baseHandler) => ({ isAuthorized: (req, baseIsAuthorized) => { console.log('[PLUGIN-ENHANCER] Checking authorization'); return baseIsAuthorized(); }, getUser: (req, baseGetUser) => { if (req.tokenAuth && req.tokenAuth.user) { console.log('[PLUGIN-ENHANCER] Returning token user'); return req.tokenAuth.user; } return baseGetUser(); }, middleware: (req, res, next) => { console.log('[PLUGIN-ENHANCER] Middleware processing request'); // Simulate token validation if (req.headers.authorization === 'Bearer test-token') { req.tokenAuth = { user: 'token-user' }; } next(); }, defineRoutes: (app, cors, updateOwner) => { console.log('[PLUGIN-ENHANCER] Defining routes'); app.get('/plugin/test/tokens', 'token-management-handler'); } }), startServer: (params) => { console.log('[PLUGIN] Regular plugin server started'); } }; // Mock authz enhancer (simplified) const mockTokenEnhancer = (log, loga, argv, baseHandler) => ({ middleware: (req, res, next) => { console.log('[TOKENS] Processing token middleware'); next(); }, getUser: (req, baseGetUser) => { const baseUser = baseGetUser(); if (baseUser) return baseUser; // Check for token auth if (req.headers && req.headers.authorization) { console.log('[TOKENS] Token authentication detected'); return 'token-user'; } return ''; }, defineRoutes: (app, cors, updateOwner) => { app.post('/auth/tokens', 'create-token-handler'); app.get('/auth/tokens', 'list-tokens-handler'); } }); const mockRateLimitEnhancer = (log, loga, argv, baseHandler) => ({ middleware: (req, res, next) => { console.log('[RATELIMIT] Processing rate limit middleware'); next(); }, defineRoutes: (app, cors, updateOwner) => { app.get('/auth/ratelimit/status', 'ratelimit-status-handler'); } }); // Set up module mocking require = function(module) { switch(module) { case 'wiki-security-friends': return mockFriendsPlugin; case 'wiki-plugin-useraccesstokens': return mockPluginWithSecurityEnhancer; case 'wiki-plugin-ratelimit': return mockPluginWithSecurityEnhancer; // Same structure for testing default: return originalRequire(module); } }; // Mock dependencies that would normally be provided by the wiki server const mockLog = (msg) => console.log(`[LOG] ${msg}`); const mockLoga = mockLog; // Support running from different directories let statusPath = '/tmp/fedwiki-test-status'; let indexPath = '../index.js'; // Default: relative to test directory // Check if we're running from the root directory const cwd = process.cwd(); const testDir = __dirname; if (fs.existsSync(path.join(cwd, 'index.js'))) { // Running from project root - use relative path to current working directory statusPath = './test-status'; indexPath = path.join(cwd, 'index.js'); } else { // Running from test directory or elsewhere - use relative to test file statusPath = './test-status'; indexPath = '../index.js'; } const mockArgv = { status: statusPath, auth_provider: mockFriendsPlugin, authz_enhancers: [mockPluginWithSecurityEnhancer, mockPluginWithSecurityEnhancer], // Two mock plugins ratelimit_config: { windowMs: 60000, // 1 minute for testing maxRequests: 10, maxAuthRequests: 3 } }; // Mock Express app const mockApp = { use: (path, middleware) => { if (typeof path === 'function') { console.log(`[APP] Added global middleware`); } else { console.log(`[APP] Added middleware for: ${path || 'all routes'}`); } }, get: (path, ...handlers) => console.log(`[APP] Added GET route: ${path}`), post: (path, ...handlers) => console.log(`[APP] Added POST route: ${path}`), delete: (path, ...handlers) => console.log(`[APP] Added DELETE route: ${path}`) }; const mockCors = (req, res, next) => next(); const mockUpdateOwner = (owner) => console.log(`[OWNER] Updated to: ${owner}`); console.log('=== Composable Security Architecture Test ===\n'); console.log(`Running from: ${process.cwd()}`); console.log(`Using index path: ${indexPath}`); console.log(`Using status path: ${statusPath}\n`); // Test 1: Load the composable security system console.log('1. Loading composable security system...'); try { const ComposableSecurity = require(indexPath); const security = ComposableSecurity(mockLog, mockLoga, mockArgv); console.log('✓ Composable security loaded successfully'); console.log(`✓ Debug info:`, security._debug); console.log(); // Test 2: Initialize routes console.log('2. Initializing routes...'); security.defineRoutes(mockApp, mockCors, mockUpdateOwner); console.log('✓ Routes initialized'); console.log(); // Test 3: Test owner management console.log('3. Testing owner management...'); security.retrieveOwner(() => { const owner = security.getOwner(); console.log(`✓ Current owner: ${owner}`); }); console.log(); // Test 4: Test request handling console.log('4. Testing request handling...'); // Mock requests const mockReqAuth = { session: { friend: 'test-secret' }, ip: '127.0.0.1', path: '/test', get: () => null, query: {} }; const mockReqToken = { session: {}, ip: '127.0.0.1', path: '/api/test', headers: { authorization: 'Bearer uat_test123' }, get: (header) => header === 'Authorization' ? 'Bearer uat_test123' : null, query: {} }; const mockReqUnauth = { session: {}, ip: '127.0.0.1', path: '/test', get: () => null, query: {} }; // Test authenticated request console.log('Testing authenticated request...'); console.log(` User: ${security.getUser(mockReqAuth)}`); console.log(` Authorized: ${security.isAuthorized(mockReqAuth)}`); console.log(` Admin: ${security.isAdmin(mockReqAuth)}`); // Test token request console.log('Testing token request...'); console.log(` User: ${security.getUser(mockReqToken)}`); console.log(` Authorized: ${security.isAuthorized(mockReqToken)}`); // Test unauthenticated request console.log('Testing unauthenticated request...'); console.log(` User: ${security.getUser(mockReqUnauth)}`); console.log(` Authorized: ${security.isAuthorized(mockReqUnauth)}`); console.log('✓ Request handling tests completed'); console.log(); console.log('=== Test Summary ==='); console.log('✓ All tests completed successfully'); console.log('✓ Composable security architecture is working'); console.log('✓ Auth provider and authz enhancers loaded correctly'); console.log('✓ Security interface maintained compatibility'); } catch (error) { console.error('✗ Test failed:', error.message); console.error(error.stack); process.exit(1); } // Test 5: Plugin-based enhancers console.log('\n5. Testing plugin-based enhancers...'); try { const mockArgvPlugin = { status: statusPath, auth_provider: 'wiki-security-friends', authz_enhancers: ['wiki-plugin-useraccesstokens', 'wiki-plugin-ratelimit'] }; const ComposableSecurity = require(indexPath); const securityPlugin = ComposableSecurity(mockLog, mockLoga, mockArgvPlugin); console.log('✓ Plugin-based security loaded successfully'); console.log(`✓ Loaded enhancers: ${securityPlugin._debug.loadedEnhancers.join(', ')}`); // Test routes initialization console.log('Initializing plugin-based routes...'); securityPlugin.defineRoutes(mockApp, mockCors, mockUpdateOwner); // Test token request with plugin enhancer const mockReqPluginToken = { session: {}, ip: '127.0.0.1', path: '/api/test', headers: { authorization: 'Bearer test-token' }, get: (header) => header === 'Authorization' ? 'Bearer test-token' : null, query: {} }; console.log('Testing plugin token request...'); console.log(` User: ${securityPlugin.getUser(mockReqPluginToken)}`); console.log(` Authorized: ${securityPlugin.isAuthorized(mockReqPluginToken)}`); console.log('✓ Plugin-based enhancer tests completed'); console.log(); } catch (error) { console.error('✗ Plugin-based test failed:', error.message); console.error(error.stack); process.exit(1); } console.log('=== Final Test Summary ==='); console.log('✓ All tests completed successfully'); console.log('✓ Plugin-based enhancers working correctly'); console.log('✓ Composable security architecture is fully functional');