Picking up meteor.js user logout - javascript

Is there any way to pick up when a user logs out of the website? I need to do some clean up when they do so. Using the built-in meteor.js user accounts.
I'll be doing some validation using it, so I need a solution that cannot be trigger on behalf of other users on the client side - preferably something completely server side.

You may use Deps.autorun to setup a custom handler observing Meteor.userId() reactive variable changes.
Meteor.userId() (and Meteor.user()) are reactive variables returning respectively the currently logged in userId (null if none) and the corresponding user document (record) in the Meteor.users collection.
As a consequence one can track signing in/out of a Meteor application by reacting to the modification of those reactive data sources.
client/main.js :
var lastUser=null;
Meteor.startup(function(){
Deps.autorun(function(){
var userId=Meteor.userId();
if(userId){
console.log(userId+" connected");
// do something with Meteor.user()
}
else if(lastUser){
console.log(lastUser._id+" disconnected");
// can't use Meteor.user() anymore
// do something with lastUser (read-only !)
Meteor.call("userDisconnected",lastUser._id);
}
lastUser=Meteor.user();
});
});
In this code sample, I'm setting up a source file local variable (lastUser) to keep track of the last user that was logged in the application.
Then in Meteor.startup, I use Deps.autorun to setup a reactive context (code that will get re-executed whenever one of the reactive data sources accessed is modified).
This reactive context tracks Meteor.userId() variation and reacts accordingly.
In the deconnection code, you can't use Meteor.user() but if you want to access the last user document you can use the lastUser variable.
You can call a server method with the lastUser._id as argument if you want to modify the document after logging out.
server/server.js
Meteor.methods({
userDisconnected:function(userId){
check(userId,String);
var user=Meteor.users.findOne(userId);
// do something with user (read-write)
}
});
Be aware though that malicious clients can call this server method with anyone userId, so you shouldn't do anything critical unless you setup some verification code.

Use the user-status package that I've created: https://github.com/mizzao/meteor-user-status. This is completely server-side.
See the docs for usage, but you can attach an event handler to a session logout:
UserStatus.events.on "connectionLogout", (fields) ->
console.log(fields.userId + " with connection " + fields.connectionId + " logged out")
Note that a user can be logged in from different places at once with multiple sessions. This smart package detects all of them as well as whether the user is online at all. For more information or to implement your own method, check out the code.
Currently the package doesn't distinguish between browser window closes and logouts, and treats them as the same.

We had a similar, though not exact requirement. We wanted to do a bit of clean up on the client when they signed out. We did it by hijacking Meteor.logout:
if (Meteor.isClient) {
var _logout = Meteor.logout;
Meteor.logout = function customLogout() {
// Do your thing here
_logout.apply(Meteor, arguments);
}
}

The answer provided by #saimeunt looks about right, but it is a bit fluffy for what I needed. Instead I went with a very simple approach like this:
if (Meteor.isClient) {
Deps.autorun(function () {
if(!Meteor.userId())
{
Session.set('store', null);
}
});
}
This is however triggered during a page load if the user has not yet logged in, which might be undesirable. So you could go with something like this instead:
if (Meteor.isClient) {
var userWasLoggedIn = false;
Deps.autorun(function (c) {
if(!Meteor.userId())
{
if(userWasLoggedIn)
{
console.log('Clean up');
Session.set('store', null);
}
}
else
{
userWasLoggedIn = true;
}
});
}

None of the solutions worked for me, since they all suffered from the problem of not being able to distinguish between manual logout by the user vs. browser page reload/close.
I'm now going with a hack, but at least it works (as long as you don't provide any other means of logging out than the default accounts-ui buttons):
Template._loginButtons.events({
'click #login-buttons-logout': function(ev) {
console.log("manual log out");
// do stuff
}
});

You can use the following Meteor.logout - http://docs.meteor.com/#meteor_logout

Related

Meteor: onConnection, check if user is logged in

