10 KiB
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:
- Mutually Exclusive Plugins: You can only use one security plugin at a time
- Limited Extensibility: Adding new authentication methods requires replacing the entire security system
- 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:
# 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:
# 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:
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
// 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:
{
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):
{
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:
# 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
- Extend
wiki-serverto support new parameter format - Implement composite security handler (as shown with
wiki-security-composable) - 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 enhancerswiki-plugin-useraccesstokens- Authorization enhancer for API tokenswiki-plugin-ratelimit- Authorization enhancer for request rate limiting
Additional authorization enhancers can be created following the established pattern
How It Works Today:
# 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:
- Loads the specified authentication provider (e.g.,
wiki-security-friends) - Loads authorization enhancer plugins that export
securityEnhancerfunctions - Composes them using a function composition pattern
- 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/).
// 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/orwiki-security-passportjs/client/
Current Workaround: wiki-security-composable must copy client assets from the base authentication provider to its own directory during initialization:
// 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:
# Proposed native server support
wiki --auth_provider wiki-security-friends --authz_enhancers wiki-plugin-useraccesstokens,wiki-plugin-ratelimit
With backward compatibility:
# 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:
// 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:
// 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;
}
// 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:
// 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 */ }
};
};