Skip to main content

Auth, Tenancy & Actors

Three questions, three mechanisms: They meet in getOrganizationAndActorId(), which returns the second and third together.

Identity

Portal does not use Auth0. It is the one surface with its own scheme — share token plus postal-code verification, then a signed cookie bound to that token. See Portal. Console has a second gate. requireAuth() checks the session; requireSuperadmin() additionally checks isConsoleSuperadmin(subject).

Tenancy

Two ways to miss a tenant filter:
  1. csv_import_batches uses organization_id, not org_id. Tooling keyed on org_id skips it.
  2. Four tables have no tenancy column at all and are scoped only through a parent FK. A direct WHERE org_id = … is impossible; the query must join.
Console is cross-tenant by design — it reads across organisations and holds an Auth0 ManagementClient. Treat it as a stated exception, not a violation. See Console.

Actors

Every mutation is attributed. actors relates to 94 of 98 models, almost always as paired *_modified_by / *_deleted_by relations. Actors are per-organisationorg_id is non-null, so the same user acting in two organisations has two actor rows. Integration-created records get an actor labelled from the integration’s ACTOR_LABEL constant — which is why even the placeholder integrations define one.

Where attribution is enforced

  • Activitiesactors is the single most-imported core domain from the activity layer (21 imports), ahead of documents.
  • Coreactors has an in-degree of 13 inside core.
  • AccountingcreateAccountingImpact calls findOrCreateSystemActor at create-accounting-impact.ts:278, before any write.

Audit trails

Three separate tables, for different things: Security events go to a separate Axiom dataset, not the database — /admin and /dev access is logged from proxy.ts:251-259. See Observability.

Notes

getOrganizationAndActorId() is the pattern to copy. Returning both values from one call is what stops a developer scoping a query correctly but attributing the write to nobody. API keys are a fourth identity path. api_keys backs the partner API routes; Mirakl credentials are read through mirakl/get-api-credentials.ts. MFA is managed, not enforced here. 14 files under webapp app/lib/auth/ handle Auth0 user management including MFA enrolment, TOTP deletion, and password changes — the enforcement lives in Auth0, not in application code.