I'm using the onConnection hook and some template helpers to do some stuff with statistics. But now, I want't to exclude these operations when I'm a registered user.
The Problem, I can't use Meteor.user() in the onConnection hook, so how can i check if a user is logged in ?
Concerning code, there is not much to show
Meteor.onConnection(function(conn) {
if(Meteor.user()) {
console.log("you are logged in")
} else {
console.log("u are not logged in")
}
});
It's not the true example but it shows simple what i want to do
The Error
err [Error: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions.]
I understand that i can just use Meteor.user() in methods, but how can i find out in the onConnection if a user is logged in ?
For statistics purposes I'd recommend to use publications. They have more sophisticated api which allows you to have more control over your connection.
Meteor.publish('users.trackPresence', function() {
// Both this.userId && this.connection are available to be called from here
this.onStop(function(){
// user went offline
});
this.ready();
});
and on the client you can check if user is present and not even subscribe if this is the case:
Tracker.autorun(function(){
if (!Meteor.userId())
Meteor.subscribe('users.trackPresence');
});
Meteor automatically handles unsubscribe/resubscribe when you subscribe from within a Tracker.autorun
read more about pubsub api here
https://docs.meteor.com/api/pubsub.html
Obviously as you stated, the Meteor docs do not provide any insight for how to achieve this. I spent a decent amount of time going thru the accounts-base source and don't see any way to do what you are asking natively.
With that said, if you could update the Users collection each time they login and save their current IP address, then you could use this as a way to see if the current connection is logged in. Here is an example.
Meteor.onConnection((connection) => {
var user = Meteor.users.findOne({
'user.profile.currentIp': connection.clientAddress
});
if (user) {
console.log("you are logged in")
} else {
console.log("u are not logged in")
}
});
Be sure to add login and logout hooks to set and remove the user's current IP. I have not tested this approach, but in theory it should work.

Meteor - Publish/Subscribe to user data while not logged in

