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 one permission level. requireAuth() checks the dedicated Console session. Every admitted user has the same rights. The Auth0 admission policy must exclude customer accounts; see Console architecture for the rollout evidence requirement.

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-organisation — org_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

  • Activities — actors is the single most-imported core domain from the activity layer (21 imports), ahead of documents.
  • Core — actors has an in-degree of 13 inside core.
  • Accounting — createAccountingImpact 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.