Exception when using a server route and onBeforeAction
Asked Answered
Y

2

0

I'm seeing strange behavior when trying to add pdf file generation.

The following code, on the if statement, throws: both\routes.js

Router.onBeforeAction(function () {   if (!Meteor.user() || Meteor.loggingIn()) {
    this.redirect('welcome.view');   }   else {
    Meteor.call("userFileDirectory", function (error, result) {
      if (error)
        throw error;
      else
        console.log(result);
 });
    this.next();   } }, {   except: ['welcome.view'] });

Error: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions. at Object.Meteor.userId (packages/accounts-base/accounts_server.js:19:1) at Object.Meteor.user (packages/accounts-base/accounts_server.js:24:1) at [object Object].Router.onBeforeAction.except (app/both/3-router/routes.js:10:15) at packages/iron:router/lib/router.js:277:1 at [object Object]._.extend.withValue (packages/meteor/dynamics_nodejs.js:56:1) at [object Object].hookWithOptions (packages/iron:router/lib/router.js:276:1) at boundNext (packages/iron:middleware-stack/lib/middleware_stack.js:251:1) at runWithEnvironment (packages/meteor/dynamics_nodejs.js:108:1) at packages/meteor/dynamics_nodejs.js:121:1 at [object Object].dispatch (packages/iron:middleware-stack/lib/middleware_stack.js:275:1)

Only when I add this code into the file, and the /pdf route is taken:

Router.route('/pdf', function() {
  var filePath = process.env.PWD + "/server/.files/users/test.pdf";
  console.log(filePath);
  var fs = Npm.require('fs');
  var data = fs.readFileSync(filePath);
  this.response.write(data);
  this.response.end();
}, {
  where: 'server'
});

The above code works fine; the pdf is rendered to the screen and no exception is thrown, when I take out the onBeforeAction code.

The opposite is also true, if I take out the server route, there is no route that causes an exception.

Yonne answered 29/12, 2014 at 17:22 Comment(0)
L
1

This occurs because the route you're using is a server side route. The technique Meteor uses to authenticate a user is done via the DDP protocol, over websockets.

When your browser makes a GET/POST request to the server it doesn't have any information regarding the user's authentication state.

You use Meteor.user() in your Route.onBeforeAction but it has no access to this information.

The solution to this is find an alternative way to authenticate the user. One such method is to use cookie's.

This is known issue with Meteor's authentication system, see: https://github.com/EventedMind/iron-router/issues/649

Liberty answered 29/12, 2014 at 19:51 Comment(4)
I read your SO answer here: #16728422, can I use this process now (that was written in '13)? If so, would I place the cookie checking code in the onBeforeAction method or in the server side route?Yonne
I think you answer the why, the how to fix it was answered by David Weldon here: #27715118Yonne
What I mean to say is, the question is about the exception, and the exception is fixed per David Weldon answer... but the server side authentication system/method still needs to be worked out (which was part of your answer, but didn't exactly answer the question)... And again, you did answer the why it's happening, but in terms of authentication only.Yonne
I'll give you the answer here (just with the caveat as stated above). Yet, as a follow up, the SO question specifically about Server side routes and authentication is here: #27734610Yonne
B
1

A better way than cookies could be a named collection of Meteor that stores userId and some sessionId:

You can store current userId on the client side before the call to the server:

var sessionId = Random.id(); 
col = new Mongo.Collection('session');
col.insert({
  sessionId: sid,
  userId: Meteor.userId(),
  issued: new Date()
});

And then pass sessionId to the server through a GET/POST request and read it on the server:

var sid = this.request.query.sid;
var user = col.findOne({sessionId: sid}); // returns an object

Using a separate parameter is better than using userId itself because you can revoke this sessionId after some time or immediately after the server call.

Proper allow/deny permissions are required to prevent anyone from updating the collection. Also, please note that you can't trust new Date() on the client's side.

Bootery answered 8/6, 2015 at 13:9 Comment(6)
Thanks. But can you speak to why this is a better approach?Yonne
It seems to be a wrong code for this task as we may have many users, sorry for this!Bootery
A meteor collection is a secured and reactive way to know if a user is still authenticated/authorized to perform an action. Cookies are not. But the code above won't work ( We have to use some ID both on the client and server to refer to our duplicate userId is stored in the collection.Bootery
You say the answer you posted does not work and is incorrect, yet you posted it as an answer? Sorry, I'm confused. Will you post a answer that is 'correct', reactive, uses a collection, and works on the server too?Yonne
"A meteor collection is a secured and reactive way to know if a user is still authenticated/authorized to perform an action"... but that's almost the whole point -- on a server side route you DO NOT know who the user is. How can I pull from a collection without knowing who is doing the pulling? I just don't understand, I think code will help.Yonne
Sure thing. @Aaron, I put some clarification to the code that may help. We currently plan to use this approach to securely download files from the server. Hope it can help you or someone else too.Bootery

© 2022 - 2024 — McMap. All rights reserved.