Meteor.users information from collection helpers - meteor

I'm trying to get some information via the collection-helpers package for non-logged in users and I'm obviously missing something fundamental here as I'm getting nowhere.
I have a relationship set up what is happily returning the profile.name element for the owner of a document, as long as that happens to coincide with the logged in user, but, I'm getting nothing back for non-logged in users (because of the security on the client side).
I've added a new publication on both client and server as
// User Profile
Meteor.publish("userProfile", function() {
return Meteor.users.find({_id: this.userId},
{fields: {'profile': 1}});
});
and have subscribed to this publication in the js associated with the page I'm trying to display it in
// Don't need this to be reactive, so
Meteor.subscribe("userProfile");
but am still not getting access to the profile data in the document with
<h4>Posted by: {{projOwner.profile.name}}</h4>
where projOwner looks like
projectDocs.helpers({
projOwner: function() {
console.log(this.owner._id);
var owner = Meteor.users.findOne(this.owner._id);
//console.log("Owner is: " +owner);
return owner;
}
});
What am I doing wrong??

In a publish function, this.userId is always the id of the currently logged in user. The profile of the current user is automatically published so that function doesn't do anything useful.
The real problem here is you need to get the correct subset of users published to the client. Maybe that's the project owner of the document you are looking at, maybe it's a all of the users in a group, etc. Without knowing more about your problem it's hard to say.
An easy place to start with is just publishing all of the users to make sure your code works, and then try reducing the set. Remember that publish functions can take arguments, so you could pass in, for example, the id of a project and then publish the owner like so:
Meteor.publish('projectOwner', function(projectId) {
check(projectId, String);
var project = Projects.findOne(projectId);
return Meteor.users.find(project.owner, {
fields: {'profile': 1}
});
});

Related

Meteor.users publish and subscribe do not match

I have removed the auto-publish package from my Meteor app and I have also created a publication on my server:
Meteor.publish("userData", function () {
return Meteor.users.find(
{_id: this.userId},
{fields: {'profile': 0}}
);
});
As you can see above I have set the profile to 0 which means I would like to exclude it. However...
On my client I have this code:
Meteor.subscribe("userData", (param) => {
console.log( Meteor.users.find(Meteor.userId()).fetch() )
})
and the output still includes the profile:
createdAt: Sat May 19 2018 11:16:25 GMT+0800 (+08) {}
emails: [{…}]
profile: {name: "Second Second"}
services: {password: {…}, resume: {…}}
username: "seconduser"
_id: "ESmRokNscFcBA9yN4"
__proto__: Object
length: 1
__proto__: Array(0)
What's the reason for this?
It is possible that some other subscription has subscribed to the profile field of the user.
You can find out if this is the case by looking at the information sent over the websocket.
Open the debugger,
find a "networking" tab,
find the websocket connection,
find the content or "frames".
There you can see which subs have been made and which updates the server publishes to a collection. See what it looks like without your sub; maybe the user doc is published already.
You see the profile field on the client since you have already edited and thus enabled this special field of the User object while creating or updating user. In order to secure this object from client-side modifications you can deny all writes from the client with the following server-side code.
// Deny all client-side updates to user documents
Meteor.users.deny({
update () { return true; },
});
So, even though the profile field is be available on the client within the Meteor.user() object, no modification can be made by the client.
If it is a custom data that you publish than you can control its exposition in your way. For example, let's assume that we introduce a new field customProfile into the user object, than with the following code, the customProfile will not be visible to the client.
Meteor.publish("userData", function () {
console.log('publishing userData with id', this.userId);
return Meteor.users.find(
{_id: this.userId},
{fields: {'customProfile': 0}}
);
});
You may find more information in the guide.
First of all, make sure whether you want to use Meteor.subscribe() or you want to use this.subscribe(). There is a lot of difference between them.
Meteor.subscribe() will keep subscription undestroyed when you change between screens/routes/UI.
this.subscribe() will have the scope of subscription till the life of Template exists. When you switch to other routes/path/UI, the subscription will be destroyed. This is used in a specific case when you have multiple kinds of subscription among consecutive transitions of the screen and problem occurs for unwanted data shown in UI despite filtering in Collection Query.
For more insight, click here.
Comming to your exact question, well when Meteor knows that you are a valid and logged in user, it sends entire Users specific collection fields _id, emails, profile, username on UI. So, it is recommended that you put only the required data into the User collection. Whether or not you make special kind of subscription to self-data, you will always be able to access your own data on UI, even on production build. You can check by putting console.log(Meteor.user()); in chrome console. This is how Meteor.user() is made, whether you like it or not. It was assumed by MDG (Meteor Development Group) that when the user has logged in, a user can fully access his/her own data at UI as it is safe and valid.
see below image for reference,

