@c15t/backend 2.0.0-rc.4 → 2.0.0-rc.5
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/dist/core.cjs +830 -74
- package/dist/core.js +807 -75
- package/dist/db/schema.cjs +37 -0
- package/dist/db/schema.js +33 -2
- package/dist/edge.cjs +1106 -0
- package/dist/edge.js +1069 -0
- package/dist/router.cjs +613 -64
- package/dist/router.js +613 -64
- 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 +1 -2
- 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 → dist-types}/db/registry/consent-policy.d.ts +0 -1
- 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 → dist-types}/db/registry/index.d.ts +22 -2
- package/dist-types/db/registry/runtime-policy-decision.d.ts +60 -0
- package/{dist → dist-types}/db/registry/subject.d.ts +0 -1
- package/{dist → dist-types}/db/registry/types.d.ts +1 -2
- 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 -1
- package/{dist → dist-types}/db/schema/1.0.0/subject.d.ts +0 -1
- 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 +1 -2
- 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 +5 -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 +432 -17
- 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 -2
- package/{dist → dist-types}/db/schema/index.d.ts +862 -33
- package/{dist → dist-types}/db/tenant-scope.d.ts +0 -1
- package/{dist → dist-types}/define-config.d.ts +0 -1
- package/dist-types/edge/index.d.ts +5 -0
- package/dist-types/edge/init-handler.d.ts +38 -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 +4 -5
- 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/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 +0 -1
- 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 +0 -1
- package/{dist → dist-types}/handlers/subject/patch.handler.d.ts +0 -1
- package/{dist → dist-types}/handlers/subject/post.handler.d.ts +12 -1
- package/{dist → dist-types}/handlers/utils/consent-enrichment.d.ts +0 -1
- package/{dist → dist-types}/init.d.ts +0 -1
- 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 +1 -2
- 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/{src/routes/index.ts → dist-types/routes/index.d.ts} +0 -1
- package/{dist → dist-types}/routes/init.d.ts +0 -1
- 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 → dist-types}/types/index.d.ts +110 -6
- package/dist-types/utils/background.d.ts +6 -0
- package/{dist → dist-types}/utils/create-telemetry-options.d.ts +0 -1
- 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 +0 -1
- package/{dist → dist-types}/utils/logger.d.ts +1 -2
- 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 +197 -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 +248 -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 +33 -19
- package/.turbo/turbo-build.log +0 -49
- package/CHANGELOG.md +0 -123
- 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.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.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.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 -26
- 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 +0 -10
- 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.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 -369
- 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 -371
- package/src/db/registry/subject.ts +0 -126
- 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 -11
- 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 -28
- 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 -13
- package/src/db/schema/index.ts +0 -15
- package/src/db/tenant-scope.test.ts +0 -747
- package/src/db/tenant-scope.ts +0 -103
- package/src/define-config.ts +0 -19
- 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 -69
- 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 -92
- package/src/handlers/subject/list.handler.ts +0 -92
- package/src/handlers/subject/patch.handler.ts +0 -119
- package/src/handlers/subject/post.handler.test.ts +0 -294
- package/src/handlers/subject/post.handler.ts +0 -268
- 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 -122
- package/src/init.ts +0 -88
- 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 -193
- 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/init.ts +0 -105
- 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 -391
- package/src/utils/create-telemetry-options.test.ts +0 -286
- 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 -183
- package/src/utils/instrumentation.ts +0 -194
- package/src/utils/logger.ts +0 -41
- package/src/utils/metrics.test.ts +0 -311
- package/src/utils/metrics.ts +0 -402
- package/src/utils/telemetry-pii.test.ts +0 -323
- package/src/version.ts +0 -2
- package/tsconfig.json +0 -11
- package/vitest.config.ts +0 -28
- /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.js
CHANGED
|
@@ -5,12 +5,14 @@ import { Hono } from "hono";
|
|
|
5
5
|
import { cors } from "hono/cors";
|
|
6
6
|
import { HTTPException } from "hono/http-exception";
|
|
7
7
|
import { describeRoute, openAPIRouteHandler, resolver, validator } from "hono-openapi";
|
|
8
|
+
import { EEA_COUNTRY_CODES, EU_COUNTRY_CODES, POLICY_MATCH_DATASET_VERSION, UK_COUNTRY_CODES, inspectPolicies, policyMatchers, resolvePolicyDecision, validatePolicyI18nConfig } from "@c15t/schema/types";
|
|
9
|
+
import { deepMergeTranslations, selectLanguage } from "@c15t/translations";
|
|
10
|
+
import { baseTranslations } from "@c15t/translations/all";
|
|
8
11
|
import base_x from "base-x";
|
|
9
12
|
import { fumadb } from "fumadb";
|
|
10
13
|
import { column, idColumn, schema, table as schema_table } from "fumadb/schema";
|
|
11
|
-
import { checkConsentOutputSchema, checkConsentQuerySchema, getSubjectInputSchema, getSubjectOutputSchema, initOutputSchema, listSubjectsOutputSchema, listSubjectsQuerySchema, patchSubjectOutputSchema, postSubjectInputSchema, postSubjectOutputSchema, statusOutputSchema, subjectIdSchema } from "@c15t/schema";
|
|
12
|
-
import {
|
|
13
|
-
import { baseTranslations } from "@c15t/translations/all";
|
|
14
|
+
import { checkConsentOutputSchema, checkConsentQuerySchema, compactDefined, dedupeTrimmedStrings, getSubjectInputSchema, getSubjectOutputSchema, initOutputSchema, listSubjectsOutputSchema, listSubjectsQuerySchema, patchSubjectOutputSchema, policyPackPresets, postSubjectInputSchema, postSubjectOutputSchema, statusOutputSchema, subjectIdSchema } from "@c15t/schema";
|
|
15
|
+
import { SignJWT, errors, jwtVerify } from "jose";
|
|
14
16
|
import { object, optional, string } from "valibot";
|
|
15
17
|
function extractBearerToken(authHeader) {
|
|
16
18
|
if (!authHeader) return null;
|
|
@@ -137,7 +139,7 @@ function createCORSOptions(trustedOrigins) {
|
|
|
137
139
|
]
|
|
138
140
|
};
|
|
139
141
|
}
|
|
140
|
-
const version_version = '2.0.0-rc.
|
|
142
|
+
const version_version = '2.0.0-rc.5';
|
|
141
143
|
const config_createOpenAPIConfig = (options)=>({
|
|
142
144
|
enabled: options.openapi?.enabled !== false,
|
|
143
145
|
specPath: '/spec.json',
|
|
@@ -253,6 +255,133 @@ function getIpAddress(req, options) {
|
|
|
253
255
|
}
|
|
254
256
|
return null;
|
|
255
257
|
}
|
|
258
|
+
const DEFAULT_PROFILE = 'default';
|
|
259
|
+
const warnedKeys = new Set();
|
|
260
|
+
function isSupportedBaseLanguage(lang) {
|
|
261
|
+
return lang in baseTranslations;
|
|
262
|
+
}
|
|
263
|
+
function warnOnce(logger, key, message, metadata) {
|
|
264
|
+
if (!logger || warnedKeys.has(key)) return;
|
|
265
|
+
warnedKeys.add(key);
|
|
266
|
+
logger.warn(message, metadata);
|
|
267
|
+
}
|
|
268
|
+
function normalizeLanguage(value) {
|
|
269
|
+
if (!value) return;
|
|
270
|
+
const normalized = value.split(',')[0]?.split(';')[0]?.trim().toLowerCase();
|
|
271
|
+
if (!normalized) return;
|
|
272
|
+
return normalized.split('-')[0] ?? void 0;
|
|
273
|
+
}
|
|
274
|
+
function normalizeProfiles(params) {
|
|
275
|
+
const profiles = params.i18n?.messages;
|
|
276
|
+
const legacy = params.customTranslations;
|
|
277
|
+
if (profiles && Object.keys(profiles).length > 0) {
|
|
278
|
+
if (legacy && Object.keys(legacy).length > 0) warnOnce(params.logger, 'i18n.customTranslations.ignored', '`customTranslations` is deprecated and ignored when `i18n.messages` is configured.');
|
|
279
|
+
return profiles;
|
|
280
|
+
}
|
|
281
|
+
if (legacy && Object.keys(legacy).length > 0) {
|
|
282
|
+
warnOnce(params.logger, 'i18n.customTranslations.deprecated', '`customTranslations` is deprecated. Use `i18n.messages` instead.');
|
|
283
|
+
return {
|
|
284
|
+
[DEFAULT_PROFILE]: {
|
|
285
|
+
translations: legacy
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
return {};
|
|
290
|
+
}
|
|
291
|
+
function buildCandidates(input) {
|
|
292
|
+
const raw = [
|
|
293
|
+
{
|
|
294
|
+
language: input.language,
|
|
295
|
+
reason: 'profile_language'
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
language: input.fallbackLanguage,
|
|
299
|
+
reason: 'profile_fallback'
|
|
300
|
+
}
|
|
301
|
+
];
|
|
302
|
+
const dedupe = new Set();
|
|
303
|
+
return raw.filter((candidate)=>{
|
|
304
|
+
const key = candidate.language;
|
|
305
|
+
if (dedupe.has(key)) return false;
|
|
306
|
+
dedupe.add(key);
|
|
307
|
+
return true;
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
function getProfileLanguages(profiles, profile) {
|
|
311
|
+
return Object.keys(profiles[profile]?.translations ?? {}).sort();
|
|
312
|
+
}
|
|
313
|
+
function getSelectableLanguages(input) {
|
|
314
|
+
return getProfileLanguages(input.profiles, input.profile);
|
|
315
|
+
}
|
|
316
|
+
function resolveFallbackLanguage(input) {
|
|
317
|
+
const configuredFallbackLanguage = normalizeLanguage(input.profile?.fallbackLanguage) ?? 'en';
|
|
318
|
+
const profileLanguages = Object.keys(input.profile?.translations ?? {}).sort();
|
|
319
|
+
if (profileLanguages.includes(configuredFallbackLanguage)) return configuredFallbackLanguage;
|
|
320
|
+
if (profileLanguages.includes('en')) return 'en';
|
|
321
|
+
return profileLanguages[0] ?? configuredFallbackLanguage;
|
|
322
|
+
}
|
|
323
|
+
function resolveActiveProfile(input) {
|
|
324
|
+
const requestedProfile = input.policyProfile ?? input.defaultProfile;
|
|
325
|
+
if (input.profiles[requestedProfile]) return requestedProfile;
|
|
326
|
+
if (input.policyProfile) warnOnce(input.logger, `i18n.profile.missing:${requestedProfile}`, `Policy i18n profile '${requestedProfile}' does not exist. Falling back to default profile '${input.defaultProfile}'.`);
|
|
327
|
+
return input.defaultProfile;
|
|
328
|
+
}
|
|
329
|
+
function validateMessages(options) {
|
|
330
|
+
return validatePolicyI18nConfig({
|
|
331
|
+
customTranslations: options.customTranslations,
|
|
332
|
+
i18n: options.i18n,
|
|
333
|
+
policies: options.policies
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
function translations_getTranslationsData(acceptLanguage, customTranslations, options) {
|
|
337
|
+
const profiles = normalizeProfiles({
|
|
338
|
+
customTranslations,
|
|
339
|
+
i18n: options?.i18n,
|
|
340
|
+
logger: options?.logger
|
|
341
|
+
});
|
|
342
|
+
const defaultProfile = options?.i18n?.defaultProfile ?? DEFAULT_PROFILE;
|
|
343
|
+
const profile = resolveActiveProfile({
|
|
344
|
+
profiles,
|
|
345
|
+
defaultProfile,
|
|
346
|
+
policyProfile: options?.policyI18n?.messageProfile,
|
|
347
|
+
logger: options?.logger
|
|
348
|
+
});
|
|
349
|
+
const configuredLanguages = Object.keys(profiles).length > 0 ? getSelectableLanguages({
|
|
350
|
+
profiles,
|
|
351
|
+
profile
|
|
352
|
+
}) : Object.keys(baseTranslations);
|
|
353
|
+
const fallbackLanguage = Object.keys(profiles).length > 0 ? resolveFallbackLanguage({
|
|
354
|
+
profile: profiles[profile]
|
|
355
|
+
}) : 'en';
|
|
356
|
+
const policyLanguage = normalizeLanguage(options?.policyI18n?.language);
|
|
357
|
+
const requestedLanguage = policyLanguage ?? selectLanguage(configuredLanguages, {
|
|
358
|
+
header: acceptLanguage,
|
|
359
|
+
fallback: fallbackLanguage
|
|
360
|
+
});
|
|
361
|
+
const candidates = buildCandidates({
|
|
362
|
+
language: requestedLanguage,
|
|
363
|
+
fallbackLanguage
|
|
364
|
+
});
|
|
365
|
+
const selectedCandidate = candidates.find((candidate)=>!!profiles[profile]?.translations[candidate.language]);
|
|
366
|
+
if (selectedCandidate && 'profile_language' !== selectedCandidate.reason) warnOnce(options?.logger, `i18n.fallback:${profile}:${requestedLanguage}:${selectedCandidate.language}`, `Policy translation fallback used (${selectedCandidate.reason}).`, {
|
|
367
|
+
requestedProfile: profile,
|
|
368
|
+
requestedLanguage,
|
|
369
|
+
resolvedProfile: profile,
|
|
370
|
+
resolvedLanguage: selectedCandidate.language
|
|
371
|
+
});
|
|
372
|
+
let language = selectedCandidate?.language ?? requestedLanguage;
|
|
373
|
+
if (!selectedCandidate && !isSupportedBaseLanguage(language)) {
|
|
374
|
+
warnOnce(options?.logger, `i18n.base-fallback:${language}`, `No translation found for '${language}'. Falling back to base English translations.`);
|
|
375
|
+
language = 'en';
|
|
376
|
+
}
|
|
377
|
+
const base = isSupportedBaseLanguage(language) ? baseTranslations[language] : baseTranslations.en;
|
|
378
|
+
const custom = selectedCandidate ? profiles[profile]?.translations[selectedCandidate.language] : void 0;
|
|
379
|
+
const translations = custom ? deepMergeTranslations(base, custom) : base;
|
|
380
|
+
return {
|
|
381
|
+
translations: translations,
|
|
382
|
+
language
|
|
383
|
+
};
|
|
384
|
+
}
|
|
256
385
|
function extractErrorMessage(error) {
|
|
257
386
|
if (error instanceof AggregateError && error.errors?.length > 0) {
|
|
258
387
|
const inner = error.errors.map((e)=>e instanceof Error ? e.message : String(e)).join('; ');
|
|
@@ -575,6 +704,7 @@ const prefixes = {
|
|
|
575
704
|
consentPolicy: 'pol',
|
|
576
705
|
consentPurpose: 'pur',
|
|
577
706
|
domain: 'dom',
|
|
707
|
+
runtimePolicyDecision: 'rpd',
|
|
578
708
|
subject: 'sub'
|
|
579
709
|
};
|
|
580
710
|
const b58 = base_x('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
|
|
@@ -846,6 +976,54 @@ function domainRegistry({ db, ctx }) {
|
|
|
846
976
|
}
|
|
847
977
|
};
|
|
848
978
|
}
|
|
979
|
+
function runtimePolicyDecisionRegistry({ db, ctx }) {
|
|
980
|
+
const { logger } = ctx;
|
|
981
|
+
return {
|
|
982
|
+
findOrCreateRuntimePolicyDecision: async (input)=>{
|
|
983
|
+
const existing = await db.findFirst('runtimePolicyDecision', {
|
|
984
|
+
where: (b)=>b('dedupeKey', '=', input.dedupeKey)
|
|
985
|
+
});
|
|
986
|
+
if (existing) return existing;
|
|
987
|
+
logger.debug('Creating runtime policy decision', {
|
|
988
|
+
policyId: input.policyId,
|
|
989
|
+
fingerprint: input.fingerprint,
|
|
990
|
+
matchedBy: input.matchedBy
|
|
991
|
+
});
|
|
992
|
+
return db.create('runtimePolicyDecision', {
|
|
993
|
+
id: `rpd_${crypto.randomUUID().replaceAll('-', '')}`,
|
|
994
|
+
tenantId: input.tenantId,
|
|
995
|
+
policyId: input.policyId,
|
|
996
|
+
fingerprint: input.fingerprint,
|
|
997
|
+
matchedBy: input.matchedBy,
|
|
998
|
+
countryCode: input.countryCode,
|
|
999
|
+
regionCode: input.regionCode,
|
|
1000
|
+
jurisdiction: input.jurisdiction,
|
|
1001
|
+
language: input.language,
|
|
1002
|
+
model: input.model,
|
|
1003
|
+
policyI18n: input.policyI18n ? {
|
|
1004
|
+
json: input.policyI18n
|
|
1005
|
+
} : void 0,
|
|
1006
|
+
uiMode: input.uiMode,
|
|
1007
|
+
bannerUi: input.bannerUi ? {
|
|
1008
|
+
json: input.bannerUi
|
|
1009
|
+
} : void 0,
|
|
1010
|
+
dialogUi: input.dialogUi ? {
|
|
1011
|
+
json: input.dialogUi
|
|
1012
|
+
} : void 0,
|
|
1013
|
+
categories: input.categories ? {
|
|
1014
|
+
json: input.categories
|
|
1015
|
+
} : void 0,
|
|
1016
|
+
preselectedCategories: input.preselectedCategories ? {
|
|
1017
|
+
json: input.preselectedCategories
|
|
1018
|
+
} : void 0,
|
|
1019
|
+
proofConfig: input.proofConfig ? {
|
|
1020
|
+
json: input.proofConfig
|
|
1021
|
+
} : void 0,
|
|
1022
|
+
dedupeKey: input.dedupeKey
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
849
1027
|
function subjectRegistry({ db, ctx }) {
|
|
850
1028
|
const { logger } = ctx;
|
|
851
1029
|
return {
|
|
@@ -923,7 +1101,8 @@ const createRegistry = (ctx)=>({
|
|
|
923
1101
|
...subjectRegistry(ctx),
|
|
924
1102
|
...consentPurposeRegistry(ctx),
|
|
925
1103
|
...policyRegistry(ctx),
|
|
926
|
-
...domainRegistry(ctx)
|
|
1104
|
+
...domainRegistry(ctx),
|
|
1105
|
+
...runtimePolicyDecisionRegistry(ctx)
|
|
927
1106
|
});
|
|
928
1107
|
const auditLogTable = schema_table('auditLog', {
|
|
929
1108
|
id: idColumn('id', 'varchar(255)'),
|
|
@@ -1090,6 +1269,8 @@ const consent_consentTable = schema_table('consent', {
|
|
|
1090
1269
|
tcString: column('tcString', 'string').nullable(),
|
|
1091
1270
|
uiSource: column('uiSource', 'string').nullable(),
|
|
1092
1271
|
consentAction: column('consentAction', 'string').nullable(),
|
|
1272
|
+
runtimePolicyDecisionId: column('runtimePolicyDecisionId', 'string').nullable(),
|
|
1273
|
+
runtimePolicySource: column('runtimePolicySource', 'string').nullable(),
|
|
1093
1274
|
tenantId: column('tenantId', 'string').nullable()
|
|
1094
1275
|
});
|
|
1095
1276
|
const consent_policy_consentPolicyTable = schema_table('consentPolicy', {
|
|
@@ -1115,6 +1296,27 @@ const domain_domainTable = schema_table('domain', {
|
|
|
1115
1296
|
updatedAt: column('updatedAt', 'timestamp').defaultTo$('now'),
|
|
1116
1297
|
tenantId: column('tenantId', 'string').nullable()
|
|
1117
1298
|
});
|
|
1299
|
+
const runtimePolicyDecisionTable = schema_table('runtimePolicyDecision', {
|
|
1300
|
+
id: idColumn('id', 'varchar(255)'),
|
|
1301
|
+
tenantId: column('tenantId', 'string').nullable(),
|
|
1302
|
+
policyId: column('policyId', 'string'),
|
|
1303
|
+
fingerprint: column('fingerprint', 'string'),
|
|
1304
|
+
matchedBy: column('matchedBy', 'string'),
|
|
1305
|
+
countryCode: column('countryCode', 'string').nullable(),
|
|
1306
|
+
regionCode: column('regionCode', 'string').nullable(),
|
|
1307
|
+
jurisdiction: column('jurisdiction', 'string'),
|
|
1308
|
+
language: column('language', 'string').nullable(),
|
|
1309
|
+
model: column('model', 'string'),
|
|
1310
|
+
policyI18n: column('policyI18n', 'json').nullable(),
|
|
1311
|
+
uiMode: column('uiMode', 'string').nullable(),
|
|
1312
|
+
bannerUi: column('bannerUi', 'json').nullable(),
|
|
1313
|
+
dialogUi: column('dialogUi', 'json').nullable(),
|
|
1314
|
+
categories: column('categories', 'json').nullable(),
|
|
1315
|
+
preselectedCategories: column('preselectedCategories', 'json').nullable(),
|
|
1316
|
+
proofConfig: column('proofConfig', 'json').nullable(),
|
|
1317
|
+
dedupeKey: column('dedupeKey', 'string').unique(),
|
|
1318
|
+
createdAt: column('createdAt', 'timestamp').defaultTo$('now')
|
|
1319
|
+
});
|
|
1118
1320
|
const subject_subjectTable = schema_table('subject', {
|
|
1119
1321
|
id: idColumn('id', 'varchar(255)'),
|
|
1120
1322
|
externalId: column('externalId', 'string').nullable(),
|
|
@@ -1129,6 +1331,7 @@ const v2 = schema({
|
|
|
1129
1331
|
subject: subject_subjectTable,
|
|
1130
1332
|
domain: domain_domainTable,
|
|
1131
1333
|
consentPolicy: consent_policy_consentPolicyTable,
|
|
1334
|
+
runtimePolicyDecision: runtimePolicyDecisionTable,
|
|
1132
1335
|
consentPurpose: consent_purpose_consentPurposeTable,
|
|
1133
1336
|
consent: consent_consentTable,
|
|
1134
1337
|
auditLog: audit_log_auditLogTable
|
|
@@ -1144,6 +1347,9 @@ const v2 = schema({
|
|
|
1144
1347
|
consentPolicy: ({ many })=>({
|
|
1145
1348
|
consents: many('consent')
|
|
1146
1349
|
}),
|
|
1350
|
+
runtimePolicyDecision: ({ many })=>({
|
|
1351
|
+
consents: many('consent')
|
|
1352
|
+
}),
|
|
1147
1353
|
consentPurpose: ()=>({}),
|
|
1148
1354
|
consent: ({ one })=>({
|
|
1149
1355
|
subject: one('subject', [
|
|
@@ -1157,6 +1363,10 @@ const v2 = schema({
|
|
|
1157
1363
|
policy: one('consentPolicy', [
|
|
1158
1364
|
'policyId',
|
|
1159
1365
|
'id'
|
|
1366
|
+
]).foreignKey(),
|
|
1367
|
+
runtimePolicyDecision: one('runtimePolicyDecision', [
|
|
1368
|
+
'runtimePolicyDecisionId',
|
|
1369
|
+
'id'
|
|
1160
1370
|
]).foreignKey()
|
|
1161
1371
|
}),
|
|
1162
1372
|
auditLog: ({ one })=>({
|
|
@@ -1243,6 +1453,18 @@ function withTenantScope(db, tenantId) {
|
|
|
1243
1453
|
}
|
|
1244
1454
|
});
|
|
1245
1455
|
}
|
|
1456
|
+
function policy_inspectPolicies(policies, options) {
|
|
1457
|
+
return inspectPolicies(policies, options);
|
|
1458
|
+
}
|
|
1459
|
+
async function policy_resolvePolicyDecision(params) {
|
|
1460
|
+
return resolvePolicyDecision({
|
|
1461
|
+
policies: params.policies,
|
|
1462
|
+
countryCode: params.countryCode,
|
|
1463
|
+
regionCode: params.regionCode,
|
|
1464
|
+
jurisdiction: params.jurisdiction,
|
|
1465
|
+
iabEnabled: params.iabEnabled
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1246
1468
|
let globalLogger;
|
|
1247
1469
|
function initLogger(options) {
|
|
1248
1470
|
globalLogger = logger_createLogger({
|
|
@@ -1269,6 +1491,18 @@ const init = (options)=>{
|
|
|
1269
1491
|
const rawOrm = client.orm('2.0.0');
|
|
1270
1492
|
const orm = options.tenantId ? withTenantScope(rawOrm, options.tenantId) : rawOrm;
|
|
1271
1493
|
const { ipAddress: _ipAddressConfig, ...baseOptions } = options;
|
|
1494
|
+
const i18nValidation = validateMessages({
|
|
1495
|
+
i18n: options.i18n,
|
|
1496
|
+
customTranslations: options.customTranslations,
|
|
1497
|
+
policies: options.policyPacks
|
|
1498
|
+
});
|
|
1499
|
+
for (const warning of i18nValidation.warnings)logger.warn(`i18n: ${warning}`);
|
|
1500
|
+
if (i18nValidation.errors.length > 0) throw new Error(`Invalid i18n configuration:\n${i18nValidation.errors.map((error)=>`- ${error}`).join('\n')}`);
|
|
1501
|
+
const policyValidation = policy_inspectPolicies(options.policyPacks ?? [], {
|
|
1502
|
+
iabEnabled: options.iab?.enabled === true
|
|
1503
|
+
});
|
|
1504
|
+
for (const warning of policyValidation.warnings)logger.warn(`policyPacks: ${warning}`);
|
|
1505
|
+
if (policyValidation.errors.length > 0) throw new Error(policyValidation.errors[0]);
|
|
1272
1506
|
const context = {
|
|
1273
1507
|
...baseOptions,
|
|
1274
1508
|
appName,
|
|
@@ -1628,6 +1862,123 @@ function createGVLResolver(options) {
|
|
|
1628
1862
|
}
|
|
1629
1863
|
};
|
|
1630
1864
|
}
|
|
1865
|
+
const POLICY_SNAPSHOT_JWT_HEADER = {
|
|
1866
|
+
alg: 'HS256',
|
|
1867
|
+
typ: 'JWT'
|
|
1868
|
+
};
|
|
1869
|
+
const DEFAULT_POLICY_SNAPSHOT_ISSUER = 'c15t';
|
|
1870
|
+
const DEFAULT_POLICY_SNAPSHOT_AUDIENCE = 'c15t-policy-snapshot';
|
|
1871
|
+
function resolveSnapshotIssuer(options) {
|
|
1872
|
+
return options?.issuer?.trim() || DEFAULT_POLICY_SNAPSHOT_ISSUER;
|
|
1873
|
+
}
|
|
1874
|
+
function resolveSnapshotAudience(params) {
|
|
1875
|
+
const configuredAudience = params.options?.audience?.trim();
|
|
1876
|
+
if (configuredAudience) return configuredAudience;
|
|
1877
|
+
return params.tenantId ? `${DEFAULT_POLICY_SNAPSHOT_AUDIENCE}:${params.tenantId}` : DEFAULT_POLICY_SNAPSHOT_AUDIENCE;
|
|
1878
|
+
}
|
|
1879
|
+
function getSigningKey(secret) {
|
|
1880
|
+
return new TextEncoder().encode(secret);
|
|
1881
|
+
}
|
|
1882
|
+
function isPolicySnapshotPayload(payload) {
|
|
1883
|
+
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;
|
|
1884
|
+
}
|
|
1885
|
+
async function createPolicySnapshotToken(params) {
|
|
1886
|
+
const { options } = params;
|
|
1887
|
+
if (!options?.signingKey) return;
|
|
1888
|
+
const iat = Math.floor(Date.now() / 1000);
|
|
1889
|
+
const ttlSeconds = options.ttlSeconds ?? 1800;
|
|
1890
|
+
const exp = iat + ttlSeconds;
|
|
1891
|
+
const iss = resolveSnapshotIssuer(options);
|
|
1892
|
+
const aud = resolveSnapshotAudience({
|
|
1893
|
+
options,
|
|
1894
|
+
tenantId: params.tenantId
|
|
1895
|
+
});
|
|
1896
|
+
const payload = {
|
|
1897
|
+
iss,
|
|
1898
|
+
aud,
|
|
1899
|
+
sub: params.policyId,
|
|
1900
|
+
tenantId: params.tenantId,
|
|
1901
|
+
policyId: params.policyId,
|
|
1902
|
+
fingerprint: params.fingerprint,
|
|
1903
|
+
matchedBy: params.matchedBy,
|
|
1904
|
+
country: params.country,
|
|
1905
|
+
region: params.region,
|
|
1906
|
+
jurisdiction: params.jurisdiction,
|
|
1907
|
+
language: params.language,
|
|
1908
|
+
model: params.model,
|
|
1909
|
+
policyI18n: params.policyI18n,
|
|
1910
|
+
expiryDays: params.expiryDays,
|
|
1911
|
+
scopeMode: params.scopeMode,
|
|
1912
|
+
uiMode: params.uiMode,
|
|
1913
|
+
bannerUi: params.bannerUi,
|
|
1914
|
+
dialogUi: params.dialogUi,
|
|
1915
|
+
categories: params.categories,
|
|
1916
|
+
preselectedCategories: params.preselectedCategories,
|
|
1917
|
+
gpc: params.gpc,
|
|
1918
|
+
proofConfig: params.proofConfig,
|
|
1919
|
+
iat,
|
|
1920
|
+
exp
|
|
1921
|
+
};
|
|
1922
|
+
const token = await new SignJWT(payload).setProtectedHeader(POLICY_SNAPSHOT_JWT_HEADER).setIssuedAt(iat).setExpirationTime(exp).sign(getSigningKey(options.signingKey));
|
|
1923
|
+
return {
|
|
1924
|
+
token,
|
|
1925
|
+
payload
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
async function verifyPolicySnapshotToken(params) {
|
|
1929
|
+
const { token, options, tenantId } = params;
|
|
1930
|
+
if (!options?.signingKey) return {
|
|
1931
|
+
valid: false,
|
|
1932
|
+
reason: 'missing'
|
|
1933
|
+
};
|
|
1934
|
+
if (!token) return {
|
|
1935
|
+
valid: false,
|
|
1936
|
+
reason: 'missing'
|
|
1937
|
+
};
|
|
1938
|
+
if (3 !== token.split('.').length) return {
|
|
1939
|
+
valid: false,
|
|
1940
|
+
reason: 'malformed'
|
|
1941
|
+
};
|
|
1942
|
+
try {
|
|
1943
|
+
const { payload, protectedHeader } = await jwtVerify(token, getSigningKey(options.signingKey), {
|
|
1944
|
+
issuer: resolveSnapshotIssuer(options),
|
|
1945
|
+
audience: resolveSnapshotAudience({
|
|
1946
|
+
options,
|
|
1947
|
+
tenantId
|
|
1948
|
+
})
|
|
1949
|
+
});
|
|
1950
|
+
const header = protectedHeader;
|
|
1951
|
+
if ('HS256' !== header.alg || 'JWT' !== header.typ) return {
|
|
1952
|
+
valid: false,
|
|
1953
|
+
reason: 'invalid'
|
|
1954
|
+
};
|
|
1955
|
+
if (!isPolicySnapshotPayload(payload)) return {
|
|
1956
|
+
valid: false,
|
|
1957
|
+
reason: 'invalid'
|
|
1958
|
+
};
|
|
1959
|
+
if (payload.sub !== payload.policyId) return {
|
|
1960
|
+
valid: false,
|
|
1961
|
+
reason: 'invalid'
|
|
1962
|
+
};
|
|
1963
|
+
if ((tenantId ?? void 0) !== (payload.tenantId ?? void 0)) return {
|
|
1964
|
+
valid: false,
|
|
1965
|
+
reason: 'invalid'
|
|
1966
|
+
};
|
|
1967
|
+
return {
|
|
1968
|
+
valid: true,
|
|
1969
|
+
payload
|
|
1970
|
+
};
|
|
1971
|
+
} catch (error) {
|
|
1972
|
+
if (error instanceof errors.JWTExpired) return {
|
|
1973
|
+
valid: false,
|
|
1974
|
+
reason: 'expired'
|
|
1975
|
+
};
|
|
1976
|
+
return {
|
|
1977
|
+
valid: false,
|
|
1978
|
+
reason: 'invalid'
|
|
1979
|
+
};
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1631
1982
|
function geo_normalizeHeader(value) {
|
|
1632
1983
|
if (!value) return null;
|
|
1633
1984
|
return Array.isArray(value) ? value[0] ?? null : value;
|
|
@@ -1783,26 +2134,118 @@ function getJurisdiction(location, options) {
|
|
|
1783
2134
|
if (options.disableGeoLocation) return 'GDPR';
|
|
1784
2135
|
return checkJurisdiction(location.countryCode, location.regionCode);
|
|
1785
2136
|
}
|
|
1786
|
-
function
|
|
1787
|
-
|
|
2137
|
+
function stripIabTranslations(translations) {
|
|
2138
|
+
const { iab: _iab, ...rest } = translations;
|
|
2139
|
+
return rest;
|
|
1788
2140
|
}
|
|
1789
|
-
function
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
2141
|
+
function resolveNoPolicyFallback() {
|
|
2142
|
+
return {
|
|
2143
|
+
id: 'no_banner',
|
|
2144
|
+
model: 'none',
|
|
2145
|
+
ui: {
|
|
2146
|
+
mode: 'none'
|
|
2147
|
+
}
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2150
|
+
async function resolveInitPayload(request, options, logger) {
|
|
2151
|
+
const acceptLanguage = request.headers.get('accept-language') || 'en';
|
|
2152
|
+
const location = await getLocation(request, options);
|
|
2153
|
+
const jurisdiction = getJurisdiction(location, options);
|
|
2154
|
+
const hasExplicitPolicyPack = void 0 !== options.policyPacks;
|
|
2155
|
+
const isExplicitEmptyPolicyPack = hasExplicitPolicyPack && (options.policyPacks?.length ?? 0) === 0;
|
|
2156
|
+
const policyDecision = isExplicitEmptyPolicyPack ? void 0 : await policy_resolvePolicyDecision({
|
|
2157
|
+
policies: options.policyPacks,
|
|
2158
|
+
countryCode: location.countryCode,
|
|
2159
|
+
regionCode: location.regionCode,
|
|
2160
|
+
jurisdiction,
|
|
2161
|
+
iabEnabled: options.iab?.enabled === true
|
|
2162
|
+
});
|
|
2163
|
+
if (hasExplicitPolicyPack && !isExplicitEmptyPolicyPack && !policyDecision) logger?.warn('Policy packs configured but no policy matched', {
|
|
2164
|
+
country: location.countryCode,
|
|
2165
|
+
region: location.regionCode
|
|
2166
|
+
});
|
|
2167
|
+
const resolvedPolicy = hasExplicitPolicyPack ? policyDecision?.policy ?? resolveNoPolicyFallback() : void 0;
|
|
2168
|
+
const iabOptions = options.iab;
|
|
2169
|
+
const shouldIncludeIabPayload = iabOptions?.enabled === true && (!hasExplicitPolicyPack || resolvedPolicy?.model === 'iab');
|
|
2170
|
+
const translationsResult = translations_getTranslationsData(acceptLanguage, options.customTranslations, {
|
|
2171
|
+
i18n: options.i18n,
|
|
2172
|
+
policyI18n: resolvedPolicy?.i18n,
|
|
2173
|
+
logger
|
|
2174
|
+
});
|
|
2175
|
+
const responseTranslations = shouldIncludeIabPayload ? translationsResult : {
|
|
2176
|
+
...translationsResult,
|
|
2177
|
+
translations: stripIabTranslations(translationsResult.translations)
|
|
2178
|
+
};
|
|
2179
|
+
let gvl = null;
|
|
2180
|
+
if (shouldIncludeIabPayload && iabOptions) {
|
|
2181
|
+
const language = translationsResult.language.split('-')[0] || 'en';
|
|
2182
|
+
const gvlResolver = createGVLResolver({
|
|
2183
|
+
appName: options.appName || 'c15t',
|
|
2184
|
+
bundled: iabOptions.bundled,
|
|
2185
|
+
cacheAdapter: options.cache?.adapter,
|
|
2186
|
+
vendorIds: iabOptions.vendorIds,
|
|
2187
|
+
endpoint: iabOptions.endpoint
|
|
2188
|
+
});
|
|
2189
|
+
gvl = await gvlResolver.get(language);
|
|
2190
|
+
}
|
|
2191
|
+
const customVendors = shouldIncludeIabPayload ? iabOptions?.customVendors : void 0;
|
|
2192
|
+
const snapshot = policyDecision ? await createPolicySnapshotToken({
|
|
2193
|
+
options: options.policySnapshot,
|
|
2194
|
+
tenantId: options.tenantId,
|
|
2195
|
+
policyId: policyDecision.policy.id,
|
|
2196
|
+
fingerprint: policyDecision.fingerprint,
|
|
2197
|
+
matchedBy: policyDecision.matchedBy,
|
|
2198
|
+
country: location?.countryCode ?? null,
|
|
2199
|
+
region: location?.regionCode ?? null,
|
|
2200
|
+
jurisdiction,
|
|
2201
|
+
language: translationsResult.language,
|
|
2202
|
+
model: policyDecision.policy.model,
|
|
2203
|
+
policyI18n: policyDecision.policy.i18n,
|
|
2204
|
+
expiryDays: policyDecision.policy.consent?.expiryDays,
|
|
2205
|
+
scopeMode: policyDecision.policy.consent?.scopeMode,
|
|
2206
|
+
uiMode: policyDecision.policy.ui?.mode,
|
|
2207
|
+
bannerUi: policyDecision.policy.ui?.banner,
|
|
2208
|
+
dialogUi: policyDecision.policy.ui?.dialog,
|
|
2209
|
+
categories: policyDecision.policy.consent?.categories,
|
|
2210
|
+
preselectedCategories: policyDecision.policy.consent?.preselectedCategories,
|
|
2211
|
+
gpc: policyDecision.policy.consent?.gpc,
|
|
2212
|
+
proofConfig: policyDecision.policy.proof
|
|
2213
|
+
}) : void 0;
|
|
2214
|
+
const gpc = '1' === request.headers.get('sec-gpc');
|
|
2215
|
+
getMetrics()?.recordInit({
|
|
2216
|
+
jurisdiction,
|
|
2217
|
+
country: location?.countryCode ?? void 0,
|
|
2218
|
+
region: location?.regionCode ?? void 0,
|
|
2219
|
+
gpc
|
|
1799
2220
|
});
|
|
1800
|
-
const base = isSupportedBaseLanguage(preferredLanguage) ? baseTranslations[preferredLanguage] : baseTranslations.en;
|
|
1801
|
-
const custom = supportedCustomLanguages.includes(preferredLanguage) ? customTranslations?.[preferredLanguage] : {};
|
|
1802
|
-
const translations = custom ? deepMergeTranslations(base, custom) : base;
|
|
1803
2221
|
return {
|
|
1804
|
-
|
|
1805
|
-
|
|
2222
|
+
jurisdiction,
|
|
2223
|
+
location,
|
|
2224
|
+
translations: responseTranslations,
|
|
2225
|
+
branding: options.branding || 'c15t',
|
|
2226
|
+
...shouldIncludeIabPayload && {
|
|
2227
|
+
gvl,
|
|
2228
|
+
customVendors
|
|
2229
|
+
},
|
|
2230
|
+
...resolvedPolicy && {
|
|
2231
|
+
policy: resolvedPolicy
|
|
2232
|
+
},
|
|
2233
|
+
...policyDecision && {
|
|
2234
|
+
policyDecision: {
|
|
2235
|
+
policyId: policyDecision.policy.id,
|
|
2236
|
+
fingerprint: policyDecision.fingerprint,
|
|
2237
|
+
matchedBy: policyDecision.matchedBy,
|
|
2238
|
+
country: location.countryCode,
|
|
2239
|
+
region: location.regionCode,
|
|
2240
|
+
jurisdiction
|
|
2241
|
+
}
|
|
2242
|
+
},
|
|
2243
|
+
...snapshot?.token && {
|
|
2244
|
+
policySnapshotToken: snapshot.token
|
|
2245
|
+
},
|
|
2246
|
+
...shouldIncludeIabPayload && iabOptions?.cmpId != null && {
|
|
2247
|
+
cmpId: iabOptions.cmpId
|
|
2248
|
+
}
|
|
1806
2249
|
};
|
|
1807
2250
|
}
|
|
1808
2251
|
const createInitRoute = (options)=>{
|
|
@@ -1815,7 +2258,7 @@ const createInitRoute = (options)=>{
|
|
|
1815
2258
|
- **Location** – User's location (null if geo-location is disabled)
|
|
1816
2259
|
- **Translations** – Consent manager copy (from \`Accept-Language\` header)
|
|
1817
2260
|
- **Branding** – Configured branding key
|
|
1818
|
-
- **GVL** – Global Vendor List when
|
|
2261
|
+
- **GVL** – Global Vendor List when IAB is active for the request
|
|
1819
2262
|
|
|
1820
2263
|
Use for geo-targeted consent banners and regional compliance.`,
|
|
1821
2264
|
tags: [
|
|
@@ -1832,42 +2275,9 @@ Use for geo-targeted consent banners and regional compliance.`,
|
|
|
1832
2275
|
}
|
|
1833
2276
|
}
|
|
1834
2277
|
}), async (c)=>{
|
|
1835
|
-
const
|
|
1836
|
-
const
|
|
1837
|
-
|
|
1838
|
-
const jurisdiction = getJurisdiction(location, options);
|
|
1839
|
-
const translationsResult = translations_getTranslationsData(acceptLanguage, options.customTranslations);
|
|
1840
|
-
let gvl = null;
|
|
1841
|
-
if (options.iab?.enabled) {
|
|
1842
|
-
const language = translationsResult.language.split('-')[0] || 'en';
|
|
1843
|
-
const gvlResolver = createGVLResolver({
|
|
1844
|
-
appName: options.appName || 'c15t',
|
|
1845
|
-
bundled: options.iab.bundled,
|
|
1846
|
-
cacheAdapter: options.cache?.adapter,
|
|
1847
|
-
vendorIds: options.iab.vendorIds,
|
|
1848
|
-
endpoint: options.iab.endpoint
|
|
1849
|
-
});
|
|
1850
|
-
gvl = await gvlResolver.get(language);
|
|
1851
|
-
}
|
|
1852
|
-
const customVendors = options.iab?.customVendors;
|
|
1853
|
-
const gpc = '1' === request.headers.get('sec-gpc');
|
|
1854
|
-
getMetrics()?.recordInit({
|
|
1855
|
-
jurisdiction,
|
|
1856
|
-
country: location?.countryCode ?? void 0,
|
|
1857
|
-
region: location?.regionCode ?? void 0,
|
|
1858
|
-
gpc
|
|
1859
|
-
});
|
|
1860
|
-
return c.json({
|
|
1861
|
-
jurisdiction,
|
|
1862
|
-
location,
|
|
1863
|
-
translations: translationsResult,
|
|
1864
|
-
branding: options.branding || 'c15t',
|
|
1865
|
-
gvl,
|
|
1866
|
-
customVendors,
|
|
1867
|
-
...options.iab?.cmpId != null && {
|
|
1868
|
-
cmpId: options.iab.cmpId
|
|
1869
|
-
}
|
|
1870
|
-
});
|
|
2278
|
+
const ctx = c.get('c15tContext');
|
|
2279
|
+
const payload = await resolveInitPayload(c.req.raw, options, ctx?.logger);
|
|
2280
|
+
return c.json(payload);
|
|
1871
2281
|
});
|
|
1872
2282
|
return app;
|
|
1873
2283
|
};
|
|
@@ -2223,6 +2633,119 @@ const patchSubjectHandler = async (c)=>{
|
|
|
2223
2633
|
});
|
|
2224
2634
|
}
|
|
2225
2635
|
};
|
|
2636
|
+
function buildRuntimeDecisionDedupeKey(input) {
|
|
2637
|
+
return [
|
|
2638
|
+
input.tenantId ?? 'default',
|
|
2639
|
+
input.fingerprint,
|
|
2640
|
+
input.matchedBy,
|
|
2641
|
+
input.countryCode ?? 'none',
|
|
2642
|
+
input.regionCode ?? 'none',
|
|
2643
|
+
input.jurisdiction,
|
|
2644
|
+
input.language ?? 'none'
|
|
2645
|
+
].join('|');
|
|
2646
|
+
}
|
|
2647
|
+
function buildDecisionPayload(params) {
|
|
2648
|
+
const { tenantId, snapshot, decision, location, jurisdiction, language, proofConfig } = params;
|
|
2649
|
+
if (snapshot?.valid && snapshot.payload) {
|
|
2650
|
+
const sp = snapshot.payload;
|
|
2651
|
+
return {
|
|
2652
|
+
tenantId,
|
|
2653
|
+
policyId: sp.policyId,
|
|
2654
|
+
fingerprint: sp.fingerprint,
|
|
2655
|
+
matchedBy: sp.matchedBy,
|
|
2656
|
+
countryCode: sp.country,
|
|
2657
|
+
regionCode: sp.region,
|
|
2658
|
+
jurisdiction: sp.jurisdiction,
|
|
2659
|
+
language: sp.language,
|
|
2660
|
+
model: sp.model,
|
|
2661
|
+
policyI18n: sp.policyI18n,
|
|
2662
|
+
uiMode: sp.uiMode,
|
|
2663
|
+
bannerUi: sp.bannerUi,
|
|
2664
|
+
dialogUi: sp.dialogUi,
|
|
2665
|
+
categories: sp.categories,
|
|
2666
|
+
preselectedCategories: sp.preselectedCategories,
|
|
2667
|
+
proofConfig: sp.proofConfig,
|
|
2668
|
+
dedupeKey: buildRuntimeDecisionDedupeKey({
|
|
2669
|
+
tenantId,
|
|
2670
|
+
fingerprint: sp.fingerprint,
|
|
2671
|
+
matchedBy: sp.matchedBy,
|
|
2672
|
+
countryCode: sp.country,
|
|
2673
|
+
regionCode: sp.region,
|
|
2674
|
+
jurisdiction: sp.jurisdiction,
|
|
2675
|
+
language: sp.language
|
|
2676
|
+
}),
|
|
2677
|
+
source: 'snapshot_token'
|
|
2678
|
+
};
|
|
2679
|
+
}
|
|
2680
|
+
if (decision) return {
|
|
2681
|
+
tenantId,
|
|
2682
|
+
policyId: decision.policy.id,
|
|
2683
|
+
fingerprint: decision.fingerprint,
|
|
2684
|
+
matchedBy: decision.matchedBy,
|
|
2685
|
+
countryCode: location.countryCode,
|
|
2686
|
+
regionCode: location.regionCode,
|
|
2687
|
+
jurisdiction,
|
|
2688
|
+
language,
|
|
2689
|
+
model: decision.policy.model,
|
|
2690
|
+
policyI18n: decision.policy.i18n,
|
|
2691
|
+
uiMode: decision.policy.ui?.mode,
|
|
2692
|
+
bannerUi: decision.policy.ui?.banner,
|
|
2693
|
+
dialogUi: decision.policy.ui?.dialog,
|
|
2694
|
+
categories: decision.policy.consent?.categories,
|
|
2695
|
+
preselectedCategories: decision.policy.consent?.preselectedCategories,
|
|
2696
|
+
proofConfig,
|
|
2697
|
+
dedupeKey: buildRuntimeDecisionDedupeKey({
|
|
2698
|
+
tenantId,
|
|
2699
|
+
fingerprint: decision.fingerprint,
|
|
2700
|
+
matchedBy: decision.matchedBy,
|
|
2701
|
+
countryCode: location.countryCode,
|
|
2702
|
+
regionCode: location.regionCode,
|
|
2703
|
+
jurisdiction,
|
|
2704
|
+
language
|
|
2705
|
+
}),
|
|
2706
|
+
source: 'write_time_fallback'
|
|
2707
|
+
};
|
|
2708
|
+
}
|
|
2709
|
+
function parseLanguageFromHeader(header) {
|
|
2710
|
+
if (!header) return;
|
|
2711
|
+
const firstLanguage = header.split(',')[0]?.split(';')[0]?.trim();
|
|
2712
|
+
if (!firstLanguage) return;
|
|
2713
|
+
return firstLanguage.split('-')[0]?.toLowerCase();
|
|
2714
|
+
}
|
|
2715
|
+
function resolveSnapshotFailureMode(ctx) {
|
|
2716
|
+
return ctx.policySnapshot?.onValidationFailure ?? 'reject';
|
|
2717
|
+
}
|
|
2718
|
+
function buildSnapshotHttpException(reason) {
|
|
2719
|
+
switch(reason){
|
|
2720
|
+
case 'missing':
|
|
2721
|
+
return new HTTPException(409, {
|
|
2722
|
+
message: 'Policy snapshot token is required',
|
|
2723
|
+
cause: {
|
|
2724
|
+
code: 'POLICY_SNAPSHOT_REQUIRED'
|
|
2725
|
+
}
|
|
2726
|
+
});
|
|
2727
|
+
case 'expired':
|
|
2728
|
+
return new HTTPException(409, {
|
|
2729
|
+
message: 'Policy snapshot token has expired',
|
|
2730
|
+
cause: {
|
|
2731
|
+
code: 'POLICY_SNAPSHOT_EXPIRED'
|
|
2732
|
+
}
|
|
2733
|
+
});
|
|
2734
|
+
case 'malformed':
|
|
2735
|
+
case 'invalid':
|
|
2736
|
+
return new HTTPException(409, {
|
|
2737
|
+
message: 'Policy snapshot token is invalid',
|
|
2738
|
+
cause: {
|
|
2739
|
+
code: 'POLICY_SNAPSHOT_INVALID'
|
|
2740
|
+
}
|
|
2741
|
+
});
|
|
2742
|
+
default:
|
|
2743
|
+
{
|
|
2744
|
+
const _exhaustive = reason;
|
|
2745
|
+
throw new Error(`Unhandled policy snapshot verification failure reason: ${_exhaustive}`);
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2226
2749
|
const postSubjectHandler = async (c)=>{
|
|
2227
2750
|
const ctx = c.get('c15tContext');
|
|
2228
2751
|
const logger = ctx.logger;
|
|
@@ -2234,9 +2757,6 @@ const postSubjectHandler = async (c)=>{
|
|
|
2234
2757
|
const givenAt = new Date(givenAtEpoch);
|
|
2235
2758
|
const rawConsentAction = 'consentAction' in input ? input.consentAction : void 0;
|
|
2236
2759
|
let derivedConsentAction;
|
|
2237
|
-
if ('all' === rawConsentAction) derivedConsentAction = 'accept_all';
|
|
2238
|
-
else if ('necessary' === rawConsentAction) derivedConsentAction = 'opt-out' === input.jurisdictionModel ? 'opt_out' : 'reject_all';
|
|
2239
|
-
else if ('custom' === rawConsentAction) derivedConsentAction = 'custom';
|
|
2240
2760
|
logger.debug('Request parameters', {
|
|
2241
2761
|
type,
|
|
2242
2762
|
subjectId,
|
|
@@ -2245,6 +2765,50 @@ const postSubjectHandler = async (c)=>{
|
|
|
2245
2765
|
domain
|
|
2246
2766
|
});
|
|
2247
2767
|
try {
|
|
2768
|
+
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.');
|
|
2769
|
+
const request = c.req.raw ?? new Request('https://c15t.local/subjects');
|
|
2770
|
+
const acceptLanguage = request.headers.get('accept-language');
|
|
2771
|
+
const requestLanguage = parseLanguageFromHeader(acceptLanguage);
|
|
2772
|
+
const location = await getLocation(request, ctx);
|
|
2773
|
+
const resolvedJurisdiction = getJurisdiction(location, ctx);
|
|
2774
|
+
const snapshotVerification = await verifyPolicySnapshotToken({
|
|
2775
|
+
token: input.policySnapshotToken,
|
|
2776
|
+
options: ctx.policySnapshot,
|
|
2777
|
+
tenantId: ctx.tenantId
|
|
2778
|
+
});
|
|
2779
|
+
const hasValidSnapshot = snapshotVerification.valid;
|
|
2780
|
+
const snapshotPayload = snapshotVerification.valid ? snapshotVerification.payload : null;
|
|
2781
|
+
const shouldRequireSnapshot = !!ctx.policySnapshot?.signingKey && 'reject' === resolveSnapshotFailureMode(ctx);
|
|
2782
|
+
if (!hasValidSnapshot && shouldRequireSnapshot) throw buildSnapshotHttpException(snapshotVerification.reason);
|
|
2783
|
+
const resolvedPolicyDecision = hasValidSnapshot ? void 0 : await policy_resolvePolicyDecision({
|
|
2784
|
+
policies: ctx.policyPacks,
|
|
2785
|
+
countryCode: location.countryCode,
|
|
2786
|
+
regionCode: location.regionCode,
|
|
2787
|
+
jurisdiction: resolvedJurisdiction,
|
|
2788
|
+
iabEnabled: ctx.iab?.enabled === true
|
|
2789
|
+
});
|
|
2790
|
+
const effectivePolicy = hasValidSnapshot && snapshotPayload ? {
|
|
2791
|
+
id: snapshotPayload.policyId,
|
|
2792
|
+
model: snapshotPayload.model,
|
|
2793
|
+
i18n: snapshotPayload.policyI18n,
|
|
2794
|
+
consent: {
|
|
2795
|
+
expiryDays: snapshotPayload.expiryDays,
|
|
2796
|
+
scopeMode: snapshotPayload.scopeMode,
|
|
2797
|
+
categories: snapshotPayload.categories,
|
|
2798
|
+
preselectedCategories: snapshotPayload.preselectedCategories,
|
|
2799
|
+
gpc: snapshotPayload.gpc
|
|
2800
|
+
},
|
|
2801
|
+
ui: {
|
|
2802
|
+
mode: snapshotPayload.uiMode,
|
|
2803
|
+
banner: snapshotPayload.bannerUi,
|
|
2804
|
+
dialog: snapshotPayload.dialogUi
|
|
2805
|
+
},
|
|
2806
|
+
proof: snapshotPayload.proofConfig
|
|
2807
|
+
} : resolvedPolicyDecision?.policy;
|
|
2808
|
+
const effectiveModel = effectivePolicy?.model ?? ('opt-in' === input.jurisdictionModel || 'opt-out' === input.jurisdictionModel || 'iab' === input.jurisdictionModel ? input.jurisdictionModel : void 0);
|
|
2809
|
+
if ('all' === rawConsentAction) derivedConsentAction = 'accept_all';
|
|
2810
|
+
else if ('necessary' === rawConsentAction) derivedConsentAction = 'opt-out' === effectiveModel ? 'opt_out' : 'reject_all';
|
|
2811
|
+
else if ('custom' === rawConsentAction) derivedConsentAction = 'custom';
|
|
2248
2812
|
const subject = await registry.findOrCreateSubject({
|
|
2249
2813
|
subjectId,
|
|
2250
2814
|
externalSubjectId,
|
|
@@ -2271,6 +2835,7 @@ const postSubjectHandler = async (c)=>{
|
|
|
2271
2835
|
});
|
|
2272
2836
|
let policyId;
|
|
2273
2837
|
let purposeIds = [];
|
|
2838
|
+
let appliedPreferences;
|
|
2274
2839
|
const inputPolicyId = 'policyId' in input ? input.policyId : void 0;
|
|
2275
2840
|
if (inputPolicyId) {
|
|
2276
2841
|
policyId = inputPolicyId;
|
|
@@ -2303,20 +2868,66 @@ const postSubjectHandler = async (c)=>{
|
|
|
2303
2868
|
policyId = policy.id;
|
|
2304
2869
|
}
|
|
2305
2870
|
if (preferences) {
|
|
2306
|
-
const
|
|
2871
|
+
const allowedCategories = effectivePolicy?.consent?.categories;
|
|
2872
|
+
const effectiveScopeMode = effectivePolicy?.consent?.scopeMode ?? 'permissive';
|
|
2873
|
+
const hasWildcardCategoryScope = allowedCategories?.includes('*') === true;
|
|
2874
|
+
const appliedPreferenceEntries = Object.entries(preferences);
|
|
2875
|
+
let filteredAppliedPreferenceEntries = appliedPreferenceEntries;
|
|
2876
|
+
if (allowedCategories && allowedCategories.length > 0 && !hasWildcardCategoryScope) {
|
|
2877
|
+
const disallowed = appliedPreferenceEntries.map(([purpose])=>purpose).filter((purpose)=>!allowedCategories.includes(purpose));
|
|
2878
|
+
filteredAppliedPreferenceEntries = appliedPreferenceEntries.filter(([purpose])=>allowedCategories.includes(purpose));
|
|
2879
|
+
if (disallowed.length > 0 && 'strict' === effectiveScopeMode) throw new HTTPException(400, {
|
|
2880
|
+
message: 'Preferences include categories not allowed by policy',
|
|
2881
|
+
cause: {
|
|
2882
|
+
code: 'PURPOSE_NOT_ALLOWED',
|
|
2883
|
+
disallowed
|
|
2884
|
+
}
|
|
2885
|
+
});
|
|
2886
|
+
}
|
|
2887
|
+
appliedPreferences = Object.fromEntries(filteredAppliedPreferenceEntries);
|
|
2888
|
+
const filteredConsentedPurposeCodes = filteredAppliedPreferenceEntries.filter(([_, isConsented])=>isConsented).map(([purposeCode])=>purposeCode);
|
|
2307
2889
|
logger.debug('Consented purposes', {
|
|
2308
|
-
consentedPurposes
|
|
2890
|
+
consentedPurposes: filteredConsentedPurposeCodes
|
|
2309
2891
|
});
|
|
2310
|
-
const purposesRaw = await Promise.all(
|
|
2892
|
+
const purposesRaw = await Promise.all(filteredConsentedPurposeCodes.map((purposeCode)=>registry.findOrCreateConsentPurposeByCode(purposeCode)));
|
|
2311
2893
|
const purposes = purposesRaw.map((purpose)=>purpose?.id ?? null).filter((id)=>Boolean(id));
|
|
2312
2894
|
logger.debug('Filtered purposes', {
|
|
2313
2895
|
purposes
|
|
2314
2896
|
});
|
|
2315
2897
|
if (0 === purposes.length) logger.warn('No valid purpose IDs found after filtering. Using empty list.', {
|
|
2316
|
-
consentedPurposes
|
|
2898
|
+
consentedPurposes: filteredConsentedPurposeCodes
|
|
2317
2899
|
});
|
|
2318
2900
|
purposeIds = purposes;
|
|
2319
2901
|
}
|
|
2902
|
+
const expiryDays = effectivePolicy?.consent?.expiryDays;
|
|
2903
|
+
const validUntil = 'number' == typeof expiryDays && Number.isFinite(expiryDays) ? new Date(givenAt.getTime() + 86400000 * Math.max(0, expiryDays)) : void 0;
|
|
2904
|
+
const proofConfig = effectivePolicy?.proof;
|
|
2905
|
+
const shouldStoreIp = proofConfig?.storeIp ?? true;
|
|
2906
|
+
const shouldStoreUserAgent = proofConfig?.storeUserAgent ?? true;
|
|
2907
|
+
const shouldStoreLanguage = proofConfig?.storeLanguage ?? false;
|
|
2908
|
+
const effectiveLanguage = (snapshotPayload?.language && hasValidSnapshot ? snapshotPayload.language : requestLanguage) ?? void 0;
|
|
2909
|
+
const metadataWithPolicy = {
|
|
2910
|
+
...metadata ?? {},
|
|
2911
|
+
...shouldStoreLanguage && effectiveLanguage ? {
|
|
2912
|
+
policyLanguage: effectiveLanguage
|
|
2913
|
+
} : {}
|
|
2914
|
+
};
|
|
2915
|
+
const effectiveJurisdiction = hasValidSnapshot && snapshotPayload ? snapshotPayload.jurisdiction : resolvedJurisdiction;
|
|
2916
|
+
const decisionPayload = buildDecisionPayload({
|
|
2917
|
+
tenantId: ctx.tenantId,
|
|
2918
|
+
snapshot: hasValidSnapshot && snapshotPayload ? {
|
|
2919
|
+
valid: true,
|
|
2920
|
+
payload: snapshotPayload
|
|
2921
|
+
} : null,
|
|
2922
|
+
decision: resolvedPolicyDecision,
|
|
2923
|
+
location: {
|
|
2924
|
+
countryCode: location.countryCode,
|
|
2925
|
+
regionCode: location.regionCode
|
|
2926
|
+
},
|
|
2927
|
+
jurisdiction: resolvedJurisdiction,
|
|
2928
|
+
language: effectiveLanguage,
|
|
2929
|
+
proofConfig
|
|
2930
|
+
});
|
|
2320
2931
|
const existingConsent = await db.findFirst('consent', {
|
|
2321
2932
|
where: (b)=>b.and(b('subjectId', '=', subject.id), b('domainId', '=', domainRecord.id), b('policyId', '=', policyId), b('givenAt', '=', givenAt))
|
|
2322
2933
|
});
|
|
@@ -2331,6 +2942,7 @@ const postSubjectHandler = async (c)=>{
|
|
|
2331
2942
|
domain: domainRecord.name,
|
|
2332
2943
|
type,
|
|
2333
2944
|
metadata,
|
|
2945
|
+
appliedPreferences,
|
|
2334
2946
|
uiSource: input.uiSource,
|
|
2335
2947
|
givenAt: existingConsent.givenAt
|
|
2336
2948
|
});
|
|
@@ -2342,6 +2954,42 @@ const postSubjectHandler = async (c)=>{
|
|
|
2342
2954
|
policyId,
|
|
2343
2955
|
purposeIds
|
|
2344
2956
|
});
|
|
2957
|
+
const runtimePolicyDecision = decisionPayload ? await tx.findFirst('runtimePolicyDecision', {
|
|
2958
|
+
where: (b)=>b('dedupeKey', '=', decisionPayload.dedupeKey)
|
|
2959
|
+
}) ?? await tx.create('runtimePolicyDecision', {
|
|
2960
|
+
id: `rpd_${crypto.randomUUID().replaceAll('-', '')}`,
|
|
2961
|
+
tenantId: decisionPayload.tenantId,
|
|
2962
|
+
policyId: decisionPayload.policyId,
|
|
2963
|
+
fingerprint: decisionPayload.fingerprint,
|
|
2964
|
+
matchedBy: decisionPayload.matchedBy,
|
|
2965
|
+
countryCode: decisionPayload.countryCode,
|
|
2966
|
+
regionCode: decisionPayload.regionCode,
|
|
2967
|
+
jurisdiction: decisionPayload.jurisdiction,
|
|
2968
|
+
language: decisionPayload.language,
|
|
2969
|
+
model: decisionPayload.model,
|
|
2970
|
+
policyI18n: decisionPayload.policyI18n ? {
|
|
2971
|
+
json: decisionPayload.policyI18n
|
|
2972
|
+
} : void 0,
|
|
2973
|
+
uiMode: decisionPayload.uiMode,
|
|
2974
|
+
bannerUi: decisionPayload.bannerUi ? {
|
|
2975
|
+
json: decisionPayload.bannerUi
|
|
2976
|
+
} : void 0,
|
|
2977
|
+
dialogUi: decisionPayload.dialogUi ? {
|
|
2978
|
+
json: decisionPayload.dialogUi
|
|
2979
|
+
} : void 0,
|
|
2980
|
+
categories: decisionPayload.categories ? {
|
|
2981
|
+
json: decisionPayload.categories
|
|
2982
|
+
} : void 0,
|
|
2983
|
+
preselectedCategories: decisionPayload.preselectedCategories ? {
|
|
2984
|
+
json: decisionPayload.preselectedCategories
|
|
2985
|
+
} : void 0,
|
|
2986
|
+
proofConfig: decisionPayload.proofConfig ? {
|
|
2987
|
+
json: decisionPayload.proofConfig
|
|
2988
|
+
} : void 0,
|
|
2989
|
+
dedupeKey: decisionPayload.dedupeKey
|
|
2990
|
+
}).catch(async ()=>tx.findFirst('runtimePolicyDecision', {
|
|
2991
|
+
where: (b)=>b('dedupeKey', '=', decisionPayload.dedupeKey)
|
|
2992
|
+
})) : void 0;
|
|
2345
2993
|
const consentRecord = await tx.create('consent', {
|
|
2346
2994
|
id: await utils_generateUniqueId(tx, 'consent', ctx),
|
|
2347
2995
|
subjectId: subject.id,
|
|
@@ -2350,17 +2998,20 @@ const postSubjectHandler = async (c)=>{
|
|
|
2350
2998
|
purposeIds: {
|
|
2351
2999
|
json: purposeIds
|
|
2352
3000
|
},
|
|
2353
|
-
metadata:
|
|
2354
|
-
json:
|
|
3001
|
+
metadata: Object.keys(metadataWithPolicy).length > 0 ? {
|
|
3002
|
+
json: metadataWithPolicy
|
|
2355
3003
|
} : void 0,
|
|
2356
|
-
ipAddress: ctx.ipAddress,
|
|
2357
|
-
userAgent: ctx.userAgent,
|
|
2358
|
-
jurisdiction:
|
|
2359
|
-
jurisdictionModel:
|
|
3004
|
+
ipAddress: shouldStoreIp ? ctx.ipAddress : null,
|
|
3005
|
+
userAgent: shouldStoreUserAgent ? ctx.userAgent : null,
|
|
3006
|
+
jurisdiction: effectiveJurisdiction,
|
|
3007
|
+
jurisdictionModel: effectiveModel,
|
|
2360
3008
|
tcString: input.tcString,
|
|
2361
3009
|
uiSource: input.uiSource,
|
|
2362
3010
|
consentAction: derivedConsentAction,
|
|
2363
|
-
givenAt
|
|
3011
|
+
givenAt,
|
|
3012
|
+
validUntil,
|
|
3013
|
+
runtimePolicyDecisionId: runtimePolicyDecision?.id,
|
|
3014
|
+
runtimePolicySource: decisionPayload?.source
|
|
2364
3015
|
});
|
|
2365
3016
|
logger.debug('Created consent', {
|
|
2366
3017
|
consentRecord: consentRecord.id
|
|
@@ -2379,7 +3030,7 @@ const postSubjectHandler = async (c)=>{
|
|
|
2379
3030
|
});
|
|
2380
3031
|
const metrics = getMetrics();
|
|
2381
3032
|
if (metrics) {
|
|
2382
|
-
const jurisdiction =
|
|
3033
|
+
const jurisdiction = effectiveJurisdiction;
|
|
2383
3034
|
metrics.recordConsentCreated({
|
|
2384
3035
|
type,
|
|
2385
3036
|
jurisdiction
|
|
@@ -2401,6 +3052,7 @@ const postSubjectHandler = async (c)=>{
|
|
|
2401
3052
|
domain: domainRecord.name,
|
|
2402
3053
|
type,
|
|
2403
3054
|
metadata,
|
|
3055
|
+
appliedPreferences,
|
|
2404
3056
|
uiSource: input.uiSource,
|
|
2405
3057
|
givenAt: result.consent.givenAt
|
|
2406
3058
|
});
|
|
@@ -2524,6 +3176,86 @@ const createSubjectRoutes = ()=>{
|
|
|
2524
3176
|
return app;
|
|
2525
3177
|
};
|
|
2526
3178
|
const defineConfig = (config)=>config;
|
|
3179
|
+
const DEFAULT_FALLBACK_POLICY_INPUT = {
|
|
3180
|
+
id: 'world_no_banner',
|
|
3181
|
+
isDefault: true,
|
|
3182
|
+
model: 'none',
|
|
3183
|
+
uiMode: 'none'
|
|
3184
|
+
};
|
|
3185
|
+
function mergeMatch(input) {
|
|
3186
|
+
return policyMatchers.merge(input.countries?.length ? policyMatchers.countries(input.countries) : {}, input.regions?.length ? policyMatchers.regions(input.regions) : {}, input.isDefault ? policyMatchers["default"]() : {});
|
|
3187
|
+
}
|
|
3188
|
+
function compactUiSurface(value) {
|
|
3189
|
+
if (!value) return;
|
|
3190
|
+
return compactDefined({
|
|
3191
|
+
allowedActions: dedupeTrimmedStrings(value.allowedActions),
|
|
3192
|
+
primaryAction: value.primaryAction,
|
|
3193
|
+
layout: value.layout,
|
|
3194
|
+
direction: value.direction,
|
|
3195
|
+
uiProfile: value.uiProfile,
|
|
3196
|
+
scrollLock: value.scrollLock
|
|
3197
|
+
});
|
|
3198
|
+
}
|
|
3199
|
+
function buildPolicyConfig(input) {
|
|
3200
|
+
const categories = dedupeTrimmedStrings(input.categories);
|
|
3201
|
+
const preselectedCategories = dedupeTrimmedStrings(input.preselectedCategories);
|
|
3202
|
+
return {
|
|
3203
|
+
id: input.id,
|
|
3204
|
+
match: mergeMatch(input),
|
|
3205
|
+
i18n: input.i18n,
|
|
3206
|
+
consent: compactDefined({
|
|
3207
|
+
model: input.model,
|
|
3208
|
+
expiryDays: input.expiryDays,
|
|
3209
|
+
scopeMode: input.scopeMode,
|
|
3210
|
+
categories,
|
|
3211
|
+
preselectedCategories,
|
|
3212
|
+
gpc: input.gpc
|
|
3213
|
+
}),
|
|
3214
|
+
ui: compactDefined({
|
|
3215
|
+
mode: input.uiMode,
|
|
3216
|
+
banner: compactUiSurface(input.banner),
|
|
3217
|
+
dialog: compactUiSurface(input.dialog)
|
|
3218
|
+
}),
|
|
3219
|
+
proof: compactDefined({
|
|
3220
|
+
storeIp: input.proof?.storeIp,
|
|
3221
|
+
storeUserAgent: input.proof?.storeUserAgent,
|
|
3222
|
+
storeLanguage: input.proof?.storeLanguage
|
|
3223
|
+
})
|
|
3224
|
+
};
|
|
3225
|
+
}
|
|
3226
|
+
function buildPolicyPack(inputs) {
|
|
3227
|
+
return inputs.map((input)=>buildPolicyConfig(input));
|
|
3228
|
+
}
|
|
3229
|
+
function buildPolicyPackWithDefault(inputs, defaultPolicy) {
|
|
3230
|
+
const pack = buildPolicyPack(inputs);
|
|
3231
|
+
const hasDefault = pack.some((policy)=>policy.match.isDefault);
|
|
3232
|
+
if (hasDefault) return pack;
|
|
3233
|
+
const fallbackInput = defaultPolicy ? {
|
|
3234
|
+
...defaultPolicy,
|
|
3235
|
+
isDefault: true,
|
|
3236
|
+
countries: void 0,
|
|
3237
|
+
regions: void 0
|
|
3238
|
+
} : DEFAULT_FALLBACK_POLICY_INPUT;
|
|
3239
|
+
return [
|
|
3240
|
+
...pack,
|
|
3241
|
+
buildPolicyConfig(fallbackInput)
|
|
3242
|
+
];
|
|
3243
|
+
}
|
|
3244
|
+
function composePacks(...packs) {
|
|
3245
|
+
const seen = new Set();
|
|
3246
|
+
const result = [];
|
|
3247
|
+
for (const pack of packs)for (const policy of pack)if (!seen.has(policy.id)) {
|
|
3248
|
+
seen.add(policy.id);
|
|
3249
|
+
result.push(policy);
|
|
3250
|
+
}
|
|
3251
|
+
return result;
|
|
3252
|
+
}
|
|
3253
|
+
const policyBuilder = {
|
|
3254
|
+
create: buildPolicyConfig,
|
|
3255
|
+
createPack: buildPolicyPack,
|
|
3256
|
+
createPackWithDefault: buildPolicyPackWithDefault,
|
|
3257
|
+
composePacks
|
|
3258
|
+
};
|
|
2527
3259
|
const c15tInstance = (options)=>{
|
|
2528
3260
|
const context = init(options);
|
|
2529
3261
|
const logger = logger_createLogger(options.logger);
|
|
@@ -2700,4 +3432,4 @@ const c15tInstance = (options)=>{
|
|
|
2700
3432
|
getDocsUI
|
|
2701
3433
|
};
|
|
2702
3434
|
};
|
|
2703
|
-
export { c15tInstance, defineConfig, version_version as version };
|
|
3435
|
+
export { EEA_COUNTRY_CODES, EU_COUNTRY_CODES, POLICY_MATCH_DATASET_VERSION, UK_COUNTRY_CODES, c15tInstance, defineConfig, policy_inspectPolicies as inspectPolicies, policyBuilder, policyMatchers, policyPackPresets, version_version as version };
|