How do you test express controllers that use mongoose models? - unit-testing

In my controller i have a function that creates a user but also checks to make sure that the user does not already exist and then a dashboard function which gets the user from the request and returns any petitions that have been created by that user.
I've looked at mocha, chai and sinon to carry out the tests along with various online resources but have no idea how to begin testing these two functions since they rely on models. Can anyone point me in the right direction to testing the controller or know of any resources which maybe able to help me?
Controller:
const bcrypt = require('bcryptjs');
const passport = require('passport');
const Users = require('../models/Users');
const Petitions = require('../models/Petitions');
const UserController = {
async register(req, res) {
const {name, email, password, passwordCon} = req.body;
let errors = []
// check required fields
if (!name || !email || !password || !passwordCon) {
errors.push({ msg: 'Please enter all fields' });
}
// check passwords match
if (password !== passwordCon) {
errors.push({ msg: 'Passwords do not match' });
}
// check password length
if (password.length < 6) {
errors.push({ msg: 'Password must be at least 6 characters' });
}
// if validation fails, render messages
if (errors.length > 0) {
res.render('user/register', {
errors,
name,
email,
password,
passwordCon
})
} else {
// validation passed
Users.findOne({email: email})
.then(user => {
if (user) {
// user exists
errors.push({msg: 'Email already in use'});
res.render('user/register', {
errors,
name,
email,
password,
passwordCon
});
} else {
const newUser = new Users({
name: name,
email: email,
password: password
});
// hash password
bcrypt.genSalt(10, (error, salt) =>
bcrypt.hash(newUser.password, salt, (error, hash) => {
if (error) throw error;
// set password to hashed
newUser.password = hash;
// save user
newUser.save()
.then(user => {
req.flash('success_msg', 'Registration Success');
res.redirect('/user/login');
})
.catch(error => console.log(error));
}))
}
});
}
},
async dashboard(req, res) {
const user = req.user;
const petitions = await Petitions.find({createdBy: user._id});
console.log('here');
res.render('user/dashboard', {
user: req.user,
petitions: petitions
})
}
};
module.exports = UserController;
Models:
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
createdOn: {
type: Date,
default: Date.now
},
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
petitions: [
{ type: mongoose.Schema.Types.ObjectId, ref: 'Petitions' }
]
})
const Users = mongoose.model('Users', UserSchema);
module.exports = Users;
const mongoose = require('mongoose');
const PetitionSchema = new mongoose.Schema({
createdOn: {
type: Date,
default: Date.now
},
title: {
type: String,
required: true
},
signaturesNeeded: {
type: String,
required: true
},
description: {
type: String,
required: true
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Users'
},
signatures: [
{ type: mongoose.Schema.Types.ObjectId, ref: 'Users' }
]
})
const Petitions = mongoose.model('Petitions', PetitionSchema);
module.exports = Petitions;

Related

Stubbing other repository calls in current repository test

