Meteor accounts framework signed/logged in event - meteor

I'm using the accounts-ui package and would like to process some javascript as soon as the user is logged in/and or registered.
Is there an event that gets called as soon as the user signs in?

The raw login API (eg loginWithFacebook, loginWithPassword, etc) has a callback that fires when login is complete, but this is not currently exposed through accounts-ui. This may change.
A potential workaround, inspired by Werner's suggestion, but also taking page load into account:
var oldUserId = undefined;
Meteor.autorun(function() {
var newUserId = Meteor.userId();
if (oldUserId === null && newUserId) {
console.log('The user logged in');
} else if (newUserId === null && oldUserId) {
console.log('The user logged out');
}
oldUserId = Meteor.userId();
});

You could check the result of the Meteor.userId() function inside Meteor.autorun():
Meteor.autorun(function() {
if (Meteor.userId()) {
console.log('The user logged in');
}
});

Just to give an alternative; I monkey patched the callback function. It looks a bit more complex because the credentialRequestCompleteHandler requires a function that returns a function but besides that its a plain monkey patch. Stick this in main.js or something that gets processed late and only once. I hope it helps for future reference.
var orgCallback = Accounts.oauth.credentialRequestCompleteHandler;
Accounts.oauth.credentialRequestCompleteHandler = function(callback){
return function (credentialTokenOrError) {
var tmpFunc = orgCallback(callback);
tmpFunc(credentialTokenOrError);
alert("do your own thing here");
}
}

Related

user object not loaded after logging out and quickly logging back in in Meteor

