failed: Access denied on meteor collection - meteor

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.

Related

Meteor method create insert hook and bind userId on the server

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

insert still works after Meteor.logout

This Meteor app has the insecure and autopublish removed and accounts-password added.
It uses Accounts.createUser({username: someName, password: somePwrd});
It avoids using allow/deny and uses instead Meteor.call to insert documents because reading in the docs, it says that
Server code is trusted and isn't subject to allow and deny restrictions. That includes methods that are called with Meteor.call — they are expected to do their own access checking rather than relying on allow and deny.
But when I fire up the Meteor.logout(), I am still able to insert new documents to Tasks1 collection. How can that be? I though logout will stop inserting any new documents. How can I fix it? Thanks
///////////////////////////
//both/both.js
///////////////////////////
Tasks1 = new Mongo.Collection('tasks1');
///////////////////////////
//server/server.js
///////////////////////////
Meteor.publish('tasks1', function(){
return Tasks1.find({userId: this.userId});
});
Meteor.methods({
addTasks1: function (doc) {
Tasks1.insert(doc);
}
});
///////////////////////////
//client/client.js
///////////////////////////
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;
Meteor.call('addTasks1', params);
}
}
}
});
Template.mainMenu.events({
'click #logout': function () {
Meteor.logout();
}
});
In your server addTasks1 method, you should first check if the user is a user like so:
Meteor.methods({
addTasks1: function(doc) {
if (!Meteor.userId()) {
throw new Meteor.Error("Not Authorized");
} else {
Tasks1.insert(doc);
}
}
})
Logout alone doesn't stop users from being able to insert. You must edit your method code to achieve this.
addTasks1: function (doc) {
if (Meteor.userId()) {
Tasks1.insert(doc);
}
}

Meteor reactive transform

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");

Add extra user field

In my Meteor app I use the default accounts package, which gives me the default login and registration functionality. Now I want to add an extra field to user, say nickname, and for the logged in user the possibility to edit this information.
For editing the profile I suppose I should be doing something like this:
Template.profileEdit.events({
'submit form': function(e) {
e.preventDefault();
if(!Meteor.user())
throw new Meteor.Error(401, "You need to login first");
var currentUserId = this._id;
var user = {
"profile.nickname": $(e.target).find('[name=nickname]').val()
};
Meteor.users.update(currentUserId, {
$set: user
}, function(error){
if(error){
alert(error.reason);
} else {
Router.go('myProfile', {_id: currentUserId});
}
});
}
});
But I doesn't store the info if I look in Mongo. Also when showing the profile, {{profile.nickname}} returns empty. What is wrong here?
Edit: added collections\users.js to show permissions:
Meteor.users.allow({
update: function (userId, doc) {
if (userId && doc._id === userId) {
return true;
}
}
});
Meteor.users.deny({
update: function(userId, user, fieldNames) {
return (_.without(fieldNames, 'profile.nickname').length > 0);
}
});
Yeah, I believe that should do the job, although I haven't actually run the code. The idea is certainly right.
The main things to be aware of are:
The necessity to allow the user doc to be edited from the client with an appropriate Meteor.users.allow() block on the server, assuming you're going to remove the "insecure" package (which you need to before doing anything in production).
The fact that "by default the server publishes username, emails, and profile", so you'll need to write a Meteor.publish function on the server and subscribe to it if you want to expose any other fields within the user document to the client once you've removed the "autopublish" package (which again, you really should).

meteor allow rules

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?

Resources