Meteor: Security in Templates and Iron Router - javascript

I'm enjoying working with Meteor and trying out new things, but I often try to keep security in mind. So while I'm building out a prototype app, I'm trying to find the best practices for keeping the app secure. One thing I keep coming across is restricting a user based on either a roll, or whether or not they're logged in. Here are two examples of issues I'm having.
// First example, trying to only fire an event if the user is an admin
// This is using the alaning:roles package
Template.homeIndex.events({
"click .someclass": function(event) {
if (Roles.userIsInRole(Meteor.user(), 'admin', 'admin-group') {
// Do something only if an admin in admin-group
}
});
My problem with the above is I can override this by typing:
Roles.userIsInRole = function() { return true; } in this console. Ouch.
The second example is using Iron Router. Here I want to allow a user to the "/chat" route only if they're logged in.
Router.route("/chat", {
name: 'chatHome',
onBeforeAction: function() {
// Not secure! Meteor.user = function() { return true; } in the console.
if (!Meteor.user()) {
return this.redirect('homeIndex');
} else {
this.next();
}
},
waitOn: function () {
if (!!Meteor.user()) {
return Meteor.subscribe("messages");
}
},
data: function () {
return {
chatActive: true
}
}
});
Again I run into the same problem. Meteor.user = function() { return true; } in this console blows this pattern up. The only way around this I have found thus far is using a Meteor.method call, which seems improper, as they are stubs that require callbacks.
What is the proper way to address this issue?
Edit:
Using a Meteor.call callback doesn't work for me since it's calling for a response asynchronously. It's moving out of the hook before it can handle the response.
onBeforeAction: function() {
var self = this;
Meteor.call('someBooleanFunc', function(err, res) {
if (!res) {
return self.redirect('homeIndex');
} else {
self.next();
}
})
},

I guess you should try adding a check in the publish method in server.
Something like this:
Meteor.publish('messages') {
if (Roles.userIsInRole(this.userId, 'admin', 'admin-group')) {
return Meteor.messages.find();
}
else {
// user not authorized. do not publish messages
this.stop();
return;
}
});
You may do a similar check in your call methods in server.

Related

Meteor restarts client after Meteor.call()

I'm using: meteor-base#1.4.0.
I want to redirect the user to a different page but after the insert the client it's refreshed. This means that i'm filling up a form, I store the data, I do the navigation and then with the client refresh I'm back to the form page.
// Methods.js
import { Meteor } from "meteor/meteor";
import { Players } from "../imports/api/players";
Meteor.methods({
insertPlayer(player) {
Players.insert(player);
}
})
I'm calling the method like this:
// New-player.jsx
Meteor.call("insertPlayer", player, (error) => {
if(error) {
alert("Oups something went wrong: " + error.reason);
} else {
this.props.history.push("/");
}
})
And i'm storing the values as:
// Players.js
Players.allow({
insert() { return false; },
update() { return false; },
remove() { return false; }
});
Players.deny({
insert() { return true; },
update() { return true; },
remove() { return true; }
})
Any idea what it might cause this behavior?
I'm I missing any config?
The project can be found here: https://github.com/roedit/soccer-app
I assume you are using a form submit event? Make sure before you call the Meteor Method insertPlayer you are using event.preventDefault(). If you do not event.preventDefault(), the page will refresh.

Meteor Iron Router WaitOn Subscription

I am really struggling with waiting on a subscription to load for a specific route before returning the data to the template. I can see on from the publish on the server that a document is found, but on the client there is no document.
If I do a find().count() on the publish, it shows 1 document found, which is correct, but when I do the count on the subscription, it shows 0 documents.
I have tried a number of different methods, like using subscriptions:function() instead of waitOn:function(), but nothing works.
Collections.js lib:
SinglePackage = new Mongo.Collection("SinglePackage");
SinglePackage.allow({
insert: function(){
return true;
},
update: function(){
return true;
},
remove: function(){
return true;
}
});
Publications.js server:
Meteor.publish("SinglePackage", function(pack_id) {
return Packages.find({shortId: pack_id});
});
Iron Router:
Router.route('/package/:id', {
name: 'package.show',
template: 'Package_page',
layoutTemplate: 'Landing_layout',
waitOn: function() {
return Meteor.subscribe('SinglePackage', this.params.id);
},
data: function() {
return SinglePackage.find();
},
action: function () {
if (this.ready()) {
this.render();
} else {
this.render('Loading');
}
}
});
Am I doing something very wrong, or is this just a complicated thing to achieve? One would think that waitOn would make the rest of the function wait until the subscription is ready.
Any help would be highly appreciated.
It appears that the data function is running before the subscription is ready. Even if the data function did run after the subscription was ready, it wouldn't be a reactive data source rendering the pub/sub here pointless. Here's a great article on reactive data sources.
Referring to the example from the Iron Router Docs for subscriptions, you would do something like this:
Router.route('/package/:id', {
subscriptions: function() {
// returning a subscription handle or an array of subscription handles
// adds them to the wait list.
return Meteor.subscribe('SinglePackage', this.params.id);
},
action: function () {
if (this.ready()) {
this.render();
} else {
this.render('Loading');
}
}
});
Then in your template.js:
Template.Package_page.helpers({
singlePackage() {
// This is now a reactive data source and will automatically update whenever SinglePackage changes in Mongo.
return Package.find().fetch();
}
});
In your template.html you can now use singlePackage:
<template name="Package_page">
{#with singlePackage} <!-- Use #each if you're singlePackage is an array -->
ID: {_id}
{/with}
</template>

AngularJS tutorial Thinkster.io chapter 7

UPDATE: The tutorial was updated and the following question really no longer applies
Learning about AngularJS from the site thinkster.io (free ebook). But at the moment i'm stuck at chapter 7 - Creating your own user data using firebase. This is an tutorial about angularjs that works with firebase.
I have wrote all the code according to the site, but i'm getting these console errors when I want to register a user. It will create the user (in firebase -simplelogin), but not the user object (in firebase - data).:
TypeError: undefined is not a function
at Object.User.create (http://localhost:9000/scripts/services/user.js:46:19)
at http://localhost:9000/scripts/controllers/auth.js:32:22
etc.
This is the code (same as the site), the error is in the create() function and talks about the users.$save() function, snippet of User.create():
users.$save(username).then(function () {
setCurrentUser(username);
});
Complete code of user.js:
news.factory("User", function ($firebase, FIREBASE_URL, $rootScope, $log) {
var reference, users, User;
reference = new Firebase(FIREBASE_URL + "users");
users = $firebase(reference);
function setCurrentUser(username) {
$rootScope.currentUser = User.findByUsername(username);
}
$rootScope.$on("$firebaseSimpleLogin:login", function (event, authUser) {
var query = $firebase(reference.startAt(authUser.uid).endAt(authUser.uid));
query.$on("loaded", function () {
setCurrentUser(query.$getIndex()[0]);
});
});
$rootScope.$on("$firebaseSimpleLogin:logout", function () {
delete $rootScope.currentUser;
});
User = {
create: function (authUser, username) {
users[username] = {
md5_hash: authUser.md5_hash,
username: username,
"$priority": authUser.uid
};
$log.debug(users);
users.$save(username).then(function () {
setCurrentUser(username);
});
},
findByUsername: function (username) {
if (username) {
return users.$child(username);
}
},
getCurrent: function () {
return $rootScope.currentUser;
},
signedIn: function () {
return $rootScope.currentUser !== undefined;
}
};
return User;
});
Edit 1:
Registering a user now works, got it working (saving in firebase, simple login and data):
users = $firebase(reference).$asObject();
Notice the users.save() function:
create: function (authUser, username) {
users[username] = {
md5_hash: authUser.md5_hash,
username: username,
$priority: authUser.uid
};
$log.debug(users);
users.$save().then(function () {
setCurrentUser(users);
});
},
findByUsername: function (users) {
if (users) {
return users;
}
},
Edit 2:
Now I get an error at the log in of the user (see below), when I want to log in, I get an error on this this function, query.$on():
TypeError: undefined is not a function
at http://localhost:9000/scripts/services/user.js:26:19
$rootScope.$on("$firebaseSimpleLogin:login", function (event, authUser) {
var query = $firebase(reference.startAt(authUser.uid).endAt(authUser.uid));
query.$on("loaded", function () {
setCurrentUser(query.$getIndex()[0]);
});
});
What is wrong now?
This is an answer on edit 2: I have used firebase(ref), query.$loaded and searched for the right object, that's it. Maybe someone have an different answer, please post them :).
I have finally completed chapter 07!
In general (solution for Edit 2):
$rootScope.$on("$firebaseSimpleLogin:login", function (event, authUser) {
var query = $firebase(reference).$asObject();
query.$loaded(function (result) {
angular.forEach(result, function (key) {
if (key.md5_hash === authUser.md5_hash) {
setCurrentUser(key);
}
});
});
});
This is not the ideal solution, but the free ebook (atm of writing) is far from ideal. Then again, these kind of situations helps you to understand a little bit more about the firebase api and how it works with angular. But can be frustrated at times, when you just want to go through the tutorial ;).
Note! I have saved the User object and pass the User object to the findUsername() and setCurrentUser() functions instead of just the user.username.
You can also use the native array function, like some().
I think your system uses the newer version of Angularfire (version>= 0.8). Which means for running through loops that are arrays ...you need to attach .$asArray() at the end of the user definition field. Check the updates of Firebase.

Meteor Roles package - userIsInRole always returns false

I want to have a filter on routing level, checking if the user is in a specific role.
this.route('gamePage', {
path: '/game/:slug/',
onBeforeAction: teamFilter,
waitOn: function() { return […]; },
data: function() { return Games.findOne({slug: this.params.slug}); }
});
Here is my filter:
var teamFilter = function(pause) {
if (Meteor.user()) {
Meteor.call('checkPermission', this.params.slug, Meteor.userId(), function(error, result) {
if (error) {
throwError(error.reason, error.details);
return null;
}
console.log(result); // returns always false
if (!result) {
this.render('noAccess');
pause();
}
});
}
}
In my collection:
checkPermission: function(gameSlug, userId) {
if (serverVar) { // only executed on the server
var game = Games.findOne({slug: gameSlug});
if (game) {
if (!Roles.userIsInRole(userId, game._id, ['administrator', 'team'])) {
return false;
} else {
return true;
}
}
}
}
My first problem is that Roles.userIsInRole(userId, game._id, ['administrator', 'team'] always returns false. At first, I had this code in my router.js, but then I thought that it does not work because of a missing publication/subscription, so I ensured that the code runs only on the server. I checked the database and the user is in the role.
My second problem is that I get an exception (Exception in delivering result of invoking 'checkPermission': http://localhost:3000/lib/router.js?77b3b67967715e480a1ce463f3447ec61898e7d5:14:28) at this point: this.render('noAccess'); and I don't know why.
I already read this: meteor Roles.userIsInRole() always returning false but it didn't solve my problem.
Any help would be greatly appreciated.
In teamFilter hook you call Meteor.method checkPermission which works asynchronously and OnBeforeAction expects synchronous execution ( no callbacks ). That is why you always receive false.
Another thing is that you are using Roles.userIsInRole incorrectly:
Should be:
Roles.userIsInRole(this.userId, ['view-secrets','admin'], group)
In this case I would check roles on client side:
Roles.userIsInRole(userId, ['administrator', 'team'])
Probably you are worried about security with this solution.
I don't think you should.
What is the most important is data and data is protected by publish function which should check the roles.
Please note that all templates are accessible to client.
You can add roles to the user only on server for that you can user Meteor.call({}); check here method from client to call method on server's main.js and you can check after this method call if the role is added in users collection using meteor mongo and db.users.find({}).pretty() and see if the roles array is added the user of that usedId then you can use Roles.userIsInRole() function anywhere on client to check loggedin users role.

Meteor.js Publishing and Subscribing?

Okay, so I am a bit confused about something with Meteor.js. I created a site with it to test the various concepts, and it worked fine. Once I removed "insecure" and "autopublish", I get multiple "access denied" errors when trying to retrieve and push to the server. I belive it has something to do with the following snippet:
Template.posts.posts = function () {
return Posts.find({}, {sort: {time: -1}});
}
I think that it is trying to access the collection directly, which it was allowed to do with "insecure" and "autopublish" enabled, but once they were disabled it was given access denied. Another piece I think is problematic:
else {
Posts.insert({
user: Meteor.user().profile.name,
post: post.value,
time: Date.now(),
});
I think that the same sort of thing is happening: it is trying to access the collection directly, which it is not allowed to do.
My question is, how do I re-factor it so that I do not need "insecure" and "autopublish" enabled?
Thanks.
EDIT
Final:
/**
* Models
*/
Posts = new Meteor.Collection('posts');
posts = Posts
if (Meteor.isClient) {
Meteor.subscribe('posts');
}
if (Meteor.isServer) {
Meteor.publish('posts', function() {
return posts.find({}, {time:-1, limit: 100});
});
posts.allow({
insert: function (document) {
return true;
},
update: function () {
return false;
},
remove: function () {
return false;
}
});
}
Ok, so there are two parts to this question:
Autopublish
To publish databases in meteor, you need to have code on both the server-side, and client-side of the project. Assuming you have instantiated the collection (Posts = new Meteor.Collection('posts')), then you need
if (Meteor.isServer) {
Meteor.publish('posts', function(subsargs) {
//subsargs are args passed in the next section
return posts.find()
//or
return posts.find({}, {time:-1, limit: 5}) //etc
})
}
Then for the client
if (Meteor.isClient) {
Meteor.subscribe('posts', subsargs) //here is where you can pass arguments
}
Insecure
The purpose of insecure is to allow the client to indiscriminately add, modify, and remove any database entries it wants. However, most of the time you don't want that. Once you remove insecure, you need to set up rules on the server detailing who can do what. These two functions are db.allow and db.deny. E.g.
if (Meteor.isServer) {
posts.allow({
insert:function(userId, document) {
if (userId === "ABCDEFGHIJKLMNOP") { //e.g check if admin
return true;
}
return false;
},
update: function(userId,doc,fieldNames,modifier) {
if (fieldNames.length === 1 && fieldNames[0] === "post") { //they are only updating the post
return true;
}
return false;
},
remove: function(userId, doc) {
if (doc.user === userId) { //if the creator is trying to remove it
return true;
}
return false;
}
});
}
Likewise, db.deny will behave the exact same way, except a response of true will mean "do not allow this action"
Hope this answers all your questions

Categories

Resources