I've searched and read through pretty much all the questions relating to Meteor's pub/sub functionality but cant seem to find an answer that directly addresses my question.
I am learning Meteor and would like to create the following;
A user visits a page and is presented with an input box, they enter their username and if its a valid username I present them with the password input field.
The problem I seem to be having is that I cant access the Meteor.users collection unless I am logged in (the auto-publish and insecure packages have been removed).
So my question is 2 fold;
Is is possible to access the Meteor.users.find({username: 'value of input box'}) without being logged in?
If so, what do I need to do?
Here is how I have my code structured below;
/server/publications.js:
Meteor.publish("staff", function () {
return Meteor.users.find();
});
/lib/router.js:
Router.configure({
layoutTemplate: 'layout'
});
Router.map(function() {
this.route('home', {path: '/', data: function() {return Meteor.subscribe('staff');} });
});
/client/views/home/home.js:
Template.home.events({
'submit form': function(e) {
e.preventDefault();
var guest = $("input#name");
var staffMember = Meteor.users.findOne({username: $(guest).val()});
});
Unless I am logged in as any user, Meteor.users.find().fetch() always returns an empty array. Any help is greatly appreciated.
You are publishing all the Users to be visible on the client. That is not a very secure position to be in since all your user data will be visible on all the clients. The pragmatic way to solve the problem is to allow the user to see only a limited number of users, i.e. publish a different set of users, depending on the userId.
As #Hubert's answer below indicates, you also need to subscribe to the publication on the client to actually fetch the published accounts over.
You can do this checking for validity securely by creating a Method call on the server:
/server/methods.js:
Meteor.method({
validUserName: function (userName) {
check(userName, string);
// You have access to Meteor.users here.
// This assumes that mere existence of a user with the same username will
// render the entered username invalid. If there are more checks you want to
// make, they should be made here.
return !!Meteor.users.find({username: userName});
}
});
/client/views/home/home.js:
Template.home.events({
'submit form': function(e) {
e.preventDefault();
var guest = $("input#name");
var userNameEntered = $(guest).val();
Session.set('requestInProgress', true);
Meteor.call('validUserName', function (isUserNameValid) {
Session.set('requestInProgress', false);
// ... take action here.
});
});
You're publishing all your user data to everyone, now you just need to subscibe to it:
Deps.autorun(function(){
Meteor.subscribe('staff');
});
That said, make sure that in such subscription you publish only non-sensitive user data, as it will be visible to everyone. Ideally, create another subscription for that purpose and filter just the username via the fields param.
Related
I have an interface in my website where a user can create an "unclaimed" order for another user and send them an invitation. The invitation link will be formatted as /enrollment/orders/:_id, where _id is the id of the order.
One catch is that this can be sent multiple times. On the first time, the user that is invited might not have a password set.
Meteor.publish('enrolled_order', function (token) {
// if user has their password reset, token will also be set for user's account
return Orders.find({
'enrollment.token': token
});
});
Here's the catch: During this publication, I want to check certain aspects of the user record and take different actions instead of publishing it. For security, I believe this will need to be done on the server to work appropriately.
if there is no this.userId, I want to send them to login.
if the user does not have a password set, I want to redirect them to the reset password page.
Is this possible via a meteor publication?
I think you would need to do this in the router. If you're using Iron Router, you could use onBeforeAction to check whether the user is logged in:
Router.route('/enrollment/orders/:_id', {
subscriptions: function() {
return Meteor.subscribe('enrolled_order', this.params._id);
},
onBeforeAction: function() {
if( ! Meteor.userId() ) {
Router.go( 'loginTemplateName' );
}
}
});
You aren't going to want to return the hashed password directly to the client, so maybe setting a variable to say whether the password exists or not might be the best approach?
I can't remember if onBeforeAction happens before waitOn, but you might be able to get away with waitOn instead of subscriptions.
I want to create a form to add additional profile details (address, phone number) for registered users.
This is my js file:
Template.userProfileEdit.events({
'submit form': function(event, template){
event.preventDefault();
var addressVar = template.find('#inputaddress');
var profile = {};
var fields = template.findAll('input[type="text"]');
for(var i = 0; i<fields.length; i++) {
profile[$(fields[i]).attr('name')] = $(fields[i]).val();
}
if(Meteor.users.update(Meteor.userId(), {$set: {profile: profile}})) {
$(template.find('#thirdNodal')).modal('hide');
}
}
});
Template.userProfileEdit.helpers({
address: function(){
return Meteor.user().profile.address;
}
});
Accounts.findUserByUsername = function (username) {
return Accounts.findUserByQuery({
username: username
});
};
Accounts.findUserByEmail = function (email) {
return Accounts.findUserByQuery({
email: email
});
};
And this is my html form:
<tr>
<td>Address</td>
<td>{{currentUser.profile.address}}</td>
</tr>
Does anybody know the easiest way how to insert(address) and update it? Or is it ok?
This isn't far from a working solution, but I see a couple issues.
Meteor.users.update(Meteor.userId(), {$set: {profile: profile}}) is going to override the profile object, erasing anything already stored there. Also, giving the user client-side permissions to update a user document is a security disaster waiting to happen. Ideally you should make a call to a meteor method located in your server folder instead, so that you can keep your permissions tight by validating the changes on the server.
Second, if you utilize meteor's template system (blaze) you should hardly ever have to use jQuery selectors to retrieve values. Instead, look up HTML forms so that you can use Template events for easy access to form fields upon clicking a submit button.
You should complete the Meteor tutorial which will provide you with all of this information and more.
I'm not able to use the node server debugger so I'm posting here to see if I can get a nudge in the right direction.
I am trying to allow multiple users to edit documents created by any of the users within their specific company. My code is below. Any help would be appreciated.
(Server)
ComponentsCollection.allow({
// Passing in the user object (has profile object {company: "1234"}
// Passing in document (has companyId field that is equal to "1234"
update: function(userObject, components) {
return ownsDocument(userObject, components);
}
});
(Server)
// check to ensure user editing document created/owned by the company
ownsDocument = function(userObject, doc) {
return userObject.profile.company === doc.companyId;
}
The error I'm getting is: Exception while invoking method '/components/update' TypeError: Cannot read property 'company' of undefined
I'm trying to be as secure as possible, though am doing some checks before presenting any data to the user, so I'm not sure if this additional check is necessary. Any advice on security for allowing multiple users to edit documents created by the company would be awesome. Thanks in advance. -Chris
Update (solution):
// check that the userId specified owns the documents
ownsDocument = function(userId, doc) {
// Gets the user form the userId being passed in
var userObject = Meteor.users.findOne(userId);
// Checking if the user is associated with the company that created the document being modified
// Returns true/false respectively
return doc.companyId === userObject.profile.companyId;
}
Looking at the docs, it looks like the first argument to the allow/deny functions is a user ID, not a user document. So you'll have to do Meteor.users.findOne(userId) to get to the document first.
Do keep in mind that users can write to their own profile subdocument, so if you don't disable that, users will be able to change their own company, allowing them to edit any post. You should move company outside of profile.
(If you can't use a proper debugger, old-fashioned console.log still works. Adding console.log(userObject) to ownsDocument probably would have revealed the solution.)
I'm trying to use the Meteor Roles package: https://github.com/alanning/meteor-roles
to obviously create a new field in user model.
The user is created no problem but the 'roles' field I'm trying to define isn't created. I can add things like 'Profile' and details within that too. But for some reason I can't make a roles field. Here's my form:
Template.signup.events({
'submit #signup-form' : function(e, t) {
e.preventDefault();
var roles = ['admin'],
email = t.find('#email').value,
password = t.find('#password').value;
Accounts.createUser({email: email, password : password, roles: roles}, function(err){
if (err) {
alert("User Not Added")
} else {
console.log("User Added.")
}
});
}
});
Eventually I'll need to publish this to the client but for right now I just want the field to show in MongoDb, which it's not.
3 things:
I feel like the code above should work but I'm clearly missing something
In the package docs it mentions this Roles.addUsersToRoles which I
tried but no luck
Or do I need to possibly update the record, after it's been created?
I did go into the DB and manually added the field and associated string to update it (with $set) and it worked. But from the form itself though, no luck.
Any pointers would be much appreciated. Thank you.
The Accounts.createUser function only lets you add arbitrary user properties via the profile option which is where they end up getting stored in mongo. That is why Meteor is ignoring the roles: roles part of your Accounts.createUser call.
It is true that the meteor-roles package stores the list of roles assigned to a user directly in the users collection, but that is almost just an implementation detail and you are probably best off sticking to the API that meteor-roles provides for adding users to a role:
Roles.addUsersToRoles(<userId>,[<list of roles>])
The userId passed to Roles.addUsersToRoles is the value returned by Accounts.createUser when its called on the server which is probably where you want to be doing this as that feels way more secure.
The Accounts.createUser function only takes username, email, password and profile as params for the user object. See the documentation here. So, to add another field to a new user object, you need to add it in a second step:
var uid = Accounts.createUser({email: email, password: password});
Meteor.users.update(uid, {$set: {roles: roles}});
Is there any way to pick up when a user logs out of the website? I need to do some clean up when they do so. Using the built-in meteor.js user accounts.
I'll be doing some validation using it, so I need a solution that cannot be trigger on behalf of other users on the client side - preferably something completely server side.
You may use Deps.autorun to setup a custom handler observing Meteor.userId() reactive variable changes.
Meteor.userId() (and Meteor.user()) are reactive variables returning respectively the currently logged in userId (null if none) and the corresponding user document (record) in the Meteor.users collection.
As a consequence one can track signing in/out of a Meteor application by reacting to the modification of those reactive data sources.
client/main.js :
var lastUser=null;
Meteor.startup(function(){
Deps.autorun(function(){
var userId=Meteor.userId();
if(userId){
console.log(userId+" connected");
// do something with Meteor.user()
}
else if(lastUser){
console.log(lastUser._id+" disconnected");
// can't use Meteor.user() anymore
// do something with lastUser (read-only !)
Meteor.call("userDisconnected",lastUser._id);
}
lastUser=Meteor.user();
});
});
In this code sample, I'm setting up a source file local variable (lastUser) to keep track of the last user that was logged in the application.
Then in Meteor.startup, I use Deps.autorun to setup a reactive context (code that will get re-executed whenever one of the reactive data sources accessed is modified).
This reactive context tracks Meteor.userId() variation and reacts accordingly.
In the deconnection code, you can't use Meteor.user() but if you want to access the last user document you can use the lastUser variable.
You can call a server method with the lastUser._id as argument if you want to modify the document after logging out.
server/server.js
Meteor.methods({
userDisconnected:function(userId){
check(userId,String);
var user=Meteor.users.findOne(userId);
// do something with user (read-write)
}
});
Be aware though that malicious clients can call this server method with anyone userId, so you shouldn't do anything critical unless you setup some verification code.
Use the user-status package that I've created: https://github.com/mizzao/meteor-user-status. This is completely server-side.
See the docs for usage, but you can attach an event handler to a session logout:
UserStatus.events.on "connectionLogout", (fields) ->
console.log(fields.userId + " with connection " + fields.connectionId + " logged out")
Note that a user can be logged in from different places at once with multiple sessions. This smart package detects all of them as well as whether the user is online at all. For more information or to implement your own method, check out the code.
Currently the package doesn't distinguish between browser window closes and logouts, and treats them as the same.
We had a similar, though not exact requirement. We wanted to do a bit of clean up on the client when they signed out. We did it by hijacking Meteor.logout:
if (Meteor.isClient) {
var _logout = Meteor.logout;
Meteor.logout = function customLogout() {
// Do your thing here
_logout.apply(Meteor, arguments);
}
}
The answer provided by #saimeunt looks about right, but it is a bit fluffy for what I needed. Instead I went with a very simple approach like this:
if (Meteor.isClient) {
Deps.autorun(function () {
if(!Meteor.userId())
{
Session.set('store', null);
}
});
}
This is however triggered during a page load if the user has not yet logged in, which might be undesirable. So you could go with something like this instead:
if (Meteor.isClient) {
var userWasLoggedIn = false;
Deps.autorun(function (c) {
if(!Meteor.userId())
{
if(userWasLoggedIn)
{
console.log('Clean up');
Session.set('store', null);
}
}
else
{
userWasLoggedIn = true;
}
});
}
None of the solutions worked for me, since they all suffered from the problem of not being able to distinguish between manual logout by the user vs. browser page reload/close.
I'm now going with a hack, but at least it works (as long as you don't provide any other means of logging out than the default accounts-ui buttons):
Template._loginButtons.events({
'click #login-buttons-logout': function(ev) {
console.log("manual log out");
// do stuff
}
});
You can use the following Meteor.logout - http://docs.meteor.com/#meteor_logout