I am facing a strange behaviour, and can't debug it properly. I need your help.
If I log out of Meteor, everything seems fine.
If I wait for around 2 seconds and log back in again, everything is still very fine. But if I logout and quickly login again, right after the login process, the Meteor.user() object is set to null, which leads my router to redirect the user back to the login page.
Any idea why this is happening, and how could I prevent it?
I have spent 2h trying several things without success. Any suggestion is most welcome.
EDIT
This is my global onBeforeAction function :
Router.onBeforeAction(function() {
// Ensures the user is logged in
if (!Meteor.userId()) {
please_login();
}
// Email address not verified for 24h? Please verify it!
else {
var self = this;
// Waits for the user object to be passed over DDP
function wait_for_user_data() {
if (Meteor.user() && Meteor.user().emails && Meteor.user().profile) {
var user = Meteor.user();
var now = new Date().getTime();
var ca = user.createdAt.getTime();// Created At
var cs = (now - ca) / (24 * 60 * 60 * 1000);// Created Since (in days)
var urls = ["email_verification_required", "email_verification"];
if (cs > 1 &&
!user.emails[0].verified &&
urls.indexOf(self.url.split("/")[1]) == -1) {
Router.go("email_verification_required");
}
else {
self.next();
}
}
else {
setTimeout(wait_for_user_data, 500);
}
}
wait_for_user_data();
}
},
{except: ['home', 'login', 'register', 'password_recovery', "email_verification", "profile"]})
What actually happens is the following :
When I login right after having logged out, self.next() is called, but the current user properties (Meteor.user().emails and Meteor.user().profile) aren't loaded yet for some reason. They are undefined. As you can see, I tried to work around this by waiting until they are defined, but then I receive the following error message :
Route dispatch never rendered. Did you forget to call this.next()
in an onBeforeAction?
This seems to cause Meteor to set Meteor.user() to null, and so my user gets redirected to the login page...
EDIT BIS
This is how I am handling the publish/subscribe of the users data. I have 2 different pub/sub set, one for all users, and the other one for the logged in user only.
Meteor.publish('users', function() {
var args = {};
var fields = {
'_id': 1,
'createdAt': 1,
'profile.firstname' : 1,
'profile.lastname' : 1,
'profile.lang' : 1,
};
// Only admins can access those sensible information
if (this.userId && Roles.userIsInRole(this.userId, 'admin')) {
fields["emails"] = 1;
fields["profile.celular"] = 1;
args : {$ne : {_id: this.userId}};
}
return Meteor.users.find(args, {fields: fields});
});
Meteor.publish('user', function() {
return Meteor.users.find({_id: this.userId});
});
This is how I subscribe to those two publications within my router's configurations :
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn: function() {
return [Meteor.subscribe('user'), Meteor.subscribe('users')];
},
});
Without a code example (and knowing which router you're using) I'm going to take a guess that you're router code looks something like :
if (!Meteor.user()) {
//... redirect or change tempalte etc
}
There is another value you can check,
if (!Meteor.user() && !Meteor.loggingIn()) {
//... redirect or change tempalte etc
}
you can use various combinations of these to handle the login state, such as having a logging in loading view etc
With more information
The redirect is being called in a callback, currently for the login with password function, having a look at the docs it should be called on the onLogin callback linked as this ensures that login was successful, though doing the redirect here, may not provide the same range of control as doing this in a reactive context such as a before hook on a controller, or an autorun function created in the login template onCreated callback.
The way I've deal with this in the past is to define an explicit subscription to the user object then waitOn that subscription in the router. Much simpler than writing a polling loop but mostly I did it because I needed specific fields that didn't come over by default. Hopefully this can help you with your problem.
Server:
Meteor.publish('me',function(){
if ( this.userId ){
// return the fields I need
return Meteor.users.findOne({ _id: this.userId },{ fields: { field1: 1, field2: 1, ... }});
}
else this.ready();
});
Router:
waitOn: function(){
return Meteor.subscribe('me');
}

Meteor / Jasmine / Velocity : how to test a server method requiring logged in user?

Using velocity/jasmine, I'm a bit stuck on how I should test a server-side method requiring that there be a currently logged-in user. Is there a way to make Meteor think a user is logged in via stub/fake ?
myServerSideModel.doThisServerSideThing = function(){
var user = Meteor.user();
if(!user) throw new Meteor.Error('403', 'not-autorized');
}
Jasmine.onTest(function () {
describe("doThisServerSideThing", function(){
it('should only work if user is logged in', function(){
// this only works on the client :(
Meteor.loginWithPassword('user','pwd', function(err){
expect(err).toBeUndefined();
});
});
});
});
What you could do is add users just to your test suite. You could do this by populating these users in a the server-side test script:
Something like:
Jasmine.onTest(function () {
Meteor.startup(function() {
if (!Meteor.users.findOne({username:'test-user'})) {
Accounts.createUser
username: 'test-user'
... etc
Then, a good strategy could be to use the beforeAll in your test to login (this is client side):
Jasmine.onTest(function() {
beforeAll(function(done) {
Meteor.loginWithPassword('test-user','pwd', done);
}
}
This is assuming your test isn't logged in yet. You can make this more fancy by checking for Meteor.user() and properly logging out in an afterAll, etc. Note how you can handily pass the done callback to many of the Accounts functions.
Essentially, you don't have to mock a user. Just make sure you have the right users, with the correct roles, available in the Velocity/Jasmine DB.
Lets say you have a server side method like this:
Meteor.methods({
serverMethod: function(){
// check if user logged in
if(!this.userId) throw new Meteor.Error('not-authenticated', 'You must be logged in to do this!')
// more stuff if user is logged in...
// ....
return 'some result';
}
});
You do not need to make a Meteor.loginWithPassword before executing the method. All you got to do is stub the this.userId by changing the this context of the method function call.
All defined meteor methods are available on the Meteor.methodMap object. So just call the function with a different this context
describe('Method: serverMethod', function(){
it('should error if not authenticated', function(){
var thisContext = {userId: null};
expect(Meteor.methodMap.serverMethod.call(thisContext).toThrow();
});
it('should return a result if authenticated', function(){
var thisContext = {userId: 1};
var result = Meteor.methodMap.serverMethod.call(thisContext);
expect(result).toEqual('some result');
});
});
EDIT: This solution was only tested on Meteor <= 1.0.x
What are you testing and why does it require a user to be logged in? Most of the methods I have that need a user object I pass the user object into. This allows me to call from a test without actually being logged in. So in the actual running of the code I would pass...
var r = myMethod(Meteor.user());
but when running from the test I would call like...
it('should be truthy', function () {
var r = myMethod({_id: '1', username: 'testUser', ...});
expect(r).toBeTruthy();
});
I think that Meteor.server.method_handlers["nameOfMyMethod"] allows you to call/apply a Meteor method and supply this as the first parameter at least in the current version (1.3.3)
this.userId = userId;
Meteor.server.method_handlers["cart/addToCart"].apply(this, arguments);

Meteor never see's me as looged in

I want to detect if a logged in user has no records in the collection and then start them with some default values. So I use this at the beginning of my isClient part;
if (Meteor.isClient) {
if(Meteor.user()){
Meteor.subscribe('collection');
var currentUserId = Meteor.userId();
if (collection.find({"userID": currentUserId}).count() == 0)
{
Meteor.call('initUser', currentUserId);
}
} else {
console.log("You are not logged in");
}
}
Problem is that it never see's me as logged in. Do I need to be calling this from a template or something? In the tutorial that I did they just had it all by itself.
Your code looks good to me but it does not live in a reactive computation, meaning that it's going to run only once at the beginning of your code and never again, just like regular sequential programming.
You need to surround your code with a Tracker.autorun like this :
if (Meteor.isClient) {
Tracker.autorun(function(){
// Meteor.userId() is a reactive data source that will trigger invalidation
// of the reactive computation whenever it is modified (on login/logout)
var currentUserId = Meteor.userId();
if(currentUserId){
Meteor.subscribe('collection',function(){
if (collection.find({
userID: currentUserId
}).count() === 0){
Meteor.call('initUser', currentUserId);
}
});
} else {
console.log("You are not logged in");
}
}
}
I've refactored it to only use Meteor.userId() because you don't use the currentUser properties in this piece of code.

Add extra user field

In my Meteor app I use the default accounts package, which gives me the default login and registration functionality. Now I want to add an extra field to user, say nickname, and for the logged in user the possibility to edit this information.
For editing the profile I suppose I should be doing something like this:
Template.profileEdit.events({
'submit form': function(e) {
e.preventDefault();
if(!Meteor.user())
throw new Meteor.Error(401, "You need to login first");
var currentUserId = this._id;
var user = {
"profile.nickname": $(e.target).find('[name=nickname]').val()
};
Meteor.users.update(currentUserId, {
$set: user
}, function(error){
if(error){
alert(error.reason);
} else {
Router.go('myProfile', {_id: currentUserId});
}
});
}
});
But I doesn't store the info if I look in Mongo. Also when showing the profile, {{profile.nickname}} returns empty. What is wrong here?
Edit: added collections\users.js to show permissions:
Meteor.users.allow({
update: function (userId, doc) {
if (userId && doc._id === userId) {
return true;
}
}
});
Meteor.users.deny({
update: function(userId, user, fieldNames) {
return (_.without(fieldNames, 'profile.nickname').length > 0);
}
});
Yeah, I believe that should do the job, although I haven't actually run the code. The idea is certainly right.
The main things to be aware of are:
The necessity to allow the user doc to be edited from the client with an appropriate Meteor.users.allow() block on the server, assuming you're going to remove the "insecure" package (which you need to before doing anything in production).
The fact that "by default the server publishes username, emails, and profile", so you'll need to write a Meteor.publish function on the server and subscribe to it if you want to expose any other fields within the user document to the client once you've removed the "autopublish" package (which again, you really should).

How to get Meteor.user() to return on the server side?

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.
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.
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...
});
You can use the logged in callback
Accounts.onLogin((obj)->
user = ob.user
)
Accounts.onLogin(function(obj){
var user = ob.user
})
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.

Resources