I'm learning NativeScript/Angular 2 and would need to get help with this issue.
In order to implement a multi-role login system within the Firebase platform I thought about this solution
Login the user through Firebase authentication
Query the /stores/ path for a store which has a merchantEmail field same as the e-mail that has just logged in
If I find it, I set the store ID inside a BackendService service which uses getString/setString to store tokens, then route to a MerchantDashboardComponent
If I don't find it, just route to a BuyerDashboardComponent
This is part of my code in the login.service:
login (email: string, password: string) {
return firebase.login({
type: firebase.LoginType.PASSWORD,
email: email,
password: password
}).then(
(result: any) => {
firebase.query(
(_result) => { // Here I set BackendService.storeID
Inside the .query() callback I am assigning the tokens I need in the application.
This is the method I'm using in my login.component:
doLogin () {
this.isAuthenticating = true;
if (!this.validateEmail()) {
alert("Please insert a valid email");
return false;
}
this.loginService.login(this.email, this.password).then(
() => {
this.isAuthenticating = false;
if (BackendService.loginError)
alert(BackendService.loginError)
else if (BackendService.storeID != '') {
this.router.navigate(['/merchant-dashboard'], {clearHistory: true});
}
else {
this.router.navigate(['/home/categories'], {clearHistory: true});
}
}
);
}
Everything works except for the fact that the Merchant gets routed to the Buyer dashboard. I've managed to discover that the execution order is not what I expected to be, in fact:
firebase.login() gets executed and returns a Promise
.then() handler is executed inside the doLogin() method
Only after this, the firebase.query() method completes the callback and my tokens are available, but doLogin() has already navigated the user because storeID is still empty when I need it
I hope I've been clear as much as possible.
Thanks for your attention.
Greetings,
Davide
So, the problem was in the login service method.
I now return the Promise generated by firebase.query(), which causes then() calls to chain in the correct order.
Yep that was exactly was I was going to propose to wrap it in a promise and create a chain.
example code
return new Promise<any>((resolve, reject) => {
firebase.login({ loginArguments })
.then((result: any) => {
var onQueryEvent = function (result) {
};
return firebase.query(
onQueryEvent,
"/owner",
{
// query arguments follows here
}
).then(res => {
return res;
})
})
.then(finalResult => {
console.log(finalResult);
try {
resolve(finalResult);
} catch (e) {
reject(e);
}
})
});
I have created a simple login app using react native that let's users signup, login, and logout. my signup function takes a username that is then used in the createUser callback to generate a db entry with the uid as the key, and the username entered as a value. The answer in this post is the structure I followed - How do you include a username when storing email and password using Firebase (BaaS) in an Android app?
After the user is logged in, I'd like to get the username and display it but I'm having trouble figuring this out.
This is the code I currently have to attempt and do it:
var ref = new Firebase("https://myreactapp.firebaseio.com");
module.exports = React.createClass({
getInitialState: function() {
var authData = ref.getAuth();
var user = ref.child("users/" + authData.uid + "/username");
return {
username: user
};
},
This is how the code looks when I signup and the structure of my db.
var self = this;
let ref = new Firebase("https://myreactapp.firebaseio.com");
ref.createUser({
email : this.state.email,
password : this.state.password
}, function(error, authData) {
if (error) {
return this.setState({errorMessage: 'Error creating user'});
} else {
ref.child("users").child(authData.uid).set({
username: self.state.username
});
console.log("Successfully created user account with uid:", authData.uid);
ToastAndroid.show('Account Created', ToastAndroid.SHORT)
return self.props.navigator.pop();
}
});
----------------Not actual code-------------------------------------------------
DB
+users
--<uid>
-username -> value
--<uid>
-username -> value
I try to login and get an error of maximum call stack exceeded, but I have a feeling I'm going at this the wrong way. I've looked online, but everything I found was for retrieving data that is either being added, changed or deleted. All I want is to get this username once.
I am currently trying to create an sails+angular web-app.
I already have a user-authentication system working (based on this tutorial : https://github.com/balderdashy/activity-overlord-2-preview). I am trying to integrate a very simple chat inside using websocket.
My issue is to link websocket's "user" to the authenticated user.
Because when an authenticated user writes a message, I want to send the message as data but not the id of the current user, i would like to get this id from the sail's controller.
This is my actual sails chatController :
module.exports = {
addmsg:function (req,res) {
var data_from_client = req.params.all();
if(req.isSocket && req.method === 'GET'){
// will be used later
}
else if(req.isSocket && req.method === 'POST'){
var socketId = sails.sockets.getId(req);
/* Chat.create(data_from_client)
.exec(function(error,data_from_client){
console.log(data_from_client);
Chat.publishCreate({message : data_from_client.message , user:currentuser});
}); */
}
else if(req.isSocket){
console.log( 'User subscribed to ' + req.socket.id );
}
}
}
and this is my angular's controller
io.socket.get('http://localhost:1337/chat/addmsg');
$scope.sendMsg = function(){
io.socket.post('http://localhost:1337/chat/addmsg',{message: $scope.chatMessage});
};
req.session.me
...was the solution.
I can't quite seem to figure out how to reset the Firebase Auth object once it has loaded for the first time.
I am looking for the bool true value in auth.password.isTemporaryPassword which forces the user to reset their password. Once the user has carried this out this procedure and reset, the auth.password.isTemporaryPassword remains true.
The only way around this I have found is to log the user out and log them in again which refreshes the auth object.
login:
var ref = new Firebase(environment);
$firebaseAuth(ref)
.$authWithPassword({
email: email,
password: password
},sessionObj)
.then(function(authData) {
if (password.isTemporaryPassword === true) {
$state.go('resetpassword');
}
})
.catch(function(error) {
deferred.reject(error);
});
reset password:
$scope.reset.oldPassword = "oldPass";
$scope.reset.newPassword = "newPass";
$scope.reset.email = "usermail";
ref.changePassword($scope.reset, function(err) {
if(err) {
...
}
else {
$state.go('home')
}
})
password.isTemporaryPassword remains true until I log the user in again which seems hacky.
You should be able to use the onAuth function to listen for changes to authentication state:
ref.onAuth(function(authData) {
//user authenticated & needs to change her password
if(authData && authData.password.isTemporaryPassword) {
$state.go('resetpassword');
}
//else user is logged in with valid password
else if(authData) {
}
//else user is logged out
else {
}
});
I have recently followed a tutorial over on Thinkster for creating a web app using Angular and Firebase.
The tutorial uses the Firebase simpleLogin method allows a 'profile' to be created that includes a username.
Factory:
app.factory('Auth', function($firebaseSimpleLogin, $firebase, FIREBASE_URL, $rootScope) {
var ref = new Firebase(FIREBASE_URL);
var auth = $firebaseSimpleLogin(ref);
var Auth = {
register: function(user) {
return auth.$createUser(user.email, user.password);
},
createProfile: function(user) {
var profile = {
username: user.username,
md5_hash: user.md5_hash
};
var profileRef = $firebase(ref.child('profile'));
return profileRef.$set(user.uid, profile);
},
login: function(user) {
return auth.$login('password', user);
},
logout: function() {
auth.$logout();
},
resolveUser: function() {
return auth.$getCurrentUser();
},
signedIn: function() {
return !!Auth.user.provider;
},
user: {}
};
$rootScope.$on('$firebaseSimpleLogin:login', function(e, user) {
angular.copy(user, Auth.user);
Auth.user.profile = $firebase(ref.child('profile').child(Auth.user.uid)).$asObject();
console.log(Auth.user);
});
$rootScope.$on('$firebaseSimpleLogin:logout', function() {
console.log('logged out');
if (Auth.user && Auth.user.profile) {
Auth.user.profile.$destroy();
}
angular.copy({}, Auth.user);
});
return Auth;
});
Controller:
$scope.register = function() {
Auth.register($scope.user).then(function(user) {
return Auth.login($scope.user).then(function() {
user.username = $scope.user.username;
return Auth.createProfile(user);
}).then(function() {
$location.path('/');
});
}, function(error) {
$scope.error = error.toString();
});
};
At the very end of the tutorial there is a 'next steps' section which includes:
Enforce username uniqueness-- this one is tricky, check out Firebase priorities and see if you can use them to query user profiles by username
I have searched and searched but can't find a clear explanation of how to do this, particularly in terms of the setPriority() function of Firebase
I'm quite the Firebase newbie so any help here would be gratefully recieved.
There are a few similar questions, but I can't seem to get my head around how to sort this out.
Enormous thanks in advance.
EDIT
From Marein's answer I have updated the register function in my controller to:
$scope.register = function() {
var ref = new Firebase(FIREBASE_URL);
var q = ref.child('profile').orderByChild('username').equalTo($scope.user.username);
q.once('value', function(snapshot) {
if (snapshot.val() === null) {
Auth.register($scope.user).then(function(user) {
return Auth.login($scope.user).then(function() {
user.username = $scope.user.username;
return Auth.createProfile(user);
}).then(function() {
$location.path('/');
});
}, function(error) {
$scope.error = error.toString();
});
} else {
// username already exists, ask user for a different name
}
});
};
But it is throwing an 'undefined is not a function' error in the line var q = ref.child('profile').orderByChild('username').equalTo($scope.user.username);. I have commented out the code after and tried just console.log(q) but still no joy.
EDIT 2
The issue with the above was that the Thinkster tutorial uses Firebase 0.8 and orderByChild is available only in later versions. Updated and Marein's answer is perfect.
There are two things to do here, a client-side check and a server-side rule.
At the client side, you want to check whether the username already exists, so that you can tell the user that their input is invalid, before sending it to the server. Where exactly you implement this up to you, but the code would look something like this:
var ref = new Firebase('https://YourFirebase.firebaseio.com');
var q = ref.child('profiles').orderByChild('username').equalTo(newUsername);
q.once('value', function(snapshot) {
if (snapshot.val() === null) {
// username does not yet exist, go ahead and add new user
} else {
// username already exists, ask user for a different name
}
});
You can use this to check before writing to the server. However, what if a user is malicious and decides to use the JS console to write to the server anyway? To prevent this you need server-side security.
I tried to come up with an example solution but I ran into a problem. Hopefully someone more knowledgeable will come along. My problem is as follows. Let's say your database structure looks like this:
{
"profiles" : {
"profile1" : {
"username" : "Nick",
"md5_hash" : "..."
},
"profile2" : {
"username" : "Marein",
"md5_hash" : "..."
}
}
}
When adding a new profile, you'd want to have a rule ensuring that no profile object with the same username property exists. However, as far as I know the Firebase security language does not support this, with this data structure.
A solution would be to change the datastructure to use username as the key for each profile (instead of profile1, profile2, ...). That way there can only ever be one object with that username, automatically. Database structure would be:
{
"profiles" : {
"Nick" : {
"md5_hash" : "..."
},
"Marein" : {
"md5_hash" : "..."
}
}
}
This might be a viable solution in this case. However, what if not only the username, but for example also the email has to be unique? They can't both be the object key (unless we use string concatenation...).
One more thing that comes to mind is to, in addition to the list of profiles, keep a separate list of usernames and a separate list of emails as well. Then those can be used easily in security rules to check whether the given username and email already exist. The rules would look something like this:
{
"rules" : {
".write" : true,
".read" : true,
"profiles" : {
"$profile" : {
"username" : {
".validate" : "!root.child('usernames').child(newData.val()).exists()"
}
}
},
"usernames" : {
"$username" : {
".validate" : "newData.isString()"
}
}
}
}
However now we run into another problem; how to ensure that when a new profile is created, the username (and email) are also placed into these new lists? [1]
This in turn can be solved by taking the profile creation code out of the client and placing it on a server instead. The client would then need to ask the server to create a new profile, and the server would ensure that all the necessary tasks are executed.
However, it seems we have gone very far down a hole to answer this question. Perhaps I have overlooked something and things are simpler than they seem. Any thoughts are appreciated.
Also, apologies if this answer is more like a question than an answer, I'm new to SO and not sure yet what is appropriate as an answer.
[1] Although maybe you could argue that this does not need to be ensured, as a malicious user would only harm themselves by not claiming their unique identity?
I had a similar problem. But it was after registering the user with password and email. In the user profile could save a user name that must be unique and I have found a solution, maybe this can serve you.
Query for username unique in Firebase
var ref = new Firebase(FIREBASE_URL + '/users');
ref.orderByChild("username").equalTo(profile.username).on("child_added", function(snapshot) {
if (currentUser != snapshot.key()) {
scope.used = true;
}
});
ref.orderByChild("username").equalTo(profile.username).once("value", function(snap) {
//console.log("initial data loaded!", Object.keys(snap.val()).length === count);
if (scope.used) {
console.log('username already exists');
scope.used = false;
}else{
console.log('username doesnt exists, update it');
userRef.child('username').set(profile.username);
}
});
};