I am trying to write test cases for an Node.js backend project
The database is using pg-promise. I run into issue when trying to stub the repository and it tries to call other repositories.
Here is the repository file user.js
async findByRefToken(refToken) {
return await this.db.oneOrNone(`SELECT id FROM useraccount WHERE username = $1`, refToken);
}
async createNormal(data) {
// generate a verify email token
const verifyToken = await this.generateToken();
// create unique ref code
const tokenid = nanoid(Number(env.REF_TOKEN_LENGTH));
const refToken = await this.generateUniqueRefToken(tokenid);
// find the referral's id from the ref token
let referredBy = null;
if(data.referred) {
referredBy = await this.findByRefToken(data.referred);
}
const meta = {
dob: data.dob,
gender: data.gender,
country: data.country,
phone: data.phone,
email_verified: false,
verify_token: verifyToken,
ref_token: refToken,
referred: referredBy ? referredBy.id : null
};
const verificationMeta = {
kyc_verified: false
};
const normalUser = {
id: data.userId,
first_name: data.firstName.trim(),
last_name: data.lastName.trim(),
active: true,
email: data.email.trim().toLowerCase(),
username: data.username.trim(),
role: 'Normal',
password: bcrypt.hashSync(data.password, Number(env.SALT_ROUNDS)),
meta: meta,
verification_meta: verificationMeta
};
const newUser = await this.db.one(CREATE_NORMAL_USER, normalUser);
return {
id: newUser.id,
firstName: data.firstName,
email: data.email,
verifyToken: verifyToken,
refToken: refToken,
referredBy: referredBy
};
}
I am trying to test the createNormal function, but it would also call other repo function like findByRefToken and await this.db.one(CREATE_NORMAL_USER, normalUser); Is there anyway to stub them away?
And here is the test written user.test.js
const chai = require("chai");
const sinon = require("sinon");
const expect = chai.expect;
const {faker} = require("#faker-js/faker");
const UserRepository = require("../../repos/user");
describe("UserRepository", function() {
const stubValue = {
dob: faker.date.birthdate(),
gender: faker.name.gender(),
country: faker.address.country(),
phone: faker.phone.number(),
email_verified: false,
id: faker.datatype.uuid(),
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
username: faker.name.fullName(),
password: faker.random.alphaNumeric(5),
email: faker.internet.email(),
verifyToken: faker.random.alphaNumeric(12),
refToken: faker.random.alphaNumeric(12),
referredBy: faker.random.alphaNumeric(12)
};
describe("create", function () {
it("should add a new user to the db", async function () {
// const stub = sinon.stub(UserRepository, "createNormal").resolves(stubValue.refToken);
const userRepository = new UserRepository();
const user = await userRepository.createNormal(stubValue);
expect(user.id).to.equal(stubValue.id);
expect(user.name).to.equal(stubValue.name);
expect(user.phone).to.equal(stubValue.phone);
expect(user.id).to.equal(stubValue.id);
expect(user.verifyToken).to.equal(stubValue.verifyToken);
});
});
});
Thank you for responding
The method creates and saves a new user in the database. What is left to test if you stub the database calls?
My recommendation would be to not mock; set up the appropriate structures in the database and use them. You can use factory-bot to make this simpler.
Note: it's strange that it doesn't return a user.
I'd extract making the user from saving the user. I'd also put all the token and referral stuff into their own methods.
async newNormal(data) {
const verifyToken = await this.generateToken();
// create unique ref code
const tokenid = this.generateTokenId();
const refToken = await this.generateUniqueRefToken(tokenid);
const referredBy = await this.findByRefToken(data.referred);
const meta = {
dob: data.dob,
gender: data.gender,
country: data.country,
phone: data.phone,
email_verified: false,
verify_token: verifyToken,
ref_token: refToken,
referred: referredBy ? referredBy.id : null
};
return {
id: data.userId,
first_name: data.firstName.trim(),
last_name: data.lastName.trim(),
active: true,
email: data.email.trim().toLowerCase(),
username: data.username.trim(),
role: 'Normal',
password: bcrypt.hashSync(data.password, Number(env.SALT_ROUNDS)),
meta: meta,
verification_meta: {
kyc_verified: false
};
};
}
async createNormal(data) {
const userData = await this.newNormal(data);
return await this.db.one(CREATE_NORMAL_USER, userData);
}
Now createNormal is an integration method. All it does is call newNormal and pass the result through to a SQL query. You can test it by mocking newNormal and db.one.
The important work is happening in newNormal, focus testing on that. You can mock generateToken, generateTokenId, generateUniqueRefToken and findByRefToken. But, again, this test would be easier and more realistic by just setting up the necessary data.

Calling RDS data service executeStatement throws "Parameter X has value with no field set"

