Skip to content

Architecture and Design

Eric Fitzgerald edited this page Apr 8, 2026 · 16 revisions

Architecture and Design

This page describes the system architecture, component design, data models, and authentication flows for the TMI (Threat Modeling Improved) system.

System Components

TMI consists of two main applications:

Component Technology Repository Description
tmi Go / Gin ericfitz/tmi Backend API server providing RESTful endpoints, WebSocket collaboration, OAuth authentication, and multi-database (PostgreSQL, MySQL, SQLite, SQL Server, Oracle) / Redis data storage
tmi-ux TypeScript / Angular ericfitz/tmi-ux Frontend web application providing the user interface, DFD diagram editor (AntV/X6), and client-side session management

Throughout this page, sections are labeled to indicate which component they apply to:

  • [Backend: tmi] - Go server-side implementation
  • [Frontend: tmi-ux] - Angular client-side implementation
  • [Both] - Applies to both components or describes their interaction

Table of Contents

System Overview

High-Level Architecture [Both]

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│    tmi-ux       │◄──►│   Load Balancer  │◄──►│      tmi        │
│ (Angular/TS)    │    │   (Nginx/HAProxy)│    │    (Go/Gin)     │
└─────────────────┘    └──────────────────┘    └─────────────────┘
                                                        │
                                                        ▼
┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│ OAuth/SAML IdPs │◄──►│  Authentication  │◄──►│    RDBMS        │
│  (Configurable) │    │   & JWT Layer    │    │   Primary DB    │
└─────────────────┘    └──────────────────┘    └─────────────────┘
                                                        │
                                                        ▼
┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│  WebSocket Hub  │◄──►│   Redis Cache    │◄──►│   Monitoring    │
│ (Collaboration) │    │  (Sessions/RT)   │    │   & Logging     │
└─────────────────┘    └──────────────────┘    └─────────────────┘

Core Components

tmi Server [Backend: tmi]

The Go backend API server provides:

  • HTTP API: RESTful endpoints for CRUD operations
  • WebSocket Hub: Real-time collaboration coordination
  • Authentication: OAuth integration with JWT tokens
  • Authorization: Role-based access control (RBAC)
  • Business Logic: Threat modeling and diagram management

tmi-ux Application [Frontend: tmi-ux]

The Angular web application provides:

  • User Interface: Responsive single-page application
  • DFD Editor: AntV/X6-based diagram editor with collaborative editing
  • Session Management: Client-side token handling and refresh
  • Offline Capabilities: Local state management and auto-save

Data Layer [Backend: tmi]

  • RDBMS: Primary data storage for persistent entities (PostgreSQL, MySQL, SQLite, SQL Server, or Oracle)
  • Redis: Session storage and real-time coordination
  • File Storage: Static assets and uploaded content

External Integrations [Both]

  • OAuth Providers: Dynamically configured via config, environment, or database (e.g., Google, GitHub, Microsoft)
  • SAML Providers: Enterprise SSO integration with configurable SAML providers
  • Monitoring Stack: Observability and alerting systems

Architecture Patterns

1. Domain-Driven Design (DDD) [Backend: tmi]

The tmi backend follows DDD principles to organize business logic:

Entities: Core business objects with unique identity

  • ThreatModel - Container for security analysis
  • Diagram - Data flow diagrams
  • Threat - Identified security risks
  • User - Authenticated user accounts

Value Objects: Immutable data containers

  • Authorization - Access control entry (principal_type, provider_id, provider, role)
  • Metadata - Key-value metadata pair (entity_type, entity_id, key, value)
  • Cell - Diagram cell data (id, shape, data) --- stored as JSON within Diagram

Aggregates: Consistency boundaries

  • ThreatModel aggregate contains Diagrams, Threats, Assets, Documents, Notes, Repositories
  • Diagram aggregate contains Cells (stored as JSON array within the Diagram entity)

Stores: Data access abstraction (interface + GORM implementation pattern)

  • ThreatModelStoreInterface / GormThreatModelStore - Threat model persistence
  • DiagramStoreInterface / GormDiagramStore - Diagram persistence
  • UserStore / GormUserStore - User account management
  • Additional stores for Assets, Documents, Notes, Repositories, Threats, Metadata, etc.

Services: Business logic coordination

  • auth.Service - OAuth and JWT management (auth/service.go)
  • Authorization middleware and utilities (api/middleware.go, api/auth_utils.go)
  • WebSocketHub - WebSocket collaboration coordination (api/websocket.go)

2. Hexagonal Architecture (Ports & Adapters) [Backend: tmi]

Core Domain: Business logic independent of external concerns

  • Located in /api and /internal packages
  • No dependencies on frameworks or external systems

Ports: Interfaces for external communication

  • Provider interface for OAuth providers (auth/provider.go)
  • ThreatModelStoreInterface, DiagramStoreInterface, etc. for persistence (api/store.go)
  • ProviderRegistry interface for provider configuration (auth/provider_registry.go)

Adapters: Implementations of external integrations

  • Dynamically configured OAuth providers via ProviderRegistry (config, environment, or database)
  • GormThreatModelStore, GormDiagramStore, etc. - GORM-based persistence (PostgreSQL, MySQL, SQLite, SQL Server, Oracle)
  • Redis-backed ticket store, state store, and cache

Dependency Inversion: The core depends on abstractions, not concrete implementations.

3. Event-Driven Architecture [Both]

Command-Query Separation: Clear separation between reads and writes.

  • Commands: POST, PUT, PATCH, DELETE operations
  • Queries: GET operations

Domain Events: Business event notifications.

  • ThreatModelCreated
  • DiagramUpdated
  • UserJoinedCollaboration

Real-time Events: WebSocket-based real-time updates.

  • diagram_operation - Diagram changes
  • presenter_cursor - Cursor position in presenter mode
  • join/leave - Collaboration session events

4. Architecture Validation [Frontend: tmi-ux]

The tmi-ux frontend uses automated tooling to enforce architecture boundaries and validate design decisions.

ESLint Architecture Rules

The tmi-ux project uses ESLint rules (defined in eslint.config.js) to enforce architecture boundaries automatically.

