@consenti/api 0.1.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 +623 -0
- package/dist/dashboard/assets/index-BBbhHyc_.js +2637 -0
- package/dist/dashboard/assets/index-DYK73WL5.css +1 -0
- package/dist/dashboard/favicon.ico +0 -0
- package/dist/dashboard/icon.svg +30 -0
- package/dist/dashboard/index.html +15 -0
- package/dist/dashboard/logo-dark.svg +50 -0
- package/dist/dashboard/logo.svg +50 -0
- package/dist/index.cjs +9162 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1085 -0
- package/dist/index.d.ts +1085 -0
- package/dist/index.js +9116 -0
- package/dist/index.js.map +1 -0
- package/dist/widget/widget.js +2305 -0
- package/package.json +97 -0
package/README.md
ADDED
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
# @consenti/api
|
|
2
|
+
|
|
3
|
+
Zero-dependency, GDPR-compliant Consent Management Platform backend for Node.js.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@consenti/api)
|
|
6
|
+
[](../../LICENSE)
|
|
7
|
+
[](https://nodejs.org)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Why this package?
|
|
12
|
+
|
|
13
|
+
- **Three SQLite drivers** — `node:sqlite` (Node 22.5+ built-in, zero install), `node-sqlite3-wasm` (WASM, zero compilation, optional peer dep), `better-sqlite3` (native, optional peer dep)
|
|
14
|
+
- **One function to mount** — `createConsenti()` returns a router you drop into any Node.js HTTP framework
|
|
15
|
+
- **Self-contained admin dashboard** — full SPA served at your `basePath`, no separate deploy
|
|
16
|
+
- **Multi-regulation** — GDPR, CCPA, CPRA, GPC, TCF v2.2, DPDPA, LGPD, PDPA, PIPEDA, APPI, KVKK, POPIA, UK-GDPR
|
|
17
|
+
- **Pluggable storage** — SQLite (zero-config, default) · PostgreSQL · MySQL · MongoDB
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
- **Node.js ≥ 20.0.0** — Node 22 or 24 LTS recommended for production
|
|
24
|
+
|
|
25
|
+
## Which SQLite driver should I use?
|
|
26
|
+
|
|
27
|
+
| Driver | Config | Node requirement | Extra install? |
|
|
28
|
+
|---|---|---|---|
|
|
29
|
+
| `node:sqlite` | `driver: 'node:sqlite'` | ≥ 22.5 (stable in 24) | **No** — Node built-in |
|
|
30
|
+
| `node-sqlite3-wasm` | `driver: 'node-sqlite3-wasm'` | ≥ 20 | Yes — `npm install node-sqlite3-wasm` |
|
|
31
|
+
| `better-sqlite3` | `driver: 'better-sqlite3'` | ≥ 20 | Yes — `npm install better-sqlite3` |
|
|
32
|
+
| `'sqlite'` | `driver: 'sqlite'` | ≥ 20 | Alias for `better-sqlite3` |
|
|
33
|
+
|
|
34
|
+
`node:sqlite` is the only driver that requires no extra install — it ships with Node 22.5+.
|
|
35
|
+
On Node 20/21, choose between `node-sqlite3-wasm` (pure WASM, no compilation) or `better-sqlite3` (native binary, faster but may fail on Alpine/musl/ARM).
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install @consenti/api
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
All SQLite drivers and other database adapters are optional peer dependencies — only install what you need:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# SQLite (choose one — node:sqlite needs no install on Node 22.5+)
|
|
49
|
+
npm install node-sqlite3-wasm # WASM SQLite — Node 20+, zero compilation
|
|
50
|
+
npm install better-sqlite3 # native SQLite — Node 20+, faster but needs a C++ toolchain
|
|
51
|
+
|
|
52
|
+
# Other databases
|
|
53
|
+
npm install pg # PostgreSQL
|
|
54
|
+
npm install mysql2 # MySQL
|
|
55
|
+
npm install mongodb # MongoDB
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Quick Start
|
|
61
|
+
|
|
62
|
+
### Express
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import express from 'express'
|
|
66
|
+
import { createConsenti } from '@consenti/api'
|
|
67
|
+
|
|
68
|
+
const app = express()
|
|
69
|
+
|
|
70
|
+
const consenti = createConsenti({
|
|
71
|
+
storage: { driver: 'sqlite', path: './consenti.db' },
|
|
72
|
+
auth: {
|
|
73
|
+
mode: 'local',
|
|
74
|
+
adminEmail: 'admin@example.com',
|
|
75
|
+
adminPassword: process.env.CONSENTI_ADMIN_PASSWORD!,
|
|
76
|
+
},
|
|
77
|
+
dashboard: true,
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
app.use(consenti.router)
|
|
81
|
+
|
|
82
|
+
app.listen(3000, () => {
|
|
83
|
+
console.log('Server → http://localhost:3000')
|
|
84
|
+
console.log('Admin → http://localhost:3000/consenti/')
|
|
85
|
+
console.log('API → http://localhost:3000/consenti/api/v1/')
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Standalone (no framework)
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { createConsenti } from '@consenti/api'
|
|
93
|
+
|
|
94
|
+
const consenti = createConsenti({
|
|
95
|
+
storage: { driver: 'sqlite', path: './consenti.db' },
|
|
96
|
+
auth: {
|
|
97
|
+
mode: 'local',
|
|
98
|
+
adminEmail: 'admin@example.com',
|
|
99
|
+
adminPassword: process.env.CONSENTI_ADMIN_PASSWORD!,
|
|
100
|
+
},
|
|
101
|
+
dashboard: true,
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
await consenti.start(3001)
|
|
105
|
+
// Admin dashboard → http://localhost:3001/consenti/
|
|
106
|
+
// REST API → http://localhost:3001/consenti/api/v1/
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Framework Adapters
|
|
112
|
+
|
|
113
|
+
### Express / Connect
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
app.use(consenti.router) // as middleware (calls next() for unmatched routes)
|
|
117
|
+
// — or —
|
|
118
|
+
app.use(consenti.handler) // as a terminal handler (no next())
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Fastify
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import Fastify from 'fastify'
|
|
125
|
+
const app = Fastify()
|
|
126
|
+
|
|
127
|
+
const consenti = createConsenti({ /* ... */ })
|
|
128
|
+
|
|
129
|
+
app.all('/consenti/*', async (req, reply) => {
|
|
130
|
+
reply.hijack()
|
|
131
|
+
consenti.handler(req.raw, reply.raw)
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Next.js App Router
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
// app/consenti/[...path]/route.ts
|
|
139
|
+
import { createConsenti } from '@consenti/api'
|
|
140
|
+
import type { NextRequest } from 'next/server'
|
|
141
|
+
|
|
142
|
+
let consenti: Awaited<ReturnType<typeof createConsenti>>
|
|
143
|
+
|
|
144
|
+
async function getConsenti() {
|
|
145
|
+
if (!consenti) {
|
|
146
|
+
consenti = createConsenti({
|
|
147
|
+
storage: { driver: 'sqlite', path: './consenti.db' },
|
|
148
|
+
auth: {
|
|
149
|
+
mode: 'local',
|
|
150
|
+
adminEmail: process.env.CONSENTI_ADMIN_EMAIL!,
|
|
151
|
+
adminPassword: process.env.CONSENTI_ADMIN_PASSWORD!,
|
|
152
|
+
},
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
return consenti
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export async function GET(req: NextRequest) {
|
|
159
|
+
const c = await getConsenti()
|
|
160
|
+
return c.handleRequest(req)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export { GET as POST, GET as PUT, GET as DELETE, GET as PATCH }
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Hono / Fetch-based
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
return consenti.honoApp.fetch(request)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Full Configuration
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import { createConsenti } from '@consenti/api'
|
|
178
|
+
|
|
179
|
+
const consenti = createConsenti({
|
|
180
|
+
// ── Required ────────────────────────────────────────────────────────────────
|
|
181
|
+
storage: {
|
|
182
|
+
driver: 'node-sqlite3-wasm', // 'node-sqlite3-wasm' | 'node:sqlite' | 'better-sqlite3' | 'pg' | 'mongodb' | 'mysql'
|
|
183
|
+
path: './consenti.db', // SQLite only — file path
|
|
184
|
+
|
|
185
|
+
// PostgreSQL
|
|
186
|
+
// driver: 'pg',
|
|
187
|
+
// url: 'postgresql://user:pass@localhost:5432/consenti',
|
|
188
|
+
// pool: { max: 10, idleTimeoutMillis: 30000 },
|
|
189
|
+
|
|
190
|
+
// MongoDB
|
|
191
|
+
// driver: 'mongodb',
|
|
192
|
+
// url: 'mongodb://localhost:27017',
|
|
193
|
+
// dbName: 'consenti',
|
|
194
|
+
|
|
195
|
+
// MySQL
|
|
196
|
+
// driver: 'mysql',
|
|
197
|
+
// url: 'mysql://user:pass@localhost:3306/consenti',
|
|
198
|
+
},
|
|
199
|
+
auth: {
|
|
200
|
+
mode: 'local', // 'local' | 'oauth' | 'saml' | 'none'
|
|
201
|
+
adminEmail: 'admin@example.com',
|
|
202
|
+
adminPassword: process.env.CONSENTI_ADMIN_PASSWORD!,
|
|
203
|
+
jwtSecret: process.env.CONSENTI_ADMIN_JWT_SECRET, // auto-generated if omitted (changes on restart)
|
|
204
|
+
sessionTtl: 86400, // JWT TTL in seconds (default: 24 h)
|
|
205
|
+
|
|
206
|
+
// OAuth / OIDC (e.g. Auth0, Keycloak)
|
|
207
|
+
// mode: 'oauth',
|
|
208
|
+
// oidc: {
|
|
209
|
+
// issuer: 'https://your-idp.example.com',
|
|
210
|
+
// clientId: 'your-client-id',
|
|
211
|
+
// clientSecret: process.env.OIDC_SECRET,
|
|
212
|
+
// redirectUri: 'https://app.example.com/consenti/admin/auth/callback',
|
|
213
|
+
// },
|
|
214
|
+
|
|
215
|
+
// SAML
|
|
216
|
+
// mode: 'saml',
|
|
217
|
+
// saml: {
|
|
218
|
+
// issuer: 'consenti',
|
|
219
|
+
// entryPoint: 'https://idp.example.com/sso/saml',
|
|
220
|
+
// cert: process.env.SAML_CERT,
|
|
221
|
+
// callbackUrl: 'https://app.example.com/consenti/admin/auth/saml/callback',
|
|
222
|
+
// },
|
|
223
|
+
|
|
224
|
+
// Custom — bring your own validator
|
|
225
|
+
// mode: 'custom',
|
|
226
|
+
// validateUser: async (req) => ({ id, email, roles, permissions } | null),
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
// ── Optional ────────────────────────────────────────────────────────────────
|
|
230
|
+
basePath: '/consenti', // URL prefix (default: '/consenti')
|
|
231
|
+
dashboard: true, // serve admin SPA (default: false)
|
|
232
|
+
trustProxy: false, // true when behind nginx / Cloudflare
|
|
233
|
+
|
|
234
|
+
rateLimit: {
|
|
235
|
+
enabled: true,
|
|
236
|
+
windowMs: 60_000, // rolling window (default: 60 s)
|
|
237
|
+
maxRequests: 60, // max requests per IP per window (default: 60)
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
cors: {
|
|
241
|
+
origins: ['https://yoursite.com'], // never use '*' in production
|
|
242
|
+
credentials: true,
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
compliance: {
|
|
246
|
+
gdpr: true, // opt-in enforcement
|
|
247
|
+
ccpa: false, // opt-out model
|
|
248
|
+
gpc: true, // store gpc_detected on records
|
|
249
|
+
tcf: {
|
|
250
|
+
enabled: false, // IAB TCF v2.2
|
|
251
|
+
cmpId: 123, // your registered CMP ID
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
ageGate: {
|
|
256
|
+
enabled: false,
|
|
257
|
+
minimumAge: 13,
|
|
258
|
+
requireParentalConsent: false,
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
dataRetention: {
|
|
262
|
+
purgeAfterDays: 365, // auto-purge old records (0 = disabled)
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
multiTenant: {
|
|
266
|
+
enabled: false, // isolate data per tenant
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
plugins: [], // ConsentiServerPlugin instances
|
|
270
|
+
})
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## First Boot
|
|
276
|
+
|
|
277
|
+
On first start with SQLite, Consenti:
|
|
278
|
+
|
|
279
|
+
1. Creates the database file at the configured path
|
|
280
|
+
2. Runs all migrations (creates `visitors`, `consent_records`, `consent_history`, `profiles`, `admin_users`, `audit_logs` tables)
|
|
281
|
+
3. Creates the admin user from `auth.adminEmail` + `auth.adminPassword`
|
|
282
|
+
4. Seeds the default profile (`profileId: '0'`)
|
|
283
|
+
|
|
284
|
+
Subsequent starts run only pending migrations.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Public REST API
|
|
289
|
+
|
|
290
|
+
Base path: `/consenti/api/v1` (change with `basePath` in config). No authentication required.
|
|
291
|
+
|
|
292
|
+
| Method | Path | Description |
|
|
293
|
+
|------------|-------------------------------|-------------------------------------------------|
|
|
294
|
+
| `GET` | `/profiles/:id` | Get profile resolved in its default locale |
|
|
295
|
+
| `GET` | `/profiles/:id/:locale` | Get profile resolved for a specific locale |
|
|
296
|
+
| `POST` | `/consent` | Submit a consent record |
|
|
297
|
+
| `GET` | `/consent/:visitorId` | Get current consent for a visitor |
|
|
298
|
+
| `PUT` | `/consent/:visitorId` | Update consent for a visitor |
|
|
299
|
+
| `DELETE` | `/consent/:visitorId` | GDPR right-to-erasure — delete all visitor data |
|
|
300
|
+
| `GET` | `/consent/:visitorId/verify` | Check if consent is still valid |
|
|
301
|
+
|
|
302
|
+
### Submit consent
|
|
303
|
+
|
|
304
|
+
```http
|
|
305
|
+
POST /consenti/api/v1/consent
|
|
306
|
+
Content-Type: application/json
|
|
307
|
+
|
|
308
|
+
{
|
|
309
|
+
"profileId": "my-profile-id",
|
|
310
|
+
"consentJson": {
|
|
311
|
+
"necessary": "granted",
|
|
312
|
+
"analytics": "granted",
|
|
313
|
+
"marketing": "denied"
|
|
314
|
+
},
|
|
315
|
+
"visitorId": "uuid-v4",
|
|
316
|
+
"profileVersion": 3,
|
|
317
|
+
"locale": "en",
|
|
318
|
+
"gpcDetected": false,
|
|
319
|
+
"source": "banner"
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Response `201`:
|
|
324
|
+
|
|
325
|
+
```json
|
|
326
|
+
{
|
|
327
|
+
"id": "record-uuid",
|
|
328
|
+
"visitorId": "visitor-uuid",
|
|
329
|
+
"profileId": "my-profile-id",
|
|
330
|
+
"profileVersion": 3,
|
|
331
|
+
"locale": "en",
|
|
332
|
+
"consentJson": { "necessary": "granted", "analytics": "granted", "marketing": "denied" },
|
|
333
|
+
"gpcDetected": false,
|
|
334
|
+
"source": "banner",
|
|
335
|
+
"createdAt": "2026-06-26T10:00:00.000Z",
|
|
336
|
+
"updatedAt": "2026-06-26T10:00:00.000Z"
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Verify consent
|
|
341
|
+
|
|
342
|
+
```http
|
|
343
|
+
GET /consenti/api/v1/consent/visitor-uuid/verify
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
```json
|
|
347
|
+
{ "valid": true }
|
|
348
|
+
|
|
349
|
+
// or, when stale:
|
|
350
|
+
{ "valid": false, "reason": "profile_version_mismatch" }
|
|
351
|
+
// other reasons: "consent_expired" | "missing_cookies"
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## Admin REST API
|
|
357
|
+
|
|
358
|
+
Base path: `/consenti/admin`. Every route requires `Authorization: Bearer <jwt>`.
|
|
359
|
+
|
|
360
|
+
Obtain a token via `POST /consenti/admin/auth/login`.
|
|
361
|
+
|
|
362
|
+
### Auth
|
|
363
|
+
|
|
364
|
+
| Method | Path | Description |
|
|
365
|
+
|--------|-------------------|-----------------------------------|
|
|
366
|
+
| `POST` | `/auth/login` | Authenticate — returns a JWT |
|
|
367
|
+
| `GET` | `/auth/me` | Get current authenticated user |
|
|
368
|
+
| `POST` | `/auth/logout` | Invalidate session |
|
|
369
|
+
|
|
370
|
+
```http
|
|
371
|
+
POST /consenti/admin/auth/login
|
|
372
|
+
Content-Type: application/json
|
|
373
|
+
|
|
374
|
+
{ "email": "admin@example.com", "password": "your-password" }
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
```json
|
|
378
|
+
{ "token": "eyJhbGciOiJIUzI1NiJ9..." }
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Profiles
|
|
382
|
+
|
|
383
|
+
| Method | Path | Description |
|
|
384
|
+
|------------|-------------------|------------------------------------------|
|
|
385
|
+
| `GET` | `/profiles` | List all profiles |
|
|
386
|
+
| `POST` | `/profiles` | Create a profile |
|
|
387
|
+
| `GET` | `/profiles/:id` | Get a profile |
|
|
388
|
+
| `PUT` | `/profiles/:id` | Update a profile (auto-bumps version) |
|
|
389
|
+
| `DELETE` | `/profiles/:id` | Delete a profile |
|
|
390
|
+
|
|
391
|
+
### Consents
|
|
392
|
+
|
|
393
|
+
| Method | Path | Description |
|
|
394
|
+
|----------|----------------------------------|---------------------------------------------|
|
|
395
|
+
| `GET` | `/consents` | List consent records (paginated, filterable)|
|
|
396
|
+
| `GET` | `/consents/:visitorId` | Get consent record for a visitor |
|
|
397
|
+
| `GET` | `/consents/:visitorId/history` | Get consent change history |
|
|
398
|
+
|
|
399
|
+
Query params for `GET /consents`: `page`, `limit`, `profileId`, `from`, `to`.
|
|
400
|
+
|
|
401
|
+
### Visitors
|
|
402
|
+
|
|
403
|
+
| Method | Path | Description |
|
|
404
|
+
|--------|----------------|-------------------------------------|
|
|
405
|
+
| `GET` | `/visitors` | List visitor records (paginated) |
|
|
406
|
+
|
|
407
|
+
IPs are never stored raw — only SHA-256 hashes appear in the `ipHash` field.
|
|
408
|
+
|
|
409
|
+
### Users & Roles
|
|
410
|
+
|
|
411
|
+
| Method | Path | Description |
|
|
412
|
+
|------------|-----------------------------------|----------------------------------|
|
|
413
|
+
| `GET` | `/users` | List admin users |
|
|
414
|
+
| `POST` | `/users` | Create an admin user |
|
|
415
|
+
| `PUT` | `/users/:id` | Update an admin user |
|
|
416
|
+
| `GET` | `/roles` | List roles |
|
|
417
|
+
| `POST` | `/roles` | Create a role |
|
|
418
|
+
| `GET` | `/roles/:id/permissions` | Get permissions for a role |
|
|
419
|
+
| `POST` | `/roles/:id/permissions` | Assign a permission to a role |
|
|
420
|
+
| `DELETE` | `/roles/:id/permissions/:permId` | Revoke a permission from a role |
|
|
421
|
+
| `GET` | `/permissions` | List all available permissions |
|
|
422
|
+
|
|
423
|
+
### API Keys
|
|
424
|
+
|
|
425
|
+
| Method | Path | Description |
|
|
426
|
+
|------------|-----------------|------------------------------------------------------|
|
|
427
|
+
| `GET` | `/apikeys` | List API keys |
|
|
428
|
+
| `POST` | `/apikeys` | Create an API key (raw key returned once — save it!) |
|
|
429
|
+
| `DELETE` | `/apikeys/:id` | Revoke an API key |
|
|
430
|
+
|
|
431
|
+
### Stats & Reporting
|
|
432
|
+
|
|
433
|
+
| Method | Path | Description |
|
|
434
|
+
|--------|---------------------------|------------------------------------------|
|
|
435
|
+
| `GET` | `/stats/overview` | Total consents, visitors, GPC count |
|
|
436
|
+
| `GET` | `/stats/timeline` | Daily consent counts (`?days=30`) |
|
|
437
|
+
| `GET` | `/stats/categories` | Per-category grant/deny breakdown |
|
|
438
|
+
| `GET` | `/stats/countries` | Consent count by country |
|
|
439
|
+
| `GET` | `/stats/gpc` | GPC detection statistics |
|
|
440
|
+
| `GET` | `/export/consents` | Export consent records (CSV or JSON) |
|
|
441
|
+
| `GET` | `/export/consents/xlsx` | Export as XLSX |
|
|
442
|
+
| `GET` | `/export/audit` | Export audit log (CSV or JSON) |
|
|
443
|
+
|
|
444
|
+
### Audit Log
|
|
445
|
+
|
|
446
|
+
| Method | Path | Description |
|
|
447
|
+
|--------|-----------|---------------------------------------------------------|
|
|
448
|
+
| `GET` | `/audit` | Paginated audit log; filter by `action`, `resourceType`, date range |
|
|
449
|
+
|
|
450
|
+
### Multi-Tenant
|
|
451
|
+
|
|
452
|
+
| Method | Path | Description |
|
|
453
|
+
|------------|------------------|----------------------|
|
|
454
|
+
| `GET` | `/tenants` | List tenants |
|
|
455
|
+
| `POST` | `/tenants` | Create a tenant |
|
|
456
|
+
| `PUT` | `/tenants/:id` | Update a tenant |
|
|
457
|
+
| `DELETE` | `/tenants/:id` | Delete a tenant |
|
|
458
|
+
|
|
459
|
+
Only active when `multiTenant.enabled: true`.
|
|
460
|
+
|
|
461
|
+
### IAB TCF
|
|
462
|
+
|
|
463
|
+
| Method | Path | Description |
|
|
464
|
+
|--------|-------------------|---------------------------|
|
|
465
|
+
| `GET` | `/tcf/vendors` | List IAB TCF vendors |
|
|
466
|
+
| `GET` | `/tcf/purposes` | List IAB TCF purposes |
|
|
467
|
+
|
|
468
|
+
Only active when `tcf.enabled: true`.
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
## Admin Dashboard
|
|
473
|
+
|
|
474
|
+
Served at `{basePath}/` when `dashboard: true`.
|
|
475
|
+
|
|
476
|
+
| Section | Description |
|
|
477
|
+
|-------------------|--------------------------------------------------------------------------|
|
|
478
|
+
| Dashboard | Consent overview, timeline chart, country breakdown, GPC stats |
|
|
479
|
+
| Profiles | Create / edit / copy / delete consent profiles via 3-step wizard |
|
|
480
|
+
| Cookie Templates | Reusable cookie/purpose definitions |
|
|
481
|
+
| UI Templates | Reusable banner + modal layout settings |
|
|
482
|
+
| Consents | Browse, filter, export consent records; per-visitor history |
|
|
483
|
+
| Visitors | Visitor list with geographic data |
|
|
484
|
+
| Users | Admin user management |
|
|
485
|
+
| Roles | RBAC roles and fine-grained permission assignment |
|
|
486
|
+
| Sites | Multi-tenant site management |
|
|
487
|
+
| TCF Vendors | IAB Global Vendor List |
|
|
488
|
+
| Audit Log | Immutable log of all admin actions |
|
|
489
|
+
| Settings | API keys, branding |
|
|
490
|
+
|
|
491
|
+
### Profile Creation Wizard
|
|
492
|
+
|
|
493
|
+
**Step 1 — Cookie Template:** Define cookie IDs and their properties:
|
|
494
|
+
|
|
495
|
+
| Field | Type | Description |
|
|
496
|
+
|---------------------|-----------------------------------|---------------------------------------------------|
|
|
497
|
+
| `id` | `string` | Unique identifier referenced in button arrays |
|
|
498
|
+
| `type` | `'consent' \| 'legitimate_interest'` | Legal basis |
|
|
499
|
+
| `mandatory` | `boolean` | Always granted; toggle disabled in modal |
|
|
500
|
+
| `listenGpc` | `boolean` | Auto-denied when GPC signal is active |
|
|
501
|
+
| `expiry` | `number` | Days until re-consent is required (0 = session) |
|
|
502
|
+
|
|
503
|
+
**Step 2 — UI Template:** Configure banner and modal layout:
|
|
504
|
+
|
|
505
|
+
- **Main Banner / GPC Banner:** `position`, `overlayOpacity`, `showClose`, `headingTag`, `buttons`
|
|
506
|
+
- **Preference Modal:** `position`, `overlayOpacity`, `showClose`, `persistent`, `buttons`, `categories`
|
|
507
|
+
- Button `type`: `primary | secondary | text | reject | submit | manage | close`
|
|
508
|
+
- Button `cookies`: `*` (grant all) · `!` (deny all) · comma-separated IDs
|
|
509
|
+
|
|
510
|
+
**Step 3 — Content:** Enter localised copy with live preview:
|
|
511
|
+
|
|
512
|
+
- Main Banner: heading, body HTML
|
|
513
|
+
- GPC Banner: heading, body HTML
|
|
514
|
+
- Preference Modal: heading, subheading, intro HTML, per-category heading + description
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
## Plugins (Server-side)
|
|
519
|
+
|
|
520
|
+
```ts
|
|
521
|
+
import { ConsentiServerPlugin } from '@consenti/api'
|
|
522
|
+
import type { PluginContext, CreateConsentInput, ConsentDbRecord } from '@consenti/api'
|
|
523
|
+
|
|
524
|
+
class MyPlugin extends ConsentiServerPlugin {
|
|
525
|
+
name = 'my-plugin'
|
|
526
|
+
|
|
527
|
+
async initialize(ctx: PluginContext) {
|
|
528
|
+
// ctx.storage — access storage adapter directly
|
|
529
|
+
console.log('Plugin initialised')
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
async destroy() {}
|
|
533
|
+
|
|
534
|
+
// All hooks are optional:
|
|
535
|
+
async beforeConsentSave(data: CreateConsentInput) {
|
|
536
|
+
return data // modify or validate; return modified data
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
async afterConsentSave(record: ConsentDbRecord) {
|
|
540
|
+
// send to analytics, trigger webhooks, etc.
|
|
541
|
+
await fetch('/my-webhook', { method: 'POST', body: JSON.stringify(record) })
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async beforeConsentUpdate(data: unknown) { return data }
|
|
545
|
+
async afterConsentUpdate(record: unknown) {}
|
|
546
|
+
async beforeProfileFetch(id: string) { return id }
|
|
547
|
+
async afterProfileFetch(profile: unknown) { return profile }
|
|
548
|
+
async beforeUserCreate(data: unknown) { return data }
|
|
549
|
+
async afterUserCreate(user: unknown) {}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const consenti = createConsenti({
|
|
553
|
+
// ...
|
|
554
|
+
plugins: [new MyPlugin()],
|
|
555
|
+
})
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
|
|
560
|
+
## Programmatic Service Access
|
|
561
|
+
|
|
562
|
+
```ts
|
|
563
|
+
const consenti = createConsenti({ /* ... */ })
|
|
564
|
+
|
|
565
|
+
// Access services directly — bypass HTTP layer
|
|
566
|
+
const profile = await consenti.services.profile.get('profile-id')
|
|
567
|
+
const consent = await consenti.services.consent.submit({ /* ... */ })
|
|
568
|
+
const visitors = await consenti.services.visitor.list({ tenantId: 'default' })
|
|
569
|
+
|
|
570
|
+
// Access storage adapter
|
|
571
|
+
await consenti.storage.getConsents({ tenantId: 'default', from: '2026-01-01' })
|
|
572
|
+
|
|
573
|
+
// Event bus
|
|
574
|
+
consenti.eventBus.on('ready', () => console.log('Backend ready'))
|
|
575
|
+
|
|
576
|
+
// Graceful shutdown
|
|
577
|
+
await consenti.destroy()
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
## Security
|
|
583
|
+
|
|
584
|
+
| Concern | Approach |
|
|
585
|
+
|---------------------------|------------------------------------------------------------------------------|
|
|
586
|
+
| IP addresses | Never stored raw — SHA-256 hashed: `createHash('sha256').update(ip).digest('hex')` |
|
|
587
|
+
| Passwords | `scrypt` via `node:crypto` — no external bcrypt |
|
|
588
|
+
| JWTs | `createHmac` via `node:crypto` — no jsonwebtoken package |
|
|
589
|
+
| Admin routes | Always require `Authorization: Bearer <jwt>` |
|
|
590
|
+
| Stack traces | Suppressed in `NODE_ENV=production` responses |
|
|
591
|
+
| CORS | Explicit allowlist — never use `origins: '*'` in production |
|
|
592
|
+
| Rate limiting | Applied to all public API routes by default |
|
|
593
|
+
|
|
594
|
+
---
|
|
595
|
+
|
|
596
|
+
## Environment Variables
|
|
597
|
+
|
|
598
|
+
| Variable | Description |
|
|
599
|
+
|---------------------------|-----------------------------------------------------------------------------|
|
|
600
|
+
| `NODE_ENV` | Set to `'production'` to suppress stack traces in error responses |
|
|
601
|
+
| `CONSENTI_ADMIN_PASSWORD` | Override `auth.adminPassword` from config |
|
|
602
|
+
| `CONSENTI_ADMIN_JWT_SECRET` | Override `auth.jwtSecret` from config |
|
|
603
|
+
| `CONSENTI_DB_PATH` | Override `storage.path` (SQLite) |
|
|
604
|
+
| `CONSENTI_BASE_PATH` | Override `basePath` |
|
|
605
|
+
|
|
606
|
+
---
|
|
607
|
+
|
|
608
|
+
## Interactive API Docs
|
|
609
|
+
|
|
610
|
+
When running, Swagger UI is available at `{basePath}/api/docs`.
|
|
611
|
+
OpenAPI JSON at `{basePath}/api/openapi.json`.
|
|
612
|
+
|
|
613
|
+
---
|
|
614
|
+
|
|
615
|
+
## Contributing
|
|
616
|
+
|
|
617
|
+
See [CONTRIBUTING.md](../../CONTRIBUTING.md) in the monorepo root.
|
|
618
|
+
|
|
619
|
+
---
|
|
620
|
+
|
|
621
|
+
## License
|
|
622
|
+
|
|
623
|
+
Apache 2.0 — see [LICENSE](../../LICENSE).
|