@lazyapps/encryption 0.0.0-init.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.
@@ -0,0 +1,162 @@
1
+ import { encryptValue } from './fieldEncryption.js';
2
+ import { getLogger } from '@lazyapps/logger';
3
+
4
+ export const createStorageEncryptor = (
5
+ storageFactory,
6
+ readModelEncryption,
7
+ envelope,
8
+ ) => {
9
+ const log = getLogger('Encryption/Storage', 'INIT');
10
+
11
+ const encryptFields = (collection, target, subjectIdContext) => {
12
+ const collectionSchema = readModelEncryption[collection];
13
+ if (!collectionSchema) return Promise.resolve(target);
14
+ return Object.entries(collectionSchema).reduce(
15
+ (promise, [fieldName, fieldConfig]) =>
16
+ promise.then((t) => {
17
+ const value = t[fieldName];
18
+ if (value === undefined || value === null) return t;
19
+ const subjectId =
20
+ t[fieldConfig.subjectField] ||
21
+ (subjectIdContext && subjectIdContext[fieldConfig.subjectField]);
22
+ if (!subjectId) return t;
23
+ return envelope
24
+ .getDEK(subjectId, fieldConfig.context)
25
+ .then((dek) => ({
26
+ ...t,
27
+ [fieldName]: {
28
+ ...encryptValue(dek.key, value),
29
+ ctx: fieldConfig.context,
30
+ kid: subjectId,
31
+ kv: dek.version,
32
+ },
33
+ }));
34
+ }),
35
+ Promise.resolve({ ...target }),
36
+ );
37
+ };
38
+
39
+ const encryptUpdate = (collection, filter, update) => {
40
+ if (update.$set) {
41
+ return encryptFields(collection, update.$set, filter).then(
42
+ (encryptedSet) => ({ ...update, $set: encryptedSet }),
43
+ );
44
+ }
45
+ const hasOperators = Object.keys(update).some((k) => k.startsWith('$'));
46
+ if (!hasOperators) {
47
+ return encryptFields(collection, update, filter);
48
+ }
49
+ return Promise.resolve(update);
50
+ };
51
+
52
+ const encryptBulkOp = (collection, op) => {
53
+ if (op.insertOne)
54
+ return encryptFields(collection, op.insertOne.document).then((doc) => ({
55
+ insertOne: { document: doc },
56
+ }));
57
+ if (op.updateOne)
58
+ return encryptUpdate(
59
+ collection,
60
+ op.updateOne.filter,
61
+ op.updateOne.update,
62
+ ).then((update) => ({
63
+ updateOne: { ...op.updateOne, update },
64
+ }));
65
+ if (op.updateMany)
66
+ return encryptUpdate(
67
+ collection,
68
+ op.updateMany.filter,
69
+ op.updateMany.update,
70
+ ).then((update) => ({
71
+ updateMany: { ...op.updateMany, update },
72
+ }));
73
+ if (op.replaceOne)
74
+ return encryptFields(
75
+ collection,
76
+ op.replaceOne.replacement,
77
+ op.replaceOne.filter,
78
+ ).then((replacement) => ({
79
+ replaceOne: { ...op.replaceOne, replacement },
80
+ }));
81
+ return Promise.resolve(op);
82
+ };
83
+
84
+ const wrapPerRequest = (perRequest) => (correlationId) => {
85
+ const methods = perRequest(correlationId);
86
+ const encLog = getLogger('Encryption/Storage', correlationId);
87
+
88
+ return {
89
+ ...methods,
90
+
91
+ insertOne: (collection, doc) =>
92
+ encryptFields(collection, doc).then((encrypted) => {
93
+ encLog.debug(`Encrypting insertOne for ${collection}`);
94
+ return methods.insertOne(collection, encrypted);
95
+ }),
96
+
97
+ insertMany: (collection, docs) =>
98
+ Promise.all(docs.map((doc) => encryptFields(collection, doc))).then(
99
+ (encrypted) => {
100
+ encLog.debug(
101
+ `Encrypting insertMany (${docs.length} docs) ` +
102
+ `for ${collection}`,
103
+ );
104
+ return methods.insertMany(collection, encrypted);
105
+ },
106
+ ),
107
+
108
+ updateOne: (collection, filter, update, ...rest) =>
109
+ encryptUpdate(collection, filter, update).then((encrypted) => {
110
+ encLog.debug(`Encrypting updateOne for ${collection}`);
111
+ return methods.updateOne(collection, filter, encrypted, ...rest);
112
+ }),
113
+
114
+ updateMany: (collection, filter, update, ...rest) =>
115
+ encryptUpdate(collection, filter, update).then((encrypted) => {
116
+ encLog.debug(`Encrypting updateMany for ${collection}`);
117
+ return methods.updateMany(collection, filter, encrypted, ...rest);
118
+ }),
119
+
120
+ findOneAndUpdate: (collection, filter, update, ...rest) =>
121
+ encryptUpdate(collection, filter, update).then((encrypted) => {
122
+ encLog.debug(`Encrypting findOneAndUpdate for ${collection}`);
123
+ return methods.findOneAndUpdate(
124
+ collection,
125
+ filter,
126
+ encrypted,
127
+ ...rest,
128
+ );
129
+ }),
130
+
131
+ findOneAndReplace: (collection, filter, replacement, ...rest) =>
132
+ encryptFields(collection, replacement, filter).then((encrypted) => {
133
+ encLog.debug(`Encrypting findOneAndReplace for ${collection}`);
134
+ return methods.findOneAndReplace(
135
+ collection,
136
+ filter,
137
+ encrypted,
138
+ ...rest,
139
+ );
140
+ }),
141
+
142
+ bulkWrite: (collection, operations) =>
143
+ Promise.all(operations.map((op) => encryptBulkOp(collection, op))).then(
144
+ (encrypted) => {
145
+ encLog.debug(
146
+ `Encrypting bulkWrite (${operations.length} ops) ` +
147
+ `for ${collection}`,
148
+ );
149
+ return methods.bulkWrite(collection, encrypted);
150
+ },
151
+ ),
152
+ };
153
+ };
154
+
155
+ log.info('Storage encryption wrapper initialized');
156
+
157
+ return (...args) =>
158
+ storageFactory(...args).then((storage) => ({
159
+ ...storage,
160
+ perRequest: wrapPerRequest(storage.perRequest),
161
+ }));
162
+ };
@@ -0,0 +1,34 @@
1
+ const validationError = (message) => {
2
+ const err = new Error(message);
3
+ err.name = 'ValidationError';
4
+ return err;
5
+ };
6
+
7
+ export const subjectLifecycleAggregate = {
8
+ initial: () => ({}),
9
+
10
+ commands: {
11
+ FORGET_SUBJECT: (aggregate, payload) => {
12
+ if (!payload.subjectId) {
13
+ throw validationError('Missing subjectId in payload');
14
+ }
15
+ if (aggregate.forgotten) {
16
+ throw validationError(
17
+ `Subject ${payload.subjectId} has already been forgotten`,
18
+ );
19
+ }
20
+ return {
21
+ type: 'SUBJECT_FORGOTTEN',
22
+ payload,
23
+ };
24
+ },
25
+ },
26
+
27
+ projections: {
28
+ SUBJECT_FORGOTTEN: (aggregate, event) => ({
29
+ ...aggregate,
30
+ forgotten: true,
31
+ forgottenAt: event.timestamp,
32
+ }),
33
+ },
34
+ };