2
0
Эх сурвалжийг харах

Export Wekan now server-based with proper auth

Xavier Priour 9 жил өмнө
parent
commit
d08e1cc45b

+ 1 - 1
.meteor/packages

@@ -73,4 +73,4 @@ perak:markdown
 seriousm:emoji-continued
 templates:tabs
 verron:autosize
-pfafman:filesaver
+simple:json-routes

+ 1 - 1
.meteor/versions

@@ -111,7 +111,6 @@ peerlibrary:blaze-components@0.15.1
 peerlibrary:computed-field@0.3.1
 peerlibrary:reactive-field@0.1.0
 perak:markdown@1.0.5
-pfafman:filesaver@0.2.2
 promise@0.5.1
 raix:eventemitter@0.1.3
 raix:handlebar-helpers@0.2.5
@@ -126,6 +125,7 @@ seriousm:emoji-continued@1.4.0
 service-configuration@1.0.5
 session@1.1.1
 sha@1.0.4
+simple:json-routes@1.0.4
 softwarerero:accounts-t9n@1.1.7
 spacebars@1.0.7
 spacebars-compiler@1.0.7

+ 1 - 1
client/components/boards/boardHeader.jade

@@ -56,7 +56,7 @@ template(name="boardMenuPopup")
     if currentUser.isBoardAdmin
       hr
       ul.pop-over-list
-        li: a.js-export-board {{_ 'export-board'}}
+        li: a(href="{{exportUrl}}", download="{{exportFilename}}") {{_ 'export-board'}}
         li: a.js-archive-board {{_ 'archive-board'}}
 
 template(name="boardVisibilityList")

+ 9 - 17
client/components/boards/boardHeader.js

@@ -1,4 +1,3 @@
-/* global saveAs */
 Template.boardMenuPopup.events({
   'click .js-rename-board': Popup.open('boardChangeTitle'),
   'click .js-open-archives'() {
@@ -14,25 +13,18 @@ Template.boardMenuPopup.events({
     // confirm that the board was successfully archived.
     FlowRouter.go('home');
   }),
-  'click .js-export-board'() {
-    const boardId = Session.get('currentBoard');
-    Meteor.call('exportBoard', boardId, (error, response) => {
-      if(error) {
-        // the only error we can anticipate is accessing a non-authorized board
-        // and this should have been caugh by UI before.
-        // So no treatment here for the time being.
-      } else {
-        const dataToSave = new Blob([JSON.stringify(response)], {type: 'application/json;charset=utf-8'});
-        const filename = `wekan-export-board-${boardId}.json`;
-        saveAs(dataToSave, filename);
-      }
-    });
-  },
 });
 
 Template.boardMenuPopup.helpers({
-  urlExport() {
-    return Meteor.absoluteUrl(`api/b/${Session.get('currentBoard')}`);
+  exportUrl() {
+    const boardId = Session.get('currentBoard');
+    const userId = Meteor.userId();
+    const loginToken = Accounts._storedLoginToken();
+    return Meteor.absoluteUrl(`api/b/${boardId}/${userId}/${loginToken}`);
+  },
+  exportFilename() {
+    const boardId = Session.get('currentBoard');
+    return `wekan-export-board-${boardId}.json`;
   },
 });
 

+ 3 - 3
models/boards.js

@@ -80,15 +80,15 @@ Boards.attachSchema(new SimpleSchema({
 
 Boards.helpers({
   /**
-   * Is current logged-in user authorized to view this board?
+   * Is supplied user authorized to view this board?
    */
-  isVisibleByUser() {
+  isVisibleBy(user) {
     if(this.isPublic()) {
       // public boards are visible to everyone
       return true;
     } else {
       // otherwise you have to be logged-in and active member
-      return this.isActiveMember(Meteor.userId());
+      return this.isActiveMember(user._id);
     }
   },
 

+ 27 - 3
models/export.js

@@ -1,11 +1,30 @@
+/* global JsonRoutes */
+if(Meteor.isServer) {
+  JsonRoutes.add('get', '/api/b/:boardId/:userId/:loginToken', function (req, res) {
+    const { userId, loginToken, boardId } = req.params;
+    const hashToken = Accounts._hashLoginToken(loginToken);
+    const user = Meteor.users.findOne({
+      _id: userId,
+      'services.resume.loginTokens.hashedToken': hashToken,
+    });
+
+    const exporter = new Exporter(boardId);
+    if(user && exporter.canExport(user)) {
+      JsonRoutes.sendResult(res, 200, exporter.build());
+    } else {
+      // we could send an explicit error message, but on the other
+      // hand the only way to get there is by hacking the UI so...
+      JsonRoutes.sendResult(res, 403);
+    }
+  });
+}
 
 
 Meteor.methods({
   exportBoard(boardId) {
     check(boardId, String);
-    const board = Boards.findOne(boardId);
-    if(board.isVisibleByUser()) {
-      const exporter = new Exporter(boardId);
+    const exporter = new Exporter(boardId);
+    if(exporter.canExport(Meteor.user())) {
       return exporter.build();
     } else {
       throw new Meteor.Error('error-board-notAMember');
@@ -56,4 +75,9 @@ class Exporter {
     result.users = Users.find(byUserIds, userFields).fetch();
     return result;
   }
+
+  canExport(user) {
+    const board = Boards.findOne(this._boardId);
+    return board && board.isVisibleBy(user);
+  }
 }