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:
1 parent
499dec6d31
commit
001923da2b
27 files changed
+1327
-23
No files matched your search
@@ -11,7 +11,7 @@ export const authRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/auth/session",
|
||||
middlewares: [authenticate("*", "bearer")],
|
||||
middlewares: [authenticate("*", "bearer", { allowUnregistered: true })],
|
||||
},
|
||||
{
|
||||
method: ["DELETE"],
|
||||
|
||||
@@ -64,7 +64,12 @@ export const validateToken = () => {
|
||||
provider: auth_provider,
|
||||
},
|
||||
{
|
||||
select: ["provider_metadata", "auth_identity_id", "entity_id"],
|
||||
select: [
|
||||
"provider_metadata",
|
||||
"auth_identity_id",
|
||||
"entity_id",
|
||||
"user_metadata",
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
@@ -77,6 +82,7 @@ export const validateToken = () => {
|
||||
auth_identity_id: providerIdentity.auth_identity_id!,
|
||||
actor_id: providerIdentity.entity_id,
|
||||
app_metadata: {},
|
||||
user_metadata: providerIdentity.user_metadata ?? {},
|
||||
}
|
||||
|
||||
return next()
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework"
|
||||
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
|
||||
|
||||
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||
const config = req.scope.resolve(ContainerRegistrationKeys.CONFIG_MODULE)
|
||||
|
||||
res.status(200).json({
|
||||
enabled:
|
||||
!!config.projectConfig.http.authMethodsPerActor?.user?.includes("cloud"),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
createUserAccountWorkflow,
|
||||
setAuthAppMetadataWorkflow,
|
||||
} from "@medusajs/core-flows"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
MedusaError,
|
||||
} from "@medusajs/framework/utils"
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
|
||||
|
||||
// If user already exists for this auth identity, reject
|
||||
if (req.auth_context.actor_id) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"The user is already registered and cannot create a new account."
|
||||
)
|
||||
}
|
||||
|
||||
if (!req.auth_context.user_metadata.email) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Email is required to create a user account."
|
||||
)
|
||||
}
|
||||
|
||||
// Check that the auth identity is from Medusa Cloud
|
||||
const providerIdentities = await query
|
||||
.graph({
|
||||
entity: "auth_identity",
|
||||
fields: ["id", "provider_identities.provider"],
|
||||
filters: {
|
||||
id: req.auth_context.auth_identity_id,
|
||||
},
|
||||
})
|
||||
.then((result) => result.data[0]?.provider_identities)
|
||||
if (
|
||||
providerIdentities?.length !== 1 ||
|
||||
providerIdentities[0].provider !== "cloud"
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.UNAUTHORIZED,
|
||||
"Only cloud identities can create a user account"
|
||||
)
|
||||
}
|
||||
|
||||
// Check if a user already exists
|
||||
const user = await query
|
||||
.graph({
|
||||
entity: "user",
|
||||
fields: ["id"],
|
||||
filters: {
|
||||
email: req.auth_context.user_metadata.email,
|
||||
},
|
||||
})
|
||||
.then((result) => result.data[0])
|
||||
|
||||
if (user) {
|
||||
await setAuthAppMetadataWorkflow(req.scope).run({
|
||||
input: {
|
||||
authIdentityId: req.auth_context.auth_identity_id,
|
||||
actorType: "user",
|
||||
value: user.id,
|
||||
},
|
||||
})
|
||||
|
||||
const updatedUser = await query
|
||||
.graph({
|
||||
entity: "user",
|
||||
fields: ["*"],
|
||||
filters: {
|
||||
id: user.id,
|
||||
},
|
||||
})
|
||||
.then((result) => result.data[0])
|
||||
|
||||
res.status(200).json({ user: updatedUser })
|
||||
return
|
||||
}
|
||||
|
||||
const { result: createdUser } = await createUserAccountWorkflow(
|
||||
req.scope
|
||||
).run({
|
||||
input: {
|
||||
authIdentityId: req.auth_context.auth_identity_id,
|
||||
userData: {
|
||||
email: req.auth_context.user_metadata.email as string,
|
||||
first_name: req.auth_context.user_metadata.given_name as string,
|
||||
last_name: req.auth_context.user_metadata.family_name as string,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
res.status(200).json({ user: createdUser })
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { authenticate, MiddlewareRoute } from "@medusajs/framework/http"
|
||||
|
||||
export const cloudRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
{
|
||||
matcher: "/cloud/auth",
|
||||
method: ["GET"],
|
||||
middlewares: [],
|
||||
},
|
||||
{
|
||||
matcher: "/cloud/auth/users",
|
||||
method: ["POST"],
|
||||
middlewares: [
|
||||
// Allow users who are authenticated but don't yet have an actor (user record)
|
||||
authenticate("user", ["session", "bearer"], {
|
||||
allowUnregistered: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -37,16 +37,21 @@ import { adminShippingOptionRoutesMiddlewares } from "./admin/shipping-options/m
|
||||
import { adminShippingProfilesMiddlewares } from "./admin/shipping-profiles/middlewares"
|
||||
import { adminStockLocationRoutesMiddlewares } from "./admin/stock-locations/middlewares"
|
||||
import { adminStoreRoutesMiddlewares } from "./admin/stores/middlewares"
|
||||
import { adminTaxProviderRoutesMiddlewares } from "./admin/tax-providers/middlewares"
|
||||
import { adminTaxRateRoutesMiddlewares } from "./admin/tax-rates/middlewares"
|
||||
import { adminTaxRegionRoutesMiddlewares } from "./admin/tax-regions/middlewares"
|
||||
import { adminTaxProviderRoutesMiddlewares } from "./admin/tax-providers/middlewares"
|
||||
import { adminUploadRoutesMiddlewares } from "./admin/uploads/middlewares"
|
||||
import { adminUserRoutesMiddlewares } from "./admin/users/middlewares"
|
||||
import { viewConfigurationRoutesMiddlewares } from "./admin/views/[entity]/configurations/middlewares"
|
||||
import { columnRoutesMiddlewares } from "./admin/views/[entity]/columns/middlewares"
|
||||
import { viewConfigurationRoutesMiddlewares } from "./admin/views/[entity]/configurations/middlewares"
|
||||
import { adminWorkflowsExecutionsMiddlewares } from "./admin/workflows-executions/middlewares"
|
||||
import { authRoutesMiddlewares } from "./auth/middlewares"
|
||||
|
||||
import { adminIndexRoutesMiddlewares } from "./admin/index/middlewares"
|
||||
import { adminLocalesRoutesMiddlewares } from "./admin/locales/middlewares"
|
||||
import { adminShippingOptionTypeRoutesMiddlewares } from "./admin/shipping-option-types/middlewares"
|
||||
import { adminTranslationsRoutesMiddlewares } from "./admin/translations/middlewares"
|
||||
import { cloudRoutesMiddlewares } from "./cloud/middlewares"
|
||||
import { hooksRoutesMiddlewares } from "./hooks/middlewares"
|
||||
import { storeCartRoutesMiddlewares } from "./store/carts/middlewares"
|
||||
import { storeCollectionRoutesMiddlewares } from "./store/collections/middlewares"
|
||||
@@ -64,10 +69,6 @@ import { storeProductRoutesMiddlewares } from "./store/products/middlewares"
|
||||
import { storeRegionRoutesMiddlewares } from "./store/regions/middlewares"
|
||||
import { storeReturnReasonRoutesMiddlewares } from "./store/return-reasons/middlewares"
|
||||
import { storeShippingOptionRoutesMiddlewares } from "./store/shipping-options/middlewares"
|
||||
import { adminShippingOptionTypeRoutesMiddlewares } from "./admin/shipping-option-types/middlewares"
|
||||
import { adminIndexRoutesMiddlewares } from "./admin/index/middlewares"
|
||||
import { adminLocalesRoutesMiddlewares } from "./admin/locales/middlewares"
|
||||
import { adminTranslationsRoutesMiddlewares } from "./admin/translations/middlewares"
|
||||
|
||||
export default defineMiddlewares([
|
||||
...storeRoutesMiddlewares,
|
||||
@@ -140,4 +141,5 @@ export default defineMiddlewares([
|
||||
...viewConfigurationRoutesMiddlewares,
|
||||
...columnRoutesMiddlewares,
|
||||
...adminIndexRoutesMiddlewares,
|
||||
...cloudRoutesMiddlewares,
|
||||
])
|
||||
Reference in new issue
Block a user