From e7c4cc375174775bb0cfe52e5dc0270237150b3c Mon Sep 17 00:00:00 2001 From: Philip Korsholm <88927411+pKorsholm@users.noreply.github.com> Date: Tue, 22 Nov 2022 13:30:05 +0100 Subject: [PATCH] fix(medusa): Separate JWT auth strategies per domain (#2646) **What** Separate JWT auth strategies per domain Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com> Co-authored-by: Adrien de Peretti <25098370+adrien2p@users.noreply.github.com> --- .changeset/new-zebras-turn.md | 6 +++++ packages/medusa-core-utils/src/errors.ts | 1 + .../api/middlewares/authenticate-customer.ts | 7 ++++-- .../src/api/middlewares/authenticate.ts | 8 +++++-- .../src/api/middlewares/error-handler.ts | 3 +++ packages/medusa/src/api/middlewares/index.ts | 10 +++++---- .../require-customer-authentication.ts | 12 ++++++++++ .../api/routes/admin/auth/create-session.ts | 8 +++---- .../api/routes/store/auth/create-session.ts | 4 ++-- .../api/routes/store/auth/delete-session.ts | 2 +- .../medusa/src/api/routes/store/auth/index.ts | 2 +- .../routes/store/customers/create-address.ts | 2 +- .../routes/store/customers/create-customer.ts | 6 ++--- .../routes/store/customers/delete-address.ts | 2 +- .../src/api/routes/store/customers/index.ts | 6 ++--- .../api/routes/store/customers/list-orders.ts | 22 ++++++------------- .../routes/store/customers/update-address.ts | 7 +++--- .../routes/store/customers/update-customer.ts | 8 +++---- packages/medusa/src/api/routes/store/index.js | 4 ++-- packages/medusa/src/helpers/test-request.js | 2 +- packages/medusa/src/loaders/passport.ts | 20 ++++++++++++++--- 21 files changed, 90 insertions(+), 52 deletions(-) create mode 100644 .changeset/new-zebras-turn.md create mode 100644 packages/medusa/src/api/middlewares/require-customer-authentication.ts diff --git a/.changeset/new-zebras-turn.md b/.changeset/new-zebras-turn.md new file mode 100644 index 0000000000..0f74fe8e2d --- /dev/null +++ b/.changeset/new-zebras-turn.md @@ -0,0 +1,6 @@ +--- +"@medusajs/medusa": patch +"medusa-core-utils": patch +--- + +jwt fix diff --git a/packages/medusa-core-utils/src/errors.ts b/packages/medusa-core-utils/src/errors.ts index aca37b9409..c9412e7587 100644 --- a/packages/medusa-core-utils/src/errors.ts +++ b/packages/medusa-core-utils/src/errors.ts @@ -8,6 +8,7 @@ export const MedusaErrorTypes = { DUPLICATE_ERROR: "duplicate_error", INVALID_ARGUMENT: "invalid_argument", INVALID_DATA: "invalid_data", + UNAUTHORIZED: "unauthorized", NOT_FOUND: "not_found", NOT_ALLOWED: "not_allowed", UNEXPECTED_STATE: "unexpected_state", diff --git a/packages/medusa/src/api/middlewares/authenticate-customer.ts b/packages/medusa/src/api/middlewares/authenticate-customer.ts index ae86c0b9b3..604ab22e47 100644 --- a/packages/medusa/src/api/middlewares/authenticate-customer.ts +++ b/packages/medusa/src/api/middlewares/authenticate-customer.ts @@ -1,10 +1,13 @@ +import { NextFunction, Request, RequestHandler, Response } from "express" import passport from "passport" -import { Request, Response, NextFunction, RequestHandler } from "express" +// Optional customer authentication +// If authenticated, middleware attaches customer to request (as user) otherwise we pass through +// If you want to require authentication, use `requireCustomerAuthentication` in `packages/medusa/src/api/middlewares/require-customer-authentication.ts` export default (): RequestHandler => { return (req: Request, res: Response, next: NextFunction): void => { passport.authenticate( - ["jwt", "bearer"], + ["store-jwt", "bearer"], { session: false }, (err, user) => { if (err) { diff --git a/packages/medusa/src/api/middlewares/authenticate.ts b/packages/medusa/src/api/middlewares/authenticate.ts index d5aa4d4ed6..6d96429828 100644 --- a/packages/medusa/src/api/middlewares/authenticate.ts +++ b/packages/medusa/src/api/middlewares/authenticate.ts @@ -1,8 +1,12 @@ +import { NextFunction, Request, RequestHandler, Response } from "express" import passport from "passport" -import { Request, Response, NextFunction, RequestHandler } from "express" export default (): RequestHandler => { return (req: Request, res: Response, next: NextFunction): void => { - passport.authenticate(["jwt", "bearer"], { session: false })(req, res, next) + passport.authenticate(["admin-jwt", "bearer"], { session: false })( + req, + res, + next + ) } } diff --git a/packages/medusa/src/api/middlewares/error-handler.ts b/packages/medusa/src/api/middlewares/error-handler.ts index 36ab5b639f..aed0d42618 100644 --- a/packages/medusa/src/api/middlewares/error-handler.ts +++ b/packages/medusa/src/api/middlewares/error-handler.ts @@ -43,6 +43,9 @@ export default () => { errObj.message = "The request conflicted with another request. You may retry the request with the provided Idempotency-Key." break + case MedusaError.Types.UNAUTHORIZED: + statusCode = 401 + break case MedusaError.Types.DUPLICATE_ERROR: statusCode = 422 errObj.code = INVALID_REQUEST_ERROR diff --git a/packages/medusa/src/api/middlewares/index.ts b/packages/medusa/src/api/middlewares/index.ts index 2557b5d956..0647f8e822 100644 --- a/packages/medusa/src/api/middlewares/index.ts +++ b/packages/medusa/src/api/middlewares/index.ts @@ -1,17 +1,19 @@ -import { default as authenticateCustomer } from "./authenticate-customer" import { default as authenticate } from "./authenticate" -import { default as normalizeQuery } from "./normalized-query" +import { default as authenticateCustomer } from "./authenticate-customer" import { default as wrap } from "./await-middleware" +import { default as normalizeQuery } from "./normalized-query" +import { default as requireCustomerAuthentication } from "./require-customer-authentication" -export { getRequestedBatchJob } from "./batch-job/get-requested-batch-job" export { canAccessBatchJob } from "./batch-job/can-access-batch-job" +export { getRequestedBatchJob } from "./batch-job/get-requested-batch-job" export { doesConditionBelongToDiscount } from "./discount/does-condition-belong-to-discount" -export { transformQuery } from "./transform-query" export { transformBody } from "./transform-body" +export { transformQuery } from "./transform-query" export default { authenticate, authenticateCustomer, + requireCustomerAuthentication, normalizeQuery, wrap, } diff --git a/packages/medusa/src/api/middlewares/require-customer-authentication.ts b/packages/medusa/src/api/middlewares/require-customer-authentication.ts new file mode 100644 index 0000000000..20cd53fb3c --- /dev/null +++ b/packages/medusa/src/api/middlewares/require-customer-authentication.ts @@ -0,0 +1,12 @@ +import { NextFunction, Request, RequestHandler, Response } from "express" +import passport from "passport" + +export default (): RequestHandler => { + return (req: Request, res: Response, next: NextFunction): void => { + passport.authenticate(["store-jwt", "bearer"], { session: false })( + req, + res, + next + ) + } +} diff --git a/packages/medusa/src/api/routes/admin/auth/create-session.ts b/packages/medusa/src/api/routes/admin/auth/create-session.ts index 85eb37fcfb..c6ca2d26f5 100644 --- a/packages/medusa/src/api/routes/admin/auth/create-session.ts +++ b/packages/medusa/src/api/routes/admin/auth/create-session.ts @@ -1,10 +1,10 @@ import { IsEmail, IsNotEmpty, IsString } from "class-validator" -import AuthService from "../../../../services/auth" -import { EntityManager } from "typeorm" -import { MedusaError } from "medusa-core-utils" -import _ from "lodash" import jwt from "jsonwebtoken" +import _ from "lodash" +import { MedusaError } from "medusa-core-utils" +import { EntityManager } from "typeorm" +import AuthService from "../../../../services/auth" import { validator } from "../../../../utils/validator" /** diff --git a/packages/medusa/src/api/routes/store/auth/create-session.ts b/packages/medusa/src/api/routes/store/auth/create-session.ts index 10360e409e..4ade637b89 100644 --- a/packages/medusa/src/api/routes/store/auth/create-session.ts +++ b/packages/medusa/src/api/routes/store/auth/create-session.ts @@ -1,9 +1,9 @@ import { IsEmail, IsNotEmpty } from "class-validator" import jwt from "jsonwebtoken" +import { EntityManager } from "typeorm" import AuthService from "../../../../services/auth" import CustomerService from "../../../../services/customer" import { validator } from "../../../../utils/validator" -import { EntityManager } from "typeorm" /** * @oas [post] /auth @@ -79,7 +79,7 @@ export default async (req, res) => { const { projectConfig: { jwt_secret }, } = req.scope.resolve("configModule") - req.session.jwt = jwt.sign( + req.session.jwt_store = jwt.sign( { customer_id: result.customer?.id }, jwt_secret!, { diff --git a/packages/medusa/src/api/routes/store/auth/delete-session.ts b/packages/medusa/src/api/routes/store/auth/delete-session.ts index b9851dbb46..8f4cc3b6f9 100644 --- a/packages/medusa/src/api/routes/store/auth/delete-session.ts +++ b/packages/medusa/src/api/routes/store/auth/delete-session.ts @@ -31,6 +31,6 @@ * $ref: "#/components/responses/500_error" */ export default async (req, res) => { - req.session.jwt = {} + req.session.jwt_store = {} res.json({}) } diff --git a/packages/medusa/src/api/routes/store/auth/index.ts b/packages/medusa/src/api/routes/store/auth/index.ts index c41b37b827..cf9d2fd369 100644 --- a/packages/medusa/src/api/routes/store/auth/index.ts +++ b/packages/medusa/src/api/routes/store/auth/index.ts @@ -1,6 +1,6 @@ import { Router } from "express" -import { Customer } from "./../../../.." import middlewares from "../../../middlewares" +import { Customer } from "./../../../.." const route = Router() diff --git a/packages/medusa/src/api/routes/store/customers/create-address.ts b/packages/medusa/src/api/routes/store/customers/create-address.ts index cf6c34058d..251186df25 100644 --- a/packages/medusa/src/api/routes/store/customers/create-address.ts +++ b/packages/medusa/src/api/routes/store/customers/create-address.ts @@ -1,10 +1,10 @@ import { Type } from "class-transformer" import { ValidateNested } from "class-validator" +import { EntityManager } from "typeorm" import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "." import CustomerService from "../../../../services/customer" import { AddressCreatePayload } from "../../../../types/common" import { validator } from "../../../../utils/validator" -import { EntityManager } from "typeorm" /** * @oas [post] /customers/me/addresses diff --git a/packages/medusa/src/api/routes/store/customers/create-customer.ts b/packages/medusa/src/api/routes/store/customers/create-customer.ts index 2b2646c840..f8a8b67661 100644 --- a/packages/medusa/src/api/routes/store/customers/create-customer.ts +++ b/packages/medusa/src/api/routes/store/customers/create-customer.ts @@ -1,11 +1,11 @@ import { IsEmail, IsOptional, IsString } from "class-validator" import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "." +import jwt from "jsonwebtoken" +import { EntityManager } from "typeorm" import { Customer } from "../../../.." import CustomerService from "../../../../services/customer" -import jwt from "jsonwebtoken" import { validator } from "../../../../utils/validator" -import { EntityManager } from "typeorm" /** * @oas [post] /customers @@ -121,7 +121,7 @@ export default async (req, res) => { const { projectConfig: { jwt_secret }, } = req.scope.resolve("configModule") - req.session.jwt = jwt.sign({ customer_id: customer.id }, jwt_secret!, { + req.session.jwt_store = jwt.sign({ customer_id: customer.id }, jwt_secret!, { expiresIn: "30d", }) diff --git a/packages/medusa/src/api/routes/store/customers/delete-address.ts b/packages/medusa/src/api/routes/store/customers/delete-address.ts index b8e104b877..90717c618a 100644 --- a/packages/medusa/src/api/routes/store/customers/delete-address.ts +++ b/packages/medusa/src/api/routes/store/customers/delete-address.ts @@ -1,7 +1,7 @@ import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "." -import CustomerService from "../../../../services/customer" import { EntityManager } from "typeorm" +import CustomerService from "../../../../services/customer" /** * @oas [delete] /customers/me/addresses/{address_id} diff --git a/packages/medusa/src/api/routes/store/customers/index.ts b/packages/medusa/src/api/routes/store/customers/index.ts index 5fcbf2d8aa..7ca03be055 100644 --- a/packages/medusa/src/api/routes/store/customers/index.ts +++ b/packages/medusa/src/api/routes/store/customers/index.ts @@ -2,11 +2,11 @@ import { Router } from "express" import { Customer, Order } from "../../../.." import { PaginatedResponse } from "../../../../types/common" import middlewares, { transformQuery } from "../../../middlewares" -import { StoreGetCustomersCustomerOrdersParams } from "./list-orders" import { - defaultStoreOrdersRelations, defaultStoreOrdersFields, + defaultStoreOrdersRelations, } from "../orders" +import { StoreGetCustomersCustomerOrdersParams } from "./list-orders" const route = Router() @@ -34,7 +34,7 @@ export default (app, container) => { ) // Authenticated endpoints - route.use(middlewares.authenticate()) + route.use(middlewares.requireCustomerAuthentication()) route.get("/me", middlewares.wrap(require("./get-customer").default)) route.post("/me", middlewares.wrap(require("./update-customer").default)) diff --git a/packages/medusa/src/api/routes/store/customers/list-orders.ts b/packages/medusa/src/api/routes/store/customers/list-orders.ts index 59733ef8d7..ae45a84dfc 100644 --- a/packages/medusa/src/api/routes/store/customers/list-orders.ts +++ b/packages/medusa/src/api/routes/store/customers/list-orders.ts @@ -1,8 +1,3 @@ -import { - FulfillmentStatus, - OrderStatus, - PaymentStatus, -} from "../../../../models/order" import { IsEnum, IsNumber, @@ -11,11 +6,15 @@ import { ValidateNested, } from "class-validator" import { Request, Response } from "express" +import { + FulfillmentStatus, + OrderStatus, + PaymentStatus, +} from "../../../../models/order" -import { DateComparisonOperator } from "../../../../types/common" -import { MedusaError } from "medusa-core-utils" -import OrderService from "../../../../services/order" import { Type } from "class-transformer" +import OrderService from "../../../../services/order" +import { DateComparisonOperator } from "../../../../types/common" /** * @oas [get] /customers/me/orders @@ -194,13 +193,6 @@ import { Type } from "class-transformer" export default async (req: Request, res: Response) => { const id: string | undefined = req.user?.customer_id - if (!id) { - throw new MedusaError( - MedusaError.Types.UNEXPECTED_STATE, - "Not authorized to list orders" - ) - } - const orderService: OrderService = req.scope.resolve("orderService") req.filterableFields = { diff --git a/packages/medusa/src/api/routes/store/customers/update-address.ts b/packages/medusa/src/api/routes/store/customers/update-address.ts index 803b2b0830..d059501a15 100644 --- a/packages/medusa/src/api/routes/store/customers/update-address.ts +++ b/packages/medusa/src/api/routes/store/customers/update-address.ts @@ -1,9 +1,9 @@ import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "." -import { AddressPayload } from "../../../../types/common" -import CustomerService from "../../../../services/customer" -import { validator } from "../../../../utils/validator" import { EntityManager } from "typeorm" +import CustomerService from "../../../../services/customer" +import { AddressPayload } from "../../../../types/common" +import { validator } from "../../../../utils/validator" /** * @oas [post] /customers/me/addresses/{address_id} @@ -69,6 +69,7 @@ import { EntityManager } from "typeorm" */ export default async (req, res) => { const id = req.user.customer_id + const { address_id } = req.params const validated = await validator( diff --git a/packages/medusa/src/api/routes/store/customers/update-customer.ts b/packages/medusa/src/api/routes/store/customers/update-customer.ts index 7cc495c550..ee9fcbc074 100644 --- a/packages/medusa/src/api/routes/store/customers/update-customer.ts +++ b/packages/medusa/src/api/routes/store/customers/update-customer.ts @@ -1,11 +1,11 @@ import { IsEmail, IsObject, IsOptional, IsString } from "class-validator" import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "." -import { AddressPayload } from "../../../../types/common" -import CustomerService from "../../../../services/customer" -import { IsType } from "../../../../utils/validators/is-type" -import { validator } from "../../../../utils/validator" import { EntityManager } from "typeorm" +import CustomerService from "../../../../services/customer" +import { AddressPayload } from "../../../../types/common" +import { validator } from "../../../../utils/validator" +import { IsType } from "../../../../utils/validators/is-type" /** * @oas [post] /customers/me diff --git a/packages/medusa/src/api/routes/store/index.js b/packages/medusa/src/api/routes/store/index.js index 926f1a7cfb..07464c5adf 100644 --- a/packages/medusa/src/api/routes/store/index.js +++ b/packages/medusa/src/api/routes/store/index.js @@ -1,15 +1,15 @@ import cors from "cors" import { Router } from "express" import middlewares from "../../middlewares" +import productTypesRoutes from "../admin/product-types" import authRoutes from "./auth" import cartRoutes from "./carts" import collectionRoutes from "./collections" import customerRoutes from "./customers" import giftCardRoutes from "./gift-cards" -import orderRoutes from "./orders" import orderEditRoutes from "./order-edits" +import orderRoutes from "./orders" import productRoutes from "./products" -import productTypesRoutes from "../admin/product-types" import regionRoutes from "./regions" import returnReasonRoutes from "./return-reasons" import returnRoutes from "./returns" diff --git a/packages/medusa/src/helpers/test-request.js b/packages/medusa/src/helpers/test-request.js index fdbc5ae98d..1d27d2d6bf 100644 --- a/packages/medusa/src/helpers/test-request.js +++ b/packages/medusa/src/helpers/test-request.js @@ -99,7 +99,7 @@ export async function request(method, url, opts = {}) { } if (opts.clientSession) { if (opts.clientSession.jwt) { - opts.clientSession.jwt = jwt.sign( + opts.clientSession.jwt_store = jwt.sign( opts.clientSession.jwt, config.projectConfig.jwt_secret, { diff --git a/packages/medusa/src/loaders/passport.ts b/packages/medusa/src/loaders/passport.ts index a3180564cc..30739d6d37 100644 --- a/packages/medusa/src/loaders/passport.ts +++ b/packages/medusa/src/loaders/passport.ts @@ -1,10 +1,10 @@ -import passport from "passport" -import { AuthService } from "../services" import { Express } from "express" -import { ConfigModule, MedusaContainer } from "../types/global" +import passport from "passport" import { Strategy as BearerStrategy } from "passport-http-bearer" import { Strategy as JWTStrategy } from "passport-jwt" import { Strategy as LocalStrategy } from "passport-local" +import { AuthService } from "../services" +import { ConfigModule, MedusaContainer } from "../types/global" export default async ({ app, @@ -46,6 +46,7 @@ export default async ({ // calls will be authenticated based on the JWT const { jwt_secret } = configModule.projectConfig passport.use( + "admin-jwt", new JWTStrategy( { jwtFromRequest: (req) => req.session.jwt, @@ -57,6 +58,19 @@ export default async ({ ) ) + passport.use( + "store-jwt", + new JWTStrategy( + { + jwtFromRequest: (req) => req.session.jwt_store, + secretOrKey: jwt_secret, + }, + async (jwtPayload, done) => { + return done(null, jwtPayload) + } + ) + ) + // Alternatively use bearer token to authenticate to the admin api passport.use( new BearerStrategy(async (token, done) => {