@northstartech/cipher 0.0.0-stage → 1.0.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/README.md +541 -2
- package/dist/client.d.ts +237 -0
- package/dist/client.js +555 -0
- package/dist/client.js.map +1 -0
- package/dist/crypto.d.ts +72 -0
- package/dist/crypto.js +244 -0
- package/dist/crypto.js.map +1 -0
- package/dist/encoding.d.ts +17 -0
- package/dist/encoding.js +79 -0
- package/dist/encoding.js.map +1 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +22 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/drizzle.d.ts +96 -0
- package/dist/integrations/drizzle.js +285 -0
- package/dist/integrations/drizzle.js.map +1 -0
- package/dist/integrations/mongoose.d.ts +72 -0
- package/dist/integrations/mongoose.js +582 -0
- package/dist/integrations/mongoose.js.map +1 -0
- package/dist/integrations/prisma.d.ts +65 -0
- package/dist/integrations/prisma.js +588 -0
- package/dist/integrations/prisma.js.map +1 -0
- package/dist/integrations/sequelize.d.ts +62 -0
- package/dist/integrations/sequelize.js +677 -0
- package/dist/integrations/sequelize.js.map +1 -0
- package/dist/integrations/shared.d.ts +268 -0
- package/dist/integrations/shared.js +513 -0
- package/dist/integrations/shared.js.map +1 -0
- package/dist/integrations/typeorm.d.ts +109 -0
- package/dist/integrations/typeorm.js +457 -0
- package/dist/integrations/typeorm.js.map +1 -0
- package/dist/object.d.ts +53 -0
- package/dist/object.js +226 -0
- package/dist/object.js.map +1 -0
- package/dist/search.d.ts +250 -0
- package/dist/search.js +258 -0
- package/dist/search.js.map +1 -0
- package/package.json +101 -4
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
import { CipherError } from '../errors.js';
|
|
2
|
+
import { SearchIndex } from '../search.js';
|
|
3
|
+
/** Text prefix of every stored ciphertext (protocol §3). */
|
|
4
|
+
export const CIPHERTEXT_PREFIX = 'nsc:v1:';
|
|
5
|
+
/** Placeholder written by the two-phase insert before the real id is known. Never decryptable. */
|
|
6
|
+
export const PENDING_SENTINEL = 'nsc:pending:v1';
|
|
7
|
+
/** Default suffix of the blind-index column next to a searchable field (`email` → `email_bidx`). */
|
|
8
|
+
export const INDEX_SUFFIX = '_bidx';
|
|
9
|
+
const NORMALIZATIONS = new Set(['exact', 'case_insensitive', 'email', 'alphanumeric', 'digits']);
|
|
10
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
11
|
+
const TOKEN_VALUES_PER_REQUEST = 100; // protocol §11.3: 1–100 values per /v1/indexes/token call
|
|
12
|
+
const MAX_CONTEXT_VALUE_BYTES = 512; // protocol §3.1
|
|
13
|
+
export function configError(message) {
|
|
14
|
+
return new CipherError('CONFIGURATION_ERROR', message);
|
|
15
|
+
}
|
|
16
|
+
export function requestError(message) {
|
|
17
|
+
return new CipherError('INVALID_REQUEST', message);
|
|
18
|
+
}
|
|
19
|
+
/** Validate and normalize a field spec (`true` or options). */
|
|
20
|
+
export function normalizeFieldSpec(spec, label) {
|
|
21
|
+
if (spec === true)
|
|
22
|
+
return { searchable: false };
|
|
23
|
+
if (!spec || typeof spec !== 'object')
|
|
24
|
+
throw configError(`${label}: expected \`true\` or an options object`);
|
|
25
|
+
const o = spec;
|
|
26
|
+
for (const k of Object.keys(o)) {
|
|
27
|
+
if (k !== 'searchable' && k !== 'indexField')
|
|
28
|
+
throw configError(`${label}: unknown option "${k}"`);
|
|
29
|
+
}
|
|
30
|
+
if (o.indexField !== undefined && (typeof o.indexField !== 'string' || !o.indexField))
|
|
31
|
+
throw configError(`${label}: indexField must be a non-empty string`);
|
|
32
|
+
if (o.searchable === undefined || o.searchable === false) {
|
|
33
|
+
if (o.indexField !== undefined)
|
|
34
|
+
throw configError(`${label}: indexField requires searchable`);
|
|
35
|
+
return { searchable: false };
|
|
36
|
+
}
|
|
37
|
+
if (o.searchable === true)
|
|
38
|
+
return { searchable: true, normalization: 'exact', indexField: o.indexField };
|
|
39
|
+
if (typeof o.searchable !== 'string' || !NORMALIZATIONS.has(o.searchable)) {
|
|
40
|
+
throw configError(`${label}: searchable must be true or one of ${[...NORMALIZATIONS].join(', ')}`);
|
|
41
|
+
}
|
|
42
|
+
return { searchable: true, normalization: o.searchable, indexField: o.indexField };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Canonical `record_id` for a primary-key value. UUIDs are lowercased because
|
|
46
|
+
* PostgreSQL returns them lowercase whatever case was inserted; ObjectIds use
|
|
47
|
+
* their hex form; integers their decimal form.
|
|
48
|
+
*/
|
|
49
|
+
export function recordIdOf(value, label) {
|
|
50
|
+
let s;
|
|
51
|
+
if (typeof value === 'string')
|
|
52
|
+
s = UUID_RE.test(value) ? value.toLowerCase() : value;
|
|
53
|
+
else if (typeof value === 'number') {
|
|
54
|
+
if (!Number.isSafeInteger(value))
|
|
55
|
+
throw requestError(`${label}: primary key ${value} is not a safe integer`);
|
|
56
|
+
s = String(value);
|
|
57
|
+
}
|
|
58
|
+
else if (typeof value === 'bigint')
|
|
59
|
+
s = value.toString();
|
|
60
|
+
else if (value && typeof value === 'object' && typeof value.toHexString === 'function') {
|
|
61
|
+
s = value.toHexString();
|
|
62
|
+
}
|
|
63
|
+
else if (value === undefined || value === null) {
|
|
64
|
+
throw requestError(`${label}: the primary key is not known, so the value cannot be bound to its record`);
|
|
65
|
+
}
|
|
66
|
+
else
|
|
67
|
+
throw requestError(`${label}: unsupported primary key type ${typeof value}`);
|
|
68
|
+
const bytes = new TextEncoder().encode(s).length;
|
|
69
|
+
if (bytes < 1 || bytes > MAX_CONTEXT_VALUE_BYTES)
|
|
70
|
+
throw requestError(`${label}: primary key must be 1–${MAX_CONTEXT_VALUE_BYTES} bytes`);
|
|
71
|
+
return s;
|
|
72
|
+
}
|
|
73
|
+
/** Plaintext of an encrypted field: strings only (store other types serialized). */
|
|
74
|
+
export function plaintextOf(value, label) {
|
|
75
|
+
if (typeof value !== 'string') {
|
|
76
|
+
throw requestError(`${label}: encrypted fields hold strings (got ${value === null ? 'null' : typeof value}); serialize other types before assigning`);
|
|
77
|
+
}
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
/** True for a value written by Northstar Cipher (`nsc:v1:…`). */
|
|
81
|
+
export function isCiphertext(value) {
|
|
82
|
+
return typeof value === 'string' && value.startsWith(CIPHERTEXT_PREFIX);
|
|
83
|
+
}
|
|
84
|
+
/** Strip the `CODE: ` prefix CipherError adds to its message. */
|
|
85
|
+
function bareMessage(e) {
|
|
86
|
+
const p = `${e.code}: `;
|
|
87
|
+
return e.message.startsWith(p) ? e.message.slice(p.length) : e.message;
|
|
88
|
+
}
|
|
89
|
+
function located(e, label) {
|
|
90
|
+
if (e instanceof CipherError) {
|
|
91
|
+
return new CipherError(e.code, `${label}: ${bareMessage(e)}`, { requestId: e.requestId, status: e.status, retryable: e.retryable });
|
|
92
|
+
}
|
|
93
|
+
return new CipherError('INTERNAL', `${label}: ${e?.message ?? String(e)}`);
|
|
94
|
+
}
|
|
95
|
+
/** Encrypt values in ONE logical batch (the SDK chunks at 1 000 items), reporting per-item outcomes. */
|
|
96
|
+
export async function encryptSettled(cipher, items, opts = {}) {
|
|
97
|
+
if (!items.length)
|
|
98
|
+
return [];
|
|
99
|
+
const results = await cipher.encryptManySettled(items.map((it) => ({
|
|
100
|
+
...(opts.keyId ? { keyId: opts.keyId } : {}),
|
|
101
|
+
plaintext: it.plaintext,
|
|
102
|
+
context: { collection: it.collection, field: it.field, recordId: it.recordId },
|
|
103
|
+
...(it.normalization ? { index: { normalization: it.normalization } } : {}),
|
|
104
|
+
})));
|
|
105
|
+
if (results.length !== items.length)
|
|
106
|
+
throw new CipherError('PROTOCOL_ERROR', `encrypt batch returned ${results.length} results for ${items.length} items`);
|
|
107
|
+
return results.map((r, i) => {
|
|
108
|
+
const it = items[i];
|
|
109
|
+
if (!r.ok)
|
|
110
|
+
return { ok: false, error: located(r.error, it.label) };
|
|
111
|
+
if (!isCiphertext(r.value.ciphertext))
|
|
112
|
+
return { ok: false, error: new CipherError('PROTOCOL_ERROR', `${it.label}: service returned a malformed ciphertext`) };
|
|
113
|
+
if (it.normalization && !r.value.index)
|
|
114
|
+
return { ok: false, error: new CipherError('PROTOCOL_ERROR', `${it.label}: service did not return a blind index`) };
|
|
115
|
+
return { ok: true, value: { ciphertext: r.value.ciphertext, ...(it.normalization ? { index: r.value.index } : {}) } };
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Encrypt values in ONE logical batch. Throws on the first failed item: callers
|
|
120
|
+
* must not write anything in that case.
|
|
121
|
+
*/
|
|
122
|
+
export async function encryptValues(cipher, items, opts = {}) {
|
|
123
|
+
return (await encryptSettled(cipher, items, opts)).map((r) => {
|
|
124
|
+
if (!r.ok)
|
|
125
|
+
throw r.error;
|
|
126
|
+
return r.value;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Coalesce encryptions requested in the same tick (e.g. per-entity ORM hooks
|
|
131
|
+
* fired for one `save([...])`) into one batch request. Each caller still gets
|
|
132
|
+
* all of its own items or an error (records stay independent).
|
|
133
|
+
*/
|
|
134
|
+
export function encryptCoalescer(cipher, opts = {}) {
|
|
135
|
+
let queue = [];
|
|
136
|
+
const flush = async () => {
|
|
137
|
+
const batch = queue;
|
|
138
|
+
queue = [];
|
|
139
|
+
try {
|
|
140
|
+
const results = await encryptSettled(cipher, batch.flatMap((b) => b.items), opts);
|
|
141
|
+
let k = 0;
|
|
142
|
+
for (const b of batch) {
|
|
143
|
+
const mine = results.slice(k, (k += b.items.length));
|
|
144
|
+
const failed = mine.find((r) => !r.ok);
|
|
145
|
+
if (failed && !failed.ok)
|
|
146
|
+
b.reject(failed.error);
|
|
147
|
+
else
|
|
148
|
+
b.resolve(mine.map((r) => r.value));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (e) {
|
|
152
|
+
for (const b of batch)
|
|
153
|
+
b.reject(e);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
return (items) => new Promise((resolve, reject) => {
|
|
157
|
+
if (!items.length)
|
|
158
|
+
return resolve([]);
|
|
159
|
+
if (!queue.length)
|
|
160
|
+
setTimeout(() => void flush(), 0);
|
|
161
|
+
queue.push({ items, resolve, reject });
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Classify and decrypt stored values in ONE logical batch. `null`/`undefined`
|
|
166
|
+
* pass through without a request. Plaintext, the pending sentinel and foreign
|
|
167
|
+
* values throw `INVALID_CIPHERTEXT` before anything is sent.
|
|
168
|
+
*/
|
|
169
|
+
export async function decryptValues(cipher, items, opts = {}) {
|
|
170
|
+
const out = new Array(items.length).fill(null);
|
|
171
|
+
const pending = [];
|
|
172
|
+
items.forEach((it, i) => {
|
|
173
|
+
const v = it.stored;
|
|
174
|
+
if (v === null || v === undefined)
|
|
175
|
+
return;
|
|
176
|
+
if (isCiphertext(v))
|
|
177
|
+
pending.push({ i, ciphertext: v });
|
|
178
|
+
else if (v === PENDING_SENTINEL) {
|
|
179
|
+
throw new CipherError('INVALID_CIPHERTEXT', `${it.label}: value is the pending sentinel of an interrupted two-phase write; re-save the record`);
|
|
180
|
+
}
|
|
181
|
+
else if (opts.readLegacyPlaintext && typeof v === 'string')
|
|
182
|
+
out[i] = v;
|
|
183
|
+
else
|
|
184
|
+
throw new CipherError('INVALID_CIPHERTEXT', `${it.label}: stored value is not Northstar ciphertext; refusing to read it as plaintext`);
|
|
185
|
+
});
|
|
186
|
+
if (!pending.length)
|
|
187
|
+
return out;
|
|
188
|
+
const results = await cipher.decryptManySettled(pending.map((p) => {
|
|
189
|
+
const it = items[p.i];
|
|
190
|
+
return { ciphertext: p.ciphertext, context: { collection: it.collection, field: it.field, recordId: it.recordId } };
|
|
191
|
+
}), opts.purpose ? { purpose: opts.purpose } : {});
|
|
192
|
+
if (results.length !== pending.length)
|
|
193
|
+
throw new CipherError('PROTOCOL_ERROR', `decrypt batch returned ${results.length} results for ${pending.length} items`);
|
|
194
|
+
results.forEach((r, k) => {
|
|
195
|
+
const p = pending[k];
|
|
196
|
+
if (!r.ok)
|
|
197
|
+
throw located(r.error, items[p.i].label);
|
|
198
|
+
out[p.i] = r.value.plaintext;
|
|
199
|
+
});
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
/** Decrypt one stored value through the client's auto-batching (`cipher.decrypt`). */
|
|
203
|
+
export async function decryptOne(cipher, item, opts = {}) {
|
|
204
|
+
const v = item.stored;
|
|
205
|
+
if (v === null || v === undefined)
|
|
206
|
+
return null;
|
|
207
|
+
if (!isCiphertext(v)) {
|
|
208
|
+
if (v === PENDING_SENTINEL) {
|
|
209
|
+
throw new CipherError('INVALID_CIPHERTEXT', `${item.label}: value is the pending sentinel of an interrupted two-phase write; re-save the record`);
|
|
210
|
+
}
|
|
211
|
+
if (opts.readLegacyPlaintext && typeof v === 'string')
|
|
212
|
+
return v;
|
|
213
|
+
throw new CipherError('INVALID_CIPHERTEXT', `${item.label}: stored value is not Northstar ciphertext; refusing to read it as plaintext`);
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
const r = await cipher.decrypt({
|
|
217
|
+
ciphertext: v,
|
|
218
|
+
context: { collection: item.collection, field: item.field, recordId: item.recordId },
|
|
219
|
+
...(opts.purpose ? { purpose: opts.purpose } : {}),
|
|
220
|
+
});
|
|
221
|
+
return r.plaintext;
|
|
222
|
+
}
|
|
223
|
+
catch (e) {
|
|
224
|
+
throw located(e, item.label);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Exact-match search tokens for one or more values: the union of every token
|
|
229
|
+
* (active and query-only index versions) to match with `IN (…)`.
|
|
230
|
+
*/
|
|
231
|
+
export async function searchTokensFor(cipher, input) {
|
|
232
|
+
const raw = Array.isArray(input.values) ? input.values : [input.values];
|
|
233
|
+
const values = raw.map((v) => {
|
|
234
|
+
if (typeof v !== 'string')
|
|
235
|
+
throw requestError(`${input.label}: search values must be strings`);
|
|
236
|
+
return v;
|
|
237
|
+
});
|
|
238
|
+
const tokens = new Set();
|
|
239
|
+
for (let i = 0; i < values.length; i += TOKEN_VALUES_PER_REQUEST) {
|
|
240
|
+
const lists = await cipher.searchTokens({
|
|
241
|
+
...(input.keyId ? { keyId: input.keyId } : {}),
|
|
242
|
+
collection: input.collection,
|
|
243
|
+
field: input.field,
|
|
244
|
+
...(input.normalization ? { normalization: input.normalization } : {}),
|
|
245
|
+
values: values.slice(i, i + TOKEN_VALUES_PER_REQUEST),
|
|
246
|
+
});
|
|
247
|
+
for (const list of lists)
|
|
248
|
+
for (const t of list)
|
|
249
|
+
tokens.add(t);
|
|
250
|
+
}
|
|
251
|
+
return [...tokens];
|
|
252
|
+
}
|
|
253
|
+
/** Validate `search` integration options. */
|
|
254
|
+
export function normalizeSearchSync(o, label) {
|
|
255
|
+
const s = o;
|
|
256
|
+
if (!s || typeof s !== 'object' || !(s.index instanceof SearchIndex))
|
|
257
|
+
throw configError(`${label}: search.index must be a SearchIndex (cipher.index(name))`);
|
|
258
|
+
if (!Array.isArray(s.fields) || !s.fields.length || !s.fields.every((f) => typeof f === 'string' && f))
|
|
259
|
+
throw configError(`${label}: search.fields must list the attributes to index`);
|
|
260
|
+
if (s.onError !== undefined && typeof s.onError !== 'function')
|
|
261
|
+
throw configError(`${label}: search.onError must be a function`);
|
|
262
|
+
return s;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Value of one search field: stored ciphertext of an encrypted attribute becomes
|
|
266
|
+
* `{ ciphertext, context }` (decrypted inside the enclave), other values become
|
|
267
|
+
* JSON scalars (dates as RFC 3339). Objects (JSON columns, relations) are skipped.
|
|
268
|
+
*/
|
|
269
|
+
export function searchValue(value, encrypted, label) {
|
|
270
|
+
if (value === undefined)
|
|
271
|
+
return undefined;
|
|
272
|
+
if (value === null)
|
|
273
|
+
return null;
|
|
274
|
+
if (encrypted && isCiphertext(value))
|
|
275
|
+
return SearchIndex.ciphertext(value, encrypted);
|
|
276
|
+
if (value === PENDING_SENTINEL)
|
|
277
|
+
throw new CipherError('INVALID_CIPHERTEXT', `${label}: the record holds the pending sentinel of an unfinished two-phase write`);
|
|
278
|
+
if (typeof value === 'string' || typeof value === 'boolean')
|
|
279
|
+
return value;
|
|
280
|
+
if (typeof value === 'number')
|
|
281
|
+
return Number.isFinite(value) ? value : null;
|
|
282
|
+
if (typeof value === 'bigint')
|
|
283
|
+
return Number.isSafeInteger(Number(value)) ? Number(value) : value.toString();
|
|
284
|
+
if (value instanceof Date)
|
|
285
|
+
return Number.isNaN(value.getTime()) ? null : value.toISOString();
|
|
286
|
+
if (Array.isArray(value)) {
|
|
287
|
+
return value.map((v) => searchValue(v, undefined, label)).filter((v) => v === null || (v !== undefined && typeof v !== 'object'));
|
|
288
|
+
}
|
|
289
|
+
if (typeof value.toHexString === 'function')
|
|
290
|
+
return value.toHexString();
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Build the search document of a record (stored row or ORM instance): the id is
|
|
295
|
+
* the canonical record id, encrypted attributes holding ciphertext keep it, and
|
|
296
|
+
* attributes holding plaintext (already decrypted records) are sent as values.
|
|
297
|
+
* Attributes missing from the record are left out.
|
|
298
|
+
*/
|
|
299
|
+
export function searchDocumentOf(record, plan) {
|
|
300
|
+
const recordId = recordIdOf(plan.id, plan.label);
|
|
301
|
+
const fields = {};
|
|
302
|
+
for (const f of plan.fields) {
|
|
303
|
+
const v = searchValue(record[f.attr], f.encrypted ? { ...f.encrypted, recordId } : undefined, `${plan.label}.${f.attr}`);
|
|
304
|
+
if (v !== undefined)
|
|
305
|
+
fields[f.attr] = v;
|
|
306
|
+
}
|
|
307
|
+
return { id: recordId, fields };
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Apply a change to a synced index (merge mode: attributes a partially loaded
|
|
311
|
+
* record lacks keep their indexed values). `deferred`: running after a commit,
|
|
312
|
+
* so a failure without `onError` is logged instead of thrown.
|
|
313
|
+
*/
|
|
314
|
+
export async function applySearchChange(sync, change, deferred = false) {
|
|
315
|
+
const upserted = (change.upsert ?? []).map((d) => String(d.id));
|
|
316
|
+
const deleted = change.delete ?? [];
|
|
317
|
+
try {
|
|
318
|
+
if (upserted.length) {
|
|
319
|
+
const r = await sync.index.upsert(change.upsert, { mode: 'merge', ...(sync.wait ? { wait: true } : {}) });
|
|
320
|
+
const first = r.errors[0];
|
|
321
|
+
if (first)
|
|
322
|
+
throw new CipherError(first.error.code, `search index ${sync.index.name} rejected document ${first.id}: ${bareMessage(first.error)}`);
|
|
323
|
+
}
|
|
324
|
+
if (deleted.length)
|
|
325
|
+
await sync.index.delete(deleted, sync.wait ? { wait: true } : {});
|
|
326
|
+
}
|
|
327
|
+
catch (e) {
|
|
328
|
+
if (sync.onError)
|
|
329
|
+
return sync.onError(e, { upserted, deleted });
|
|
330
|
+
if (!deferred)
|
|
331
|
+
throw e;
|
|
332
|
+
console.error(`[northstar-cipher] search index ${sync.index.name} is out of date (${upserted.length} upsert(s), ${deleted.length} delete(s)): ${e?.message ?? String(e)}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Index changes collected during a transaction and applied once it commits:
|
|
337
|
+
* one request per index, the last write of each record wins (a record deleted
|
|
338
|
+
* and written again is reset first).
|
|
339
|
+
*/
|
|
340
|
+
export class SearchChangeSet {
|
|
341
|
+
byIndex = new Map();
|
|
342
|
+
add(sync, change) {
|
|
343
|
+
let m = this.byIndex.get(sync);
|
|
344
|
+
if (!m)
|
|
345
|
+
this.byIndex.set(sync, (m = new Map()));
|
|
346
|
+
for (const id of change.delete ?? [])
|
|
347
|
+
m.set(id, { fields: null, reset: true });
|
|
348
|
+
for (const d of change.upsert ?? []) {
|
|
349
|
+
const id = String(d.id);
|
|
350
|
+
const prev = m.get(id);
|
|
351
|
+
m.set(id, { fields: { ...(prev?.fields ?? {}), ...d.fields }, reset: prev?.reset ?? false });
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/** Fold a committed savepoint's changes into its parent. */
|
|
355
|
+
merge(other) {
|
|
356
|
+
for (const [sync, m] of other.byIndex) {
|
|
357
|
+
for (const [id, e] of m) {
|
|
358
|
+
if (e.fields === null)
|
|
359
|
+
this.add(sync, { delete: [id] });
|
|
360
|
+
else {
|
|
361
|
+
if (e.reset)
|
|
362
|
+
this.add(sync, { delete: [id] });
|
|
363
|
+
this.add(sync, { upsert: [{ id, fields: e.fields }] });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async apply(deferred) {
|
|
369
|
+
for (const [sync, m] of this.byIndex) {
|
|
370
|
+
const deletes = [...m].filter(([, e]) => e.reset).map(([id]) => id);
|
|
371
|
+
const upserts = [...m].filter(([, e]) => e.fields !== null).map(([id, e]) => ({ id, fields: e.fields }));
|
|
372
|
+
if (deletes.length)
|
|
373
|
+
await applySearchChange(sync, { delete: deletes }, deferred);
|
|
374
|
+
if (upserts.length)
|
|
375
|
+
await applySearchChange(sync, { upsert: upserts }, deferred);
|
|
376
|
+
}
|
|
377
|
+
this.byIndex.clear();
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// ------------------------------------------------------------------ record ids
|
|
381
|
+
/** Quote a PostgreSQL identifier. */
|
|
382
|
+
export function quoteIdent(name) {
|
|
383
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
384
|
+
}
|
|
385
|
+
/** `"schema"."table"` (schema optional) for `pg_get_serial_sequence`, which parses its first argument as an identifier. */
|
|
386
|
+
export function qualifiedTable(table, schema) {
|
|
387
|
+
return schema ? `${quoteIdent(schema)}.${quoteIdent(table)}` : quoteIdent(table);
|
|
388
|
+
}
|
|
389
|
+
/** SQL reserving `$3` values from the serial/identity sequence of `$1` (qualified table) `$2` (column). */
|
|
390
|
+
export const RESERVE_IDS_SQL = 'SELECT nextval(pg_get_serial_sequence($1, $2))::text AS id FROM generate_series(1, $3::int)';
|
|
391
|
+
/** Check rows returned by {@link RESERVE_IDS_SQL}. */
|
|
392
|
+
export function reservedIdsFrom(rows, count, label) {
|
|
393
|
+
const list = Array.isArray(rows) ? rows : rows?.rows;
|
|
394
|
+
if (!Array.isArray(list) || list.length !== count)
|
|
395
|
+
throw new CipherError('PROTOCOL_ERROR', `${label}: sequence reservation returned an unexpected result`);
|
|
396
|
+
return list.map((r) => {
|
|
397
|
+
const id = r?.id;
|
|
398
|
+
if (id === null || id === undefined) {
|
|
399
|
+
throw configError(`${label}: the primary key has no serial/identity sequence; supply ids explicitly or use a client-generated id`);
|
|
400
|
+
}
|
|
401
|
+
return String(id);
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
const B36 = '0123456789abcdefghijklmnopqrstuvwxyz';
|
|
405
|
+
const CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
|
|
406
|
+
const URL_ALPHABET = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyz';
|
|
407
|
+
function rand(n) {
|
|
408
|
+
return globalThis.crypto.getRandomValues(new Uint8Array(n));
|
|
409
|
+
}
|
|
410
|
+
function randomChars(alphabet, n) {
|
|
411
|
+
// Rejection sampling keeps the distribution uniform.
|
|
412
|
+
const limit = 256 - (256 % alphabet.length);
|
|
413
|
+
let out = '';
|
|
414
|
+
while (out.length < n) {
|
|
415
|
+
for (const b of rand(n * 2)) {
|
|
416
|
+
if (b < limit)
|
|
417
|
+
out += alphabet[b % alphabet.length];
|
|
418
|
+
if (out.length === n)
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return out;
|
|
423
|
+
}
|
|
424
|
+
function uuidV4() {
|
|
425
|
+
return globalThis.crypto.randomUUID();
|
|
426
|
+
}
|
|
427
|
+
function uuidV7() {
|
|
428
|
+
const b = rand(16);
|
|
429
|
+
const ms = BigInt(Date.now());
|
|
430
|
+
for (let i = 0; i < 6; i++)
|
|
431
|
+
b[i] = Number((ms >> BigInt(8 * (5 - i))) & 0xffn);
|
|
432
|
+
b[6] = (b[6] & 0x0f) | 0x70;
|
|
433
|
+
b[8] = (b[8] & 0x3f) | 0x80;
|
|
434
|
+
const h = Array.from(b, (x) => x.toString(16).padStart(2, '0')).join('');
|
|
435
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
436
|
+
}
|
|
437
|
+
let cuidCounter = Math.floor(Math.random() * 1679616);
|
|
438
|
+
const cuidFingerprint = randomChars(B36, 4);
|
|
439
|
+
/** cuid (v1 layout: `c` + time + counter + fingerprint + random, 25 chars). */
|
|
440
|
+
function cuidV1() {
|
|
441
|
+
cuidCounter = (cuidCounter + 1) % 1679616;
|
|
442
|
+
return `c${Date.now().toString(36).padStart(8, '0').slice(-8)}${cuidCounter.toString(36).padStart(4, '0')}${cuidFingerprint}${randomChars(B36, 8)}`;
|
|
443
|
+
}
|
|
444
|
+
/** cuid2-compatible id: a lowercase letter followed by 23 random base-36 chars. */
|
|
445
|
+
function cuidV2() {
|
|
446
|
+
return randomChars(B36.slice(10), 1) + randomChars(B36, 23);
|
|
447
|
+
}
|
|
448
|
+
function ulid() {
|
|
449
|
+
let t = Date.now();
|
|
450
|
+
let time = '';
|
|
451
|
+
for (let i = 0; i < 10; i++) {
|
|
452
|
+
time = CROCKFORD[t % 32] + time;
|
|
453
|
+
t = Math.floor(t / 32);
|
|
454
|
+
}
|
|
455
|
+
return time + randomChars(CROCKFORD, 16);
|
|
456
|
+
}
|
|
457
|
+
function nanoid() {
|
|
458
|
+
return randomChars(URL_ALPHABET, 21);
|
|
459
|
+
}
|
|
460
|
+
/** Client-side id generators (all CSPRNG based, no dependencies). */
|
|
461
|
+
export const ID_GENERATORS = {
|
|
462
|
+
uuid: uuidV4,
|
|
463
|
+
uuidv4: uuidV4,
|
|
464
|
+
uuidv7: uuidV7,
|
|
465
|
+
cuid: cuidV1,
|
|
466
|
+
cuid2: cuidV2,
|
|
467
|
+
ulid,
|
|
468
|
+
nanoid,
|
|
469
|
+
};
|
|
470
|
+
export function isIdGeneratorName(v) {
|
|
471
|
+
return typeof v === 'string' && Object.prototype.hasOwnProperty.call(ID_GENERATORS, v);
|
|
472
|
+
}
|
|
473
|
+
/** True for a UUID-shaped string. */
|
|
474
|
+
export function isUuid(v) {
|
|
475
|
+
return typeof v === 'string' && UUID_RE.test(v);
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Remembers, per model instance/document, the plaintext of fields whose stored
|
|
479
|
+
* representation was just swapped for ciphertext, so integrations can restore
|
|
480
|
+
* plaintext after the write (or after a failed write) and never re-encrypt their
|
|
481
|
+
* own ciphertext on a retry.
|
|
482
|
+
*/
|
|
483
|
+
export class PlaintextLedger {
|
|
484
|
+
map = new WeakMap();
|
|
485
|
+
set(owner, attr, plaintext, ciphertext) {
|
|
486
|
+
let m = this.map.get(owner);
|
|
487
|
+
if (!m)
|
|
488
|
+
this.map.set(owner, (m = new Map()));
|
|
489
|
+
m.set(attr, { plaintext, ciphertext });
|
|
490
|
+
}
|
|
491
|
+
get(owner, attr) {
|
|
492
|
+
return this.map.get(owner)?.get(attr);
|
|
493
|
+
}
|
|
494
|
+
entries(owner) {
|
|
495
|
+
return [...(this.map.get(owner)?.entries() ?? [])];
|
|
496
|
+
}
|
|
497
|
+
has(owner) {
|
|
498
|
+
return (this.map.get(owner)?.size ?? 0) > 0;
|
|
499
|
+
}
|
|
500
|
+
clear(owner) {
|
|
501
|
+
this.map.delete(owner);
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* The plaintext a current value stands for: if `current` is exactly the
|
|
505
|
+
* ciphertext (or sentinel) this ledger produced for `attr`, the remembered
|
|
506
|
+
* plaintext; otherwise `current` itself.
|
|
507
|
+
*/
|
|
508
|
+
resolve(owner, attr, current) {
|
|
509
|
+
const e = this.get(owner, attr);
|
|
510
|
+
return e && e.ciphertext !== null && current === e.ciphertext ? e.plaintext : current;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/integrations/shared.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeORM integration: an `@Encrypted()` property decorator and an entity subscriber.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* import { Encrypted, northstarCipherSubscriber, cipherWhere } from '@northstartech/cipher/typeorm'
|
|
6
|
+
*
|
|
7
|
+
* @Entity('todos')
|
|
8
|
+
* class Todo {
|
|
9
|
+
* @PrimaryGeneratedColumn('uuid') id!: string
|
|
10
|
+
* @Encrypted() @Column({ type: 'text', nullable: true }) title!: string | null
|
|
11
|
+
* @Encrypted({ searchable: 'email' }) @Column({ type: 'text', nullable: true }) email!: string | null
|
|
12
|
+
* @Index() @Column({ type: 'varchar', length: 64, nullable: true }) email_bidx!: string | null
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* const ds = new DataSource({ …, entities: [Todo], subscribers: [northstarCipherSubscriber(cipher)] })
|
|
16
|
+
* await ds.getRepository(Todo).save({ title: 'Buy milk', email: 'ana@example.com' })
|
|
17
|
+
* const todos = await ds.getRepository(Todo).find() // afterLoad decrypts in one batch request
|
|
18
|
+
* const mine = await ds.getRepository(Todo).findBy(await cipherWhere(ds, Todo, 'email', 'ANA@example.com'))
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Record ids: `uuid` primary keys are generated client-side before the insert;
|
|
22
|
+
* generated integer keys (increment/identity/rowid) use the two-phase write
|
|
23
|
+
* (sentinel, then ciphertext in `afterInsert`, inside `save()`'s transaction).
|
|
24
|
+
*
|
|
25
|
+
* Northstar Search: `northstarCipherSubscriber(cipher, { search: [{ entity: Todo, index: cipher.index('todos'), fields: ['title', 'email'] }] })`
|
|
26
|
+
* keeps an index in sync after each transaction commits (encrypted properties are
|
|
27
|
+
* indexed from their stored ciphertext, which the enclave decrypts);
|
|
28
|
+
* `searchDocuments(ds, Todo, rows, fields)` builds documents for a (re)index.
|
|
29
|
+
*/
|
|
30
|
+
import { type DataSource, type EntitySubscriberInterface, In, type InsertEvent, type LoadEvent, type ObjectLiteral, type RemoveEvent, type SoftRemoveEvent, type TransactionCommitEvent, type TransactionRollbackEvent, type UpdateEvent } from 'typeorm';
|
|
31
|
+
import type { NorthstarCipher } from '../client.js';
|
|
32
|
+
import { CipherError } from '../errors.js';
|
|
33
|
+
import { type CipherIntegrationOptions, type EncryptedFieldOptions, type SearchSyncOptions, isCiphertext } from './shared.js';
|
|
34
|
+
import type { SearchDocument } from '../search.js';
|
|
35
|
+
export { CIPHERTEXT_PREFIX, PENDING_SENTINEL } from './shared.js';
|
|
36
|
+
export type { EncryptedFieldOptions, CipherIntegrationOptions, SearchSyncOptions } from './shared.js';
|
|
37
|
+
type Ctor = abstract new (...args: never[]) => unknown;
|
|
38
|
+
/**
|
|
39
|
+
* Mark an entity property as encrypted. Use with a text column:
|
|
40
|
+
* `@Encrypted() @Column({ type: 'text', nullable: true }) title!: string | null`.
|
|
41
|
+
* Searchable properties need a `<property>_bidx` varchar(64) column (or `indexField`).
|
|
42
|
+
*/
|
|
43
|
+
export declare function Encrypted(options?: EncryptedFieldOptions): PropertyDecorator;
|
|
44
|
+
type Entity = ObjectLiteral;
|
|
45
|
+
/** Keep a Northstar Search index in sync with an entity (`save()`/`remove()`/`softRemove()` through repositories and managers). */
|
|
46
|
+
export interface TypeOrmSearchSync extends SearchSyncOptions {
|
|
47
|
+
/** Entity class (or entity name). */
|
|
48
|
+
entity: Ctor | string;
|
|
49
|
+
}
|
|
50
|
+
export interface NorthstarTypeOrmOptions extends CipherIntegrationOptions {
|
|
51
|
+
/**
|
|
52
|
+
* Northstar Search indexes to keep in sync. Changes made in a transaction are
|
|
53
|
+
* applied once it commits (dropped on rollback); `QueryBuilder` and
|
|
54
|
+
* `Repository.update()/delete()` writes are not tracked: reindex after those.
|
|
55
|
+
*/
|
|
56
|
+
search?: TypeOrmSearchSync[];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Entity subscriber that encrypts `@Encrypted()` properties before INSERT/UPDATE
|
|
60
|
+
* and decrypts them after load. Register an instance (or use
|
|
61
|
+
* {@link northstarCipherSubscriber} for the DataSource `subscribers` option).
|
|
62
|
+
*/
|
|
63
|
+
export declare class NorthstarCipherSubscriber implements EntitySubscriberInterface<Entity> {
|
|
64
|
+
readonly cipher: NorthstarCipher;
|
|
65
|
+
readonly options: NorthstarTypeOrmOptions;
|
|
66
|
+
private readonly ledger;
|
|
67
|
+
private readonly encrypt;
|
|
68
|
+
private readonly searchPlans;
|
|
69
|
+
/** Index changes per query runner and transaction depth, applied when the outermost transaction commits. */
|
|
70
|
+
private readonly pendingSearch;
|
|
71
|
+
constructor(cipher: NorthstarCipher, options?: NorthstarTypeOrmOptions);
|
|
72
|
+
private searchPlanFor;
|
|
73
|
+
/** Index a change now, or once the transaction it belongs to commits. */
|
|
74
|
+
private queueSearch;
|
|
75
|
+
afterTransactionCommit(event: TransactionCommitEvent): Promise<void>;
|
|
76
|
+
afterTransactionRollback(event: TransactionRollbackEvent): void;
|
|
77
|
+
private searchDocument;
|
|
78
|
+
/** Encrypt `fields` of one entity (one batch, coalesced with other entities of the same save). */
|
|
79
|
+
private encryptEntity;
|
|
80
|
+
private restore;
|
|
81
|
+
beforeInsert(event: InsertEvent<Entity>): Promise<void>;
|
|
82
|
+
afterInsert(event: InsertEvent<Entity>): Promise<void>;
|
|
83
|
+
beforeUpdate(event: UpdateEvent<Entity>): Promise<void>;
|
|
84
|
+
afterUpdate(event: UpdateEvent<Entity>): Promise<void>;
|
|
85
|
+
afterRemove(event: RemoveEvent<Entity>): Promise<void>;
|
|
86
|
+
afterSoftRemove(event: SoftRemoveEvent<Entity>): Promise<void>;
|
|
87
|
+
private unindex;
|
|
88
|
+
/** afterInsert of an entity without encrypted properties. */
|
|
89
|
+
private indexPlain;
|
|
90
|
+
afterLoad(entity: Entity, event?: LoadEvent<Entity>): Promise<void> | void;
|
|
91
|
+
}
|
|
92
|
+
/** Subscriber class bound to `cipher`, for `new DataSource({ subscribers: [northstarCipherSubscriber(cipher)] })`. */
|
|
93
|
+
export declare function northstarCipherSubscriber(cipher: NorthstarCipher, options?: NorthstarTypeOrmOptions): new () => NorthstarCipherSubscriber;
|
|
94
|
+
/**
|
|
95
|
+
* Exact-match condition for a searchable encrypted property:
|
|
96
|
+
* `repo.findBy(await cipherWhere(ds, Todo, 'email', 'ana@example.com'))` → `{ email_bidx: In(tokens) }`.
|
|
97
|
+
*/
|
|
98
|
+
export declare function cipherWhere(dataSource: DataSource, entity: Ctor | string, property: string, value: string | string[]): Promise<Record<string, ReturnType<typeof In<string>>>>;
|
|
99
|
+
/**
|
|
100
|
+
* Northstar Search documents for entities or rows of `entity`. Rows read without
|
|
101
|
+
* the subscriber's decryption (e.g. `ds.createQueryBuilder(Todo, 't').getRawMany()`
|
|
102
|
+
* mapped to property names, or a DataSource without the subscriber) hold stored
|
|
103
|
+
* ciphertext, sent with the context it is bound to and decrypted inside the
|
|
104
|
+
* enclave; decrypted entities send their plaintext.
|
|
105
|
+
*/
|
|
106
|
+
export declare function searchDocuments(dataSource: DataSource, entity: Ctor | string, rows: Entity[], fields: string[]): SearchDocument[];
|
|
107
|
+
/** True when a raw column value is Northstar ciphertext (useful in migrations and tests). */
|
|
108
|
+
export { isCiphertext };
|
|
109
|
+
export { CipherError };
|