diff --git a/imports/plugins/core/discounts/client/components/form.js b/imports/plugins/core/discounts/client/components/form.js index ad77351fcc3..e5fbf80c895 100644 --- a/imports/plugins/core/discounts/client/components/form.js +++ b/imports/plugins/core/discounts/client/components/form.js @@ -19,9 +19,10 @@ export default class DiscountForm extends Component { // debounce helper so to wait on user input this.debounceDiscounts = debounce(() => { this.setState({ validationMessage: "" }); + const { collection, id, token } = this.props; const { discount } = this.state; // handle discount code validation messages after attempt to apply - Meteor.call("discounts/codes/apply", this.props.id, discount, this.props.collection, (error, result) => { + Meteor.call("discounts/codes/apply", id, discount, collection, token, (error, result) => { if (error) { Alerts.toast(i18next.t(error.reason), "error"); } @@ -38,9 +39,6 @@ export default class DiscountForm extends Component { } }); }, 800); - - this.handleChange = this.handleChange.bind(this); - this.handleClick = this.handleClick.bind(this); } componentWillUnmount() { @@ -64,7 +62,7 @@ export default class DiscountForm extends Component { } // handle keydown and change events - handleChange(event) { + handleChange = (event) => { const { attempts } = this.state; // ensure we don't submit on enter if (event.keyCode === 13) { @@ -85,7 +83,7 @@ export default class DiscountForm extends Component { } // handle display or not - handleClick(event) { + handleClick = (event) => { event.preventDefault(); this.setState({ validatedInput: true }); } @@ -163,5 +161,6 @@ DiscountForm.propTypes = { collection: PropTypes.string, discount: PropTypes.string, id: PropTypes.string, + token: PropTypes.string, validatedInput: PropTypes.bool // eslint-disable-line react/boolean-prop-naming }; diff --git a/imports/plugins/core/discounts/client/components/list.js b/imports/plugins/core/discounts/client/components/list.js index 9694abcedad..15c02e3f779 100644 --- a/imports/plugins/core/discounts/client/components/list.js +++ b/imports/plugins/core/discounts/client/components/list.js @@ -14,8 +14,10 @@ class DiscountList extends Component { // handle remove click handleClick(event, codeId) { - return Meteor.call("discounts/codes/remove", this.props.id, codeId, this.props.collection); + const { collection, id, token } = this.props; + return Meteor.call("discounts/codes/remove", id, codeId, collection, token); } + // list items renderList() { const listItems = this.props.listItems.map((listItem) => this.renderItem(listItem)); @@ -24,6 +26,7 @@ class DiscountList extends Component {
{listItems}
); } + // render item renderItem(listItem) { let TrashCan; @@ -47,8 +50,9 @@ class DiscountList extends Component { // load form input view renderNoneFound() { + const { collection, id, token, validatedInput } = this.props; return ( - + ); } @@ -63,9 +67,16 @@ DiscountList.propTypes = { collection: PropTypes.string, id: PropTypes.string, listItems: PropTypes.array, + token: PropTypes.string, validatedInput: PropTypes.bool // eslint-disable-line react/boolean-prop-naming }; +/** + * @summary Tracker reactive props + * @param {Object} props Incoming props + * @param {Function} onData Callback for more props + * @returns {undefined} + */ function composer(props, onData) { const currentCart = Reaction.Collections[props.collection].findOne({ _id: props.id diff --git a/imports/plugins/core/discounts/server/index.js b/imports/plugins/core/discounts/server/index.js index 70a8129598e..f738cabaac7 100644 --- a/imports/plugins/core/discounts/server/index.js +++ b/imports/plugins/core/discounts/server/index.js @@ -2,4 +2,3 @@ import "./i18n"; import "./security/discounts"; import "./publications/discounts"; -import "./methods"; diff --git a/imports/plugins/core/discounts/server/methods/index.js b/imports/plugins/core/discounts/server/methods/index.js deleted file mode 100644 index c8abe684b3c..00000000000 --- a/imports/plugins/core/discounts/server/methods/index.js +++ /dev/null @@ -1 +0,0 @@ -import "./methods"; diff --git a/imports/plugins/core/discounts/server/methods/methods.app-test.js b/imports/plugins/core/discounts/server/methods/methods.app-test.js deleted file mode 100644 index fbce8307034..00000000000 --- a/imports/plugins/core/discounts/server/methods/methods.app-test.js +++ /dev/null @@ -1,40 +0,0 @@ -/* eslint prefer-arrow-callback:0 */ -import { Meteor } from "meteor/meteor"; -import { Roles } from "meteor/alanning:roles"; -import { expect } from "meteor/practicalmeteor:chai"; -import { sinon } from "meteor/practicalmeteor:sinon"; -import { Discounts } from "/imports/plugins/core/discounts/lib/collections"; - -const code = { - discount: 12, - label: "Discount 5", - description: "Discount by 5%", - discountMethod: "code", - code: "promocode" -}; - -describe("discounts methods", function () { - let sandbox; - - beforeEach(function () { - sandbox = sinon.sandbox.create(); - }); - - afterEach(function () { - sandbox.restore(); - }); - - describe("discounts/deleteRate", function () { - it("should delete rate with discounts permission", function () { - this.timeout(15000); - sandbox.stub(Roles, "userIsInRole", () => true); - const discountInsertSpy = sandbox.spy(Discounts, "insert"); - const discountId = Meteor.call("discounts/addCode", code); - expect(discountInsertSpy).to.have.been.called; - - Meteor.call("discounts/deleteRate", discountId); - const discountCount = Discounts.find(discountId).count(); - expect(discountCount).to.equal(0); - }); - }); -}); diff --git a/imports/plugins/core/discounts/server/methods/methods.js b/imports/plugins/core/discounts/server/methods/methods.js deleted file mode 100644 index 175bf94f2f0..00000000000 --- a/imports/plugins/core/discounts/server/methods/methods.js +++ /dev/null @@ -1,79 +0,0 @@ -import { Meteor } from "meteor/meteor"; -import { Match, check } from "meteor/check"; -import ReactionError from "@reactioncommerce/reaction-error"; -import { Cart } from "/lib/collections"; -import appEvents from "/imports/node-app/core/util/appEvents"; -import { Discounts } from "../../lib/collections"; -import Reaction from "../api"; - -/** - * - * @namespace Discounts/Methods - */ - -export const methods = { - /** - * @name discounts/deleteRate - * @method - * @memberof Discounts/Methods - * @param {String} discountId discount id to delete - * @return {String} returns update/insert result - */ - "discounts/deleteRate"(discountId) { - check(discountId, String); - - // check permissions to delete - if (!Reaction.hasPermission("discounts")) { - throw new ReactionError("access-denied", "Access Denied"); - } - - return Discounts.remove({ _id: discountId }); - }, - - /** - * @name discounts/setRate - * @method - * @memberof Discounts/Methods - * @summary Update the cart discounts without hooks - * @param {String} cartId cartId - * @param {Number} discountRate discountRate - * @param {Object} discounts discounts - * @return {Number} returns update result - */ - "discounts/setRate"(cartId, discountRate, discounts) { - check(cartId, String); - check(discountRate, Number); - check(discounts, Match.Optional(Array)); - - const result = Cart.update({ _id: cartId }, { - $set: { - discounts, - discount: discountRate - } - }); - - const updatedCart = Cart.findOne({ _id: cartId }); - Promise.await(appEvents.emit("afterCartUpdate", updatedCart)); - - return result; - }, - - /** - * @name discounts/editRate - * @method - * @memberof Discounts/Rates/Methods - * @param {Object} details An object with _id and modifier props - * @return {String} Update result - */ - "discounts/editRate"(details) { - check(details, { - _id: String, - modifier: Object // actual schema validation happens during update below - }); - if (!Reaction.hasPermission("discount-rates")) throw new ReactionError("access-denied", "Access Denied"); - const { _id, modifier } = details; - return Discounts.update(_id, modifier); - } -}; - -Meteor.methods(methods); diff --git a/imports/plugins/included/discount-codes/client/templates/codes.html b/imports/plugins/included/discount-codes/client/templates/codes.html index 5aaaca952d5..6ccb39e294b 100644 --- a/imports/plugins/included/discount-codes/client/templates/codes.html +++ b/imports/plugins/included/discount-codes/client/templates/codes.html @@ -1,5 +1,5 @@ diff --git a/imports/plugins/included/discount-codes/client/templates/codes.js b/imports/plugins/included/discount-codes/client/templates/codes.js index ae6d818dd0f..babc28eac34 100644 --- a/imports/plugins/included/discount-codes/client/templates/codes.js +++ b/imports/plugins/included/discount-codes/client/templates/codes.js @@ -7,8 +7,11 @@ Template.discountCodesCheckout.helpers({ DiscountList() { return DiscountList; }, - cartId() { - const { cart } = getCart(); - return cart && cart._id; + cartProps() { + const { cart, token } = getCart(); + return { + cartId: cart && cart._id, + cartToken: token + }; } }); diff --git a/imports/plugins/included/discount-codes/client/templates/settings.js b/imports/plugins/included/discount-codes/client/templates/settings.js index 8c1e180e64d..a907c3561cd 100644 --- a/imports/plugins/included/discount-codes/client/templates/settings.js +++ b/imports/plugins/included/discount-codes/client/templates/settings.js @@ -170,7 +170,7 @@ Template.customDiscountCodes.events({ }, (isConfirm) => { if (isConfirm) { if (id) { - Meteor.call("discounts/deleteRate", id); + Meteor.call("discounts/deleteCode", id); instance.state.set({ isEditing: false, editingId: null diff --git a/imports/plugins/included/discount-codes/server/methods/methods.app-test.js b/imports/plugins/included/discount-codes/server/methods/methods.app-test.js index 80457a42840..7c43fa09a65 100644 --- a/imports/plugins/included/discount-codes/server/methods/methods.app-test.js +++ b/imports/plugins/included/discount-codes/server/methods/methods.app-test.js @@ -5,6 +5,7 @@ import { Factory } from "meteor/dburles:factory"; import { expect } from "meteor/practicalmeteor:chai"; import { sinon } from "meteor/practicalmeteor:sinon"; import { Discounts } from "/imports/plugins/core/discounts/lib/collections"; +import { getShop } from "/imports/plugins/core/core/server/fixtures/shops"; import { Cart } from "/lib/collections"; import Reaction from "/imports/plugins/core/core/server/Reaction"; import ReactionError from "@reactioncommerce/reaction-error"; @@ -22,7 +23,17 @@ before(function () { }); describe("discount code methods", function () { + const shop = getShop(); let sandbox; + let user; + let account; + let accountId; + + before(function () { + user = Factory.create("user"); + account = Factory.create("account", { userId: user._id }); + accountId = account._id; + }); beforeEach(function () { sandbox = sinon.sandbox.create(); @@ -51,12 +62,29 @@ describe("discount code methods", function () { }); }); + describe("discounts/deleteCode", function () { + it("should delete rate with discounts permission", function () { + this.timeout(15000); + sandbox.stub(Roles, "userIsInRole", () => true); + const discountInsertSpy = sandbox.spy(Discounts, "insert"); + const discountId = Meteor.call("discounts/addCode", code); + expect(discountInsertSpy).to.have.been.called; + + Meteor.call("discounts/deleteCode", discountId); + const discountCount = Discounts.find(discountId).count(); + expect(discountCount).to.equal(0); + }); + }); + describe("discounts/codes/apply", function () { it("should apply code when called for a cart with multiple items from same shop", function () { + this.timeout(5000); + sandbox.stub(Reaction, "getCartShopId", () => shop._id); + sandbox.stub(Reaction, "getUserId", () => user._id); sandbox.stub(Reaction, "hasPermission", () => true); // make a cart with two items from same shop - const cart = Factory.create("cartMultiItems"); + const cart = Factory.create("cartMultiItems", { accountId }); Meteor.call("discounts/addCode", code); Meteor.call("discounts/codes/apply", cart._id, code.code); @@ -69,10 +97,13 @@ describe("discount code methods", function () { }); it("should not apply code when applied to multi-shop order or cart", function () { + this.timeout(5000); + sandbox.stub(Reaction, "getCartShopId", () => shop._id); + sandbox.stub(Reaction, "getUserId", () => user._id); sandbox.stub(Reaction, "hasPermission", () => true); // make a cart with two items from separate shops - const cart = Factory.create("cartMultiShop"); + const cart = Factory.create("cartMultiShop", { accountId }); expect(() => Meteor.call("discounts/codes/apply", cart._id, code.code)).to.throw(ReactionError, /multiShopError/); diff --git a/imports/plugins/included/discount-codes/server/methods/methods.js b/imports/plugins/included/discount-codes/server/methods/methods.js index 20ab683bb0c..b8058d3503c 100644 --- a/imports/plugins/included/discount-codes/server/methods/methods.js +++ b/imports/plugins/included/discount-codes/server/methods/methods.js @@ -1,9 +1,10 @@ import Random from "@reactioncommerce/random"; import { Meteor } from "meteor/meteor"; -import { Match, check } from "meteor/check"; +import { check, Match } from "meteor/check"; import Reaction from "/imports/plugins/core/core/server/Reaction"; import ReactionError from "@reactioncommerce/reaction-error"; import appEvents from "/imports/node-app/core/util/appEvents"; +import getCart from "/imports/plugins/core/cart/server/util/getCart"; import { Discounts } from "/imports/plugins/core/discounts/lib/collections"; import { DiscountCodes as DiscountSchema } from "../../lib/collections/schemas"; @@ -20,18 +21,18 @@ export const methods = { * @method * @memberof Discounts/Codes/Methods * @param {Object} doc A Discounts document to be inserted - * @param {String} [docId] DEPRECATED. Existing ID to trigger an update. Use discounts/editCode method instead. * @return {String} Insert result */ - "discounts/addCode"(doc, docId) { + "discounts/addCode"(doc) { check(doc, Object); // actual schema validation happens during insert below - // Backward compatibility - check(docId, Match.Optional(String)); - if (docId) return Meteor.call("discounts/editCode", { _id: docId, modifier: doc }); + const shopId = Reaction.getShopId(); - if (!Reaction.hasPermission("discount-codes")) throw new ReactionError("access-denied", "Access Denied"); - doc.shopId = Reaction.getShopId(); + if (!Reaction.hasPermission("discounts", Reaction.getUserId(), shopId)) { + throw new ReactionError("access-denied", "Access Denied"); + } + + doc.shopId = shopId; return Discounts.insert(doc); }, @@ -47,9 +48,37 @@ export const methods = { _id: String, modifier: Object // actual schema validation happens during update below }); - if (!Reaction.hasPermission("discount-codes")) throw new ReactionError("access-denied", "Access Denied"); + + const shopId = Reaction.getShopId(); + + if (!Reaction.hasPermission("discounts", Reaction.getUserId(), shopId)) { + throw new ReactionError("access-denied", "Access Denied"); + } + const { _id, modifier } = details; - return Discounts.update(_id, modifier); + return Discounts.update({ _id, shopId }, modifier); + }, + + /** + * @name discounts/deleteCode + * @method + * @memberof Discounts/Codes/Methods + * @param {String} discountId discount id to delete + * @return {String} returns remove result + */ + "discounts/deleteCode"(discountId) { + check(discountId, String); + + const shopId = Reaction.getShopId(); + + if (!Reaction.hasPermission("discounts", Reaction.getUserId(), shopId)) { + throw new ReactionError("access-denied", "Access Denied"); + } + + return Discounts.remove({ + _id: discountId, + shopId + }); }, /** @@ -60,14 +89,42 @@ export const methods = { * @param {String} id cart id of which to remove a code * @param {String} codeId discount Id from cart.billing * @param {String} collection collection (either Orders or Cart) + * @param {String} [token] Cart or order token if anonymous * @return {String} returns update/insert result */ - "discounts/codes/remove"(id, codeId, collection = "Cart") { + "discounts/codes/remove"(id, codeId, collection = "Cart", token) { check(id, String); check(codeId, String); check(collection, String); + check(token, Match.Maybe(String)); + const Collection = Reaction.Collections[collection]; + if (collection === "Cart") { + let { cart } = getCart(id, { cartToken: token, throwIfNotFound: true }); + + // If we found a cart, then the current account owns it + if (!cart) { + cart = Collection.findOne({ _id: id }); + if (!cart) { + throw new ReactionError("not-found", "Cart not found"); + } + + if (!Reaction.hasPermission("discounts", Reaction.getUserId(), cart.shopId)) { + throw new ReactionError("access-denied", "Access Denied"); + } + } + } else { + const order = Collection.findOne({ _id: id }); + if (!order) { + throw new ReactionError("not-found", "Order not found"); + } + + if (!Reaction.hasPermission("discounts", Reaction.getUserId(), order.shopId)) { + throw new ReactionError("access-denied", "Access Denied"); + } + } + // TODO: update a history record of transaction // The Payment schema currency defaultValue is adding {} to the $pull condition. // If this issue is eventually fixed, autoValues can be re-enabled here @@ -94,22 +151,53 @@ export const methods = { * @param {String} id cart/order id of which to remove a code * @param {String} code valid discount code * @param {String} collection collection (either Orders or Cart) + * @param {String} [token] Cart or order token if anonymous * @return {Boolean} returns true if successfully applied */ - "discounts/codes/apply"(id, code, collection = "Cart") { + "discounts/codes/apply"(id, code, collection = "Cart", token) { check(id, String); check(code, String); check(collection, String); + check(token, Match.Maybe(String)); let userCount = 0; let orderCount = 0; + const Collection = Reaction.Collections[collection]; + let objectToApplyDiscount; + + if (collection === "Cart") { + let { cart } = getCart(id, { cartToken: token, throwIfNotFound: true }); + + // If we found a cart, then the current account owns it + if (!cart) { + cart = Collection.findOne({ _id: id }); + if (!cart) { + throw new ReactionError("not-found", "Cart not found"); + } + + if (!Reaction.hasPermission("discounts", Reaction.getUserId(), cart.shopId)) { + throw new ReactionError("access-denied", "Access Denied"); + } + } + + objectToApplyDiscount = cart; + } else { + const order = Collection.findOne({ _id: id }); + if (!order) { + throw new ReactionError("not-found", "Order not found"); + } + + if (!Reaction.hasPermission("discounts", Reaction.getUserId(), order.shopId)) { + throw new ReactionError("access-denied", "Access Denied"); + } + + objectToApplyDiscount = order; + } + // check to ensure discounts can only apply to single shop carts // TODO: Remove this check after implementation of shop-by-shop discounts - const Collection = Reaction.Collections[collection]; - const objectToApplyDiscount = Collection.findOne({ _id: id }); - const items = objectToApplyDiscount && objectToApplyDiscount.items; // loop through all items and filter down to unique shops (in order to get participating shops in the order/cart) - const uniqueShopObj = items.reduce((shopObj, item) => { + const uniqueShopObj = objectToApplyDiscount.items.reduce((shopObj, item) => { if (!shopObj[item.shopId]) { shopObj[item.shopId] = true; } diff --git a/imports/plugins/included/email-smtp/server/methods/retryFailed.js b/imports/plugins/included/email-smtp/server/methods/retryFailed.js index 09f2612f803..d88c241dba7 100644 --- a/imports/plugins/included/email-smtp/server/methods/retryFailed.js +++ b/imports/plugins/included/email-smtp/server/methods/retryFailed.js @@ -13,7 +13,7 @@ import ReactionError from "@reactioncommerce/reaction-error"; * @return {Boolean} - returns true if job is successfully restarted */ export default function retryFailed(jobId) { - if (!Reaction.hasPermission(["owner", "admin", "reaction-email"], this.userId)) { + if (!Reaction.hasPermission(["owner", "admin", "reaction-email"], this.userId, Reaction.getPrimaryShopId())) { Logger.error("email/retryFailed: Access Denied"); throw new ReactionError("access-denied", "Access Denied"); } diff --git a/imports/plugins/included/shipping-rates/server/index.js b/imports/plugins/included/shipping-rates/server/index.js index 6641da28c8a..3979f964b5a 100644 --- a/imports/plugins/included/shipping-rates/server/index.js +++ b/imports/plugins/included/shipping-rates/server/index.js @@ -1,2 +1 @@ import "./i18n"; -import "./methods/rates"; diff --git a/imports/plugins/included/shipping-rates/server/methods/rates.js b/imports/plugins/included/shipping-rates/server/methods/rates.js deleted file mode 100644 index bbc7b0dc505..00000000000 --- a/imports/plugins/included/shipping-rates/server/methods/rates.js +++ /dev/null @@ -1,111 +0,0 @@ -import Random from "@reactioncommerce/random"; -import { Meteor } from "meteor/meteor"; -import { check, Match } from "meteor/check"; -import { Shipping } from "/lib/collections"; -import { ShippingMethod } from "/lib/collections/schemas"; -import Reaction from "/imports/plugins/core/core/server/Reaction"; -import ReactionError from "@reactioncommerce/reaction-error"; - -const shippingRoles = ["admin", "owner", "shipping", "reaction-shippo"]; - -export const methods = { - /** - * shipping/rates/add - * add new shipping flat rate methods - * @summary insert shipping method for a flat rate provider - * @param {Object} rate a valid ShippingMethod object - * @return {Number} insert result - */ - "shipping/rates/add"(rate) { - check(rate, { - name: String, - label: String, - group: String, - cost: Match.OneOf(Number, null, undefined), - handling: Number, - rate: Number, - enabled: Boolean - }); - - if (!Reaction.hasPermission(shippingRoles)) { - throw new ReactionError("access-denied", "Access Denied"); - } - - const shopId = Reaction.getShopId(); - if (!Shipping.findOne({ "provider.name": "flatRates", shopId })) { - Shipping.insert({ - name: "Default Shipping Provider", - shopId, - provider: { - name: "flatRates", - label: "Flat Rate" - } - }); - } - - rate._id = Random.id(); - return Shipping.update({ - shopId, - "provider.name": "flatRates" - }, { - $addToSet: { - methods: rate - } - }); - }, - - /** - * shipping/rates/update - * @summary update shipping rate methods - * @param { Object } method shipping method object - * @return { Number } update result - */ - "shipping/rates/update"(method) { - ShippingMethod.validate(method); - if (!Reaction.hasPermission(shippingRoles)) { - throw new ReactionError("access-denied", "Access Denied"); - } - const methodId = method._id; - - return Shipping.update({ - "methods._id": methodId - }, { - $set: { - "methods.$": method - } - }); - }, - - /** - * shipping/rates/delete - * @summary delete shipping rate method - * @param { String } rateId id of method to delete - * @return { Number } update result - */ - "shipping/rates/delete"(rateId) { - check(rateId, String); - - if (!Reaction.hasPermission(shippingRoles)) { - throw new ReactionError("access-denied", "Access Denied"); - } - - const rates = Shipping.findOne({ "methods._id": rateId }); - const { methods: shippingMethods } = rates; - const updatedMethods = shippingMethods.filter((method) => method._id !== rateId); - - // HACK: not sure why we need to do this.. but it works. - // Replaced a $pull which in theory is better, but was broken. - // Issue w/ pull was introduced during the simpl-schema update - const deleted = Shipping.update({ - "methods._id": rateId - }, { - $set: { - methods: updatedMethods - } - }); - - return deleted; - } -}; - -Meteor.methods(methods); diff --git a/imports/plugins/included/taxes-rates/server/methods.js b/imports/plugins/included/taxes-rates/server/methods.js index b174156f2b3..633723bb1a4 100644 --- a/imports/plugins/included/taxes-rates/server/methods.js +++ b/imports/plugins/included/taxes-rates/server/methods.js @@ -1,6 +1,6 @@ import ReactionError from "@reactioncommerce/reaction-error"; import { Meteor } from "meteor/meteor"; -import { Match, check } from "meteor/check"; +import { check } from "meteor/check"; import Reaction from "/imports/plugins/core/core/server/Reaction"; import { Taxes } from "../lib/collections"; @@ -22,12 +22,13 @@ const methods = { "taxes/deleteRate"(taxId) { check(taxId, String); - // check permissions to delete - if (!Reaction.hasPermission("taxes")) { + const shopId = Reaction.getShopId(); + + if (!Reaction.hasPermission("taxes", Reaction.getUserId(), shopId)) { throw new ReactionError("access-denied", "Access Denied"); } - return Taxes.remove(taxId); + return Taxes.remove({ _id: taxId, shopId }); }, /** @@ -35,18 +36,18 @@ const methods = { * @method * @memberof TaxesRates/Methods * @param {Object} doc A Taxes document to be inserted - * @param {String} [docId] DEPRECATED. Existing ID to trigger an update. Use taxes/editRate method instead. * @return {String} Insert result */ - "taxes/addRate"(doc, docId) { + "taxes/addRate"(doc) { check(doc, Object); // actual schema validation happens during insert below - // Backward compatibility - check(docId, Match.Optional(String)); - if (docId) return Meteor.call("taxes/editRate", { _id: docId, modifier: doc }); + const shopId = Reaction.getShopId(); - if (!Reaction.hasPermission("taxes")) throw new ReactionError("access-denied", "Access Denied"); - doc.shopId = Reaction.getShopId(); + if (!Reaction.hasPermission("taxes", Reaction.getUserId(), shopId)) { + throw new ReactionError("access-denied", "Access Denied"); + } + + doc.shopId = shopId; return Taxes.insert(doc); }, @@ -62,9 +63,15 @@ const methods = { _id: String, modifier: Object // actual schema validation happens during update below }); - if (!Reaction.hasPermission("taxes")) throw new ReactionError("access-denied", "Access Denied"); + + const shopId = Reaction.getShopId(); + + if (!Reaction.hasPermission("taxes", Reaction.getUserId(), shopId)) { + throw new ReactionError("access-denied", "Access Denied"); + } + const { _id, modifier } = details; - return Taxes.update(_id, modifier); + return Taxes.update({ _id, shopId }, modifier); } };