Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(medusa): Separate JWT auth strategies per domain #2646

Merged
merged 19 commits into from
Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/new-zebras-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@medusajs/medusa": patch
"medusa-core-utils": patch
---

jwt fix
1 change: 1 addition & 0 deletions packages/medusa-core-utils/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
8 changes: 6 additions & 2 deletions packages/medusa/src/api/middlewares/authenticate.ts
Original file line number Diff line number Diff line change
@@ -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
)
}
}
3 changes: 3 additions & 0 deletions packages/medusa/src/api/middlewares/error-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions packages/medusa/src/api/middlewares/index.ts
Original file line number Diff line number Diff line change
@@ -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,
}
Original file line number Diff line number Diff line change
@@ -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 => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought(non-blocking): when this is called the authenticateCustomer middleware will already be applied. This means the JWT is parsed. We could just verify with a simple if that the customer has been parsed. Not an important change but just wanted to highlight that this might be duplicate work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, I would just say that a user could use them independently and therefore I am not sure we should go with the idea that we know that internally it has already been parsed. Wdyt?

passport.authenticate(["store-jwt", "bearer"], { session: false })(
req,
res,
next
)
}
}
8 changes: 4 additions & 4 deletions packages/medusa/src/api/routes/admin/auth/create-session.ts
Original file line number Diff line number Diff line change
@@ -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"

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/medusa/src/api/routes/store/auth/create-session.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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!,
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@
* $ref: "#/components/responses/500_error"
*/
export default async (req, res) => {
req.session.jwt = {}
req.session.jwt_store = {}
res.json({})
}
2 changes: 1 addition & 1 deletion packages/medusa/src/api/routes/store/auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Router } from "express"
import { Customer } from "./../../../.."
import middlewares from "../../../middlewares"
import { Customer } from "./../../../.."

const route = Router()

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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",
})

Expand Down
Original file line number Diff line number Diff line change
@@ -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}
Expand Down
6 changes: 3 additions & 3 deletions packages/medusa/src/api/routes/store/customers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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))
Expand Down
22 changes: 7 additions & 15 deletions packages/medusa/src/api/routes/store/customers/list-orders.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
import {
FulfillmentStatus,
OrderStatus,
PaymentStatus,
} from "../../../../models/order"
import {
IsEnum,
IsNumber,
Expand All @@ -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
Expand Down Expand Up @@ -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 = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "."

import { AddressPayload } from "../../../../types/common"
import { EntityManager } from "typeorm"
import CustomerService from "../../../../services/customer"
import { AddressPayload } from "../../../../types/common"
import { validator } from "../../../../utils/validator"
import { EntityManager } from "typeorm"

/**
* @oas [post] /customers/me/addresses/{address_id}
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { IsEmail, IsObject, IsOptional, IsString } from "class-validator"
import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "."

import { AddressPayload } from "../../../../types/common"
import { EntityManager } from "typeorm"
import CustomerService from "../../../../services/customer"
import { IsType } from "../../../../utils/validators/is-type"
import { AddressPayload } from "../../../../types/common"
import { validator } from "../../../../utils/validator"
import { EntityManager } from "typeorm"
import { IsType } from "../../../../utils/validators/is-type"

/**
* @oas [post] /customers/me
Expand Down
4 changes: 2 additions & 2 deletions packages/medusa/src/api/routes/store/index.js
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
2 changes: 1 addition & 1 deletion packages/medusa/src/helpers/test-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
{
Expand Down
20 changes: 17 additions & 3 deletions packages/medusa/src/loaders/passport.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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) => {
Expand Down