System Architecture¶
Overview¶
Floh is a three-tier application consisting of an Angular frontend, a Fastify REST API backend, and a relational database with Redis for job queuing.
Component Diagram¶
┌─────────────────┐ ┌──────────────────────┐ ┌──────────────┐
│ Angular 21 UI │────▶│ Fastify REST API │────▶│ PostgreSQL / │
│ (PrimeNG) │ │ (Node.js 24) │ │ MySQL │
└─────────────────┘ │ │ └──────────────┘
│ ┌─────────────────┐ │ ┌──────────────┐
│ │ Workflow Engine │ │────▶│ Redis │
│ │ BullMQ Scheduler │ │ │ (BullMQ) │
│ └─────────────────┘ │ └──────────────┘
│ │ ┌──────────────┐
│ ┌─────────────────┐ │────▶│ SMTP Server │
│ │ Notification Svc │ │ └──────────────┘
│ └─────────────────┘ │ ┌──────────────┐
│ │────▶│ OIDC Provider│
└──────────────────────┘ └──────────────┘
Backend Modules¶
| Module | Responsibility |
|---|---|
| auth | OIDC JWT validation, RBAC guards |
| users | User CRUD, role assignment |
| workflows | Definition CRUD, run lifecycle |
| tasks | Step/task management |
| approvals | Approval routing and decisions |
| notifications | Email and in-app notifications |
| connectors | Connector registry and execution |
| scheduler | Cron-based workflow triggers |
| audit | Immutable audit logging |
| reports | Aggregate statistics queries |
| health | System health check |
Data Flow: Workflow Execution¶
- User starts a workflow run via POST
/workflows/:id/start - The engine creates step records from the definition
- Each step is executed based on its type:
- action — runs immediately, stores result
- approval — creates approval records, pauses workflow
- notification — sends email and in-app notification (internal users resolved by ID; external addresses supported)
- connector — invokes registered connector with timeout
- condition — evaluates expression, sets branch variable
- On step completion, the engine checks transitions to determine the next step
- On error, the engine follows the
onErrorstrategy (stop/skip/retry) - When all steps complete, the run is marked completed
Escalation Flow¶
- An approval step is created with a timeout
- BullMQ schedules a delayed job for the deadline
- If the deadline passes without a decision, the escalation fires
- The step is reassigned to the escalation target
- Both parties are notified
Database Schema¶
13 tables: user, role, permission, user_role, role_permission, workflow_definition, workflow_run, workflow_step, approval, notification, connector_definition, audit_log, scheduled_trigger.
All primary keys are UUIDs. JSON data (steps, variables, config) is stored as TEXT columns and serialized/deserialized in the repository layer.
Public Portal Architecture¶
The public portal enables external users (invitees, task assignees, approvers) to interact with Floh without direct access to the firewalled admin interface.
┌───── Firewall ─────┐
│ │
┌──────────────┐ ┌─────────────┐ ┌──────────────────────┐ ┌──────────────┐
│ Portal SPA │──▶│ Portal BFF │──▶│ Fastify REST API │──▶│ PostgreSQL / │
│ (Angular) │ │ (Fastify │ │ (Node.js 24) │ │ MySQL │
│ │ │ stateless │ │ │ └──────────────┘
│ port 4201 │ │ proxy) │ │ │ ┌──────────────┐
│ │ │ port 3001 │ │ │──▶│ Redis │
└──────────────┘ └─────────────┘ └──────────────────────┘ └──────────────┘
Portal BFF (Backend-for-Frontend)¶
The Portal BFF is a stateless Fastify proxy that sits outside the firewall. It has no direct database, Redis, or OIDC connections, minimizing its attack surface.
| Responsibility | Implementation |
|---|---|
| Route whitelisting | Only a predefined set of user-facing API patterns are forwarded |
| Scope enforcement | scope=all is stripped from tasks/approvals so portal users only see their own |
| Header injection | Adds X-Portal-Origin so the internal server redirects to the portal after auth |
| Rate limiting | @fastify/rate-limit protects against abuse |
| Security headers | @fastify/helmet with strict CSP |
| CORS | Locked to the portal frontend URL |
Portal SPA¶
A minimal Angular application with only the routes external users need:
/welcome— landing page/dashboard— pending invitations, tasks, and approvals/tasks— task inbox (tasks and approvals, alwaysscope=mine)/invitations/respond— accept or decline invitations/auth/callback— OIDC callback handler
The portal SPA has no sidebar, admin panel, workflow designer, or any administrative functionality.
Portal-Aware Auth Redirects¶
When the BFF proxies auth requests, it injects an X-Portal-Origin header. The internal server checks this header against ALLOWED_PORTAL_ORIGINS and redirects to the portal frontend (instead of the admin frontend) after login, logout, and errors.
For more details, see the Portal Guide.
Deployment¶
Docker Compose¶
The default stack includes: - postgres — PostgreSQL 16 - redis — Redis 7 - server — Fastify API (port 3000) - web — Angular app via nginx (port 80) - mailhog — SMTP test server (port 8025)
Use docker-compose.mysql.yml as an override for MySQL deployments.
Portal Compose¶
The portal extends the default stack with: - portal-bff — Stateless proxy (port 3001) - portal-web — Portal SPA via nginx (port 4201)
Environment Configuration¶
All configuration is via environment variables. See .env.example for the full list.