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

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,45 @@ 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') {
1001
+ if (nextStage["$unwind"] === "$" + lookupField) {
1002
+ nextStageIsUnwind = true;
1003
+ }
1004
+ if (nextStage["$unwind"] && nextStage["$unwind"].path === "$" + lookupField) {
1005
+ nextStageIsUnwind = true;
1006
+ }
1007
+ }
1008
+ }
1009
+ if (!nextStageIsUnwind) {
1010
+ throw new Error('No support for $lookup where the next stage is not an $unwind and the resources has a findFunc');
1011
+ }
1012
+ // Push the $unwind, add our own findFunc, and increment the pipelineStage counter
1013
+ retVal.push({ $unwind: "$" + lookupField });
1014
+ const lookedUpFindQry = await this.doFindFuncPromise(req, resource);
1015
+ // Now we need to put the lookup base into the criteria
1016
+ for (const prop in lookedUpFindQry) {
1017
+ if (lookedUpFindQry.hasOwnProperty(prop)) {
1018
+ lookedUpFindQry[`${lookupField}.${prop}`] = lookedUpFindQry[prop];
1019
+ delete lookedUpFindQry[prop];
1020
+ }
1021
+ }
1022
+ retVal.push({ $match: lookedUpFindQry });
1023
+ }
973
1024
  }
974
1025
  break;
975
1026
  default:
976
- // nothing
977
- break;
1027
+ // anything else is either known to be dangerous, not yet needed or we don't know what it is
1028
+ throw new Error('Unsupported pipeline instruction ' + keys[0]);
978
1029
  }
979
1030
  if (stage) {
980
1031
  retVal.push(stage);
@@ -1040,13 +1091,18 @@ class FormsAngular {
1040
1091
  let hiddenFields = self.generateHiddenFields(resource, false);
1041
1092
  let toDo = {
1042
1093
  runAggregation: function (cb) {
1043
- runPipelineObj = self.sanitisePipeline(runPipelineObj, hiddenFields, queryObj);
1044
- resource.model.aggregate(runPipelineObj)
1045
- .then((results) => {
1046
- cb(null, results);
1094
+ self.sanitisePipeline(runPipelineObj, hiddenFields, queryObj, req)
1095
+ .then((runPipelineObj) => {
1096
+ resource.model.aggregate(runPipelineObj)
1097
+ .then((results) => {
1098
+ cb(null, results);
1099
+ })
1100
+ .catch((err) => {
1101
+ cb(err);
1102
+ });
1047
1103
  })
1048
1104
  .catch((err) => {
1049
- cb(err);
1105
+ throw new Error('Error in sanitisePipeline ' + err);
1050
1106
  });
1051
1107
  }
1052
1108
  };
@@ -1166,7 +1222,6 @@ class FormsAngular {
1166
1222
  callback(err);
1167
1223
  }
1168
1224
  else {
1169
- // TODO: Could loop through schema.params and just send back the values
1170
1225
  callback(null, {
1171
1226
  success: true,
1172
1227
  schema: schema,
@@ -1327,13 +1382,26 @@ class FormsAngular {
1327
1382
  }
1328
1383
  }
1329
1384
  ;
1330
- filteredFind(resource, req, aggregationParam, findParam, projectParam, sortOrder, limit, skip, callback) {
1385
+ async doFindFuncPromise(req, resource) {
1386
+ return new Promise((resolve, reject) => {
1387
+ this.doFindFunc(req, resource, (err, queryObj) => {
1388
+ if (err) {
1389
+ reject(err);
1390
+ }
1391
+ else {
1392
+ resolve(queryObj);
1393
+ }
1394
+ });
1395
+ });
1396
+ }
1397
+ ;
1398
+ async filteredFind(resource, req, aggregationParam, findParam, projectParam, sortOrder, limit, skip, callback) {
1331
1399
  const that = this;
1332
1400
  let hiddenFields = this.generateHiddenFields(resource, false);
1333
1401
  let stashAggregationResults;
1334
- function doAggregation(queryObj, cb) {
1402
+ async function doAggregation(queryObj, cb) {
1335
1403
  if (aggregationParam) {
1336
- aggregationParam = that.sanitisePipeline(aggregationParam, hiddenFields, queryObj);
1404
+ aggregationParam = await that.sanitisePipeline(aggregationParam, hiddenFields, queryObj, req);
1337
1405
  resource.model.aggregate(aggregationParam)
1338
1406
  .then((aggregationResults) => {
1339
1407
  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.248",
7
7
  "engines": {
8
8
  "node": ">=8.x",
9
9
  "npm": ">=5.x"