Meteor.user() alternatives - javascript

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())

Related

Would giving response to client while letting asynchronous operation continue to run a good idea?

So I need to implement an "expensive" API endpoint. Basically, the user/client would need to be able to create a "group" of existing users.
So this "create group" API would need to check that each users fulfill the criteria, i.e. all users in the same group would need to be from the same region, same gender, within an age group etc. This operation can be quite expensive, especially since there are no limit on how many users in one group, so its possible that the client requests group of 1000 users for example.
My idea is that the endpoint will just create entry in database and mark the "group" as pending, while the checking process is still happening, then after its completed, it will update the group status to "completed" or "error" with error message, then the client would need to periodically fetch the status if its still pending.
My implementation idea is something along this line
const createGroup = async (req, res) => {
const { ownerUserId, userIds } = req.body;
// This will create database entry of group with "pending" status and return the primary key
const groupId = await insertGroup(ownerUserId, 'pending');
// This is an expensive function which will do checking over the network, and would take 0.5s per user id for example
// I would like this to keep running after this API endpoint send the response to client
checkUser(userIds)
.then((isUserIdsValid) => {
if (isUserIdsValid) {
updateGroup(groupId, 'success');
} else {
updateGroup(groupId, 'error');
}
})
.catch((err) => {
console.error(err);
updateGroup(groupId, 'error');
});
// The client will receive a groupId to check periodically whether its ready via separate API
res.status(200).json({ groupId });
};
My question is, is it a good idea to do this? Do I missing something important that I should consider?
Yes, this is the standard approach to long-running operations. Instead of offering a createGroup API that creates and returns a group, think of it as having an addGroupCreationJob API that creates and returns a job.
Instead of polling (periodically fetching the status to check whether it's still pending), you can use a notification API (events via websocket, SSE, webhooks etc) and even subscribe to the progress of processing. But sure, a check-status API (via GET request on the job identifier) is the lowest common denominator that all kinds of clients will be able to use.
Did I not consider something important?
Failure handling is getting much more complicated. Since you no longer create the group in a single transaction, you might find your application left in some intermediate state, e.g. when the service crashed (due to unrelated things) during the checkUser() call. You'll need something to ensure that there are no pending groups in your database for which no actual creation process is running. You'll need to give users the ability to retry a job - will insertGroup work if there already is a group with the same identifier in the error state? If you separate the group and the jobs into independent entities, do you need to ensure that no two pending jobs are trying to create the same group? Last but not least you might want to allow users to cancel a currently running job.

Redirect a user if they do not have a set password before publishing

I have an interface in my website where a user can create an "unclaimed" order for another user and send them an invitation. The invitation link will be formatted as /enrollment/orders/:_id, where _id is the id of the order.
One catch is that this can be sent multiple times. On the first time, the user that is invited might not have a password set.
Meteor.publish('enrolled_order', function (token) {
// if user has their password reset, token will also be set for user's account
return Orders.find({
'enrollment.token': token
});
});
Here's the catch: During this publication, I want to check certain aspects of the user record and take different actions instead of publishing it. For security, I believe this will need to be done on the server to work appropriately.
if there is no this.userId, I want to send them to login.
if the user does not have a password set, I want to redirect them to the reset password page.
Is this possible via a meteor publication?
I think you would need to do this in the router. If you're using Iron Router, you could use onBeforeAction to check whether the user is logged in:
Router.route('/enrollment/orders/:_id', {
subscriptions: function() {
return Meteor.subscribe('enrolled_order', this.params._id);
},
onBeforeAction: function() {
if( ! Meteor.userId() ) {
Router.go( 'loginTemplateName' );
}
}
});
You aren't going to want to return the hashed password directly to the client, so maybe setting a variable to say whether the password exists or not might be the best approach?
I can't remember if onBeforeAction happens before waitOn, but you might be able to get away with waitOn instead of subscriptions.

MEAN / AngularJS app check if object already posted

I have thig angularJS frontend and I use express, node and mongo on the backend.
My situation looks like:
//my data to push on server
$scope.things = [{title:"title", other proprieties}, {title:"title", other proprieties}, {title:"title", other proprieties}]
$scope.update = function() {
$scope.things.forEach(function(t) {
Thing.create({
title: t.title,
//other values here
}, function() {
console.log('Thing added');
})
})
};
//where Thing.create its just an $http.post factory
The HTML part looks like:
//html part
<button ng-click="update()">Update Thing</button>
Then on the same page the user has the ability to change the $scope.things and my problem is that when I call update() again all the things are posted twice because literally thats what I'm doing.
Can someone explain me how to check if the 'thing' its already posted to the server just to update the values ($http.put) and if its not posted on server to $http.post.
Or maybe its other way to do this?
I see a few decisions to be made:
1) Should you send the request after the user clicks the "Update" button (like you're currently doing)? Or should you send the request when the user changes the Thing (using ngChange)?
2) If going with the button approach for (1), should you send a request for each Thing (like you're currently doing), or should you first check to see if the Thing has been updated/newly created on the front end.
3) How can you deal with the fact that some Thing's are newly created and others are simply updated? Multiple routes? If so, then how do you know which route to send the request to? Same route? How?
1
To me, the upside of using the "Update" button seems to be that it's clear to the user how it works. By clicking "Update" (and maybe seeing a flash message afterwards), the user knows (and gets visual feedback) that the Thing's have been updated.
The cost to using the "Update" button is that there might be unnecessary requests being made. Network communication is slow, so if you have a lot of Thing's, having a request being made for each Thing could be notably slow.
Ultimately, this seems to be a UX vs. speed decision to me. It depends on the situation and goals, but personally I'd lean towards the "Update" button.
2
The trade-off here seems to be between code simplicity and performance. The simpler solution would just be to make a request for each Thing regardless of whether it has been updated/newly created (for the Thing's that previously existed and haven't changed, no harm will be done - they simply won't get changed).
The more complex but more performant approach would be to keep track of whether or not a Thing has been updated/newly created. You could add a flag called dirty to Thing's to keep track of this.
When a user clicks to create a new Thing, the new Thing would be given a flag of dirty: true.
When you query to get all things from the database, they all should have dirty: false (whether or not you want to store the dirty property on the database or simply append it on the server/front end is up to you).
When a user changes an existing Thing, the dirty property would be set to true.
Then, using the dirty property you could only make requests for the Thing's that are dirty:
$scope.things.forEach(function(thing) {
if (thing.dirty) {
// make request
}
});
The right solution depends on the specifics of your situation, but I tend to err on the side of code simplicity over performance.
3
If you're using Mongoose, the default behavior is to add an _id field to created documents (it's also the default behavior as MongoDB itself as well). So if you haven't overridden this default behavior, and if you aren't explicitly preventing this _id field from being sent back to the client, it should exist for Thing's that have been previously created, thus allow you to distinguish them from newly created Thing's (because newly created Thing's won't have the _id field).
With this, you can conditionally call create or update like so:
$scope.things.forEach(function(thing) {
if (thing._id) {
Thing.update(thing._id, thing);
}
else {
Thing.create(thing);
}
});
Alternatively, you could use a single route that performs "create or update" for you. You can do this by setting { upsert: true } in your update call.
In general, upsert will check to see if a document matches the query criteria... if there's a match, it updates it, if not, it creates it. In your situation, you could probably use upsert in the context of Mongoose's findByIdAndUpdate like so:
Thing.findByIdAndUpdate(id, newThing, { upsert: true }, function(err, doc) {
...
});
See this SO post.
#Adam Zemer neatly addressed concerns I raised in a comment, however I disagree on some points.
Firstly, to answer the question of having an update button or not, you have to ask yourself. Is there any reason why the user would like to discard his changes and not save the work he did. If the answer is no, then it is clear to me that the update should not be place and here is why.
To avoid your user from loosing his work you would need to add confirmations if he attempts to change the page, or close his browser, etc. On the other if everything is continuously saved he has the peace of mind that his work is always saved and you dont have to implement anything to prevent him from loosing his work.
You reduce his workload, one less click for a task may seem insignificant but he might click it many time be sure to have his work save. Also, if its a recurrent tasks it will definitely improve his experience.
Performance wise and code readability wise, you do small requests and do not have to implement any complicated logic to do so. Simple ng-change on inputs.
To make it clear to him that his work is continuously save you can simply say somewhere all your changes are saved and change this to saving changes... when you make a request. For exemple uses, look at office online or google docs.
Then all you would have to do is use the upsert parameter on your mongoDB query to be able to create and update your things with a single request. Here is how your controller would look.
$scope.update = function(changedThing) { // Using the ng-change you send the thing itself in parammeter
var $scope.saving = true; // To display the saving... message
Thing.update({ // This service call your method that update with upsert
title: changedThing.title,
//other values here
}).then( // If you made an http request, I suppose it returns a promise.
function success() {
$scope.saving = false;
console.log('Thing added');
},
function error() {
//handle errors
})
};

