I need to create new user when I recived message from rabbitmq. But Accounts.createUser doesn't work outside methods even if I wrap it with Meteor.bindEnvironment. But for example IronRouter works well when I call createUser. How I can create new users outside of Meteor methods or client?
var newUserCreate = Meteor.bindEnvironment(function(msg){
var email, username = msg.data.email;
var password = msg.data.password;
Accounts.createUser({username: username, email: email, password: password});
}, function(e){throw e;})
And just call newUserCreate(msg)
When you write
var a, b = 42;
Then b is equal to 42, but a stays undefined. My guess is that the method fails because you pass email: undefined in params. So try to rewrite it and see what happens:
var newUserCreate = Meteor.bindEnvironment(function(msg) {
var email = msg.data.email;
var username = msg.data.email;
var password = msg.data.password;
var result = Accounts.createUser({username: username, email: email, password: password});
console.log("RESULT", result);
}, function(e){
throw e;
});
It should 'just work'. Remember don't pass a callback on the server it wont fire. You have to instead look for what it returns. Fibers will make sure meteor waits until the user is created before the next statement is run. What is returned is the new user's _id.
e.g
var result = Account.createUser(params);
Related
After a user logs in using $firebaseAuth, Google sends the user's displayName, email, and photoURL. I then want to look up the user's account in my Firebase database. I can't use $getRecord(key) because Google doesn't tell me the user's key. It appears that I should use $keyAt(recordOrIndex), and then use $getRecord(key). $keyAt(recordOrIndex) works fine with an index. $keyAt(recordOrIndex) works fine with a record that I retrieved with $getRecord(key). I can't get $keyAt(recordOrIndex) to work with an object that I made from the user data that Google returned using $firebaseAuth.
I tried both the complete object (displayName, email, photoURL) and an object consisting of only the email address. The latter is what I would prefer to use. Neither worked.
app.controller('LoginModalInstanceCtrl', ['$scope', '$location', '$uibModalInstance', '$firebaseArray', '$firebaseObject', '$firebaseAuth', function($scope, $location, $uibModalInstance, $firebaseArray, $firebaseObject, $firebaseAuth) {
// Create Firebase3 reference
var ref = firebase.database().ref();
// Set up Firebase Auth
$scope.authObj = $firebaseAuth();
var authData = $scope.authObj.$getAuth();
$scope.authData = authData;
// Google OAuth login handler
$scope.loginGoogle = function() {
$scope.authData = null;
$scope.error = null;
$scope.authObj.$signInWithPopup("google")
.then(function(authData) {
$scope.authData = authData;
console.log(authData);
console.log("Your displayName is:", authData.user.displayName);
console.log("Your email is:", authData.user.email);
console.log("Your photoURL is:", authData.user.photoURL);
var record = {
displayName: authData.user.displayName,
email: authData.user.email,
photoURL: authData.user.photoURL
};
var emailObject = {
email: authData.user.email
};
// look up account
var users = $firebaseArray(ref.child('users'));
users.$loaded()
.then(function() {
console.log("Array loaded!");
var key1 = users.$keyAt(1);
console.log(key1); // -Khi6OxAo339ye6xoG3i
var record = users.$getRecord(key1);
console.log(record); // Object with displayName, email, and photoURL
var key1 = users.$keyAt(record);
console.log(key1); // -Khi6OxAo339ye6xoG3i
var objectKey = users.$keyAt(object);
console.log(objectKey); // null
var emailKey = users.$keyAt(emailObject);
console.log(emailKey); // null
});
$uibModalInstance.close(); // close modal window
$location.path('/languagetwo/'); // return to the homepage
}).catch(function(error) {
console.error("Authentication failed:", error);
});
};
Should I use $firebaseObject instead of $firebaseArray:
var user = $firebaseObject(ref.child('users').child( SOMETHING HERE? ));
The answer appears to be no, you can't search Firebase Database using AngularFire. (Maybe AngularFire 2 has search, I didn't look.) What I did instead was to use "plain vanilla" Firebase:
var users = firebase.database().ref('users');
users.orderByChild('email').equalTo(authData.user.email).once('value').then(function(snapshot) {
console.log(snapshot.val());
});
The first line sets up the Firebase ref and is the similar to as before, except that I'm going straight to the users array, instead of using $FirebaseArray to get to the users array.
The second line is a completely different syntax. First, you have to specify the order that you want the returned object to be in. Yes, it returns an object, not an array. I tried snapshot.val().length() and found that it's not an array. What orderByChild('email') does is to access the 'email' property of the objects in the 'users' array.
Next we do the query. equalTo(authData.user.email) returns only the objects in which the email address from $FirebaseAuth equals the email address in our 'users' array.
Next, once('value') creates a promise and waits for the async data. I tried using on() but couldn't get it to work, too many arguments or something. once() requires an argument, which can be value, child_added, child_changed, child_removed, or child_moved. The value argument is for getting data from a location without changing the child nodes.
We can then set up our then promise fulfillment. You can call the returned data anything. Here it's called snapshot.
Lastly snapshot.val() provides the data from the database, looking just like it does in the Firebase Console.
There are some irreversible actions that user can do in my app. To add a level of security, I'd like to verify that the person performing such an action is actually the logged in user. How can I achieve it?
For users with passwords, I'd like a prompt that would ask for entering user password again. How can I later verify this password, without sending it over the wire?
Is a similar action possible for users logged via external service? If yes, how to achieve it?
I can help with the first question. As of this writing, meteor doesn't have a checkPassword method, but here's how you can do it:
On the client, I'm going to assume you have a form with an input called password and a button called check-password. The event code could look something like this:
Template.userAccount.events({
'click #check-password': function() {
var digest = Package.sha.SHA256($('#password').val());
Meteor.call('checkPassword', digest, function(err, result) {
if (result) {
console.log('the passwords match!');
}
});
}
});
Then on the server, we can implement the checkPassword method like so:
Meteor.methods({
checkPassword: function(digest) {
check(digest, String);
if (this.userId) {
var user = Meteor.user();
var password = {digest: digest, algorithm: 'sha-256'};
var result = Accounts._checkPassword(user, password);
return result.error == null;
} else {
return false;
}
}
});
For more details, please see my blog post. I will do my best to keep it up to date.
I haven't done this before, but I think you will need something like this on your server
Accounts.registerLoginHandler(function(loginRequest) {
console.log(loginRequest)
var userId = null;
var username = loginRequest.username;
// I'M NOT SURE HOW METEOR PASSWORD IS HASHED...
// SO YOU NEED TO DO A BIT MORE RESEARCH ON THAT SIDE
// BUT LET'S SAY YOU HAVE IT NOW
var password = loginRequest.password;
var user = Meteor.users.findOne({
$and: [
{username: username},
{password: password}
]
});
if(!user) {
// ERROR
} else {
// VERIFIED
}
});
then you can call this function from the client side like this:
// FETCH THE USERNAME AND PASSWORD SOMEHOW
var loginRequest = {username: username, password: password};
Accounts.callLoginMethod({
methodArguments: [loginRequest]
});
I have a project on github for different purpose, but you can get a sense of how it is structured: https://github.com/534N/apitest
Hope this helps,
I have found the best way to validate the users password is to use the Accounts.changePassword command and
pass in the same password for old and new password. https://docs.meteor.com/api/passwords.html#Accounts-changePassword
Accounts.changePassword(this.password, this.password, (error) => {
if(error) {
//The password provided was incorrect
}
})
If the password provided is wrong, you will get an error back and the users password will not be changed.
If the password is correct, the users password will be updated with the same password as is currently set.
The following code snippet works, unless the user's browser config (iOS with Chrome, e.g.) sends it to the $authWithOAuthRedirect block - then it fails.
And by fails, I mean the $authWithOAuthRedirect method works and the user can approve the authentication, but it fails to send the scope properly to Google, and email access is not requested.
var provider = 'google';
var scope = {scope:'email'};
var auth = $firebaseAuth(FirebaseInstance.firebase);
auth.$authWithOAuthPopup(provider, scope).then(function (authData, error) {
if (error && error.code === "TRANSPORT UNAVAILABLE") {
auth.$authWithOAuthRedirect(provider, function(error) {}, scope);
}
});
Simplified, this code will fail to request the user's email:
var provider = 'google';
var scope = {scope:'email'};
var auth = $firebaseAuth(FirebaseInstance.firebase);
auth.$authWithOAuthRedirect(provider, function(error) {}, scope);
Thanks for your help!
I think the problem is that you are using the syntax for the non-angular version: Firebase.authWithOAuthRedirect(provider[, callback, scope])
You should be using the AngularFire version:
$firebaseAuth.$authWithOAuthRedirect(provider[, options])
This version returns a promise so your simplified code should look like this:
var provider = 'google';
var scope = {scope:'email'};
var auth = $firebaseAuth(FirebaseInstance.firebase);
auth.$authWithOAuthRedirect(provider, scope).then(function (authObject) {
// Handle success
}, function (error) {
// Handle error
});
Using velocity/jasmine, I'm a bit stuck on how I should test a server-side method requiring that there be a currently logged-in user. Is there a way to make Meteor think a user is logged in via stub/fake ?
myServerSideModel.doThisServerSideThing = function(){
var user = Meteor.user();
if(!user) throw new Meteor.Error('403', 'not-autorized');
}
Jasmine.onTest(function () {
describe("doThisServerSideThing", function(){
it('should only work if user is logged in', function(){
// this only works on the client :(
Meteor.loginWithPassword('user','pwd', function(err){
expect(err).toBeUndefined();
});
});
});
});
What you could do is add users just to your test suite. You could do this by populating these users in a the server-side test script:
Something like:
Jasmine.onTest(function () {
Meteor.startup(function() {
if (!Meteor.users.findOne({username:'test-user'})) {
Accounts.createUser
username: 'test-user'
... etc
Then, a good strategy could be to use the beforeAll in your test to login (this is client side):
Jasmine.onTest(function() {
beforeAll(function(done) {
Meteor.loginWithPassword('test-user','pwd', done);
}
}
This is assuming your test isn't logged in yet. You can make this more fancy by checking for Meteor.user() and properly logging out in an afterAll, etc. Note how you can handily pass the done callback to many of the Accounts functions.
Essentially, you don't have to mock a user. Just make sure you have the right users, with the correct roles, available in the Velocity/Jasmine DB.
Lets say you have a server side method like this:
Meteor.methods({
serverMethod: function(){
// check if user logged in
if(!this.userId) throw new Meteor.Error('not-authenticated', 'You must be logged in to do this!')
// more stuff if user is logged in...
// ....
return 'some result';
}
});
You do not need to make a Meteor.loginWithPassword before executing the method. All you got to do is stub the this.userId by changing the this context of the method function call.
All defined meteor methods are available on the Meteor.methodMap object. So just call the function with a different this context
describe('Method: serverMethod', function(){
it('should error if not authenticated', function(){
var thisContext = {userId: null};
expect(Meteor.methodMap.serverMethod.call(thisContext).toThrow();
});
it('should return a result if authenticated', function(){
var thisContext = {userId: 1};
var result = Meteor.methodMap.serverMethod.call(thisContext);
expect(result).toEqual('some result');
});
});
EDIT: This solution was only tested on Meteor <= 1.0.x
What are you testing and why does it require a user to be logged in? Most of the methods I have that need a user object I pass the user object into. This allows me to call from a test without actually being logged in. So in the actual running of the code I would pass...
var r = myMethod(Meteor.user());
but when running from the test I would call like...
it('should be truthy', function () {
var r = myMethod({_id: '1', username: 'testUser', ...});
expect(r).toBeTruthy();
});
I think that Meteor.server.method_handlers["nameOfMyMethod"] allows you to call/apply a Meteor method and supply this as the first parameter at least in the current version (1.3.3)
this.userId = userId;
Meteor.server.method_handlers["cart/addToCart"].apply(this, arguments);
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.