meteor.js : find users by email - meteor

In my meteor.js app, I'm trying to write a simple admin page which can find a user by his/her email address.
I can see that in the Meteor.users collection there is an 'emails' array, which has objects like so
{ address : 'foo#foo.com',
verified : false
}
Normally in Mongodb I can search inside this 'emails' array like so :
Meteor.users.find({ emails.address : 'foo#foo.com' });
But this query is throwing an error :
While building the application:
client/admin.js:224:41: Unexpected token .
Aka Meteor doesn't like the nested query...
Any ideas on how to query the Meteor.users collection by email address ?

You can also use what you had, just put it in quotes:
Meteor.users.find({ "emails.address" : 'foo#foo.com' });

If on the server, Meteor has a special function for this :
Accounts.findUserByEmail(email).
I believe this is the recommended way.

Emails holds an array of emails. Each email has an address.
Try { emails: { $elemMatch: { address: "foo#foo.com" } } }.
Information on $elemMatch is here.
Information on emails as an array is here.

By default, Meteor only publishes the logged in user and you can, as you mention, run queries against that user. In order to access the other users you have to publish them on the server:
Meteor.publish("allUsers", function () {
return Meteor.users.find({});
});
And subscribe to them on the client:
Meteor.subscribe('allUsers');
And run the following command
Meteor.users.find({"emails": "me#example.com"}).fetch()
OR
Meteor.users.find({"emails.0": "me#example.com"}).fetch()
Refer this

If you want to find all emails inside Accounts array, and do an insensitive query:
const hasUser = Meteor.users.findOne({
emails: {
$elemMatch: {
address: {
$regex : new RegExp(doc.email, "i")
}
}
}
});

One possible workaround, if this works on the server but not the client, is to use a users_by_email method on the server:
if (Meteor.isServer) {
Meteor.methods({
'get_users_by_email': function(email) {
return Users.find({ emails.address: email }).fetch();
}
});
}
if (Meteor.isClient) {
foo_users = Meteor.call('get_users_by_email', 'foo#bar.baz');
}

Related

Meteor.user with Additional Fields on Client

In Meteor, one can add additional fields to the root-level of the new user document like so:
// See: https://guide.meteor.com/accounts.html#adding-fields-on-registration
Accounts.onCreateUser((options, user) =>
// Add custom field to user document...
user.customField = "custom data";
return user;
});
On the client, one can retrieve some data about the current user like so:
// { _id: "...", emails: [...] }
Meteor.user()
By default, the customField does not exist on the returned user. How can one retrieve that additional field via the Meteor.user() call such that we get { _id: "...", emails: [...], customField: "..." }? At present, the documentation on publishing custom data appears to suggest publishing an additional collection. This is undesired for reasons of overhead in code and traffic. Can one override the default fields for Meteor.user() calls to provide additional fields?
You have a couple of solutions that you can use to solve this.
Null Publication
Meteor.publish(null, function () {
if (this.userId !== null) {
return Meteor.users.find({ _id: this.userId }, { fields: { customField: 1 } });
} else {
return this.ready();
}
}, { is_auto: true });
This will give you the desired result but will also result in an additional database lookup.. While this is don't by _id and is extremely efficient, I still find this to be an unnecessary overhead.
2.Updating the fields the Meteor publishes for the user by default.
Accounts._defaultPublishFields.projection = { customField: 1, ...Accounts._defaultPublishFields.projection };
This has to be ran outside of any Meteor.startup blocks. If ran within one, this will not work. This method will not result in extra calls to your database and is my preferred method of accomplishing this.
You are actually misunderstanding the documentation. It is not suggesting to populate and publish a separate collection, just a separate publication. That's different. You can have multiple publications/subscriptions that all feed the same collection. So all you need to do is:
Server:
Meteor.publish('my-custom-user-data', function() {
return Meteor.users.find(this.userId, {fields: {customField: 1}});
});
Client:
Meteor.subscribe('my-custom-user-data');

How to populate client-side Meteor.user.services after OAuth with built-in accounts-ui package in Meteor v1.4+?

