@cookiemunch/sdk 0.9.1 → 0.11.0
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/CHANGELOG.md +16 -0
- package/README.md +18 -0
- package/dist/chunk-U5OJSPE5.js +182 -0
- package/dist/chunk-U5OJSPE5.js.map +1 -0
- package/dist/index.cjs +13 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +116 -10
- package/dist/index.js +17 -178
- package/dist/index.js.map +1 -1
- package/dist/rights.cjs +258 -0
- package/dist/rights.cjs.map +1 -0
- package/dist/rights.d.ts +75 -0
- package/dist/rights.js +232 -0
- package/dist/rights.js.map +1 -0
- package/dist/types.d.ts +16 -0
- package/package.json +6 -1
package/dist/rights.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CANARY_ISSUER
|
|
3
|
+
} from "./chunk-U5OJSPE5.js";
|
|
4
|
+
|
|
5
|
+
// src/rights.ts
|
|
6
|
+
var CANONICAL = {
|
|
7
|
+
access: "access",
|
|
8
|
+
export: "access",
|
|
9
|
+
deletion: "deletion",
|
|
10
|
+
erase: "deletion",
|
|
11
|
+
erasure: "deletion",
|
|
12
|
+
closure: "closure",
|
|
13
|
+
restriction: "restriction",
|
|
14
|
+
rectification: "rectification"
|
|
15
|
+
};
|
|
16
|
+
var OUTCOMES = /* @__PURE__ */ new Set(["pending", "processing", "completed", "completed_with_retention", "no_match", "rejected"]);
|
|
17
|
+
var CANARY_EMAIL = /^canary-([0-9a-f]{16})@canary\.cookiemunch\.net$/;
|
|
18
|
+
var CANARY_SUBJECT = /^canary_([0-9a-f]{16})$/;
|
|
19
|
+
var CANARY_EXT = /^canary:([0-9a-f]{16})$/;
|
|
20
|
+
var enc = new TextEncoder();
|
|
21
|
+
var dec = new TextDecoder();
|
|
22
|
+
function b64url(bytes) {
|
|
23
|
+
let s = "";
|
|
24
|
+
for (const b of bytes) s += String.fromCharCode(b);
|
|
25
|
+
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
26
|
+
}
|
|
27
|
+
function fromB64url(s) {
|
|
28
|
+
if (!/^[A-Za-z0-9_-]*$/.test(s)) return null;
|
|
29
|
+
try {
|
|
30
|
+
const bin = atob(s.replace(/-/g, "+").replace(/_/g, "/"));
|
|
31
|
+
return Uint8Array.from(bin, (c) => c.charCodeAt(0));
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function sameKey(presented, expected) {
|
|
37
|
+
const a = enc.encode(presented);
|
|
38
|
+
const b = enc.encode(expected);
|
|
39
|
+
let diff = a.length ^ b.length;
|
|
40
|
+
for (let i = 0; i < b.length; i++) diff |= (a[i] ?? 0) ^ b[i];
|
|
41
|
+
return diff === 0;
|
|
42
|
+
}
|
|
43
|
+
function json(status, body) {
|
|
44
|
+
return new Response(JSON.stringify(body), {
|
|
45
|
+
status,
|
|
46
|
+
headers: { "content-type": "application/json", "x-version": "2", "cache-control": "no-store" }
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
var bad = (error, field) => json(400, { error, field });
|
|
50
|
+
var hidden = () => new Response("Not Found", { status: 404, headers: { "content-type": "text/plain" } });
|
|
51
|
+
function isObject(v) {
|
|
52
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
53
|
+
}
|
|
54
|
+
function createRightsHandlers(o) {
|
|
55
|
+
if (typeof o.key !== "string" || !o.key) throw new Error("createRightsHandlers: key is required");
|
|
56
|
+
if (typeof o.idSecret !== "string" || o.idSecret.length < 32) throw new Error("createRightsHandlers: idSecret must be at least 32 characters");
|
|
57
|
+
if (typeof o.lookup !== "function") throw new Error("createRightsHandlers: lookup is required");
|
|
58
|
+
const subtle = globalThis.crypto?.subtle;
|
|
59
|
+
if (!subtle) throw new Error("createRightsHandlers: needs Web Crypto (globalThis.crypto.subtle)");
|
|
60
|
+
const store = o.store ?? /* @__PURE__ */ new Map();
|
|
61
|
+
const now = o.now ?? (() => Date.now());
|
|
62
|
+
const hmacKey = subtle.importKey("raw", enc.encode(o.idSecret), { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"]);
|
|
63
|
+
async function mintId(claims) {
|
|
64
|
+
const payload = b64url(enc.encode(JSON.stringify(claims)));
|
|
65
|
+
const sig = new Uint8Array(await subtle.sign("HMAC", await hmacKey, enc.encode(payload)));
|
|
66
|
+
return `r1.${payload}.${b64url(sig)}`;
|
|
67
|
+
}
|
|
68
|
+
async function readId(id) {
|
|
69
|
+
const [v, payload, sig, extra] = id.split(".");
|
|
70
|
+
if (v !== "r1" || !payload || !sig || extra !== void 0) return null;
|
|
71
|
+
const sigBytes = fromB64url(sig);
|
|
72
|
+
if (!sigBytes || !await subtle.verify("HMAC", await hmacKey, sigBytes, enc.encode(payload))) return null;
|
|
73
|
+
const raw = fromB64url(payload);
|
|
74
|
+
try {
|
|
75
|
+
return raw ? JSON.parse(dec.decode(raw)) : null;
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const getStored = async (id) => {
|
|
81
|
+
const v = await store.get(`req:${id}`);
|
|
82
|
+
return v ? JSON.parse(v) : null;
|
|
83
|
+
};
|
|
84
|
+
const putStored = async (id, s) => {
|
|
85
|
+
await store.set(`req:${id}`, JSON.stringify(s));
|
|
86
|
+
};
|
|
87
|
+
const view = (id, c, s) => ({
|
|
88
|
+
id,
|
|
89
|
+
type: c.y,
|
|
90
|
+
canonicalType: c.t,
|
|
91
|
+
status: s.status,
|
|
92
|
+
...s.eta ? { eta: s.eta } : {},
|
|
93
|
+
...s.detail ? { detail: s.detail } : {},
|
|
94
|
+
...s.retention ? { retention: s.retention } : {},
|
|
95
|
+
...s.result !== void 0 ? { result: s.result } : {}
|
|
96
|
+
});
|
|
97
|
+
const callbackFor = (t) => ({ access: o.access, deletion: o.erase, closure: o.close ?? o.erase, restriction: o.restrict, rectification: o.rectify })[t];
|
|
98
|
+
async function act(id, c, s, cb) {
|
|
99
|
+
const out = CANARY_SUBJECT.test(c.s) || !cb ? void 0 : await cb({
|
|
100
|
+
id,
|
|
101
|
+
subjectId: c.s,
|
|
102
|
+
type: c.t,
|
|
103
|
+
requestedType: c.y,
|
|
104
|
+
caseId: c.c,
|
|
105
|
+
...s.reason ? { reason: s.reason } : {},
|
|
106
|
+
...c.a !== void 0 ? { executeAfter: new Date(c.a).toISOString() } : {}
|
|
107
|
+
});
|
|
108
|
+
const o2 = out ?? { status: "completed" };
|
|
109
|
+
if (!OUTCOMES.has(o2.status)) throw new Error(`a rights callback answered an unknown status "${String(o2.status)}"`);
|
|
110
|
+
const next = { status: o2.status, ...s.reason ? { reason: s.reason } : {} };
|
|
111
|
+
if (o2.eta) next.eta = o2.eta;
|
|
112
|
+
if (o2.detail) next.detail = o2.detail;
|
|
113
|
+
if (o2.retention) next.retention = o2.retention;
|
|
114
|
+
if (o2.result !== void 0) next.result = o2.result;
|
|
115
|
+
await putStored(id, next);
|
|
116
|
+
return next;
|
|
117
|
+
}
|
|
118
|
+
async function lookup(body) {
|
|
119
|
+
if (!Array.isArray(body.emails) || body.emails.some((e) => typeof e !== "string")) return bad("emails must be an array of strings", "emails");
|
|
120
|
+
const emails = body.emails.map((e) => e.trim());
|
|
121
|
+
const raw = body.identity ?? body.atlas;
|
|
122
|
+
if (raw !== void 0 && raw !== null && !(isObject(raw) && typeof raw.iss === "string" && typeof raw.sub === "string")) {
|
|
123
|
+
return bad("identity must be { iss, sub, ext?, principal? }", "identity");
|
|
124
|
+
}
|
|
125
|
+
const identity = isObject(raw) ? raw : void 0;
|
|
126
|
+
const canaryByEmail = emails.map((e) => CANARY_EMAIL.exec(e.toLowerCase())?.[1]).find(Boolean);
|
|
127
|
+
const canaryExt = typeof identity?.ext === "string" ? CANARY_EXT.exec(identity.ext)?.[1] : void 0;
|
|
128
|
+
if (canaryByEmail || canaryExt || identity?.iss === CANARY_ISSUER) {
|
|
129
|
+
const tag = identity ? (identity.iss === CANARY_ISSUER ? CANARY_SUBJECT.exec(identity.sub)?.[1] : void 0) ?? canaryExt : canaryByEmail;
|
|
130
|
+
return json(200, tag ? { subjectId: `canary_${tag}`, status: "found" } : { subjectId: null, status: "no_match" });
|
|
131
|
+
}
|
|
132
|
+
const subjectId = await o.lookup({ emails, ...identity ? { identity } : {} });
|
|
133
|
+
return json(200, typeof subjectId === "string" && subjectId ? { subjectId, status: "found" } : { subjectId: null, status: "no_match" });
|
|
134
|
+
}
|
|
135
|
+
async function open(body) {
|
|
136
|
+
const t = typeof body.type === "string" ? CANONICAL[body.type] : void 0;
|
|
137
|
+
if (!t) return bad("type must be one of access, export, deletion, erase, erasure, closure, restriction, rectification", "type");
|
|
138
|
+
if (typeof body.subjectId !== "string" || !body.subjectId) return bad("subjectId is required", "subjectId");
|
|
139
|
+
if (typeof body.caseId !== "string" || !body.caseId) return bad("caseId is required", "caseId");
|
|
140
|
+
if (body.reason !== void 0 && typeof body.reason !== "string") return bad("reason must be a string", "reason");
|
|
141
|
+
const after = typeof body.executeAfter === "string" ? Date.parse(body.executeAfter) : NaN;
|
|
142
|
+
if (body.executeAfter !== void 0 && body.executeAfter !== null && Number.isNaN(after)) return bad("executeAfter must be an ISO date-time", "executeAfter");
|
|
143
|
+
const cb = callbackFor(t);
|
|
144
|
+
if (!cb && !CANARY_SUBJECT.test(body.subjectId)) return bad(`this system does not handle ${t}`, "type");
|
|
145
|
+
const c = { s: body.subjectId, t, y: body.type, c: body.caseId, n: b64url(globalThis.crypto.getRandomValues(new Uint8Array(9))) };
|
|
146
|
+
if (!Number.isNaN(after)) c.a = after;
|
|
147
|
+
const id = await mintId(c);
|
|
148
|
+
const s = { status: "pending", ...typeof body.reason === "string" && body.reason ? { reason: body.reason } : {} };
|
|
149
|
+
const erasing = t === "deletion" || t === "closure";
|
|
150
|
+
if (erasing && c.a !== void 0 && c.a > now()) {
|
|
151
|
+
const scheduled = { ...s, status: "scheduled", eta: new Date(c.a).toISOString() };
|
|
152
|
+
await putStored(id, scheduled);
|
|
153
|
+
return json(201, view(id, c, scheduled));
|
|
154
|
+
}
|
|
155
|
+
return json(201, view(id, c, await act(id, c, s, cb)));
|
|
156
|
+
}
|
|
157
|
+
async function read(id, c) {
|
|
158
|
+
let s = await getStored(id);
|
|
159
|
+
if (!s) {
|
|
160
|
+
return json(200, view(id, c, { status: "rejected", detail: "this system no longer holds the request's state; nothing was done. Open it again." }));
|
|
161
|
+
}
|
|
162
|
+
if (s.status === "scheduled" && c.a !== void 0 && now() >= c.a) s = await act(id, c, s, callbackFor(c.t));
|
|
163
|
+
else if ((s.status === "pending" || s.status === "processing") && o.status && !CANARY_SUBJECT.test(c.s)) s = await act(id, c, s, o.status);
|
|
164
|
+
return json(200, view(id, c, s));
|
|
165
|
+
}
|
|
166
|
+
async function cancel(id, c) {
|
|
167
|
+
const s = await getStored(id);
|
|
168
|
+
if (!s) return json(409, { error: "this system no longer holds the request's state", field: "id" });
|
|
169
|
+
if (s.status === "cancelled") return json(200, view(id, c, s));
|
|
170
|
+
if (s.status !== "scheduled" && s.status !== "pending") return json(409, { error: `too late to cancel a ${s.status} request`, field: "id" });
|
|
171
|
+
const next = { status: "cancelled", ...s.reason ? { reason: s.reason } : {} };
|
|
172
|
+
await putStored(id, next);
|
|
173
|
+
return json(200, view(id, c, next));
|
|
174
|
+
}
|
|
175
|
+
async function handle(req) {
|
|
176
|
+
const m = /^Bearer\s+(.+)$/i.exec(req.headers.get("authorization") ?? "");
|
|
177
|
+
if (!m || !sameKey(m[1].trim(), o.key)) return hidden();
|
|
178
|
+
const path = new URL(req.url).pathname.replace(/\/+$/, "");
|
|
179
|
+
const route = /\/(lookup|requests)(?:\/([^/]+)(\/cancel)?)?$/.exec(path);
|
|
180
|
+
const method = req.method.toUpperCase();
|
|
181
|
+
if (!route) return json(404, { error: "not_found" });
|
|
182
|
+
const [, what, rawId, isCancel] = route;
|
|
183
|
+
let body = {};
|
|
184
|
+
let text = "";
|
|
185
|
+
if (method === "POST") {
|
|
186
|
+
text = await req.text();
|
|
187
|
+
try {
|
|
188
|
+
const parsed = text ? JSON.parse(text) : {};
|
|
189
|
+
if (!isObject(parsed)) return bad("body must be a JSON object", "body");
|
|
190
|
+
body = parsed;
|
|
191
|
+
} catch {
|
|
192
|
+
return bad("body must be JSON", "body");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const idemKey = method === "POST" && what === "requests" ? req.headers.get("idempotency-key") : null;
|
|
196
|
+
if (idemKey !== null) {
|
|
197
|
+
if (idemKey.length < 16 || idemKey.length > 128) return bad("Idempotency-Key must be 16-128 characters", "Idempotency-Key");
|
|
198
|
+
const seen = await store.get(`idem:${idemKey}`);
|
|
199
|
+
if (seen) {
|
|
200
|
+
const first = JSON.parse(seen);
|
|
201
|
+
return first.body === text ? json(first.status, first.answer) : json(409, { error: "Idempotency-Key reused with a different body", field: "Idempotency-Key" });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
let res;
|
|
205
|
+
if (what === "lookup" && !rawId && method === "POST") res = await lookup(body);
|
|
206
|
+
else if (what === "requests" && !rawId && method === "POST") res = await open(body);
|
|
207
|
+
else if (what === "requests" && rawId) {
|
|
208
|
+
let id;
|
|
209
|
+
try {
|
|
210
|
+
id = decodeURIComponent(rawId);
|
|
211
|
+
} catch {
|
|
212
|
+
return json(404, { error: "unknown request" });
|
|
213
|
+
}
|
|
214
|
+
const c = await readId(id);
|
|
215
|
+
if (!c) return json(404, { error: "unknown request" });
|
|
216
|
+
if (isCancel && method === "POST") res = await cancel(id, c);
|
|
217
|
+
else if (!isCancel && method === "GET") res = await read(id, c);
|
|
218
|
+
else return json(404, { error: "not_found" });
|
|
219
|
+
} else return json(404, { error: "not_found" });
|
|
220
|
+
if (idemKey !== null && res.ok) await store.set(`idem:${idemKey}`, JSON.stringify({ body: text, status: res.status, answer: await res.clone().json() }));
|
|
221
|
+
return res;
|
|
222
|
+
}
|
|
223
|
+
const fetch = (req) => handle(req).catch((err) => {
|
|
224
|
+
console.error("createRightsHandlers:", err);
|
|
225
|
+
return json(500, { error: "internal_error" });
|
|
226
|
+
});
|
|
227
|
+
return { fetch, GET: fetch, POST: fetch };
|
|
228
|
+
}
|
|
229
|
+
export {
|
|
230
|
+
createRightsHandlers
|
|
231
|
+
};
|
|
232
|
+
//# sourceMappingURL=rights.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/rights.ts"],"sourcesContent":["/**\n * `@cookiemunch/sdk/rights` — the cm-standard-v2 executor contract, server side.\n *\n * A product that holds personal data implements four calls and Cookie Munch drives it (docs:\n * cookiemunch.net/docs/executor-contract-v2). This builds those four calls from callbacks, so the\n * product writes only \"find this person\", \"export them\", \"erase them\":\n *\n * import { createRightsHandlers } from '@cookiemunch/sdk/rights';\n * const rights = createRightsHandlers({\n * key: process.env.CM_EXECUTOR_KEY!, // the secretKey you registered with Cookie Munch\n * idSecret: process.env.CM_REQUEST_ID_SECRET!,\n * lookup: async ({ emails, identity }) => (await findUser(emails, identity))?.id ?? null,\n * access: async (r) => ({ status: 'completed', result: await exportUser(r.subjectId) }),\n * erase: async (r) => { await deleteUser(r.subjectId); },\n * });\n * // Next.js app/privacy/[...path]/route.ts: export const { GET, POST } = rights;\n * // Deno: Deno.serve(rights.fetch) Workers / Bun: export default rights\n *\n * Built in: a constant-time key check (no key or a wrong key is a bare 404, so the endpoint is not\n * advertised), `X-Version` and `Cache-Control: no-store` on every authorised answer, Idempotency-Key\n * replay, HMAC-signed request ids (a forged id is a 404), `executeAfter` scheduling and cancel, and\n * the canary subject, answered here so your callbacks never see it.\n *\n * A separate entry point so the API client never loads it. Web platform only (Request, Response,\n * crypto.subtle): Node 20+, Deno, Bun and edge runtimes.\n */\nimport { CANARY_ISSUER } from './executor-conformance.js';\n\nexport type RightsType = 'access' | 'deletion' | 'closure' | 'restriction' | 'rectification';\nexport type RightsStatus = 'pending' | 'scheduled' | 'processing' | 'completed' | 'completed_with_retention' | 'no_match' | 'rejected' | 'cancelled';\n\nexport interface RightsIdentity {\n iss: string;\n sub: string;\n ext?: string;\n principal?: string;\n}\n\nexport interface RightsLookupInput {\n emails: string[];\n /** A signed-in identity (sent as `identity`, or `atlas`). Match on it before the emails, and never through an email that belongs to a different identity. */\n identity?: RightsIdentity;\n}\n\nexport interface RightsRetention {\n category: string;\n basis: 'legal_obligation' | 'legal_claims' | 'contract' | 'public_interest' | 'legitimate_interest';\n until?: string;\n}\n\n/** What a callback answers. Returning nothing means `completed`. */\nexport interface RightsOutcome {\n status: 'pending' | 'processing' | 'completed' | 'completed_with_retention' | 'no_match' | 'rejected';\n eta?: string;\n /** Plain words; required in spirit for `rejected`. */\n detail?: string;\n retention?: RightsRetention[];\n /** For `access`: `{ downloadUrl, expiresAt, sha256 }`, or the bundle inline (at most 5 MB). */\n result?: unknown;\n}\n\n/** The request a callback acts on. */\nexport interface RightsRequest {\n id: string;\n subjectId: string;\n type: RightsType;\n /** The word Cookie Munch sent (`erasure`, `export`, …). */\n requestedType: string;\n caseId: string;\n reason?: string;\n executeAfter?: string;\n}\n\ntype Callback = (r: RightsRequest) => Promise<RightsOutcome | void> | RightsOutcome | void;\n\n/** Where request state and idempotency answers live. A `Map` is one; so is most of a Redis client. */\nexport interface RightsStore {\n get(key: string): Promise<string | null | undefined> | string | null | undefined;\n set(key: string, value: string): unknown;\n}\n\nexport interface RightsHandlersOptions {\n /** The key Cookie Munch sends as `Authorization: Bearer <key>`. */\n key: string;\n /** Signs request ids; at least 32 characters, the same on every instance and across restarts. */\n idSecret: string;\n /** The subject id for these emails / this identity, or null. */\n lookup: (input: RightsLookupInput) => Promise<string | null | undefined> | string | null | undefined;\n access?: Callback;\n /** `deletion` (and `closure`, when there is no `close`). Runs at `executeAfter`, not before. */\n erase?: Callback;\n close?: Callback;\n restrict?: Callback;\n rectify?: Callback;\n /** Read back a request a callback answered `pending` or `processing` for. */\n status?: Callback;\n /**\n * Default: in memory, which a restart empties. A scheduled erasure whose state is gone is never\n * run (it answers `rejected`), so give production a durable store.\n */\n store?: RightsStore;\n now?: () => number;\n}\n\nexport interface RightsHandlers {\n /** The whole contract, routed on the path's tail: mount it under any prefix. */\n fetch(req: Request): Promise<Response>;\n GET(req: Request): Promise<Response>;\n POST(req: Request): Promise<Response>;\n}\n\nconst CANONICAL: Record<string, RightsType> = {\n access: 'access', export: 'access', deletion: 'deletion', erase: 'deletion', erasure: 'deletion',\n closure: 'closure', restriction: 'restriction', rectification: 'rectification',\n};\nconst OUTCOMES = new Set(['pending', 'processing', 'completed', 'completed_with_retention', 'no_match', 'rejected']);\nconst CANARY_EMAIL = /^canary-([0-9a-f]{16})@canary\\.cookiemunch\\.net$/;\nconst CANARY_SUBJECT = /^canary_([0-9a-f]{16})$/;\nconst CANARY_EXT = /^canary:([0-9a-f]{16})$/;\n\n/** What the signed id carries: enough to answer without trusting the store for it. */\ntype IdClaims = { s: string; t: RightsType; y: string; c: string; a?: number; n: string };\n/** The request's mutable state, in the store under `req:<id>`. */\ntype Stored = { status: RightsStatus; eta?: string; detail?: string; retention?: RightsRetention[]; result?: unknown; reason?: string };\n\nconst enc = new TextEncoder();\nconst dec = new TextDecoder();\n\nfunction b64url(bytes: Uint8Array): string {\n let s = '';\n for (const b of bytes) s += String.fromCharCode(b);\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\nfunction fromB64url(s: string) {\n if (!/^[A-Za-z0-9_-]*$/.test(s)) return null;\n try {\n const bin = atob(s.replace(/-/g, '+').replace(/_/g, '/'));\n return Uint8Array.from(bin, (c) => c.charCodeAt(0));\n } catch {\n return null;\n }\n}\n\n/** Constant-time in the presented value: every byte of the expected key is compared. */\nfunction sameKey(presented: string, expected: string): boolean {\n const a = enc.encode(presented);\n const b = enc.encode(expected);\n let diff = a.length ^ b.length;\n for (let i = 0; i < b.length; i++) diff |= (a[i] ?? 0) ^ b[i]!;\n return diff === 0;\n}\n\nfunction json(status: number, body: unknown): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'content-type': 'application/json', 'x-version': '2', 'cache-control': 'no-store' },\n });\n}\nconst bad = (error: string, field: string) => json(400, { error, field });\n// No X-Version and nothing contract-shaped: to a caller without the key this is any other 404.\nconst hidden = () => new Response('Not Found', { status: 404, headers: { 'content-type': 'text/plain' } });\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return !!v && typeof v === 'object' && !Array.isArray(v);\n}\n\nexport function createRightsHandlers(o: RightsHandlersOptions): RightsHandlers {\n if (typeof o.key !== 'string' || !o.key) throw new Error('createRightsHandlers: key is required');\n if (typeof o.idSecret !== 'string' || o.idSecret.length < 32) throw new Error('createRightsHandlers: idSecret must be at least 32 characters');\n if (typeof o.lookup !== 'function') throw new Error('createRightsHandlers: lookup is required');\n const subtle = globalThis.crypto?.subtle;\n if (!subtle) throw new Error('createRightsHandlers: needs Web Crypto (globalThis.crypto.subtle)');\n // shortcut: the default store never forgets an entry; fine for the volume of rights requests, pass `store` for more.\n const store: RightsStore = o.store ?? new Map<string, string>();\n const now = o.now ?? (() => Date.now());\n const hmacKey = subtle.importKey('raw', enc.encode(o.idSecret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign', 'verify']);\n\n async function mintId(claims: IdClaims): Promise<string> {\n const payload = b64url(enc.encode(JSON.stringify(claims)));\n const sig = new Uint8Array(await subtle.sign('HMAC', await hmacKey, enc.encode(payload)));\n return `r1.${payload}.${b64url(sig)}`;\n }\n async function readId(id: string): Promise<IdClaims | null> {\n const [v, payload, sig, extra] = id.split('.');\n if (v !== 'r1' || !payload || !sig || extra !== undefined) return null;\n const sigBytes = fromB64url(sig);\n if (!sigBytes || !(await subtle.verify('HMAC', await hmacKey, sigBytes, enc.encode(payload)))) return null;\n const raw = fromB64url(payload);\n try {\n return raw ? (JSON.parse(dec.decode(raw)) as IdClaims) : null;\n } catch {\n return null;\n }\n }\n\n const getStored = async (id: string): Promise<Stored | null> => {\n const v = await store.get(`req:${id}`);\n return v ? (JSON.parse(v) as Stored) : null;\n };\n const putStored = async (id: string, s: Stored) => {\n await store.set(`req:${id}`, JSON.stringify(s));\n };\n\n const view = (id: string, c: IdClaims, s: Stored) => ({\n id,\n type: c.y,\n canonicalType: c.t,\n status: s.status,\n ...(s.eta ? { eta: s.eta } : {}),\n ...(s.detail ? { detail: s.detail } : {}),\n ...(s.retention ? { retention: s.retention } : {}),\n ...(s.result !== undefined ? { result: s.result } : {}),\n });\n\n const callbackFor = (t: RightsType): Callback | undefined =>\n ({ access: o.access, deletion: o.erase, closure: o.close ?? o.erase, restriction: o.restrict, rectification: o.rectify })[t];\n\n /** Run a callback (or, for the canary, nothing) and fold its answer into the stored state. */\n async function act(id: string, c: IdClaims, s: Stored, cb: Callback | undefined): Promise<Stored> {\n // The canary is a synthetic person: requests on it complete without touching anything real.\n const out = CANARY_SUBJECT.test(c.s) || !cb\n ? undefined\n : ((await cb({\n id,\n subjectId: c.s,\n type: c.t,\n requestedType: c.y,\n caseId: c.c,\n ...(s.reason ? { reason: s.reason } : {}),\n ...(c.a !== undefined ? { executeAfter: new Date(c.a).toISOString() } : {}),\n })) as RightsOutcome | undefined);\n const o2: RightsOutcome = out ?? { status: 'completed' };\n if (!OUTCOMES.has(o2.status)) throw new Error(`a rights callback answered an unknown status \"${String(o2.status)}\"`);\n const next: Stored = { status: o2.status, ...(s.reason ? { reason: s.reason } : {}) };\n if (o2.eta) next.eta = o2.eta;\n if (o2.detail) next.detail = o2.detail;\n if (o2.retention) next.retention = o2.retention;\n if (o2.result !== undefined) next.result = o2.result;\n await putStored(id, next);\n return next;\n }\n\n async function lookup(body: Record<string, unknown>): Promise<Response> {\n if (!Array.isArray(body.emails) || body.emails.some((e) => typeof e !== 'string')) return bad('emails must be an array of strings', 'emails');\n const emails = (body.emails as string[]).map((e) => e.trim());\n const raw = body.identity ?? body.atlas;\n if (raw !== undefined && raw !== null && !(isObject(raw) && typeof raw.iss === 'string' && typeof raw.sub === 'string')) {\n return bad('identity must be { iss, sub, ext?, principal? }', 'identity');\n }\n const identity = isObject(raw) ? (raw as unknown as RightsIdentity) : undefined;\n\n const canaryByEmail = emails.map((e) => CANARY_EMAIL.exec(e.toLowerCase())?.[1]).find(Boolean);\n const canaryExt = typeof identity?.ext === 'string' ? CANARY_EXT.exec(identity.ext)?.[1] : undefined;\n if (canaryByEmail || canaryExt || identity?.iss === CANARY_ISSUER) {\n // When an identity is given only it decides: the canary's email next to anyone else's identity is no one.\n const tag = identity ? (identity.iss === CANARY_ISSUER ? CANARY_SUBJECT.exec(identity.sub)?.[1] : undefined) ?? canaryExt : canaryByEmail;\n return json(200, tag ? { subjectId: `canary_${tag}`, status: 'found' } : { subjectId: null, status: 'no_match' });\n }\n const subjectId = await o.lookup({ emails, ...(identity ? { identity } : {}) });\n return json(200, typeof subjectId === 'string' && subjectId ? { subjectId, status: 'found' } : { subjectId: null, status: 'no_match' });\n }\n\n async function open(body: Record<string, unknown>): Promise<Response> {\n const t = typeof body.type === 'string' ? CANONICAL[body.type] : undefined;\n if (!t) return bad('type must be one of access, export, deletion, erase, erasure, closure, restriction, rectification', 'type');\n if (typeof body.subjectId !== 'string' || !body.subjectId) return bad('subjectId is required', 'subjectId');\n if (typeof body.caseId !== 'string' || !body.caseId) return bad('caseId is required', 'caseId');\n if (body.reason !== undefined && typeof body.reason !== 'string') return bad('reason must be a string', 'reason');\n const after = typeof body.executeAfter === 'string' ? Date.parse(body.executeAfter) : NaN;\n if (body.executeAfter !== undefined && body.executeAfter !== null && Number.isNaN(after)) return bad('executeAfter must be an ISO date-time', 'executeAfter');\n const cb = callbackFor(t);\n if (!cb && !CANARY_SUBJECT.test(body.subjectId)) return bad(`this system does not handle ${t}`, 'type');\n\n const c: IdClaims = { s: body.subjectId, t, y: body.type as string, c: body.caseId, n: b64url(globalThis.crypto.getRandomValues(new Uint8Array(9))) };\n if (!Number.isNaN(after)) c.a = after;\n const id = await mintId(c);\n const s: Stored = { status: 'pending', ...(typeof body.reason === 'string' && body.reason ? { reason: body.reason } : {}) };\n const erasing = t === 'deletion' || t === 'closure';\n if (erasing && c.a !== undefined && c.a > now()) {\n const scheduled: Stored = { ...s, status: 'scheduled', eta: new Date(c.a).toISOString() };\n await putStored(id, scheduled);\n return json(201, view(id, c, scheduled));\n }\n return json(201, view(id, c, await act(id, c, s, cb)));\n }\n\n async function read(id: string, c: IdClaims): Promise<Response> {\n let s = await getStored(id);\n if (!s) {\n // A genuine id whose state is gone (an emptied store): never act on it, say so.\n return json(200, view(id, c, { status: 'rejected', detail: \"this system no longer holds the request's state; nothing was done. Open it again.\" }));\n }\n if (s.status === 'scheduled' && c.a !== undefined && now() >= c.a) s = await act(id, c, s, callbackFor(c.t));\n else if ((s.status === 'pending' || s.status === 'processing') && o.status && !CANARY_SUBJECT.test(c.s)) s = await act(id, c, s, o.status);\n return json(200, view(id, c, s));\n }\n\n async function cancel(id: string, c: IdClaims): Promise<Response> {\n const s = await getStored(id);\n if (!s) return json(409, { error: \"this system no longer holds the request's state\", field: 'id' });\n if (s.status === 'cancelled') return json(200, view(id, c, s));\n if (s.status !== 'scheduled' && s.status !== 'pending') return json(409, { error: `too late to cancel a ${s.status} request`, field: 'id' });\n const next: Stored = { status: 'cancelled', ...(s.reason ? { reason: s.reason } : {}) };\n await putStored(id, next);\n return json(200, view(id, c, next));\n }\n\n async function handle(req: Request): Promise<Response> {\n const m = /^Bearer\\s+(.+)$/i.exec(req.headers.get('authorization') ?? '');\n if (!m || !sameKey(m[1]!.trim(), o.key)) return hidden();\n\n const path = new URL(req.url).pathname.replace(/\\/+$/, '');\n const route = /\\/(lookup|requests)(?:\\/([^/]+)(\\/cancel)?)?$/.exec(path);\n const method = req.method.toUpperCase();\n if (!route) return json(404, { error: 'not_found' });\n const [, what, rawId, isCancel] = route;\n\n let body: Record<string, unknown> = {};\n let text = '';\n if (method === 'POST') {\n text = await req.text();\n try {\n const parsed: unknown = text ? JSON.parse(text) : {};\n if (!isObject(parsed)) return bad('body must be a JSON object', 'body');\n body = parsed;\n } catch {\n return bad('body must be JSON', 'body');\n }\n }\n\n // Replay: the same key and body answer what they answered first; another body is a 409.\n const idemKey = method === 'POST' && what === 'requests' ? req.headers.get('idempotency-key') : null;\n if (idemKey !== null) {\n if (idemKey.length < 16 || idemKey.length > 128) return bad('Idempotency-Key must be 16-128 characters', 'Idempotency-Key');\n const seen = await store.get(`idem:${idemKey}`);\n if (seen) {\n const first = JSON.parse(seen) as { body: string; status: number; answer: unknown };\n return first.body === text ? json(first.status, first.answer) : json(409, { error: 'Idempotency-Key reused with a different body', field: 'Idempotency-Key' });\n }\n }\n\n let res: Response;\n if (what === 'lookup' && !rawId && method === 'POST') res = await lookup(body);\n else if (what === 'requests' && !rawId && method === 'POST') res = await open(body);\n else if (what === 'requests' && rawId) {\n let id: string;\n try {\n id = decodeURIComponent(rawId);\n } catch {\n return json(404, { error: 'unknown request' });\n }\n const c = await readId(id);\n if (!c) return json(404, { error: 'unknown request' });\n if (isCancel && method === 'POST') res = await cancel(id, c);\n else if (!isCancel && method === 'GET') res = await read(id, c);\n else return json(404, { error: 'not_found' });\n } else return json(404, { error: 'not_found' });\n\n if (idemKey !== null && res.ok) await store.set(`idem:${idemKey}`, JSON.stringify({ body: text, status: res.status, answer: await res.clone().json() }));\n return res;\n }\n\n // A throwing callback is a 500 Cookie Munch retries; nothing is recorded for it.\n const fetch = (req: Request) =>\n handle(req).catch((err: unknown) => {\n console.error('createRightsHandlers:', err);\n return json(500, { error: 'internal_error' });\n });\n return { fetch, GET: fetch, POST: fetch };\n}\n"],"mappings":"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","names":[]}
|
package/dist/types.d.ts
CHANGED
|
@@ -70,6 +70,8 @@ export interface SiteUpdate {
|
|
|
70
70
|
domain?: string;
|
|
71
71
|
name?: string | null;
|
|
72
72
|
aliases?: string[];
|
|
73
|
+
/** With a new domain: keep the old one as an alias, so consent is still accepted there while both run. Remove it from aliases when the move is done. */
|
|
74
|
+
keepOldDomain?: boolean;
|
|
73
75
|
}
|
|
74
76
|
export interface SiteUpdateResult {
|
|
75
77
|
dryRun?: true;
|
|
@@ -352,6 +354,8 @@ export interface DsarCaseCreate {
|
|
|
352
354
|
/** product and atlas need dsar:write and attest the person; admin (default) files it received. */
|
|
353
355
|
/** `identity`: the site's sign-in provider. `atlas` is its former name. */
|
|
354
356
|
source?: 'product' | 'identity' | 'admin' | 'atlas';
|
|
357
|
+
/** The person deleted their own account. Default for a closure from product or identity. */
|
|
358
|
+
origin?: 'account_deletion';
|
|
355
359
|
reason?: string;
|
|
356
360
|
/** Connections not to ask: system names, connection ids, or `self`. */
|
|
357
361
|
skipSystems?: string[];
|
|
@@ -377,6 +381,16 @@ export interface DsarCaseTask {
|
|
|
377
381
|
completedAt: string | null;
|
|
378
382
|
detail: string | null;
|
|
379
383
|
retention?: RetentionLine[];
|
|
384
|
+
/** Present while this erasure waits for `approveTask` / `rejectTask` (status reads pending). */
|
|
385
|
+
awaitingApproval?: true;
|
|
386
|
+
/** What the system said the erasure would remove. */
|
|
387
|
+
preview?: {
|
|
388
|
+
at: number;
|
|
389
|
+
text?: string;
|
|
390
|
+
error?: string;
|
|
391
|
+
};
|
|
392
|
+
/** This row's own account at that system, when one email matched several. */
|
|
393
|
+
subjectId?: string;
|
|
380
394
|
}
|
|
381
395
|
/** A case across every connected system. */
|
|
382
396
|
export interface DsarCase {
|
|
@@ -387,6 +401,8 @@ export interface DsarCase {
|
|
|
387
401
|
requestStatus: DsarStatus;
|
|
388
402
|
cbid?: string;
|
|
389
403
|
source: 'product' | 'identity' | 'rights_page' | 'admin' | 'atlas' | null;
|
|
404
|
+
/** account_deletion: the person deleted their own account. */
|
|
405
|
+
origin: 'account_deletion' | null;
|
|
390
406
|
reason: string | null;
|
|
391
407
|
openedAt: string;
|
|
392
408
|
/** Nothing is erased anywhere before this. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cookiemunch/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Typed REST client for the Cookie Munch Developer API (/v1)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
"types": "./dist/consent.d.ts",
|
|
18
18
|
"import": "./dist/consent.js",
|
|
19
19
|
"require": "./dist/consent.cjs"
|
|
20
|
+
},
|
|
21
|
+
"./rights": {
|
|
22
|
+
"types": "./dist/rights.d.ts",
|
|
23
|
+
"import": "./dist/rights.js",
|
|
24
|
+
"require": "./dist/rights.cjs"
|
|
20
25
|
}
|
|
21
26
|
},
|
|
22
27
|
"files": [
|