@lucaapp/service-utils 5.34.0 → 5.35.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/dist/lib/api/endpoint.js
CHANGED
|
@@ -110,6 +110,12 @@ const sendErrorResponse = (response, error, knownErrors, debug) => {
|
|
|
110
110
|
sendKnownErrorResponse(response, statusCode, error, debug);
|
|
111
111
|
return;
|
|
112
112
|
}
|
|
113
|
+
if (isTypedError(error)) {
|
|
114
|
+
// The service knew what went wrong and named it; the route just never said
|
|
115
|
+
// what to answer. Without this line that is a silent 500, and the only way
|
|
116
|
+
// anyone finds one is a 5xx alert days later.
|
|
117
|
+
response.log?.warn({ errorType: error.type, url: response.req?.originalUrl }, 'Typed error is not declared on this route, answering 500');
|
|
118
|
+
}
|
|
113
119
|
sendUnknownErrorResponse(response, error, debug);
|
|
114
120
|
return;
|
|
115
121
|
};
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import type { Model, ModelStatic, Sequelize } from 'sequelize';
|
|
2
2
|
import { DataTypes } from 'sequelize';
|
|
3
|
+
type AuditOperation = 'create' | 'update' | 'destroy';
|
|
3
4
|
export interface PartitionTable {
|
|
4
5
|
tableName: string;
|
|
5
6
|
timestamp: Date;
|
|
6
7
|
}
|
|
8
|
+
interface DiffEntry {
|
|
9
|
+
key: string;
|
|
10
|
+
values: {
|
|
11
|
+
old?: unknown;
|
|
12
|
+
new?: unknown;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
7
15
|
interface AddHistoryOptions {
|
|
8
16
|
exclude?: string[];
|
|
17
|
+
/**
|
|
18
|
+
* Attributes whose change is recorded, but whose values are not. The diff
|
|
19
|
+
* keeps the key with a placeholder, so the log shows THAT the attribute
|
|
20
|
+
* changed without storing large or sensitive payloads.
|
|
21
|
+
*/
|
|
22
|
+
redact?: string[];
|
|
9
23
|
}
|
|
10
24
|
interface AtomicAuditLogOptions {
|
|
11
25
|
attributeRevisionModelTableName?: string;
|
|
@@ -18,6 +32,8 @@ export interface PartitionResult {
|
|
|
18
32
|
archiveTableName: string;
|
|
19
33
|
timestamp: Date;
|
|
20
34
|
}
|
|
35
|
+
export declare const REDACTED_VALUE = "[redacted]";
|
|
36
|
+
export declare const computeDiff: <T extends Model>(instance: T, operation: AuditOperation, exclude: string[], redact: string[]) => DiffEntry[];
|
|
21
37
|
export declare class AtomicAuditLog {
|
|
22
38
|
private sequelize;
|
|
23
39
|
private tableName;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AtomicAuditLog = void 0;
|
|
3
|
+
exports.AtomicAuditLog = exports.computeDiff = exports.REDACTED_VALUE = void 0;
|
|
4
4
|
const sequelize_1 = require("sequelize");
|
|
5
5
|
const DEFAULT_EXCLUDE = [
|
|
6
6
|
'id',
|
|
@@ -11,25 +11,32 @@ const DEFAULT_EXCLUDE = [
|
|
|
11
11
|
'revision',
|
|
12
12
|
];
|
|
13
13
|
const isValidValue = (value) => value !== undefined && value !== null;
|
|
14
|
-
|
|
14
|
+
exports.REDACTED_VALUE = '[redacted]';
|
|
15
|
+
const computeCreateDiff = (currentValues, excludeSet, redactSet) => {
|
|
15
16
|
const diff = [];
|
|
16
17
|
for (const [key, value] of Object.entries(currentValues)) {
|
|
17
18
|
if (!excludeSet.has(key) && isValidValue(value)) {
|
|
18
|
-
diff.push({
|
|
19
|
+
diff.push({
|
|
20
|
+
key,
|
|
21
|
+
values: { new: redactSet.has(key) ? exports.REDACTED_VALUE : value },
|
|
22
|
+
});
|
|
19
23
|
}
|
|
20
24
|
}
|
|
21
25
|
return diff;
|
|
22
26
|
};
|
|
23
|
-
const computeDestroyDiff = (previousValues, excludeSet) => {
|
|
27
|
+
const computeDestroyDiff = (previousValues, excludeSet, redactSet) => {
|
|
24
28
|
const diff = [];
|
|
25
29
|
for (const [key, value] of Object.entries(previousValues)) {
|
|
26
30
|
if (!excludeSet.has(key) && isValidValue(value)) {
|
|
27
|
-
diff.push({
|
|
31
|
+
diff.push({
|
|
32
|
+
key,
|
|
33
|
+
values: { old: redactSet.has(key) ? exports.REDACTED_VALUE : value },
|
|
34
|
+
});
|
|
28
35
|
}
|
|
29
36
|
}
|
|
30
37
|
return diff;
|
|
31
38
|
};
|
|
32
|
-
const computeUpdateDiff = (instance, previousValues, currentValues, excludeSet) => {
|
|
39
|
+
const computeUpdateDiff = (instance, previousValues, currentValues, excludeSet, redactSet) => {
|
|
33
40
|
const diff = [];
|
|
34
41
|
const changedKeys = instance.changed();
|
|
35
42
|
if (!changedKeys || !Array.isArray(changedKeys)) {
|
|
@@ -39,24 +46,29 @@ const computeUpdateDiff = (instance, previousValues, currentValues, excludeSet)
|
|
|
39
46
|
if (!excludeSet.has(key)) {
|
|
40
47
|
diff.push({
|
|
41
48
|
key,
|
|
42
|
-
values:
|
|
49
|
+
values: redactSet.has(key)
|
|
50
|
+
? { old: exports.REDACTED_VALUE, new: exports.REDACTED_VALUE }
|
|
51
|
+
: { old: previousValues[key], new: currentValues[key] },
|
|
43
52
|
});
|
|
44
53
|
}
|
|
45
54
|
}
|
|
46
55
|
return diff;
|
|
47
56
|
};
|
|
48
|
-
|
|
57
|
+
// exported for tests
|
|
58
|
+
const computeDiff = (instance, operation, exclude, redact) => {
|
|
49
59
|
const previousValues = instance.previous();
|
|
50
60
|
const currentValues = instance.dataValues;
|
|
51
61
|
const excludeSet = new Set([...DEFAULT_EXCLUDE, ...exclude]);
|
|
62
|
+
const redactSet = new Set(redact);
|
|
52
63
|
if (operation === 'create') {
|
|
53
|
-
return computeCreateDiff(currentValues, excludeSet);
|
|
64
|
+
return computeCreateDiff(currentValues, excludeSet, redactSet);
|
|
54
65
|
}
|
|
55
66
|
if (operation === 'destroy') {
|
|
56
|
-
return computeDestroyDiff(previousValues, excludeSet);
|
|
67
|
+
return computeDestroyDiff(previousValues, excludeSet, redactSet);
|
|
57
68
|
}
|
|
58
|
-
return computeUpdateDiff(instance, previousValues, currentValues, excludeSet);
|
|
69
|
+
return computeUpdateDiff(instance, previousValues, currentValues, excludeSet, redactSet);
|
|
59
70
|
};
|
|
71
|
+
exports.computeDiff = computeDiff;
|
|
60
72
|
class AtomicAuditLog {
|
|
61
73
|
constructor(sequelize, options = {}) {
|
|
62
74
|
this.sequelize = sequelize;
|
|
@@ -86,12 +98,12 @@ class AtomicAuditLog {
|
|
|
86
98
|
return `${this.tableName}_View`;
|
|
87
99
|
}
|
|
88
100
|
addHistory(model, options = {}) {
|
|
89
|
-
const { exclude = [] } = options;
|
|
101
|
+
const { exclude = [], redact = [] } = options;
|
|
90
102
|
const modelName = model.name;
|
|
91
103
|
const primaryKeyAttribute = model.primaryKeyAttribute || 'uuid';
|
|
92
|
-
const createHook = this.createAuditHook(modelName, primaryKeyAttribute, exclude, 'create');
|
|
93
|
-
const updateHook = this.createAuditHook(modelName, primaryKeyAttribute, exclude, 'update');
|
|
94
|
-
const destroyHook = this.createAuditHook(modelName, primaryKeyAttribute, exclude, 'destroy');
|
|
104
|
+
const createHook = this.createAuditHook(modelName, primaryKeyAttribute, exclude, redact, 'create');
|
|
105
|
+
const updateHook = this.createAuditHook(modelName, primaryKeyAttribute, exclude, redact, 'update');
|
|
106
|
+
const destroyHook = this.createAuditHook(modelName, primaryKeyAttribute, exclude, redact, 'destroy');
|
|
95
107
|
model.addHook('afterCreate', createHook);
|
|
96
108
|
model.addHook('afterUpdate', updateHook);
|
|
97
109
|
model.addHook('afterDestroy', destroyHook);
|
|
@@ -144,10 +156,10 @@ class AtomicAuditLog {
|
|
|
144
156
|
transaction,
|
|
145
157
|
});
|
|
146
158
|
}
|
|
147
|
-
createAuditHook(modelName, primaryKeyAttribute, exclude, operation) {
|
|
159
|
+
createAuditHook(modelName, primaryKeyAttribute, exclude, redact, operation) {
|
|
148
160
|
return async (instance, options) => {
|
|
149
161
|
try {
|
|
150
|
-
const diff = computeDiff(instance, operation, exclude);
|
|
162
|
+
const diff = (0, exports.computeDiff)(instance, operation, exclude, redact);
|
|
151
163
|
if (diff.length === 0) {
|
|
152
164
|
return;
|
|
153
165
|
}
|