I have a game that I know works properly with an ID per the code on the client side. For example if I were to use the below with {{game._id}} it works properly:
Template.gamePage.game = function() {
return GameCollection.findOne({current: true});
};
However, I am trying to gain access to the publications of 'submissions; only for the specific game ID. Console log below returns undefined.
router.js
this.route('gamePage', {
path: '/games/:_id?',
waitOn: function() {
console.log(this.params._id);
return [
Meteor.subscribe('randomQuestions', Random.id()),
Meteor.subscribe('submissions', this.params._id)
];
}
});
I suspect that params._id pulls from games/:_id, however, I would like it so that that it remains games/:_id? so that I do not have an unnecessary long address.
Any thoughts on why I am getting undefined for params._id
I think you have one button for to access a game, for example...
Tracker.autorun(function () {
Session.set('gameCurrent');
});
Template.gamePage.helpers({
allGames: function(){
return GameCollection.find({});
},
getCurrentGame:function(){
return Session.get('gameCurrent');
}
})
// with this action you access a the route with the id specified
Template.gamePage.events({
'click button#game' : function(event,template){
Session.set('gameCurrent',this._id);
Router.go('editEmail',{_id:this._id})
}
})
Remember that Session only works in the client.
Related
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.
Can someone explain to me why when I have collections code inside router will cause the route to be called when a method is called?
Consider the following code:
home.html
<template name="home">
{{ duplicate }}
<form>
<input type="text" name="test" value="somevalue">
<input type="submit" value="Submit">
</form>
</template>
script.js
Template.home.events({
'submit form': function (e) {
e.preventDefault();
console.log('Enter Meteor call');
Meteor.call('createDoc', { 'test': e.target.test.value });
}
});
route.js
Router.onBeforeAction(function () {
console.log('Enter onBeforeAction');
$('#loading').show();
this.next();
});
Router.route('/', function () {
console.log('Enter action');
var foo = collection.findOne({ test: 'somevalue' }) ? 'true' : 'false';
this.render('home', {
data: {
'duplicate' : foo
}
});
Template.home.rendered = function () {
console.log('Enter rendered');
$('#loading').hide();
};
});
methods.js
collection = new Mongo.Collection('collection');
Meteor.methods({
createDoc: function (data) {
console.log('Enter createDoc');
collection.insert(data);
}
});
The problem is that if I press submit on the form, after the method is called the router will activate, even though e.preventDefault() presents. The console log shows this behaviour clearly:
"Enter Meteor call" script.js:4:3
"Enter createDoc" methods.js:5:3
"Enter onBeforeAction" routes.js:2:2
"Enter action" routes.js:8:2
"Enter onBeforeAction" routes.js:2:2
"Enter action" routes.js:8:2
Furthermore, you can see that the router is called twice and that it never enters Template.home.rendered. This causes the loading div to appear and never leaves. I can confirm that data are being inserted correctly.
If I remove collection.findOne() in routes.js, however, this behaviour will disappear and everything works as expected.
Questions
Why is the route being called only when I have collection.findOne() inside the route?
Why collection.findOne({ test: 'somevalue' }) never returns anything inside the route? (I know how I can get around this by using Session variables and helpers in script.js, but I want to know exactly why)
This is causing a lot of unexpected behaviour in my app. Thank you very much in advance.
As answered by others the problem you have arises from the fact that Meteor will reactively re-run code that runs in a reactive context, if and only if, that code issues a call to a reactive data source.
In your case, the call to findOne is a call to a reactive data source and the context in Router.route('/', function () { // context }); is a reactive context.
There are two important tools that let you control this behavior: one is good design. Be aware of the reactivity and try to design your code around it.
The other is checking Tracker.active and using Tracker.nonreactive to avoid reactivity inside a reactive data context.
This should answer your first question. As to why your findOne query never finds anything: have you published the data from the server to the client? Please check out Publish-Subscribe. You basically need:
// on the server
Meteor.publish('myPublication', function(author) {
return collection.find();
});
// on the client
Meteor.subscribe('myPublication');
The call to collection.findOne() inside the route is listening to any new changes on the database, every time text is saved on the database the query is run.
A possible solution:
Router.js
Router.onBeforeAction(function () {
console.log('Enter onBeforeAction');
$('#loading').show();
this.next();
});
Router.route('/', {
template: 'home',
waitOn: function() {
return Meteor.subscribe('collection');
},
data: function() {
var foo = collection.findOne({ test: 'somevalue' }) ? 'true' : 'false';
return {
'duplicate': foo
};
},
action: function() {
this.render();
}
});
And a publish file on server/publish.js
Meteor.publish('collection', function () {
return collection.find();
});
I hope this can help you solving your problem.
Best.
I'm trying to make a publishment statement to publish
ONLY the author(OP)'s profile avatar. I am thinking of grabbing the _id of the page. And from that page, I will grab the userId which is the author's _id and try to show the profile.
However, I have been very unsuccessful, and currently, I am using the following. Publishing EVERY user's profile avatar.
Publications.js
//Need to filter this to show only OP.
Meteor.publish("userPostAvatar", function() {
return Meteor.users.find( {} ,
{
fields: {'profile.avatar': 1}
})
});
Meteor.publish('singlePost', function(id) {
check(id, String);
return Posts.find(id);
});
Router.js
Router.route('/posts/:_id', {
name: 'postPage',
waitOn: function() {
return [
Meteor.subscribe('singlePost', this.params._id),
Meteor.subscribe('userStatus'),
Meteor.subscribe('userPostAvatar')
];
},
data: function() {
return Posts.findOne({_id:this.params._id});
}
});
You can do a simple join in the userPostAvatar publish function like this:
Meteor.publish('userPostAvatar', function(postId) {
check(postId, String);
var post = Posts.findOne(postId);
return Meteor.users.find(post.authorId, {fields: {profile: 1}});
});
This assumes posts have an authorId field - adjust as needed for your use case. Note three important things:
You will need to subscribe with this.params._id just as you did for singlePost.
The join is non-reactive. If the author changes, the avatar will not be republished. Given the general nature of posts I assume this isn't a problem.
I didn't publish the nested field profile.avatar on purpose because doing so can cause weird behavior on the client. See this question for more details.
I believe you can achieve this within the iron:router data context, by finding the post, associated author (whatever the field is), and then the subsequent user avatar. You can return an object to the iron:router data context. Then you can access post and avatar in the template as variables (so you might need to adjust the template output a little).
Publications.js
Meteor.publish("userPostAvatar", function() {
return Meteor.users.findOne( {} ,
{
fields: {'profile.avatar': 1}
})
});
Meteor.publish('singlePost', function(id) {
check(id, String);
return Posts.find(id);
});
Router.js
Router.route('/posts/:_id', {
name: 'postPage',
waitOn: function() {
return [
Meteor.subscribe('singlePost', this.params._id),
Meteor.subscribe('userStatus'),
Meteor.subscribe('userPostAvatar')
];
},
data: function() {
var post = Posts.findOne({_id: this.params._id});
var avatar = Users.findOne(post.authorId).profile.avatar;
return {
post: post,
avatar: avatar
};
}
});
Two problems with this method are that you could achieve the same thing with template helpers, and the user publication hasn't been limited to one user (I'm unsure how to do this unless we know the authorId within the waitOn, although maybe you could try moving the logic to there instead of the data context as my example shows).
I am trying to set my selectedDocId upon routing to a page, but Meteor is throwing undefined . I am using an onBeforeAction in an attempt to store the id that is concatenated at the end of my url via /:id, but I can't get them aligned.
Router.route('speaker', {
path:'/speakers/:_id',
template: 'speaker',
data: function(){
return Speakers.findOne(this.params._id);
},
onBeforeAction: function(){
console.log('speaker route has run');
Session.set('selectedDocId', this._id);
//returns undefined
console.log(this._id);
this.next();
}
});
Template.speaker.helpers({
editingDoc: function(){
return Speakers.findOne({_id: Session.get('selectedDocId')});
}
});
Instead of this._id, use this.params._id just like you did in the data hook.
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.