@c15t/backend 2.0.0-rc.8 → 2.0.2
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 +12 -12
- package/dist/302.js +3 -4
- package/dist/915.js +52 -23
- package/dist/942.js +5 -0
- package/dist/cache.cjs +1 -1
- package/dist/core.cjs +60 -27
- package/dist/core.js +6 -4
- package/dist/edge.cjs +14 -9
- package/dist/edge.js +11 -9
- package/dist/router.cjs +52 -24
- package/dist-types/cache/gvl-resolver.d.ts +3 -3
- package/dist-types/middleware/cors/matches-wildcard.d.ts +13 -0
- package/dist-types/policies/builder.d.ts +7 -7
- package/dist-types/types/index.d.ts +14 -14
- package/dist-types/version.d.ts +1 -1
- package/docs/api/configuration.md +12 -12
- package/docs/guides/database-setup.md +4 -4
- package/docs/guides/iab-tcf.md +8 -5
- package/docs/quickstart.md +9 -9
- package/package.json +12 -9
- package/readme.json +1 -1
package/README.md
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<a href="https://c15t.com?utm_source=
|
|
2
|
+
<a href="https://c15t.com?utm_source=npm&utm_medium=readme&utm_campaign=oss_readme&utm_content=%40c15t%2Fbackend" target="_blank" rel="noopener noreferrer">
|
|
3
3
|
<picture>
|
|
4
4
|
<source media="(prefers-color-scheme: dark)" srcset="../../docs/assets/c15t-banner-readme-dark.svg" type="image/svg+xml">
|
|
5
5
|
<img src="../../docs/assets/c15t-banner-readme-light.svg" alt="c15t Banner" type="image/svg+xml">
|
|
6
6
|
</picture>
|
|
7
7
|
</a>
|
|
8
|
-
<br />
|
|
9
|
-
<h1 align="center">@c15t/backend: Consent Management Backend</h1>
|
|
10
8
|
</p>
|
|
11
9
|
|
|
10
|
+
# @c15t/backend: Consent Management Backend
|
|
11
|
+
|
|
12
12
|
[](https://github.com/c15t/c15t)
|
|
13
13
|
[](https://github.com/c15t/c15t/actions/workflows/ci.yml)
|
|
14
|
-
[](https://github.com/c15t/c15t/blob/main/LICENSE.md)
|
|
15
15
|
[](https://c15t.link/discord)
|
|
16
16
|
[](https://www.npmjs.com/package/@c15t/backend)
|
|
17
17
|
[](https://github.com/c15t/c15t)
|
|
18
18
|
[](https://github.com/c15t/c15t/commits/main)
|
|
19
19
|
[](https://github.com/c15t/c15t/issues)
|
|
20
20
|
|
|
21
|
-
Consent policy engine and API for c15t. Powers the cookie banner, consent manager, and
|
|
21
|
+
Consent policy engine and API for c15t. Powers the cookie banner, consent manager, and preference center. Webhooks, audit logs, storage adapters. Self-host or use inth.com
|
|
22
22
|
|
|
23
23
|
## Key Features
|
|
24
24
|
|
|
@@ -54,24 +54,24 @@ For further information, guides, and examples visit the [reference documentation
|
|
|
54
54
|
|
|
55
55
|
- Join our [Discord community](https://c15t.link/discord)
|
|
56
56
|
- Open an issue on our [GitHub repository](https://github.com/c15t/c15t/issues)
|
|
57
|
-
- Visit [
|
|
58
|
-
- Contact our support team via email [support@
|
|
57
|
+
- Visit [inth.com](https://inth.com) and use the chat widget
|
|
58
|
+
- Contact our support team via email [support@inth.com](mailto:support@inth.com)
|
|
59
59
|
|
|
60
60
|
## Contributing
|
|
61
61
|
|
|
62
|
-
- We're open to all community contributions
|
|
62
|
+
- We're open to all community contributions.
|
|
63
63
|
- Read our [Contribution Guidelines](https://c15t.com/docs/oss/contributing)
|
|
64
64
|
- Review our [Code of Conduct](https://c15t.com/docs/oss/code-of-conduct)
|
|
65
65
|
- Fork the repository
|
|
66
66
|
- Create a new branch for your feature
|
|
67
67
|
- Submit a pull request
|
|
68
|
-
- **All contributions, big or small, are welcome and appreciated
|
|
68
|
+
- **All contributions, big or small, are welcome and appreciated.**
|
|
69
69
|
|
|
70
70
|
## Security
|
|
71
71
|
|
|
72
72
|
If you believe you have found a security vulnerability in c15t, we encourage you to **_responsibly disclose this and NOT open a public issue_**. We will investigate all legitimate reports.
|
|
73
73
|
|
|
74
|
-
Our preference is that you make use of GitHub's private vulnerability reporting feature to disclose potential security vulnerabilities in our
|
|
74
|
+
Our preference is that you make use of GitHub's private vulnerability reporting feature to disclose potential security vulnerabilities in our open-source software. To do this, please visit [https://github.com/c15t/c15t/security](https://github.com/c15t/c15t/security) and click the "Report a vulnerability" button.
|
|
75
75
|
|
|
76
76
|
### Security Policy
|
|
77
77
|
|
|
@@ -82,8 +82,8 @@ Our preference is that you make use of GitHub's private vulnerability reporting
|
|
|
82
82
|
|
|
83
83
|
## License
|
|
84
84
|
|
|
85
|
-
[
|
|
85
|
+
[Apache License 2.0](https://github.com/c15t/c15t/blob/main/LICENSE.md)
|
|
86
86
|
|
|
87
87
|
---
|
|
88
88
|
|
|
89
|
-
**Built
|
|
89
|
+
**Built by [Inth](https://inth.com?utm_source=npm&utm_medium=readme&utm_campaign=oss_readme&utm_content=%40c15t%2Fbackend)**
|
package/dist/302.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { SpanKind, SpanStatusCode as api_SpanStatusCode, context, metrics, trace } from "@opentelemetry/api";
|
|
2
|
-
const version = '2.0.0-rc.8';
|
|
3
2
|
function extractErrorMessage(error) {
|
|
4
3
|
if (error instanceof AggregateError && error.errors?.length > 0) {
|
|
5
4
|
const inner = error.errors.map((e)=>e instanceof Error ? e.message : String(e)).join('; ');
|
|
@@ -14,7 +13,7 @@ function createTelemetryOptions(appName = 'c15t', telemetryConfig, tenantId) {
|
|
|
14
13
|
const defaultAttributes = {
|
|
15
14
|
...telemetryConfig?.defaultAttributes || {},
|
|
16
15
|
'service.name': String(appName),
|
|
17
|
-
'service.version':
|
|
16
|
+
'service.version': "2.0.2"
|
|
18
17
|
};
|
|
19
18
|
if (tenantId) defaultAttributes['tenant.id'] = tenantId;
|
|
20
19
|
const config = {
|
|
@@ -369,7 +368,7 @@ function createCacheKey(appName, namespace, ...parts) {
|
|
|
369
368
|
];
|
|
370
369
|
return allParts.join(':');
|
|
371
370
|
}
|
|
372
|
-
const GVL_ENDPOINT = 'https://gvl.
|
|
371
|
+
const GVL_ENDPOINT = 'https://gvl.inth.app';
|
|
373
372
|
const inflightRequests = new Map();
|
|
374
373
|
async function fetchGVLWithLanguage(language, vendorIds, endpoint = GVL_ENDPOINT) {
|
|
375
374
|
const sortedVendorIds = vendorIds ? [
|
|
@@ -470,4 +469,4 @@ function createGVLResolver(options) {
|
|
|
470
469
|
}
|
|
471
470
|
};
|
|
472
471
|
}
|
|
473
|
-
export { GVL_TTL_MS, clearMemoryCache, createCacheKey, createGVLCacheKey, createGVLResolver, createMemoryCacheAdapter, createRequestSpan, createTelemetryOptions, extractErrorMessage, getMemoryCacheSize, getMetrics, getTraceContext, handleSpanError, isTelemetryEnabled,
|
|
472
|
+
export { GVL_TTL_MS, clearMemoryCache, createCacheKey, createGVLCacheKey, createGVLResolver, createMemoryCacheAdapter, createRequestSpan, createTelemetryOptions, extractErrorMessage, getMemoryCacheSize, getMetrics, getTraceContext, handleSpanError, isTelemetryEnabled, withDatabaseSpan, withSpanContext };
|
package/dist/915.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Hono } from "hono";
|
|
|
5
5
|
import { describeRoute, resolver, validator } from "hono-openapi";
|
|
6
6
|
import { HTTPException } from "hono/http-exception";
|
|
7
7
|
import { errors, jwtVerify } from "jose";
|
|
8
|
-
import {
|
|
8
|
+
import { extractErrorMessage, getMetrics, withDatabaseSpan } from "./302.js";
|
|
9
9
|
import { getLocation, resolveInitPayload, policy_resolvePolicyDecision, verifyPolicySnapshotToken, getJurisdiction } from "./583.js";
|
|
10
10
|
import * as __rspack_external_valibot from "valibot";
|
|
11
11
|
const prefixes = {
|
|
@@ -709,7 +709,7 @@ const statusHandler = async (c)=>{
|
|
|
709
709
|
try {
|
|
710
710
|
await ctx.db.findFirst('subject', {});
|
|
711
711
|
return c.json({
|
|
712
|
-
version:
|
|
712
|
+
version: "2.0.2",
|
|
713
713
|
timestamp: new Date(),
|
|
714
714
|
client: clientInfo
|
|
715
715
|
});
|
|
@@ -1372,6 +1372,7 @@ const postSubjectHandler = async (c)=>{
|
|
|
1372
1372
|
let purposeIds = [];
|
|
1373
1373
|
let appliedPreferences;
|
|
1374
1374
|
const inputPolicyId = 'policyId' in input ? input.policyId : void 0;
|
|
1375
|
+
const inputPolicyHash = 'policyHash' in input ? input.policyHash : void 0;
|
|
1375
1376
|
if (legalDocumentConsent && legalDocumentSnapshotVerification.valid) {
|
|
1376
1377
|
if (legalDocumentSnapshotVerification.payload.type !== type) throw buildLegalDocumentSnapshotHttpException('invalid');
|
|
1377
1378
|
const effectiveDate = new Date(legalDocumentSnapshotVerification.payload.effectiveDate);
|
|
@@ -1384,26 +1385,47 @@ const postSubjectHandler = async (c)=>{
|
|
|
1384
1385
|
});
|
|
1385
1386
|
policyId = documentPolicy.id;
|
|
1386
1387
|
} else if (legalDocumentConsent) {
|
|
1387
|
-
if (!ctx.legalDocumentSnapshot?.signingKey && !inputPolicyId) throw buildLegalDocumentProofHttpException('Legal document consent requires policyId when snapshot verification is disabled');
|
|
1388
|
-
if (
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1388
|
+
if (!ctx.legalDocumentSnapshot?.signingKey && !inputPolicyId && !inputPolicyHash) throw buildLegalDocumentProofHttpException('Legal document consent requires policyId or policyHash when snapshot verification is disabled');
|
|
1389
|
+
if (inputPolicyId) {
|
|
1390
|
+
policyId = inputPolicyId;
|
|
1391
|
+
const policy = await registry.findConsentPolicyById(inputPolicyId);
|
|
1392
|
+
if (!policy) throw new HTTPException(404, {
|
|
1393
|
+
message: 'Policy not found',
|
|
1394
|
+
cause: {
|
|
1395
|
+
code: 'POLICY_NOT_FOUND',
|
|
1396
|
+
policyId,
|
|
1397
|
+
type
|
|
1398
|
+
}
|
|
1399
|
+
});
|
|
1400
|
+
if (!policy.isActive) throw new HTTPException(400, {
|
|
1401
|
+
message: 'Policy is inactive',
|
|
1402
|
+
cause: {
|
|
1403
|
+
code: 'POLICY_INACTIVE',
|
|
1404
|
+
policyId,
|
|
1405
|
+
type
|
|
1406
|
+
}
|
|
1407
|
+
});
|
|
1408
|
+
} else if (inputPolicyHash) {
|
|
1409
|
+
const policy = await registry.findLegalDocumentPolicyByHash(type, inputPolicyHash);
|
|
1410
|
+
if (!policy) throw new HTTPException(404, {
|
|
1411
|
+
message: 'Policy not found',
|
|
1412
|
+
cause: {
|
|
1413
|
+
code: 'POLICY_NOT_FOUND',
|
|
1414
|
+
type,
|
|
1415
|
+
policyHash: inputPolicyHash
|
|
1416
|
+
}
|
|
1417
|
+
});
|
|
1418
|
+
if (!policy.isActive) throw new HTTPException(400, {
|
|
1419
|
+
message: 'Policy is inactive',
|
|
1420
|
+
cause: {
|
|
1421
|
+
code: 'POLICY_INACTIVE',
|
|
1422
|
+
policyId: policy.id,
|
|
1423
|
+
type,
|
|
1424
|
+
policyHash: inputPolicyHash
|
|
1425
|
+
}
|
|
1426
|
+
});
|
|
1427
|
+
policyId = policy.id;
|
|
1428
|
+
}
|
|
1407
1429
|
} else if (inputPolicyId) {
|
|
1408
1430
|
policyId = inputPolicyId;
|
|
1409
1431
|
const policy = await registry.findConsentPolicyById(inputPolicyId);
|
|
@@ -1466,6 +1488,13 @@ const postSubjectHandler = async (c)=>{
|
|
|
1466
1488
|
});
|
|
1467
1489
|
purposeIds = purposes;
|
|
1468
1490
|
}
|
|
1491
|
+
if (!policyId) throw new HTTPException(500, {
|
|
1492
|
+
message: 'Failed to resolve policy',
|
|
1493
|
+
cause: {
|
|
1494
|
+
code: 'POLICY_RESOLUTION_FAILED',
|
|
1495
|
+
type
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1469
1498
|
const expiryDays = effectivePolicy?.consent?.expiryDays;
|
|
1470
1499
|
const validUntil = 'number' == typeof expiryDays && Number.isFinite(expiryDays) ? new Date(givenAt.getTime() + 86400000 * Math.max(0, expiryDays)) : void 0;
|
|
1471
1500
|
const proofConfig = effectivePolicy?.proof;
|
|
@@ -1668,7 +1697,7 @@ const createSubjectRoutes = ()=>{
|
|
|
1668
1697
|
}), validator('param', getSubjectInputSchema), getSubjectHandler);
|
|
1669
1698
|
app.post('/', describeRoute({
|
|
1670
1699
|
summary: 'Record consent for a subject',
|
|
1671
|
-
description: "Creates a new consent record (append-only). Creates the subject if it does not exist.\n\n**Request body by `type`:**\n- `cookie_banner` – Requires `preferences` object\n- `privacy_policy`, `dpa`, `terms_and_conditions` –
|
|
1700
|
+
description: "Creates a new consent record (append-only). Creates the subject if it does not exist.\n\n**Request body by `type`:**\n- `cookie_banner` – Requires `preferences` object\n- `privacy_policy`, `dpa`, `terms_and_conditions` – Prefer a signed `documentSnapshotToken`; otherwise use a release `policyHash`, with `policyId` kept only for compatibility\n- `marketing_communications`, `age_verification`, `other` – Optional `preferences`",
|
|
1672
1701
|
tags: [
|
|
1673
1702
|
'Subject',
|
|
1674
1703
|
'Consent'
|
package/dist/942.js
ADDED
package/dist/cache.cjs
CHANGED
|
@@ -400,7 +400,7 @@ function createCacheKey(appName, namespace, ...parts) {
|
|
|
400
400
|
];
|
|
401
401
|
return allParts.join(':');
|
|
402
402
|
}
|
|
403
|
-
const GVL_ENDPOINT = 'https://gvl.
|
|
403
|
+
const GVL_ENDPOINT = 'https://gvl.inth.app';
|
|
404
404
|
const inflightRequests = new Map();
|
|
405
405
|
async function fetchGVLWithLanguage(language, vendorIds, endpoint = GVL_ENDPOINT) {
|
|
406
406
|
const sortedVendorIds = vendorIds ? [
|
package/dist/core.cjs
CHANGED
|
@@ -349,7 +349,7 @@ var __webpack_exports__ = {};
|
|
|
349
349
|
(()=>{
|
|
350
350
|
__webpack_require__.r(__webpack_exports__);
|
|
351
351
|
__webpack_require__.d(__webpack_exports__, {
|
|
352
|
-
version: ()=>
|
|
352
|
+
version: ()=>"2.0.2",
|
|
353
353
|
c15tInstance: ()=>c15tInstance,
|
|
354
354
|
EEA_COUNTRY_CODES: ()=>types_namespaceObject.EEA_COUNTRY_CODES,
|
|
355
355
|
EU_COUNTRY_CODES: ()=>types_namespaceObject.EU_COUNTRY_CODES,
|
|
@@ -393,6 +393,10 @@ var __webpack_exports__ = {};
|
|
|
393
393
|
const token = extractBearerToken(authHeader);
|
|
394
394
|
return validateApiKey(token, validKeys);
|
|
395
395
|
}
|
|
396
|
+
function matchesWildcard(origin, wildcardPattern) {
|
|
397
|
+
const wildcardDomain = wildcardPattern.slice(2);
|
|
398
|
+
return origin !== wildcardDomain && origin.endsWith(`.${wildcardDomain}`);
|
|
399
|
+
}
|
|
396
400
|
const WWW_REGEX = /^www\./;
|
|
397
401
|
const PROTOCOL_WWW_REGEX = /^https?:\/\/(www\.)?/;
|
|
398
402
|
const SUPPORTED_METHODS = [
|
|
@@ -476,6 +480,7 @@ var __webpack_exports__ = {};
|
|
|
476
480
|
const isTrusted = expandedTrusted.some((trusted)=>{
|
|
477
481
|
const normalizedTrusted = normalizeOrigin(trusted);
|
|
478
482
|
if ('localhost' === normalizedTrusted) return 'localhost' === normalizedOrigin || normalizedOrigin.startsWith('localhost:') || '127.0.0.1' === normalizedOrigin || normalizedOrigin.startsWith('127.0.0.1:') || '[::1]' === normalizedOrigin || normalizedOrigin.startsWith('[::1]:');
|
|
483
|
+
if (normalizedTrusted.startsWith('*.')) return matchesWildcard(normalizedOrigin, normalizedTrusted);
|
|
479
484
|
return normalizedTrusted === normalizedOrigin;
|
|
480
485
|
});
|
|
481
486
|
return isTrusted ? origin : null;
|
|
@@ -738,7 +743,6 @@ var __webpack_exports__ = {};
|
|
|
738
743
|
language
|
|
739
744
|
};
|
|
740
745
|
}
|
|
741
|
-
const version_version = '2.0.0-rc.8';
|
|
742
746
|
function extractErrorMessage(error) {
|
|
743
747
|
if (error instanceof AggregateError && error.errors?.length > 0) {
|
|
744
748
|
const inner = error.errors.map((e)=>e instanceof Error ? e.message : String(e)).join('; ');
|
|
@@ -753,7 +757,7 @@ var __webpack_exports__ = {};
|
|
|
753
757
|
const defaultAttributes = {
|
|
754
758
|
...telemetryConfig?.defaultAttributes || {},
|
|
755
759
|
'service.name': String(appName),
|
|
756
|
-
'service.version':
|
|
760
|
+
'service.version': "2.0.2"
|
|
757
761
|
};
|
|
758
762
|
if (tenantId) defaultAttributes['tenant.id'] = tenantId;
|
|
759
763
|
const config = {
|
|
@@ -2029,7 +2033,7 @@ var __webpack_exports__ = {};
|
|
|
2029
2033
|
].sort((a, b)=>a - b).join(',') : 'all';
|
|
2030
2034
|
return `${appName}:gvl:${language}:${sortedIds}`;
|
|
2031
2035
|
}
|
|
2032
|
-
const GVL_ENDPOINT = 'https://gvl.
|
|
2036
|
+
const GVL_ENDPOINT = 'https://gvl.inth.app';
|
|
2033
2037
|
const inflightRequests = new Map();
|
|
2034
2038
|
async function fetchGVLWithLanguage(language, vendorIds, endpoint = GVL_ENDPOINT) {
|
|
2035
2039
|
const sortedVendorIds = vendorIds ? [
|
|
@@ -2672,7 +2676,7 @@ Use for geo-targeted consent banners and regional compliance.`,
|
|
|
2672
2676
|
try {
|
|
2673
2677
|
await ctx.db.findFirst('subject', {});
|
|
2674
2678
|
return c.json({
|
|
2675
|
-
version:
|
|
2679
|
+
version: "2.0.2",
|
|
2676
2680
|
timestamp: new Date(),
|
|
2677
2681
|
client: clientInfo
|
|
2678
2682
|
});
|
|
@@ -3336,6 +3340,7 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
3336
3340
|
let purposeIds = [];
|
|
3337
3341
|
let appliedPreferences;
|
|
3338
3342
|
const inputPolicyId = 'policyId' in input ? input.policyId : void 0;
|
|
3343
|
+
const inputPolicyHash = 'policyHash' in input ? input.policyHash : void 0;
|
|
3339
3344
|
if (legalDocumentConsent && legalDocumentSnapshotVerification.valid) {
|
|
3340
3345
|
if (legalDocumentSnapshotVerification.payload.type !== type) throw buildLegalDocumentSnapshotHttpException('invalid');
|
|
3341
3346
|
const effectiveDate = new Date(legalDocumentSnapshotVerification.payload.effectiveDate);
|
|
@@ -3348,26 +3353,47 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
3348
3353
|
});
|
|
3349
3354
|
policyId = documentPolicy.id;
|
|
3350
3355
|
} else if (legalDocumentConsent) {
|
|
3351
|
-
if (!ctx.legalDocumentSnapshot?.signingKey && !inputPolicyId) throw buildLegalDocumentProofHttpException('Legal document consent requires policyId when snapshot verification is disabled');
|
|
3352
|
-
if (
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3356
|
+
if (!ctx.legalDocumentSnapshot?.signingKey && !inputPolicyId && !inputPolicyHash) throw buildLegalDocumentProofHttpException('Legal document consent requires policyId or policyHash when snapshot verification is disabled');
|
|
3357
|
+
if (inputPolicyId) {
|
|
3358
|
+
policyId = inputPolicyId;
|
|
3359
|
+
const policy = await registry.findConsentPolicyById(inputPolicyId);
|
|
3360
|
+
if (!policy) throw new http_exception_namespaceObject.HTTPException(404, {
|
|
3361
|
+
message: 'Policy not found',
|
|
3362
|
+
cause: {
|
|
3363
|
+
code: 'POLICY_NOT_FOUND',
|
|
3364
|
+
policyId,
|
|
3365
|
+
type
|
|
3366
|
+
}
|
|
3367
|
+
});
|
|
3368
|
+
if (!policy.isActive) throw new http_exception_namespaceObject.HTTPException(400, {
|
|
3369
|
+
message: 'Policy is inactive',
|
|
3370
|
+
cause: {
|
|
3371
|
+
code: 'POLICY_INACTIVE',
|
|
3372
|
+
policyId,
|
|
3373
|
+
type
|
|
3374
|
+
}
|
|
3375
|
+
});
|
|
3376
|
+
} else if (inputPolicyHash) {
|
|
3377
|
+
const policy = await registry.findLegalDocumentPolicyByHash(type, inputPolicyHash);
|
|
3378
|
+
if (!policy) throw new http_exception_namespaceObject.HTTPException(404, {
|
|
3379
|
+
message: 'Policy not found',
|
|
3380
|
+
cause: {
|
|
3381
|
+
code: 'POLICY_NOT_FOUND',
|
|
3382
|
+
type,
|
|
3383
|
+
policyHash: inputPolicyHash
|
|
3384
|
+
}
|
|
3385
|
+
});
|
|
3386
|
+
if (!policy.isActive) throw new http_exception_namespaceObject.HTTPException(400, {
|
|
3387
|
+
message: 'Policy is inactive',
|
|
3388
|
+
cause: {
|
|
3389
|
+
code: 'POLICY_INACTIVE',
|
|
3390
|
+
policyId: policy.id,
|
|
3391
|
+
type,
|
|
3392
|
+
policyHash: inputPolicyHash
|
|
3393
|
+
}
|
|
3394
|
+
});
|
|
3395
|
+
policyId = policy.id;
|
|
3396
|
+
}
|
|
3371
3397
|
} else if (inputPolicyId) {
|
|
3372
3398
|
policyId = inputPolicyId;
|
|
3373
3399
|
const policy = await registry.findConsentPolicyById(inputPolicyId);
|
|
@@ -3430,6 +3456,13 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
3430
3456
|
});
|
|
3431
3457
|
purposeIds = purposes;
|
|
3432
3458
|
}
|
|
3459
|
+
if (!policyId) throw new http_exception_namespaceObject.HTTPException(500, {
|
|
3460
|
+
message: 'Failed to resolve policy',
|
|
3461
|
+
cause: {
|
|
3462
|
+
code: 'POLICY_RESOLUTION_FAILED',
|
|
3463
|
+
type
|
|
3464
|
+
}
|
|
3465
|
+
});
|
|
3433
3466
|
const expiryDays = effectivePolicy?.consent?.expiryDays;
|
|
3434
3467
|
const validUntil = 'number' == typeof expiryDays && Number.isFinite(expiryDays) ? new Date(givenAt.getTime() + 86400000 * Math.max(0, expiryDays)) : void 0;
|
|
3435
3468
|
const proofConfig = effectivePolicy?.proof;
|
|
@@ -3632,7 +3665,7 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
3632
3665
|
}), (0, external_hono_openapi_namespaceObject.validator)('param', schema_.getSubjectInputSchema), getSubjectHandler);
|
|
3633
3666
|
app.post('/', (0, external_hono_openapi_namespaceObject.describeRoute)({
|
|
3634
3667
|
summary: 'Record consent for a subject',
|
|
3635
|
-
description: "Creates a new consent record (append-only). Creates the subject if it does not exist.\n\n**Request body by `type`:**\n- `cookie_banner` – Requires `preferences` object\n- `privacy_policy`, `dpa`, `terms_and_conditions` –
|
|
3668
|
+
description: "Creates a new consent record (append-only). Creates the subject if it does not exist.\n\n**Request body by `type`:**\n- `cookie_banner` – Requires `preferences` object\n- `privacy_policy`, `dpa`, `terms_and_conditions` – Prefer a signed `documentSnapshotToken`; otherwise use a release `policyHash`, with `policyId` kept only for compatibility\n- `marketing_communications`, `age_verification`, `other` – Optional `preferences`",
|
|
3636
3669
|
tags: [
|
|
3637
3670
|
'Subject',
|
|
3638
3671
|
'Consent'
|
|
@@ -3845,7 +3878,7 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
3845
3878
|
openapi: '3.1.0',
|
|
3846
3879
|
info: {
|
|
3847
3880
|
title: options.appName || 'c15t API',
|
|
3848
|
-
version:
|
|
3881
|
+
version: "2.0.2",
|
|
3849
3882
|
description: 'API for consent management'
|
|
3850
3883
|
},
|
|
3851
3884
|
servers: [
|
package/dist/core.js
CHANGED
|
@@ -7,7 +7,8 @@ import { HTTPException } from "hono/http-exception";
|
|
|
7
7
|
import { openAPIRouteHandler } from "hono-openapi";
|
|
8
8
|
import { compactDefined, dedupeTrimmedStrings, policyPackPresets } from "@c15t/schema";
|
|
9
9
|
import { EEA_COUNTRY_CODES, EU_COUNTRY_CODES, POLICY_MATCH_DATASET_VERSION, UK_COUNTRY_CODES, policyMatchers } from "@c15t/schema/types";
|
|
10
|
-
import {
|
|
10
|
+
import { matchesWildcard } from "./942.js";
|
|
11
|
+
import { extractErrorMessage, withDatabaseSpan, getTraceContext as create_telemetry_options_getTraceContext, createRequestSpan, handleSpanError, createTelemetryOptions, getMetrics, isTelemetryEnabled, withSpanContext } from "./302.js";
|
|
11
12
|
import { createLegalDocumentRoutes, createSubjectRoutes, generateUniqueId, createStatusRoute, createInitRoute, createConsentRoutes, policyRegistry } from "./915.js";
|
|
12
13
|
import { validateMessages, inspectPolicies as policy_inspectPolicies } from "./583.js";
|
|
13
14
|
import { DB } from "./db/schema.js";
|
|
@@ -119,6 +120,7 @@ function createCORSOptions(trustedOrigins) {
|
|
|
119
120
|
const isTrusted = expandedTrusted.some((trusted)=>{
|
|
120
121
|
const normalizedTrusted = normalizeOrigin(trusted);
|
|
121
122
|
if ('localhost' === normalizedTrusted) return 'localhost' === normalizedOrigin || normalizedOrigin.startsWith('localhost:') || '127.0.0.1' === normalizedOrigin || normalizedOrigin.startsWith('127.0.0.1:') || '[::1]' === normalizedOrigin || normalizedOrigin.startsWith('[::1]:');
|
|
123
|
+
if (normalizedTrusted.startsWith('*.')) return matchesWildcard(normalizedOrigin, normalizedTrusted);
|
|
122
124
|
return normalizedTrusted === normalizedOrigin;
|
|
123
125
|
});
|
|
124
126
|
return isTrusted ? origin : null;
|
|
@@ -765,7 +767,7 @@ const c15tInstance = (options)=>{
|
|
|
765
767
|
openapi: '3.1.0',
|
|
766
768
|
info: {
|
|
767
769
|
title: options.appName || 'c15t API',
|
|
768
|
-
version:
|
|
770
|
+
version: "2.0.2",
|
|
769
771
|
description: 'API for consent management'
|
|
770
772
|
},
|
|
771
773
|
servers: [
|
|
@@ -879,7 +881,7 @@ const c15tInstance = (options)=>{
|
|
|
879
881
|
getDocsUI
|
|
880
882
|
};
|
|
881
883
|
};
|
|
884
|
+
var core_version = "2.0.2";
|
|
882
885
|
export { defineConfig } from "./define-config.js";
|
|
883
886
|
export { inspectPolicies } from "./583.js";
|
|
884
|
-
export { version
|
|
885
|
-
export { EEA_COUNTRY_CODES, EU_COUNTRY_CODES, POLICY_MATCH_DATASET_VERSION, UK_COUNTRY_CODES, c15tInstance, policyBuilder, policyMatchers, policyPackPresets };
|
|
887
|
+
export { EEA_COUNTRY_CODES, EU_COUNTRY_CODES, POLICY_MATCH_DATASET_VERSION, UK_COUNTRY_CODES, c15tInstance, core_version as version, policyBuilder, policyMatchers, policyPackPresets };
|
package/dist/edge.cjs
CHANGED
|
@@ -348,7 +348,7 @@ function createGVLCacheKey(appName, language, vendorIds) {
|
|
|
348
348
|
].sort((a, b)=>a - b).join(',') : 'all';
|
|
349
349
|
return `${appName}:gvl:${language}:${sortedIds}`;
|
|
350
350
|
}
|
|
351
|
-
const GVL_ENDPOINT = 'https://gvl.
|
|
351
|
+
const GVL_ENDPOINT = 'https://gvl.inth.app';
|
|
352
352
|
const inflightRequests = new Map();
|
|
353
353
|
async function fetchGVLWithLanguage(language, vendorIds, endpoint = GVL_ENDPOINT) {
|
|
354
354
|
const sortedVendorIds = vendorIds ? [
|
|
@@ -908,13 +908,12 @@ async function resolveInitPayload(request, options, logger) {
|
|
|
908
908
|
}
|
|
909
909
|
};
|
|
910
910
|
}
|
|
911
|
-
|
|
912
|
-
function matchesWildcard(hostname, wildcardPattern, logger) {
|
|
911
|
+
function matchesWildcard(origin, wildcardPattern) {
|
|
913
912
|
const wildcardDomain = wildcardPattern.slice(2);
|
|
914
|
-
|
|
915
|
-
logger?.debug(`Wildcard match result: ${isValid} ${hostname} ends with .${wildcardDomain}`);
|
|
916
|
-
return isValid;
|
|
913
|
+
return origin !== wildcardDomain && origin.endsWith(`.${wildcardDomain}`);
|
|
917
914
|
}
|
|
915
|
+
const STRIP_REGEX = /^(?:https?:\/\/)|^(?:wss?:\/\/)|(?:\/+$)|(?::\d+$)/g;
|
|
916
|
+
const WWW_REGEX = /^www\./;
|
|
918
917
|
function isOriginTrusted(origin, trustedDomains, logger) {
|
|
919
918
|
try {
|
|
920
919
|
if (0 === trustedDomains.length) throw new Error('No trusted domains');
|
|
@@ -933,9 +932,15 @@ function isOriginTrusted(origin, trustedDomains, logger) {
|
|
|
933
932
|
}
|
|
934
933
|
const strippedDomain = domain.replace(STRIP_REGEX, '').toLowerCase();
|
|
935
934
|
logger?.debug(`Checking against stripped domain: ${strippedDomain}`);
|
|
936
|
-
if (strippedDomain.startsWith('*.'))
|
|
937
|
-
|
|
938
|
-
|
|
935
|
+
if (strippedDomain.startsWith('*.')) {
|
|
936
|
+
const isMatch = matchesWildcard(originHostname, strippedDomain);
|
|
937
|
+
logger?.debug(`Wildcard match result: ${isMatch} ${originHostname} matches ${strippedDomain}`);
|
|
938
|
+
return isMatch;
|
|
939
|
+
}
|
|
940
|
+
const normalizedOriginHostname = originHostname.replace(WWW_REGEX, '');
|
|
941
|
+
const normalizedDomain = strippedDomain.replace(WWW_REGEX, '');
|
|
942
|
+
const isMatch = normalizedOriginHostname === normalizedDomain;
|
|
943
|
+
logger?.debug(`Exact match result: ${isMatch} ${normalizedOriginHostname} === ${normalizedDomain}`);
|
|
939
944
|
return isMatch;
|
|
940
945
|
});
|
|
941
946
|
} catch (error) {
|
package/dist/edge.js
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { createLogger } from "@c15t/logger";
|
|
2
|
+
import { matchesWildcard } from "./942.js";
|
|
2
3
|
import { inspectPolicies as policy_inspectPolicies, policy_resolvePolicySync, resolveInitPayload, validateMessages, checkJurisdiction } from "./583.js";
|
|
3
4
|
const STRIP_REGEX = /^(?:https?:\/\/)|^(?:wss?:\/\/)|(?:\/+$)|(?::\d+$)/g;
|
|
4
|
-
|
|
5
|
-
const wildcardDomain = wildcardPattern.slice(2);
|
|
6
|
-
const isValid = hostname !== wildcardDomain && hostname.endsWith(`.${wildcardDomain}`);
|
|
7
|
-
logger?.debug(`Wildcard match result: ${isValid} ${hostname} ends with .${wildcardDomain}`);
|
|
8
|
-
return isValid;
|
|
9
|
-
}
|
|
5
|
+
const WWW_REGEX = /^www\./;
|
|
10
6
|
function isOriginTrusted(origin, trustedDomains, logger) {
|
|
11
7
|
try {
|
|
12
8
|
if (0 === trustedDomains.length) throw new Error('No trusted domains');
|
|
@@ -25,9 +21,15 @@ function isOriginTrusted(origin, trustedDomains, logger) {
|
|
|
25
21
|
}
|
|
26
22
|
const strippedDomain = domain.replace(STRIP_REGEX, '').toLowerCase();
|
|
27
23
|
logger?.debug(`Checking against stripped domain: ${strippedDomain}`);
|
|
28
|
-
if (strippedDomain.startsWith('*.'))
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
if (strippedDomain.startsWith('*.')) {
|
|
25
|
+
const isMatch = matchesWildcard(originHostname, strippedDomain);
|
|
26
|
+
logger?.debug(`Wildcard match result: ${isMatch} ${originHostname} matches ${strippedDomain}`);
|
|
27
|
+
return isMatch;
|
|
28
|
+
}
|
|
29
|
+
const normalizedOriginHostname = originHostname.replace(WWW_REGEX, '');
|
|
30
|
+
const normalizedDomain = strippedDomain.replace(WWW_REGEX, '');
|
|
31
|
+
const isMatch = normalizedOriginHostname === normalizedDomain;
|
|
32
|
+
logger?.debug(`Exact match result: ${isMatch} ${normalizedOriginHostname} === ${normalizedDomain}`);
|
|
31
33
|
return isMatch;
|
|
32
34
|
});
|
|
33
35
|
} catch (error) {
|
package/dist/router.cjs
CHANGED
|
@@ -550,7 +550,7 @@ function createGVLCacheKey(appName, language, vendorIds) {
|
|
|
550
550
|
].sort((a, b)=>a - b).join(',') : 'all';
|
|
551
551
|
return `${appName}:gvl:${language}:${sortedIds}`;
|
|
552
552
|
}
|
|
553
|
-
const GVL_ENDPOINT = 'https://gvl.
|
|
553
|
+
const GVL_ENDPOINT = 'https://gvl.inth.app';
|
|
554
554
|
const inflightRequests = new Map();
|
|
555
555
|
async function fetchGVLWithLanguage(language, vendorIds, endpoint = GVL_ENDPOINT) {
|
|
556
556
|
const sortedVendorIds = vendorIds ? [
|
|
@@ -1212,7 +1212,6 @@ class consent_policy_LegalDocumentPolicyConflictError extends Error {
|
|
|
1212
1212
|
this.name = 'LegalDocumentPolicyConflictError';
|
|
1213
1213
|
}
|
|
1214
1214
|
}
|
|
1215
|
-
const version_version = '2.0.0-rc.8';
|
|
1216
1215
|
function getHeaders(headers) {
|
|
1217
1216
|
if (!headers) return {
|
|
1218
1217
|
countryCode: null,
|
|
@@ -1247,7 +1246,7 @@ const statusHandler = async (c)=>{
|
|
|
1247
1246
|
try {
|
|
1248
1247
|
await ctx.db.findFirst('subject', {});
|
|
1249
1248
|
return c.json({
|
|
1250
|
-
version:
|
|
1249
|
+
version: "2.0.2",
|
|
1251
1250
|
timestamp: new Date(),
|
|
1252
1251
|
client: clientInfo
|
|
1253
1252
|
});
|
|
@@ -1911,6 +1910,7 @@ const postSubjectHandler = async (c)=>{
|
|
|
1911
1910
|
let purposeIds = [];
|
|
1912
1911
|
let appliedPreferences;
|
|
1913
1912
|
const inputPolicyId = 'policyId' in input ? input.policyId : void 0;
|
|
1913
|
+
const inputPolicyHash = 'policyHash' in input ? input.policyHash : void 0;
|
|
1914
1914
|
if (legalDocumentConsent && legalDocumentSnapshotVerification.valid) {
|
|
1915
1915
|
if (legalDocumentSnapshotVerification.payload.type !== type) throw buildLegalDocumentSnapshotHttpException('invalid');
|
|
1916
1916
|
const effectiveDate = new Date(legalDocumentSnapshotVerification.payload.effectiveDate);
|
|
@@ -1923,26 +1923,47 @@ const postSubjectHandler = async (c)=>{
|
|
|
1923
1923
|
});
|
|
1924
1924
|
policyId = documentPolicy.id;
|
|
1925
1925
|
} else if (legalDocumentConsent) {
|
|
1926
|
-
if (!ctx.legalDocumentSnapshot?.signingKey && !inputPolicyId) throw buildLegalDocumentProofHttpException('Legal document consent requires policyId when snapshot verification is disabled');
|
|
1927
|
-
if (
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1926
|
+
if (!ctx.legalDocumentSnapshot?.signingKey && !inputPolicyId && !inputPolicyHash) throw buildLegalDocumentProofHttpException('Legal document consent requires policyId or policyHash when snapshot verification is disabled');
|
|
1927
|
+
if (inputPolicyId) {
|
|
1928
|
+
policyId = inputPolicyId;
|
|
1929
|
+
const policy = await registry.findConsentPolicyById(inputPolicyId);
|
|
1930
|
+
if (!policy) throw new http_exception_namespaceObject.HTTPException(404, {
|
|
1931
|
+
message: 'Policy not found',
|
|
1932
|
+
cause: {
|
|
1933
|
+
code: 'POLICY_NOT_FOUND',
|
|
1934
|
+
policyId,
|
|
1935
|
+
type
|
|
1936
|
+
}
|
|
1937
|
+
});
|
|
1938
|
+
if (!policy.isActive) throw new http_exception_namespaceObject.HTTPException(400, {
|
|
1939
|
+
message: 'Policy is inactive',
|
|
1940
|
+
cause: {
|
|
1941
|
+
code: 'POLICY_INACTIVE',
|
|
1942
|
+
policyId,
|
|
1943
|
+
type
|
|
1944
|
+
}
|
|
1945
|
+
});
|
|
1946
|
+
} else if (inputPolicyHash) {
|
|
1947
|
+
const policy = await registry.findLegalDocumentPolicyByHash(type, inputPolicyHash);
|
|
1948
|
+
if (!policy) throw new http_exception_namespaceObject.HTTPException(404, {
|
|
1949
|
+
message: 'Policy not found',
|
|
1950
|
+
cause: {
|
|
1951
|
+
code: 'POLICY_NOT_FOUND',
|
|
1952
|
+
type,
|
|
1953
|
+
policyHash: inputPolicyHash
|
|
1954
|
+
}
|
|
1955
|
+
});
|
|
1956
|
+
if (!policy.isActive) throw new http_exception_namespaceObject.HTTPException(400, {
|
|
1957
|
+
message: 'Policy is inactive',
|
|
1958
|
+
cause: {
|
|
1959
|
+
code: 'POLICY_INACTIVE',
|
|
1960
|
+
policyId: policy.id,
|
|
1961
|
+
type,
|
|
1962
|
+
policyHash: inputPolicyHash
|
|
1963
|
+
}
|
|
1964
|
+
});
|
|
1965
|
+
policyId = policy.id;
|
|
1966
|
+
}
|
|
1946
1967
|
} else if (inputPolicyId) {
|
|
1947
1968
|
policyId = inputPolicyId;
|
|
1948
1969
|
const policy = await registry.findConsentPolicyById(inputPolicyId);
|
|
@@ -2005,6 +2026,13 @@ const postSubjectHandler = async (c)=>{
|
|
|
2005
2026
|
});
|
|
2006
2027
|
purposeIds = purposes;
|
|
2007
2028
|
}
|
|
2029
|
+
if (!policyId) throw new http_exception_namespaceObject.HTTPException(500, {
|
|
2030
|
+
message: 'Failed to resolve policy',
|
|
2031
|
+
cause: {
|
|
2032
|
+
code: 'POLICY_RESOLUTION_FAILED',
|
|
2033
|
+
type
|
|
2034
|
+
}
|
|
2035
|
+
});
|
|
2008
2036
|
const expiryDays = effectivePolicy?.consent?.expiryDays;
|
|
2009
2037
|
const validUntil = 'number' == typeof expiryDays && Number.isFinite(expiryDays) ? new Date(givenAt.getTime() + 86400000 * Math.max(0, expiryDays)) : void 0;
|
|
2010
2038
|
const proofConfig = effectivePolicy?.proof;
|
|
@@ -2207,7 +2235,7 @@ const createSubjectRoutes = ()=>{
|
|
|
2207
2235
|
}), (0, external_hono_openapi_namespaceObject.validator)('param', schema_namespaceObject.getSubjectInputSchema), getSubjectHandler);
|
|
2208
2236
|
app.post('/', (0, external_hono_openapi_namespaceObject.describeRoute)({
|
|
2209
2237
|
summary: 'Record consent for a subject',
|
|
2210
|
-
description: "Creates a new consent record (append-only). Creates the subject if it does not exist.\n\n**Request body by `type`:**\n- `cookie_banner` – Requires `preferences` object\n- `privacy_policy`, `dpa`, `terms_and_conditions` –
|
|
2238
|
+
description: "Creates a new consent record (append-only). Creates the subject if it does not exist.\n\n**Request body by `type`:**\n- `cookie_banner` – Requires `preferences` object\n- `privacy_policy`, `dpa`, `terms_and_conditions` – Prefer a signed `documentSnapshotToken`; otherwise use a release `policyHash`, with `policyId` kept only for compatibility\n- `marketing_communications`, `age_verification`, `other` – Optional `preferences`",
|
|
2211
2239
|
tags: [
|
|
2212
2240
|
'Subject',
|
|
2213
2241
|
'Consent'
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 1. Bundled translations (checked first)
|
|
6
6
|
* 2. In-memory cache
|
|
7
7
|
* 3. External cache (Redis/KV)
|
|
8
|
-
* 4. Fetch from gvl.
|
|
8
|
+
* 4. Fetch from gvl.inth.app
|
|
9
9
|
*
|
|
10
10
|
* @packageDocumentation
|
|
11
11
|
*/
|
|
@@ -38,7 +38,7 @@ export interface GVLResolverOptions {
|
|
|
38
38
|
vendorIds?: number[];
|
|
39
39
|
/**
|
|
40
40
|
* Override the default GVL endpoint.
|
|
41
|
-
* @default 'https://gvl.
|
|
41
|
+
* @default 'https://gvl.inth.app'
|
|
42
42
|
*/
|
|
43
43
|
endpoint?: string;
|
|
44
44
|
}
|
|
@@ -63,7 +63,7 @@ export interface GVLResolver {
|
|
|
63
63
|
* 1. **Bundled** - Check bundled translations (0ms)
|
|
64
64
|
* 2. **In-Memory** - Check worker/process memory cache (0ms)
|
|
65
65
|
* 3. **External Cache** - Check Redis/KV if configured (20-40ms)
|
|
66
|
-
* 4. **Fetch** - Fetch from gvl.
|
|
66
|
+
* 4. **Fetch** - Fetch from gvl.inth.app with Accept-Language (100-300ms)
|
|
67
67
|
*
|
|
68
68
|
* @param options - Resolver configuration
|
|
69
69
|
* @returns A GVL resolver instance
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared wildcard matching utilities for CORS origin checks.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Checks if an origin matches a wildcard domain pattern.
|
|
8
|
+
*
|
|
9
|
+
* @param origin - Hostname or normalized origin to check
|
|
10
|
+
* @param wildcardPattern - Wildcard pattern (e.g. *.example.com)
|
|
11
|
+
* @returns true if the origin is a subdomain of the wildcard pattern
|
|
12
|
+
*/
|
|
13
|
+
export declare function matchesWildcard(origin: string, wildcardPattern: string): boolean;
|
|
@@ -8,8 +8,8 @@ import type { PolicyConfig, PolicyModel, PolicyScopeMode, PolicyUiMode, PolicyUi
|
|
|
8
8
|
* Use the builder helpers to normalize this input into runtime-ready policy
|
|
9
9
|
* configs.
|
|
10
10
|
*
|
|
11
|
-
* @see {@link https://
|
|
12
|
-
* @see {@link https://
|
|
11
|
+
* @see {@link https://c15t.com/docs/self-host/guides/policy-packs}
|
|
12
|
+
* @see {@link https://c15t.com/docs/frameworks/react/concepts/policy-packs}
|
|
13
13
|
*/
|
|
14
14
|
export interface PolicyBuilderInput {
|
|
15
15
|
id: string;
|
|
@@ -57,7 +57,7 @@ export interface PolicyBuilderInput {
|
|
|
57
57
|
* Empty optional fields are removed from the final output, matcher input is
|
|
58
58
|
* merged into `match`, and duplicate string arrays are deduplicated.
|
|
59
59
|
*
|
|
60
|
-
* @see {@link https://
|
|
60
|
+
* @see {@link https://c15t.com/docs/self-host/guides/policy-packs}
|
|
61
61
|
*/
|
|
62
62
|
export declare function buildPolicyConfig(input: PolicyBuilderInput): PolicyConfig;
|
|
63
63
|
/**
|
|
@@ -68,7 +68,7 @@ export declare function buildPolicyConfig(input: PolicyBuilderInput): PolicyConf
|
|
|
68
68
|
* duplicate region/country matchers use first-match-wins semantics within the
|
|
69
69
|
* same matcher type.
|
|
70
70
|
*
|
|
71
|
-
* @see {@link https://
|
|
71
|
+
* @see {@link https://c15t.com/docs/self-host/guides/policy-packs}
|
|
72
72
|
*/
|
|
73
73
|
export declare function buildPolicyPack(inputs: PolicyBuilderInput[]): PolicyConfig[];
|
|
74
74
|
/**
|
|
@@ -84,7 +84,7 @@ export declare function buildPolicyPack(inputs: PolicyBuilderInput[]): PolicyCon
|
|
|
84
84
|
* When `defaultPolicy` is provided, its `countries` and `regions` fields are
|
|
85
85
|
* stripped and replaced with `match.isDefault = true`.
|
|
86
86
|
*
|
|
87
|
-
* @see {@link https://
|
|
87
|
+
* @see {@link https://c15t.com/docs/self-host/guides/policy-packs}
|
|
88
88
|
*/
|
|
89
89
|
export declare function buildPolicyPackWithDefault(inputs: PolicyBuilderInput[], defaultPolicy?: PolicyBuilderInput): PolicyConfig[];
|
|
90
90
|
/**
|
|
@@ -107,7 +107,7 @@ export declare function buildPolicyPackWithDefault(inputs: PolicyBuilderInput[],
|
|
|
107
107
|
* );
|
|
108
108
|
* ```
|
|
109
109
|
*
|
|
110
|
-
* @see {@link https://
|
|
110
|
+
* @see {@link https://c15t.com/docs/self-host/guides/policy-packs}
|
|
111
111
|
*/
|
|
112
112
|
export declare function composePacks(...packs: PolicyConfig[][]): PolicyConfig[];
|
|
113
113
|
/**
|
|
@@ -117,7 +117,7 @@ export declare function composePacks(...packs: PolicyConfig[][]): PolicyConfig[]
|
|
|
117
117
|
* Useful when you prefer a grouped API such as `policyBuilder.createPack()`
|
|
118
118
|
* inside backend config files.
|
|
119
119
|
*
|
|
120
|
-
* @see {@link https://
|
|
120
|
+
* @see {@link https://c15t.com/docs/self-host/guides/policy-packs}
|
|
121
121
|
*/
|
|
122
122
|
export declare const policyBuilder: {
|
|
123
123
|
create: typeof buildPolicyConfig;
|
|
@@ -13,7 +13,7 @@ export interface DatabaseOptions {
|
|
|
13
13
|
/**
|
|
14
14
|
* The database adapter to use.
|
|
15
15
|
*
|
|
16
|
-
* @see {@link https://
|
|
16
|
+
* @see {@link https://c15t.com/docs/self-host/guides/database-setup}
|
|
17
17
|
*/
|
|
18
18
|
adapter: FumaDB<FumaDBSchema>['adapter'];
|
|
19
19
|
/**
|
|
@@ -26,7 +26,7 @@ export interface DatabaseOptions {
|
|
|
26
26
|
* Useful when sharing a database with other applications to avoid naming conflicts.
|
|
27
27
|
*
|
|
28
28
|
* @example 'c15t_' // tables become: c15t_subject, c15t_consent, etc.
|
|
29
|
-
* @see {@link https://
|
|
29
|
+
* @see {@link https://c15t.com/docs/self-host/guides/database-setup}
|
|
30
30
|
*/
|
|
31
31
|
tablePrefix?: string;
|
|
32
32
|
}
|
|
@@ -177,7 +177,7 @@ export interface IABOptions {
|
|
|
177
177
|
vendorIds?: number[];
|
|
178
178
|
/**
|
|
179
179
|
* Override the default GVL endpoint.
|
|
180
|
-
* @default 'https://gvl.
|
|
180
|
+
* @default 'https://gvl.inth.app'
|
|
181
181
|
*/
|
|
182
182
|
endpoint?: string;
|
|
183
183
|
/**
|
|
@@ -297,7 +297,7 @@ export interface C15TOptions {
|
|
|
297
297
|
/**
|
|
298
298
|
* The database adapter to use.
|
|
299
299
|
*
|
|
300
|
-
* @see {@link https://
|
|
300
|
+
* @see {@link https://c15t.com/docs/self-host/guides/database-setup}
|
|
301
301
|
*/
|
|
302
302
|
adapter: FumaDB<FumaDBSchema>['adapter'];
|
|
303
303
|
/**
|
|
@@ -310,7 +310,7 @@ export interface C15TOptions {
|
|
|
310
310
|
* Useful when sharing a database with other applications to avoid naming conflicts.
|
|
311
311
|
*
|
|
312
312
|
* @example 'c15t_' // tables become: c15t_subject, c15t_consent, etc.
|
|
313
|
-
* @see {@link https://
|
|
313
|
+
* @see {@link https://c15t.com/docs/self-host/guides/database-setup}
|
|
314
314
|
*/
|
|
315
315
|
tablePrefix?: string;
|
|
316
316
|
/**
|
|
@@ -319,13 +319,13 @@ export interface C15TOptions {
|
|
|
319
319
|
* and cache key prefixing.
|
|
320
320
|
*
|
|
321
321
|
* @default "c15t"
|
|
322
|
-
* @see {@link https://
|
|
322
|
+
* @see {@link https://c15t.com/docs/self-host/api/configuration}
|
|
323
323
|
*/
|
|
324
324
|
appName?: string;
|
|
325
325
|
/**
|
|
326
326
|
* Base path prefix for all API routes (e.g. `/api/self-host`).
|
|
327
327
|
*
|
|
328
|
-
* @see {@link https://
|
|
328
|
+
* @see {@link https://c15t.com/docs/self-host/api/endpoints}
|
|
329
329
|
*/
|
|
330
330
|
basePath?: string;
|
|
331
331
|
/**
|
|
@@ -333,7 +333,7 @@ export interface C15TOptions {
|
|
|
333
333
|
* Protocol is optional; matching is protocol-agnostic and normalized.
|
|
334
334
|
*
|
|
335
335
|
* @example ['example.com', 'app.example.com', 'localhost:3000']
|
|
336
|
-
* @see {@link https://
|
|
336
|
+
* @see {@link https://c15t.com/docs/self-host/api/configuration}
|
|
337
337
|
*/
|
|
338
338
|
trustedOrigins: string[];
|
|
339
339
|
/** Logger configuration. */
|
|
@@ -373,7 +373,7 @@ export interface C15TOptions {
|
|
|
373
373
|
* explicit no-banner mode. In production, prefer including a default policy
|
|
374
374
|
* so unmatched traffic still resolves deterministically.
|
|
375
375
|
*
|
|
376
|
-
* @see {@link https://
|
|
376
|
+
* @see {@link https://c15t.com/docs/self-host/guides/policy-packs}
|
|
377
377
|
*/
|
|
378
378
|
policyPacks?: PolicyConfig[];
|
|
379
379
|
/**
|
|
@@ -386,7 +386,7 @@ export interface C15TOptions {
|
|
|
386
386
|
/**
|
|
387
387
|
* OpenAPI spec generation and documentation UI options.
|
|
388
388
|
*
|
|
389
|
-
* @see {@link https://
|
|
389
|
+
* @see {@link https://c15t.com/docs/self-host/api/endpoints}
|
|
390
390
|
*/
|
|
391
391
|
openapi?: OpenAPIOptions;
|
|
392
392
|
/**
|
|
@@ -394,20 +394,20 @@ export interface C15TOptions {
|
|
|
394
394
|
* Telemetry is opt-in and disabled by default.
|
|
395
395
|
* Users must provide their own SDK setup (Node, Bun, edge, etc.).
|
|
396
396
|
*
|
|
397
|
-
* @see {@link https://
|
|
397
|
+
* @see {@link https://c15t.com/docs/self-host/guides/observability}
|
|
398
398
|
*/
|
|
399
399
|
telemetry?: TelemetryOptions;
|
|
400
400
|
/**
|
|
401
401
|
* IP address tracking and masking options.
|
|
402
402
|
*
|
|
403
|
-
* @see {@link https://
|
|
403
|
+
* @see {@link https://c15t.com/docs/self-host/api/configuration}
|
|
404
404
|
*/
|
|
405
405
|
ipAddress?: IPAddressOptions;
|
|
406
406
|
/**
|
|
407
407
|
* Cache configuration for external persistent storage.
|
|
408
408
|
* Used for caching GVL and other data.
|
|
409
409
|
*
|
|
410
|
-
* @see {@link https://
|
|
410
|
+
* @see {@link https://c15t.com/docs/self-host/guides/caching}
|
|
411
411
|
*/
|
|
412
412
|
cache?: CacheOptions;
|
|
413
413
|
/**
|
|
@@ -425,7 +425,7 @@ export interface C15TOptions {
|
|
|
425
425
|
* Disabled by default - most users don't need IAB TCF.
|
|
426
426
|
* Set enabled: true to activate IAB support.
|
|
427
427
|
*
|
|
428
|
-
* @see {@link https://
|
|
428
|
+
* @see {@link https://c15t.com/docs/self-host/guides/iab-tcf}
|
|
429
429
|
*/
|
|
430
430
|
iab?: IABOptions;
|
|
431
431
|
/**
|
package/dist-types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "2.0.
|
|
1
|
+
export declare const version = "2.0.2";
|
|
@@ -10,24 +10,24 @@ All options are passed to `c15tInstance()`. Only `adapter` and `trustedOrigins`
|
|
|
10
10
|
|
|
11
11
|
|Property|Type|Description|Default|Required|
|
|
12
12
|
|:--|:--|:--|:--|:--:|
|
|
13
|
-
|adapter|[FumaDBAdapter](https://
|
|
13
|
+
|adapter|[FumaDBAdapter](https://c15t.com/docs/self-host/guides/database-setup)|The database adapter to use.|-|✅ Required|
|
|
14
14
|
|tenantId|string \|undefined|Tenant ID for multi-tenant deployments. When set, all database queries are automatically scoped to this tenant.|-|Optional|
|
|
15
|
-
|tablePrefix|[string \|undefined](https://
|
|
16
|
-
|appName|[string \|undefined](https://
|
|
17
|
-
|basePath|[string \|undefined](https://
|
|
18
|
-
|trustedOrigins|[string\[\]](https://
|
|
15
|
+
|tablePrefix|[string \|undefined](https://c15t.com/docs/self-host/guides/database-setup)|Optional prefix for all database table names. Useful when sharing a database with other applications to avoid naming conflicts.|-|Optional|
|
|
16
|
+
|appName|[string \|undefined](https://c15t.com/docs/self-host/api/configuration)|Application name used as backend metadata and identity. Returned by \`/init\` (\`appName\`), used in logs, telemetry defaults (\`service.name\`), and cache key prefixing.|"c15t"|Optional|
|
|
17
|
+
|basePath|[string \|undefined](https://c15t.com/docs/self-host/api/endpoints)|Base path prefix for all API routes (e.g. \`/api/self-host\`).|-|Optional|
|
|
18
|
+
|trustedOrigins|[string\[\]](https://c15t.com/docs/self-host/api/configuration)|Allowed origins for CORS. Required for browser-based consent collection. Protocol is optional; matching is protocol-agnostic and normalized.|-|✅ Required|
|
|
19
19
|
|logger|LoggerOptions \|undefined|Logger configuration.|-|Optional|
|
|
20
20
|
|disableGeoLocation|boolean \|undefined|Disables the use of Geo Location to determine the jurisdiction. When enabled, the jurisdiction will be set to "GDPR" to show the strictest version of the banner as we don't know the jurisdiction in this case.|false|Optional|
|
|
21
21
|
|customTranslations|Record\<string, Partial\<Translations>> \|undefined|Override base translations.|-|Optional|
|
|
22
22
|
|i18n|I18nOptions \|undefined|Internationalization message profiles used by runtime policies.|-|Optional|
|
|
23
|
-
|policyPacks|[PolicyConfig \|undefined](https://
|
|
23
|
+
|policyPacks|[PolicyConfig \|undefined](https://c15t.com/docs/self-host/guides/policy-packs)|Runtime regional policy pack resolved per request.|-|Optional|
|
|
24
24
|
|branding|"c15t" \|"consent" \|"none" \|"inth" \|undefined|Select which branding to show in the consent banner. Use "inth" for the INTH brand. "consent" is a deprecated alias for "inth". Use "none" to hide branding.|"c15t"|Optional|
|
|
25
|
-
|openapi|[OpenAPIOptions \|undefined](https://
|
|
26
|
-
|telemetry|[TelemetryOptions \|undefined](https://
|
|
27
|
-
|ipAddress|[IPAddressOptions \|undefined](https://
|
|
28
|
-
|cache|[CacheOptions \|undefined](https://
|
|
25
|
+
|openapi|[OpenAPIOptions \|undefined](https://c15t.com/docs/self-host/api/endpoints)|OpenAPI spec generation and documentation UI options.|-|Optional|
|
|
26
|
+
|telemetry|[TelemetryOptions \|undefined](https://c15t.com/docs/self-host/guides/observability)|OpenTelemetry configuration for tracing and metrics. Telemetry is opt-in and disabled by default. Users must provide their own SDK setup (Node, Bun, edge, etc.).|-|Optional|
|
|
27
|
+
|ipAddress|[IPAddressOptions \|undefined](https://c15t.com/docs/self-host/api/configuration)|IP address tracking and masking options.|-|Optional|
|
|
28
|
+
|cache|[CacheOptions \|undefined](https://c15t.com/docs/self-host/guides/caching)|Cache configuration for external persistent storage. Used for caching GVL and other data.|-|Optional|
|
|
29
29
|
|apiKeys|string\[] \|undefined|API keys for authenticated endpoints. Used for server-side endpoints like GET /subjects.|-|Optional|
|
|
30
|
-
|iab|[IABOptions \|undefined](https://
|
|
30
|
+
|iab|[IABOptions \|undefined](https://c15t.com/docs/self-host/guides/iab-tcf)|IAB TCF configuration including GVL, CMP registration, and custom vendors. Disabled by default - most users don't need IAB TCF. Set enabled: true to activate IAB support.|-|Optional|
|
|
31
31
|
|policySnapshot|PolicySnapshotOptions \|undefined|Optional signed policy snapshots used to keep /init and /subjects consistent.|-|Optional|
|
|
32
32
|
|legalDocumentSnapshot|LegalDocumentSnapshotOptions \|undefined|Optional signed legal-document snapshots issued by external document renderers.|-|Optional|
|
|
33
33
|
|background|BackgroundOptions \|undefined|Optional background task runner for non-critical side effects.|-|Optional|
|
|
@@ -157,7 +157,7 @@ OpenAPI specification options
|
|
|
157
157
|
|cmpId|number \|undefined|CMP ID registered with IAB Europe. This is returned to clients via the /init endpoint so they can use the correct CMP identity in TC Strings. See List of registered CMPs: https\://iabeurope.eu/cmp-list/|-|Optional|
|
|
158
158
|
|bundled|Object \|undefined \|null|Bundled GVL translations by language code. These are checked first before any cache or fetch.|-|Optional|
|
|
159
159
|
|vendorIds|number\[] \|undefined|Vendor IDs to filter when fetching non-bundled languages. Reduces payload size.|-|Optional|
|
|
160
|
-
|endpoint|string \|undefined|Override the default GVL endpoint.|'https\://gvl.
|
|
160
|
+
|endpoint|string \|undefined|Override the default GVL endpoint.|'https\://gvl.inth.app'|Optional|
|
|
161
161
|
|customVendors|Array\<Object> \|undefined|Custom vendors not registered with IAB. These are synced to the frontend via the /init endpoint.|-|Optional|
|
|
162
162
|
|
|
163
163
|
## Return Value
|
|
@@ -95,10 +95,10 @@ To create & update the database you can use the migrator which is included in th
|
|
|
95
95
|
|
|
96
96
|
|Package manager|Command|
|
|
97
97
|
|:--|:--|
|
|
98
|
-
|npm|`npx @c15t/cli
|
|
99
|
-
|pnpm|`pnpm dlx @c15t/cli
|
|
100
|
-
|yarn|`yarn dlx @c15t/cli
|
|
101
|
-
|bun|`bunx @c15t/cli
|
|
98
|
+
|npm|`npx @c15t/cli`|
|
|
99
|
+
|pnpm|`pnpm dlx @c15t/cli`|
|
|
100
|
+
|yarn|`yarn dlx @c15t/cli`|
|
|
101
|
+
|bun|`bunx @c15t/cli`|
|
|
102
102
|
|
|
103
103
|
## Table Prefix
|
|
104
104
|
|
package/docs/guides/iab-tcf.md
CHANGED
|
@@ -6,15 +6,15 @@ The c15t backend optionally supports [IAB TCF v2.3](https://iabeurope.eu/transpa
|
|
|
6
6
|
|
|
7
7
|
## CMP Registration
|
|
8
8
|
|
|
9
|
-
[
|
|
9
|
+
[Inth](https://inth.com), c15t's hosted platform, is IAB TCF certified. If you use Inth instead of self-hosting, the CMP ID is automatically provided to clients — no additional configuration needed.
|
|
10
10
|
|
|
11
|
-
If you self-host
|
|
11
|
+
If you self-host, you need your own CMP registration with IAB Europe and must configure your CMP ID via `iab.cmpId`. Registering your own CMP may also involve IAB Europe fees, so check IAB Europe's current CMP registration terms and pricing before choosing this route. The backend returns this value in the `/init` response so clients use the correct CMP identity in TC Strings.
|
|
12
12
|
|
|
13
13
|
> ℹ️ **Info:**
|
|
14
14
|
> A valid (non-zero) CMP ID is required for IAB TCF compliance. If neither the backend nor the client provides a CMP ID, IAB initialization will fail with an error.
|
|
15
15
|
>
|
|
16
16
|
> ℹ️ **Info:**
|
|
17
|
-
> If you heavily customize or build your own IAB banner or dialog
|
|
17
|
+
> If you heavily customize or build your own IAB banner or dialog instead of using the default IABConsentBanner and IABConsentDialog components provided by c15t, you cannot use Inth's CMP ID. You must register your own CMP with IAB Europe and configure your CMP ID via iab.cmpId.
|
|
18
18
|
|
|
19
19
|
## Enable IAB
|
|
20
20
|
|
|
@@ -25,13 +25,13 @@ export const c15t = c15tInstance({
|
|
|
25
25
|
// ...
|
|
26
26
|
iab: {
|
|
27
27
|
enabled: true,
|
|
28
|
-
cmpId:
|
|
28
|
+
cmpId: Number('<YOUR_CMP_ID>'), // replace with your registered CMP ID
|
|
29
29
|
vendorIds: [755, 52, 69], // only include vendors you use
|
|
30
30
|
},
|
|
31
31
|
});
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
The backend fetches the GVL from `https://gvl.
|
|
34
|
+
The backend fetches the GVL from `https://gvl.inth.com` by default and caches it. The `/init` endpoint returns the filtered GVL and your CMP ID to the frontend, so use the same `iab.cmpId` pattern in every self-hosted configuration.
|
|
35
35
|
|
|
36
36
|
## Custom GVL Endpoint
|
|
37
37
|
|
|
@@ -40,6 +40,7 @@ Point to your own GVL mirror:
|
|
|
40
40
|
```ts
|
|
41
41
|
iab: {
|
|
42
42
|
enabled: true,
|
|
43
|
+
cmpId: Number('<YOUR_CMP_ID>'), // replace with your registered CMP ID
|
|
43
44
|
endpoint: 'https://your-gvl-mirror.com',
|
|
44
45
|
vendorIds: [755, 52, 69],
|
|
45
46
|
},
|
|
@@ -55,6 +56,7 @@ import gvlDe from './gvl/de.json';
|
|
|
55
56
|
|
|
56
57
|
iab: {
|
|
57
58
|
enabled: true,
|
|
59
|
+
cmpId: Number('<YOUR_CMP_ID>'), // replace with your registered CMP ID
|
|
58
60
|
bundled: {
|
|
59
61
|
en: gvlEn,
|
|
60
62
|
de: gvlDe,
|
|
@@ -72,6 +74,7 @@ Add your own vendors alongside IAB-registered ones:
|
|
|
72
74
|
```ts
|
|
73
75
|
iab: {
|
|
74
76
|
enabled: true,
|
|
77
|
+
cmpId: Number('<YOUR_CMP_ID>'), // replace with your registered CMP ID
|
|
75
78
|
vendorIds: [755],
|
|
76
79
|
customVendors: [
|
|
77
80
|
{
|
package/docs/quickstart.md
CHANGED
|
@@ -6,7 +6,7 @@ The `@c15t/backend` package gives you a fully self-hosted consent management API
|
|
|
6
6
|
|
|
7
7
|
The backend exposes a standard `(request: Request) => Promise<Response>` handler, so it works with any JavaScript runtime (Node.js, Bun, Deno, Cloudflare Workers) and any HTTP framework.
|
|
8
8
|
|
|
9
|
-
If you want a fully managed experience we recommend using [
|
|
9
|
+
If you want a fully managed experience we recommend using [inth.com](https://inth.com).
|
|
10
10
|
|
|
11
11
|
## Installation
|
|
12
12
|
|
|
@@ -76,10 +76,10 @@ If you want a fully managed experience we recommend using [consent.io](https://c
|
|
|
76
76
|
|
|
77
77
|
|Package manager|Command|
|
|
78
78
|
|:--|:--|
|
|
79
|
-
|npm|`npx @c15t/cli
|
|
80
|
-
|pnpm|`pnpm dlx @c15t/cli
|
|
81
|
-
|yarn|`yarn dlx @c15t/cli
|
|
82
|
-
|bun|`bunx @c15t/cli
|
|
79
|
+
|npm|`npx @c15t/cli`|
|
|
80
|
+
|pnpm|`pnpm dlx @c15t/cli`|
|
|
81
|
+
|yarn|`yarn dlx @c15t/cli`|
|
|
82
|
+
|bun|`bunx @c15t/cli`|
|
|
83
83
|
|
|
84
84
|
See Database Setup for adapter-specific migration guides. If you plan to use policy packs with runtime audit storage, also apply the runtime policy decision migration from the Policy Packs guide.
|
|
85
85
|
|
|
@@ -119,10 +119,10 @@ Install c15t agent skills to let AI agents help with styling, i18n, scripts & ot
|
|
|
119
119
|
|
|
120
120
|
|Package manager|Command|
|
|
121
121
|
|:--|:--|
|
|
122
|
-
|npm|`npx @c15t/cli
|
|
123
|
-
|pnpm|`pnpm dlx @c15t/cli
|
|
124
|
-
|yarn|`yarn dlx @c15t/cli
|
|
125
|
-
|bun|`bunx @c15t/cli
|
|
122
|
+
|npm|`npx @c15t/cli skills`|
|
|
123
|
+
|pnpm|`pnpm dlx @c15t/cli skills`|
|
|
124
|
+
|yarn|`yarn dlx @c15t/cli skills`|
|
|
125
|
+
|bun|`bunx @c15t/cli skills`|
|
|
126
126
|
|
|
127
127
|
See [AI Agents](/docs/ai-agents) for bundled package docs and agent skills.
|
|
128
128
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c15t/backend",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "Consent policy engine and API for c15t. Powers the cookie banner, consent manager, and
|
|
3
|
+
"version": "2.0.2",
|
|
4
|
+
"description": "Consent policy engine and API for c15t. Powers the cookie banner, consent manager, and preference center. Webhooks, audit logs, storage adapters. Self-host or use inth.com",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"consent",
|
|
7
7
|
"privacy",
|
|
@@ -20,12 +20,15 @@
|
|
|
20
20
|
"consent-banner"
|
|
21
21
|
],
|
|
22
22
|
"homepage": "https://c15t.com/docs/self-host/v2",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/c15t/c15t/issues"
|
|
25
|
+
},
|
|
23
26
|
"repository": {
|
|
24
27
|
"type": "git",
|
|
25
28
|
"url": "https://github.com/c15t/c15t.git",
|
|
26
29
|
"directory": "packages/backend"
|
|
27
30
|
},
|
|
28
|
-
"license": "
|
|
31
|
+
"license": "Apache-2.0",
|
|
29
32
|
"type": "module",
|
|
30
33
|
"exports": {
|
|
31
34
|
".": {
|
|
@@ -113,19 +116,19 @@
|
|
|
113
116
|
"build": "bun prebuild && rslib build && bun ../../scripts/normalize-dist-types.mjs && bun ../../scripts/agent-docs/generate-package-docs.ts @c15t/backend",
|
|
114
117
|
"build:agent-docs": "bun ../../scripts/agent-docs/generate-package-docs.ts @c15t/backend",
|
|
115
118
|
"check-types": "bun prebuild && tsc --noEmit",
|
|
116
|
-
"dev": "bun prebuild && rslib build &&
|
|
119
|
+
"dev": "sh -c 'bun prebuild && rslib build --no-dts --no-clean && rslib build --watch --no-dts --no-clean'",
|
|
117
120
|
"fmt": "bun biome format --write . && bun biome check --formatter-enabled=false --linter-enabled=false --write",
|
|
118
121
|
"knip": "knip",
|
|
119
122
|
"lint": "bun biome lint ./src",
|
|
120
|
-
"prepack": "
|
|
123
|
+
"prepack": "bun run build",
|
|
121
124
|
"start": "node dist/server.cjs",
|
|
122
125
|
"test": "bun prebuild && vitest run",
|
|
123
126
|
"test:watch": "bun prebuild && vitest"
|
|
124
127
|
},
|
|
125
128
|
"dependencies": {
|
|
126
|
-
"@c15t/logger": "
|
|
127
|
-
"@c15t/schema": "2.0.0
|
|
128
|
-
"@c15t/translations": "2.0.0
|
|
129
|
+
"@c15t/logger": "2.0.0",
|
|
130
|
+
"@c15t/schema": "2.0.0",
|
|
131
|
+
"@c15t/translations": "2.0.0",
|
|
129
132
|
"@hono/standard-validator": "^0.2.2",
|
|
130
133
|
"@hono/valibot-validator": "0.6.1",
|
|
131
134
|
"@opentelemetry/api": "1.9.1",
|
|
@@ -141,7 +144,7 @@
|
|
|
141
144
|
"valibot": "1.3.1"
|
|
142
145
|
},
|
|
143
146
|
"devDependencies": {
|
|
144
|
-
"@c15t/typescript-config": "0.0.1
|
|
147
|
+
"@c15t/typescript-config": "0.0.1",
|
|
145
148
|
"@c15t/vitest-config": "1.0.0",
|
|
146
149
|
"@opentelemetry/sdk-trace-base": "^2.6.1",
|
|
147
150
|
"@types/node": "25.5.0",
|
package/readme.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"title": "@c15t/backend: Consent Management Backend",
|
|
3
|
-
"description": "Consent policy engine and API for c15t. Powers the cookie banner, consent manager, and
|
|
3
|
+
"description": "Consent policy engine and API for c15t. Powers the cookie banner, consent manager, and preference center. Webhooks, audit logs, storage adapters. Self-host or use inth.com",
|
|
4
4
|
"features": [
|
|
5
5
|
"Consent Management: Track and manage user consent preferences",
|
|
6
6
|
"Geo-Location: Identify user's location to show relevant consent preferences",
|