Core Layer Isolation (src/app/core/**):

  • Core services must not import from feature modules (pages/*, auth/services/*).
  • Use interfaces in core/interfaces/ for cross-layer communication instead.

Domain Layer Purity (src/app/**/domain/**):

  • Domain objects must not depend on Angular (@angular/core, @angular/common).
  • Domain objects must not depend on RxJS (rxjs, rxjs/*).
  • Domain objects must not depend on infrastructure, application, or service layers.
  • Domain objects must not depend on third-party framework libraries (@antv/*, @jsverse/*).
  • Keep domain objects as pure TypeScript classes with business logic only.

Import Restrictions:

  • Do not import from *.module files (use standalone components instead).
  • Do not import the entire @angular/material library (import specific modules).
  • Prefer @app/shared/imports for common imports.

Service Provisioning:

  • Root services use providedIn: 'root'.
  • Feature services are provided in the component providers array.

Running Architecture Validation

# Run all linting including architecture rules
pnpm run lint:all

# Run only TypeScript/architecture linting
pnpm run lint

# Fix auto-fixable issues
pnpm run lint:all --fix

Circular Dependency Detection

Use madge to detect and visualize circular dependencies:

# Install madge globally
npm install -g madge

# Check for circular dependencies
madge --circular src/

# Generate visual dependency graph
madge --image graph.svg src/

Layer Boundary Verification

Layer Allowed Dependencies
Core Must not depend on features
Features May depend on core
Domain No external dependencies (pure TypeScript)
Infrastructure May depend on domain and core

Architecture Metrics

Track these metrics to monitor architecture health:

Metric Target
Circular Dependencies 0
Core to Feature Imports 0
Domain to Framework Imports 0
Domain Services with Angular/RxJS 0
NgModule Files (except third-party) 0

For complete validation procedures and troubleshooting, see the TMI-UX source repository at docs/migrated/reference/architecture/validation.md.

Component Architecture

Server Components [Backend: tmi]

1. API Layer (/api)

Handler Pattern:

// api/threat_model_handlers.go
type ThreatModelHandler struct {
    wsHub *WebSocketHub
}

func (h *ThreatModelHandler) CreateThreatModel(c *gin.Context) {
    // 1. Parse request
    // 2. Validate input
    // 3. Apply business logic
    // 4. Persist to database
    // 5. Return response
}

Key Handlers:

  • threat_model_handlers.go - Threat model CRUD
  • threat_model_diagram_handlers.go - Diagram operations within threat models
  • websocket_handlers.go, websocket_session_handlers.go, websocket_presenter_handlers.go - WebSocket collaboration management
  • Authentication handlers are in /auth/handlers.go, /auth/handlers_oauth.go, /auth/handlers_saml.go, etc.

2. Authentication Layer (/auth)

OAuth Flow:

// auth/provider.go
type Provider interface {
    GetOAuth2Config() *oauth2.Config
    GetAuthorizationURL(state string) string
    ExchangeCode(ctx context.Context, code string) (*TokenResponse, error)
    GetUserInfo(ctx context.Context, accessToken string) (*UserInfo, error)
    ValidateIDToken(ctx context.Context, idToken string) (*IDTokenClaims, error)
}

Providers (dynamically configured via config, environment, or database):

  • OAuth providers (e.g., Google, GitHub, Microsoft) registered via ProviderRegistry
  • SAML providers for enterprise SSO
  • Test provider (development only)

JWT Management:

// auth/service.go
type Claims struct {
    Email              string   `json:"email"`
    EmailVerified      bool     `json:"email_verified,omitempty"`
    Name               string   `json:"name"`
    IdentityProvider   string   `json:"idp,omitempty"`
    Groups             []string `json:"groups,omitempty"`
    IsAdministrator    *bool    `json:"tmi_is_administrator,omitempty"`
    IsSecurityReviewer *bool    `json:"tmi_is_security_reviewer,omitempty"`
    jwt.RegisteredClaims
}

3. Database Layer

Store Pattern:

// Interface-based store abstraction (api/store.go)
type ThreatModelStoreInterface interface {
    Get(id string) (ThreatModel, error)
    GetIncludingDeleted(id string) (ThreatModel, error)
    GetAuthorization(id string) ([]Authorization, User, error)
    List(offset, limit int, filter func(ThreatModel) bool) []ThreatModel
    ListWithCounts(offset, limit int, filter func(ThreatModel) bool, filters *ThreatModelFilters) ([]TMListItem, int)
    Create(item ThreatModel, idSetter func(ThreatModel, string) ThreatModel) (ThreatModel, error)
    Update(id string, item ThreatModel) error
    Delete(id string) error
}

// Global store instances
var ThreatModelStore ThreatModelStoreInterface
var DiagramStore DiagramStoreInterface
// ... additional stores for Assets, Documents, Notes, Repositories, Threats, Metadata, etc.

// GORM-backed implementation (api/database_store_gorm.go)
type GormThreatModelStore struct {
    db    *gorm.DB
    mutex sync.RWMutex
}

Benefits:

  • Interface-based abstraction allows swapping implementations.
  • GORM-backed stores support PostgreSQL, MySQL, SQLite, SQL Server, and Oracle.
  • Mutexes provide concurrency protection.
  • Each entity type has a clearly separated store.

Web Application Components [Frontend: tmi-ux]

1. Core Services

API Service (core/services/api.service.ts):

@Injectable({providedIn: 'root'})
export class ApiService {
  private apiUrl = environment.apiUrl;

  getThreatModels(): Observable<ThreatModel[]> {
    return this.http.get<ThreatModel[]>(`${this.apiUrl}/threat_models`);
  }
}

Logger Service (core/services/logger.service.ts):

export class LoggerService {
  private logLevel: LogLevel;

  debug(message: string, ...args: any[]): void {
    if (this.shouldLog(LogLevel.DEBUG)) {
      console.log(`[DEBUG] ${this.timestamp()} ${message}`, ...args);
    }
  }
}

Authentication Service (core/services/auth.service.ts):

  • OAuth flow management
  • JWT token storage
  • Session management
  • User profile retrieval

2. Page Components

Threat Model Page (pages/tm/):

  • List view of threat models
  • Create/edit threat models
  • Access control management

Diagram Editor (pages/dfd/):

  • AntV/X6-based diagram editor
  • WebSocket-based collaboration
  • Real-time synchronization

The DFD editor follows a layered architecture with clear separation of concerns.

State Management Layer (pages/dfd/application/services/):

  • AppStateService - Orchestrates diagram state, including sync status, remote operations, and conflict resolution.
  • AppWebSocketEventProcessor - Processes incoming WebSocket events and emits typed application-level events.
  • AppOperationStateManager - Manages operation state flags and drag tracking for history coordination.

Key Patterns:

  • Event-based Communication: Services communicate through Observable event streams rather than bidirectional dependencies. AppOperationStateManager emits stateEvents$ that AppStateService subscribes to, avoiding circular dependencies.
  • Processed Event Interfaces: Raw WebSocket events are transformed into typed application events (ProcessedDiagramOperation, ProcessedDiagramSync, etc.) before consumption by the state layer.
  • Single Responsibility: Event processing is separated from state management, enabling independent testing and maintenance.

For implementation details, see the source repository: docs/migrated/refactoring/app-state-service-refactor-plan.md.

DFD Change Propagation Architecture:

The DFD component implements a change propagation system for handling user interactions, collaborative editing, auto-save, and visual effects. The system has evolved into a complex architecture with:

  • 15+ services involved in change propagation.
  • 20+ decision points that affect flow.
  • 4 different pathways for node creation.
  • 3 state stores requiring synchronization.
  • 2,274+ lines of coordination logic in the main DfdComponent.

Architecture Strengths:

  • Robust collaborative editing with real-time synchronization.
  • History management that excludes visual-only changes.
  • Comprehensive auto-save system that prevents data loss.
  • Flexible visual effects that provide immediate user feedback.

Known Architectural Issues:

  1. Multiple Code Paths: Similar operations can follow different code paths depending on context.
  2. Scattered Orchestration: Logic is spread across the DfdComponent and multiple services.
  3. Complex State Management: Overlapping responsibilities between stores.
  4. Permission Checking Complexity: Race conditions and unclear fallback logic.
  5. Auto-save Logic Duplication: Auto-save logic is repeated across different event sources.

Change Propagation Patterns:

The system implements four distinct patterns for propagating changes:

Pattern Flow Use Case
Local User Actions X6 Events -> History System -> Auto-save Direct user interactions
Collaborative Mode X6 Events -> Diagram Operation Broadcast -> WebSocket Multi-user editing
Remote Operations WebSocket -> State Service -> Direct Graph Updates Receiving remote changes
Visual Effects Visual Effects Service -> Direct Styling (history excluded) Non-semantic visual feedback

Developer Guidelines:

  • DFD features: Review the user actions flow to understand current behavior, check which systems your changes will affect, and consider the impact on both solo and collaborative modes.
  • Collaborative editing: Understand WebSocket message handling, review permission checking complexity, and study state synchronization patterns.
  • Auto-save and persistence: Understand the auto-save triggering logic, review history filtering mechanisms, and consider performance implications.

For comprehensive documentation including Mermaid flow diagrams, see the source repository at docs/migrated/reference/architecture/dfd-change-propagation/.

Visual Effects Pipeline (pages/dfd/infrastructure/services/):

The visual effects system provides immediate visual feedback for user interactions without interfering with semantic change propagation or the history system. Key services:

  • InfraVisualEffectsService - Manages creation highlights with fade-out animations.
  • InfraX6SelectionAdapter - Handles X6-specific selection implementation and visual effects.
  • InfraPortStateService - Manages port visibility state and connection tracking.
  • DfdStateStore - Local UI state store (cells$, selectedNode$, canUndo$, canRedo$, isLoading$).

Visual Effect Types:

Effect Type Trigger Implementation
Selection Effects User selects cells InfraX6SelectionAdapter.applySelectionEffect() applies red glow filter and stroke styling
Hover Effects Mouse enters/leaves cell InfraX6SelectionAdapter.applyHoverEffect() applies brightness filter
Creation Highlights Programmatic cell creation InfraVisualEffectsService.applyCreationHighlight() with 500ms fade animation
Port Visibility Connection interactions InfraPortStateService shows/hides ports based on connections and hover state

History Suppression: Visual effects use AppOperationStateManager.executeVisualEffect() to execute styling changes without polluting the undo/redo history.

Styling Constants (from DFD_STYLING in pages/dfd/constants/styling-constants.ts):

  • Selection: Red glow (rgba(255, 0, 0, 0.8)), 8px blur, stroke color #000000, stroke width 2px
  • Hover: Red glow (rgba(255, 0, 0, 0.6)), 4px blur
  • Creation: Blue glow (rgba(0, 150, 255, 0.9)), 12px blur, 500ms fade duration

State Synchronization: The system manages multiple state stores:

  • DfdStateStore - Local UI state (cells$, selectedNode$, canUndo$, canRedo$, isLoading$).
  • AppStateService - Collaborative state (pendingOperations, syncState, conflicts).
  • X6 Graph - Source of truth for diagram structure.

For detailed diagrams and flow documentation, see the source repository: docs/migrated/reference/architecture/dfd-change-propagation/visual-effects-pipeline.md

Unified Operation Flow Layer (pages/dfd/application/):

The DFD editor uses a unified operation flow architecture where all graph modifications flow through a single, well-defined pipeline. This approach ensures consistency, enables proper history tracking, supports real-time collaboration, and provides a single point for persistence operations.

Key Principles:

  1. Single Source of Truth: All changes flow through AppGraphOperationManager.
  2. Source Awareness: Every operation knows its origin (user, remote, load, undo/redo).
  3. Converged Pipeline: Maximize code reuse across different operation sources.
  4. History at the Right Level: Track graph-interaction-level operations, not low-level X6 events.
  5. Coordinated Side Effects: History, broadcasting, and auto-save are coordinated based on operation source.

Operation Sources:

Source Description Record History? Broadcast? Auto-Save?
user-interaction Direct user actions in the UI Yes If in collaboration session If NOT in session
remote-collaboration Operations from other users via WebSocket No No (already broadcast) No (handled by remote)
diagram-load Loading diagram from server/storage No No No
undo-redo Undo/redo operations from history No If in session If NOT in session
auto-correction System corrections (e.g., z-order fixes) No Maybe Maybe

Unified Operation Pipeline:

┌────────────────────────────────────────────────────────────┐
│                    OPERATION SOURCES                        │
├─────────────┬──────────────┬──────────────┬────────────────┤
│ User Action │ Diagram Load │ Remote Ops   │ Undo/Redo      │
│ (UI Events) │ (REST/WS)    │ (WebSocket)  │ (History Svc)  │
└──────┬──────┴──────┬───────┴──────┬───────┴────────┬───────┘
       │             │              │                │
       ▼             ▼              ▼                ▼
┌────────────────────────────────────────────────────────────┐
│             CONVERT TO GraphOperation                       │
│  - NodeOperations: create-node, update-node, delete-node   │
│  - EdgeOperations: create-edge, update-edge, delete-edge   │
│  - BatchOperations: batch of multiple operations           │
│  - LoadOperations: load-diagram with full cell array       │
└───────────────────────────┬────────────────────────────────┘
                            │
                            ▼
┌────────────────────────────────────────────────────────────┐
│          AppGraphOperationManager.execute()                 │
│  - Routes operation to appropriate executor                 │
│  - Provides OperationContext with source, flags, metadata  │
│  - Manages operation lifecycle and error handling          │
└───────────────────────────┬────────────────────────────────┘
                            │
                            ▼
┌────────────────────────────────────────────────────────────┐
│                   OPERATION EXECUTORS                       │
│  - NodeOperationExecutor: Handles node operations          │
│  - EdgeOperationExecutor: Handles edge operations          │
│  - BatchOperationExecutor: Handles batch operations        │
│  - LoadDiagramExecutor: Handles diagram loading            │
└───────────────────────────┬────────────────────────────────┘
                            │
                            ▼
┌────────────────────────────────────────────────────────────┐
│             INFRASTRUCTURE SERVICES                         │
│  - InfraNodeService: Creates/modifies nodes in X6          │
│  - InfraEdgeService: Creates/modifies edges in X6          │
│  - InfraX6GraphAdapter: Low-level X6 graph operations      │
└───────────────────────────┬────────────────────────────────┘
                            │
                            ▼
┌────────────────────────────────────────────────────────────┐
│           POST-OPERATION PROCESSING                         │
│  - Operation completed successfully                         │
│  - OperationResult returned with affected cell IDs         │
│  - History recorded (if user-interaction)                  │
│  - Broadcast/auto-save triggered (based on session mode)   │
└────────────────────────────────────────────────────────────┘

Service Responsibilities:

Service Role Does NOT
AppDfdOrchestrator High-level coordination and public API Directly modify graph, handle history, manage persistence
AppGraphOperationManager Operation routing and lifecycle management Know about history, broadcasting, or persistence
Operation Executors Execute specific operation types via infrastructure Record history, broadcast, or trigger persistence
AppOperationStateManager Manage operation state flags and drag tracking Modify graph or record history directly
AppHistoryService Custom undo/redo history management Trigger persistence directly
AppRemoteOperationHandler Handle operations from remote collaboration Broadcast back (would create infinite loop)
AppPersistenceCoordinator Save diagram to server or local storage Modify graph state

State Flags:

AppOperationStateManager manages the following flags:

  • isApplyingRemoteChange - Suppresses history and broadcast.
  • isDiagramLoading - Suppresses history and broadcast.
  • isUndoRedoOperation - Suppresses history; allows broadcast and save.

Benefits of Unified Architecture:

  • Consistency: All operations follow the same path with no special cases.
  • Testability: A single pipeline to test with clear mock boundaries.
  • Observability: All operations emit events for easy tracking.
  • Extensibility: You add new operation types by implementing executors.
  • Collaboration Support: Remote operations integrate seamlessly.

For complete implementation details including code examples and history entry format, see the source repository: docs/migrated/reference/architecture/unified-operation-flow.md

History Tracking Patterns (pages/dfd/application/):

The DFD editor implements a custom GraphOperation history system for undo/redo functionality, replacing the X6 built-in history plugin. The system uses three distinct patterns.

Retroactive Pattern - Used when X6 creates elements through user interaction (dragging to create nodes or edges):

  1. X6 creates the element and fires an event (e.g., node:added, edge:connected).
  2. The event handler validates the element and creates a retroactive GraphOperation with metadata.retroactive = true.
  3. The operation executor detects that the element already exists and captures its state for history.

Implementation files: app-edge.service.ts (handleEdgeAdded()), app-dfd.facade.ts (handleNodeAdded()), edge-operation-executor.ts / node-operation-executor.ts.

Drag Completion Pattern - Used for operations with multiple interim events (movement, resizing, vertices):

  1. X6 fires continuous events during the drag.
  2. AppOperationStateManager tracks drag state; dragCompleted$ fires on mouseup.
  3. The drag completion handler creates a GraphOperation with before/after state (only the final state is recorded).

Implementation files: app-dfd.facade.ts (handleDragCompletion(), _handleNodeMove(), _handleNodeResize(), _handleEdgeVerticesDrag()).

Direct Operation Pattern - Used for explicit user actions (delete, cut, paste):

  1. The user triggers an action; code creates a GraphOperation before modifying X6.
  2. The operation executes, modifying X6 and recording the change in history.

Implementation files: app-dfd.facade.ts (deleteSelectedCells(), cut(), copy(), paste()).

Supported Operations:

  • Node: Creation (retroactive), Movement (drag), Resizing (drag), Deletion (direct), Label editing (direct), Embedding (event)
  • Edge: Creation (retroactive), Deletion (direct), Label editing (direct), Vertices drag (drag), Reconnection (event)
  • Clipboard: Cut (direct), Paste (retroactive), Copy (no history)

Key Services:

Service Responsibility
AppGraphOperationManager Executes GraphOperations and manages operation pipeline
AppOperationStateManager Tracks drag state and coordinates history operations
AppHistoryService Manages undo/redo stack
NodeOperationExecutor / EdgeOperationExecutor Handle create/update/delete operations

Excluded from History: Z-order changes, metadata changes, and data asset assignments are intentionally excluded from history tracking.

For implementation details, see the source repository: docs/migrated/development/HISTORY_TRACKING_IMPLEMENTATION.md

User Actions Flow:

User interactions with the DFD graph follow different propagation paths depending on whether the user is in collaborative mode or solo editing mode. The system filters changes to ensure that only semantic changes trigger saves and broadcasts.

Key Services and Their Roles:

Service Role
AppDfdFacade Single entry point for most graph operations (node/edge creation, deletion, clipboard)
AppEventHandlersService Handles keyboard events, context menu, and user interactions
InfraWebsocketCollaborationAdapter Broadcasts changes via WebSocket in collaborative mode
AppOperationStateManager Controls operation state flags, drag tracking, and coordinates with history
AppHistoryService Manages undo/redo stacks and history entries
AppDiagramService Handles saving and loading diagram data via REST or WebSocket
AppStateService Manages local component state and collaborative state
InfraVisualEffectsService Applies visual effects (creation highlights) without affecting history

User Action Categories:

  1. Node Creation: User adds a node via toolbar --> AppDfdFacade.createNodeWithIntelligentPositioning() --> InfraNodeService.addGraphNode() --> X6 graph adds node --> X6 event node:added --> AppDfdFacade.handleNodeAdded() creates CreateNodeOperation for history.

  2. Node Drag/Move: User drags a node --> X6 handles mouse events and updates position --> On drag completion, AppDfdFacade.handleDragCompletion() creates UpdateNodeOperation with position change for history.

  3. Node Deletion: User selects a node and presses Delete --> AppEventHandlersService.onDeleteSelected() --> Starts atomic operation via broadcaster --> InfraX6SelectionAdapter.deleteSelected() --> Creates DeleteNodeOperation / DeleteEdgeOperation for each cell.

  4. Edge Creation: User drags from a source port --> X6 validates the connection --> AppEdgeService.handleEdgeAdded() validates DFD rules --> Creates CreateEdgeOperation for history.

Collaborative vs Solo Mode Decision:

Change Event
    |
    v
DfdCollaborationService.isCollaborating()?
    |
    +-- Yes --> Has edit permissions?
    |               |
    |               +-- Yes --> InfraWebsocketCollaborationAdapter.sendDiagramOperation()
    |               |               --> WebSocket broadcast to collaborators
    |               |
    |               +-- No --> Block change, show error
    |
    +-- No --> Solo mode: record in AppHistoryService
                    --> Trigger auto-save via AppDiagramService.saveDiagramChanges()

History Filtering Logic:

The system filters changes to determine what to record in history:

  • Position and size changes: Included in history as semantic changes.
  • Visual attributes only (filters, selection styling): Excluded from history via AppOperationStateManager.executeVisualEffect().
  • Tool changes: Filtered out during history processing.
  • Remote changes during sync: Excluded via the isApplyingRemoteChange flag.

Performance Considerations:

  • Batching: Multiple related changes are batched into single operations via AppOperationStateManager.executeAtomicOperation().
  • Filtering: Visual-only changes are filtered to reduce noise.
  • Debouncing: Auto-save is debounced via AppHistoryService to prevent excessive API calls.
  • History Limits: History stacks are capped to prevent memory issues.

For implementation details, see the source repository: docs/migrated/reference/architecture/dfd-change-propagation/user-actions-flow.md

Auto-save Decision Trees (pages/dfd/application/services/):

The auto-save system uses comprehensive decision logic documented in Mermaid flowcharts:

  • Primary Auto-save Decision Flow: Determines when changes should trigger persistence based on load state, collaboration mode, and semantic vs. visual changes.
  • History Recording Decision Tree: Controls which operations are recorded in undo/redo history.
  • Visual Attribute Detection: Identifies and filters visual-only changes (selection effects, shadows, port highlights).
  • Collaboration Mode History Handling: Manages the relationship between local and server-side history in collaborative sessions.
  • Error Scenarios and Recovery: Documents save failure handling and history corruption recovery flows.

For complete decision tree diagrams, see: docs/migrated/reference/architecture/dfd-change-propagation/autosave-decision-tree.md

Threat Management (pages/tm/components/threat-page/):

  • Framework-based threat analysis (STRIDE and others via threat_type array)
  • Threat categorization with CVSS scoring and CWE identifiers
  • Mitigation tracking

4. Service Provisioning Standards [Frontend: tmi-ux]

This section defines the standards for providing services in the tmi-ux Angular application. Following these standards ensures consistent behavior, prevents circular dependencies, and maintains clean architecture.

Key Principles

1. Use Root-Level Provisioning for Shared Services

Services that manage application-wide state or are used across multiple components use root-level provisioning:

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  // Service implementation
}

Examples of root-provided services:

  • Authentication services (AuthService)
  • API services (ApiService, ThreatModelService)
  • Logging services (LoggerService)
  • Global state management services
  • WebSocket services for real-time collaboration

2. Use Component-Level Provisioning for Component-Specific Services

Services that are tightly coupled to a specific component's lifecycle are provided at the component level:

@Component({
  selector: 'app-example',
  providers: [ComponentSpecificService],
})
export class ExampleComponent {
  // Component implementation
}

Examples of component-provided services:

  • Graph adapters (e.g., X6 adapters in DFD component)
  • Event handlers specific to a component
  • History managers for undo/redo within a component
  • Services that manage component-specific UI state
Common Pitfalls to Avoid

1. Duplicate Provisioning

Incorrect -- providing a root-level service again in a component:

// service.ts
@Injectable({
  providedIn: 'root'  // Already provided at root
})
export class SharedService {}

// component.ts
@Component({
  providers: [SharedService]  // DON'T DO THIS - creates duplicate instance
})

Correct -- use the root-provided instance:

// component.ts
@Component({
  // No providers array needed for root-provided services
})
export class ExampleComponent {
  constructor(private sharedService: SharedService) {}
}

2. Circular Dependencies

Incorrect -- core services depending on feature services:

// In core/services/
export class CoreService {
  constructor(private featureService: FeatureService) {} // Upward dependency
}

Correct -- use dependency injection tokens or interfaces:

// In core/services/
export const FEATURE_HANDLER = new InjectionToken<FeatureHandler>('FeatureHandler');

export class CoreService {
  constructor(@Optional() @Inject(FEATURE_HANDLER) private handler?: FeatureHandler) {}
}
Service Categories and Provisioning Guidelines
Category Location Provisioning Examples
Core Services src/app/core/services/ providedIn: 'root' ApiService, LoggerService, AuthService
Feature Services src/app/pages/[feature]/services/ Context-dependent Shared: root; Component-specific: component
Infrastructure Adapters src/app/pages/[feature]/infrastructure/adapters/ Component providers X6 graph adapters, DOM manipulation services
Shared UI Services src/app/shared/services/ providedIn: 'root' NotificationService, DialogService
Migration Checklist

When reviewing or refactoring services:

  1. Check if the service is already provided at root level
  2. Remove duplicate provisioning from component providers arrays
  3. Verify no circular dependencies exist
  4. Ensure core services don't import from feature modules
  5. Test that singleton behavior is preserved where expected
  6. Document any exceptions to these standards with clear justification
Testing Considerations
  • Unit tests should mock dependencies appropriately
  • Integration tests should verify singleton behavior
  • Use TestBed.configureTestingModule() to override provisioning in tests when needed
Exceptions

Some valid exceptions to these standards include:

  1. Testing isolation: Providing a service at the component level for easier testing.
  2. Multiple instances required: Rare cases where multiple service instances are needed.
  3. Legacy code: Existing patterns that are too risky to refactor immediately.

Document all exceptions with a comment explaining the reasoning.

5. PDF Report Generation [Frontend: tmi-ux]

The tmi-ux application generates PDF reports on the client side using the pdf-lib library, with diagram images rendered from stored SVG data.

Architecture Overview:

[DFD Editor] ──save──► [SVG Thumbnail Capture] ──► [API Storage]
                              │                         │
                              ▼                         ▼
                       Base64 SVG Data          diagram.image.svg
                                                        │
                                                        ▼
[PDF Report Request] ──► [ThreatModelReportService] ──► [SVG→PNG Conversion]
                                    │                         │
                                    ▼                         ▼
                            [pdf-lib Document] ◄── [Embedded PNG Images]
                                    │
                                    ▼
                            [Downloaded PDF]

Key Components:

  • ThreatModelReportService (pages/tm/services/report/threat-model-report.service.ts): Main service for PDF generation using pdf-lib
  • AppExportService (pages/dfd/application/services/app-export.service.ts): Prepares diagram exports and SVG optimization
  • AppSvgOptimizationService (pages/dfd/application/services/app-svg-optimization.service.ts): SVGO-based SVG optimization for thumbnails

Diagram Image Storage Model:

Diagrams store pre-rendered SVG images for use in PDF reports and thumbnails.

interface DiagramImage {
  svg?: string;         // Base64-encoded SVG representation
  update_vector?: number; // Version when SVG was generated
}

interface Diagram {
  id: string;
  name: string;
  // ... other fields
  image?: DiagramImage;  // Pre-rendered image data
}

SVG Capture Flow:

  1. When a user saves a diagram or navigates away from the DFD editor, the component captures an SVG thumbnail.
  2. The _captureDiagramSvgThumbnail() method in dfd.component.ts uses X6's toSVG() export.
  3. The SVG is processed and optimized via AppExportService.processSvg() with SVGO optimization.
  4. The base64-encoded SVG is stored with the diagram via saveManuallyWithImage().

PDF Rendering Flow:

  1. ThreatModelReportService.generateReport() creates a pdf-lib document.
  2. For each diagram with stored SVG data (diagram.image?.svg):
    • The SVG is decoded from base64.
    • convertSvgToPng() renders the SVG to a canvas at 300 DPI for print quality.
    • The PNG data is embedded into the PDF document.
  3. If no SVG is available, the system shows placeholder text as a fallback.

Print Quality Considerations:

  • DPI Scaling: Canvas renders at 4.17x scale (300 DPI / 72 PDF points)
  • Aspect Ratio: Preserved from original SVG dimensions
  • Width: Uses full printable width minus margins
  • Page Size: Configurable (US Letter, A4) via user preferences

Localization Support:

  • Custom fonts loaded per language (Noto Sans family for international character support)
  • RTL support for Arabic and Hebrew
  • Translated section headers and labels via Transloco

Data Architecture [Backend: tmi]

Entity Relationship Diagram

ThreatModel (1) ──< (N) Diagram
    │                    │
    │                    └──< (N) Cell (stored as JSON array in Diagram)
    │
    ├──< (N) Threat
    │
    ├──< (N) Asset
    │
    ├──< (N) Document
    │
    ├──< (N) Note
    │
    └──< (N) Repository

Metadata (polymorphic, keyed by EntityType + EntityID)
    - Applies to: ThreatModel, Diagram, Threat, Asset, Document, Note, Repository

Key Entities

ThreatModel

The primary container for security analysis work.

Fields:

  • id (UUID) - Unique identifier
  • name (string) - Display name
  • description (string) - Detailed description
  • owner (User object) - Owner user
  • authorization (array) - Access control list
  • created_at (timestamp) - Creation time
  • modified_at (timestamp) - Last modification
  • created_by (User object) - User who created the threat model
  • status (string) - Status in the organization's SDLC process
  • threat_model_framework (string) - Framework (default: STRIDE)
  • alias (string array) - Alternative names/identifiers
  • is_confidential (bool) - Immutable after creation
  • issue_uri (string) - Link to issue tracker
  • security_reviewer (User object, optional) - Assigned security reviewer
  • project_id (UUID, optional) - Associated project
  • diagram_count (int) - Number of diagrams (calculated)
  • threat_count (int) - Number of threats (calculated)
  • document_count (int) - Number of documents (calculated)
  • asset_count (int) - Number of assets (calculated)
  • note_count (int) - Number of notes (calculated)

Authorization Structure:

{
  "owner": { "email": "alice@example.com", "name": "Alice" },
  "authorization": [
    {
      "provider_id": "bob@example.com",
      "principal_type": "user",
      "provider": "google",
      "role": "writer"
    },
    {
      "provider_id": "security-team",
      "principal_type": "group",
      "provider": "google",
      "role": "reader"
    },
    {
      "provider_id": "everyone",
      "principal_type": "group",
      "provider": "*",
      "role": "reader"
    }
  ]
}

Diagram

A visual representation of system data flows.

Fields:

  • id (UUID) - Unique identifier
  • threat_model_id (UUID) - Parent threat model
  • name (string) - Diagram name
  • description (string) - Purpose/scope
  • type (string) - Diagram type with version (e.g., "DFD-1.0.0")
  • cells (array) - Diagram elements (nodes and edges following X6 structure)
  • update_vector (int) - Server-managed monotonic version counter for conflict resolution
  • image (object, optional) - Pre-rendered SVG image data with version
  • color_palette (array, optional) - Custom color palette for diagram elements
  • include_in_report (bool) - Whether to include in generated reports
  • timmy_enabled (bool) - Whether AI assistant is enabled

Node Shape Types:

  • actor - External entity
  • process - Processing component
  • store - Data storage
  • security-boundary - Trust boundary
  • text-box - Annotations

Edge Shape Types:

  • flow - Data flow between nodes

Cell Structure (follows AntV/X6 format):

{
  "id": "uuid",
  "shape": "process",
  "data": {
    "label": "Authentication Service",
    "_metadata": [{"key": "...", "value": "..."}]
  }
}

Node cells include position and size in their X6 data properties. Edge cells include source and target references.

Threat

An identified security risk or vulnerability.

Fields:

  • id (UUID)
  • threat_model_id (UUID)
  • name (string) - Threat name
  • description (string) - Detailed description
  • threat_type (string array) - Threat classification types (e.g., STRIDE categories like "Spoofing", "Tampering")
  • severity (string) - Severity level
  • priority (string) - Priority level for addressing
  • status (string) - Current status
  • mitigation (string) - Recommended or planned mitigation
  • mitigated (bool) - Whether the threat has been mitigated
  • score (float) - Numeric risk/impact score
  • cvss (array) - CVSS scoring information
  • cwe_id (string array) - CWE identifiers
  • diagram_id (UUID, optional) - Associated diagram
  • cell_id (UUID, optional) - Associated cell within diagram
  • asset_id (UUID, optional) - Associated asset
  • issue_uri (string, optional) - Link to issue tracker

Data Storage Strategy

┌─── Application Layer ───┐
│  Go Structs & Interfaces │
├─── Business Logic ──────┤
│   Domain Models & Rules  │
├─── Data Access Layer ───┤
│    Repository Pattern    │
├─── Storage Layer ───────┤
│  RDBMS     │   Redis     │
│ (Persistent)│ (Temporary) │
└─────────────────────────┘

RDBMS (Primary Storage)

TMI supports five relational databases via GORM: PostgreSQL, MySQL, SQLite, SQL Server, and Oracle.

  • ACID Compliance: Strong consistency for business data.
  • Relational Integrity: Foreign key constraints.
  • Schema Migrations: GORM AutoMigrate for all supported databases.
  • Performance: Optimized indexes.

Redis (Cache and Sessions)

  • Session Storage: JWT session validation.
  • Real-time Coordination: WebSocket connection tracking.
  • Caching: Frequently accessed data.
  • Rate Limiting: Request throttling.

Authentication and Authorization [Both]

For a comprehensive guide to how authentication works in TMI -- including detailed sequence diagrams, client credentials, and helper functions -- see Authentication.

The TMI system implements authentication flows across both components:

  • OAuth 2.0 with PKCE (RFC 7636) for enhanced security
  • Client Credentials Grant for automation and service-to-service authentication
  • SAML 2.0 for enterprise SSO integration
  • JWT tokens for API authentication (HS256, RS256, or ES256 signing)

For detailed sequence diagrams and visual documentation of all authentication flows, see the source repository at docs/migrated/reference/architecture/oauth-flow-diagrams.md, which includes:

  • Complete PKCE flow sequences with Redis state management.
  • Token refresh flows and lifecycle diagrams.
  • State management and CSRF protection details.
  • Multi-provider support (Google, GitHub, Microsoft, TMI test provider).
  • SAML SP-initiated and IdP-initiated flows.
  • Error handling scenarios.

OAuth 2.0 Flow Overview

[Client] → [OAuth Provider] → [TMI Auth] → [JWT Token] → [Protected Resources]
    ↑           ↑                ↑            ↑              ↑
  Login    Provider Auth    Token Exchange  Bearer Auth   Resource Access

Flow Steps

  1. Client Initiation: Client redirects to TMI OAuth endpoint

    GET /oauth2/authorize?idp=google
    
  2. Provider Selection: User selects OAuth provider

  3. Provider Authentication: User authenticates with provider

  4. Authorization Grant: Provider returns authorization to TMI

  5. Token Exchange: TMI exchanges authorization for user info and generates JWT

  6. Client Access: Client receives JWT token for API access

    {
      "access_token": "eyJhbGc...",
      "token_type": "Bearer",
      "expires_in": 3600
    }

User Identification Architecture [Backend: tmi]

The tmi backend uses a consolidated user identification architecture with clear separation between internal and external identifiers.

User Identity Fields

Field Purpose Visibility
internal_uuid Database primary key, foreign keys, rate limiting, quotas Internal only (never in API responses)
provider OAuth provider name (tmi, google, github, microsoft, azure) Internal and API
provider_user_id Provider's user identifier (in JWT sub claim) In API id field
email User's email address API responses
name Display name API responses

Key Design Decisions

  1. Provider-Scoped Users: Users from different OAuth providers are treated as separate users, even when they share the same email address. For example, alice@google and alice@github are distinct users.

  2. Consolidated Schema: All user data is stored in a single users table with (provider, provider_user_id) as a unique constraint. There is no separate user_providers table.

  3. JWT Claims: The JWT sub claim always contains the provider_user_id, never the internal_uuid. This ensures that external systems work with provider identifiers.

  4. Context Utilities: Handler code uses GetUserFromContext(), GetUserInternalUUID(), and GetUserProviderID() for type-safe access to user identity.

  5. Redis Caching: User lookups are cached with a 15-minute TTL to reduce database queries.

Database Schema

CREATE TABLE users (
    internal_uuid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    provider TEXT NOT NULL,
    provider_user_id TEXT NOT NULL,
    email TEXT NOT NULL,
    name TEXT NOT NULL,
    -- ... other fields
    UNIQUE(provider, provider_user_id)
);

User and Group Management [Backend: tmi]

The tmi backend provides comprehensive user and group management through admin APIs and provider-scoped endpoints.

User Deletion Strategy

TMI uses hard deletion with automatic ownership transfer:

  1. Begin a transaction.
  2. For each owned threat model:
    • Find an alternate owner (another user with the owner role).
    • If an alternate owner exists, transfer ownership and remove the deleting user's permissions.
    • If no alternate owner exists, delete the threat model (CASCADE deletes diagrams, threats, etc.).
  3. Delete remaining permissions (reader/writer on other threat models).
  4. Delete the user record (CASCADE deletes related entities).
  5. Commit the transaction.
  6. Write an audit log entry with deletion statistics.

This approach ensures:

  • Data consistency with the existing self-deletion flow.
  • Transactional integrity (no orphaned resources).
  • Automatic ownership transfer that preserves collaborative work.
  • Hard deletion that complies with data minimization principles.

Group Management

Groups in TMI follow a provider-scoped model:

  • Provider-specific groups: Sourced from IdP claims (SAML/OAuth).
  • Provider-independent groups: Created by administrators with provider="*".
  • Special "everyone" group: Grants access to all authenticated users.

Authorization Middleware

TMI uses layered authorization middleware:

  1. Admin-Only (AdministratorMiddleware): All /admin/* endpoints require administrator privileges.
  2. Same-Provider (SameProviderMiddleware): SAML/OAuth endpoints validate that the JWT idp claim matches the path parameter.
  3. SAML-Only (SAMLProviderOnlyMiddleware): Rejects non-SAML providers.

Admin API Endpoints

Endpoint Method Purpose
/admin/users GET List all users with filtering
/admin/users/{id} GET/PATCH Get/update user details
/admin/users DELETE Delete user by provider + provider_id
/admin/groups GET/POST List/create groups
/admin/groups/{id} GET/PATCH Get/update group details

SAML UI Endpoints

Endpoint Purpose
/saml/providers/{idp}/users List users from caller's SAML provider
/oauth2/providers/{idp}/groups List groups from caller's provider

Security: Users can only access data from their own provider (prevents cross-provider information leakage).

Client-Side Session Management [Frontend: tmi-ux]

The tmi-ux application implements client-side session management that automatically logs users out when authentication tokens expire, with advance warning and the option to extend the session.

Session Management Components

AuthService (src/app/auth/services/auth.service.ts):

  • Handles token storage, validation, and refresh.
  • Key methods: getValidToken(), refreshToken(), isTokenValid(), shouldRefreshToken() (15-minute window), logout().

SessionManagerService (src/app/auth/services/session-manager.service.ts):

  • Manages the session lifecycle with timers.
  • Monitors token expiry times and sets appropriate timers.
  • Shows a warning dialog 5 minutes before token expiry for inactive users.
  • Coordinates with AuthService for token refresh.

JWT Interceptor (src/app/auth/interceptors/jwt.interceptor.ts):

  • Attaches tokens automatically and handles reactive refresh.
  • Adds Bearer tokens to API requests.
  • Handles 401 responses with automatic token refresh and request retry.

SessionExpiryDialogComponent (src/app/core/components/session-expiry-dialog/):

  • Shows a countdown timer 5 minutes before session expiry.
  • Provides "Extend Session" and "Logout" buttons.
  • Automatically dismisses when the user takes action or the timer expires.

Session Types

Type Token Structure Expiry Refresh Mechanism
OAuth (Google, GitHub, etc.) JWT access + refresh token 1 hour (access), longer (refresh) /oauth2/refresh endpoint
Local Development Fake JWT token Configurable via authTokenExpiryMinutes N/A (self-contained)
Test User Mock JWT token Same as local Creates new mock token

Session Lifecycle

  1. Login: User authenticates --> AuthService stores token --> SessionManager starts expiry timers.
  2. Active Session: Activity check timer runs every minute; proactive refresh occurs 15 minutes before expiry for active users.
  3. Warning: 5 minutes before expiry (for inactive users), SessionExpiryDialog shows a countdown.
  4. Extension: User clicks "Extend Session" --> token is refreshed --> timers reset.
  5. Logout: Manual, automatic (expired), or failed refresh --> all timers stop --> redirect to home.

Timer Settings

  • warningTime: 5 minutes before expiry to show warning for inactive users
  • proactiveRefreshTime: 15 minutes before expiry for proactive refresh of active users
  • activityCheckInterval: 1 minute for checking user activity

Token Refresh Scenarios

Proactive Refresh (for active users):

  • Activity check timer detects active user with token expiring within 15 minutes
  • Automatic background refresh without user interaction

Warning Dialog Refresh (for inactive users):

  • Warning dialog appears 5 minutes before expiry
  • User clicks "Extend Session" to trigger refresh

Reactive Refresh (401 handling):

  • JWT Interceptor detects 401 response
  • Attempts token refresh and retries original request

Security Considerations

  • Token Encryption: Tokens are encrypted in localStorage using a browser fingerprint.
  • Session Fixation Prevention: OAuth flows include CSRF protection via state parameters.
  • Token Rotation: Refresh tokens are rotated on each refresh.
  • Cross-tab Behavior: All tabs share the same token storage; logging out in one tab affects all tabs.

For complete session management details including sequence diagrams, see the source repository: docs/migrated/reference/architecture/session-management.md

Role-Based Access Control (RBAC) [Backend: tmi]

Roles

Owner:

  • Full control over resource
  • Can read, write, and delete
  • Can change owner and authorization
  • Can delete the resource

Writer:

  • Can read and modify resource
  • Cannot change owner or authorization fields
  • Cannot delete the resource

Reader:

  • Read-only access
  • Cannot modify anything or delete the resource

Complete Authorization Rules

  1. Authorized User Create: Any authenticated user may create a new empty object.

  2. Owner Role Permissions: Users with the owner role can read any field and write to any mutable field in the object, and may delete the object.

  3. Writer Role Permissions: Users with the writer role can read any field and write to any mutable field EXCEPT the owner and authorization fields. Writers may not delete the object.

  4. Reader Role Permissions: Users with the reader role can only read fields but cannot modify anything or delete the object.

  5. Owner Field Precedence: The user listed in the "owner" field automatically gets owner role permissions, regardless of what appears in the authorization field. If the same user appears in both the owner field and authorization list, the owner field takes absolute precedence and they receive owner permissions.

  6. Owner Transfer Protection: When an owner changes the owner field to a different user, the handler automatically adds the original owner as a principal in the authorization field with "owner" role. This prevents owners from losing access when transferring ownership.

  7. Principal Duplication Validation: Input validation prevents duplicate principals in the authorization field to maintain data integrity. Requests containing duplicate principals will be rejected as invalid.

  8. Permission Resolution for Multiple Roles: If a user appears multiple times in the authorization field with different roles, the system grants the highest role (owner > writer > reader). However, input validation should prevent this scenario.

  9. Role Updates: If a patch request for the authorization field includes a principal that already exists in the authorization field, the new role value will overwrite the existing role for that principal.

  10. Role Precedence: Owner > Writer > Reader.

  11. Canonical Order: There is no canonical ordering to entries in authorization; authorization checks do not short-circuit on the first match for a user. If the requesting user is not the owner, then all authorization entries are considered in the authorization decision.

  12. Pseudo-Group "everyone": The special group "everyone" grants access to all authenticated users regardless of their identity provider (IdP) or actual group memberships. When an authorization entry has principal_type: "group" and provider_id: "everyone", any authenticated user receives the specified role. The provider field is ignored for the "everyone" pseudo-group.

Permission Resolution Logic

The system uses the following logic to determine a user's effective permissions:

Owner Check First: If the user matches the value in the "owner" field, they receive owner-level permissions regardless of any authorization list entries.

Authorization List Check: If the user is not the owner, the system checks the authorization list for their permissions. All entries in authorization are checked in accordance with rule 11.

Multiple Role Resolution: If a user appears multiple times in the authorization list with different roles (which should be prevented by input validation), the system grants the highest role.

Owner-Principal Duplication: The system gracefully handles cases where the owner also appears in the authorization list:

  • Owner in auth list with lower role: Owner field wins
  • Owner in auth list with equal role: Both provide same permissions
  • Supports ownership transitions: Allows scenarios for safe ownership transfers with no unowned intermediate state

Authorization Checking

  1. Authorization checking is primarily performed by middleware. The middleware allows creates for authorized users (rule 1), and for requests involving a specific object, the middleware retrieves the Owner and Authorization fields from the server, and implements rules 2, 3, 4, 5, 8 and 11.

  2. Rules 6, 7, and 9 are implemented in the handler, since they require reading the entire request.

Pseudo-Groups

The "everyone" Pseudo-Group

The "everyone" pseudo-group is a cross-IdP group that matches all authenticated users:

  • Provider ID: "everyone"
  • Principal Type: "group"
  • Provider Requirement: None (the provider field is ignored for pseudo-groups; conventionally set to "*")
  • Behavior: Any authenticated user automatically matches this group, regardless of:
    • Their identity provider (e.g., tmi, Google, SAML)
    • Their actual group memberships from the IdP
    • Their email domain or other user attributes

Permission Examples

Public Read Access:

{
  "owner": {"email": "admin@example.com"},
  "authorization": [
    {
      "provider_id": "everyone",
      "principal_type": "group",
      "provider": "*",
      "role": "reader"
    }
  ]
}

Public Write Access:

{
  "owner": {"email": "admin@example.com"},
  "authorization": [
    {
      "provider_id": "everyone",
      "principal_type": "group",
      "provider": "*",
      "role": "writer"
    }
  ]
}

Mixed Authorization (everyone + specific grants):

{
  "owner": {"email": "admin@example.com"},
  "authorization": [
    {
      "provider_id": "everyone",
      "principal_type": "group",
      "provider": "*",
      "role": "reader"
    },
    {
      "provider_id": "editors-team",
      "principal_type": "group",
      "provider": "google",
      "role": "writer"
    },
    {
      "provider_id": "reviewer@example.com",
      "principal_type": "user",
      "provider": "google",
      "role": "writer"
    }
  ]
}

In this example:

  • All authenticated users can read (via "everyone")
  • Members of the "editors-team" group from Google can write
  • The user "reviewer@example.com" can write
  • The owner "admin@example.com" has full control

Role Precedence with Pseudo-Groups:

When a user matches multiple authorization entries (including "everyone"), the highest role wins:

  • alice@example.com with explicit owner role receives owner permissions
  • All other authenticated users receive reader permissions (via "everyone")

Team Collaboration:

{
  "owner": {"email": "alice@example.com"},
  "authorization": [
    {
      "provider_id": "bob@example.com",
      "principal_type": "user",
      "provider": "google",
      "role": "owner"
    },
    {
      "provider_id": "charlie@example.com",
      "principal_type": "user",
      "provider": "google",
      "role": "writer"
    }
  ]
}

User-Immutable Properties

Certain fields are server-controlled and cannot be modified by users:

  1. ID (UUID) - Generated by the server at object creation time and never changed thereafter
  2. created_at - Set by the server to the current system time at object creation, represented in UTC in RFC 3339 format
  3. modified_at - Set by the server to the current system time at object mutation, represented in UTC in RFC 3339 format
  4. created_by - Set at creation (from JWT)
  5. Count fields - Calculated from database (diagram_count, threat_count, document_count, asset_count, note_count)

Attempts by users to change created_at or modified_at values are rejected.

Client-Side Authorization Enforcement [Frontend: tmi-ux]

The tmi-ux Angular application enforces role-based access control in the UI through a reactive authorization service and component-level permission checks.

ThreatModelAuthorizationService (pages/tm/services/threat-model-authorization.service.ts):

A centralized service that manages threat model authorization state reactively.

// Observable streams for reactive permission checking
get canEdit$(): Observable<boolean>           // Writer or Owner
get canManagePermissions$(): Observable<boolean>  // Owner only
get currentUserPermission$(): Observable<'reader' | 'writer' | 'owner' | null>

// Synchronous methods for imperative checks
canEdit(): boolean
canManagePermissions(): boolean
getCurrentUserPermission(): 'reader' | 'writer' | 'owner' | null

Permission Checking Patterns:

Components subscribe to the authorization service and update local properties.

// In component initialization
this.authorizationService.canEdit$.subscribe(canEdit => {
  this.canEdit = canEdit;
  this.updateFormEditability();  // Enable/disable form controls
});

Template-Level Enforcement:

UI elements are conditionally rendered or disabled based on permissions.

<!-- Hide action buttons for readers -->
@if (canEdit) {
  <button (click)="addAsset()">Add Asset</button>
}

<!-- Show visual indicator for read-only mode -->
@if (!canEdit && !isNewThreatModel) {
  <mat-icon [matTooltip]="'common.readOnlyTooltip' | transloco">edit_off</mat-icon>
}

Dialog Read-Only Mode:

All editor dialogs receive an isReadOnly flag to disable editing when the user has reader permissions.

const dialogData: AssetEditorDialogData = {
  asset: existingAsset,
  isReadOnly: !this.canEdit,  // Passed to dialog
};

Method Guards:

Component methods that modify data include early-return guards.

addAsset(): void {
  if (!this.canEdit) {
    this.logger.warn('Cannot add asset - insufficient permissions');
    return;
  }
  // ... proceed with add operation
}

This client-side enforcement complements the server-side RBAC to provide defense in depth and a better user experience by preventing unauthorized actions before API calls.

Collaboration Architecture [Both]

Real-time Collaboration System

[User A] ──WebSocket──┐
                       ├── [TMI WebSocket Hub] ──Redis── [Session State]
[User B] ──WebSocket──┘           │
                                  ├── [Operation Log] ──RDBMS
                                  └── [Conflict Resolution]

WebSocket Protocol [Both]

The server (tmi) implements the WebSocket protocol, and the client (tmi-ux) consumes it.

Connection Management

Connection URL:

ws://localhost:8080/threat_models/{tm_id}/diagrams/{diagram_id}/ws

Authenticated WebSocket connections:

  • The WebSocket endpoint is behind JWT authentication middleware (Bearer token in the HTTP header).
  • A ticket-based authentication flow is also available: clients first request a short-lived ticket via GET /ws/ticket?session_id={id}, then present the ticket when connecting.
  • Tickets expire after 30 seconds and are single-use.
  • User permissions are validated on connection (read access to the threat model is required).
  • The user is added to the collaboration session upon connection.

Message Types

Diagram Operations:

{
  "message_type": "diagram_operation",
  "user_id": "alice@example.com",
  "operation_id": "uuid",
  "operation": {
    "type": "patch",
    "cells": [
      {
        "id": "cell-uuid",
        "operation": "add",
        "data": { "shape": "process", "x": 100, "y": 200 }
      }
    ]
  }
}

Presenter Mode:

{
  "message_type": "presenter_request",
  "user_id": "alice@example.com"
}

Cursor Sharing:

{
  "message_type": "presenter_cursor",
  "user_id": "alice@example.com",
  "cursor_position": { "x": 150, "y": 300 }
}

Collaboration Features

  • Multi-user Editing: Simultaneous diagram editing with conflict resolution.
  • Presenter Mode: A designated presenter with cursor sharing for meetings.
  • State Synchronization: Automatic state correction and resync when conflicts are detected.
  • Undo/Redo: Collaborative undo/redo with history management.

Conflict Resolution

Update Vector: Each diagram has an update_vector (version number).

State Sync Message: On connection or resync, the client receives the current state:

{
  "message_type": "diagram_state",
  "diagram_id": "uuid",
  "update_vector": 42,
  "cells": [ /* current state */ ]
}

