@c15t/backend 2.0.0 → 2.0.4

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 CHANGED
@@ -1,14 +1,14 @@
1
1
  <p align="center">
2
- <a href="https://c15t.com?utm_source=github&utm_medium=repopage_%40c15t%2Fbackend" target="_blank" rel="noopener noreferrer">
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
  [![GitHub stars](https://img.shields.io/github/stars/c15t/c15t?style=flat-square)](https://github.com/c15t/c15t)
13
13
  [![CI](https://img.shields.io/github/actions/workflow/status/c15t/c15t/ci.yml?style=flat-square)](https://github.com/c15t/c15t/actions/workflows/ci.yml)
14
14
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=flat-square)](https://github.com/c15t/c15t/blob/main/LICENSE.md)
@@ -18,7 +18,7 @@
18
18
  [![Last Commit](https://img.shields.io/github/last-commit/c15t/c15t?style=flat-square)](https://github.com/c15t/c15t/commits/main)
19
19
  [![Open Issues](https://img.shields.io/github/issues/c15t/c15t?style=flat-square)](https://github.com/c15t/c15t/issues)
20
20
 
21
- Consent policy engine and API for c15t. Powers the cookie banner, consent manager, and preferences centre. Webhooks, audit logs, storage adapters. Self host or use consent.io
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 [consent.io](https://consent.io) and use the chat widget
58
- - Contact our support team via email [support@consent.io](mailto:support@consent.io)
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 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.
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
 
@@ -86,4 +86,4 @@ Our preference is that you make use of GitHub's private vulnerability reporting
86
86
 
87
87
  ---
88
88
 
89
- **Built by [Inth](https://inth.com?utm_source=github&utm_medium=repopage_%40c15t%2Fbackend)**
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
@@ -13,7 +13,7 @@ function createTelemetryOptions(appName = 'c15t', telemetryConfig, tenantId) {
13
13
  const defaultAttributes = {
14
14
  ...telemetryConfig?.defaultAttributes || {},
15
15
  'service.name': String(appName),
16
- 'service.version': "2.0.0"
16
+ 'service.version': "2.0.4"
17
17
  };
18
18
  if (tenantId) defaultAttributes['tenant.id'] = tenantId;
19
19
  const config = {
@@ -368,7 +368,7 @@ function createCacheKey(appName, namespace, ...parts) {
368
368
  ];
369
369
  return allParts.join(':');
370
370
  }
371
- const GVL_ENDPOINT = 'https://gvl.consent.io';
371
+ const GVL_ENDPOINT = 'https://gvl.inth.app';
372
372
  const inflightRequests = new Map();
373
373
  async function fetchGVLWithLanguage(language, vendorIds, endpoint = GVL_ENDPOINT) {
374
374
  const sortedVendorIds = vendorIds ? [
package/dist/915.js CHANGED
@@ -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: "2.0.0",
712
+ version: "2.0.4",
713
713
  timestamp: new Date(),
714
714
  client: clientInfo
715
715
  });
@@ -1462,10 +1462,14 @@ const postSubjectHandler = async (c)=>{
1462
1462
  const hasWildcardCategoryScope = allowedCategories?.includes('*') === true;
1463
1463
  const appliedPreferenceEntries = Object.entries(preferences);
1464
1464
  let filteredAppliedPreferenceEntries = appliedPreferenceEntries;
1465
- if (allowedCategories && allowedCategories.length > 0 && !hasWildcardCategoryScope) {
1466
- const disallowed = appliedPreferenceEntries.map(([purpose])=>purpose).filter((purpose)=>!allowedCategories.includes(purpose));
1467
- filteredAppliedPreferenceEntries = appliedPreferenceEntries.filter(([purpose])=>allowedCategories.includes(purpose));
1468
- if (disallowed.length > 0 && 'strict' === effectiveScopeMode) throw new HTTPException(400, {
1465
+ if ('strict' === effectiveScopeMode && allowedCategories && allowedCategories.length > 0 && !hasWildcardCategoryScope) {
1466
+ const strictAllowedCategories = new Set([
1467
+ 'necessary',
1468
+ ...allowedCategories
1469
+ ]);
1470
+ const disallowed = appliedPreferenceEntries.map(([purpose])=>purpose).filter((purpose)=>!strictAllowedCategories.has(purpose));
1471
+ filteredAppliedPreferenceEntries = appliedPreferenceEntries.filter(([purpose])=>strictAllowedCategories.has(purpose));
1472
+ if (disallowed.length > 0) throw new HTTPException(400, {
1469
1473
  message: 'Preferences include categories not allowed by policy',
1470
1474
  cause: {
1471
1475
  code: 'PURPOSE_NOT_ALLOWED',
package/dist/942.js ADDED
@@ -0,0 +1,5 @@
1
+ function matchesWildcard(origin, wildcardPattern) {
2
+ const wildcardDomain = wildcardPattern.slice(2);
3
+ return origin !== wildcardDomain && origin.endsWith(`.${wildcardDomain}`);
4
+ }
5
+ export { matchesWildcard };
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.consent.io';
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: ()=>"2.0.0",
352
+ version: ()=>"2.0.4",
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;
@@ -752,7 +757,7 @@ var __webpack_exports__ = {};
752
757
  const defaultAttributes = {
753
758
  ...telemetryConfig?.defaultAttributes || {},
754
759
  'service.name': String(appName),
755
- 'service.version': "2.0.0"
760
+ 'service.version': "2.0.4"
756
761
  };
757
762
  if (tenantId) defaultAttributes['tenant.id'] = tenantId;
758
763
  const config = {
@@ -2028,7 +2033,7 @@ var __webpack_exports__ = {};
2028
2033
  ].sort((a, b)=>a - b).join(',') : 'all';
2029
2034
  return `${appName}:gvl:${language}:${sortedIds}`;
2030
2035
  }
2031
- const GVL_ENDPOINT = 'https://gvl.consent.io';
2036
+ const GVL_ENDPOINT = 'https://gvl.inth.app';
2032
2037
  const inflightRequests = new Map();
2033
2038
  async function fetchGVLWithLanguage(language, vendorIds, endpoint = GVL_ENDPOINT) {
2034
2039
  const sortedVendorIds = vendorIds ? [
@@ -2671,7 +2676,7 @@ Use for geo-targeted consent banners and regional compliance.`,
2671
2676
  try {
2672
2677
  await ctx.db.findFirst('subject', {});
2673
2678
  return c.json({
2674
- version: "2.0.0",
2679
+ version: "2.0.4",
2675
2680
  timestamp: new Date(),
2676
2681
  client: clientInfo
2677
2682
  });
@@ -3425,10 +3430,14 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
3425
3430
  const hasWildcardCategoryScope = allowedCategories?.includes('*') === true;
3426
3431
  const appliedPreferenceEntries = Object.entries(preferences);
3427
3432
  let filteredAppliedPreferenceEntries = appliedPreferenceEntries;
3428
- if (allowedCategories && allowedCategories.length > 0 && !hasWildcardCategoryScope) {
3429
- const disallowed = appliedPreferenceEntries.map(([purpose])=>purpose).filter((purpose)=>!allowedCategories.includes(purpose));
3430
- filteredAppliedPreferenceEntries = appliedPreferenceEntries.filter(([purpose])=>allowedCategories.includes(purpose));
3431
- if (disallowed.length > 0 && 'strict' === effectiveScopeMode) throw new http_exception_namespaceObject.HTTPException(400, {
3433
+ if ('strict' === effectiveScopeMode && allowedCategories && allowedCategories.length > 0 && !hasWildcardCategoryScope) {
3434
+ const strictAllowedCategories = new Set([
3435
+ 'necessary',
3436
+ ...allowedCategories
3437
+ ]);
3438
+ const disallowed = appliedPreferenceEntries.map(([purpose])=>purpose).filter((purpose)=>!strictAllowedCategories.has(purpose));
3439
+ filteredAppliedPreferenceEntries = appliedPreferenceEntries.filter(([purpose])=>strictAllowedCategories.has(purpose));
3440
+ if (disallowed.length > 0) throw new http_exception_namespaceObject.HTTPException(400, {
3432
3441
  message: 'Preferences include categories not allowed by policy',
3433
3442
  cause: {
3434
3443
  code: 'PURPOSE_NOT_ALLOWED',
@@ -3873,7 +3882,7 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
3873
3882
  openapi: '3.1.0',
3874
3883
  info: {
3875
3884
  title: options.appName || 'c15t API',
3876
- version: "2.0.0",
3885
+ version: "2.0.4",
3877
3886
  description: 'API for consent management'
3878
3887
  },
3879
3888
  servers: [
package/dist/core.js CHANGED
@@ -7,6 +7,7 @@ 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 { matchesWildcard } from "./942.js";
10
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";
@@ -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: "2.0.0",
770
+ version: "2.0.4",
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
  };
882
- var core_version = "2.0.0";
884
+ var core_version = "2.0.4";
883
885
  export { defineConfig } from "./define-config.js";
884
886
  export { inspectPolicies } from "./583.js";
885
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.consent.io';
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
- const STRIP_REGEX = /^(?:https?:\/\/)|^(?:wss?:\/\/)|(?:\/+$)|(?::\d+$)/g;
912
- function matchesWildcard(hostname, wildcardPattern, logger) {
911
+ function matchesWildcard(origin, wildcardPattern) {
913
912
  const wildcardDomain = wildcardPattern.slice(2);
914
- const isValid = hostname !== wildcardDomain && hostname.endsWith(`.${wildcardDomain}`);
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('*.')) return matchesWildcard(originHostname, strippedDomain, logger);
937
- const isMatch = originHostname === strippedDomain;
938
- logger?.debug(`Exact match result: ${isMatch} ${originHostname} === ${strippedDomain}`);
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
- function matchesWildcard(hostname, wildcardPattern, logger) {
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('*.')) return matchesWildcard(originHostname, strippedDomain, logger);
29
- const isMatch = originHostname === strippedDomain;
30
- logger?.debug(`Exact match result: ${isMatch} ${originHostname} === ${strippedDomain}`);
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.consent.io';
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 ? [
@@ -1246,7 +1246,7 @@ const statusHandler = async (c)=>{
1246
1246
  try {
1247
1247
  await ctx.db.findFirst('subject', {});
1248
1248
  return c.json({
1249
- version: "2.0.0",
1249
+ version: "2.0.4",
1250
1250
  timestamp: new Date(),
1251
1251
  client: clientInfo
1252
1252
  });
@@ -2000,10 +2000,14 @@ const postSubjectHandler = async (c)=>{
2000
2000
  const hasWildcardCategoryScope = allowedCategories?.includes('*') === true;
2001
2001
  const appliedPreferenceEntries = Object.entries(preferences);
2002
2002
  let filteredAppliedPreferenceEntries = appliedPreferenceEntries;
2003
- if (allowedCategories && allowedCategories.length > 0 && !hasWildcardCategoryScope) {
2004
- const disallowed = appliedPreferenceEntries.map(([purpose])=>purpose).filter((purpose)=>!allowedCategories.includes(purpose));
2005
- filteredAppliedPreferenceEntries = appliedPreferenceEntries.filter(([purpose])=>allowedCategories.includes(purpose));
2006
- if (disallowed.length > 0 && 'strict' === effectiveScopeMode) throw new http_exception_namespaceObject.HTTPException(400, {
2003
+ if ('strict' === effectiveScopeMode && allowedCategories && allowedCategories.length > 0 && !hasWildcardCategoryScope) {
2004
+ const strictAllowedCategories = new Set([
2005
+ 'necessary',
2006
+ ...allowedCategories
2007
+ ]);
2008
+ const disallowed = appliedPreferenceEntries.map(([purpose])=>purpose).filter((purpose)=>!strictAllowedCategories.has(purpose));
2009
+ filteredAppliedPreferenceEntries = appliedPreferenceEntries.filter(([purpose])=>strictAllowedCategories.has(purpose));
2010
+ if (disallowed.length > 0) throw new http_exception_namespaceObject.HTTPException(400, {
2007
2011
  message: 'Preferences include categories not allowed by policy',
2008
2012
  cause: {
2009
2013
  code: 'PURPOSE_NOT_ALLOWED',
@@ -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.consent.io
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.consent.io'
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.consent.io with Accept-Language (100-300ms)
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;
@@ -177,7 +177,7 @@ export interface IABOptions {
177
177
  vendorIds?: number[];
178
178
  /**
179
179
  * Override the default GVL endpoint.
180
- * @default 'https://gvl.consent.io'
180
+ * @default 'https://gvl.inth.app'
181
181
  */
182
182
  endpoint?: string;
183
183
  /**
@@ -1 +1 @@
1
- export declare const version = "2.0.0";
1
+ export declare const version = "2.0.4";
@@ -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.consent.io'|Optional|
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
@@ -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
- [inth.com](https://inth.com) is pending validation as an IAB Europe-registered CMP for c15t. Once approved, when using inth.com as your hosted backend, the CMP ID will be automatically provided to clients — no additional configuration needed.
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 and have your own CMP registration with IAB Europe, configure your CMP ID via `iab.cmpId`. This value is returned to clients in the `/init` response so they use the correct CMP identity in TC Strings.
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 (rather than using the default IABConsentBanner and IABConsentDialog components provided by c15t), you cannot use inth.com's CMP ID. You must register your own CMP with IAB Europe and configure your CMP ID via iab.cmpId.
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: 10, // your registered CMP ID (inth.com provides this automatically)
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.inth.com` by default and caches it. The `/init` endpoint returns the filtered GVL and your CMP ID to the frontend.
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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@c15t/backend",
3
- "version": "2.0.0",
4
- "description": "Consent policy engine and API for c15t. Powers the cookie banner, consent manager, and preferences centre. Webhooks, audit logs, storage adapters. Self host or use consent.io",
3
+ "version": "2.0.4",
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,6 +20,9 @@
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",
@@ -113,18 +116,18 @@
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 && bun ../../scripts/normalize-dist-types.mjs",
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": "cd ../.. && bunx turbo run build --filter=@c15t/backend",
123
+ "prepack": "bun ../../scripts/verify-package-artifacts.ts",
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
129
  "@c15t/logger": "2.0.0",
127
- "@c15t/schema": "2.0.0",
130
+ "@c15t/schema": "2.0.1",
128
131
  "@c15t/translations": "2.0.0",
129
132
  "@hono/standard-validator": "^0.2.2",
130
133
  "@hono/valibot-validator": "0.6.1",
@@ -133,9 +136,9 @@
133
136
  "@scalar/hono-api-reference": "0.10.5",
134
137
  "@valibot/to-json-schema": "1.6.0",
135
138
  "base-x": "5.0.1",
136
- "defu": "6.1.4",
139
+ "defu": "6.1.5",
137
140
  "fumadb": "0.2.2",
138
- "hono": "4.12.9",
141
+ "hono": "4.12.14",
139
142
  "hono-openapi": "1.3.0",
140
143
  "jose": "6.2.2",
141
144
  "valibot": "1.3.1"
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 preferences centre. Webhooks, audit logs, storage adapters. Self host or use consent.io",
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",