@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,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drizzle ORM helpers. Drizzle has no async lifecycle hooks, so encryption is an
|
|
3
|
+
* explicit, typed step around your queries.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { encrypted, northstarDrizzle, cipherEq } from '@northstartech/cipher/drizzle'
|
|
7
|
+
*
|
|
8
|
+
* export const todos = encrypted(
|
|
9
|
+
* pgTable('todos', { id: serial('id').primaryKey(), title: text('title'), email: text('email'), emailBidx: varchar('email_bidx', { length: 64 }) }),
|
|
10
|
+
* { title: true, email: { searchable: 'email', indexField: 'emailBidx' } },
|
|
11
|
+
* )
|
|
12
|
+
* const nsc = northstarDrizzle(cipher, { db }) // `db` reserves serial ids on PostgreSQL
|
|
13
|
+
*
|
|
14
|
+
* await db.insert(todos).values(await nsc.encryptRows(todos, [{ title: 'Buy milk', email: 'ana@example.com' }]))
|
|
15
|
+
* const rows = await nsc.decryptRows(todos, await db.select().from(todos)) // one decrypt request
|
|
16
|
+
* const tokens = await nsc.searchTokens(todos.email, 'ANA@example.com')
|
|
17
|
+
* const mine = await nsc.decryptRows(todos, await db.select().from(todos).where(cipherEq(todos.email, tokens)))
|
|
18
|
+
*
|
|
19
|
+
* // Northstar Search: index stored rows as they are (the enclave decrypts the ciphertext)
|
|
20
|
+
* await cipher.index('todos').upsert(searchDocuments(todos, await db.select().from(todos), ['title', 'email', 'done']))
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import { Column, Table, getTableColumns, getTableName, inArray, is, sql } from 'drizzle-orm';
|
|
24
|
+
import { CipherError } from '../errors.js';
|
|
25
|
+
import { INDEX_SUFFIX, PENDING_SENTINEL, configError, decryptValues, encryptValues, normalizeFieldSpec, plaintextOf, qualifiedTable, recordIdOf, requestError, reservedIdsFrom, searchDocumentOf, searchTokensFor, } from './shared.js';
|
|
26
|
+
export { CIPHERTEXT_PREFIX, PENDING_SENTINEL } from './shared.js';
|
|
27
|
+
const tablePlans = new WeakMap();
|
|
28
|
+
const columnPlans = new WeakMap();
|
|
29
|
+
const SERIAL_TYPES = new Set(['PgSerial', 'PgSmallSerial', 'PgBigSerial53', 'PgBigSerial64']);
|
|
30
|
+
const STRING_TYPES = new Set(['PgText', 'PgVarchar', 'PgChar', 'MySqlText', 'MySqlVarChar', 'MySqlChar', 'SQLiteText']);
|
|
31
|
+
/**
|
|
32
|
+
* Declare the encrypted columns of a Drizzle table (call once, next to the table
|
|
33
|
+
* definition). Returns the table unchanged so it can wrap the definition.
|
|
34
|
+
*/
|
|
35
|
+
export function encrypted(table, fields, options = {}) {
|
|
36
|
+
if (!is(table, Table))
|
|
37
|
+
throw configError('Drizzle: encrypted(table, fields) expects a Drizzle table');
|
|
38
|
+
if (tablePlans.has(table))
|
|
39
|
+
throw configError(`Drizzle: ${getTableName(table)} is already configured`);
|
|
40
|
+
const columns = getTableColumns(table);
|
|
41
|
+
const name = getTableName(table);
|
|
42
|
+
const byDbName = new Map(Object.entries(columns).map(([k, c]) => [c.name, k]));
|
|
43
|
+
let pkKey = options.primaryKey;
|
|
44
|
+
if (pkKey === undefined) {
|
|
45
|
+
const pks = Object.entries(columns).filter(([, c]) => c.primary);
|
|
46
|
+
if (pks.length !== 1)
|
|
47
|
+
throw configError(`Drizzle: ${name} needs exactly one .primaryKey() column (or pass { primaryKey }) to bind ciphertext to records`);
|
|
48
|
+
pkKey = pks[0][0];
|
|
49
|
+
}
|
|
50
|
+
const pkColumn = columns[pkKey];
|
|
51
|
+
if (!pkColumn)
|
|
52
|
+
throw configError(`Drizzle: ${name}.${pkKey}: no such column`);
|
|
53
|
+
const plan = { table, name, schema: table[Symbol.for('drizzle:Schema')], pkKey, pkColumn, fields: [], byKey: new Map() };
|
|
54
|
+
for (const [key, spec] of Object.entries(fields)) {
|
|
55
|
+
if (spec === undefined)
|
|
56
|
+
continue;
|
|
57
|
+
const lb = `Drizzle: ${name}.${key}`;
|
|
58
|
+
const column = columns[key];
|
|
59
|
+
if (!column)
|
|
60
|
+
throw configError(`${lb}: no such column`);
|
|
61
|
+
if (key === pkKey)
|
|
62
|
+
throw configError(`${lb}: the primary key cannot be encrypted`);
|
|
63
|
+
if (!STRING_TYPES.has(column.columnType))
|
|
64
|
+
throw configError(`${lb}: encrypted columns must be text (got ${column.columnType})`);
|
|
65
|
+
const n = normalizeFieldSpec(spec, lb);
|
|
66
|
+
let indexKey;
|
|
67
|
+
if (n.searchable) {
|
|
68
|
+
indexKey = n.indexField ?? byDbName.get(`${column.name}${INDEX_SUFFIX}`) ?? (columns[`${key}${INDEX_SUFFIX}`] ? `${key}${INDEX_SUFFIX}` : columns[`${key}Bidx`] ? `${key}Bidx` : undefined);
|
|
69
|
+
if (!indexKey || !columns[indexKey])
|
|
70
|
+
throw configError(`${lb}: searchable columns need a blind-index column (${column.name}${INDEX_SUFFIX} varchar(64), indexed) or indexField`);
|
|
71
|
+
}
|
|
72
|
+
const f = {
|
|
73
|
+
key,
|
|
74
|
+
column,
|
|
75
|
+
name: column.name,
|
|
76
|
+
...(n.normalization ? { normalization: n.normalization } : {}),
|
|
77
|
+
...(indexKey ? { indexKey, indexColumn: columns[indexKey] } : {}),
|
|
78
|
+
};
|
|
79
|
+
plan.fields.push(f);
|
|
80
|
+
plan.byKey.set(key, f);
|
|
81
|
+
columnPlans.set(column, { plan, field: f, isIndex: false });
|
|
82
|
+
if (f.indexColumn)
|
|
83
|
+
columnPlans.set(f.indexColumn, { plan, field: f, isIndex: true });
|
|
84
|
+
}
|
|
85
|
+
if (!plan.fields.length)
|
|
86
|
+
throw configError(`Drizzle: ${name}: no encrypted columns listed`);
|
|
87
|
+
tablePlans.set(table, plan);
|
|
88
|
+
return table;
|
|
89
|
+
}
|
|
90
|
+
function planOf(table) {
|
|
91
|
+
const p = tablePlans.get(table);
|
|
92
|
+
if (!p)
|
|
93
|
+
throw configError(`Drizzle: ${is(table, Table) ? getTableName(table) : 'value'} is not configured; wrap the table with encrypted(table, { … })`);
|
|
94
|
+
return p;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Exact-match condition: `cipherEq(todos.email, tokens)` → `email_bidx IN (…tokens)`.
|
|
98
|
+
* Accepts the encrypted column (mapped to its blind index) or the blind-index column.
|
|
99
|
+
* Empty token lists match nothing.
|
|
100
|
+
*/
|
|
101
|
+
export function cipherEq(column, tokens) {
|
|
102
|
+
const hit = columnPlans.get(column);
|
|
103
|
+
if (!hit || !hit.field.indexColumn)
|
|
104
|
+
throw configError(`Drizzle: ${column?.name ?? 'column'} is not a searchable encrypted column or its blind index`);
|
|
105
|
+
const flat = [...new Set(tokens.flat().map((t) => {
|
|
106
|
+
if (typeof t !== 'string')
|
|
107
|
+
throw requestError('Drizzle: cipherEq tokens must be strings (from searchTokens)');
|
|
108
|
+
return t;
|
|
109
|
+
}))];
|
|
110
|
+
if (!flat.length)
|
|
111
|
+
return sql `false`;
|
|
112
|
+
return inArray(hit.field.indexColumn, flat);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Two-phase write, step 1 (databases where the id is only known after INSERT):
|
|
116
|
+
* the row with every non-null encrypted value replaced by the `nsc:pending:v1`
|
|
117
|
+
* sentinel. Insert it, then `update(...).set(await nsc.encryptRow(table, row, { recordId }))`
|
|
118
|
+
* `.where(and(eq(table.id, id), eq(table.col, PENDING_SENTINEL)))`, ideally in one transaction.
|
|
119
|
+
*/
|
|
120
|
+
export function pendingRow(table, row) {
|
|
121
|
+
const plan = planOf(table);
|
|
122
|
+
const out = { ...row };
|
|
123
|
+
for (const f of plan.fields) {
|
|
124
|
+
if (out[f.key] === undefined)
|
|
125
|
+
continue;
|
|
126
|
+
if (out[f.key] !== null)
|
|
127
|
+
out[f.key] = PENDING_SENTINEL;
|
|
128
|
+
if (f.indexKey)
|
|
129
|
+
out[f.indexKey] = null;
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Northstar Search documents for stored rows (`db.select().from(table)`, not
|
|
135
|
+
* decrypted): encrypted columns are sent as their ciphertext with the context
|
|
136
|
+
* they are bound to and decrypted inside the enclave, so indexing needs no
|
|
137
|
+
* decrypt request. Rows already decrypted with `decryptRows` work too (their
|
|
138
|
+
* plaintext is sent). Document ids are the primary keys; field names are the
|
|
139
|
+
* table's JS keys.
|
|
140
|
+
*/
|
|
141
|
+
export function searchDocuments(table, rows, fields) {
|
|
142
|
+
const plan = planOf(table);
|
|
143
|
+
const columns = getTableColumns(table);
|
|
144
|
+
const plans = fields.map((key) => {
|
|
145
|
+
if (!columns[key])
|
|
146
|
+
throw configError(`Drizzle: ${plan.name}.${key}: no such column`);
|
|
147
|
+
const f = plan.byKey.get(key);
|
|
148
|
+
return { attr: key, ...(f ? { encrypted: { collection: plan.name, field: f.name } } : {}) };
|
|
149
|
+
});
|
|
150
|
+
return rows.map((row) => searchDocumentOf(row, { id: row[plan.pkKey], fields: plans, label: plan.name }));
|
|
151
|
+
}
|
|
152
|
+
/** Bind the helpers to a Northstar Cipher client. */
|
|
153
|
+
export function northstarDrizzle(cipher, options = {}) {
|
|
154
|
+
if (!cipher || typeof cipher.encryptManySettled !== 'function')
|
|
155
|
+
throw configError('Drizzle: northstarDrizzle(cipher) requires a NorthstarCipher client');
|
|
156
|
+
const reserveIds = async (table, count, db = options.db) => {
|
|
157
|
+
const plan = planOf(table);
|
|
158
|
+
if (!db)
|
|
159
|
+
throw configError(`Drizzle: ${plan.name}.${plan.pkColumn.name}: pass { db } to reserve serial ids`);
|
|
160
|
+
if (!SERIAL_TYPES.has(plan.pkColumn.columnType) && !plan.pkColumn.generatedIdentity) {
|
|
161
|
+
throw configError(`Drizzle: ${plan.name}.${plan.pkColumn.name} is not a PostgreSQL serial/identity column`);
|
|
162
|
+
}
|
|
163
|
+
if (plan.pkColumn.generatedIdentity?.type === 'always') {
|
|
164
|
+
throw configError(`Drizzle: ${plan.name}.${plan.pkColumn.name} is GENERATED ALWAYS AS IDENTITY, which rejects explicit ids; use generatedByDefaultAsIdentity()`);
|
|
165
|
+
}
|
|
166
|
+
const res = await db.execute(sql `SELECT nextval(pg_get_serial_sequence(${qualifiedTable(plan.name, plan.schema)}, ${plan.pkColumn.name}))::text AS id FROM generate_series(1, ${count}::int)`);
|
|
167
|
+
const big = plan.pkColumn.columnType === 'PgBigSerial64' || plan.pkColumn.dataType === 'bigint';
|
|
168
|
+
return reservedIdsFrom(res, count, `${plan.name}.${plan.pkColumn.name}`).map((v) => (big ? BigInt(v) : Number(v)));
|
|
169
|
+
};
|
|
170
|
+
const fillIds = async (plan, rows, db) => {
|
|
171
|
+
const missing = rows.filter((r) => r[plan.pkKey] === undefined || r[plan.pkKey] === null);
|
|
172
|
+
if (!missing.length)
|
|
173
|
+
return;
|
|
174
|
+
const gen = plan.pkColumn.defaultFn;
|
|
175
|
+
if (gen) {
|
|
176
|
+
for (const r of missing) {
|
|
177
|
+
const v = gen();
|
|
178
|
+
if (v && typeof v === 'object')
|
|
179
|
+
throw configError(`Drizzle: ${plan.name}.${plan.pkColumn.name}: $defaultFn returns SQL; generate ids in JS to encrypt before insert`);
|
|
180
|
+
r[plan.pkKey] = v;
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (SERIAL_TYPES.has(plan.pkColumn.columnType) || plan.pkColumn.generatedIdentity) {
|
|
185
|
+
const ids = await reserveIds(plan.table, missing.length, db);
|
|
186
|
+
missing.forEach((r, i) => (r[plan.pkKey] = ids[i]));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
throw requestError(`${plan.name}.${plan.pkColumn.name} is unknown before the insert: pass it, give the column a $defaultFn (e.g. crypto.randomUUID), ` +
|
|
190
|
+
`pass { db } for PostgreSQL serial keys, or use the two-phase write (pendingRow, then encryptRow with { recordId })`);
|
|
191
|
+
};
|
|
192
|
+
const encryptMany = async (plan, rows, ids) => {
|
|
193
|
+
const out = rows.map((r) => ({ ...r }));
|
|
194
|
+
const items = [];
|
|
195
|
+
const targets = [];
|
|
196
|
+
out.forEach((row, i) => {
|
|
197
|
+
for (const f of plan.fields) {
|
|
198
|
+
if (f.indexKey && row[f.indexKey] !== undefined && row[f.key] === undefined) {
|
|
199
|
+
throw requestError(`${plan.name}.${f.indexColumn.name} is the blind index of ${f.name} and is managed by Northstar Cipher`);
|
|
200
|
+
}
|
|
201
|
+
const v = row[f.key];
|
|
202
|
+
if (v === undefined)
|
|
203
|
+
continue;
|
|
204
|
+
if (v === null) {
|
|
205
|
+
if (f.indexKey)
|
|
206
|
+
row[f.indexKey] = null;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const lb = `${plan.name}.${f.name}`;
|
|
210
|
+
const recordId = recordIdOf(ids[i], lb);
|
|
211
|
+
items.push({ collection: plan.name, field: f.name, recordId, plaintext: plaintextOf(v, lb), normalization: f.normalization, label: `${lb} (${plan.pkColumn.name}=${recordId})` });
|
|
212
|
+
targets.push({ row, f });
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
const enc = await encryptValues(cipher, items, { keyId: options.keyId });
|
|
216
|
+
enc.forEach((e, i) => {
|
|
217
|
+
const { row, f } = targets[i];
|
|
218
|
+
row[f.key] = e.ciphertext;
|
|
219
|
+
if (f.indexKey)
|
|
220
|
+
row[f.indexKey] = e.index ?? null;
|
|
221
|
+
});
|
|
222
|
+
return out;
|
|
223
|
+
};
|
|
224
|
+
const decryptMany = async (plan, rows, purpose) => {
|
|
225
|
+
const out = rows.map((r) => ({ ...r }));
|
|
226
|
+
const items = [];
|
|
227
|
+
const targets = [];
|
|
228
|
+
for (const row of out) {
|
|
229
|
+
for (const f of plan.fields) {
|
|
230
|
+
if (!(f.key in row))
|
|
231
|
+
continue;
|
|
232
|
+
const stored = row[f.key];
|
|
233
|
+
const lb = `${plan.name}.${f.name}`;
|
|
234
|
+
if (stored !== null && stored !== undefined && (row[plan.pkKey] === undefined || row[plan.pkKey] === null)) {
|
|
235
|
+
throw requestError(`${lb}: select the primary key (${plan.pkKey}) together with encrypted columns`);
|
|
236
|
+
}
|
|
237
|
+
const recordId = stored === null || stored === undefined ? '' : recordIdOf(row[plan.pkKey], lb);
|
|
238
|
+
items.push({ collection: plan.name, field: f.name, recordId, stored, label: `${lb} (${plan.pkColumn.name}=${recordId})` });
|
|
239
|
+
targets.push({ row, key: f.key });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const plain = await decryptValues(cipher, items, { purpose: purpose ?? options.purpose, readLegacyPlaintext: options.readLegacyPlaintext === true });
|
|
243
|
+
plain.forEach((p, i) => (targets[i].row[targets[i].key] = p));
|
|
244
|
+
return out;
|
|
245
|
+
};
|
|
246
|
+
return {
|
|
247
|
+
async encryptRow(table, row, opts = {}) {
|
|
248
|
+
const plan = planOf(table);
|
|
249
|
+
if (!row || typeof row !== 'object')
|
|
250
|
+
throw requestError('Drizzle: encryptRow expects a row object');
|
|
251
|
+
const copy = { ...row };
|
|
252
|
+
let id = opts.recordId;
|
|
253
|
+
if (id === undefined) {
|
|
254
|
+
if (plan.fields.some((f) => copy[f.key] !== undefined && copy[f.key] !== null))
|
|
255
|
+
await fillIds(plan, [copy], opts.db);
|
|
256
|
+
id = copy[plan.pkKey];
|
|
257
|
+
}
|
|
258
|
+
return (await encryptMany(plan, [copy], [id]))[0];
|
|
259
|
+
},
|
|
260
|
+
async encryptRows(table, rows, opts = {}) {
|
|
261
|
+
const plan = planOf(table);
|
|
262
|
+
const copies = rows.map((r) => ({ ...r }));
|
|
263
|
+
await fillIds(plan, copies.filter((r) => plan.fields.some((f) => r[f.key] !== undefined && r[f.key] !== null)), opts.db);
|
|
264
|
+
return (await encryptMany(plan, copies, copies.map((r) => r[plan.pkKey])));
|
|
265
|
+
},
|
|
266
|
+
async decryptRows(table, rows, opts = {}) {
|
|
267
|
+
return decryptMany(planOf(table), rows, opts.purpose);
|
|
268
|
+
},
|
|
269
|
+
async decryptRow(table, row, opts = {}) {
|
|
270
|
+
if (row === undefined || row === null)
|
|
271
|
+
return row;
|
|
272
|
+
return (await decryptMany(planOf(table), [row], opts.purpose))[0];
|
|
273
|
+
},
|
|
274
|
+
async searchTokens(column, value) {
|
|
275
|
+
const hit = columnPlans.get(column);
|
|
276
|
+
if (!hit || !hit.field.indexColumn)
|
|
277
|
+
throw configError(`Drizzle: ${column?.name ?? 'column'} is not a searchable encrypted column`);
|
|
278
|
+
const f = hit.field;
|
|
279
|
+
return searchTokensFor(cipher, { collection: hit.plan.name, field: f.name, normalization: f.normalization, values: value, keyId: options.keyId, label: `${hit.plan.name}.${f.name}` });
|
|
280
|
+
},
|
|
281
|
+
reserveIds,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
export { CipherError };
|
|
285
|
+
//# sourceMappingURL=drizzle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drizzle.js","sourceRoot":"","sources":["../../src/integrations/drizzle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mongoose (v9) schema plugin: transparent field-level encryption.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* import { northstarCipherPlugin } from '@northstartech/cipher/mongoose'
|
|
6
|
+
*
|
|
7
|
+
* const todoSchema = new Schema({ title: String, email: String })
|
|
8
|
+
* todoSchema.plugin(northstarCipherPlugin, { cipher, fields: ['title', { path: 'email', searchable: 'email' }] })
|
|
9
|
+
* const Todo = model('Todo', todoSchema)
|
|
10
|
+
*
|
|
11
|
+
* await Todo.create({ title: 'Buy milk', email: 'ana@example.com' })
|
|
12
|
+
* const todos = await Todo.find() // one decrypt request per query
|
|
13
|
+
* const mine = await Todo.find().cipherWhere('email', 'ANA@example.com') // exact match on email_bidx
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* ObjectIds exist before the insert, so `_id` is the record id. Validation always
|
|
17
|
+
* sees plaintext; setters (`lowercase`, `trim`, …) apply to plaintext and never
|
|
18
|
+
* to ciphertext. See README "Mongoose".
|
|
19
|
+
*
|
|
20
|
+
* Northstar Search: `search: { index: cipher.index('todos'), fields: ['title', 'email', 'done'] }`
|
|
21
|
+
* keeps an index in sync on `save()`, `insertMany()`, `doc.deleteOne()` and
|
|
22
|
+
* `findOneAndDelete()` (encrypted paths are indexed from their stored ciphertext,
|
|
23
|
+
* which the enclave decrypts); `Todo.cipherSearchDocuments(docs)` builds documents for a (re)index.
|
|
24
|
+
*/
|
|
25
|
+
import { type Schema } from 'mongoose';
|
|
26
|
+
import type { NorthstarCipher } from '../client.js';
|
|
27
|
+
import { CipherError } from '../errors.js';
|
|
28
|
+
import { type CipherIntegrationOptions, type EncryptedFieldOptions, type EncryptedFieldSpec, type SearchSyncOptions } from './shared.js';
|
|
29
|
+
import type { SearchDocument } from '../search.js';
|
|
30
|
+
export { CIPHERTEXT_PREFIX, PENDING_SENTINEL } from './shared.js';
|
|
31
|
+
export type { EncryptedFieldOptions, EncryptedFieldSpec, CipherIntegrationOptions, SearchSyncOptions } from './shared.js';
|
|
32
|
+
export type MongooseFieldSpec = string | ({
|
|
33
|
+
path: string;
|
|
34
|
+
} & EncryptedFieldOptions);
|
|
35
|
+
export interface NorthstarMongooseOptions extends CipherIntegrationOptions {
|
|
36
|
+
cipher: NorthstarCipher;
|
|
37
|
+
/** Encrypted String paths: `['title', { path: 'email', searchable: 'email' }]` or `{ title: true, … }`. */
|
|
38
|
+
fields: MongooseFieldSpec[] | Record<string, EncryptedFieldSpec>;
|
|
39
|
+
/**
|
|
40
|
+
* Keep a Northstar Search index in sync (`save()`, `insertMany()`, `doc.deleteOne()`,
|
|
41
|
+
* `findOneAndDelete()`; changes are indexed when the write returns, also inside
|
|
42
|
+
* transactions). Query updates and `deleteMany()` are not tracked: reindex after those.
|
|
43
|
+
*/
|
|
44
|
+
search?: SearchSyncOptions;
|
|
45
|
+
}
|
|
46
|
+
/** Query helper added by the plugin (add to your schema's `TQueryHelpers` type). */
|
|
47
|
+
export interface CipherQueryHelpers {
|
|
48
|
+
/** Exact match on a searchable encrypted path (resolved to `<path>_bidx: { $in: tokens }` when the query runs). */
|
|
49
|
+
cipherWhere(path: string, value: string | string[]): this;
|
|
50
|
+
}
|
|
51
|
+
/** Statics added by the plugin. */
|
|
52
|
+
export interface CipherModelStatics {
|
|
53
|
+
/** `{ <path>_bidx: { $in: tokens } }` for use in any filter. */
|
|
54
|
+
cipherFilter(path: string, value: string | string[]): Promise<Record<string, {
|
|
55
|
+
$in: string[];
|
|
56
|
+
}>>;
|
|
57
|
+
/** Decrypt documents or plain objects obtained outside `find` (aggregate, cursors, change streams). */
|
|
58
|
+
cipherDecrypt<T>(docs: T | T[]): Promise<T | T[]>;
|
|
59
|
+
/**
|
|
60
|
+
* Northstar Search documents for documents or plain objects (`fields` default to
|
|
61
|
+
* `search.fields`). Raw reads skip decryption, so
|
|
62
|
+
* `Todo.cipherSearchDocuments(await Todo.collection.find().toArray())` (or `aggregate()`
|
|
63
|
+
* results) index the stored ciphertext, which the enclave decrypts.
|
|
64
|
+
*/
|
|
65
|
+
cipherSearchDocuments(docs: unknown[], fields?: string[]): SearchDocument[];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Mongoose schema plugin. `schema.plugin(northstarCipherPlugin, { cipher, fields })`.
|
|
69
|
+
* Apply before compiling the model.
|
|
70
|
+
*/
|
|
71
|
+
export declare function northstarCipherPlugin(schema: Schema, options: NorthstarMongooseOptions): void;
|
|
72
|
+
export { CipherError };
|