Conflict Detection: The server detects conflicts when the client's version does not match.

State Correction: The server sends a correction message to resync the client:

{
  "message_type": "state_correction",
  "update_vector": 45
}

Change Propagation Matrix [Frontend: tmi-ux]

The following matrix shows which systems each operation type affects in the tmi-ux DFD component.

Operation Type X6 Graph History Auto-save WebSocket Visual Effects State Store Collaboration
User Node Creation Direct Semantic Triggered If collab Highlight Update Broadcast
User Node Drag Direct Position Triggered If collab None Update Broadcast
User Node Delete Direct Semantic Triggered If collab None Update Broadcast
User Edge Creation Direct Semantic Triggered If collab Highlight Update Broadcast
User Selection Selection Excluded No save No broadcast Selection Update If presenter
User Hover Visual Excluded No save No broadcast Hover No update No broadcast
Remote Node Add Applied Suppressed No save Received Highlight Update Source
Remote Node Update Applied Suppressed No save Received None Update Source
Remote Node Delete Applied Suppressed No save Received None Update Source
Diagram Load Batch Suppressed No save No broadcast None Full update No broadcast
Undo/Redo (Solo) History Applied Triggered No broadcast None Update Not collab
Undo/Redo (Collab) Applied Suppressed No save Server-side None Update Server managed
Port Visibility Visual Excluded No save No broadcast Ports No update No broadcast
Cell Properties Direct Metadata Triggered If collab None Update Broadcast
Threat Changes No change No history Triggered No broadcast None No update No broadcast