Not sure if I clearly understood how to provide parameter values when executing rds.executeStatement command.
When I execute the below code I get this error thrown -
{
"errorType": "BadRequestException",
"errorMessage": "Parameter \"userId\" has value with no field set",
"code": "BadRequestException",
"message": "Parameter \"userId\" has value with no field set"
}
Here is my code, How am I supposed to provide the userId and givenName values to the parameters array here.
const AWS = require('aws-sdk');
var RDS = new AWS.RDSDataService({
apiVersion: '2018-08-01'
});
exports.handler = async (event, context) => {
var userId;
var givenName;
var params = {
secretArn: 'secretArn',
resourceArn: 'resourceArn',
database: 'db',
parameters: [{
name: "userId",
value: {
"stringValue": userId
}
},
{
name: "givenName",
value: {
"stringValue": givenName
}
}
]
};
event.Records.forEach(function(record) {
if (record.eventName == 'INSERT') {
userId = record.dynamodb.NewImage.pk.S;
givenName = record.dynamodb.NewImage.sk.S;
params.sql = `INSERT INTO Users (UserId, GivenName) VALUES(:userId, :givenName);`
}
});
await RDS.executeStatement(params).promise();
console.log(params.parameters[0].value);
return 'done';
};
UPDATE March 13th
Attaching the cloudwatch logs printing out userId and givenName -
UPDATE March 16th - Function Updates
const AWS = require('aws-sdk');
const RDS = new AWS.RDSDataService({ apiVersion: '2018-08-01' });
exports.handler = async (event, context) => {
var userId;
var givenName;
var count = 0;
var params = {
secretArn: 'secretArn',
resourceArn: 'resourceArn',
database: 'bol_db',
parameters: [{
name: "userId",
value: {
"stringValue": userId
}
},
{
name: "givenName",
value: {
"stringValue": givenName
}
}
]
};
const promises = event.Records.map(async function(record) {
count++;
context.callbackWaitsForEmptyEventLoop = false;
if (record.eventName == 'INSERT') {
userId = record.dynamodb.NewImage.pk.S;
givenName = record.dynamodb.NewImage.sk.S;
console.log('userId - ' + userId);
console.log('givenName - ' + givenName);
console.log('Params -'+params.parameters);
params.sql = "INSERT INTO Users (UserId, GivenName) VALUES(:userId, :givenName);"
let result = await RDS.executeStatement(params).promise();
console.log('Result -' + result);
}
});
await Promise.all(promises);
console.log(count);
return 'done';
};
It seems that you're setting "stringValue": userId before userId has a value. In JavaScript you can't assign userId later and expect it to be propagated to all places that you used it before.
You should try with var params = {...} inside the .map function or, alternatively, inside the .map function you can loop through the parameter list and if you find the correct one, assign the value then.

AWS Cognito: Best practice to handle same user (with same email address) signing in from different identity providers (Google, Facebook)

