Meteor user permissions - meteor

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

Related

today is good day [duplicate]

Precondition
$npm install --save firebase#4.11.0
issue
I'm using firebase authentication on my web application.
In my app, I implemented onAuthStateChanged for client side js like below.
firebase.auth().onAuthStateChanged((user) => {
if(user) {
//logged in
} else {
//do sth
}
});
After login, I confirmed this method will return actual user obj, but if I refresh the page, then user might be null.
Curiously, sometimes user won't be null.
I'm afraid there are some limitation of calling onAuthStateChanged, but currently I have no idea.
How should I deal with this issue?
update
Let me share my minimal example.
My app is working with express.js.
There are two URLs like below.
/login
/main
In the login page, I implemented authentication method.
If the login is successfully finished, then user will be redirected to '/main'.
//login.js
import firebase from 'firebase';
var config = {...};
firebase.initializeApp(config);
var provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider)
.then((result) => {
return result.user.getIdToken(true);
}).then((idToken) => {
if(idToken) {
location.href = '/main';
}
});
In the main page, there is no login method.
main.js is only checking whether user is logged in.
//main.js
import firebase from 'firebase';
var config = {...};
firebase.initializeApp(config);
firebase.auth().onAuthStateChanged((user) => {
if (user) {
//initialize main page.
} else {
location.href = '/login';
}
}
I think login status is stored on LocalStorage of web browser.
This means that, after finishing loading of main.js, onAuthStateChanged will be automatically fired with user information, but not working as I expected.
I'm sure that persistence of login information is correct because official document says the default setting is LOCAL for web client.
https://firebase.google.com/docs/auth/web/auth-state-persistence
my question
Should I implement onAuthStateChanged with another way?
How can I ensure user is logged in after reload?
e.g.
import $ from 'jquery';
$(document).on('ready', () => {
onAuthStateChanged((user) => {...});
});
Or could you show me the correct way?
Workaround
I decided to remove session and set redirection to login page if null is returned. This is not a solution, but a workaround currently...
You're not calling onAuthStateChanged. Instead you're telling Firebase to call you when the authentication state changes, which may happen a few times when the page is being re-loaded
When a page is getting loaded and there was previously a user signed in, the auth state may change a few times, while the client is figuring out if the user's authentication state it still valid. For that reason, you may see a call with no user before seeing the final call with the actual signed in user.
The fact it's sometimes null and sometimes not null likely points to an async problem. Are you making the check in the if statement above? All references to the user should be within the callback. If that all checks out, maybe check that authentication is being properly initiated.
onAuthStateChanged is an observer as stated in firebase docs, which gets triggered when the auth state is changed like user signed in, signed out, pwd change. To check if user is logged in or not you should use firebase.auth().currentUser which will give you the current logged in user. As you said your state is local firebase.auth().currentUser will always give you user unless user is signed out.

Nuxt Middleware with Firebase and FirebaseUI: Error: Redirected when going from "/anything" to "/login" via a navigation guard

Nuxt SSR app using FirebaseUI to handle auth flows. Logging in and out works perfectly. When I add Middleware to check auth state and redirect if not logged in I get this error:
Error: Redirected when going from "/list-cheatsheets" to "/login" via a navigation guard.
middleware/auth.js
export default function ({ store, redirect }) {
// If the user is not authenticated
if (!store.state.user) {
return redirect('/login')
}
}
There is absolutely no other redirecting that I can find in the app....
I have been digging and trying things for hours. Others who get this error that I have found aren't using Nuxt and none of those solutions work.
As there is a bounty one cannot mark it duplicate thus following up is a copy of my answer at Redirecting twice in a single Vue navigation
tldr: vm.$router.push(route) is a promise and needs to .catch(e=>gotCaught(e)) errors.
This will be changed in the next major#4
Currently#3 errors are not distinguished whether they are NavigationFailures or regular Errors.
The naive expected route after vm.$router.push(to) should be to. Thus one can expect some failure message once there was a redirect. Before patching router.push to be a promise the error was ignored silently.
The current solution is to antipattern a .catch(...) onto every push, or to anticipate the change in design and wrap it to expose the failure as result.
Future plans have it to put those informations into the result:
let failure = await this.$router.push(to);
if(failure.type == NavigationFailureType[type]){}
else{}
Imo this error is just by design and should be handled:
hook(route, current, (to: any) => { ... abort(createNavigationRedirectedError(current, route)) ...}
So basically if to contains a redirect it is an error, which kinda is equal to using vm.$router.push into a guard.
To ignore the unhandled error behaviour one can pass an empty onComplete (breaks in future releases):
vm.$router.push(Route, ()=>{})
or wrap it in try .. catch
try {
await this.$router.push("/")
} catch {
}
which prevents the promise to throw uncaught.
to support this without redirecting twice means you put the guard to your exit:
let path = "/"
navguard({path}, undefined, (to)=>this.$router.push(to||path))
which will polute every component redirecting to home
btw the router-link component uses an empty onComplete
Assumption that redirecting twice is not allowed is wrong.

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.

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.

Resources