2
0

access-point-server.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. var path = Npm.require("path");
  2. HTTP.publishFormats({
  3. fileRecordFormat: function (input) {
  4. // Set the method scope content type to json
  5. this.setContentType('application/json');
  6. if (FS.Utility.isArray(input)) {
  7. return EJSON.stringify(FS.Utility.map(input, function (obj) {
  8. return FS.Utility.cloneFileRecord(obj);
  9. }));
  10. } else {
  11. return EJSON.stringify(FS.Utility.cloneFileRecord(input));
  12. }
  13. }
  14. });
  15. /**
  16. * @method FS.HTTP.setHeadersForGet
  17. * @public
  18. * @param {Array} headers - List of headers, where each is a two-item array in which item 1 is the header name and item 2 is the header value.
  19. * @param {Array|String} [collections] - Which collections the headers should be added for. Omit this argument to add the header for all collections.
  20. * @returns {undefined}
  21. */
  22. FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) {
  23. if (typeof collections === "string") {
  24. collections = [collections];
  25. }
  26. if (collections) {
  27. FS.Utility.each(collections, function(collectionName) {
  28. getHeadersByCollection[collectionName] = headers || [];
  29. });
  30. } else {
  31. getHeaders = headers || [];
  32. }
  33. };
  34. /**
  35. * @method FS.HTTP.publish
  36. * @public
  37. * @param {FS.Collection} collection
  38. * @param {Function} func - Publish function that returns a cursor.
  39. * @returns {undefined}
  40. *
  41. * Publishes all documents returned by the cursor at a GET URL
  42. * with the format baseUrl/record/collectionName. The publish
  43. * function `this` is similar to normal `Meteor.publish`.
  44. */
  45. FS.HTTP.publish = function fsHttpPublish(collection, func) {
  46. var name = baseUrl + '/record/' + collection.name;
  47. // Mount collection listing URL using http-publish package
  48. HTTP.publish({
  49. name: name,
  50. defaultFormat: 'fileRecordFormat',
  51. collection: collection,
  52. collectionGet: true,
  53. collectionPost: false,
  54. documentGet: true,
  55. documentPut: false,
  56. documentDelete: false
  57. }, func);
  58. FS.debug && console.log("Registered HTTP method GET URLs:\n\n" + name + '\n' + name + '/:id\n');
  59. };
  60. /**
  61. * @method FS.HTTP.unpublish
  62. * @public
  63. * @param {FS.Collection} collection
  64. * @returns {undefined}
  65. *
  66. * Unpublishes a restpoint created by a call to `FS.HTTP.publish`
  67. */
  68. FS.HTTP.unpublish = function fsHttpUnpublish(collection) {
  69. // Mount collection listing URL using http-publish package
  70. HTTP.unpublish(baseUrl + '/record/' + collection.name);
  71. };
  72. _existingMountPoints = {};
  73. /**
  74. * @method defaultSelectorFunction
  75. * @private
  76. * @returns { collection, file }
  77. *
  78. * This is the default selector function
  79. */
  80. var defaultSelectorFunction = function() {
  81. var self = this;
  82. // Selector function
  83. //
  84. // This function will have to return the collection and the
  85. // file. If file not found undefined is returned - if null is returned the
  86. // search was not possible
  87. var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
  88. // Get the collection name from the url
  89. var collectionName = opts.collectionName;
  90. // Get the id from the url
  91. var id = opts.id;
  92. // Get the collection
  93. var collection = FS._collections[collectionName];
  94. //if Mongo ObjectIds are used, then we need to use that in find statement
  95. if(collection.options.idGeneration && collection.options.idGeneration === 'MONGO') {
  96. // Get the file if possible else return null
  97. var file = (id && collection)? collection.findOne({ _id: new Meteor.Collection.ObjectID(id)}): null;
  98. } else {
  99. var file = (id && collection)? collection.findOne({ _id: id }): null;
  100. }
  101. // Return the collection and the file
  102. return {
  103. collection: collection,
  104. file: file,
  105. storeName: opts.store,
  106. download: opts.download,
  107. filename: opts.filename
  108. };
  109. };
  110. /*
  111. * @method FS.HTTP.mount
  112. * @public
  113. * @param {array of string} mountPoints mount points to map rest functinality on
  114. * @param {function} selector_f [selector] function returns `{ collection, file }` for mount points to work with
  115. *
  116. */
  117. FS.HTTP.mount = function(mountPoints, selector_f) {
  118. // We take mount points as an array and we get a selector function
  119. var selectorFunction = selector_f || defaultSelectorFunction;
  120. var accessPoint = {
  121. 'stream': true,
  122. 'auth': expirationAuth,
  123. 'post': function(data) {
  124. // Use the selector for finding the collection and file reference
  125. var ref = selectorFunction.call(this);
  126. // We dont support post - this would be normal insert eg. of filerecord?
  127. throw new Meteor.Error(501, "Not implemented", "Post is not supported");
  128. },
  129. 'put': function(data) {
  130. // Use the selector for finding the collection and file reference
  131. var ref = selectorFunction.call(this);
  132. // Make sure we have a collection reference
  133. if (!ref.collection)
  134. throw new Meteor.Error(404, "Not Found", "No collection found");
  135. // Make sure we have a file reference
  136. if (ref.file === null) {
  137. // No id supplied so we will create a new FS.File instance and
  138. // insert the supplied data.
  139. return FS.HTTP.Handlers.PutInsert.apply(this, [ref]);
  140. } else {
  141. if (ref.file) {
  142. return FS.HTTP.Handlers.PutUpdate.apply(this, [ref]);
  143. } else {
  144. throw new Meteor.Error(404, "Not Found", 'No file found');
  145. }
  146. }
  147. },
  148. 'get': function(data) {
  149. // Use the selector for finding the collection and file reference
  150. var ref = selectorFunction.call(this);
  151. // Make sure we have a collection reference
  152. if (!ref.collection)
  153. throw new Meteor.Error(404, "Not Found", "No collection found");
  154. // Make sure we have a file reference
  155. if (ref.file === null) {
  156. // No id supplied so we will return the published list of files ala
  157. // http.publish in json format
  158. return FS.HTTP.Handlers.GetList.apply(this, [ref]);
  159. } else {
  160. if (ref.file) {
  161. return FS.HTTP.Handlers.Get.apply(this, [ref]);
  162. } else {
  163. throw new Meteor.Error(404, "Not Found", 'No file found');
  164. }
  165. }
  166. },
  167. 'delete': function(data) {
  168. // Use the selector for finding the collection and file reference
  169. var ref = selectorFunction.call(this);
  170. // Make sure we have a collection reference
  171. if (!ref.collection)
  172. throw new Meteor.Error(404, "Not Found", "No collection found");
  173. // Make sure we have a file reference
  174. if (ref.file) {
  175. return FS.HTTP.Handlers.Del.apply(this, [ref]);
  176. } else {
  177. throw new Meteor.Error(404, "Not Found", 'No file found');
  178. }
  179. }
  180. };
  181. var accessPoints = {};
  182. // Add debug message
  183. FS.debug && console.log('Registered HTTP method URLs:');
  184. FS.Utility.each(mountPoints, function(mountPoint) {
  185. // Couple mountpoint and accesspoint
  186. accessPoints[mountPoint] = accessPoint;
  187. // Remember our mountpoints
  188. _existingMountPoints[mountPoint] = mountPoint;
  189. // Add debug message
  190. FS.debug && console.log(mountPoint);
  191. });
  192. // XXX: HTTP:methods should unmount existing mounts in case of overwriting?
  193. HTTP.methods(accessPoints);
  194. };
  195. /**
  196. * @method FS.HTTP.unmount
  197. * @public
  198. * @param {string | array of string} [mountPoints] Optional, if not specified all mountpoints are unmounted
  199. *
  200. */
  201. FS.HTTP.unmount = function(mountPoints) {
  202. // The mountPoints is optional, can be string or array if undefined then
  203. // _existingMountPoints will be used
  204. var unmountList;
  205. // Container for the mount points to unmount
  206. var unmountPoints = {};
  207. if (typeof mountPoints === 'undefined') {
  208. // Use existing mount points - unmount all
  209. unmountList = _existingMountPoints;
  210. } else if (mountPoints === ''+mountPoints) {
  211. // Got a string
  212. unmountList = [mountPoints];
  213. } else if (mountPoints.length) {
  214. // Got an array
  215. unmountList = mountPoints;
  216. }
  217. // If we have a list to unmount
  218. if (unmountList) {
  219. // Iterate over each item
  220. FS.Utility.each(unmountList, function(mountPoint) {
  221. // Check _existingMountPoints to make sure the mount point exists in our
  222. // context / was created by the FS.HTTP.mount
  223. if (_existingMountPoints[mountPoint]) {
  224. // Mark as unmount
  225. unmountPoints[mountPoint] = false;
  226. // Release
  227. delete _existingMountPoints[mountPoint];
  228. }
  229. });
  230. FS.debug && console.log('FS.HTTP.unmount:');
  231. FS.debug && console.log(unmountPoints);
  232. // Complete unmount
  233. HTTP.methods(unmountPoints);
  234. }
  235. };
  236. // ### FS.Collection maps on HTTP pr. default on the following restpoints:
  237. // *
  238. // baseUrl + '/files/:collectionName/:id/:filename',
  239. // baseUrl + '/files/:collectionName/:id',
  240. // baseUrl + '/files/:collectionName'
  241. //
  242. // Change/ replace the existing mount point by:
  243. // ```js
  244. // // unmount all existing
  245. // FS.HTTP.unmount();
  246. // // Create new mount point
  247. // FS.HTTP.mount([
  248. // '/cfs/files/:collectionName/:id/:filename',
  249. // '/cfs/files/:collectionName/:id',
  250. // '/cfs/files/:collectionName'
  251. // ]);
  252. // ```
  253. //
  254. mountUrls = function mountUrls() {
  255. // We unmount first in case we are calling this a second time
  256. FS.HTTP.unmount();
  257. FS.HTTP.mount([
  258. baseUrl + '/files/:collectionName/:id/:filename',
  259. baseUrl + '/files/:collectionName/:id',
  260. baseUrl + '/files/:collectionName'
  261. ]);
  262. };
  263. // Returns the userId from URL token
  264. var expirationAuth = function expirationAuth() {
  265. var self = this;
  266. // Read the token from '/hello?token=base64'
  267. var encodedToken = self.query.token;
  268. FS.debug && console.log("token: "+encodedToken);
  269. if (!encodedToken || !Meteor.users) return false;
  270. // Check the userToken before adding it to the db query
  271. // Set the this.userId
  272. var tokenString = FS.Utility.atob(encodedToken);
  273. var tokenObject;
  274. try {
  275. tokenObject = JSON.parse(tokenString);
  276. } catch(err) {
  277. throw new Meteor.Error(400, 'Bad Request');
  278. }
  279. // XXX: Do some check here of the object
  280. var userToken = tokenObject.authToken;
  281. if (userToken !== ''+userToken) {
  282. throw new Meteor.Error(400, 'Bad Request');
  283. }
  284. // If we have an expiration token we should check that it's still valid
  285. if (tokenObject.expiration != null) {
  286. // check if its too old
  287. var now = Date.now();
  288. if (tokenObject.expiration < now) {
  289. FS.debug && console.log('Expired token: ' + tokenObject.expiration + ' is less than ' + now);
  290. throw new Meteor.Error(500, 'Expired token');
  291. }
  292. }
  293. // We are not on a secure line - so we have to look up the user...
  294. var user = Meteor.users.findOne({
  295. $or: [
  296. {'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(userToken)},
  297. {'services.resume.loginTokens.token': userToken}
  298. ]
  299. });
  300. // Set the userId in the scope
  301. return user && user._id;
  302. };
  303. HTTP.methods(
  304. {'/cfs/servertime': {
  305. get: function(data) {
  306. return Date.now().toString();
  307. }
  308. }
  309. });
  310. // Unify client / server api
  311. FS.HTTP.now = function() {
  312. return Date.now();
  313. };
  314. // Start up the basic mount points
  315. Meteor.startup(function () {
  316. mountUrls();
  317. });