@dynamatix/gb-schemas 2.17.13 → 2.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/applicants/applicant-additional-income.model.d.ts +1 -0
- package/dist/applicants/applicant-additional-income.model.d.ts.map +1 -1
- package/dist/applicants/applicant-additional-income.type.d.ts +1 -0
- package/dist/applicants/applicant-additional-income.type.d.ts.map +1 -1
- package/dist/applicants/applicant-commitment-creditCard.model.d.ts +1 -0
- package/dist/applicants/applicant-commitment-creditCard.model.d.ts.map +1 -1
- package/dist/applicants/applicant-commitment-loan.model.d.ts +1 -0
- package/dist/applicants/applicant-commitment-loan.model.d.ts.map +1 -1
- package/dist/applicants/applicant-commitment-mortgage.model.d.ts +1 -0
- package/dist/applicants/applicant-commitment-mortgage.model.d.ts.map +1 -1
- package/dist/applicants/applicant-commitment-residence.model.d.ts +1 -0
- package/dist/applicants/applicant-commitment-residence.model.d.ts.map +1 -1
- package/dist/applicants/applicant-commitment-secureLoan.model.d.ts +1 -0
- package/dist/applicants/applicant-commitment-secureLoan.model.d.ts.map +1 -1
- package/dist/applicants/applicant-commitment-unsecuredLoan.model.d.ts +1 -0
- package/dist/applicants/applicant-commitment-unsecuredLoan.model.d.ts.map +1 -1
- package/dist/applicants/applicant-credit-data.model.d.ts +1 -0
- package/dist/applicants/applicant-credit-data.model.d.ts.map +1 -1
- package/dist/applicants/applicant-credit-profile.model.d.ts +1 -0
- package/dist/applicants/applicant-credit-profile.model.d.ts.map +1 -1
- package/dist/applicants/applicant-credit-report.model.d.ts +1 -0
- package/dist/applicants/applicant-credit-report.model.d.ts.map +1 -1
- package/dist/applicants/applicant-credit-report.type.d.ts +1 -0
- package/dist/applicants/applicant-credit-report.type.d.ts.map +1 -1
- package/dist/applicants/applicant-employment-income.model.d.ts +1 -0
- package/dist/applicants/applicant-employment-income.model.d.ts.map +1 -1
- package/dist/applicants/applicant-employment-income.type.d.ts +1 -0
- package/dist/applicants/applicant-employment-income.type.d.ts.map +1 -1
- package/dist/applicants/applicant-employment.model.d.ts +1 -0
- package/dist/applicants/applicant-employment.model.d.ts.map +1 -1
- package/dist/applicants/applicant-expenditure.model.d.ts +1 -0
- package/dist/applicants/applicant-expenditure.model.d.ts.map +1 -1
- package/dist/applicants/applicant-expenditure.type.d.ts +1 -0
- package/dist/applicants/applicant-expenditure.type.d.ts.map +1 -1
- package/dist/applicants/applicant-income-settings.model.d.ts +1 -0
- package/dist/applicants/applicant-income-settings.model.d.ts.map +1 -1
- package/dist/applicants/applicant-income-settings.type.d.ts +1 -0
- package/dist/applicants/applicant-income-settings.type.d.ts.map +1 -1
- package/dist/applicants/applicant-income-summary.model.d.ts +1 -0
- package/dist/applicants/applicant-income-summary.model.d.ts.map +1 -1
- package/dist/applicants/applicant-income-summary.type.d.ts +1 -0
- package/dist/applicants/applicant-income-summary.type.d.ts.map +1 -1
- package/dist/applicants/applicant-large-exposure.model.d.ts +1 -0
- package/dist/applicants/applicant-large-exposure.model.d.ts.map +1 -1
- package/dist/applicants/applicant-large-exposure.type.d.ts +1 -0
- package/dist/applicants/applicant-large-exposure.type.d.ts.map +1 -1
- package/dist/applicants/applicant-pension-income.model.d.ts +1 -0
- package/dist/applicants/applicant-pension-income.model.d.ts.map +1 -1
- package/dist/applicants/applicant-pension-income.type.d.ts +1 -0
- package/dist/applicants/applicant-pension-income.type.d.ts.map +1 -1
- package/dist/applicants/applicant-property-income.model.d.ts +1 -0
- package/dist/applicants/applicant-property-income.model.d.ts.map +1 -1
- package/dist/applicants/applicant-property-income.type.d.ts +1 -0
- package/dist/applicants/applicant-property-income.type.d.ts.map +1 -1
- package/dist/applicants/applicant-risk-narrative.model.d.ts +1 -0
- package/dist/applicants/applicant-risk-narrative.model.d.ts.map +1 -1
- package/dist/applicants/applicant-self-employed-income.model.d.ts +1 -0
- package/dist/applicants/applicant-self-employed-income.model.d.ts.map +1 -1
- package/dist/applicants/applicant-self-employed-income.type.d.ts +1 -0
- package/dist/applicants/applicant-self-employed-income.type.d.ts.map +1 -1
- package/dist/applicants/applicant-self-employment.model.d.ts +1 -0
- package/dist/applicants/applicant-self-employment.model.d.ts.map +1 -1
- package/dist/applicants/applicant-sole-trader-income.model.d.ts +1 -0
- package/dist/applicants/applicant-sole-trader-income.model.d.ts.map +1 -1
- package/dist/applicants/applicant-sole-trader-income.type.d.ts +1 -0
- package/dist/applicants/applicant-sole-trader-income.type.d.ts.map +1 -1
- package/dist/applicants/applicant-uk-tax-credits.model.d.ts +1 -0
- package/dist/applicants/applicant-uk-tax-credits.model.d.ts.map +1 -1
- package/dist/applicants/applicant-uk-tax-credits.type.d.ts +1 -0
- package/dist/applicants/applicant-uk-tax-credits.type.d.ts.map +1 -1
- package/dist/applicants/applicant-welcome-call.model.d.ts +1 -0
- package/dist/applicants/applicant-welcome-call.model.d.ts.map +1 -1
- package/dist/applicants/applicant-welcome-call.type.d.ts +1 -0
- package/dist/applicants/applicant-welcome-call.type.d.ts.map +1 -1
- package/dist/applicants/applicant.model.d.ts +1 -54
- package/dist/applicants/applicant.model.d.ts.map +1 -1
- package/dist/applicants/applicant.model.js +33 -33
- package/dist/applicants/applicant.type.d.ts +1 -1
- package/dist/applicants/applicant.type.d.ts.map +1 -1
- package/dist/applications/application-audit.model.d.ts +1 -0
- package/dist/applications/application-audit.model.d.ts.map +1 -1
- package/dist/applications/application-checklist-Item.model.d.ts +1 -0
- package/dist/applications/application-checklist-Item.model.d.ts.map +1 -1
- package/dist/applications/application-company-model.d.ts +1 -0
- package/dist/applications/application-company-model.d.ts.map +1 -1
- package/dist/applications/application-credit-profile.model.d.ts +1 -0
- package/dist/applications/application-credit-profile.model.d.ts.map +1 -1
- package/dist/applications/application-direct-debit.model.d.ts +4 -3
- package/dist/applications/application-direct-debit.model.d.ts.map +1 -1
- package/dist/applications/application-direct-debit.model.js +20 -10
- package/dist/applications/application-direct-debit.type.d.ts +1 -0
- package/dist/applications/application-direct-debit.type.d.ts.map +1 -1
- package/dist/applications/application-euc.model.d.ts +1 -0
- package/dist/applications/application-euc.model.d.ts.map +1 -1
- package/dist/applications/application-euc.type.d.ts +1 -0
- package/dist/applications/application-euc.type.d.ts.map +1 -1
- package/dist/applications/application-fieldconfig.model.d.ts +1 -0
- package/dist/applications/application-fieldconfig.model.d.ts.map +1 -1
- package/dist/applications/application-illustration-model.d.ts +1 -0
- package/dist/applications/application-illustration-model.d.ts.map +1 -1
- package/dist/applications/application-legal.model.d.ts +1 -0
- package/dist/applications/application-legal.model.d.ts.map +1 -1
- package/dist/applications/application-mortgage.model.d.ts +1 -0
- package/dist/applications/application-mortgage.model.d.ts.map +1 -1
- package/dist/applications/application-mortgage.type.d.ts +1 -0
- package/dist/applications/application-mortgage.type.d.ts.map +1 -1
- package/dist/applications/application-note.model.d.ts +1 -0
- package/dist/applications/application-note.model.d.ts.map +1 -1
- package/dist/applications/application-note.type.d.ts +1 -0
- package/dist/applications/application-note.type.d.ts.map +1 -1
- package/dist/applications/application-offer.model.d.ts +1 -0
- package/dist/applications/application-offer.model.d.ts.map +1 -1
- package/dist/applications/application-offer.type.d.ts +1 -0
- package/dist/applications/application-offer.type.d.ts.map +1 -1
- package/dist/applications/application-onboarding.model.d.ts +1 -0
- package/dist/applications/application-onboarding.model.d.ts.map +1 -1
- package/dist/applications/application-product.model.d.ts +1 -0
- package/dist/applications/application-product.model.d.ts.map +1 -1
- package/dist/applications/application-product.type.d.ts +1 -0
- package/dist/applications/application-product.type.d.ts.map +1 -1
- package/dist/applications/application-productfeatures.model.d.ts +1 -0
- package/dist/applications/application-productfeatures.model.d.ts.map +1 -1
- package/dist/applications/application-productfeatures.type.d.ts +1 -0
- package/dist/applications/application-productfeatures.type.d.ts.map +1 -1
- package/dist/applications/application-rationale.model.d.ts +1 -0
- package/dist/applications/application-rationale.model.d.ts.map +1 -1
- package/dist/applications/application-rationale.type.d.ts +1 -0
- package/dist/applications/application-rationale.type.d.ts.map +1 -1
- package/dist/applications/application-risk-narrative.model.d.ts +1 -0
- package/dist/applications/application-risk-narrative.model.d.ts.map +1 -1
- package/dist/applications/application-valuation-report.model.d.ts +1 -0
- package/dist/applications/application-valuation-report.model.d.ts.map +1 -1
- package/dist/applications/application-valuation-report.type.d.ts +1 -0
- package/dist/applications/application-valuation-report.type.d.ts.map +1 -1
- package/dist/applications/application-valuation.model.d.ts +1 -0
- package/dist/applications/application-valuation.model.d.ts.map +1 -1
- package/dist/applications/application-valuation.type.d.ts +1 -0
- package/dist/applications/application-valuation.type.d.ts.map +1 -1
- package/dist/applications/application.model.d.ts +1 -0
- package/dist/applications/application.model.d.ts.map +1 -1
- package/dist/applications/applications-task.model.d.ts +1 -0
- package/dist/applications/applications-task.model.d.ts.map +1 -1
- package/dist/applications/applications-task.type.d.ts +1 -0
- package/dist/applications/applications-task.type.d.ts.map +1 -1
- package/dist/applications/broker.model.d.ts +1 -0
- package/dist/applications/broker.model.d.ts.map +1 -1
- package/dist/applications/broker.type.d.ts +1 -0
- package/dist/applications/broker.type.d.ts.map +1 -1
- package/dist/applications/solicitor.model.d.ts +1 -0
- package/dist/applications/solicitor.model.d.ts.map +1 -1
- package/dist/applications/solicitor.type.d.ts +1 -0
- package/dist/applications/solicitor.type.d.ts.map +1 -1
- package/dist/product-catalogues/product-catalogue.model.d.ts +1 -0
- package/dist/product-catalogues/product-catalogue.model.d.ts.map +1 -1
- package/dist/product-catalogues/product-definitions.model.d.ts +1 -0
- package/dist/product-catalogues/product-definitions.model.d.ts.map +1 -1
- package/dist/product-catalogues/product-definitions.type.d.ts +1 -0
- package/dist/product-catalogues/product-definitions.type.d.ts.map +1 -1
- package/dist/product-catalogues/product-variant.model.d.ts +1 -0
- package/dist/product-catalogues/product-variant.model.d.ts.map +1 -1
- package/dist/product-catalogues/product-variant.type.d.ts +1 -0
- package/dist/product-catalogues/product-variant.type.d.ts.map +1 -1
- package/dist/properties/property.model.d.ts +1 -0
- package/dist/properties/property.model.d.ts.map +1 -1
- package/dist/properties/security.model.d.ts +1 -0
- package/dist/properties/security.model.d.ts.map +1 -1
- package/dist/shared/alert.model.d.ts +1 -0
- package/dist/shared/alert.model.d.ts.map +1 -1
- package/dist/shared/api-log.model.d.ts +1 -0
- package/dist/shared/api-log.model.d.ts.map +1 -1
- package/dist/shared/api-performance.model.d.ts +1 -0
- package/dist/shared/api-performance.model.d.ts.map +1 -1
- package/dist/shared/api-performance.type.d.ts +1 -0
- package/dist/shared/api-performance.type.d.ts.map +1 -1
- package/dist/shared/apprivo-sync-journey.model.d.ts +1 -0
- package/dist/shared/apprivo-sync-journey.model.d.ts.map +1 -1
- package/dist/shared/checklist.model.d.ts +1 -0
- package/dist/shared/checklist.model.d.ts.map +1 -1
- package/dist/shared/encryption/encrypted-field-map.d.ts +146 -0
- package/dist/shared/encryption/encrypted-field-map.d.ts.map +1 -0
- package/dist/shared/encryption/encrypted-field-map.js +130 -0
- package/dist/shared/encryption/encryption-filter.guard.d.ts +69 -0
- package/dist/shared/encryption/encryption-filter.guard.d.ts.map +1 -0
- package/dist/shared/encryption/encryption-filter.guard.js +103 -0
- package/dist/shared/encryption/encryption.config.d.ts +55 -0
- package/dist/shared/encryption/encryption.config.d.ts.map +1 -0
- package/dist/shared/encryption/encryption.config.js +76 -0
- package/dist/shared/encryption/encryption.init.d.ts +66 -0
- package/dist/shared/encryption/encryption.init.d.ts.map +1 -0
- package/dist/shared/encryption/encryption.init.js +86 -0
- package/dist/shared/encryption/encryption.plugin.d.ts +225 -0
- package/dist/shared/encryption/encryption.plugin.d.ts.map +1 -0
- package/dist/shared/encryption/encryption.plugin.js +372 -0
- package/dist/shared/encryption/encryption.service.d.ts +110 -0
- package/dist/shared/encryption/encryption.service.d.ts.map +1 -0
- package/dist/shared/encryption/encryption.service.js +151 -0
- package/dist/shared/encryption/index.d.ts +8 -0
- package/dist/shared/encryption/index.d.ts.map +1 -0
- package/dist/shared/encryption/index.js +6 -0
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +2 -0
- package/dist/shared/job-run.model.d.ts +1 -0
- package/dist/shared/job-run.model.d.ts.map +1 -1
- package/dist/shared/job-setting.model.d.ts +1 -0
- package/dist/shared/job-setting.model.d.ts.map +1 -1
- package/dist/shared/lookup-group.model.d.ts +1 -0
- package/dist/shared/lookup-group.model.d.ts.map +1 -1
- package/dist/shared/lookup.model.d.ts +7 -0
- package/dist/shared/lookup.model.d.ts.map +1 -1
- package/dist/shared/lookup.model.js +5 -0
- package/dist/shared/schema-doc.model.d.ts +1 -0
- package/dist/shared/schema-doc.model.d.ts.map +1 -1
- package/dist/shared/system-parameter.model.d.ts +1 -0
- package/dist/shared/system-parameter.model.d.ts.map +1 -1
- package/dist/shared/task-document.model.d.ts +1 -0
- package/dist/shared/task-document.model.d.ts.map +1 -1
- package/dist/shared/task.model.d.ts +1 -0
- package/dist/shared/task.model.d.ts.map +1 -1
- package/dist/shared/webhook-event.model.d.ts +1 -0
- package/dist/shared/webhook-event.model.d.ts.map +1 -1
- package/dist/shared/workflow-trigger.model.d.ts +1 -0
- package/dist/shared/workflow-trigger.model.d.ts.map +1 -1
- package/dist/shared/workflow-trigger.type.d.ts +1 -0
- package/dist/shared/workflow-trigger.type.d.ts.map +1 -1
- package/dist/shared/workflow.middleware.d.ts +1 -0
- package/dist/shared/workflow.middleware.d.ts.map +1 -1
- package/dist/shared/workflow.plugin.d.ts +1 -0
- package/dist/shared/workflow.plugin.d.ts.map +1 -1
- package/dist/types/base.types.d.ts +1 -0
- package/dist/types/base.types.d.ts.map +1 -1
- package/dist/underwriter/underwriter.model.d.ts +1 -0
- package/dist/underwriter/underwriter.model.d.ts.map +1 -1
- package/dist/users/auth-log.model.d.ts +1 -0
- package/dist/users/auth-log.model.d.ts.map +1 -1
- package/dist/users/permission.model.d.ts +1 -0
- package/dist/users/permission.model.d.ts.map +1 -1
- package/dist/users/role-group.model.d.ts +1 -0
- package/dist/users/role-group.model.d.ts.map +1 -1
- package/dist/users/role.model.d.ts +1 -0
- package/dist/users/role.model.d.ts.map +1 -1
- package/dist/users/tasks.model.d.ts +1 -0
- package/dist/users/tasks.model.d.ts.map +1 -1
- package/dist/users/user.model.d.ts +1 -0
- package/dist/users/user.model.d.ts.map +1 -1
- package/dist/users/user.type.d.ts +1 -0
- package/dist/users/user.type.d.ts.map +1 -1
- package/dist/value-objects/account-number.d.ts +1 -0
- package/dist/value-objects/account-number.d.ts.map +1 -1
- package/dist/value-objects/pound.d.ts +1 -0
- package/dist/value-objects/pound.d.ts.map +1 -1
- package/dist/value-objects/sort-code.d.ts +1 -0
- package/dist/value-objects/sort-code.d.ts.map +1 -1
- package/package.json +6 -3
- package/dist/applicants/applicant-direct-debit.model.d.ts +0 -86
- package/dist/applicants/applicant-direct-debit.model.d.ts.map +0 -1
- package/dist/applicants/applicant-direct-debit.model.js +0 -16
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { Schema } from 'mongoose';
|
|
2
|
+
import { EncryptedFieldMapBuilder } from './encrypted-field-map.js';
|
|
3
|
+
import { EncryptionBootstrap } from './encryption.init.js';
|
|
4
|
+
import { EncryptionFilterGuard } from './encryption-filter.guard.js';
|
|
5
|
+
const READ_HOOKS = ['findOne', 'findOneAndUpdate', 'findOneAndReplace', 'findOneAndDelete'];
|
|
6
|
+
const WRITE_QUERY_HOOKS = ['updateOne', 'updateMany', 'findOneAndUpdate', 'findOneAndReplace', 'replaceOne'];
|
|
7
|
+
const GUARDED_HOOKS = [
|
|
8
|
+
'find', 'findOne', 'countDocuments', 'updateOne', 'updateMany', 'findOneAndUpdate',
|
|
9
|
+
'findOneAndReplace', 'replaceOne', 'deleteOne', 'deleteMany', 'findOneAndDelete'
|
|
10
|
+
];
|
|
11
|
+
const UPDATE_CONTAINERS = ['$set', '$setOnInsert'];
|
|
12
|
+
/**
|
|
13
|
+
* Global Mongoose plugin routing `ghEncrypt: true` fields through the
|
|
14
|
+
* {@link EncryptionService} — plumbing only, no crypto in hooks.
|
|
15
|
+
*
|
|
16
|
+
* Write hooks (`save`, `insertMany`, update/replace queries) encrypt
|
|
17
|
+
* annotated values to BSON Binary subtype 6; read hooks (`find`, `findOne`,
|
|
18
|
+
* `findOneAnd*`, `distinct`) decrypt hydrated docs and `.lean()` results.
|
|
19
|
+
* Filters and sorts on encrypted paths fail loud via
|
|
20
|
+
* {@link EncryptionFilterGuard}. When {@link EncryptionBootstrap} is not
|
|
21
|
+
* initialized, every hook is a transparent pass-through so consumers that
|
|
22
|
+
* have not opted in keep working on plaintext.
|
|
23
|
+
*
|
|
24
|
+
* Annotated paths are re-typed to `Mixed` (original type stashed in
|
|
25
|
+
* `ghOriginalType`) so ciphertext survives Mongoose casting. Aggregation is
|
|
26
|
+
* not interceptable and is handled per call site (Enc-5).
|
|
27
|
+
*/
|
|
28
|
+
export class EncryptionPlugin {
|
|
29
|
+
constructor() { }
|
|
30
|
+
/**
|
|
31
|
+
* Applies the plugin to a schema: validates and collects annotated
|
|
32
|
+
* paths, re-types them to `Mixed`, and registers guard/write/read hooks.
|
|
33
|
+
* No-op for schemas without annotations; safe to call more than once
|
|
34
|
+
* (encrypt/decrypt transforms are idempotent).
|
|
35
|
+
*
|
|
36
|
+
* @param schema - The Mongoose schema to instrument.
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* const applicantSchema = new mongoose.Schema({
|
|
40
|
+
* firstName: { type: String, ghEncrypt: true }
|
|
41
|
+
* });
|
|
42
|
+
* EncryptionPlugin.apply(applicantSchema);
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
static apply(schema) {
|
|
46
|
+
const entries = EncryptedFieldMapBuilder.buildForSchema(schema);
|
|
47
|
+
if (entries.length === 0) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
EncryptionPlugin.retypeToMixed(schema, entry);
|
|
52
|
+
}
|
|
53
|
+
const paths = entries.map((entry) => entry.path);
|
|
54
|
+
EncryptionPlugin.registerGuardHooks(schema, paths);
|
|
55
|
+
EncryptionPlugin.registerWriteHooks(schema, paths);
|
|
56
|
+
EncryptionPlugin.registerReadHooks(schema, paths);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Re-types one annotated path to `Mixed` so Binary ciphertext is not
|
|
60
|
+
* mangled by the original String/Number cast, stashing the original
|
|
61
|
+
* BSON type in `ghOriginalType` for the field-map/tooling.
|
|
62
|
+
*
|
|
63
|
+
* @param schema - The owning schema.
|
|
64
|
+
* @param entry - The annotated path being re-typed.
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* // internal use only — called by apply()
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
static retypeToMixed(schema, entry) {
|
|
71
|
+
const schemaType = schema.path(entry.path);
|
|
72
|
+
const options = {
|
|
73
|
+
...schemaType.options,
|
|
74
|
+
type: Schema.Types.Mixed,
|
|
75
|
+
ghOriginalType: entry.bsonType
|
|
76
|
+
};
|
|
77
|
+
const nested = entry.path
|
|
78
|
+
.split('.')
|
|
79
|
+
.reverse()
|
|
80
|
+
.reduce((acc, segment) => ({ [segment]: acc }), options);
|
|
81
|
+
schema.add(nested);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Registers fail-loud filter/sort guards on every query hook.
|
|
85
|
+
*
|
|
86
|
+
* @param schema - The owning schema.
|
|
87
|
+
* @param paths - Encrypted dot paths of the schema.
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* // internal use only — called by apply()
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
static registerGuardHooks(schema, paths) {
|
|
94
|
+
schema.pre([...GUARDED_HOOKS], function () {
|
|
95
|
+
if (!EncryptionBootstrap.isInitialized()) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const sort = this.getOptions().sort;
|
|
99
|
+
EncryptionFilterGuard.assertQuerySafe(this.getFilter(), sort, paths);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Registers encrypt-on-write hooks: `save`, `insertMany` and the
|
|
104
|
+
* update/replace query family (`$set`, `$setOnInsert`, dot paths,
|
|
105
|
+
* parent-object values and full replacement docs).
|
|
106
|
+
*
|
|
107
|
+
* @param schema - The owning schema.
|
|
108
|
+
* @param paths - Encrypted dot paths of the schema.
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* // internal use only — called by apply()
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
static registerWriteHooks(schema, paths) {
|
|
115
|
+
schema.pre('save', async function () {
|
|
116
|
+
await EncryptionPlugin.transformDocument(this, paths, EncryptionPlugin.encryptTransform());
|
|
117
|
+
});
|
|
118
|
+
schema.pre('insertMany', async function (_model, docs) {
|
|
119
|
+
for (const doc of docs ?? []) {
|
|
120
|
+
await EncryptionPlugin.transformTarget(doc, paths, EncryptionPlugin.encryptTransform());
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
schema.pre([...WRITE_QUERY_HOOKS], async function () {
|
|
124
|
+
const update = this.getUpdate();
|
|
125
|
+
if (update) {
|
|
126
|
+
await EncryptionPlugin.encryptUpdate(update, paths);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Registers decrypt-on-read hooks: `find`, the `findOne*` family,
|
|
132
|
+
* `distinct`, plus post-`save`/`insertMany` restores so callers keep
|
|
133
|
+
* seeing plaintext on the documents they just persisted.
|
|
134
|
+
*
|
|
135
|
+
* @param schema - The owning schema.
|
|
136
|
+
* @param paths - Encrypted dot paths of the schema.
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* // internal use only — called by apply()
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
static registerReadHooks(schema, paths) {
|
|
143
|
+
const decrypt = (target) => EncryptionPlugin.transformTarget(target, paths, EncryptionPlugin.decryptTransform());
|
|
144
|
+
schema.post('find', async function (docs) {
|
|
145
|
+
for (const doc of docs ?? []) {
|
|
146
|
+
await decrypt(doc);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
schema.post([...READ_HOOKS], async function (doc) {
|
|
150
|
+
await decrypt(doc);
|
|
151
|
+
});
|
|
152
|
+
schema.post('save', async function () {
|
|
153
|
+
await decrypt(this);
|
|
154
|
+
});
|
|
155
|
+
schema.post('insertMany', async function (docs) {
|
|
156
|
+
for (const doc of docs ?? []) {
|
|
157
|
+
await decrypt(doc);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
schema.post('distinct', async function (values) {
|
|
161
|
+
const service = EncryptionPlugin.activeService();
|
|
162
|
+
if (!service || !Array.isArray(values)) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
166
|
+
if (EncryptionPlugin.isCiphertext(values[index])) {
|
|
167
|
+
values[index] = await service.decrypt(values[index]);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Encrypts annotated values inside an update object: exact dot-path
|
|
174
|
+
* keys, parent-object values containing encrypted leaves, and full
|
|
175
|
+
* replacement documents (updates without atomic operators).
|
|
176
|
+
*
|
|
177
|
+
* @param update - The mutable update object from `Query.getUpdate()`.
|
|
178
|
+
* @param paths - Encrypted dot paths of the schema.
|
|
179
|
+
* @example
|
|
180
|
+
* ```ts
|
|
181
|
+
* // internal use only — called by the update-query pre hook
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
static async encryptUpdate(update, paths) {
|
|
185
|
+
const transform = EncryptionPlugin.encryptTransform();
|
|
186
|
+
const hasOperators = Object.keys(update).some((key) => key.startsWith('$'));
|
|
187
|
+
if (!hasOperators) {
|
|
188
|
+
await EncryptionPlugin.transformTarget(update, paths, transform);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
for (const container of UPDATE_CONTAINERS) {
|
|
192
|
+
const target = update[container];
|
|
193
|
+
if (!target) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
for (const [key, value] of Object.entries(target)) {
|
|
197
|
+
for (const path of paths) {
|
|
198
|
+
if (key === path) {
|
|
199
|
+
target[key] = await transform(value);
|
|
200
|
+
}
|
|
201
|
+
else if (path.startsWith(`${key}.`)) {
|
|
202
|
+
await EncryptionPlugin.transformPlain(value, path.slice(key.length + 1).split('.'), transform);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Applies a value transform to every encrypted path of one target,
|
|
210
|
+
* dispatching between hydrated documents (getter-free `get`/`set`) and
|
|
211
|
+
* plain objects (`.lean()` results, insert payloads).
|
|
212
|
+
*
|
|
213
|
+
* @param target - Hydrated document or plain object; non-objects no-op.
|
|
214
|
+
* @param paths - Encrypted dot paths of the schema.
|
|
215
|
+
* @param transform - Value transform (encrypt or decrypt).
|
|
216
|
+
* @example
|
|
217
|
+
* ```ts
|
|
218
|
+
* // internal use only
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
static async transformTarget(target, paths, transform) {
|
|
222
|
+
if (!target || typeof target !== 'object') {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const doc = target;
|
|
226
|
+
const isDocument = typeof doc.get === 'function' && typeof doc.set === 'function';
|
|
227
|
+
for (const path of paths) {
|
|
228
|
+
if (isDocument) {
|
|
229
|
+
await EncryptionPlugin.transformDocPath(doc, path, transform);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
await EncryptionPlugin.transformPlain(target, path.split('.'), transform);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Applies a transform to every encrypted path of one hydrated document.
|
|
238
|
+
*
|
|
239
|
+
* @param doc - The hydrated Mongoose document.
|
|
240
|
+
* @param paths - Encrypted dot paths of the schema.
|
|
241
|
+
* @param transform - Value transform (encrypt or decrypt).
|
|
242
|
+
* @example
|
|
243
|
+
* ```ts
|
|
244
|
+
* // internal use only
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
247
|
+
static async transformDocument(doc, paths, transform) {
|
|
248
|
+
for (const path of paths) {
|
|
249
|
+
await EncryptionPlugin.transformDocPath(doc, path, transform);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Transforms one dot path on a hydrated document, reading with getters
|
|
254
|
+
* disabled so value-object formatters never leak into ciphertext.
|
|
255
|
+
*
|
|
256
|
+
* @param doc - The hydrated Mongoose document.
|
|
257
|
+
* @param path - Encrypted dot path.
|
|
258
|
+
* @param transform - Value transform (encrypt or decrypt).
|
|
259
|
+
* @example
|
|
260
|
+
* ```ts
|
|
261
|
+
* // internal use only
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
264
|
+
static async transformDocPath(doc, path, transform) {
|
|
265
|
+
const value = doc.get(path, null, { getters: false });
|
|
266
|
+
if (value === null || value === undefined) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const next = await transform(value);
|
|
270
|
+
if (next !== value) {
|
|
271
|
+
doc.set(path, next);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Transforms one segmented path on a plain object, descending nested
|
|
276
|
+
* objects and fanning out over arrays.
|
|
277
|
+
*
|
|
278
|
+
* @param target - Plain object (or array element) being walked.
|
|
279
|
+
* @param segments - Remaining path segments.
|
|
280
|
+
* @param transform - Value transform (encrypt or decrypt).
|
|
281
|
+
* @example
|
|
282
|
+
* ```ts
|
|
283
|
+
* // internal use only
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
static async transformPlain(target, segments, transform) {
|
|
287
|
+
if (target === null || target === undefined || typeof target !== 'object') {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (Array.isArray(target)) {
|
|
291
|
+
for (const element of target) {
|
|
292
|
+
await EncryptionPlugin.transformPlain(element, segments, transform);
|
|
293
|
+
}
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const [head, ...rest] = segments;
|
|
297
|
+
if (rest.length === 0) {
|
|
298
|
+
const value = target[head];
|
|
299
|
+
if (value !== null && value !== undefined) {
|
|
300
|
+
target[head] = await transform(value);
|
|
301
|
+
}
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
await EncryptionPlugin.transformPlain(target[head], rest, transform);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Builds the encrypt transform: plaintext → Binary; ciphertext and the
|
|
308
|
+
* uninitialized-bootstrap case pass through untouched.
|
|
309
|
+
*
|
|
310
|
+
* @returns Async value transform used by all write hooks.
|
|
311
|
+
* @example
|
|
312
|
+
* ```ts
|
|
313
|
+
* // internal use only
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
static encryptTransform() {
|
|
317
|
+
return async (value) => {
|
|
318
|
+
const service = EncryptionPlugin.activeService();
|
|
319
|
+
if (!service || EncryptionPlugin.isCiphertext(value)) {
|
|
320
|
+
return value;
|
|
321
|
+
}
|
|
322
|
+
return service.encrypt(value);
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Builds the decrypt transform: Binary → plaintext; non-ciphertext and
|
|
327
|
+
* the uninitialized-bootstrap case pass through untouched.
|
|
328
|
+
*
|
|
329
|
+
* @returns Async value transform used by all read hooks.
|
|
330
|
+
* @example
|
|
331
|
+
* ```ts
|
|
332
|
+
* // internal use only
|
|
333
|
+
* ```
|
|
334
|
+
*/
|
|
335
|
+
static decryptTransform() {
|
|
336
|
+
return async (value) => {
|
|
337
|
+
const service = EncryptionPlugin.activeService();
|
|
338
|
+
if (!service || !EncryptionPlugin.isCiphertext(value)) {
|
|
339
|
+
return value;
|
|
340
|
+
}
|
|
341
|
+
return service.decrypt(value);
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Returns the bootstrapped service, or `null` when encryption is not
|
|
346
|
+
* initialized (pass-through mode for non-opted-in consumers).
|
|
347
|
+
*
|
|
348
|
+
* @returns The active {@link EncryptionService} or `null`.
|
|
349
|
+
* @example
|
|
350
|
+
* ```ts
|
|
351
|
+
* // internal use only
|
|
352
|
+
* ```
|
|
353
|
+
*/
|
|
354
|
+
static activeService() {
|
|
355
|
+
return EncryptionBootstrap.isInitialized() ? EncryptionBootstrap.getService() : null;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Duck-type check for BSON Binary subtype 6 (encrypted) values.
|
|
359
|
+
*
|
|
360
|
+
* @param value - Candidate value.
|
|
361
|
+
* @returns `true` when the value is ciphertext.
|
|
362
|
+
* @example
|
|
363
|
+
* ```ts
|
|
364
|
+
* // internal use only
|
|
365
|
+
* ```
|
|
366
|
+
*/
|
|
367
|
+
static isCiphertext(value) {
|
|
368
|
+
const candidate = value;
|
|
369
|
+
return Boolean(candidate && typeof candidate === 'object' &&
|
|
370
|
+
candidate._bsontype === 'Binary' && candidate.sub_type === 6);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { MongoClient, Binary } from 'mongodb';
|
|
2
|
+
import { EncryptionConfig } from './encryption.config.js';
|
|
3
|
+
/**
|
|
4
|
+
* Explicit Client-Side Field Level Encryption service.
|
|
5
|
+
*
|
|
6
|
+
* Wraps the driver's {@link ClientEncryption} with the Gatehouse conventions:
|
|
7
|
+
* a single shared DEK (alt name `gatehouse-dek`) stored in the
|
|
8
|
+
* `encryption.__keyVault` collection and wrapped by the `local` master key
|
|
9
|
+
* from {@link EncryptionConfig}. Only RANDOM
|
|
10
|
+
* (`AEAD_AES_256_CBC_HMAC_SHA_512-Random`) mode is exposed — encrypted fields
|
|
11
|
+
* are never queryable, by design. There is deliberately no deterministic API.
|
|
12
|
+
*
|
|
13
|
+
* Works against MongoDB Community — explicit encryption needs no mongocryptd
|
|
14
|
+
* or crypt_shared sidecar.
|
|
15
|
+
*/
|
|
16
|
+
export declare class EncryptionService {
|
|
17
|
+
private readonly config;
|
|
18
|
+
private static readonly RANDOM_ALGORITHM;
|
|
19
|
+
private clientEncryption;
|
|
20
|
+
private dataKeyId;
|
|
21
|
+
constructor(config: EncryptionConfig);
|
|
22
|
+
/**
|
|
23
|
+
* Initializes the service against a connected client: ensures the key
|
|
24
|
+
* vault collection and its unique partial index on `keyAltNames`, then
|
|
25
|
+
* resolves the shared DEK by alt name — creating it on first ever run.
|
|
26
|
+
* Idempotent and safe to call from multiple services against the same DB.
|
|
27
|
+
*
|
|
28
|
+
* @param client - A connected {@link MongoClient} for the target cluster.
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const service = new EncryptionService(EncryptionConfig.load(process.env));
|
|
32
|
+
* await service.init(mongoClient);
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
init(client: MongoClient): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Encrypts a value with the shared DEK in RANDOM mode. The same plaintext
|
|
38
|
+
* yields different ciphertext on every call, so encrypted fields must
|
|
39
|
+
* never appear in query filters, sorts, or indexes.
|
|
40
|
+
*
|
|
41
|
+
* @param value - Any BSON-serialisable value (string, number, object, array).
|
|
42
|
+
* @returns Ciphertext as BSON {@link Binary} subtype 6.
|
|
43
|
+
* @throws Error when called before {@link init}.
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const ciphertext = await service.encrypt('AB123456C');
|
|
47
|
+
* await collection.updateOne({ _id }, { $set: { niNumber: ciphertext } });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
encrypt(value: unknown): Promise<Binary>;
|
|
51
|
+
/**
|
|
52
|
+
* Decrypts ciphertext previously produced by {@link encrypt}, restoring
|
|
53
|
+
* the original BSON value (string, number, object or array).
|
|
54
|
+
*
|
|
55
|
+
* @param value - BSON {@link Binary} subtype 6 ciphertext.
|
|
56
|
+
* @returns The original plaintext value.
|
|
57
|
+
* @throws Error when called before {@link init}.
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* const plaintext = await service.decrypt(doc.niNumber);
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
decrypt<T = unknown>(value: Binary): Promise<T>;
|
|
64
|
+
/**
|
|
65
|
+
* Releases the underlying libmongocrypt resources. Call on shutdown.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* await service.close();
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
close(): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Creates the key vault's unique partial index on `keyAltNames`, matching
|
|
75
|
+
* the official CSFLE key vault requirements. Idempotent.
|
|
76
|
+
*
|
|
77
|
+
* @param client - Connected client used to reach the key vault namespace.
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* // internal use only — called by init()
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
private ensureKeyVaultIndex;
|
|
84
|
+
/**
|
|
85
|
+
* Resolves the shared DEK by its alt name, creating it if this is the
|
|
86
|
+
* very first bootstrap of the environment. The unique index guarantees a
|
|
87
|
+
* concurrent double-create loses cleanly, so this is race-safe.
|
|
88
|
+
*
|
|
89
|
+
* @returns The DEK id used for all encrypt operations.
|
|
90
|
+
* @example
|
|
91
|
+
* ```ts
|
|
92
|
+
* // internal use only — called by init()
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
private resolveOrCreateDataKey;
|
|
96
|
+
/**
|
|
97
|
+
* Guards against use before {@link init} with a descriptive error that
|
|
98
|
+
* tells the caller exactly what to do.
|
|
99
|
+
*
|
|
100
|
+
* @param requireKey - Whether the DEK must also be resolved already.
|
|
101
|
+
* @returns The non-null encryption handle and DEK id.
|
|
102
|
+
* @throws Error when the service has not been initialized.
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* // internal use only
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
private requireInitialized;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=encryption.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encryption.service.d.ts","sourceRoot":"","sources":["../../../shared/encryption/encryption.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,WAAW,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D;;;;;;;;;;;;GAYG;AACH,qBAAa,iBAAiB;IAOP,OAAO,CAAC,QAAQ,CAAC,MAAM;IAN1C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAA0C;IAElF,OAAO,CAAC,gBAAgB,CAAiC;IAEzD,OAAO,CAAC,SAAS,CAAuB;gBAEJ,MAAM,EAAE,gBAAgB;IAE5D;;;;;;;;;;;;OAYG;IACU,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IASrD;;;;;;;;;;;;;OAaG;IACU,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAQrD;;;;;;;;;;;OAWG;IACU,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAK5D;;;;;;;OAOG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOnC;;;;;;;;;OASG;YACW,mBAAmB;IAQjC;;;;;;;;;;OAUG;YACW,sBAAsB;IAWpC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,kBAAkB;CAe7B"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { ClientEncryption } from 'mongodb';
|
|
2
|
+
/**
|
|
3
|
+
* Explicit Client-Side Field Level Encryption service.
|
|
4
|
+
*
|
|
5
|
+
* Wraps the driver's {@link ClientEncryption} with the Gatehouse conventions:
|
|
6
|
+
* a single shared DEK (alt name `gatehouse-dek`) stored in the
|
|
7
|
+
* `encryption.__keyVault` collection and wrapped by the `local` master key
|
|
8
|
+
* from {@link EncryptionConfig}. Only RANDOM
|
|
9
|
+
* (`AEAD_AES_256_CBC_HMAC_SHA_512-Random`) mode is exposed — encrypted fields
|
|
10
|
+
* are never queryable, by design. There is deliberately no deterministic API.
|
|
11
|
+
*
|
|
12
|
+
* Works against MongoDB Community — explicit encryption needs no mongocryptd
|
|
13
|
+
* or crypt_shared sidecar.
|
|
14
|
+
*/
|
|
15
|
+
export class EncryptionService {
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.config = config;
|
|
18
|
+
this.clientEncryption = null;
|
|
19
|
+
this.dataKeyId = null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Initializes the service against a connected client: ensures the key
|
|
23
|
+
* vault collection and its unique partial index on `keyAltNames`, then
|
|
24
|
+
* resolves the shared DEK by alt name — creating it on first ever run.
|
|
25
|
+
* Idempotent and safe to call from multiple services against the same DB.
|
|
26
|
+
*
|
|
27
|
+
* @param client - A connected {@link MongoClient} for the target cluster.
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const service = new EncryptionService(EncryptionConfig.load(process.env));
|
|
31
|
+
* await service.init(mongoClient);
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
async init(client) {
|
|
35
|
+
await this.ensureKeyVaultIndex(client);
|
|
36
|
+
this.clientEncryption = new ClientEncryption(client, {
|
|
37
|
+
keyVaultNamespace: this.config.keyVaultNamespace,
|
|
38
|
+
kmsProviders: { local: { key: this.config.masterKey } }
|
|
39
|
+
});
|
|
40
|
+
this.dataKeyId = await this.resolveOrCreateDataKey();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Encrypts a value with the shared DEK in RANDOM mode. The same plaintext
|
|
44
|
+
* yields different ciphertext on every call, so encrypted fields must
|
|
45
|
+
* never appear in query filters, sorts, or indexes.
|
|
46
|
+
*
|
|
47
|
+
* @param value - Any BSON-serialisable value (string, number, object, array).
|
|
48
|
+
* @returns Ciphertext as BSON {@link Binary} subtype 6.
|
|
49
|
+
* @throws Error when called before {@link init}.
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* const ciphertext = await service.encrypt('AB123456C');
|
|
53
|
+
* await collection.updateOne({ _id }, { $set: { niNumber: ciphertext } });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
async encrypt(value) {
|
|
57
|
+
const { clientEncryption, dataKeyId } = this.requireInitialized();
|
|
58
|
+
return clientEncryption.encrypt(value, {
|
|
59
|
+
keyId: dataKeyId,
|
|
60
|
+
algorithm: EncryptionService.RANDOM_ALGORITHM
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Decrypts ciphertext previously produced by {@link encrypt}, restoring
|
|
65
|
+
* the original BSON value (string, number, object or array).
|
|
66
|
+
*
|
|
67
|
+
* @param value - BSON {@link Binary} subtype 6 ciphertext.
|
|
68
|
+
* @returns The original plaintext value.
|
|
69
|
+
* @throws Error when called before {@link init}.
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* const plaintext = await service.decrypt(doc.niNumber);
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
async decrypt(value) {
|
|
76
|
+
const { clientEncryption } = this.requireInitialized();
|
|
77
|
+
return clientEncryption.decrypt(value);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Releases the underlying libmongocrypt resources. Call on shutdown.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* await service.close();
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
async close() {
|
|
88
|
+
if (this.clientEncryption) {
|
|
89
|
+
this.clientEncryption = null;
|
|
90
|
+
this.dataKeyId = null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Creates the key vault's unique partial index on `keyAltNames`, matching
|
|
95
|
+
* the official CSFLE key vault requirements. Idempotent.
|
|
96
|
+
*
|
|
97
|
+
* @param client - Connected client used to reach the key vault namespace.
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* // internal use only — called by init()
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
async ensureKeyVaultIndex(client) {
|
|
104
|
+
const [dbName, collectionName] = this.config.keyVaultNamespace.split('.');
|
|
105
|
+
await client.db(dbName).collection(collectionName).createIndex({ keyAltNames: 1 }, { unique: true, partialFilterExpression: { keyAltNames: { $exists: true } } });
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Resolves the shared DEK by its alt name, creating it if this is the
|
|
109
|
+
* very first bootstrap of the environment. The unique index guarantees a
|
|
110
|
+
* concurrent double-create loses cleanly, so this is race-safe.
|
|
111
|
+
*
|
|
112
|
+
* @returns The DEK id used for all encrypt operations.
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* // internal use only — called by init()
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
async resolveOrCreateDataKey() {
|
|
119
|
+
const { clientEncryption } = this.requireInitialized(false);
|
|
120
|
+
const existing = await clientEncryption.getKeyByAltName(this.config.keyAltName);
|
|
121
|
+
if (existing) {
|
|
122
|
+
return existing._id;
|
|
123
|
+
}
|
|
124
|
+
return clientEncryption.createDataKey('local', {
|
|
125
|
+
keyAltNames: [this.config.keyAltName]
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Guards against use before {@link init} with a descriptive error that
|
|
130
|
+
* tells the caller exactly what to do.
|
|
131
|
+
*
|
|
132
|
+
* @param requireKey - Whether the DEK must also be resolved already.
|
|
133
|
+
* @returns The non-null encryption handle and DEK id.
|
|
134
|
+
* @throws Error when the service has not been initialized.
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* // internal use only
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
requireInitialized(requireKey = true) {
|
|
141
|
+
if (!this.clientEncryption || (requireKey && !this.dataKeyId)) {
|
|
142
|
+
throw new Error('EncryptionService is not initialized. Call init(mongoClient) at bootstrap ' +
|
|
143
|
+
'before encrypting or decrypting.');
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
clientEncryption: this.clientEncryption,
|
|
147
|
+
dataKeyId: this.dataKeyId
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
EncryptionService.RANDOM_ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_512-Random';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { EncryptionConfig } from './encryption.config.js';
|
|
2
|
+
export { EncryptionService } from './encryption.service.js';
|
|
3
|
+
export { EncryptionBootstrap } from './encryption.init.js';
|
|
4
|
+
export { EncryptedFieldMapBuilder } from './encrypted-field-map.js';
|
|
5
|
+
export { EncryptionPlugin } from './encryption.plugin.js';
|
|
6
|
+
export { EncryptionFilterGuard } from './encryption-filter.guard.js';
|
|
7
|
+
export type { EncryptedFieldEntry, EncryptedFieldMap } from './encrypted-field-map.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../shared/encryption/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,YAAY,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { EncryptionConfig } from './encryption.config.js';
|
|
2
|
+
export { EncryptionService } from './encryption.service.js';
|
|
3
|
+
export { EncryptionBootstrap } from './encryption.init.js';
|
|
4
|
+
export { EncryptedFieldMapBuilder } from './encrypted-field-map.js';
|
|
5
|
+
export { EncryptionPlugin } from './encryption.plugin.js';
|
|
6
|
+
export { EncryptionFilterGuard } from './encryption-filter.guard.js';
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -16,4 +16,6 @@ export { default as IWebhookEvent } from './webhook-event.type';
|
|
|
16
16
|
export { default as ApiPerformanceLogModel } from './api-performance.model';
|
|
17
17
|
export { default as IApiPerformance } from './api-performance.type';
|
|
18
18
|
export { initializeWorkflowMiddleware, initializeWorkflowMiddlewareWithCheck, isWorkflowMiddlewareConfigured } from './workflow.init';
|
|
19
|
+
export { EncryptionConfig, EncryptionService, EncryptionBootstrap, EncryptedFieldMapBuilder, EncryptionPlugin, EncryptionFilterGuard } from './encryption/index.js';
|
|
20
|
+
export type { EncryptedFieldEntry, EncryptedFieldMap } from './encryption/index.js';
|
|
19
21
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../shared/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAC7D,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAChE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAC,MAAM,yBAAyB,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGpE,OAAO,EAAE,4BAA4B,EAAE,qCAAqC,EAAE,8BAA8B,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../shared/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAC7D,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAChE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAC,MAAM,yBAAyB,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGpE,OAAO,EAAE,4BAA4B,EAAE,qCAAqC,EAAE,8BAA8B,EAAE,MAAM,iBAAiB,CAAC;AAGtI,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACpK,YAAY,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/shared/index.js
CHANGED
|
@@ -14,3 +14,5 @@ export { default as ApiLogModel } from './api-log.model';
|
|
|
14
14
|
export { default as ApiPerformanceLogModel } from './api-performance.model';
|
|
15
15
|
// Workflow middleware initialization exports
|
|
16
16
|
export { initializeWorkflowMiddleware, initializeWorkflowMiddlewareWithCheck, isWorkflowMiddlewareConfigured } from './workflow.init';
|
|
17
|
+
// Field-level encryption exports
|
|
18
|
+
export { EncryptionConfig, EncryptionService, EncryptionBootstrap, EncryptedFieldMapBuilder, EncryptionPlugin, EncryptionFilterGuard } from './encryption/index.js';
|