CMP Portal — Dev Guide
Last updated: 2025-09-21 (Consolidated under docs/cmp)
A minimal admin portal for managing CMP Sites and publishing live configurations. Uses shared React auth hooks (@digiwedge/hooks-auth-web) against the IDP and stores tokens in @digiwedge/global-state. The UI is wrapped in a single providers component (theme + RTL + toasts), with a nested route shell for a consistent admin-grade UX.
Environment
VITE_REGISTRY_URL— points to the Registry API base (/api).- Recommended for local dev:
http://localhost:3318/api(matches the Registry app default). - Note: the portal code has a fallback of
http://localhost:3410if this var is not set. Always setVITE_REGISTRY_URLexplicitly to avoid surprises.
- Recommended for local dev:
VITE_CDN_CMP_URL(optional; CDN URL for the drop‑in bundle to populate snippet + SRI lookup)VITE_SITE_KEY(defaultDEV_SITE_KEY)- Auth (IDP hooks):
VITE_IDP_BASE_URL— base URL for IDP REST API, e.g.http://localhost:3101- Add to
index.htmlhead:<meta name="idp-audience" content="cmp-portal" />(audience is sent asX-Client-Audienceduring login/refresh) - Tokens persist in storage under keys:
accessToken,refreshToken,jti.useRehydrateTokens()reads these on app start. - Dev bypass option (for local only): set
VITE_BYPASS_AUTH=1and set the Registry envDEV_ADMIN_BEARERto a static value. The Portal will gate routes by the bypass flag and the Registry will accept that static bearer on/api/admin/*when not in production.
Create apps/cmp/portal/.env.local:
VITE_REGISTRY_URL=http://localhost:3318/api
VITE_CDN_CMP_URL=http://localhost:8080/dw-cmp.min.js
VITE_SITE_KEY=DEV_SITE_KEY
# IDP login (hooks)
VITE_IDP_BASE_URL=http://localhost:3101
# Dev bypass (local only)
VITE_BYPASS_AUTH=1
# If using static dev bearer, configure Registry's DEV_ADMIN_BEARER to match
Run
pnpm nx serve cmp-portal
Features
-
Login with IDP (PKCE). Requires the
cmp.adminscope/role.- Uses
@digiwedge/hooks-auth-webfor login, refresh, logout, and optional MFA. Tokens are persisted by@digiwedge/global-stateand rehydrated on load. - Routes:
/loginand/signuprender shared AntD pages from@digiwedge/ui-antd. - The header Login button navigates to
/login; Logout uses the hook’suseLogout().
- Uses
-
Shell and navigation
- Nested routes under an AppShell with header + sider; header shows environment badge, theme switch, language switch (LTR/RTL), and a debounced Site Select (remote search).
- Public Home loads first (
/→/home). Protected routes live under<Protected><AppShell/></Protected>. - Lightweight route progress bar appears during navigation.
- Status pages:
/403(Forbidden), catch-all/404(NotFound); 500s are handled by an ErrorBoundary.
-
Sites
- URL-backed search/pagination; skeleton and empty states.
- “Create Site” drawer with inline validation (key/name/domain), optimistic success: newly created site is selected in the header.
- Drop‑in snippet copier, verification commands, key rotation, and overrides drawer.
-
Analytics
- Tabs: Overview, Cookies, Vendors, Artifacts, Audit.
/analyticsnow renders a dashboard:- Consent funnel stacked area chart (7/30/90d or custom range) with CSV export and filters for site, region, and bucket (day/hour).
- Experiment summary bars (variant sessions, accept rate %, p50/p95 decision latency) with CSV export and variant selector.
- Filters are URL-backed; charts skeleton‑load, handle empty states, and respect registry availability.
- Diagnostics adds:
- Consent Mode v2 checklist + simulator
- Consent Coverage (7d) card (TCF/GPP string coverage, daily)
- Experiments card (A/B demo results; views, accept%, adjust%)
- Auth & access diagnostics now surface IDP status (IDLE/OK/MFA/FAIL) and an Access Control latency badge once capability checks run.
-
Config
- List view with URL‑backed search/pagination.
- Upsert (Create/Edit) drawer with inline validation and optimistic updates; Delete with confirmation.
- Editor remains available at
/config/editwhen needed for advanced use.
-
Sites page lists Sites with actions:
- Copy snippet — copies the drop‑in script tag with
data-site-keyanddata-config-url; includes SRI from${CDN}.sriwhen available. - Copy verify cmds — copies a set of curl commands to check security headers and cookies, plus a one‑liner to run the scanner with
CMP_REGISTRY_URLfor classification. - Rotate key — rotates the site key; you must update installed snippets.
- The last selected Site is remembered in
localStorageundercmp.portal.lastSiteKey.
- Copy snippet — copies the drop‑in script tag with
-
Consent analytics widget — per site, last 7/30 days: total, accept all, reject all, partial, and GPC %.
-
Export consents — CSV/JSON/JSONL export by date range with optional filters (region, GPC).
-
Manage overrides — drawer to search/list/paginate overrides; CSV export/import; scope filters (global/site).
-
Reclassify — classify a host and save/remove overrides (site-aware).
-
Vendors helper — add/remove hosts with categories and optional
src; CSV import (with template); suggestion via classifier. -
Config editor:
- Publish new live config with updated category defaults and vendor map.
- Localization & Policy editor — edit
ui.strings(title, description, button labels, policy link). - Policy Block generator — copyable HTML snippet for your cookie policy page.
Cookies analytics tab
- A dedicated Cookies tab shows aggregated cookie evidence per site with filters:
- Range (7d/30d/custom), First‑party vs Third‑party, Category, Min confidence, Search (name/domain).
- “Review queue” lists unknown/low‑confidence cookies (no values), with 1‑click actions:
- Accept as Definition (global pattern) → POST
/api/admin/cookies/definitions:batch - Override for this site → POST
/api/admin/cookies/overrides
- Accept as Definition (global pattern) → POST
- Flags (Secure/HttpOnly/SameSite) and retention are displayed; counts and last seen are provided for triage.
Notes
- Cookie values are never stored; only names and attributes. Categories and vendors are inferred by definitions/overrides and safe heuristics.
Vendors analytics tab
- Time-series rollup of cookie evidence grouped by vendor.
- Controls: bucket (
hourorday) and date range; multi-select vendors; line or stacked-area modes. - Shows a “Top vendors” table for the selected range. Click vendors to focus the chart.
- Backed by
GET /api/admin/analytics/sites/by-key/:siteKey/vendors/rollup?bucket=hour|day&range=….
Artifacts tab
- Lists recent export artifacts with filename, size, created time, and a copy/download action.
- Backed by
GET /api/admin/exports?siteKey=&limit=&offset=andGET /api/admin/exports/:artifactId. - Includes pagination and bulk delete. Select rows and click “Delete selected” to remove.
- Delete uses
DELETE /api/admin/exports/:artifactId(tenant‑checked).
Audit tab
- Shows recent admin actions (AuditLog) with filters:
- Quick action chips (e.g., overrides upsert/delete, scans baseline promote, exports start, sites domains add/remove/copy).
- Date range and action selector.
- Click a row to open a details drawer (full JSON).
- Export CSV with current filters via
GET /api/admin/audit/logs/export.
Exports page (streamed + jobs)
- Two modes:
- Streamed export: downloads CSV/JSON/JSONL directly with progress for large responses.
- Job export: starts an async job, polls for completion, then exposes an artifact for download and history.
- Endpoints used:
- Start:
POST /api/admin/exports/consents:start→{ jobId } - Poll:
GET /api/admin/exports/jobs/:id→{ status, artifactId? } - Download/list:
GET /api/admin/exports/:artifactIdandGET /api/admin/exports?siteKey=&limit=&offset=
- Start:
- CSV export for Cookies tab uses
GET /api/admin/analytics/sites/by-key/:siteKey/cookies/export?…and respects the active filters.
How it talks to the Registry
- Loads live config via
GET /v1/config?site_key=...&v=live. - Publishes via
POST /admin/sites/by-key/:siteKey/configswithpublish: true. - Lists Sites via
GET /admin/sites?limit=50&offset=0(Site Select remote search usesq,page,pageSize). - Consent analytics via
GET /admin/analytics/sites/by-key/:siteKey/consents. - Consent coverage (Diagnostics) via
GET /api/admin/analytics/consent?siteKey=&from=&to=. - Experiments demo events via
POST /api/admin/analytics/experiments/eventand results viaGET /api/admin/analytics/experiments. - Export via
GET /admin/analytics/sites/by-key/:siteKey/consents/export?format=csv|json&range=.... - Config list and upsert rely on
/admin/configendpoints (GET/POST/PUT/DELETE) — adjust paths if your Registry uses different routes.
All admin routes require a Bearer token from your IDP. The token must include cmp.admin via one of: scope, roles (CMP_ADMIN or cmp.admin), or permissions.
UI Routes Overview
Public routes (no auth required):
/→ redirects to/home/home— landing page with quick links and scanner info/scanner— public scanner view/health— environment and health widgets/login,/signup— authentication and registration pages (AntD)
Protected routes (require Bearer token; guard by cmp.admin where applicable):
/sites— Sites list and actions; header maintains selected site/analytics— raw list view (URL‑backed search/pagination)/scans— Saved scans list with a Failed queue and status/requeue actions/overrides— Overrides manager (RBAC:cmp.admin)/export— Exports page (streamed + jobs)/scans— Saved scans list/config— Config list with upsert drawer (create/edit/delete)/config/edit— Advanced editor when needed
Notes
- Routes above live under a nested AppShell that provides header, sidebar, theme, language and Site Select.
- Navigation shows a slim progress bar.
- 403, 404, and 500 UX are provided (Forbidden, NotFound, ErrorBoundary).
Auth Redirects
- Protected routes use a guard that preserves deep links via
redirectTo:- Visiting a protected route while logged out redirects to
/login?redirectTo=<original>. - After login, the user is sent back to the original route.
- Visiting a protected route while logged out redirects to
- Public pages (
/login,/signup,/home,/scanner,/health) are wrapped in a public‑only guard:- If already authenticated, the user is redirected to
redirectToor the last protected route, then/sites.
- If already authenticated, the user is redirected to
- Last route persistence:
- The shell stores the last protected route under
localStorage['cmp.portal.lastRoute']and uses it whenredirectTois absent.
- The shell stores the last protected route under
Health
- The Health page probes only public endpoints and does not require a token:
- Registry:
GET /health/liveness,GET /health/ready,GET /health/config - IDP (optional):
GET ${VITE_IDP_BASE_URL}/health
- Registry:
- Each check shows a simple OK/DOWN pill with a Retry button.