I implemented my own login system, because I'm using a third party web service to authenticate users against an enterprise authentication system. As such, I built a form that calls a server method to make the web service call to the auth system, and if the credentials are valid, it sets a session variable with the user's id. This is how I change the template to show the main screen of the application and not the login screen. Works fine. And the logout button then just sets that userid session variable to false, effectively hiding the main application screen and showing the login form again.
<body>
{{#if loggedInUser}}
{{> navbar}}
{{> mainScreen}}
{{else}}
{{> customLogin}}
{{/if}}
</body>
Template.navbar.helpers({
loggedInUser: function () {
return Session.get('userName');
}
});
'click #logoutButton': function () {
Session.set("userName", false);
}
What I have discovered though, is that the local minimongo collections/subscriptions are still in the browser, and accessible in the console, after the user logs out.
I did some searching but didn't find concrete solutions as to how to properly clear out (or stop?) these subscriptions on the client. In fact, the top 3 hits on a search for "meteor publish subscribe " don't mention stopping or security upon logout.
One suggestion on SO was to save the subscription handle ... but I'm calling subscribe multiple times, so it seems I would have to store up an array depending on how many different subscribes the user triggered during their use of the application, and then go through them calling "stop" on each handle when logging out??
I'm hoping there's a simple way to stop all subscriptions... seems like a logical thing to do for security when a user clicks a logout button.
Thanks!
Could you not use .stop() function on the collection?
var subscription = Meteor.subscribe("info");
//on logout
subscription.stop();
According to the docs:
stop()
Cancel the subscription. This will typically result in the server directing the client to remove the subscription's data from the client's cache.
Updated: Maybe check out this package: Subs Manager. It appears they may be able to do what you want, specifically from their readme:
Clear Subscriptions
In somecases, we need to clear the all the subscriptions we cache. So, this is how we can do it.
var subs = new SubsManager();
// later in some other place
subs.clear();
Related
I'm writing a licence validation part for my application and want to redirect the user to a renewal page if and only if their licence has expired.
I am using FlowRouter and Blaze.
All my authenticated routes are in a group:
let authenticated = FlowRouter.group({
triggersEnter: [checkAuthenticated, checkSubscription]
});
I then check if the subscription is valid like so:
const checkSubscription = function(context){
let path = FlowRouter.current().path;
if (!Meteor.userId()){
return;
}
const sub = new Subscription();
if (sub.isInvalid() && path !=="/manage-practice/subscription"){
FlowRouter.go("/manage-practice/subscription");
}
};
My class subscription uses a collection that I can only load once a user has logged in. My problem is that the router usually triggers this redirection before this data has been loaded.
Is there a best practice approach to solve this?
Redirect with Triggers
I'm not sure about this being 'best practice' but one approach is to use the Flow Router redirect functionality on your login event.
You can see examples at: https://atmospherejs.com/kadira/flow-router#redirecting-with-triggers and https://github.com/meteor-useraccounts/flow-routing.
The initial login path (using Accounts.onLogin();) could be to a generic 'loading...' template while you evaluate the user's collection. On a callback you can then use the custom redirect function to either redirect to the requested page in your app, or redirect the user to your '/manage-practice/subscription' path.
FlowRouter.wait()
I have to confess I wasn't previously familiar with this second option, but I've just come across FlowRouter.wait(). This can be useful to delay the default routing process until some other evaluation is complete. I suspect this might only be relevant if a user logs directly into a page within your authenticated routing group.
Documentation: https://atmospherejs.com/kadira/flow-router#flowrouter-wait-and-flowrouter-initialize
I am using Meteor with Iron Router and Stripe. Everything is working great, but I cant figure out how to re-direct user to a final order complete page after the Stripe charge is completed.
On my client side I have a modal box that appears which contains a button that says, "Pay" When the Pay button is clicked an event is fired that calls up and opens Stripe Checkout.
The Stripe Checkout then initiates on the client and the user is able to enter the card details and submit the payment. The server side method for charging the card thru Stripe is completed and I also have some other basic database tasks that are being performed to log the result and complete the order status.
I have created a route using Iron router that I want the user to be re-directed too after the Stripe Payment is completed.
As of now the modal box continues to stay on the screen. I am trying to make the Router.go send user to the order page that had been setup after the order is finished.
I beleive the Iron Router Router.go is used client-side only. How can I complete the order process and make the client-side modal box disappear after the Stripe charge is completed and re-direct user to a final complete page.
When I use the Router.go on server-side I am getting error:
Exception in callback of async function: TypeError: Object function router(req, res, next) {//XXX this assumes no other routers on the parent stack which we should probably fix
You can't force the user's browser away from it's page if you don't already have some logic on the page to "remote control it". Fortunately there are a number of options for the latter:
If you are using a router you could make the route itself reactive, depending on the content of a subscribed collection. Alternatively, you can have an Tracker.autorun or an observeChanges block on your client side code that checks for changes to that "control" collection and then execute Router.go accordingly.
As for the control collection, a simple collection like:
var Control = new Mongo.Collection('control');
would do, and then you insert into it from the server when the event occurs (Control.insert({route: "newroute"})), and check for content changes on the client, for instance like so:
Control.find().observeChanges({
added: function(id, doc) {
Router.go(doc.route);
});
I'm attempting to hook into the Friends of Symfony User Bundle events with a custom event listener. There are many examples of creating a event listener for the FOS User Bundle and I've had no trouble creating the listener, receiving the event and executing my custom code. I am listening for the FOSUserEvents::REGISTRATION_CONFIRMED event and sending out some notification emails regarding the newly confirmed user.
But there is one problem with my approach. Namely:
Without my event listener the user is logged in and redirected to their profile page after they visit the confirmation page. However the presence of my event listener stops the normal event propagation, and the user is not logged in or redirected away from the confirmation page.
Is this expected behavior? I see contradictory information in the documentation.
The FOS User Bundle Docs are ambiguous on the point. Although they seem to imply from the code example here that custom events stop normal propagation, or at least replace the normal action for a given event.
While the Symfony Event documentation here describes the stopPropagation function that exists for the purpose of blocking event propagation, but only when necessary.
So Do User Bundle custom event listeners block propagation to the normal event that comes built in? If yes, is there anyway around this? So custom code that does not need to modify the request or response can simply listen for the event and not affect the normal actions that come with the User Bundle.
To answer my own question:
FOS User Bundle custom event listeners have no effect on the ones that come with the bundle. They will all be processed and run as you would expect from reading the symfony docs.
It is worth noting that the User Bundle documentation I referenced above is a little hand-wavy about what the custom event is supposed to be used for. If you examine the controller code in the user bundle that is dispatching each of the events - after the "pre-action" events are dispatched, and event listeners are processed (including any custom ones you register), the controller examines the event object extracting specific properties that control how the action is complete. In the example given in the documentation the custom listener for the RESETTING_RESET_SUCCESS event is using event->setResponse. Here is the relevant section of code from the resetAction in the ResettingController:
$dispatcher->dispatch(FOSUserEvents::RESETTING_RESET_SUCCESS, $event);
$userManager->updateUser($user);
if (null === $response = $event->getResponse()) {
$url = $this->container->get('router')->generate('fos_user_profile_show');
$response = new RedirectResponse($url);
}
$dispatcher->dispatch(FOSUserEvents::RESETTING_RESET_COMPLETED, new FilterUserResponseEvent($user, $request, $response));
return $response;
The only thing a custom listener can do to effect the built in controller action, is to pass a RedirectResponse object back in the event, which the controller will then use as the return value for the action - redirecting end users to your chosen URL.
So the final mystery is why my custom event listener on the REGISTRATION_CONFIRMED event broke automatic login after the email confirmation link was visited. The root cause of that problem was a nasty race condition bug in Symfony itself. The bug was fixed late last year but I am working on a client's application and they are still running an older version of symfony that has the bug. In short, sending an email with swiftmailer, and spooling the email to memory, could cause a race where the HTTP response is returned before the session file is written out. A subsequent redirect to the client would result in them loading the older - non-logged in - session file, giving the appearance of not being logged in for that next page load.
I'll post the details about the bugs in a second answer since stack overflow is apparently afraid of too many links in one answer.
The symfony bug mentioned in my previous answer has been fixed in 2.3.22, 2.5.7 and 2.6 and up. If you are curious here are the github issues that deal with it:
The bug report: https://github.com/symfony/symfony/issues/6417
The fix:
https://github.com/symfony/symfony/pull/12341
If you are unable to upgrade symfony to a fixed version, the workaround is to stop swiftmailer from spooling to memory (i.e. send immediately, before a response is returned to the browser). Comment out this line in your swiftmailer configuration:
# spool: { type: memory }
I want to integrate Google's Oauth2 in my symfony-1.4 CRM. I have successfully implemented this, I have extended sfGuardAuth in my own module, and now mysfGuardAuth is being used for siging and signout. Is there where I handle Google's Oauth2 with 2 extra actions:
executeCkeckGoogleAccess();
executeOauth();
The problem is to checkout if Google's token is still a valid one, I have to redirect in each action of everymodule to the action checkGoogleAccess in mysfGuardAuth module.
What I want is to check this in an implicit way in the same place where symfony, or sfGuard or whatever checks for the right perms or credentials before executing or not executing the requested action.
I only want to write the code once.
Thank you.
After some research this is how sfGuard checks everything.
When you make a request to a module action, before the action is executed, a new sfContext is dispached.
The sfContext gets the user that extends sfGuardUser and has some methods that are executed. There is where perms, session status and everithing else is checked
The user must be configured in apps/yourApp/lib
By default is apps/yourApp/lib/myUser which extends sfGuardUser. The most apropiate way to achieve this is to create a new user class like: apps/yourApp/lib/yourAppUser which extends aswell sfGuardUser, and there extend the methods initialize and/or shutdown with the functionality you want.
By this way I have achieved to get Google's Oauth2 working in my app.
I hope this is usefull for more people.
UPDATE
All described above is true, but if you want to check something always before an action execution you must use filters instead of whats described before.
Filters are executed before each action, so there you can checkout whatever you need having access to the current context, and set up new attributes for the user. In my case I wanna check if the requested action needs a google token, if true, then Another filter will check if the user has alraedy a valid token, in that case, nothing happens, otherwise, the user is redirected to the module/action which handles google token requests.
Comunication between diferent filters, actions and requests are handled via user attributes.
the user is an object of the clas myOwnUser which extends sfGuardSecurityUser, there the function signOut is extended in order to delete all attributes saved in "myOwnNamespace"
Using meteor, I'd like to be able to augment a user record after they've logged in (authenticated) with an external service to get their authorization claims.
Update
I am using the {{loginButtons}} handlebars helper widget.
Currently, I see an Accounts.validateNewUser and an Accounts.onCreateUser that can be hooked into during the creation of a new user. These would be helpful initially, but my need is recurrent.
I understand that there is the allow function that hangs off the Meteor.Collection as a means of authorizing a user's access to the collection -- which is precisely where I would use the claims that I intend to augment the user with to determine authorization.
Does anyone know of a hook during the login process that would allow me to do this?
The easiest way to get around the lack of a callback is to use the relevant reactive variable:
Tracker.autorun(function() {
if (Meteor.userId()) {
// do something when they've just logged in.
}
});
The context setup by autorun will only re-run when the value of Meteor.userId() changes -- i.e. when they login.
Starting with Meteor 0.7.2 version, there's a server side hook available :
Accounts.onLogin()
From the Meteor docs on login with password there appears to be a callback already in place for what you need to do:
Meteor.loginWithPassword(user, password, [callback])
callback Function
Optional callback. Called with no arguments on
success, or with a single Error argument on failure.
The login callback is supported for both Meteor and external authentication services.
#Makita, thank you for the answer. I did see that callback param but what I failed to mention in my question was that I had no low-level hook to it because I am using the {{loginButtons}} handlebar helper to inject the user management widget (which is way awesome).
The problem with this approach was that I did not have access to a callback after authentication happened, so I created this pull request which I hope will be merged to solve the issue:
https://github.com/meteor/meteor/pull/479
With this, you should be able to call:
Accounts.ui.config({
onSuccess: function (err) {
//perform addl authorization on Meteor.user() here
}
});
For people coming to this in 2014, You can use the onLogin callback server side
Accounts.onLogin((obj)->
user = ob.user
)
For the accepted answer, there was an issue on page reload that messed up that solution. I ended up doing something like this (Its using angular meteor but you should get the gist, just substitute autorun for Tracker)
.run(function($meteor,$rootScope,$state,$localstorage){
$meteor.autorun($rootScope, function(){
var id = Meteor.userId();
if(id == undefined || id == null){
id = '';
}
if($localstorage.get('user_id','') != id){
$localstorage.set('user_id',id);
if(Meteor.userId()){
//On login
$state.go('profile',{user_id: Meteor.userId()});
}else{
//On logout
$state.go('main');
}
}
});
});
I only recommend this solution for development, when I stop using the default accounts-ui I'm going to have to implement the lower level functions and there will be no need for this.