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:
authored and GitHub committed 2022-11-22 12:30:05 +00:00
1 parent 6a1df8b1a9
commit e7c4cc3751
21 files changed
+90 -52

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
---
"@medusajs/medusa": patch
"medusa-core-utils": patch
---
jwt fix
+1
View File
@@ -8,6 +8,7 @@ export const MedusaErrorTypes = {
DUPLICATE_ERROR: "duplicate_error", DUPLICATE_ERROR: "duplicate_error",
INVALID_ARGUMENT: "invalid_argument", INVALID_ARGUMENT: "invalid_argument",
INVALID_DATA: "invalid_data", INVALID_DATA: "invalid_data",
UNAUTHORIZED: "unauthorized",
NOT_FOUND: "not_found", NOT_FOUND: "not_found",
NOT_ALLOWED: "not_allowed", NOT_ALLOWED: "not_allowed",
UNEXPECTED_STATE: "unexpected_state", UNEXPECTED_STATE: "unexpected_state",
@@ -1,10 +1,13 @@
import { NextFunction, Request, RequestHandler, Response } from "express"
import passport from "passport" 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 => { export default (): RequestHandler => {
return (req: Request, res: Response, next: NextFunction): void => { return (req: Request, res: Response, next: NextFunction): void => {
passport.authenticate( passport.authenticate(
["jwt", "bearer"], ["store-jwt", "bearer"],
{ session: false }, { session: false },
(err, user) => { (err, user) => {
if (err) { if (err) {
@@ -1,8 +1,12 @@
import { NextFunction, Request, RequestHandler, Response } from "express"
import passport from "passport" import passport from "passport"
import { Request, Response, NextFunction, RequestHandler } from "express"
export default (): RequestHandler => { export default (): RequestHandler => {
return (req: Request, res: Response, next: NextFunction): void => { 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
)
} }
} }
@@ -43,6 +43,9 @@ export default () => {
errObj.message = errObj.message =
"The request conflicted with another request. You may retry the request with the provided Idempotency-Key." "The request conflicted with another request. You may retry the request with the provided Idempotency-Key."
break break
case MedusaError.Types.UNAUTHORIZED:
statusCode = 401
break
case MedusaError.Types.DUPLICATE_ERROR: case MedusaError.Types.DUPLICATE_ERROR:
statusCode = 422 statusCode = 422
errObj.code = INVALID_REQUEST_ERROR errObj.code = INVALID_REQUEST_ERROR
+6 -4
View File
@@ -1,17 +1,19 @@
import { default as authenticateCustomer } from "./authenticate-customer"
import { default as authenticate } from "./authenticate" 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 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 { 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 { doesConditionBelongToDiscount } from "./discount/does-condition-belong-to-discount"
export { transformQuery } from "./transform-query"
export { transformBody } from "./transform-body" export { transformBody } from "./transform-body"
export { transformQuery } from "./transform-query"
export default { export default {
authenticate, authenticate,
authenticateCustomer, authenticateCustomer,
requireCustomerAuthentication,
normalizeQuery, normalizeQuery,
wrap, wrap,
} }
@@ -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
)
}
}
@@ -1,10 +1,10 @@
import { IsEmail, IsNotEmpty, IsString } from "class-validator" 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 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" import { validator } from "../../../../utils/validator"
/** /**
@@ -1,9 +1,9 @@
import { IsEmail, IsNotEmpty } from "class-validator" import { IsEmail, IsNotEmpty } from "class-validator"
import jwt from "jsonwebtoken" import jwt from "jsonwebtoken"
import { EntityManager } from "typeorm"
import AuthService from "../../../../services/auth" import AuthService from "../../../../services/auth"
import CustomerService from "../../../../services/customer" import CustomerService from "../../../../services/customer"
import { validator } from "../../../../utils/validator" import { validator } from "../../../../utils/validator"
import { EntityManager } from "typeorm"
/** /**
* @oas [post] /auth * @oas [post] /auth
@@ -79,7 +79,7 @@ export default async (req, res) => {
const { const {
projectConfig: { jwt_secret }, projectConfig: { jwt_secret },
} = req.scope.resolve("configModule") } = req.scope.resolve("configModule")
req.session.jwt = jwt.sign( req.session.jwt_store = jwt.sign(
{ customer_id: result.customer?.id }, { customer_id: result.customer?.id },
jwt_secret!, jwt_secret!,
{ {
@@ -31,6 +31,6 @@
* $ref: "#/components/responses/500_error" * $ref: "#/components/responses/500_error"
*/ */
export default async (req, res) => { export default async (req, res) => {
req.session.jwt = {} req.session.jwt_store = {}
res.json({}) res.json({})
} }
@@ -1,6 +1,6 @@
import { Router } from "express" import { Router } from "express"
import { Customer } from "./../../../.."
import middlewares from "../../../middlewares" import middlewares from "../../../middlewares"
import { Customer } from "./../../../.."
const route = Router() const route = Router()
@@ -1,10 +1,10 @@
import { Type } from "class-transformer" import { Type } from "class-transformer"
import { ValidateNested } from "class-validator" import { ValidateNested } from "class-validator"
import { EntityManager } from "typeorm"
import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "." import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "."
import CustomerService from "../../../../services/customer" import CustomerService from "../../../../services/customer"
import { AddressCreatePayload } from "../../../../types/common" import { AddressCreatePayload } from "../../../../types/common"
import { validator } from "../../../../utils/validator" import { validator } from "../../../../utils/validator"
import { EntityManager } from "typeorm"
/** /**
* @oas [post] /customers/me/addresses * @oas [post] /customers/me/addresses
@@ -1,11 +1,11 @@
import { IsEmail, IsOptional, IsString } from "class-validator" import { IsEmail, IsOptional, IsString } from "class-validator"
import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "." import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "."
import jwt from "jsonwebtoken"
import { EntityManager } from "typeorm"
import { Customer } from "../../../.." import { Customer } from "../../../.."
import CustomerService from "../../../../services/customer" import CustomerService from "../../../../services/customer"
import jwt from "jsonwebtoken"
import { validator } from "../../../../utils/validator" import { validator } from "../../../../utils/validator"
import { EntityManager } from "typeorm"
/** /**
* @oas [post] /customers * @oas [post] /customers
@@ -121,7 +121,7 @@ export default async (req, res) => {
const { const {
projectConfig: { jwt_secret }, projectConfig: { jwt_secret },
} = req.scope.resolve("configModule") } = 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", expiresIn: "30d",
}) })
@@ -1,7 +1,7 @@
import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "." import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "."
import CustomerService from "../../../../services/customer"
import { EntityManager } from "typeorm" import { EntityManager } from "typeorm"
import CustomerService from "../../../../services/customer"
/** /**
* @oas [delete] /customers/me/addresses/{address_id} * @oas [delete] /customers/me/addresses/{address_id}
@@ -2,11 +2,11 @@ import { Router } from "express"
import { Customer, Order } from "../../../.." import { Customer, Order } from "../../../.."
import { PaginatedResponse } from "../../../../types/common" import { PaginatedResponse } from "../../../../types/common"
import middlewares, { transformQuery } from "../../../middlewares" import middlewares, { transformQuery } from "../../../middlewares"
import { StoreGetCustomersCustomerOrdersParams } from "./list-orders"
import { import {
defaultStoreOrdersRelations,
defaultStoreOrdersFields, defaultStoreOrdersFields,
defaultStoreOrdersRelations,
} from "../orders" } from "../orders"
import { StoreGetCustomersCustomerOrdersParams } from "./list-orders"
const route = Router() const route = Router()
@@ -34,7 +34,7 @@ export default (app, container) => {
) )
// Authenticated endpoints // Authenticated endpoints
route.use(middlewares.authenticate()) route.use(middlewares.requireCustomerAuthentication())
route.get("/me", middlewares.wrap(require("./get-customer").default)) route.get("/me", middlewares.wrap(require("./get-customer").default))
route.post("/me", middlewares.wrap(require("./update-customer").default)) route.post("/me", middlewares.wrap(require("./update-customer").default))
@@ -1,8 +1,3 @@
import {
FulfillmentStatus,
OrderStatus,
PaymentStatus,
} from "../../../../models/order"
import { import {
IsEnum, IsEnum,
IsNumber, IsNumber,
@@ -11,11 +6,15 @@ import {
ValidateNested, ValidateNested,
} from "class-validator" } from "class-validator"
import { Request, Response } from "express" 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 { Type } from "class-transformer"
import OrderService from "../../../../services/order"
import { DateComparisonOperator } from "../../../../types/common"
/** /**
* @oas [get] /customers/me/orders * @oas [get] /customers/me/orders
@@ -194,13 +193,6 @@ import { Type } from "class-transformer"
export default async (req: Request, res: Response) => { export default async (req: Request, res: Response) => {
const id: string | undefined = req.user?.customer_id 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") const orderService: OrderService = req.scope.resolve("orderService")
req.filterableFields = { req.filterableFields = {
@@ -1,9 +1,9 @@
import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "." import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "."
import { AddressPayload } from "../../../../types/common"
import CustomerService from "../../../../services/customer"
import { validator } from "../../../../utils/validator"
import { EntityManager } from "typeorm" 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} * @oas [post] /customers/me/addresses/{address_id}
@@ -69,6 +69,7 @@ import { EntityManager } from "typeorm"
*/ */
export default async (req, res) => { export default async (req, res) => {
const id = req.user.customer_id const id = req.user.customer_id
const { address_id } = req.params const { address_id } = req.params
const validated = await validator( const validated = await validator(
@@ -1,11 +1,11 @@
import { IsEmail, IsObject, IsOptional, IsString } from "class-validator" import { IsEmail, IsObject, IsOptional, IsString } from "class-validator"
import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "." 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 { 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 * @oas [post] /customers/me
@@ -1,15 +1,15 @@
import cors from "cors" import cors from "cors"
import { Router } from "express" import { Router } from "express"
import middlewares from "../../middlewares" import middlewares from "../../middlewares"
import productTypesRoutes from "../admin/product-types"
import authRoutes from "./auth" import authRoutes from "./auth"
import cartRoutes from "./carts" import cartRoutes from "./carts"
import collectionRoutes from "./collections" import collectionRoutes from "./collections"
import customerRoutes from "./customers" import customerRoutes from "./customers"
import giftCardRoutes from "./gift-cards" import giftCardRoutes from "./gift-cards"
import orderRoutes from "./orders"
import orderEditRoutes from "./order-edits" import orderEditRoutes from "./order-edits"
import orderRoutes from "./orders"
import productRoutes from "./products" import productRoutes from "./products"
import productTypesRoutes from "../admin/product-types"
import regionRoutes from "./regions" import regionRoutes from "./regions"
import returnReasonRoutes from "./return-reasons" import returnReasonRoutes from "./return-reasons"
import returnRoutes from "./returns" import returnRoutes from "./returns"
+1 -1
View File
@@ -99,7 +99,7 @@ export async function request(method, url, opts = {}) {
} }
if (opts.clientSession) { if (opts.clientSession) {
if (opts.clientSession.jwt) { if (opts.clientSession.jwt) {
opts.clientSession.jwt = jwt.sign( opts.clientSession.jwt_store = jwt.sign(
opts.clientSession.jwt, opts.clientSession.jwt,
config.projectConfig.jwt_secret, config.projectConfig.jwt_secret,
{ {
+17 -3
View File
@@ -1,10 +1,10 @@
import passport from "passport"
import { AuthService } from "../services"
import { Express } from "express" 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 BearerStrategy } from "passport-http-bearer"
import { Strategy as JWTStrategy } from "passport-jwt" import { Strategy as JWTStrategy } from "passport-jwt"
import { Strategy as LocalStrategy } from "passport-local" import { Strategy as LocalStrategy } from "passport-local"
import { AuthService } from "../services"
import { ConfigModule, MedusaContainer } from "../types/global"
export default async ({ export default async ({
app, app,
@@ -46,6 +46,7 @@ export default async ({
// calls will be authenticated based on the JWT // calls will be authenticated based on the JWT
const { jwt_secret } = configModule.projectConfig const { jwt_secret } = configModule.projectConfig
passport.use( passport.use(
"admin-jwt",
new JWTStrategy( new JWTStrategy(
{ {
jwtFromRequest: (req) => req.session.jwt, 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 // Alternatively use bearer token to authenticate to the admin api
passport.use( passport.use(
new BearerStrategy(async (token, done) => { new BearerStrategy(async (token, done) => {