I may be totally off line here, but I'm trying to extend the loginWithPassword method in Meteor to handle only returning users with a few parameters set in their profile.
I'm creating the users fine and once created they login as that user type and all is good, but, when I try and login again I hit a wall.
I've tried implementing my own login handler as follows...
Accounts.registerLoginHandler(function(loginRequest) {
console.log("Got to Accounts.registerLoginHandler");
console.log(loginRequest);
var userId = null;
var user = Meteor.loginWithPassword(loginRequest.email, loginRequest.password, function(error){
if(error !== undefined){
setAlert('error', 'Error in processing login. ' + error.reason + '.');
}
});
var userWithType;
if(user){ // we have the right username and password
console.log("Found a user and logged them in");
userWithType = Meteor.users.findOne({'id': user._id, 'profile.type': loginRequest.type});
}
if(userWithType){
console.log("Found User of that type")
userId = user._id;
}
console.log("UserId", userId);
return {
id: userId
}
});
But am getting an error when I get to this code that says
Got to Accounts.registerLoginHandler
{ email: 'blah2#blah', password: 'blha', type: 'user' }
Exception while invoking method 'login' TypeError: Object #<Object> has no method 'loginWithPassword'
at app/server/login.js:8:23
at tryAllLoginHandlers (app/packages/accounts-base/accounts_server.js:53:18)
at Meteor.methods.login (app/packages/accounts-base/accounts_server.js:73:18)
at maybeAuditArgumentChecks (app/packages/livedata/livedata_server.js:1367:12)
at _.extend.protocol_handlers.method.exception (app/packages/livedata/livedata_server.js:596:20)
at _.extend.withValue (app/packages/meteor/dynamics_nodejs.js:31:17)
at app/packages/livedata/livedata_server.js:595:44
at _.extend.withValue (app/packages/meteor/dynamics_nodejs.js:31:17)
at _.extend.protocol_handlers.method (app/packages/livedata/livedata_server.js:594:48)
at _.extend.processMessage.processNext (app/packages/livedata/livedata_server.js:488:43)
I'm obviously missing a this pointer or something like that, but don't know enough about this framework to know if I'm totally off track here even trying to get this to work.
Ta
P.
I am not too familiar with it but from http://docs.meteor.com, Meteor.loginWithPassword () can only be called on the client. You have written it into the server side code from the tutorial.
That is throwing the error you see. If you move it to the client you will also see that it only returns to the callback function so your variable user will remain undefined.
Meteor.user().profile is available on the client so you can just check the type there in the callback of loginWithPassword to check the information at login.
Related
I have created a function which was previously working to store user details in the User node in my Firebase database. However upon trying it today it is now returning an error message saying
TypeError: Cannot read property 'uid' of null
The code I have written simply stores the auth.uid as a child of user and then within that node it stores all data of that user. As of Friday this worked for a few users but now it is now producing the above error message.
To fix this do I need to call the offAuth method upon logging out? If so, How would I go about this?
My code so far is below.
ref.onAuth(function(authData) {
ref.child('user').child(authData.uid).set(authData).then(function(auth) {
console.log('Data Saved Successfully!');
}).catch(function(error) {
console.log(error);
})
})
The onAuth() method is called whenever the authentication state of the user changes. This means that it is called both when the user is authenticated and when they get unauthenticated. In the latter case, the authData argument will be null and you have to handle that in your code.
From the Firebase documentation:
Use the onAuth() method to listen for changes in user authentication state.
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com");
ref.onAuth(function(authData) {
if (authData) {
console.log("User " + authData.uid + " is logged in with " + authData.provider);
} else {
console.log("User is logged out");
}
});
You're missing that if in your code.
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);
I have the following controller that uses AngularFire
app.controller("authController", function($scope, $firebaseSimpleLogin){
var ref = new Firebase("https://myapp.firebaseIO.com/");
$scope.auth = $firebaseSimpleLogin(ref, function(error, user){
if(error){
console.log(error);
}
else if(user){
console.log(user);
}
else{
console.log("user logged out");
}
});
// This shows a valid object
console.log($scope.auth);
$scope.createAccount = function(){
console.log("found me");
$scope.auth.$createUser($scope.email, $scope.password, function(error, user){
console.log("something");
console.log(user);
if(!error){
console.log(user);
}
else{
console.log(error);
}
});
};
});
When I bind the $scope.createAccount function to an ng-click event and click on the bound button, console.log("found me") runs in the browser, but none of the other console.log commands in $scope.createAccount is shown.
The console.log($scope.auth) command I have before setting the $scope.createAccount function shows a valid object with the $createUser function defined.
I am not receiving any console errors when I run $scope.createAccount so I am assuming the call has been "successfully" made.
Why am I able to see the auth object, but not receiving a callback after calling $createUser?
This was happening because I was doing callbacks based on JavaScript notation instead of Angular notation. Since I was using the AngularFire methods (they have the same names as the vanilla JavaScript SDK methods, but with a $ sign in front of them), I needed to handle callbacks using Angular's $promise methodology.
I changed
$scope.auth.$createUser($scope.email, $scope.password, function(error, user){
// do things;
});
to
$scope.auth.$createUser($scope.email, $scope.password)
.then(function(user){
// do things if success
}, function(error){
// do things if failure
});
and the callback worked as expected.
There is an exception to the above with the vanilla JS firebaseSimpleLogin constructor vs Angular $firebaseSimpleLogin constructor. On the vanilla JS constructor, there are callbacks on the constructor that allows you to specify what your script should do when a user logs in / logs out. It follows the following format:
var auth = new firebaseSimpleLogin(ref, function(error, user){
if(error){
// do things if login failure
}
else if(user){
// do things when login succeeds
}
else{
// do things when user logs out
}
});
If you try to do the same with the Angular constructor like so:
$scope.auth = $firebaseSimpleLogin(ref)
.then(function(user){
// do things when login succeeds
}, function(error){
// do things when login fails
});
you'll receive an error. There are no callback methods with the Angular constructor. I am guessing this was done on purpose since with Angular, you have data binding and you can simply $watch $scope.auth.user for changes and perform operations in your app depending on the variable's state. When $scope.auth.user is set to null, the user is logged out. If the value is set to anything else than null, then the user is logged in.
in a file called /server/main.js (in order to ensure it is loaded last).
console.dir(Meteor.user());
Throws:
Error: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions.
So I try to use, in the same file:
console.dir(this.userId);
returns:
undefined
so, not giving up, I'm thinking "that's fine I'll just read from the cookies in the header":
var connect = Npm.require('connect');
__meteor_bootstrap__.app.use(connect.query()).use(function(req, res, next) {
console.dir(req.headers);
next();
});
.... returns nothing in terms of cookies except for 'cookie: 'uvf=1''
I'm not sure what to conclude - this is senseless as I can otherwise use the Meteor.Account framework just fine, read/set user properties, etc. The server is clearly aware of the user, and the current user clearly logged in.
I'm at a complete loss, any explanation / hint / pointer would be greatly appreciated.
You have to use Meteor.user() in a place where a request is made from the client (such as a Meteor.methods or a Meteor.publish).
It can't be placed anywhere else because meteor wouldn't know at that point in the code the user is supposed to bound to. If there is a place a request of some form is made from the client it can do this:
In a Meteor.publish:
Meteor.publish("collection", function() {
//returns undefined if not logged in so check if logged in first
if(this.userId) {
var user = Meteor.users.findOne(this.userId);
//var user is the same info as would be given in Meteor.user();
}
});
In a Meteor.methods:
Meteor.methods({
"test":function() {
//should print the user details if logged in, undefined otherwise.
console.log(Meteor.user());
}
}
To use Meteor.user() on a server side route:
You need Meteor router installed as a package via meteorite to allow you to have a server rendered page. (installed via mrt install router)
A server side route could then handle the web request:
Meteor.Router.add('/awebpage', function(id) {
var userId = this.params.userid;
var logintoken = this.params.logintoken;
var isdirect = this.param.direct;
var user = Meteor.users.findOne({_id:userId,"services.resume.loginTokens.token":logintoken});
if(user) {
//the user is successfully logged in
return "You, "+user.profile.name+", are logged in!";
}
else
{
if(isdirect) {
return "<h3>Loading</h3><script>window.location.href="/awebpage?direct=true&userid="+localStorage.getItem("Meteor.userId") +"&logintoken="+localStorage.getItem("Meteor.loginToken")</script>";
}
else
{
return "Not logged in"
}
}
});
So now when you visit /awebpage it would check whether the user is logged in and do the thing you want when they are logged in. Initially there is a redirect to relay the data from localstorage back to the URI.
You can expose the userId with Meteor.publish() to global scope. Then you can use it with Meteor.Router's server side routes.
--
/server/publications.js
CurrentUserId = null;
Meteor.publish(null, function() {
CurrentUserId = this.userId;
});
-
/server/routes.js
Meteor.Router.add('/upload', 'POST', function() {
if (!CurrentUserId)
return [403, 'Forbidden'];
// proceed with upload...
});
You can use the logged in callback
Accounts.onLogin((obj)->
user = ob.user
)
Accounts.onLogin(function(obj){
var user = ob.user
})
I recently wrote a blog post describing solution to this: https://blog.hagmajer.com/server-side-routing-with-authentication-in-meteor-6625ed832a94.
You basically need to set up a server route using a https://atmospherejs.com/mhagmajer/server-router package and you can get current user with this.userId just like with Meteor methods.
I am trying to build a basic web application w/ user authentication via email/password registration using Firebase.
My setup right now includes a main.js file that consists of the following:
var dbRef = new Firebase('https://url.firebaseIO.com');
var authClient = new FirebaseAuthClient(dbRef, function(error, user) {
if (error) {
// an error occurred while attempting login
console.log(error);
} else if (user) {
// user authenticated with Firebase
console.log('User ID: ' + user.id + ', Provider: ' + user.provider);
} else {
// user is logged out
console.log('logged out!');
}
});
function next(){
window.location = 'index.html';
}
function test(){
authClient.login('password', {
email: email,
password: password,
rememberMe: true
},next());
// window.location = 'index.html';
}
I obtain email/password values from a form and login. That works. But as soon as I include a callback function to then redirect them to a new authenticated page, it no longer works. In fact, most of the time I get an "UNKOWN ERROR" response.
When I get to the next page, I am no longer logged in. If I remove the next() function and stay on the same page, it works - even if I then trigger the next function from the console. Is there a different way you are supposed to proceed to another page?
I'm pretty sure there is some sort of communication issue (possibly the login does not get a return before the page is switched?) because if I add a 1s timeout before the next function, it then works. But surely this is not best practice?
Thanks!
Per https://www.firebase.com/docs/security/simple-login-email-password.html, the authClient.login() method does not actually accept a callback, so the problem you're seeing is likely the result of navigating away from the current page before the callback is returned, as you suggested.
I would recommend doing the redirect in the callback you're passing during the instantiation of the auth client. (new FirebaseAuthClient(ref, callback)) and redirect if you detect a logged-in user. This callback will be invoked once upon instantiation with the current authentication state of the user, and then again any time the user's authentication state changes (such as on login or logout).