UNPKG

@openpolicy/sdk

Version:

Public API for defining privacy policies with OpenPolicy

313 lines (312 loc) 9.32 kB
import { cookies, dataCollected, thirdParties } from "./auto-collected.js"; //#region ../core/dist/index.js /** * Helpers that build a {@link ProvisionRequirement} for `data.context[category].provision`. * Each call carries the consequences of failing to provide the data, as * required by GDPR Article 13(2)(e). */ const Statutory = (consequences) => ({ basis: "statutory", consequences }); const Contractual = (consequences) => ({ basis: "contractual", consequences }); const ContractPrerequisite = (consequences) => ({ basis: "contract-prerequisite", consequences }); const Voluntary = (consequences) => ({ basis: "voluntary", consequences }); function stableSerialize(value) { if (value === null || typeof value !== "object") return JSON.stringify(value); if (Array.isArray(value)) return `[${value.map(stableSerialize).join(",")}]`; return `{${Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${stableSerialize(v)}`).join(",")}}`; } function fnv1a32(str) { let hash = 2166136261; for (let i = 0; i < str.length; i++) { hash ^= str.charCodeAt(i); hash = Math.imul(hash, 16777619); } return (hash >>> 0).toString(16).padStart(8, "0"); } const PRIVACY_HASH_FIELDS = [ "company", "effectiveDate", "jurisdictions", "locale", "data", "children", "thirdParties", "automatedDecisionMaking", "cookies" ]; const COOKIE_HASH_FIELDS = [ "company", "effectiveDate", "jurisdictions", "locale", "cookies", "thirdParties", "trackingTechnologies", "consentMechanism" ]; function hashSlice(config, fields) { const slice = {}; for (const field of fields) { const value = config[field]; if (value !== void 0) slice[field] = value; } return fnv1a32(stableSerialize(slice)); } function hasAnyPrivacyField$2(config) { return config.data !== void 0 || config.children !== void 0; } function shouldHashPrivacy(config) { if (config.policies) return config.policies.includes("privacy"); return hasAnyPrivacyField$2(config); } function shouldHashCookie(config) { if (config.policies) return config.policies.includes("cookie"); return config.cookies !== void 0; } function computePrivacyVersion(config) { if (!shouldHashPrivacy(config)) return void 0; return hashSlice(config, PRIVACY_HASH_FIELDS); } function computeCookieVersion(config) { if (!shouldHashCookie(config)) return void 0; return hashSlice(config, COOKIE_HASH_FIELDS); } //#endregion //#region src/collecting.ts /** * Sentinel used as a label value to explicitly exclude a field from the * compiled privacy policy. Every key of the `value` passed to `collecting()` * must appear in the label record — pass `Ignore` for fields that should not * appear in the policy (e.g. `hashedPassword: Ignore`). * * It is a `unique symbol` so it cannot collide with a real label string and * so the type checker treats it nominally. */ const Ignore = Symbol("@openpolicy/ignore"); /** * Declares data collected at the point of storage. Returns `value` unchanged * at runtime — the Vite plugin / CLI static analyser (OP-152) will scan calls * to `collecting()` at build time and merge the declarations into the * compiled privacy policy. * * The third argument is a plain object literal whose **keys** are field names * matching your stored value (for convenient access without a typed callback) * and whose **values** are the human-readable labels used in the compiled * policy. Only the string values are used by the analyser; the object is * never evaluated at runtime. This shape lets you: * - keep `value` matching your ORM/table schema exactly, * - describe fields with friendly labels for the policy, * - exclude a field from the policy by setting its label to `Ignore` * (imported from `@openpolicy/sdk`) — every key of `value` must appear * in the label record, so e.g. `hashedPassword: Ignore` is how you hide * a sensitive column. * * The category argument and the string values of the label record must be * string literals — dynamic values are silently skipped by the analyser. * * @example * ```ts * import { collecting, Ignore } from "@openpolicy/sdk"; * * export async function createUser( * name: string, * email: string, * hashedPassword: string, * ) { * return db.insert(users).values( * collecting( * "Account Information", * { name, email, hashedPassword }, // real ORM columns — returned unchanged * { name: "Name", email: "Email address", hashedPassword: Ignore }, * ), * ); * } * ``` */ function collecting(_category, value, _label) { return value; } //#endregion //#region src/compliance.ts const Compliance = { GDPR: { jurisdictions: ["eu"] }, UK_GDPR: { jurisdictions: ["uk"] }, CCPA: { jurisdictions: ["us-ca"] } }; //#endregion //#region src/data.ts const DataCategories = { AccountInfo: { "Account Information": ["Name", "Email address"] }, SessionData: { "Session Data": [ "IP address", "User agent", "Browser type" ] }, PaymentInfo: { "Payment Information": [ "Card last 4 digits", "Billing name", "Billing address" ] }, UsageData: { "Usage Data": [ "Pages visited", "Features used", "Time spent" ] }, DeviceInfo: { "Device Information": [ "Device type", "Operating system", "Browser version" ] }, LocationData: { "Location Data": [ "Country", "City", "Timezone" ] }, Communications: { Communications: ["Email content", "Support tickets"] } }; const Retention = { UntilAccountDeletion: "Until account deletion", UntilSessionExpiry: "Until session expiry", ThirtyDays: "30 days", NinetyDays: "90 days", OneYear: "1 year", ThreeYears: "3 years", AsRequiredByLaw: "As required by applicable law" }; const LegalBases = { Consent: "consent", Contract: "contract", LegalObligation: "legal_obligation", VitalInterests: "vital_interests", PublicTask: "public_task", LegitimateInterests: "legitimate_interests" }; //#endregion //#region src/define-cookie.ts function defineCookie(_category) {} //#endregion //#region src/providers.ts const Providers = { Stripe: { name: "Stripe", purpose: "Payment processing", policyUrl: "https://stripe.com/privacy" }, Paddle: { name: "Paddle", purpose: "Payment processing and subscription management", policyUrl: "https://www.paddle.com/legal/privacy" }, LemonSqueezy: { name: "Lemon Squeezy", purpose: "Payment processing and subscription management", policyUrl: "https://www.lemonsqueezy.com/privacy" }, PayPal: { name: "PayPal", purpose: "Payment processing", policyUrl: "https://www.paypal.com/webapps/mpp/ua/privacy-full" }, GoogleAnalytics: { name: "Google Analytics", purpose: "Usage analytics", policyUrl: "https://policies.google.com/privacy" }, PostHog: { name: "PostHog", purpose: "Product analytics and session recording", policyUrl: "https://posthog.com/privacy" }, Plausible: { name: "Plausible Analytics", purpose: "Privacy-friendly usage analytics", policyUrl: "https://plausible.io/privacy" }, Mixpanel: { name: "Mixpanel", purpose: "Product analytics and event tracking", policyUrl: "https://mixpanel.com/legal/privacy-policy" }, Vercel: { name: "Vercel", purpose: "Hosting and deployment infrastructure", policyUrl: "https://vercel.com/legal/privacy-policy" }, Cloudflare: { name: "Cloudflare", purpose: "CDN, DNS, and security services", policyUrl: "https://www.cloudflare.com/privacypolicy/" }, AWS: { name: "Amazon Web Services", purpose: "Cloud infrastructure and hosting", policyUrl: "https://aws.amazon.com/privacy/" }, Auth0: { name: "Auth0", purpose: "Authentication and identity management", policyUrl: "https://auth0.com/privacy" }, Clerk: { name: "Clerk", purpose: "Authentication and user management", policyUrl: "https://clerk.com/privacy" }, Resend: { name: "Resend", purpose: "Transactional email delivery", policyUrl: "https://resend.com/legal/privacy-policy" }, Postmark: { name: "Postmark", purpose: "Transactional email delivery", policyUrl: "https://wildbit.com/privacy-policy" }, SendGrid: { name: "SendGrid", purpose: "Transactional email delivery", policyUrl: "https://www.twilio.com/en-us/legal/privacy" }, Loops: { name: "Loops", purpose: "Email marketing and automation", policyUrl: "https://loops.so/privacy" }, Sentry: { name: "Sentry", purpose: "Error monitoring and performance tracking", policyUrl: "https://sentry.io/privacy/" }, Datadog: { name: "Datadog", purpose: "Infrastructure monitoring and observability", policyUrl: "https://www.datadoghq.com/legal/privacy/" } }; //#endregion //#region src/third-parties.ts function thirdParty(_name, _purpose, _policyUrl) {} //#endregion //#region src/index.ts function defineConfig(config) { const resolved = config; return { ...resolved, privacyVersion: resolved.privacyVersion ?? computePrivacyVersion(resolved), cookieVersion: resolved.cookieVersion ?? computeCookieVersion(resolved) }; } //#endregion export { Compliance, ContractPrerequisite, Contractual, DataCategories, Ignore, LegalBases, Providers, Retention, Statutory, Voluntary, collecting, computeCookieVersion, computePrivacyVersion, cookies, dataCollected, defineConfig, defineCookie, thirdParties, thirdParty }; //# sourceMappingURL=index.js.map