Legend:

  • Affected: The system is involved in processing this operation
  • Not Affected: The system does not process this operation
  • If collab: Only affected when in collaborative mode
  • If presenter: Only affected for presenter in collaborative mode

Critical Decision Points

1. Collaborative Mode Detection

Multiple services check DfdCollaborationService.isCollaborating(). This is the most critical decision point, affecting almost every operation flow.

When collaborating:

  • Use WebSocket for persistence.
  • Suppress local history.
  • Enable operation broadcasting via AppEventHandlersService.
  • Check collaboration permissions.

When in solo mode:

  • No permission checks.
  • Use REST for persistence.
  • Enable local history.
  • Disable the broadcaster.

2. History Filtering Decision

AppOperationStateManager.shouldExcludeAttribute() determines which operations create history entries and trigger auto-save. Visual-only attributes (filters, ports, highlights, tool changes) are excluded from history to prevent cluttering undo/redo.

3. Permission Validation

InfraWebsocketCollaborationAdapter.sendDiagramOperation() checks permissions before sending operations. In collaborative mode, it checks DfdCollaborationService.hasPermission('edit'). If collaboration permissions have not yet loaded, it falls back to the threat model permission.

4. Auto-save Validation

The DFD component's auto-save logic validates that:

  • The initial diagram load has completed.
  • The user is not in collaborative mode (WebSocket handles persistence in that case).
  • All required data is available.