When signing in a user with the same email address through the Google and Facebook identity providers, AWS Cognito creates multiple entries in the user pool, one entry per identity provider used:
I have used the example code provided in this tutorial to set up AWS Cognito: The Complete Guide to User Authentication with the Amplify Framework
How can I create just one user instead of multiple users?
Is it possible to have AWS Cognito automatically combine (federate) the entries
from multiple providers into one entry or should AWS Lambda functions be used to accomplish this?
Yes. You can do it by using AdminLinkProviderForUser https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminLinkProviderForUser.html
The idea is:
In PreSignUp lambda hook, we Link Provider to User if User already signed up. E.g:
import CognitoIdentityServiceProvider from 'aws-sdk/clients/cognitoidentityserviceprovider'
const cognitoIdp = new CognitoIdentityServiceProvider()
const getUserByEmail = async (userPoolId, email) => {
const params = {
UserPoolId: userPoolId,
Filter: `email = "${email}"`
}
return cognitoIdp.listUsers(params).promise()
}
const linkProviderToUser = async (username, userPoolId, providerName, providerUserId) => {
const params = {
DestinationUser: {
ProviderAttributeValue: username,
ProviderName: 'Cognito'
},
SourceUser: {
ProviderAttributeName: 'Cognito_Subject',
ProviderAttributeValue: providerUserId,
ProviderName: providerName
},
UserPoolId: userPoolId
}
const result = await (new Promise((resolve, reject) => {
cognitoIdp.adminLinkProviderForUser(params, (err, data) => {
if (err) {
reject(err)
return
}
resolve(data)
})
}))
return result
}
exports.handler = async (event, context, callback) => {
if (event.triggerSource === 'PreSignUp_ExternalProvider') {
const userRs = await getUserByEmail(event.userPoolId, event.request.userAttributes.email)
if (userRs && userRs.Users.length > 0) {
const [ providerName, providerUserId ] = event.userName.split('_') // event userName example: "Facebook_12324325436"
await linkProviderToUser(userRs.Users[0].Username, event.userPoolId, providerName, providerUserId)
} else {
console.log('user not found, skip.')
}
}
return callback(null, event)
}
Then when user use OAuth with Facebook/Google with User Pool, the Pool will return this User linked.
Note: You may see 2 records in User Pool UI, but when access User record detail, They already merged.
I have been fiddling around with the same issue for a bit. Accepted answer sort of works but does not cover all scenarios. The main one is that once the user signs up with the external login, they will never be able to sign up with a username and password. Currently, Cognito does not allow linking Cognito users to external users.
My scenarios are as follows:
Scenarios
When the user signs up with a username password and signs up with an external provider, link them.
When the user signs up with an external provider allow them to signup with a username and password.
Have a common username between all linked users to use it as a unique id in other services.
My proposed solution is to always create the Cognito user first and link all external users to it.
Proposed solution
user signs up with username/password first then with an external user. No dramas, just link the external user with the Cognito user.
user signs up with external user first then wants to sign up with username/password. In this scenario, create a Cognito user first then link the external user to this new Cognito user. If the user tries to signup with a username/password in the future, they will get a user already exists error. In this case, they can use the forgot password flow to recover then log in.
const {
CognitoIdentityServiceProvider
} = require('aws-sdk');
const handler = async event => {
const userPoolId = event.userPoolId;
const trigger = event.triggerSource;
const email = event.request.userAttributes.email;
const givenName = event.request.userAttributes.given_name;
const familyName = event.request.userAttributes.family_name;
const emailVerified = event.request.userAttributes.email_verified;
const identity = event.userName;
const client = new CognitoIdentityServiceProvider();
if (trigger === 'PreSignUp_ExternalProvider') {
await client.listUsers({
UserPoolId: userPoolId,
AttributesToGet: ['email', 'family_name', 'given_name'],
Filter: `email = "${email}"`
})
.promise()
.then(({
Users
}) => Users.sort((a, b) => (a.UserCreateDate > b.UserCreateDate ? 1 : -1)))
.then(users => users.length > 0 ? users[0] : null)
.then(async user => {
// user with username password already exists, do nothing
if (user) {
return user;
}
// user with username password does not exists, create one
const newUser = await client.adminCreateUser({
UserPoolId: userPoolId,
Username: email,
MessageAction: 'SUPPRESS', // dont send email to user
UserAttributes: [{
Name: 'given_name',
Value: givenName
},
{
Name: 'family_name',
Value: familyName
},
{
Name: 'email',
Value: email
},
{
Name: 'email_verified',
Value: emailVerified
}
]
})
.promise();
// gotta set the password, else user wont be able to reset it
await client.adminSetUserPassword({
UserPoolId: userPoolId,
Username: newUser.Username,
Password: '<generate random password>',
Permanent: true
}).promise();
return newUser.Username;
}).then(username => {
// link external user to cognito user
const split = identity.split('_');
const providerValue = split.length > 1 ? split[1] : null;
const provider = ['Google', 'Facebook'].find(
val => split[0].toUpperCase() === val.toUpperCase()
);
if (!provider || !providerValue) {
return Promise.reject(new Error('Invalid external user'));
}
return client.adminLinkProviderForUser({
UserPoolId: userPoolId,
DestinationUser: {
ProviderName: 'Cognito',
ProviderAttributeValue: username
},
SourceUser: {
ProviderName: provider,
ProviderAttributeName: 'Cognito_Subject',
ProviderAttributeValue: providerValue
}
})
.promise()
});
}
return event;
};
module.exports = {
handler
};
The solution I created handles, I think, all cases. It also tackles some common issues with Cognito.
If the user is signing up with an external provider, link them to any existing account, including Cognito (username/password) or external provider account.
When linking to existing accounts, link only to the oldest account. This is important is you have more than 2 login options.
If the user is signing up with Cognito (username/password), if an external provider already exists, reject the signup with a custom error message (because the accounts cannot be linked).
Note that when linking accounts, the Cognito pre-signup trigger returns an "Already found an entry for username" error. Your client should handle this and reattempt authentication, or ask the user to sign in again. More info on this here:
Cognito auth flow fails with "Already found an entry for username Facebook_10155611263153532"
Here is my lambda, executed on the Cognito pre-signup trigger
const AWS = require("aws-sdk");
const cognito = new AWS.CognitoIdentityServiceProvider();
exports.handler = (event, context, callback) => {
function checkForExistingUsers(event, linkToExistingUser) {
console.log("Executing checkForExistingUsers");
var params = {
UserPoolId: event.userPoolId,
AttributesToGet: ['sub', 'email'],
Filter: "email = \"" + event.request.userAttributes.email + "\""
};
return new Promise((resolve, reject) =>
cognito.listUsers(params, (err, result) => {
if (err) {
reject(err);
return;
}
if (result && result.Users && result.Users[0] && result.Users[0].Username && linkToExistingUser) {
console.log("Found existing users: ", result.Users);
if (result.Users.length > 1){
result.Users.sort((a, b) => (a.UserCreateDate > b.UserCreateDate) ? 1 : -1);
console.log("Found more than one existing users. Ordered by createdDate: ", result.Users);
}
linkUser(result.Users[0].Username, event).then(result => {
resolve(result);
})
.catch(error => {
reject(err);
return;
});
} else {
resolve(result);
}
})
);
}
function linkUser(sub, event) {
console.log("Linking user accounts with target sub: " + sub + "and event: ", event);
//By default, assume the existing account is a Cognito username/password
var destinationProvider = "Cognito";
var destinationSub = sub;
//If the existing user is in fact an external user (Xero etc), override the the provider
if (sub.includes("_")) {
destinationProvider = sub.split("_")[0];
destinationSub = sub.split("_")[1];
}
var params = {
DestinationUser: {
ProviderAttributeValue: destinationSub,
ProviderName: destinationProvider
},
SourceUser: {
ProviderAttributeName: 'Cognito_Subject',
ProviderAttributeValue: event.userName.split("_")[1],
ProviderName: event.userName.split("_")[0]
},
UserPoolId: event.userPoolId
};
console.log("Parameters for adminLinkProviderForUser: ", params);
return new Promise((resolve, reject) =>
cognito.adminLinkProviderForUser(params, (err, result) => {
if (err) {
console.log("Error encountered whilst linking users: ", err);
reject(err);
return;
}
console.log("Successfully linked users.");
resolve(result);
})
);
}
console.log(JSON.stringify(event));
if (event.triggerSource == "PreSignUp_SignUp" || event.triggerSource == "PreSignUp_AdminCreateUser") {
checkForExistingUsers(event, false).then(result => {
if (result != null && result.Users != null && result.Users[0] != null) {
console.log("Found at least one existing account with that email address: ", result);
console.log("Rejecting sign-up");
//prevent sign-up
callback("An external provider account alreadys exists for that email address", null);
} else {
//proceed with sign-up
callback(null, event);
}
})
.catch(error => {
console.log("Error checking for existing users: ", error);
//proceed with sign-up
callback(null, event);
});
}
if (event.triggerSource == "PreSignUp_ExternalProvider") {
checkForExistingUsers(event, true).then(result => {
console.log("Completed looking up users and linking them: ", result);
callback(null, event);
})
.catch(error => {
console.log("Error checking for existing users: ", error);
//proceed with sign-up
callback(null, event);
});
}
};
If you want to allow the user to continue login with email & password ("Option 1: User Signs Up with Username and Signs In with Username or Alias)") besides identity provider (google, facebook, etc) then the accepted solution won't be enough as Cognito can only have one email as verified.
I solve this by adding a Post Confirmation trigger which automatically verify user email if needed:
const AWS = require('aws-sdk');
const cognitoIdp = new AWS.CognitoIdentityServiceProvider();
const markUserEmailAsVerified = async (username, userPoolId) => {
console.log('marking email as verified for user with username: ' + username);
const params = {
UserAttributes: [
{
Name: 'email_verified',
Value: 'true'
}
// other user attributes like phone_number or email themselves, etc
],
UserPoolId: userPoolId,
Username: username
};
const result = await new Promise((resolve, reject) => {
cognitoIdp.adminUpdateUserAttributes(params, (err, data) => {
if (err) {
console.log(
'Failed to mark user email as verified with error:\n' +
err +
'\n. Manual action is required to mark user email as verified otherwise he/she cannot login with email & password'
);
reject(err);
return;
}
resolve(data);
});
});
return result;
};
exports.handler = async (event, context, callback) => {
console.log('event data:\n' + JSON.stringify(event));
const isEmailVerified = event.request.userAttributes.email_verified;
if (isEmailVerified === 'false') {
await markUserEmailAsVerified(event.userName, event.userPoolId);
}
return callback(null, event);
};
Note: This doesn't seem standard development or common requirement so take as it.
In aws-sdk-js-v3 I'm using #subash approach. I find that when you make an error callback, no extra user is created. Just the one that you create with your email.
const {
CognitoIdentityProviderClient,
ListUsersCommand,
AdminCreateUserCommand,
AdminLinkProviderForUserCommand,
AdminSetUserPasswordCommand,
} = require('#aws-sdk/client-cognito-identity-provider')
const client = new CognitoIdentityProviderClient({
region: process.env.REGION,
})
const crypto = require("crypto")
exports.handler = async(event, context, callback) => {
try {
const {
triggerSource,
userPoolId,
userName,
request: {
userAttributes: { email, name }
}
} = event
if (triggerSource === 'PreSignUp_ExternalProvider') {
const listParam = {
UserPoolId: userPoolId,
Filter: `email = "${email}"`,
}
const listData = await client.send(new ListUsersCommand(listParam))
let [providerName, providerUserId] = userName.split('_')
providerName = providerName.charAt(0).toUpperCase() + providerName.slice(1)
let linkParam = {
SourceUser: {
ProviderAttributeName: 'Cognito_Subject',
ProviderAttributeValue: providerUserId,
ProviderName: providerName,
},
UserPoolId: userPoolId,
}
if (listData && listData.Users.length > 0) {
linkParam['DestinationUser'] = {
ProviderAttributeValue: listData.Users[0].Username,
ProviderName: 'Cognito',
}
}
else {
const createParam = {
UserPoolId: userPoolId,
Username: email,
MessageAction: 'SUPPRESS',
UserAttributes: [{
//optional name attribute.
Name: 'name',
Value: name,
}, {
Name: 'email',
Value: email,
}, {
Name: 'email_verified',
Value: 'true',
}],
}
const createData = await client.send(new AdminCreateUserCommand(createParam))
const pwParam = {
UserPoolId: userPoolId,
Username: createData.User.Username,
Password: crypto.randomBytes(40).toString('hex'),
Permanent: true,
}
await client.send(new AdminSetUserPasswordCommand(pwParam))
linkParam['DestinationUser'] = {
ProviderAttributeValue: createData.User.Username,
ProviderName: 'Cognito',
}
}
await client.send(new AdminLinkProviderForUserCommand(linkParam))
//throw error to prevent additional user creation
callback(Error('Social account was set, retry to sign in.'), null)
}
else {
callback(null, event)
}
}
catch (err) {
console.error(err)
}
}
However, it is a bad UX as the first sign in with federated identity will only create the user but not allowing it to authenticate. However, the subsequent sign in with federated identity will show no such issue. Let me know, if you get any other solution for that first sign in.
It's also useful to keep email_verified as true so that user can recover their password. Especially true if you are using aws-amplify authenticator. This should be in your post authentication trigger.
const {
CognitoIdentityProviderClient,
AdminUpdateUserAttributesCommand,
} = require('#aws-sdk/client-cognito-identity-provider')
const client = new CognitoIdentityProviderClient({
region: process.env.REGION,
})
exports.handler = async(event, context, callback) => {
try {
const {
userPoolId,
userName,
request: {
userAttributes: { email_verified }
}
} = event
if (!email_verified) {
const param = {
UserPoolId: userPoolId,
Username: userName,
UserAttributes: [{
Name: 'email_verified',
Value: 'true',
}],
}
await client.send(new AdminUpdateUserAttributesCommand(param))
}
callback(null, event)
}
catch (err) {
console.error(err)
}
}

