How to make FlowRouter wait for users collection on the client - meteor

I'm writing a section of my app which requires the user to have the 'operator' role.
I'm checking for this in FlowRouter's triggersEnter function. I want that the user which doesn't have the operator role to be shown a restricted access page.
I use FlowRouter, Roles and brettle:accounts-deluxe which auto logs in as guest every visitor.
Here is my code, routes.js:
FlowRouter.route('/switchboard', {
name: 'switchboard',
triggersEnter: [function (context, redirect, stop) {
if (!Roles.userIsInRole(Meteor.userId(), ['operator'])) {
BlazeLayout.render('main', {
content: 'restrictedAccess'
});
stop();
}
}],
action: function () {
BlazeLayout.render('main', {
content: 'switchboard'
});
}
});
Everything works as expected on localhost, but when the app is deployed using mup, on a server, at the time triggersEnter is ran, Meteor.user() is undefined (Meteor.userId() returns ok), and the result of Roles.userIsInRole call is false, although looking in the database it's clear the user has the operator role.
I think the users subscription is not available at the time triggersEnter is ran meaning that the users collection is not published on the client. I have this feeling because if i access the route by clicking on a link the userIsInRole result is ok, but if I refresh the page I get the problem described.
I would like to know why is this happening only on the server and how can I fix it.

The reason is that FlowRouter triggersEnter is not blocking templates from rendering and it makes checking roles before Roles collections are subscribed. Solution is to use FlowRouter.wait() on app init and then make the global subscription for Roles (you need it global - not tied to template level) collection and call FlowRouter.initialize() when its ready.
That way FlowRouter will wait for your collection and will be initialized after it's ready to check.
Update
On localhost there is much less latency between local db and app. When your app is deployed is takes more time for client to fetch data from database. In result on localhost your collection is ready when FlowRouter initializes and on deployed app it isn't.