5. Visual Effect Application

InfraVisualEffectsService.applyCreationHighlight() prevents visual conflicts by:

  • Skipping cells with active effects.
  • Skipping currently selected cells.
  • Using history suppression during animations.

Error Handling and Recovery

WebSocket Failure Recovery

InfraWebsocketCollaborationAdapter._sendOperationWithRetry() handles failures:

  • Network/Timeout errors: Queue for retry with exponential backoff.
  • Permission errors: Block the operation immediately.
  • Connection lost: Queue operations for reconnection.

State Correction

When the server detects a version mismatch (the client's update vector differs from the server's), it sends a state correction message that triggers a full diagram resync.

For complete implementation details, see the source repository: docs/reference/architecture/dfd-change-propagation/

Security Design [Both]

Data Security

  • Encryption in Transit: TLS 1.3 for all communications.
  • Encryption at Rest: Database and file storage encryption.
  • Data Validation: Input sanitization and validation, including JSON schema validation, SQL injection prevention, and XSS prevention in the web application.
  • Audit Logging: A comprehensive audit trail for all operations, recording who accessed what, when modifications occurred, and what changes were made.

Authentication Security

JWT Security:

  • Configurable signing algorithm: HS256, RS256, or ES256.
  • Configurable expiration (default: 1 hour / 3600 seconds).
  • Key management via environment variables (HMAC secret, RSA key paths, or ECDSA key paths).

