From 889125cdc471bcefe5ea9454a69a11007a0342d5 Mon Sep 17 00:00:00 2001 From: dougal83 Date: Thu, 6 Jun 2019 14:48:23 +0100 Subject: [PATCH] fix(loopback4-example-shopping): prevent duplicate user email Prevent creation of user with email value that belongs to an existing user fix #103 Signed-off-by: dougal83 --- .../acceptance/user.controller.acceptance.ts | 18 ++++++++++++++++++ .../src/controllers/user.controller.ts | 19 ++++++++++++++----- packages/shopping/src/models/user.model.ts | 15 ++++++++++++++- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/packages/shopping/src/__tests__/acceptance/user.controller.acceptance.ts b/packages/shopping/src/__tests__/acceptance/user.controller.acceptance.ts index 8240aab5c..490d6ab8a 100644 --- a/packages/shopping/src/__tests__/acceptance/user.controller.acceptance.ts +++ b/packages/shopping/src/__tests__/acceptance/user.controller.acceptance.ts @@ -38,6 +38,7 @@ describe('UserController', () => { before('setupApplication', async () => { ({app, client} = await setupApplication()); }); + before(migrateSchema); before(createPasswordHasher); before(givenAnExpiredToken); @@ -114,6 +115,19 @@ describe('UserController', () => { ); }); + it('throws error for POST /users with an existing email', async () => { + await client + .post('/users') + .send(user) + .expect(200); + const res = await client + .post('/users') + .send(user) + .expect(409); + + expect(res.body.error.message).to.equal('Email value is already taken'); + }); + it('returns a user with given id when GET /users/{id} is invoked', async () => { const newUser = await createAUser(); delete newUser.password; @@ -249,6 +263,10 @@ describe('UserController', () => { await userRepo.deleteAll(); } + async function migrateSchema() { + await app.migrateSchema(); + } + async function createAUser() { const encryptedPassword = await passwordHasher.hashPassword(user.password); const newUser = await userRepo.create( diff --git a/packages/shopping/src/controllers/user.controller.ts b/packages/shopping/src/controllers/user.controller.ts index 1715cb070..0e1c1deb7 100644 --- a/packages/shopping/src/controllers/user.controller.ts +++ b/packages/shopping/src/controllers/user.controller.ts @@ -5,7 +5,7 @@ import {repository} from '@loopback/repository'; import {validateCredentials} from '../services/validator'; -import {post, param, get, requestBody} from '@loopback/rest'; +import {post, param, get, requestBody, HttpErrors} from '@loopback/rest'; import {User, Product} from '../models'; import {UserRepository} from '../repositories'; import {RecommenderService} from '../services/recommender.service'; @@ -52,11 +52,20 @@ export class UserController { // encrypt the password user.password = await this.passwordHasher.hashPassword(user.password); - // create the new user - const savedUser = await this.userRepository.create(user); - delete savedUser.password; + try { + // create the new user + const savedUser = await this.userRepository.create(user); + delete savedUser.password; - return savedUser; + return savedUser; + } catch (error) { + // MongoError 11000 duplicate key + if (error.code === 11000 && error.errmsg.includes('index: uniqueEmail')) { + throw new HttpErrors.Conflict('Email value is already taken'); + } else { + throw error; + } + } } @get('/users/{userId}', { diff --git a/packages/shopping/src/models/user.model.ts b/packages/shopping/src/models/user.model.ts index 371e6b8d1..2235a0cf6 100644 --- a/packages/shopping/src/models/user.model.ts +++ b/packages/shopping/src/models/user.model.ts @@ -6,7 +6,20 @@ import {Entity, model, property, hasMany} from '@loopback/repository'; import {Order} from './order.model'; -@model() +@model({ + settings: { + indexes: { + uniqueEmail: { + keys: { + email: 1, + }, + options: { + unique: true, + }, + }, + }, + }, +}) export class User extends Entity { @property({ type: 'string',