Platform Architecture
The platform layer adds cross-tenant management capabilities on top of Ironflow’s multi-tenant architecture. This document explains how the system works under the hood.
Platform vs Tenant Identity
Section titled “Platform vs Tenant Identity”Ironflow has two distinct identity domains:
All API keys (both tenant and platform) are stored in the unified api_keys table with ak_ ID prefix. The key value prefix (ifkey_ vs ifplatform_) is a cosmetic distinction. Platform roles are stored in the unified roles table.
Platform identity is global — a platform user or key can manage any tenant. Platform credentials are scoped to org_platform, a sentinel organization that exists solely to anchor platform resources in the database.
Tenant identity is organization-scoped — a tenant API key can only access resources within its own organization. Within an organization, resources are further organized into Projects, and each project contains one or more Environments. The hierarchy is: Organization → Project → Environment → Resources.
Authentication Flow
Section titled “Authentication Flow”Platform authentication supports two methods:
Email/Password → JWT
Section titled “Email/Password → JWT”The JWT payload includes a boolean platform claim ("platform": true), which the auth middleware uses to distinguish platform tokens from tenant tokens. See internal/server/jwt.go.
API Key
Section titled “API Key”Platform API keys use the ifplatform_ prefix (vs ifkey_ for tenant keys). Both go through the same unified auth middleware:
- Extract key from
Authorization: Bearer <key>header - SHA-256 hash the key, look up in the unified
api_keystable - Load assigned roles from
api_key_rolesjunction table - If the key belongs to
org_platform, setRequestContext.IsPlatform = true
Impersonation Model
Section titled “Impersonation Model”When a platform token includes the X-Ironflow-Org header, the request is “impersonated” — the platform identity acts within a tenant’s context:
Without the X-Ironflow-Org header, platform tokens are restricted to /api/v1/platform/* routes only. Any attempt to access tenant endpoints (like /api/v1/functions) without impersonation returns 403.
RBAC Model
Section titled “RBAC Model”Platform RBAC uses the same architecture as tenant RBAC but with its own action namespace:
Platform routes are protected by the RequirePlatform guard middleware in internal/platform/guard.go. CEL evaluation is wired natively at startup: rbaccel.NewEvaluator() is constructed in internal/server/policy_handlers.go and composed with the built-in RBAC evaluator via rbac.NewLayeredEvaluator() (internal/auth/rbac/evaluator.go). System RBAC (Layer 1) is authoritative for ALLOW; tenant-edited CEL (Layer 2) is subtractive deny-only. There is no license gate — all RBAC + CEL functionality is available unconditionally.
The three built-in platform roles (role_platform_admin, role_platform_operator, role_platform_viewer) ship with hardcoded permissions. platform_admin uses a wildcard (*) that matches all actions. Custom roles require explicit action grants via policies.
Audit Trail
Section titled “Audit Trail”Platform operations generate two types of audit records:
Platform Events
Section titled “Platform Events”Handler-level events for resource lifecycle:
| Event | Trigger |
|---|---|
platform.user.created/updated/deleted | User CRUD operations |
platform.key.created/revoked | Key creation, rotation, deletion |
platform.role.changed | Role create/update/delete |
platform.impersonated | Any impersonated request |
Authorization Decisions
Section titled “Authorization Decisions”The auth middleware logs every allow/deny decision with the full context:
- Platform user/key ID
- Requested action
- Impersonated org (if applicable)
- Result (allow/deny)
Recording
Section titled “Recording”Audit events are emitted asynchronously (go authaudit.Emitter().Emit*(...)) using context.WithoutCancel() to avoid cancellation from the HTTP request lifecycle. Events are stored in the audit_events table with scope = "platform".
Data Model
Section titled “Data Model”Platform resources are stored in unified tables alongside tenant resources, differentiated by org_id = org_platform.
platform_users ├── id (puser_*) ├── email (unique) ├── name ├── password_hash ├── is_active └── last_login_at
roles (unified — platform roles have org_id = org_platform) ├── id (role_platform_admin, role_platform_operator, role_platform_viewer) ├── org_id ├── name (unique per org) └── is_default (built-in flag)
policies (unified — platform policies have org_id = org_platform) ├── id (pol_*) ├── org_id ├── name, effect, actions, resources, condition └── (attached to roles via role_policies junction table)
user_roles (unified) ├── user_id → platform_users or users └── role_id → roles
api_key_roles (unified) ├── api_key_id → api_keys └── role_id → roles
api_keys (unified — platform keys have org_id = org_platform) ├── id (ak_*) ├── key_hash (SHA-256) ├── key_prefix (ifplatform_* or ifkey_*) └── org_id
audit_events (scope=platform) ├── id (ULID-shaped TEXT primary key) ├── environment_id ├── run_id ├── function_id ├── step_id ├── event_type (enum — see schema for full list) ├── payload (JSON) ├── metadata (JSON, nullable) ├── scope ('tenant' | 'platform', default 'tenant') ├── platform_key_id (nullable) ├── platform_user_id (nullable) ├── impersonated_org_id (nullable) └── created_atNext Steps
Section titled “Next Steps”- Managing Platform Users & Keys — create and manage platform credentials
- Impersonating Tenants — cross-tenant operations
- Platform RBAC — roles and permissions
- Platform API Reference — full endpoint documentation