Related
I am writing an application in Express.js with a separate controller layer and a service layer. Here is my current code:
user.service.js
exports.registerUser = async function (email, password) {
const hash = await bcrypt.hash(password, 10);
const countUser = await User.countDocuments({email: email});
if(countUser > 0) {
throw ({ status: 409, code: 'USER_ALREADY_EXISTS', message: 'This e-mail address is already taken.' });
}
const user = new User({
email: email,
password: hash
});
return await user.save();
};
exports.loginUser = async function (email, password) {
const user = await User.findOne({ email: email });
const countUser = await User.countDocuments({email: email});
if(countUser === 0) {
throw ({ status: 404, code: 'USER_NOT_EXISTS', message: 'E-mail address does not exist.' });
}
const validPassword = await bcrypt.compare(password, user.password);
if (validPassword) {
const token = jwt.sign({ email: user.email, userId: user._id }, process.env.JWT_KEY, { expiresIn: "10s" });
return {
token: token,
expiresIn: 3600,
userId: user._id
}
} else {
throw ({ status: 401, code: 'LOGIN_INVALID', message: 'Invalid authentication credentials.' });
}
};
user.controller.js
exports.userRegister = async function (req, res, next) {
try {
const user = await UserService.registerUser(req.body.email, req.body.password);
res.status(201).json({ data: user });
} catch (e) {
if(!e.status) {
res.status(500).json( { error: { code: 'UNKNOWN_ERROR', message: 'An unknown error occurred.' } });
} else {
res.status(e.status).json( { error: { code: e.code, message: e.message } });
}
}
}
exports.userLogin = async function (req, res, next) {
try {
const user = await UserService.loginUser(req.body.email, req.body.password);
res.status(200).json({ data: user });
} catch (e) {
if(!e.status) {
res.status(500).json( { error: { code: 'UNKNOWN_ERROR', message: 'An unknown error occurred.' } });
} else {
res.status(e.status).json( { error: { code: e.code, message: e.message } });
}
}
}
The code works, but requires some corrections. I have a problem with error handling. I want to handle only some errors. If another error has occurred, the 500 Internal Server Error will be returned.
1) Can I use "throw" object from the service layer? Is this a good practice?
2) How to avoid duplication of this code in each controller:
if(!e.status) {
res.status(500).json( { error: { code: 'UNKNOWN_ERROR', message: 'An unknown error occurred.' } });
} else {
res.status(e.status).json( { error: { code: e.code, message: e.message } });
}
3) Does the code require other corrections? I'm just learning Node.js and I want to write the rest of the application well.
Yes, you can throw errors from service layer, it is good practice to catch errors with try/catch block in controller
I handle this with a custom error middleware, just use a next function in a catch block.
catch (e) {
next(e)
}
Example of error middleware (for more info check docs, fill free to move a middleware to file)
app.use(function (err, req, res, next) {
// err is error from next(e) function
// you can do all error processing here, logging, parsing error messages, etc...
res.status(500).send('Something broke!')
})
From my point of view it looks good. If you looking for some best practice and tools, try eslint (with AirBnb config for example) for linting, dotenv for a environment variables management, also check Node.js Best Practice
i want to give you an example:
this code in your controller
findCar(idCar)
} catch (error) {
switch (error.message) {
case ErrorConstants.ELEMENT_NOT_FOUND('LISTING'): {
return {
response: {
message: ErrorMessages.ELEMENT_NOT_FOUND_MESSAGE('LISTING'),
},
statusCode,
}
}
default: {
return {
response: {
message: ErrorMessages.UNKNOWN_ERROR_MESSAGE,
},
statusCode,
}
}
}
}
and this code in your service
findCar: async listingId => {
try {
if (some condition) {
throw new Error(ErrorConstants.ELEMENT_NOT_FOUND('LISTING'))
}
return { ... }
} catch (error) {
console.error(error.message)
throw new Error(ErrorConstants.UNKNOWN_ERROR)
}
},
controller is going to catch the service's errors
I have recently been developing a MERN application and I have recently came into the trouble that express is saying that I am setting headers after they are sent.
I am using mongo db and trying to update a user profile.
I have tried to comment out my res.send points to find the issue but I have failed to do so.
Here is my post method for updating the user profile:
app.post("/api/account/update", (req, res) => {
const { body } = req;
// Validating and Checking Email
if (body.email) {
var email = body.email;
email = email.toLowerCase();
email = email.trim();
body.email = email;
User.find(
{
email: body.email
},
(err, previousUsers) => {
if (previousUsers.length > 0) {
return res.send({
success: false,
message:
"Error: There is already another account with that email address"
});
} else {
}
}
);
}
// Validating Names Function
function checkName(name) {
var alphaExp = /^[a-zA-Z]+$/;
if (!name.match(alphaExp)) {
return res.send({
success: false,
message: "Error: Names cannot contain special characters or numbers"
});
}
}
checkName(body.firstName);
checkName(body.lastName);
// Making sure that all fields cannot be empty
if (!body.email && !body.firstName && !body.lastName) {
return res.send({
success: false,
message: "Error: You cannot submit nothing"
});
}
// Getting User ID from the current session
UserSession.findById(body.tokenID, function(err, userData) {
// Finding User ID using the current users session token
if (userData.isDeleted) {
return res.send({
success: false,
message:
"Error: Session token is no longer valid, please login to recieve a new one"
});
}
// Deleting the token ID from the body object as user table entry doesnt store tokens
delete body.tokenID;
// Finding the user profile and updating fields that are present
User.findByIdAndUpdate(userData.userId, body, function(err, userInfo) {
if (!err) {
return res.send({
success: true,
message: "Success: User was updated successfully"
});
}
});
});
});
This is the call that I am doing to the backend of the site:
onUpdateProfile: function(fieldsObj) {
return new Promise(function(resolve, reject) {
// Get Session Token
const obj = getFromStorage("the_main_app");
// Defining what fields are getting updated
fieldsObj.tokenID = obj.token;
// Post request to backend
fetch("/api/account/update", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(fieldsObj)
})
.then(res => {
console.log("Verify Token - Res");
return res.json();
})
.then(json => {
console.log("Verify Token JSON", json);
if (json.success) {
window.location.href = `/manage-account?success=${json.success}`;
} else {
window.location.href = `/manage-account?success=${json.success}`;
}
});
});
}
Here is my error message that I am getting:
Error: Can't set headers after they are sent.
at validateHeader (_http_outgoing.js:491:11)
at ServerResponse.setHeader (_http_outgoing.js:498:3)
at ServerResponse.header (C:\Users\kieran.corkin\Desktop\Projects\Mern Template Final\mern-cra-and-server\server\node_modules\express\lib\response.js:767:10)
at ServerResponse.send (C:\Users\kieran.corkin\Desktop\Projects\Mern Template Final\mern-cra-and-server\server\node_modules\express\lib\response.js:170:12)
at ServerResponse.json (C:\Users\kieran.corkin\Desktop\Projects\Mern Template Final\mern-cra-and-server\server\node_modules\express\lib\response.js:267:15)
at ServerResponse.send (C:\Users\kieran.corkin\Desktop\Projects\Mern Template Final\mern-cra-and-server\server\node_modules\express\lib\response.js:158:21)
at C:\Users\kieran.corkin\Desktop\Projects\Mern Template Final\mern-cra-and-server\server\routes\api\account.js:270:22
at C:\Users\kieran.corkin\Desktop\Projects\Mern Template Final\mern-cra-and-server\server\node_modules\mongoose\lib\model.js:4641:16
at process.nextTick (C:\Users\kieran.corkin\Desktop\Projects\Mern Template Final\mern-cra-and-server\server\node_modules\mongoose\lib\query.js:2624:28)
at _combinedTickCallback (internal/process/next_tick.js:131:7)
at process._tickCallback (internal/process/next_tick.js:180:9)
[nodemon] app crashed - waiting for file changes before starting...
Can anyone help me with this?
EDIT
I have changed my code, this seems to now work however I feel like its a little messy when put together. Any refactoring tips?
Code:
app.post("/api/account/update", (req, res) => {
// Preform checks on data that is passed through
const { body } = req;
var messages = {
ExistedUser:
"Error: There is already another account with that email address",
NameFormat: "Error: Names cannot contain special characters or numbers",
BlankInputs: "Error: You cannot submit nothing",
accountLoggedOut:
"Error: Session token is no longer valid, please login to recieve a new one",
successfullyUpdated: "Success: User was updated successfully"
};
var usersFound;
if (body.email) {
var email = body.email;
email = email.toLowerCase();
email = email.trim();
body.email = email;
User.find(
{
email: body.email
},
(err, UserCount) => {
usersFound = UserCount;
}
);
}
function capitalize(text) {
return text.replace(/\b\w/g, function(m) {
return m.toUpperCase();
});
}
if (body.firstName) {
body.firstName = capitalize(body.firstName);
}
if (body.lastName) {
body.lastName = capitalize(body.lastName);
}
//Making sure that all fields cannot be empty
if (!body.email && !body.firstName && !body.lastName) {
return res.send({
success: false,
message: messages.BlankInputs
});
}
// Getting User ID from the current session
UserSession.findById(body.tokenID, function(err, userData) {
// Finding User ID using the current users session token
if (userData.isDeleted) {
return res.end({
success: false,
message: messages.accountLoggedOut
});
}
if (userData) {
// Deleting the token ID from the body object as user table entry doesnt store tokens
delete body.tokenID;
// Finding the user profile and updating fields that are present
User.findByIdAndUpdate(userData.userId, body, function(err, userInfo) {
if (userInfo) {
if (!usersFound.length > 0) {
return res.send({
success: true,
message: messages.successfullyUpdated
});
} else {
return res.send({
success: false,
message: messages.ExistedUser
});
}
}
});
}
});
});
You're calling res.send() twice. res.send() ends the process. You ought to refactor such that you call res.write() and only call res.send() when you're done.
This StackOverflow link describes the difference in more detail. What is the difference between res.send and res.write in express?
I believe this is happening, as you're trying to send a response after the first / initial response has already been sent to the browser. For example:
checkName(body.firstName);
checkName(body.lastName);
Running this function twice is going to try and yield 2 different "response" messages.
The product of a single route, should ultimately be a single response.
Thanks for all your help on this issue.
Here is my final code that allowed it to work.
I have also tried to "refactor" it too. Let me know if you'd do something else.
app.post("/api/account/update", (req, res) => {
const { body } = req;
console.log(body, "Logged body");
// Defining objects to be used at the end of request
var updateUserInfo = {
userInfo: {},
sessionToken: body.tokenID
};
var hasErrors = {
errors: {}
};
// Checking that there is at least one value to update
if (!body.email && !body.firstName && !body.lastName) {
var blankError = {
success: false,
message: "Error: You cannot change your details to nothing"
};
hasErrors.errors = { ...hasErrors.errors, ...blankError };
} else {
console.log("Normal Body", body);
clean(body);
console.log("Cleaned Body", body);
updateUserInfo.userInfo = body;
delete updateUserInfo.userInfo.tokenID;
}
// Function to check if object is empty
function isEmpty(obj) {
if (Object.keys(obj).length === 0) {
return true;
} else {
return false;
}
}
// Function to remove objects from body if blank
function clean(obj) {
for (var propName in obj) {
if (obj[propName] === "" || obj[propName] === null) {
delete obj[propName];
}
}
}
// Checking and Formatting Names Given
function capitalize(text) {
return text.replace(/\b\w/g, function(m) {
return m.toUpperCase();
});
}
if (body.firstName) {
body.firstName = capitalize(body.firstName);
}
if (body.lastName) {
body.lastName = capitalize(body.lastName);
}
// Checking and formatting email
if (body.email) {
body.email = body.email.toLowerCase();
body.email = body.email.trim();
// Checking for email in database
User.find({ email: body.email }, (err, EmailsFound) => {
if (EmailsFound.length > 0) {
var EmailsFoundErr = {
success: false,
message: "There is already an account with that email address"
};
hasErrors.errors = { ...hasErrors.errors, ...EmailsFoundErr };
}
});
}
// Getting User Session Token
UserSession.findById(updateUserInfo.sessionToken, function(err, userData) {
// Finding User ID using the current users session token
if (userData.isDeleted) {
var userDeletedError = {
success: false,
message:
"Your account is currently logged out, you must login to change account details"
};
hasErrors.errors = { ...hasErrors.errors, ...userDeletedError };
} else {
// Finding the user profile and updating fields that are present
User.findByIdAndUpdate(
userData.userId,
updateUserInfo.userInfo,
function(err, userInfo) {
// userInfo varable contains user db entry
if (err) {
var updateUserError = {
success: false,
message: "Error: Server Error"
};
hasErrors.errors = {
...hasErrors.errors,
...updateUserError
};
}
if (isEmpty(hasErrors.errors)) {
res.send({
success: true,
message: "Success: You have updated your profile!"
});
} else {
res.send({
success: false,
message: hasErrors.errors
});
}
}
);
}
});
});
I have a simple login page built with React that uses the AWS Cognito API to authenticate the user. There are some authentication scenarios (password needs to be updated, need to enter an MFA code, etc.) that require me to get user input mid-execution of the authenticateUser workflow. I'm trying to find a way to get the user input dynamically without using the built-in prompt() method, especially when a user is entering a new password. Based on how the authenticateUser workflow is structured, I'm trying to get all user input within the workflow.
Perhaps I'm not thinking about this problem in the right way, but how can I have another React component dynamically render, get user input (new password, MFA code, etc), and then use that input within the authenticateUser workflow?
The main Login component has a form that upon clicking the Submit button triggers the following function:
handleSubmit = async (event) => {
event.preventDefault();
this.setState({ isLoading: true, loginError: null });
try {
await this.login(this.state.username, this.state.password);
this.props.userHasAuthenticated(true);
}
catch(e) {
//alert(e);
this.setState({ isLoading: false, loginError: e.toString() });
}
}
And then we have the login function that goes through the authenticateUser workflow:
login(username, password) {
const userPool = new CognitoUserPool({
UserPoolId: config.cognito.USER_POOL_ID,
ClientId: config.cognito.APP_CLIENT_ID
});
const authenticationData = {
Username: username,
Password: password
};
const user = new CognitoUser({ Username: username, Pool: userPool });
const authenticationDetails = new AuthenticationDetails(authenticationData);
return new Promise((resolve, reject) => (
user.authenticateUser(authenticationDetails, {
onSuccess: (result) => {
// User authentication was successful
resolve();
},
onFailure: (err) => {
var error = err.toString();
// If password expired
if (error.includes('Password reset required for the user')) {
var verificationCode = prompt('Password reset required. Please enter the verification code sent to your trusted device.' ,'');
var newPassword1 = '';
var newPassword2 = '';
while (newPassword1 !== newPassword2 || newPassword1.length < 8) {
newPassword1 = prompt('Please enter a new password.','');
newPassword2 = prompt('Please confirm your new password','');
}
user.confirmPassword(verificationCode, newPassword1, {
onSuccess: (result) => {
//Not sure if this handleSubmit does anything
//this.handleSubmit;
this.setState({ loginError: 'Password updated successfully! Please login with new password.', loginAlert: "success", updatePasswordUpdateAttribute: true });
return;
},
onFailure: (err) => {
this.setState({ loginError: err.toString() });
reject(err)
}
});
}
// User authentication was not successful
console.error(err);
reject(err)
},
mfaRequired: (codeDeliveryDetails) => {
// MFA is required to complete user authentication.
// Get the code from user and call
var verificationCode = prompt('Please enter the multi-factor code sent to your trusted device.' ,'');
user.sendMFACode(verificationCode, this);
},
newPasswordRequired: (userAttributes, requiredAttributes) => {
// User was signed up by an admin and must provide new
// password and required attributes, if any, to complete
// authentication.
// Get these details and call
// newPassword: password that user has given
// attributesData: object with key as attribute name and value that the user has given.
user.completeNewPasswordChallenge(newPassword1, null, {
onSuccess: (result) => {
this.updatePasswordAttribute(user);
resolve()
},
onFailure: (err) => {
this.setState({ loginError: err.toString() });
reject(err)
}
})
}
})
));
}
I'm currently making simple user-authentication app.
now I'm done with backend process with node js and passport.
what I've done was returning json response if authentication goes well or not.
router.post('/register', (req, res) => {
if(!utils.emailChecker(req.body.username)) {
return res.status(403).json({
error: "Invalid Username",
code: 403
});
}
if(!utils.passwordChecker(req.body.password)) {
return res.status(403).json({
error: "Invalid Password",
code: 403
});
}
//mysql query : variables must be inside "" or '';
let sql = `SELECT * FROM users WHERE username="${req.body.username}"`;
connection.query(sql, (err, result) => {
if(err) throw err;
if(utils.duplicateChecker(result)) {
return res.status(409).json({
error: "Username Exist",
code: 409
});
} else {
hasher({password: req.body.password}, (err, pass, salt, hash) => {
let user = {
authId: 'local: '+req.body.username,
username: req.body.username,
password: hash,
salt: salt,
displayName: req.body.displayName
};
let sql = 'INSERT INTO users SET ?';
connection.query(sql, user, (err, rows) => {
if(err) {
throw new Error("register error!");
} else {
req.login(user, (err) => {
req.session.save(() => {
return res.json({ success: true });
});
});
}
});
});
}
});
});
As you can see above, every time request makes error or goes perfect, json that contains error & code or success property is returned.
What I want to do is that getting these jsons via http service of angular2.
#Injectable()
export class UserAuthenticationService {
private loginUrl = "http://localhost:4200/auth/login";
private registerSuccessUrl = "http://localhost:4200/auth/register";
headers = new Headers({
'Content-Type': 'application/json'
});
constructor(private http: Http) { }
/*
body: {
username,
password,
}
*/
logIn(user: Object) {
return this.http
.post(this.registerSuccessUrl, JSON.stringify(user),
{ headers: this.headers });
}
What I've tried is this way. Make http post request using backend url.
and implement function on AuthComponent.
export class AuthComponent {
username: string = '';
password: string = '';
remembered: boolean = false;
submitted = false;
constructor(private userAuthenticationService: UserAuthenticationService) {}
onsubmit() {
this.userAuthenticationService.logIn({ username: this.username, password: this.password });
this.submitted = true;
}
}
But result is I just get json object on screen. { success: true }!
How can I get this json object thru http call and make use of 'success' property?
You are not using the server's response.
onsubmit() {
this.userAuthenticationService
.logIn({ username: this.username, password: this.password })
.subscribe(result => {
//here check result.success
}, error => console.error(error));
this.submitted = true;
}
The Http calls are asynchronous. Hence, using something like :
const data =this.userAuthenticationService.logIn({ username: this.username, password: this.password }); would not work. Rather subcribe to the response like this :
this.userAuthenticationService.logIn({ username: this.username, password: this.password }).subscribe(
data => {
this.submitted = data.success;
});
Here data is the response object from the server.
So in my app I obviously want to provide the means for users to reset their passwords. The issue I'm having though is that the new documentation for User Pools is pretty ambiguous on this topic. Here is what they tell you to do for a Forgot Password flow, and the link you may find it at:
cognitoUser.forgotPassword({
onSuccess: function (result) {
console.log('call result: ' + result);
},
onFailure: function(err) {
alert(err);
},
inputVerificationCode() {
var verificationCode = prompt('Please input verification code ' ,'');
var newPassword = prompt('Enter new password ' ,'');
cognitoUser.confirmPassword(verificationCode, newPassword, this);
}
});
http://docs.aws.amazon.com/cognito/latest/developerguide/using-amazon-cognito-user-identity-pools-javascript-examples.html
However when I drop this code into my project where a cognitoUser is defined and signed in, nothing seems to happen. I understand I need to somehow integrate this code with sending a verification code to the user, and asking them for a new password, but can't find anything on how to do this. Thoughts?
Thanks
AWS' docs are terrible on this topic (Cognito). You basically need to setup cognitoUser, then call forgotPassword
export function resetPassword(username) {
// const poolData = { UserPoolId: xxxx, ClientId: xxxx };
// userPool is const userPool = new AWSCognito.CognitoUserPool(poolData);
// setup cognitoUser first
cognitoUser = new AWSCognito.CognitoUser({
Username: username,
Pool: userPool
});
// call forgotPassword on cognitoUser
cognitoUser.forgotPassword({
onSuccess: function(result) {
console.log('call result: ' + result);
},
onFailure: function(err) {
alert(err);
},
inputVerificationCode() { // this is optional, and likely won't be implemented as in AWS's example (i.e, prompt to get info)
var verificationCode = prompt('Please input verification code ', '');
var newPassword = prompt('Enter new password ', '');
cognitoUser.confirmPassword(verificationCode, newPassword, this);
}
});
}
// confirmPassword can be separately built out as follows...
export function confirmPassword(username, verificationCode, newPassword) {
cognitoUser = new AWSCognito.CognitoUser({
Username: username,
Pool: userPool
});
return new Promise((resolve, reject) => {
cognitoUser.confirmPassword(verificationCode, newPassword, {
onFailure(err) {
reject(err);
},
onSuccess() {
resolve();
},
});
});
}
Resetting the password with forgot password flow has two steps:
Start the process by requesting for a verification code from the service. A code will be delivered to the user's phone/email.
Set the new password using the delivered verification code.
Use these two functions to perform the above steps and reset the password:
cognitoUser.forgotPassword(): This will start the forgot password process flow. The service generates a verification code and sends it to the user. The "data", returned through callback.inputVerificationCode(data), indicates where the verification code was sent.
cognitoUser.confirmPassword(): Use the delivered verification code with this function to set a new password.
I had this same issue. Was able to work through it by using confirmPassword() in the following way.
//validation of input from form
req.checkBody('email', 'Username is required').notEmpty();
req.checkBody('password', 'Password is required').notEmpty();
req.checkBody('confirmationcode', 'Confirmation Code is required').notEmpty();
var confirmationCode = req.body.confirmationcode;
var password = req.body.password;
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var userData = {
Username: req.body.email,
Pool: userPool
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.confirmPassword(confirmationCode, password, {
onFailure(err) {
console.log(err);
},
onSuccess() {
console.log("Success");
},
});
If as me, you find how to handle this case with amplify
import { Auth } from 'aws-amplify';
// Send confirmation code to user's email
Auth.forgotPassword(username)
.then(data => console.log(data))
.catch(err => console.log(err));
// Collect confirmation code and new password, then
Auth.forgotPasswordSubmit(username, code, new_password)
.then(data => console.log(data))
.catch(err => console.log(err));
See https://docs.amplify.aws/lib/auth/manageusers/q/platform/js#forgot-password
So Even I faced a same issue, Even in AWS cognito documentation it was not clear, basically the process involves two steps.
call cognitoUser.forgotPassword() this will start forgot password process flow, and the user will receive a verification code.
then call cognitoUser.confirmPassword() which will reset the password verifying the code send to the email of user.
Below I have given a cognitoUserClass(Typescript) which has static methods forgotPassword() and confirmPassword() methods which implements those two steps.
import * as AmazonCognitoIdentity from 'amazon-cognito-identity-js'
class cognitoUserClass {
static cognitouser: AmazonCognitoIdentity.CognitoUser
static userPool = new AmazonCognitoIdentity.CognitoUserPool({
UserPoolId: 'your pool id',
ClientId: 'your client id',
})
static forgotPassword(userName: string): void {
const userData = {
Username: userName,
Pool: cognitoUserClass.userPool,
}
cognitoUserClass.cognitouser = new AmazonCognitoIdentity.CognitoUser(
userData
)
cognitoUserClass.cognitouser.forgotPassword({
onSuccess: (data) => {
console.log(data)
},
onFailure: (err) => {
console.log('ERR:', err)
},
})
}
static confirmPassword(
verificationCode: string,
newPassword: string
): void {
cognitoUserClass.cognitouser.confirmPassword(
verificationCode,
newPassword,
{
onFailure(err) {
console.log(err)
},
onSuccess(data) {
console.log(data)
},
}
)
}
}
export { cognitoUserClass }
After you've got the verification code, using aws-amplify it's as easy as follows
import { Auth } from "aws-amplify";
Auth.forgotPasswordSubmit(email, verificationCode, newPassword)
.then(() => {
//redirect to sign-in page
})
.catch(error => {
//error logic
})