OAuth Security:

  • CSRF protection with the state parameter.
  • Authorization code flow (not implicit flow).
  • Token validation on every request.

Session Management:

  • Redis-based session storage.
  • Automatic cleanup of expired sessions.
  • Logout invalidates tokens.

Authorization Security

  • Middleware-based Authorization: Every request is checked before reaching the handler.
  • Resource-level Permissions: Permissions are checked for specific resources, not just endpoints.
  • Principle of Least Privilege: The default role is reader; higher permissions require explicit grants.

Container Security [Backend: tmi]

  • Chainguard Base Images: The tmi server uses Chainguard images for a minimal attack surface with daily security updates.
  • Non-root Execution: All containers run as nonroot:nonroot by default.
  • Security Scanning: Regular vulnerability scanning with Grype (Anchore).
  • Static Binaries: Built with CGO_ENABLED=0 for no runtime dependencies.
  • Minimal Attack Surface: No shell, package manager, or unnecessary tools in runtime images (~57 MB total).

Scalability Architecture [Backend: tmi]

Horizontal Scaling

  • Stateless Servers: The tmi servers are designed for horizontal scaling.
  • Load Balancing: Round-robin or least-connections load balancing.
  • Session Affinity: Redis-based session storage for multi-server deployment.
  • Database Scaling: Read replicas and connection pooling.

