My mobile hybrid app uses an expressJS server as the backend to proxy requests to parse.com via the REST API. I also use express for my own user authentication with an SSO provider. I've followed this tutorial and modified the approach a bit to work for my own setup (not using CloudCode, also not authenticating with GitHub). I'm also using the newish Revokable Sessions that came about earlier this year (March 2015?) Essentially the tutorial and my auth approach can be boiled down to doing the following on a remote backend (ExpressJS / CloudCode):
Login As User to Obtain Session Token
Create a new user if username doesn't already exist, and then continue
Create a new random password for the user (update the users password with masterKey)
With new password, log in as the user to generate the Parse sessionToken
Pass back the sessionToken to the client app
This all works fine, it seems to be the 'approach' for logging in with third-party authentication providers.
Problem is that each time my user logs in, the sessionToken essentially is re-created (as in, it destroys the old and creates a new token). I can see this in the dataBrowser as the session objectId is different. Also, and the main problem is that my user may have logged in on other devices, or on the web app, and essentially each time a user switches devices, the session token is invalidated on the other devices. Returning to that device then requires another login.
The new enhance session blog post mentions that the new revokable sessions provides for 'unique sessions' per device, however from my experience this doesn't seem to be working for users logging in over the REST API from my express backend. Perhaps this unique session would only work if the app itself was communicating to parse and thus would be able to pass along an installationId to distinguish the different devices.
I've posted my authentication code below, the parse object is from this npm parse library
upsertUser function:
/**
* This function checks to see if this user has logged in before.
* If the user is found, update their password in order to 'become' them and then return
* the user's Parse session token. If not found, createNewUser
*/
exports.upsertUser = function(ssoData, deferred) {
var userCreated = (typeof deferred != "undefined") ? true : false;
var deferred = deferred || q.defer();
var query = {
where: {username: ssoData.uid.trim()}
};
// set masterKey
parse.masterKey = parseConfig.MASTER_KEY;
// find existing user by username
parse.getUsers( query, function (err, res, body, success) {
if ( body.length ) {
var userId = body[0].objectId;
var username = body[0].username;
var password = new Buffer(24);
_.times(24, function (i) {
password.set(i, _.random(0, 255));
});
password = password.toString('base64');
parse.updateUser(userId, {password: password}, function (err, res, body, success) {
if ( typeof body.updatedAt != 'undefined' ) {
console.log('user update at: ', body.updatedAt);
parse.loginUser(username, password, function (err, res, body, success) {
deferred.resolve( body.sessionToken );
});
}
});
} else if ( userCreated === false ) {
console.log('object not found, create new user');
self.createNewUser(ssoData, deferred);
} else {
deferred.resolve();
}
});
return deferred.promise;
}
createNewUser function:
/**
* This function creates a Parse User with a random login and password, and
* Once completed, this will return upsertUser.
*/
exports.createNewUser = function(ssoData, deferred) {
// Generate a random username and password.
var password = new Buffer(24);
_.times(24, function(i) {
password.set(i, _.random(0, 255));
});
var newUser = {
username: ssoData.uid,
password: password.toString('base64'),
};
// Sign up the new User
parse.createUser(newUser, function(err, res, body, success) {
if (err) {
console.log('new parse user err', err)
}
if (typeof body.sessionToken != "undefined") {
self.upsertUser(ssoData, deferred);
} else {
deferred.resolve();
}
});
}
Any ideas how I can avoid invalidating sessionTokens upon subsequent logins?
Shoot, it seems there's a toggle on the settings page that I missed:
Revoke existing session tokens when user changes password
Simple as that i guess ;-) Still, I don't think I'll get unique sessions across devices.
Related
CASE DESCRIPTION:
I'm building a fullstack chat application on node.js (with Express), socket.io and mongoDB. Everything works fine until a moment when user puts the sign-up data that's already used in another account. I have created two functions: first to check if the username like query one already exists and second one for email addresses. (All the functions connecting with db are using callbacks to ensure proper ansynchronous working, also I have tried creating local variable and assigning a value (t/f) to it and returning it, still being 'undefined').
usernameExists = function(username){
var query = {
username: username
}
mongodb.connect(dburl, {useNewUrlParser: true, useUnifiedTopology: true}, function(err, db){
var chat = db.db('chat');
chat.collection('users').countDocuments(query, function(err, number){
if (number > 0){
return true;
} else {
return false;
}
});
});
}
Email one:
emailExists = function(email){
var query = {
email: email
}
mongodb.connect(dburl, {useNewUrlParser: true, useUnifiedTopology: true}, function(err, db){
var chat = db.db('chat');
chat.collection('users').countDocuments(query, function(err, number){
if (number > 0){
return true;
} else {
return false;
}
});
});
}
PROBLEM DESCRIPTION:
When the user puts the data into form on the frontend even the exactly same document is created in the database. I have tried logging the number and the result of this methods: number seems to be good (>0), however result is 'undefined'.
In advance thanks for any help!
I am building a simple VoIP app using asterisk-manager module on nodeJS. The asterisk server is installed on centos 7 (basic install) and is hosted on a virtual machine. The code below, listens for agent login event, and popups a url when it receives dtmf key:
var port = 5038,
host = 'asteriskIP',
username = 'popup',
password = 'popup',
open = require('open'),
mysql = require('mysql'),
ami = new require('asterisk-manager')(port, host, username, password, true);
ami.keepConnected();
//Mysql server connection pool
var pool = mysql.createPool({
host: host,
user: 'user',
password: 'password',
database: 'db'
});
ami.on('newstate', function (stateEvent) {
var channelState = stateEvent.channelstate;
if (channelState === '6') {
return false;
}
/*
Listen for new channel after agent login
*/
ami.on('newchannel', function (e) {
/* Check if caller id number is empty (This is necessary owning to new channel created as a result of
DTMF. If this returns true, return false else execute mysql query.
*/
if (e.calleridnum === '' && isNaN(e.calleridnum)) {
return false;
} else if (e.calleridnum !== '' && !isNaN(e.calleridnum)) {
var callerId = e.calleridnum;
sql = "INSERT INTO dtmf (caller_id) VALUES ?",
values = [[callerId]];
pool.query(sql, [values], function (error) {
if (error) throw error;
});
/*
Listen for DTMF on 'end' and update current caller table
*/
ami.on('dtmf', function (evt) {
var end = evt.end;
if (end === 'Yes') {
var digit = evt.digit;
sql = `UPDATE dtmf SET caller_lang = ${digit} WHERE caller_id = ?`,
values = [[callerId]];
pool.query(sql, [values], function (error) {
if (error) throw error;
});
/*
This piece of code retrieves DTMF code input and popsup
a url in the agents browser window.
*/
ami.on('bridge', function (evt) {
var state = evt.bridgestate;
if (state === 'Link') {
switch (digit) {
case '1':
open('http://someurl?' + digit);
break;
case '2':
open('http://someurl?' + digit);
break;
default:
}
}
})
}
});
}
return false;
});
});
Everything works fine when I run this code on my mac. However, when I deployed the code to the virtual machine, it inserts and updates the database normally, but no url pops up in my browser. Please is there a way nodeJS app deployed on a virtual machine, can open a window on a users local browser? Thanks.
For security reason there is no browser or OS that will let you pop up a browser window on someone else computer without first being connected.
For that i think you would have to build a client app for example a widget, service or browser extension that would be running on the person computer ... This client could use Socket.io to listen and react to event happening on the Node.js end.
That could be one solution.
I am coding a simple registration form using mongoose.
I have use a javascript file to process the values of the registration form.
Here is my registrationButtonAction.js
window.onload = function() {
var User = require('/models/Mongoose Database/user_database');
// this line is causing the problem
var registerButton = document.getElementById("registerMe");
var firstName = document.getElementById("firstName");
var lastName = document.getElementById("lastName");
var usernameRegister = document.getElementById("usernameRegister");
var passwordRegister = document.getElementById("passwordRegister");
var repasswordRegister = document.getElementById("repasswordRegister");
registerButton.onclick = function () {
if(!firstName.value || !passwordRegister.value || !repasswordRegister.value || !usernameRegister.value){
alert("Enter all required fields");
}else if (passwordRegister.value != repasswordRegister.value){
alert("Passwords must match");
}else {
var newUser = new User({
username : usernameRegister.value,
password : passwordRegister.value
});
User.find({username:usernameRegister.value}, function (error, user) {
if (error) throw error;
if(user){
window.location("/register");
}else {
newUser.save(function (error) {
if(error) throw error;
});
window.location("/login");
}
// user.comparePassword(passwordRegister.value, function (error, isMatch) {
// if (error) throw error;
//
// return 1;
//})
});
}
}
}
When I comment the var User = require('/models/Mongoose Database/user_database');, all the checks are working fine inside the onclick function. But when I uncomment it, it is not recognizing the button click.
I want to know whether this is a correct way of taking values from the registration page and storing them in a mongoose database.
You are mixing server and client code. Mongoose models and Node.js functions are not accessible inside window.onload on your client.
To put it simply, you need to create a REST API to perform database operations on the server. You have all the right tools aready, just need to reorder them.
The flow would be as such :
get the values entered in the browser
call an endpoint on your server (for example /api/createUser)
in the express router, have a route called /api/createUser in which you can access your User model and perform creation/deletion/update, etc.
My suggestion would be for you to go through this tutorial which should remove your confusion and bring you up to speed in no time. Good Luck!
Also, Passport can help you with authentication, but I believe you should first learn how to build a basic API. Authentication is a tricky beast ;)
I'm using the nodeJS password-hash-and-salt library to encrypt password which I then store in a DB. This works fine. The problem comes in when I try to verify the password. Looking at the documentation, it should be simple... The example works flawlessly, but verifying a hash which was previously stored in a DB is failing. I've verified that the hash is not changing in-database, and that the hash is been returned when queried... I've also hashed a string and then tried to verify that string using the same string in-code (with no variables). I must be doing SOMETHING wrong... Anyone have any ideas? (Code below, library link here https://github.com/florianheinemann/password-hash-and-salt)
verifyPassword = function(user,pw){
// Connect to db
myuser = {};
var con = connect();
// Run query
con.query('SELECT UID,password FROM users WHERE username = ?', user, function(err,res){
if(err){ throw err; }
else {
myuser.userID = res[0].UID;
myuser.pswdV = res[0].password;
// Verifying hash
//console.log(pw);
//console.log(myuser.pswdV);
password(pw).verifyAgainst(myuser.pswdV, function(error, verified) {
//console.log(verified);
if(error)
throw new Error('Something went wrong!');
if(!verified) {
//socket.emit('popError','Invalid username or password. Please check your password and try again.');
//return {err:'Invalid username or password. Please check your password and try again.'}
console.log('Not verified');
} else {
var token = crypto.randomBytes(64);
token = buf.toString('hex');
myuser.secret = token;
delete myuser.pswdV;
con.query('UPDATE users SET magicSecret = ? WHERE username = ?', [token,user], function(err,res){
if(err) {
socket.emit('popError','Failed to login, this is a server problem.');
//return {err:'Failed to login, this is a server problem.'}
}
else {
socket.emit('login_success',myuser);
// return {err:myuser}
}
});
}
});
}
});
con.end(function(err) {
// The connection is terminated gracefully
// Ensures all previously enqueued queries are still
// before sending a COM_QUIT packet to the MySQL server.
});
};
Please excuse the debugging code... This should give you an idea of what I'm trying to do. If you see why it's failing please let me know.
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.