Meteor not publishing all user fields with autopublish package - meteor

I have not removed autopublish nor insecure package but when I run Meteor.user() it shows the services. Facebook object but not the services.google object (nor the services.password but I don't need that one).
The Google and Facebook objects are definitely both in the database, why would it only show one? How would you go about troubleshooting? I thought all fields were puckishness when autupub was on.

First, make sure "accounts-base", "accounts-facebook" and "accounts-google" are listed in your ".meteor/packages" file. Everything indicates that you don't have the "accounts-google" package added to that file.
The "accounts-base" package, which publishes "Meteor.users", overwrites the default autopublish functionality (https://github.com/meteor/meteor/blob/devel/packages/accounts-base/accounts_common.js#L82).
To see which fields get auto published refer to the source code at accounts_server.js, starts at: https://github.com/meteor/meteor/blob/devel/packages/accounts-base/accounts_server.js#L683
You'll see the following (read the comment):
// If autopublish is on, publish these user fields. Login service
// packages (eg accounts-google) add to these by calling
// Accounts.addAutopublishFields Notably, this isn't implemented with
// multiple publishes since DDP only merges only across top-level
// fields, not subfields (such as 'services.facebook.accessToken')
var autopublishFields = {
loggedInUser: ['profile', 'username', 'emails'],
otherUsers: ['profile', 'username']
};
Which means "Accounts.addAutopublishFields" method will be called by the "accounts-google", "accounts-facebook", etc packages to add fields to that publication.
Now for example if you look at the "accounts-google" package, in the "google.js" file, it has the following:
Accounts.addAutopublishFields({
forLoggedInUser: _.map(
// publish access token since it can be used from the client (if
// transmitted over ssl or on
// localhost). https://developers.google.com/accounts/docs/OAuth2UserAgent
// refresh token probably shouldn't be sent down.
Google.whitelistedFields.concat(['accessToken', 'expiresAt']), // don't publish refresh token
function (subfield) { return 'services.google.' + subfield; }),
forOtherUsers: _.map(
// even with autopublish, no legitimate web app should be
// publishing all users' emails
_.without(Google.whitelistedFields, 'email', 'verified_email'),
function (subfield) { return 'services.google.' + subfield; })
});
Which means that if you have the "accounts-google" packaged enabled, it publishes the following fields to "Meteor.users":
When logged in: 'profile', 'username', 'emails' and all "service.google" fields except 'accessToken', 'expiresAt'
When logged off: 'profile', 'username' and all "service.google" fields except 'email', 'verified_email'
Same thing for the "accounts-facebook" package.

Related

How to prevent Meteor's accounts-base from auto-publishing the email of the current user?

According to Meteor's documentation on the Accounts package:
By default, the current user’s username, emails and profile are published to the client.
Is it possible to prevent Meteor from auto-publishing these fields? I know it's just for the user that is logged in, but that user could take a walk or be online somewhere public.
This structure of the code seems to be defined in accounts_server.js (search for autopublish and email - lines 37 and 696).
The most straightforward way to do this is going to be to modify the value of Accounts._defaultPublishFields.projection and remove the emails key. An easy way to do this while keeping the other values is to use a combination of rest and spread like so:
import { Accounts } from 'meteor/accounts-base';
const { emails, ...fields } = Accounts._defaultPublishFields.projection;
Accounts._defaultPublishFields.projection = { ...fields };
Just make sure this runs on the server and you should be good to go.

Setting up 'Trigger Email' Firebase Extension

I learned about firebase and cloud functions recently and have been able to develop simple applications with them.
I now want to expand my knowledge and really struggling with Trigger Email Extension.
On a specific event on my firebase, I want to fire an email to the user in a custom format, but I am unable to even activate the extension for now.
Can someone please explain with example please about these fields marked in the picture?
I had this question too, but got it resolved. Here's your answer:
"Email documents collection" is the collection that will be read to trigger the emails. I recommend leaving named "mail" unless you already have a collection named mail.
"Users collection (Optional)" refers to a collection (if any) that you want to use in tandem with a user auth system. I haven't had this specific use case yet, but I imagine once you understand how Trigger Email operates, it should be somewhat self-explanatory.
"Templates collection (Optional)" is helpful for templates in which you can use handlebar.js is automatically input specific information per user. (eg. <p>Hello, {{first_name}}</p> etc.) Similar to the previously mentioned collections, you can name it whatever you want.
How to create a template (I have yet to actually implement this, so take this with a grain of salt):
In your templates collection, you want to name each document with a memorable ID. Firebase gives the example:
{
subject: "#{{username}} is now following you!",
html: "Just writing to let you know that <code>#{{username}}</code> ({{name}}) is now following you.",
attachments: [
{
filename: "{{username}}.jpg",
path: "{{imagePath}}"
}
]
}
...specifying a good ID would be following. As you can see, the documents should be structured just like any other email you would send out.
Here is an example of using the above template in javascript:
firestore()
.collection("mail")
.add({
toUids: ["abc123"], // This relates to the Users Collection
template: {
name: "following", // Specify the template
// Specify the information for the Handlebars
// which can also be pulled from your users (toUids)
// if you have the data stored in a user collection.
// Of course that gets more into the world of a user auth system.
data: {
username: "ada",
name: "Ada Lovelace",
imagePath: "https://path-to-file/image-name.jpg"
},
},
})
I hope this helps. Let me know if you have an issues getting this set up.

How to let a user choose different Facebook permissions in the same page?

Suppose you want to build a webpage with Facebook PHP SDK, where you want to allow the user to select the information Facebook will return to the server. I've came with the following code to allow an user to either choose from allowing Facebook to send only the basic profile or else to also send the pages managed by this user.
session_start();
// Load the Facebook PHP SDK
require_once __DIR__ . '/facebook-sdk-v5/autoload.php';
define('APP_ID', 'xxxxxxxxxxx');
define('APP_SECRET', 'xxxxxxxxxxxxxxxxxxxx');
$fbProfile = new Facebook\Facebook([
'app_id' => APP_ID,
'app_secret' => APP_SECRET,
'default_graph_version' => 'v2.7'
]);
$fbPages = new Facebook\Facebook([
'app_id' => APP_ID,
'app_secret' => APP_SECRET,
'default_graph_version' => 'v2.7'
]);
$helperProfile = $fbProfile->getRedirectLoginHelper();
$redirectUrlProfile = 'http://www.example.com/link1.php';
$loginUrlProfile = $helperProfile->getLoginUrl($redirectUrlProfile);
echo 'Get profile with Facebook!<br>';
$helperPages = $fbPages->getRedirectLoginHelper();
$permissions = ['pages_show_list']; // Optional permissions
$redirectUrlPages = "http://www.example.com/link2.php";
$loginUrlPages = $helperPages->getLoginUrl($redirectUrlPages, $permissions);
echo 'Get pages with Facebook!';
If I use the above code (commenting the non-relevant parts) with only one facebook object to either retrieve the profile or the pages managed by user (but not both), everything works fine. But if I use both objects concurrently to give a choice to the user, I get a FacebookSDKException. I guess this is due to CRSF cookies.
Is there any way to circunvent this problem?
I guess this is due to CRSF cookies.
Correct. Calling the getLoginUrl method creates a new random state value and writes it into the session, overwriting any previously stored one.
So if you call the method twice (or more times), login will only work if you call the login dialog via the last login URL created, because only that contains a state value that matches the one stored in the session.
If you want to keep using two different redirect URIs, then you need to implement an additional step to create the correct login URL, and only that one.
So you have two links in your page, both pointing to a script on your server, and passing what permissions to ask for via a GET parameter (whether you want to pass permission names directly, or just a flag like ?loginmode=1/?loginmode=2, is up to you.)
In that script, you decide which redirect URI and scope value to call the getLoginUrl method with - once. And then your script just redirects to that login URL.
(But keep in mind that the step that exchanges the code for an access token also requires the redirect URI parameter to be passed, again - and again with the exact same value that was used in the login dialog call.)
So doing it the way #luschn suggested in comments, using the JS SDK for login purposes, is probably much easier. FB.login can be called with different scopes from different points in your client-side JS code without any such problems.

How to get Iron-router query parameters in server hook

I am trying to add a referral system to my project, so currently I am basing it off of this package. The issue I am running into is my project only uses accounts-google and not accounts-password. The way this package works is it adds the iron router query parameters for the referrerCode (/register?r=ReferralCodeHere)through a preSignUpHook. I believe this only works with accounts-password wont work when creating an account with an API such as accounts-google.
My idea around this is to use a Meteor.users.before.insert hook to grab the iron router query parameters and insert them into my referrerCode field in Meteor.users since I'm already using Meteor Collection Hooks for a couple of other things.
The issue is I havent been able to find a way to get the query parameters on the server, I was hoping to do something like this:
Meteor.users.before.insert(function(userId, doc) {
doc.referrerCode = Referrer._referrerCode; // Link 1
});
(Link 1)
But this will just come up as undefined.
If I'm at my register page and it has a query like this for example: example.com/register?r=12345 Then I run Router.current().params.query.r on the client it returns 12345. Basically I just need to have that saved to the referralCode field in Meteor.users when a new user creates an account, if a referral code exists in the register URL.
I'm a bit lost with this one. I thought about setting it as a Session variable and then getting that in the before.insert hook, but that again only works on the client side. I'm thinking a meteor method might be best for this, but I'm not exactly sure how I would structure it. Any help is greatly appreciated!
Put the referral token into profile
Use that in your hook
Below I've copied some code that I've used before. It is built around an Invitations collection that tracks who invited who:
client:
var profile = {};
... any other profile settings you've captured
if ( token ) profile.referralToken = token;
Accounts.createUser({ email: email, password: password, profile: profile }, function(err){ ...})
hook:
if ( options.profile.referralToken ){ // referral case
var invitation = Invitations.findOne({ token: options.profile.referralToken });
if ( invitation )
user.invitationId = invitation._id; // the invitation used
user.invitedBy = invitation.userId; // the referring user
}
delete options.profile.referralToken;
}
return user;

Meteor.user() not fully loaded after login

I am using Meteor 0.8.2 with accounts-facebook. I set up a limited publication for the users this way:
Meteor.publish('users', function () {
return Meteor.users.find({}, {fields: {'profile.picture': 1, 'profile.gender':1, 'profile.type':1}, sort: {'profile.likes': -1}});
});
Now this works great: when I requests a user list from the client I get a list of all users, with the current user's fields all shown and only the 3 published fields for the others. Except: right after login.
When I login and type Meteor.user(), here is what I get:
_id: "uACx6sTiHSc4j4khk"
profile: Object { gender="male", type="1", picture="http://....jpg"}
This stays like that until I refresh the page using the browser button. After refreshing, Meteor.user() gives all the fields available, while Meteor.users.find() still gives the correct restrictions. (except for the current user of course)
Why does my current user not get all its fields right away? I read about a Meteor.userLoaded() method used to wait for the user to be loaded, but it seems to be obsolete in the latest version.
You're running into an interaction between the restriction of merging fields across publications, and the default user publication which sends the profile field.
First, note that there is a built-in publication that always sends the currently logged in user's entire profile field to that user:
https://github.com/meteor/meteor/blob/devel/packages/accounts-base/accounts_server.js#L1172
Second, merging of fields at more than one level deep is currently not supported:
https://github.com/meteor/meteor/issues/998
What you currently have is an issue where the default publication is sending something like the following
{
username: ...,
emails: [ ... ],
profile: {
... all fields ...
}
}
whereas the publication you have set up is sending
{
profile: {
picture: ...
gender: ...
type: ...
}
}
These get merged on the client according to the rules for how subscriptions are resolved (http://docs.meteor.com/#meteor_subscribe). In particular, see the last paragraph. Meteor knows to merge the username and email fields with the profile field. However, it doesn't do this merging at the inner level. So one of the profile fields will get chosen arbitrarily to show up in the client's collection. If the first one wins, you will see profile.likes. If the second one wins, you won't.
It's likely that this behavior is somewhat deterministic and changes depending on whether a normal login handler is called or a resume handler (i.e. when reloading the browser). Hence why it looks like it hasn't loaded.
As Andrew explained, and as I kinda thought, what happened is that there is another "hidden" publication for the current user, which conflicts with mine. All I had to do in order to fix this was to simply exclude the current user from my publication, since it is already fully published by default:
Meteor.publish('users', function () {
return Meteor.users.find({_id:{$ne: this.userId}}, {fields: {'profile.picture': 1, 'profile.gender':1, 'profile.type':1}, sort: {'profile.likes': -1}});
});
This simple $ne does it for me.

Resources