@consenti/api 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +212 -74
- package/dist/chunk-COR72NFQ.js +2 -0
- package/dist/chunk-COR72NFQ.js.map +1 -0
- package/dist/chunk-S2IEYESR.js +2 -0
- package/dist/chunk-S2IEYESR.js.map +1 -0
- package/dist/dashboard/assets/ApiConfig-CZMtYRfK.js +1 -0
- package/dist/dashboard/assets/{ArchivedProfileList-Bw-fx-BD.js → ArchivedProfileList-BlQ2gEWz.js} +1 -1
- package/dist/dashboard/assets/AuditLog-BpZoAFOe.js +1 -0
- package/dist/dashboard/assets/{ChangePassword-D2rPkzD1.js → ChangePassword-BZkP2dXQ.js} +1 -1
- package/dist/dashboard/assets/{ConfirmDialog-DTtw7VJ-.js → ConfirmDialog-BDlnfTdx.js} +1 -1
- package/dist/dashboard/assets/ConsentList-DgJMrhih.js +1 -0
- package/dist/dashboard/assets/ConsentTemplateEditor-DA7B5aI_.js +1 -0
- package/dist/dashboard/assets/ConsentTemplateList-DMPd2y5s.js +1 -0
- package/dist/dashboard/assets/HeadingTagInput-D2S9xDMJ.js +1 -0
- package/dist/dashboard/assets/{HowItWorks-AeyjfQGO.js → HowItWorks-CJCrdr3o.js} +1 -1
- package/dist/dashboard/assets/PermissionGate-C69dhwV1.js +1 -0
- package/dist/dashboard/assets/PreviewPane-D7mDeBT8.js +38 -0
- package/dist/dashboard/assets/ProfileEditor-CSFdOweN.js +2 -0
- package/dist/dashboard/assets/ProfileList-C4cIDrM6.js +1 -0
- package/dist/dashboard/assets/{ProfileVersionHistory-1h8sUgog.js → ProfileVersionHistory-CX5-Oupy.js} +2 -2
- package/dist/dashboard/assets/{RecordDetailModal-FaFhzQXi.js → RecordDetailModal-DIpGxfqA.js} +1 -1
- package/dist/dashboard/assets/Reports-B1Ww0ovT.js +1 -0
- package/dist/dashboard/assets/RoleList-CQ1GjR0W.js +1 -0
- package/dist/dashboard/assets/{Select-DjJNwlbY.js → Select-C_JusbT5.js} +1 -1
- package/dist/dashboard/assets/Settings-sIMVWQ0V.js +1 -0
- package/dist/dashboard/assets/SetupWizard-ChFBxqo3.js +1 -0
- package/dist/dashboard/assets/Table-BUxoz0YR.js +1 -0
- package/dist/dashboard/assets/TenantList-DCsT7ghb.js +1 -0
- package/dist/dashboard/assets/UITemplateEditor-BAaMnuOU.js +2 -0
- package/dist/dashboard/assets/UITemplateList-C3UMbnfs.js +1 -0
- package/dist/dashboard/assets/UserList-CHGNnS50.js +1 -0
- package/dist/dashboard/assets/VendorList-C-sswM1A.js +1 -0
- package/dist/dashboard/assets/VisitorList-DNB9Ia72.js +1 -0
- package/dist/dashboard/assets/check-D0g4XvmN.js +1 -0
- package/dist/dashboard/assets/client-CYntrO2g.js +1 -0
- package/dist/dashboard/assets/{copy-C01KoUUH.js → copy-DEi_W2TM.js} +1 -1
- package/dist/dashboard/assets/createLucideIcon-DAiYBrnd.js +1 -0
- package/dist/dashboard/assets/de-jwngydrC.js +25 -0
- package/dist/dashboard/assets/dist-GoiMJCTt.js +1 -0
- package/dist/dashboard/assets/es-B6Jp0OPe.js +25 -0
- package/dist/dashboard/assets/{external-link-DVe00ZBK.js → external-link-CFZxxe3W.js} +1 -1
- package/dist/dashboard/assets/fr-CshYcQPW.js +25 -0
- package/dist/dashboard/assets/index-Cr5M1Qy_.css +2 -0
- package/dist/dashboard/assets/index-Tr_BcjkN.js +26 -0
- package/dist/dashboard/assets/ja-CgjZNBrc.js +25 -0
- package/dist/dashboard/assets/{profiles-DmrdGv7t.js → profiles-C_SuoLJD.js} +1 -1
- package/dist/dashboard/assets/{roles-C0dJPFl4.js → roles-D7L936FV.js} +1 -1
- package/dist/dashboard/assets/rotate-ccw-clock-6y2NDrJ7.js +1 -0
- package/dist/dashboard/assets/search-DKwfLBOv.js +1 -0
- package/dist/dashboard/assets/templates-CIyMjskA.js +1 -0
- package/dist/dashboard/assets/{templates-B2xiivh4.js → templates-DY68xNys.js} +1 -1
- package/dist/dashboard/assets/{trash-2-BBnkdgm_.js → trash-2-eX9s6x8V.js} +1 -1
- package/dist/dashboard/assets/{trash-Dj1jhzHJ.js → trash-O30ycMcz.js} +1 -1
- package/dist/dashboard/assets/{useHashQuery-B5ytMIMp.js → useHashQuery-BDjUPXDR.js} +1 -1
- package/dist/dashboard/assets/{users-czK8LRVh.js → users-BRrNOK0W.js} +1 -1
- package/dist/dashboard/assets/x-BhZiH8AL.js +1 -0
- package/dist/dashboard/assets/zh-CN-BnLZkfBC.js +25 -0
- package/dist/dashboard/index.html +4 -4
- package/dist/dist-QJYW5KUK.js +2 -0
- package/dist/index.cjs +68 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +214 -52
- package/dist/index.d.ts +214 -52
- package/dist/index.js +65 -65
- package/dist/index.js.map +1 -1
- package/dist/profiles-FCX454HS.js +2 -0
- package/dist/widget/widget.css +1 -1
- package/dist/widget/widget.js +1 -1
- package/package.json +24 -9
- package/dist/chunk-IY4L6MNB.js +0 -2
- package/dist/chunk-IY4L6MNB.js.map +0 -1
- package/dist/chunk-O5UG4OYI.js +0 -2
- package/dist/chunk-O5UG4OYI.js.map +0 -1
- package/dist/dashboard/assets/ApiConfig-CE-Noelz.js +0 -1
- package/dist/dashboard/assets/AuditLog-D6HiTQ1o.js +0 -1
- package/dist/dashboard/assets/ConsentList-BWOvPOMG.js +0 -1
- package/dist/dashboard/assets/ConsentTemplateEditor-BKIEnLmA.js +0 -1
- package/dist/dashboard/assets/ConsentTemplateList-DCys2nM3.js +0 -1
- package/dist/dashboard/assets/HeadingTagInput-BTjrdzlS.js +0 -1
- package/dist/dashboard/assets/PermissionGate-Bhhf7SMh.js +0 -1
- package/dist/dashboard/assets/PreviewPane-Dq5K1h2L.js +0 -35
- package/dist/dashboard/assets/ProfileEditor-BXtaE0tL.js +0 -2
- package/dist/dashboard/assets/ProfileList-CWKSAgu0.js +0 -1
- package/dist/dashboard/assets/Reports-DWEjQOKh.js +0 -1
- package/dist/dashboard/assets/RoleList-BSGoqlzp.js +0 -1
- package/dist/dashboard/assets/Settings-CVYrlyLm.js +0 -1
- package/dist/dashboard/assets/SetupWizard-4NTb5bZj.js +0 -1
- package/dist/dashboard/assets/Table-DH_jh8_N.js +0 -1
- package/dist/dashboard/assets/TenantList-5AwFtoHf.js +0 -1
- package/dist/dashboard/assets/UITemplateEditor-9A55scBe.js +0 -1
- package/dist/dashboard/assets/UITemplateList-BS7X4u4F.js +0 -1
- package/dist/dashboard/assets/UserList-SPkMyFRy.js +0 -1
- package/dist/dashboard/assets/VendorList-C5fPhr6U.js +0 -1
- package/dist/dashboard/assets/VisitorList-COP1SyNd.js +0 -1
- package/dist/dashboard/assets/check-o5cp1s2J.js +0 -1
- package/dist/dashboard/assets/client-ymc9nwWr.js +0 -1
- package/dist/dashboard/assets/createLucideIcon-MdjI3m3u.js +0 -1
- package/dist/dashboard/assets/de-DET92Jb5.js +0 -5
- package/dist/dashboard/assets/dist-DlhLroYC.js +0 -1
- package/dist/dashboard/assets/es-CJNunrnU.js +0 -5
- package/dist/dashboard/assets/fr-akfTuf5x.js +0 -5
- package/dist/dashboard/assets/history-CHTts_m2.js +0 -1
- package/dist/dashboard/assets/index-DW3qOrry.css +0 -2
- package/dist/dashboard/assets/index-H_Zm0X-u.js +0 -6
- package/dist/dashboard/assets/ja-B0nLWkOd.js +0 -5
- package/dist/dashboard/assets/search-Bd2RdDRh.js +0 -1
- package/dist/dashboard/assets/templates-CnMoLavN.js +0 -1
- package/dist/dashboard/assets/x-BRuyBEQP.js +0 -1
- package/dist/dashboard/assets/zh-CN-Dxg3G-BL.js +0 -5
- package/dist/dist-Z5RVTVBT.js +0 -2
- package/dist/profiles-XTYPWLVC.js +0 -2
- /package/dist/{dist-Z5RVTVBT.js.map → dist-QJYW5KUK.js.map} +0 -0
- /package/dist/{profiles-XTYPWLVC.js.map → profiles-FCX454HS.js.map} +0 -0
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,32 @@ type DeepPartial<T> = T extends Function ? T : T extends Array<infer InferredArr
|
|
|
13
13
|
[Key in keyof T]?: DeepPartial<T[Key]> | null;
|
|
14
14
|
} : T;
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Single source of truth for all compliance groups, regulations, and country mappings.
|
|
18
|
+
*
|
|
19
|
+
* This is DATA, not types. The `Compliance` and `ComplianceGroupId` literal types
|
|
20
|
+
* derived from the ID arrays below live in @consenti/types (packages/types/src/compliance.ts),
|
|
21
|
+
* via a type-only import — that keeps @consenti/types free of runtime code.
|
|
22
|
+
*
|
|
23
|
+
* ─── HOW TO UPDATE ────────────────────────────────────────────────────────────
|
|
24
|
+
*
|
|
25
|
+
* ADD A NEW REGULATION:
|
|
26
|
+
* 1. Add regulation ID string to `COMPLIANCE_REGULATION_IDS` below
|
|
27
|
+
* (the `Compliance` type in @consenti/types auto-updates from it).
|
|
28
|
+
* 2. Add it to the appropriate group's `compliances` array below.
|
|
29
|
+
* 3. Add / update the country entry in `EMBEDDED_COMPLIANCE_MAP` below.
|
|
30
|
+
* That is all — no other files need touching.
|
|
31
|
+
*
|
|
32
|
+
* ADD A NEW COMPLIANCE GROUP:
|
|
33
|
+
* 1. Add the kebab-case ID to `COMPLIANCE_GROUP_IDS` below.
|
|
34
|
+
* 2. Add a full group entry to `COMPLIANCE_GROUPS` below (label, compliances, flags).
|
|
35
|
+
* 3. Add a validation rule set to `COMPLIANCE_VALIDATION_RULES` below.
|
|
36
|
+
* 4. Map affected countries in `EMBEDDED_COMPLIANCE_MAP` to the new group.
|
|
37
|
+
*
|
|
38
|
+
* ADD OR UPDATE A COUNTRY:
|
|
39
|
+
* Edit the entry in `EMBEDDED_COMPLIANCE_MAP.countries` below. That is the ONLY
|
|
40
|
+
* place that needs changing for country-level jurisdiction updates.
|
|
41
|
+
*/
|
|
16
42
|
declare const COMPLIANCE_GROUP_IDS: readonly ["opt-in", "opt-out", "opt-out-strict", "opt-in-dpdpa", "opt-in-china", "opt-in-brazil", "general-privacy-consent", "notice-only"];
|
|
17
43
|
declare const COOKIE_PURPOSE_IDS: readonly ["necessary", "functional", "preferences", "analytics", "marketing"];
|
|
18
44
|
|
|
@@ -42,6 +68,8 @@ interface LocaleContentInput {
|
|
|
42
68
|
mainBanner: MainBanner;
|
|
43
69
|
gpcBanner?: GpcBanner;
|
|
44
70
|
preferenceModal: PreferenceModal;
|
|
71
|
+
/** Only meaningful when the profile's `ageGate.enabled` is true. */
|
|
72
|
+
ageGateModal?: AgeGateModalContent;
|
|
45
73
|
}
|
|
46
74
|
interface StorageConfig {
|
|
47
75
|
driver: 'sqlite' | 'better-sqlite3' | 'node-sqlite3-wasm' | 'node:sqlite' | 'mongodb' | 'mysql' | 'postgresql' | 'json';
|
|
@@ -85,7 +113,7 @@ interface SamlConfig {
|
|
|
85
113
|
}
|
|
86
114
|
interface AuthConfig {
|
|
87
115
|
mode: 'local' | 'jwt' | 'custom' | 'oidc' | 'saml';
|
|
88
|
-
|
|
116
|
+
masterSecret?: string;
|
|
89
117
|
validateUser?: (req: unknown) => Promise<AdminUser | null>;
|
|
90
118
|
adminEmail?: string;
|
|
91
119
|
adminPassword?: string;
|
|
@@ -101,6 +129,14 @@ type GeoResult = {
|
|
|
101
129
|
country: string | null;
|
|
102
130
|
region: string | null;
|
|
103
131
|
locale: string | null;
|
|
132
|
+
/** Optional escape hatch: when set, `GeoResolverService` uses this compliance group directly
|
|
133
|
+
* instead of computing one from `country`/`region` against the jurisdiction map — for a
|
|
134
|
+
* provider that already carries legal-grade jurisdiction data (e.g. a paid geo-IP service) and
|
|
135
|
+
* needs to route a visitor into an operator-defined custom group the built-in map can't express.
|
|
136
|
+
* `country`/`region`/`locale` are still used for locale resolution and the `requiresSensitiveOptIn`
|
|
137
|
+
* region carve-out, which are independent of which group applies. Optional — omitting it (or
|
|
138
|
+
* returning `undefined`) preserves the existing country/region-map resolution exactly as before. */
|
|
139
|
+
complianceGroup?: string;
|
|
104
140
|
};
|
|
105
141
|
type CountryResolverFn = (ctx: {
|
|
106
142
|
ip: string;
|
|
@@ -178,31 +214,49 @@ interface OptInStats {
|
|
|
178
214
|
byLocale: Record<string, OptInByLocale>;
|
|
179
215
|
byDate: OptInByDate[];
|
|
180
216
|
}
|
|
181
|
-
type ComplianceMapOverride = 'default' | ComplianceMapData | 'auto';
|
|
182
217
|
interface ComplianceMapData {
|
|
183
218
|
version: string;
|
|
184
219
|
countries: Record<string, CountryComplianceEntry>;
|
|
185
220
|
}
|
|
186
221
|
interface CountryComplianceEntry {
|
|
187
222
|
complianceGroup: ComplianceGroupId;
|
|
188
|
-
|
|
189
|
-
|
|
223
|
+
/** Fallback group used when only the country (not the region) is known. Defaults to
|
|
224
|
+
* `complianceGroup` when omitted — only set this explicitly when a region within this
|
|
225
|
+
* country can be stricter than the country-wide baseline (see `overriddenRegions`). */
|
|
226
|
+
default?: ComplianceGroupId;
|
|
227
|
+
description?: string;
|
|
190
228
|
overriddenRegions?: Record<string, {
|
|
191
229
|
complianceGroup: ComplianceGroupId;
|
|
192
|
-
description
|
|
230
|
+
description?: string;
|
|
193
231
|
}>;
|
|
194
232
|
}
|
|
195
233
|
interface ComplianceConfig {
|
|
196
|
-
/**
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
ccpa?: boolean;
|
|
234
|
+
/** Server-side GPC enforcement on `POST`/`PUT /consent`: re-applies GPC-driven denial when a
|
|
235
|
+
* write carries `gpcDetected`, independent of the widget's own client-side `gpcMode` handling.
|
|
236
|
+
* `false` disables this defense-in-depth check entirely. Default: enabled. */
|
|
200
237
|
gpc?: boolean | 'strict';
|
|
201
238
|
type?: ComplianceGroupId | 'auto';
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
239
|
+
geoDataProvider?: 'default' | 'maxmind' | 'geoip' | 'hosted-geoip-lite' | CountryResolverFn;
|
|
240
|
+
/** `'default'` (embedded map, the default), a URL to fetch a JSON `ComplianceMapData` document
|
|
241
|
+
* from (refreshed in the background per the response's `Cache-Control`/`Expires` header, 24h
|
|
242
|
+
* fallback if absent), or an inline operator-supplied `ComplianceMapData` object. Invalid data
|
|
243
|
+
* from either a URL or an object logs an error and falls back to `'default'`. */
|
|
244
|
+
complianceMap?: 'default' | string | ComplianceMapData;
|
|
245
|
+
tcf?: TcfConfig;
|
|
246
|
+
gpp?: GppConfig;
|
|
247
|
+
/** Signs server-stored consent records (HMAC-SHA256 at create/update time, hex-encoded into
|
|
248
|
+
* `signature`, checked on `GET /consent/:visitorId/verify`), ownership cookies, and
|
|
249
|
+
* parental-consent request/resolve tokens (see `POST /consent/:visitorId/parental-consent-request`),
|
|
250
|
+
* and salts hashed IPs — independent of the widget's own cookie-signing
|
|
251
|
+
* (`core.cookieSigningKey`). Auto-generated in memory when unset, so all of the above are
|
|
252
|
+
* always active — but an auto-generated value isn't persisted, so a restart invalidates
|
|
253
|
+
* outstanding ownership cookies, in-flight parental-consent tokens, and (if compared across
|
|
254
|
+
* restarts) hashed-IP continuity. Set this explicitly for anything beyond local dev. */
|
|
255
|
+
dataSigningHash?: string;
|
|
256
|
+
dataRetention?: DataRetentionConfig;
|
|
257
|
+
/** Expiry window for parental-consent tokens issued by `POST /consent/:visitorId/parental-consent-request`.
|
|
258
|
+
* Default: 7 days. */
|
|
259
|
+
parentalConsentTokenTtlDays?: number;
|
|
206
260
|
}
|
|
207
261
|
interface DashboardConfig {
|
|
208
262
|
enabled?: boolean;
|
|
@@ -211,23 +265,35 @@ interface DashboardConfig {
|
|
|
211
265
|
interface MultiTenantConfig {
|
|
212
266
|
enabled: boolean;
|
|
213
267
|
}
|
|
214
|
-
interface AgeGateConfig {
|
|
215
|
-
enabled: boolean;
|
|
216
|
-
minimumAge: number;
|
|
217
|
-
requireParentalConsent?: boolean;
|
|
218
|
-
}
|
|
219
268
|
interface TcfConfig {
|
|
220
269
|
enabled: boolean;
|
|
221
270
|
cmpId: number;
|
|
222
271
|
cmpVersion: number;
|
|
272
|
+
/** ISO 3166-1 alpha-2 country code of the publisher (e.g. 'DE', 'FR'). Required for
|
|
273
|
+
* spec-correct binary TC-string encoding — without it, real encoding is skipped and
|
|
274
|
+
* Consenti falls back to the simplified (non-spec) TC string. No default: there is no
|
|
275
|
+
* honest country to assume on a publisher's behalf. */
|
|
276
|
+
publisherCC?: string;
|
|
277
|
+
}
|
|
278
|
+
/** IAB Global Privacy Platform (GPP), US National ("usnat") section only — the umbrella section
|
|
279
|
+
* most MSPA/programmatic US bidders read. Individual state sections (US-CA, US-CO, etc.) aren't
|
|
280
|
+
* encoded yet. Requires the optional `@iabgpp/cmpapi` peer dependency (see `gpp/gpp-string.ts`) —
|
|
281
|
+
* unlike TCF there's no simplified fallback format, since a non-spec GPP string has no consumer
|
|
282
|
+
* that would accept it: when the dependency isn't installed, `gppString`/`window.__gpp` are
|
|
283
|
+
* simply not produced. */
|
|
284
|
+
interface GppConfig {
|
|
285
|
+
enabled: boolean;
|
|
286
|
+
cmpId: number;
|
|
287
|
+
cmpVersion: number;
|
|
288
|
+
/** MSPA "covered transaction" — whether this deployment's data transactions fall under MSPA
|
|
289
|
+
* signatory obligations. No honest default, so it's required rather than optional. */
|
|
290
|
+
mspaCoveredTransaction: boolean;
|
|
291
|
+
/** IAB's tri-state encoding for both MSPA fields: 0 = not applicable, 1 = yes, 2 = no. */
|
|
292
|
+
mspaOptOutOptionMode: 0 | 1 | 2;
|
|
293
|
+
mspaServiceProviderMode: 0 | 1 | 2;
|
|
223
294
|
}
|
|
224
295
|
interface DataRetentionConfig {
|
|
225
296
|
purgeAfterDays: number;
|
|
226
|
-
/** When set, audit log entries older than this are purged on the same daily timer as consent
|
|
227
|
-
* record retention. Independent of `purgeAfterDays` since audit trails are often required to
|
|
228
|
-
* be kept longer than the consent records they reference for compliance/investigation
|
|
229
|
-
* purposes. Unset by default — audit logs are kept indefinitely unless explicitly configured. */
|
|
230
|
-
auditLogPurgeAfterDays?: number;
|
|
231
297
|
}
|
|
232
298
|
interface BrandingConfig {
|
|
233
299
|
/** Display name shown in the dashboard (login page, header, browser tab). Default: 'Consenti'. */
|
|
@@ -250,18 +316,10 @@ interface ConsentiServerConfig {
|
|
|
250
316
|
compliance?: ComplianceConfig;
|
|
251
317
|
multiTenant?: MultiTenantConfig;
|
|
252
318
|
plugins?: ConsentiServerPlugin[];
|
|
253
|
-
ageGate?: AgeGateConfig;
|
|
254
|
-
tcf?: TcfConfig;
|
|
255
|
-
dataRetention?: DataRetentionConfig;
|
|
256
319
|
maxBodySize?: number;
|
|
257
320
|
trustedProxies?: string[];
|
|
258
321
|
branding?: BrandingConfig;
|
|
259
322
|
s3Api?: S3ApiConfig;
|
|
260
|
-
/** When set, every consent record is HMAC-SHA256 signed at create/update time (hex-encoded,
|
|
261
|
-
* stored in `signature`) and the signature is checked on `GET /consent/:visitorId/verify`.
|
|
262
|
-
* No-op and no schema burden when unset — opt-in tamper-evidence for server-stored records,
|
|
263
|
-
* independent of the widget's own cookie-signing (`core.cookieSigningKey`). */
|
|
264
|
-
consentSigningKey?: string;
|
|
265
323
|
/** Called after every profile activate/deactivate/delete. isPurge=true means invalidate; false means warm. */
|
|
266
324
|
handleCache?: (paths: string[], profileId: string, isPurge: boolean) => void;
|
|
267
325
|
}
|
|
@@ -279,13 +337,40 @@ interface TenantSettings {
|
|
|
279
337
|
* fallback. The public API has no auth token, so this is its only access gate. */
|
|
280
338
|
allowedOrigins?: string[];
|
|
281
339
|
/** Admin API origin allowlist — an additional CORS-layer check on top of Bearer-token auth
|
|
282
|
-
* for browser-originated `/consenti/admin/*` requests. Empty/unset means no restriction
|
|
340
|
+
* for browser-originated `/consenti/admin/v1/*` requests. Empty/unset means no restriction
|
|
283
341
|
* (Bearer token auth alone still applies); only enforced when the request carries an
|
|
284
342
|
* `Origin` header — server-to-server callers are unaffected. */
|
|
285
343
|
adminAllowedOrigins?: string[];
|
|
286
344
|
/** Whether this tenant has completed (or skipped) the first-run setup wizard. Gates the
|
|
287
345
|
* one-time `#/setup` redirect in the dashboard — never reset once true. Default: false. */
|
|
288
346
|
setupCompleted?: boolean;
|
|
347
|
+
/** Whether `POST /setup/seed-profiles` has been called at least once for this tenant (an
|
|
348
|
+
* empty `groups: []` call counts). `POST /setup/complete` rejects until this is true, closing
|
|
349
|
+
* the bypass where the wizard's step-1 skip (or a direct API call) could mark setup complete
|
|
350
|
+
* without ever passing through config review or the profile step. */
|
|
351
|
+
profilesSeeded?: boolean;
|
|
352
|
+
/** Last-confirmed TCF `{cmpId, cmpVersion}` registration snapshot, hashed rather than stored
|
|
353
|
+
* in full — the live values always come from the static `compliance.tcf` config, never the DB.
|
|
354
|
+
* A hash mismatch (including never having confirmed) means TCF is treated as unconfirmed and
|
|
355
|
+
* fails closed: no `window.__tcfapi`, no TC string, a logged server error. Re-confirming in the
|
|
356
|
+
* dashboard (against the cached IAB CMP List) writes a fresh hash here. */
|
|
357
|
+
tcfConfirmation?: RegistrationConfirmation;
|
|
358
|
+
/** Same governance shape as `tcfConfirmation`, for the GPP US National section's own
|
|
359
|
+
* `compliance.gpp.{cmpId, cmpVersion}`. Self-attestation only — IAB doesn't publish a
|
|
360
|
+
* CMP-List equivalent for GPP, so there's no external registry to validate against. */
|
|
361
|
+
gppConfirmation?: RegistrationConfirmation;
|
|
362
|
+
}
|
|
363
|
+
/** Hash-based confirmation record — proves a specific `{cmpId, cmpVersion}` config was reviewed
|
|
364
|
+
* and confirmed as registered, without persisting the config values themselves in the DB. */
|
|
365
|
+
interface RegistrationConfirmation {
|
|
366
|
+
/** Hash of the confirmed `{cmpId, cmpVersion}` (and `publisherCC` for TCF). Compared against a
|
|
367
|
+
* fresh hash of the live static config on every check — any drift (including a first-ever
|
|
368
|
+
* check) means unconfirmed. */
|
|
369
|
+
configHash: string;
|
|
370
|
+
confirmedAt: string;
|
|
371
|
+
/** User id of the admin who checked the confirmation box. Undefined for confirmations made
|
|
372
|
+
* before this field existed. */
|
|
373
|
+
confirmedBy?: string;
|
|
289
374
|
}
|
|
290
375
|
interface ApiKey {
|
|
291
376
|
id: string;
|
|
@@ -323,9 +408,10 @@ interface ConsentDbRecord {
|
|
|
323
408
|
ageVerified?: boolean;
|
|
324
409
|
parentalConsentToken?: string;
|
|
325
410
|
tcfString?: string;
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
*
|
|
411
|
+
gppString?: string;
|
|
412
|
+
/** HMAC-SHA256 signature over the record's core fields, hex-encoded — tamper-evidence for
|
|
413
|
+
* server-stored records via `dataSigningHash` (auto-generated when unset), independent of the
|
|
414
|
+
* widget's own cookie-signing (`core.cookieSigningKey`). */
|
|
329
415
|
signature?: string;
|
|
330
416
|
createdAt: string;
|
|
331
417
|
updatedAt: string;
|
|
@@ -405,10 +491,11 @@ interface AuditLog {
|
|
|
405
491
|
createdAt: string;
|
|
406
492
|
}
|
|
407
493
|
/** List-view shape for consents — omits the fields not shown in a table row (`consentJson`,
|
|
408
|
-
* `parentalConsentToken`, `tcfString`, `signature`), so paginated list queries don't
|
|
409
|
-
* those blobs off disk for every row. Fetch the full `ConsentDbRecord` via
|
|
410
|
-
* when a single record's full detail is actually needed (e.g. opening a
|
|
411
|
-
|
|
494
|
+
* `parentalConsentToken`, `tcfString`, `gppString`, `signature`), so paginated list queries don't
|
|
495
|
+
* have to pull those blobs off disk for every row. Fetch the full `ConsentDbRecord` via
|
|
496
|
+
* `getConsent(visitorId)` when a single record's full detail is actually needed (e.g. opening a
|
|
497
|
+
* detail modal). */
|
|
498
|
+
type ConsentSummary = Omit<ConsentDbRecord, 'consentJson' | 'parentalConsentToken' | 'tcfString' | 'gppString' | 'signature'>;
|
|
412
499
|
/** List-view shape for audit logs — omits `oldData`/`newData` (full before/after snapshots) for
|
|
413
500
|
* the same reason as `ConsentSummary`. Fetch the full `AuditLog` via `getAuditLogById(id)` when a
|
|
414
501
|
* single entry's full detail is needed. */
|
|
@@ -443,6 +530,7 @@ interface CreateConsentInput {
|
|
|
443
530
|
ageVerified?: boolean;
|
|
444
531
|
parentalConsentToken?: string;
|
|
445
532
|
tcfString?: string;
|
|
533
|
+
gppString?: string;
|
|
446
534
|
signature?: string;
|
|
447
535
|
}
|
|
448
536
|
interface UpdateConsentInput {
|
|
@@ -639,7 +727,6 @@ interface StorageAdapter {
|
|
|
639
727
|
deleteApiKey(id: string): Promise<void>;
|
|
640
728
|
getApiKeys(tenantId: string): Promise<ApiKey[]>;
|
|
641
729
|
purgeExpiredConsents(olderThanDays: number): Promise<number>;
|
|
642
|
-
purgeExpiredAuditLogs(olderThanDays: number): Promise<number>;
|
|
643
730
|
createConsentTemplate(data: CreateConsentTemplateInput): Promise<ServerConsentTemplate>;
|
|
644
731
|
updateConsentTemplate(id: string, data: UpdateConsentTemplateInput): Promise<ServerConsentTemplate>;
|
|
645
732
|
deleteConsentTemplate(id: string): Promise<void>;
|
|
@@ -799,6 +886,17 @@ interface PreferenceModal extends Omit<MainBanner, "position"> {
|
|
|
799
886
|
receiptLabel?: string;
|
|
800
887
|
/** Description shown beneath the consent-receipt checkbox. Falls back to a default when omitted. */
|
|
801
888
|
receiptDescription?: string;
|
|
889
|
+
/**
|
|
890
|
+
* Renders a "Forget me" control in the modal body, below the categories — only shown to
|
|
891
|
+
* visitors who already have a stored consent decision (nothing to erase otherwise). Wired to
|
|
892
|
+
* the same `DELETE /consent/:visitorId` erasure endpoint as `widget.deleteConsent()`, giving
|
|
893
|
+
* visitors self-service access to GDPR Art. 17 / CCPA-CPRA / LGPD Art. 18 / UK GDPR / DPDPA /
|
|
894
|
+
* KVKK / POPIA / PDPA-TH / APPI erasure-equivalent rights without the host wiring a link
|
|
895
|
+
* themselves. See the "Right to erasure" guide for the full compliance mapping.
|
|
896
|
+
*/
|
|
897
|
+
showForgetMe?: boolean;
|
|
898
|
+
/** Label for the "Forget me" button (shown when `showForgetMe` is true). Falls back to a default when omitted. */
|
|
899
|
+
forgetMeLabel?: string;
|
|
802
900
|
}
|
|
803
901
|
/**
|
|
804
902
|
* A UI-template button definition. Keyed by its own machine id (e.g. `'accept-all'`) inside
|
|
@@ -833,11 +931,16 @@ interface TemplateModalDef extends Omit<TemplateBannerDef, 'position'> {
|
|
|
833
931
|
persistent?: boolean;
|
|
834
932
|
hasSubheading?: boolean;
|
|
835
933
|
mobileFullScreenBreakpoint?: number;
|
|
934
|
+
/** Renders a "Forget me" control below the categories, for visitors who already have a stored
|
|
935
|
+
* consent decision. See {@link PreferenceModal.showForgetMe}. */
|
|
936
|
+
showForgetMe?: boolean;
|
|
836
937
|
}
|
|
837
938
|
interface LocaleTranslations {
|
|
838
939
|
mainBanner: DeepPartial<MainBanner>;
|
|
839
940
|
gpcBanner?: DeepPartial<GpcBanner>;
|
|
840
941
|
preferenceModal: DeepPartial<PreferenceModal>;
|
|
942
|
+
/** Only meaningful when the profile's `ageGate.enabled` is true. */
|
|
943
|
+
ageGateModal?: DeepPartial<AgeGateModalContent>;
|
|
841
944
|
}
|
|
842
945
|
interface ProfileTranslations {
|
|
843
946
|
[locale: string]: LocaleTranslations;
|
|
@@ -847,6 +950,29 @@ interface DpdpaConfig {
|
|
|
847
950
|
grievanceEmail: string;
|
|
848
951
|
purposeDescription?: string;
|
|
849
952
|
}
|
|
953
|
+
/**
|
|
954
|
+
* Per-profile age gate — GDPR Art. 8's consent age varies 13–16 by EU member state and DPDPA has
|
|
955
|
+
* its own child-data age rules, so one global age (the old `ConsentiServerConfig.ageGate`) was
|
|
956
|
+
* wrong for a multi-region deployment. Mirrors `dpdpa` exactly: profile-scoped, not global.
|
|
957
|
+
*/
|
|
958
|
+
interface AgeGateConfig {
|
|
959
|
+
enabled: boolean;
|
|
960
|
+
minimumAge: number;
|
|
961
|
+
requireParentalConsent?: boolean;
|
|
962
|
+
}
|
|
963
|
+
/** Per-locale age-gate modal text — translated the same way `mainBanner`/`preferenceModal`
|
|
964
|
+
* content is, unlike the widget's own hardcoded UI chrome (close button, locale switcher). */
|
|
965
|
+
interface AgeGateModalContent {
|
|
966
|
+
heading?: string;
|
|
967
|
+
htmlText: string;
|
|
968
|
+
confirmButtonLabel: string;
|
|
969
|
+
denyButtonLabel: string;
|
|
970
|
+
parentalConsent: {
|
|
971
|
+
heading?: string;
|
|
972
|
+
htmlText: string;
|
|
973
|
+
confirmButtonLabel: string;
|
|
974
|
+
};
|
|
975
|
+
}
|
|
850
976
|
interface LocaleTextContent {
|
|
851
977
|
mainBanner: {
|
|
852
978
|
heading?: string;
|
|
@@ -873,7 +999,11 @@ interface LocaleTextContent {
|
|
|
873
999
|
}>;
|
|
874
1000
|
receiptLabel?: string;
|
|
875
1001
|
receiptDescription?: string;
|
|
1002
|
+
/** Per-locale override of `PreferenceModal.forgetMeLabel`; only meaningful when `showForgetMe` is true. */
|
|
1003
|
+
forgetMeLabel?: string;
|
|
876
1004
|
};
|
|
1005
|
+
/** Only meaningful when the profile's `ageGate.enabled` is true. */
|
|
1006
|
+
ageGateModal?: AgeGateModalContent;
|
|
877
1007
|
}
|
|
878
1008
|
interface ProfileConfig {
|
|
879
1009
|
id: string;
|
|
@@ -888,6 +1018,12 @@ interface ProfileConfig {
|
|
|
888
1018
|
darkMode?: boolean;
|
|
889
1019
|
allowedOrigins?: string[];
|
|
890
1020
|
dpdpa?: DpdpaConfig;
|
|
1021
|
+
ageGate?: AgeGateConfig;
|
|
1022
|
+
/** Default locale's age-gate modal text — mirrors `mainBanner`/`preferenceModal` (inline,
|
|
1023
|
+
* required-shape-when-present), not `dpdpa` (which has no locale text at all). Every other
|
|
1024
|
+
* locale's `ageGateModal` lives in `localeContents`/`LocaleContentInput` like the rest of
|
|
1025
|
+
* that locale's content. */
|
|
1026
|
+
ageGateModal?: AgeGateModalContent;
|
|
891
1027
|
regulation?: 'gdpr' | 'ccpa' | 'cpra' | 'dpdpa' | 'uk-gdpr' | 'lgpd' | 'pipeda' | 'popia' | 'pdpa-th' | 'appi' | 'kvkk';
|
|
892
1028
|
regulations?: string[];
|
|
893
1029
|
consentTemplateId?: string;
|
|
@@ -1005,6 +1141,8 @@ interface PublicProfileResponse {
|
|
|
1005
1141
|
allowReceipt?: boolean;
|
|
1006
1142
|
darkMode?: boolean;
|
|
1007
1143
|
dpdpa?: DpdpaConfig;
|
|
1144
|
+
ageGate?: AgeGateConfig;
|
|
1145
|
+
ageGateModal?: AgeGateModalContent;
|
|
1008
1146
|
showFooterMetadata?: boolean;
|
|
1009
1147
|
enhanceAccessibility?: boolean;
|
|
1010
1148
|
createdAt?: string;
|
|
@@ -1123,6 +1261,20 @@ declare class ProfileService {
|
|
|
1123
1261
|
* requested via `getVersionFile()`, which works for these ids too.
|
|
1124
1262
|
*/
|
|
1125
1263
|
listArchivedProfiles(): Promise<ArchivedProfileSummary[]>;
|
|
1264
|
+
/** Strips display text from an embedded profile's button map — a `UITemplate`'s buttons own
|
|
1265
|
+
* layout/behavior only, never text (button labels are authored per-locale on the profile
|
|
1266
|
+
* instead, see `LocaleTextContent.*.buttonLabels`). */
|
|
1267
|
+
private toTemplateButtonMap;
|
|
1268
|
+
/** Builds a `UITemplate`'s structural definitions (position, buttons, close/locale-switcher
|
|
1269
|
+
* flags) from an embedded profile's English translation — no heading/htmlText/button text,
|
|
1270
|
+
* which stay inline on the seeded profile as per-locale content, never on the template. */
|
|
1271
|
+
private buildSeedUiTemplatePayload;
|
|
1272
|
+
/**
|
|
1273
|
+
* Idempotently gets-or-creates the `ConsentTemplate` + `UITemplate` pair backing one seeded
|
|
1274
|
+
* compliance group's profile, named consistently with the profile itself (`Default — {group}`).
|
|
1275
|
+
* Looked up by name/tenant first so re-running the setup wizard never creates duplicates.
|
|
1276
|
+
*/
|
|
1277
|
+
private getOrCreateSeedTemplates;
|
|
1126
1278
|
/**
|
|
1127
1279
|
* Seeds a default profile for a compliance group from the embedded English profile in
|
|
1128
1280
|
* `@consenti/utils`, merged with locale text overlays for de/es/fr/ja. Every locale — including
|
|
@@ -1132,6 +1284,14 @@ declare class ProfileService {
|
|
|
1132
1284
|
* Only creates the profile if no active profile already exists for the compliance group.
|
|
1133
1285
|
* Safe to call repeatedly (idempotent).
|
|
1134
1286
|
*
|
|
1287
|
+
* Also seeds (idempotently) a `ConsentTemplate`/`UITemplate` pair for the group and links the
|
|
1288
|
+
* profile to both via `consentTemplateId`/`uiTemplateId` — same as a dashboard-authored profile
|
|
1289
|
+
* is required to. The profile is a *snapshot*, not a live pointer: `cookies`/`mainBanner`/
|
|
1290
|
+
* `gpcBanner`/`preferenceModal` stay fully resolved inline (DB row + on-disk locale files)
|
|
1291
|
+
* exactly as before; the template ids are additive, purely for traceability and so the editor
|
|
1292
|
+
* knows which template to re-offer on next edit. Editing the template later never silently
|
|
1293
|
+
* changes this profile — only an explicit re-save of the profile itself does.
|
|
1294
|
+
*
|
|
1135
1295
|
* @param complianceGroup - One of the 8 compliance group IDs.
|
|
1136
1296
|
*/
|
|
1137
1297
|
seedDefaultProfile(complianceGroup: string): Promise<void>;
|
|
@@ -1186,9 +1346,14 @@ declare class ConsentService {
|
|
|
1186
1346
|
private eventBus?;
|
|
1187
1347
|
private tcfConfig?;
|
|
1188
1348
|
private storage?;
|
|
1189
|
-
private
|
|
1190
|
-
|
|
1349
|
+
private dataSigningHash?;
|
|
1350
|
+
private gppConfig?;
|
|
1351
|
+
constructor(consents: ConsentRepo, visitors: VisitorRepo, profiles: ProfileRepo, audit: AuditRepo, tenantId?: string, compliance?: ComplianceConfig | undefined, pluginEngine?: PluginEngine | undefined, eventBus?: EventEmitter | undefined, tcfConfig?: TcfConfig | undefined, storage?: StorageAdapter | undefined, dataSigningHash?: string | undefined, gppConfig?: GppConfig | undefined);
|
|
1191
1352
|
private sign;
|
|
1353
|
+
/** Fails closed: no `storage` to check against means unconfirmed, never assumed confirmed. */
|
|
1354
|
+
private isTcfConfirmed;
|
|
1355
|
+
/** Same fail-closed shape as isTcfConfirmed() — see registration-confirmation.ts. */
|
|
1356
|
+
private isGppConfirmed;
|
|
1192
1357
|
/**
|
|
1193
1358
|
* Profiles created via the dashboard wizard reference a consent template (`consentTemplateId`)
|
|
1194
1359
|
* rather than embedding `cookies`/`preferenceModal.categories` directly on `profileJson` — those
|
|
@@ -1210,7 +1375,8 @@ declare class VisitorService {
|
|
|
1210
1375
|
private visitors;
|
|
1211
1376
|
private tenantId;
|
|
1212
1377
|
private eventBus?;
|
|
1213
|
-
|
|
1378
|
+
private dataSigningHash;
|
|
1379
|
+
constructor(visitors: VisitorRepo, tenantId?: string, eventBus?: EventEmitter | undefined, dataSigningHash?: string);
|
|
1214
1380
|
upsert(data: {
|
|
1215
1381
|
visitorId: string;
|
|
1216
1382
|
ip?: string;
|
|
@@ -1335,7 +1501,6 @@ declare class BetterSqlite3Adapter implements StorageAdapter {
|
|
|
1335
1501
|
getSettings(tenantId: string): Promise<TenantSettings>;
|
|
1336
1502
|
updateSettings(tenantId: string, data: Partial<TenantSettings>): Promise<TenantSettings>;
|
|
1337
1503
|
purgeExpiredConsents(olderThanDays: number): Promise<number>;
|
|
1338
|
-
purgeExpiredAuditLogs(olderThanDays: number): Promise<number>;
|
|
1339
1504
|
private mapApiKey;
|
|
1340
1505
|
createApiKey(data: CreateApiKeyInput): Promise<ApiKey>;
|
|
1341
1506
|
getApiKeyByHash(keyHash: string): Promise<ApiKey | null>;
|
|
@@ -1440,7 +1605,6 @@ declare class MongoDBAdapter implements StorageAdapter {
|
|
|
1440
1605
|
getSettings(tenantId: string): Promise<TenantSettings>;
|
|
1441
1606
|
updateSettings(tenantId: string, data: Partial<TenantSettings>): Promise<TenantSettings>;
|
|
1442
1607
|
purgeExpiredConsents(olderThanDays: number): Promise<number>;
|
|
1443
|
-
purgeExpiredAuditLogs(olderThanDays: number): Promise<number>;
|
|
1444
1608
|
createConsentTemplate(): Promise<never>;
|
|
1445
1609
|
updateConsentTemplate(): Promise<never>;
|
|
1446
1610
|
deleteConsentTemplate(): Promise<void>;
|
|
@@ -1533,7 +1697,6 @@ declare class MySQLAdapter implements StorageAdapter {
|
|
|
1533
1697
|
updateSettings(tenantId: string, data: Partial<TenantSettings>): Promise<TenantSettings>;
|
|
1534
1698
|
deleteTenant(id: string): Promise<void>;
|
|
1535
1699
|
purgeExpiredConsents(olderThanDays: number): Promise<number>;
|
|
1536
|
-
purgeExpiredAuditLogs(olderThanDays: number): Promise<number>;
|
|
1537
1700
|
createConsentTemplate(): Promise<never>;
|
|
1538
1701
|
updateConsentTemplate(): Promise<never>;
|
|
1539
1702
|
deleteConsentTemplate(): Promise<void>;
|
|
@@ -1625,7 +1788,6 @@ declare class PostgreSQLAdapter implements StorageAdapter {
|
|
|
1625
1788
|
getSettings(tenantId: string): Promise<TenantSettings>;
|
|
1626
1789
|
updateSettings(tenantId: string, data: Partial<TenantSettings>): Promise<TenantSettings>;
|
|
1627
1790
|
purgeExpiredConsents(olderThanDays: number): Promise<number>;
|
|
1628
|
-
purgeExpiredAuditLogs(olderThanDays: number): Promise<number>;
|
|
1629
1791
|
createConsentTemplate(): Promise<never>;
|
|
1630
1792
|
updateConsentTemplate(): Promise<never>;
|
|
1631
1793
|
deleteConsentTemplate(): Promise<void>;
|
|
@@ -1649,8 +1811,8 @@ declare class PostgreSQLAdapter implements StorageAdapter {
|
|
|
1649
1811
|
*
|
|
1650
1812
|
* Watches a single consent parameter across *every* visitor's submission (not just one
|
|
1651
1813
|
* browser's own state) and fires `onGrant`/`onDeny` when that parameter's status actually
|
|
1652
|
-
* changes — via the same `eventBus` `createConsenti()` already emits `consent
|
|
1653
|
-
* `consent
|
|
1814
|
+
* changes — via the same `eventBus` `createConsenti()` already emits `consent:created`/
|
|
1815
|
+
* `consent:updated` on. Useful for server-side integrations (CRM sync, suppression lists,
|
|
1654
1816
|
* downstream webhooks) that need to react to consent decisions without polling the database.
|
|
1655
1817
|
*
|
|
1656
1818
|
* There is no server equivalent of the widget's `ConsentScript` — injecting a `<script>` tag
|
|
@@ -1691,7 +1853,7 @@ interface ConsentActionOptions {
|
|
|
1691
1853
|
onDeny?: (params: ConsentActionParams) => void;
|
|
1692
1854
|
}
|
|
1693
1855
|
/**
|
|
1694
|
-
* Watches a single consent parameter across every incoming `consent
|
|
1856
|
+
* Watches a single consent parameter across every incoming `consent:created`/`consent:updated`
|
|
1695
1857
|
* event and fires `onGrant`/`onDeny` on transitions. Call {@link ConsentAction.destroy} to
|
|
1696
1858
|
* remove the event listeners when the hook is no longer needed.
|
|
1697
1859
|
*/
|
|
@@ -1750,8 +1912,8 @@ interface CategoryActionOptions {
|
|
|
1750
1912
|
onDeny?: (params: CategoryActionParams) => void;
|
|
1751
1913
|
}
|
|
1752
1914
|
/**
|
|
1753
|
-
* Watches a category's rollup consent status across every incoming `consent
|
|
1754
|
-
* `consent
|
|
1915
|
+
* Watches a category's rollup consent status across every incoming `consent:created`/
|
|
1916
|
+
* `consent:updated` event and fires `onGrant`/`onDeny` on transitions. Call
|
|
1755
1917
|
* {@link CategoryAction.destroy} to remove the event listeners when no longer needed.
|
|
1756
1918
|
*/
|
|
1757
1919
|
declare class CategoryAction {
|