Verify account endpoint in loopback

I am trying to implement verify account endpoint in loopback4 thorugh mailgun, since I am new in loopback4 and typescript in general, I am not sure if I am doing the right way. I want to retype the following code in the picture for loopback.I have already done with saving active flag in database and generating secretToken at signing up.
My code in loopback
#post('/users/verify')
async verify(
#requestBody(VerifyRequestBody) userData: User
): Promise<{ userData: object }> {
try {
const foundUser = await this.userRepository.findOne({
where: {
secretToken: userData.secretToken,
}
})
if (!foundUser) {
throw new HttpErrors.Forbidden(`No user found`)
}
foundUser.active = true;
foundUser.secretToken = ''
const savedUser = await this.userRepository.create(foundUser);
} catch (err) {
return err
}
}
User model, I am using MongoDB
import { Entity, model, property } from '#loopback/repository';
#model({ settings: {} })
export class User extends Entity {
#property({
type: 'string',
id: true,
})
id: string;
#property({
type: 'string',
required: true,
})
email: string;
#property({
type: 'string',
required: true,
})
password: string;
#property({
type: 'string',
required: true,
})
firstName: string;
#property({
type: 'string',
required: true,
})
lastName: string;
#property({
type: 'string',
required: false
})
secretToken: string;
#property({
type: 'boolean',
required: false,
default: false
})
active: boolean;
#property.array(String)
permissions: String[]
constructor(data?: Partial<User>) {
super(data);
}
}
export interface UserRelations {
// describe navigational properties here
}
export type UserWithRelations = User & UserRelations;
User repository
import { DefaultCrudRepository } from '#loopback/repository';
import { User, UserRelations } from '../models';
import { MongoDsDataSource } from '../datasources';
import { inject } from '#loopback/core';
export type Credentials = {
email: string,
password: string,
active: boolean
}
export type Verify = {
secretToken: string
}
export class UserRepository extends DefaultCrudRepository<
User,
typeof User.prototype.id,
UserRelations
> {
constructor(#inject('datasources.mongoDS') dataSource: MongoDsDataSource) {
super(User, dataSource);
}
}

