@c15t/backend 2.0.0-rc.1 → 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 +51 -37
- package/.turbo/turbo-build.log +0 -49
- package/CHANGELOG.md +0 -99
- 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/core.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __webpack_modules__ = {
|
|
3
|
-
"./src/db/schema/index.ts" (
|
|
3
|
+
"./src/db/schema/index.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
4
4
|
__webpack_require__.d(__webpack_exports__, {
|
|
5
5
|
DB: ()=>DB
|
|
6
6
|
});
|
|
@@ -79,7 +79,6 @@ var __webpack_modules__ = {
|
|
|
79
79
|
});
|
|
80
80
|
const subjectTable = (0, schema_namespaceObject.table)('subject', {
|
|
81
81
|
id: (0, schema_namespaceObject.idColumn)('id', 'varchar(255)'),
|
|
82
|
-
isIdentified: (0, schema_namespaceObject.column)('isIdentified', 'bool').defaultTo$(()=>false),
|
|
83
82
|
externalId: (0, schema_namespaceObject.column)('externalId', 'string').nullable(),
|
|
84
83
|
identityProvider: (0, schema_namespaceObject.column)('identityProvider', 'string').nullable(),
|
|
85
84
|
lastIpAddress: (0, schema_namespaceObject.column)('lastIpAddress', 'string').nullable(),
|
|
@@ -172,12 +171,16 @@ var __webpack_modules__ = {
|
|
|
172
171
|
jurisdictionModel: (0, schema_namespaceObject.column)('jurisdictionModel', 'string').nullable(),
|
|
173
172
|
tcString: (0, schema_namespaceObject.column)('tcString', 'string').nullable(),
|
|
174
173
|
uiSource: (0, schema_namespaceObject.column)('uiSource', 'string').nullable(),
|
|
174
|
+
consentAction: (0, schema_namespaceObject.column)('consentAction', 'string').nullable(),
|
|
175
|
+
runtimePolicyDecisionId: (0, schema_namespaceObject.column)('runtimePolicyDecisionId', 'string').nullable(),
|
|
176
|
+
runtimePolicySource: (0, schema_namespaceObject.column)('runtimePolicySource', 'string').nullable(),
|
|
175
177
|
tenantId: (0, schema_namespaceObject.column)('tenantId', 'string').nullable()
|
|
176
178
|
});
|
|
177
179
|
const consent_policy_consentPolicyTable = (0, schema_namespaceObject.table)('consentPolicy', {
|
|
178
180
|
id: (0, schema_namespaceObject.idColumn)('id', 'varchar(255)'),
|
|
179
181
|
version: (0, schema_namespaceObject.column)('version', 'string'),
|
|
180
182
|
type: (0, schema_namespaceObject.column)('type', 'string'),
|
|
183
|
+
hash: (0, schema_namespaceObject.column)('hash', 'string').nullable(),
|
|
181
184
|
effectiveDate: (0, schema_namespaceObject.column)('effectiveDate', 'timestamp'),
|
|
182
185
|
isActive: (0, schema_namespaceObject.column)('isActive', 'bool').defaultTo$(()=>true),
|
|
183
186
|
createdAt: (0, schema_namespaceObject.column)('createdAt', 'timestamp').defaultTo$('now'),
|
|
@@ -197,9 +200,29 @@ var __webpack_modules__ = {
|
|
|
197
200
|
updatedAt: (0, schema_namespaceObject.column)('updatedAt', 'timestamp').defaultTo$('now'),
|
|
198
201
|
tenantId: (0, schema_namespaceObject.column)('tenantId', 'string').nullable()
|
|
199
202
|
});
|
|
203
|
+
const runtimePolicyDecisionTable = (0, schema_namespaceObject.table)('runtimePolicyDecision', {
|
|
204
|
+
id: (0, schema_namespaceObject.idColumn)('id', 'varchar(255)'),
|
|
205
|
+
tenantId: (0, schema_namespaceObject.column)('tenantId', 'string').nullable(),
|
|
206
|
+
policyId: (0, schema_namespaceObject.column)('policyId', 'string'),
|
|
207
|
+
fingerprint: (0, schema_namespaceObject.column)('fingerprint', 'string'),
|
|
208
|
+
matchedBy: (0, schema_namespaceObject.column)('matchedBy', 'string'),
|
|
209
|
+
countryCode: (0, schema_namespaceObject.column)('countryCode', 'string').nullable(),
|
|
210
|
+
regionCode: (0, schema_namespaceObject.column)('regionCode', 'string').nullable(),
|
|
211
|
+
jurisdiction: (0, schema_namespaceObject.column)('jurisdiction', 'string'),
|
|
212
|
+
language: (0, schema_namespaceObject.column)('language', 'string').nullable(),
|
|
213
|
+
model: (0, schema_namespaceObject.column)('model', 'string'),
|
|
214
|
+
policyI18n: (0, schema_namespaceObject.column)('policyI18n', 'json').nullable(),
|
|
215
|
+
uiMode: (0, schema_namespaceObject.column)('uiMode', 'string').nullable(),
|
|
216
|
+
bannerUi: (0, schema_namespaceObject.column)('bannerUi', 'json').nullable(),
|
|
217
|
+
dialogUi: (0, schema_namespaceObject.column)('dialogUi', 'json').nullable(),
|
|
218
|
+
categories: (0, schema_namespaceObject.column)('categories', 'json').nullable(),
|
|
219
|
+
preselectedCategories: (0, schema_namespaceObject.column)('preselectedCategories', 'json').nullable(),
|
|
220
|
+
proofConfig: (0, schema_namespaceObject.column)('proofConfig', 'json').nullable(),
|
|
221
|
+
dedupeKey: (0, schema_namespaceObject.column)('dedupeKey', 'string').unique(),
|
|
222
|
+
createdAt: (0, schema_namespaceObject.column)('createdAt', 'timestamp').defaultTo$('now')
|
|
223
|
+
});
|
|
200
224
|
const subject_subjectTable = (0, schema_namespaceObject.table)('subject', {
|
|
201
225
|
id: (0, schema_namespaceObject.idColumn)('id', 'varchar(255)'),
|
|
202
|
-
isIdentified: (0, schema_namespaceObject.column)('isIdentified', 'bool').defaultTo$(()=>false),
|
|
203
226
|
externalId: (0, schema_namespaceObject.column)('externalId', 'string').nullable(),
|
|
204
227
|
identityProvider: (0, schema_namespaceObject.column)('identityProvider', 'string').nullable(),
|
|
205
228
|
createdAt: (0, schema_namespaceObject.column)('createdAt', 'timestamp').defaultTo$('now'),
|
|
@@ -212,6 +235,7 @@ var __webpack_modules__ = {
|
|
|
212
235
|
subject: subject_subjectTable,
|
|
213
236
|
domain: domain_domainTable,
|
|
214
237
|
consentPolicy: consent_policy_consentPolicyTable,
|
|
238
|
+
runtimePolicyDecision: runtimePolicyDecisionTable,
|
|
215
239
|
consentPurpose: consent_purpose_consentPurposeTable,
|
|
216
240
|
consent: consent_consentTable,
|
|
217
241
|
auditLog: audit_log_auditLogTable
|
|
@@ -227,6 +251,9 @@ var __webpack_modules__ = {
|
|
|
227
251
|
consentPolicy: ({ many })=>({
|
|
228
252
|
consents: many('consent')
|
|
229
253
|
}),
|
|
254
|
+
runtimePolicyDecision: ({ many })=>({
|
|
255
|
+
consents: many('consent')
|
|
256
|
+
}),
|
|
230
257
|
consentPurpose: ()=>({}),
|
|
231
258
|
consent: ({ one })=>({
|
|
232
259
|
subject: one('subject', [
|
|
@@ -240,6 +267,10 @@ var __webpack_modules__ = {
|
|
|
240
267
|
policy: one('consentPolicy', [
|
|
241
268
|
'policyId',
|
|
242
269
|
'id'
|
|
270
|
+
]).foreignKey(),
|
|
271
|
+
runtimePolicyDecision: one('runtimePolicyDecision', [
|
|
272
|
+
'runtimePolicyDecisionId',
|
|
273
|
+
'id'
|
|
243
274
|
]).foreignKey()
|
|
244
275
|
}),
|
|
245
276
|
auditLog: ({ one })=>({
|
|
@@ -264,7 +295,7 @@ var __webpack_modules__ = {
|
|
|
264
295
|
]
|
|
265
296
|
});
|
|
266
297
|
},
|
|
267
|
-
"./src/define-config.ts" (
|
|
298
|
+
"./src/define-config.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
268
299
|
__webpack_require__.d(__webpack_exports__, {
|
|
269
300
|
defineConfig: ()=>defineConfig
|
|
270
301
|
});
|
|
@@ -306,7 +337,7 @@ function __webpack_require__(moduleId) {
|
|
|
306
337
|
})();
|
|
307
338
|
(()=>{
|
|
308
339
|
__webpack_require__.r = (exports1)=>{
|
|
309
|
-
if (
|
|
340
|
+
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
310
341
|
value: 'Module'
|
|
311
342
|
});
|
|
312
343
|
Object.defineProperty(exports1, '__esModule', {
|
|
@@ -320,7 +351,15 @@ var __webpack_exports__ = {};
|
|
|
320
351
|
__webpack_require__.d(__webpack_exports__, {
|
|
321
352
|
version: ()=>version_version,
|
|
322
353
|
c15tInstance: ()=>c15tInstance,
|
|
323
|
-
|
|
354
|
+
EEA_COUNTRY_CODES: ()=>types_namespaceObject.EEA_COUNTRY_CODES,
|
|
355
|
+
EU_COUNTRY_CODES: ()=>types_namespaceObject.EU_COUNTRY_CODES,
|
|
356
|
+
UK_COUNTRY_CODES: ()=>types_namespaceObject.UK_COUNTRY_CODES,
|
|
357
|
+
policyMatchers: ()=>types_namespaceObject.policyMatchers,
|
|
358
|
+
policyBuilder: ()=>policyBuilder,
|
|
359
|
+
policyPackPresets: ()=>schema_.policyPackPresets,
|
|
360
|
+
inspectPolicies: ()=>inspectPolicies,
|
|
361
|
+
defineConfig: ()=>define_config.defineConfig,
|
|
362
|
+
POLICY_MATCH_DATASET_VERSION: ()=>types_namespaceObject.POLICY_MATCH_DATASET_VERSION
|
|
324
363
|
});
|
|
325
364
|
const logger_namespaceObject = require("@c15t/logger");
|
|
326
365
|
const api_namespaceObject = require("@opentelemetry/api");
|
|
@@ -455,10 +494,10 @@ var __webpack_exports__ = {};
|
|
|
455
494
|
};
|
|
456
495
|
}
|
|
457
496
|
const createOpenAPIConfig = (options)=>({
|
|
458
|
-
enabled: options.
|
|
497
|
+
enabled: options.openapi?.enabled !== false,
|
|
459
498
|
specPath: '/spec.json',
|
|
460
499
|
docsPath: '/docs',
|
|
461
|
-
...options.
|
|
500
|
+
...options.openapi || {}
|
|
462
501
|
});
|
|
463
502
|
const DEFAULT_IP_HEADERS = [
|
|
464
503
|
'x-client-ip',
|
|
@@ -553,7 +592,7 @@ var __webpack_exports__ = {};
|
|
|
553
592
|
return ip;
|
|
554
593
|
}
|
|
555
594
|
function getIpAddress(req, options) {
|
|
556
|
-
const ipAddressConfig = options.
|
|
595
|
+
const ipAddressConfig = options.ipAddress;
|
|
557
596
|
if (ipAddressConfig?.tracking === false) return null;
|
|
558
597
|
const ipHeaders = ipAddressConfig?.ipAddressHeaders || DEFAULT_IP_HEADERS;
|
|
559
598
|
const headers = req instanceof Request ? req.headers : req;
|
|
@@ -569,7 +608,137 @@ var __webpack_exports__ = {};
|
|
|
569
608
|
}
|
|
570
609
|
return null;
|
|
571
610
|
}
|
|
572
|
-
const
|
|
611
|
+
const types_namespaceObject = require("@c15t/schema/types");
|
|
612
|
+
const translations_namespaceObject = require("@c15t/translations");
|
|
613
|
+
const all_namespaceObject = require("@c15t/translations/all");
|
|
614
|
+
const DEFAULT_PROFILE = 'default';
|
|
615
|
+
const warnedKeys = new Set();
|
|
616
|
+
function isSupportedBaseLanguage(lang) {
|
|
617
|
+
return lang in all_namespaceObject.baseTranslations;
|
|
618
|
+
}
|
|
619
|
+
function warnOnce(logger, key, message, metadata) {
|
|
620
|
+
if (!logger || warnedKeys.has(key)) return;
|
|
621
|
+
warnedKeys.add(key);
|
|
622
|
+
logger.warn(message, metadata);
|
|
623
|
+
}
|
|
624
|
+
function normalizeLanguage(value) {
|
|
625
|
+
if (!value) return;
|
|
626
|
+
const normalized = value.split(',')[0]?.split(';')[0]?.trim().toLowerCase();
|
|
627
|
+
if (!normalized) return;
|
|
628
|
+
return normalized.split('-')[0] ?? void 0;
|
|
629
|
+
}
|
|
630
|
+
function normalizeProfiles(params) {
|
|
631
|
+
const profiles = params.i18n?.messages;
|
|
632
|
+
const legacy = params.customTranslations;
|
|
633
|
+
if (profiles && Object.keys(profiles).length > 0) {
|
|
634
|
+
if (legacy && Object.keys(legacy).length > 0) warnOnce(params.logger, 'i18n.customTranslations.ignored', '`customTranslations` is deprecated and ignored when `i18n.messages` is configured.');
|
|
635
|
+
return profiles;
|
|
636
|
+
}
|
|
637
|
+
if (legacy && Object.keys(legacy).length > 0) {
|
|
638
|
+
warnOnce(params.logger, 'i18n.customTranslations.deprecated', '`customTranslations` is deprecated. Use `i18n.messages` instead.');
|
|
639
|
+
return {
|
|
640
|
+
[DEFAULT_PROFILE]: {
|
|
641
|
+
translations: legacy
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
return {};
|
|
646
|
+
}
|
|
647
|
+
function buildCandidates(input) {
|
|
648
|
+
const raw = [
|
|
649
|
+
{
|
|
650
|
+
language: input.language,
|
|
651
|
+
reason: 'profile_language'
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
language: input.fallbackLanguage,
|
|
655
|
+
reason: 'profile_fallback'
|
|
656
|
+
}
|
|
657
|
+
];
|
|
658
|
+
const dedupe = new Set();
|
|
659
|
+
return raw.filter((candidate)=>{
|
|
660
|
+
const key = candidate.language;
|
|
661
|
+
if (dedupe.has(key)) return false;
|
|
662
|
+
dedupe.add(key);
|
|
663
|
+
return true;
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
function getProfileLanguages(profiles, profile) {
|
|
667
|
+
return Object.keys(profiles[profile]?.translations ?? {}).sort();
|
|
668
|
+
}
|
|
669
|
+
function getSelectableLanguages(input) {
|
|
670
|
+
return getProfileLanguages(input.profiles, input.profile);
|
|
671
|
+
}
|
|
672
|
+
function resolveFallbackLanguage(input) {
|
|
673
|
+
const configuredFallbackLanguage = normalizeLanguage(input.profile?.fallbackLanguage) ?? 'en';
|
|
674
|
+
const profileLanguages = Object.keys(input.profile?.translations ?? {}).sort();
|
|
675
|
+
if (profileLanguages.includes(configuredFallbackLanguage)) return configuredFallbackLanguage;
|
|
676
|
+
if (profileLanguages.includes('en')) return 'en';
|
|
677
|
+
return profileLanguages[0] ?? configuredFallbackLanguage;
|
|
678
|
+
}
|
|
679
|
+
function resolveActiveProfile(input) {
|
|
680
|
+
const requestedProfile = input.policyProfile ?? input.defaultProfile;
|
|
681
|
+
if (input.profiles[requestedProfile]) return requestedProfile;
|
|
682
|
+
if (input.policyProfile) warnOnce(input.logger, `i18n.profile.missing:${requestedProfile}`, `Policy i18n profile '${requestedProfile}' does not exist. Falling back to default profile '${input.defaultProfile}'.`);
|
|
683
|
+
return input.defaultProfile;
|
|
684
|
+
}
|
|
685
|
+
function validateMessages(options) {
|
|
686
|
+
return (0, types_namespaceObject.validatePolicyI18nConfig)({
|
|
687
|
+
customTranslations: options.customTranslations,
|
|
688
|
+
i18n: options.i18n,
|
|
689
|
+
policies: options.policies
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
function translations_getTranslationsData(acceptLanguage, customTranslations, options) {
|
|
693
|
+
const profiles = normalizeProfiles({
|
|
694
|
+
customTranslations,
|
|
695
|
+
i18n: options?.i18n,
|
|
696
|
+
logger: options?.logger
|
|
697
|
+
});
|
|
698
|
+
const defaultProfile = options?.i18n?.defaultProfile ?? DEFAULT_PROFILE;
|
|
699
|
+
const profile = resolveActiveProfile({
|
|
700
|
+
profiles,
|
|
701
|
+
defaultProfile,
|
|
702
|
+
policyProfile: options?.policyI18n?.messageProfile,
|
|
703
|
+
logger: options?.logger
|
|
704
|
+
});
|
|
705
|
+
const configuredLanguages = Object.keys(profiles).length > 0 ? getSelectableLanguages({
|
|
706
|
+
profiles,
|
|
707
|
+
profile
|
|
708
|
+
}) : Object.keys(all_namespaceObject.baseTranslations);
|
|
709
|
+
const fallbackLanguage = Object.keys(profiles).length > 0 ? resolveFallbackLanguage({
|
|
710
|
+
profile: profiles[profile]
|
|
711
|
+
}) : 'en';
|
|
712
|
+
const policyLanguage = normalizeLanguage(options?.policyI18n?.language);
|
|
713
|
+
const requestedLanguage = policyLanguage ?? (0, translations_namespaceObject.selectLanguage)(configuredLanguages, {
|
|
714
|
+
header: acceptLanguage,
|
|
715
|
+
fallback: fallbackLanguage
|
|
716
|
+
});
|
|
717
|
+
const candidates = buildCandidates({
|
|
718
|
+
language: requestedLanguage,
|
|
719
|
+
fallbackLanguage
|
|
720
|
+
});
|
|
721
|
+
const selectedCandidate = candidates.find((candidate)=>!!profiles[profile]?.translations[candidate.language]);
|
|
722
|
+
if (selectedCandidate && 'profile_language' !== selectedCandidate.reason) warnOnce(options?.logger, `i18n.fallback:${profile}:${requestedLanguage}:${selectedCandidate.language}`, `Policy translation fallback used (${selectedCandidate.reason}).`, {
|
|
723
|
+
requestedProfile: profile,
|
|
724
|
+
requestedLanguage,
|
|
725
|
+
resolvedProfile: profile,
|
|
726
|
+
resolvedLanguage: selectedCandidate.language
|
|
727
|
+
});
|
|
728
|
+
let language = selectedCandidate?.language ?? requestedLanguage;
|
|
729
|
+
if (!selectedCandidate && !isSupportedBaseLanguage(language)) {
|
|
730
|
+
warnOnce(options?.logger, `i18n.base-fallback:${language}`, `No translation found for '${language}'. Falling back to base English translations.`);
|
|
731
|
+
language = 'en';
|
|
732
|
+
}
|
|
733
|
+
const base = isSupportedBaseLanguage(language) ? all_namespaceObject.baseTranslations[language] : all_namespaceObject.baseTranslations.en;
|
|
734
|
+
const custom = selectedCandidate ? profiles[profile]?.translations[selectedCandidate.language] : void 0;
|
|
735
|
+
const translations = custom ? (0, translations_namespaceObject.deepMergeTranslations)(base, custom) : base;
|
|
736
|
+
return {
|
|
737
|
+
translations: translations,
|
|
738
|
+
language
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
const version_version = '2.0.0-rc.10';
|
|
573
742
|
function extractErrorMessage(error) {
|
|
574
743
|
if (error instanceof AggregateError && error.errors?.length > 0) {
|
|
575
744
|
const inner = error.errors.map((e)=>e instanceof Error ? e.message : String(e)).join('; ');
|
|
@@ -598,18 +767,18 @@ var __webpack_exports__ = {};
|
|
|
598
767
|
return config;
|
|
599
768
|
}
|
|
600
769
|
function isTelemetryEnabled(options) {
|
|
601
|
-
if (options) return options.
|
|
770
|
+
if (options) return options.telemetry?.enabled === true;
|
|
602
771
|
return cachedConfig?.enabled === true;
|
|
603
772
|
}
|
|
604
773
|
const getTracer = (options)=>{
|
|
605
774
|
if (!isTelemetryEnabled(options)) return api_namespaceObject.trace.getTracer('c15t-noop');
|
|
606
|
-
const tracer = options?.
|
|
775
|
+
const tracer = options?.telemetry?.tracer ?? cachedConfig?.tracer;
|
|
607
776
|
if (tracer) return tracer;
|
|
608
777
|
return api_namespaceObject.trace.getTracer(options?.appName ?? 'c15t');
|
|
609
778
|
};
|
|
610
779
|
const getMeter = (options)=>{
|
|
611
780
|
if (!isTelemetryEnabled(options)) return api_namespaceObject.metrics.getMeter('c15t-noop');
|
|
612
|
-
const meter = options?.
|
|
781
|
+
const meter = options?.telemetry?.meter ?? cachedConfig?.meter;
|
|
613
782
|
if (meter) return meter;
|
|
614
783
|
return api_namespaceObject.metrics.getMeter(options?.appName ?? 'c15t');
|
|
615
784
|
};
|
|
@@ -619,7 +788,7 @@ var __webpack_exports__ = {};
|
|
|
619
788
|
const createRequestSpan = (method, path, options)=>{
|
|
620
789
|
if (!isTelemetryEnabled(options)) return null;
|
|
621
790
|
const tracer = getTracer(options);
|
|
622
|
-
const defaultAttrs = options?.
|
|
791
|
+
const defaultAttrs = options?.telemetry?.defaultAttributes || getDefaultAttributes();
|
|
623
792
|
const span = tracer.startSpan(`${method} ${path}`, {
|
|
624
793
|
attributes: {
|
|
625
794
|
'http.method': method,
|
|
@@ -661,7 +830,7 @@ var __webpack_exports__ = {};
|
|
|
661
830
|
}
|
|
662
831
|
}
|
|
663
832
|
function resolveDefaultAttributes(options) {
|
|
664
|
-
return options?.
|
|
833
|
+
return options?.telemetry?.defaultAttributes || getDefaultAttributes();
|
|
665
834
|
}
|
|
666
835
|
async function withDatabaseSpan(attributes, operation, options) {
|
|
667
836
|
if (!isTelemetryEnabled(options)) return operation();
|
|
@@ -894,6 +1063,7 @@ var __webpack_exports__ = {};
|
|
|
894
1063
|
consentPolicy: 'pol',
|
|
895
1064
|
consentPurpose: 'pur',
|
|
896
1065
|
domain: 'dom',
|
|
1066
|
+
runtimePolicyDecision: 'rpd',
|
|
897
1067
|
subject: 'sub'
|
|
898
1068
|
};
|
|
899
1069
|
const b58 = external_base_x_default()('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
|
|
@@ -963,6 +1133,23 @@ var __webpack_exports__ = {};
|
|
|
963
1133
|
throw error;
|
|
964
1134
|
}
|
|
965
1135
|
}
|
|
1136
|
+
class LegalDocumentPolicyConflictError extends Error {
|
|
1137
|
+
constructor(message){
|
|
1138
|
+
super(message);
|
|
1139
|
+
this.name = 'LegalDocumentPolicyConflictError';
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
async function buildLegalDocumentPolicyId(input) {
|
|
1143
|
+
const digest = await (0, types_namespaceObject.hashSha256Hex)([
|
|
1144
|
+
input.tenantId ?? 'default',
|
|
1145
|
+
input.type,
|
|
1146
|
+
input.hash
|
|
1147
|
+
].join('|'));
|
|
1148
|
+
return `pol_${digest}`;
|
|
1149
|
+
}
|
|
1150
|
+
function hasLegalDocumentPolicyConflict(policy, input) {
|
|
1151
|
+
return policy.version !== input.version || policy.hash !== input.hash || policy.effectiveDate.getTime() !== input.effectiveDate.getTime();
|
|
1152
|
+
}
|
|
966
1153
|
function policyRegistry({ db, ctx }) {
|
|
967
1154
|
const { logger } = ctx;
|
|
968
1155
|
return {
|
|
@@ -991,6 +1178,176 @@ var __webpack_exports__ = {};
|
|
|
991
1178
|
throw error;
|
|
992
1179
|
}
|
|
993
1180
|
},
|
|
1181
|
+
findLatestPolicyByType: async (type)=>{
|
|
1182
|
+
const start = Date.now();
|
|
1183
|
+
try {
|
|
1184
|
+
const result = await withDatabaseSpan({
|
|
1185
|
+
operation: 'findLatest',
|
|
1186
|
+
entity: 'consentPolicy'
|
|
1187
|
+
}, async ()=>db.findFirst('consentPolicy', {
|
|
1188
|
+
where: (b)=>b.and(b('isActive', '=', true), b('type', '=', type)),
|
|
1189
|
+
orderBy: [
|
|
1190
|
+
'effectiveDate',
|
|
1191
|
+
'desc'
|
|
1192
|
+
]
|
|
1193
|
+
}));
|
|
1194
|
+
getMetrics()?.recordDbQuery({
|
|
1195
|
+
operation: 'findLatest',
|
|
1196
|
+
entity: 'consentPolicy'
|
|
1197
|
+
}, Date.now() - start);
|
|
1198
|
+
return result;
|
|
1199
|
+
} catch (error) {
|
|
1200
|
+
getMetrics()?.recordDbError({
|
|
1201
|
+
operation: 'findLatest',
|
|
1202
|
+
entity: 'consentPolicy'
|
|
1203
|
+
});
|
|
1204
|
+
throw error;
|
|
1205
|
+
}
|
|
1206
|
+
},
|
|
1207
|
+
findLegalDocumentPolicyByHash: async (type, hash)=>{
|
|
1208
|
+
const start = Date.now();
|
|
1209
|
+
try {
|
|
1210
|
+
const policyId = await buildLegalDocumentPolicyId({
|
|
1211
|
+
tenantId: ctx.tenantId,
|
|
1212
|
+
type,
|
|
1213
|
+
hash
|
|
1214
|
+
});
|
|
1215
|
+
const result = await withDatabaseSpan({
|
|
1216
|
+
operation: 'findByHash',
|
|
1217
|
+
entity: 'consentPolicy'
|
|
1218
|
+
}, async ()=>db.findFirst('consentPolicy', {
|
|
1219
|
+
where: (b)=>b('id', '=', policyId)
|
|
1220
|
+
}));
|
|
1221
|
+
getMetrics()?.recordDbQuery({
|
|
1222
|
+
operation: 'findByHash',
|
|
1223
|
+
entity: 'consentPolicy'
|
|
1224
|
+
}, Date.now() - start);
|
|
1225
|
+
return result;
|
|
1226
|
+
} catch (error) {
|
|
1227
|
+
getMetrics()?.recordDbError({
|
|
1228
|
+
operation: 'findByHash',
|
|
1229
|
+
entity: 'consentPolicy'
|
|
1230
|
+
});
|
|
1231
|
+
throw error;
|
|
1232
|
+
}
|
|
1233
|
+
},
|
|
1234
|
+
syncCurrentLegalDocumentPolicy: async (input)=>{
|
|
1235
|
+
const start = Date.now();
|
|
1236
|
+
try {
|
|
1237
|
+
const result = await withDatabaseSpan({
|
|
1238
|
+
operation: 'syncCurrent',
|
|
1239
|
+
entity: 'consentPolicy'
|
|
1240
|
+
}, async ()=>{
|
|
1241
|
+
const policyId = await buildLegalDocumentPolicyId({
|
|
1242
|
+
tenantId: ctx.tenantId,
|
|
1243
|
+
type: input.type,
|
|
1244
|
+
hash: input.hash
|
|
1245
|
+
});
|
|
1246
|
+
return db.transaction(async (tx)=>{
|
|
1247
|
+
const existing = await tx.findFirst('consentPolicy', {
|
|
1248
|
+
where: (b)=>b('id', '=', policyId)
|
|
1249
|
+
});
|
|
1250
|
+
if (existing) {
|
|
1251
|
+
if (hasLegalDocumentPolicyConflict(existing, input)) throw new LegalDocumentPolicyConflictError('Release metadata conflicts with existing consent policy');
|
|
1252
|
+
await tx.updateMany('consentPolicy', {
|
|
1253
|
+
where: (b)=>b.and(b('type', '=', input.type), b('isActive', '=', true), b('id', '!=', existing.id)),
|
|
1254
|
+
set: {
|
|
1255
|
+
isActive: false
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
1258
|
+
if (!existing.isActive) {
|
|
1259
|
+
await tx.updateMany('consentPolicy', {
|
|
1260
|
+
where: (b)=>b('id', '=', existing.id),
|
|
1261
|
+
set: {
|
|
1262
|
+
isActive: true
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1265
|
+
return {
|
|
1266
|
+
...existing,
|
|
1267
|
+
isActive: true
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
return existing;
|
|
1271
|
+
}
|
|
1272
|
+
await tx.updateMany('consentPolicy', {
|
|
1273
|
+
where: (b)=>b.and(b('type', '=', input.type), b('isActive', '=', true)),
|
|
1274
|
+
set: {
|
|
1275
|
+
isActive: false
|
|
1276
|
+
}
|
|
1277
|
+
});
|
|
1278
|
+
const policy = await tx.create('consentPolicy', {
|
|
1279
|
+
id: policyId,
|
|
1280
|
+
version: input.version,
|
|
1281
|
+
type: input.type,
|
|
1282
|
+
hash: input.hash,
|
|
1283
|
+
effectiveDate: input.effectiveDate,
|
|
1284
|
+
isActive: true
|
|
1285
|
+
});
|
|
1286
|
+
return policy;
|
|
1287
|
+
});
|
|
1288
|
+
});
|
|
1289
|
+
getMetrics()?.recordDbQuery({
|
|
1290
|
+
operation: 'syncCurrent',
|
|
1291
|
+
entity: 'consentPolicy'
|
|
1292
|
+
}, Date.now() - start);
|
|
1293
|
+
return result;
|
|
1294
|
+
} catch (error) {
|
|
1295
|
+
getMetrics()?.recordDbError({
|
|
1296
|
+
operation: 'syncCurrent',
|
|
1297
|
+
entity: 'consentPolicy'
|
|
1298
|
+
});
|
|
1299
|
+
throw error;
|
|
1300
|
+
}
|
|
1301
|
+
},
|
|
1302
|
+
findOrCreateLegalDocumentPolicy: async (input)=>{
|
|
1303
|
+
const start = Date.now();
|
|
1304
|
+
try {
|
|
1305
|
+
const result = await withDatabaseSpan({
|
|
1306
|
+
operation: 'findOrCreateLegalDocument',
|
|
1307
|
+
entity: 'consentPolicy'
|
|
1308
|
+
}, async ()=>{
|
|
1309
|
+
const policyId = await buildLegalDocumentPolicyId({
|
|
1310
|
+
tenantId: ctx.tenantId,
|
|
1311
|
+
type: input.type,
|
|
1312
|
+
hash: input.hash
|
|
1313
|
+
});
|
|
1314
|
+
const existing = await db.findFirst('consentPolicy', {
|
|
1315
|
+
where: (b)=>b('id', '=', policyId)
|
|
1316
|
+
});
|
|
1317
|
+
if (existing) {
|
|
1318
|
+
if (hasLegalDocumentPolicyConflict(existing, input)) throw new LegalDocumentPolicyConflictError('Release metadata conflicts with existing consent policy');
|
|
1319
|
+
return existing;
|
|
1320
|
+
}
|
|
1321
|
+
const policy = await db.create('consentPolicy', {
|
|
1322
|
+
id: policyId,
|
|
1323
|
+
version: input.version,
|
|
1324
|
+
type: input.type,
|
|
1325
|
+
hash: input.hash,
|
|
1326
|
+
effectiveDate: input.effectiveDate,
|
|
1327
|
+
isActive: false
|
|
1328
|
+
}).catch(async ()=>{
|
|
1329
|
+
const concurrent = await db.findFirst('consentPolicy', {
|
|
1330
|
+
where: (b)=>b('id', '=', policyId)
|
|
1331
|
+
});
|
|
1332
|
+
if (!concurrent) throw new LegalDocumentPolicyConflictError('Failed to create legal document consent policy');
|
|
1333
|
+
if (hasLegalDocumentPolicyConflict(concurrent, input)) throw new LegalDocumentPolicyConflictError('Release metadata conflicts with existing consent policy');
|
|
1334
|
+
return concurrent;
|
|
1335
|
+
});
|
|
1336
|
+
return policy;
|
|
1337
|
+
});
|
|
1338
|
+
getMetrics()?.recordDbQuery({
|
|
1339
|
+
operation: 'findOrCreateLegalDocument',
|
|
1340
|
+
entity: 'consentPolicy'
|
|
1341
|
+
}, Date.now() - start);
|
|
1342
|
+
return result;
|
|
1343
|
+
} catch (error) {
|
|
1344
|
+
getMetrics()?.recordDbError({
|
|
1345
|
+
operation: 'findOrCreateLegalDocument',
|
|
1346
|
+
entity: 'consentPolicy'
|
|
1347
|
+
});
|
|
1348
|
+
throw error;
|
|
1349
|
+
}
|
|
1350
|
+
},
|
|
994
1351
|
findOrCreatePolicy: async (type)=>{
|
|
995
1352
|
const start = Date.now();
|
|
996
1353
|
try {
|
|
@@ -1165,6 +1522,54 @@ var __webpack_exports__ = {};
|
|
|
1165
1522
|
}
|
|
1166
1523
|
};
|
|
1167
1524
|
}
|
|
1525
|
+
function runtimePolicyDecisionRegistry({ db, ctx }) {
|
|
1526
|
+
const { logger } = ctx;
|
|
1527
|
+
return {
|
|
1528
|
+
findOrCreateRuntimePolicyDecision: async (input)=>{
|
|
1529
|
+
const existing = await db.findFirst('runtimePolicyDecision', {
|
|
1530
|
+
where: (b)=>b('dedupeKey', '=', input.dedupeKey)
|
|
1531
|
+
});
|
|
1532
|
+
if (existing) return existing;
|
|
1533
|
+
logger.debug('Creating runtime policy decision', {
|
|
1534
|
+
policyId: input.policyId,
|
|
1535
|
+
fingerprint: input.fingerprint,
|
|
1536
|
+
matchedBy: input.matchedBy
|
|
1537
|
+
});
|
|
1538
|
+
return db.create('runtimePolicyDecision', {
|
|
1539
|
+
id: `rpd_${crypto.randomUUID().replaceAll('-', '')}`,
|
|
1540
|
+
tenantId: input.tenantId,
|
|
1541
|
+
policyId: input.policyId,
|
|
1542
|
+
fingerprint: input.fingerprint,
|
|
1543
|
+
matchedBy: input.matchedBy,
|
|
1544
|
+
countryCode: input.countryCode,
|
|
1545
|
+
regionCode: input.regionCode,
|
|
1546
|
+
jurisdiction: input.jurisdiction,
|
|
1547
|
+
language: input.language,
|
|
1548
|
+
model: input.model,
|
|
1549
|
+
policyI18n: input.policyI18n ? {
|
|
1550
|
+
json: input.policyI18n
|
|
1551
|
+
} : void 0,
|
|
1552
|
+
uiMode: input.uiMode,
|
|
1553
|
+
bannerUi: input.bannerUi ? {
|
|
1554
|
+
json: input.bannerUi
|
|
1555
|
+
} : void 0,
|
|
1556
|
+
dialogUi: input.dialogUi ? {
|
|
1557
|
+
json: input.dialogUi
|
|
1558
|
+
} : void 0,
|
|
1559
|
+
categories: input.categories ? {
|
|
1560
|
+
json: input.categories
|
|
1561
|
+
} : void 0,
|
|
1562
|
+
preselectedCategories: input.preselectedCategories ? {
|
|
1563
|
+
json: input.preselectedCategories
|
|
1564
|
+
} : void 0,
|
|
1565
|
+
proofConfig: input.proofConfig ? {
|
|
1566
|
+
json: input.proofConfig
|
|
1567
|
+
} : void 0,
|
|
1568
|
+
dedupeKey: input.dedupeKey
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1168
1573
|
function subjectRegistry({ db, ctx }) {
|
|
1169
1574
|
const { logger } = ctx;
|
|
1170
1575
|
return {
|
|
@@ -1194,8 +1599,7 @@ var __webpack_exports__ = {};
|
|
|
1194
1599
|
const newSubject = await db.create('subject', {
|
|
1195
1600
|
id: subjectId,
|
|
1196
1601
|
externalId: externalSubjectId ?? null,
|
|
1197
|
-
identityProvider: externalSubjectId ? identityProvider ?? 'external' : 'anonymous'
|
|
1198
|
-
isIdentified: !!externalSubjectId
|
|
1602
|
+
identityProvider: externalSubjectId ? identityProvider ?? 'external' : 'anonymous'
|
|
1199
1603
|
});
|
|
1200
1604
|
logger.debug('Created new subject', {
|
|
1201
1605
|
subject: newSubject
|
|
@@ -1209,8 +1613,7 @@ var __webpack_exports__ = {};
|
|
|
1209
1613
|
const subject = await db.create('subject', {
|
|
1210
1614
|
id: await generateUniqueId(db, 'subject', ctx),
|
|
1211
1615
|
externalId: externalSubjectId,
|
|
1212
|
-
identityProvider: identityProvider ?? 'external'
|
|
1213
|
-
isIdentified: true
|
|
1616
|
+
identityProvider: identityProvider ?? 'external'
|
|
1214
1617
|
});
|
|
1215
1618
|
return subject;
|
|
1216
1619
|
}
|
|
@@ -1218,8 +1621,7 @@ var __webpack_exports__ = {};
|
|
|
1218
1621
|
const subject = await db.create('subject', {
|
|
1219
1622
|
id: await generateUniqueId(db, 'subject', ctx),
|
|
1220
1623
|
externalId: null,
|
|
1221
|
-
identityProvider: 'anonymous'
|
|
1222
|
-
isIdentified: false
|
|
1624
|
+
identityProvider: 'anonymous'
|
|
1223
1625
|
});
|
|
1224
1626
|
logger.debug('Created new anonymous subject', {
|
|
1225
1627
|
subject
|
|
@@ -1245,7 +1647,8 @@ var __webpack_exports__ = {};
|
|
|
1245
1647
|
...subjectRegistry(ctx),
|
|
1246
1648
|
...consentPurposeRegistry(ctx),
|
|
1247
1649
|
...policyRegistry(ctx),
|
|
1248
|
-
...domainRegistry(ctx)
|
|
1650
|
+
...domainRegistry(ctx),
|
|
1651
|
+
...runtimePolicyDecisionRegistry(ctx)
|
|
1249
1652
|
});
|
|
1250
1653
|
var schema = __webpack_require__("./src/db/schema/index.ts");
|
|
1251
1654
|
const SCOPED_METHODS = new Set([
|
|
@@ -1311,6 +1714,18 @@ var __webpack_exports__ = {};
|
|
|
1311
1714
|
}
|
|
1312
1715
|
});
|
|
1313
1716
|
}
|
|
1717
|
+
function inspectPolicies(policies, options) {
|
|
1718
|
+
return (0, types_namespaceObject.inspectPolicies)(policies, options);
|
|
1719
|
+
}
|
|
1720
|
+
async function resolvePolicyDecision(params) {
|
|
1721
|
+
return (0, types_namespaceObject.resolvePolicyDecision)({
|
|
1722
|
+
policies: params.policies,
|
|
1723
|
+
countryCode: params.countryCode,
|
|
1724
|
+
regionCode: params.regionCode,
|
|
1725
|
+
jurisdiction: params.jurisdiction,
|
|
1726
|
+
iabEnabled: params.iabEnabled
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1314
1729
|
let globalLogger;
|
|
1315
1730
|
function initLogger(options) {
|
|
1316
1731
|
globalLogger = (0, logger_namespaceObject.createLogger)({
|
|
@@ -1325,7 +1740,7 @@ var __webpack_exports__ = {};
|
|
|
1325
1740
|
...options.logger,
|
|
1326
1741
|
appName: String(appName)
|
|
1327
1742
|
});
|
|
1328
|
-
const telemetryOptions = createTelemetryOptions(String(appName), options.
|
|
1743
|
+
const telemetryOptions = createTelemetryOptions(String(appName), options.telemetry, options.tenantId);
|
|
1329
1744
|
if (isTelemetryEnabled(options)) logger.debug('Telemetry is enabled', {
|
|
1330
1745
|
hasTracer: !!telemetryOptions?.tracer,
|
|
1331
1746
|
hasMeter: !!telemetryOptions?.meter,
|
|
@@ -1336,15 +1751,29 @@ var __webpack_exports__ = {};
|
|
|
1336
1751
|
const client = db.client(options.adapter);
|
|
1337
1752
|
const rawOrm = client.orm('2.0.0');
|
|
1338
1753
|
const orm = options.tenantId ? withTenantScope(rawOrm, options.tenantId) : rawOrm;
|
|
1754
|
+
const { ipAddress: _ipAddressConfig, ...baseOptions } = options;
|
|
1755
|
+
const i18nValidation = validateMessages({
|
|
1756
|
+
i18n: options.i18n,
|
|
1757
|
+
customTranslations: options.customTranslations,
|
|
1758
|
+
policies: options.policyPacks
|
|
1759
|
+
});
|
|
1760
|
+
for (const warning of i18nValidation.warnings)logger.warn(`i18n: ${warning}`);
|
|
1761
|
+
if (i18nValidation.errors.length > 0) throw new Error(`Invalid i18n configuration:\n${i18nValidation.errors.map((error)=>`- ${error}`).join('\n')}`);
|
|
1762
|
+
const policyValidation = inspectPolicies(options.policyPacks ?? [], {
|
|
1763
|
+
iabEnabled: options.iab?.enabled === true
|
|
1764
|
+
});
|
|
1765
|
+
for (const warning of policyValidation.warnings)logger.warn(`policyPacks: ${warning}`);
|
|
1766
|
+
if (policyValidation.errors.length > 0) throw new Error(policyValidation.errors[0]);
|
|
1339
1767
|
const context = {
|
|
1340
|
-
...
|
|
1768
|
+
...baseOptions,
|
|
1341
1769
|
appName,
|
|
1342
1770
|
logger,
|
|
1343
1771
|
db: orm,
|
|
1344
1772
|
registry: createRegistry({
|
|
1345
1773
|
db: orm,
|
|
1346
1774
|
ctx: {
|
|
1347
|
-
logger
|
|
1775
|
+
logger,
|
|
1776
|
+
tenantId: options.tenantId
|
|
1348
1777
|
}
|
|
1349
1778
|
})
|
|
1350
1779
|
};
|
|
@@ -1371,7 +1800,7 @@ var __webpack_exports__ = {};
|
|
|
1371
1800
|
for (const p of policyMap.values())uniqueTypes.add(p.type);
|
|
1372
1801
|
const latestPolicyByType = new Map();
|
|
1373
1802
|
for (const type of uniqueTypes){
|
|
1374
|
-
const latest = await registry.
|
|
1803
|
+
const latest = await registry.findLatestPolicyByType(type);
|
|
1375
1804
|
if (latest) latestPolicyByType.set(type, latest.id);
|
|
1376
1805
|
}
|
|
1377
1806
|
return {
|
|
@@ -1397,11 +1826,17 @@ var __webpack_exports__ = {};
|
|
|
1397
1826
|
}
|
|
1398
1827
|
return consents.map((consent)=>{
|
|
1399
1828
|
let policyType = 'unknown';
|
|
1829
|
+
let policyVersion;
|
|
1830
|
+
let policyHash;
|
|
1831
|
+
let policyEffectiveDate;
|
|
1400
1832
|
let isLatestPolicy = false;
|
|
1401
1833
|
if (consent.policyId) {
|
|
1402
1834
|
const policy = policyMap.get(consent.policyId);
|
|
1403
1835
|
if (policy) {
|
|
1404
1836
|
policyType = policy.type;
|
|
1837
|
+
policyVersion = policy.version;
|
|
1838
|
+
policyHash = policy.hash ?? void 0;
|
|
1839
|
+
policyEffectiveDate = policy.effectiveDate;
|
|
1405
1840
|
isLatestPolicy = latestPolicyByType.get(policyType) === consent.policyId;
|
|
1406
1841
|
}
|
|
1407
1842
|
}
|
|
@@ -1418,6 +1853,9 @@ var __webpack_exports__ = {};
|
|
|
1418
1853
|
id: consent.id,
|
|
1419
1854
|
type: policyType,
|
|
1420
1855
|
policyId: consent.policyId ?? void 0,
|
|
1856
|
+
policyVersion,
|
|
1857
|
+
policyHash,
|
|
1858
|
+
policyEffectiveDate,
|
|
1421
1859
|
isLatestPolicy,
|
|
1422
1860
|
preferences,
|
|
1423
1861
|
givenAt: consent.givenAt
|
|
@@ -1532,11 +1970,7 @@ var __webpack_exports__ = {};
|
|
|
1532
1970
|
const app = new external_hono_namespaceObject.Hono();
|
|
1533
1971
|
app.get('/check', (0, external_hono_openapi_namespaceObject.describeRoute)({
|
|
1534
1972
|
summary: 'Check consent by external user ID',
|
|
1535
|
-
description:
|
|
1536
|
-
|
|
1537
|
-
**Query parameters:**
|
|
1538
|
-
- \`externalId\` – External user ID to check
|
|
1539
|
-
- \`type\` – Consent type(s) to check (comma-separated)`,
|
|
1973
|
+
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)",
|
|
1540
1974
|
tags: [
|
|
1541
1975
|
'Consent'
|
|
1542
1976
|
],
|
|
@@ -1696,6 +2130,124 @@ var __webpack_exports__ = {};
|
|
|
1696
2130
|
}
|
|
1697
2131
|
};
|
|
1698
2132
|
}
|
|
2133
|
+
const external_jose_namespaceObject = require("jose");
|
|
2134
|
+
const POLICY_SNAPSHOT_JWT_HEADER = {
|
|
2135
|
+
alg: 'HS256',
|
|
2136
|
+
typ: 'JWT'
|
|
2137
|
+
};
|
|
2138
|
+
const DEFAULT_POLICY_SNAPSHOT_ISSUER = 'c15t';
|
|
2139
|
+
const DEFAULT_POLICY_SNAPSHOT_AUDIENCE = 'c15t-policy-snapshot';
|
|
2140
|
+
function resolveSnapshotIssuer(options) {
|
|
2141
|
+
return options?.issuer?.trim() || DEFAULT_POLICY_SNAPSHOT_ISSUER;
|
|
2142
|
+
}
|
|
2143
|
+
function resolveSnapshotAudience(params) {
|
|
2144
|
+
const configuredAudience = params.options?.audience?.trim();
|
|
2145
|
+
if (configuredAudience) return configuredAudience;
|
|
2146
|
+
return params.tenantId ? `${DEFAULT_POLICY_SNAPSHOT_AUDIENCE}:${params.tenantId}` : DEFAULT_POLICY_SNAPSHOT_AUDIENCE;
|
|
2147
|
+
}
|
|
2148
|
+
function getSigningKey(secret) {
|
|
2149
|
+
return new TextEncoder().encode(secret);
|
|
2150
|
+
}
|
|
2151
|
+
function isPolicySnapshotPayload(payload) {
|
|
2152
|
+
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;
|
|
2153
|
+
}
|
|
2154
|
+
async function createPolicySnapshotToken(params) {
|
|
2155
|
+
const { options } = params;
|
|
2156
|
+
if (!options?.signingKey) return;
|
|
2157
|
+
const iat = Math.floor(Date.now() / 1000);
|
|
2158
|
+
const ttlSeconds = options.ttlSeconds ?? 1800;
|
|
2159
|
+
const exp = iat + ttlSeconds;
|
|
2160
|
+
const iss = resolveSnapshotIssuer(options);
|
|
2161
|
+
const aud = resolveSnapshotAudience({
|
|
2162
|
+
options,
|
|
2163
|
+
tenantId: params.tenantId
|
|
2164
|
+
});
|
|
2165
|
+
const payload = {
|
|
2166
|
+
iss,
|
|
2167
|
+
aud,
|
|
2168
|
+
sub: params.policyId,
|
|
2169
|
+
tenantId: params.tenantId,
|
|
2170
|
+
policyId: params.policyId,
|
|
2171
|
+
fingerprint: params.fingerprint,
|
|
2172
|
+
matchedBy: params.matchedBy,
|
|
2173
|
+
country: params.country,
|
|
2174
|
+
region: params.region,
|
|
2175
|
+
jurisdiction: params.jurisdiction,
|
|
2176
|
+
language: params.language,
|
|
2177
|
+
model: params.model,
|
|
2178
|
+
policyI18n: params.policyI18n,
|
|
2179
|
+
expiryDays: params.expiryDays,
|
|
2180
|
+
scopeMode: params.scopeMode,
|
|
2181
|
+
uiMode: params.uiMode,
|
|
2182
|
+
bannerUi: params.bannerUi,
|
|
2183
|
+
dialogUi: params.dialogUi,
|
|
2184
|
+
categories: params.categories,
|
|
2185
|
+
preselectedCategories: params.preselectedCategories,
|
|
2186
|
+
gpc: params.gpc,
|
|
2187
|
+
proofConfig: params.proofConfig,
|
|
2188
|
+
iat,
|
|
2189
|
+
exp
|
|
2190
|
+
};
|
|
2191
|
+
const token = await new external_jose_namespaceObject.SignJWT(payload).setProtectedHeader(POLICY_SNAPSHOT_JWT_HEADER).setIssuedAt(iat).setExpirationTime(exp).sign(getSigningKey(options.signingKey));
|
|
2192
|
+
return {
|
|
2193
|
+
token,
|
|
2194
|
+
payload
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
async function verifyPolicySnapshotToken(params) {
|
|
2198
|
+
const { token, options, tenantId } = params;
|
|
2199
|
+
if (!options?.signingKey) return {
|
|
2200
|
+
valid: false,
|
|
2201
|
+
reason: 'missing'
|
|
2202
|
+
};
|
|
2203
|
+
if (!token) return {
|
|
2204
|
+
valid: false,
|
|
2205
|
+
reason: 'missing'
|
|
2206
|
+
};
|
|
2207
|
+
if (3 !== token.split('.').length) return {
|
|
2208
|
+
valid: false,
|
|
2209
|
+
reason: 'malformed'
|
|
2210
|
+
};
|
|
2211
|
+
try {
|
|
2212
|
+
const { payload, protectedHeader } = await (0, external_jose_namespaceObject.jwtVerify)(token, getSigningKey(options.signingKey), {
|
|
2213
|
+
issuer: resolveSnapshotIssuer(options),
|
|
2214
|
+
audience: resolveSnapshotAudience({
|
|
2215
|
+
options,
|
|
2216
|
+
tenantId
|
|
2217
|
+
})
|
|
2218
|
+
});
|
|
2219
|
+
const header = protectedHeader;
|
|
2220
|
+
if ('HS256' !== header.alg || 'JWT' !== header.typ) return {
|
|
2221
|
+
valid: false,
|
|
2222
|
+
reason: 'invalid'
|
|
2223
|
+
};
|
|
2224
|
+
if (!isPolicySnapshotPayload(payload)) return {
|
|
2225
|
+
valid: false,
|
|
2226
|
+
reason: 'invalid'
|
|
2227
|
+
};
|
|
2228
|
+
if (payload.sub !== payload.policyId) return {
|
|
2229
|
+
valid: false,
|
|
2230
|
+
reason: 'invalid'
|
|
2231
|
+
};
|
|
2232
|
+
if ((tenantId ?? void 0) !== (payload.tenantId ?? void 0)) return {
|
|
2233
|
+
valid: false,
|
|
2234
|
+
reason: 'invalid'
|
|
2235
|
+
};
|
|
2236
|
+
return {
|
|
2237
|
+
valid: true,
|
|
2238
|
+
payload
|
|
2239
|
+
};
|
|
2240
|
+
} catch (error) {
|
|
2241
|
+
if (error instanceof external_jose_namespaceObject.errors.JWTExpired) return {
|
|
2242
|
+
valid: false,
|
|
2243
|
+
reason: 'expired'
|
|
2244
|
+
};
|
|
2245
|
+
return {
|
|
2246
|
+
valid: false,
|
|
2247
|
+
reason: 'invalid'
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
1699
2251
|
function geo_normalizeHeader(value) {
|
|
1700
2252
|
if (!value) return null;
|
|
1701
2253
|
return Array.isArray(value) ? value[0] ?? null : value;
|
|
@@ -1837,7 +2389,7 @@ var __webpack_exports__ = {};
|
|
|
1837
2389
|
return jurisdiction;
|
|
1838
2390
|
}
|
|
1839
2391
|
async function getLocation(request, options) {
|
|
1840
|
-
if (options.
|
|
2392
|
+
if (options.disableGeoLocation) return {
|
|
1841
2393
|
countryCode: null,
|
|
1842
2394
|
regionCode: null
|
|
1843
2395
|
};
|
|
@@ -1848,30 +2400,121 @@ var __webpack_exports__ = {};
|
|
|
1848
2400
|
};
|
|
1849
2401
|
}
|
|
1850
2402
|
function getJurisdiction(location, options) {
|
|
1851
|
-
if (options.
|
|
2403
|
+
if (options.disableGeoLocation) return 'GDPR';
|
|
1852
2404
|
return checkJurisdiction(location.countryCode, location.regionCode);
|
|
1853
2405
|
}
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
return
|
|
1857
|
-
}
|
|
1858
|
-
function
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
2406
|
+
function stripIabTranslations(translations) {
|
|
2407
|
+
const { iab: _iab, ...rest } = translations;
|
|
2408
|
+
return rest;
|
|
2409
|
+
}
|
|
2410
|
+
function resolveNoPolicyFallback() {
|
|
2411
|
+
return {
|
|
2412
|
+
id: 'no_banner',
|
|
2413
|
+
model: 'none',
|
|
2414
|
+
ui: {
|
|
2415
|
+
mode: 'none'
|
|
2416
|
+
}
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2419
|
+
async function resolveInitPayload(request, options, logger) {
|
|
2420
|
+
const acceptLanguage = request.headers.get('accept-language') || 'en';
|
|
2421
|
+
const location = await getLocation(request, options);
|
|
2422
|
+
const jurisdiction = getJurisdiction(location, options);
|
|
2423
|
+
const hasExplicitPolicyPack = void 0 !== options.policyPacks;
|
|
2424
|
+
const isExplicitEmptyPolicyPack = hasExplicitPolicyPack && (options.policyPacks?.length ?? 0) === 0;
|
|
2425
|
+
const policyDecision = isExplicitEmptyPolicyPack ? void 0 : await resolvePolicyDecision({
|
|
2426
|
+
policies: options.policyPacks,
|
|
2427
|
+
countryCode: location.countryCode,
|
|
2428
|
+
regionCode: location.regionCode,
|
|
2429
|
+
jurisdiction,
|
|
2430
|
+
iabEnabled: options.iab?.enabled === true
|
|
2431
|
+
});
|
|
2432
|
+
if (hasExplicitPolicyPack && !isExplicitEmptyPolicyPack && !policyDecision) logger?.warn('Policy packs configured but no policy matched', {
|
|
2433
|
+
country: location.countryCode,
|
|
2434
|
+
region: location.regionCode
|
|
2435
|
+
});
|
|
2436
|
+
const resolvedPolicy = hasExplicitPolicyPack ? policyDecision?.policy ?? resolveNoPolicyFallback() : void 0;
|
|
2437
|
+
const iabOptions = options.iab;
|
|
2438
|
+
const shouldIncludeIabPayload = iabOptions?.enabled === true && (!hasExplicitPolicyPack || resolvedPolicy?.model === 'iab');
|
|
2439
|
+
const translationsResult = translations_getTranslationsData(acceptLanguage, options.customTranslations, {
|
|
2440
|
+
i18n: options.i18n,
|
|
2441
|
+
policyI18n: resolvedPolicy?.i18n,
|
|
2442
|
+
logger
|
|
2443
|
+
});
|
|
2444
|
+
const responseTranslations = shouldIncludeIabPayload ? translationsResult : {
|
|
2445
|
+
...translationsResult,
|
|
2446
|
+
translations: stripIabTranslations(translationsResult.translations)
|
|
2447
|
+
};
|
|
2448
|
+
let gvl = null;
|
|
2449
|
+
if (shouldIncludeIabPayload && iabOptions) {
|
|
2450
|
+
const language = translationsResult.language.split('-')[0] || 'en';
|
|
2451
|
+
const gvlResolver = createGVLResolver({
|
|
2452
|
+
appName: options.appName || 'c15t',
|
|
2453
|
+
bundled: iabOptions.bundled,
|
|
2454
|
+
cacheAdapter: options.cache?.adapter,
|
|
2455
|
+
vendorIds: iabOptions.vendorIds,
|
|
2456
|
+
endpoint: iabOptions.endpoint
|
|
2457
|
+
});
|
|
2458
|
+
gvl = await gvlResolver.get(language);
|
|
2459
|
+
}
|
|
2460
|
+
const customVendors = shouldIncludeIabPayload ? iabOptions?.customVendors : void 0;
|
|
2461
|
+
const snapshot = policyDecision ? await createPolicySnapshotToken({
|
|
2462
|
+
options: options.policySnapshot,
|
|
2463
|
+
tenantId: options.tenantId,
|
|
2464
|
+
policyId: policyDecision.policy.id,
|
|
2465
|
+
fingerprint: policyDecision.fingerprint,
|
|
2466
|
+
matchedBy: policyDecision.matchedBy,
|
|
2467
|
+
country: location?.countryCode ?? null,
|
|
2468
|
+
region: location?.regionCode ?? null,
|
|
2469
|
+
jurisdiction,
|
|
2470
|
+
language: translationsResult.language,
|
|
2471
|
+
model: policyDecision.policy.model,
|
|
2472
|
+
policyI18n: policyDecision.policy.i18n,
|
|
2473
|
+
expiryDays: policyDecision.policy.consent?.expiryDays,
|
|
2474
|
+
scopeMode: policyDecision.policy.consent?.scopeMode,
|
|
2475
|
+
uiMode: policyDecision.policy.ui?.mode,
|
|
2476
|
+
bannerUi: policyDecision.policy.ui?.banner,
|
|
2477
|
+
dialogUi: policyDecision.policy.ui?.dialog,
|
|
2478
|
+
categories: policyDecision.policy.consent?.categories,
|
|
2479
|
+
preselectedCategories: policyDecision.policy.consent?.preselectedCategories,
|
|
2480
|
+
gpc: policyDecision.policy.consent?.gpc,
|
|
2481
|
+
proofConfig: policyDecision.policy.proof
|
|
2482
|
+
}) : void 0;
|
|
2483
|
+
const gpc = '1' === request.headers.get('sec-gpc');
|
|
2484
|
+
getMetrics()?.recordInit({
|
|
2485
|
+
jurisdiction,
|
|
2486
|
+
country: location?.countryCode ?? void 0,
|
|
2487
|
+
region: location?.regionCode ?? void 0,
|
|
2488
|
+
gpc
|
|
1868
2489
|
});
|
|
1869
|
-
const base = isSupportedBaseLanguage(preferredLanguage) ? translations_namespaceObject.baseTranslations[preferredLanguage] : translations_namespaceObject.baseTranslations.en;
|
|
1870
|
-
const custom = supportedCustomLanguages.includes(preferredLanguage) ? customTranslations?.[preferredLanguage] : {};
|
|
1871
|
-
const translations = custom ? (0, translations_namespaceObject.deepMergeTranslations)(base, custom) : base;
|
|
1872
2490
|
return {
|
|
1873
|
-
|
|
1874
|
-
|
|
2491
|
+
jurisdiction,
|
|
2492
|
+
location,
|
|
2493
|
+
translations: responseTranslations,
|
|
2494
|
+
branding: options.branding || 'c15t',
|
|
2495
|
+
...shouldIncludeIabPayload && {
|
|
2496
|
+
gvl,
|
|
2497
|
+
customVendors
|
|
2498
|
+
},
|
|
2499
|
+
...resolvedPolicy && {
|
|
2500
|
+
policy: resolvedPolicy
|
|
2501
|
+
},
|
|
2502
|
+
...policyDecision && {
|
|
2503
|
+
policyDecision: {
|
|
2504
|
+
policyId: policyDecision.policy.id,
|
|
2505
|
+
fingerprint: policyDecision.fingerprint,
|
|
2506
|
+
matchedBy: policyDecision.matchedBy,
|
|
2507
|
+
country: location.countryCode,
|
|
2508
|
+
region: location.regionCode,
|
|
2509
|
+
jurisdiction
|
|
2510
|
+
}
|
|
2511
|
+
},
|
|
2512
|
+
...snapshot?.token && {
|
|
2513
|
+
policySnapshotToken: snapshot.token
|
|
2514
|
+
},
|
|
2515
|
+
...shouldIncludeIabPayload && iabOptions?.cmpId != null && {
|
|
2516
|
+
cmpId: iabOptions.cmpId
|
|
2517
|
+
}
|
|
1875
2518
|
};
|
|
1876
2519
|
}
|
|
1877
2520
|
const createInitRoute = (options)=>{
|
|
@@ -1884,7 +2527,7 @@ var __webpack_exports__ = {};
|
|
|
1884
2527
|
- **Location** – User's location (null if geo-location is disabled)
|
|
1885
2528
|
- **Translations** – Consent manager copy (from \`Accept-Language\` header)
|
|
1886
2529
|
- **Branding** – Configured branding key
|
|
1887
|
-
- **GVL** – Global Vendor List when
|
|
2530
|
+
- **GVL** – Global Vendor List when IAB is active for the request
|
|
1888
2531
|
|
|
1889
2532
|
Use for geo-targeted consent banners and regional compliance.`,
|
|
1890
2533
|
tags: [
|
|
@@ -1901,40 +2544,98 @@ Use for geo-targeted consent banners and regional compliance.`,
|
|
|
1901
2544
|
}
|
|
1902
2545
|
}
|
|
1903
2546
|
}), async (c)=>{
|
|
1904
|
-
const
|
|
1905
|
-
const
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
});
|
|
1919
|
-
gvl = await gvlResolver.get(language);
|
|
2547
|
+
const ctx = c.get('c15tContext');
|
|
2548
|
+
const payload = await resolveInitPayload(c.req.raw, options, ctx?.logger);
|
|
2549
|
+
return c.json(payload);
|
|
2550
|
+
});
|
|
2551
|
+
return app;
|
|
2552
|
+
};
|
|
2553
|
+
const syncCurrentLegalDocumentHandler = async (c)=>{
|
|
2554
|
+
const ctx = c.get('c15tContext');
|
|
2555
|
+
const logger = ctx.logger;
|
|
2556
|
+
logger.info('Handling PUT /legal-documents/:type/current request');
|
|
2557
|
+
if (!ctx.apiKeyAuthenticated) throw new http_exception_namespaceObject.HTTPException(401, {
|
|
2558
|
+
message: 'API key required. Use Authorization: Bearer <api_key>',
|
|
2559
|
+
cause: {
|
|
2560
|
+
code: 'UNAUTHORIZED'
|
|
1920
2561
|
}
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
2562
|
+
});
|
|
2563
|
+
const type = c.req.param('type');
|
|
2564
|
+
const body = await c.req.json();
|
|
2565
|
+
const effectiveDate = new Date(body.effectiveDate);
|
|
2566
|
+
if (Number.isNaN(effectiveDate.getTime())) throw new http_exception_namespaceObject.HTTPException(422, {
|
|
2567
|
+
message: 'effectiveDate must be a valid ISO-8601 string',
|
|
2568
|
+
cause: {
|
|
2569
|
+
code: 'INPUT_VALIDATION_FAILED'
|
|
2570
|
+
}
|
|
2571
|
+
});
|
|
2572
|
+
try {
|
|
2573
|
+
const policy = await ctx.registry.syncCurrentLegalDocumentPolicy({
|
|
2574
|
+
type,
|
|
2575
|
+
version: body.version,
|
|
2576
|
+
hash: body.hash,
|
|
2577
|
+
effectiveDate
|
|
1928
2578
|
});
|
|
1929
2579
|
return c.json({
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
2580
|
+
policy: {
|
|
2581
|
+
id: policy.id,
|
|
2582
|
+
type: policy.type,
|
|
2583
|
+
version: policy.version,
|
|
2584
|
+
hash: policy.hash,
|
|
2585
|
+
effectiveDate: policy.effectiveDate,
|
|
2586
|
+
isActive: policy.isActive
|
|
2587
|
+
}
|
|
1936
2588
|
});
|
|
1937
|
-
})
|
|
2589
|
+
} catch (error) {
|
|
2590
|
+
logger.error('Error in PUT /legal-documents/:type/current handler', {
|
|
2591
|
+
error: extractErrorMessage(error),
|
|
2592
|
+
errorType: error instanceof Error ? error.constructor.name : typeof error
|
|
2593
|
+
});
|
|
2594
|
+
if (error instanceof LegalDocumentPolicyConflictError) throw new http_exception_namespaceObject.HTTPException(409, {
|
|
2595
|
+
message: error.message,
|
|
2596
|
+
cause: {
|
|
2597
|
+
code: 'LEGAL_DOCUMENT_RELEASE_CONFLICT'
|
|
2598
|
+
}
|
|
2599
|
+
});
|
|
2600
|
+
if (error instanceof http_exception_namespaceObject.HTTPException) throw error;
|
|
2601
|
+
throw new http_exception_namespaceObject.HTTPException(500, {
|
|
2602
|
+
message: 'Internal server error',
|
|
2603
|
+
cause: {
|
|
2604
|
+
code: 'INTERNAL_SERVER_ERROR'
|
|
2605
|
+
}
|
|
2606
|
+
});
|
|
2607
|
+
}
|
|
2608
|
+
};
|
|
2609
|
+
const createLegalDocumentRoutes = ()=>{
|
|
2610
|
+
const app = new external_hono_namespaceObject.Hono();
|
|
2611
|
+
app.put('/:type/current', (0, external_hono_openapi_namespaceObject.describeRoute)({
|
|
2612
|
+
summary: 'Sync the current legal document release (API key required)',
|
|
2613
|
+
description: 'Marks a legal document release as the latest known version for its type. Requires a Bearer API key.',
|
|
2614
|
+
tags: [
|
|
2615
|
+
'LegalDocument'
|
|
2616
|
+
],
|
|
2617
|
+
security: [
|
|
2618
|
+
{
|
|
2619
|
+
bearerAuth: []
|
|
2620
|
+
}
|
|
2621
|
+
],
|
|
2622
|
+
responses: {
|
|
2623
|
+
200: {
|
|
2624
|
+
description: 'Current legal document release synced successfully',
|
|
2625
|
+
content: {
|
|
2626
|
+
'application/json': {
|
|
2627
|
+
schema: (0, external_hono_openapi_namespaceObject.resolver)(schema_.legalDocumentCurrentOutputSchema)
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
},
|
|
2631
|
+
401: {
|
|
2632
|
+
description: 'Missing or invalid API key'
|
|
2633
|
+
},
|
|
2634
|
+
409: {
|
|
2635
|
+
description: 'Release metadata conflicts with an existing release'
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
}), (0, external_hono_openapi_namespaceObject.validator)('param', schema_.legalDocumentCurrentParamsSchema), (0, external_hono_openapi_namespaceObject.validator)('json', schema_.legalDocumentCurrentInputSchema), syncCurrentLegalDocumentHandler);
|
|
1938
2639
|
return app;
|
|
1939
2640
|
};
|
|
1940
2641
|
function getHeaders(headers) {
|
|
@@ -2023,6 +2724,12 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2023
2724
|
const subjectId = c.req.param('id');
|
|
2024
2725
|
const type = c.req.query('type');
|
|
2025
2726
|
const typeFilter = type?.split(',').map((t)=>t.trim()) || [];
|
|
2727
|
+
if (!subjectId) throw new http_exception_namespaceObject.HTTPException(400, {
|
|
2728
|
+
message: 'Subject ID is required',
|
|
2729
|
+
cause: {
|
|
2730
|
+
code: 'SUBJECT_ID_REQUIRED'
|
|
2731
|
+
}
|
|
2732
|
+
});
|
|
2026
2733
|
logger.debug('Request parameters', {
|
|
2027
2734
|
subjectId,
|
|
2028
2735
|
typeFilter
|
|
@@ -2051,7 +2758,6 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2051
2758
|
subject: {
|
|
2052
2759
|
id: subject.id,
|
|
2053
2760
|
externalId: subject.externalId ?? void 0,
|
|
2054
|
-
isIdentified: subject.isIdentified,
|
|
2055
2761
|
createdAt: subject.createdAt
|
|
2056
2762
|
},
|
|
2057
2763
|
consents: filteredConsents,
|
|
@@ -2107,7 +2813,6 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2107
2813
|
return {
|
|
2108
2814
|
id: subject.id,
|
|
2109
2815
|
externalId: subject.externalId ?? externalId,
|
|
2110
|
-
isIdentified: subject.isIdentified,
|
|
2111
2816
|
createdAt: subject.createdAt,
|
|
2112
2817
|
consents: consentItems
|
|
2113
2818
|
};
|
|
@@ -2216,6 +2921,12 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2216
2921
|
const subjectId = c.req.param('id');
|
|
2217
2922
|
const body = await c.req.json();
|
|
2218
2923
|
const { externalId, identityProvider = 'external' } = body;
|
|
2924
|
+
if (!subjectId) throw new http_exception_namespaceObject.HTTPException(400, {
|
|
2925
|
+
message: 'Subject ID is required',
|
|
2926
|
+
cause: {
|
|
2927
|
+
code: 'SUBJECT_ID_REQUIRED'
|
|
2928
|
+
}
|
|
2929
|
+
});
|
|
2219
2930
|
logger.debug('Request parameters', {
|
|
2220
2931
|
subjectId,
|
|
2221
2932
|
externalId,
|
|
@@ -2238,7 +2949,6 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2238
2949
|
set: {
|
|
2239
2950
|
externalId,
|
|
2240
2951
|
identityProvider,
|
|
2241
|
-
isIdentified: true,
|
|
2242
2952
|
updatedAt: new Date()
|
|
2243
2953
|
}
|
|
2244
2954
|
});
|
|
@@ -2258,10 +2968,6 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2258
2968
|
identityProvider: {
|
|
2259
2969
|
from: subject.identityProvider,
|
|
2260
2970
|
to: identityProvider
|
|
2261
|
-
},
|
|
2262
|
-
isIdentified: {
|
|
2263
|
-
from: subject.isIdentified,
|
|
2264
|
-
to: true
|
|
2265
2971
|
}
|
|
2266
2972
|
},
|
|
2267
2973
|
metadata: {
|
|
@@ -2280,8 +2986,7 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2280
2986
|
success: true,
|
|
2281
2987
|
subject: {
|
|
2282
2988
|
id: subjectId,
|
|
2283
|
-
externalId
|
|
2284
|
-
isIdentified: true
|
|
2989
|
+
externalId
|
|
2285
2990
|
}
|
|
2286
2991
|
});
|
|
2287
2992
|
} catch (error) {
|
|
@@ -2298,6 +3003,234 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2298
3003
|
});
|
|
2299
3004
|
}
|
|
2300
3005
|
};
|
|
3006
|
+
const DEFAULT_ISSUER = 'c15t';
|
|
3007
|
+
const DEFAULT_AUDIENCE = 'c15t-legal-document-snapshot';
|
|
3008
|
+
function isLegalDocumentPolicyType(type) {
|
|
3009
|
+
return 'privacy_policy' === type || 'terms_and_conditions' === type || 'dpa' === type;
|
|
3010
|
+
}
|
|
3011
|
+
function snapshot_resolveSnapshotIssuer(options) {
|
|
3012
|
+
return options?.issuer?.trim() || DEFAULT_ISSUER;
|
|
3013
|
+
}
|
|
3014
|
+
function snapshot_resolveSnapshotAudience(params) {
|
|
3015
|
+
const configuredAudience = params.options?.audience?.trim();
|
|
3016
|
+
if (configuredAudience) return configuredAudience;
|
|
3017
|
+
return params.tenantId ? `${DEFAULT_AUDIENCE}:${params.tenantId}` : DEFAULT_AUDIENCE;
|
|
3018
|
+
}
|
|
3019
|
+
function snapshot_getSigningKey(secret) {
|
|
3020
|
+
return new TextEncoder().encode(secret);
|
|
3021
|
+
}
|
|
3022
|
+
function isLegalDocumentSnapshotPayload(payload) {
|
|
3023
|
+
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;
|
|
3024
|
+
}
|
|
3025
|
+
async function verifyLegalDocumentSnapshotToken(params) {
|
|
3026
|
+
const { token, options, tenantId } = params;
|
|
3027
|
+
if (!options?.signingKey) return {
|
|
3028
|
+
valid: false,
|
|
3029
|
+
reason: 'missing'
|
|
3030
|
+
};
|
|
3031
|
+
if (!token) return {
|
|
3032
|
+
valid: false,
|
|
3033
|
+
reason: 'missing'
|
|
3034
|
+
};
|
|
3035
|
+
if (3 !== token.split('.').length) return {
|
|
3036
|
+
valid: false,
|
|
3037
|
+
reason: 'malformed'
|
|
3038
|
+
};
|
|
3039
|
+
try {
|
|
3040
|
+
const { payload, protectedHeader } = await (0, external_jose_namespaceObject.jwtVerify)(token, snapshot_getSigningKey(options.signingKey), {
|
|
3041
|
+
issuer: snapshot_resolveSnapshotIssuer(options),
|
|
3042
|
+
audience: snapshot_resolveSnapshotAudience({
|
|
3043
|
+
options,
|
|
3044
|
+
tenantId
|
|
3045
|
+
})
|
|
3046
|
+
});
|
|
3047
|
+
const header = protectedHeader;
|
|
3048
|
+
if ('HS256' !== header.alg || 'JWT' !== header.typ) return {
|
|
3049
|
+
valid: false,
|
|
3050
|
+
reason: 'invalid'
|
|
3051
|
+
};
|
|
3052
|
+
if (!isLegalDocumentSnapshotPayload(payload)) return {
|
|
3053
|
+
valid: false,
|
|
3054
|
+
reason: 'invalid'
|
|
3055
|
+
};
|
|
3056
|
+
if (payload.sub !== payload.hash) return {
|
|
3057
|
+
valid: false,
|
|
3058
|
+
reason: 'invalid'
|
|
3059
|
+
};
|
|
3060
|
+
if ((tenantId ?? void 0) !== (payload.tenantId ?? void 0)) return {
|
|
3061
|
+
valid: false,
|
|
3062
|
+
reason: 'invalid'
|
|
3063
|
+
};
|
|
3064
|
+
return {
|
|
3065
|
+
valid: true,
|
|
3066
|
+
payload
|
|
3067
|
+
};
|
|
3068
|
+
} catch (error) {
|
|
3069
|
+
if (error instanceof external_jose_namespaceObject.errors.JWTExpired) return {
|
|
3070
|
+
valid: false,
|
|
3071
|
+
reason: 'expired'
|
|
3072
|
+
};
|
|
3073
|
+
return {
|
|
3074
|
+
valid: false,
|
|
3075
|
+
reason: 'invalid'
|
|
3076
|
+
};
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
function buildRuntimeDecisionDedupeKey(input) {
|
|
3080
|
+
return [
|
|
3081
|
+
input.tenantId ?? 'default',
|
|
3082
|
+
input.fingerprint,
|
|
3083
|
+
input.matchedBy,
|
|
3084
|
+
input.countryCode ?? 'none',
|
|
3085
|
+
input.regionCode ?? 'none',
|
|
3086
|
+
input.jurisdiction,
|
|
3087
|
+
input.language ?? 'none'
|
|
3088
|
+
].join('|');
|
|
3089
|
+
}
|
|
3090
|
+
function buildDecisionPayload(params) {
|
|
3091
|
+
const { tenantId, snapshot, decision, location, jurisdiction, language, proofConfig } = params;
|
|
3092
|
+
if (snapshot?.valid && snapshot.payload) {
|
|
3093
|
+
const sp = snapshot.payload;
|
|
3094
|
+
return {
|
|
3095
|
+
tenantId,
|
|
3096
|
+
policyId: sp.policyId,
|
|
3097
|
+
fingerprint: sp.fingerprint,
|
|
3098
|
+
matchedBy: sp.matchedBy,
|
|
3099
|
+
countryCode: sp.country,
|
|
3100
|
+
regionCode: sp.region,
|
|
3101
|
+
jurisdiction: sp.jurisdiction,
|
|
3102
|
+
language: sp.language,
|
|
3103
|
+
model: sp.model,
|
|
3104
|
+
policyI18n: sp.policyI18n,
|
|
3105
|
+
uiMode: sp.uiMode,
|
|
3106
|
+
bannerUi: sp.bannerUi,
|
|
3107
|
+
dialogUi: sp.dialogUi,
|
|
3108
|
+
categories: sp.categories,
|
|
3109
|
+
preselectedCategories: sp.preselectedCategories,
|
|
3110
|
+
proofConfig: sp.proofConfig,
|
|
3111
|
+
dedupeKey: buildRuntimeDecisionDedupeKey({
|
|
3112
|
+
tenantId,
|
|
3113
|
+
fingerprint: sp.fingerprint,
|
|
3114
|
+
matchedBy: sp.matchedBy,
|
|
3115
|
+
countryCode: sp.country,
|
|
3116
|
+
regionCode: sp.region,
|
|
3117
|
+
jurisdiction: sp.jurisdiction,
|
|
3118
|
+
language: sp.language
|
|
3119
|
+
}),
|
|
3120
|
+
source: 'snapshot_token'
|
|
3121
|
+
};
|
|
3122
|
+
}
|
|
3123
|
+
if (decision) return {
|
|
3124
|
+
tenantId,
|
|
3125
|
+
policyId: decision.policy.id,
|
|
3126
|
+
fingerprint: decision.fingerprint,
|
|
3127
|
+
matchedBy: decision.matchedBy,
|
|
3128
|
+
countryCode: location.countryCode,
|
|
3129
|
+
regionCode: location.regionCode,
|
|
3130
|
+
jurisdiction,
|
|
3131
|
+
language,
|
|
3132
|
+
model: decision.policy.model,
|
|
3133
|
+
policyI18n: decision.policy.i18n,
|
|
3134
|
+
uiMode: decision.policy.ui?.mode,
|
|
3135
|
+
bannerUi: decision.policy.ui?.banner,
|
|
3136
|
+
dialogUi: decision.policy.ui?.dialog,
|
|
3137
|
+
categories: decision.policy.consent?.categories,
|
|
3138
|
+
preselectedCategories: decision.policy.consent?.preselectedCategories,
|
|
3139
|
+
proofConfig,
|
|
3140
|
+
dedupeKey: buildRuntimeDecisionDedupeKey({
|
|
3141
|
+
tenantId,
|
|
3142
|
+
fingerprint: decision.fingerprint,
|
|
3143
|
+
matchedBy: decision.matchedBy,
|
|
3144
|
+
countryCode: location.countryCode,
|
|
3145
|
+
regionCode: location.regionCode,
|
|
3146
|
+
jurisdiction,
|
|
3147
|
+
language
|
|
3148
|
+
}),
|
|
3149
|
+
source: 'write_time_fallback'
|
|
3150
|
+
};
|
|
3151
|
+
}
|
|
3152
|
+
function parseLanguageFromHeader(header) {
|
|
3153
|
+
if (!header) return;
|
|
3154
|
+
const firstLanguage = header.split(',')[0]?.split(';')[0]?.trim();
|
|
3155
|
+
if (!firstLanguage) return;
|
|
3156
|
+
return firstLanguage.split('-')[0]?.toLowerCase();
|
|
3157
|
+
}
|
|
3158
|
+
function isLegalDocumentType(type) {
|
|
3159
|
+
return 'privacy_policy' === type || 'terms_and_conditions' === type || 'dpa' === type;
|
|
3160
|
+
}
|
|
3161
|
+
function resolveSnapshotFailureMode(ctx) {
|
|
3162
|
+
return ctx.policySnapshot?.onValidationFailure ?? 'reject';
|
|
3163
|
+
}
|
|
3164
|
+
function buildSnapshotHttpException(reason) {
|
|
3165
|
+
switch(reason){
|
|
3166
|
+
case 'missing':
|
|
3167
|
+
return new http_exception_namespaceObject.HTTPException(409, {
|
|
3168
|
+
message: 'Policy snapshot token is required',
|
|
3169
|
+
cause: {
|
|
3170
|
+
code: 'POLICY_SNAPSHOT_REQUIRED'
|
|
3171
|
+
}
|
|
3172
|
+
});
|
|
3173
|
+
case 'expired':
|
|
3174
|
+
return new http_exception_namespaceObject.HTTPException(409, {
|
|
3175
|
+
message: 'Policy snapshot token has expired',
|
|
3176
|
+
cause: {
|
|
3177
|
+
code: 'POLICY_SNAPSHOT_EXPIRED'
|
|
3178
|
+
}
|
|
3179
|
+
});
|
|
3180
|
+
case 'malformed':
|
|
3181
|
+
case 'invalid':
|
|
3182
|
+
return new http_exception_namespaceObject.HTTPException(409, {
|
|
3183
|
+
message: 'Policy snapshot token is invalid',
|
|
3184
|
+
cause: {
|
|
3185
|
+
code: 'POLICY_SNAPSHOT_INVALID'
|
|
3186
|
+
}
|
|
3187
|
+
});
|
|
3188
|
+
default:
|
|
3189
|
+
{
|
|
3190
|
+
const _exhaustive = reason;
|
|
3191
|
+
throw new Error(`Unhandled policy snapshot verification failure reason: ${_exhaustive}`);
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
function buildLegalDocumentSnapshotHttpException(reason) {
|
|
3196
|
+
switch(reason){
|
|
3197
|
+
case 'missing':
|
|
3198
|
+
return new http_exception_namespaceObject.HTTPException(409, {
|
|
3199
|
+
message: 'Legal document snapshot token is required',
|
|
3200
|
+
cause: {
|
|
3201
|
+
code: 'LEGAL_DOCUMENT_SNAPSHOT_REQUIRED'
|
|
3202
|
+
}
|
|
3203
|
+
});
|
|
3204
|
+
case 'expired':
|
|
3205
|
+
return new http_exception_namespaceObject.HTTPException(409, {
|
|
3206
|
+
message: 'Legal document snapshot token has expired',
|
|
3207
|
+
cause: {
|
|
3208
|
+
code: 'LEGAL_DOCUMENT_SNAPSHOT_EXPIRED'
|
|
3209
|
+
}
|
|
3210
|
+
});
|
|
3211
|
+
case 'malformed':
|
|
3212
|
+
case 'invalid':
|
|
3213
|
+
return new http_exception_namespaceObject.HTTPException(409, {
|
|
3214
|
+
message: 'Legal document snapshot token is invalid',
|
|
3215
|
+
cause: {
|
|
3216
|
+
code: 'LEGAL_DOCUMENT_SNAPSHOT_INVALID'
|
|
3217
|
+
}
|
|
3218
|
+
});
|
|
3219
|
+
default:
|
|
3220
|
+
{
|
|
3221
|
+
const _exhaustive = reason;
|
|
3222
|
+
throw new Error(`Unhandled legal document snapshot verification failure reason: ${_exhaustive}`);
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
function buildLegalDocumentProofHttpException(message) {
|
|
3227
|
+
return new http_exception_namespaceObject.HTTPException(409, {
|
|
3228
|
+
message,
|
|
3229
|
+
cause: {
|
|
3230
|
+
code: 'LEGAL_DOCUMENT_PROOF_REQUIRED'
|
|
3231
|
+
}
|
|
3232
|
+
});
|
|
3233
|
+
}
|
|
2301
3234
|
const postSubjectHandler = async (c)=>{
|
|
2302
3235
|
const ctx = c.get('c15tContext');
|
|
2303
3236
|
const logger = ctx.logger;
|
|
@@ -2307,6 +3240,8 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2307
3240
|
const { type, subjectId, identityProvider, externalSubjectId, domain, metadata, givenAt: givenAtEpoch } = input;
|
|
2308
3241
|
const preferences = 'preferences' in input ? input.preferences : void 0;
|
|
2309
3242
|
const givenAt = new Date(givenAtEpoch);
|
|
3243
|
+
const rawConsentAction = 'consentAction' in input ? input.consentAction : void 0;
|
|
3244
|
+
let derivedConsentAction;
|
|
2310
3245
|
logger.debug('Request parameters', {
|
|
2311
3246
|
type,
|
|
2312
3247
|
subjectId,
|
|
@@ -2315,6 +3250,64 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2315
3250
|
domain
|
|
2316
3251
|
});
|
|
2317
3252
|
try {
|
|
3253
|
+
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.');
|
|
3254
|
+
const request = c.req.raw ?? new Request('https://c15t.local/subjects');
|
|
3255
|
+
const acceptLanguage = request.headers.get('accept-language');
|
|
3256
|
+
const requestLanguage = parseLanguageFromHeader(acceptLanguage);
|
|
3257
|
+
const location = await getLocation(request, ctx);
|
|
3258
|
+
const resolvedJurisdiction = getJurisdiction(location, ctx);
|
|
3259
|
+
const legalDocumentConsent = isLegalDocumentType(type);
|
|
3260
|
+
const runtimeSnapshotVerification = legalDocumentConsent ? {
|
|
3261
|
+
valid: false,
|
|
3262
|
+
reason: 'missing'
|
|
3263
|
+
} : await verifyPolicySnapshotToken({
|
|
3264
|
+
token: input.policySnapshotToken,
|
|
3265
|
+
options: ctx.policySnapshot,
|
|
3266
|
+
tenantId: ctx.tenantId
|
|
3267
|
+
});
|
|
3268
|
+
const legalDocumentSnapshotVerification = legalDocumentConsent ? await verifyLegalDocumentSnapshotToken({
|
|
3269
|
+
token: input.documentSnapshotToken,
|
|
3270
|
+
options: ctx.legalDocumentSnapshot,
|
|
3271
|
+
tenantId: ctx.tenantId
|
|
3272
|
+
}) : {
|
|
3273
|
+
valid: false,
|
|
3274
|
+
reason: 'missing'
|
|
3275
|
+
};
|
|
3276
|
+
const hasValidSnapshot = runtimeSnapshotVerification.valid;
|
|
3277
|
+
const snapshotPayload = runtimeSnapshotVerification.valid ? runtimeSnapshotVerification.payload : null;
|
|
3278
|
+
const shouldRequireSnapshot = !legalDocumentConsent && !!ctx.policySnapshot?.signingKey && 'reject' === resolveSnapshotFailureMode(ctx);
|
|
3279
|
+
if (!hasValidSnapshot && shouldRequireSnapshot) throw buildSnapshotHttpException(runtimeSnapshotVerification.reason);
|
|
3280
|
+
const shouldRequireLegalDocumentSnapshot = legalDocumentConsent && !!ctx.legalDocumentSnapshot?.signingKey;
|
|
3281
|
+
if (shouldRequireLegalDocumentSnapshot && !legalDocumentSnapshotVerification.valid) throw buildLegalDocumentSnapshotHttpException(legalDocumentSnapshotVerification.reason);
|
|
3282
|
+
const resolvedPolicyDecision = hasValidSnapshot ? void 0 : legalDocumentConsent ? void 0 : await resolvePolicyDecision({
|
|
3283
|
+
policies: ctx.policyPacks,
|
|
3284
|
+
countryCode: location.countryCode,
|
|
3285
|
+
regionCode: location.regionCode,
|
|
3286
|
+
jurisdiction: resolvedJurisdiction,
|
|
3287
|
+
iabEnabled: ctx.iab?.enabled === true
|
|
3288
|
+
});
|
|
3289
|
+
const effectivePolicy = hasValidSnapshot && snapshotPayload ? {
|
|
3290
|
+
id: snapshotPayload.policyId,
|
|
3291
|
+
model: snapshotPayload.model,
|
|
3292
|
+
i18n: snapshotPayload.policyI18n,
|
|
3293
|
+
consent: {
|
|
3294
|
+
expiryDays: snapshotPayload.expiryDays,
|
|
3295
|
+
scopeMode: snapshotPayload.scopeMode,
|
|
3296
|
+
categories: snapshotPayload.categories,
|
|
3297
|
+
preselectedCategories: snapshotPayload.preselectedCategories,
|
|
3298
|
+
gpc: snapshotPayload.gpc
|
|
3299
|
+
},
|
|
3300
|
+
ui: {
|
|
3301
|
+
mode: snapshotPayload.uiMode,
|
|
3302
|
+
banner: snapshotPayload.bannerUi,
|
|
3303
|
+
dialog: snapshotPayload.dialogUi
|
|
3304
|
+
},
|
|
3305
|
+
proof: snapshotPayload.proofConfig
|
|
3306
|
+
} : resolvedPolicyDecision?.policy;
|
|
3307
|
+
const effectiveModel = effectivePolicy?.model ?? ('opt-in' === input.jurisdictionModel || 'opt-out' === input.jurisdictionModel || 'iab' === input.jurisdictionModel ? input.jurisdictionModel : void 0);
|
|
3308
|
+
if ('all' === rawConsentAction) derivedConsentAction = 'accept_all';
|
|
3309
|
+
else if ('necessary' === rawConsentAction) derivedConsentAction = 'opt-out' === effectiveModel ? 'opt_out' : 'reject_all';
|
|
3310
|
+
else if ('custom' === rawConsentAction) derivedConsentAction = 'custom';
|
|
2318
3311
|
const subject = await registry.findOrCreateSubject({
|
|
2319
3312
|
subjectId,
|
|
2320
3313
|
externalSubjectId,
|
|
@@ -2341,8 +3334,63 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2341
3334
|
});
|
|
2342
3335
|
let policyId;
|
|
2343
3336
|
let purposeIds = [];
|
|
3337
|
+
let appliedPreferences;
|
|
2344
3338
|
const inputPolicyId = 'policyId' in input ? input.policyId : void 0;
|
|
2345
|
-
|
|
3339
|
+
const inputPolicyHash = 'policyHash' in input ? input.policyHash : void 0;
|
|
3340
|
+
if (legalDocumentConsent && legalDocumentSnapshotVerification.valid) {
|
|
3341
|
+
if (legalDocumentSnapshotVerification.payload.type !== type) throw buildLegalDocumentSnapshotHttpException('invalid');
|
|
3342
|
+
const effectiveDate = new Date(legalDocumentSnapshotVerification.payload.effectiveDate);
|
|
3343
|
+
if (Number.isNaN(effectiveDate.getTime())) throw buildLegalDocumentSnapshotHttpException('invalid');
|
|
3344
|
+
const documentPolicy = await registry.findOrCreateLegalDocumentPolicy({
|
|
3345
|
+
type,
|
|
3346
|
+
version: legalDocumentSnapshotVerification.payload.version,
|
|
3347
|
+
hash: legalDocumentSnapshotVerification.payload.hash,
|
|
3348
|
+
effectiveDate
|
|
3349
|
+
});
|
|
3350
|
+
policyId = documentPolicy.id;
|
|
3351
|
+
} else if (legalDocumentConsent) {
|
|
3352
|
+
if (!ctx.legalDocumentSnapshot?.signingKey && !inputPolicyId && !inputPolicyHash) throw buildLegalDocumentProofHttpException('Legal document consent requires policyId or policyHash when snapshot verification is disabled');
|
|
3353
|
+
if (inputPolicyId) {
|
|
3354
|
+
policyId = inputPolicyId;
|
|
3355
|
+
const policy = await registry.findConsentPolicyById(inputPolicyId);
|
|
3356
|
+
if (!policy) throw new http_exception_namespaceObject.HTTPException(404, {
|
|
3357
|
+
message: 'Policy not found',
|
|
3358
|
+
cause: {
|
|
3359
|
+
code: 'POLICY_NOT_FOUND',
|
|
3360
|
+
policyId,
|
|
3361
|
+
type
|
|
3362
|
+
}
|
|
3363
|
+
});
|
|
3364
|
+
if (!policy.isActive) throw new http_exception_namespaceObject.HTTPException(400, {
|
|
3365
|
+
message: 'Policy is inactive',
|
|
3366
|
+
cause: {
|
|
3367
|
+
code: 'POLICY_INACTIVE',
|
|
3368
|
+
policyId,
|
|
3369
|
+
type
|
|
3370
|
+
}
|
|
3371
|
+
});
|
|
3372
|
+
} else if (inputPolicyHash) {
|
|
3373
|
+
const policy = await registry.findLegalDocumentPolicyByHash(type, inputPolicyHash);
|
|
3374
|
+
if (!policy) throw new http_exception_namespaceObject.HTTPException(404, {
|
|
3375
|
+
message: 'Policy not found',
|
|
3376
|
+
cause: {
|
|
3377
|
+
code: 'POLICY_NOT_FOUND',
|
|
3378
|
+
type,
|
|
3379
|
+
policyHash: inputPolicyHash
|
|
3380
|
+
}
|
|
3381
|
+
});
|
|
3382
|
+
if (!policy.isActive) throw new http_exception_namespaceObject.HTTPException(400, {
|
|
3383
|
+
message: 'Policy is inactive',
|
|
3384
|
+
cause: {
|
|
3385
|
+
code: 'POLICY_INACTIVE',
|
|
3386
|
+
policyId: policy.id,
|
|
3387
|
+
type,
|
|
3388
|
+
policyHash: inputPolicyHash
|
|
3389
|
+
}
|
|
3390
|
+
});
|
|
3391
|
+
policyId = policy.id;
|
|
3392
|
+
}
|
|
3393
|
+
} else if (inputPolicyId) {
|
|
2346
3394
|
policyId = inputPolicyId;
|
|
2347
3395
|
const policy = await registry.findConsentPolicyById(inputPolicyId);
|
|
2348
3396
|
if (!policy) throw new http_exception_namespaceObject.HTTPException(404, {
|
|
@@ -2373,20 +3421,73 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2373
3421
|
policyId = policy.id;
|
|
2374
3422
|
}
|
|
2375
3423
|
if (preferences) {
|
|
2376
|
-
const
|
|
3424
|
+
const allowedCategories = effectivePolicy?.consent?.categories;
|
|
3425
|
+
const effectiveScopeMode = effectivePolicy?.consent?.scopeMode ?? 'permissive';
|
|
3426
|
+
const hasWildcardCategoryScope = allowedCategories?.includes('*') === true;
|
|
3427
|
+
const appliedPreferenceEntries = Object.entries(preferences);
|
|
3428
|
+
let filteredAppliedPreferenceEntries = appliedPreferenceEntries;
|
|
3429
|
+
if (allowedCategories && allowedCategories.length > 0 && !hasWildcardCategoryScope) {
|
|
3430
|
+
const disallowed = appliedPreferenceEntries.map(([purpose])=>purpose).filter((purpose)=>!allowedCategories.includes(purpose));
|
|
3431
|
+
filteredAppliedPreferenceEntries = appliedPreferenceEntries.filter(([purpose])=>allowedCategories.includes(purpose));
|
|
3432
|
+
if (disallowed.length > 0 && 'strict' === effectiveScopeMode) throw new http_exception_namespaceObject.HTTPException(400, {
|
|
3433
|
+
message: 'Preferences include categories not allowed by policy',
|
|
3434
|
+
cause: {
|
|
3435
|
+
code: 'PURPOSE_NOT_ALLOWED',
|
|
3436
|
+
disallowed
|
|
3437
|
+
}
|
|
3438
|
+
});
|
|
3439
|
+
}
|
|
3440
|
+
appliedPreferences = Object.fromEntries(filteredAppliedPreferenceEntries);
|
|
3441
|
+
const filteredConsentedPurposeCodes = filteredAppliedPreferenceEntries.filter(([_, isConsented])=>isConsented).map(([purposeCode])=>purposeCode);
|
|
2377
3442
|
logger.debug('Consented purposes', {
|
|
2378
|
-
consentedPurposes
|
|
3443
|
+
consentedPurposes: filteredConsentedPurposeCodes
|
|
2379
3444
|
});
|
|
2380
|
-
const purposesRaw = await Promise.all(
|
|
3445
|
+
const purposesRaw = await Promise.all(filteredConsentedPurposeCodes.map((purposeCode)=>registry.findOrCreateConsentPurposeByCode(purposeCode)));
|
|
2381
3446
|
const purposes = purposesRaw.map((purpose)=>purpose?.id ?? null).filter((id)=>Boolean(id));
|
|
2382
3447
|
logger.debug('Filtered purposes', {
|
|
2383
3448
|
purposes
|
|
2384
3449
|
});
|
|
2385
3450
|
if (0 === purposes.length) logger.warn('No valid purpose IDs found after filtering. Using empty list.', {
|
|
2386
|
-
consentedPurposes
|
|
3451
|
+
consentedPurposes: filteredConsentedPurposeCodes
|
|
2387
3452
|
});
|
|
2388
3453
|
purposeIds = purposes;
|
|
2389
3454
|
}
|
|
3455
|
+
if (!policyId) throw new http_exception_namespaceObject.HTTPException(500, {
|
|
3456
|
+
message: 'Failed to resolve policy',
|
|
3457
|
+
cause: {
|
|
3458
|
+
code: 'POLICY_RESOLUTION_FAILED',
|
|
3459
|
+
type
|
|
3460
|
+
}
|
|
3461
|
+
});
|
|
3462
|
+
const expiryDays = effectivePolicy?.consent?.expiryDays;
|
|
3463
|
+
const validUntil = 'number' == typeof expiryDays && Number.isFinite(expiryDays) ? new Date(givenAt.getTime() + 86400000 * Math.max(0, expiryDays)) : void 0;
|
|
3464
|
+
const proofConfig = effectivePolicy?.proof;
|
|
3465
|
+
const shouldStoreIp = proofConfig?.storeIp ?? true;
|
|
3466
|
+
const shouldStoreUserAgent = proofConfig?.storeUserAgent ?? true;
|
|
3467
|
+
const shouldStoreLanguage = proofConfig?.storeLanguage ?? false;
|
|
3468
|
+
const effectiveLanguage = (snapshotPayload?.language && hasValidSnapshot ? snapshotPayload.language : requestLanguage) ?? void 0;
|
|
3469
|
+
const metadataWithPolicy = {
|
|
3470
|
+
...metadata ?? {},
|
|
3471
|
+
...shouldStoreLanguage && effectiveLanguage ? {
|
|
3472
|
+
policyLanguage: effectiveLanguage
|
|
3473
|
+
} : {}
|
|
3474
|
+
};
|
|
3475
|
+
const effectiveJurisdiction = hasValidSnapshot && snapshotPayload ? snapshotPayload.jurisdiction : resolvedJurisdiction;
|
|
3476
|
+
const decisionPayload = buildDecisionPayload({
|
|
3477
|
+
tenantId: ctx.tenantId,
|
|
3478
|
+
snapshot: hasValidSnapshot && snapshotPayload ? {
|
|
3479
|
+
valid: true,
|
|
3480
|
+
payload: snapshotPayload
|
|
3481
|
+
} : null,
|
|
3482
|
+
decision: resolvedPolicyDecision,
|
|
3483
|
+
location: {
|
|
3484
|
+
countryCode: location.countryCode,
|
|
3485
|
+
regionCode: location.regionCode
|
|
3486
|
+
},
|
|
3487
|
+
jurisdiction: resolvedJurisdiction,
|
|
3488
|
+
language: effectiveLanguage,
|
|
3489
|
+
proofConfig
|
|
3490
|
+
});
|
|
2390
3491
|
const existingConsent = await db.findFirst('consent', {
|
|
2391
3492
|
where: (b)=>b.and(b('subjectId', '=', subject.id), b('domainId', '=', domainRecord.id), b('policyId', '=', policyId), b('givenAt', '=', givenAt))
|
|
2392
3493
|
});
|
|
@@ -2401,6 +3502,7 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2401
3502
|
domain: domainRecord.name,
|
|
2402
3503
|
type,
|
|
2403
3504
|
metadata,
|
|
3505
|
+
appliedPreferences,
|
|
2404
3506
|
uiSource: input.uiSource,
|
|
2405
3507
|
givenAt: existingConsent.givenAt
|
|
2406
3508
|
});
|
|
@@ -2412,6 +3514,42 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2412
3514
|
policyId,
|
|
2413
3515
|
purposeIds
|
|
2414
3516
|
});
|
|
3517
|
+
const runtimePolicyDecision = decisionPayload ? await tx.findFirst('runtimePolicyDecision', {
|
|
3518
|
+
where: (b)=>b('dedupeKey', '=', decisionPayload.dedupeKey)
|
|
3519
|
+
}) ?? await tx.create('runtimePolicyDecision', {
|
|
3520
|
+
id: `rpd_${crypto.randomUUID().replaceAll('-', '')}`,
|
|
3521
|
+
tenantId: decisionPayload.tenantId,
|
|
3522
|
+
policyId: decisionPayload.policyId,
|
|
3523
|
+
fingerprint: decisionPayload.fingerprint,
|
|
3524
|
+
matchedBy: decisionPayload.matchedBy,
|
|
3525
|
+
countryCode: decisionPayload.countryCode,
|
|
3526
|
+
regionCode: decisionPayload.regionCode,
|
|
3527
|
+
jurisdiction: decisionPayload.jurisdiction,
|
|
3528
|
+
language: decisionPayload.language,
|
|
3529
|
+
model: decisionPayload.model,
|
|
3530
|
+
policyI18n: decisionPayload.policyI18n ? {
|
|
3531
|
+
json: decisionPayload.policyI18n
|
|
3532
|
+
} : void 0,
|
|
3533
|
+
uiMode: decisionPayload.uiMode,
|
|
3534
|
+
bannerUi: decisionPayload.bannerUi ? {
|
|
3535
|
+
json: decisionPayload.bannerUi
|
|
3536
|
+
} : void 0,
|
|
3537
|
+
dialogUi: decisionPayload.dialogUi ? {
|
|
3538
|
+
json: decisionPayload.dialogUi
|
|
3539
|
+
} : void 0,
|
|
3540
|
+
categories: decisionPayload.categories ? {
|
|
3541
|
+
json: decisionPayload.categories
|
|
3542
|
+
} : void 0,
|
|
3543
|
+
preselectedCategories: decisionPayload.preselectedCategories ? {
|
|
3544
|
+
json: decisionPayload.preselectedCategories
|
|
3545
|
+
} : void 0,
|
|
3546
|
+
proofConfig: decisionPayload.proofConfig ? {
|
|
3547
|
+
json: decisionPayload.proofConfig
|
|
3548
|
+
} : void 0,
|
|
3549
|
+
dedupeKey: decisionPayload.dedupeKey
|
|
3550
|
+
}).catch(async ()=>tx.findFirst('runtimePolicyDecision', {
|
|
3551
|
+
where: (b)=>b('dedupeKey', '=', decisionPayload.dedupeKey)
|
|
3552
|
+
})) : void 0;
|
|
2415
3553
|
const consentRecord = await tx.create('consent', {
|
|
2416
3554
|
id: await utils_generateUniqueId(tx, 'consent', ctx),
|
|
2417
3555
|
subjectId: subject.id,
|
|
@@ -2420,16 +3558,20 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2420
3558
|
purposeIds: {
|
|
2421
3559
|
json: purposeIds
|
|
2422
3560
|
},
|
|
2423
|
-
metadata:
|
|
2424
|
-
json:
|
|
3561
|
+
metadata: Object.keys(metadataWithPolicy).length > 0 ? {
|
|
3562
|
+
json: metadataWithPolicy
|
|
2425
3563
|
} : void 0,
|
|
2426
|
-
ipAddress: ctx.ipAddress,
|
|
2427
|
-
userAgent: ctx.userAgent,
|
|
2428
|
-
jurisdiction:
|
|
2429
|
-
jurisdictionModel:
|
|
3564
|
+
ipAddress: shouldStoreIp ? ctx.ipAddress : null,
|
|
3565
|
+
userAgent: shouldStoreUserAgent ? ctx.userAgent : null,
|
|
3566
|
+
jurisdiction: effectiveJurisdiction,
|
|
3567
|
+
jurisdictionModel: effectiveModel,
|
|
2430
3568
|
tcString: input.tcString,
|
|
2431
3569
|
uiSource: input.uiSource,
|
|
2432
|
-
|
|
3570
|
+
consentAction: derivedConsentAction,
|
|
3571
|
+
givenAt,
|
|
3572
|
+
validUntil,
|
|
3573
|
+
runtimePolicyDecisionId: runtimePolicyDecision?.id,
|
|
3574
|
+
runtimePolicySource: decisionPayload?.source
|
|
2433
3575
|
});
|
|
2434
3576
|
logger.debug('Created consent', {
|
|
2435
3577
|
consentRecord: consentRecord.id
|
|
@@ -2448,7 +3590,7 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2448
3590
|
});
|
|
2449
3591
|
const metrics = getMetrics();
|
|
2450
3592
|
if (metrics) {
|
|
2451
|
-
const jurisdiction =
|
|
3593
|
+
const jurisdiction = effectiveJurisdiction;
|
|
2452
3594
|
metrics.recordConsentCreated({
|
|
2453
3595
|
type,
|
|
2454
3596
|
jurisdiction
|
|
@@ -2470,6 +3612,7 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2470
3612
|
domain: domainRecord.name,
|
|
2471
3613
|
type,
|
|
2472
3614
|
metadata,
|
|
3615
|
+
appliedPreferences,
|
|
2473
3616
|
uiSource: input.uiSource,
|
|
2474
3617
|
givenAt: result.consent.givenAt
|
|
2475
3618
|
});
|
|
@@ -2479,6 +3622,12 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2479
3622
|
errorType: error instanceof Error ? error.constructor.name : typeof error
|
|
2480
3623
|
});
|
|
2481
3624
|
if (error instanceof http_exception_namespaceObject.HTTPException) throw error;
|
|
3625
|
+
if (error instanceof LegalDocumentPolicyConflictError) throw new http_exception_namespaceObject.HTTPException(409, {
|
|
3626
|
+
message: error.message,
|
|
3627
|
+
cause: {
|
|
3628
|
+
code: 'LEGAL_DOCUMENT_RELEASE_CONFLICT'
|
|
3629
|
+
}
|
|
3630
|
+
});
|
|
2482
3631
|
throw new http_exception_namespaceObject.HTTPException(500, {
|
|
2483
3632
|
message: 'Internal server error',
|
|
2484
3633
|
cause: {
|
|
@@ -2491,11 +3640,7 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2491
3640
|
const app = new external_hono_namespaceObject.Hono();
|
|
2492
3641
|
app.get('/:id', (0, external_hono_openapi_namespaceObject.describeRoute)({
|
|
2493
3642
|
summary: 'Get subject consent status',
|
|
2494
|
-
description:
|
|
2495
|
-
|
|
2496
|
-
**Query:** \`type\` – Filter by consent type(s), comma-separated (e.g. \`privacy_policy,cookie_banner\`).
|
|
2497
|
-
|
|
2498
|
-
**Response:** \`subject\`, \`consents\` (matching filter), \`isValid\` (valid consent for requested type(s)).`,
|
|
3643
|
+
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)).",
|
|
2499
3644
|
tags: [
|
|
2500
3645
|
'Subject',
|
|
2501
3646
|
'Consent'
|
|
@@ -2516,12 +3661,7 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2516
3661
|
}), (0, external_hono_openapi_namespaceObject.validator)('param', schema_.getSubjectInputSchema), getSubjectHandler);
|
|
2517
3662
|
app.post('/', (0, external_hono_openapi_namespaceObject.describeRoute)({
|
|
2518
3663
|
summary: 'Record consent for a subject',
|
|
2519
|
-
description:
|
|
2520
|
-
|
|
2521
|
-
**Request body by \`type\`:**
|
|
2522
|
-
- \`cookie_banner\` – Requires \`preferences\` object
|
|
2523
|
-
- \`privacy_policy\`, \`dpa\`, \`terms_and_conditions\` – Optional \`policyId\`
|
|
2524
|
-
- \`marketing_communications\`, \`age_verification\`, \`other\` – Optional \`preferences\``,
|
|
3664
|
+
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`",
|
|
2525
3665
|
tags: [
|
|
2526
3666
|
'Subject',
|
|
2527
3667
|
'Consent'
|
|
@@ -2593,6 +3733,86 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2593
3733
|
return app;
|
|
2594
3734
|
};
|
|
2595
3735
|
var define_config = __webpack_require__("./src/define-config.ts");
|
|
3736
|
+
const DEFAULT_FALLBACK_POLICY_INPUT = {
|
|
3737
|
+
id: 'world_no_banner',
|
|
3738
|
+
isDefault: true,
|
|
3739
|
+
model: 'none',
|
|
3740
|
+
uiMode: 'none'
|
|
3741
|
+
};
|
|
3742
|
+
function mergeMatch(input) {
|
|
3743
|
+
return types_namespaceObject.policyMatchers.merge(input.countries?.length ? types_namespaceObject.policyMatchers.countries(input.countries) : {}, input.regions?.length ? types_namespaceObject.policyMatchers.regions(input.regions) : {}, input.isDefault ? types_namespaceObject.policyMatchers["default"]() : {});
|
|
3744
|
+
}
|
|
3745
|
+
function compactUiSurface(value) {
|
|
3746
|
+
if (!value) return;
|
|
3747
|
+
return (0, schema_.compactDefined)({
|
|
3748
|
+
allowedActions: (0, schema_.dedupeTrimmedStrings)(value.allowedActions),
|
|
3749
|
+
primaryActions: value.primaryActions,
|
|
3750
|
+
layout: value.layout,
|
|
3751
|
+
direction: value.direction,
|
|
3752
|
+
uiProfile: value.uiProfile,
|
|
3753
|
+
scrollLock: value.scrollLock
|
|
3754
|
+
});
|
|
3755
|
+
}
|
|
3756
|
+
function buildPolicyConfig(input) {
|
|
3757
|
+
const categories = (0, schema_.dedupeTrimmedStrings)(input.categories);
|
|
3758
|
+
const preselectedCategories = (0, schema_.dedupeTrimmedStrings)(input.preselectedCategories);
|
|
3759
|
+
return {
|
|
3760
|
+
id: input.id,
|
|
3761
|
+
match: mergeMatch(input),
|
|
3762
|
+
i18n: input.i18n,
|
|
3763
|
+
consent: (0, schema_.compactDefined)({
|
|
3764
|
+
model: input.model,
|
|
3765
|
+
expiryDays: input.expiryDays,
|
|
3766
|
+
scopeMode: input.scopeMode,
|
|
3767
|
+
categories,
|
|
3768
|
+
preselectedCategories,
|
|
3769
|
+
gpc: input.gpc
|
|
3770
|
+
}),
|
|
3771
|
+
ui: (0, schema_.compactDefined)({
|
|
3772
|
+
mode: input.uiMode,
|
|
3773
|
+
banner: compactUiSurface(input.banner),
|
|
3774
|
+
dialog: compactUiSurface(input.dialog)
|
|
3775
|
+
}),
|
|
3776
|
+
proof: (0, schema_.compactDefined)({
|
|
3777
|
+
storeIp: input.proof?.storeIp,
|
|
3778
|
+
storeUserAgent: input.proof?.storeUserAgent,
|
|
3779
|
+
storeLanguage: input.proof?.storeLanguage
|
|
3780
|
+
})
|
|
3781
|
+
};
|
|
3782
|
+
}
|
|
3783
|
+
function buildPolicyPack(inputs) {
|
|
3784
|
+
return inputs.map((input)=>buildPolicyConfig(input));
|
|
3785
|
+
}
|
|
3786
|
+
function buildPolicyPackWithDefault(inputs, defaultPolicy) {
|
|
3787
|
+
const pack = buildPolicyPack(inputs);
|
|
3788
|
+
const hasDefault = pack.some((policy)=>policy.match.isDefault);
|
|
3789
|
+
if (hasDefault) return pack;
|
|
3790
|
+
const fallbackInput = defaultPolicy ? {
|
|
3791
|
+
...defaultPolicy,
|
|
3792
|
+
isDefault: true,
|
|
3793
|
+
countries: void 0,
|
|
3794
|
+
regions: void 0
|
|
3795
|
+
} : DEFAULT_FALLBACK_POLICY_INPUT;
|
|
3796
|
+
return [
|
|
3797
|
+
...pack,
|
|
3798
|
+
buildPolicyConfig(fallbackInput)
|
|
3799
|
+
];
|
|
3800
|
+
}
|
|
3801
|
+
function composePacks(...packs) {
|
|
3802
|
+
const seen = new Set();
|
|
3803
|
+
const result = [];
|
|
3804
|
+
for (const pack of packs)for (const policy of pack)if (!seen.has(policy.id)) {
|
|
3805
|
+
seen.add(policy.id);
|
|
3806
|
+
result.push(policy);
|
|
3807
|
+
}
|
|
3808
|
+
return result;
|
|
3809
|
+
}
|
|
3810
|
+
const policyBuilder = {
|
|
3811
|
+
create: buildPolicyConfig,
|
|
3812
|
+
createPack: buildPolicyPack,
|
|
3813
|
+
createPackWithDefault: buildPolicyPackWithDefault,
|
|
3814
|
+
composePacks
|
|
3815
|
+
};
|
|
2596
3816
|
const c15tInstance = (options)=>{
|
|
2597
3817
|
const context = init(options);
|
|
2598
3818
|
const logger = (0, logger_namespaceObject.createLogger)(options.logger);
|
|
@@ -2605,7 +3825,7 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2605
3825
|
app.use('*', async (c, next)=>{
|
|
2606
3826
|
const request = c.req.raw;
|
|
2607
3827
|
const startTime = Date.now();
|
|
2608
|
-
const apiKeyAuthenticated = validateRequestAuth(request.headers, options.
|
|
3828
|
+
const apiKeyAuthenticated = validateRequestAuth(request.headers, options.apiKeys);
|
|
2609
3829
|
const enrichedContext = {
|
|
2610
3830
|
...context,
|
|
2611
3831
|
ipAddress: getIpAddress(request, options),
|
|
@@ -2674,16 +3894,16 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2674
3894
|
}));
|
|
2675
3895
|
const publicSpecUrl = `${basePath}${openApiConfig.specPath}`.replace(/\/+/g, '/');
|
|
2676
3896
|
app.get(openApiConfig.docsPath, (0, hono_api_reference_namespaceObject.apiReference)({
|
|
2677
|
-
|
|
2678
|
-
url: publicSpecUrl
|
|
2679
|
-
},
|
|
3897
|
+
url: publicSpecUrl,
|
|
2680
3898
|
pageTitle: `${options.appName || 'c15t API'} Documentation`
|
|
2681
3899
|
}));
|
|
2682
3900
|
}
|
|
2683
3901
|
app.route('/init', createInitRoute(options));
|
|
3902
|
+
app.route('/legal-documents', createLegalDocumentRoutes());
|
|
2684
3903
|
app.route('/subjects', createSubjectRoutes());
|
|
2685
3904
|
app.route('/consents', createConsentRoutes());
|
|
2686
3905
|
app.route('/status', createStatusRoute());
|
|
3906
|
+
app.route('/', createStatusRoute());
|
|
2687
3907
|
app.onError((err, c)=>{
|
|
2688
3908
|
const ctx = c.get('c15tContext');
|
|
2689
3909
|
const log = ctx?.logger || logger;
|
|
@@ -2769,12 +3989,28 @@ Use for health checks, load balancer probes, and debugging. Performs a lightweig
|
|
|
2769
3989
|
};
|
|
2770
3990
|
};
|
|
2771
3991
|
})();
|
|
3992
|
+
exports.EEA_COUNTRY_CODES = __webpack_exports__.EEA_COUNTRY_CODES;
|
|
3993
|
+
exports.EU_COUNTRY_CODES = __webpack_exports__.EU_COUNTRY_CODES;
|
|
3994
|
+
exports.POLICY_MATCH_DATASET_VERSION = __webpack_exports__.POLICY_MATCH_DATASET_VERSION;
|
|
3995
|
+
exports.UK_COUNTRY_CODES = __webpack_exports__.UK_COUNTRY_CODES;
|
|
2772
3996
|
exports.c15tInstance = __webpack_exports__.c15tInstance;
|
|
2773
3997
|
exports.defineConfig = __webpack_exports__.defineConfig;
|
|
3998
|
+
exports.inspectPolicies = __webpack_exports__.inspectPolicies;
|
|
3999
|
+
exports.policyBuilder = __webpack_exports__.policyBuilder;
|
|
4000
|
+
exports.policyMatchers = __webpack_exports__.policyMatchers;
|
|
4001
|
+
exports.policyPackPresets = __webpack_exports__.policyPackPresets;
|
|
2774
4002
|
exports.version = __webpack_exports__.version;
|
|
2775
4003
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
4004
|
+
"EEA_COUNTRY_CODES",
|
|
4005
|
+
"EU_COUNTRY_CODES",
|
|
4006
|
+
"POLICY_MATCH_DATASET_VERSION",
|
|
4007
|
+
"UK_COUNTRY_CODES",
|
|
2776
4008
|
"c15tInstance",
|
|
2777
4009
|
"defineConfig",
|
|
4010
|
+
"inspectPolicies",
|
|
4011
|
+
"policyBuilder",
|
|
4012
|
+
"policyMatchers",
|
|
4013
|
+
"policyPackPresets",
|
|
2778
4014
|
"version"
|
|
2779
4015
|
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
2780
4016
|
Object.defineProperty(exports, '__esModule', {
|