Performance Optimization

  • Caching Strategy: Multi-layer caching with Redis and application cache.
  • Connection Pooling: Database and Redis connection pooling.
  • Lazy Loading: On-demand resource loading and pagination.
  • CDN Integration: Static asset delivery through CDN.

Resource Management

  • Memory Management: Efficient Go garbage collection tuning.
  • Connection Limits: Appropriate limits for concurrent connections.
  • Rate Limiting: API and WebSocket rate limiting.
  • Resource Quotas: Per-user and per-organization resource limits.

Deployment Architecture [Backend: tmi]

Container Strategy

Docker Images: Multi-stage builds with Chainguard base images.

  • Builder: cgr.dev/chainguard/go:latest for secure Go compilation.
  • Runtime: cgr.dev/chainguard/static:latest for minimal attack surface (~57 MB).

Static Binaries: Built with CGO_ENABLED=0 for maximum portability.

Security Hardening: Non-root execution (nonroot:nonroot), no shell in runtime.

Configuration Management: Environment-based configuration.

Health Checks: Comprehensive health check endpoints.

Database Support: Container builds support PostgreSQL, MySQL, SQL Server, and SQLite. Oracle is excluded due to a CGO requirement.

Orchestration

  • Kubernetes Deployment: Cloud-native orchestration option.
  • Service Mesh: Advanced networking and security.
  • GitOps: Infrastructure and application deployment automation.
  • Blue-Green Deployment: Zero-downtime deployment strategy.

Implementation Reference

User and Group Management APIs

TMI provides admin-level user and group management APIs.

Admin-Only APIs (Cross-Provider) --- all implemented:

  • GET/PATCH/DELETE /admin/users - List, update, and delete users across all providers.
  • GET/POST/PATCH/DELETE /admin/groups - Manage groups, including provider-independent groups (provider="*").
  • Group deletion currently returns 501 (placeholder for future implementation).

