Consenti

Backend — Admin Dashboard

Consenti ships a full admin SPA (Preact + Tailwind CSS) baked directly into the @consenti/api package. No separate deployment or build step is needed — it is served from dist/dashboard/ inside the package.

Enabling the dashboard

ts
createConsenti({
  storage: { driver: 'node:sqlite', path: './consenti-data' },
  auth: { mode: 'local', adminEmail: '[email protected]', adminPassword: process.env.CONSENTI_ADMIN_PASSWORD! },
  dashboard: true,   // ← enables the SPA
})

The dashboard is then available at /consenti/ (or your configured basePath).

Dashboard sections

SectionDescriptionAccess
DashboardConsent overview, timeline chart, country breakdown, GPC statsAll users
ProfilesCreate / edit / copy / delete / activate / deactivate consent profilesAll users
Profile HistoryEdit history viewer — every save increments the profile's version in place (stable id); compare locale JSON across past versionsAll users
Consent TemplatesReusable parameter definitions plus the categories that own their legal basisAll users
UI TemplatesReusable banner + modal layout settingsAll users
ConsentsBrowse, filter, and export consent records; per-visitor historyAll users
VisitorsVisitor list with geographic data (IPs are SHA-256 hashed, never raw)All users
UsersAdmin user management with tenant scopingAll users
RolesRBAC roles and fine-grained permission assignmentAll users
SitesMulti-tenant site managementSuperadmin only
TCF VendorsIAB Global Vendor List (when tcf.enabled: true)All users
Audit LogAppend-only log of all admin actions, never deleted by ConsentiAll users
Settings / APIAPI keys, branding, OpenAPI docsSuperadmin only
Setup WizardOne-time first-run welcome / config / default-profiles / confirmation flow, shown once after installAll users
ℹ️The Sites and API/Settings menu items are hidden for non-superadmin users. TCF Vendors is hidden unless tcf.enabled: true.

First-run setup wizard

The first time any admin logs in, the dashboard shows a 4-step setup wizard at #/setup instead of the regular dashboard — welcome, a read-only view of the resolved server config (secrets redacted), an accordion to install any of the 8 default compliance profiles, and a confirmation step with a production-readiness checklist (JSON storage driver / default credentials warnings, otherwise only visible in the server console). It is gated by tenant_settings.setup_completed, set once the wizard finishes or is skipped — it never reappears afterward, and there is no way to re-open it from the dashboard.

Auth modes

ModeConfigDescription
localauth: { mode: 'local', adminEmail, adminPassword }Built-in email + password. Passwords hashed with scrypt via node:crypto. Brute-force protection built in.
jwtauth: { mode: 'jwt', masterSecret }Validate externally issued JWTs (HS256). Your auth service issues tokens; Consenti verifies them.
oidcauth: { mode: 'oidc', oidc: { issuer, clientId, ... } } OpenID Connect Authorization Code + PKCE. Supports RS256 / ES256 from any OIDC provider (Auth0, Keycloak, Google).
samlauth: { mode: 'saml', saml: { issuer, entryPoint, cert, ... } } SAML 2.0 SP-initiated SSO. Requires samlify peer dep.
customauth: { mode: 'custom', validateUser: async (req) => AdminUser | null } Bring your own authentication. Consenti still enforces RBAC on the returned user's roles.

Profile creation wizard

Creating a profile follows a 4-step wizard:

Step 1 — Profile metadata

FieldTypeDescription
namestringHuman-readable profile name
defaultLocaleBCP 47 (searchable select)Locale used when visitor locale is unavailable; written as default.json
complianceGroupradio card gridRegulation group for geo-routing — selects one of the 8 built-in groups
customComplianceGroupstring (lower-kebab-case)Required under the "None / Custom" option — the identifier the widget's compliance.type targets to fetch this profile. Drives activation and one-active-per-group conflict detection like complianceGroup; no GPC/compliance-rule defaults apply since none exist for a custom name
gpcMode'ignore' | 'honor' | 'strict'GPC signal handling. Overrides the compliance group default.
expiryDaysnumberDays until consent expires and the visitor is asked again — profile-wide. Default: 365.
darkModebooleanEnable dark mode in the consent banner
hidePoweredByboolean (default: checked/hidden)Hide "Powered by Consenti" badge — checked by default, matching the widget's own default for a profile that never sets this field
allowReceiptbooleanAllow visitors to download a PDF consent receipt
allowedOriginsstring[]Allowlisted domains for CORS on this profile's consent endpoints
complianceConfigRecord<string, string>Per-compliance extra config (e.g. DPDPA data fiduciary name). Only shown when the compliance group requires it.

