feat: add Medusa Cloud OAuth provider (#14395)

* feat: add Medusa Cloud OAuth provider

* add Cloud login button

* fetch whether cloud auth is enabled through api

* allow unregistered to get session

* handle existing users

* address PR comments

* prevent double execution

* a few more fixes

* fix callback url

* fix spelling

* refresh session

* 200 instead of 201

* only allow cloud identities to create user

* fix condition
This commit is contained in:
Pedro Guzman
2025-12-30 17:30:10 +01:00
committed by GitHub
parent 499dec6d31
commit 001923da2b
27 changed files with 1327 additions and 23 deletions

View File

@@ -2013,18 +2013,23 @@ describe("defineConfig", function () {
it("should add cloud options to the project config and relevant modules if the environment variables are set", function () {
const originalEnv = { ...process.env }
process.env.MEDUSA_BACKEND_URL = "test-backend-url"
process.env.MEDUSA_CLOUD_ENVIRONMENT_HANDLE = "test-environment"
process.env.MEDUSA_CLOUD_API_KEY = "test-api-key"
process.env.MEDUSA_CLOUD_EMAILS_ENDPOINT = "test-emails-endpoint"
process.env.MEDUSA_CLOUD_PAYMENTS_ENDPOINT = "test-payments-endpoint"
process.env.MEDUSA_CLOUD_WEBHOOK_SECRET = "test-webhook-secret"
process.env.MEDUSA_CLOUD_OAUTH_AUTHORIZE_ENDPOINT =
"test-oauth-authorize-endpoint"
process.env.MEDUSA_CLOUD_OAUTH_TOKEN_ENDPOINT = "test-oauth-token-endpoint"
process.env.MEDUSA_CLOUD_OAUTH_DISABLED = "true"
const config = defineConfig()
process.env = { ...originalEnv }
expect(config).toMatchInlineSnapshot(`
{
"admin": {
"backendUrl": "/",
"backendUrl": "test-backend-url",
"path": "/app",
},
"featureFlags": {},
@@ -2035,6 +2040,15 @@ describe("defineConfig", function () {
},
"auth": {
"options": {
"cloud": {
"api_key": "test-api-key",
"callback_url": "test-backend-url/app/login?auth_provider=cloud",
"disabled": true,
"environment_handle": "test-environment",
"oauth_authorize_endpoint": "test-oauth-authorize-endpoint",
"oauth_token_endpoint": "test-oauth-token-endpoint",
"sandbox_handle": undefined,
},
"providers": [
{
"id": "emailpass",
@@ -2176,6 +2190,10 @@ describe("defineConfig", function () {
"apiKey": "test-api-key",
"emailsEndpoint": "test-emails-endpoint",
"environmentHandle": "test-environment",
"oauthAuthorizeEndpoint": "test-oauth-authorize-endpoint",
"oauthCallbackUrl": undefined,
"oauthDisabled": true,
"oauthTokenEndpoint": "test-oauth-token-endpoint",
"paymentsEndpoint": "test-payments-endpoint",
"sandboxHandle": undefined,
"webhookSecret": "test-webhook-secret",
@@ -2205,20 +2223,25 @@ describe("defineConfig", function () {
`)
})
it("should add cloud options to the project config and relevant modules if the environment varianbles is set for a sandbox", function () {
it("should add cloud options to the project config and relevant modules if the environment variable is set for a sandbox", function () {
const originalEnv = { ...process.env }
process.env.MEDUSA_BACKEND_URL = "test-backend-url"
process.env.MEDUSA_CLOUD_SANDBOX_HANDLE = "test-sandbox"
process.env.MEDUSA_CLOUD_API_KEY = "test-api-key"
process.env.MEDUSA_CLOUD_EMAILS_ENDPOINT = "test-emails-endpoint"
process.env.MEDUSA_CLOUD_PAYMENTS_ENDPOINT = "test-payments-endpoint"
process.env.MEDUSA_CLOUD_WEBHOOK_SECRET = "test-webhook-secret"
process.env.MEDUSA_CLOUD_OAUTH_AUTHORIZE_ENDPOINT =
"test-oauth-authorize-endpoint"
process.env.MEDUSA_CLOUD_OAUTH_TOKEN_ENDPOINT = "test-oauth-token-endpoint"
process.env.MEDUSA_CLOUD_OAUTH_DISABLED = "true"
const config = defineConfig()
process.env = { ...originalEnv }
expect(config).toMatchInlineSnapshot(`
{
"admin": {
"backendUrl": "/",
"backendUrl": "test-backend-url",
"path": "/app",
},
"featureFlags": {},
@@ -2229,6 +2252,15 @@ describe("defineConfig", function () {
},
"auth": {
"options": {
"cloud": {
"api_key": "test-api-key",
"callback_url": "test-backend-url/app/login?auth_provider=cloud",
"disabled": true,
"environment_handle": undefined,
"oauth_authorize_endpoint": "test-oauth-authorize-endpoint",
"oauth_token_endpoint": "test-oauth-token-endpoint",
"sandbox_handle": "test-sandbox",
},
"providers": [
{
"id": "emailpass",
@@ -2370,6 +2402,10 @@ describe("defineConfig", function () {
"apiKey": "test-api-key",
"emailsEndpoint": "test-emails-endpoint",
"environmentHandle": undefined,
"oauthAuthorizeEndpoint": "test-oauth-authorize-endpoint",
"oauthCallbackUrl": undefined,
"oauthDisabled": true,
"oauthTokenEndpoint": "test-oauth-token-endpoint",
"paymentsEndpoint": "test-payments-endpoint",
"sandboxHandle": "test-sandbox",
"webhookSecret": "test-webhook-secret",
@@ -2415,6 +2451,9 @@ describe("defineConfig", function () {
webhookSecret: "overriden-webhook-secret",
emailsEndpoint: "overriden-emails-endpoint",
paymentsEndpoint: "overriden-payments-endpoint",
oauthAuthorizeEndpoint: "overriden-oauth-authorize-endpoint",
oauthTokenEndpoint: "overriden-oauth-token-endpoint",
oauthDisabled: true,
},
},
})
@@ -2434,6 +2473,15 @@ describe("defineConfig", function () {
},
"auth": {
"options": {
"cloud": {
"api_key": "overriden-api-key",
"callback_url": "//app/login?auth_provider=cloud",
"disabled": true,
"environment_handle": "overriden-environment",
"oauth_authorize_endpoint": "overriden-oauth-authorize-endpoint",
"oauth_token_endpoint": "overriden-oauth-token-endpoint",
"sandbox_handle": undefined,
},
"providers": [
{
"id": "emailpass",
@@ -2575,6 +2623,10 @@ describe("defineConfig", function () {
"apiKey": "overriden-api-key",
"emailsEndpoint": "overriden-emails-endpoint",
"environmentHandle": "overriden-environment",
"oauthAuthorizeEndpoint": "overriden-oauth-authorize-endpoint",
"oauthCallbackUrl": undefined,
"oauthDisabled": true,
"oauthTokenEndpoint": "overriden-oauth-token-endpoint",
"paymentsEndpoint": "overriden-payments-endpoint",
"sandboxHandle": undefined,
"webhookSecret": "overriden-webhook-secret",

View File

@@ -1,4 +1,5 @@
import {
AdminOptions,
ConfigModule,
InputConfig,
InputConfigModules,
@@ -50,7 +51,7 @@ export function defineConfig(config: InputConfig = {}): ConfigModule {
const projectConfig = normalizeProjectConfig(config.projectConfig, options)
const adminConfig = normalizeAdminConfig(config.admin)
const modules = resolveModules(config.modules, options, config.projectConfig)
applyCloudOptionsToModules(modules, projectConfig?.cloud)
applyCloudOptionsToModules(modules, projectConfig?.cloud, adminConfig)
const plugins = resolvePlugins(config.plugins, options)
return {
@@ -378,6 +379,11 @@ function normalizeProjectConfig(
webhookSecret: process.env.MEDUSA_CLOUD_WEBHOOK_SECRET,
emailsEndpoint: process.env.MEDUSA_CLOUD_EMAILS_ENDPOINT,
paymentsEndpoint: process.env.MEDUSA_CLOUD_PAYMENTS_ENDPOINT,
oauthAuthorizeEndpoint: process.env.MEDUSA_CLOUD_OAUTH_AUTHORIZE_ENDPOINT,
oauthTokenEndpoint: process.env.MEDUSA_CLOUD_OAUTH_TOKEN_ENDPOINT,
oauthCallbackUrl: process.env.MEDUSA_CLOUD_OAUTH_CALLBACK_URL,
oauthDisabled:
process.env.MEDUSA_CLOUD_OAUTH_DISABLED === "true" ? true : undefined,
...cloud,
}
const hasCloudOptions = Object.values(mergedCloudOptions).some(
@@ -449,6 +455,21 @@ function normalizeProjectConfig(
...restOfProjectConfig,
} satisfies ConfigModule["projectConfig"]
if (
isCloud &&
!mergedCloudOptions.oauthDisabled &&
mergedCloudOptions.oauthAuthorizeEndpoint &&
mergedCloudOptions.oauthTokenEndpoint
) {
const userAuthMethods = config.http.authMethodsPerActor?.user ?? [
"emailpass",
]
config.http.authMethodsPerActor = {
...config.http.authMethodsPerActor,
user: userAuthMethods.concat("cloud"),
}
}
return config
}
@@ -468,7 +489,8 @@ function normalizeAdminConfig(
function applyCloudOptionsToModules(
modules: Exclude<ConfigModule["modules"], undefined>,
config?: MedusaCloudOptions
config?: MedusaCloudOptions,
adminConfig?: AdminOptions
) {
if (!config) {
return
@@ -504,6 +526,24 @@ function applyCloudOptionsToModules(
...(module.options ?? {}),
}
break
case Modules.AUTH:
let callbackUrl = config.oauthCallbackUrl
if (!callbackUrl && adminConfig?.backendUrl) {
callbackUrl = `${adminConfig?.backendUrl}${adminConfig?.path}/login?auth_provider=cloud`
}
module.options = {
cloud: {
oauth_authorize_endpoint: config.oauthAuthorizeEndpoint,
oauth_token_endpoint: config.oauthTokenEndpoint,
environment_handle: config.environmentHandle,
sandbox_handle: config.sandboxHandle,
api_key: config.apiKey,
callback_url: callbackUrl,
disabled: config.oauthDisabled,
},
...(module.options ?? {}),
}
break
default:
break
}