feat: Destroy session + introduce http config (#7336)

This commit is contained in:
Oli Juhl
2024-05-19 12:40:28 +02:00
committed by GitHub
parent ce75755ac6
commit bf4724b8e6
26 changed files with 568 additions and 396 deletions

View File

@@ -12,7 +12,10 @@ const adminHeaders = {
jest.setTimeout(30000)
medusaIntegrationTestRunner({
env: { MEDUSA_FF_MEDUSA_V2: true },
force_modules_migration: true,
env: {
MEDUSA_FF_MEDUSA_V2: true,
},
testSuite: ({ dbConnection, getContainer, api }) => {
let container
@@ -58,21 +61,48 @@ medusaIntegrationTestRunner({
)
})
// TODO: Remove in V2, as this is no longer supported
it("creates admin JWT token correctly", async () => {
breaking(async () => {
const response = await api
.post("/admin/auth/token", {
email: "admin@medusa.js",
password: "secret_password",
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.access_token).toEqual(expect.any(String))
it("should test the entire authentication lifecycle", async () => {
// sign in
const response = await api.post("/auth/admin/emailpass", {
email: "admin@medusa.js",
password: "secret_password",
})
expect(response.status).toEqual(200)
expect(response.data).toEqual({ token: expect.any(String) })
const headers = {
headers: { ["authorization"]: `Bearer ${response.data.token}` },
}
// convert token to session
const cookieRequest = await api.post("/auth/session", {}, headers)
expect(cookieRequest.status).toEqual(200)
// extract cookie
const [cookie] = cookieRequest.headers["set-cookie"][0].split(";")
const cookieHeader = {
headers: { Cookie: cookie },
}
// perform cookie authenticated request
const authedRequest = await api.get(
"/admin/products?limit=1",
cookieHeader
)
expect(authedRequest.status).toEqual(200)
// sign out
const signOutRequest = await api.delete("/auth/session", cookieHeader)
expect(signOutRequest.status).toEqual(200)
// attempt to perform authenticated request
const unAuthedRequest = await api
.get("/admin/products?limit=1", cookieHeader)
.catch((e) => e)
expect(unAuthedRequest.response.status).toEqual(401)
})
},
})

View File

@@ -24,10 +24,12 @@ module.exports = {
redis_url: redisUrl,
database_url: DB_URL,
database_type: "postgres",
jwt_secret: "test",
cookie_secret: "test",
http_compression: {
enabled: enableResponseCompression,
http: {
compression: {
enabled: enableResponseCompression,
},
jwtSecret: "test",
cookieSecret: "test",
},
},
featureFlags: {
@@ -39,75 +41,70 @@ module.exports = {
options: { ttl: cacheTTL },
},
workflows: true,
// We don't want to load the modules if v2 is not enabled, as they run data operations and migrations on load.
...(enableMedusaV2
? {
[Modules.AUTH]: {
scope: "internal",
resources: "shared",
resolve: "@medusajs/auth",
options: {
providers: [
{
name: "emailpass",
scopes: {
admin: {},
store: {},
},
},
],
[Modules.AUTH]: {
scope: "internal",
resources: "shared",
resolve: "@medusajs/auth",
options: {
providers: [
{
name: "emailpass",
scopes: {
admin: {},
store: {},
},
},
[Modules.USER]: {
scope: "internal",
resources: "shared",
resolve: "@medusajs/user",
],
},
},
[Modules.USER]: {
scope: "internal",
resources: "shared",
resolve: "@medusajs/user",
options: {
jwt_secret: "test",
},
},
[Modules.CACHE]: {
resolve: "@medusajs/cache-inmemory",
options: { ttl: 0 }, // Cache disabled
},
[Modules.STOCK_LOCATION]: {
resolve: "@medusajs/stock-location-next",
options: {},
},
[Modules.INVENTORY]: {
resolve: "@medusajs/inventory-next",
options: {},
},
[Modules.FILE]: {
resolve: "@medusajs/file",
options: {
providers: [
{
resolve: "@medusajs/file-local-next",
options: {
jwt_secret: "test",
config: {
local: {},
},
},
},
[Modules.CACHE]: {
resolve: "@medusajs/cache-inmemory",
options: { ttl: 0 }, // Cache disabled
},
[Modules.STOCK_LOCATION]: {
resolve: "@medusajs/stock-location-next",
options: {},
},
[Modules.INVENTORY]: {
resolve: "@medusajs/inventory-next",
options: {},
},
[Modules.FILE]: {
resolve: "@medusajs/file",
options: {
providers: [
{
resolve: "@medusajs/file-local-next",
options: {
config: {
local: {},
},
},
},
],
},
},
[Modules.PRODUCT]: true,
[Modules.PRICING]: true,
[Modules.PROMOTION]: true,
[Modules.CUSTOMER]: true,
[Modules.SALES_CHANNEL]: true,
[Modules.CART]: true,
[Modules.WORKFLOW_ENGINE]: true,
[Modules.REGION]: true,
[Modules.API_KEY]: true,
[Modules.STORE]: true,
[Modules.TAX]: true,
[Modules.CURRENCY]: true,
[Modules.PAYMENT]: true,
[Modules.FULFILLMENT]: true,
}
: {}),
],
},
},
[Modules.PRODUCT]: true,
[Modules.PRICING]: true,
[Modules.PROMOTION]: true,
[Modules.CUSTOMER]: true,
[Modules.SALES_CHANNEL]: true,
[Modules.CART]: true,
[Modules.WORKFLOW_ENGINE]: true,
[Modules.REGION]: true,
[Modules.API_KEY]: true,
[Modules.STORE]: true,
[Modules.TAX]: true,
[Modules.CURRENCY]: true,
[Modules.PAYMENT]: true,
[Modules.FULFILLMENT]: true,
},
}

View File

@@ -34,7 +34,7 @@ medusaIntegrationTestRunner({
const authService: IAuthModuleService = appContainer.resolve(
ModuleRegistrationName.AUTH
)
const { jwt_secret } =
const { http } =
appContainer.resolve("configModule").projectConfig
const authUser = await authService.create({
entity_id: "store_user",
@@ -42,7 +42,7 @@ medusaIntegrationTestRunner({
scope: "store",
})
const token = jwt.sign(authUser, jwt_secret)
const token = jwt.sign(authUser, http.jwtSecret)
const response = await api.post(
`/store/customers`,

View File

@@ -7,7 +7,7 @@ export const createAuthenticatedCustomer = async (
appContainer: MedusaContainer,
customerData: Partial<CreateCustomerDTO> = {}
) => {
const { jwt_secret } = appContainer.resolve("configModule").projectConfig
const { http } = appContainer.resolve("configModule").projectConfig
const authService = appContainer.resolve(ModuleRegistrationName.AUTH)
const customerModuleService = appContainer.resolve(
ModuleRegistrationName.CUSTOMER
@@ -27,7 +27,7 @@ export const createAuthenticatedCustomer = async (
app_metadata: { customer_id: customer.id },
})
const token = jwt.sign(authUser, jwt_secret)
const token = jwt.sign(authUser, http.jwtSecret)
return { customer, authUser, jwt: token }
}

View File

@@ -38,8 +38,10 @@ module.exports = {
projectConfig: {
database_url: DB_URL,
database_type: "postgres",
jwt_secret: "test",
cookie_secret: "test",
http: {
jwtSecret: "test",
cookieSecret: "test",
},
},
featureFlags: {
medusa_v2: enableMedusaV2,

View File

@@ -19,6 +19,7 @@ import {
UIMatch,
useLocation,
useMatches,
useNavigate,
} from "react-router-dom"
import { Skeleton } from "../../common/skeleton"
@@ -27,6 +28,8 @@ import { useMe } from "../../../hooks/api/users"
import { useSearch } from "../../../providers/search-provider"
import { useSidebar } from "../../../providers/sidebar-provider"
import { useTheme } from "../../../providers/theme-provider"
import { useLogout } from "../../../hooks/api/auth"
import { queryClient } from "../../../lib/medusa"
export const Shell = ({ children }: PropsWithChildren) => {
return (
@@ -200,20 +203,19 @@ const ThemeToggle = () => {
}
const Logout = () => {
// const navigate = useNavigate()
// const { mutateAsync: logoutMutation } = useAdminDeleteSession()
const navigate = useNavigate()
const { mutateAsync: logoutMutation } = useLogout()
const handleLayout = async () => {
// await logoutMutation(undefined, {
// onSuccess: () => {
// /**
// * When the user logs out, we want to clear the query cache
// */
// queryClient.clear()
// navigate("/login")
// },
// })
// noop
await logoutMutation(undefined, {
onSuccess: () => {
/**
* When the user logs out, we want to clear the query cache
*/
queryClient.clear()
navigate("/login")
},
})
}
return (

View File

@@ -14,3 +14,10 @@ export const useEmailPassLogin = (
...options,
})
}
export const useLogout = (options?: UseMutationOptions<void, Error>) => {
return useMutation({
mutationFn: () => sdk.auth.logout(),
...options,
})
}

View File

@@ -30,4 +30,12 @@ export class Auth {
this.client.setToken(token)
}
}
logout = async () => {
await this.client.fetch("/auth/session", {
method: "DELETE",
})
this.client.clearToken()
}
}

View File

@@ -109,6 +109,28 @@ export class Client {
this.setToken_(token)
}
clearToken() {
this.clearToken_()
}
protected clearToken_() {
const { storageMethod, storageKey } = this.getTokenStorageInfo_()
switch (storageMethod) {
case "local": {
window.localStorage.removeItem(storageKey)
break
}
case "session": {
window.sessionStorage.removeItem(storageKey)
break
}
case "memory": {
this.token = ""
break
}
}
}
protected initClient(): ClientFetch {
const defaultHeaders = new Headers({
"content-type": "application/json",

View File

@@ -67,7 +67,7 @@ type SessionOptions = {
*/
saveUninitialized?: boolean
/**
* The secret to sign the session ID cookie. By default, the value of `cookie_secret` is used.
* The secret to sign the session ID cookie. By default, the value of `http.cookieSecret` is used.
* Refer to [express-sessions documentation](https://www.npmjs.com/package/express-session#secret) for details.
*/
secret?: string
@@ -111,193 +111,6 @@ export type HttpCompressionOptions = {
* Essential configurations related to the Medusa backend, such as database and CORS configurations.
*/
export type ProjectConfigOptions = {
/**
* The Medusa backends API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backends API Routes.
*
* `store_cors` is a string used to specify the accepted URLs or patterns for store API Routes. It can either be one accepted origin, or a comma-separated list of accepted origins.
*
* Every origin in that list must either be:
*
* 1. A URL. For example, `http://localhost:8000`. The URL must not end with a backslash;
* 2. Or a regular expression pattern that can match more than one origin. For example, `.example.com`. The regex pattern that the backend tests for is `^([\/~@;%#'])(.*?)\1([gimsuy]*)$`.
*
* @example
* Some example values of common use cases:
*
* ```bash
* # Allow different ports locally starting with 800
* STORE_CORS=/http:\/\/localhost:800\d+$/
*
* # Allow any origin ending with vercel.app. For example, storefront.vercel.app
* STORE_CORS=/vercel\.app$/
*
* # Allow all HTTP requests
* STORE_CORS=/http:\/\/.+/
* ```
*
* Then, set the configuration in `medusa-config.js`:
*
* ```js title="medusa-config.js"
* module.exports = {
* projectConfig: {
* store_cors: process.env.STORE_CORS,
* // ...
* },
* // ...
* }
* ```
*
* If youre adding the value directly within `medusa-config.js`, make sure to add an extra escaping `/` for every backslash in the pattern. For example:
*
* ```js title="medusa-config.js"
* module.exports = {
* projectConfig: {
* store_cors: "/vercel\\.app$/",
* // ...
* },
* // ...
* }
* ```
*/
store_cors?: string
/**
* The Medusa backends API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backends API Routes.
*
* `admin_cors` is a string used to specify the accepted URLs or patterns for admin API Routes. It can either be one accepted origin, or a comma-separated list of accepted origins.
*
* Every origin in that list must either be:
*
* 1. A URL. For example, `http://localhost:7001`. The URL must not end with a backslash;
* 2. Or a regular expression pattern that can match more than one origin. For example, `.example.com`. The regex pattern that the backend tests for is `^([\/~@;%#'])(.*?)\1([gimsuy]*)$`.
*
* @example
* Some example values of common use cases:
*
* ```bash
* # Allow different ports locally starting with 700
* ADMIN_CORS=/http:\/\/localhost:700\d+$/
*
* # Allow any origin ending with vercel.app. For example, admin.vercel.app
* ADMIN_CORS=/vercel\.app$/
*
* # Allow all HTTP requests
* ADMIN_CORS=/http:\/\/.+/
* ```
*
* Then, set the configuration in `medusa-config.js`:
*
* ```js title="medusa-config.js"
* module.exports = {
* projectConfig: {
* admin_cors: process.env.ADMIN_CORS,
* // ...
* },
* // ...
* }
* ```
*
* If youre adding the value directly within `medusa-config.js`, make sure to add an extra escaping `/` for every backslash in the pattern. For example:
*
* ```js title="medusa-config.js"
* module.exports = {
* projectConfig: {
* admin_cors: "/http:\\/\\/localhost:700\\d+$/",
* // ...
* },
* // ...
* }
* ```
*/
admin_cors?: string
/**
* The Medusa backends API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backends API Routes.
*
* `auth_cors` is a string used to specify the accepted URLs or patterns for API Routes starting with `/auth`. It can either be one accepted origin, or a comma-separated list of accepted origins.
*
* Every origin in that list must either be:
*
* 1. A URL. For example, `http://localhost:7001`. The URL must not end with a backslash;
* 2. Or a regular expression pattern that can match more than one origin. For example, `.example.com`. The regex pattern that the backend tests for is `^([\/~@;%#'])(.*?)\1([gimsuy]*)$`.
*
* @example
* Some example values of common use cases:
*
* ```bash
* # Allow different ports locally starting with 700
* AUTH_CORS=/http:\/\/localhost:700\d+$/
*
* # Allow any origin ending with vercel.app. For example, admin.vercel.app
* AUTH_CORS=/vercel\.app$/
*
* # Allow all HTTP requests
* AUTH_CORS=/http:\/\/.+/
* ```
*
* Then, set the configuration in `medusa-config.js`:
*
* ```js title="medusa-config.js"
* module.exports = {
* projectConfig: {
* auth_cors: process.env.AUTH_CORS,
* // ...
* },
* // ...
* }
* ```
*
* If youre adding the value directly within `medusa-config.js`, make sure to add an extra escaping `/` for every backslash in the pattern. For example:
*
* ```js title="medusa-config.js"
* module.exports = {
* projectConfig: {
* auth_cors: "/http:\\/\\/localhost:700\\d+$/",
* // ...
* },
* // ...
* }
* ```
*/
auth_cors?: string
/**
* A random string used to create cookie tokens. Although this configuration option is not required, its highly recommended to set it for better security.
*
* In a development environment, if this option is not set, the default secret is `supersecret` However, in production, if this configuration is not set, an error is thrown and
* the backend crashes.
*
* @example
* ```js title="medusa-config.js"
* module.exports = {
* projectConfig: {
* cookie_secret: process.env.COOKIE_SECRET ||
* "supersecret",
* // ...
* },
* // ...
* }
* ```
*/
cookie_secret?: string
/**
* A random string used to create authentication tokens. Although this configuration option is not required, its highly recommended to set it for better security.
*
* In a development environment, if this option is not set the default secret is `supersecret` However, in production, if this configuration is not set an error, an
* error is thrown and the backend crashes.
*
* @example
* ```js title="medusa-config.js"
* module.exports = {
* projectConfig: {
* jwt_secret: process.env.JWT_SECRET ||
* "supersecret",
* // ...
* },
* // ...
* }
* ```
*/
jwt_secret?: string
/**
* The name of the database to connect to. If specified in `database_url`, then its not required to include it.
*
@@ -562,6 +375,7 @@ export type ProjectConfigOptions = {
session_options?: SessionOptions
/**
* @deprecated - use `http.compression` instead
* Configure HTTP compression from the application layer. If you have access to the HTTP server, the recommended approach would be to enable it there.
* However, some platforms don't offer access to the HTTP layer and in those cases, this is a good alternative.
*
@@ -624,6 +438,268 @@ export type ProjectConfigOptions = {
* ```
*/
worker_mode?: "shared" | "worker" | "server"
/**
* Configure the application's http-specific settings
*
* @example
* ```js title="medusa-config.js"
* module.exports = {
* projectConfig: {
* http: {
* cookieSecret: "some-super-secret",
* compression: { ... },
* }
* // ...
* },
* // ...
* }
* ```
*/
http: {
/**
* A random string used to create authentication tokens in the http layer. Although this configuration option is not required, its highly recommended to set it for better security.
*
* In a development environment, if this option is not set the default secret is `supersecret` However, in production, if this configuration is not set an error, an
* error is thrown and the backend crashes.
*
* @example
* ```js title="medusa-config.js"
* module.exports = {
* projectConfig: {
* http: {
* cookieSecret: "supersecret"
* }
* },
* // ...
* }
* ```
*/
jwtSecret?: string
/**
* The expiration time for the JWT token. If not provided, the default value is `24h`.
*
* @example
* ```js title="medusa-config.js"
* module.exports = {
* projectConfig: {
* http: {
* jwtExpiresIn: "2d"
* }
* },
* // ...
* }
* ```
*/
jwtExpiresIn?: string
/**
* A random string used to create cookie tokens in the http layer. Although this configuration option is not required, its highly recommended to set it for better security.
*
* In a development environment, if this option is not set, the default secret is `supersecret` However, in production, if this configuration is not set, an error is thrown and
* the backend crashes.
*
* @example
* ```js title="medusa-config.js"
* module.exports = {
* projectConfig: {
* http: {
* cookieSecret: "supersecret"
* }
* // ...
* },
* // ...
* }
* ```
*/
cookieSecret?: string
/**
* The Medusa backends API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backends API Routes.
*
* `cors` is a string used to specify the accepted URLs or patterns for API Routes starting with `/auth`. It can either be one accepted origin, or a comma-separated list of accepted origins.
*
* Every origin in that list must either be:
*
* 1. A URL. For example, `http://localhost:7001`. The URL must not end with a backslash;
* 2. Or a regular expression pattern that can match more than one origin. For example, `.example.com`. The regex pattern that the backend tests for is `^([\/~@;%#'])(.*?)\1([gimsuy]*)$`.
*
* @example
* Some example values of common use cases:
*
* ```bash
* # Allow different ports locally starting with 700
* AUTH_CORS=/http:\/\/localhost:700\d+$/
*
* # Allow any origin ending with vercel.app. For example, admin.vercel.app
* AUTH_CORS=/vercel\.app$/
*
* # Allow all HTTP requests
* AUTH_CORS=/http:\/\/.+/
* ```
*
* Then, set the configuration in `medusa-config.js`:
*
* ```js title="medusa-config.js"
* module.exports = {
* projectConfig: {
* http: {
* authCors: process.env.AUTH_CORS
* }
* // ...
* },
* // ...
* }
* ```
*
* If youre adding the value directly within `medusa-config.js`, make sure to add an extra escaping `/` for every backslash in the pattern. For example:
*
* ```js title="medusa-config.js"
* module.exports = {
* projectConfig: {
* http: {
* authCors: "/http:\\/\\/localhost:700\\d+$/",
* }
* // ...
* },
* // ...
* }
* ```
*/
authCors: string
/**
*
* Configure HTTP compression from the application layer. If you have access to the HTTP server, the recommended approach would be to enable it there.
* However, some platforms don't offer access to the HTTP layer and in those cases, this is a good alternative.
*
* Its value is an object that has the following properties:
*
* If you enable HTTP compression and you want to disable it for specific API Routes, you can pass in the request header `"x-no-compression": true`.
*
* @example
* ```js title="medusa-config.js"
* module.exports = {
* projectConfig: {
* http: {
* compression: {
* enabled: true,
* level: 6,
* memLevel: 8,
* threshold: 1024,
* }
* },
* // ...
* },
* // ...
* }
* ```
*/
compression?: HttpCompressionOptions
/**
* The Medusa backends API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backends API Routes.
*
* `store_cors` is a string used to specify the accepted URLs or patterns for store API Routes. It can either be one accepted origin, or a comma-separated list of accepted origins.
*
* Every origin in that list must either be:
*
* 1. A URL. For example, `http://localhost:8000`. The URL must not end with a backslash;
* 2. Or a regular expression pattern that can match more than one origin. For example, `.example.com`. The regex pattern that the backend tests for is `^([\/~@;%#'])(.*?)\1([gimsuy]*)$`.
*
* @example
* Some example values of common use cases:
*
* ```bash
* # Allow different ports locally starting with 800
* STORE_CORS=/http:\/\/localhost:800\d+$/
*
* # Allow any origin ending with vercel.app. For example, storefront.vercel.app
* STORE_CORS=/vercel\.app$/
*
* # Allow all HTTP requests
* STORE_CORS=/http:\/\/.+/
* ```
*
* Then, set the configuration in `medusa-config.js`:
*
* ```js title="medusa-config.js"
* module.exports = {
* projectConfig: {
* http: {
* storeCors: process.env.STORE_CORS,
* }
* // ...
* },
* // ...
* }
* ```
*
* If youre adding the value directly within `medusa-config.js`, make sure to add an extra escaping `/` for every backslash in the pattern. For example:
*
* ```js title="medusa-config.js"
* module.exports = {
* projectConfig: {
* http: {
* storeCors: "/vercel\\.app$/",
* }
* // ...
* },
* // ...
* }
* ```
*/
storeCors: string
/**
* The Medusa backends API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backends API Routes.
*
* `admin_cors` is a string used to specify the accepted URLs or patterns for admin API Routes. It can either be one accepted origin, or a comma-separated list of accepted origins.
*
* Every origin in that list must either be:
*
* 1. A URL. For example, `http://localhost:7001`. The URL must not end with a backslash;
* 2. Or a regular expression pattern that can match more than one origin. For example, `.example.com`. The regex pattern that the backend tests for is `^([\/~@;%#'])(.*?)\1([gimsuy]*)$`.
*
* @example
* Some example values of common use cases:
*
* ```bash
* # Allow different ports locally starting with 700
* ADMIN_CORS=/http:\/\/localhost:700\d+$/
*
* # Allow any origin ending with vercel.app. For example, admin.vercel.app
* ADMIN_CORS=/vercel\.app$/
*
* # Allow all HTTP requests
* ADMIN_CORS=/http:\/\/.+/
* ```
*
* Then, set the configuration in `medusa-config.js`:
*
* ```js title="medusa-config.js"
* module.exports = {
* projectConfig: {
* http: {
* adminCors: process.env.ADMIN_CORS,
* }
* // ...
* },
* // ...
* }
* ```
*
* If youre adding the value directly within `medusa-config.js`, make sure to add an extra escaping `/` for every backslash in the pattern. For example:
*
* ```js title="medusa-config.js"
* module.exports = {
* projectConfig: {
* http: {
* adminCors: process.env.ADMIN_CORS,
* }
* // ...
* },
* // ...
* }
* ```
*/
adminCors: string
}
}
/**

View File

@@ -53,19 +53,28 @@ export const POST = async (
userData: req.validatedBody,
authUserId: req.auth.auth_user_id,
},
throwOnError: false,
}
const { errors } = await createUserAccountWorkflow(req.scope).run(input)
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
const { result } = await createUserAccountWorkflow(req.scope).run(input)
const user = await refetchUser(
req.auth.auth_user_id,
req.scope,
req.remoteQueryConfig.fields
)
const { jwt_secret } = req.scope.resolve(
const { http } = req.scope.resolve(
ContainerRegistrationKeys.CONFIG_MODULE
).projectConfig
const token = jwt.sign(user, jwt_secret)
const token = jwt.sign(user, http.jwtSecret, {
expiresIn: http.jwtExpiresIn,
})
res.status(200).json({ user, token })
}

View File

@@ -25,9 +25,11 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const { success, error, authUser, successRedirectUrl } = authResult
if (success) {
const { jwt_secret } = req.scope.resolve("configModule").projectConfig
const { http } = req.scope.resolve("configModule").projectConfig
const token = jwt.sign(authUser, jwt_secret)
const { jwtSecret, jwtExpiresIn } = http
const token = jwt.sign(authUser, jwtSecret, { expiresIn: jwtExpiresIn })
if (successRedirectUrl) {
const url = new URL(successRedirectUrl!)

View File

@@ -30,8 +30,11 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
}
if (success) {
const { jwt_secret } = req.scope.resolve("configModule").projectConfig
const token = jwt.sign(authUser, jwt_secret)
const { http } = req.scope.resolve("configModule").projectConfig
const token = jwt.sign(authUser, http.jwtSecret, {
expiresIn: http.jwtExpiresIn,
})
return res.status(200).json({ token })
}

View File

@@ -7,6 +7,11 @@ export const authRoutesMiddlewares: MiddlewareRoute[] = [
matcher: "/auth/session",
middlewares: [authenticate(/.*/, "bearer")],
},
{
method: ["DELETE"],
matcher: "/auth/session",
middlewares: [authenticate(/.*/, ["session"])],
},
{
method: ["POST"],
matcher: "/auth/:scope/:auth_provider/callback",

View File

@@ -11,3 +11,11 @@ export const POST = async (
res.status(200).json({ user: req.auth })
}
export const DELETE = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
req.session.destroy()
res.json({ success: true })
}

View File

@@ -1,6 +1,6 @@
import { ConfigModule } from "@medusajs/types"
import { getConfigFile, isDefined } from "medusa-core-utils"
import logger from "./logger"
import { ConfigModule } from "@medusajs/types"
const isProduction = ["production", "prod"].includes(process.env.NODE_ENV || "")
@@ -18,47 +18,55 @@ export const handleConfigError = (error: Error): void => {
process.exit(1)
}
export default (rootDirectory: string): ConfigModule => {
const { configModule, error } = getConfigFile<ConfigModule>(
rootDirectory,
`medusa-config`
)
const buildHttpConfig = (projectConfig: ConfigModule["projectConfig"]) => {
const http = projectConfig.http ?? {}
if (error) {
handleConfigError(error)
http.jwtExpiresIn = http?.jwtExpiresIn ?? "1d"
http.authCors = http.authCors ?? ""
http.storeCors = http.storeCors ?? ""
http.adminCors = http.adminCors ?? ""
http.jwtSecret = http?.jwtSecret ?? process.env.JWT_SECRET
if (!http.jwtSecret) {
errorHandler(
`[medusa-config] ⚠️ http.jwtSecret not found.${
isProduction ? "" : "Using default 'supersecret'."
}`
)
http.jwtSecret = "supersecret"
}
if (!configModule?.projectConfig?.redis_url) {
http.cookieSecret =
projectConfig.http?.cookieSecret ?? process.env.COOKIE_SECRET
if (!http.cookieSecret) {
errorHandler(
`[medusa-config] ⚠️ http.cookieSecret not found.${
isProduction ? "" : " Using default 'supersecret'."
}`
)
http.cookieSecret = "supersecret"
}
return http
}
const normalizeProjectConfig = (
projectConfig: ConfigModule["projectConfig"]
) => {
if (!projectConfig?.redis_url) {
console.log(
`[medusa-config] ⚠️ redis_url not found. A fake redis instance will be used.`
)
}
const jwt_secret =
configModule?.projectConfig?.jwt_secret ?? process.env.JWT_SECRET
if (!jwt_secret) {
errorHandler(
`[medusa-config] ⚠️ jwt_secret not found.${
isProduction
? ""
: " fallback to either cookie_secret or default 'supersecret'."
}`
)
}
projectConfig.http = buildHttpConfig(projectConfig)
const cookie_secret =
configModule?.projectConfig?.cookie_secret ?? process.env.COOKIE_SECRET
if (!cookie_secret) {
errorHandler(
`[medusa-config] ⚠️ cookie_secret not found.${
isProduction
? ""
: " fallback to either cookie_secret or default 'supersecret'."
}`
)
}
let worker_mode = projectConfig?.worker_mode
let worker_mode = configModule?.projectConfig?.worker_mode
if (!isDefined(worker_mode)) {
const env = process.env.MEDUSA_WORKER_MODE
if (isDefined(env)) {
@@ -71,12 +79,25 @@ export default (rootDirectory: string): ConfigModule => {
}
return {
projectConfig: {
jwt_secret: jwt_secret ?? "supersecret",
cookie_secret: cookie_secret ?? "supersecret",
...configModule?.projectConfig,
worker_mode,
},
...projectConfig,
worker_mode,
}
}
export default (rootDirectory: string): ConfigModule => {
const { configModule, error } = getConfigFile<ConfigModule>(
rootDirectory,
`medusa-config`
)
if (error) {
handleConfigError(error)
}
const projectConfig = normalizeProjectConfig(configModule.projectConfig)
return {
projectConfig,
admin: configModule?.admin ?? {},
modules: configModule.modules ?? {},
featureFlags: configModule?.featureFlags ?? {},

View File

@@ -1,10 +1,10 @@
import { ConfigModule } from "@medusajs/types"
import createStore from "connect-redis"
import cookieParser from "cookie-parser"
import { Express } from "express"
import session from "express-session"
import morgan from "morgan"
import Redis from "ioredis"
import { ConfigModule } from "@medusajs/types"
import morgan from "morgan"
type Options = {
app: Express
@@ -28,14 +28,14 @@ export default async ({
sameSite = "none"
}
const { cookie_secret, session_options } = configModule.projectConfig
const { http, session_options } = configModule.projectConfig
const sessionOpts = {
name: session_options?.name ?? "connect.sid",
resave: session_options?.resave ?? true,
rolling: session_options?.rolling ?? false,
saveUninitialized: session_options?.saveUninitialized ?? true,
proxy: true,
secret: session_options?.secret ?? cookie_secret,
secret: session_options?.secret ?? http?.cookieSecret,
cookie: {
sameSite,
secure,

View File

@@ -4,11 +4,13 @@ export const storeGlobalMiddlewareMock = jest.fn()
export const config = {
projectConfig: {
store_cors: "http://localhost:8000",
admin_cors: "http://localhost:7001",
database_logging: false,
jwt_secret: "supersecret",
cookie_secret: "superSecret",
http: {
storeCors: "http://localhost:8000",
adminCors: "http://localhost:7001",
jwtSecret: "supersecret",
cookieSecret: "superSecret",
},
},
featureFlags: {},
plugins: [],

View File

@@ -124,7 +124,7 @@ export const createServer = async (rootDir) => {
user_id: opts.adminSession.userId || opts.adminSession.jwt?.userId,
domain: "admin",
},
config.projectConfig.jwt_secret
config.projectConfig.http.jwtSecret
)
headers.Authorization = `Bearer ${token}`
@@ -137,7 +137,7 @@ export const createServer = async (rootDir) => {
opts.clientSession.jwt?.customer_id,
domain: "store",
},
config.projectConfig.jwt_secret
config.projectConfig.http.jwtSecret
)
headers.Authorization = `Bearer ${token}`

View File

@@ -1,30 +1,30 @@
import { ConfigModule } from "@medusajs/types"
import { promiseAll, wrapHandler } from "@medusajs/utils"
import cors from "cors"
import { type Express, json, Router, text, urlencoded } from "express"
import { Router, json, text, urlencoded, type Express } from "express"
import { readdir } from "fs/promises"
import { parseCorsOrigins } from "medusa-core-utils"
import { extname, join, sep } from "path"
import { MedusaRequest, MedusaResponse } from "../../../types/routing"
import {
authenticateCustomer,
authenticateLegacy,
errorHandler,
requireCustomerAuthentication,
} from "../../../utils/middlewares"
import { MedusaRequest, MedusaResponse } from "../../../types/routing"
import logger from "../../logger"
import {
AsyncRouteHandler,
GlobalMiddlewareDescriptor,
HTTP_METHODS,
MiddlewareRoute,
MiddlewaresConfig,
MiddlewareVerb,
MiddlewaresConfig,
ParserConfigArgs,
RouteConfig,
RouteDescriptor,
RouteVerb,
} from "./types"
import { ConfigModule } from "@medusajs/types"
const log = ({
activityId,
@@ -610,7 +610,7 @@ export class RoutesLoader {
descriptor.route,
cors({
origin: parseCorsOrigins(
this.configModule.projectConfig.admin_cors || ""
this.configModule.projectConfig.http.adminCors
),
credentials: true,
})
@@ -625,7 +625,7 @@ export class RoutesLoader {
descriptor.route,
cors({
origin: parseCorsOrigins(
this.configModule.projectConfig.auth_cors || ""
this.configModule.projectConfig.http.authCors
),
credentials: true,
})
@@ -640,7 +640,7 @@ export class RoutesLoader {
descriptor.route,
cors({
origin: parseCorsOrigins(
this.configModule.projectConfig.store_cors || ""
this.configModule.projectConfig.http.storeCors
),
credentials: true,
})

View File

@@ -3,9 +3,6 @@ import {
MedusaAppMigrateDown,
MedusaAppMigrateUp,
MedusaAppOutput,
MedusaModule,
MODULE_PACKAGE_NAMES,
Modules,
ModulesDefinition,
} from "@medusajs/modules-sdk"
import {
@@ -188,29 +185,6 @@ export const loadMedusaApp = async (
injectedDependencies,
})
// TODO: Remove this and make it more dynamic on ensuring all modules are loaded.
const requiredModuleKeys = [Modules.PRODUCT, Modules.PRICING]
const missingPackages: string[] = []
for (const requiredModuleKey of requiredModuleKeys) {
const isModuleInstalled = MedusaModule.isInstalled(requiredModuleKey)
if (!isModuleInstalled) {
missingPackages.push(
MODULE_PACKAGE_NAMES[requiredModuleKey] || requiredModuleKey
)
}
}
if (missingPackages.length) {
throw new Error(
`Medusa requires the following packages/module registration: (${missingPackages.join(
", "
)})`
)
}
if (!config.registerInContainer) {
return medusaApp
}

View File

@@ -41,7 +41,7 @@ export default async ({
// After a user has authenticated a JWT will be placed on a cookie, all
// calls will be authenticated based on the JWT
const { jwt_secret } = configModule.projectConfig
const { http } = configModule.projectConfig
passport.use(
"admin-session",
new CustomStrategy(async (req, done) => {
@@ -97,7 +97,7 @@ export default async ({
new JWTStrategy(
{
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: jwt_secret,
secretOrKey: http.jwtSecret,
},
(token, done) => {
if (token.domain !== "admin") {
@@ -121,7 +121,7 @@ export default async ({
new JWTStrategy(
{
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: jwt_secret,
secretOrKey: http.jwtSecret,
},
(token, done) => {
if (token.domain !== "store") {

View File

@@ -1,7 +1,7 @@
import { PaymentWebhookEvents } from "@medusajs/utils"
import { IPaymentModuleService, ProviderWebhookPayload } from "@medusajs/types"
import { SubscriberArgs, SubscriberConfig } from "../types/subscribers"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPaymentModuleService, ProviderWebhookPayload } from "@medusajs/types"
import { PaymentWebhookEvents } from "@medusajs/utils"
import { SubscriberArgs, SubscriberConfig } from "../types/subscribers"
type SerializedBuffer = {
data: ArrayBuffer

View File

@@ -1,13 +1,8 @@
import { Request, Response, NextFunction } from "express"
import { HttpCompressionOptions, ProjectConfigOptions } from "@medusajs/types"
import compression from "compression"
import { Logger } from "@medusajs/types"
import {
ProjectConfigOptions,
HttpCompressionOptions,
} from "@medusajs/types"
import { Request, Response } from "express"
export function shouldCompressResponse(req: Request, res: Response) {
const logger: Logger = req.scope.resolve("logger")
const { projectConfig } = req.scope.resolve("configModule")
const { enabled } = compressionOptions(projectConfig)
@@ -27,9 +22,10 @@ export function shouldCompressResponse(req: Request, res: Response) {
export function compressionOptions(
config: ProjectConfigOptions
): HttpCompressionOptions {
const responseCompressionOptions = config.http_compression ?? {}
const responseCompressionOptions = config.http.compression ?? {}
responseCompressionOptions.enabled = responseCompressionOptions.enabled ?? false
responseCompressionOptions.enabled =
responseCompressionOptions.enabled ?? false
responseCompressionOptions.level = responseCompressionOptions.level ?? 6
responseCompressionOptions.memLevel = responseCompressionOptions.memLevel ?? 8
responseCompressionOptions.threshold =

View File

@@ -1,5 +1,10 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { ApiKeyDTO, AuthUserDTO, IApiKeyModuleService } from "@medusajs/types"
import {
ApiKeyDTO,
AuthUserDTO,
ConfigModule,
IApiKeyModuleService,
} from "@medusajs/types"
import { stringEqualsOrRegexMatch } from "@medusajs/utils"
import { NextFunction, RequestHandler } from "express"
import jwt, { JwtPayload } from "jsonwebtoken"
@@ -55,10 +60,11 @@ export const authenticate = (
)
if (!authUser) {
const { jwt_secret } = req.scope.resolve("configModule").projectConfig
const { http } =
req.scope.resolve<ConfigModule>("configModule").projectConfig
authUser = getAuthUserFromJwtToken(
req.headers.authorization,
jwt_secret,
http.jwtSecret!,
authTypes,
authScope
)

View File

@@ -8,6 +8,8 @@ import {
import { AuthUserService } from "@services"
import Scrypt from "scrypt-kdf"
const EXPIRATION = "1d"
class EmailPasswordProvider extends AbstractAuthModuleProvider {
public static PROVIDER = "emailpass"
public static DISPLAY_NAME = "Email/Password Authentication"