Picking up meteor.js user logout

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

Facebook client side authorization flow and get access_token for all scenarios like when scope for an app changes, etc

I am trying to do 3 simple things using JS thru a client side authorization flow :
Check if the user is logged in. If not, display the login dialog with proper scope.
If user is already logged into Facebook on the browser, then ensure the app has been authorized (if not, display the Authorize dialog).
If user has already authorized the app then ensure that the access_token on the authResponse has the right scope (permissions) required by the app. (if not, redisplay the authorize dialog for the new permissions).
I am trying to leverage js sdk for this, without wanting to write any hand-coded dialog's etc, since the sdk handles the browsers/devices nuances automagically.
I could not find this properly described anywhere either on FB documentation or otherwise. (no documentation around the fact that the permissions/scope for the app can change post authorization).
Thanks you in advance.
After an arduous attempt I have this working and here are the steps to do this with the minimum amount of code.
*The Approach *
Use fb-login-button to facilitate login. Login Button is the only thing that is displayed when the user first arrives on the page. All other forms are 'hidden', forcing the user to click on this button before being able to go forward.
setup onlogin="function();" on the fb-login-button - so that you can hide the login-button and display the rest of the forms/etc for the user to proceed on your app. Please note that onlogin is called only once (either when the user logs in or authorizes the app with the new scope) - so it is safe to always hide the login-button and display the remaining of the page/form for the user to proceed.
The most important step : use the same fb-login-button to also do the authorization with the right scope : so even if the user is logged in - force the user to click the login-button. fb-login-button in this case, will check if the scope of existing access_token (for already logged in fb user), is a subset of that requested on the fb-login-button attribute. fb-login-button will display the authorize dialog for you automatically in this case. In any case the onLogin callback is triggered (when the login-button should be hidden() and the remaining page/forms be shown()).
Subscribe to the FB.events to get the authResponses and the corresponding access_token every time it is set, which happens 2 times :
a. When the fetch is first displayed for already logged in user
b. After the user authorizes the app first time or second time with the new scope.
This allows you to keep the authResponse and access_token fresh to be used for all subsequent graph calls/etc..
*Implementation Steps *
fb-login-button must be created with onlogin callback :
<div class="fb-login-button" onLogin="onLogin();" scope="publish_actions"
data-size="large">Use This App</div>
Please note that the text does not say Login, but 'Use this App', since the button is displayed for authorization in addition to just logging in.
Subscribe to the Events.
// authRepsonse Change is needed when the user hits the UseApp button
// and permissions are re-fetched - then a new token needs to be refreshed
FB.Event.subscribe('auth.authResponseChange', handleResponseChange);
// when the user logins and all permissions are all set, clicking on the
// use-app will not do anything and authResponse will always be null.
// so this subscription is necessary
FB.Event.subscribe('auth.statusChange', handleResponseChange);
Login Function to hide Login button and display form.
function onLogin () {
hideLogin();
showMain();
}
Handle Status Changes to save Token.
// global variable that contains the access_token
// for use in graph calls/etc.
var authResponse = null;
function handleResponseChange(response) {
if (response && response.authResponse) {
authResponse = response.authResponse
if (response.status == 'connected') {
updateUserInfo(response);
return;
}
}
else {
authResponse = null;
}
hideMain();
showLogin();
}
Hope this helps and I believe that this is probably the most optimal way of handling client side authentication flow without doing any extra authorization/login dialog and let FB sdk do the trick.

Categories

Resources