Skip to content

HiveMatrix Architecture - Core Concepts

Version 4.2 Part 1 of 3: Core Architecture Fundamentals

This document covers the fundamental architecture patterns, authentication flows, and service communication that form the foundation of HiveMatrix. Read this first before exploring development practices or service-specific implementations.

Other Parts: - Architecture - Development - Development practices, security, tools, and patterns - Architecture - Services - Service-specific deep dives (Brainhair, Ledger, KnowledgeTree)


1. Core Philosophy & Goals

This document is the single source of truth for the HiveMatrix architecture. Its primary audience is the AI development assistant responsible for writing and maintaining the platform's code. Adherence to these principles is mandatory.

Our goals are, in order of priority:

  1. AI Maintainability: Each individual application (e.g., Codex, Ledger) must remain small, focused, and simple. We sacrifice some traditional development conveniences to achieve this.
  2. Modularity: The platform is a collection of independent, fully functional applications that can be composed together.
  3. Simplicity & Explicitness: We favor simple, explicit patterns over complex, "magical" ones. Assume code is correct and error out to expose flaws rather than building defensive checks.

2. The Monolithic Service Pattern

Each module in HiveMatrix (e.g., Codex, Ledger, KnowledgeTree) is a self-contained, monolithic application. Each application is a single, deployable unit responsible for its own business logic, database, and UI rendering.

  • Server-Side Rendering: Applications must render their user interfaces on the server side, returning complete HTML documents.
  • Data APIs: Applications may also expose data-only APIs (e.g., /api/tickets) that return JSON.
  • Data Isolation: Each service owns its own database. You are forbidden from accessing another service's database directly.

3. End-to-End Authentication Flow

The platform operates on a centralized login model orchestrated by Core and Nexus. No service handles user credentials directly. All authentication flows through Keycloak, and sessions are managed by Core with revocation support.

Initial Login Flow

  1. Initial Request: A user navigates to https://your-server/ (Nexus on port 443).
  2. Auth Check: Nexus checks the user's session. If no valid session token exists, it stores the target URL and redirects to the login endpoint.
  3. Keycloak Proxy: The user is redirected to /keycloak/realms/hivematrix/protocol/openid-connect/auth. Nexus proxies this to the local Keycloak server (port 8080) with proper X-Forwarded headers.
  4. Keycloak Login: User enters credentials on the Keycloak login page (proxied through Nexus).
  5. OAuth Callback: After successful login, Keycloak redirects to https://your-server/keycloak-callback with an authorization code.
  6. Token Exchange: Nexus receives the callback and:
    • Exchanges the authorization code for Keycloak access token (using backend localhost:8080 connection)
    • Calls Core's /api/token/exchange endpoint with the Keycloak access token
  7. Session Creation: Core receives the Keycloak token and:
    • Validates it with Keycloak's userinfo endpoint
    • Extracts user info and group membership
    • Determines permission level from Keycloak groups
    • Creates a server-side session with a unique session ID
    • Mints a HiveMatrix JWT signed with Core's private RSA key containing:
    • User identity (sub, name, email, preferred_username)
    • Permission level (admin, technician, billing, or client)
    • Group membership
    • jti (JWT ID) - The session ID for revocation tracking
    • Standard JWT claims (iss, iat, exp)
    • 1-hour expiration (exp)
    • Stores session in memory with TTL (Time To Live)
  8. JWT to Nexus: Core returns the HiveMatrix JWT to Nexus.
  9. Session Storage: Nexus stores the JWT in the user's Flask session cookie.
  10. Final Redirect: Nexus redirects the user to their originally requested URL.
  11. Authenticated Access: For subsequent requests:
    • Nexus retrieves the JWT from the session
    • Validates the JWT signature using Core's public key
    • Checks with Core that the session (jti) hasn't been revoked
    • If valid, proxies the request to backend services with Authorization: Bearer <token> header
  12. Backend Verification: Backend services verify the JWT using Core's public key at /.well-known/jwks.json.

Permission Levels

HiveMatrix supports four permission levels, determined by Keycloak group membership:

  • admin: Members of the admins group - full system access
  • technician: Members of the technicians group - technical operations
  • billing: Members of the billing group - financial operations
  • client: Default level for users not in any special group - limited access

