Different userId get same document with server publish this.userId - meteor

With autopublish package is removed, While this Meteor code is running, a different userId has been confirmed on 2 different browsers consoles Meteor.userId();
But when a string is typed in the inputText of one of them, and a collection.insert is done, the other shows the same string.
I thought that this.userId was good enough for the server to publish only the documents that belongs to each of the different clients simultaneously.
Why is this happening and how to fix it? Thanks
Server
Meteor.publish('displayCol', function () {
return DisplayCol.find({userId: this.userId});
});
DisplayCol.before.insert(function (userId, doc) {
doc.userId = userId;
});
Client
Template.index.helpers({
taskInputs: function () {
var ready = Meteor.subscribe('displayCol').ready();
var data = DisplayCol.find({});
return {items: data, ready: ready};
}
});

Do you have autopublish still installed? If so, both clients will get everything automatically. Remove it with 'meteor remove autopublish'
You can also add the {userId: Meteor.userId()} condition on the client side.

Related

Collection subscription but no documents to client

This Meteor code gives full records to the client for one of the collections but the client browser gives:
MyCol.findOne({});
undefined
I also have a server side code which inserts documents into the collection and mongo terminal verifies documents exist.
Any idea why? Thanks
//app.lib.js
MyCol = new Mongo.Collection('myCol');
//server
Meteor.publish('myCol', function() {
return MyCol.find({});
});
//client main.js
Meteor.subscribe('myCol');
Template.checks.helpers({
'values': function() {
return MyCol.find({}); // no documents found,
}
});

Modify data in Meteor.publish before sending down to client

I have the following use case:
I have a users table in MongoDB on the backend, which is a separate service than the frontend. I use DDP.connect() to connect to this backend service.
Each user has a set of "subjects"
Each subject in the users table is referenced by id, not name. There is a separate table called "subjects" that holds the subjects by id.
I want to publish the user down to the client, however I want the published user to be populated with the subjects first.
I tried the following, inspired by this blog post:
// in the backend service on port 3030
Meteor.publish('users', function(userId) {
var _this = this;
Meteor.users.find({
_id: userId
}).forEach(function(user) {
user.profile = populate(user.profile);
console.log(user);
_this.changed('users', userId, {
_id: userId,
profile: user.profile
});
});
_this.ready();
});
// in the client
var UserService = DDP.connect('http://localhost:3030');
var UserServiceProfile = UserService.subscribe('users', Meteor.userId());
console.log(UserServiceProfile);
This gives the following error on the backend:
Exception from sub users id akuWx5TqsrArFnQBZ Error: Could not find element with id XhQu77F5ChjcMTSPr to change.
So I tried changing _this.changed to _this.added. I don't get any errors, but the changes aren't showing up in the client minimongo, even though I can see that the populate function worked through the console.log(user) line.
I'm not sure how you'd fix your code, but you might not need to. It looks like you want the https://atmospherejs.com/maximum/server-transform package.
Add the server-transform package to your project, and replace your code with this (I'm going to assume you also have underscore added to your project, and the subjects collection in your database corresponds to a global variable called Subjects in your code.):
Meteor.publishTransformed('users', function(userId) {
return Meteor.users.find({
_id: userId
}).serverTransform({
'profile.subjects': function(doc) {
var subjects = [];
_(doc.profile.subjects).each(function(subjectId) {
subjects.push(Subjects.findOne(subjectId));
});
return subjects;
}
});
});

Meteor Collection and Security

I am curious if I'm setting up the allow statement on this collection correctly. I'm using aldeed:autoform and aldeed:collection2.
Below is the snapshot of a issue-collection.js from a toy project.
Is this the proper way to set up allow checks? Do these run on both client (for minimongo) and server? Specifically, on most update calls, is return !!userId && (doc.userId == userId); enough to ensure the user is logged in AND the logged in user is the owner of the document?
Clarification and actual question: Do the allow and deny methods run on BOTH server and client? Or do they run only on the client?
Issues = new Mongo.Collection("issues");
if (Meteor.isClient){
Meteor.subscribe("issues");
}
if(Meteor.isServer){
Meteor.publish('issues', function () {
return Issues.find({}, {limit: ServerSettings.maxSubscribe});
});
}
Issues.attachSchema(new SimpleSchema({
issue: {
type: String,
label: "Describe the issue you noticed",
max:256
}
}));
//SECURITY - Allow Callbacks for posting
Issues.allow({
insert: function(userId, doc) {
/* Throw in some defaults. */
doc.userId = userId;
doc.sumbitDate = new Date();
doc.date = new Date();
// only allow posting if you are logged in
return !! userId;
},
update: function(userId, doc) {
// only allow updating if you are logged in
return !!userId && (doc.userId == userId);
},
remove: function(userID, doc) {
//only allow deleting if you are owner
return doc.submittedById === Meteor.userId();
}
});
Remember, allow/deny comes from the client. And you can't trust anything that comes from the client (userId, date, etc).
What you want to do is call a Meteor.method from the client & _.extend the document with trustworthy data from the server.
As an example, rewrite that code in the browser console & change the value for the userId.
Check out Discover Meteor blog to learn more (it's probably the best source for learning basic patterns) https://www.discovermeteor.com/blog.

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).

Accessing this.userId not working when calling from within Meteor.SetTimeout

I've been trying to access the this.userId variable from within a Meteor.methods call, but it doesn't seem to work when I try to call the method via Meteor.setTimeout or Meteor.setInterval.
This is what I've got:
if (Meteor.is_server) {
Meteor.methods({
getAccessToken : function() {
try {
console.log(this.userId);
return Meteor.users.findOne({_id: this.userId}).services.facebook.accessToken;
} catch(e) {
return null;
}
}
});
var fetch_feed = function() {
console.log(Meteor.call("getAccessToken"));
[...] // A bunch of other code
};
Meteor.startup(function() {
Meteor.setInterval(fetch_feed, 60000); // fetch a facebook group feed every minute
Meteor.setTimeout(fetch_feed, 3000); // initially fetch the feed after 3 seconds
});
}
Watching the terminal log, the this.userId always returns a null. But if I try calling the method from the client side, or through the console, it returns the correct ID.
How come this doesn't work from within a Meteor.setInterval? Is it a bug or am I doing something wrong?
Meteor userId's are associated with client connections. The server may interact with many clients and this.userId inside a method will tell you which client has asked for the method to be run.
If the server uses Meteor.call() to run a method then it will not have a userId since it is not running for any client.
The methods allow clients to call for functions to be run on the server. For things the server will trigger itself a javascript function will do.
There is a solution I used - sometimes you do not want to make the method a function but really want it to remain a method. In that case, a hack to make this work:
var uniqueVar_D8kMWHtMMZJRCraiJ = Meteor.userId();
Meteor.setTimeout(function() {
// hack to make Meteor.userId() work on next async
// call to current method
if(! Meteor._userId) Meteor._userId = Meteor.userId;
Meteor.userId = function() {
return Meteor._userId() || uniqueVar_D8kMWHtMMZJRCraiJ
};
Meteor.apply(methodName, args);
}
, 100);
Some brief explanation: we save Meteor.userId in Meteor._userId and overwrite Meteor.userId with a function that returns Meteor._userId() if it is true and otherwise the historic value of Meteor.userId() before any of this happened. That historic value is saved in an impossible to occur twice var name so that no context conflicts can happen.

Resources