SAML UI-Driven APIs (Provider-Scoped) --- all implemented:

  • GET /saml/providers/{idp}/users - List users for SAML provider autocomplete.
  • GET /oauth2/providers/{idp}/groups - List groups with authorization checks.

Implementation Components:

  • Database stores: api/user_store.go, api/group_store.go
  • Admin handlers: api/admin_user_handlers.go, api/admin_group_handlers.go
  • SAML handlers: api/saml_user_handlers.go
  • Provider auth middleware: api/provider_auth_middleware.go

For complete design decisions, see the source repository: docs/migrated/developer/planning/user-group-management-decisions.md

Source Repository Reference Materials

The TMI source repository contains reference documentation organized in the docs/reference/ directory.

Directory Structure

Directory Content
docs/reference/architecture/ System architecture diagrams, OAuth flow diagrams, ADRs
docs/reference/features/ Feature implementation documentation
docs/reference/libraries/ Third-party library integration guides (see DFD-Graphing-Library-Reference)
docs/reference/schemas/ Database schema definitions and data models
docs/reference/apis/ OpenAPI, AsyncAPI, and Arazzo specifications

Feature Documentation

The docs/reference/features/ directory contains technical documentation for major features.

  • collaborative-editing.md - Real-time collaborative editing implementation, covering WebSocket communication architecture, state synchronization, conflict resolution, presence and awareness, and permission management (Owner/Writer/Reader roles).
  • dfd-user-interaction-guide.md - DFD editor user interaction patterns, covering node and edge manipulation, selection and multi-select, keyboard shortcuts, context menus, and visual feedback.
  • status-severity-priority-values.md - Status, severity, and priority value definitions.

Key Reference Files

  • collaboration-protocol.md - Complete WebSocket collaboration protocol specification, including roles (Host, Participant, Presenter), message types, session lifecycle, undo/redo mechanics, and security considerations.
  • tmi-openapi.json - OpenAPI 3.0.3 specification for the REST API.
  • tmi-asyncapi.yml - AsyncAPI specification for WebSocket real-time collaboration.
  • tmi.arazzo.yaml / tmi.arazzo.json - Arazzo workflow specifications with PKCE OAuth flows.

For detailed API specifications, see API-Specifications.

Validation Framework [Frontend: tmi-ux]

The tmi-ux ThreatModel validation system provides client-side validation for threat model objects, ensuring they conform to the OpenAPI specification and maintain internal consistency. The system is flexible and extensible, supporting different diagram types and validation requirements.

Basic Usage

Service Injection

import { ThreatModelValidatorService } from './validation';

@Component({...})
export class ThreatModelComponent {
  constructor(private validator: ThreatModelValidatorService) {}
}

Validation Methods

// Full validation (schema + diagrams + references + custom rules)
const result = this.validator.validate(threatModel);

if (result.valid) {
  console.log('Threat model is valid!');
} else {
  console.log('Validation errors:', result.errors);
  console.log('Warnings:', result.warnings);
}

// Schema-only validation (faster, useful during editing)
const schemaResult = this.validator.validateSchema(threatModel);

// Reference-only validation (useful for incremental validation)
const refResult = this.validator.validateReferences(threatModel);

Validation Configuration

import { ValidationConfig, FieldValidationRule } from './validation';

const customRules: FieldValidationRule[] = [
  {
    field: 'name',
    required: true,
    type: 'string',
    minLength: 5,
    maxLength: 100,
    pattern: /^[A-Za-z0-9\s-]+$/,
  },
  {
    field: 'custom_field',
    required: false,
    customValidator: (value, context) => {
      if (value && !value.startsWith('PREFIX_')) {
        return ValidationUtils.createError(
          'INVALID_PREFIX',
          'Custom field must start with PREFIX_',
          context.currentPath + '.custom_field',
        );
      }
      return null;
    },
  },
];

const config: Partial<ValidationConfig> = {
  includeWarnings: true,  // Include warnings in result (default: true)
  failFast: false,        // Stop on first error (default: false)
  maxErrors: 50,          // Maximum errors before stopping (default: 100)
  customRules,            // Additional field validation rules
};

const result = this.validator.validate(threatModel, config);

Custom Diagram Validators

You can extend validation for new diagram types by implementing the DiagramValidator interface:

import { DiagramValidator, ValidationContext, ValidationError } from './validation';

class CustomDiagramValidator implements DiagramValidator {
  diagramType = 'CUSTOM-2.0.0';
  versionPattern = /^CUSTOM-2\.\d+\.\d+$/;

  validate(diagram: any, context: ValidationContext): ValidationError[] {
    const errors: ValidationError[] = [];

    if (!diagram.customProperty) {
      errors.push(
        ValidationUtils.createError(
          'MISSING_CUSTOM_PROPERTY',
          'Custom diagrams must have customProperty',
          ValidationUtils.buildPath(context.currentPath, 'customProperty'),
        ),
      );
    }

    return errors;
  }

  validateCells(cells: any[], context: ValidationContext): ValidationError[] {
    // Custom cell validation logic
    return [];
  }
}

// Register the custom validator
this.validator.registerDiagramValidator(new CustomDiagramValidator());

Validation Results

The validation result provides detailed information about validation status.

interface ValidationResult {
  valid: boolean;                    // Overall validation status
  errors: ValidationError[];         // Blocking errors
  warnings: ValidationError[];       // Non-blocking warnings
  metadata: {
    timestamp: string;               // ISO 8601 timestamp
    validatorVersion: string;        // Validator version (1.0.0)
    duration: number;                // Validation time in milliseconds
  };
}

interface ValidationError {
  code: string;                      // Error code for programmatic handling
  message: string;                   // Human-readable message
  path: string;                      // JSONPath to the field
  severity: 'error' | 'warning' | 'info';
  context?: Record<string, unknown>; // Additional context data
}

Error Codes Reference

Schema Validation Errors

Code Description
FIELD_REQUIRED Required field is missing
INVALID_TYPE Field type doesn't match expected type
INVALID_ENUM_VALUE Value is not in allowed enum values
MIN_LENGTH_VIOLATION String/array is too short
MAX_LENGTH_VIOLATION String/array is too long
PATTERN_MISMATCH String doesn't match required pattern

Reference Validation Errors

Code Description
INVALID_DIAGRAM_REFERENCE Threat references non-existent diagram
INVALID_CELL_REFERENCE Threat references non-existent cell
INVALID_THREAT_MODEL_REFERENCE Threat has wrong threat_model_id
OWNER_NOT_IN_AUTHORIZATION Owner not present in authorization list
ORPHANED_CELL_REFERENCE Cell ID specified without diagram ID
ORPHANED_THREATS Threats not associated with any diagram

Diagram Validation Errors

Code Description
UNSUPPORTED_DIAGRAM_TYPE No validator found for diagram type
INVALID_CELL Cell object is malformed
MISSING_CELL_ID Cell is missing required ID
INVALID_CELL_ID Cell ID is not a valid UUID
MISSING_SHAPE Cell is missing shape property
INVALID_CELL_TYPE Cell shape is not a valid node or edge type
DUPLICATE_CELL_IDS Multiple cells have same ID
INVALID_EDGE_SOURCE Edge source references non-existent cell
INVALID_EDGE_TARGET Edge target references non-existent cell
SELF_REFERENCING_EDGE Edge source equals target
MISSING_POSITION Node missing position coordinates
MISSING_SIZE Node missing size dimensions
INVALID_DIMENSIONS Node dimensions below minimum requirements

System Errors

Code Description
VALIDATION_EXCEPTION Internal validation error
MAX_ERRORS_EXCEEDED Too many errors found

Common Usage Patterns

Import Validation

async importThreatModel(file: File) {
  try {
    const data = JSON.parse(await file.text());
    const result = this.validator.validate(data);

    if (!result.valid) {
      this.showValidationErrors(result.errors);
      return;
    }

    await this.threatModelService.importThreatModel(data);
  } catch (error) {
    console.error('Import failed:', error);
  }
}

Real-time Validation

@Component({...})
export class ThreatModelEditComponent {
  private validationSubject = new Subject<any>();

  ngOnInit() {
    // Debounced validation for performance
    this.validationSubject.pipe(
      debounceTime(500),
      distinctUntilChanged(),
      switchMap(threatModel =>
        of(this.validator.validateSchema(threatModel))
      )
    ).subscribe(result => {
      this.validationErrors = result.errors;
      this.validationWarnings = result.warnings;
    });
  }

  onThreatModelChange(threatModel: any) {
    this.validationSubject.next(threatModel);
  }
}

Performance Considerations

  1. Use schema-only validation (validateSchema) during editing for faster feedback.
  2. Use full validation (validate) before saving or importing.
  3. Debounce validation in real-time scenarios to avoid excessive processing.
  4. Configure maxErrors to limit processing when many errors are expected.
  5. Use failFast for quick feedback when only the first error matters.

Extending the System

Adding New Diagram Types

  1. Implement the DiagramValidator interface.
  2. Define validation rules for the new type.
  3. Register the validator with registerDiagramValidator().
  4. The system automatically handles version patterns (e.g., DFD-1.0.x).

Adding Custom Field Validation

  1. Create FieldValidationRule objects.
  2. Include them in the validation config via customRules.
  3. Use the customValidator function for complex logic.

Next Steps

Home

Releases


Getting Started

Deployment

Operation

Troubleshooting

Development

Integrations

Tools

API Reference

Reference

Clone this wiki locally