I have a question on meteor's parties example.
If I call this code:
Parties.allow({
insert: function () {
return true;
},
remove: function (){
return true;
},
update: function() {
return true;
}
});
everybody can do insert, remove and update.
The code from the example is
Parties.allow({
insert: function (userId, party) {
return false; // no cowboy inserts -- use createPage method
},
update: function (userId, parties, fields, modifier) {
return _.all(parties, function (party) {
if (userId !== party.owner)
return false; // not the owner
var allowed = ["title", "description", "x", "y"];
if (_.difference(fields, allowed).length)
return false; // tried to write to forbidden field
// A good improvement would be to validate the type of the new
// value of the field (and if a string, the length.) In the
// future Meteor will have a schema system to makes that easier.
return true;
});
},
remove: function (userId, parties) {
return ! _.any(parties, function (party) {
// deny if not the owner, or if other people are going
return party.owner !== userId || attending(party) > 0;
});
}
});
So my question is where the variables useriD and party at this line for example
insert: function (userId, party) {
are defined?
Are these the variables I call in the method
Meteor.call("createParty", variable1, variable2)
? But this wouldn't make sense because the client calls
Meteor.call('createParty', {
title: title,
description: description,
x: coords.x,
y: coords.y,
public: public
}
I hope somebody can explain the allow functions to me? Thanks!
To understand allow/deny, you need to understand where the userId and doc parameters come from. (Just as in any function definition, the actual parameter names don't matter.) Looking just at the Parties insert example:
Parties.allow({
insert: function (userId, party) {
return false; // no cowboy inserts -- use createPage method
}
});
The party parameter is the doc that's being inserted:
Parties.insert(doc);
The userId parameter is set automatically IF you're using the Meteor Accounts auth system. Otherwise, you have to set it yourself on the server. How do you do that?
In general, you call code on the server from the client by using Meteor.call(). Since there's no built-in API to set userId (other than Accounts), you have to write your own (goes in your server code):
Meteor.methods({
setUserId: function(userId) {
this.setUserId(userId);
}
});
Then you can call it like this, anywhere in your client code:
Meteor.call('setUserId', userId);
1) Where the variables useriD and party are defined? Nowhere! the intention is that no user can call this function.
This is in order to proctect the database from users that could insert manually new parties using the console. Remmember that Meteor replicates the database in client and server.
Any user could insert manually new parties through the console. This is fine. But then the server would reject the insert since it is not allowed.
2) Are these the variables I call in the method Meteor.call("createParty", variable1, variable2)? Yes the variables are available, but this code is not using the correct definition which is:
Meteor.methods({
createParty: function (options) {
And afterwards it is used as
Meteor.call('createParty',
{ title: title, public: public, ... }, // options array!!
function (error, party) { ... } // function executed after the call
);
Did it help you?
Related
I implemented a hook function, where I attach some createdAt and updatedAt fields to the doc that is inserted to a collection. I can attach this to any collection like this:
export const insertHook = function (doc) {
try {
const user = Meteor.user();
doc.createdBy = user && user._id ? user._id : null;
doc.createdAt = new Date().getTime();
} catch (e) {
console.err(e);
}
};
Attaching the hook to the collection is basically passing it via a third option in the constructor:
class HookedCollection extends Mongo.Collection {
constructor(name, options, hooks={}) {
super(name, options);
this.insertHook = hooks.insertHook;
}
insert(doc, callback) {
if (this.insertHook && Meteor.isServer)
this.insertHook.call(this, doc);
}
}
export const MyDocs = new HookedCollection("mydocs", {}, {insertHook});
In a Meteor method I just do a normal insert:
Meteor.methods({
insertDoc:function(doc) {
//check doc...
return MyDocs.insert(doc);
}
});
Which creates basically the following error:
Error: Meteor.userId can only be invoked in method calls or publications.
I tried several ways of bind but always ended up in this error. Is there really no way at all to bind the userId to the function?
According to Meteor docs Meteor.userId() is available anywhere but publish functions (Server side Publish function).
You aren't using Meteor.userId() directly in the method but in a callback (see discussion in this github issue). You can pass the userId information to your callback function as a parameter from the method, for example:
// Using Meteor.userId()
Meteor.methods({
insertDoc:function(doc) {
//check doc...
return MyDocs.insert(doc, Meteor.userId());
}
});
// Or using this.userId
Meteor.methods({
insertDoc:function(doc) {
//check doc...
return MyDocs.insert(doc, this.userId());
}
});
As a general rule use Meteor.userId() in the client (that queries the database) and this.userId in the server. More information in this other question Meteor - Why should I use this.userId over Meteor.userId() whenever possible? and in Meteor forums
This Meteor app has the insecure and autopublish removed and accounts-password added.
It uses Accounts.createUser({username: someName, password: somePwrd}); which can be verified on the mongo prompt.
I am trying to Tasks1.insert(params); and getting access denied
I don't know why it get Access denied for update and insert on the browser console. Please tell me why and how to fix it? Thanks
//both.js
Tasks1 = new Mongo.Collection('tasks1');
/////////////////////////////////////////////////////
//server.js
Meteor.publish('tasks1', function(){
return Tasks1.find({userId: this.userId});
});
Meteor.methods({
logMeIn: function(credentials) {
var idPin = credentials[0] + credentials[1];
Accounts.createUser({username: idPin, password: credentials[1]});
}
});
Meteor.users.allow({
insert: function (userId, doc) {
console.log(userId);
//var u = Meteor.users.findOne({_id:userId});
return true;
}
});
/////////////////////////////////////////////////////
//client.js
Template.login.events({
'click #logMe': function() {
var credentials = [$('#id').val(), $('#pin').val()];
Meteor.call('logMeIn', credentials, function(err, result) {
if (result) {
console.log('logged in!');
}
});
}
});
Template.footer.events({
'click button': function () {
if ( this.text === "SUBMIT" ) {
var inputs = document.getElementsByTagName('input');
for (var i = 0; i < inputs.length; i++) {
var params = {};
params[inputs[i].name] = inputs[i].value;
Tasks1.insert(params); //<<<<<<----------------------
}
}
}
});
Update:
Since you have edited your question and added that Tasks1.insert(params); is getting access denied message, you should add allow rules on Tasks collection and not Meteor.users collection.
Tasks.allow({
insert: function (userId, doc) {
return true;
},
update: function (userId, doc, fieldNames, modifier) {
return true;
},
remove: function (userId, doc) {
return true;
}
});
If Accounts.createUser is working without allow rules on Meteor.users then please remove them as it might allow users to insert/delete others from client itself.
End of update.
Since you removed insecure, you need to add allow/deny rules for inserting, updating or deleting files from a collection.
Meteor.users.allow({
insert: function (userId, doc) {
//Normally I would check if (this.userId) to see if the method is called by logged in user or guest
//you can also add some checks here like user role based check etc.,
return true;
},
update: function (userId, doc, fieldNames, modifier) {
//similar checks like insert
return true;
},
remove: function (userId, doc) {
//similar checks like insert
return true;
}
});
Check the API documentation for more details.
Defining your Meteor.methods like this will define it for both server and client. This means the you will be trying to create a user TWICE, once on the server (the one that works) and another time on the client. The client does not have the right to insert user documents so you receive this error.
There are two options for you:
1: Define the method on the server only by surrounding it by if(Meteor.isServer) or putting it in a folder named "server"
2: leave it as is, it will not cause harm but keep showing the error in console.
I am sure there is a 3rd and maybe 4th solution, but those are the two I'd use.
I have a mobile app in development and I'm transforming one of the collections to get the user last seen time, avatar etc.
PlayerRecord.prototype = {
constructor : PlayerRecord,
getAssociatedUser: function () {
return Meteor.users.findOne( this.user_id );
},
lastSeenFormatted: function () {
var user = this.getAssociatedUser();
return (user && user.last_seen) ? user.last_seen : 'Never';
}
}
My problem is that, if the user last seen returns Never initially but then the user is seen, my string return over there is not updated...obviously.
How would you advise me to handle this situation?
Did you check whether any user had a value for last_seen? This field has to be explicitly published.
According to the Meteor docs (http://docs.meteor.com/#/full/meteor_user):
By default, the current user's username, emails and profile are
published to the client. You can publish additional fields for the
current user with:
// server
Meteor.publish("userData", function () {
if (this.userId) {
return Meteor.users.find({_id: this.userId},
{fields: {'last_seen': 1}});
} else {
this.ready();
}
});
// client
Meteor.subscribe("userData");
My question is based on this topic in Angular Google group.
I want to provide a service which stores some basic data retrieved from the backend via $http, then I only need to fetch those data once. like,
var load = function() {
return $http.get(...).then(function(data) {
return data.user;
});
};
module.factory("userProvider", function() {
var user;
var getUser = function() {
if(!user) {
load().then(function(data) {
user = data;
});
}
return user;
};
return {
getUser : getUser
}
});
module.controller("UserController", ["userProvider", function UserController("userProvider") {
var user = userProvider.getUser();
// do something with user
}]);
The problem is that the promise chain ends in userProvider but not in controller, so the user is undefined the first time I use this controller since the data has not been returned.
How can I use such a storage service and return the data correctly? Thanks!
You can just create your own promise. Here is the modified code:
module.factory( "userProvider", function( $q ) {
// A place to hold the user so we only need fetch it once.
var user;
function getUser() {
// If we've already cached it, return that one.
// But return a promise version so it's consistent across invocations
if ( angular.isDefined( user ) ) return $q.when( user );
// Otherwise, let's get it the first time and save it for later.
return whateverFunctionGetsTheUserTheFirstTime()
.then( function( data ) {
user = data;
return user;
});
};
// The public API
return {
getUser: getUser()
};
});
Update: The solution below by #yohairosen is a great one for many circumstances, but not for all. In some circumstances, we would only want to cache the successful result, as I have done here. If, for example, a failure of this request indicates the user needs to log in first, we would not want the next call (presumably after logging in) to deliver the cached failure. In cases where the method isn't necessarily consistent from call-to-call in all circumstances, this method is better; in all other cases, #yohairosen's solution is simpler and recommended.
It's a bit of an overhead to create your own promise, angular's $http creates one for you anyway. What you're looking for is caching and http can handle it for you by passing cache:true to the service call.
So you can simply do something like this:
module.factory("userProvider", function() {
var getUser = function() {
return $http.get(..., {cache:true}).then(function(data) {
return data.user;
});
return {
getUser : getUser
}
});
As for the new meteor release, I`d like to understand how can I forbid messages with certain words to be added to a collection.
Let's say I'm passing: Messages.insert({message:"Holy ducking smokes", at: new Date()});
What should the code within if (Meteor.is_server) be like so it would block any entries containing "duck"?
Something like this?
Messages.deny({
insert: function(userId, doc) {
if (doc.message.match(/\bduck\b/i)) return true;
return false;
}
}
That will deny the client from inserting the record if the message contains the bounded word "duck". Obviously you could execute other logic there (eg. censoring) if you need to.
One way I could do this was setting a .allow within the Meteor.is_server to test if the value differs from what I`m filtering.
It should look something like this:
if (Meteor.is_server) {
Messages.allow({
insert: function (userId, doc) {
var currentMessage = Messages.findOne({message:doc.message}) ;
if (doc.message == 'duck') { //here i`m filtering stirngs
return false;
} else { return true; }
},
update: function () { (...) },
remove: function () { (...) },
});
}
But I guess using methods would be a better approach to this, as it makes sense to use a single validation rule to both server and client side.