Step 2 — Consent Template

Select or create a Consent Template — parameters and the categories that own their legal basis, edited together. Clicking Next calls POST /admin/profiles/validatewith the template's cookies, categories, and the chosen compliance group:

  • Compliance errors (red): block advancing to Step 3 until resolved
  • Compliance warnings (amber): show an acknowledgment checkbox — must be checked to proceed, but do not block saving

The parameter table's Pre Grant column is editable per-profile — toggling it writes a delta into ProfileConfig.cookiesOverriderather than changing the Consent Template itself, so other profiles sharing that template are unaffected. It's locked checked-and-disabled for any parameter whose category's legal basis isn't consent (mandatory/legitimate-interest parameters are already effectively pre-granted, matching the same lock used at template-authoring time).

Selecting a compliance group (or a Consent Template, if the group is already selected) auto-defaults every eligible parameter's pre-grant:

  • opt-out / opt-out-strict: force off — an amber banner explains pre-grant was overridden for compliance
  • every other group: force onfor parameters the template didn't already pre-grant

Only parameters whose effective value actually differs from the template's authored one get a delta — the override stays minimal. Manually toggling a checkbox afterward works the same way and is preserved independently of the auto-default.

Step 3 — UI Template and locale authoring

Select or create a UI Template. New templates start blank; click Load Defaults in the amber callout to populate a starter structure.

  • + Add locale opens a searchable BCP 47 locale selector
  • Non-default locale tabs show the default locale's copy as placeholder text in heading, subheading, and category-heading inputs
  • Import / Export: available above the wizard card on every content step (Main Banner / GPC Banner / Preference Modal), covering the whole profile regardless of which step is active. JSON exports { "en": { "mainBanner.heading": "...", "category.necessary.heading": "...", ... }, "fr-FR": {...} } — a flat dot-path key/value map per locale the profile has content for, with button/category columns keyed by their template ids. CSV exports one row per every supported BCP 47 locale (not just ones with content) so translators never hand-type a locale code, with the same dot-path keys as CSV columns; blank rows on import are skipped

Step 4 — Content and readability

Enter localised copy (heading, body HTML) for the main banner, GPC banner, and preference modal categories. Inline advisory warnings appear when:

  • Heading exceeds 80 characters
  • Average sentence length in body exceeds 25 words
  • Total word count in body exceeds 150 words

These are informational only — the profile can still be saved.

Profile activation and hot-serve

Profiles are inactive after creation. To serve them via geo-routing:

  1. Click Activate in the profile list or call POST /admin/profiles/:id/activate
  2. Consenti copies locale JSON files from ${profileId}/${version}/ to ${complianceGroup}/
  3. The static file route serves these files immediately — zero DB on the hot path

Only one profile per compliance group can be active. Activating a new profile automatically deactivates the existing one.

Profile edit history

A profile's id is stable across every edit — each save increments its version in place and writes a new resolved-JSON snapshot under that version number, so the full edit history stays inspectable on disk without keeping old DB rows around. The history page shows:

  • Left panel: list of all versions, newest first, with dates
  • Right panel: prettified JSON for the selected version; locale switcher dropdown

Deleting a profile removes its DB row but not its on-disk version snapshots — the Archived Profiles page (linked from the Profiles list) finds those orphaned profile-id directories from a directory listing alone and opens the same history viewer for them.

Template save safety

When saving a Consent Template or UI Template that is used by one or more profiles, a confirmation dialog lists the affected profiles. You must confirm before changes are applied.

  • Deleting a Consent Template used by active profiles is blocked (422) — deactivate the profiles first
  • Removing a parameter from a Consent Template, or leaving a parameter in zero/multiple categories, is blocked by save-time validation

Serving behind a reverse proxy

nginx.conf
nginx
location /consenti/ {
    proxy_pass http://localhost:3001/consenti/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}
ℹ️When behind a reverse proxy that terminates HTTPS, add the proxy IP to trustedProxies in createConsenti() so that IP hashing and rate limiting use the real client IP from X-Forwarded-For.