Services can access the user's permission level via g.user.get('permission_level') and enforce authorization using the @admin_required decorator or custom permission checks.

Session Management & Logout Flow

HiveMatrix implements revokable sessions with automatic expiration to ensure proper security.

Session Lifecycle

Session Creation: - When a user logs in, Core creates a server-side session with: - Unique session ID (stored as jti in the JWT) - User data (sub, name, email, permission_level, groups) - Creation timestamp (created_at) - Expiration timestamp (expires_at) - 1 hour from creation - Revocation flag (revoked) - initially false

Session Validation: - On each request, Nexus calls Core's /api/token/validate endpoint - Core checks: 1. JWT signature is valid 2. JWT has not expired (exp claim) 3. Session ID (jti) exists in the session store 4. Session has not expired (expires_at) 5. Session has not been revoked (revoked flag) - If any check fails, the session is invalid and the user must re-authenticate

Session Expiration: - Sessions automatically expire after 1 hour - Expired sessions are removed from memory during cleanup - Users must log in again after expiration

Logout Flow

  1. User Clicks Logout: User navigates to /logout endpoint on Nexus
  2. Retrieve Token: Nexus retrieves the JWT from the user's session
  3. Revoke at Core: Nexus calls Core's /api/token/revoke with the JWT:
    POST /api/token/revoke
    {
      "token": "<jwt_token>"
    }
    
  4. Mark as Revoked: Core:
  5. Decodes the JWT to extract session ID (jti)
  6. Marks the session as revoked in the session store
  7. Returns success response
  8. Clear Client State: Nexus:
  9. Clears the server-side Flask session
  10. Returns HTML that clears browser storage and cookies
  11. Redirects to home page
  12. Re-authentication Required: Next request to any protected page:
  13. Nexus has no session → redirects to login
  14. OR if somehow a token is still cached → Core validation fails (session revoked)

Core Session Manager

The SessionManager class in hivematrix-core/app/session_manager.py provides:

class SessionManager:
    def create_session(user_data) -> session_id
    def validate_session(session_id) -> user_data or None
    def revoke_session(session_id) -> bool
    def cleanup_expired() -> count

Production Note: The current implementation uses in-memory storage. For production deployments with multiple Core instances, sessions should be stored in Redis or a database for shared state.

Core API Endpoints

Token Exchange:

POST /api/token/exchange
Body: { "access_token": "<keycloak_access_token>" }
Response: { "token": "<hivematrix_jwt>" }

Token Validation:

POST /api/token/validate
Body: { "token": "<hivematrix_jwt>" }
Response: { "valid": true, "user": {...} } or { "valid": false, "error": "..." }

Token Revocation:

POST /api/token/revoke
Body: { "token": "<hivematrix_jwt>" }
Response: { "message": "Session revoked successfully" }

Public Key (JWKS):

GET /.well-known/jwks.json
Response: { "keys": [{ "kty": "RSA", "kid": "...", ... }] }

4. Service-to-Service Communication

Services may need to call each other's APIs (e.g., Treasury calling Codex to get billing data). This is done using service tokens minted by Core.

Service Token Flow

  1. Request Service Token: The calling service (e.g., Treasury) makes a POST request to Core's /service-token endpoint:

    {
      "calling_service": "treasury",
      "target_service": "codex"
    }
    

  2. Core Mints Token: Core creates a short-lived JWT (5 minutes) with:

    {
      "iss": "hivematrix.core",
      "sub": "service:treasury",
      "calling_service": "treasury",
      "target_service": "codex",
      "type": "service",
      "iat": 1234567890,
      "exp": 1234568190
    }
    

  3. Make Authenticated Request: The calling service uses this token in the Authorization header when calling the target service's API.

  4. Target Service Verification: The target service verifies the token using Core's public key and checks the type field to determine if it's a service call.

Service Client Helper

All services include a service_client.py helper that automates this flow:

from app.service_client import call_service

# Make a service-to-service API call
response = call_service('codex', '/api/companies')
companies = response.json()

The call_service function: - Automatically requests a service token from Core (with 5-minute caching - see Architecture - Services) - Adds the Authorization header - Makes the HTTP request - Returns the response

Performance Note: Service tokens are cached for 4.5 minutes to reduce load on Core service. This caching reduces HTTP calls to Core by ~90% while maintaining security (tokens still expire after 5 minutes). See Service Token Caching for implementation details.

