Files
wiki-security-composable/Server Changes Proposal.md
2025-08-02 02:48:51 -05:00

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:

  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:

# 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

  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:

# 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/).

// 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:

// 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 */ }
  };
};