AngularJS & Socket.IO - Return value from Service (emit here) to Controller is undefined || promises, asynchronicity - javascript

I don't get my code to work properly.
I'm developing an app with AngularJS including a connection to a backend server via socket.io. I'm working on a login which is intended to send an user's data to the server. The server is intended to respond with "valid" and the user's data (name, dateOfBirth, ...) if the sent data is correct (email and password). Elements are:
BackendService (Factory which executes emit to server)
AppController (Controller which calls the login function of BackendService)
Node.js Server (computes if the sent data is valid so that the user can be logged in)
The intention is that the login function in the Factory returns a "login code" which tells the controller if the login is correct. Unfortunately the function returns "undefined". During my research, I found out that it might be because of asynchronicity and promises. However, I couldn't apply the given information to my problem as the majority was about $http. In addition - if the structure of my code is in need of improvement, let me know!
Here's my code:
Node.js Server
socket.on('logincust', function (p1, fn) {
connection.query("SELECT Salt FROM Customer WHERE Email = ?", [p1.Email], function (err, data, fields)
{
if (err) {
throw err;
}
if (data.length > 0) {
var hash = crypto.createHash('sha256').update(p1.Password + data[0].Salt).digest('base64');
connection.query("SELECT LName,FName,Email,Telephone,Address,DateOfBirth FROM Customer WHERE Password = ?", [hash], function (err, data2, fields) {
if (err) {
throw err;
}
if (data2.length > 0) {
fn('valid', data2[0]);
}
else {
fn('invalidpassword', 'nodata')
}
})
}
else {
fn('invalidemail','nodata')
}
})
})
BackendService (Factory)
"use strict";
mobileClientApp.factory('BackendService', function() {
var mySocket = io.connect('http://thisLinkIsPrivate:8888/ns');
return {
login: function (pUserData) {
if (mySocket.connected) {
mySocket.emit('logincust', pUserData, function (resp, data) {
if (resp == "valid") {
var parsedData = JSON.stringify(data);
console.log(parsedData);
user.lName = parsedData.LName; // Fill userData
user.fName = parsedData.FName;
user.email = parsedData.Email;
user.phoneCallcenter = parsedData.Telephone;
console.info("Login successful.");
return 0;
}
else {
if (resp == "invalidpassword") {
return 1;
}
else if (resp == "invalidemail") {
return 2;
}
}
});
}
else { // Socket is not connected
console.warn("Socket not connected.);
user.fName = "Peter";
user.lName = "Offline";
return -1;
}
};
Angular Controller
$scope.doLogin = function() {
var user = {'Email': this.loginData.username, 'Password': this.loginData.password};
var isLoggedIn = BackendService.login(user); // 0 - logged in, 1 - invalid password, 2 - invalid email, -1 - socket not connected
console.log("BackendService.login(user): " + BackendService.login(user)); // is undefined!
console.log("isLoggedIn: " + isLoggedIn); // undefined!
if (isLoggedIn == 0 || isLoggedIn == -1) {
$location.path('/app/landing');
}
else {
$scope.errorMessage = "Invalid login data!";
}
};

Yes, the problem seems to be asynchrony. If you want to have access to results from login method you should pass a callback to it. Since after you called it, the next execution will be your console log and that will happen before your SQL query returns results.

Related

Simple MSAL Login/Authentication in JavaScript

I'm trying to do a simple login to Azure AD using the MSAL for JavaScript v2.0 library. We want users to be able to authenticate into our site with their work Microsoft accounts. All I need to do is be able to authenticate/login the user via Microsoft, and if they can login via their work Microsoft account, then they're granted access to our site.
I'm using the Javascript library and have followed the code from the Github page and while the login prompt is coming up, afterwards I have no idea how to check if the user is signed in.
Here's the code I'm using, which is basically what's in the sample code from Github:
<script type="text/javascript" src="https://alcdn.msauth.net/browser/2.15.0/js/msal-browser.min.js"></script>
<script type="text/javascript">
const msalConfig = {
auth: {
clientId: "[ClientID goes here]",
authority: "https://login.microsoftonline.com/[tenant ID]",
knownAuthorities: ["login.microsoftonline.com"],
protocolMode: "OIDC",
redirectUri: "[page on our site that doesn't have MSAL auth, listed in Azure Reply URLs]"
},
cache: {
cacheLocation: "sessionStorage", // This configures where your cache will be stored
storeAuthStateInCookie: true, // Set this to "true" if you are having issues on IE11 or Edge
},
system: {
loggerOptions: {
loggerCallback: (level, message, containsPii) => {
if (containsPii) {
return;
}
switch (level) {
case msal.LogLevel.Error:
console.error(message);
return;
case msal.LogLevel.Info:
console.info(message);
return;
case msal.LogLevel.Verbose:
console.debug(message);
return;
case msal.LogLevel.Warning:
console.warn(message);
return;
}
}
}
}
};
// Add here scopes for id token to be used at MS Identity Platform endpoints.
const loginRequest = {
scopes: ["User.Read"]
};
const silentRequest = {
scopes: ["openid", "profile", "User.Read"]
};
const ua = window.navigator.userAgent;
const msie = ua.indexOf("MSIE ");
const msie11 = ua.indexOf("Trident/");
const msedge = ua.indexOf("Edge/");
const isIE = msie > 0 || msie11 > 0;
const isEdge = msedge > 0;
let signInType;
let accountId = "";
let credType = "";
// Create the main myMSALObj instance
const myMSALObj = new msal.PublicClientApplication(msalConfig);
// Register Callbacks for Redirect flow
myMSALObj.handleRedirectPromise().then(handleResponse).catch((error) => {
console.log(error);
});
function handleResponse(resp) {
alert("beginning handleResponse");
if (resp !== null) {
accountId = resp.account.homeAccountId;
credType = resp.account.credentialType;
myMSALObj.setActiveAccount(resp.account);
alert("response not null (already auth), accountId: " + accountId + ", credType: " + credType);
}
else {
const currentAccounts = myMSALObj.getAllAccounts();
if (!currentAccounts || currentAccounts.length < 1) {
alert("currentAccounts null/empty, going to signIn");
signIn("loginRedirect");
//return;
}
else if (currentAccounts.length > 1) {
// add choose account code here
alert("currentAccounts has multiple");
}
else if (currentAccounts.length === 1) {
const activeAccount = currentAccounts[0];
myMSALObj.setActiveAccount(activeAccount);
accountId = activeAccount.homeAccountId;
credType = activeAccount.credentialType;
alert("currentAccounts == 1; accountId: " + accountId + ", credType: " + credType);
}
}
}
async function signIn(method) {
signInType = isIE ? "loginRedirect" : method;
if (signInType === "loginPopup") {
return myMSALObj.loginPopup(loginRequest).then(handleResponse).catch(function (error) {
console.log(error);
});
}
else if (signInType === "loginRedirect") {
return myMSALObj.loginRedirect(loginRequest);
}
}
function signOut() {
const logoutRequest = {
account: myMSALObj.getAccountByHomeId(accountId)
};
myMSALObj.logoutRedirect(logoutRequest);
}
async function getTokenPopup(request, account) {
request.account = account;
return await myMSALObj.acquireTokenSilent(request).catch(async (error) => {
console.log("silent token acquisition fails.");
if (error instanceof msal.InteractionRequiredAuthError) {
console.log("acquiring token using popup");
return myMSALObj.acquireTokenPopup(request).catch(error => {
console.error(error);
});
}
else {
console.error(error);
}
});
}
// This function can be removed if you do not need to support IE
async function getTokenRedirect(request, account) {
request.account = account;
return await myMSALObj.acquireTokenSilent(request).catch(async (error) => {
console.log("silent token acquisition fails.");
if (error instanceof msal.InteractionRequiredAuthError) {
// fallback to interaction when silent call fails
console.log("acquiring token using redirect");
myMSALObj.acquireTokenRedirect(request);
}
else {
console.error(error);
}
});
}
So what happens upon going to this page is I get the two alerts saying "beginning handleResponse" and then "currentAccounts null/empty, going to signIn."
Then I'm redirected to MS sign-in page which I do with my work MS account. This succeeds.
I'm then redirected to the site I have listed in Azure Reply URLs, another page on our site that isn't secure and has no Azure login code.
The problem is I have no idea where to check that the user is signed in. If I try and check immediately after the signIn("loginRedirect") call in the handleResponse() function on the first page, the code never gets hit apparently. If I try and check on the page I'm redirected to, by instantiating the MSAL object and calling getAllAccounts(), this returns null.
It seems maybe on the page I'm redirected to I could call the ssoSilent() function (seems like this can check if user is authenicated?), but this requires a username/AccountId parameter. Well I don't frickin know this if a user hasn't (possibly) been authenticated yet! I don't really understand that.
So I don't know. It's probably something stupid I'm doing but I'm a pretty basic JavaScript person and am pretty much a total noob with authenication stuff. Any help would be epic.

Is it invalid to write re.send() function inside mydb.collection().find().toArray callback?

I'am setting up a login page for my app. I want to send a file after verifing if the login page is provided with proper username and password.
I have a handler for a post request which checks if the user entered correct username and password.
app.post('/login',function(req,res){
var data="";
var flag_isthere=0,wrongpass=0;
console.log('login-done');
req.setEncoding('UTF-8')
req.on('data',function(chunk){
data+=chunk;
});
req.on('end',function()
{
MongoClient.connect("mongodb://localhost:27017/userdetails",{useNewUrlParser: true ,useUnifiedTopology: true },function(err,db)
{
if(err) throw err;
var q = JSON.parse(data)
const mydb=db.db('userdetails')
var c=mydb.collection('signup').find().toArray(
function(err,res)
{
for(var i=0;i<res.length;i++)
if( (res[i].email==q['email']) ) //check if the account exists
{
flag_isthere=1;
if( (res[i].pass != q['pass'] ) )
wrongpass=1;
break;
}
if(flag_isthere==0)
{
console.log(q['email'], ' is not registered')
}
else
{
console.log('Already exists!!!');
}
if( wrongpass==1)
{
console.log('password entered is wrong')
}
if(flag_isthere==1 && wrongpass==0)
{
console.log('Congratulations,username and password is correct');
res.send( { login:'OK', error:'' } ); //this statement is giving an error in node JS part
}
});//var c
})//mongoclient.connect
})//req.on
res.send({ login:'OK', error:'' }); //this works properly in node JS
console.log(flag_isthere , wrongpass ) //but here the flag_isthere==0 and wrongpass==0 , so it won't get validated
});
It gives the error as
TypeError: res.send is not a function
at E:\ITT_project_shiva\loginserver_new.js:112:25
at result (E:\ITT_project_shiva\node_modules\mongodb\lib\operations\execute_operation.js:75:17)
at executeCallback (E:\ITT_project_shiva\node_modules\mongodb\lib\operations\execute_operation.js:68:9)
at handleCallback (E:\ITT_project_shiva\node_modules\mongodb\lib\utils.js:129:55)
at cursor.close (E:\ITT_project_shiva\node_modules\mongodb\lib\operations\to_array.js:36:13)
at handleCallback (E:\ITT_project_shiva\node_modules\mongodb\lib\utils.js:129:55)
at completeClose (E:\ITT_project_shiva\node_modules\mongodb\lib\cursor.js:859:16)
at Cursor.close (E:\ITT_project_shiva\node_modules\mongodb\lib\cursor.js:878:12)
at cursor._next (E:\ITT_project_shiva\node_modules\mongodb\lib\operations\to_array.js:35:25)
at handleCallback (E:\ITT_project_shiva\node_modules\mongodb\lib\core\cursor.js:32:5)
[nodemon] app crashed - waiting for file changes before starting...
How do I send the response to the user after proper validation?
It's not that you're doing it from the callback that's the problem. There are two different problems:
You're shadowing res by redefining it in the callback's parameter list
(Once you fix that) You're calling res.send twice:
Once at the end of your posthandler
Once within the callback
send implicitly completes the response, so you can only call it once.
In your case, you want to call it from within your callback, once you've determined that none of the records matches.
See *** comments for a rough guideline (but keep reading):
app.post('/login', function(req, res) {
var data = "";
var flag_isthere = 0,
wrongpass = 0;
console.log('login-done');
req.setEncoding('UTF-8')
req.on('data', function(chunk) {
data += chunk;
});
req.on('end', function() {
MongoClient.connect("mongodb://localhost:27017/userdetails", {
useNewUrlParser: true,
useUnifiedTopology: true
}, function(err, db) {
if (err) throw err;
var q = JSON.parse(data)
const mydb = db.db('userdetails')
var c = mydb.collection('signup').find().toArray(
function(err, array) { // *** Renamed `res` to `array
for (var i = 0; i < array.length; i++)
if ((array[i].email == q['email'])) //check if the account exists
{
flag_isthere = 1;
if ((array[i].pass != q['pass']))
wrongpass = 1;
break;
}
if (flag_isthere == 0) {
console.log(q['email'], ' is not registered')
} else {
console.log('Already exists!!!');
}
// *** Handle result here
if (flag_isthere == 1 && wrongpass == 0) {
console.log('Congratulations,username and password is correct');
res.send({ login: 'OK', error: '' }); //this statement is giving an error in node JS part
} else if (wrongpass == 1) {
console.log('password entered is wrong')
// *** res.send(/*...*/)
} else {
// Handle the issue that there was no match
// *** res.send(/*...*/)
}
}
); //var c
}) //mongoclient.connect
}) //req.on
// *** Don't try to send a response here, you don't know the answer yet
});
but, it seems like you should be able to find just the one user (via findOne? I don't do MongoDB), rather than finding all of them and then looping through the resulting array.
See also the answers to these two questions, which may help you with asynchronous code issues:
How do I return the response from an asynchronous call?
Why is my variable unaltered after I modify it inside of a function?
A couple of other notes:
I strongly recommend using booleans for flags, not numbers.
NEVER store actual passwords in your database!! Store a strong hash, and then compare hashes.
You might find async/await syntax more convenient to work with. I think recent MongoDB clients support promises (which you need for async/await).

Firebase Functions - Return after Realtime Database fetched inside User Fetch

I have a Firebase Cloud Function that is called in my app with JavaScript.
When the function is called it fetches the user data from the user ID, then fetched a record from the Realtime Database to check for a match.
This function works but is returning "null" and finishing early instead of returning the success or error message when the match is detected.
How can I make the return text be the success or error from the match and only complete once this match is decided?
exports.matchNumber = functions.https.onCall((data, context) => {
// ID String passed from the client.
const ID = data.ID;
const uid = context.auth.uid;
//Get user data
admin.auth().getUser(uid)
.then(function(userRecord) {
// Get a database reference to our posts
var db = admin.database();
var ref = db.ref("path/to/data/" + ID);
return ref.on("value", function(snapshot) {
//Fetch current phone number
var phoneORStr = (snapshot.val() && snapshot.val().phone) || "";
//Fetch the current auth user phone number
var userAuthPhoneNumber = userRecord.toJSON().phoneNumber;
//Check if they match
if (userAuthPhoneNumber === phoneORStr) {
console.log("Phone numbers match");
var updateRef = db.ref("path/to/data/" + ID);
updateRef.update({
"userID": uid
});
return {text: "Success"};
} else {
console.log("Phone numbers DO NOT match");
return {text: "Phone number does not match the one on record."};
}
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
return {text: "Error fetching current data."};
});
})
.catch(function(error) {
console.log('Error fetching user data:', error);
return {text: "Error fetching data for authenticated user."};
});
});
Thank you
The Firebase ref.on() method doesn't return a promise, so the return statements you have in there do nothing.
You're looking for ref.once(), which returns a promise, and thus will bubble up the return statements you have within it:
return ref.once("value").then(function(snapshot) {
...
As Doug pointed out, you'll also need to return the promise from the top level. So:
//Get user data
return admin.auth().getUser(uid)
.then(function(userRecord) {

I need a way to break this loop or an alternative way to do this

I'm new to js and firebase. I'm trying to use firebase database for a custom login by using my own table called users. I have used a for each loop to go through the data. But the else part is executed multiple time because of this. I need to break the loop so it won't happen.
This is my data:-
{"users" : [ {
"userId" : "1",
"username" : "admin",
"password" : "admin",
"type" : "admin"
}, {
"userId" : "2",
"username" : "cashier",
"password" : "cashier",
"type" : "cashier"
}]
}**
This is the code I wrote:
var database=firebase.database();
function SignIn(){
var txtuser=document.getElementById('username').value;
var txtpass=document.getElementById('password').value;
var error=false;
firebase.database().ref('users').orderByKey().once('value').then(function(snapshot) {
snapshot.forEach(function(childSnapshot) {
var users = childSnapshot.child('username').val();
var pass=childSnapshot.child('password').val();
if(txtuser==users && txtpass==pass){
var type=childSnapshot.child('type').val();
if(type=="admin"){
location.href="admin.html";
}
else if(type=="cashier"){
location.href="cashier.html";
}
}
else{
error=true;
}
});
});
if(error==true)
{
window.alert("Invalid Credentials");
location.href="index.html";
}
}
Password Authentication
Instead of using your method of storing authentication details in the database, use the Sign in a user with an email address and password flow.
However, because you are using usernames not emails, append your storage bucket domain to the username (which will normally be PROJECT_ID.appspot.com).
So your "admin" and "cashier" users would become "admin#PROJECT_ID.appspot.com" and "cashier#PROJECT_ID.appspot.com". For the sake of email authentication, these are valid email addresses, even though they don't have inboxes.
You can then use firebase.auth() across your web app to manage your user's access control to pages like "admin.html" and "cashier.html".
Note: If you ever send out email to your users, make sure to omit emails that match "*#PROJECT_ID.appspot.com"
Answering the question
WARNING: Do not authenticate this way. Please use above method.
Passwords should never be stored in plain text
Passwords should never be stored in plain text
Users should never have access to another user's credentials in any database
For the sake of answering the question, you could use the following code:
var database=firebase.database();
function SignIn(){
var txtuser=document.getElementById('username').value;
var txtpass=document.getElementById('password').value;
firebase.database().ref('users').orderByChild('username').equalTo(txtuser).once('value')
.then(function(snapshot) {
if (!snapshot.hasChildren()) {
throw "username not found";
} else if (snapshot.numChildren() != 1) {
throw "duplicate usernames";
}
// only one child at this point, so only called once
snapshot.forEach(function(childSnapshot) {
if (pass != childSnapshot.child('password').val()) {
throw "password mismatch";
}
var type=childSnapshot.child('type').val();
if(type=="admin") {
location.href = "admin.html";
} else if(type=="cashier") {
location.href = "cashier.html";
} else {
throw "unknown user type";
}
})
})
.catch(function(error) { // catches any errors thrown by promises
location.href = "index.html";
});
}
In the above code, each throw is caught by the Promise returned by the Firebase query. You can read up on Promises here.
Just check if error is set to true inside the .forEach and use return to "break" out:
var database=firebase.database();
function SignIn(){
var txtuser=document.getElementById('username').value;
var txtpass=document.getElementById('password').value;
var error=false;
firebase.database().ref('users').orderByKey().once('value').then(function(snapshot) {
snapshot.forEach(function(childSnapshot) {
var users, pass;
if (error) { return; } // <-- "break" the "loop"
users = childSnapshot.child('username').val();
pass = childSnapshot.child('password').val();
if(txtuser == users && txtpass == pass){
var type=childSnapshot.child('type').val();
if(type == "admin"){
location.href="admin.html";
}
else if(type == "cashier"){
location.href="cashier.html";
}
} else {
error = true;
}
});
if(error) {
window.alert("Invalid Credentials");
location.href="index.html";
}
});
}

How to build Twitter OAuth authentication?

This is my JavaScript code for following operation: https://api.twitter.com/1.1/users/show.json?screen_name=barackobama
Does not work anymore since the Twitter API 1.1: {"errors":[{"message":"Bad Authentication data","code":215}]}
I know you need the OAuth access Token now, I even created one in Twitter, but I don't know how to add it in this Script.
Thanks for any Help!
function TwitterFollowers(aUsername)
{
user = _twitterFetchUserData(aUsername);
return user.followers_count;
}
function TwitterFollowings(aUsername)
{
user = _twitterFetchUserData(aUsername);
return user.friends_count;
}
function TwitterListed(aUsername)
{
user = _twitterFetchUserData(aUsername);
return user.listed_count;
}
function TwitterId(aUsername)
{
user = _twitterFetchUserData(aUsername);
return user.id;
}
function TwitterFullname(aUsername)
{
user = _twitterFetchUserData(aUsername);
return user.name;
}
function TwitterCreatedDate(aUsername)
{
user = _twitterFetchUserData(aUsername);
return user.created_at;
}
function TwitterVerified(aUsername)
{
user = _twitterFetchUserData(aUsername);
return user.verified;
}
function TwitterTimezone(aUsername)
{
user = _twitterFetchUserData(aUsername);
return user.time_zone;
}
function TwitterLocation(aUsername)
{
user = _twitterFetchUserData(aUsername);
return user.location;
}
function TwitterHomepage(aUsername)
{
user = _twitterFetchUserData(aUsername);
return user.url;
}
function TwitterDescription(aUsername)
{
user = _twitterFetchUserData(aUsername);
return user.description;
}
// internal function invisible to Google SpreadSheets
var _twitterFetchUserData = function(aUsername)
{
if (aUsername === undefined || aUsername === null)
throw "No parameter specified. Write any Twitter USERNAME as parameter."
if (aUsername == "")
throw "USERNAME cannot be empty string. Write any Twitter USERNAME as parameter.";
// See https://dev.twitter.com/docs/api/1/get/users/show for API documentation
var url = "https://api.twitter.com/1.1/users/show.json?screen_name=" + encodeURIComponent(aUsername);
try
{
var response = UrlFetchApp.fetch(url);
}
catch (e)
{
throw "Please check if Twitter Username \"" + aUsername + "\" exists. " + e;
}
if (response.getResponseCode() != 200)
throw "Unexpected response code from Twitter.";
var responseText = response.getContentText();
if (responseText == null || responseText == "")
throw "Empty response from Twitter.";
var user = Utilities.jsonParse(responseText);
if (user == null)
throw "Problem with response from Twitter. Invalid JSON.";
return user;
};
According to the new version Twitter API v1.1 all requests to Twitter must be signed by using OAuth 1.0A. You may want to visit here to see how it works. I would suggest to use Twitter libraries to authenticate and sign your requests like codebird-js(have a look at the examples below or at the website given). It's really easy to use. To authenticate is as simple as below:
var cb = new Codebird;
cb.setConsumerKey('YOURKEY', 'YOURSECRET');
cb.setToken('YOURTOKEN', 'YOURTOKENSECRET');
and then make calls e.g. tweet:
cb.__call(
'statuses_update',
{'status': 'Whohoo, I just tweeted!'},
function (reply) {
// ...
}
);
Hope it helps.

Categories