unifiedAuth.js middleware bridges the Directus identity to the AnythingLLM Prisma user, so the creator authenticates once and gains access to the full platform.
Auth middleware chain
Every authenticated request passes through three middleware layers in order:unifiedAuth.js
Extracts the Bearer token and attempts Directus JWT validation first. On success, it fetches the
subscription_tier and anythingllm_user_id fields via the admin token (these fields are restricted by the Base Policy and cannot be fetched by the user’s own token). It then bridges to the AnythingLLM Prisma user via anythingllm_user_id, attaching both user objects:req.directusUser— read by all GenieHelper custom endpoint handlersres.locals.user— read by all upstream AnythingLLM native endpoint handlersres.locals.multiUserMode = true
validatedRequest.js
The upstream AnythingLLM multi-user guard. In multi-user mode (which is always the case in GenieHelper), it fully delegates to
unifiedAuth. This ensures the Directus JWT works transparently across all routes, including the upstream AnythingLLM workspace and chat endpoints.subscriptionValidator.js
Runs
canPerform(userId, operationType, directusClient) for quota-gated operations. Reads user_usage_counts from Directus and compares against config/tier_rate_limits.json. This check is fail-closed — any error in the quota check (network failure, missing record, unexpected exception) returns allowed: false, reason: 'quota_check_failed'. The operation is denied, not permitted.Directus → AnythingLLM bridge
Creator accounts exist in two systems: Directus (the CMS and primary identity store) and AnythingLLM (the agent runtime with its own Prisma database). The bridge links them.| Field | Collection | Purpose |
|---|---|---|
anythingllm_user_id | Directus directus_users | Stores the Prisma user ID from AnythingLLM’s database. Set at registration time by register.js after both users are created. |
subscription_tier | Directus directus_users | Creator’s current tier (starter, creator, pro, studio). Read during auth to determine quota limits and feature access. |
anythingllm_user_id is missing on a valid Directus user, unifiedAuth returns HTTP 503 with code ANYTHINGLLM_USER_MISSING. This indicates a broken account state requiring the RBAC sync webhook to be re-run.
RBAC roles
Directus roles control both data access (via item-level policies) and feature access (via tier-based feature flags in the React frontend).Base Policy
Applied to all subscriber tiers (Starter, Creator, Pro, Studio). Grants read/write access to owned collections with
user_id=$CURRENT_USER row filters. Cannot access other creators’ data.Pro Policy
Applied to Pro and Studio tiers in addition to Base Policy. Unlocks extended fan CRM fields, advanced analytics collections, and higher quota limits defined in
tier_rate_limits.json.Admin bypass
The
poweradmin role bypasses all quota checks. Additionally, if ANTHROPIC_API_KEY is set in server/.env, the admin gets Claude API access (Anthropic) instead of the local Ollama models.Per-user workspace isolation
Enforced at the Directus policy layer —
user_id=$CURRENT_USER row filter is applied to all creator-owned collections. Data isolation does not depend on application-level logic. Do not remove these filters.Subscription quota enforcement
subscriptionValidator.js implements a canPerform() function that gates quota-limited operations.
- Reads
user_usage_countsfrom Directus for the authenticated user — the current billing cycle’s operation counts - Reads
config/tier_rate_limits.jsonfor the user’s subscription tier - Compares current usage against the tier limit for the requested
operationType - Returns
{ allowed: boolean, reason: string, remaining: number }
{ allowed: false, reason: 'quota_check_failed' }. This is intentional — the alternative (fail-open) would silently over-serve users when the quota system has an error.
The operation types that are quota-gated:
| Operation type | Description |
|---|---|
caption_generation | AI caption generation via /api/captions/generate |
scheduled_queue_size | Maximum posts in the scheduled queue at one time |
chat_messages | Chat messages to the agent workspace per billing cycle |
scrape_runs | Platform scrape jobs per billing cycle |
broadcast_sends | Message broadcasts per billing cycle |
Credential encryption
Platform credentials (OnlyFans session tokens, Fansly cookies, Instagram auth, etc.) are encrypted with AES-256-GCM per user before storage. The encryption key never leavesserver/.env.
supabase_vault PostgreSQL extension and accessed via the credentials.js endpoint (POST /api/credentials to store, GET /api/credentials to retrieve). The endpoint enforces that a user can only read their own credentials — the req.directusUser.id from unifiedAuth is used as the isolation key, not any value from the request body.
Required environment variables
| Variable | File | Purpose |
|---|---|---|
CREDENTIALS_ENC_KEY_B64 | server/.env | Base64-encoded 256-bit AES-GCM key for platform credential encryption. Losing this key means losing access to all stored credentials. |
DIRECTUS_ADMIN_TOKEN | server/.env | Used exclusively by register.js and rbacSync.js — pre-auth flows that require admin access to create users and sync roles. Stale tokens cause 401 errors on registration. |
MCP_SERVICE_TOKEN | server/.env | Scoped service token used by all other server-side Directus reads and writes. Lower privilege than DIRECTUS_ADMIN_TOKEN. Used by unifiedAuth to fetch restricted user fields and by all endpoint handlers for Directus operations. |
RBAC_SYNC_WEBHOOK_SECRET | server/.env | Shared secret validating the Directus → rbacSync.js webhook. Must match the secret configured in the Directus Flow that fires on user role changes. |
Registration flow
Registration is invite-gated. New accounts are created byregister.js (POST /api/register):
Invite validation
The registration request must include a valid invite token. Rate-limited to 10 requests per 15 minutes per IP via
express-rate-limit.Directus user creation
A new Directus user is created with the appropriate role for the selected tier, using the
DIRECTUS_ADMIN_TOKEN. The default tier is controlled by the DEFAULT_NEW_USER_TIER environment variable (defaults to pro).AnythingLLM user creation
A corresponding Prisma user is created in the AnythingLLM database. The Prisma user ID is written back to the Directus user as
anythingllm_user_id.Workspace provisioning
workspaceProvisioner.js creates a private AnythingLLM workspace for the new creator, fetches the prime_directive system prompt from Directus system_config, and applies it to the workspace.