288 lines
10 KiB
Markdown
288 lines
10 KiB
Markdown
# 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 */ }
|
|
};
|
|
};
|
|
```
|