@c15t/backend 2.0.0-rc.0 → 2.0.0-rc.10
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 +3 -3
- package/dist/302.js +473 -0
- package/dist/583.js +540 -0
- package/dist/915.js +1771 -0
- package/dist/cache.cjs +5 -5
- package/dist/cache.js +4 -415
- package/dist/core.cjs +1356 -120
- package/dist/core.js +163 -1981
- package/dist/db/adapters/drizzle.cjs +1 -1
- package/dist/db/adapters/drizzle.js +1 -2
- package/dist/db/adapters/kysely.cjs +1 -1
- package/dist/db/adapters/kysely.js +1 -2
- package/dist/db/adapters/mongo.cjs +1 -1
- package/dist/db/adapters/mongo.js +1 -2
- package/dist/db/adapters/prisma.cjs +1 -1
- package/dist/db/adapters/prisma.js +1 -2
- package/dist/db/adapters/typeorm.cjs +1 -1
- package/dist/db/adapters/typeorm.js +1 -2
- package/dist/db/adapters.cjs +1 -1
- package/dist/db/migrator.cjs +1 -1
- package/dist/db/schema.cjs +43 -3
- package/dist/db/schema.js +35 -4
- package/dist/define-config.cjs +1 -1
- package/dist/edge.cjs +1106 -0
- package/dist/edge.js +190 -0
- package/dist/router.cjs +885 -123
- package/dist/router.js +1 -1507
- package/dist/{types.cjs → types/index.cjs} +1 -1
- package/{dist → dist-types}/cache/adapters/cloudflare-kv.d.ts +0 -1
- package/{dist → dist-types}/cache/adapters/index.d.ts +0 -1
- package/{dist → dist-types}/cache/adapters/memory.d.ts +0 -1
- package/{dist → dist-types}/cache/adapters/upstash-redis.d.ts +0 -1
- package/{dist → dist-types}/cache/gvl-resolver.d.ts +0 -1
- package/{dist → dist-types}/cache/index.d.ts +0 -1
- package/{dist → dist-types}/cache/keys.d.ts +0 -1
- package/{dist → dist-types}/cache/types.d.ts +0 -1
- package/{dist → dist-types}/core.d.ts +8 -1
- package/{dist → dist-types}/db/migrator/index.d.ts +0 -1
- package/dist-types/db/registry/consent-policy.d.ts +78 -0
- package/{dist → dist-types}/db/registry/consent-purpose.d.ts +0 -1
- package/{dist → dist-types}/db/registry/domain.d.ts +0 -1
- package/dist-types/db/registry/index.d.ts +118 -0
- package/dist-types/db/registry/runtime-policy-decision.d.ts +60 -0
- package/{dist → dist-types}/db/registry/subject.d.ts +0 -2
- package/{dist → dist-types}/db/registry/types.d.ts +1 -1
- package/{dist → dist-types}/db/registry/utils/generate-id.d.ts +0 -1
- package/{dist → dist-types}/db/registry/utils.d.ts +0 -1
- package/{dist → dist-types}/db/schema/1.0.0/audit-log.d.ts +0 -1
- package/{dist → dist-types}/db/schema/1.0.0/consent-policy.d.ts +0 -1
- package/{dist → dist-types}/db/schema/1.0.0/consent-purpose.d.ts +0 -1
- package/{dist → dist-types}/db/schema/1.0.0/consent-record.d.ts +0 -1
- package/{dist → dist-types}/db/schema/1.0.0/consent.d.ts +1 -2
- package/{dist → dist-types}/db/schema/1.0.0/domain.d.ts +0 -1
- package/{dist → dist-types}/db/schema/1.0.0/index.d.ts +0 -32
- package/{dist → dist-types}/db/schema/1.0.0/subject.d.ts +0 -2
- package/{dist → dist-types}/db/schema/2.0.0/audit-log.d.ts +1 -2
- package/{dist → dist-types}/db/schema/2.0.0/consent-policy.d.ts +3 -3
- package/{dist → dist-types}/db/schema/2.0.0/consent-purpose.d.ts +1 -2
- package/{dist → dist-types}/db/schema/2.0.0/consent.d.ts +7 -2
- package/{dist → dist-types}/db/schema/2.0.0/domain.d.ts +1 -2
- package/{dist → dist-types}/db/schema/2.0.0/index.d.ts +455 -28
- package/dist-types/db/schema/2.0.0/runtime-policy-decision.d.ts +23 -0
- package/{dist → dist-types}/db/schema/2.0.0/subject.d.ts +1 -3
- package/{dist → dist-types}/db/schema/index.d.ts +908 -86
- package/{dist → dist-types}/db/tenant-scope.d.ts +0 -1
- package/dist-types/define-config.d.ts +17 -0
- package/dist-types/edge/index.d.ts +5 -0
- package/dist-types/edge/init-handler.d.ts +40 -0
- package/dist-types/edge/resolve-consent.d.ts +80 -0
- package/dist-types/edge/types.d.ts +13 -0
- package/{dist → dist-types}/handlers/consent/check.handler.d.ts +0 -1
- package/{src/handlers/consent/index.ts → dist-types/handlers/consent/index.d.ts} +0 -1
- package/{dist → dist-types}/handlers/init/geo.d.ts +2 -3
- package/{dist → dist-types}/handlers/init/index.d.ts +2 -3
- package/dist-types/handlers/init/policy.d.ts +26 -0
- package/dist-types/handlers/init/resolve-init.d.ts +44 -0
- package/dist-types/handlers/init/translations.d.ts +48 -0
- package/dist-types/handlers/legal-document/current.handler.d.ts +11 -0
- package/dist-types/handlers/legal-document/snapshot.d.ts +39 -0
- package/dist-types/handlers/policy/snapshot.d.ts +99 -0
- package/{src/handlers/status/index.ts → dist-types/handlers/status/index.d.ts} +0 -1
- package/{dist → dist-types}/handlers/status/status.handler.d.ts +0 -1
- package/{dist → dist-types}/handlers/subject/get.handler.d.ts +3 -2
- package/{src/handlers/subject/index.ts → dist-types/handlers/subject/index.d.ts} +0 -1
- package/{dist → dist-types}/handlers/subject/list.handler.d.ts +3 -2
- package/{dist → dist-types}/handlers/subject/patch.handler.d.ts +0 -2
- package/{dist → dist-types}/handlers/subject/post.handler.d.ts +12 -1
- package/{dist → dist-types}/handlers/utils/consent-enrichment.d.ts +3 -1
- package/{dist → dist-types}/init.d.ts +4 -7
- package/{dist → dist-types}/middleware/auth/index.d.ts +0 -1
- package/{dist → dist-types}/middleware/auth/validate-api-key.d.ts +0 -1
- package/{dist → dist-types}/middleware/cors/cors.d.ts +0 -1
- package/{src/middleware/cors/index.ts → dist-types/middleware/cors/index.d.ts} +0 -1
- package/{dist → dist-types}/middleware/cors/is-origin-trusted.d.ts +0 -1
- package/{dist → dist-types}/middleware/cors/process-cors.d.ts +0 -1
- package/{dist → dist-types}/middleware/openapi/config.d.ts +0 -1
- package/{dist → dist-types}/middleware/openapi/handlers.d.ts +0 -1
- package/{src/middleware/openapi/index.ts → dist-types/middleware/openapi/index.d.ts} +0 -1
- package/{dist → dist-types}/middleware/process-ip/index.d.ts +0 -1
- package/dist-types/policies/builder.d.ts +127 -0
- package/dist-types/policies/defaults.d.ts +2 -0
- package/dist-types/policies/matchers.d.ts +3 -0
- package/{dist → dist-types}/router.d.ts +0 -1
- package/{dist → dist-types}/routes/consent.d.ts +0 -1
- package/{dist → dist-types}/routes/index.d.ts +1 -1
- package/{dist → dist-types}/routes/init.d.ts +0 -1
- package/dist-types/routes/legal-document.d.ts +7 -0
- package/{dist → dist-types}/routes/status.d.ts +0 -1
- package/{dist → dist-types}/routes/subject.d.ts +0 -1
- package/{dist → dist-types}/types/api.d.ts +0 -1
- package/dist-types/types/index.d.ts +464 -0
- package/dist-types/utils/background.d.ts +6 -0
- package/{dist → dist-types}/utils/create-telemetry-options.d.ts +1 -2
- package/{dist → dist-types}/utils/env.d.ts +0 -1
- package/{dist → dist-types}/utils/extract-error-message.d.ts +0 -1
- package/{dist → dist-types}/utils/instrumentation.d.ts +2 -3
- package/{dist → dist-types}/utils/logger.d.ts +0 -1
- package/{dist → dist-types}/utils/metrics.d.ts +0 -1
- package/dist-types/version.d.ts +1 -0
- package/docs/README.md +49 -0
- package/docs/api/configuration.md +208 -0
- package/docs/api/endpoints.md +211 -0
- package/docs/guides/caching.md +85 -0
- package/docs/guides/database-setup.md +128 -0
- package/docs/guides/edge-deployment.md +251 -0
- package/docs/guides/framework-integration.md +142 -0
- package/docs/guides/iab-tcf.md +89 -0
- package/docs/guides/observability.md +96 -0
- package/docs/guides/policy-packs.md +396 -0
- package/docs/quickstart.md +129 -0
- package/package.json +53 -39
- package/.turbo/turbo-build.log +0 -49
- package/CHANGELOG.md +0 -89
- package/dist/cache/adapters/cloudflare-kv.d.ts.map +0 -1
- package/dist/cache/adapters/index.d.ts.map +0 -1
- package/dist/cache/adapters/memory.d.ts.map +0 -1
- package/dist/cache/adapters/upstash-redis.d.ts.map +0 -1
- package/dist/cache/gvl-resolver.d.ts.map +0 -1
- package/dist/cache/index.d.ts.map +0 -1
- package/dist/cache/keys.d.ts.map +0 -1
- package/dist/cache/types.d.ts.map +0 -1
- package/dist/core.d.ts.map +0 -1
- package/dist/db/adapters/drizzle.d.ts +0 -2
- package/dist/db/adapters/drizzle.d.ts.map +0 -1
- package/dist/db/adapters/index.d.ts +0 -2
- package/dist/db/adapters/index.d.ts.map +0 -1
- package/dist/db/adapters/kysely.d.ts +0 -2
- package/dist/db/adapters/kysely.d.ts.map +0 -1
- package/dist/db/adapters/mongo.d.ts +0 -2
- package/dist/db/adapters/mongo.d.ts.map +0 -1
- package/dist/db/adapters/prisma.d.ts +0 -2
- package/dist/db/adapters/prisma.d.ts.map +0 -1
- package/dist/db/adapters/typeorm.d.ts +0 -2
- package/dist/db/adapters/typeorm.d.ts.map +0 -1
- package/dist/db/migrator/index.d.ts.map +0 -1
- package/dist/db/registry/consent-policy.d.ts +0 -23
- package/dist/db/registry/consent-policy.d.ts.map +0 -1
- package/dist/db/registry/consent-purpose.d.ts.map +0 -1
- package/dist/db/registry/domain.d.ts.map +0 -1
- package/dist/db/registry/index.d.ts +0 -57
- package/dist/db/registry/index.d.ts.map +0 -1
- package/dist/db/registry/subject.d.ts.map +0 -1
- package/dist/db/registry/types.d.ts.map +0 -1
- package/dist/db/registry/utils/generate-id.d.ts.map +0 -1
- package/dist/db/registry/utils.d.ts.map +0 -1
- package/dist/db/schema/1.0.0/audit-log.d.ts.map +0 -1
- package/dist/db/schema/1.0.0/consent-policy.d.ts.map +0 -1
- package/dist/db/schema/1.0.0/consent-purpose.d.ts.map +0 -1
- package/dist/db/schema/1.0.0/consent-record.d.ts.map +0 -1
- package/dist/db/schema/1.0.0/consent.d.ts.map +0 -1
- package/dist/db/schema/1.0.0/domain.d.ts.map +0 -1
- package/dist/db/schema/1.0.0/index.d.ts.map +0 -1
- package/dist/db/schema/1.0.0/subject.d.ts.map +0 -1
- package/dist/db/schema/2.0.0/audit-log.d.ts.map +0 -1
- package/dist/db/schema/2.0.0/consent-policy.d.ts.map +0 -1
- package/dist/db/schema/2.0.0/consent-purpose.d.ts.map +0 -1
- package/dist/db/schema/2.0.0/consent.d.ts.map +0 -1
- package/dist/db/schema/2.0.0/domain.d.ts.map +0 -1
- package/dist/db/schema/2.0.0/index.d.ts.map +0 -1
- package/dist/db/schema/2.0.0/subject.d.ts.map +0 -1
- package/dist/db/schema/index.d.ts.map +0 -1
- package/dist/db/tenant-scope.d.ts.map +0 -1
- package/dist/define-config.d.ts +0 -5
- package/dist/define-config.d.ts.map +0 -1
- package/dist/handlers/consent/check.handler.d.ts.map +0 -1
- package/dist/handlers/consent/index.d.ts +0 -12
- package/dist/handlers/consent/index.d.ts.map +0 -1
- package/dist/handlers/init/geo.d.ts.map +0 -1
- package/dist/handlers/init/index.d.ts.map +0 -1
- package/dist/handlers/init/translations.d.ts +0 -28
- package/dist/handlers/init/translations.d.ts.map +0 -1
- package/dist/handlers/status/index.d.ts +0 -7
- package/dist/handlers/status/index.d.ts.map +0 -1
- package/dist/handlers/status/status.handler.d.ts.map +0 -1
- package/dist/handlers/subject/get.handler.d.ts.map +0 -1
- package/dist/handlers/subject/index.d.ts +0 -10
- package/dist/handlers/subject/index.d.ts.map +0 -1
- package/dist/handlers/subject/list.handler.d.ts.map +0 -1
- package/dist/handlers/subject/patch.handler.d.ts.map +0 -1
- package/dist/handlers/subject/post.handler.d.ts.map +0 -1
- package/dist/handlers/utils/consent-enrichment.d.ts.map +0 -1
- package/dist/init.d.ts.map +0 -1
- package/dist/middleware/auth/index.d.ts.map +0 -1
- package/dist/middleware/auth/validate-api-key.d.ts.map +0 -1
- package/dist/middleware/cors/cors.d.ts.map +0 -1
- package/dist/middleware/cors/index.d.ts +0 -30
- package/dist/middleware/cors/index.d.ts.map +0 -1
- package/dist/middleware/cors/is-origin-trusted.d.ts.map +0 -1
- package/dist/middleware/cors/process-cors.d.ts.map +0 -1
- package/dist/middleware/openapi/config.d.ts.map +0 -1
- package/dist/middleware/openapi/handlers.d.ts.map +0 -1
- package/dist/middleware/openapi/index.d.ts +0 -12
- package/dist/middleware/openapi/index.d.ts.map +0 -1
- package/dist/middleware/process-ip/index.d.ts.map +0 -1
- package/dist/router.d.ts.map +0 -1
- package/dist/routes/consent.d.ts.map +0 -1
- package/dist/routes/index.d.ts.map +0 -1
- package/dist/routes/init.d.ts.map +0 -1
- package/dist/routes/status.d.ts.map +0 -1
- package/dist/routes/subject.d.ts.map +0 -1
- package/dist/types/api.d.ts.map +0 -1
- package/dist/types/index.d.ts +0 -255
- package/dist/types/index.d.ts.map +0 -1
- package/dist/utils/create-telemetry-options.d.ts.map +0 -1
- package/dist/utils/env.d.ts.map +0 -1
- package/dist/utils/extract-error-message.d.ts.map +0 -1
- package/dist/utils/index.d.ts +0 -4
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/instrumentation.d.ts.map +0 -1
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/metrics.d.ts.map +0 -1
- package/dist/version.d.ts +0 -2
- package/dist/version.d.ts.map +0 -1
- package/knip.json +0 -31
- package/rslib.config.ts +0 -93
- package/src/cache/adapters/cloudflare-kv.ts +0 -71
- package/src/cache/adapters/index.ts +0 -22
- package/src/cache/adapters/memory.ts +0 -111
- package/src/cache/adapters/upstash-redis.ts +0 -113
- package/src/cache/gvl-resolver.ts +0 -289
- package/src/cache/index.ts +0 -34
- package/src/cache/keys.ts +0 -68
- package/src/cache/types.ts +0 -66
- package/src/core.ts +0 -368
- package/src/db/migrator/index.ts +0 -80
- package/src/db/registry/consent-policy.test.ts +0 -451
- package/src/db/registry/consent-policy.ts +0 -82
- package/src/db/registry/consent-purpose.test.ts +0 -428
- package/src/db/registry/consent-purpose.ts +0 -61
- package/src/db/registry/domain.test.ts +0 -445
- package/src/db/registry/domain.ts +0 -91
- package/src/db/registry/index.ts +0 -14
- package/src/db/registry/subject.test.ts +0 -388
- package/src/db/registry/subject.ts +0 -129
- package/src/db/registry/types.ts +0 -10
- package/src/db/registry/utils/generate-id.test.ts +0 -216
- package/src/db/registry/utils/generate-id.ts +0 -133
- package/src/db/registry/utils.ts +0 -133
- package/src/db/schema/1.0.0/audit-log.ts +0 -15
- package/src/db/schema/1.0.0/consent-policy.ts +0 -14
- package/src/db/schema/1.0.0/consent-purpose.ts +0 -14
- package/src/db/schema/1.0.0/consent-record.ts +0 -10
- package/src/db/schema/1.0.0/consent.ts +0 -20
- package/src/db/schema/1.0.0/domain.ts +0 -12
- package/src/db/schema/1.0.0/index.ts +0 -48
- package/src/db/schema/1.0.0/subject.ts +0 -12
- package/src/db/schema/2.0.0/audit-log.ts +0 -18
- package/src/db/schema/2.0.0/consent-policy.ts +0 -28
- package/src/db/schema/2.0.0/consent-purpose.ts +0 -12
- package/src/db/schema/2.0.0/consent.ts +0 -26
- package/src/db/schema/2.0.0/domain.ts +0 -12
- package/src/db/schema/2.0.0/index.ts +0 -47
- package/src/db/schema/2.0.0/subject.ts +0 -14
- package/src/db/schema/index.ts +0 -15
- package/src/db/tenant-scope.test.ts +0 -750
- package/src/db/tenant-scope.ts +0 -103
- package/src/define-config.ts +0 -5
- package/src/handlers/consent/check.handler.ts +0 -126
- package/src/handlers/init/geo.test.ts +0 -317
- package/src/handlers/init/geo.ts +0 -195
- package/src/handlers/init/index.test.ts +0 -205
- package/src/handlers/init/index.ts +0 -114
- package/src/handlers/init/translations.test.ts +0 -121
- package/src/handlers/init/translations.ts +0 -72
- package/src/handlers/status/status.handler.test.ts +0 -155
- package/src/handlers/status/status.handler.ts +0 -51
- package/src/handlers/subject/get.handler.ts +0 -93
- package/src/handlers/subject/list.handler.ts +0 -93
- package/src/handlers/subject/patch.handler.ts +0 -122
- package/src/handlers/subject/post.handler.test.ts +0 -294
- package/src/handlers/subject/post.handler.ts +0 -254
- package/src/handlers/utils/consent-enrichment.test.ts +0 -380
- package/src/handlers/utils/consent-enrichment.ts +0 -218
- package/src/init.test.ts +0 -126
- package/src/init.ts +0 -87
- package/src/middleware/auth/index.ts +0 -11
- package/src/middleware/auth/validate-api-key.test.ts +0 -86
- package/src/middleware/auth/validate-api-key.ts +0 -107
- package/src/middleware/cors/cors.test.ts +0 -135
- package/src/middleware/cors/cors.ts +0 -186
- package/src/middleware/cors/is-origin-trusted.test.ts +0 -164
- package/src/middleware/cors/is-origin-trusted.ts +0 -130
- package/src/middleware/cors/process-cors.ts +0 -91
- package/src/middleware/openapi/config.ts +0 -29
- package/src/middleware/openapi/handlers.ts +0 -34
- package/src/middleware/process-ip/index.test.ts +0 -195
- package/src/middleware/process-ip/index.ts +0 -199
- package/src/router.ts +0 -15
- package/src/routes/consent.ts +0 -52
- package/src/routes/index.ts +0 -10
- package/src/routes/init.ts +0 -102
- package/src/routes/status.ts +0 -46
- package/src/routes/subject.ts +0 -152
- package/src/types/api.ts +0 -48
- package/src/types/index.ts +0 -288
- package/src/utils/create-telemetry-options.test.ts +0 -302
- package/src/utils/create-telemetry-options.ts +0 -229
- package/src/utils/env.ts +0 -84
- package/src/utils/extract-error-message.ts +0 -21
- package/src/utils/instrumentation.test.ts +0 -185
- package/src/utils/instrumentation.ts +0 -196
- package/src/utils/logger.ts +0 -41
- package/src/utils/metrics.test.ts +0 -323
- package/src/utils/metrics.ts +0 -402
- package/src/utils/telemetry-pii.test.ts +0 -325
- package/src/version.ts +0 -2
- package/tsconfig.json +0 -11
- package/vitest.config.ts +0 -28
- /package/dist/{types.js → types/index.js} +0 -0
- /package/{src/db/adapters/drizzle.ts → dist-types/db/adapters/drizzle.d.ts} +0 -0
- /package/{src/db/adapters/index.ts → dist-types/db/adapters/index.d.ts} +0 -0
- /package/{src/db/adapters/kysely.ts → dist-types/db/adapters/kysely.d.ts} +0 -0
- /package/{src/db/adapters/mongo.ts → dist-types/db/adapters/mongo.d.ts} +0 -0
- /package/{src/db/adapters/prisma.ts → dist-types/db/adapters/prisma.d.ts} +0 -0
- /package/{src/db/adapters/typeorm.ts → dist-types/db/adapters/typeorm.d.ts} +0 -0
- /package/{src/utils/index.ts → dist-types/utils/index.d.ts} +0 -0
package/dist/router.cjs
CHANGED
|
@@ -22,7 +22,7 @@ var __webpack_require__ = {};
|
|
|
22
22
|
})();
|
|
23
23
|
(()=>{
|
|
24
24
|
__webpack_require__.r = (exports1)=>{
|
|
25
|
-
if (
|
|
25
|
+
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
26
26
|
value: 'Module'
|
|
27
27
|
});
|
|
28
28
|
Object.defineProperty(exports1, '__esModule', {
|
|
@@ -42,7 +42,7 @@ const schema_namespaceObject = require("@c15t/schema");
|
|
|
42
42
|
const external_hono_namespaceObject = require("hono");
|
|
43
43
|
const external_hono_openapi_namespaceObject = require("hono-openapi");
|
|
44
44
|
const http_exception_namespaceObject = require("hono/http-exception");
|
|
45
|
-
function
|
|
45
|
+
function extract_error_message_extractErrorMessage(error) {
|
|
46
46
|
if (error instanceof AggregateError && error.errors?.length > 0) {
|
|
47
47
|
const inner = error.errors.map((e)=>e instanceof Error ? e.message : String(e)).join('; ');
|
|
48
48
|
return `AggregateError: ${inner}`;
|
|
@@ -54,18 +54,18 @@ const api_namespaceObject = require("@opentelemetry/api");
|
|
|
54
54
|
let cachedConfig = null;
|
|
55
55
|
let cachedDefaultAttributes = {};
|
|
56
56
|
function create_telemetry_options_isTelemetryEnabled(options) {
|
|
57
|
-
if (options) return options.
|
|
57
|
+
if (options) return options.telemetry?.enabled === true;
|
|
58
58
|
return cachedConfig?.enabled === true;
|
|
59
59
|
}
|
|
60
60
|
const create_telemetry_options_getTracer = (options)=>{
|
|
61
61
|
if (!create_telemetry_options_isTelemetryEnabled(options)) return api_namespaceObject.trace.getTracer('c15t-noop');
|
|
62
|
-
const tracer = options?.
|
|
62
|
+
const tracer = options?.telemetry?.tracer ?? cachedConfig?.tracer;
|
|
63
63
|
if (tracer) return tracer;
|
|
64
64
|
return api_namespaceObject.trace.getTracer(options?.appName ?? 'c15t');
|
|
65
65
|
};
|
|
66
66
|
const getMeter = (options)=>{
|
|
67
67
|
if (!create_telemetry_options_isTelemetryEnabled(options)) return api_namespaceObject.metrics.getMeter('c15t-noop');
|
|
68
|
-
const meter = options?.
|
|
68
|
+
const meter = options?.telemetry?.meter ?? cachedConfig?.meter;
|
|
69
69
|
if (meter) return meter;
|
|
70
70
|
return api_namespaceObject.metrics.getMeter(options?.appName ?? 'c15t');
|
|
71
71
|
};
|
|
@@ -75,7 +75,7 @@ function getDefaultAttributes() {
|
|
|
75
75
|
const handleSpanError = (span, error)=>{
|
|
76
76
|
span.setStatus({
|
|
77
77
|
code: api_namespaceObject.SpanStatusCode.ERROR,
|
|
78
|
-
message:
|
|
78
|
+
message: extract_error_message_extractErrorMessage(error)
|
|
79
79
|
});
|
|
80
80
|
if (error instanceof Error) span.setAttribute('error.type', error.name);
|
|
81
81
|
};
|
|
@@ -244,7 +244,7 @@ function createMetrics(meter) {
|
|
|
244
244
|
};
|
|
245
245
|
}
|
|
246
246
|
let metricsInstance = null;
|
|
247
|
-
function
|
|
247
|
+
function metrics_getMetrics(options) {
|
|
248
248
|
if (metricsInstance) return metricsInstance;
|
|
249
249
|
if (!create_telemetry_options_isTelemetryEnabled(options)) return null;
|
|
250
250
|
metricsInstance = createMetrics(getMeter(options));
|
|
@@ -270,7 +270,7 @@ async function batchLoadPolicies(policyIds, ctx) {
|
|
|
270
270
|
for (const p of policyMap.values())uniqueTypes.add(p.type);
|
|
271
271
|
const latestPolicyByType = new Map();
|
|
272
272
|
for (const type of uniqueTypes){
|
|
273
|
-
const latest = await registry.
|
|
273
|
+
const latest = await registry.findLatestPolicyByType(type);
|
|
274
274
|
if (latest) latestPolicyByType.set(type, latest.id);
|
|
275
275
|
}
|
|
276
276
|
return {
|
|
@@ -296,11 +296,17 @@ async function enrichConsents(consents, ctx) {
|
|
|
296
296
|
}
|
|
297
297
|
return consents.map((consent)=>{
|
|
298
298
|
let policyType = 'unknown';
|
|
299
|
+
let policyVersion;
|
|
300
|
+
let policyHash;
|
|
301
|
+
let policyEffectiveDate;
|
|
299
302
|
let isLatestPolicy = false;
|
|
300
303
|
if (consent.policyId) {
|
|
301
304
|
const policy = policyMap.get(consent.policyId);
|
|
302
305
|
if (policy) {
|
|
303
306
|
policyType = policy.type;
|
|
307
|
+
policyVersion = policy.version;
|
|
308
|
+
policyHash = policy.hash ?? void 0;
|
|
309
|
+
policyEffectiveDate = policy.effectiveDate;
|
|
304
310
|
isLatestPolicy = latestPolicyByType.get(policyType) === consent.policyId;
|
|
305
311
|
}
|
|
306
312
|
}
|
|
@@ -317,6 +323,9 @@ async function enrichConsents(consents, ctx) {
|
|
|
317
323
|
id: consent.id,
|
|
318
324
|
type: policyType,
|
|
319
325
|
policyId: consent.policyId ?? void 0,
|
|
326
|
+
policyVersion,
|
|
327
|
+
policyHash,
|
|
328
|
+
policyEffectiveDate,
|
|
320
329
|
isLatestPolicy,
|
|
321
330
|
preferences,
|
|
322
331
|
givenAt: consent.givenAt
|
|
@@ -408,14 +417,14 @@ const checkConsentHandler = async (c)=>{
|
|
|
408
417
|
externalId,
|
|
409
418
|
results
|
|
410
419
|
});
|
|
411
|
-
const metrics =
|
|
420
|
+
const metrics = metrics_getMetrics();
|
|
412
421
|
if (metrics) for (const [type, result] of Object.entries(results))metrics.recordConsentCheck(type, result.hasConsent);
|
|
413
422
|
return c.json({
|
|
414
423
|
results
|
|
415
424
|
});
|
|
416
425
|
} catch (error) {
|
|
417
426
|
logger.error('Error in GET /consents/check handler', {
|
|
418
|
-
error:
|
|
427
|
+
error: extract_error_message_extractErrorMessage(error),
|
|
419
428
|
errorType: error instanceof Error ? error.constructor.name : typeof error
|
|
420
429
|
});
|
|
421
430
|
if (error instanceof http_exception_namespaceObject.HTTPException) throw error;
|
|
@@ -431,11 +440,7 @@ const createConsentRoutes = ()=>{
|
|
|
431
440
|
const app = new external_hono_namespaceObject.Hono();
|
|
432
441
|
app.get('/check', (0, external_hono_openapi_namespaceObject.describeRoute)({
|
|
433
442
|
summary: 'Check consent by external user ID',
|
|
434
|
-
description:
|
|
435
|
-
|
|
436
|
-
**Query parameters:**
|
|
437
|
-
- \`externalId\` – External user ID to check
|
|
438
|
-
- \`type\` – Consent type(s) to check (comma-separated)`,
|
|
443
|
+
description: "Pre-banner cross-device consent check. Use to avoid showing the banner when the user has already consented on another device.\n\n**Query parameters:**\n- `externalId` – External user ID to check\n- `type` – Consent type(s) to check (comma-separated)",
|
|
439
444
|
tags: [
|
|
440
445
|
'Consent'
|
|
441
446
|
],
|
|
@@ -470,7 +475,7 @@ async function executeWithSpan(span, operation) {
|
|
|
470
475
|
}
|
|
471
476
|
}
|
|
472
477
|
function resolveDefaultAttributes(options) {
|
|
473
|
-
return options?.
|
|
478
|
+
return options?.telemetry?.defaultAttributes || getDefaultAttributes();
|
|
474
479
|
}
|
|
475
480
|
async function withExternalSpan(attributes, operation, options) {
|
|
476
481
|
if (!create_telemetry_options_isTelemetryEnabled(options)) return operation();
|
|
@@ -596,14 +601,14 @@ async function fetchGVLWithLanguage(language, vendorIds, endpoint = GVL_ENDPOINT
|
|
|
596
601
|
if (!parsed.vendorListVersion || !parsed.purposes || !parsed.vendors) throw new Error('Invalid GVL response: missing required fields');
|
|
597
602
|
return parsed;
|
|
598
603
|
});
|
|
599
|
-
|
|
604
|
+
metrics_getMetrics()?.recordGvlFetch({
|
|
600
605
|
language,
|
|
601
606
|
source: 'fetch',
|
|
602
607
|
status: 200
|
|
603
608
|
}, Date.now() - fetchStart);
|
|
604
609
|
return gvl;
|
|
605
610
|
} catch (error) {
|
|
606
|
-
|
|
611
|
+
metrics_getMetrics()?.recordGvlError({
|
|
607
612
|
language,
|
|
608
613
|
errorType: error instanceof Error ? error.name : 'UnknownError'
|
|
609
614
|
});
|
|
@@ -624,18 +629,18 @@ function createGVLResolver(options) {
|
|
|
624
629
|
if (bundled?.[language]) return bundled[language];
|
|
625
630
|
const memoryHit = await withCacheSpan('get', 'memory', ()=>memoryCache.get(cacheKey));
|
|
626
631
|
if (memoryHit) {
|
|
627
|
-
|
|
632
|
+
metrics_getMetrics()?.recordCacheHit('memory');
|
|
628
633
|
return memoryHit;
|
|
629
634
|
}
|
|
630
|
-
|
|
635
|
+
metrics_getMetrics()?.recordCacheMiss('memory');
|
|
631
636
|
if (cacheAdapter) {
|
|
632
637
|
const externalHit = await withCacheSpan('get', 'external', ()=>cacheAdapter.get(cacheKey));
|
|
633
638
|
if (externalHit) {
|
|
634
|
-
|
|
639
|
+
metrics_getMetrics()?.recordCacheHit('external');
|
|
635
640
|
await withCacheSpan('set', 'memory', ()=>memoryCache.set(cacheKey, externalHit, 300000));
|
|
636
641
|
return externalHit;
|
|
637
642
|
}
|
|
638
|
-
|
|
643
|
+
metrics_getMetrics()?.recordCacheMiss('external');
|
|
639
644
|
}
|
|
640
645
|
const gvl = await fetchGVLWithLanguage(language, vendorIds, endpoint);
|
|
641
646
|
if (gvl) {
|
|
@@ -646,6 +651,124 @@ function createGVLResolver(options) {
|
|
|
646
651
|
}
|
|
647
652
|
};
|
|
648
653
|
}
|
|
654
|
+
const external_jose_namespaceObject = require("jose");
|
|
655
|
+
const POLICY_SNAPSHOT_JWT_HEADER = {
|
|
656
|
+
alg: 'HS256',
|
|
657
|
+
typ: 'JWT'
|
|
658
|
+
};
|
|
659
|
+
const DEFAULT_POLICY_SNAPSHOT_ISSUER = 'c15t';
|
|
660
|
+
const DEFAULT_POLICY_SNAPSHOT_AUDIENCE = 'c15t-policy-snapshot';
|
|
661
|
+
function resolveSnapshotIssuer(options) {
|
|
662
|
+
return options?.issuer?.trim() || DEFAULT_POLICY_SNAPSHOT_ISSUER;
|
|
663
|
+
}
|
|
664
|
+
function resolveSnapshotAudience(params) {
|
|
665
|
+
const configuredAudience = params.options?.audience?.trim();
|
|
666
|
+
if (configuredAudience) return configuredAudience;
|
|
667
|
+
return params.tenantId ? `${DEFAULT_POLICY_SNAPSHOT_AUDIENCE}:${params.tenantId}` : DEFAULT_POLICY_SNAPSHOT_AUDIENCE;
|
|
668
|
+
}
|
|
669
|
+
function getSigningKey(secret) {
|
|
670
|
+
return new TextEncoder().encode(secret);
|
|
671
|
+
}
|
|
672
|
+
function isPolicySnapshotPayload(payload) {
|
|
673
|
+
return 'string' == typeof payload.policyId && 'string' == typeof payload.fingerprint && 'string' == typeof payload.matchedBy && 'string' == typeof payload.jurisdiction && 'string' == typeof payload.model && 'string' == typeof payload.iss && 'string' == typeof payload.aud && 'string' == typeof payload.sub && 'number' == typeof payload.iat && 'number' == typeof payload.exp;
|
|
674
|
+
}
|
|
675
|
+
async function createPolicySnapshotToken(params) {
|
|
676
|
+
const { options } = params;
|
|
677
|
+
if (!options?.signingKey) return;
|
|
678
|
+
const iat = Math.floor(Date.now() / 1000);
|
|
679
|
+
const ttlSeconds = options.ttlSeconds ?? 1800;
|
|
680
|
+
const exp = iat + ttlSeconds;
|
|
681
|
+
const iss = resolveSnapshotIssuer(options);
|
|
682
|
+
const aud = resolveSnapshotAudience({
|
|
683
|
+
options,
|
|
684
|
+
tenantId: params.tenantId
|
|
685
|
+
});
|
|
686
|
+
const payload = {
|
|
687
|
+
iss,
|
|
688
|
+
aud,
|
|
689
|
+
sub: params.policyId,
|
|
690
|
+
tenantId: params.tenantId,
|
|
691
|
+
policyId: params.policyId,
|
|
692
|
+
fingerprint: params.fingerprint,
|
|
693
|
+
matchedBy: params.matchedBy,
|
|
694
|
+
country: params.country,
|
|
695
|
+
region: params.region,
|
|
696
|
+
jurisdiction: params.jurisdiction,
|
|
697
|
+
language: params.language,
|
|
698
|
+
model: params.model,
|
|
699
|
+
policyI18n: params.policyI18n,
|
|
700
|
+
expiryDays: params.expiryDays,
|
|
701
|
+
scopeMode: params.scopeMode,
|
|
702
|
+
uiMode: params.uiMode,
|
|
703
|
+
bannerUi: params.bannerUi,
|
|
704
|
+
dialogUi: params.dialogUi,
|
|
705
|
+
categories: params.categories,
|
|
706
|
+
preselectedCategories: params.preselectedCategories,
|
|
707
|
+
gpc: params.gpc,
|
|
708
|
+
proofConfig: params.proofConfig,
|
|
709
|
+
iat,
|
|
710
|
+
exp
|
|
711
|
+
};
|
|
712
|
+
const token = await new external_jose_namespaceObject.SignJWT(payload).setProtectedHeader(POLICY_SNAPSHOT_JWT_HEADER).setIssuedAt(iat).setExpirationTime(exp).sign(getSigningKey(options.signingKey));
|
|
713
|
+
return {
|
|
714
|
+
token,
|
|
715
|
+
payload
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
async function verifyPolicySnapshotToken(params) {
|
|
719
|
+
const { token, options, tenantId } = params;
|
|
720
|
+
if (!options?.signingKey) return {
|
|
721
|
+
valid: false,
|
|
722
|
+
reason: 'missing'
|
|
723
|
+
};
|
|
724
|
+
if (!token) return {
|
|
725
|
+
valid: false,
|
|
726
|
+
reason: 'missing'
|
|
727
|
+
};
|
|
728
|
+
if (3 !== token.split('.').length) return {
|
|
729
|
+
valid: false,
|
|
730
|
+
reason: 'malformed'
|
|
731
|
+
};
|
|
732
|
+
try {
|
|
733
|
+
const { payload, protectedHeader } = await (0, external_jose_namespaceObject.jwtVerify)(token, getSigningKey(options.signingKey), {
|
|
734
|
+
issuer: resolveSnapshotIssuer(options),
|
|
735
|
+
audience: resolveSnapshotAudience({
|
|
736
|
+
options,
|
|
737
|
+
tenantId
|
|
738
|
+
})
|
|
739
|
+
});
|
|
740
|
+
const header = protectedHeader;
|
|
741
|
+
if ('HS256' !== header.alg || 'JWT' !== header.typ) return {
|
|
742
|
+
valid: false,
|
|
743
|
+
reason: 'invalid'
|
|
744
|
+
};
|
|
745
|
+
if (!isPolicySnapshotPayload(payload)) return {
|
|
746
|
+
valid: false,
|
|
747
|
+
reason: 'invalid'
|
|
748
|
+
};
|
|
749
|
+
if (payload.sub !== payload.policyId) return {
|
|
750
|
+
valid: false,
|
|
751
|
+
reason: 'invalid'
|
|
752
|
+
};
|
|
753
|
+
if ((tenantId ?? void 0) !== (payload.tenantId ?? void 0)) return {
|
|
754
|
+
valid: false,
|
|
755
|
+
reason: 'invalid'
|
|
756
|
+
};
|
|
757
|
+
return {
|
|
758
|
+
valid: true,
|
|
759
|
+
payload
|
|
760
|
+
};
|
|
761
|
+
} catch (error) {
|
|
762
|
+
if (error instanceof external_jose_namespaceObject.errors.JWTExpired) return {
|
|
763
|
+
valid: false,
|
|
764
|
+
reason: 'expired'
|
|
765
|
+
};
|
|
766
|
+
return {
|
|
767
|
+
valid: false,
|
|
768
|
+
reason: 'invalid'
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
}
|
|
649
772
|
function geo_normalizeHeader(value) {
|
|
650
773
|
if (!value) return null;
|
|
651
774
|
return Array.isArray(value) ? value[0] ?? null : value;
|
|
@@ -787,7 +910,7 @@ function checkJurisdiction(countryCode, regionCode) {
|
|
|
787
910
|
return jurisdiction;
|
|
788
911
|
}
|
|
789
912
|
async function getLocation(request, options) {
|
|
790
|
-
if (options.
|
|
913
|
+
if (options.disableGeoLocation) return {
|
|
791
914
|
countryCode: null,
|
|
792
915
|
regionCode: null
|
|
793
916
|
};
|
|
@@ -798,30 +921,253 @@ async function getLocation(request, options) {
|
|
|
798
921
|
};
|
|
799
922
|
}
|
|
800
923
|
function getJurisdiction(location, options) {
|
|
801
|
-
if (options.
|
|
924
|
+
if (options.disableGeoLocation) return 'GDPR';
|
|
802
925
|
return checkJurisdiction(location.countryCode, location.regionCode);
|
|
803
926
|
}
|
|
927
|
+
const schema_types_namespaceObject = require("@c15t/schema/types");
|
|
928
|
+
async function resolvePolicyDecision(params) {
|
|
929
|
+
return (0, schema_types_namespaceObject.resolvePolicyDecision)({
|
|
930
|
+
policies: params.policies,
|
|
931
|
+
countryCode: params.countryCode,
|
|
932
|
+
regionCode: params.regionCode,
|
|
933
|
+
jurisdiction: params.jurisdiction,
|
|
934
|
+
iabEnabled: params.iabEnabled
|
|
935
|
+
});
|
|
936
|
+
}
|
|
804
937
|
const translations_namespaceObject = require("@c15t/translations");
|
|
938
|
+
const all_namespaceObject = require("@c15t/translations/all");
|
|
939
|
+
const DEFAULT_PROFILE = 'default';
|
|
940
|
+
const warnedKeys = new Set();
|
|
805
941
|
function isSupportedBaseLanguage(lang) {
|
|
806
|
-
return lang in
|
|
942
|
+
return lang in all_namespaceObject.baseTranslations;
|
|
943
|
+
}
|
|
944
|
+
function warnOnce(logger, key, message, metadata) {
|
|
945
|
+
if (!logger || warnedKeys.has(key)) return;
|
|
946
|
+
warnedKeys.add(key);
|
|
947
|
+
logger.warn(message, metadata);
|
|
948
|
+
}
|
|
949
|
+
function normalizeLanguage(value) {
|
|
950
|
+
if (!value) return;
|
|
951
|
+
const normalized = value.split(',')[0]?.split(';')[0]?.trim().toLowerCase();
|
|
952
|
+
if (!normalized) return;
|
|
953
|
+
return normalized.split('-')[0] ?? void 0;
|
|
954
|
+
}
|
|
955
|
+
function normalizeProfiles(params) {
|
|
956
|
+
const profiles = params.i18n?.messages;
|
|
957
|
+
const legacy = params.customTranslations;
|
|
958
|
+
if (profiles && Object.keys(profiles).length > 0) {
|
|
959
|
+
if (legacy && Object.keys(legacy).length > 0) warnOnce(params.logger, 'i18n.customTranslations.ignored', '`customTranslations` is deprecated and ignored when `i18n.messages` is configured.');
|
|
960
|
+
return profiles;
|
|
961
|
+
}
|
|
962
|
+
if (legacy && Object.keys(legacy).length > 0) {
|
|
963
|
+
warnOnce(params.logger, 'i18n.customTranslations.deprecated', '`customTranslations` is deprecated. Use `i18n.messages` instead.');
|
|
964
|
+
return {
|
|
965
|
+
[DEFAULT_PROFILE]: {
|
|
966
|
+
translations: legacy
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
return {};
|
|
807
971
|
}
|
|
808
|
-
function
|
|
809
|
-
const
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
972
|
+
function buildCandidates(input) {
|
|
973
|
+
const raw = [
|
|
974
|
+
{
|
|
975
|
+
language: input.language,
|
|
976
|
+
reason: 'profile_language'
|
|
977
|
+
},
|
|
978
|
+
{
|
|
979
|
+
language: input.fallbackLanguage,
|
|
980
|
+
reason: 'profile_fallback'
|
|
981
|
+
}
|
|
814
982
|
];
|
|
815
|
-
const
|
|
983
|
+
const dedupe = new Set();
|
|
984
|
+
return raw.filter((candidate)=>{
|
|
985
|
+
const key = candidate.language;
|
|
986
|
+
if (dedupe.has(key)) return false;
|
|
987
|
+
dedupe.add(key);
|
|
988
|
+
return true;
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
function getProfileLanguages(profiles, profile) {
|
|
992
|
+
return Object.keys(profiles[profile]?.translations ?? {}).sort();
|
|
993
|
+
}
|
|
994
|
+
function getSelectableLanguages(input) {
|
|
995
|
+
return getProfileLanguages(input.profiles, input.profile);
|
|
996
|
+
}
|
|
997
|
+
function resolveFallbackLanguage(input) {
|
|
998
|
+
const configuredFallbackLanguage = normalizeLanguage(input.profile?.fallbackLanguage) ?? 'en';
|
|
999
|
+
const profileLanguages = Object.keys(input.profile?.translations ?? {}).sort();
|
|
1000
|
+
if (profileLanguages.includes(configuredFallbackLanguage)) return configuredFallbackLanguage;
|
|
1001
|
+
if (profileLanguages.includes('en')) return 'en';
|
|
1002
|
+
return profileLanguages[0] ?? configuredFallbackLanguage;
|
|
1003
|
+
}
|
|
1004
|
+
function resolveActiveProfile(input) {
|
|
1005
|
+
const requestedProfile = input.policyProfile ?? input.defaultProfile;
|
|
1006
|
+
if (input.profiles[requestedProfile]) return requestedProfile;
|
|
1007
|
+
if (input.policyProfile) warnOnce(input.logger, `i18n.profile.missing:${requestedProfile}`, `Policy i18n profile '${requestedProfile}' does not exist. Falling back to default profile '${input.defaultProfile}'.`);
|
|
1008
|
+
return input.defaultProfile;
|
|
1009
|
+
}
|
|
1010
|
+
function translations_getTranslationsData(acceptLanguage, customTranslations, options) {
|
|
1011
|
+
const profiles = normalizeProfiles({
|
|
1012
|
+
customTranslations,
|
|
1013
|
+
i18n: options?.i18n,
|
|
1014
|
+
logger: options?.logger
|
|
1015
|
+
});
|
|
1016
|
+
const defaultProfile = options?.i18n?.defaultProfile ?? DEFAULT_PROFILE;
|
|
1017
|
+
const profile = resolveActiveProfile({
|
|
1018
|
+
profiles,
|
|
1019
|
+
defaultProfile,
|
|
1020
|
+
policyProfile: options?.policyI18n?.messageProfile,
|
|
1021
|
+
logger: options?.logger
|
|
1022
|
+
});
|
|
1023
|
+
const configuredLanguages = Object.keys(profiles).length > 0 ? getSelectableLanguages({
|
|
1024
|
+
profiles,
|
|
1025
|
+
profile
|
|
1026
|
+
}) : Object.keys(all_namespaceObject.baseTranslations);
|
|
1027
|
+
const fallbackLanguage = Object.keys(profiles).length > 0 ? resolveFallbackLanguage({
|
|
1028
|
+
profile: profiles[profile]
|
|
1029
|
+
}) : 'en';
|
|
1030
|
+
const policyLanguage = normalizeLanguage(options?.policyI18n?.language);
|
|
1031
|
+
const requestedLanguage = policyLanguage ?? (0, translations_namespaceObject.selectLanguage)(configuredLanguages, {
|
|
816
1032
|
header: acceptLanguage,
|
|
817
|
-
fallback:
|
|
1033
|
+
fallback: fallbackLanguage
|
|
1034
|
+
});
|
|
1035
|
+
const candidates = buildCandidates({
|
|
1036
|
+
language: requestedLanguage,
|
|
1037
|
+
fallbackLanguage
|
|
1038
|
+
});
|
|
1039
|
+
const selectedCandidate = candidates.find((candidate)=>!!profiles[profile]?.translations[candidate.language]);
|
|
1040
|
+
if (selectedCandidate && 'profile_language' !== selectedCandidate.reason) warnOnce(options?.logger, `i18n.fallback:${profile}:${requestedLanguage}:${selectedCandidate.language}`, `Policy translation fallback used (${selectedCandidate.reason}).`, {
|
|
1041
|
+
requestedProfile: profile,
|
|
1042
|
+
requestedLanguage,
|
|
1043
|
+
resolvedProfile: profile,
|
|
1044
|
+
resolvedLanguage: selectedCandidate.language
|
|
818
1045
|
});
|
|
819
|
-
|
|
820
|
-
|
|
1046
|
+
let language = selectedCandidate?.language ?? requestedLanguage;
|
|
1047
|
+
if (!selectedCandidate && !isSupportedBaseLanguage(language)) {
|
|
1048
|
+
warnOnce(options?.logger, `i18n.base-fallback:${language}`, `No translation found for '${language}'. Falling back to base English translations.`);
|
|
1049
|
+
language = 'en';
|
|
1050
|
+
}
|
|
1051
|
+
const base = isSupportedBaseLanguage(language) ? all_namespaceObject.baseTranslations[language] : all_namespaceObject.baseTranslations.en;
|
|
1052
|
+
const custom = selectedCandidate ? profiles[profile]?.translations[selectedCandidate.language] : void 0;
|
|
821
1053
|
const translations = custom ? (0, translations_namespaceObject.deepMergeTranslations)(base, custom) : base;
|
|
822
1054
|
return {
|
|
823
1055
|
translations: translations,
|
|
824
|
-
language
|
|
1056
|
+
language
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
function stripIabTranslations(translations) {
|
|
1060
|
+
const { iab: _iab, ...rest } = translations;
|
|
1061
|
+
return rest;
|
|
1062
|
+
}
|
|
1063
|
+
function resolveNoPolicyFallback() {
|
|
1064
|
+
return {
|
|
1065
|
+
id: 'no_banner',
|
|
1066
|
+
model: 'none',
|
|
1067
|
+
ui: {
|
|
1068
|
+
mode: 'none'
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
async function resolveInitPayload(request, options, logger) {
|
|
1073
|
+
const acceptLanguage = request.headers.get('accept-language') || 'en';
|
|
1074
|
+
const location = await getLocation(request, options);
|
|
1075
|
+
const jurisdiction = getJurisdiction(location, options);
|
|
1076
|
+
const hasExplicitPolicyPack = void 0 !== options.policyPacks;
|
|
1077
|
+
const isExplicitEmptyPolicyPack = hasExplicitPolicyPack && (options.policyPacks?.length ?? 0) === 0;
|
|
1078
|
+
const policyDecision = isExplicitEmptyPolicyPack ? void 0 : await resolvePolicyDecision({
|
|
1079
|
+
policies: options.policyPacks,
|
|
1080
|
+
countryCode: location.countryCode,
|
|
1081
|
+
regionCode: location.regionCode,
|
|
1082
|
+
jurisdiction,
|
|
1083
|
+
iabEnabled: options.iab?.enabled === true
|
|
1084
|
+
});
|
|
1085
|
+
if (hasExplicitPolicyPack && !isExplicitEmptyPolicyPack && !policyDecision) logger?.warn('Policy packs configured but no policy matched', {
|
|
1086
|
+
country: location.countryCode,
|
|
1087
|
+
region: location.regionCode
|
|
1088
|
+
});
|
|
1089
|
+
const resolvedPolicy = hasExplicitPolicyPack ? policyDecision?.policy ?? resolveNoPolicyFallback() : void 0;
|
|
1090
|
+
const iabOptions = options.iab;
|
|
1091
|
+
const shouldIncludeIabPayload = iabOptions?.enabled === true && (!hasExplicitPolicyPack || resolvedPolicy?.model === 'iab');
|
|
1092
|
+
const translationsResult = translations_getTranslationsData(acceptLanguage, options.customTranslations, {
|
|
1093
|
+
i18n: options.i18n,
|
|
1094
|
+
policyI18n: resolvedPolicy?.i18n,
|
|
1095
|
+
logger
|
|
1096
|
+
});
|
|
1097
|
+
const responseTranslations = shouldIncludeIabPayload ? translationsResult : {
|
|
1098
|
+
...translationsResult,
|
|
1099
|
+
translations: stripIabTranslations(translationsResult.translations)
|
|
1100
|
+
};
|
|
1101
|
+
let gvl = null;
|
|
1102
|
+
if (shouldIncludeIabPayload && iabOptions) {
|
|
1103
|
+
const language = translationsResult.language.split('-')[0] || 'en';
|
|
1104
|
+
const gvlResolver = createGVLResolver({
|
|
1105
|
+
appName: options.appName || 'c15t',
|
|
1106
|
+
bundled: iabOptions.bundled,
|
|
1107
|
+
cacheAdapter: options.cache?.adapter,
|
|
1108
|
+
vendorIds: iabOptions.vendorIds,
|
|
1109
|
+
endpoint: iabOptions.endpoint
|
|
1110
|
+
});
|
|
1111
|
+
gvl = await gvlResolver.get(language);
|
|
1112
|
+
}
|
|
1113
|
+
const customVendors = shouldIncludeIabPayload ? iabOptions?.customVendors : void 0;
|
|
1114
|
+
const snapshot = policyDecision ? await createPolicySnapshotToken({
|
|
1115
|
+
options: options.policySnapshot,
|
|
1116
|
+
tenantId: options.tenantId,
|
|
1117
|
+
policyId: policyDecision.policy.id,
|
|
1118
|
+
fingerprint: policyDecision.fingerprint,
|
|
1119
|
+
matchedBy: policyDecision.matchedBy,
|
|
1120
|
+
country: location?.countryCode ?? null,
|
|
1121
|
+
region: location?.regionCode ?? null,
|
|
1122
|
+
jurisdiction,
|
|
1123
|
+
language: translationsResult.language,
|
|
1124
|
+
model: policyDecision.policy.model,
|
|
1125
|
+
policyI18n: policyDecision.policy.i18n,
|
|
1126
|
+
expiryDays: policyDecision.policy.consent?.expiryDays,
|
|
1127
|
+
scopeMode: policyDecision.policy.consent?.scopeMode,
|
|
1128
|
+
uiMode: policyDecision.policy.ui?.mode,
|
|
1129
|
+
bannerUi: policyDecision.policy.ui?.banner,
|
|
1130
|
+
dialogUi: policyDecision.policy.ui?.dialog,
|
|
1131
|
+
categories: policyDecision.policy.consent?.categories,
|
|
1132
|
+
preselectedCategories: policyDecision.policy.consent?.preselectedCategories,
|
|
1133
|
+
gpc: policyDecision.policy.consent?.gpc,
|
|
1134
|
+
proofConfig: policyDecision.policy.proof
|
|
1135
|
+
}) : void 0;
|
|
1136
|
+
const gpc = '1' === request.headers.get('sec-gpc');
|
|
1137
|
+
metrics_getMetrics()?.recordInit({
|
|
1138
|
+
jurisdiction,
|
|
1139
|
+
country: location?.countryCode ?? void 0,
|
|
1140
|
+
region: location?.regionCode ?? void 0,
|
|
1141
|
+
gpc
|
|
1142
|
+
});
|
|
1143
|
+
return {
|
|
1144
|
+
jurisdiction,
|
|
1145
|
+
location,
|
|
1146
|
+
translations: responseTranslations,
|
|
1147
|
+
branding: options.branding || 'c15t',
|
|
1148
|
+
...shouldIncludeIabPayload && {
|
|
1149
|
+
gvl,
|
|
1150
|
+
customVendors
|
|
1151
|
+
},
|
|
1152
|
+
...resolvedPolicy && {
|
|
1153
|
+
policy: resolvedPolicy
|
|
1154
|
+
},
|
|
1155
|
+
...policyDecision && {
|
|
1156
|
+
policyDecision: {
|
|
1157
|
+
policyId: policyDecision.policy.id,
|
|
1158
|
+
fingerprint: policyDecision.fingerprint,
|
|
1159
|
+
matchedBy: policyDecision.matchedBy,
|
|
1160
|
+
country: location.countryCode,
|
|
1161
|
+
region: location.regionCode,
|
|
1162
|
+
jurisdiction
|
|
1163
|
+
}
|
|
1164
|
+
},
|
|
1165
|
+
...snapshot?.token && {
|
|
1166
|
+
policySnapshotToken: snapshot.token
|
|
1167
|
+
},
|
|
1168
|
+
...shouldIncludeIabPayload && iabOptions?.cmpId != null && {
|
|
1169
|
+
cmpId: iabOptions.cmpId
|
|
1170
|
+
}
|
|
825
1171
|
};
|
|
826
1172
|
}
|
|
827
1173
|
const createInitRoute = (options)=>{
|
|
@@ -834,7 +1180,7 @@ const createInitRoute = (options)=>{
|
|
|
834
1180
|
- **Location** – User's location (null if geo-location is disabled)
|
|
835
1181
|
- **Translations** – Consent manager copy (from \`Accept-Language\` header)
|
|
836
1182
|
- **Branding** – Configured branding key
|
|
837
|
-
- **GVL** – Global Vendor List when
|
|
1183
|
+
- **GVL** – Global Vendor List when IAB is active for the request
|
|
838
1184
|
|
|
839
1185
|
Use for geo-targeted consent banners and regional compliance.`,
|
|
840
1186
|
tags: [
|
|
@@ -851,43 +1197,22 @@ Use for geo-targeted consent banners and regional compliance.`,
|
|
|
851
1197
|
}
|
|
852
1198
|
}
|
|
853
1199
|
}), async (c)=>{
|
|
854
|
-
const
|
|
855
|
-
const
|
|
856
|
-
|
|
857
|
-
const jurisdiction = getJurisdiction(location, options);
|
|
858
|
-
const translationsResult = translations_getTranslationsData(acceptLanguage, options.advanced?.customTranslations);
|
|
859
|
-
let gvl = null;
|
|
860
|
-
if (options.advanced?.gvl?.enabled) {
|
|
861
|
-
const language = translationsResult.language.split('-')[0] || 'en';
|
|
862
|
-
const gvlResolver = createGVLResolver({
|
|
863
|
-
appName: options.appName || 'c15t',
|
|
864
|
-
bundled: options.advanced.gvl.bundled,
|
|
865
|
-
cacheAdapter: options.advanced.cache?.adapter,
|
|
866
|
-
vendorIds: options.advanced.gvl.vendorIds,
|
|
867
|
-
endpoint: options.advanced.gvl.endpoint
|
|
868
|
-
});
|
|
869
|
-
gvl = await gvlResolver.get(language);
|
|
870
|
-
}
|
|
871
|
-
const customVendors = options.advanced?.gvl?.customVendors;
|
|
872
|
-
const gpc = '1' === request.headers.get('sec-gpc');
|
|
873
|
-
getMetrics()?.recordInit({
|
|
874
|
-
jurisdiction,
|
|
875
|
-
country: location?.countryCode ?? void 0,
|
|
876
|
-
region: location?.regionCode ?? void 0,
|
|
877
|
-
gpc
|
|
878
|
-
});
|
|
879
|
-
return c.json({
|
|
880
|
-
jurisdiction,
|
|
881
|
-
location,
|
|
882
|
-
translations: translationsResult,
|
|
883
|
-
branding: options.advanced?.branding || 'c15t',
|
|
884
|
-
gvl,
|
|
885
|
-
customVendors
|
|
886
|
-
});
|
|
1200
|
+
const ctx = c.get('c15tContext');
|
|
1201
|
+
const payload = await resolveInitPayload(c.req.raw, options, ctx?.logger);
|
|
1202
|
+
return c.json(payload);
|
|
887
1203
|
});
|
|
888
1204
|
return app;
|
|
889
1205
|
};
|
|
890
|
-
const
|
|
1206
|
+
const external_base_x_namespaceObject = require("base-x");
|
|
1207
|
+
var external_base_x_default = /*#__PURE__*/ __webpack_require__.n(external_base_x_namespaceObject);
|
|
1208
|
+
external_base_x_default()('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
|
|
1209
|
+
class consent_policy_LegalDocumentPolicyConflictError extends Error {
|
|
1210
|
+
constructor(message){
|
|
1211
|
+
super(message);
|
|
1212
|
+
this.name = 'LegalDocumentPolicyConflictError';
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
const version_version = '2.0.0-rc.10';
|
|
891
1216
|
function getHeaders(headers) {
|
|
892
1217
|
if (!headers) return {
|
|
893
1218
|
countryCode: null,
|
|
@@ -974,6 +1299,12 @@ const getSubjectHandler = async (c)=>{
|
|
|
974
1299
|
const subjectId = c.req.param('id');
|
|
975
1300
|
const type = c.req.query('type');
|
|
976
1301
|
const typeFilter = type?.split(',').map((t)=>t.trim()) || [];
|
|
1302
|
+
if (!subjectId) throw new http_exception_namespaceObject.HTTPException(400, {
|
|
1303
|
+
message: 'Subject ID is required',
|
|
1304
|
+
cause: {
|
|
1305
|
+
code: 'SUBJECT_ID_REQUIRED'
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
977
1308
|
logger.debug('Request parameters', {
|
|
978
1309
|
subjectId,
|
|
979
1310
|
typeFilter
|
|
@@ -1002,7 +1333,6 @@ const getSubjectHandler = async (c)=>{
|
|
|
1002
1333
|
subject: {
|
|
1003
1334
|
id: subject.id,
|
|
1004
1335
|
externalId: subject.externalId ?? void 0,
|
|
1005
|
-
isIdentified: subject.isIdentified,
|
|
1006
1336
|
createdAt: subject.createdAt
|
|
1007
1337
|
},
|
|
1008
1338
|
consents: filteredConsents,
|
|
@@ -1010,7 +1340,7 @@ const getSubjectHandler = async (c)=>{
|
|
|
1010
1340
|
});
|
|
1011
1341
|
} catch (error) {
|
|
1012
1342
|
logger.error('Error in GET /subjects/:id handler', {
|
|
1013
|
-
error:
|
|
1343
|
+
error: extract_error_message_extractErrorMessage(error),
|
|
1014
1344
|
errorType: error instanceof Error ? error.constructor.name : typeof error
|
|
1015
1345
|
});
|
|
1016
1346
|
if (error instanceof http_exception_namespaceObject.HTTPException) throw error;
|
|
@@ -1058,7 +1388,6 @@ const listSubjectsHandler = async (c)=>{
|
|
|
1058
1388
|
return {
|
|
1059
1389
|
id: subject.id,
|
|
1060
1390
|
externalId: subject.externalId ?? externalId,
|
|
1061
|
-
isIdentified: subject.isIdentified,
|
|
1062
1391
|
createdAt: subject.createdAt,
|
|
1063
1392
|
consents: consentItems
|
|
1064
1393
|
};
|
|
@@ -1072,7 +1401,7 @@ const listSubjectsHandler = async (c)=>{
|
|
|
1072
1401
|
});
|
|
1073
1402
|
} catch (error) {
|
|
1074
1403
|
logger.error('Error in GET /subjects handler', {
|
|
1075
|
-
error:
|
|
1404
|
+
error: extract_error_message_extractErrorMessage(error),
|
|
1076
1405
|
errorType: error instanceof Error ? error.constructor.name : typeof error
|
|
1077
1406
|
});
|
|
1078
1407
|
if (error instanceof http_exception_namespaceObject.HTTPException) throw error;
|
|
@@ -1084,9 +1413,7 @@ const listSubjectsHandler = async (c)=>{
|
|
|
1084
1413
|
});
|
|
1085
1414
|
}
|
|
1086
1415
|
};
|
|
1087
|
-
const
|
|
1088
|
-
var external_base_x_default = /*#__PURE__*/ __webpack_require__.n(external_base_x_namespaceObject);
|
|
1089
|
-
const prefixes = {
|
|
1416
|
+
const utils_prefixes = {
|
|
1090
1417
|
auditLog: 'log',
|
|
1091
1418
|
consent: 'cns',
|
|
1092
1419
|
consentPolicy: 'pol',
|
|
@@ -1094,10 +1421,10 @@ const prefixes = {
|
|
|
1094
1421
|
domain: 'dom',
|
|
1095
1422
|
subject: 'sub'
|
|
1096
1423
|
};
|
|
1097
|
-
const
|
|
1098
|
-
function
|
|
1424
|
+
const utils_b58 = external_base_x_default()('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
|
|
1425
|
+
function utils_generateId(model) {
|
|
1099
1426
|
const buf = crypto.getRandomValues(new Uint8Array(20));
|
|
1100
|
-
const prefix =
|
|
1427
|
+
const prefix = utils_prefixes[model];
|
|
1101
1428
|
const EPOCH_TIMESTAMP = 1700000000000;
|
|
1102
1429
|
const t = Date.now() - EPOCH_TIMESTAMP;
|
|
1103
1430
|
const high = Math.floor(t / 0x100000000);
|
|
@@ -1110,9 +1437,9 @@ function generateId(model) {
|
|
|
1110
1437
|
buf[5] = low >>> 16 & 255;
|
|
1111
1438
|
buf[6] = low >>> 8 & 255;
|
|
1112
1439
|
buf[7] = 255 & low;
|
|
1113
|
-
return `${prefix}_${
|
|
1440
|
+
return `${prefix}_${utils_b58.encode(buf)}`;
|
|
1114
1441
|
}
|
|
1115
|
-
async function
|
|
1442
|
+
async function utils_generateUniqueId(db, model, ctx, options = {}) {
|
|
1116
1443
|
const { maxRetries = 10, attempt = 0, baseDelay = 5 } = options;
|
|
1117
1444
|
if (attempt >= maxRetries) {
|
|
1118
1445
|
const error = new Error(`Failed to generate unique ID for ${model} after ${maxRetries} attempts`);
|
|
@@ -1122,7 +1449,7 @@ async function generateUniqueId(db, model, ctx, options = {}) {
|
|
|
1122
1449
|
});
|
|
1123
1450
|
throw error;
|
|
1124
1451
|
}
|
|
1125
|
-
const id =
|
|
1452
|
+
const id = utils_generateId(model);
|
|
1126
1453
|
try {
|
|
1127
1454
|
const existing = await db.findFirst(model, {
|
|
1128
1455
|
where: (b)=>b('id', '=', id)
|
|
@@ -1136,7 +1463,7 @@ async function generateUniqueId(db, model, ctx, options = {}) {
|
|
|
1136
1463
|
});
|
|
1137
1464
|
const delay = Math.min(baseDelay * 2 ** attempt, 1000);
|
|
1138
1465
|
await new Promise((resolve)=>setTimeout(resolve, delay));
|
|
1139
|
-
return
|
|
1466
|
+
return utils_generateUniqueId(db, model, ctx, {
|
|
1140
1467
|
maxRetries,
|
|
1141
1468
|
attempt: attempt + 1,
|
|
1142
1469
|
baseDelay
|
|
@@ -1152,7 +1479,7 @@ async function generateUniqueId(db, model, ctx, options = {}) {
|
|
|
1152
1479
|
if (attempt < maxRetries - 1) {
|
|
1153
1480
|
const delay = Math.min(baseDelay * 2 ** attempt, 2000);
|
|
1154
1481
|
await new Promise((resolve)=>setTimeout(resolve, delay));
|
|
1155
|
-
return
|
|
1482
|
+
return utils_generateUniqueId(db, model, ctx, {
|
|
1156
1483
|
maxRetries,
|
|
1157
1484
|
attempt: attempt + 1,
|
|
1158
1485
|
baseDelay
|
|
@@ -1169,6 +1496,12 @@ const patchSubjectHandler = async (c)=>{
|
|
|
1169
1496
|
const subjectId = c.req.param('id');
|
|
1170
1497
|
const body = await c.req.json();
|
|
1171
1498
|
const { externalId, identityProvider = 'external' } = body;
|
|
1499
|
+
if (!subjectId) throw new http_exception_namespaceObject.HTTPException(400, {
|
|
1500
|
+
message: 'Subject ID is required',
|
|
1501
|
+
cause: {
|
|
1502
|
+
code: 'SUBJECT_ID_REQUIRED'
|
|
1503
|
+
}
|
|
1504
|
+
});
|
|
1172
1505
|
logger.debug('Request parameters', {
|
|
1173
1506
|
subjectId,
|
|
1174
1507
|
externalId,
|
|
@@ -1191,12 +1524,11 @@ const patchSubjectHandler = async (c)=>{
|
|
|
1191
1524
|
set: {
|
|
1192
1525
|
externalId,
|
|
1193
1526
|
identityProvider,
|
|
1194
|
-
isIdentified: true,
|
|
1195
1527
|
updatedAt: new Date()
|
|
1196
1528
|
}
|
|
1197
1529
|
});
|
|
1198
1530
|
await tx.create('auditLog', {
|
|
1199
|
-
id: await
|
|
1531
|
+
id: await utils_generateUniqueId(tx, 'auditLog', ctx),
|
|
1200
1532
|
subjectId,
|
|
1201
1533
|
entityType: 'subject',
|
|
1202
1534
|
entityId: subjectId,
|
|
@@ -1211,10 +1543,6 @@ const patchSubjectHandler = async (c)=>{
|
|
|
1211
1543
|
identityProvider: {
|
|
1212
1544
|
from: subject.identityProvider,
|
|
1213
1545
|
to: identityProvider
|
|
1214
|
-
},
|
|
1215
|
-
isIdentified: {
|
|
1216
|
-
from: subject.isIdentified,
|
|
1217
|
-
to: true
|
|
1218
1546
|
}
|
|
1219
1547
|
},
|
|
1220
1548
|
metadata: {
|
|
@@ -1228,18 +1556,17 @@ const patchSubjectHandler = async (c)=>{
|
|
|
1228
1556
|
externalId,
|
|
1229
1557
|
identityProvider
|
|
1230
1558
|
});
|
|
1231
|
-
|
|
1559
|
+
metrics_getMetrics()?.recordSubjectLinked(identityProvider);
|
|
1232
1560
|
return c.json({
|
|
1233
1561
|
success: true,
|
|
1234
1562
|
subject: {
|
|
1235
1563
|
id: subjectId,
|
|
1236
|
-
externalId
|
|
1237
|
-
isIdentified: true
|
|
1564
|
+
externalId
|
|
1238
1565
|
}
|
|
1239
1566
|
});
|
|
1240
1567
|
} catch (error) {
|
|
1241
1568
|
logger.error('Error in PATCH /subjects/:id handler', {
|
|
1242
|
-
error:
|
|
1569
|
+
error: extract_error_message_extractErrorMessage(error),
|
|
1243
1570
|
errorType: error instanceof Error ? error.constructor.name : typeof error
|
|
1244
1571
|
});
|
|
1245
1572
|
if (error instanceof http_exception_namespaceObject.HTTPException) throw error;
|
|
@@ -1251,6 +1578,234 @@ const patchSubjectHandler = async (c)=>{
|
|
|
1251
1578
|
});
|
|
1252
1579
|
}
|
|
1253
1580
|
};
|
|
1581
|
+
const DEFAULT_ISSUER = 'c15t';
|
|
1582
|
+
const DEFAULT_AUDIENCE = 'c15t-legal-document-snapshot';
|
|
1583
|
+
function isLegalDocumentPolicyType(type) {
|
|
1584
|
+
return 'privacy_policy' === type || 'terms_and_conditions' === type || 'dpa' === type;
|
|
1585
|
+
}
|
|
1586
|
+
function snapshot_resolveSnapshotIssuer(options) {
|
|
1587
|
+
return options?.issuer?.trim() || DEFAULT_ISSUER;
|
|
1588
|
+
}
|
|
1589
|
+
function snapshot_resolveSnapshotAudience(params) {
|
|
1590
|
+
const configuredAudience = params.options?.audience?.trim();
|
|
1591
|
+
if (configuredAudience) return configuredAudience;
|
|
1592
|
+
return params.tenantId ? `${DEFAULT_AUDIENCE}:${params.tenantId}` : DEFAULT_AUDIENCE;
|
|
1593
|
+
}
|
|
1594
|
+
function snapshot_getSigningKey(secret) {
|
|
1595
|
+
return new TextEncoder().encode(secret);
|
|
1596
|
+
}
|
|
1597
|
+
function isLegalDocumentSnapshotPayload(payload) {
|
|
1598
|
+
return 'string' == typeof payload.iss && 'string' == typeof payload.aud && 'string' == typeof payload.sub && isLegalDocumentPolicyType(payload.type) && 'string' == typeof payload.version && 'string' == typeof payload.hash && 'string' == typeof payload.effectiveDate && 'number' == typeof payload.iat && 'number' == typeof payload.exp;
|
|
1599
|
+
}
|
|
1600
|
+
async function verifyLegalDocumentSnapshotToken(params) {
|
|
1601
|
+
const { token, options, tenantId } = params;
|
|
1602
|
+
if (!options?.signingKey) return {
|
|
1603
|
+
valid: false,
|
|
1604
|
+
reason: 'missing'
|
|
1605
|
+
};
|
|
1606
|
+
if (!token) return {
|
|
1607
|
+
valid: false,
|
|
1608
|
+
reason: 'missing'
|
|
1609
|
+
};
|
|
1610
|
+
if (3 !== token.split('.').length) return {
|
|
1611
|
+
valid: false,
|
|
1612
|
+
reason: 'malformed'
|
|
1613
|
+
};
|
|
1614
|
+
try {
|
|
1615
|
+
const { payload, protectedHeader } = await (0, external_jose_namespaceObject.jwtVerify)(token, snapshot_getSigningKey(options.signingKey), {
|
|
1616
|
+
issuer: snapshot_resolveSnapshotIssuer(options),
|
|
1617
|
+
audience: snapshot_resolveSnapshotAudience({
|
|
1618
|
+
options,
|
|
1619
|
+
tenantId
|
|
1620
|
+
})
|
|
1621
|
+
});
|
|
1622
|
+
const header = protectedHeader;
|
|
1623
|
+
if ('HS256' !== header.alg || 'JWT' !== header.typ) return {
|
|
1624
|
+
valid: false,
|
|
1625
|
+
reason: 'invalid'
|
|
1626
|
+
};
|
|
1627
|
+
if (!isLegalDocumentSnapshotPayload(payload)) return {
|
|
1628
|
+
valid: false,
|
|
1629
|
+
reason: 'invalid'
|
|
1630
|
+
};
|
|
1631
|
+
if (payload.sub !== payload.hash) return {
|
|
1632
|
+
valid: false,
|
|
1633
|
+
reason: 'invalid'
|
|
1634
|
+
};
|
|
1635
|
+
if ((tenantId ?? void 0) !== (payload.tenantId ?? void 0)) return {
|
|
1636
|
+
valid: false,
|
|
1637
|
+
reason: 'invalid'
|
|
1638
|
+
};
|
|
1639
|
+
return {
|
|
1640
|
+
valid: true,
|
|
1641
|
+
payload
|
|
1642
|
+
};
|
|
1643
|
+
} catch (error) {
|
|
1644
|
+
if (error instanceof external_jose_namespaceObject.errors.JWTExpired) return {
|
|
1645
|
+
valid: false,
|
|
1646
|
+
reason: 'expired'
|
|
1647
|
+
};
|
|
1648
|
+
return {
|
|
1649
|
+
valid: false,
|
|
1650
|
+
reason: 'invalid'
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
function buildRuntimeDecisionDedupeKey(input) {
|
|
1655
|
+
return [
|
|
1656
|
+
input.tenantId ?? 'default',
|
|
1657
|
+
input.fingerprint,
|
|
1658
|
+
input.matchedBy,
|
|
1659
|
+
input.countryCode ?? 'none',
|
|
1660
|
+
input.regionCode ?? 'none',
|
|
1661
|
+
input.jurisdiction,
|
|
1662
|
+
input.language ?? 'none'
|
|
1663
|
+
].join('|');
|
|
1664
|
+
}
|
|
1665
|
+
function buildDecisionPayload(params) {
|
|
1666
|
+
const { tenantId, snapshot, decision, location, jurisdiction, language, proofConfig } = params;
|
|
1667
|
+
if (snapshot?.valid && snapshot.payload) {
|
|
1668
|
+
const sp = snapshot.payload;
|
|
1669
|
+
return {
|
|
1670
|
+
tenantId,
|
|
1671
|
+
policyId: sp.policyId,
|
|
1672
|
+
fingerprint: sp.fingerprint,
|
|
1673
|
+
matchedBy: sp.matchedBy,
|
|
1674
|
+
countryCode: sp.country,
|
|
1675
|
+
regionCode: sp.region,
|
|
1676
|
+
jurisdiction: sp.jurisdiction,
|
|
1677
|
+
language: sp.language,
|
|
1678
|
+
model: sp.model,
|
|
1679
|
+
policyI18n: sp.policyI18n,
|
|
1680
|
+
uiMode: sp.uiMode,
|
|
1681
|
+
bannerUi: sp.bannerUi,
|
|
1682
|
+
dialogUi: sp.dialogUi,
|
|
1683
|
+
categories: sp.categories,
|
|
1684
|
+
preselectedCategories: sp.preselectedCategories,
|
|
1685
|
+
proofConfig: sp.proofConfig,
|
|
1686
|
+
dedupeKey: buildRuntimeDecisionDedupeKey({
|
|
1687
|
+
tenantId,
|
|
1688
|
+
fingerprint: sp.fingerprint,
|
|
1689
|
+
matchedBy: sp.matchedBy,
|
|
1690
|
+
countryCode: sp.country,
|
|
1691
|
+
regionCode: sp.region,
|
|
1692
|
+
jurisdiction: sp.jurisdiction,
|
|
1693
|
+
language: sp.language
|
|
1694
|
+
}),
|
|
1695
|
+
source: 'snapshot_token'
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
if (decision) return {
|
|
1699
|
+
tenantId,
|
|
1700
|
+
policyId: decision.policy.id,
|
|
1701
|
+
fingerprint: decision.fingerprint,
|
|
1702
|
+
matchedBy: decision.matchedBy,
|
|
1703
|
+
countryCode: location.countryCode,
|
|
1704
|
+
regionCode: location.regionCode,
|
|
1705
|
+
jurisdiction,
|
|
1706
|
+
language,
|
|
1707
|
+
model: decision.policy.model,
|
|
1708
|
+
policyI18n: decision.policy.i18n,
|
|
1709
|
+
uiMode: decision.policy.ui?.mode,
|
|
1710
|
+
bannerUi: decision.policy.ui?.banner,
|
|
1711
|
+
dialogUi: decision.policy.ui?.dialog,
|
|
1712
|
+
categories: decision.policy.consent?.categories,
|
|
1713
|
+
preselectedCategories: decision.policy.consent?.preselectedCategories,
|
|
1714
|
+
proofConfig,
|
|
1715
|
+
dedupeKey: buildRuntimeDecisionDedupeKey({
|
|
1716
|
+
tenantId,
|
|
1717
|
+
fingerprint: decision.fingerprint,
|
|
1718
|
+
matchedBy: decision.matchedBy,
|
|
1719
|
+
countryCode: location.countryCode,
|
|
1720
|
+
regionCode: location.regionCode,
|
|
1721
|
+
jurisdiction,
|
|
1722
|
+
language
|
|
1723
|
+
}),
|
|
1724
|
+
source: 'write_time_fallback'
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
function parseLanguageFromHeader(header) {
|
|
1728
|
+
if (!header) return;
|
|
1729
|
+
const firstLanguage = header.split(',')[0]?.split(';')[0]?.trim();
|
|
1730
|
+
if (!firstLanguage) return;
|
|
1731
|
+
return firstLanguage.split('-')[0]?.toLowerCase();
|
|
1732
|
+
}
|
|
1733
|
+
function isLegalDocumentType(type) {
|
|
1734
|
+
return 'privacy_policy' === type || 'terms_and_conditions' === type || 'dpa' === type;
|
|
1735
|
+
}
|
|
1736
|
+
function resolveSnapshotFailureMode(ctx) {
|
|
1737
|
+
return ctx.policySnapshot?.onValidationFailure ?? 'reject';
|
|
1738
|
+
}
|
|
1739
|
+
function buildSnapshotHttpException(reason) {
|
|
1740
|
+
switch(reason){
|
|
1741
|
+
case 'missing':
|
|
1742
|
+
return new http_exception_namespaceObject.HTTPException(409, {
|
|
1743
|
+
message: 'Policy snapshot token is required',
|
|
1744
|
+
cause: {
|
|
1745
|
+
code: 'POLICY_SNAPSHOT_REQUIRED'
|
|
1746
|
+
}
|
|
1747
|
+
});
|
|
1748
|
+
case 'expired':
|
|
1749
|
+
return new http_exception_namespaceObject.HTTPException(409, {
|
|
1750
|
+
message: 'Policy snapshot token has expired',
|
|
1751
|
+
cause: {
|
|
1752
|
+
code: 'POLICY_SNAPSHOT_EXPIRED'
|
|
1753
|
+
}
|
|
1754
|
+
});
|
|
1755
|
+
case 'malformed':
|
|
1756
|
+
case 'invalid':
|
|
1757
|
+
return new http_exception_namespaceObject.HTTPException(409, {
|
|
1758
|
+
message: 'Policy snapshot token is invalid',
|
|
1759
|
+
cause: {
|
|
1760
|
+
code: 'POLICY_SNAPSHOT_INVALID'
|
|
1761
|
+
}
|
|
1762
|
+
});
|
|
1763
|
+
default:
|
|
1764
|
+
{
|
|
1765
|
+
const _exhaustive = reason;
|
|
1766
|
+
throw new Error(`Unhandled policy snapshot verification failure reason: ${_exhaustive}`);
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
function buildLegalDocumentSnapshotHttpException(reason) {
|
|
1771
|
+
switch(reason){
|
|
1772
|
+
case 'missing':
|
|
1773
|
+
return new http_exception_namespaceObject.HTTPException(409, {
|
|
1774
|
+
message: 'Legal document snapshot token is required',
|
|
1775
|
+
cause: {
|
|
1776
|
+
code: 'LEGAL_DOCUMENT_SNAPSHOT_REQUIRED'
|
|
1777
|
+
}
|
|
1778
|
+
});
|
|
1779
|
+
case 'expired':
|
|
1780
|
+
return new http_exception_namespaceObject.HTTPException(409, {
|
|
1781
|
+
message: 'Legal document snapshot token has expired',
|
|
1782
|
+
cause: {
|
|
1783
|
+
code: 'LEGAL_DOCUMENT_SNAPSHOT_EXPIRED'
|
|
1784
|
+
}
|
|
1785
|
+
});
|
|
1786
|
+
case 'malformed':
|
|
1787
|
+
case 'invalid':
|
|
1788
|
+
return new http_exception_namespaceObject.HTTPException(409, {
|
|
1789
|
+
message: 'Legal document snapshot token is invalid',
|
|
1790
|
+
cause: {
|
|
1791
|
+
code: 'LEGAL_DOCUMENT_SNAPSHOT_INVALID'
|
|
1792
|
+
}
|
|
1793
|
+
});
|
|
1794
|
+
default:
|
|
1795
|
+
{
|
|
1796
|
+
const _exhaustive = reason;
|
|
1797
|
+
throw new Error(`Unhandled legal document snapshot verification failure reason: ${_exhaustive}`);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
function buildLegalDocumentProofHttpException(message) {
|
|
1802
|
+
return new http_exception_namespaceObject.HTTPException(409, {
|
|
1803
|
+
message,
|
|
1804
|
+
cause: {
|
|
1805
|
+
code: 'LEGAL_DOCUMENT_PROOF_REQUIRED'
|
|
1806
|
+
}
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1254
1809
|
const postSubjectHandler = async (c)=>{
|
|
1255
1810
|
const ctx = c.get('c15tContext');
|
|
1256
1811
|
const logger = ctx.logger;
|
|
@@ -1260,6 +1815,8 @@ const postSubjectHandler = async (c)=>{
|
|
|
1260
1815
|
const { type, subjectId, identityProvider, externalSubjectId, domain, metadata, givenAt: givenAtEpoch } = input;
|
|
1261
1816
|
const preferences = 'preferences' in input ? input.preferences : void 0;
|
|
1262
1817
|
const givenAt = new Date(givenAtEpoch);
|
|
1818
|
+
const rawConsentAction = 'consentAction' in input ? input.consentAction : void 0;
|
|
1819
|
+
let derivedConsentAction;
|
|
1263
1820
|
logger.debug('Request parameters', {
|
|
1264
1821
|
type,
|
|
1265
1822
|
subjectId,
|
|
@@ -1268,6 +1825,64 @@ const postSubjectHandler = async (c)=>{
|
|
|
1268
1825
|
domain
|
|
1269
1826
|
});
|
|
1270
1827
|
try {
|
|
1828
|
+
if ('cookie_banner' === type) logger.warn('`cookie_banner` policy type is deprecated in 2.0 RC and will be removed in 2.0 GA. Use backend runtime `policyPacks` for banner behavior.');
|
|
1829
|
+
const request = c.req.raw ?? new Request('https://c15t.local/subjects');
|
|
1830
|
+
const acceptLanguage = request.headers.get('accept-language');
|
|
1831
|
+
const requestLanguage = parseLanguageFromHeader(acceptLanguage);
|
|
1832
|
+
const location = await getLocation(request, ctx);
|
|
1833
|
+
const resolvedJurisdiction = getJurisdiction(location, ctx);
|
|
1834
|
+
const legalDocumentConsent = isLegalDocumentType(type);
|
|
1835
|
+
const runtimeSnapshotVerification = legalDocumentConsent ? {
|
|
1836
|
+
valid: false,
|
|
1837
|
+
reason: 'missing'
|
|
1838
|
+
} : await verifyPolicySnapshotToken({
|
|
1839
|
+
token: input.policySnapshotToken,
|
|
1840
|
+
options: ctx.policySnapshot,
|
|
1841
|
+
tenantId: ctx.tenantId
|
|
1842
|
+
});
|
|
1843
|
+
const legalDocumentSnapshotVerification = legalDocumentConsent ? await verifyLegalDocumentSnapshotToken({
|
|
1844
|
+
token: input.documentSnapshotToken,
|
|
1845
|
+
options: ctx.legalDocumentSnapshot,
|
|
1846
|
+
tenantId: ctx.tenantId
|
|
1847
|
+
}) : {
|
|
1848
|
+
valid: false,
|
|
1849
|
+
reason: 'missing'
|
|
1850
|
+
};
|
|
1851
|
+
const hasValidSnapshot = runtimeSnapshotVerification.valid;
|
|
1852
|
+
const snapshotPayload = runtimeSnapshotVerification.valid ? runtimeSnapshotVerification.payload : null;
|
|
1853
|
+
const shouldRequireSnapshot = !legalDocumentConsent && !!ctx.policySnapshot?.signingKey && 'reject' === resolveSnapshotFailureMode(ctx);
|
|
1854
|
+
if (!hasValidSnapshot && shouldRequireSnapshot) throw buildSnapshotHttpException(runtimeSnapshotVerification.reason);
|
|
1855
|
+
const shouldRequireLegalDocumentSnapshot = legalDocumentConsent && !!ctx.legalDocumentSnapshot?.signingKey;
|
|
1856
|
+
if (shouldRequireLegalDocumentSnapshot && !legalDocumentSnapshotVerification.valid) throw buildLegalDocumentSnapshotHttpException(legalDocumentSnapshotVerification.reason);
|
|
1857
|
+
const resolvedPolicyDecision = hasValidSnapshot ? void 0 : legalDocumentConsent ? void 0 : await resolvePolicyDecision({
|
|
1858
|
+
policies: ctx.policyPacks,
|
|
1859
|
+
countryCode: location.countryCode,
|
|
1860
|
+
regionCode: location.regionCode,
|
|
1861
|
+
jurisdiction: resolvedJurisdiction,
|
|
1862
|
+
iabEnabled: ctx.iab?.enabled === true
|
|
1863
|
+
});
|
|
1864
|
+
const effectivePolicy = hasValidSnapshot && snapshotPayload ? {
|
|
1865
|
+
id: snapshotPayload.policyId,
|
|
1866
|
+
model: snapshotPayload.model,
|
|
1867
|
+
i18n: snapshotPayload.policyI18n,
|
|
1868
|
+
consent: {
|
|
1869
|
+
expiryDays: snapshotPayload.expiryDays,
|
|
1870
|
+
scopeMode: snapshotPayload.scopeMode,
|
|
1871
|
+
categories: snapshotPayload.categories,
|
|
1872
|
+
preselectedCategories: snapshotPayload.preselectedCategories,
|
|
1873
|
+
gpc: snapshotPayload.gpc
|
|
1874
|
+
},
|
|
1875
|
+
ui: {
|
|
1876
|
+
mode: snapshotPayload.uiMode,
|
|
1877
|
+
banner: snapshotPayload.bannerUi,
|
|
1878
|
+
dialog: snapshotPayload.dialogUi
|
|
1879
|
+
},
|
|
1880
|
+
proof: snapshotPayload.proofConfig
|
|
1881
|
+
} : resolvedPolicyDecision?.policy;
|
|
1882
|
+
const effectiveModel = effectivePolicy?.model ?? ('opt-in' === input.jurisdictionModel || 'opt-out' === input.jurisdictionModel || 'iab' === input.jurisdictionModel ? input.jurisdictionModel : void 0);
|
|
1883
|
+
if ('all' === rawConsentAction) derivedConsentAction = 'accept_all';
|
|
1884
|
+
else if ('necessary' === rawConsentAction) derivedConsentAction = 'opt-out' === effectiveModel ? 'opt_out' : 'reject_all';
|
|
1885
|
+
else if ('custom' === rawConsentAction) derivedConsentAction = 'custom';
|
|
1271
1886
|
const subject = await registry.findOrCreateSubject({
|
|
1272
1887
|
subjectId,
|
|
1273
1888
|
externalSubjectId,
|
|
@@ -1294,8 +1909,63 @@ const postSubjectHandler = async (c)=>{
|
|
|
1294
1909
|
});
|
|
1295
1910
|
let policyId;
|
|
1296
1911
|
let purposeIds = [];
|
|
1912
|
+
let appliedPreferences;
|
|
1297
1913
|
const inputPolicyId = 'policyId' in input ? input.policyId : void 0;
|
|
1298
|
-
|
|
1914
|
+
const inputPolicyHash = 'policyHash' in input ? input.policyHash : void 0;
|
|
1915
|
+
if (legalDocumentConsent && legalDocumentSnapshotVerification.valid) {
|
|
1916
|
+
if (legalDocumentSnapshotVerification.payload.type !== type) throw buildLegalDocumentSnapshotHttpException('invalid');
|
|
1917
|
+
const effectiveDate = new Date(legalDocumentSnapshotVerification.payload.effectiveDate);
|
|
1918
|
+
if (Number.isNaN(effectiveDate.getTime())) throw buildLegalDocumentSnapshotHttpException('invalid');
|
|
1919
|
+
const documentPolicy = await registry.findOrCreateLegalDocumentPolicy({
|
|
1920
|
+
type,
|
|
1921
|
+
version: legalDocumentSnapshotVerification.payload.version,
|
|
1922
|
+
hash: legalDocumentSnapshotVerification.payload.hash,
|
|
1923
|
+
effectiveDate
|
|
1924
|
+
});
|
|
1925
|
+
policyId = documentPolicy.id;
|
|
1926
|
+
} else if (legalDocumentConsent) {
|
|
1927
|
+
if (!ctx.legalDocumentSnapshot?.signingKey && !inputPolicyId && !inputPolicyHash) throw buildLegalDocumentProofHttpException('Legal document consent requires policyId or policyHash when snapshot verification is disabled');
|
|
1928
|
+
if (inputPolicyId) {
|
|
1929
|
+
policyId = inputPolicyId;
|
|
1930
|
+
const policy = await registry.findConsentPolicyById(inputPolicyId);
|
|
1931
|
+
if (!policy) throw new http_exception_namespaceObject.HTTPException(404, {
|
|
1932
|
+
message: 'Policy not found',
|
|
1933
|
+
cause: {
|
|
1934
|
+
code: 'POLICY_NOT_FOUND',
|
|
1935
|
+
policyId,
|
|
1936
|
+
type
|
|
1937
|
+
}
|
|
1938
|
+
});
|
|
1939
|
+
if (!policy.isActive) throw new http_exception_namespaceObject.HTTPException(400, {
|
|
1940
|
+
message: 'Policy is inactive',
|
|
1941
|
+
cause: {
|
|
1942
|
+
code: 'POLICY_INACTIVE',
|
|
1943
|
+
policyId,
|
|
1944
|
+
type
|
|
1945
|
+
}
|
|
1946
|
+
});
|
|
1947
|
+
} else if (inputPolicyHash) {
|
|
1948
|
+
const policy = await registry.findLegalDocumentPolicyByHash(type, inputPolicyHash);
|
|
1949
|
+
if (!policy) throw new http_exception_namespaceObject.HTTPException(404, {
|
|
1950
|
+
message: 'Policy not found',
|
|
1951
|
+
cause: {
|
|
1952
|
+
code: 'POLICY_NOT_FOUND',
|
|
1953
|
+
type,
|
|
1954
|
+
policyHash: inputPolicyHash
|
|
1955
|
+
}
|
|
1956
|
+
});
|
|
1957
|
+
if (!policy.isActive) throw new http_exception_namespaceObject.HTTPException(400, {
|
|
1958
|
+
message: 'Policy is inactive',
|
|
1959
|
+
cause: {
|
|
1960
|
+
code: 'POLICY_INACTIVE',
|
|
1961
|
+
policyId: policy.id,
|
|
1962
|
+
type,
|
|
1963
|
+
policyHash: inputPolicyHash
|
|
1964
|
+
}
|
|
1965
|
+
});
|
|
1966
|
+
policyId = policy.id;
|
|
1967
|
+
}
|
|
1968
|
+
} else if (inputPolicyId) {
|
|
1299
1969
|
policyId = inputPolicyId;
|
|
1300
1970
|
const policy = await registry.findConsentPolicyById(inputPolicyId);
|
|
1301
1971
|
if (!policy) throw new http_exception_namespaceObject.HTTPException(404, {
|
|
@@ -1326,20 +1996,73 @@ const postSubjectHandler = async (c)=>{
|
|
|
1326
1996
|
policyId = policy.id;
|
|
1327
1997
|
}
|
|
1328
1998
|
if (preferences) {
|
|
1329
|
-
const
|
|
1999
|
+
const allowedCategories = effectivePolicy?.consent?.categories;
|
|
2000
|
+
const effectiveScopeMode = effectivePolicy?.consent?.scopeMode ?? 'permissive';
|
|
2001
|
+
const hasWildcardCategoryScope = allowedCategories?.includes('*') === true;
|
|
2002
|
+
const appliedPreferenceEntries = Object.entries(preferences);
|
|
2003
|
+
let filteredAppliedPreferenceEntries = appliedPreferenceEntries;
|
|
2004
|
+
if (allowedCategories && allowedCategories.length > 0 && !hasWildcardCategoryScope) {
|
|
2005
|
+
const disallowed = appliedPreferenceEntries.map(([purpose])=>purpose).filter((purpose)=>!allowedCategories.includes(purpose));
|
|
2006
|
+
filteredAppliedPreferenceEntries = appliedPreferenceEntries.filter(([purpose])=>allowedCategories.includes(purpose));
|
|
2007
|
+
if (disallowed.length > 0 && 'strict' === effectiveScopeMode) throw new http_exception_namespaceObject.HTTPException(400, {
|
|
2008
|
+
message: 'Preferences include categories not allowed by policy',
|
|
2009
|
+
cause: {
|
|
2010
|
+
code: 'PURPOSE_NOT_ALLOWED',
|
|
2011
|
+
disallowed
|
|
2012
|
+
}
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
2015
|
+
appliedPreferences = Object.fromEntries(filteredAppliedPreferenceEntries);
|
|
2016
|
+
const filteredConsentedPurposeCodes = filteredAppliedPreferenceEntries.filter(([_, isConsented])=>isConsented).map(([purposeCode])=>purposeCode);
|
|
1330
2017
|
logger.debug('Consented purposes', {
|
|
1331
|
-
consentedPurposes
|
|
2018
|
+
consentedPurposes: filteredConsentedPurposeCodes
|
|
1332
2019
|
});
|
|
1333
|
-
const purposesRaw = await Promise.all(
|
|
2020
|
+
const purposesRaw = await Promise.all(filteredConsentedPurposeCodes.map((purposeCode)=>registry.findOrCreateConsentPurposeByCode(purposeCode)));
|
|
1334
2021
|
const purposes = purposesRaw.map((purpose)=>purpose?.id ?? null).filter((id)=>Boolean(id));
|
|
1335
2022
|
logger.debug('Filtered purposes', {
|
|
1336
2023
|
purposes
|
|
1337
2024
|
});
|
|
1338
2025
|
if (0 === purposes.length) logger.warn('No valid purpose IDs found after filtering. Using empty list.', {
|
|
1339
|
-
consentedPurposes
|
|
2026
|
+
consentedPurposes: filteredConsentedPurposeCodes
|
|
1340
2027
|
});
|
|
1341
2028
|
purposeIds = purposes;
|
|
1342
2029
|
}
|
|
2030
|
+
if (!policyId) throw new http_exception_namespaceObject.HTTPException(500, {
|
|
2031
|
+
message: 'Failed to resolve policy',
|
|
2032
|
+
cause: {
|
|
2033
|
+
code: 'POLICY_RESOLUTION_FAILED',
|
|
2034
|
+
type
|
|
2035
|
+
}
|
|
2036
|
+
});
|
|
2037
|
+
const expiryDays = effectivePolicy?.consent?.expiryDays;
|
|
2038
|
+
const validUntil = 'number' == typeof expiryDays && Number.isFinite(expiryDays) ? new Date(givenAt.getTime() + 86400000 * Math.max(0, expiryDays)) : void 0;
|
|
2039
|
+
const proofConfig = effectivePolicy?.proof;
|
|
2040
|
+
const shouldStoreIp = proofConfig?.storeIp ?? true;
|
|
2041
|
+
const shouldStoreUserAgent = proofConfig?.storeUserAgent ?? true;
|
|
2042
|
+
const shouldStoreLanguage = proofConfig?.storeLanguage ?? false;
|
|
2043
|
+
const effectiveLanguage = (snapshotPayload?.language && hasValidSnapshot ? snapshotPayload.language : requestLanguage) ?? void 0;
|
|
2044
|
+
const metadataWithPolicy = {
|
|
2045
|
+
...metadata ?? {},
|
|
2046
|
+
...shouldStoreLanguage && effectiveLanguage ? {
|
|
2047
|
+
policyLanguage: effectiveLanguage
|
|
2048
|
+
} : {}
|
|
2049
|
+
};
|
|
2050
|
+
const effectiveJurisdiction = hasValidSnapshot && snapshotPayload ? snapshotPayload.jurisdiction : resolvedJurisdiction;
|
|
2051
|
+
const decisionPayload = buildDecisionPayload({
|
|
2052
|
+
tenantId: ctx.tenantId,
|
|
2053
|
+
snapshot: hasValidSnapshot && snapshotPayload ? {
|
|
2054
|
+
valid: true,
|
|
2055
|
+
payload: snapshotPayload
|
|
2056
|
+
} : null,
|
|
2057
|
+
decision: resolvedPolicyDecision,
|
|
2058
|
+
location: {
|
|
2059
|
+
countryCode: location.countryCode,
|
|
2060
|
+
regionCode: location.regionCode
|
|
2061
|
+
},
|
|
2062
|
+
jurisdiction: resolvedJurisdiction,
|
|
2063
|
+
language: effectiveLanguage,
|
|
2064
|
+
proofConfig
|
|
2065
|
+
});
|
|
1343
2066
|
const existingConsent = await db.findFirst('consent', {
|
|
1344
2067
|
where: (b)=>b.and(b('subjectId', '=', subject.id), b('domainId', '=', domainRecord.id), b('policyId', '=', policyId), b('givenAt', '=', givenAt))
|
|
1345
2068
|
});
|
|
@@ -1354,6 +2077,7 @@ const postSubjectHandler = async (c)=>{
|
|
|
1354
2077
|
domain: domainRecord.name,
|
|
1355
2078
|
type,
|
|
1356
2079
|
metadata,
|
|
2080
|
+
appliedPreferences,
|
|
1357
2081
|
uiSource: input.uiSource,
|
|
1358
2082
|
givenAt: existingConsent.givenAt
|
|
1359
2083
|
});
|
|
@@ -1365,24 +2089,64 @@ const postSubjectHandler = async (c)=>{
|
|
|
1365
2089
|
policyId,
|
|
1366
2090
|
purposeIds
|
|
1367
2091
|
});
|
|
2092
|
+
const runtimePolicyDecision = decisionPayload ? await tx.findFirst('runtimePolicyDecision', {
|
|
2093
|
+
where: (b)=>b('dedupeKey', '=', decisionPayload.dedupeKey)
|
|
2094
|
+
}) ?? await tx.create('runtimePolicyDecision', {
|
|
2095
|
+
id: `rpd_${crypto.randomUUID().replaceAll('-', '')}`,
|
|
2096
|
+
tenantId: decisionPayload.tenantId,
|
|
2097
|
+
policyId: decisionPayload.policyId,
|
|
2098
|
+
fingerprint: decisionPayload.fingerprint,
|
|
2099
|
+
matchedBy: decisionPayload.matchedBy,
|
|
2100
|
+
countryCode: decisionPayload.countryCode,
|
|
2101
|
+
regionCode: decisionPayload.regionCode,
|
|
2102
|
+
jurisdiction: decisionPayload.jurisdiction,
|
|
2103
|
+
language: decisionPayload.language,
|
|
2104
|
+
model: decisionPayload.model,
|
|
2105
|
+
policyI18n: decisionPayload.policyI18n ? {
|
|
2106
|
+
json: decisionPayload.policyI18n
|
|
2107
|
+
} : void 0,
|
|
2108
|
+
uiMode: decisionPayload.uiMode,
|
|
2109
|
+
bannerUi: decisionPayload.bannerUi ? {
|
|
2110
|
+
json: decisionPayload.bannerUi
|
|
2111
|
+
} : void 0,
|
|
2112
|
+
dialogUi: decisionPayload.dialogUi ? {
|
|
2113
|
+
json: decisionPayload.dialogUi
|
|
2114
|
+
} : void 0,
|
|
2115
|
+
categories: decisionPayload.categories ? {
|
|
2116
|
+
json: decisionPayload.categories
|
|
2117
|
+
} : void 0,
|
|
2118
|
+
preselectedCategories: decisionPayload.preselectedCategories ? {
|
|
2119
|
+
json: decisionPayload.preselectedCategories
|
|
2120
|
+
} : void 0,
|
|
2121
|
+
proofConfig: decisionPayload.proofConfig ? {
|
|
2122
|
+
json: decisionPayload.proofConfig
|
|
2123
|
+
} : void 0,
|
|
2124
|
+
dedupeKey: decisionPayload.dedupeKey
|
|
2125
|
+
}).catch(async ()=>tx.findFirst('runtimePolicyDecision', {
|
|
2126
|
+
where: (b)=>b('dedupeKey', '=', decisionPayload.dedupeKey)
|
|
2127
|
+
})) : void 0;
|
|
1368
2128
|
const consentRecord = await tx.create('consent', {
|
|
1369
|
-
id: await
|
|
2129
|
+
id: await utils_generateUniqueId(tx, 'consent', ctx),
|
|
1370
2130
|
subjectId: subject.id,
|
|
1371
2131
|
domainId: domainRecord.id,
|
|
1372
2132
|
policyId,
|
|
1373
2133
|
purposeIds: {
|
|
1374
2134
|
json: purposeIds
|
|
1375
2135
|
},
|
|
1376
|
-
metadata:
|
|
1377
|
-
json:
|
|
2136
|
+
metadata: Object.keys(metadataWithPolicy).length > 0 ? {
|
|
2137
|
+
json: metadataWithPolicy
|
|
1378
2138
|
} : void 0,
|
|
1379
|
-
ipAddress: ctx.ipAddress,
|
|
1380
|
-
userAgent: ctx.userAgent,
|
|
1381
|
-
jurisdiction:
|
|
1382
|
-
jurisdictionModel:
|
|
2139
|
+
ipAddress: shouldStoreIp ? ctx.ipAddress : null,
|
|
2140
|
+
userAgent: shouldStoreUserAgent ? ctx.userAgent : null,
|
|
2141
|
+
jurisdiction: effectiveJurisdiction,
|
|
2142
|
+
jurisdictionModel: effectiveModel,
|
|
1383
2143
|
tcString: input.tcString,
|
|
1384
2144
|
uiSource: input.uiSource,
|
|
1385
|
-
|
|
2145
|
+
consentAction: derivedConsentAction,
|
|
2146
|
+
givenAt,
|
|
2147
|
+
validUntil,
|
|
2148
|
+
runtimePolicyDecisionId: runtimePolicyDecision?.id,
|
|
2149
|
+
runtimePolicySource: decisionPayload?.source
|
|
1386
2150
|
});
|
|
1387
2151
|
logger.debug('Created consent', {
|
|
1388
2152
|
consentRecord: consentRecord.id
|
|
@@ -1399,9 +2163,9 @@ const postSubjectHandler = async (c)=>{
|
|
|
1399
2163
|
consent: consentRecord
|
|
1400
2164
|
};
|
|
1401
2165
|
});
|
|
1402
|
-
const metrics =
|
|
2166
|
+
const metrics = metrics_getMetrics();
|
|
1403
2167
|
if (metrics) {
|
|
1404
|
-
const jurisdiction =
|
|
2168
|
+
const jurisdiction = effectiveJurisdiction;
|
|
1405
2169
|
metrics.recordConsentCreated({
|
|
1406
2170
|
type,
|
|
1407
2171
|
jurisdiction
|
|
@@ -1423,15 +2187,22 @@ const postSubjectHandler = async (c)=>{
|
|
|
1423
2187
|
domain: domainRecord.name,
|
|
1424
2188
|
type,
|
|
1425
2189
|
metadata,
|
|
2190
|
+
appliedPreferences,
|
|
1426
2191
|
uiSource: input.uiSource,
|
|
1427
2192
|
givenAt: result.consent.givenAt
|
|
1428
2193
|
});
|
|
1429
2194
|
} catch (error) {
|
|
1430
2195
|
logger.error('Error in POST /subjects handler', {
|
|
1431
|
-
error:
|
|
2196
|
+
error: extract_error_message_extractErrorMessage(error),
|
|
1432
2197
|
errorType: error instanceof Error ? error.constructor.name : typeof error
|
|
1433
2198
|
});
|
|
1434
2199
|
if (error instanceof http_exception_namespaceObject.HTTPException) throw error;
|
|
2200
|
+
if (error instanceof consent_policy_LegalDocumentPolicyConflictError) throw new http_exception_namespaceObject.HTTPException(409, {
|
|
2201
|
+
message: error.message,
|
|
2202
|
+
cause: {
|
|
2203
|
+
code: 'LEGAL_DOCUMENT_RELEASE_CONFLICT'
|
|
2204
|
+
}
|
|
2205
|
+
});
|
|
1435
2206
|
throw new http_exception_namespaceObject.HTTPException(500, {
|
|
1436
2207
|
message: 'Internal server error',
|
|
1437
2208
|
cause: {
|
|
@@ -1444,11 +2215,7 @@ const createSubjectRoutes = ()=>{
|
|
|
1444
2215
|
const app = new external_hono_namespaceObject.Hono();
|
|
1445
2216
|
app.get('/:id', (0, external_hono_openapi_namespaceObject.describeRoute)({
|
|
1446
2217
|
summary: 'Get subject consent status',
|
|
1447
|
-
description:
|
|
1448
|
-
|
|
1449
|
-
**Query:** \`type\` – Filter by consent type(s), comma-separated (e.g. \`privacy_policy,cookie_banner\`).
|
|
1450
|
-
|
|
1451
|
-
**Response:** \`subject\`, \`consents\` (matching filter), \`isValid\` (valid consent for requested type(s)).`,
|
|
2218
|
+
description: "Returns the subject's consent status for this device. Use to check if the subject has valid consent for given policy types.\n\n**Query:** `type` – Filter by consent type(s), comma-separated (e.g. `privacy_policy,cookie_banner`).\n\n**Response:** `subject`, `consents` (matching filter), `isValid` (valid consent for requested type(s)).",
|
|
1452
2219
|
tags: [
|
|
1453
2220
|
'Subject',
|
|
1454
2221
|
'Consent'
|
|
@@ -1469,12 +2236,7 @@ const createSubjectRoutes = ()=>{
|
|
|
1469
2236
|
}), (0, external_hono_openapi_namespaceObject.validator)('param', schema_namespaceObject.getSubjectInputSchema), getSubjectHandler);
|
|
1470
2237
|
app.post('/', (0, external_hono_openapi_namespaceObject.describeRoute)({
|
|
1471
2238
|
summary: 'Record consent for a subject',
|
|
1472
|
-
description:
|
|
1473
|
-
|
|
1474
|
-
**Request body by \`type\`:**
|
|
1475
|
-
- \`cookie_banner\` – Requires \`preferences\` object
|
|
1476
|
-
- \`privacy_policy\`, \`dpa\`, \`terms_and_conditions\` – Optional \`policyId\`
|
|
1477
|
-
- \`marketing_communications\`, \`age_verification\`, \`other\` – Optional \`preferences\``,
|
|
2239
|
+
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`",
|
|
1478
2240
|
tags: [
|
|
1479
2241
|
'Subject',
|
|
1480
2242
|
'Consent'
|