@contrast/protect 1.2.1 → 1.4.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.
Files changed (59) hide show
  1. package/lib/error-handlers/constants.js +15 -0
  2. package/lib/error-handlers/index.js +17 -0
  3. package/lib/error-handlers/install/express4.js +89 -0
  4. package/lib/error-handlers/install/fastify3.js +17 -4
  5. package/lib/error-handlers/install/koa2.js +16 -2
  6. package/lib/esm-loader.mjs +15 -0
  7. package/lib/get-source-context.js +33 -0
  8. package/lib/hardening/constants.js +20 -0
  9. package/lib/hardening/handlers.js +65 -0
  10. package/lib/hardening/index.js +29 -0
  11. package/lib/hardening/install/node-serialize0.js +59 -0
  12. package/lib/index.d.ts +127 -19
  13. package/lib/index.js +19 -0
  14. package/lib/input-analysis/constants.js +20 -0
  15. package/lib/input-analysis/handlers.js +201 -16
  16. package/lib/input-analysis/index.js +40 -3
  17. package/lib/input-analysis/install/body-parser1.js +122 -0
  18. package/lib/input-analysis/install/cookie-parser1.js +80 -0
  19. package/lib/input-analysis/install/express4.js +103 -0
  20. package/lib/input-analysis/install/fastify3.js +51 -24
  21. package/lib/input-analysis/install/formidable1.js +72 -0
  22. package/lib/input-analysis/install/http.js +30 -4
  23. package/lib/input-analysis/install/koa-body5.js +63 -0
  24. package/lib/input-analysis/install/koa-bodyparser4.js +64 -0
  25. package/lib/input-analysis/install/koa2.js +38 -48
  26. package/lib/input-analysis/install/multer1.js +88 -0
  27. package/lib/input-analysis/install/qs6.js +57 -0
  28. package/lib/input-analysis/install/universal-cookie4.js +52 -0
  29. package/lib/input-analysis/ip-analysis.js +76 -0
  30. package/lib/input-analysis/virtual-patches.js +109 -0
  31. package/lib/input-tracing/constants.js +15 -0
  32. package/lib/input-tracing/handlers/index.js +225 -66
  33. package/lib/input-tracing/index.js +25 -2
  34. package/lib/input-tracing/install/child-process.js +28 -7
  35. package/lib/input-tracing/install/eval.js +60 -0
  36. package/lib/input-tracing/install/fs.js +21 -4
  37. package/lib/input-tracing/install/http.js +63 -0
  38. package/lib/input-tracing/install/mongodb.js +233 -0
  39. package/lib/input-tracing/install/mysql.js +21 -4
  40. package/lib/input-tracing/install/postgres.js +20 -4
  41. package/lib/input-tracing/install/sequelize.js +22 -5
  42. package/lib/input-tracing/install/sqlite3.js +21 -4
  43. package/lib/input-tracing/install/vm.js +132 -0
  44. package/lib/make-response-blocker.js +15 -0
  45. package/lib/make-source-context.js +22 -1
  46. package/lib/security-exception.js +15 -0
  47. package/lib/semantic-analysis/handlers.js +160 -0
  48. package/lib/semantic-analysis/index.js +38 -0
  49. package/lib/throw-security-exception.js +17 -6
  50. package/package.json +10 -12
  51. package/lib/cli-rewriter.js +0 -20
  52. package/lib/input-analysis/install/co-body.js +0 -51
  53. package/lib/input-analysis/install/cookie-parser.js +0 -48
  54. package/lib/input-analysis/install/formidable.js +0 -53
  55. package/lib/input-analysis/install/multer.js +0 -52
  56. package/lib/input-analysis/install/qs.js +0 -40
  57. package/lib/input-analysis/install/universal-cookie.js +0 -34
  58. package/lib/input-tracing/handlers/nosql-injection-mongo.js +0 -48
  59. package/lib/utils.js +0 -88
