I'm having quite a hard time understanding how to chain promises. I'm writing login function for my app, leverating Loopback's Angular SDK. The objective, upon validating a user's credentials, is to confirm that the user's account is active, then fetch some additional properties including the user's role and set a flag to true if the user has admin privileges.
Here's my code...
$scope.login = function (user) {
User.login(user).$promise.then(
function (data) {
$rootScope.activeUser = data;
$rootScope.user_id = $rootScope.activeUser.user.username;
console.log('Active User: ', $rootScope.activeUser.user.email);
console.log('Status: ', $rootScope.activeUser.user.status);
if ($rootScope.activeUser.user.status === 'Y') {
$scope.loginError = false;
function checkAdmin(eid) {
Se_user.findById({
id: eid
}).$promise.then(
function (data1) {
var user_properties = data1;
if (user_properties.role === 'Admin') {
$rootScope.isAdmin = true;
console.log('isAdminInside: ', $rootScope.isAdmin);
return true;
} else {
//$rootScope.isAdmin = false;
return false;
}
});
};
var isAdmin = checkAdmin($rootScope.user_id);
console.log('isAdminOutside: ', $rootScope.isAdmin);
$state.go('home');
} else {
$scope.loginError = true;
$scope.loginErrorMessage = "Your account has been disabled. Please contact BMT Support for assistance";
}
},
function (err) {
console.log('Error: ', err)
$scope.loginError = true;
$scope.loginErrorMessage = "You've entered an invalid User ID or Password. Please try again.";
});
};
I've been troubleshooting by writing to the console, here's a sample of the output...
Active User: user#email.com
Status: Y
isAdminOutside: undefined
isAdminInside: true
How should I restructure so that the result of checkAdmin is properly returned after a successful login of an active user?
Try changing this part of code :
function checkAdmin(eid) {
return Se_user.findById({
id: eid
}).$promise.then(
function(data1) {
var user_properties = data1;
if (user_properties.role === 'Admin') {
$rootScope.isAdmin = true;
console.log('isAdminInside: ', $rootScope.isAdmin);
return true;
} else {
//$rootScope.isAdmin = false;
return false;
}
});
};
var isAdmin = checkAdmin($rootScope.user_id)
.then(function(val) {
console.log('isAdminOutside: ', val);
$state.go('home');
});
Related
I am trying to build a JavaScript class that allows me to interact with my Home Assistant server via web sockets (ws library.) The script is intended to be executed in the node.js environment.
const WebSocket = require('ws');
class HomeAssistantWebSocket {
constructor(config = {}) {
this.config = config;
this.initialize();
}
config;
initialized = false;
initializeErrors = [];
authenticated = false;
ws = null;
authenticate = () => {
let {
config,
ws,
serialize
} = this;
console.log("Attempting to authenticate...");
ws.send(serialize({
"type": "auth",
"access_token": config["access_token"]
}));
return true;
}
openConnection = () => {
let {
ws
} = this;
ws.on('open', () => {
console.log("Connection opened!");
});
ws.on('message', (data) => {
this.handleMessage(data);
});
}
deserialize = (string) => {
try {
return JSON.parse(string);
} catch (error) {
return false;
}
}
handleMessage = (data) => {
let {
authenticate,
deserialize,
ws
} = this;
data = deserialize(data);
console.log(data);
if(data["type"] === "auth_required") {
authenticate();
}
if (data["type"] === "auth_ok" && !this.authenticated) {
this.authenticated = true;
console.log("Successfully authenticated");
}
if (data["type"] === "auth_ok") {
ws.send(JSON.stringify({
"id": 20,
"type": "subscribe_events",
}));
}
}
initialize = () => {
let {
config,
initialized,
initializeErrors,
} = this;
if (Object.keys(config).length < 1) {
initializeErrors.push("No config present.");
} else if (!config.hasOwnProperty("access_token") && typeof config["access_token"] === "string") {
initializeErrors.push("Config must contain a valid access_token.");
} else if (!config.hasOwnProperty("home_assistant_url") && typeof config["home_assistant_url"] === "string") {
initializeErrors.push("Config must contain a valid home_assistant_url");
}
if (this.initializeErrors.length === 0) {
this.ws = new WebSocket(config["home_assistant_url"]);
this.openConnection();
initialized = true;
console.log("Attempting to open connection...");
} else {
console.log("Failed to initialize:");
this.initializeErrors.forEach((e) => {
console.log(e);
});
}
return true;
}
serialize = (json) => {
try {
return JSON.Stringify(json);
} catch (error) {
return false;
}
}
}
const haWS = new HomeAssistantWebSocket({
"access_token": "redacted_access_token",
"home_assistant_url": "ws://homeassistant.local:8123/api/websocket"
});
I am running in to an issue where my code ceases execution after the authentication phase. My code prints the following in the console and then the script stops executing. No errors are present.
Connection opened!
{ type: 'auth_required', ha_version: '2021.2.3' }
Attempting to authenticate...
I have verified my code does properly connect to the web socket api and is communicating with the home assistant server. Does anyone see anything wrong with my code that would cause the script to stop execution/garbage collect the ws on message to prevent further messages from being received?
I have a very basic example working as expected outside of a class that makes it pass the authentication phase and leaves the socket open and receives data as expected. Any help would be greatly appreciated.
serialize = (json) => {
try {
return JSON.stringify(json);
} catch (error) {
return false;
}
}
I found the issue in the serialize function. I had an improper reference to the JSON.stringify function. In my code it was JSON.Stringify. It should be JSON.stringify.
It's always the little things...
I have this function that has a chain of promises and I want to return an object:
var signIn = function(fb_ID,fb_accessToken) {
console.log("signin!")
var promise = new Parse.Promise();
var TokenStorage = Parse.Object.extend("tokenStorage");
var query = new Parse.Query(TokenStorage);
query.equalTo('facebookID', fb_ID);
query.ascending('createdAt');
var password;
var username;
var output = {};
var user;
// Check if this ID has previously logged in, using the master key
return query.first({ useMasterKey: true }).then(function(tokenData) {
// Update the accessToken if it is different.
if (fb_accessToken !== tokenData.get('fb_accessToken')) {
console.log('1')
user = tokenData.get('user');
user.fetch({
success: function(data) {
username = data.get('username')
tokenData.set('fb_accessToken', fb_accessToken);
/*
password = new Buffer(24);
_.times(24, function(i) {
password.set(i, _.random(0, 255));
password = password.toString('base64')
})
*/
password = (Date.now().toString(36) + Math.random().toString(36).substr(2, 10)).toUpperCase();
user.setPassword(password);
tokenData.set('password',password)
console.log('fetch')
return
}
}).then(function(){
tokenData.save(null, { useMasterKey: true }).then(function(tokenuser) {
console.log('tokensave')
user.save();
return Parse.User.logIn(username, password);
}).then(function(data) {
// Return the user object.
console.log('output return')
output.success = true
output.isnewuser = false
output.username = username;
output.password = password;
output.fb_accessToken = fb_accessToken;
//return Parse.Promise.as(output);
//promise.resolve(output)
//return promise
return output
});
})
} else {
console.log('2')
Parse.Promise.as().then(function() {
username = tokenData.get('username');
password = tokenData.get('password');
return
}).then(function(){
return Parse.User.logIn(username, password)
.then(function(data) {
// Return the user object
output.success = true
output.isnewuser = false
output.username = username;
output.password = password;
output.fb_accessToken = fb_accessToken;
console.log('oo'+JSON.stringify(output))
//return Parse.Promise.as(output);
//promise.resolve(output)
return output
});
})
}
})
//return promise
}
The function has two if statements and I would like that both will return 'output' object when I call:
signIn(fb_ID,fb_accessToken).then(function(data){
if (data.success == true) {
console.log('inn'+JSON.stringify(data))
response.success(data)
}
else {
console.log('errr')
response.error(data)
}
})
The 'data' object should be the 'output' object inside the SignIn function. Any idea?
You should put return before user.fetch and tokenData.save in first if block. In the else block put return before Parse.promise.as.
Your current implementation looks like something similar to this.
var someFunction = function () {
return mainPromise().then(function () {
if (someCondition) {
somePromiseOne().then(function () {
return data;
});
}
else {
somePromiseTwo().then(function () {
return data;
});
}
});
};
In the both cases you should put return before somePromiseOne and somePromiseTwo in order to pass the data to the promise chain.
You can do quite a lot to tidy things up ... and get it working.
First, you can (and should) move the code from the user.fetch({ success:... }) callback into the following then's callback. "Property-style" callbacks should be avoided when a promise interface is available (and already used).
Then, you might aim for flow control as follows :
doSomethingAsync().then(function() {
var promise;
if(condition) {
promise = ...; // deliver username and password
} else {
promise = ...; // deliver username and password
}
return promise.then(function(details) {
return login().then(function() {
// fully compose output object here
return details;
});
});
});
In practice, the code will be more extensive due to the number of async steps and the volume of synchronous code, particularly in the first condition.
In full, you might end up with something like this :
var signIn = function(fb_ID, fb_accessToken) {
var query = new Parse.Query(Parse.Object.extend('tokenStorage'));
query.equalTo('facebookID', fb_ID);
query.ascending('createdAt');
// Check if this ID has previously logged in, using the master key
return query.first({ useMasterKey: true }).then(function(tokenData) {
var promise;
// Update the accessToken if it is different.
if (fb_accessToken !== tokenData.get('fb_accessToken')) {
var user = tokenData.get('user');
promise = user.fetch().then(function(data) {
var details = {
username: data.get('username'),
password: (Date.now().toString(36) + Math.random().toString(36).substr(2, 10)).toUpperCase()
};
tokenData.set('fb_accessToken', fb_accessToken);
tokenData.set('password', details.password);
user.setPassword(details.password);
// Assuming here that `tokenData.save()` and `user.save()` are both async and can be executed in parallel.
return Parse.Promise.when([
tokenData.save(null, { useMasterKey: true }),
user.save()
]).then(function() {
return details;
});
});
} else {
// here `promise` mimics the final successful state of the promise in the other condition.
// ie. promise-wrapped details
promise = Parse.Promise.as({
username: tokenData.get('username'),
password: tokenData.get('password')
});
}
return promise.then(function(details) {
// whichever condition was executed, username and password details are delivered here in an identical format.
return Parse.User.logIn(details.username, details.password).then(function() {
details.success = true;
details.isnewuser = false;
details.fb_accessToken = fb_accessToken;
return details; // deliver details to signIn's caller
});
});
});
}
I'm very confusing because of 'this' property.
What does "delete this.user;" mean in AuthenticationFactory. I think function "check" is a method so it will be bind with 'auth' object. But, there is no 'user' property in 'auth' Object. Can you explain it?
Also, in 'UserAuthFactory' (delete AuthenticationFactory.user, delete AuthenticationFactory.userRole)
I can't figure out what are "user" and "userRole" properties. There are no such properties in AuthenticationFactory.
Here the my code from http://thejackalofjavascript.com/architecting-a-restful-node-js-app/
myApp.factory('AuthenticationFactory', function($window) {
var auth = {
isLogged: false,
check: function() {
if ($window.sessionStorage.token && $window.sessionStorage.user) {
this.isLogged = true;
} else {
this.isLogged = false;
delete this.user;
}
}
}
return auth;
});
myApp.factory('UserAuthFactory', function($window, $location, $http, AuthenticationFactory) {
return {
login: function(username, password) {
return $http.post('http://localhost:3000/login', {
username: username,
password: password
});
},
logout: function() {
if (AuthenticationFactory.isLogged) {
AuthenticationFactory.isLogged = false;
delete AuthenticationFactory.user;
delete AuthenticationFactory.userRole;
delete $window.sessionStorage.token;
delete $window.sessionStorage.user;
delete $window.sessionStorage.userRole;
$location.path("/login");
}
}
}
});
If you look further down, to the controller code:
$scope.login = function() {
var username = $scope.user.username,
password = $scope.user.password;
if (username !== undefined && password !== undefined) {
UserAuthFactory.login(username, password).success(function(data) {
AuthenticationFactory.isLogged = true;
AuthenticationFactory.user = data.user.username;
AuthenticationFactory.userRole = data.user.role;
$window.sessionStorage.token = data.token;
$window.sessionStorage.user = data.user.username; // to fetch the user details on refresh
$window.sessionStorage.userRole = data.user.role; // to fetch the user details on refresh
$location.path("/");
}).error(function(status) {
alert('Oops something went wrong!');
});
} else {
alert('Invalid credentials');
}
};
On a successfully login, the controller is adding the properties user and userRole to the AuthenticationFactory.
Writing a Parse Cloud Function (which uses Parse Javascript SDK) and I am having trouble checking to see if the current user has role "Admin". I'm looking at the web view of the Role class and a role with the name "Admin" exists, if I click "View Relations" for users, it shows the current user. I doubt it should matter, but "Admin" is the only role and the current user is the only user with a role. Lastly, the "Admin" role has an ACL of Public Read, so that shouldn't be causing any issues either.
Code is as follows:
...
var queryRole = new Parse.Query(Parse.Role);
queryRole.equalTo('name', 'Admin');
queryRole.equalTo("users", Parse.User.current());
queryRole.first({
success: function(result) { // Role Object
var role = result;
role ? authorized = true : console.log('Shiet, user not Admin');
},
error: function(error) {
console.log("Bruh, queryRole error");
}
})
console.log('After test: Auth = ' + authorized);
if (!authorized) {
response.error("You ain't no admin, measly user");
return;
}
...
This results in the following in the log:
Before test: Auth = false
After test: Auth = false
Give this a shot:
var authorized = false;
console.log('Before test: Auth = ' + authorized);
var queryRole = new Parse.Query(Parse.Role);
queryRole.equalTo('name', 'Admin');
queryRole.first({
success: function(result) { // Role Object
console.log("Okay, that's a start... in success 1 with results: " + result);
var role = result;
var adminRelation = new Parse.Relation(role, 'users');
var queryAdmins = adminRelation.query();
queryAdmins.equalTo('objectId', Parse.User.current().id);
queryAdmins.first({
success: function(result) { // User Object
var user = result;
user ? authorized = true : console.log('Shiet, user not Admin');
}
});
},
error: function(error) {
console.log("Bruh, can't find the Admin role");
}
}).then(function() {
console.log('After test: Auth = ' + authorized);
});
I got a simpler solution, give this a try:
var adminRoleQuery = new Parse.Query(Parse.Role);
adminRoleQuery.equalTo('name', 'admin');
adminRoleQuery.equalTo('users', req.user);
return adminRoleQuery.first().then(function(adminRole) {
if (!adminRole) {
throw new Error('Not an admin');
}
});
For those of you looking for a Parse Server (2018) answer, see below:
Parse.Cloud.define('authorizedUserTest', function(request, response) {
if(!request.params.username){
//response.error('no username');
response.error(request.params);
}
var queryRole = new Parse.Query(Parse.Role);
queryRole.equalTo('name','Admin');
queryRole.first({ useMasterKey: true }).then(function(promise){
var role = promise;
var relation = new Parse.Relation(role, 'users');
var admins = relation.query();
admins.equalTo('username', request.user.get('username'));
admins.first({ useMasterKey: true }).then(function(user){
if(user){
response.success(true)
}
else {
response.success(false)
}
}, function(err){
response.error('User is not an admin')
})
}, function(err){
response.error(err)
})
});
request.params is equal to a dictionary {"username":inputUsernameHere}.
Feel free to comment if you have questions.
I expect the function app.patch to immediately return if the validation callback argument isValid is set to false in the validate function.
but it is not :-/
I can't see the error, what am I doing wrong?
function route(app) {
app.patch('/category/:category_id', function(req, res) {
var id = req.params.category_id;
var title = req.body.title;
validate('title', title, function(response, isValid) {
if(!isValid) {
res.json(422, response);
return;
};
});
console.log("should not get to here");
...
});
var validate = function validate(field, value, callback) {
if (value === undefined || value.trim() === '') {
var response = { };
response.message = "Validation failed";
callback(response, false);
} else {
callback(null, true);
}
};
};
module.exports = route;
I can't see the error, what am I doing wrong?
You return from the validate's callback function, but not from the patch's callback function.
How can I improve my code?
If validate is synchronous (as in the code you posted), don't use a callback. Just return the result:
app.patch('/category/:category_id', function(req, res) {
var id = req.params.category_id;
var title = req.body.title;
var response = validate('title', title);
if (response) {
res.json(422, response);
return;
}
console.log("will not get to here");
…
});
function validate(field, value, callback) {
if (value === undefined || value.trim() === '') {
return response = {message: "Validation failed"};
return null;
}
If you want/need to use callbacks, move all the code from should not get here into the validate callback function, after that if-statement where you return:
app.patch('/category/:category_id', function(req, res) {
var id = req.params.category_id;
var title = req.body.title;
validate('title', title, function(response, isValid) {
if (!isValid) {
res.json(422, response);
return;
}
console.log("will not get to here");
…
});
});
function validate(field, value, callback) {
// something async, then
callback({message: "Validation failed"}, false);
// or
callback(null, true);
}