Consent Docs

Tenancy and access

How data is kept apart by project and property, how the console and the keys are scoped, and what the database enforces.

Concept4 min readFor architects, security reviewers and auditors

Every stored row belongs to one project, and every request is tied to one project before it reads anything. Three things keep projects apart: the session, the key and the database's own constraints.

The hierarchy#

Organisation, account and project come from the PIEDAP IAM service. Rows of configuration, records and requests carry all three ids. The first level the product owns is the property: one site or app.

  • Purposes, policies, settings, webhook endpoints, rights requests, the breach register, anchors and the audit log belong to the project.
  • Forms, region rules, scans and consent records belong to a property.
  • A key belongs to the project, or to one property.

Three tables differ. The audit log may leave the ids empty, a leaf tombstone carries only the project, and an idempotency record carries the project and the key.

How the console is scoped#

  • Sign-in is handled by IAM. The API verifies the session cookie's signature and loads the session from Redis. A failed check clears the cookies.
  • The console takes the organisation, account and project from the session, never from request parameters, and filters on the project.
  • A state-changing request must carry an X-Requested-With header or a bearer token. A cross-site form cannot send either, which protects against CSRF.
  • Credentialed cross-origin requests are answered only for the origins the operator configured and for PIEDAP domains.
  • The development shortcut that skips sign-in refuses to start outside development or test mode.

Access is granted per project, then per role

The operator decides in IAM who can open each project. Inside a project, every console route checks the permission it needs against the person's role. Until an owner turns roles on, everyone in the project has every permission. See Access and roles.

How keys are scoped#

A key belongs to a project and, optionally, to one property. A key tied to a property serves only that property's forms, and reads or changes a consent record by its id only on that property. A call that names another property is refused with 403 PROPERTY_NOT_FOR_KEY. Calls on a person as a whole are not limited to the key's property. They reach the person's records on every property of the project.

Limit Applies to Effect
Allowed origins Publishable key A live key answers only the listed origins and app ids
Scopes Secret key Calls on a person as a whole need a scope granted to that key
Environment Both A consent write made with a test key reaches only test records, and one made with a live key only live ones. Calls on a person as a whole stay in the key's environment
Operator approval Both A new key is a test key. It becomes live when the operator approves it

The endpoints that approve live keys are for the operator only. They take a separate platform key, compared in constant time, and answer 503 when none is configured.

What the database enforces#

Foreign keys inside the hierarchy include the project id. A row cannot point at another project's data, even if a query forgot to filter. Keys that must stay on one site also include the property id. Changing a referenced id is always refused.

Rule Effect
A form, version or property with consent records cannot be deleted The evidence keeps the wording it points at
A record names the property of its form and a version of that form A record cannot cite another site's notice
A region rule points at a form of its own property A site cannot serve another site's form
Event numbers are unique within a record Two events cannot claim the same place in a chain
One anchor per project per day A day cannot be anchored twice

Two links use a single column: a form's policy and a record's region rule. The application checks the project when it writes them.

Take a project with two properties, a shop website and a mobile app. A publishable key tied to the website cannot read the app's forms or its consent records. A region rule on the website cannot point at one of the app's forms, because the database refuses the row.

What the service enforces#

These rules are checked by the application, not by a constraint.

  • One record per person per form, per environment. A database lock serialises concurrent first answers. An erased record is left as it is, and a new answer starts a new record.
  • A consent is written against its form's property. A request that arrives on one property and names another property's form is refused with FORM_NOT_ON_PROPERTY.
  • A published version is not edited. Changes go to the draft, and publishing makes a new version.
  • Events are not updated or deleted while their record exists. The demo data generator is the one exception, and it is refused in production unless the operator allows it.

Limits#

  • A role cannot be widened for one person. Six fixed roles cover the work, and a person holds one of them.
  • There is no "person" table. A person is whatever shares an identifier, so a lookup returns every record that matches the hash you give.
  • The audit log is append-only and each entry is sealed to the one before it. It is not anchored daily as consent evidence is.

↑↓ to move, Enter to open