Meteor: Restricting an admin route only to admin roles - meteor

Im trying to restrict a route to only users whose roles are admin
Router.route('/admin', {
if(Roles.userIsInRole(Meteor.user(), ['admin'])) {
template: 'admin' };
else
template: 'restricted'
});
returned with unexpected token

The template Iron Router option is for the simple case when you just need to route to a constant template, that will never change nor need any specific parameter.
If your route is more complex (as in your case when you return a different template based on the current user's role), you have to use the action router option instead.
Note that if you are using Iron Router, the new syntax is Router.route('/path', actionFunction)

Managed to get it working thanks to ghybs's suggestion. Ive updated it to
Router.route('/admin', {
action: function() {
if(Roles.userIsInRole(Meteor.user(), ['admin'])) {
this.render('admin') }
else
this.render('denied')
}
});
If someone can provide a more tight & secure code please do input :D Thanks

Related

Meteor: How do I only allow the user to access on page?

I am using iron router, and I have a special type of account (certain value in Meteor.user().profile) and I am wondering if there is a way using iron router, that instead of having to put a restriction in every single route (except for home. login, etc) that I can simply only allow them one page?
Is there a way to do this?
Check out this page... http://www.manuel-schoebel.com/blog/meteorjs-iron-router-filters-before-and-after-hooks
Basically, you can setup a global onBeforeAction hook that will run before each route, then redirect the user if needed to a default route. You can also use before for specific routes, here's the example from the page...
////////////////
// BeforeHooks
////////////////
// I use an object that contains all before hooks
var IR_BeforeHooks = {
isLoggedIn: function(pause) {
if (!(Meteor.loggingIn() || Meteor.user())) {
Notify.setError(__('Please login.'));
this.render('login');
pause();
}
},
somethingForAnyRoute: function() { ... },
...
// add more before hooks here
}
// (Global) Before hooks for any route
Router.onBeforeAction(IR_BeforeHooks.somethingForAnyRoute);
...
// Before hooks for specific routes
// Must be equal to the route names of the Iron Router route map
Router.before(IR_BeforeHooks.isLoggedIn, {only: ['userAreaA', 'userAreaB']});
Also, triggering your route protection from something in Meteor.user().profile can be a bad idea, since the user can access and change anything in profile from the client console. This should be directly in the Meteor.user() object for security, then it is looked at server side. I'm not an expert in this area, so more research might be required if security is a concern.

Run code in specific part of URL in Iron Router

I have an application which makes heavy use of features, separated by specific users, which have different roles.
The problem is that I want to restrict access to some templates, if, for instance, the user is not an Admin.
Currently, I have this:
Router.route('createUser', {
path: '/admin/users/',
onBeforeAction: function() {
if(!isAdmin()) {
Router.go('/');
}
this.next();
}
});
But, specifying that if(isAdmin()) call to every other route is a pain. I want to know if there is any other easy and less error prone way to do it.
Maybe some regex magic would do, but I don't seem to find any examples of use.
First i will recommend you to read this meteor:common-mistakes on the profile editing part
So i will recommend you to better use the alanningroles-meteor package.
Is super easy to use, here is a Online DEMO and the Source Code if you have doubts.
On the router level you can create an onBefore hooks like this.
isAdmin = function(){
var currentUser = Meteor.user()
isUserSuperAdmin = Roles.userIsInRole(currentUser,'Super-Admin'); //using alaning roles.
if(isUserSuperAdmin){ //or use isAdmin();
this.next();
}else{
this.render('accessDenied')
}
}
Router.onBeforeAction('accessDenied', {only: ['admin','otherAdminRoute',etc]});
You can have an onBeforeAction hook combined with only for all routes like so:
var isAdmin = function() {
// Whatever logic you have for checking admin
if (!admin) {
Router.go("/");
}
this.next();
}
Router.onBeforeAction(isAdmin, {
only: ["admin/users"] // Specify other admin templates here
});

First Sign-in after Signup Show Config Page Once Effectively

I'm trying to show a popup or a template page if user has signed in for the first time after sign up basically allowing them configure some stuff on that page before going to dashboard home, It's only needed for convenience and here is what I got (telescope code)
Router.onBeforeAction(hasCompletedChannels);
hasCompletedChannels: function() {
if(!this.ready()) return;
var user = Meteor.user();
if (user && ! userCompletedChannels(user)){
this.render('connectChannels');
} else {
this.next();
}
}
Which I don't really like because this will always run every time, I want it to run just once, And don't even execute the check function. Is it possible to detect first sign in? (After signup)
I think you could just tie it to the specific route. Right now you're tying it to the Router object (every render forces that check as you point out). So if you define your login function to send someone to a specific route after sign-in, you could just verify on that route.
The function Accounts.onLogin gives you a way to do stuff after the login.
Something like
Router.route('profile', {
path: '/profile',
onBeforeAction: function() {
// Check some stoof
// If first time logged in
// render first time template
// else
// this.next() will render the profile page
},
waitOn: function() {
return [
// some subs
];
},
data: function() {
// some data
}
});
I'm assuming that its going to get routed to a page called profile (seems to make sense). You could check for first time logged in by some attribute you use in the user object and the fields you want filled out and force a render of a different template, or a subtemplate. Check out the Iron Router guide for more ideas on ways to configure it.
Best of luck

How to restrict a route to an admin user in Meteor?

as an iOS developer primarily, I'm very new to webdev. I'm looking into Meteor and have some questions regarding routing -- my apologies if they are very easy.
I am using the Meteor Router package to create routes, but I would like to have some pages only accessible to the admin user.
Meteor.Router.add({
'/' : 'home',
'/admin' : 'admin'
});
So I have a simple route setup as above, but I'm not sure how to restrict access to the /admin route.
Is it as simple as something like this? What would be a good way to restrict the route to the /admin page and show a warning or perhaps even redirect them back to the / page?
Thank you!
client.html
<head>
<title>My App</title>
</head>
<body>
{{renderPage}}
</body>
<template name="home">
{{greeting}}
</template>
<template name="admin">
{{greeting}}
</template>
client.js
Template.admin.greeting = function () {
var currentUser = Meteor.user();
if (null !== currentUser && 'admin' === currentUser.username) {
return "Hello Admin!";
}
else{
return "Sorry, only admins can see this page";
}
};
The best way to restrict access to a route is with the router itself (rather than pushing the problem to your controller). You have a couple of choices in how you do this:
Routing Function
You could make the /admin route look like:
'/admin': function() {
return {
as: 'admin',
to: function() {
if (Meteor.user() && Meteor.user().username === 'admin') {
return 'admin';
} else {
return 'unauthorized';
}
}
};
}
I'm assuming you have an unauthorized template that renders a 403 page or something informative.
Filter
Alternatively, you can leave your original /admin route as it was and add a filter:
Meteor.Router.filters({
'needsAdmin': function(page) {
if (Meteor.user() && Meteor.user().username === 'admin') {
return page;
} else {
return 'unauthorized';
}
}
});
and use it like so:
Meteor.Router.filter('needsAdmin', {only: 'admin'});
Personally, I like the filter option because it's reusable and it's a little more obvious what's going on.
Another solution is to use Roles package and make sure the user has the 'admin' role before serving data.
$ mrt add roles
Then you can check for roles like with a nice syntax:
if(!Roles.userIsInRole(Meteor.user(), ['admin'])) {
// Redirect...
}
Roles is integrated with the Meteor accounts system and plays nicely with most of the accounts packages.
If you are looking to manage accounts (create/delete Roles and add/remove Roles from a given user) I've created the package Accounts Admin UI. The README has a quickstart and some some notes on how to integrate this with other routing packages.
$ mrt add accounts-admin-ui-bootstrap-3
Use the and parameter:
Meteor.Router.add({
'/admin': { to: 'admin', and: function() {
if (!Meteor.user() || Meteor.user().name != 'admin'){
Meteor.Router.to('/');
}
}}
});
Everyone here has made great points on how to protect an admin panel on the router level. Another possibility is to skip the router all together. I've recently done this with Meteor Candy, a drop-in admin package for Meteor.
The idea is, you could create a Reactive-Dict to hold the state of the admin interface. If you place it into a package, you can ensure that it never collides with your application code. And with the new Dynamic Imports feature, you can virtually keep it off the client until its needed.
Here's how that might work:
<template name="adminPanel">
{{#if show}}
{{> adminPanelUI}}
{{/if}}
</template>
AdminUI = new ReactiveDict();
Meteor.defer(function () {
Blaze.render(Template.MeteorCandy, document.body);
});
Template.adminPanel.helpers({
show: function () {
if (AdminUI.get('show')) {
return true;
}
}
})
On top of that, all you would need is to define the occasion that sets "show" to a truth-y value.

Meteor: redirect a user if already logged in

I'm using meteor-router, and I'd like to redirect a user to /user if he requests / and he is already logged in.
As expected, this just renders the user_index template rather than changing the URL:
Meteor.Router.add
'/': -> if Meteor.userId() then 'user_index' else 'index'
I want to do something like this:
Meteor.Router.add
'/': -> if Meteor.userId() then Meteor.Router.to '/user' else 'index'
update 6/4/14:
This question is no longer relevant, and iron-router should be used instead.
meteor-router is now deprecated. Instead use iron-router which can redirect based on logged in status using:
Router.configure({layoutTemplate: 'mainLayout'});
Router.map(function() {
this.route('splash', {path: '/'});
this.route('home');
});
var mustBeSignedIn = function(pause) {
if (!(Meteor.user() || Meteor.loggingIn())) {
Router.go('splash');
pause();
}
};
var goToDashboard = function(pause) {
if (Meteor.user()) {
Router.go('home');
pause();
}
};
Router.onBeforeAction(mustBeSignedIn, {except: ['splash']});
Router.onBeforeAction(goToDashboard, {only: ['splash']});
Example taken from: Meteor.js - Check logged in status before render
--OR--
Use the accounts-entry package. From their site:
Ensuring signed in users for routes
Use AccountsEntry.signInRequired(this) to require signed in users for
a route. Stick that in your before hook function and it will redirect
to sign in and stop any rendering. Accounts Entry also tracks where
the user was trying to go and will route them back after sign in.
You're looking for a filter -- here is a sample from the docs:
Meteor.Router.filters({
'checkLoggedIn': function(page) {
if (Meteor.loggingIn()) {
return 'loading';
} else if (Meteor.user()) {
return page;
} else {
return 'signin';
}
}
});
// applies to all pages
Meteor.Router.filter('checkLoggedIn');
According to this issue it looks like redirects are not part of meteor-router, and may not be. For now I ended up working around the issue. If the project changes to accommodate redirects I'll update my answer, or someone else can post another answer.
update 1/23/13:
I switched to using mini-pages, which correctly deals with this case and includes a lot of great functionality like layouts.
Meteor Router lets you directly access the response object, so you can just do a 302 redirect. Something like the following will work:
Meteor.Router.add("/test/:_id", (id) ->
this.response.writeHead '302', {'Location': '/blah/' + id}
)
You can do this by using a standard filter and wrapping the redirect in a defer object.
Meteor.Router.filters({
requireLogin: function(page) {
if(! (Meteor.loggingIn()|| Meteor.user()) ){
Meteor.defer(function () {
Meteor.Router.to('/login');
});
}
return page;
}
Meteor.Router.filter('requireLogin', {except: 'login'});

Resources