How to avoid that Accounts.onCreateUser overwrites users.profile? - meteor

on the client site I have an extraSignupField -> name. This is stored in users.profile.name. Here is the code:
Meteor.startup(function() {
Accounts.ui.config({
passwordSignupFields: 'EMAIL_ONLY'
});
AccountsEntry.config({
homeRoute: '/',
dashboardRoute: '/dashboard',
profileRoute: '/profile',
language: 'de',
showSignupCode: false,
extraSignUpFields: [{
field: "name",
label: "Name",
type: "text",
required: true
}]
});
});
On the server site I run onCreateUser, this gets fired but it only sets the values which at mentioned there. NO users.profile entry is created when onCreateUser is in place.
Here is the server code:
Meteor.startup(function() {
AccountsEntry.config({
signupCode: null
});
Accounts.onCreateUser(function (options, user) {
user.username = options.email;
return user;
});
});
My goal is to keep the data from users.profile AND the username entry. Unfortunately I got stuck at this point.
Thanks a lot
Michael

When you write your own Accounts.onCreateUser, it overrides the default. In the default, options.profile is copied to user.profile. You can restore default behavior by making this copy in your own version of the function. You see this in the docs.
Accounts.onCreateUser( function (options, user) {
if (options.profile) user.profile = options.profile;
user.username = options.email;
return user;
}

Related

Meteor "update failed: Access denied" when user attempts to update their own profile?

Im adding a field to a user's account on creation. This is working fine:
Accounts.onCreateUser((options, user) => {
user.groups = [2];
return user;
});
I need to make a function that allows the user to change this. When I run this from the front-end I get an error "update failed: Access denied"
Meteor.users.update(
{ _id: Meteor.userId() },
{
$set: { groups: [4, 5] },
},
);
In server/main.js I have:
Meteor.publish('currentUser', function() {
return Meteor.users.find({ _id: this.userId }, { fields: { groups: 1 } });
});
Do not make db updates from the client directly. No client can ever be trusted.
Having said that, there are two ways to deal with this:
ONE
As per the documentation :
By default, the current user’s username, emails and profile are
published to the client. You can publish additional fields for the
current user with:
// Server
Meteor.publish('userData', function () {
if (this.userId) {
return Meteor.users.find({ _id: this.userId }, {
fields: { groups: 1 }
});
} else {
this.ready();
}
});
Meteor.users.allow({
update: function(userId, user) {
return true;
/**
* Don't use `return true` in production!
* You probably need something like this:
* return Meteor.users.findOne(userId).profile.isAdmin;
*/
}
});
// Client
Meteor.subscribe('userData');
Meteor allow rules
TWO
Define a meteor method in the server and have the server update the relevant data for you AFTER it does some validations. Server code is always trusted.
server.js
Meteor.method({
updateGroups: function(data){
// make changes to the user record
});

Meteor SignUps Forbidden on Accounts.createUser

I'm getting the error "SignUps Forbidden" when i try to create a user account. Any ideas why?
My packages:
useraccounts:materialize
materialize:materialize
accounts-password
accounts-facebook
service-configuration
accounts-google
accounts-twitter
kadira:blaze-layout
msavin:mongol
kadira:flow-router
kevohagan:sweetalert
Client Code:
Template.register.events({
'click #register-button': function(e, t) {
e.preventDefault();
// Retrieve the input field values
var email = $('#email').val(),
firstName = $('#first-name').val(),
lastName = $('#last-name').val(),
password = $('#password').val(),
passwordAgain = $('#password-again').val();
// Trim Helper
var trimInput = function(val) {
return val.replace(/^\s*|\s*$/g, "");
}
var email = trimInput(email);
// If validation passes, supply the appropriate fields to the
// Meteor.loginWithPassword() function.
Accounts.createUser({
email: email,
firstName: firstName,
lastName: lastName,
password: password
}, function(error) {
if (error) {
return swal({
title: error.reason,
text: "Please try again",
showConfirmButton: true,
type: "error"
});
} else {
FlowRouter.go('/');
}
});
return false;
}
});
Server code
Accounts.onCreateUser(function(options, user) {
user.profile = options.profile || {};
user.profile.firstName = options.firstName;
user.profile.lastName = options.lastName;
user.profile.organization = ["Org"];
user.roles = ["User"];
return user;
});
UPDATE:
Here is a link to the repo
The problem seems to be on .....meteor\local\build\programs\server\packages. If i switch the value to false it's useless because it resets on every build.
// Client side account creation is disabled by default:
// the methos ATCreateUserServer is used instead!
// to actually disable client side account creation use:
//
// AccountsTemplates.config({
// forbidClientAccountCreation: true
// });
Accounts.config({
forbidClientAccountCreation: true
});
I had to remove the useraccounts:materialize in order to solve this problem
I don't think that the current accepted answer is the right one.
If you want to keep your packages yet override the setting you can change the value of Accounts._options.forbidClientAccountCreation in your code.
Set it to true if you want to prevent the account creation, or to false otherwise.

Add accountStatus to new users on app startup

I'm trying to add accountStatus to the users I create when I first run the application however it keeps crashing. accountStatus is not part of user.profile.
Can someone please look at my code and tell me what I'm doing wrong.
Thanks for any help.
Path: server.js
// run at Meteor app startup
Meteor.startup(function(options, user) {
// if users database is empty, seed these values
if(Meteor.users.find().count() < 1) {
// users array
var users = [
{firstName: 'Sam', lastName: 'Smith', email: 'sam#gmail.com', roles: ['is_student']},
];
// user creation
_.each(users, function(userData) {
// return id for use in roles assignment below
var userId = Accounts.createUser({
email: userData.email,
password: 'password',
profile: {
firstName: userData.firstName,
lastName: userData.lastName,
}
});
// verify user email
Meteor.users.update({ _id: userId }, { $set: { 'emails.0.verified': true } });
// add roles to user
Roles.addUsersToRoles(userId, userData.roles);
// add accountStatus and set to true
_.extend(userId, { accountStatus: true });
});
console.log('New users created!');
}
});
Look at this line:
_.extend(userId, { accountStatus: true });
And look at _.extend definition:
Copy all of the properties in the source objects over to the destination object, and return the destination object. It's in-order, so the last source will override properties of the same name in previous arguments.
What this line is supposed to do?

Empty Facebook friends list

So I'm using the "Facebook-sdk" plugin. I'm initializing Facebook like this:
FB.init({ appId: 'xxxxxxxxxxx', cookie: true, xfbml: true, oauth: true, status: true });
FB.login(function(){ // get permissions
}, {scope: 'user_friends, read_friendlists, user_photos, email, publish_actions '});
FB.getLoginStatus(function (response) { // to generate AccessToken
if (response.authResponse) {
console.log('LoginStatusResponse: ' + JSON.stringify(response, null, 4));
} else {
console.log('AuthResponse: No');
}
});
Then in response to a button click event, I'm doing:
'click #get_fb_friends' : function(event, template) {
FB.api(
"/me/friends",
function (response) {
if (response && !response.error) {
console.log('GOT them !!!' + JSON.stringify(response, null, 4));
return response;
} else {
console.log('No can do' + JSON.stringify(response, null, 4));
}
});
}
The thing is that I'm getting an empty Data variable:
GOT them !!!{
"data": []
}
PS: The query "/me" returns all information about myself, it's the "/me/friends" that doesn't work.
Can it be a permissions problem ?
Facebook has changed its api a few weeks ago,you can only access a list of your friends when they have personally accepted to use your app.. Nothing to do about it
Like #Wampie said, the API has changed, so you could try to use the v1 API (keep in mind that you'll need to request a new access token).
And you can still invite friends to your app by using the apprequests method.
FB.ui({
method: 'apprequests',
message: 'You should learn more about the Platform.'
}, function(){
console.log(arguments);
});
Example here:
https://www.fbrell.com/fb.ui/apprequests

How to make sign-up invitation only?

Using Meteor accounts (and accounts-ui) is there an easy way to make new user sign-ups invitation only? For example by providing an invitation link or an invitation code.
The only thing related I could find in the Meteor documentation is Meteor.sendEnrollmentEmail but it doesn't solve my problem.
You can do this with the built in package, but I found it alot easier and powerful to roll a simple implementation.
You'll need to:
Create a collection, eg UserInvitations to contain the invites to become a user.
Create UI for making UserInvitations / insert some using meteor mongo
Using iron-router or similar create a route, eg:
Router.map ->
#route 'register',
path: '/register/:invitationId'
template: 'userRegistration'
data: ->
return {
invitationId: #params.invitationId
}
onBeforeAction: ->
if Meteor.userId()?
Router.go('home')
return
When the form in userRegistration is submitted - call
Accounts.createUser({invitationId: Template.instance().data.invitationId /*,.. other fields */})
On the server, make an Accounts.onCreateUser hook to pass through the invitationId from options to the user
Accounts.onCreateUser(function(options, user){
user.invitationId = options.invitationId
return user;
});
Also, on the server make an Accounts.validateNewUser hook to check the invitationId and mark the invitation as used
Accounts.validateNewUser(function(user){
check(user.invitationId, String);
// validate invitation
invitation = UserInvitations.findOne({_id: user.invitationId, used: false});
if (!invitation){
throw new Meteor.Error(403, "Please provide a valid invitation");
}
// prevent the token being re-used.
UserInvitations.update({_id: user.invitationId, used: false}, {$set: {used: true}});
return true
});
Now, only users that have a valid unused invitationId can register.
EDIT: Oct 2014 - Updated to use meteor 0.9.x API's
To do it with the built in stuff, you can plumb together the existing Accounts.sendEnrollmentEmail - however it's a little more complicated than the other solution given.
Using the example code below, call the enroll method as such:
Meteor.call('enroll', 'john.smith', 'js#harvard.edu', {name: 'John Smith'});
Meteor will then email the user a link (You can configure the template with Accounts.emailTemplates)
When they click the link, meteor calls the function passed to Accounts.onEnrollmentLink - in this case you can take them to a password setup page; but you have to mess around with their done callback.
Modify the following code, where it says INSERT XXX HERE ; then in your code call SomeGlobalEnrollmentObjectThing.cancel() if the user cancels, or SomeGlobalEnrollmentObjectThing.complete(theUsersNewPassword) if they submit the new password.
if (Meteor.isServer){
Meteor.methods({
"enroll": function(username, email, profile){
var userId;
check(username, String);
check(email, String); // Or email validator
check(profile, {
name: String
}); // your own schema
// check that the current user is privileged (using roles package)
if (!Roles.isInRole(this.userId, 'admin')){
throw new Meteor.Error(403);
}
userId = Accounts.createUser({
username: username,
email: email,
profile: profile
});
Accounts.sendEnrollmentEmail(userId);
}
});
} else {
// uses `underscore`, `reactive-var` and `tracker` packages
function Enrollment(){
this.computation = null;
this.token = new ReactiveVar(null);
this.password = new ReactiveVar(null);
this.cancelled = new ReactiveVar(false);
this.done = null;
this._bind();
}
_.extend(Enrollment.prototype, {
_bind: function(){
Accounts.onEnrollmentLink(_.bind(this.action, this));
},
reset: function(){
this.token.set(null);
this.password.set(null);
this.cancelled.set(false);
this.done = null;
if (this.computation !== null){
this.computation.stop();
this.computation = null;
}
},
cancel: function(){
this.cancelled.set(true);
},
complete: function(password){
this.password.set(password);
},
action: function(token, done){
this.reset();
this.token.set(token);
this.done = done;
this.computation = Tracker.autorun(_.bind(this._computation, this));
// --- INSERT REDIRECT LOGIC HERE [TAKE TO PASSWORD SETUP PAGE]--- //
},
_computation: function(){
var password;
if (this.cancelled.get()){
this.reset();
this.done();
// --- INSERT REDIRECT LOGIC HERE [USER CANCELLED]--- //
} else {
password = this.password.get();
if (password !== null){
Accounts.resetPassword(this.token.get(), password, _.bind(this._complete, this));
}
}
},
_complete: function(err){
// TODO - check if we were reset before callback completed
this.reset();
this.done();
if (err){
// --- INSERT REDIRECT LOGIC HERE [RESET FAILED] --- //
} else {
// --- INSERT REDIRECT LOGIC HERE [SUCCESS] --- //
}
}
});
SomeGlobalEnrollmentObjectThing = new Enrollment();
}
I have created a specific solution to this, since all the other solutions only allow you to explicitly create password-based accounts. The t3db0t:accounts-invite package allows account creation with any service only when you allow them, such as with an 'accept invitation' route. Live demo here.

Resources