@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/router.cjs
CHANGED
|
@@ -646,6 +646,124 @@ function createGVLResolver(options) {
|
|
|
646
646
|
}
|
|
647
647
|
};
|
|
648
648
|
}
|
|
649
|
+
const external_jose_namespaceObject = require("jose");
|
|
650
|
+
const POLICY_SNAPSHOT_JWT_HEADER = {
|
|
651
|
+
alg: 'HS256',
|
|
652
|
+
typ: 'JWT'
|
|
653
|
+
};
|
|
654
|
+
const DEFAULT_POLICY_SNAPSHOT_ISSUER = 'c15t';
|
|
655
|
+
const DEFAULT_POLICY_SNAPSHOT_AUDIENCE = 'c15t-policy-snapshot';
|
|
656
|
+
function resolveSnapshotIssuer(options) {
|
|
657
|
+
return options?.issuer?.trim() || DEFAULT_POLICY_SNAPSHOT_ISSUER;
|
|
658
|
+
}
|
|
659
|
+
function resolveSnapshotAudience(params) {
|
|
660
|
+
const configuredAudience = params.options?.audience?.trim();
|
|
661
|
+
if (configuredAudience) return configuredAudience;
|
|
662
|
+
return params.tenantId ? `${DEFAULT_POLICY_SNAPSHOT_AUDIENCE}:${params.tenantId}` : DEFAULT_POLICY_SNAPSHOT_AUDIENCE;
|
|
663
|
+
}
|
|
664
|
+
function getSigningKey(secret) {
|
|
665
|
+
return new TextEncoder().encode(secret);
|
|
666
|
+
}
|
|
667
|
+
function isPolicySnapshotPayload(payload) {
|
|
668
|
+
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;
|
|
669
|
+
}
|
|
670
|
+
async function createPolicySnapshotToken(params) {
|
|
671
|
+
const { options } = params;
|
|
672
|
+
if (!options?.signingKey) return;
|
|
673
|
+
const iat = Math.floor(Date.now() / 1000);
|
|
674
|
+
const ttlSeconds = options.ttlSeconds ?? 1800;
|
|
675
|
+
const exp = iat + ttlSeconds;
|
|
676
|
+
const iss = resolveSnapshotIssuer(options);
|
|
677
|
+
const aud = resolveSnapshotAudience({
|
|
678
|
+
options,
|
|
679
|
+
tenantId: params.tenantId
|
|
680
|
+
});
|
|
681
|
+
const payload = {
|
|
682
|
+
iss,
|
|
683
|
+
aud,
|
|
684
|
+
sub: params.policyId,
|
|
685
|
+
tenantId: params.tenantId,
|
|
686
|
+
policyId: params.policyId,
|
|
687
|
+
fingerprint: params.fingerprint,
|
|
688
|
+
matchedBy: params.matchedBy,
|
|
689
|
+
country: params.country,
|
|
690
|
+
region: params.region,
|
|
691
|
+
jurisdiction: params.jurisdiction,
|
|
692
|
+
language: params.language,
|
|
693
|
+
model: params.model,
|
|
694
|
+
policyI18n: params.policyI18n,
|
|
695
|
+
expiryDays: params.expiryDays,
|
|
696
|
+
scopeMode: params.scopeMode,
|
|
697
|
+
uiMode: params.uiMode,
|
|
698
|
+
bannerUi: params.bannerUi,
|
|
699
|
+
dialogUi: params.dialogUi,
|
|
700
|
+
categories: params.categories,
|
|
701
|
+
preselectedCategories: params.preselectedCategories,
|
|
702
|
+
gpc: params.gpc,
|
|
703
|
+
proofConfig: params.proofConfig,
|
|
704
|
+
iat,
|
|
705
|
+
exp
|
|
706
|
+
};
|
|
707
|
+
const token = await new external_jose_namespaceObject.SignJWT(payload).setProtectedHeader(POLICY_SNAPSHOT_JWT_HEADER).setIssuedAt(iat).setExpirationTime(exp).sign(getSigningKey(options.signingKey));
|
|
708
|
+
return {
|
|
709
|
+
token,
|
|
710
|
+
payload
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
async function verifyPolicySnapshotToken(params) {
|
|
714
|
+
const { token, options, tenantId } = params;
|
|
715
|
+
if (!options?.signingKey) return {
|
|
716
|
+
valid: false,
|
|
717
|
+
reason: 'missing'
|
|
718
|
+
};
|
|
719
|
+
if (!token) return {
|
|
720
|
+
valid: false,
|
|
721
|
+
reason: 'missing'
|
|
722
|
+
};
|
|
723
|
+
if (3 !== token.split('.').length) return {
|
|
724
|
+
valid: false,
|
|
725
|
+
reason: 'malformed'
|
|
726
|
+
};
|
|
727
|
+
try {
|
|
728
|
+
const { payload, protectedHeader } = await (0, external_jose_namespaceObject.jwtVerify)(token, getSigningKey(options.signingKey), {
|
|
729
|
+
issuer: resolveSnapshotIssuer(options),
|
|
730
|
+
audience: resolveSnapshotAudience({
|
|
731
|
+
options,
|
|
732
|
+
tenantId
|
|
733
|
+
})
|
|
734
|
+
});
|
|
735
|
+
const header = protectedHeader;
|
|
736
|
+
if ('HS256' !== header.alg || 'JWT' !== header.typ) return {
|
|
737
|
+
valid: false,
|
|
738
|
+
reason: 'invalid'
|
|
739
|
+
};
|
|
740
|
+
if (!isPolicySnapshotPayload(payload)) return {
|
|
741
|
+
valid: false,
|
|
742
|
+
reason: 'invalid'
|
|
743
|
+
};
|
|
744
|
+
if (payload.sub !== payload.policyId) return {
|
|
745
|
+
valid: false,
|
|
746
|
+
reason: 'invalid'
|
|
747
|
+
};
|
|
748
|
+
if ((tenantId ?? void 0) !== (payload.tenantId ?? void 0)) return {
|
|
749
|
+
valid: false,
|
|
750
|
+
reason: 'invalid'
|
|
751
|
+
};
|
|
752
|
+
return {
|
|
753
|
+
valid: true,
|
|
754
|
+
payload
|
|
755
|
+
};
|
|
756
|
+
} catch (error) {
|
|
757
|
+
if (error instanceof external_jose_namespaceObject.errors.JWTExpired) return {
|
|
758
|
+
valid: false,
|
|
759
|
+
reason: 'expired'
|
|
760
|
+
};
|
|
761
|
+
return {
|
|
762
|
+
valid: false,
|
|
763
|
+
reason: 'invalid'
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
}
|
|
649
767
|
function geo_normalizeHeader(value) {
|
|
650
768
|
if (!value) return null;
|
|
651
769
|
return Array.isArray(value) ? value[0] ?? null : value;
|
|
@@ -801,28 +919,250 @@ function getJurisdiction(location, options) {
|
|
|
801
919
|
if (options.disableGeoLocation) return 'GDPR';
|
|
802
920
|
return checkJurisdiction(location.countryCode, location.regionCode);
|
|
803
921
|
}
|
|
922
|
+
const schema_types_namespaceObject = require("@c15t/schema/types");
|
|
923
|
+
async function resolvePolicyDecision(params) {
|
|
924
|
+
return (0, schema_types_namespaceObject.resolvePolicyDecision)({
|
|
925
|
+
policies: params.policies,
|
|
926
|
+
countryCode: params.countryCode,
|
|
927
|
+
regionCode: params.regionCode,
|
|
928
|
+
jurisdiction: params.jurisdiction,
|
|
929
|
+
iabEnabled: params.iabEnabled
|
|
930
|
+
});
|
|
931
|
+
}
|
|
804
932
|
const translations_namespaceObject = require("@c15t/translations");
|
|
805
933
|
const all_namespaceObject = require("@c15t/translations/all");
|
|
934
|
+
const DEFAULT_PROFILE = 'default';
|
|
935
|
+
const warnedKeys = new Set();
|
|
806
936
|
function isSupportedBaseLanguage(lang) {
|
|
807
937
|
return lang in all_namespaceObject.baseTranslations;
|
|
808
938
|
}
|
|
809
|
-
function
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
939
|
+
function warnOnce(logger, key, message, metadata) {
|
|
940
|
+
if (!logger || warnedKeys.has(key)) return;
|
|
941
|
+
warnedKeys.add(key);
|
|
942
|
+
logger.warn(message, metadata);
|
|
943
|
+
}
|
|
944
|
+
function normalizeLanguage(value) {
|
|
945
|
+
if (!value) return;
|
|
946
|
+
const normalized = value.split(',')[0]?.split(';')[0]?.trim().toLowerCase();
|
|
947
|
+
if (!normalized) return;
|
|
948
|
+
return normalized.split('-')[0] ?? void 0;
|
|
949
|
+
}
|
|
950
|
+
function normalizeProfiles(params) {
|
|
951
|
+
const profiles = params.i18n?.messages;
|
|
952
|
+
const legacy = params.customTranslations;
|
|
953
|
+
if (profiles && Object.keys(profiles).length > 0) {
|
|
954
|
+
if (legacy && Object.keys(legacy).length > 0) warnOnce(params.logger, 'i18n.customTranslations.ignored', '`customTranslations` is deprecated and ignored when `i18n.messages` is configured.');
|
|
955
|
+
return profiles;
|
|
956
|
+
}
|
|
957
|
+
if (legacy && Object.keys(legacy).length > 0) {
|
|
958
|
+
warnOnce(params.logger, 'i18n.customTranslations.deprecated', '`customTranslations` is deprecated. Use `i18n.messages` instead.');
|
|
959
|
+
return {
|
|
960
|
+
[DEFAULT_PROFILE]: {
|
|
961
|
+
translations: legacy
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
return {};
|
|
966
|
+
}
|
|
967
|
+
function buildCandidates(input) {
|
|
968
|
+
const raw = [
|
|
969
|
+
{
|
|
970
|
+
language: input.language,
|
|
971
|
+
reason: 'profile_language'
|
|
972
|
+
},
|
|
973
|
+
{
|
|
974
|
+
language: input.fallbackLanguage,
|
|
975
|
+
reason: 'profile_fallback'
|
|
976
|
+
}
|
|
815
977
|
];
|
|
816
|
-
const
|
|
978
|
+
const dedupe = new Set();
|
|
979
|
+
return raw.filter((candidate)=>{
|
|
980
|
+
const key = candidate.language;
|
|
981
|
+
if (dedupe.has(key)) return false;
|
|
982
|
+
dedupe.add(key);
|
|
983
|
+
return true;
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
function getProfileLanguages(profiles, profile) {
|
|
987
|
+
return Object.keys(profiles[profile]?.translations ?? {}).sort();
|
|
988
|
+
}
|
|
989
|
+
function getSelectableLanguages(input) {
|
|
990
|
+
return getProfileLanguages(input.profiles, input.profile);
|
|
991
|
+
}
|
|
992
|
+
function resolveFallbackLanguage(input) {
|
|
993
|
+
const configuredFallbackLanguage = normalizeLanguage(input.profile?.fallbackLanguage) ?? 'en';
|
|
994
|
+
const profileLanguages = Object.keys(input.profile?.translations ?? {}).sort();
|
|
995
|
+
if (profileLanguages.includes(configuredFallbackLanguage)) return configuredFallbackLanguage;
|
|
996
|
+
if (profileLanguages.includes('en')) return 'en';
|
|
997
|
+
return profileLanguages[0] ?? configuredFallbackLanguage;
|
|
998
|
+
}
|
|
999
|
+
function resolveActiveProfile(input) {
|
|
1000
|
+
const requestedProfile = input.policyProfile ?? input.defaultProfile;
|
|
1001
|
+
if (input.profiles[requestedProfile]) return requestedProfile;
|
|
1002
|
+
if (input.policyProfile) warnOnce(input.logger, `i18n.profile.missing:${requestedProfile}`, `Policy i18n profile '${requestedProfile}' does not exist. Falling back to default profile '${input.defaultProfile}'.`);
|
|
1003
|
+
return input.defaultProfile;
|
|
1004
|
+
}
|
|
1005
|
+
function translations_getTranslationsData(acceptLanguage, customTranslations, options) {
|
|
1006
|
+
const profiles = normalizeProfiles({
|
|
1007
|
+
customTranslations,
|
|
1008
|
+
i18n: options?.i18n,
|
|
1009
|
+
logger: options?.logger
|
|
1010
|
+
});
|
|
1011
|
+
const defaultProfile = options?.i18n?.defaultProfile ?? DEFAULT_PROFILE;
|
|
1012
|
+
const profile = resolveActiveProfile({
|
|
1013
|
+
profiles,
|
|
1014
|
+
defaultProfile,
|
|
1015
|
+
policyProfile: options?.policyI18n?.messageProfile,
|
|
1016
|
+
logger: options?.logger
|
|
1017
|
+
});
|
|
1018
|
+
const configuredLanguages = Object.keys(profiles).length > 0 ? getSelectableLanguages({
|
|
1019
|
+
profiles,
|
|
1020
|
+
profile
|
|
1021
|
+
}) : Object.keys(all_namespaceObject.baseTranslations);
|
|
1022
|
+
const fallbackLanguage = Object.keys(profiles).length > 0 ? resolveFallbackLanguage({
|
|
1023
|
+
profile: profiles[profile]
|
|
1024
|
+
}) : 'en';
|
|
1025
|
+
const policyLanguage = normalizeLanguage(options?.policyI18n?.language);
|
|
1026
|
+
const requestedLanguage = policyLanguage ?? (0, translations_namespaceObject.selectLanguage)(configuredLanguages, {
|
|
817
1027
|
header: acceptLanguage,
|
|
818
|
-
fallback:
|
|
1028
|
+
fallback: fallbackLanguage
|
|
1029
|
+
});
|
|
1030
|
+
const candidates = buildCandidates({
|
|
1031
|
+
language: requestedLanguage,
|
|
1032
|
+
fallbackLanguage
|
|
1033
|
+
});
|
|
1034
|
+
const selectedCandidate = candidates.find((candidate)=>!!profiles[profile]?.translations[candidate.language]);
|
|
1035
|
+
if (selectedCandidate && 'profile_language' !== selectedCandidate.reason) warnOnce(options?.logger, `i18n.fallback:${profile}:${requestedLanguage}:${selectedCandidate.language}`, `Policy translation fallback used (${selectedCandidate.reason}).`, {
|
|
1036
|
+
requestedProfile: profile,
|
|
1037
|
+
requestedLanguage,
|
|
1038
|
+
resolvedProfile: profile,
|
|
1039
|
+
resolvedLanguage: selectedCandidate.language
|
|
819
1040
|
});
|
|
820
|
-
|
|
821
|
-
|
|
1041
|
+
let language = selectedCandidate?.language ?? requestedLanguage;
|
|
1042
|
+
if (!selectedCandidate && !isSupportedBaseLanguage(language)) {
|
|
1043
|
+
warnOnce(options?.logger, `i18n.base-fallback:${language}`, `No translation found for '${language}'. Falling back to base English translations.`);
|
|
1044
|
+
language = 'en';
|
|
1045
|
+
}
|
|
1046
|
+
const base = isSupportedBaseLanguage(language) ? all_namespaceObject.baseTranslations[language] : all_namespaceObject.baseTranslations.en;
|
|
1047
|
+
const custom = selectedCandidate ? profiles[profile]?.translations[selectedCandidate.language] : void 0;
|
|
822
1048
|
const translations = custom ? (0, translations_namespaceObject.deepMergeTranslations)(base, custom) : base;
|
|
823
1049
|
return {
|
|
824
1050
|
translations: translations,
|
|
825
|
-
language
|
|
1051
|
+
language
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
function stripIabTranslations(translations) {
|
|
1055
|
+
const { iab: _iab, ...rest } = translations;
|
|
1056
|
+
return rest;
|
|
1057
|
+
}
|
|
1058
|
+
function resolveNoPolicyFallback() {
|
|
1059
|
+
return {
|
|
1060
|
+
id: 'no_banner',
|
|
1061
|
+
model: 'none',
|
|
1062
|
+
ui: {
|
|
1063
|
+
mode: 'none'
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
async function resolveInitPayload(request, options, logger) {
|
|
1068
|
+
const acceptLanguage = request.headers.get('accept-language') || 'en';
|
|
1069
|
+
const location = await getLocation(request, options);
|
|
1070
|
+
const jurisdiction = getJurisdiction(location, options);
|
|
1071
|
+
const hasExplicitPolicyPack = void 0 !== options.policyPacks;
|
|
1072
|
+
const isExplicitEmptyPolicyPack = hasExplicitPolicyPack && (options.policyPacks?.length ?? 0) === 0;
|
|
1073
|
+
const policyDecision = isExplicitEmptyPolicyPack ? void 0 : await resolvePolicyDecision({
|
|
1074
|
+
policies: options.policyPacks,
|
|
1075
|
+
countryCode: location.countryCode,
|
|
1076
|
+
regionCode: location.regionCode,
|
|
1077
|
+
jurisdiction,
|
|
1078
|
+
iabEnabled: options.iab?.enabled === true
|
|
1079
|
+
});
|
|
1080
|
+
if (hasExplicitPolicyPack && !isExplicitEmptyPolicyPack && !policyDecision) logger?.warn('Policy packs configured but no policy matched', {
|
|
1081
|
+
country: location.countryCode,
|
|
1082
|
+
region: location.regionCode
|
|
1083
|
+
});
|
|
1084
|
+
const resolvedPolicy = hasExplicitPolicyPack ? policyDecision?.policy ?? resolveNoPolicyFallback() : void 0;
|
|
1085
|
+
const iabOptions = options.iab;
|
|
1086
|
+
const shouldIncludeIabPayload = iabOptions?.enabled === true && (!hasExplicitPolicyPack || resolvedPolicy?.model === 'iab');
|
|
1087
|
+
const translationsResult = translations_getTranslationsData(acceptLanguage, options.customTranslations, {
|
|
1088
|
+
i18n: options.i18n,
|
|
1089
|
+
policyI18n: resolvedPolicy?.i18n,
|
|
1090
|
+
logger
|
|
1091
|
+
});
|
|
1092
|
+
const responseTranslations = shouldIncludeIabPayload ? translationsResult : {
|
|
1093
|
+
...translationsResult,
|
|
1094
|
+
translations: stripIabTranslations(translationsResult.translations)
|
|
1095
|
+
};
|
|
1096
|
+
let gvl = null;
|
|
1097
|
+
if (shouldIncludeIabPayload && iabOptions) {
|
|
1098
|
+
const language = translationsResult.language.split('-')[0] || 'en';
|
|
1099
|
+
const gvlResolver = createGVLResolver({
|
|
1100
|
+
appName: options.appName || 'c15t',
|
|
1101
|
+
bundled: iabOptions.bundled,
|
|
1102
|
+
cacheAdapter: options.cache?.adapter,
|
|
1103
|
+
vendorIds: iabOptions.vendorIds,
|
|
1104
|
+
endpoint: iabOptions.endpoint
|
|
1105
|
+
});
|
|
1106
|
+
gvl = await gvlResolver.get(language);
|
|
1107
|
+
}
|
|
1108
|
+
const customVendors = shouldIncludeIabPayload ? iabOptions?.customVendors : void 0;
|
|
1109
|
+
const snapshot = policyDecision ? await createPolicySnapshotToken({
|
|
1110
|
+
options: options.policySnapshot,
|
|
1111
|
+
tenantId: options.tenantId,
|
|
1112
|
+
policyId: policyDecision.policy.id,
|
|
1113
|
+
fingerprint: policyDecision.fingerprint,
|
|
1114
|
+
matchedBy: policyDecision.matchedBy,
|
|
1115
|
+
country: location?.countryCode ?? null,
|
|
1116
|
+
region: location?.regionCode ?? null,
|
|
1117
|
+
jurisdiction,
|
|
1118
|
+
language: translationsResult.language,
|
|
1119
|
+
model: policyDecision.policy.model,
|
|
1120
|
+
policyI18n: policyDecision.policy.i18n,
|
|
1121
|
+
expiryDays: policyDecision.policy.consent?.expiryDays,
|
|
1122
|
+
scopeMode: policyDecision.policy.consent?.scopeMode,
|
|
1123
|
+
uiMode: policyDecision.policy.ui?.mode,
|
|
1124
|
+
bannerUi: policyDecision.policy.ui?.banner,
|
|
1125
|
+
dialogUi: policyDecision.policy.ui?.dialog,
|
|
1126
|
+
categories: policyDecision.policy.consent?.categories,
|
|
1127
|
+
preselectedCategories: policyDecision.policy.consent?.preselectedCategories,
|
|
1128
|
+
gpc: policyDecision.policy.consent?.gpc,
|
|
1129
|
+
proofConfig: policyDecision.policy.proof
|
|
1130
|
+
}) : void 0;
|
|
1131
|
+
const gpc = '1' === request.headers.get('sec-gpc');
|
|
1132
|
+
getMetrics()?.recordInit({
|
|
1133
|
+
jurisdiction,
|
|
1134
|
+
country: location?.countryCode ?? void 0,
|
|
1135
|
+
region: location?.regionCode ?? void 0,
|
|
1136
|
+
gpc
|
|
1137
|
+
});
|
|
1138
|
+
return {
|
|
1139
|
+
jurisdiction,
|
|
1140
|
+
location,
|
|
1141
|
+
translations: responseTranslations,
|
|
1142
|
+
branding: options.branding || 'c15t',
|
|
1143
|
+
...shouldIncludeIabPayload && {
|
|
1144
|
+
gvl,
|
|
1145
|
+
customVendors
|
|
1146
|
+
},
|
|
1147
|
+
...resolvedPolicy && {
|
|
1148
|
+
policy: resolvedPolicy
|
|
1149
|
+
},
|
|
1150
|
+
...policyDecision && {
|
|
1151
|
+
policyDecision: {
|
|
1152
|
+
policyId: policyDecision.policy.id,
|
|
1153
|
+
fingerprint: policyDecision.fingerprint,
|
|
1154
|
+
matchedBy: policyDecision.matchedBy,
|
|
1155
|
+
country: location.countryCode,
|
|
1156
|
+
region: location.regionCode,
|
|
1157
|
+
jurisdiction
|
|
1158
|
+
}
|
|
1159
|
+
},
|
|
1160
|
+
...snapshot?.token && {
|
|
1161
|
+
policySnapshotToken: snapshot.token
|
|
1162
|
+
},
|
|
1163
|
+
...shouldIncludeIabPayload && iabOptions?.cmpId != null && {
|
|
1164
|
+
cmpId: iabOptions.cmpId
|
|
1165
|
+
}
|
|
826
1166
|
};
|
|
827
1167
|
}
|
|
828
1168
|
const createInitRoute = (options)=>{
|
|
@@ -835,7 +1175,7 @@ const createInitRoute = (options)=>{
|
|
|
835
1175
|
- **Location** – User's location (null if geo-location is disabled)
|
|
836
1176
|
- **Translations** – Consent manager copy (from \`Accept-Language\` header)
|
|
837
1177
|
- **Branding** – Configured branding key
|
|
838
|
-
- **GVL** – Global Vendor List when
|
|
1178
|
+
- **GVL** – Global Vendor List when IAB is active for the request
|
|
839
1179
|
|
|
840
1180
|
Use for geo-targeted consent banners and regional compliance.`,
|
|
841
1181
|
tags: [
|
|
@@ -852,46 +1192,13 @@ Use for geo-targeted consent banners and regional compliance.`,
|
|
|
852
1192
|
}
|
|
853
1193
|
}
|
|
854
1194
|
}), async (c)=>{
|
|
855
|
-
const
|
|
856
|
-
const
|
|
857
|
-
|
|
858
|
-
const jurisdiction = getJurisdiction(location, options);
|
|
859
|
-
const translationsResult = translations_getTranslationsData(acceptLanguage, options.customTranslations);
|
|
860
|
-
let gvl = null;
|
|
861
|
-
if (options.iab?.enabled) {
|
|
862
|
-
const language = translationsResult.language.split('-')[0] || 'en';
|
|
863
|
-
const gvlResolver = createGVLResolver({
|
|
864
|
-
appName: options.appName || 'c15t',
|
|
865
|
-
bundled: options.iab.bundled,
|
|
866
|
-
cacheAdapter: options.cache?.adapter,
|
|
867
|
-
vendorIds: options.iab.vendorIds,
|
|
868
|
-
endpoint: options.iab.endpoint
|
|
869
|
-
});
|
|
870
|
-
gvl = await gvlResolver.get(language);
|
|
871
|
-
}
|
|
872
|
-
const customVendors = options.iab?.customVendors;
|
|
873
|
-
const gpc = '1' === request.headers.get('sec-gpc');
|
|
874
|
-
getMetrics()?.recordInit({
|
|
875
|
-
jurisdiction,
|
|
876
|
-
country: location?.countryCode ?? void 0,
|
|
877
|
-
region: location?.regionCode ?? void 0,
|
|
878
|
-
gpc
|
|
879
|
-
});
|
|
880
|
-
return c.json({
|
|
881
|
-
jurisdiction,
|
|
882
|
-
location,
|
|
883
|
-
translations: translationsResult,
|
|
884
|
-
branding: options.branding || 'c15t',
|
|
885
|
-
gvl,
|
|
886
|
-
customVendors,
|
|
887
|
-
...options.iab?.cmpId != null && {
|
|
888
|
-
cmpId: options.iab.cmpId
|
|
889
|
-
}
|
|
890
|
-
});
|
|
1195
|
+
const ctx = c.get('c15tContext');
|
|
1196
|
+
const payload = await resolveInitPayload(c.req.raw, options, ctx?.logger);
|
|
1197
|
+
return c.json(payload);
|
|
891
1198
|
});
|
|
892
1199
|
return app;
|
|
893
1200
|
};
|
|
894
|
-
const version_version = '2.0.0-rc.
|
|
1201
|
+
const version_version = '2.0.0-rc.5';
|
|
895
1202
|
function getHeaders(headers) {
|
|
896
1203
|
if (!headers) return {
|
|
897
1204
|
countryCode: null,
|
|
@@ -1247,6 +1554,119 @@ const patchSubjectHandler = async (c)=>{
|
|
|
1247
1554
|
});
|
|
1248
1555
|
}
|
|
1249
1556
|
};
|
|
1557
|
+
function buildRuntimeDecisionDedupeKey(input) {
|
|
1558
|
+
return [
|
|
1559
|
+
input.tenantId ?? 'default',
|
|
1560
|
+
input.fingerprint,
|
|
1561
|
+
input.matchedBy,
|
|
1562
|
+
input.countryCode ?? 'none',
|
|
1563
|
+
input.regionCode ?? 'none',
|
|
1564
|
+
input.jurisdiction,
|
|
1565
|
+
input.language ?? 'none'
|
|
1566
|
+
].join('|');
|
|
1567
|
+
}
|
|
1568
|
+
function buildDecisionPayload(params) {
|
|
1569
|
+
const { tenantId, snapshot, decision, location, jurisdiction, language, proofConfig } = params;
|
|
1570
|
+
if (snapshot?.valid && snapshot.payload) {
|
|
1571
|
+
const sp = snapshot.payload;
|
|
1572
|
+
return {
|
|
1573
|
+
tenantId,
|
|
1574
|
+
policyId: sp.policyId,
|
|
1575
|
+
fingerprint: sp.fingerprint,
|
|
1576
|
+
matchedBy: sp.matchedBy,
|
|
1577
|
+
countryCode: sp.country,
|
|
1578
|
+
regionCode: sp.region,
|
|
1579
|
+
jurisdiction: sp.jurisdiction,
|
|
1580
|
+
language: sp.language,
|
|
1581
|
+
model: sp.model,
|
|
1582
|
+
policyI18n: sp.policyI18n,
|
|
1583
|
+
uiMode: sp.uiMode,
|
|
1584
|
+
bannerUi: sp.bannerUi,
|
|
1585
|
+
dialogUi: sp.dialogUi,
|
|
1586
|
+
categories: sp.categories,
|
|
1587
|
+
preselectedCategories: sp.preselectedCategories,
|
|
1588
|
+
proofConfig: sp.proofConfig,
|
|
1589
|
+
dedupeKey: buildRuntimeDecisionDedupeKey({
|
|
1590
|
+
tenantId,
|
|
1591
|
+
fingerprint: sp.fingerprint,
|
|
1592
|
+
matchedBy: sp.matchedBy,
|
|
1593
|
+
countryCode: sp.country,
|
|
1594
|
+
regionCode: sp.region,
|
|
1595
|
+
jurisdiction: sp.jurisdiction,
|
|
1596
|
+
language: sp.language
|
|
1597
|
+
}),
|
|
1598
|
+
source: 'snapshot_token'
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
if (decision) return {
|
|
1602
|
+
tenantId,
|
|
1603
|
+
policyId: decision.policy.id,
|
|
1604
|
+
fingerprint: decision.fingerprint,
|
|
1605
|
+
matchedBy: decision.matchedBy,
|
|
1606
|
+
countryCode: location.countryCode,
|
|
1607
|
+
regionCode: location.regionCode,
|
|
1608
|
+
jurisdiction,
|
|
1609
|
+
language,
|
|
1610
|
+
model: decision.policy.model,
|
|
1611
|
+
policyI18n: decision.policy.i18n,
|
|
1612
|
+
uiMode: decision.policy.ui?.mode,
|
|
1613
|
+
bannerUi: decision.policy.ui?.banner,
|
|
1614
|
+
dialogUi: decision.policy.ui?.dialog,
|
|
1615
|
+
categories: decision.policy.consent?.categories,
|
|
1616
|
+
preselectedCategories: decision.policy.consent?.preselectedCategories,
|
|
1617
|
+
proofConfig,
|
|
1618
|
+
dedupeKey: buildRuntimeDecisionDedupeKey({
|
|
1619
|
+
tenantId,
|
|
1620
|
+
fingerprint: decision.fingerprint,
|
|
1621
|
+
matchedBy: decision.matchedBy,
|
|
1622
|
+
countryCode: location.countryCode,
|
|
1623
|
+
regionCode: location.regionCode,
|
|
1624
|
+
jurisdiction,
|
|
1625
|
+
language
|
|
1626
|
+
}),
|
|
1627
|
+
source: 'write_time_fallback'
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
function parseLanguageFromHeader(header) {
|
|
1631
|
+
if (!header) return;
|
|
1632
|
+
const firstLanguage = header.split(',')[0]?.split(';')[0]?.trim();
|
|
1633
|
+
if (!firstLanguage) return;
|
|
1634
|
+
return firstLanguage.split('-')[0]?.toLowerCase();
|
|
1635
|
+
}
|
|
1636
|
+
function resolveSnapshotFailureMode(ctx) {
|
|
1637
|
+
return ctx.policySnapshot?.onValidationFailure ?? 'reject';
|
|
1638
|
+
}
|
|
1639
|
+
function buildSnapshotHttpException(reason) {
|
|
1640
|
+
switch(reason){
|
|
1641
|
+
case 'missing':
|
|
1642
|
+
return new http_exception_namespaceObject.HTTPException(409, {
|
|
1643
|
+
message: 'Policy snapshot token is required',
|
|
1644
|
+
cause: {
|
|
1645
|
+
code: 'POLICY_SNAPSHOT_REQUIRED'
|
|
1646
|
+
}
|
|
1647
|
+
});
|
|
1648
|
+
case 'expired':
|
|
1649
|
+
return new http_exception_namespaceObject.HTTPException(409, {
|
|
1650
|
+
message: 'Policy snapshot token has expired',
|
|
1651
|
+
cause: {
|
|
1652
|
+
code: 'POLICY_SNAPSHOT_EXPIRED'
|
|
1653
|
+
}
|
|
1654
|
+
});
|
|
1655
|
+
case 'malformed':
|
|
1656
|
+
case 'invalid':
|
|
1657
|
+
return new http_exception_namespaceObject.HTTPException(409, {
|
|
1658
|
+
message: 'Policy snapshot token is invalid',
|
|
1659
|
+
cause: {
|
|
1660
|
+
code: 'POLICY_SNAPSHOT_INVALID'
|
|
1661
|
+
}
|
|
1662
|
+
});
|
|
1663
|
+
default:
|
|
1664
|
+
{
|
|
1665
|
+
const _exhaustive = reason;
|
|
1666
|
+
throw new Error(`Unhandled policy snapshot verification failure reason: ${_exhaustive}`);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1250
1670
|
const postSubjectHandler = async (c)=>{
|
|
1251
1671
|
const ctx = c.get('c15tContext');
|
|
1252
1672
|
const logger = ctx.logger;
|
|
@@ -1258,9 +1678,6 @@ const postSubjectHandler = async (c)=>{
|
|
|
1258
1678
|
const givenAt = new Date(givenAtEpoch);
|
|
1259
1679
|
const rawConsentAction = 'consentAction' in input ? input.consentAction : void 0;
|
|
1260
1680
|
let derivedConsentAction;
|
|
1261
|
-
if ('all' === rawConsentAction) derivedConsentAction = 'accept_all';
|
|
1262
|
-
else if ('necessary' === rawConsentAction) derivedConsentAction = 'opt-out' === input.jurisdictionModel ? 'opt_out' : 'reject_all';
|
|
1263
|
-
else if ('custom' === rawConsentAction) derivedConsentAction = 'custom';
|
|
1264
1681
|
logger.debug('Request parameters', {
|
|
1265
1682
|
type,
|
|
1266
1683
|
subjectId,
|
|
@@ -1269,6 +1686,50 @@ const postSubjectHandler = async (c)=>{
|
|
|
1269
1686
|
domain
|
|
1270
1687
|
});
|
|
1271
1688
|
try {
|
|
1689
|
+
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.');
|
|
1690
|
+
const request = c.req.raw ?? new Request('https://c15t.local/subjects');
|
|
1691
|
+
const acceptLanguage = request.headers.get('accept-language');
|
|
1692
|
+
const requestLanguage = parseLanguageFromHeader(acceptLanguage);
|
|
1693
|
+
const location = await getLocation(request, ctx);
|
|
1694
|
+
const resolvedJurisdiction = getJurisdiction(location, ctx);
|
|
1695
|
+
const snapshotVerification = await verifyPolicySnapshotToken({
|
|
1696
|
+
token: input.policySnapshotToken,
|
|
1697
|
+
options: ctx.policySnapshot,
|
|
1698
|
+
tenantId: ctx.tenantId
|
|
1699
|
+
});
|
|
1700
|
+
const hasValidSnapshot = snapshotVerification.valid;
|
|
1701
|
+
const snapshotPayload = snapshotVerification.valid ? snapshotVerification.payload : null;
|
|
1702
|
+
const shouldRequireSnapshot = !!ctx.policySnapshot?.signingKey && 'reject' === resolveSnapshotFailureMode(ctx);
|
|
1703
|
+
if (!hasValidSnapshot && shouldRequireSnapshot) throw buildSnapshotHttpException(snapshotVerification.reason);
|
|
1704
|
+
const resolvedPolicyDecision = hasValidSnapshot ? void 0 : await resolvePolicyDecision({
|
|
1705
|
+
policies: ctx.policyPacks,
|
|
1706
|
+
countryCode: location.countryCode,
|
|
1707
|
+
regionCode: location.regionCode,
|
|
1708
|
+
jurisdiction: resolvedJurisdiction,
|
|
1709
|
+
iabEnabled: ctx.iab?.enabled === true
|
|
1710
|
+
});
|
|
1711
|
+
const effectivePolicy = hasValidSnapshot && snapshotPayload ? {
|
|
1712
|
+
id: snapshotPayload.policyId,
|
|
1713
|
+
model: snapshotPayload.model,
|
|
1714
|
+
i18n: snapshotPayload.policyI18n,
|
|
1715
|
+
consent: {
|
|
1716
|
+
expiryDays: snapshotPayload.expiryDays,
|
|
1717
|
+
scopeMode: snapshotPayload.scopeMode,
|
|
1718
|
+
categories: snapshotPayload.categories,
|
|
1719
|
+
preselectedCategories: snapshotPayload.preselectedCategories,
|
|
1720
|
+
gpc: snapshotPayload.gpc
|
|
1721
|
+
},
|
|
1722
|
+
ui: {
|
|
1723
|
+
mode: snapshotPayload.uiMode,
|
|
1724
|
+
banner: snapshotPayload.bannerUi,
|
|
1725
|
+
dialog: snapshotPayload.dialogUi
|
|
1726
|
+
},
|
|
1727
|
+
proof: snapshotPayload.proofConfig
|
|
1728
|
+
} : resolvedPolicyDecision?.policy;
|
|
1729
|
+
const effectiveModel = effectivePolicy?.model ?? ('opt-in' === input.jurisdictionModel || 'opt-out' === input.jurisdictionModel || 'iab' === input.jurisdictionModel ? input.jurisdictionModel : void 0);
|
|
1730
|
+
if ('all' === rawConsentAction) derivedConsentAction = 'accept_all';
|
|
1731
|
+
else if ('necessary' === rawConsentAction) derivedConsentAction = 'opt-out' === effectiveModel ? 'opt_out' : 'reject_all';
|
|
1732
|
+
else if ('custom' === rawConsentAction) derivedConsentAction = 'custom';
|
|
1272
1733
|
const subject = await registry.findOrCreateSubject({
|
|
1273
1734
|
subjectId,
|
|
1274
1735
|
externalSubjectId,
|
|
@@ -1295,6 +1756,7 @@ const postSubjectHandler = async (c)=>{
|
|
|
1295
1756
|
});
|
|
1296
1757
|
let policyId;
|
|
1297
1758
|
let purposeIds = [];
|
|
1759
|
+
let appliedPreferences;
|
|
1298
1760
|
const inputPolicyId = 'policyId' in input ? input.policyId : void 0;
|
|
1299
1761
|
if (inputPolicyId) {
|
|
1300
1762
|
policyId = inputPolicyId;
|
|
@@ -1327,20 +1789,66 @@ const postSubjectHandler = async (c)=>{
|
|
|
1327
1789
|
policyId = policy.id;
|
|
1328
1790
|
}
|
|
1329
1791
|
if (preferences) {
|
|
1330
|
-
const
|
|
1792
|
+
const allowedCategories = effectivePolicy?.consent?.categories;
|
|
1793
|
+
const effectiveScopeMode = effectivePolicy?.consent?.scopeMode ?? 'permissive';
|
|
1794
|
+
const hasWildcardCategoryScope = allowedCategories?.includes('*') === true;
|
|
1795
|
+
const appliedPreferenceEntries = Object.entries(preferences);
|
|
1796
|
+
let filteredAppliedPreferenceEntries = appliedPreferenceEntries;
|
|
1797
|
+
if (allowedCategories && allowedCategories.length > 0 && !hasWildcardCategoryScope) {
|
|
1798
|
+
const disallowed = appliedPreferenceEntries.map(([purpose])=>purpose).filter((purpose)=>!allowedCategories.includes(purpose));
|
|
1799
|
+
filteredAppliedPreferenceEntries = appliedPreferenceEntries.filter(([purpose])=>allowedCategories.includes(purpose));
|
|
1800
|
+
if (disallowed.length > 0 && 'strict' === effectiveScopeMode) throw new http_exception_namespaceObject.HTTPException(400, {
|
|
1801
|
+
message: 'Preferences include categories not allowed by policy',
|
|
1802
|
+
cause: {
|
|
1803
|
+
code: 'PURPOSE_NOT_ALLOWED',
|
|
1804
|
+
disallowed
|
|
1805
|
+
}
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1808
|
+
appliedPreferences = Object.fromEntries(filteredAppliedPreferenceEntries);
|
|
1809
|
+
const filteredConsentedPurposeCodes = filteredAppliedPreferenceEntries.filter(([_, isConsented])=>isConsented).map(([purposeCode])=>purposeCode);
|
|
1331
1810
|
logger.debug('Consented purposes', {
|
|
1332
|
-
consentedPurposes
|
|
1811
|
+
consentedPurposes: filteredConsentedPurposeCodes
|
|
1333
1812
|
});
|
|
1334
|
-
const purposesRaw = await Promise.all(
|
|
1813
|
+
const purposesRaw = await Promise.all(filteredConsentedPurposeCodes.map((purposeCode)=>registry.findOrCreateConsentPurposeByCode(purposeCode)));
|
|
1335
1814
|
const purposes = purposesRaw.map((purpose)=>purpose?.id ?? null).filter((id)=>Boolean(id));
|
|
1336
1815
|
logger.debug('Filtered purposes', {
|
|
1337
1816
|
purposes
|
|
1338
1817
|
});
|
|
1339
1818
|
if (0 === purposes.length) logger.warn('No valid purpose IDs found after filtering. Using empty list.', {
|
|
1340
|
-
consentedPurposes
|
|
1819
|
+
consentedPurposes: filteredConsentedPurposeCodes
|
|
1341
1820
|
});
|
|
1342
1821
|
purposeIds = purposes;
|
|
1343
1822
|
}
|
|
1823
|
+
const expiryDays = effectivePolicy?.consent?.expiryDays;
|
|
1824
|
+
const validUntil = 'number' == typeof expiryDays && Number.isFinite(expiryDays) ? new Date(givenAt.getTime() + 86400000 * Math.max(0, expiryDays)) : void 0;
|
|
1825
|
+
const proofConfig = effectivePolicy?.proof;
|
|
1826
|
+
const shouldStoreIp = proofConfig?.storeIp ?? true;
|
|
1827
|
+
const shouldStoreUserAgent = proofConfig?.storeUserAgent ?? true;
|
|
1828
|
+
const shouldStoreLanguage = proofConfig?.storeLanguage ?? false;
|
|
1829
|
+
const effectiveLanguage = (snapshotPayload?.language && hasValidSnapshot ? snapshotPayload.language : requestLanguage) ?? void 0;
|
|
1830
|
+
const metadataWithPolicy = {
|
|
1831
|
+
...metadata ?? {},
|
|
1832
|
+
...shouldStoreLanguage && effectiveLanguage ? {
|
|
1833
|
+
policyLanguage: effectiveLanguage
|
|
1834
|
+
} : {}
|
|
1835
|
+
};
|
|
1836
|
+
const effectiveJurisdiction = hasValidSnapshot && snapshotPayload ? snapshotPayload.jurisdiction : resolvedJurisdiction;
|
|
1837
|
+
const decisionPayload = buildDecisionPayload({
|
|
1838
|
+
tenantId: ctx.tenantId,
|
|
1839
|
+
snapshot: hasValidSnapshot && snapshotPayload ? {
|
|
1840
|
+
valid: true,
|
|
1841
|
+
payload: snapshotPayload
|
|
1842
|
+
} : null,
|
|
1843
|
+
decision: resolvedPolicyDecision,
|
|
1844
|
+
location: {
|
|
1845
|
+
countryCode: location.countryCode,
|
|
1846
|
+
regionCode: location.regionCode
|
|
1847
|
+
},
|
|
1848
|
+
jurisdiction: resolvedJurisdiction,
|
|
1849
|
+
language: effectiveLanguage,
|
|
1850
|
+
proofConfig
|
|
1851
|
+
});
|
|
1344
1852
|
const existingConsent = await db.findFirst('consent', {
|
|
1345
1853
|
where: (b)=>b.and(b('subjectId', '=', subject.id), b('domainId', '=', domainRecord.id), b('policyId', '=', policyId), b('givenAt', '=', givenAt))
|
|
1346
1854
|
});
|
|
@@ -1355,6 +1863,7 @@ const postSubjectHandler = async (c)=>{
|
|
|
1355
1863
|
domain: domainRecord.name,
|
|
1356
1864
|
type,
|
|
1357
1865
|
metadata,
|
|
1866
|
+
appliedPreferences,
|
|
1358
1867
|
uiSource: input.uiSource,
|
|
1359
1868
|
givenAt: existingConsent.givenAt
|
|
1360
1869
|
});
|
|
@@ -1366,6 +1875,42 @@ const postSubjectHandler = async (c)=>{
|
|
|
1366
1875
|
policyId,
|
|
1367
1876
|
purposeIds
|
|
1368
1877
|
});
|
|
1878
|
+
const runtimePolicyDecision = decisionPayload ? await tx.findFirst('runtimePolicyDecision', {
|
|
1879
|
+
where: (b)=>b('dedupeKey', '=', decisionPayload.dedupeKey)
|
|
1880
|
+
}) ?? await tx.create('runtimePolicyDecision', {
|
|
1881
|
+
id: `rpd_${crypto.randomUUID().replaceAll('-', '')}`,
|
|
1882
|
+
tenantId: decisionPayload.tenantId,
|
|
1883
|
+
policyId: decisionPayload.policyId,
|
|
1884
|
+
fingerprint: decisionPayload.fingerprint,
|
|
1885
|
+
matchedBy: decisionPayload.matchedBy,
|
|
1886
|
+
countryCode: decisionPayload.countryCode,
|
|
1887
|
+
regionCode: decisionPayload.regionCode,
|
|
1888
|
+
jurisdiction: decisionPayload.jurisdiction,
|
|
1889
|
+
language: decisionPayload.language,
|
|
1890
|
+
model: decisionPayload.model,
|
|
1891
|
+
policyI18n: decisionPayload.policyI18n ? {
|
|
1892
|
+
json: decisionPayload.policyI18n
|
|
1893
|
+
} : void 0,
|
|
1894
|
+
uiMode: decisionPayload.uiMode,
|
|
1895
|
+
bannerUi: decisionPayload.bannerUi ? {
|
|
1896
|
+
json: decisionPayload.bannerUi
|
|
1897
|
+
} : void 0,
|
|
1898
|
+
dialogUi: decisionPayload.dialogUi ? {
|
|
1899
|
+
json: decisionPayload.dialogUi
|
|
1900
|
+
} : void 0,
|
|
1901
|
+
categories: decisionPayload.categories ? {
|
|
1902
|
+
json: decisionPayload.categories
|
|
1903
|
+
} : void 0,
|
|
1904
|
+
preselectedCategories: decisionPayload.preselectedCategories ? {
|
|
1905
|
+
json: decisionPayload.preselectedCategories
|
|
1906
|
+
} : void 0,
|
|
1907
|
+
proofConfig: decisionPayload.proofConfig ? {
|
|
1908
|
+
json: decisionPayload.proofConfig
|
|
1909
|
+
} : void 0,
|
|
1910
|
+
dedupeKey: decisionPayload.dedupeKey
|
|
1911
|
+
}).catch(async ()=>tx.findFirst('runtimePolicyDecision', {
|
|
1912
|
+
where: (b)=>b('dedupeKey', '=', decisionPayload.dedupeKey)
|
|
1913
|
+
})) : void 0;
|
|
1369
1914
|
const consentRecord = await tx.create('consent', {
|
|
1370
1915
|
id: await generateUniqueId(tx, 'consent', ctx),
|
|
1371
1916
|
subjectId: subject.id,
|
|
@@ -1374,17 +1919,20 @@ const postSubjectHandler = async (c)=>{
|
|
|
1374
1919
|
purposeIds: {
|
|
1375
1920
|
json: purposeIds
|
|
1376
1921
|
},
|
|
1377
|
-
metadata:
|
|
1378
|
-
json:
|
|
1922
|
+
metadata: Object.keys(metadataWithPolicy).length > 0 ? {
|
|
1923
|
+
json: metadataWithPolicy
|
|
1379
1924
|
} : void 0,
|
|
1380
|
-
ipAddress: ctx.ipAddress,
|
|
1381
|
-
userAgent: ctx.userAgent,
|
|
1382
|
-
jurisdiction:
|
|
1383
|
-
jurisdictionModel:
|
|
1925
|
+
ipAddress: shouldStoreIp ? ctx.ipAddress : null,
|
|
1926
|
+
userAgent: shouldStoreUserAgent ? ctx.userAgent : null,
|
|
1927
|
+
jurisdiction: effectiveJurisdiction,
|
|
1928
|
+
jurisdictionModel: effectiveModel,
|
|
1384
1929
|
tcString: input.tcString,
|
|
1385
1930
|
uiSource: input.uiSource,
|
|
1386
1931
|
consentAction: derivedConsentAction,
|
|
1387
|
-
givenAt
|
|
1932
|
+
givenAt,
|
|
1933
|
+
validUntil,
|
|
1934
|
+
runtimePolicyDecisionId: runtimePolicyDecision?.id,
|
|
1935
|
+
runtimePolicySource: decisionPayload?.source
|
|
1388
1936
|
});
|
|
1389
1937
|
logger.debug('Created consent', {
|
|
1390
1938
|
consentRecord: consentRecord.id
|
|
@@ -1403,7 +1951,7 @@ const postSubjectHandler = async (c)=>{
|
|
|
1403
1951
|
});
|
|
1404
1952
|
const metrics = getMetrics();
|
|
1405
1953
|
if (metrics) {
|
|
1406
|
-
const jurisdiction =
|
|
1954
|
+
const jurisdiction = effectiveJurisdiction;
|
|
1407
1955
|
metrics.recordConsentCreated({
|
|
1408
1956
|
type,
|
|
1409
1957
|
jurisdiction
|
|
@@ -1425,6 +1973,7 @@ const postSubjectHandler = async (c)=>{
|
|
|
1425
1973
|
domain: domainRecord.name,
|
|
1426
1974
|
type,
|
|
1427
1975
|
metadata,
|
|
1976
|
+
appliedPreferences,
|
|
1428
1977
|
uiSource: input.uiSource,
|
|
1429
1978
|
givenAt: result.consent.givenAt
|
|
1430
1979
|
});
|