From d74ca341313910e46fa50ac079804cd6e82da48e Mon Sep 17 00:00:00 2001 From: austin047 Date: Sat, 30 Mar 2019 03:28:29 +0100 Subject: [PATCH 1/5] feat: addition of current user authentication in the request to create a new order Updated the create order method that handles post request to create a user order to authenticate against the current user before an order can be created, the client will have to pass the authentication token in the request header which will be authenticated before the order is created BREAKING CHANGE: For an order to be created the client must pass the authentication token in the request header, otherwise a 401 Unauthorized error will be generated feat #43 Signed-off-by: austin047 --- .../user-order.controller.acceptance.ts | 137 ++++++++++++++---- .../src/controllers/user-order.controller.ts | 12 ++ 2 files changed, 118 insertions(+), 31 deletions(-) diff --git a/packages/shopping/src/__tests__/acceptance/user-order.controller.acceptance.ts b/packages/shopping/src/__tests__/acceptance/user-order.controller.acceptance.ts index 36ca6a7e2..b40edfbba 100644 --- a/packages/shopping/src/__tests__/acceptance/user-order.controller.acceptance.ts +++ b/packages/shopping/src/__tests__/acceptance/user-order.controller.acceptance.ts @@ -9,6 +9,11 @@ import {OrderRepository, UserRepository} from '../../repositories'; import {MongoDataSource} from '../../datasources'; import {User, Order} from '../../models'; import {setupApplication} from './helper'; +import {JWTAuthenticationService} from '../../src/services/JWT.authentication.service'; +import { + PasswordHasherBindings, + JWTAuthenticationBindings, +} from '../../src/keys'; describe('UserOrderController acceptance tests', () => { let app: ShoppingApplication; @@ -26,42 +31,113 @@ describe('UserOrderController acceptance tests', () => { await app.stop(); }); - it('creates an order for a user with a given orderId', async () => { - const user = await givenAUser(); - const userId = user.id.toString(); - const order = givenAOrder({userId: userId, orderId: '1'}); + describe('Creating new orders for authenticated users', () => { + let plainPassword: string; + let jwtAuthService: JWTAuthenticationService; - await client - .post(`/users/${userId}/orders`) - .send(order) - .expect(200, order); - }); + const user = { + email: 'loopback@example.com', + password: 'p4ssw0rd', + firstname: 'Example', + surname: 'User', + }; - it('creates an order for a user without a given orderId', async () => { - const user = await givenAUser(); - const userId = user.id.toString(); - const order = givenAOrder({userId: userId}); + before('create new user for user orders', async () => { + app.bind(PasswordHasherBindings.ROUNDS).to(4); - const res = await client - .post(`/users/${userId}/orders`) - .send(order) - .expect(200); + const passwordHasher = await app.get( + PasswordHasherBindings.PASSWORD_HASHER, + ); + plainPassword = user.password; + user.password = await passwordHasher.hashPassword(user.password); + jwtAuthService = await app.get(JWTAuthenticationBindings.SERVICE); + }); - expect(res.body.orderId).to.be.a.String(); - delete res.body.orderId; - expect(res.body).to.deepEqual(order); - }); + it('creates an order for a user with a given orderId', async () => { + const newUser = await userRepo.create(user); + const userId = newUser.id.toString(); + const order = givenAOrder({userId: userId, orderId: '1'}); + + const token = await jwtAuthService.getAccessTokenForUser({ + email: newUser.email, + password: plainPassword, + }); + + await client + .post(`/users/${userId}/orders`) + .send(order) + .set('Authorization', 'Bearer ' + token) + .expect(200, order); + }); + + it('creates an order for a user without a given orderId', async () => { + const newUser = await userRepo.create(user); + const userId = newUser.id.toString(); + + const token = await jwtAuthService.getAccessTokenForUser({ + email: newUser.email, + password: plainPassword, + }); + + const order = givenAOrder({userId: userId}); + + const res = await client + .post(`/users/${userId}/orders`) + .send(order) + .set('Authorization', 'Bearer ' + token) + .expect(200); - it('throws an error when a userId in path does not match body', async () => { - const user = await givenAUser(); - const userId = user.id.toString(); - const order = givenAOrder({userId: 'hello'}); + expect(res.body.orderId).to.be.a.String(); + delete res.body.orderId; + expect(res.body).to.deepEqual(order); + }); + + it('throws an error when a userId in path does not match body', async () => { + const newUser = await userRepo.create(user); + const userId = newUser.id.toString(); + + const token = await jwtAuthService.getAccessTokenForUser({ + email: newUser.email, + password: plainPassword, + }); + + const order = givenAOrder({userId: 'hello'}); + + await client + .post(`/users/${userId}/orders`) + .set('Content-Type', 'application/json') + .set('Authorization', 'Bearer ' + token) + .send(order) + .expect(400); + }); + + it('throws an error when a user is not authenticated', async () => { + const newUser = await userRepo.create(user); + const userId = newUser.id.toString(); + + const order = givenAOrder({userId: userId}); - await client - .post(`/users/${userId}/orders`) - .set('Content-Type', 'application/json') - .send(order) - .expect(400); + await client + .post(`/users/${userId}/orders`) + .send(order) + .expect(401); + }); + + it('throws an error when a user with wrong token is provided', async () => { + const newUser = await userRepo.create(user); + const userId = newUser.id.toString(); + + const order = givenAOrder({userId: userId}); + + await client + .post(`/users/${userId}/orders`) + .send(order) + .set( + 'Authorization', + 'Bearer ' + 'Wrong token - IjoidGVzdEBsb29wYmFjay5p', + ) + .expect(401); + }); }); // TODO(virkt25): Implement after issue below is fixed. @@ -128,7 +204,6 @@ describe('UserOrderController acceptance tests', () => { firstname: 'Example', surname: 'User', }; - return await userRepo.create(user); } diff --git a/packages/shopping/src/controllers/user-order.controller.ts b/packages/shopping/src/controllers/user-order.controller.ts index 16f3354b8..8516e70df 100644 --- a/packages/shopping/src/controllers/user-order.controller.ts +++ b/packages/shopping/src/controllers/user-order.controller.ts @@ -15,6 +15,8 @@ import { HttpErrors, } from '@loopback/rest'; import {Order} from '../models'; +import {authenticate, UserProfile} from '@loopback/authentication'; +import {inject} from '@loopback/core'; /** * Controller for User's Orders @@ -35,10 +37,20 @@ export class UserOrderController { }, }, }) + @authenticate('jwt') async createOrder( @param.path.string('userId') userId: string, + @inject('authentication.currentUser') currentUser: UserProfile, @requestBody() order: Order, ): Promise { + if (currentUser.id !== userId) { + throw new HttpErrors.BadRequest( + `User id does not match looged in user: ${userId} !== ${ + currentUser.id + }`, + ); + } + if (userId !== order.userId) { throw new HttpErrors.BadRequest( `User id does not match: ${userId} !== ${order.userId}`, From 0d663c8bcade3b732f8cd0f9d8b02f61ab76b637 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 5 Apr 2019 10:39:08 -0700 Subject: [PATCH 2/5] chore: upgrade all deps Signed-off-by: Raymond Feng --- package-lock.json | 98 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index b76bac9c0..10456d4e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2997,6 +2997,12 @@ "requires": { "readable-stream": "2 || 3" } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true } } }, @@ -7873,12 +7879,6 @@ "wordwrap": "~0.0.2" } }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, "os-locale": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", @@ -8418,6 +8418,16 @@ } } }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -9064,6 +9074,21 @@ "supports-color": "^5.5.0" } }, + "sinon": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.3.1.tgz", + "integrity": "sha512-eQKMaeWovtOtYe2xThEvaHmmxf870Di+bim10c3ZPrL5bZhLGtu8cz+rOBTFz0CwBV4Q/7dYwZiqZbGVLZ+vjQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.4.0", + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/samsam": "^3.3.1", + "diff": "^3.5.0", + "lolex": "^3.1.0", + "nise": "^1.4.10", + "supports-color": "^5.5.0" + } + }, "slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", @@ -9394,6 +9419,27 @@ } } }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, "statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", @@ -10085,6 +10131,46 @@ } } }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", From 6903c1cecad3f03834664a512b2c0274387a9edc Mon Sep 17 00:00:00 2001 From: austin047 Date: Tue, 9 Apr 2019 21:10:06 +0100 Subject: [PATCH 3/5] refactor: Update user-order.controller.ts Signed-off-by: austin047 --- packages/shopping/src/controllers/user-order.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shopping/src/controllers/user-order.controller.ts b/packages/shopping/src/controllers/user-order.controller.ts index 8516e70df..fe8834f81 100644 --- a/packages/shopping/src/controllers/user-order.controller.ts +++ b/packages/shopping/src/controllers/user-order.controller.ts @@ -43,7 +43,7 @@ export class UserOrderController { @inject('authentication.currentUser') currentUser: UserProfile, @requestBody() order: Order, ): Promise { - if (currentUser.id !== userId) { + if (currentUser.id !== order.userId) { throw new HttpErrors.BadRequest( `User id does not match looged in user: ${userId} !== ${ currentUser.id From a5bb9a383474f5e8e31e4e92d2a8271126789e1e Mon Sep 17 00:00:00 2001 From: jannyHou Date: Mon, 29 Apr 2019 16:45:37 -0400 Subject: [PATCH 4/5] chore: rebase code Signed-off-by: jannyHou --- package-lock.json | 117 +++--------------- .../user-order.controller.acceptance.ts | 7 +- 2 files changed, 21 insertions(+), 103 deletions(-) diff --git a/package-lock.json b/package-lock.json index 10456d4e6..9b370b2c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2989,6 +2989,11 @@ "util-deprecate": "^1.0.1" } }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + }, "through2": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", @@ -2997,12 +3002,6 @@ "requires": { "readable-stream": "2 || 3" } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true } } }, @@ -5768,12 +5767,6 @@ "chalk": "^2.0.1" } }, - "lolex": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.0.1.tgz", - "integrity": "sha512-UHuOBZ5jjsKuzbB/gRNNW8Vg8f00Emgskdq2kvZxgBJCS0aqquAuXai/SkWORlKeZEiNQWZjFZOqIUcH9LqKCw==", - "dev": true - }, "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", @@ -7879,6 +7872,12 @@ "wordwrap": "~0.0.2" } }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, "os-locale": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", @@ -8418,16 +8417,6 @@ } } }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -9059,21 +9048,6 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, - "sinon": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.3.2.tgz", - "integrity": "sha512-thErC1z64BeyGiPvF8aoSg0LEnptSaWE7YhdWWbWXgelOyThent7uKOnnEh9zBxDbKixtr5dEko+ws1sZMuFMA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.4.0", - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/samsam": "^3.3.1", - "diff": "^3.5.0", - "lolex": "^4.0.1", - "nise": "^1.4.10", - "supports-color": "^5.5.0" - } - }, "sinon": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.3.1.tgz", @@ -9087,6 +9061,14 @@ "lolex": "^3.1.0", "nise": "^1.4.10", "supports-color": "^5.5.0" + }, + "dependencies": { + "lolex": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-3.1.0.tgz", + "integrity": "sha512-zFo5MgCJ0rZ7gQg69S4pqBsLURbFw11X68C18OcJjJQbqaXm2NoTrGl1IMM3TIz0/BnN1tIs2tzmmqvCsOMMjw==", + "dev": true + } } }, "slash": { @@ -9419,27 +9401,6 @@ } } }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, "statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", @@ -10131,46 +10092,6 @@ } } }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - } - } - }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", diff --git a/packages/shopping/src/__tests__/acceptance/user-order.controller.acceptance.ts b/packages/shopping/src/__tests__/acceptance/user-order.controller.acceptance.ts index b40edfbba..25894d2dd 100644 --- a/packages/shopping/src/__tests__/acceptance/user-order.controller.acceptance.ts +++ b/packages/shopping/src/__tests__/acceptance/user-order.controller.acceptance.ts @@ -9,11 +9,8 @@ import {OrderRepository, UserRepository} from '../../repositories'; import {MongoDataSource} from '../../datasources'; import {User, Order} from '../../models'; import {setupApplication} from './helper'; -import {JWTAuthenticationService} from '../../src/services/JWT.authentication.service'; -import { - PasswordHasherBindings, - JWTAuthenticationBindings, -} from '../../src/keys'; +import {JWTAuthenticationService} from '../../services/JWT.authentication.service'; +import {PasswordHasherBindings, JWTAuthenticationBindings} from '../../keys'; describe('UserOrderController acceptance tests', () => { let app: ShoppingApplication; From 55854d1a33997e22e2450522410c93bbb63f8adf Mon Sep 17 00:00:00 2001 From: Fuh Austin Date: Tue, 18 Jun 2019 22:31:55 +0100 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20Client=20can=20creat?= =?UTF-8?q?e=20an=20order=20without=20userId=20in=20the=20body?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Client can create an order without passing the userId in the body of the request, the serve collects the userId from the url and compares against the authenticated user(Current User) and the creates the order for that user. ✅ Closes: #43 refactor: 💡 remove debug console logs Remove console logs created for debug purposes Signed-off-by: Fuh Austin --- .../user-order.controller.acceptance.ts | 26 +++++++++++++++++++ .../src/controllers/user-order.controller.ts | 20 +++++++++----- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/packages/shopping/src/__tests__/acceptance/user-order.controller.acceptance.ts b/packages/shopping/src/__tests__/acceptance/user-order.controller.acceptance.ts index 25894d2dd..55ecacb18 100644 --- a/packages/shopping/src/__tests__/acceptance/user-order.controller.acceptance.ts +++ b/packages/shopping/src/__tests__/acceptance/user-order.controller.acceptance.ts @@ -89,6 +89,32 @@ describe('UserOrderController acceptance tests', () => { expect(res.body).to.deepEqual(order); }); + it('creates an order for a user without a userId in the body', async () => { + const newUser = await userRepo.create(user); + const userId = newUser.id.toString(); + + const token = await jwtAuthService.getAccessTokenForUser({ + email: newUser.email, + password: plainPassword, + }); + + const order = givenAOrder(); + + const res = await client + .post(`/users/${userId}/orders`) + .send(order) + .set('Authorization', 'Bearer ' + token) + .expect(200); + expect(res.body.orderId).to.be.a.String(); + expect(res.body.userId).to.equal(userId); + + delete res.body.orderId; + delete res.body.userId; + delete order.userId; + + expect(res.body).to.deepEqual(order); + }); + it('throws an error when a userId in path does not match body', async () => { const newUser = await userRepo.create(user); const userId = newUser.id.toString(); diff --git a/packages/shopping/src/controllers/user-order.controller.ts b/packages/shopping/src/controllers/user-order.controller.ts index fe8834f81..74d283a93 100644 --- a/packages/shopping/src/controllers/user-order.controller.ts +++ b/packages/shopping/src/controllers/user-order.controller.ts @@ -43,19 +43,25 @@ export class UserOrderController { @inject('authentication.currentUser') currentUser: UserProfile, @requestBody() order: Order, ): Promise { - if (currentUser.id !== order.userId) { + if (order.userId) { + if (currentUser.id !== order.userId) { + throw new HttpErrors.BadRequest( + `User id does not match looged in user: ${order.userId} !== ${ + currentUser.id + }`, + ); + } + delete order.userId; + return await this.userRepo.orders(userId).create(order); + } + + if (currentUser.id !== userId) { throw new HttpErrors.BadRequest( `User id does not match looged in user: ${userId} !== ${ currentUser.id }`, ); } - - if (userId !== order.userId) { - throw new HttpErrors.BadRequest( - `User id does not match: ${userId} !== ${order.userId}`, - ); - } delete order.userId; return await this.userRepo.orders(userId).create(order); }