@c15t/backend 2.0.0-rc.3 → 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/cache.cjs +4 -4
- package/dist/cache.js +4 -4
- package/dist/core.cjs +845 -87
- package/dist/core.js +821 -87
- 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 +621 -71
- package/dist/router.js +621 -71
- 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-types/define-config.d.ts +17 -0
- 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 +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 +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-types/types/index.d.ts +443 -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 +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 +37 -23
- package/.turbo/turbo-build.log +0 -49
- package/CHANGELOG.md +0 -115
- 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 +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 +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 +0 -263
- 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 -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 -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 -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/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 -297
- 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.cjs → types/index.cjs} +0 -0
- /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.js
CHANGED
|
@@ -5,11 +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 {
|
|
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";
|
|
13
16
|
import { object, optional, string } from "valibot";
|
|
14
17
|
function extractBearerToken(authHeader) {
|
|
15
18
|
if (!authHeader) return null;
|
|
@@ -136,12 +139,12 @@ function createCORSOptions(trustedOrigins) {
|
|
|
136
139
|
]
|
|
137
140
|
};
|
|
138
141
|
}
|
|
139
|
-
const version_version = '2.0.0-rc.
|
|
142
|
+
const version_version = '2.0.0-rc.5';
|
|
140
143
|
const config_createOpenAPIConfig = (options)=>({
|
|
141
|
-
enabled: options.
|
|
144
|
+
enabled: options.openapi?.enabled !== false,
|
|
142
145
|
specPath: '/spec.json',
|
|
143
146
|
docsPath: '/docs',
|
|
144
|
-
...options.
|
|
147
|
+
...options.openapi || {}
|
|
145
148
|
});
|
|
146
149
|
const DEFAULT_IP_HEADERS = [
|
|
147
150
|
'x-client-ip',
|
|
@@ -236,7 +239,7 @@ function maskIpAddress(ip) {
|
|
|
236
239
|
return ip;
|
|
237
240
|
}
|
|
238
241
|
function getIpAddress(req, options) {
|
|
239
|
-
const ipAddressConfig = options.
|
|
242
|
+
const ipAddressConfig = options.ipAddress;
|
|
240
243
|
if (ipAddressConfig?.tracking === false) return null;
|
|
241
244
|
const ipHeaders = ipAddressConfig?.ipAddressHeaders || DEFAULT_IP_HEADERS;
|
|
242
245
|
const headers = req instanceof Request ? req.headers : req;
|
|
@@ -252,6 +255,133 @@ function getIpAddress(req, options) {
|
|
|
252
255
|
}
|
|
253
256
|
return null;
|
|
254
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
|
+
}
|
|
255
385
|
function extractErrorMessage(error) {
|
|
256
386
|
if (error instanceof AggregateError && error.errors?.length > 0) {
|
|
257
387
|
const inner = error.errors.map((e)=>e instanceof Error ? e.message : String(e)).join('; ');
|
|
@@ -280,18 +410,18 @@ function createTelemetryOptions(appName = 'c15t', telemetryConfig, tenantId) {
|
|
|
280
410
|
return config;
|
|
281
411
|
}
|
|
282
412
|
function isTelemetryEnabled(options) {
|
|
283
|
-
if (options) return options.
|
|
413
|
+
if (options) return options.telemetry?.enabled === true;
|
|
284
414
|
return cachedConfig?.enabled === true;
|
|
285
415
|
}
|
|
286
416
|
const getTracer = (options)=>{
|
|
287
417
|
if (!isTelemetryEnabled(options)) return trace.getTracer('c15t-noop');
|
|
288
|
-
const tracer = options?.
|
|
418
|
+
const tracer = options?.telemetry?.tracer ?? cachedConfig?.tracer;
|
|
289
419
|
if (tracer) return tracer;
|
|
290
420
|
return trace.getTracer(options?.appName ?? 'c15t');
|
|
291
421
|
};
|
|
292
422
|
const getMeter = (options)=>{
|
|
293
423
|
if (!isTelemetryEnabled(options)) return api_metrics.getMeter('c15t-noop');
|
|
294
|
-
const meter = options?.
|
|
424
|
+
const meter = options?.telemetry?.meter ?? cachedConfig?.meter;
|
|
295
425
|
if (meter) return meter;
|
|
296
426
|
return api_metrics.getMeter(options?.appName ?? 'c15t');
|
|
297
427
|
};
|
|
@@ -301,7 +431,7 @@ function getDefaultAttributes() {
|
|
|
301
431
|
const createRequestSpan = (method, path, options)=>{
|
|
302
432
|
if (!isTelemetryEnabled(options)) return null;
|
|
303
433
|
const tracer = getTracer(options);
|
|
304
|
-
const defaultAttrs = options?.
|
|
434
|
+
const defaultAttrs = options?.telemetry?.defaultAttributes || getDefaultAttributes();
|
|
305
435
|
const span = tracer.startSpan(`${method} ${path}`, {
|
|
306
436
|
attributes: {
|
|
307
437
|
'http.method': method,
|
|
@@ -343,7 +473,7 @@ async function executeWithSpan(span, operation) {
|
|
|
343
473
|
}
|
|
344
474
|
}
|
|
345
475
|
function resolveDefaultAttributes(options) {
|
|
346
|
-
return options?.
|
|
476
|
+
return options?.telemetry?.defaultAttributes || getDefaultAttributes();
|
|
347
477
|
}
|
|
348
478
|
async function withDatabaseSpan(attributes, operation, options) {
|
|
349
479
|
if (!isTelemetryEnabled(options)) return operation();
|
|
@@ -574,6 +704,7 @@ const prefixes = {
|
|
|
574
704
|
consentPolicy: 'pol',
|
|
575
705
|
consentPurpose: 'pur',
|
|
576
706
|
domain: 'dom',
|
|
707
|
+
runtimePolicyDecision: 'rpd',
|
|
577
708
|
subject: 'sub'
|
|
578
709
|
};
|
|
579
710
|
const b58 = base_x('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
|
|
@@ -845,6 +976,54 @@ function domainRegistry({ db, ctx }) {
|
|
|
845
976
|
}
|
|
846
977
|
};
|
|
847
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
|
+
}
|
|
848
1027
|
function subjectRegistry({ db, ctx }) {
|
|
849
1028
|
const { logger } = ctx;
|
|
850
1029
|
return {
|
|
@@ -922,7 +1101,8 @@ const createRegistry = (ctx)=>({
|
|
|
922
1101
|
...subjectRegistry(ctx),
|
|
923
1102
|
...consentPurposeRegistry(ctx),
|
|
924
1103
|
...policyRegistry(ctx),
|
|
925
|
-
...domainRegistry(ctx)
|
|
1104
|
+
...domainRegistry(ctx),
|
|
1105
|
+
...runtimePolicyDecisionRegistry(ctx)
|
|
926
1106
|
});
|
|
927
1107
|
const auditLogTable = schema_table('auditLog', {
|
|
928
1108
|
id: idColumn('id', 'varchar(255)'),
|
|
@@ -1089,6 +1269,8 @@ const consent_consentTable = schema_table('consent', {
|
|
|
1089
1269
|
tcString: column('tcString', 'string').nullable(),
|
|
1090
1270
|
uiSource: column('uiSource', 'string').nullable(),
|
|
1091
1271
|
consentAction: column('consentAction', 'string').nullable(),
|
|
1272
|
+
runtimePolicyDecisionId: column('runtimePolicyDecisionId', 'string').nullable(),
|
|
1273
|
+
runtimePolicySource: column('runtimePolicySource', 'string').nullable(),
|
|
1092
1274
|
tenantId: column('tenantId', 'string').nullable()
|
|
1093
1275
|
});
|
|
1094
1276
|
const consent_policy_consentPolicyTable = schema_table('consentPolicy', {
|
|
@@ -1114,6 +1296,27 @@ const domain_domainTable = schema_table('domain', {
|
|
|
1114
1296
|
updatedAt: column('updatedAt', 'timestamp').defaultTo$('now'),
|
|
1115
1297
|
tenantId: column('tenantId', 'string').nullable()
|
|
1116
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
|
+
});
|
|
1117
1320
|
const subject_subjectTable = schema_table('subject', {
|
|
1118
1321
|
id: idColumn('id', 'varchar(255)'),
|
|
1119
1322
|
externalId: column('externalId', 'string').nullable(),
|
|
@@ -1128,6 +1331,7 @@ const v2 = schema({
|
|
|
1128
1331
|
subject: subject_subjectTable,
|
|
1129
1332
|
domain: domain_domainTable,
|
|
1130
1333
|
consentPolicy: consent_policy_consentPolicyTable,
|
|
1334
|
+
runtimePolicyDecision: runtimePolicyDecisionTable,
|
|
1131
1335
|
consentPurpose: consent_purpose_consentPurposeTable,
|
|
1132
1336
|
consent: consent_consentTable,
|
|
1133
1337
|
auditLog: audit_log_auditLogTable
|
|
@@ -1143,6 +1347,9 @@ const v2 = schema({
|
|
|
1143
1347
|
consentPolicy: ({ many })=>({
|
|
1144
1348
|
consents: many('consent')
|
|
1145
1349
|
}),
|
|
1350
|
+
runtimePolicyDecision: ({ many })=>({
|
|
1351
|
+
consents: many('consent')
|
|
1352
|
+
}),
|
|
1146
1353
|
consentPurpose: ()=>({}),
|
|
1147
1354
|
consent: ({ one })=>({
|
|
1148
1355
|
subject: one('subject', [
|
|
@@ -1156,6 +1363,10 @@ const v2 = schema({
|
|
|
1156
1363
|
policy: one('consentPolicy', [
|
|
1157
1364
|
'policyId',
|
|
1158
1365
|
'id'
|
|
1366
|
+
]).foreignKey(),
|
|
1367
|
+
runtimePolicyDecision: one('runtimePolicyDecision', [
|
|
1368
|
+
'runtimePolicyDecisionId',
|
|
1369
|
+
'id'
|
|
1159
1370
|
]).foreignKey()
|
|
1160
1371
|
}),
|
|
1161
1372
|
auditLog: ({ one })=>({
|
|
@@ -1242,6 +1453,18 @@ function withTenantScope(db, tenantId) {
|
|
|
1242
1453
|
}
|
|
1243
1454
|
});
|
|
1244
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
|
+
}
|
|
1245
1468
|
let globalLogger;
|
|
1246
1469
|
function initLogger(options) {
|
|
1247
1470
|
globalLogger = logger_createLogger({
|
|
@@ -1256,7 +1479,7 @@ const init = (options)=>{
|
|
|
1256
1479
|
...options.logger,
|
|
1257
1480
|
appName: String(appName)
|
|
1258
1481
|
});
|
|
1259
|
-
const telemetryOptions = createTelemetryOptions(String(appName), options.
|
|
1482
|
+
const telemetryOptions = createTelemetryOptions(String(appName), options.telemetry, options.tenantId);
|
|
1260
1483
|
if (isTelemetryEnabled(options)) logger.debug('Telemetry is enabled', {
|
|
1261
1484
|
hasTracer: !!telemetryOptions?.tracer,
|
|
1262
1485
|
hasMeter: !!telemetryOptions?.meter,
|
|
@@ -1267,8 +1490,21 @@ const init = (options)=>{
|
|
|
1267
1490
|
const client = db.client(options.adapter);
|
|
1268
1491
|
const rawOrm = client.orm('2.0.0');
|
|
1269
1492
|
const orm = options.tenantId ? withTenantScope(rawOrm, options.tenantId) : rawOrm;
|
|
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]);
|
|
1270
1506
|
const context = {
|
|
1271
|
-
...
|
|
1507
|
+
...baseOptions,
|
|
1272
1508
|
appName,
|
|
1273
1509
|
logger,
|
|
1274
1510
|
db: orm,
|
|
@@ -1626,6 +1862,123 @@ function createGVLResolver(options) {
|
|
|
1626
1862
|
}
|
|
1627
1863
|
};
|
|
1628
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
|
+
}
|
|
1629
1982
|
function geo_normalizeHeader(value) {
|
|
1630
1983
|
if (!value) return null;
|
|
1631
1984
|
return Array.isArray(value) ? value[0] ?? null : value;
|
|
@@ -1767,7 +2120,7 @@ function checkJurisdiction(countryCode, regionCode) {
|
|
|
1767
2120
|
return jurisdiction;
|
|
1768
2121
|
}
|
|
1769
2122
|
async function getLocation(request, options) {
|
|
1770
|
-
if (options.
|
|
2123
|
+
if (options.disableGeoLocation) return {
|
|
1771
2124
|
countryCode: null,
|
|
1772
2125
|
regionCode: null
|
|
1773
2126
|
};
|
|
@@ -1778,29 +2131,121 @@ async function getLocation(request, options) {
|
|
|
1778
2131
|
};
|
|
1779
2132
|
}
|
|
1780
2133
|
function getJurisdiction(location, options) {
|
|
1781
|
-
if (options.
|
|
2134
|
+
if (options.disableGeoLocation) return 'GDPR';
|
|
1782
2135
|
return checkJurisdiction(location.countryCode, location.regionCode);
|
|
1783
2136
|
}
|
|
1784
|
-
function
|
|
1785
|
-
|
|
2137
|
+
function stripIabTranslations(translations) {
|
|
2138
|
+
const { iab: _iab, ...rest } = translations;
|
|
2139
|
+
return rest;
|
|
1786
2140
|
}
|
|
1787
|
-
function
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
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
|
|
1797
2220
|
});
|
|
1798
|
-
const base = isSupportedBaseLanguage(preferredLanguage) ? baseTranslations[preferredLanguage] : baseTranslations.en;
|
|
1799
|
-
const custom = supportedCustomLanguages.includes(preferredLanguage) ? customTranslations?.[preferredLanguage] : {};
|
|
1800
|
-
const translations = custom ? deepMergeTranslations(base, custom) : base;
|
|
1801
2221
|
return {
|
|
1802
|
-
|
|
1803
|
-
|
|
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
|
+
}
|
|
1804
2249
|
};
|
|
1805
2250
|
}
|
|
1806
2251
|
const createInitRoute = (options)=>{
|
|
@@ -1813,7 +2258,7 @@ const createInitRoute = (options)=>{
|
|
|
1813
2258
|
- **Location** – User's location (null if geo-location is disabled)
|
|
1814
2259
|
- **Translations** – Consent manager copy (from \`Accept-Language\` header)
|
|
1815
2260
|
- **Branding** – Configured branding key
|
|
1816
|
-
- **GVL** – Global Vendor List when
|
|
2261
|
+
- **GVL** – Global Vendor List when IAB is active for the request
|
|
1817
2262
|
|
|
1818
2263
|
Use for geo-targeted consent banners and regional compliance.`,
|
|
1819
2264
|
tags: [
|
|
@@ -1830,42 +2275,9 @@ Use for geo-targeted consent banners and regional compliance.`,
|
|
|
1830
2275
|
}
|
|
1831
2276
|
}
|
|
1832
2277
|
}), async (c)=>{
|
|
1833
|
-
const
|
|
1834
|
-
const
|
|
1835
|
-
|
|
1836
|
-
const jurisdiction = getJurisdiction(location, options);
|
|
1837
|
-
const translationsResult = translations_getTranslationsData(acceptLanguage, options.advanced?.customTranslations);
|
|
1838
|
-
let gvl = null;
|
|
1839
|
-
if (options.advanced?.iab?.enabled) {
|
|
1840
|
-
const language = translationsResult.language.split('-')[0] || 'en';
|
|
1841
|
-
const gvlResolver = createGVLResolver({
|
|
1842
|
-
appName: options.appName || 'c15t',
|
|
1843
|
-
bundled: options.advanced.iab.bundled,
|
|
1844
|
-
cacheAdapter: options.advanced.cache?.adapter,
|
|
1845
|
-
vendorIds: options.advanced.iab.vendorIds,
|
|
1846
|
-
endpoint: options.advanced.iab.endpoint
|
|
1847
|
-
});
|
|
1848
|
-
gvl = await gvlResolver.get(language);
|
|
1849
|
-
}
|
|
1850
|
-
const customVendors = options.advanced?.iab?.customVendors;
|
|
1851
|
-
const gpc = '1' === request.headers.get('sec-gpc');
|
|
1852
|
-
getMetrics()?.recordInit({
|
|
1853
|
-
jurisdiction,
|
|
1854
|
-
country: location?.countryCode ?? void 0,
|
|
1855
|
-
region: location?.regionCode ?? void 0,
|
|
1856
|
-
gpc
|
|
1857
|
-
});
|
|
1858
|
-
return c.json({
|
|
1859
|
-
jurisdiction,
|
|
1860
|
-
location,
|
|
1861
|
-
translations: translationsResult,
|
|
1862
|
-
branding: options.advanced?.branding || 'c15t',
|
|
1863
|
-
gvl,
|
|
1864
|
-
customVendors,
|
|
1865
|
-
...options.advanced?.iab?.cmpId != null && {
|
|
1866
|
-
cmpId: options.advanced.iab.cmpId
|
|
1867
|
-
}
|
|
1868
|
-
});
|
|
2278
|
+
const ctx = c.get('c15tContext');
|
|
2279
|
+
const payload = await resolveInitPayload(c.req.raw, options, ctx?.logger);
|
|
2280
|
+
return c.json(payload);
|
|
1869
2281
|
});
|
|
1870
2282
|
return app;
|
|
1871
2283
|
};
|
|
@@ -2221,6 +2633,119 @@ const patchSubjectHandler = async (c)=>{
|
|
|
2221
2633
|
});
|
|
2222
2634
|
}
|
|
2223
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
|
+
}
|
|
2224
2749
|
const postSubjectHandler = async (c)=>{
|
|
2225
2750
|
const ctx = c.get('c15tContext');
|
|
2226
2751
|
const logger = ctx.logger;
|
|
@@ -2232,9 +2757,6 @@ const postSubjectHandler = async (c)=>{
|
|
|
2232
2757
|
const givenAt = new Date(givenAtEpoch);
|
|
2233
2758
|
const rawConsentAction = 'consentAction' in input ? input.consentAction : void 0;
|
|
2234
2759
|
let derivedConsentAction;
|
|
2235
|
-
if ('all' === rawConsentAction) derivedConsentAction = 'accept_all';
|
|
2236
|
-
else if ('necessary' === rawConsentAction) derivedConsentAction = 'opt-out' === input.jurisdictionModel ? 'opt_out' : 'reject_all';
|
|
2237
|
-
else if ('custom' === rawConsentAction) derivedConsentAction = 'custom';
|
|
2238
2760
|
logger.debug('Request parameters', {
|
|
2239
2761
|
type,
|
|
2240
2762
|
subjectId,
|
|
@@ -2243,6 +2765,50 @@ const postSubjectHandler = async (c)=>{
|
|
|
2243
2765
|
domain
|
|
2244
2766
|
});
|
|
2245
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';
|
|
2246
2812
|
const subject = await registry.findOrCreateSubject({
|
|
2247
2813
|
subjectId,
|
|
2248
2814
|
externalSubjectId,
|
|
@@ -2269,6 +2835,7 @@ const postSubjectHandler = async (c)=>{
|
|
|
2269
2835
|
});
|
|
2270
2836
|
let policyId;
|
|
2271
2837
|
let purposeIds = [];
|
|
2838
|
+
let appliedPreferences;
|
|
2272
2839
|
const inputPolicyId = 'policyId' in input ? input.policyId : void 0;
|
|
2273
2840
|
if (inputPolicyId) {
|
|
2274
2841
|
policyId = inputPolicyId;
|
|
@@ -2301,20 +2868,66 @@ const postSubjectHandler = async (c)=>{
|
|
|
2301
2868
|
policyId = policy.id;
|
|
2302
2869
|
}
|
|
2303
2870
|
if (preferences) {
|
|
2304
|
-
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);
|
|
2305
2889
|
logger.debug('Consented purposes', {
|
|
2306
|
-
consentedPurposes
|
|
2890
|
+
consentedPurposes: filteredConsentedPurposeCodes
|
|
2307
2891
|
});
|
|
2308
|
-
const purposesRaw = await Promise.all(
|
|
2892
|
+
const purposesRaw = await Promise.all(filteredConsentedPurposeCodes.map((purposeCode)=>registry.findOrCreateConsentPurposeByCode(purposeCode)));
|
|
2309
2893
|
const purposes = purposesRaw.map((purpose)=>purpose?.id ?? null).filter((id)=>Boolean(id));
|
|
2310
2894
|
logger.debug('Filtered purposes', {
|
|
2311
2895
|
purposes
|
|
2312
2896
|
});
|
|
2313
2897
|
if (0 === purposes.length) logger.warn('No valid purpose IDs found after filtering. Using empty list.', {
|
|
2314
|
-
consentedPurposes
|
|
2898
|
+
consentedPurposes: filteredConsentedPurposeCodes
|
|
2315
2899
|
});
|
|
2316
2900
|
purposeIds = purposes;
|
|
2317
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
|
+
});
|
|
2318
2931
|
const existingConsent = await db.findFirst('consent', {
|
|
2319
2932
|
where: (b)=>b.and(b('subjectId', '=', subject.id), b('domainId', '=', domainRecord.id), b('policyId', '=', policyId), b('givenAt', '=', givenAt))
|
|
2320
2933
|
});
|
|
@@ -2329,6 +2942,7 @@ const postSubjectHandler = async (c)=>{
|
|
|
2329
2942
|
domain: domainRecord.name,
|
|
2330
2943
|
type,
|
|
2331
2944
|
metadata,
|
|
2945
|
+
appliedPreferences,
|
|
2332
2946
|
uiSource: input.uiSource,
|
|
2333
2947
|
givenAt: existingConsent.givenAt
|
|
2334
2948
|
});
|
|
@@ -2340,6 +2954,42 @@ const postSubjectHandler = async (c)=>{
|
|
|
2340
2954
|
policyId,
|
|
2341
2955
|
purposeIds
|
|
2342
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;
|
|
2343
2993
|
const consentRecord = await tx.create('consent', {
|
|
2344
2994
|
id: await utils_generateUniqueId(tx, 'consent', ctx),
|
|
2345
2995
|
subjectId: subject.id,
|
|
@@ -2348,17 +2998,20 @@ const postSubjectHandler = async (c)=>{
|
|
|
2348
2998
|
purposeIds: {
|
|
2349
2999
|
json: purposeIds
|
|
2350
3000
|
},
|
|
2351
|
-
metadata:
|
|
2352
|
-
json:
|
|
3001
|
+
metadata: Object.keys(metadataWithPolicy).length > 0 ? {
|
|
3002
|
+
json: metadataWithPolicy
|
|
2353
3003
|
} : void 0,
|
|
2354
|
-
ipAddress: ctx.ipAddress,
|
|
2355
|
-
userAgent: ctx.userAgent,
|
|
2356
|
-
jurisdiction:
|
|
2357
|
-
jurisdictionModel:
|
|
3004
|
+
ipAddress: shouldStoreIp ? ctx.ipAddress : null,
|
|
3005
|
+
userAgent: shouldStoreUserAgent ? ctx.userAgent : null,
|
|
3006
|
+
jurisdiction: effectiveJurisdiction,
|
|
3007
|
+
jurisdictionModel: effectiveModel,
|
|
2358
3008
|
tcString: input.tcString,
|
|
2359
3009
|
uiSource: input.uiSource,
|
|
2360
3010
|
consentAction: derivedConsentAction,
|
|
2361
|
-
givenAt
|
|
3011
|
+
givenAt,
|
|
3012
|
+
validUntil,
|
|
3013
|
+
runtimePolicyDecisionId: runtimePolicyDecision?.id,
|
|
3014
|
+
runtimePolicySource: decisionPayload?.source
|
|
2362
3015
|
});
|
|
2363
3016
|
logger.debug('Created consent', {
|
|
2364
3017
|
consentRecord: consentRecord.id
|
|
@@ -2377,7 +3030,7 @@ const postSubjectHandler = async (c)=>{
|
|
|
2377
3030
|
});
|
|
2378
3031
|
const metrics = getMetrics();
|
|
2379
3032
|
if (metrics) {
|
|
2380
|
-
const jurisdiction =
|
|
3033
|
+
const jurisdiction = effectiveJurisdiction;
|
|
2381
3034
|
metrics.recordConsentCreated({
|
|
2382
3035
|
type,
|
|
2383
3036
|
jurisdiction
|
|
@@ -2399,6 +3052,7 @@ const postSubjectHandler = async (c)=>{
|
|
|
2399
3052
|
domain: domainRecord.name,
|
|
2400
3053
|
type,
|
|
2401
3054
|
metadata,
|
|
3055
|
+
appliedPreferences,
|
|
2402
3056
|
uiSource: input.uiSource,
|
|
2403
3057
|
givenAt: result.consent.givenAt
|
|
2404
3058
|
});
|
|
@@ -2522,6 +3176,86 @@ const createSubjectRoutes = ()=>{
|
|
|
2522
3176
|
return app;
|
|
2523
3177
|
};
|
|
2524
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
|
+
};
|
|
2525
3259
|
const c15tInstance = (options)=>{
|
|
2526
3260
|
const context = init(options);
|
|
2527
3261
|
const logger = logger_createLogger(options.logger);
|
|
@@ -2534,7 +3268,7 @@ const c15tInstance = (options)=>{
|
|
|
2534
3268
|
app.use('*', async (c, next)=>{
|
|
2535
3269
|
const request = c.req.raw;
|
|
2536
3270
|
const startTime = Date.now();
|
|
2537
|
-
const apiKeyAuthenticated = validateRequestAuth(request.headers, options.
|
|
3271
|
+
const apiKeyAuthenticated = validateRequestAuth(request.headers, options.apiKeys);
|
|
2538
3272
|
const enrichedContext = {
|
|
2539
3273
|
...context,
|
|
2540
3274
|
ipAddress: getIpAddress(request, options),
|
|
@@ -2698,4 +3432,4 @@ const c15tInstance = (options)=>{
|
|
|
2698
3432
|
getDocsUI
|
|
2699
3433
|
};
|
|
2700
3434
|
};
|
|
2701
|
-
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 };
|