Meteor publication with calculations

I have 2 collections: Meteor.users and Projecs.
Users collection have field "projects" which contains array of user's project's ids.
"projects" : [
"jut6MHx6a7kSALPEP",
"XuJNvq7KTRheK6dSZ"
]
Also I have a publication for user's projects:
Meteor.publish('projects', function() {
var userProjects = Meteor.users.findOne(this.userId).projects;
return Projects.find({_id: {$in: userProjects}});
});
Everything works fine, but when I add new project (and update users ("projects" field) who are in this project) reactive publication doesn't works. Projects page doesn't contains recently added project. It works only when I refresh page.
Subscription made in router:
waitOn: function() {
return [
Meteor.subscribe('projects')
]
},
What should I do with this publication? Thanks a lot.
This is happening because Meteor.users is not reactive. I don't know what the reason behind but I saw many developers, specially developers who try to get famous by publish really cool articles about their awesome application, exposing the tokens.
So if some idiot publish the Meteor.users to the browser, it's a security flaw. It would be even worst if it was reactive because the token would be updated in realtime. Maybe this a block to newbie who don't really know that they're doing. Just my opinion about this decision.
This collection is design to be used for managing users and after the login, it makes no sense to use to store data, as it is designed.
Yea, this is a known "problem". Publish functions aren't reactive, so Meteor.users.findOne(this.userId).projects will only be evaluated when the client subscribes. You'll find a lot of information about this if you search for "meteor reactive joins", for example https://www.discovermeteor.com/blog/reactive-joins-in-meteor/
In your case, the clients will always have access to their array of project ids, right? Then the simplest solution would probably be to do something like this on the client:
Tracker.autorun(function(){
var user = Meteor.user()
if(user){
Meteor.subscribe(user.projects)
}
})
So, when the client notices that the array of project ids has changed, it renews the subscription (I'm unsure if passing user.projects to the subscribe call is necessary, but I'm a bit afraid that the subscription isn't is renewed if it's called with the same arguments as before).
Using the reactive-publish package (I am one of authors) you can do:
Meteor.publish('projects', function () {
this.autorun(function (computation) {
var userProjects = Meteor.users.findOne(this.userId, {fields: {projects: 1}}).projects;
return Projects.find({_id: {$in: userProjects}});
});
});
Just be careful to limit the first query only to projects so that autorun is not rerun for changes in other fields.

Meteor, get all users on a specific page

We are building a chat application and are currently working on a system to see all the users in a given room.
We have a Mongo Document set up with an array of active_users where we will push and pull user names to in order to keep track of the online users. We have come to the conclusion that realizing a user has connected to a given room is fairly simple. All we need to do is in the router, when a user accesses the page, we push that user's name into the document.
Now the tricky part is realizing when that user has left that given page? Obviously jQuery isn't a reliable option, so how do we know when a user's connection to a specific page is broken?
You could do this:
Meteor.publish("page", function() {
this._session.socket.on("close", function() {
//Change your active users here
});
});
and for your page that you track
Meteor.subscribe('page');
I use this in the analytics package on atmosphere
There's an Atmosphere package called Presence that does exactly what you need.
Some extra details from the README about keeping track of custom states...
State functions
If you want to track more than just users' online state, you can set a custom state function. (The default state function returns just 'online'):
// Setup the state function on the client
Presence.state = function() {
return {
online: true,
currentRoomId: Session.get('currentRoomId')
};
}
Now we can simply query the collection to find all other users that share the same currentRoomId
Presences.find({ state: { online: true, currentRoomId: Session.get('currentRoomId') } })
Of course, presence will call your function reactively, so everyone will know as soon as things change.
Meteor has connection hooks so you can run a function when the user disconnects from the server. Setting the onClose() callback inside a method called by the client will allow you to close the userId in the function.
Code on the server could be like this:
Meteor.methods({
joinRoom: function( roomId ){
var self = this;
Rooms.update( {roomId: roomId}, {$push:{userId: self.userId}});
self.connection.onClose( function(){
Rooms.update( {roomId: roomId}, {$pull:{userId: self.userId}})
});
}
});