Check AWS Cognito to make sure the email does not exist

Hey everyone so I have the following route below. It works great however I want to be able to check AWS Cognito to make sure the users email does not already exist before I insert them into the RDS database. Is there a simple way to do this? Currently I just insert them and throw an error when they try to login to the site but I really hate that user flow and am trying to make it better. I am using nodejs and the AWS SDK.
router.post("/signup", async (req, res, next) => {
try {
const { username, password, plan, isDisplayOwner, optIn } = req.body;
const bannerToken = null;
const userAttributes = [
{
Name: "given_name",
Value: req.body.firstName
},
{
Name: "family_name",
Value: req.body.lastName
},
{
Name: "email",
Value: req.body.email
},
{
Name: "phone_number",
Value: req.body.phone
},
{
Name: "custom:company",
Value: req.body.company
}
];
await cognito
.signUp({
ClientId: process.env.AWS_COGNITO_CLIENT_ID,
Username: username,
Password: password,
UserAttributes: userAttributes
})
.promise();
const dbPlanName = plan !== "FREE" ? "Braintree" : plan;
const [[{ insertId }]] = await database.query(
"CALL insertUser (?, ?, ?, ?)",
[username, dbPlanName, isDisplayOwner, bannerToken]
);
const paramsId = {
UserAttributes: [
{
Name: "custom:CE_user_id",
Value: insertId.toString()
}
],
UserPoolId: process.env.AWS_COGNITO_USER_POOL_ID,
Username: username
};
await cognito.adminUpdateUserAttributes(paramsId).promise();
if (dbPlanName === "Braintree") {
await database.query(
"INSERT INTO pending_subscriptions (user_id, plan) VALUES(?,?)",
[insertId, plan]
);
}
if (optIn) {
await database.query(
"INSERT INTO new_email_list_opt_ins (user_id) VALUES(?)",
insertId
);
}
res.send("Signup completed. Confirm account.");
} catch (error) {
error.status = 409;
next(error);
}
});
Hey Everyone here was my solution to search to see if the user exists in cognito. You simply use the listUsers function from the AWS SDK. Note it does not return a boolean but you can just do emailExists.Users.length and if the length is greater than 0 the email already exists.
const emailParams =
{
AttributesToGet: [],
Filter: 'email = "' + req.body.email + '"',
UserPoolId: process.env.AWS_COGNITO_USER_POOL_ID
};
const emailExists = await cognito.listUsers(emailParams).promise()
if(emailExists.Users.length>0){
//user exists
throw "user exists"
} else {
// user does not exist
}