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¶
flowchart LR
UI["Angular 21 UI<br/>PrimeNG"]
subgraph api["Fastify REST API (Node.js 24)"]
Engine["Workflow Engine<br/>BullMQ Scheduler"]
Notify["Notification service"]
end
PG[("PostgreSQL / MySQL")]
Redis[("Redis")]
SMTP["SMTP Server"]
OIDC["OIDC Provider"]
UI --> api
Engine --> PG
Engine --> Redis
Notify --> SMTP
api --> OIDC
Backend Modules¶
| Module | Responsibility |
|---|---|
| auth | OIDC JWT validation, RBAC guards, client (azp) channel |
| 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¶
Core tables: user, workflow_definition, workflow_run, workflow_step, approval, notification, connector_definition, audit_log, scheduled_trigger. Authorization is not persisted — permissions come from the access token scope claim.
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. Both Authifi BFF processes sit outside the firewall; the API and datastores stay inside. The public console hostname proxies /api and /bff to console-bff, not to the API process.
flowchart LR
PortalSPA["Portal SPA<br/>:7073"]
PortalBFF["Portal BFF<br/>Authifi 3.3.0 :7071"]
ConsoleSPA["Admin SPA<br/>:7072"]
ConsoleBFF["Console BFF<br/>Authifi 3.3.0 :7074"]
subgraph fw["Firewall"]
API["Fastify REST API<br/>Node.js 24 :7070"]
PG[("PostgreSQL / MySQL")]
Redis[("Redis")]
end
PortalSPA --> PortalBFF --> API
ConsoleSPA --> ConsoleBFF --> API
API --> PG
API --> Redis
Portal BFF (Backend-for-Frontend)¶
The Portal BFF is the Authifi BFF 3.3.0 gateway that sits outside the
firewall. It is the portal OIDC relying party, owns /bff/* login/logout/
session routes, and proxies browser /api/* calls to the Floh API while
attaching the access token server-side. Browser JavaScript never reads the
portal access token or refresh token.
| Responsibility | Implementation |
|---|---|
| OIDC browser flow | /bff/login, /bff/callback, /bff/logout, /bff/session |
| API proxying | Browser /api/* goes through the BFF, which adds Bearer auth server-side |
| Token exposure guard | bff.exposeTokens=false keeps raw tokens out of browser code |
| WebSocket policy | bffProxy.ws=false |
| Path allowlisting | bffProxy.allowlist.enabled=false in the current shipped config; the cached allowlist follow-up is LSA-9831 |
| CSRF contract | floh_portal_csrf cookie and x-csrf-token header |
Console BFF¶
The admin console uses a second Authifi BFF 3.3.0 process (floh-client)
on port 7074. Browser cookies stay on the console hostname; the API hop is
Bearer-only. Live run execution-state WebSocket upgrades go through this BFF
(bffProxy.ws: true). Operators can ship console without portal secrets.
| Responsibility | Implementation |
|---|---|
| OIDC browser flow | /bff/login, /bff/callback, /bff/logout, /bff/session |
| API + WS proxying | Browser /api/* and execution-state WebSocket go through the BFF |
| Token exposure guard | bff.exposeTokens=false |
| CSRF contract | floh_console_csrf cookie and x-csrf-token on every HTTP method |
| Cookie names | Session floh_console_bff_sid — distinct from portal and leftover API floh_sid |
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¶
The console BFF is the console OIDC relying party (including execution-state WebSocket). The API is a Bearer-only resource server; browser sessions and refresh tokens are owned by the BFFs. Portal browser traffic terminates at portal-bff.
For more details, see the Portal Guide.
Deployment¶
Docker Compose¶
The default stack includes:
- postgres — PostgreSQL 16
- redis — Redis 7
- server — Fastify API (port 7070)
- console-bff — Authifi BFF
3.3.0gateway (port 7074) - web — Angular app via nginx (port 80)
- portal-bff — Authifi BFF
3.3.0gateway (port 7071) - portal-web — Portal SPA via nginx (port 7073)
- 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 — Authifi BFF
3.3.0gateway (port 7071) - portal-web — Portal SPA via nginx (port 7073)
Environment Configuration¶
All configuration is via environment variables. Portal browser traffic is always
HTTPS; Floh's committed defaults are HTTPS-first, and the Palantir deployment
keeps HTTPS on Caddy -> BFF and BFF -> API. Other deployments may terminate TLS
at a trusted ingress and explicitly choose private-network HTTP for internal
hops. See .env.example for the full list.