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>
This commit is contained in:
Philip Korsholm
2022-11-22 13:30:05 +01:00
committed by GitHub
parent 6a1df8b1a9
commit e7c4cc3751
21 changed files with 90 additions and 52 deletions

View File

@@ -0,0 +1,6 @@
---
"@medusajs/medusa": patch
"medusa-core-utils": patch
---
jwt fix

View File

@@ -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",

View File

@@ -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) {

View File

@@ -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
)
}
}

View File

@@ -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

View File

@@ -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,
}

View File

@@ -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
)
}
}

View File

@@ -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"
/**

View File

@@ -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!,
{

View File

@@ -31,6 +31,6 @@
* $ref: "#/components/responses/500_error"
*/
export default async (req, res) => {
req.session.jwt = {}
req.session.jwt_store = {}
res.json({})
}

View File

@@ -1,6 +1,6 @@
import { Router } from "express"
import { Customer } from "./../../../.."
import middlewares from "../../../middlewares"
import { Customer } from "./../../../.."
const route = Router()

View File

@@ -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

View File

@@ -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",
})

View File

@@ -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}

View File

@@ -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))

View File

@@ -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 = {

View File

@@ -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(

View File

@@ -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

View File

@@ -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"

View File

@@ -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,
{

View File

@@ -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) => {