Service Discovery

Services are registered in two configuration files:

master_services.json - Master service registry (simplified format):

{
  "codex": {
    "url": "http://localhost:5010",
    "port": 5010
  },
  "archive": {
    "url": "http://localhost:5012",
    "port": 5012
  }
}

services.json - Full service configuration (extended format):

{
  "codex": {
    "url": "http://localhost:5010",
    "path": "../hivematrix-codex",
    "port": 5010,
    "python_bin": "pyenv/bin/python",
    "run_script": "run.py",
    "visible": true
  },
  "ledger": {
    "url": "http://localhost:5030",
    "path": "../hivematrix-ledger",
    "port": 5030,
    "python_bin": "pyenv/bin/python",
    "run_script": "run.py",
    "visible": true
  }
}

When adding a new service: 1. Add entry to master_services.json (required for Nexus service discovery) 2. Add extended entry to services.json (required for Helm service management) 3. Both files must be updated for the service to be properly discovered and started

Authentication Decorator Behavior

The @token_required decorator in each service handles both user and service tokens:

@token_required
def api_endpoint():
    if g.is_service_call:
        # Service-to-service call
        calling_service = g.service
        # Service calls bypass user-level permission checks
    else:
        # User call
        user = g.user
        # Apply user permission checks as needed

Service calls automatically bypass user-level permission requirements, as they represent trusted inter-service communication.

5. Frontend: The Smart Proxy Composition Model

The user interface is a composition of the independent applications, assembled by the Nexus proxy.

The Golden Rule of Styling

Applications are forbidden from containing their own styling. All visual presentation (CSS) is handled exclusively by Nexus injecting a global stylesheet. Applications must use the BEM classes defined in this document.

The Nexus Service

Nexus acts as the central gateway. Its responsibilities are: * Enforcing authentication for all routes. * Proxying requests to the appropriate backend service based on the URL path. * Injecting the global global.css stylesheet into any HTML responses. * Discovering backend services via the services.json file.

File: hivematrix-nexus/services.json

{
  "codex": {
    "url": "http://localhost:5010"
  },
  "ledger": {
    "url": "http://localhost:5030"
  }
}

URL Prefix Handling with ProxyFix

When services are accessed through the Nexus proxy, they need to know their URL prefix to generate correct URLs. This is handled via X-Forwarded headers and werkzeug's ProxyFix middleware.

How Nexus Proxies Requests

  1. User requests: https://192.168.1.233/knowledgetree/browse/
  2. Nexus strips the service prefix before forwarding
  3. Nexus adds X-Forwarded headers including X-Forwarded-Prefix: /knowledgetree
  4. Backend service receives: /browse/ with headers indicating the prefix
  5. Backend's ProxyFix middleware sets SCRIPT_NAME from X-Forwarded-Prefix
  6. Flask's url_for() generates correct URLs: /knowledgetree/browse/

Nexus Configuration

Nexus automatically adds X-Forwarded headers when proxying to backend services:

# In hivematrix-nexus/app/routes.py
headers['Authorization'] = f"Bearer {token}"
headers['X-Forwarded-For'] = request.remote_addr
headers['X-Forwarded-Proto'] = 'https' if request.is_secure else 'http'
headers['X-Forwarded-Host'] = request.host
headers['X-Forwarded-Prefix'] = f'/{service_name}'  # e.g., /knowledgetree

Backend Service Configuration

Each service must use werkzeug's ProxyFix middleware to respect these headers:

# In app/__init__.py
from werkzeug.middleware.proxy_fix import ProxyFix

app.wsgi_app = ProxyFix(
    app.wsgi_app,
    x_for=1,      # Trust X-Forwarded-For
    x_proto=1,    # Trust X-Forwarded-Proto (http/https)
    x_host=1,     # Trust X-Forwarded-Host
    x_prefix=1    # Trust X-Forwarded-Prefix (sets SCRIPT_NAME)
)

Important: Do NOT use custom PrefixMiddleware. Nexus already strips the prefix before forwarding, so the backend receives clean paths without the service name. The ProxyFix middleware only affects URL generation, not route matching.

Authentication for AJAX Requests

When making AJAX requests from frontend JavaScript, you must include credentials: 'same-origin' in fetch options:

fetch('/api/search?query=test', {
    credentials: 'same-origin'
})

