On my meteor app I have a login system that sends you to the /dashboard path if you log in or sign up successfully. However, right now it is possible to get to the /dashboard path just by typing in localhost:3000/dashboard. How can I prevent this?
In addition to filtering the route with router hooks or custom actions, you may ensure that the template itself is displayed only to privileged users:
<template name="secret">
{{#if admin}}
...
{{/if}}
</template>
Handlebars.registerHelper('admin', function(options) {
if(Meteor.user() && Meteor.user().admin) return options.fn(this);
return options.inverse(this);
});
If you want to show a template to all registered users, you may use {{#if currentUser}} instead, in which case you don't need to register an additional helper.
You can accomplish this using before hooks. Here is a simple example with three routes: index, signin, and dashboard:
Router.map(function() {
this.route('index', {
path: '/'
});
this.route('signin');
this.route('dashboard');
});
var mustBeSignedIn = function() {
if (!(Meteor.user() || Meteor.loggingIn())) {
Router.go('signin');
this.stop();
}
};
Router.before(mustBeSignedIn, {except: ['signin']});
Before all routes except signin, we redirect the user back to the signin page unless they are logged in or in the process of logging in. You can see more examples in the using hooks section of the IR docs.
You need to check the state of the user before each route is run. If the user is not logged in (Meteor.userId() returns null) then redirect the user to the login route.
Router.before(function() {
if (!Meteor.userId()) {
this.redirect('userLoginRoute');
this.stop();
}
}, {
except: ['userLoginRoute', 'userSignupRoute', 'userNewPasswordRoute']
});
I believe you can use custom actions for iron-router. You can check Meteor.userId() if it's null (not logged in) in the custom action, and redirect accordingly.
Related
I'm currently building an app in Polymer which uses Firebase Authentication to login with your Google+ account or Email. When you run the app it will first check if the user already has a session. If not then the user is being routed to the login page which will display a login form and will handle the login by using <firebase-auth> by Polymerfire. The login page will notify the user property upwards to the my-app (app shell). If the user has been successfully authenticated we send them to the homepage my-app. This will fire the routePageChanged observer in my-app.
_routePageChanged(page) {
if(this.user && this.user.email && this.user.refreshToken) {
this.set("noUser", false);
this.page = page
} else {
this.set("noUser", true);
this.page = 'login';
}
}
Okay, so far so good. If we route a user to the login form and let them authenticate, all is working fine. However, if the user already has a session the routePageChanged observer in my-app will fire and won't have a user object yet.
Problem 1
The problem in the example above is that it'll always show the login page for a few miliseconds. We can fix this by adding a timeout (?) on the initial route and show a spinner and wait for the firebase authentication. This seems a little bit hacky as I don't know how long it will take for the user to automatically authenticate? What is the best way to wait for the authentication and then do a route?
Problem 2
In the app-login page I'm handling the login. I've chosen to use a singInWithRedirect as I want the login to be available on mobile (popups are being blocked sometimes). The problem with singInWithRedirect is that it'll also re-render/initialise the whole app after authentication. This means that after creating a valid auth session the routePageChanged observer in my-app will be fired and there won't be a user object for a few miliseconds, which will make the app route to /login.
The _userChanged function in app-login will then route back to the overview on its turn. This will also show the login page for a few miliseconds.
<firebase-auth id="auth" user="{{user}}" provider="{{provider}}" on-error="handleError" on-user-changed="_userChanged"></firebase-auth>
_userChanged(event) {
if (event.detail.value.uid) {
this.set("waitingForAuthChanged", false);
this.set("user", event.detail.value);
window.history.pushState({}, null, "/overview");
window.dispatchEvent(new CustomEvent('location-changed'));
}
}
What's the best way to properly handle all these states? I used to use the signInWithPopup function, this won't initialise the app after authentication. I'm trying to achieve the same with the redirect.
You use routePageChanged observer instead of user object observer. So try to manage all with observer of user:
<firebase-auth id="auth" user="{{user}}" provider="{{provider}}" on-error="handleError" on-user-changed="_userChanged"></firebase-auth>
...
<iron-pages role="main" selected="[[page]]" attr-for-selected="name" selected-attribute="visible" fallback-selection="404" >
<div name="home"><my-home>Home Page</my-home></div>
<div name="signin"><signin-page>Signing page</signin-page></div>
<div name="404"></div>
</iron-pages>
...
<script>
...
static get observers() {
return ['_isUserLoggedIn(user)']
...
_isUserLoggedIn(u) {
if (u) { this.set('signedIn', true);
this.set('page', 'home'); // user signed in, then go home
} else {
this.set('signedIn', false);
this.set('page', 'signin'); // user not signed in, so go sign in page
}
}
</script>
Demo
I use Meteor Dev Tools plugin in Chrome, and I’ve noticed a cool new feature, that is worrying me about the way I've coded my app.
The audit collection tool is telling me that some of my collections are insecure.
I am still using Meteor 1.2 with Blaze
1.
One of them is meteor_autoupdate_clientVersions
1.1. should I worry about this one?
1.2. How do I protect it?
Insert, Update and Remove are marked as insecure.
2.
Then I have a cycles collection, which has marked as insecure: update and remove
This collection is updated on the database now and then but not supposed to be accessed from the frontend, and is not meant to be related to any client interaction.
For this collection I have these allow/deny rules in a common folder (both client and server)
I've tried applying these rules only on the server side, but I didn't see a difference on the audit results.
2.1. Should these rules be only on the server side?
Cycles.allow({
insert: function () {
return false;
},
remove: function () {
return false;
},
update: function () {
return false;
}
});
Cycles.deny({
insert: function () {
return true;
},
remove: function () {
return true;
},
update: function () {
return true;
}
});
2.2. How do I protect this collection?
3.
And then, I also have another collection with an insecure check which is users, where remove is marked as insecure.
On this webapp I don't make any use of users, there is no login, etc.
I might want to implement this in the future, though.
3.1 Should I worry about this collection being insecure, since I don't use it at all?
3.2 How do I protect this collection?
You do not have to allow or deny. Just remove the insecure package from the meteor app.
Then you can use publish/subscribe and methods for data insert, update and delete.
Remove this please fo code from app:
Cycles.allow({
insert: function () {
return false;
},
remove: function () {
return false;
},
update: function () {
return false;
}
});
Cycles.deny({
insert: function () {
return true;
},
remove: function () {
return true;
},
update: function () {
return true;
}
});
For 1.1
This happens while the user is logging.
Basically, issue is not with this but with the login method.
see wait time: https://ui.kadira.io/pt/2fbbd026-6302-4a12-add4-355c0480f81d
why login method slow?
This happens when everytime, your app gets reconnected. So, after the sucessful login, it will re-run all the publications again. That's why you saw such a delay to login hence this publication.
There is no such remedy for this and but this is kind fine unless your app is having a lot of througput/subRate to this method/publication.
For 3.1 :
You do not have to worry about inscure anymore after removing allow/deny and insecure package. But make sure, you write secure methods.
I am using the onEnter functionality on the routes to add some custom logic on authorization in the front-end. Specifically-
If an unauthorized user lands on a page that needs authorization, store the nextPath in a new location state nextPathname and redirect the user to login page. On login, a user will be redirected to the nextPathname. Here, I am explicitly storing the nextPathname in the redux-store.
function requireAuth(nextState, replace) {
if (!s.isLoggedIn(store.getState())) {
replace({
pathname: '/login',
state: { nextPathname: nextState.location.pathname }
})
}
}
If an unapproved user lands on a page that he is not allowed, store the path name at unauthroizedPathname and take the user to a new page and show a relevant error message including the page name. Here, I am just displaying the error message with dismisses after a few seconds.
````
function requireValidation(nextState, replace) {
if (!s.isFinancierVerified(store.getState())) {
replace({
pathname: '/dashboard',
state: { unauthroizedPathname: nextState.location.pathname }
})
}
}
````
I need these states to be present only for the nextPath and not after that. However, it looks like these states are saved in the sessionStorage so they persist even through page reloads.
Is there a way I can disable these states to be stored in sessionStorage? Or any other way I can accomplish the tasks mentioned above?
I try to use a Router from route_hierarchical/client.dart to listen to an onpopstate event and enable/disable a <div> in my index.html. (Example in stagehand.pub dart plugin)
If this is done via normal <a href="/relativePath"> in index.html, it works.
But if I try to change the path via a button.onClick.listen() handler in which I call:
window.location.assign('/relativePath');
I get 404 and the router is not handling my event properly.
Should that that action not invoke a popstate event which is caught by Router like described here?
handlers.dart
...
button.onClick.listen((_){
window.location.assign('/about');
});
...
router.dart
var router = new Router();
router.root
..addRoute(name: 'about', path: '/about', enter: showAbout)
..addRoute(name: 'login', defaultRoute: true, path: '/', enter: showLogin)
..addRoute(name: 'context', path: '/context', enter: showContext);
router.listen();
}
void showAbout(RouteEvent e) {
// Extremely simple and non-scalable way to show different views.
querySelector('#login').style.display = 'none';
querySelector('#about').style.display = '';
querySelector('#context').style.display = 'none';
} ...
index.html
...
<form>
<button type="button" id="submit" disabled="true" >
Login
</button>
</form>
...
onPopState is the wrong event. This event is only fired if you navigate to an existing history entry (back, forward, pushState, go to 2nd entry in history).
What you are looking for is probably the window.onHashChange event.
OK looks like I am not achieving my goal with assuming the above behavior.
Thanks to Günther Zöchbauer for helping.
I filed it with corresponding Github project as I think it should work.
What I now use and what works including history support is
router.gotoUrl('/relativePath')
in the onButtonClick handler.
That totally does it.
I'm starting to learn about Expressjs, Twitter Bootstrap and BackBonejs.
I have created the basic Expressjs app with the command line tool and added an index.html for the sign in form. When the user click on the "Sign in" button which have an event attached, I retrieve the form information and make an ajax call to the '/login' route but it does not work ..
Here a list of the necessary files :
index.html
login.js
server.js
routes.js
Thank you for your help.
The issue is that the form is still submitting via redirect as that's its default bahavior and it hasn't been instructed to do otherwise. And, because of the redirect, the browser is is aborting the Ajax request.
To prevent the redirect, you'll want to bind to the .submit() event of the <form> and use event.preventDefault():
$(document).ready(function () {
$('#login-form').submit(function (e) {
e.preventDefault();
});
});
It may also be worthwhile to use this event for the Ajax rather than the .click() of the <button> as many browsers allow submitting through other actions besides just clicking a type="submit" button (e.g., pressing Enter when focus is on a <input type="text">):
$(document).ready(function () {
$('#login-form').submit(function (e) {
e.preventDefault();
var _login = $('#login').val(),
_password = CryptoJS.SHA512($('#password').val()),
_remember = $('input:checkbox:checked').val() ? 1 : 0;
// etc.
});
});
For one, you need to prevent the default action from the button click:
$('#btn-login').click(function () {
// ...
});
// should accept the passed event and `prevenDefault`, like...
$('#btn-login').click(function (e) {
e.preventDefault();
// ...
});