I've searched and read through pretty much all the questions relating to Meteor's pub/sub functionality but cant seem to find an answer that directly addresses my question.
I am learning Meteor and would like to create the following;
A user visits a page and is presented with an input box, they enter their username and if its a valid username I present them with the password input field.
The problem I seem to be having is that I cant access the Meteor.users collection unless I am logged in (the auto-publish and insecure packages have been removed).
So my question is 2 fold;
Is is possible to access the Meteor.users.find({username: 'value of input box'}) without being logged in?
If so, what do I need to do?
Here is how I have my code structured below;
/server/publications.js:
Meteor.publish("staff", function () {
return Meteor.users.find();
});
/lib/router.js:
Router.configure({
layoutTemplate: 'layout'
});
Router.map(function() {
this.route('home', {path: '/', data: function() {return Meteor.subscribe('staff');} });
});
/client/views/home/home.js:
Template.home.events({
'submit form': function(e) {
e.preventDefault();
var guest = $("input#name");
var staffMember = Meteor.users.findOne({username: $(guest).val()});
});
Unless I am logged in as any user, Meteor.users.find().fetch() always returns an empty array. Any help is greatly appreciated.
You are publishing all the Users to be visible on the client. That is not a very secure position to be in since all your user data will be visible on all the clients. The pragmatic way to solve the problem is to allow the user to see only a limited number of users, i.e. publish a different set of users, depending on the userId.
As #Hubert's answer below indicates, you also need to subscribe to the publication on the client to actually fetch the published accounts over.
You can do this checking for validity securely by creating a Method call on the server:
/server/methods.js:
Meteor.method({
validUserName: function (userName) {
check(userName, string);
// You have access to Meteor.users here.
// This assumes that mere existence of a user with the same username will
// render the entered username invalid. If there are more checks you want to
// make, they should be made here.
return !!Meteor.users.find({username: userName});
}
});
/client/views/home/home.js:
Template.home.events({
'submit form': function(e) {
e.preventDefault();
var guest = $("input#name");
var userNameEntered = $(guest).val();
Session.set('requestInProgress', true);
Meteor.call('validUserName', function (isUserNameValid) {
Session.set('requestInProgress', false);
// ... take action here.
});
});
You're publishing all your user data to everyone, now you just need to subscibe to it:
Deps.autorun(function(){
Meteor.subscribe('staff');
});
That said, make sure that in such subscription you publish only non-sensitive user data, as it will be visible to everyone. Ideally, create another subscription for that purpose and filter just the username via the fields param.

Meteor.user() alternatives

I am writing an application which needs to display some user information.
Because Meteor.user() is not immediately available I wrapped every user information with an handlerbar helper
Handlebars.registerHelper('isLoggingIn', function() {
return Meteor.loggingIn();
})
This worked for me until I needed to create an admin page and custom content for every user/user role.
Waiting for Meteor.user() to be available or showing general information first while waiting for the roles to load are options I would like to avoid.
I then tried an alternative way and published the currentUser with a new Collection.
Meteor.publish('currentUser', function() {
var sub = this;
var handle = Meteor.users.find({_id: this.userId}).observe({
added: function (user) {
sub.added('currentUser', user._id, user);
}
});
sub.ready();
sub.onStop(function() { handle.stop(); });
});
and
CurrentUser = new Meteor.Collection('currentUser');
In this way I can access the logged in user with CurrentUser.findOne(), and it's available at the same time as the other collections.
What I fear is that this alternative is not as secure and problem free as the common Meteor.user(), and I was wondering if my method is correct and if there are better ways to obtain the same result (user detail information immediately available) without reinventing the wheel.
Just a note you can use {{loggingIn}}, {{#if loggingIn}}.. without writing your own helper.
The option to publish the user who is logged in with a custom publish function adds an unnecessary complexity.
When it comes to security you have to assume if its from the client side, in any scenario it is untrustworthy. This means you publish relevant data for the role, etc only when they are logged in to that user.
On the server the data is immediately available as soon as the user logs in, all you have to do is publish only the data for that users role. On the client it may take some time to adjust to this, which is why you can use placeholder until the subscriptions are complete.
What might be a better option would be to use either a helper that checks for when subscriptions are completed and displays a 'loading message'. Or use a router such as iron-router (github.com/EventedMind/iron-router) that can let you wait for a subcription to complete for a particular page.
This way you can use Meteor.user(), {{#currentUser}} and roles in way you intend.
One thing to keep in mind, is if you want to check if the user is logged in, not to use:
if(Meteor.user())
but instead
if(Meteor.user() && Meteor.user().profile && Meteor.user().profile.name)
(You will have to insert a name property in your profile, though). While logging in the user gets more and more data. I've noticed if you wait for the profile field, then the user is 'ready'. It seems initially the profile field is empty (still loggin in), but it would return true if you used if(Meteor.user())

How to redirect user after successful login?

Update:
After implementing the below suggestion by Rob Sedgwick, it has become apparent that the redirect only works when the user manually "F5" refreshers the browser (Chrome). What I need to achieve is that this happens automatically in the code so the redirect happens without the user having to hot refresh. Thanks for help with this last part.
At the moment ManageTodosView from what I understand is the first action after the user has been logged in. It prints a list of to do items set up by the user. A working example can be found here http://parseplatform.github.io/Todo/ and the code is https://github.com/ParsePlatform/Todo
I'm using to code to really get user logins to work, I'm not to worries about what the output of the rest of the code is because the long term plan will be to remove it, for the time being its helpful to keep in place to show that the app functioning correctly.
I'm using this code as a base to build a web app. At the moment, once the user is logged in, they are displayed data on the same page.
I want to be able to change this so that after they login, the user is redirected to a different page and the information is then displayed to them there.
The reason for this is that the index page is just a landing/login page and I want to redirect them to a more structured HTML page with menus, etc.
Within the JS code, do I just put in a redirect, something like:
self.location="top.htm";
to this area of the code?
// The main view for the app
var AppView = Parse.View.extend({
// Instead of generating a new element, bind to the existing skeleton of
// the App already present in the HTML.
el: $("#todoapp"),
initialize: function() {
this.render();
},
render: function() {
if (Parse.User.current()) {
new ManageTodosView();
} else {
new LogInView();
}
}
});
I have added the JS code to this JSFiddle
Update:
To address the issue of the page needing a manual fresh before the redirect works, insert
window.location.href="/someurl";
into the following code section within the todoe.js file and comment out the new ManageTodosView(); code.
Parse.User.logIn(username, password, {
success: function(user) {
window.location.href="user_home.html";
//new ManageTodosView();
self.undelegateEvents();
delete self;
},
Try this one also
window.open('url','_parent');
I would suggest a more robust template for integrating Parse Todo samples with real apps. 'Marionette' offers lots of value in real world.
If you take the time to look over the app's structure and then look at the 'loginSuccess' function (scroll to very bottom of link), its pretty straightforward to redirect. You can either use the router as shown OR you can use the Marionette aggregated events which would look like:
vent.trigger('header:loggedIn', _user);
somewhere else in any module within the app....
vent.on('header:loggedIn', function (user) {
that.setViewNew(user);
});
...
setViewNew : function (user) {
User = user;
var viewOptionsUser = {
collection : this.roleList,
events : {
'keypress #new-todo': 'createTaskOnEnter'},
parentUser : User};
this.headerRegion.show(new HeaderView(viewOptionsUser));
this.mainRegion.show(new RoleRoleListCompositeView(viewOptionsUser));
}
Try something like this to redirect your user
window.location = 'www.google.com'

How to prevent auto login after create user

I add accounts-password and accounts-base packages in Meteor
When I create user like this:
Accounts.createUser({username: username, password : password}, function(err){
if (err) {
// Inform the user that account creation failed
console.log("Register Fail!")
console.log(err)
} else {
console.log("Register Success!")
// Account has been created and the user has logged
}
});
Account has been created and the user has logged.
for instance, I log in as an administrator and I want to create a account for somebody,but I don't want to log out after create account.
How to prevent auto login after create user ?
I find source code of accouts-password packages:
48 - 63 lines:
// Attempt to log in as a new user.
Accounts.createUser = function (options, callback) {
options = _.clone(options); // we'll be modifying options
if (!options.password)
throw new Error("Must set options.password");
var verifier = Meteor._srp.generateVerifier(options.password);
// strip old password, replacing with the verifier object
delete options.password;
options.srp = verifier;
Accounts.callLoginMethod({
methodName: 'createUser',
methodArguments: [options],
userCallback: callback
});
};
Should I modify the source code to solve this problem?
Any help is appreciated.
You're trying to use client side accounts management to perform a task it hasn't been designed for.
Client side accounts package purpose is to specifically allow new users to create their account and expect to be logged in immediately.
You have to remember that certain functions can be ran on the client and/or on the server with different behaviors, Accounts.createUser docs specifies that : "On the client, this function logs in as the newly created user on successful completion."
On the contrary, "On the server, it returns the newly created user id." (it doesn't mess with the currently logged in user on the client).
In order to solve your problem, you should write a server side method creating a new user and be able to call it from your client side admin panel, after filling correctly a user creation form of your own design.
If you really want this behavior you would need to modify password_server.js
and remove lines 474-475 containing:
// client gets logged in as the new user afterwards.
this.setUserId(result.id);
So the User would not be logged in after the user is created.
I had the same problem. I wanted to create an admin interface where an administrator can set a user's password but not pass it to a server method in plaintext. The client side of Accounts.createUser already deals with this, so I just alter the normal sequence of events in accounts-password/password-server.js in the presence of a flag. Its not perfect or pretty but seems to work and you don't have to modify the accounts-password package directly.
Meteor.startup(function ()
{
// store the default createUser method handler
var default_create_user = Meteor.server.method_handlers.createUser;
// remove it so we can register our own
delete Meteor.server.method_handlers.createUser;
Meteor.methods({createUser: function (options) {
var default_login_method = Accounts._loginMethod;
// check if noAutoLogin flag is present
if (options.noAutoLogin)
{
// temporarily disable the login method while creating our user
// NB: it might be possible that simultaneous calls to createUser that do want the default behavior
// would use the altered Accounts._loginMethod instead
Accounts._loginMethod = function(s, m, a, p, fn)
{
// this is the callback that is constructed with a closure of the options array and calls internal create functions
fn();
// restore default _loginMethod so other calls are not affected
Accounts._loginMethod = default_login_method;
}
}
// invoke the default create user now that the login behavior has been short-circuited
default_create_user(options);
}});
});
If you want to continue using Accounts.createUser on the client without logging the user in. You can call Meteor.logout() from createUser's optional callback.
Accounts.createUser(user, err => {
if (err) {
// handle error
return;
}
// Prevent unwanted login
Meteor.logout();
});

Categories

Resources