This ensures the access_token cookie is sent with the request. The @token_required decorator checks both: 1. Authorization: Bearer <token> header (from Nexus proxy) 2. access_token cookie (from browser, as fallback)

# In app/auth.py - token_required decorator
auth_header = request.headers.get('Authorization')
token = None

if auth_header and auth_header.startswith('Bearer '):
    token = auth_header.split(' ')[1]
else:
    # Fall back to cookie
    token = request.cookies.get('access_token')

6. Configuration Management & Auto-Installation

HiveMatrix uses a centralized configuration system managed by hivematrix-helm. All service configurations are generated and synchronized from Helm's master configuration.

Configuration Manager (config_manager.py)

The ConfigManager class in hivematrix-helm/config_manager.py is responsible for:

  • Master Configuration Storage: Maintains instance/configs/master_config.json with system-wide settings
  • Per-App Configuration Generation: Generates .flaskenv and instance/[app].conf files for each service
  • Centralized Settings: Ensures consistent Keycloak URLs, hostnames, and service URLs across all apps

Master Configuration Structure

{
  "system": {
    "hostname": "localhost",
    "environment": "development",
    "secret_key": "<generated>",
    "log_level": "INFO"
  },
  "keycloak": {
    "url": "http://localhost:8080",
    "realm": "hivematrix",
    "client_id": "core-client",
    "client_secret": "<generated>",
    "admin_username": "admin",
    "admin_password": "admin"
  },
  "databases": {
    "postgresql": {
      "host": "localhost",
      "port": 5432,
      "admin_user": "postgres"
    },
    "neo4j": {
      "uri": "bolt://localhost:7687",
      "user": "neo4j",
      "password": "password"
    }
  },
  "apps": {
    "codex": {
      "port": 5010,
      "database": "postgresql",
      "db_name": "codex_db",
      "db_user": "codex_user"
    },
    "ledger": {
      "port": 5030,
      "database": "postgresql",
      "db_name": "ledger_db",
      "db_user": "ledger_user"
    }
  }
}

.flaskenv Generation

