Run and configure AccessFlow.
An operator and admin guide — how to install AccessFlow in different environments, how to start it for the first time, and how to configure every entity that drives the review pipeline: users, roles, datasources, review plans, AI, OAuth, SMTP, and notification channels. Everything here is sourced from the engineering chapters and the project README.
Read this first
AccessFlow is a self-hosted SQL proxy. Your team connects to AccessFlow instead of directly to PostgreSQL, MySQL, MariaDB, Oracle, or Microsoft SQL Server (or any other JDBC-compatible engine, via an admin-uploaded driver). Every statement is parsed, classified, optionally AI-reviewed, and routed through a configurable human-approval workflow before it touches the database. Customer-database credentials never leave the proxy.
This guide is split into three parts: running AccessFlow (pick one of three deployment modes), first-time setup (browser wizard or GitOps env vars), and configuration (every entity an admin manages through the UI or REST API).
Running AccessFlow
Pick one of three modes. Docker Compose is the fastest path to a running instance. Helm is the production-recommended path on Kubernetes. The manual / from-source path is for contributors and for environments where containers aren't available.
Docker Compose
The repo root ships a zero-config demo stack — it pulls the published images from GHCR and starts Postgres + Redis + backend + frontend with insecure demo keys baked in so a fresh clone runs with one command:
git clone https://github.com/bablsoft/accessflow.git cd accessflow docker compose up -d # open http://localhost:5173 — the in-app setup wizard creates the first admin
docker-compose.yml embeds insecure
JWT_PRIVATE_KEY and ENCRYPTION_KEY defaults inline so it
works on a fresh clone. Do not deploy this to anything but a sandbox.
For real environments, use the production-style compose below or the Helm chart.
For a production-style compose, generate real keys and supply them via
.env:
# 32-byte hex — AES-256-GCM for datasource credential encryption ENCRYPTION_KEY=$(openssl rand -hex 32) # RSA-2048 PEM — JWT RS256 signing key JWT_PRIVATE_KEY="$(openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 2>/dev/null)" DB_PASSWORD=change-me CORS_ALLOWED_ORIGIN=https://accessflow.company.com ACCESSFLOW_PUBLIC_BASE_URL=https://accessflow.company.com
See docs/09-deployment.md
→ Docker Compose for the full production-style compose (including the optional
ollama profile for self-hosted AI).
Kubernetes & Helm
The Helm chart ships at charts/accessflow/ and is published to
https://bablsoft.github.io/accessflow. Pre-create the required Secrets
(the chart will refuse to start if they're missing or empty):
# 1. add the chart repo helm repo add accessflow https://bablsoft.github.io/accessflow helm repo update # 2. create namespace + Secrets kubectl create namespace accessflow kubectl -n accessflow create secret generic accessflow-encryption-key \ --from-literal=ENCRYPTION_KEY="$(openssl rand -hex 32)" kubectl -n accessflow create secret generic accessflow-jwt-key \ --from-file=JWT_PRIVATE_KEY=<(openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048) kubectl -n accessflow create secret generic accessflow-pg-secret \ --from-literal=DB_PASSWORD="$(openssl rand -base64 24)" # 3. install helm install accessflow accessflow/accessflow -n accessflow -f values.yaml
The chart bundles Bitnami subcharts for Postgres and Redis (toggle off with
postgresql.enabled=false / redis.enabled=false to point at
external instances). Full values reference and the Secret schema:
docs/09-deployment.md
→ Kubernetes & Helm and
charts/accessflow/README.md.
Manual / from source
For contributors and air-gapped builds. Requires JDK 25 and Node.js 24 on the host:
git clone https://github.com/bablsoft/accessflow.git cd accessflow # 1. infrastructure — Postgres 18 + Redis 8 + Mailcrab (dev-only compose) docker compose -f backend/docker-compose-dev.yml up -d # 2. backend cd backend ./mvnw spring-boot:run # 3. frontend (in another shell) cd frontend npm install npm run dev # open http://localhost:5173
For full coding standards, test commands, and the dev loop, see docs/11-development.md.
First-time setup
There are two ways to bring up a brand-new AccessFlow deployment.
Option A — Browser setup wizard
The default. Open the frontend (http://localhost:5173 for the demo
stack, or whatever URL serves the SPA in your environment). When no organization
exists yet, the app routes to /setup and walks you through:
- Create the organization.
- Create the first admin user (email + password).
- Optional: configure system SMTP so invitation emails work.
- Optional: add a first datasource and a default review plan.
After the wizard finishes, log in as the admin and finish configuring the system from the admin pages — see Configuration below.
Option B — Bootstrap via env vars (GitOps)
Set ACCESSFLOW_BOOTSTRAP_ENABLED=true and supply
ACCESSFLOW_BOOTSTRAP_* properties for the organization, admin user,
review plans, AI configs, datasources, SAML, OAuth2, notification channels, and
system SMTP. On every startup the bootstrap module reconciles the declared
configuration into the database — declared rows are upserted, omitted rows are left
alone.
Configuration
Everything below is admin-only. Each entity can be managed through the admin pages of
the SPA or through the REST API directly (full payload reference in
docs/04-api-spec.md).
All API calls require an Authorization: Bearer <JWT> header.
Users
Local users are created directly by an admin. Federated users (SAML / OAuth) are auto-provisioned on first sign-in if the feature is enabled (see SAML and OAuth).
Create a user directly — POST /api/v1/admin/users:
{
"email": "alice@company.com",
"password": "InitialPassword123!",
"display_name": "Alice Reviewer",
"role": "REVIEWER"
}
Invite a user by email — POST /api/v1/admin/users/invitations
generates a one-time signup token and emails it. The token expires after
ACCESSFLOW_SECURITY_INVITATION_TTL (default 7 days).
Other endpoints: GET /api/v1/admin/users (paginated list),
PUT /api/v1/admin/users/{id} (change role or active flag),
DELETE /api/v1/admin/users/{id} (deactivate).
User roles & RBAC
AccessFlow has four roles. Pick the lowest-privilege role for each user — admins are the only role that can change configuration. A user can never approve their own query, regardless of role.
| Capability | READONLY |
ANALYST |
REVIEWER |
ADMIN |
|---|---|---|---|---|
Submit SELECT queries | ✓ | ✓ | ✓ | ✓ |
Submit DML (INSERT / UPDATE / DELETE) | — | ✓ | ✓ | ✓ |
Submit DDL (CREATE / ALTER / DROP) | — | — | — | ✓ |
| View own query history | ✓ | ✓ | ✓ | ✓ |
| View all queries in the org | — | — | ✓ | ✓ |
| Approve / reject queries | — | — | ✓ | ✓ |
| Manage datasources | — | — | — | ✓ |
| Manage users | — | — | — | ✓ |
| Manage review plans | — | — | — | ✓ |
| View audit log | — | — | — | ✓ |
| Manage notification channels | — | — | — | ✓ |
| Configure AI | — | — | — | ✓ |
| Configure SAML / OAuth | — | — | — | ✓ |
Which role for what. Use READONLY for people who only
need to look at production data (analysts, on-call engineers reading dashboards).
Use ANALYST for people who write data through reviewed queries.
Use REVIEWER for people who approve other users' queries — typically
senior engineers or DBAs. Use ADMIN for the platform-team operators who
configure the system itself.
Datasource-level permissions. Role is the org-wide ceiling. On top of
it, every user needs an explicit row in datasource_user_permissions to
access a given datasource — that row controls can_read /
can_write / can_ddl per datasource, row caps, allowed
schemas / tables, and restricted columns (which are masked as *** in
SELECT results). See
docs/07-security.md
for the full authorization matrix.
Datasources
A datasource describes one customer database that AccessFlow proxies. PostgreSQL,
MySQL, MariaDB, Oracle, and MS SQL Server are bundled; any other JDBC engine works by
uploading its driver JAR and setting db_type=CUSTOM.
Create a datasource — POST /api/v1/datasources:
{
"name": "Production PostgreSQL",
"db_type": "POSTGRESQL",
"host": "db.company.com",
"port": 5432,
"database_name": "app_prod",
"username": "accessflow_svc",
"password": "encrypted at rest",
"ssl_mode": "REQUIRE",
"require_review_reads": false,
"require_review_writes": true,
"review_plan_id": "uuid of a review plan",
"ai_analysis_enabled": true,
"ai_config_id": "uuid of an ai_config"
}
password is AES-256-GCM encrypted on write and is never returned in any
response. The plaintext is decrypted once during HikariCP pool init and never stored
on the heap beyond that.
Grant a user access — POST /api/v1/datasources/{id}/permissions
with { user_id, can_read, can_write, can_ddl, allowed_schemas, allowed_tables,
restricted_columns, expires_at }. Without a permission row, a user cannot see
or query the datasource at all.
Custom JDBC drivers. Upload a driver JAR with
POST /api/v1/datasources/drivers (multipart: vendor_name,
target_db_type, driver_class,
expected_sha256). The SHA-256 is re-verified on every pool init and
each driver runs in an isolated classloader. The on-disk cache path is
ACCESSFLOW_DRIVER_CACHE (default ~/.accessflow/drivers) —
mount this as a persistent volume in Kubernetes.
Review plans
A review plan is a per-datasource policy that decides how a submitted query reaches execution. Each plan describes a sequence of stages; each stage names approvers (by role or explicit user) and a minimum number of approvals before the query advances.
Create a plan — POST /api/v1/admin/review-plans:
{
"name": "Production writes — two reviewers",
"description": "DML on prod needs senior + DBA sign-off.",
"requires_ai_review": true,
"requires_human_approval": true,
"min_approvals_required": 2,
"approval_timeout_hours": 24,
"auto_approve_reads": true,
"approvers": [
{ "role": "REVIEWER", "stage": 1 },
{ "role": "ADMIN", "stage": 2 }
]
}
Query status transitions. A query moves through these states:
PENDING_AI → PENDING_REVIEW → APPROVED → EXECUTED
↘ REJECTED (manual reviewer rejection)
↘ TIMED_OUT (approval-timeout auto-reject)
PENDING_REVIEW → CANCELLED (submitter only)
APPROVED → FAILED (execution error)
A single REJECTED decision at any stage terminates the query. If a query
sits in PENDING_REVIEW past the plan's approval_timeout_hours,
QueryTimeoutJob auto-rejects it. The job's cadence is configurable via
ACCESSFLOW_WORKFLOW_TIMEOUT_POLL_INTERVAL (default PT5M).
auto_approve_reads=true lets SELECTs skip human approval
entirely; requires_ai_review=true still scores the read but won't block
on a human.
AI configurations
AI analysis is per-organization. AccessFlow ships adapters for three providers — pick one that fits your data-egress policy:
- Anthropic — default model
claude-sonnet-4-20250514. - OpenAI (or any OpenAI-compatible endpoint) — default model
gpt-4o. - Ollama — self-hosted, set
endpointto the Ollama server URL.
Set the AI config — PUT /api/v1/admin/ai-configs:
{
"provider": "ANTHROPIC",
"model": "claude-sonnet-4-20250514",
"api_key": "sk-ant-..."
}
For Ollama, add "endpoint": "http://ollama:11434" (and leave
api_key empty). Provider switches take effect immediately — no restart
is required. Verify the connection with
POST /api/v1/admin/ai-configs/{id}/test before relying on it in a
review plan.
LOW / MEDIUM / HIGH / CRITICAL),
and a list of issues categorised as anti-patterns, missing indexes, restricted-column
access, etc. The score is informational — only human reviewers can finalize approval
unless the plan opts in to auto_approve_reads.
Notification channels
AccessFlow fires notifications when a query is submitted, approved, rejected, times
out, or scores CRITICAL on AI risk. Channels are configured per
organization; multiple channels of the same type can coexist (e.g. one Slack channel
per team).
Three channel types — POST /api/v1/admin/notification-channels:
- Email —
configcarriessmtp_host,smtp_port,smtp_user,smtp_password,smtp_tls,from_address,from_name. Falls back to the system SMTP when omitted. - Slack —
configcarries an incoming-webhookwebhook_url, optionalchanneloverride, optionalmention_users. Messages use Slack Block Kit so the SQL preview and action buttons render natively. - Webhook —
configcarriesurl,secret(used to computeX-AccessFlow-Signature: sha256=...),timeout_seconds,retry_attempts.
Sensitive config fields (smtp_password, webhook_secret) are
AES-256-GCM encrypted at rest and are never returned in GET responses.
Verify a channel with
POST /api/v1/admin/notification-channels/{id}/test — the dispatcher
sends a synthetic event and reports delivery success.
+30s, +2 min, +10 min. Tune the
delays via ACCESSFLOW_NOTIFICATIONS_RETRY_FIRST /
_SECOND / _THIRD. Failed deliveries log ERROR
but never affect query workflow state.
System SMTP
System SMTP is the fallback for transactional emails — password resets, invitations, and any notification email channel that doesn't carry its own SMTP overrides. Configure once per org:
{
"host": "smtp.company.com",
"port": 587,
"username": "smtp-user",
"password": "encrypted at rest",
"tls": true,
"from_address": "accessflow@company.com"
}
Verify with POST /api/v1/admin/system-smtp/test — the server tries a
real send to a sink address you supply. Remove with
DELETE /api/v1/admin/system-smtp if you'd rather rely solely on
per-channel email config.
OAuth 2.0 / OIDC
Sign-in with Google, GitHub, Microsoft, and GitLab is built-in — operators only need
to register an OAuth app with the provider and store the client id / secret in
AccessFlow. Other OIDC providers work the same way; the configuration lives in the
database (oauth2_config rows), not in application.yml, so
adding a new provider does not require a restart.
- Register an OAuth app at the provider; set the redirect URI to
{ACCESSFLOW_PUBLIC_BASE_URL}/api/v1/auth/oauth/{provider}/callback. - From the admin UI, paste the
client_id+client_secretand pick the built-in template (Google / GitHub / Microsoft / GitLab) or fill in the OIDC discovery URL for a custom provider. - Set
ACCESSFLOW_OAUTH2_FRONTEND_CALLBACK_URLif the frontend lives at a different origin than the backend's CORS origin (default is{CORS_ALLOWED_ORIGIN}/auth/oauth/callback). - Optional: enable auto-provisioning so first-time sign-ins create a user with a default role.
Email verification is required before an OAuth account is linked, and account-linking is conservative — a provider-supplied email will not silently take over an existing local user. Implementation details: docs/07-security.md → Authentication.
SAML 2.0 SSO
For SAML-based SSO (Okta, Azure AD, OneLogin, etc.) configure one record per org via
PUT /api/v1/admin/saml-config:
{
"sp_entity_id": "https://accessflow.company.com/saml",
"idp_metadata_url": "https://idp.company.com/saml/metadata",
"keystore": "base64-encoded JKS or P12",
"keystore_password":"encrypted at rest"
}
Both SP-initiated and IdP-initiated flows are supported. Attribute mapping (email, display name, role) and auto-provisioning behaviour are configurable; SAML users rely on the IdP's MFA rather than AccessFlow's TOTP. Full SP metadata, signing-cert rotation, and assertion validation rules live in docs/07-security.md.
Further reading
This page covers what an operator needs to run and configure AccessFlow. For deeper internals — module boundaries, the proxy engine, the audit log's HMAC chain, the full REST + WebSocket spec — see the engineering chapters in the docs/ folder on GitHub:
- 02 · Architecture — subsystems, technology stack, request flow.
- 04 · REST API — every endpoint, request body, and WebSocket event.
- 05 · Backend — proxy engine, workflow state machine, AI analyzer internals.
- 07 · Security — auth, authorization matrix, encryption, audit integrity.
- 08 · Notifications — event types, channel configs, signed webhook payloads.
- 09 · Deployment — full env-var reference, Helm values, Bootstrap GitOps tree.