Use the Template.subscriptionsReady flag
<template name="blogPost">
Back
{{#if Template.subscriptionsReady}}
{{#with post}}
<h3>{{title}}</h3>
<p>{{content}}</p>
{{/with}}
{{else}}
<p>Loading...</p>
{{/if}}
</template>
check full doc here:
https://kadira.io/academy/meteor-routing-guide/content/subscriptions-and-data-management/with-blaze
to see how to handle individual subscriptions

Related

Hijacking the Meteor accounts-ui logout button

I am using the accounts-ui package for Meteor to create a Sign-up/Log-in widget. I want users who are not signed in to be able to continue to use my app anonymously, so I want to detect when a user signs out.
As far as I can tell, there is a way to register a function when the user logs in but no similar event is triggered when the user logs out. The next best thing is the Meteor.logout(\[callback\]) command, which accepts a callback function.
I have found the following lines of code in /Users/<name>/.meteor/packages/accounts-ui-unstyled/.1.1.8.cfkrwq++os+web.browser+web.cordova/web.browser/login_buttons.js
Template.loginButtons.events({
'click #login-buttons-logout': function() {
Meteor.logout(function () {
loginButtonsSession.closeDropdown();
});
}
});
I want to add a call to a method of my own here, but I don't want this method to be called in all the projects where I use accounts-ui. I understand that I could copy the accounts-ui-unstyled/ folder to the packages folder at the root of this project, and modify it there, but then I will miss any updates that may be delivered for the package.
What is the best-practice method of intercepting the log-out call?
Another approach is just to track the logged-in state in a Tracker:
Tracker.autorun(function(){
if ( Meteor.userId() ){
... do things for a logged-in user
} else {
... do things for a logged-out user
}
});
This autorun block will run automatically whenever the login state changes as Meteor.userId() is a reactive data source.

Meteor user permissions

I'am trying to check the users permission (roles) in the routing (iron routing). I have a boolean property in the user which I set in the
Accounts.onCreateUser
e.g. isvalid = false. The first problem is that only username and id are exposed so I try too publish
Meteor.publish('userData',function() {
return Meteor.users.find({_id:this.userId},{fields:{'isvalid':1}});
});
In the router I check
onBeforeAction: function() {
if(!Meteor.user() || !Meteor.user().isvalid)
this.render('nopermission');
else
this.next();
}
It works but when I debug I can see that the onBeforeAction fires three times. First time the user is undefined, second time I have a user without the property isvalid and third time I have everything. In debug I can see the screen flash with the template "nopermission" but when I run it live it seems ok. I think I have done it wrong, how can I check the permission in the correct way? I know that this code runs in client and I plan to do the check even on the server and I suppose that the Meteor.user().isvalid works without any problems on the server.
Thanks for help

Firebase authWithOAuthRedirect() woes

I'm trying to update my angularjs app to support Firebase 1.1 (I was stick with Firebase 1.0.x).
It deprecates firebasesimplelogin, including authentication inside Firebase core.
I have been able to successfully implement authentication using
authWithOAuthPopup("<provider>", function(error, authData) { ... });
It accepts a callback, which is passed authentication data in authData.
On the contrary, I can't undersand how to use
authWithOAuthRedirect("<provider>", function(error) { ... });
Firebase Authentication docs page is very concise... :-(. This is all what is said:
Alternatively [instead of authWithOAuthPopup], you may prompt the user to login with a full browser redirect, and Firebase will automatically restore the session when you return to the originating page
How do I get authData, when Firebase - after redirection - returns to my page?
The authData is available by registering a listener directly on the ref (so before calling authWithOAuthRedirect).
ref.onAuth(function(authData) {
...
}
ref.authWithOAuthRedirect("google", function(error) { ... });
See https://www.firebase.com/docs/web/guide/user-auth.html#section-monitoring-authentication
I think I'm running into the same issue as you. I'm trying to do Facebook authentication.
First, I'd like to clarify the reproduction steps for my issue.
My app is loaded on the client.
User clicks login with Facebook.
ref.authWithOAuthRedirect('facebook', ...) is called.
Client is redirected to Facebook and Facebook redirects client back to Firebase app
Despite successful authentication with Facebook, the callback passed to onAuth() is invoked (only once) with authData === null.
The callback passed to onAuth() is not invoked a second time with correct authData.
However, reloading the app causes the callback passed to onAuth to be invoked with correct authData. The reasons for this are not known to me but I suspect race condition.
Here's my workaround.
Before calling ref.authWithOAuthRedirect('facebook', ...) set yourself a flag in sessionStorage.
sessionStorage.reload = true;
ref.authWithOAuthRedirect('facebook', ...)
When the client is redirected to your app back from Facebook, you should be able to check for this flag and reload the page if necessary.
if (sessionStorage.reload) {
delete sessionStorage.reload;
setTimeout(function() {
location.reload();
}, 1000)
}
setTimeout(function() { ... }, 1000) helps fight the assumed race condition. I found 500 ms is insufficient time for the race condition to be resolved.
And one small gotcha: if you reload the page too soon, then authData remains null no matter how many times you reload the page.

Race condition when going directly secured page

I am running into a race condition when an unknown user is trying to access a secured page.
Iron-Router code:
function secured() {
if ( Meteor.user() == null ) {
Meteor.loginWithLinkedin({
},function (err){
if(err){
console.log("Error when login with LinkedIn."+JSON.stringify(err));
}
});
}
}
Router.map(function () {this.route('customer_researchRequest', {
before: secured,
waitOn: waitOnHuman,
path: '/research/request',
template: 'customer_researchRequest',
layoutTemplate: 'customer_requestLayout'
});});
On the server:
ServiceConfiguration.configurations.remove({
service: 'linkedin'
});
ServiceConfiguration.configurations.insert({... settings ...});
If the user goes directly to /research/request, there is a race condition.
before condition fires
(on client)ServiceConfiguration.configurations has no configuration
client has exception about no linkedin service defined.
server publishes the ServiceConfiguration.configurations to the client
At this point, my solution is to hard code in the clientId and other linkedin config information into the linkedin authentication code ( Yech ).
Is there a better more elegant/correct solution?
Update #1: My solution was to tweak the meteor-linkedin package so that it expects the linkedIn clientId as an option and does not depend on the ServiceConfiguration.configuration. This way the clientId is always available.
Edited to address comment:
Maybe a different use of reactivity can help. Set up a deferred redirect to customer_researchRequest, by first diverting the user, then bringing them up
A) Have secured() save the original destination path to the session. Redirect to a page you allow without security (or a 'Loading...' page), to avoid your #3
B) when the login callback happens, save another flag to the session, indicating that #4 is no longer true
C) have a Deps.autorun redirect to the desired path when both flags become true.
Someone else may know a smarter way, (maybe waitOn should test for the config) but ...
The best solution turns out to be my "hack" of creating a forked meteor-linkedin which accepts the client configuration in the login call.
We edited the meteor-linkedin so that the Meteor.loginWithLinkedIn() call supplied the linkedIn clientId.
Currently, Meteor's ServiceConfiguration is stored in a mongo table and needs to be published from the server to client. The clientId is essentially a static configuration variable that might as well be encoded into the client code. Just putting the linkedin clientId directly in the login code turns out to be infinitely more reliable and simpler.
Even if Meteor was to 'fix' the publishing race condition, we would stick with our solution: it is bulletproof and guaranteed to work. You can borrow our code our meteor-linkedin and accounts-meteor-linkedin
The meteor dev people aren't planning on fixing the issue. I agree with this decision, it is much better to just have the (constant) client configuration on the client rather than being stored on the server and sent to the client.
Update: In the end for a variety of reasons, we almost entirely abandoning the meteor oauth code. The client-side centric approach with popup dialogs caused numerous problems. I talk about some of the issues on the 1911 bug report. We ended up triggering the oauth code ourselves server-side.

Meteor User Property

I'd like to be able to mark users as "admin" in the Meteor auth system, and allow that user to do special things, as well as show some gui elements I wouldn't show if they weren't an admin. I've already tried setting an "admin" property on the user object, which would work fine on the server side (for the RPCs for the admin actions), however I need to access it on the client side to change the rendering of the page, and it appears only emails is sent with the Meteor.user() object.
Basic question: How can I assign a user-specific property that both the server and client can see?
To anyone in the future, simply assign the value in the users table in the database. You can publish additional fields to the user using the following:
Meteor.publish("userData", function () {
return Meteor.users.find({_id: this.userId}, {fields: {'admin': 1}});
});
And on the client:
Meteor.subscribe("userData");
Poof. Straight from the documentation.
You can also add properties in "profile" attribute of the user. profile attribute is alrealy populated to client side :
Meteor.users.update({_id: userId}, {$set: {'profile.admin': 1}});
//on client side
Meteor.user().profile.admin

Resources