# Federated Wiki Security Architecture Enhancement ## Overview This document proposes an enhancement to Federated Wiki's security architecture that separates **authentication** (who you are) from **authorization enhancement** (what you can do). This change would enable complementary security features like API tokens, rate limiting, and fine-grained permissions to work alongside any authentication method without conflicts. ## Current Architecture Limitations ### How Federated Wiki Security Works Today Federated Wiki currently uses a single "security plugin" architecture where each plugin handles: - **Authentication** - Proving user identity (OAuth, shared secrets, etc.) - **Session management** - Storing user state - **Authorization** - Determining what users can do - **Route handling** - Login/logout endpoints - **UI components** - Login dialogs and forms ### The Problem This monolithic approach creates several issues: 1. **Mutually Exclusive Plugins**: You can only use one security plugin at a time 2. **Limited Extensibility**: Adding new authentication methods requires replacing the entire security system 3. **Feature Conflicts**: Plugins that want to enhance security (like API tokens) must either: - Duplicate all existing authentication logic, or - Use hacky workarounds to simulate existing authentication ### Real-World Impact Consider these common scenarios that are difficult or impossible today: ```bash # Impossible: OAuth login + API tokens wiki --security_type passportjs --enable_api_tokens # Impossible: Friends auth + rate limiting wiki --security_type friends --enable_rate_limiting # Impossible: Any auth + audit logging wiki --security_type passportjs --enable_audit_log ``` Each security enhancement requires creating a completely new security plugin or forking existing ones. ## Proposed Solution: Composable Authentication Architecture ### Core Concept Separate authentication from authorization enhancement: - **Authentication Component**: Handles core identity verification (one plugin) - **Authorization Enhancement Components**: Add complementary security features (multiple plugins) ### New Command Line Interface *Proposed future syntax if server adopts this architecture natively:* ```bash # Authentication (mutually exclusive) wiki --auth_type friends wiki --auth_type passportjs wiki --auth_type ldap # Authorization enhancements (complementary) wiki --auth_type friends --authz_enhancers wiki-plugin-useraccesstokens wiki --auth_type passportjs --authz_enhancers wiki-plugin-useraccesstokens,wiki-plugin-ratelimit wiki --auth_type ldap --authz_enhancers wiki-plugin-useraccesstokens,wiki-plugin-permissions,wiki-plugin-audit ``` *Current implementation using `wiki-security-composable`:* ```bash wiki --security_type composable --auth_provider wiki-security-friends --authz_enhancers wiki-plugin-useraccesstokens ``` ### Plugin Types **Authentication Plugins** (`wiki-auth-*`): - Manage core user identity - Handle owner/admin concepts - Provide login/logout flows - Store identity in consistent format **Authorization Enhancement Plugins** (`wiki-plugin-*` with `securityEnhancer` export): - Add supplementary authentication methods (API tokens, webhooks) - Implement security policies (rate limiting, permissions) - Provide monitoring/audit capabilities - Work with any authentication plugin ## Technical Implementation ### Architecture Overview ```javascript // Current: Single security handler app.securityhandler = require(argv.security_type)(log, loga, argv) // Proposed: Server-native composable architecture const authHandler = require(argv.auth_provider)(log, loga, argv) const enhancers = loadAuthzEnhancers(argv.authz_enhancers, authHandler) app.securityhandler = composeSecurityHandler(authHandler, enhancers) ``` ### Interface Contracts **Authentication Plugin Interface**: ```javascript { retrieveOwner: (callback) => void, getOwner: () => string, setOwner: (identity, callback) => void, getUser: (request) => object, isAuthorized: (request) => boolean, isAdmin: (request) => boolean, defineRoutes: (app, cors, updateOwner) => void } ``` **Authorization Enhancement Plugin Interface** (exported as `securityEnhancer` function): ```javascript { getUser?: (request) => object, // Optional: detect alternative auth isAuthorized?: (request) => boolean, // Optional: additional auth checks isAdmin?: (request) => boolean, // Optional: additional admin checks defineRoutes?: (app, cors) => void // Optional: add routes } ``` ### Backward Compatibility The current `--security_type` parameter continues to work: ```bash # Old way (still works) wiki --security_type friends # Automatically translates to: wiki --auth_type friends --authz_enhancers "" ``` Existing security plugins can be migrated incrementally without breaking changes. ### Migration Path 1. Extend `wiki-server` to support new parameter format 2. Implement composite security handler (as shown with `wiki-security-composable`) 3. Maintain full backward compatibility ## Current Implementation Status This enhancement has a **working proof-of-concept implementation** in `wiki-security-composable` that demonstrates the architecture without requiring server changes. The implementation uses the existing plugin system where authorization enhancers are regular `wiki-plugin-*` packages that export a `securityEnhancer` function. **Current Working Components:** - `wiki-security-composable` - Foundation plugin that composes authentication with authorization enhancers - `wiki-plugin-useraccesstokens` - Authorization enhancer for API tokens - `wiki-plugin-ratelimit` - Authorization enhancer for request rate limiting Additional authorization enhancers can be created following the established pattern **How It Works Today:** ```bash # Current implementation syntax wiki --security_type composable --auth_provider wiki-security-friends --authz_enhancers wiki-plugin-useraccesstokens ``` The `wiki-security-composable` plugin acts as a wrapper that: 1. Loads the specified authentication provider (e.g., `wiki-security-friends`) 2. Loads authorization enhancer plugins that export `securityEnhancer` functions 3. Composes them using a function composition pattern 4. Presents a unified security interface to the wiki server ### Client asset management challenges with `wiki-security-composable` When using `wiki-security-composable`, the server expects security assets to be in one specific location (e.g.,`wiki-security-composable/client/` when using the `--security_type composable` parameter). However, the actual authentication assets are in the base authentication provider's directory (e.g., `wiki-security-friends/client/`). ```javascript // wiki-server/lib/server.js:284 app.use('/security', express.static(path.join(argv.packageDir, argv.security_type, 'client'), staticPathOptions)) ``` This creates a hardcoded expectation that security assets are at `{packageDir}/{security_type}/client/`, but when using composable security: - `security_type = "wiki-security-composable"` - Actual authentication assets are in `wiki-security-friends/client/` or `wiki-security-passportjs/client/` **Current Workaround**: `wiki-security-composable` must copy client assets from the base authentication provider to its own directory during initialization: ```javascript // Detect base provider assets const baseProviderClientPath = path.join( path.dirname(require.resolve(`${authProvider}/package.json`)), 'client' ); // Copy to our expected location const ourClientPath = path.join(__dirname, '..', 'client'); fs.rmSync(ourClientPath, { recursive: true, force: true }); copyFiles(baseProviderClientPath, ourClientPath); ``` ## Server Enhancement Proposal The server could adopt this composable pattern natively to eliminate these workarounds and provide a cleaner developer and user experience: ### Native Configuration Support Instead of the current workaround syntax, the server could support: ```bash # Proposed native server support wiki --auth_provider wiki-security-friends --authz_enhancers wiki-plugin-useraccesstokens,wiki-plugin-ratelimit ``` With backward compatibility: ```bash # Current syntax continues to work wiki --security_type friends # translates to --auth_provider wiki-security-friends ``` ### Dynamic Asset Resolution **Server Enhancement**: The server could dynamically resolve static routes based on the actual authentication provider: ```javascript // Proposed server enhancement const authProvider = argv.auth_provider; const authAssetPath = path.join(path.dirname(require.resolve(`${authProvider}/package.json`)), 'client'); app.use('/auth', express.static(authAssetPath, staticPathOptions)); // Authorization enhancers continue using existing /plugins/ routes ``` ### Built-in Composition Logic **Server Enhancement**: The server could handle security composition natively: ```javascript // Proposed server enhancement in defaultargs.js if (argv.authz_enhancers) { if (typeof argv.authz_enhancers === 'string') { argv.authz_enhancers = argv.authz_enhancers.split(',').map(name => name.trim()); } } else { argv.authz_enhancers = []; } // Backward compatibility if (argv.security_type && !argv.auth_provider) { argv.auth_provider = argv.security_type; } ``` ```javascript // Proposed server enhancement in server.js const authHandler = require(argv.auth_provider)(log, loga, argv); const authzHandlers = []; if (argv.authz_enhancers) { argv.authz_enhancers.forEach(pluginName => { const plugin = require(pluginName); if (plugin.securityEnhancer && typeof plugin.securityEnhancer === 'function') { const handler = plugin.securityEnhancer(log, loga, argv, authHandler); authzHandlers.push(handler); } }); } // Create composite security handler app.securityhandler = composeSecurityHandler(authHandler, authzHandlers); ``` ### Authorization Enhancer Interface The current plugin interface would remain unchanged: ```javascript // wiki-plugin-useraccesstokens/index.js export const securityEnhancer = (log, loga, argv, authHandler) => { return { getUser: (req, baseGetUser) => { /* enhanced user detection */ }, isAuthorized: (req, baseIsAuthorized) => { /* enhanced authorization */ }, isAdmin: (req, baseIsAdmin) => { /* enhanced admin checks */ }, middleware: (req, res, next) => { /* token validation, etc. */ }, defineRoutes: (app, cors, updateOwner) => { /* additional routes */ } }; }; ```