I'm using accounts-ui and accounts-google in Meteor v1.4.1. I can't get the user.services object to appear scoped in the client code. In particular, I need google's profile picture.
I've configured the server-side code to authenticate with Google like so:
import { Meteor } from 'meteor/meteor';
import { ServiceConfiguration } from 'meteor/service-configuration';
const services = Meteor.settings.private.oauth;
for (let service of Object.keys(services)) {
ServiceConfiguration.configurations.upsert({
service
}, {
$set: {
clientId: services[service].app_id,
secret: services[service].secret,
loginStyle: "popup"
}
});
}
...and the client side code to configure permissions like so:
Accounts.ui.config({
requestPermissions: {
google: ['email', 'profile']
},
forceApprovalPrompt: {
google: true
},
passwordSignupFields: 'EMAIL_ONLY'
});
When users click the 'Sign-In with Google' button, a pop-up appears and they can authenticate. No prompt appears, however, despite forceApprovalPrompt being set to true for google.
The big issue is that when I execute this,
const user = Meteor.user();
console.log(user.services);
anywhere in client code, I do not see the expected user services information. I check my database and it is definitely there for the taking:
$ mongo localhost:27017
> db.users.find({})
> ... "services" : { "google" : { "accessToken" : ... } } ...
I'm curious what I'm missing? Should I explicitly define a publish function in order for user services data to exist in the client?
The services property is intentionally hidden on the client side for security reasons. There are a couple of approaches here :
Suggestions
My preferred one would be to expose a meteor method to bring you the
public keys and avatars you might need in the few places you'd need
them.
On a successful login, you could record the data you need somewhere in the user object, but outside of the services property.
As you said, you could make a new publication which explicitly specifies which fields to retrieve and which ones to hide. You have to be careful what you publish, though.
Code Examples
Meteor methods:
// server
Meteor.methods({
getProfilePicture() {
const services = Meteor.user().services;
// replace with actual profile picture property
return services.google && services.google.profilePicture;
}
});
// client
Meteor.call('getProfilePicture', (err, profilePicture) => {
console.log('profile picture url', profilePicture);
});
Update on successful user creation (you might want to have a login hook as well to reflect any avatar/picture changes in google):
// Configure what happens with profile data on user creation
Accounts.onCreateUser((options, user) => {
if (!('profile' in options)) { options.profile = {}; }
if (!('providers' in options.profile)) { options.profile.providers = {}; }
// Define additional specific profile options here
if (user.services.google) {
options.profile.providers.google = {
picture: user.services.google.picture
}
}
user.profile = options.profile;
return user;
});
Publish only select data...
// Server
Meteor.publish('userData', function () {
if (this.userId) {
return Meteor.users.find({ _id: this.userId }, {
fields: { other: 1, things: 1 }
});
} else {
this.ready();
}
});
// Client
Meteor.subscribe('userData');

How to associate post field with user email? [METEOR]

I have a posts model that I have published and is working fine. However, I have added the following field via simpleSchemas plugin:
userEmail: {
type: String,
autoValue: function() {
if (this.isInsert) {
return Meteor.user().email;
} else if (this.isUpsert) {
return {$setOnInsert: Meteor.user().email};
} else {
this.unset();
}
}
}
When I have this enabled, submit forms don't work, but don't raise any errors. Am I perhaps calling on Meteor.user().email incorrectly? How do I associate the userEmail field with the email of the user who created the post?
The correct syntaxis is.
Meteor.user().emails[0].address
The default Meteor.users collection stores emails in an array (To support multiple emails). Something like return Meteor.user().emails[0].address should work.

Create a method not limited by subscriptions

I've created a method that checks if an email already has an account:
insertGroupMember: function(eventId, memberDetails) {
var memberAccount = Meteor.users.findOne({'emails.address': memberDetails.email});
if (memberAccount) {
console.log('Existing User')
} else {
console.log('Create User')
}
}
But will only receive a result when I am subscribed to a publication with all users.emails. How can I achieve the same results without having to publish everyone's email? I think thats kind of bad for security/privacy, right?
You are correct - you don't want to publish all of the users to the client just to accomplish this. The best solution is to create a method defined only on the server, and then call it from the client.
server/methods.js
Meteor.methods({
insertGroupMember: function(eventId, memberDetails) {
...
}
});
client/someTemplate.js
Meteor.call('insertGroupMember', eventId, memberDetails, function (err, result){
...
});

Right way of accessing Meteor.users from the client

I have defined some useful fields in the users collection for my convenience. What would be the right way of allowing a client to access the corresponding field? I'm using the autopublish package, but Meteor.user() from the client side only reveals the emails array.
You have to explicitly tell Meteor which fields from users to include when querying users collection.
For example to publish custom "avatar" field on client:
// Client only code
if (Meteor.isClient) {
Meteor.subscribe("currentUserData");
...
}
// Server-only code
if (Meteor.isServer) {
Meteor.publish("currentUserData", function() {
return Meteor.users.find({}, {
fields : {
'avatar' : 1
}
});
});
...
}

Resources