37

in a file called /server/main.js (in order to ensure it is loaded last).

console.dir(Meteor.user());

Throws:

Error: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions.

So I try to use, in the same file:

console.dir(this.userId);

returns:

undefined

so, not giving up, I'm thinking "that's fine I'll just read from the cookies in the header":

var connect = Npm.require('connect');

__meteor_bootstrap__.app.use(connect.query()).use(function(req, res, next) {
    console.dir(req.headers);
    next();
});

.... returns nothing in terms of cookies except for 'cookie: 'uvf=1''

I'm not sure what to conclude - this is senseless as I can otherwise use the Meteor.Account framework just fine, read/set user properties, etc. The server is clearly aware of the user, and the current user clearly logged in.

I'm at a complete loss, any explanation / hint / pointer would be greatly appreciated.

Stephan Tual
  • 2,617
  • 3
  • 27
  • 49

4 Answers4

53

You have to use Meteor.user() in a place where a request is made from the client (such as a Meteor.methods or a Meteor.publish).

It can't be placed anywhere else because meteor wouldn't know at that point in the code the user is supposed to bound to. If there is a place a request of some form is made from the client it can do this:

In a Meteor.publish:

Meteor.publish("collection", function() {
    //returns undefined if not logged in so check if logged in first
    if(this.userId) {
        var user = Meteor.users.findOne(this.userId);
        //var user is the same info as would be given in Meteor.user();
    }
});

In a Meteor.methods:

Meteor.methods({
    "test":function() {
        //should print the user details if logged in, undefined otherwise.
        console.log(Meteor.user());
    }
}

To use Meteor.user() on a server side route:

You need Meteor router installed as a package via meteorite to allow you to have a server rendered page. (installed via mrt install router)

A server side route could then handle the web request:

 Meteor.Router.add('/awebpage', function(id) {
     var userId = this.params.userid;
     var logintoken = this.params.logintoken;
     var isdirect = this.param.direct;
     var user = Meteor.users.findOne({_id:userId,"services.resume.loginTokens.token":logintoken});
     if(user) {
         //the user is successfully logged in

         return "You, "+user.profile.name+", are logged in!";
     }
     else
     {
         if(isdirect) {
             return "<h3>Loading</h3><script>window.location.href="/awebpage?direct=true&userid="+localStorage.getItem("Meteor.userId") +"&logintoken="+localStorage.getItem("Meteor.loginToken")</script>";
         }
         else
         {
             return "Not logged in"
         }
     }
 });

So now when you visit /awebpage it would check whether the user is logged in and do the thing you want when they are logged in. Initially there is a redirect to relay the data from localstorage back to the URI.

Tarang
  • 75,157
  • 39
  • 215
  • 276
  • Thank you Akshat, here's the thing though, I'd like to use Meteor.user() to execute a conditional redirect to a different URL. So having to wait for the page to load, everything client and server side to be initialised, collections to be published, then make a method call from the client, wait for a reply and finally 'redirect' (which at that point would not be a 30x redirect anyway, because that ship has sailed), well, that doesn't work. If there's no way to do this I'm going to re-evaluate meteor, because right now I'm confused :( – Stephan Tual May 14 '13 at 00:25
  • I think I do see what you want & it is very possible!. You need to use a server side router. Ill update the answer as soon as I get a shred of time, Meteor is awesome, if you want it to do something the old fashioned way it can do it without compromise pretty much all the time. – Tarang May 14 '13 at 07:53
  • Thank you so much Akshat, I'm so happy to hear you know how to do this, that I've already marked you as the answer - looking forward to learn how to do this! – Stephan Tual May 14 '13 at 18:22
  • I wouldn't have accepted it until you were happy with it. I've updated the answer with the new content, hopefully this was what you needed it to do – Tarang May 15 '13 at 07:49
  • Thank you Akshat, I'm happy with this solution, even though it's not server-side per say (ie it's not a header redirect). I've also seen it done using mini-pages, a similar solution until Meteor evolves a bit. Thank you again! – Stephan Tual May 15 '13 at 11:57
  • Thanks - though in your snippet for Meteor.publish, I think you need to use `findOne`, ie. `var user = Meteor.users.findOne(this.userId); //var user is the same info as would be given in Meteor.user();` – Racing Tadpole May 24 '14 at 11:10
  • @RacingTadpole yes you're right. I think i had the intention of publishing the value which only works with Meteor.users.find() since the publish function needs to return a cursor. But `Meteor.users.findOne(this.userId)` is indeed what will be the same as `Meteor.user()` – Tarang May 24 '14 at 11:57
  • "You have to use Meteor.user() in a place where a request is made from the client (such as a Meteor.methods or a Meteor.publish)." I can't seem to use `Meteor.user()` in `Meteor.publish()`. Getting the same error that OP got. – mc9 Jul 02 '15 at 05:24
3

You can expose the userId with Meteor.publish() to global scope. Then you can use it with Meteor.Router's server side routes.

--

/server/publications.js

CurrentUserId = null;
Meteor.publish(null, function() {
    CurrentUserId = this.userId;
});

-

/server/routes.js

Meteor.Router.add('/upload', 'POST', function() {
    if (!CurrentUserId)
        return [403, 'Forbidden'];

    // proceed with upload...
});
user2139950
  • 101
  • 1
  • 3
  • It will not allow you to do it as publish functions should only return a cursor or array of cursors. – avalanche1 Oct 28 '15 at 15:47
  • 5
    Only if your app is going to be used by only one user at a time... otherwise this approach will fail miserably, with users getting privileges to see other some random other user's things, etc. – rdb May 11 '16 at 19:56
  • 1
    this is aweful, since as @rdb state will totally fail and impose security issues in an multiuser environment only the publish method is bound to a user the other context is out in the wild. – AirBorne04 Aug 04 '17 at 10:38
2

You can use the logged in callback

Accounts.onLogin((obj)->
  user = ob.user
)

Accounts.onLogin(function(obj){
  var user = ob.user
})
Nath
  • 6,774
  • 2
  • 28
  • 22
-1

I recently wrote a blog post describing solution to this: https://blog.hagmajer.com/server-side-routing-with-authentication-in-meteor-6625ed832a94.

You basically need to set up a server route using a https://atmospherejs.com/mhagmajer/server-router package and you can get current user with this.userId just like with Meteor methods.