meteor client minimongo retains subscribed collection info after logout. newly-logged-in-user sees old data

I'm using a publish to limit which data a user sees in their report
Meteor.publish("companyReport", function(){
if(!this.userId) return null;
var user = Meteor.users.findOne(this.userId);
var userCompany = user.profile.company;
var userRole = user.roles;
var userName = user.username;
if(function(){Roles.userIsInRole(Meteor.user(), ['chiefs']);})
{return ReportCollection.find({companyName:userCompany});}
else if (function(){Roles.userIsInRole(Meteor.user(), ['managers']);})
{return ReportCollection.find({companyName:userCompany, managerName:userName});}
else
{return null;}
});
Now, on the client side i can simply subscribe. It's generally working fine,except:
I noticed the following odd behaviour i don't want: if i am logged-in as a "chiefs" role user, and can see a company-wide report, if i logout and then login as a "managers" user, i can still see the data i am not supposedly allowed to see.
i know that my publications control mechanism is working ok, as when i login as a manager from another browser, i see the correct results (more limited)
i can only conclude that minimongo on the client is keeping the collection data from the old subscription as a "chiefs" user.
is this correct? if so, how do i flush this data upon logout? or?
what do people usually do to avoid this?
thanks
You probably do need to flush this data on logout, which will involve saving the subscription handle and then stopping it:
// when you subscribe
var reportHandle = Meteor.subscribe('companyReport');
// then when you want to log out
reportHandle.stop();
Meteor.logout();
UPDATE
If I understand your question, you want to make sure you're only ever flushing the minimongo when the user actually logs out:
Meteor.logout(function(err) {
if (err)
console.log(err);
else
reportHandle.stop();
});
ANOTHER UPDATE
Ah, now I see what you're asking. It depends on the package, as there's no onLogout event listener you can use for this. You can probably monkey-patch something quite easily, but I'm not sufficiently familiar with accounts-ui-bootstrap to say for sure.

How to know when user document loaded in Meteor Accounts

I understand that when writing code that depends on the collection being loaded into the client minimongo, that you should explicitly subscribe to the collection and pass in the appropriate callback for when it is finished loading.
My problem is that I store a lot of important subdocuments that my page needs to access in the users collection. I am using Meteor Accounts, and am trying to figure out a similar way to wait until the entire logged in user document is available. When using this to test:
console.log(Meteor.user());
the logged in case, it seems like it first registers an object with just the _id, and then sends the other fields later (I know I have to explicitly add other fields to publish from the server beyond email, etc.).
Is there a way for me to wait for the logged in user document to load completely before executing my code?
Thanks!
Deps.autorun (previously Meteor.autorun) reruns when something reactive changes, which might fit your use case:
Client js
Deps.autorun(function () {
if(Meteor.user() {
//Collection available
}
});
If you're using a subscription you can also use its callback. Have a read about it on the docs as you might have to customize it a bit, and remove the autopublish package as well as get your other collections set up to subscriptions
Server js:
Meteor.publish("userdata", function () {
//You might want to alter this depending on what you want to send down
return Meteor.users.find({}, {}});
});
Client js
Meteor.subscribe("userdata", function() {
//Collection available
});

Resources