The generate_app_dotenv(app_name) method creates .flaskenv files with:

  • Flask Configuration: FLASK_APP, FLASK_ENV, SECRET_KEY, SERVICE_NAME
  • Keycloak Configuration: Automatically adjusts URLs based on hostname (localhost vs production)
  • For core: Direct Keycloak connection (http://localhost:8080/realms/hivematrix)
  • For other services: Proxied URL (https://hostname/keycloak or http://localhost:8080)
  • Service URLs: CORE_SERVICE_URL, NEXUS_SERVICE_URL
  • Database Configuration: DB_HOST, DB_PORT, DB_NAME (if database is configured)
  • JWT Configuration: For Core service only - JWT_PRIVATE_KEY_FILE, JWT_PUBLIC_KEY_FILE, etc.

Example generated .flaskenv:

FLASK_APP=run.py
FLASK_ENV=development
SECRET_KEY=abc123...
SERVICE_NAME=codex

# Keycloak Configuration
KEYCLOAK_SERVER_URL=http://localhost:8080
KEYCLOAK_BACKEND_URL=http://localhost:8080
KEYCLOAK_REALM=hivematrix
KEYCLOAK_CLIENT_ID=core-client

# Service URLs
CORE_SERVICE_URL=http://localhost:5000
NEXUS_SERVICE_URL=http://localhost:8000

instance/app.conf Generation

The generate_app_conf(app_name) method creates ConfigParser-formatted files with:

  • Database Section: PostgreSQL connection string with credentials
  • App-Specific Sections: Custom configuration sections defined in master config

Example generated instance/codex.conf:

[database]
connection_string = postgresql://codex_user:password@localhost:5432/codex_db
db_host = localhost
db_port = 5432
db_name = codex_db
db_user = codex_user

Configuration Sync

To update all installed apps with current configuration:

cd hivematrix-helm
source pyenv/bin/activate
python config_manager.py sync-all

This is automatically called by start.sh on each startup to ensure configurations are current.

Auto-Installation Architecture

HiveMatrix uses a registry-based installation system that allows services to be installed through the Helm web interface.

App Registry (apps_registry.json)

All installable apps are defined in hivematrix-helm/apps_registry.json. This file is the authoritative source for all HiveMatrix services and is used by install_manager.py to automatically generate both services.json and master_services.json.

{
  "core_apps": {
    "core": {
      "name": "HiveMatrix Core",
      "description": "Authentication & service registry - Required",
      "git_url": "https://github.com/skelhammer/hivematrix-core",
      "port": 5000,
      "required": true,
      "dependencies": ["postgresql"],
      "install_order": 1
    },
    "nexus": {
      "name": "HiveMatrix Nexus",
      "description": "Frontend gateway and UI - Required",
      "git_url": "https://github.com/skelhammer/hivematrix-nexus",
      "port": 443,
      "required": true,
      "dependencies": ["core", "keycloak"],
      "install_order": 2
    }
  },
  "default_apps": {
    "codex": {
      "name": "HiveMatrix Codex",
      "description": "Central data hub for MSP operations",
      "git_url": "https://github.com/skelhammer/hivematrix-codex",
      "port": 5010,
      "required": false,
      "dependencies": ["postgresql", "core"],
      "install_order": 3
    },
    "ledger": {
      "name": "HiveMatrix Ledger",
      "description": "Billing calculations and invoicing (includes archive functionality)",
      "git_url": "https://github.com/skelhammer/hivematrix-ledger",
      "port": 5030,
      "required": false,
      "dependencies": ["postgresql", "core", "codex"],
      "install_order": 4
    }
  },
  "system_dependencies": {
    "keycloak": {
      "name": "Keycloak",
      "description": "Authentication server",
      "version": "26.4.0",
      "download_url": "https://github.com/keycloak/keycloak/releases/download/26.4.0/keycloak-26.4.0.tar.gz",
      "port": 8080,
      "required": true,
      "install_order": 0
    },
    "postgresql": {
      "name": "PostgreSQL",
      "description": "Relational database",
      "apt_package": "postgresql postgresql-contrib",
      "required": true
    }
  }
}

Installation Manager (install_manager.py)

The InstallManager class handles:

  1. Cloning Apps: Downloads from git repository
  2. Running Install Scripts: Executes install.sh if present
  3. Dynamic Service Discovery: Automatically scans for ALL hivematrix-* directories with run.py files
  4. Service Registry Generation: Automatically generates both master_services.json and services.json
  5. Checking Status: Monitors git status and available updates

Key Feature - Dynamic Service Discovery:

The scan_all_services() method automatically discovers all HiveMatrix services in the parent directory, not just those in apps_registry.json. This makes the system much more flexible:

  • Registry Services: For services defined in apps_registry.json, uses registry metadata (port, name, description)
  • Unknown Services: For services not in registry (e.g., manually copied or old versions), auto-generates configuration with smart port assignment
  • No Manual Configuration: After git pull or copying a service, it's automatically detected on next startup

How it works:

def scan_all_services(self):
    """Scan parent directory for all hivematrix-* services with run.py"""
    discovered = {}

    # Scan for all hivematrix-* directories
    for item in self.parent_dir.iterdir():
        if item.name.startswith('hivematrix-') and (item / 'run.py').exists():
            service_name = item.name.replace('hivematrix-', '')

            # Use registry info if available
            app_info = self.registry.get(service_name)
            if app_info:
                discovered[service_name] = app_info
            else:
                # Auto-generate config for unknown services
                discovered[service_name] = {
                    'name': f'HiveMatrix {service_name.title()}',
                    'port': 5000 + (hash(service_name) % 900),
                    'required': False
                }
    return discovered

Benefits: - Copy old service versions → automatically detected - Git pull new services → automatically registered - No manual services.json edits required - Works with any hivematrix-* service that has run.py

The update-config command reads both the registry AND scans the filesystem to generate service configuration files:

cd hivematrix-helm
source pyenv/bin/activate

# Install a new service
python install_manager.py install ledger

# Regenerate service configurations from apps_registry.json
python install_manager.py update-config

When to use update-config: - After adding a new service to apps_registry.json - After modifying service properties in apps_registry.json - When service configuration files get out of sync - This is automatically called by start.sh on startup

Or via Helm web interface.

Required Files for Auto-Installation

For a service to be installable via Helm, it must have:

1. install.sh - Installation script that: - Creates Python virtual environment (python3 -m venv pyenv) - Installs dependencies (pip install -r requirements.txt) - Creates instance/ directory - Creates initial .flaskenv (will be overwritten by config_manager) - Symlinks services.json from Helm directory - Runs any app-specific setup (database creation, etc.)

2. requirements.txt - Python dependencies:

Flask==3.0.0
python-dotenv==1.0.0
PyJWT==2.8.0
cryptography==41.0.7
SQLAlchemy==2.0.23
psycopg2-binary==2.9.9

3. run.py - Application entry point:

from app import app

if __name__ == '__main__':
    app.run(debug=True, port=5040, host='0.0.0.0')

4. app/__init__.py - Flask app initialization (see Step 1 below)

5. services.json symlink - Created by install.sh, points to ../hivematrix-helm/services.json

Template install.sh Structure

#!/bin/bash
set -e  # Exit on error

APP_NAME="myservice"  # Replace with your service name
APP_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
PARENT_DIR="$(dirname "$APP_DIR")"
HELM_DIR="$PARENT_DIR/hivematrix-helm"

# Create virtual environment
python3 -m venv pyenv
source pyenv/bin/activate

# Upgrade pip and install dependencies
pip install --upgrade pip
pip install -r requirements.txt

# Create instance directory
mkdir -p instance

# Create initial .flaskenv (will be regenerated by config_manager)
cat > .flaskenv <<EOF
FLASK_APP=run.py
FLASK_ENV=development
SERVICE_NAME=myservice
CORE_SERVICE_URL=http://localhost:5000
HELM_SERVICE_URL=http://localhost:5004
EOF

# Symlink services.json from Helm
if [ -d "$HELM_DIR" ] && [ -f "$HELM_DIR/services.json" ]; then
    ln -sf "$HELM_DIR/services.json" services.json
fi

Updating Other Services

To make existing services installable via Helm:

  1. Add to apps_registry.json: Define the service with git URL, port, and dependencies
  2. Create install.sh: Follow the template structure above
  3. Test Installation: Run python install_manager.py install <service>
  4. Update Config: Ensure config_manager can generate proper .flaskenv and .conf files

Current Status: Template is the only fully working installable service. Codex has an install.sh but may need updates. Other services need install scripts created.

7. AI Instructions for Building a New Service

All new services (e.g., Codex, Architect) must be created by copying the hivematrix-template project. This ensures all necessary patterns are included.

Step 1: Configuration

Every service requires an app/__init__.py that loads its configuration from environment variables (via .flaskenv) and config files (via instance/[service].conf).

Important: The .flaskenv file is automatically generated by config_manager.py from Helm's master configuration. You should not manually edit .flaskenv files, as they will be overwritten on the next config sync.

File: [new-service]/app/__init__.py (Example)

from flask import Flask
import json
import os

app = Flask(__name__, instance_relative_config=True)

# --- Load all required configuration from environment variables ---
# These are set in .flaskenv, which is generated by config_manager.py
app.config['CORE_SERVICE_URL'] = os.environ.get('CORE_SERVICE_URL')
app.config['SERVICE_NAME'] = os.environ.get('SERVICE_NAME', 'myservice')

if not app.config['CORE_SERVICE_URL']:
    raise ValueError("CORE_SERVICE_URL must be set in the .flaskenv file.")

# Load database connection from config file
# This file is generated by config_manager.py
import configparser
try:
    os.makedirs(app.instance_path)
except OSError:
    pass

config_path = os.path.join(app.instance_path, 'myservice.conf')
config = configparser.RawConfigParser()  # Use RawConfigParser for special chars
config.read(config_path)
app.config['MYSERVICE_CONFIG'] = config

# Database configuration
app.config['SQLALCHEMY_DATABASE_URI'] = config.get('database', 'connection_string',
    fallback=f"sqlite:///{os.path.join(app.instance_path, 'myservice.db')}")
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# Load services configuration for service-to-service calls
# This is symlinked from hivematrix-helm/services.json
try:
    with open('services.json') as f:
        services_config = json.load(f)
        app.config['SERVICES'] = services_config
except FileNotFoundError:
    print("WARNING: services.json not found. Service-to-service calls will not work.")
    app.config['SERVICES'] = {}

from extensions import db
db.init_app(app)

# Apply ProxyFix to handle X-Forwarded headers from Nexus proxy
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(
    app.wsgi_app,
    x_for=1,      # Trust X-Forwarded-For
    x_proto=1,    # Trust X-Forwarded-Proto
    x_host=1,     # Trust X-Forwarded-Host
    x_prefix=1    # Trust X-Forwarded-Prefix (sets SCRIPT_NAME for url_for)
)

from app import routes

Configuration Files (Generated by Helm)

The following files are automatically generated by config_manager.py:

.flaskenv - Generated by config_manager.py generate_app_dotenv(app_name)

FLASK_APP=run.py
FLASK_ENV=development
SECRET_KEY=<auto-generated>
SERVICE_NAME=myservice

# Keycloak Configuration (auto-adjusted for environment)
KEYCLOAK_SERVER_URL=http://localhost:8080
KEYCLOAK_BACKEND_URL=http://localhost:8080
KEYCLOAK_REALM=hivematrix
KEYCLOAK_CLIENT_ID=core-client

# Service URLs
CORE_SERVICE_URL=http://localhost:5000
NEXUS_SERVICE_URL=http://localhost:8000

instance/myservice.conf - Generated by config_manager.py generate_app_conf(app_name)

[database]
connection_string = postgresql://myservice_user:password@localhost:5432/myservice_db
db_host = localhost
db_port = 5432
db_name = myservice_db
db_user = myservice_user

To regenerate these files after updating Helm's master config:

cd hivematrix-helm
source pyenv/bin/activate
python config_manager.py write-dotenv myservice
python config_manager.py write-conf myservice
# Or sync all apps at once:
python config_manager.py sync-all

Step 2: Securing Routes

All routes that display user data or perform actions must be protected by the @token_required decorator. This decorator handles JWT verification for both user and service tokens.

File: [new-service]/app/auth.py (Do not modify - copy from template)

from functools import wraps
from flask import request, g, current_app, abort
import jwt

jwks_client = None

def init_jwks_client():
    """Initializes the JWKS client from the URL in config."""
    global jwks_client
    core_url = current_app.config.get('CORE_SERVICE_URL')
    if core_url:
        jwks_client = jwt.PyJWKClient(f"{core_url}/.well-known/jwks.json")

def token_required(f):
    """
    A decorator to protect routes, ensuring a valid JWT is present.
    This now accepts both user tokens and service tokens.
    """
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if jwks_client is None:
            init_jwks_client()

        auth_header = request.headers.get('Authorization')
        if not auth_header or not auth_header.startswith('Bearer '):
            abort(401, description="Authorization header is missing or invalid.")

        token = auth_header.split(' ')[1]

        try:
            signing_key = jwks_client.get_signing_key_from_jwt(token)
            data = jwt.decode(
                token,
                signing_key.key,
                algorithms=["RS256"],
                issuer="hivematrix.core",
                options={"verify_exp": True}
            )

            # Determine if this is a user token or service token
            if data.get('type') == 'service':
                # Service-to-service call
                g.user = None
                g.service = data.get('calling_service')
                g.is_service_call = True
            else:
                # User call
                g.user = data
                g.service = None
                g.is_service_call = False

        except jwt.PyJWTError as e:
            abort(401, description=f"Invalid Token: {e}")

        return f(*args, **kwargs)
    return decorated_function

def admin_required(f):
    """Decorator to require admin permission level."""
    @wraps(f)
    @token_required
    def decorated_function(*args, **kwargs):
        if g.is_service_call:
            # Services can access admin routes
            return f(*args, **kwargs)

        if not g.user or g.user.get('permission_level') != 'admin':
            abort(403, description="Admin access required.")

        return f(*args, **kwargs)
    return decorated_function

File: [new-service]/app/routes.py (Example)

from flask import render_template, g, jsonify
from app import app
from .auth import token_required, admin_required

@app.route('/')
@token_required
def index():
    # Prevent service calls from accessing UI routes
    if g.is_service_call:
        return {'error': 'This endpoint is for users only'}, 403

    # The user's information is available in the 'g.user' object
    user = g.user
    return render_template('index.html', user=user)

@app.route('/api/data')
@token_required
def api_data():
    # This endpoint works for both users and services
    if g.is_service_call:
        # Service-to-service call from g.service
        return jsonify({'data': 'service response'})
    else:
        # User call - can check permissions
        if g.user.get('permission_level') != 'admin':
            return {'error': 'Admin only'}, 403
        return jsonify({'data': 'user response'})

@app.route('/admin/settings')
@admin_required
def admin_settings():
    # Only admins can access this
    return render_template('admin/settings.html', user=g.user)

Step 3: Building the UI Template

HTML templates must be unstyled and use the BEM classes from the design system. User data from the JWT is passed into the template.

File: [new-service]/app/templates/index.html (Example)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My New Service</title>
</head>
<body>
    <div class="card">
        <div class="card__header">
            <h1 class="card__title">Hello, {{ user.name }}!</h1>
        </div>
        <div class="card__body">
            <p>Your username is: <strong>{{ user.preferred_username }}</strong></p>
            <p>Permission level: <strong>{{ user.permission_level }}</strong></p>
            <button class="btn btn--primary">
                <span class="btn__label">Primary Action</span>
            </button>
        </div>
    </div>
</body>
</html>

Step 4: Database Initialization

Create an init_db.py script to interactively set up the database:

import os
import sys
import configparser
from getpass import getpass
from sqlalchemy import create_engine
from dotenv import load_dotenv

load_dotenv('.flaskenv')
sys.path.append(os.path.dirname(os.path.abspath(__file__)))

from app import app
from extensions import db
from models import YourModel1, YourModel2  # Import your models

def get_db_credentials(config):
    """Prompts the user for PostgreSQL connection details."""
    print("\n--- PostgreSQL Database Configuration ---")

    # Load existing or use defaults
    db_details = {
        'host': config.get('database_credentials', 'db_host', fallback='localhost'),
        'port': config.get('database_credentials', 'db_port', fallback='5432'),
        'user': config.get('database_credentials', 'db_user', fallback='myservice_user'),
        'dbname': config.get('database_credentials', 'db_dbname', fallback='myservice_db')
    }

    host = input(f"Host [{db_details['host']}]: ") or db_details['host']
    port = input(f"Port [{db_details['port']}]: ") or db_details['port']
    dbname = input(f"Database Name [{db_details['dbname']}]: ") or db_details['dbname']
    user = input(f"User [{db_details['user']}]: ") or db_details['user']
    password = getpass("Password: ")

    return {'host': host, 'port': port, 'dbname': dbname, 'user': user, 'password': password}

def test_db_connection(creds):
    """Tests the database connection."""
    from urllib.parse import quote_plus

    escaped_password = quote_plus(creds['password'])
    conn_string = f"postgresql://{creds['user']}:{escaped_password}@{creds['host']}:{creds['port']}/{creds['dbname']}"

    try:
        engine = create_engine(conn_string)
        with engine.connect() as connection:
            print("\n✓ Database connection successful!")
            return conn_string, True
    except Exception as e:
        print(f"\n✗ Connection failed: {e}", file=sys.stderr)
        return None, False

def init_db():
    """Interactively configures and initializes the database."""
    instance_path = app.instance_path
    config_path = os.path.join(instance_path, 'myservice.conf')

    config = configparser.RawConfigParser()

    if os.path.exists(config_path):
        config.read(config_path)
        print(f"\n✓ Existing configuration found: {config_path}")
    else:
        print(f"\n→ Creating new config: {config_path}")
        os.makedirs(instance_path, exist_ok=True)

    # Database configuration
    while True:
        creds = get_db_credentials(config)
        conn_string, success = test_db_connection(creds)
        if success:
            if not config.has_section('database'):
                config.add_section('database')
            config.set('database', 'connection_string', conn_string)

            if not config.has_section('database_credentials'):
                config.add_section('database_credentials')
            for key, val in creds.items():
                if key != 'password':
                    config.set('database_credentials', f'db_{key}', val)
            break
        else:
            if input("\nRetry? (y/n): ").lower() != 'y':
                sys.exit("Database configuration aborted.")

    # Save configuration
    with open(config_path, 'w') as configfile:
        config.write(configfile)
    print(f"\n✓ Configuration saved to: {config_path}")

    # Initialize database schema
    with app.app_context():
        print("\nInitializing database schema...")
        db.create_all()
        print("✓ Database schema initialized successfully!")

if __name__ == '__main__':
    init_db()

Continue to: - Architecture - Development - Running environment, debugging, security, design system - Architecture - Services - Brainhair, Ledger, KnowledgeTree, production features