@@ -0,0 +1,160 @@
1
+ /*
2
+ * Copyright: 2022 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const {
19
+ BLOCKING_MODES,
20
+ InputType,
21
+ isString,
22
+ simpleTraverse
23
+ } = require('@contrast/common');
24
+
25
+ const SINK_EXPLOIT_PATTERN_START = /(?:^|\\|\/)(?:sh|bash|zsh|ksh|tcsh|csh|fish|cmd)/;
26
+ const stripWhiteSpace = (str) => str.replace(/\s/g, '');
27
+
28
+ // The sink instrumentation for this rule is in `protect/lib/input-tracing/install/child-process.js
29
+ module.exports = function(core) {
30
+ const { protect: { agentLib, semanticAnalysis, throwSecurityException } } = core;
31
+
32
+ function handleResult(sourceContext, sinkContext, ruleId, mode, finding) {
33
+ const sinkResults = sourceContext.findings.semanticResultsMap[ruleId];
34
+ const result = {
35
+ blocked: false,
36
+ findings: { command: sinkContext.value },
37
+ sinkContext,
38
+ ...finding
39
+ };
40
+
41
+ sourceContext.findings.semanticResultsMap[ruleId] = sinkResults ? [...sinkResults, result] : [result];
42
+
43
+ if (BLOCKING_MODES.includes(mode)) {
44
+ result.blocked = true;
45
+ const blockInfo = [mode, ruleId];
46
+ sourceContext.findings.securityException = blockInfo;
47
+ throwSecurityException(sourceContext);
48
+ }
49
+ }
50
+
51
+ semanticAnalysis.handleCmdInjectionSemanticDangerous = function(sourceContext, sinkContext) {
52
+ const ruleId = 'cmd-injection-semantic-dangerous-paths';
53
+ const { mode } = sourceContext.rules.agentRules[ruleId];
54
+
55
+ if (mode == 'off') return;
56
+
57
+ const result = agentLib.containsDangerousPath(sinkContext.value);
58
+
59
+ if (result) {
60
+ handleResult(sourceContext, sinkContext, ruleId, mode);
61
+ }
62
+ };
63
+
64
+ semanticAnalysis.handleCmdInjectionSemanticChainedCommands = function(sourceContext, sinkContext) {
65
+ const ruleId = 'cmd-injection-semantic-chained-commands';
66
+ const { mode } = sourceContext.rules.agentRules[ruleId];
67
+
68
+ if (mode == 'off') return;
69
+
70
+ const indexOfChaining = agentLib.indexOfChaining(sinkContext.value);
71
+
72
+ if (indexOfChaining != -1) {
73
+ handleResult(sourceContext, sinkContext, ruleId, mode);
74
+ }
75
+ };
76
+
77
+ semanticAnalysis.handleCommandInjectionCommandBackdoors = function(sourceContext, sinkContext) {
78
+ const ruleId = 'cmd-injection-command-backdoors';
79
+ const { mode } = sourceContext.rules.agentRules[ruleId];
80
+
81
+ if (mode == 'off') return;
82
+
83
+ const finding = findBackdoorInjection(sourceContext, sinkContext.value);
84
+
85
+ if (finding) {
86
+ handleResult(sourceContext, sinkContext, ruleId, mode, finding);
87
+ }
88
+ };
89
+
90
+ return semanticAnalysis;
91
+ };
92
+
93
+ /**
94
+ * Backdoor detection logic:
95
+ * - command is >= 2 chars
96
+ * - iterates over every piece of request and checks
97
+ * - the full value is the param to sink
98
+ * - the value matches a regex and ends the param to the sink
99
+ */
100
+ function findBackdoorInjection(sourceContext, command) {
101
+ if (command?.length < 2) {
102
+ return null;
103
+ }
104
+
105
+ const valuesOfInterest = {
106
+ [InputType.QUERYSTRING]: sourceContext.parsedQuery,
107
+ [InputType.PARAMETER_VALUE]: sourceContext.parsedParams,
108
+ [InputType.BODY]: sourceContext.parsedBody,
109
+ [InputType.COOKIE_VALUE]: sourceContext.parsedCookies,
110
+ [InputType.HEADER]: sourceContext.reqData.headers,
111
+ };
112
+
113
+ let found = false;
114
+ for (let inputType in valuesOfInterest) {
115
+ const values = valuesOfInterest[inputType];
116
+
117
+ if (values && Object.keys(values).length) {
118
+ simpleTraverse(values, (path, type, value, obj) => {
119
+ if (
120
+ !found &&
121
+ value &&
122
+ type === 'Value' &&
123
+ isString(value) &&
124
+ isBackdoorDetected(value, command)
125
+ ) {
126
+ let key;
127
+ key = inputType === InputType.HEADER ? obj.indexOf(command) - 1 : path[path.length - 1];
128
+ if (Number.isInteger(key) && obj[key]) {
129
+ key = obj[key];
130
+ }
131
+ path = path.length === 1 ? [] : Array.from(path).slice(0, path.length - 1);
132
+ inputType = path.length > 1 ? InputType.JSON_VALUE : inputType;
133
+
134
+ found = { key, inputType, path, value: command };
135
+ }
136
+ });
137
+ }
138
+ }
139
+
140
+ return found;
141
+ }
142
+
143
+ /**
144
+ * strips the whitespace of the request value and the command,
145
+ * checks if the command equals the request value
146
+ * or if the command looks like the start of a shell execution
147
+ * and ends with the request value passed to the sink
148
+ *
149
+ * @param {string} value from request key
150
+ */
151
+ function isBackdoorDetected(requestValue, command) {
152
+ const normalizedValue = stripWhiteSpace(requestValue);
153
+ const normalizedCommand = stripWhiteSpace(command);
154
+
155
+ return (
156
+ normalizedValue === normalizedCommand ||
157
+ (normalizedCommand.endsWith(normalizedValue) &&
158
+ SINK_EXPLOIT_PATTERN_START.test(normalizedCommand))
159
+ );
160
+ }
@@ -0,0 +1,38 @@
1
+ /*
2
+ * Copyright: 2022 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ /**
19
+ * SEMANTIC ANALYSIS is a STAGE of Protect.
20
+ * The information about it can be found under some of the separate rules we support for this stage:
21
+ * https://protect-spec.prod.dotnet.contsec.com/rules/cmd-injection-semantic-dangerous-paths.html#semantic-analysis
22
+ * https://protect-spec.prod.dotnet.contsec.com/rules/cmd-injection-semantic-chained-commands.html#semantic-analysis
23
+ *
24
+ * To view other STAGES see https://protect-spec.prod.dotnet.contsec.com/guide/protect-types.html#protection-types
25
+ * @param {object} core composed dependencies
26
+ * @returns {object}
27
+ */
28
+ module.exports = function(core) {
29
+ const semanticAnalysis = core.protect.semanticAnalysis = {};
30
+
31
+ // load the interfaces that will be used by input tracing instrumentation
32
+ require('./handlers')(core);
33
+
34
+ // There is no `.install()` method as this STAGE does not introduce side effects on its own,
35
+ // it uses the instrumentation that's already in place for INPUT TRACING.
36
+
37
+ return semanticAnalysis;
38
+ };
@@ -1,6 +1,20 @@
1
+ /*
2
+ * Copyright: 2022 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
1
16
  'use strict';
2
17
 
3
- const Domain = require('async-hook-domain');
4
18
  const securityException = require('./security-exception');
5
19
 
6
20
  module.exports = function(core) {
@@ -17,12 +31,9 @@ module.exports = function(core) {
17
31
 
18
32
  const err = securityException.create();
19
33
 
20
- new Domain(() => {
21
- sourceContext.block(mode, ruleId);
22
- });
23
-
24
- logger.info({ err, mode, ruleId }, 'throwing security exception');
34
+ logger.debug({ err, mode, ruleId }, 'throwing security exception');
25
35
 
36
+ // TO-DO: NODE-2556 Research fail-safe handler for all security exceptions
26
37
  throw err;
27
38
  }
28
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/protect",
3
- "version": "1.2.1",
3
+ "version": "1.4.0",
4
4
  "description": "Contrast service providing framework-agnostic Protect support",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
@@ -9,11 +9,8 @@
9
9
  ],
10
10
  "main": "lib/index.js",
11
11
  "types": "lib/index.d.ts",
12
- "bin": {
13
- "contrast-transpile": "lib/cli-rewriter.js"
14
- },
15
12
  "engines": {
16
- "npm": ">=6.13.7 <7 || >= 8.4.0",
13
+ "npm": ">= 8.4.0",
17
14
  "node": ">= 14.15.0"
18
15
  },
19
16
  "scripts": {
@@ -22,12 +19,13 @@
22
19
  "dependencies": {
23
20
  "@babel/template": "^7.16.7",
24
21
  "@babel/types": "^7.16.8",
25
- "@contrast/agent-lib": "^4.2.0",
26
- "@contrast/common": "1.0.1",
27
- "@contrast/core": "1.1.1",
28
- "@contrast/scopes": "1.0.1",
29
- "@contrast/esm-hooks": "1.1.1",
30
- "async-hook-domain": "^2.0.4",
31
- "builtin-modules": "^3.2.0"
22
+ "@contrast/agent-lib": "^5.0.0",
23
+ "@contrast/common": "1.1.0",
24
+ "@contrast/core": "1.3.0",
25
+ "@contrast/esm-hooks": "1.1.4",
26
+ "@contrast/scopes": "1.1.1",
27
+ "builtin-modules": "^3.2.0",
28
+ "ipaddr.js": "^2.0.1",
29
+ "semver": "^7.3.7"
32
30
  }
33
31
  }
@@ -1,20 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- 'use strict';
4
-
5
- const { cliRewriter } = require('@contrast/core')();
6
-
7
- cliRewriter((deps) => {
8
- if (deps.config.protect.enable) {
9
- try {
10
- const protect = require('./index')(deps);
11
- protect.rewriting.install();
12
- } catch (err) {
13
- // TODO: something else
14
- throw err;
15
- }
16
- } else {
17
- deps.logger.error('Configuration Error: mode \'Protect\' is not enabled');
18
- process.exit(1);
19
- }
20
- });
@@ -1,51 +0,0 @@
1
- 'use strict';
2
-
3
- module.exports = (core) => {
4
- const {
5
- depHooks,
6
- patcher,
7
- logger,
8
- scopes: { sources },
9
- protect: { inputAnalysis },
10
- } = core;
11
-
12
- async function postHook(data) {
13
- const { args: [, opts], result } = data;
14
- if (result) {
15
- const sourceContext = sources.getStore()?.protect;
16
- if (!sourceContext) {
17
- logger.debug('source context not available in `co-body` hook');
18
- } else {
19
- result.then((resolved) => {
20
- const parsedBody = opts?.returnRawBody ? resolved.parsed : resolved;
21
- sourceContext.parsedBody = parsedBody;
22
- inputAnalysis.handleParsedBody(sourceContext, parsedBody);
23
- });
24
- }
25
- }
26
- }
27
-
28
- return {
29
- // Patch lower level parser - `co-body` used by `koa-body` and `koa-bodyparser`
30
- install() {
31
- depHooks.resolve({ name: 'co-body' }, (coBody) => {
32
- coBody = patcher.patch(coBody, {
33
- name: 'co-body',
34
- patchType: 'framework-patch',
35
- post: postHook
36
- });
37
-
38
- ['json', 'form', 'text'].forEach((property) => {
39
- patcher.patch(coBody, property, {
40
- name: `co-body.${property}`,
41
- patchType: 'framework-patch',
42
- post: postHook
43
- });
44
- });
45
-
46
- return coBody;
47
- }
48
- );
49
- }
50
- };
51
- };
@@ -1,48 +0,0 @@
1
- 'use strict';
2
-
3
- module.exports = (core) => {
4
- const {
5
- depHooks,
6
- patcher,
7
- logger,
8
- scopes: { sources },
9
- protect: { inputAnalysis },
10
- } = core;
11
-
12
- return {
13
- // Patch `cookie-parser` package
14
- install() {
15
- depHooks.resolve({ name: 'cookie-parser' }, (cookieParser) => patcher.patch(cookieParser, {
16
- name: 'cookie-parser',
17
- patchType: 'framework-patch',
18
- post(data) {
19
- data.result = patcher.patch(data.result, {
20
- name: 'cookie-parser',
21
- patchType: 'framework-patch',
22
- pre(data) {
23
- const [req, , origNext] = data.args;
24
-
25
- async function contrastNext() {
26
- const sourceContext = sources.getStore()?.protect;
27
-
28
- if (!sourceContext) {
29
- logger.debug('source context not available in `cookie-parser` hook');
30
- } else {
31
- if (req.cookies) {
32
- sourceContext.parsedCookies = req.cookies;
33
- inputAnalysis.handleCookies(sourceContext, req.cookies);
34
- }
35
- }
36
-
37
- await origNext();
38
- }
39
-
40
- data.args[2] = contrastNext;
41
- }
42
- });
43
- }
44
- })
45
- );
46
- }
47
- };
48
- };
@@ -1,53 +0,0 @@
1
- 'use strict';
2
-
3
- module.exports = (core) => {
4
- const {
5
- depHooks,
6
- patcher,
7
- logger,
8
- scopes: { sources },
9
- protect: { inputAnalysis },
10
- } = core;
11
-
12
- return {
13
- // Patch `formidable`
14
- install() {
15
- depHooks.resolve({ name: 'formidable' }, (formidable) => {
16
- formidable.IncomingForm.prototype.parse = patcher.patch(formidable.IncomingForm.prototype.parse, {
17
- name: 'Formidable.IncomingForm.prototype.parse',
18
- patchType: 'framework-patch',
19
- pre(data) {
20
- const origCb = data.args[1];
21
-
22
- function hookedCb(...cbArgs) {
23
- const sourceContext = sources.getStore()?.protect;
24
- const [, fields, files] = cbArgs;
25
-
26
- if (!sourceContext) {
27
- logger.debug('source context not available in `formidable` hook');
28
- } else {
29
- if (fields) {
30
- sourceContext.parsedBody = fields;
31
- inputAnalysis.handleParsedBody(sourceContext, fields);
32
- }
33
- if (files) {
34
- logger.debug('Check for vulnerable filename upload nyi');
35
- // CHECK FILENAME - NYI
36
- }
37
- }
38
-
39
- if (origCb && typeof origCb === 'function') {
40
- // Should we explicitly run in the current source context?
41
- origCb.apply(this, cbArgs);
42
- }
43
- }
44
-
45
- data.args[1] = hookedCb;
46
- }
47
- });
48
-
49
- return formidable;
50
- });
51
- }
52
- };
53
- };
@@ -1,52 +0,0 @@
1
- 'use strict';
2
-
3
- module.exports = (core) => {
4
- const {
5
- depHooks,
6
- patcher,
7
- logger,
8
- scopes: { sources },
9
- protect: { inputAnalysis },
10
- } = core;
11
-
12
- return {
13
- // Patch `multer`
14
- install() {
15
- depHooks.resolve({ name: 'multer', file: 'lib/make-middleware.js' }, (multerMakeMiddleware) => patcher.patch(multerMakeMiddleware, {
16
- name: 'multer.make-middleware',
17
- patchType: 'framework-patch',
18
- post(data) {
19
- data.result = patcher.patch(data.result, {
20
- name: 'multerMiddleware',
21
- patchType: 'framework-patch',
22
- pre(data) {
23
- const [req, , origNext] = data.args;
24
-
25
- async function contrastNext() {
26
-
27
- const sourceContext = sources.getStore()?.protect;
28
-
29
- if (!sourceContext) {
30
- logger.debug('source context not available in `multer` hook');
31
- } else {
32
- if (req.body) {
33
- sourceContext.parsedBody = req.body;
34
- inputAnalysis.handleParsedBody(sourceContext, req.body);
35
- }
36
- if (req.file || req.files) {
37
- logger.debug('Check for vulnerable filename upload nyi');
38
- // CHECK FILENAME - NYI
39
- }
40
- }
41
-
42
- await origNext();
43
- }
44
-
45
- data.args[2] = contrastNext;
46
- }
47
- });
48
- }
49
- }));
50
- }
51
- };
52
- };
@@ -1,40 +0,0 @@
1
- 'use strict';
2
-
3
- module.exports = (core) => {
4
- const {
5
- depHooks,
6
- patcher,
7
- logger,
8
- scopes: { sources },
9
- protect: { inputAnalysis },
10
- } = core;
11
-
12
- return {
13
- // Patch `qs`
14
- install() {
15
- depHooks.resolve({ name: 'qs' },
16
- (qs) => patcher.patch(qs, 'parse', {
17
- name: 'qs',
18
- patchType: 'framework-patch',
19
- post({ args, result }) {
20
- if (result && Object.keys(result).length) {
21
- const sourceContext = sources.getStore()?.protect;
22
-
23
- if (!sourceContext) {
24
- logger.debug('source context not available in `qs` hook');
25
-
26
- // We need to run analysis for the `qs` result only when it's used as a query parser.
27
- // `qs` is used also for parsing bodies, but these cases we handle individually with
28
- // the respective library that's using it (e.g. `formidable`, `co-body`) because in
29
- // some cases its use is optional and we cannot rely on it.
30
- } else if (sourceContext.reqData?.queries === args[0]) {
31
- sourceContext.parsedQuery = result;
32
- inputAnalysis.handleQueryParams(sourceContext, result);
33
- }
34
- }
35
- }
36
- })
37
- );
38
- }
39
- };
40
- };
@@ -1,34 +0,0 @@
1
- 'use strict';
2
-
3
- module.exports = (core) => {
4
- const {
5
- depHooks,
6
- patcher,
7
- logger,
8
- scopes: { sources },
9
- protect: { inputAnalysis },
10
- } = core;
11
-
12
- return {
13
- // Patch `universal-cookie` package
14
- install() {
15
- depHooks.resolve({ name: 'universal-cookie', file: 'cjs/utils.js' }, (uCookieUtils) => patcher.patch(uCookieUtils, 'parseCookies', {
16
- name: 'universal-cookie.utils',
17
- patchType: 'framework-patch',
18
- post({ result }) {
19
- if (result && Object.keys(result).length) {
20
- const sourceContext = sources.getStore()?.protect;
21
-
22
- if (!sourceContext) {
23
- logger.debug('source context not available in `universal-cookie` hook');
24
- } else {
25
- sourceContext.parsedCookies = result;
26
- inputAnalysis.handleCookies(sourceContext, result);
27
- }
28
- }
29
- }
30
- })
31
- );
32
- }
33
- };
34
- };
@@ -1,48 +0,0 @@
1
- 'use strict';
2
-
3
- /* c8 ignore start */
4
- // this is just the general structure of how to handle mongo sinks
5
- const util = require('util');
6
-
7
- const { simpleTraverse } = require('../../utils');
8
-
9
- function mongoSink(results, sinkContext) {
10
- if (typeof sinkContext.value === 'object') {
11
- return handleObjectValue(results, sinkContext.value);
12
- } else if (typeof sinkContext.value === 'string') {
13
- return handleStringValue(results, sinkContext.value);
14
- }
15
-
16
- return null;
17
- }
18
-
19
- function handleObjectValue(results, object) {
20
- for (const result of results) {
21
- simpleTraverse(object, function(path, type, value) {
22
- if (type !== 'Key') {
23
- return;
24
- }
25
- // the result value is the key that was found
26
- if (result.value === value) {
27
- // does the object at this path equal the user input?
28
- let obj = object;
29
- for (const p of path) {
30
- obj = obj[p];
31
- }
32
- obj = obj[value];
33
- // does the found object in the query equal the saved object?
34
- if (util.isDeepStrictEqual(obj, object)) {
35
- //
36
- }
37
- }
38
- });
39
- }
40
- }
41
-
42
- function handleStringValue(results, string) {
43
- // nyi
44
- }
45
-
46
- module.exports = mongoSink;
47
-
48
- /* c8 ignore stop */