Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Update auth app_metadata when deleting users + customers #9041

Merged
merged 15 commits into from
Sep 10, 2024
Merged
8 changes: 6 additions & 2 deletions integration-tests/helpers/create-admin-user.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { IAuthModuleService, IUserModuleService } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/utils"
import jwt from "jsonwebtoken"
import Scrypt from "scrypt-kdf"
import { getContainer } from "../environment-helpers/use-container"

export const adminHeaders = {
Expand All @@ -26,13 +27,16 @@ export const createAdminUser = async (
email: "admin@medusa.js",
})

const hashConfig = { logN: 15, r: 8, p: 1 }
const passwordHash = await Scrypt.kdf("somepassword", hashConfig)

const authIdentity = await authModule.createAuthIdentities({
provider_identities: [
{
provider: "emailpass",
entity_id: "admin@medusa.js",
provider_metadata: {
password: "somepassword",
password: passwordHash.toString("base64"),
},
},
],
Expand All @@ -55,5 +59,5 @@ export const createAdminUser = async (

adminHeaders.headers["authorization"] = `Bearer ${token}`

return { user }
return { user, authIdentity }
}
2 changes: 1 addition & 1 deletion integration-tests/http/__tests__/auth/admin/auth.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { generateResetPasswordTokenWorkflow } from "@medusajs/core-flows"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import jwt from "jsonwebtoken"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import {
adminHeaders,
createAdminUser,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { IAuthModuleService } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/utils"
import jwt from "jsonwebtoken"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import {
adminHeaders,
Expand All @@ -13,9 +16,10 @@ medusaIntegrationTestRunner({
let customer3
let customer4
let customer5
let container
beforeEach(async () => {
const appContainer = getContainer()
await createAdminUser(dbConnection, adminHeaders, appContainer)
container = getContainer()
await createAdminUser(dbConnection, adminHeaders, container)

customer1 = (
await api.post(
Expand Down Expand Up @@ -392,5 +396,63 @@ medusaIntegrationTestRunner({
)
})
})

describe("DELETE /admin/customers/:id", () => {
it("should delete a customer and update auth identity", async () => {
const registeredCustomerToken = (
await api.post("/auth/customer/emailpass/register", {
email: "test@email.com",
password: "password",
})
).data.token

const customer = (
await api.post(
"/store/customers",
{
email: "test@email.com",
},
{
headers: {
Authorization: `Bearer ${registeredCustomerToken}`,
},
}
)
).data.customer

const response = await api.delete(
`/admin/customers/${customer.id}`,
adminHeaders
)

expect(response.status).toEqual(200)
expect(response.data).toEqual(
expect.objectContaining({
id: customer.id,
deleted: true,
object: "customer",
})
)

const { auth_identity_id } = jwt.decode(registeredCustomerToken)

const authModule: IAuthModuleService = container.resolve(
ModuleRegistrationName.AUTH
)

const authIdentity = await authModule.retrieveAuthIdentity(
auth_identity_id
)

expect(authIdentity).toEqual(
expect.objectContaining({
id: authIdentity.id,
app_metadata: expect.not.objectContaining({
customer_id: expect.any(String),
}),
})
)
})
})
},
})
56 changes: 48 additions & 8 deletions integration-tests/http/__tests__/user/admin/user.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { IAuthModuleService } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/utils"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import {
adminHeaders,
Expand All @@ -8,17 +10,18 @@ jest.setTimeout(30000)

medusaIntegrationTestRunner({
testSuite: ({ dbConnection, getContainer, api }) => {
let user
let user, container, authIdentity

beforeEach(async () => {
const container = getContainer()
const { user: adminUser } = await createAdminUser(
container = getContainer()
const { user: adminUser, authIdentity: authId } = await createAdminUser(
dbConnection,
adminHeaders,
container
)

user = adminUser
authIdentity = authId
})

describe("GET /admin/users/:id", () => {
Expand Down Expand Up @@ -102,20 +105,57 @@ medusaIntegrationTestRunner({
})

describe("DELETE /admin/users", () => {
it("Deletes a user", async () => {
const userId = "member-user"

it("Deletes a user and updates associated auth identity", async () => {
const response = await api.delete(
`/admin/users/${userId}`,
`/admin/users/${user.id}`,
adminHeaders
)

expect(response.status).toEqual(200)
expect(response.data).toEqual({
id: userId,
id: user.id,
object: "user",
deleted: true,
})

const authModule: IAuthModuleService = container.resolve(
ModuleRegistrationName.AUTH
)

const updatedAuthIdentity = await authModule.retrieveAuthIdentity(
authIdentity.id
)

// Ensure the auth identity has been updated to not contain the user's id
expect(updatedAuthIdentity).toEqual(
expect.objectContaining({
id: authIdentity.id,
app_metadata: expect.not.objectContaining({
user_id: user.id,
}),
})
)

// Authentication should still succeed
const authenticateToken = (
await api.post(`/auth/user/emailpass`, {
email: user.email,
password: "somepassword",
})
).data.token

expect(authenticateToken).toEqual(expect.any(String))

// However, it should not be possible to access routes any longer
const meResponse = await api
.get(`/admin/users/me`, {
headers: {
authorization: `Bearer ${authenticateToken}`,
},
})
.catch((e) => e)

expect(meResponse.response.status).toEqual(401)
})

// TODO: Migrate when analytics config is implemented in 2.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { isDefined, ModuleRegistrationName } from "@medusajs/utils"
export type SetAuthAppMetadataStepInput = {
authIdentityId: string
actorType: string
value: string
value: string | null // null means delete the key
}

export const setAuthAppMetadataStepId = "set-auth-app-metadata"
Expand All @@ -24,10 +24,13 @@ export const setAuthAppMetadataStep = createStep(
const authIdentity = await service.retrieveAuthIdentity(data.authIdentityId)

const appMetadata = authIdentity.app_metadata || {}
if (isDefined(appMetadata[key])) {

// If the value is null, we are deleting the association with an actor
if (isDefined(appMetadata[key]) && data.value !== null) {
throw new Error(`Key ${key} already exists in app metadata`)
}

const oldValue = appMetadata[key]
appMetadata[key] = data.value

await service.updateAuthIdentities({
Expand All @@ -38,14 +41,16 @@ export const setAuthAppMetadataStep = createStep(
return new StepResponse(authIdentity, {
id: authIdentity.id,
key: key,
value: data.value,
oldValue,
})
},
async (idAndKey, { container }) => {
if (!idAndKey) {
async (idAndKeyAndValue, { container }) => {
if (!idAndKeyAndValue) {
return
}

const { id, key } = idAndKey
const { id, key, oldValue, value } = idAndKeyAndValue

const service = container.resolve<IAuthModuleService>(
ModuleRegistrationName.AUTH
Expand All @@ -54,7 +59,11 @@ export const setAuthAppMetadataStep = createStep(
const authIdentity = await service.retrieveAuthIdentity(id)

const appMetadata = authIdentity.app_metadata || {}
if (isDefined(appMetadata[key])) {

// If the value is null, we WERE deleting the association with an actor, so we need to restore it
if (value === null) {
appMetadata[key] = oldValue
} else {
delete appMetadata[key]
}

Expand Down
10 changes: 6 additions & 4 deletions packages/core/core-flows/src/customer/workflows/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export * from "./create-addresses"
export * from "./create-customer-account"
export * from "./create-customers"
export * from "./update-customers"
export * from "./delete-addresses"
export * from "./delete-customers"
export * from "./create-customer-account"
export * from "./create-addresses"
export * from "./remove-customer-account"
export * from "./update-addresses"
export * from "./delete-addresses"
export * from "./update-customers"

Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {
WorkflowData,
WorkflowResponse,
createWorkflow,
transform,
when,
} from "@medusajs/workflows-sdk"
import { setAuthAppMetadataStep } from "../../auth"
import { useRemoteQueryStep } from "../../common"
import { deleteCustomersWorkflow } from "./delete-customers"

export type RemoveCustomerAccountWorkflowInput = {
customerId: string
}
export const removeCustomerAccountWorkflowId = "remove-customer-account"
/**
* This workflow deletes a user and remove the association in the auth identity.
*/
export const removeCustomerAccountWorkflow = createWorkflow(
removeCustomerAccountWorkflowId,
(
input: WorkflowData<RemoveCustomerAccountWorkflowInput>
): WorkflowResponse<string> => {
const customers = useRemoteQueryStep({
entry_point: "customer",
fields: ["id", "has_account"],
variables: {
id: input.customerId,
},
}).config({ name: "get-customer" })

deleteCustomersWorkflow.runAsStep({
input: {
ids: [input.customerId],
},
})

when({ customers }, ({ customers }) => {
return !!customers[0]?.has_account
}).then(() => {
const authIdentities = useRemoteQueryStep({
entry_point: "auth_identity",
fields: ["id"],
variables: {
filters: {
app_metadata: {
customer_id: input.customerId,
},
},
},
})

const authIdentity = transform(
{ authIdentities },
({ authIdentities }) => {
const authIdentity = authIdentities[0]

if (!authIdentity) {
throw new Error("Auth identity not found")
olivermrbl marked this conversation as resolved.
Show resolved Hide resolved
}

return authIdentity
}
)

setAuthAppMetadataStep({
authIdentityId: authIdentity.id,
actorType: "customer",
value: null,
})
})

return new WorkflowResponse(input.customerId)
}
)
1 change: 1 addition & 0 deletions packages/core/core-flows/src/user/workflows/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./create-user-account"
export * from "./create-users"
export * from "./delete-users"
export * from "./remove-user-account"
export * from "./update-users"

Loading
Loading