forms-angular 0.12.0-beta.246 → 0.12.0-beta.247

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.
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FormsAngular = void 0;
4
4
  const mongoose_1 = require("mongoose");
5
- // This part of forms-angular borrows _very_ heavily from https://github.com/Alexandre-Strzelewicz/angular-bridge
5
+ // This part of forms-angular borrows from https://github.com/Alexandre-Strzelewicz/angular-bridge
6
6
  // (now https://github.com/Unitech/angular-bridge
7
7
  const _ = require('lodash');
8
8
  const util = require('util');
@@ -907,7 +907,7 @@ class FormsAngular {
907
907
  }
908
908
  }
909
909
  ;
910
- sanitisePipeline(aggregationParam, hiddenFields, findFuncQry) {
910
+ async sanitisePipeline(aggregationParam, hiddenFields, findFuncQry, req) {
911
911
  let that = this;
912
912
  let array = Array.isArray(aggregationParam) ? aggregationParam : [aggregationParam];
913
913
  let retVal = [];
@@ -922,9 +922,16 @@ class FormsAngular {
922
922
  throw new Error('Invalid pipeline instruction');
923
923
  }
924
924
  switch (keys[0]) {
925
- case '$merge':
926
- case '$out':
927
- throw new Error('Cannot use potentially destructive pipeline stages');
925
+ case '$project':
926
+ case '$addFields':
927
+ case '$count':
928
+ case "$group":
929
+ case "$limit":
930
+ case "$replaceRoot":
931
+ case "$sort":
932
+ case "$unwind":
933
+ // We don't care about these - they are all (as far as we know) safe
934
+ break;
928
935
  case '$unionWith':
929
936
  /*
930
937
  Sanitise the pipeline we are doing a union with, removing hidden fields from that collection
@@ -940,7 +947,7 @@ class FormsAngular {
940
947
  unionHiddenLookupFields = this.generateHiddenFields(unionResource, false);
941
948
  }
942
949
  }
943
- stage.$unionWith.pipeline = that.sanitisePipeline(stage.$unionWith.pipeline, unionHiddenLookupFields, findFuncQry);
950
+ stage.$unionWith.pipeline = await that.sanitisePipeline(stage.$unionWith.pipeline, unionHiddenLookupFields, findFuncQry, req);
944
951
  break;
945
952
  case '$match':
946
953
  this.hackVariables(array[pipelineSection]['$match']);
@@ -954,14 +961,24 @@ class FormsAngular {
954
961
  stage = null;
955
962
  break;
956
963
  case '$lookup':
964
+ case '$graphLookup':
965
+ if (keys[0] === '$lookup') {
966
+ // For now at least, we only support simple $lookups with a single join field equality
967
+ let lookupProps = Object.keys(stage.$lookup);
968
+ if (lookupProps.length !== 4 || lookupProps.indexOf('from') === -1 || lookupProps.indexOf('localField') === -1 || lookupProps.indexOf('foreignField') === -1 || lookupProps.indexOf('as') === -1) {
969
+ throw new Error("No support for $lookup that isn't Equality Match with a Single Join Condition");
970
+ }
971
+ }
957
972
  // hide any hiddenfields in the lookup collection
958
- const collectionName = stage.$lookup.from;
959
- const lookupField = stage.$lookup.as;
973
+ const collectionName = stage[keys[0]].from;
974
+ const lookupField = stage[keys[0]].as;
960
975
  if ((collectionName + lookupField).indexOf('$') !== -1) {
961
976
  throw new Error('No support for lookups where the "from" or "as" is anything other than a simple string');
962
977
  }
963
978
  const resource = that.getResourceFromCollection(collectionName);
964
979
  if (resource) {
980
+ retVal.push(stage);
981
+ stage = null;
965
982
  if (resource.options?.hide?.length > 0) {
966
983
  const hiddenLookupFields = this.generateHiddenFields(resource, false);
967
984
  let hiddenFieldsObj = {};
@@ -970,11 +987,40 @@ class FormsAngular {
970
987
  });
971
988
  retVal.push({ $project: hiddenFieldsObj });
972
989
  }
990
+ // Now we need to make sure that we restrict the lookup to documents we have access to
991
+ if (resource.options.findFunc) {
992
+ // If the next stage is an $unwind
993
+ let nextStageIsUnwind = false;
994
+ if (array.length >= pipelineSection) {
995
+ const nextStage = array[pipelineSection + 1];
996
+ let nextKeys = Object.keys(nextStage);
997
+ if (nextKeys.length !== 1) {
998
+ throw new Error('Invalid pipeline instruction');
999
+ }
1000
+ if (nextKeys[0] === '$unwind' && nextStage["$unwind"] === "$" + lookupField) {
1001
+ nextStageIsUnwind = true;
1002
+ }
1003
+ }
1004
+ if (!nextStageIsUnwind) {
1005
+ throw new Error('No support for $lookup where the next stage is not an $unwind and the resources has a findFunc');
1006
+ }
1007
+ // Push the $unwind, add our own findFunc, and increment the pipelineStage counter
1008
+ retVal.push({ $unwind: "$" + lookupField });
1009
+ const lookedUpFindQry = await this.doFindFuncPromise(req, resource);
1010
+ // Now we need to put the lookup base into the criteria
1011
+ for (const prop in lookedUpFindQry) {
1012
+ if (lookedUpFindQry.hasOwnProperty(prop)) {
1013
+ lookedUpFindQry[`${lookupField}.${prop}`] = lookedUpFindQry[prop];
1014
+ delete lookedUpFindQry[prop];
1015
+ }
1016
+ }
1017
+ retVal.push({ $match: lookedUpFindQry });
1018
+ }
973
1019
  }
974
1020
  break;
975
1021
  default:
976
- // nothing
977
- break;
1022
+ // anything else is either known to be dangerous, not yet needed or we don't know what it is
1023
+ throw new Error('Unsupported pipeline instruction ' + keys[0]);
978
1024
  }
979
1025
  if (stage) {
980
1026
  retVal.push(stage);
@@ -1040,13 +1086,18 @@ class FormsAngular {
1040
1086
  let hiddenFields = self.generateHiddenFields(resource, false);
1041
1087
  let toDo = {
1042
1088
  runAggregation: function (cb) {
1043
- runPipelineObj = self.sanitisePipeline(runPipelineObj, hiddenFields, queryObj);
1044
- resource.model.aggregate(runPipelineObj)
1045
- .then((results) => {
1046
- cb(null, results);
1089
+ self.sanitisePipeline(runPipelineObj, hiddenFields, queryObj, req)
1090
+ .then((runPipelineObj) => {
1091
+ resource.model.aggregate(runPipelineObj)
1092
+ .then((results) => {
1093
+ cb(null, results);
1094
+ })
1095
+ .catch((err) => {
1096
+ cb(err);
1097
+ });
1047
1098
  })
1048
1099
  .catch((err) => {
1049
- cb(err);
1100
+ throw new Error('Error in sanitisePipeline ' + err);
1050
1101
  });
1051
1102
  }
1052
1103
  };
@@ -1166,7 +1217,6 @@ class FormsAngular {
1166
1217
  callback(err);
1167
1218
  }
1168
1219
  else {
1169
- // TODO: Could loop through schema.params and just send back the values
1170
1220
  callback(null, {
1171
1221
  success: true,
1172
1222
  schema: schema,
@@ -1327,13 +1377,26 @@ class FormsAngular {
1327
1377
  }
1328
1378
  }
1329
1379
  ;
1330
- filteredFind(resource, req, aggregationParam, findParam, projectParam, sortOrder, limit, skip, callback) {
1380
+ async doFindFuncPromise(req, resource) {
1381
+ return new Promise((resolve, reject) => {
1382
+ this.doFindFunc(req, resource, (err, queryObj) => {
1383
+ if (err) {
1384
+ reject(err);
1385
+ }
1386
+ else {
1387
+ resolve(queryObj);
1388
+ }
1389
+ });
1390
+ });
1391
+ }
1392
+ ;
1393
+ async filteredFind(resource, req, aggregationParam, findParam, projectParam, sortOrder, limit, skip, callback) {
1331
1394
  const that = this;
1332
1395
  let hiddenFields = this.generateHiddenFields(resource, false);
1333
1396
  let stashAggregationResults;
1334
- function doAggregation(queryObj, cb) {
1397
+ async function doAggregation(queryObj, cb) {
1335
1398
  if (aggregationParam) {
1336
- aggregationParam = that.sanitisePipeline(aggregationParam, hiddenFields, queryObj);
1399
+ aggregationParam = await that.sanitisePipeline(aggregationParam, hiddenFields, queryObj, req);
1337
1400
  resource.model.aggregate(aggregationParam)
1338
1401
  .then((aggregationResults) => {
1339
1402
  stashAggregationResults = aggregationResults;
@@ -1,4 +1,4 @@
1
- import { Error, Model, Types } from "mongoose";
1
+ import { Error, FilterQuery, Model, Types } from "mongoose";
2
2
  import {Express} from "express";
3
3
 
4
4
  declare module fngServer {
@@ -52,7 +52,7 @@ declare module fngServer {
52
52
  handleRemove?: 'allow' | 'cascade'; // default behaviour is to prevent deletion if record is used as a foreign key
53
53
  searchImportance?: boolean | number,
54
54
  onSave?: (doc, req, cb) => void,
55
- findFunc?: (req, cb) => void,
55
+ findFunc?: (req: Express.Request, cb: (err:Error, criteria?: FilterQuery<any>) => void) => void,
56
56
  getOrgCriteria?: (userOrganisation: string) => Promise<any>
57
57
  idIsList?: IIdIsList,
58
58
  searchResultFormat?: ISearchResultFormatter,
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "author": "Mark Chapman <support@forms-angular.org>",
4
4
  "description": "A form builder that sits on top of Angular.js, Twitter Bootstrap, jQuery UI, Angular-UI, Express and Mongoose. Opinionated or what?",
5
5
  "homepage": "http://forms-angular.org",
6
- "version": "0.12.0-beta.246",
6
+ "version": "0.12.0-beta.247",
7
7
  "engines": {
8
8
  "node": ">=8.x",
9
9
  "npm": ">=5.x"