diff --git a/.changeset/spotty-turkeys-exercise.md b/.changeset/spotty-turkeys-exercise.md new file mode 100644 index 00000000000..9b562f98385 --- /dev/null +++ b/.changeset/spotty-turkeys-exercise.md @@ -0,0 +1,5 @@ +--- +"@reactioncommerce/api-plugin-surcharges": patch +--- + +Added validatePermissions for the query endpoints diff --git a/apps/reaction/tests/integration/api/queries/surchargeById/surchargeById.test.js b/apps/reaction/tests/integration/api/queries/surchargeById/surchargeById.test.js index 7687a69624f..0d61649b65d 100644 --- a/apps/reaction/tests/integration/api/queries/surchargeById/surchargeById.test.js +++ b/apps/reaction/tests/integration/api/queries/surchargeById/surchargeById.test.js @@ -15,6 +15,7 @@ const opaqueSurchargeId = encodeOpaqueId("reaction/surcharge", internalSurcharge const shopName = "Test Shop"; let testApp; let surchargeById; +let mockAdminAccount; const mockSurcharge = Factory.Surcharge.makeOne({ _id: internalSurchargeId, @@ -35,6 +36,23 @@ beforeAll(async () => { await insertPrimaryShop(testApp.context, { _id: internalShopId, name: shopName }); await testApp.collections.Surcharges.insertOne(mockSurcharge); + + const adminGroup = Factory.Group.makeOne({ + _id: "adminGroup", + createdBy: null, + name: "admin", + permissions: ["reaction:legacy:surcharges/read"], + slug: "admin", + shopId: internalShopId + }); + await testApp.collections.Groups.insertOne(adminGroup); + + mockAdminAccount = Factory.Account.makeOne({ + groups: [adminGroup._id] + }); + await testApp.createUserAndAccount(mockAdminAccount); + await testApp.setLoggedInUser(mockAdminAccount); + surchargeById = testApp.query(SurchargeByIdQuery); }); diff --git a/apps/reaction/tests/integration/api/queries/surcharges/surcharges.test.js b/apps/reaction/tests/integration/api/queries/surcharges/surcharges.test.js index 3a5c6554460..75c06e7baec 100644 --- a/apps/reaction/tests/integration/api/queries/surcharges/surcharges.test.js +++ b/apps/reaction/tests/integration/api/queries/surcharges/surcharges.test.js @@ -14,6 +14,7 @@ const shopName = "Test Shop"; let testApp; let surcharges; let mockSurcharges; +let mockAdminAccount; beforeAll(async () => { testApp = new ReactionTestAPICore(); @@ -32,6 +33,22 @@ beforeAll(async () => { amount: 10 }); await testApp.collections.Surcharges.insertMany(mockSurcharges); + + const adminGroup = Factory.Group.makeOne({ + _id: "adminGroup", + createdBy: null, + name: "admin", + permissions: ["reaction:legacy:surcharges/read"], + slug: "admin", + shopId: internalShopId + }); + await testApp.collections.Groups.insertOne(adminGroup); + + mockAdminAccount = Factory.Account.makeOne({ + groups: [adminGroup._id] + }); + await testApp.createUserAndAccount(mockAdminAccount); + await testApp.setLoggedInUser(mockAdminAccount); surcharges = testApp.query(SurchargesQuery); }); diff --git a/packages/api-plugin-surcharges/src/queries/surchargeById.js b/packages/api-plugin-surcharges/src/queries/surchargeById.js index 820a0fbd835..6de822146f4 100644 --- a/packages/api-plugin-surcharges/src/queries/surchargeById.js +++ b/packages/api-plugin-surcharges/src/queries/surchargeById.js @@ -14,6 +14,8 @@ export default async function surchargeById(context, { surchargeId, shopId } = { const { collections } = context; const { Surcharges } = collections; + await context.validatePermissions("reaction:legacy:surcharges", "read", { shopId }); + const surcharge = await Surcharges.findOne({ _id: surchargeId, shopId diff --git a/packages/api-plugin-surcharges/src/queries/surchargeById.test.js b/packages/api-plugin-surcharges/src/queries/surchargeById.test.js new file mode 100644 index 00000000000..faa949c0244 --- /dev/null +++ b/packages/api-plugin-surcharges/src/queries/surchargeById.test.js @@ -0,0 +1,39 @@ +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import mockCollection from "@reactioncommerce/api-utils/tests/mockCollection.js"; +import ReactionError from "@reactioncommerce/reaction-error"; +import surchargeByIdQuery from "./surchargeById.js"; + +const fakeShopId = "FAKE_SHOP_ID"; +const fakeSurchargeId = "FAKE_SURCHARGE_ID"; +const mockSurcharge = { _id: fakeSurchargeId }; +mockContext.validatePermissions = jest.fn("validatePermissions"); +mockContext.collections.Surcharges = mockCollection("Surcharges"); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +test("returns the surcharge if validatePermission returns true", async () => { + mockContext.collections.Surcharges.findOne.mockReturnValueOnce(Promise.resolve(mockSurcharge)); + mockContext.validatePermissions.mockReturnValueOnce(Promise.resolve(undefined)); + const result = await surchargeByIdQuery(mockContext, { surchargeId: fakeSurchargeId, shopId: fakeShopId }); + + expect(mockContext.validatePermissions).toHaveBeenCalledWith( + "reaction:legacy:surcharges", + "read", + { shopId: fakeShopId } + ); + expect(mockContext.collections.Surcharges.findOne).toHaveBeenCalledWith({ _id: fakeSurchargeId, shopId: fakeShopId }); + await expect(result).toBe(mockSurcharge); +}); + + +test("throws access-denied if not allowed", async () => { + mockContext.validatePermissions.mockImplementation(() => { + throw new ReactionError("access-denied", "Access Denied"); + }); + mockContext.collections.Accounts.findOne.mockReturnValueOnce(undefined); + const result = surchargeByIdQuery(mockContext, { surchargeId: fakeSurchargeId, shopId: fakeShopId }); + const expectedResult = new ReactionError("access-denied", "Access Denied"); + await expect(result).rejects.toThrow(expectedResult); +}); diff --git a/packages/api-plugin-surcharges/src/queries/surcharges.js b/packages/api-plugin-surcharges/src/queries/surcharges.js index 8f249ce7e7c..f424ffae836 100644 --- a/packages/api-plugin-surcharges/src/queries/surcharges.js +++ b/packages/api-plugin-surcharges/src/queries/surcharges.js @@ -12,6 +12,8 @@ export default async function surcharges(context, { shopId } = {}) { const { collections } = context; const { Surcharges } = collections; + await context.validatePermissions("reaction:legacy:surcharges", "read", { shopId }); + return Surcharges.find({ shopId }); diff --git a/packages/api-plugin-surcharges/src/queries/surcharges.test.js b/packages/api-plugin-surcharges/src/queries/surcharges.test.js new file mode 100644 index 00000000000..3f8879cd0a0 --- /dev/null +++ b/packages/api-plugin-surcharges/src/queries/surcharges.test.js @@ -0,0 +1,37 @@ +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import mockCollection from "@reactioncommerce/api-utils/tests/mockCollection.js"; +import ReactionError from "@reactioncommerce/reaction-error"; +import surchargesQuery from "./surcharges.js"; + +const fakeShopId = "FAKE_SHOP_ID"; +mockContext.validatePermissions = jest.fn("validatePermissions"); +mockContext.collections.Surcharges = mockCollection("Surcharges"); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +test("returns the surcharges cursor if validatePermission returns true", async () => { + mockContext.collections.Surcharges.find.mockReturnValueOnce(Promise.resolve("CURSOR")); + mockContext.validatePermissions.mockReturnValueOnce(Promise.resolve(undefined)); + const result = await surchargesQuery(mockContext, { shopId: fakeShopId }); + + expect(mockContext.collections.Surcharges.find).toHaveBeenCalledWith({ shopId: fakeShopId }); + expect(mockContext.validatePermissions).toHaveBeenCalledWith( + "reaction:legacy:surcharges", + "read", + { shopId: fakeShopId } + ); + await expect(result).toBe("CURSOR"); +}); + + +test("throws access-denied if not allowed", async () => { + mockContext.validatePermissions.mockImplementation(() => { + throw new ReactionError("access-denied", "Access Denied"); + }); + mockContext.collections.Accounts.findOne.mockReturnValueOnce(undefined); + const result = surchargesQuery(mockContext, { shopId: fakeShopId }); + const expectedResult = new ReactionError("access-denied", "Access Denied"); + await expect(result).rejects.toThrow(expectedResult); +});