Commerce Modules

How to Create an Actor Type

In this document, learn how to create an actor type and authenticate its associated data model.

0. Create Module with Data Model#

Before creating an actor type, you must define a data model the actor type belongs to. The data model is defined in a custom module.

The rest of this guide uses this Manager data model as an example:

src/modules/manager/models/manager.ts
1import { model } from "@medusajs/utils"2
3const Manager = model.define("manager", {4  id: model.id().primaryKey(),5  firstName: model.text(),6  lastName: model.text(),7  email: model.text(),8})9
10export default Manager

1. Create Workflow#

Start by creating a workflow that does two things:

  • Create a record of the Manager data model.
  • Sets the app_metadata property of the associated AuthIdentity record based on the new actor type.

For example, create the file src/workflows/create-manager.ts. with the following content:

src/workflows/create-manager.ts
1import { 2  createWorkflow, 3  createStep,4  StepResponse,5  WorkflowResponse,6} from "@medusajs/workflows-sdk"7import { 8  setAuthAppMetadataStep,9} from "@medusajs/core-flows"10import ManagerModuleService from "../modules/manager/service"11
12type CreateManagerWorkflowInput = {13  manager: {14    first_name: string15    last_name: string16    email: string17  }18  authIdentityId: string19}20
21const createManagerStep = createStep(22  "create-manager-step",23  async ({ 24    manager: managerData,25  }: Pick<CreateManagerWorkflowInput, "manager">, 26  { container }) => {27    const managerModuleService: ManagerModuleService = 28      container.resolve("managerModuleService")29
30    const manager = await managerModuleService.createManager(31      managerData32    )33
34    return new StepResponse(manager)35  }36)37
38const createManagerWorkflow = createWorkflow(39  "create-manager",40  function (input: CreateManagerWorkflowInput) {41    const manager = createManagerStep({42      manager: input.manager,43    })44
45    setAuthAppMetadataStep({46      authIdentityId: input.authIdentityId,47      actorType: "manager",48      value: manager.id,49    })50
51    return new WorkflowResponse(manager)52  }53)54
55export default createManagerWorkflow

This workflow accepts the manager’s data and the associated auth identity’s ID as inputs. The next sections explain how the auth identity ID is retrieved.

The workflow has two steps:

  1. Create the manager using the createManagerStep.
  2. Set the app_metadata property of the associated auth identity using the setAuthAppMetadataStep step imported from @medusajs/core-flows. You specify the actor type manager in the actorType property of the step’s input.

2. Define the Create API Route#

Next, you’ll use the workflow defined in the previous section in an API route that creates a manager.

So, create the file src/api/manager/route.ts with the following content:

src/api/manager/route.ts
1import type { 2  AuthenticatedMedusaRequest,3  MedusaResponse,4} from "@medusajs/medusa"5import { MedusaError } from "@medusajs/utils"6import createManagerWorkflow from "../../workflows/create-manager"7
8type RequestBody = {9  first_name: string10  last_name: string11  email: string12}13
14export async function POST(15  req: AuthenticatedMedusaRequest<RequestBody>, 16  res: MedusaResponse17) {18  // If `actor_id` is present, the request carries 19  // authentication for an existing manager20  if (req.auth_context.actor_id) {21    throw new MedusaError(22      MedusaError.Types.INVALID_DATA,23      "Request already authenticated as a manager."24    )25  }26
27  const { result } = await createManagerWorkflow(req.scope)28    .run({29      input: {30        manager: req.body,31        authIdentityId: req.auth_context.auth_identity_id,32      },33    })34  35    res.status(200).json({ manager: result })36}

Since the manager must be associated with an AuthIdentity record, the request is expected to be authenticated, even if the manager isn’t created yet. This can be achieved by:

  1. Obtaining a token usng the /auth route.
  2. Passing the token in the bearer header of the request to this route.

In the API route, you create the manager using the workflow from the previous section and return it in the response.


3. Apply the authenticate Middleware#

The last step is to apply the authenticate middleware on the API routes that require a manager’s authentication.

To do that, create the file src/api/middlewares.ts with the following content:

src/api/middlewares.ts
1import { 2  defineMiddlewares,3  authenticate,4} from "@medusajs/medusa"5
6export default defineMiddlewares({7  routes: [8    {9      matcher: "/manager",10      method: "POST",11      middlewares: [12        authenticate("manager", ["session", "bearer"], {13          allowUnregistered: true,14        }),15      ],16    },17    {18      matcher: "/manager/me*",19      middlewares: [20        authenticate("manager", ["session", "bearer"]),21      ],22    },23  ],24})

This applies middlewares on two route patterns:

  1. The authenticate middleware is applied on the /manager API route for POST requests while allowing unregistered managers. This requires that a bearer token be passed in the request to access the manager’s auth identity but doesn’t require the manager to be registered.
  2. The authenticate middleware is applied on all routes starting with /manager/me, restricting these routes to authenticated managers only.

Retrieve Manager API Route#

For example, create the file src/api/manager/me/route.ts with the following content:

src/api/manager/me/route.ts
1import { 2  AuthenticatedMedusaRequest,3  MedusaResponse,4} from "@medusajs/medusa"5import ManagerModuleService from "../../../modules/manager/service"6
7export async function GET(8  req: AuthenticatedMedusaRequest,9  res: MedusaResponse10): Promise<void> {11  const managerModuleService: ManagerModuleService = 12    req.scope.resolve("managerModuleService")13
14  const manager = await managerModuleService.retrieveManager(15    req.auth_context.actor_id16  )17
18  res.json({ manager })19}

This route is only accessible by authenticated managers. You access the manager’s ID using req.auth_context.actor_id.


Test Custom Actor Type Authentication Flow#

To authenticate managers:

  1. Send a POST request to /auth/manager/emailpass/register to create an auth identity for the manager:
Code
1curl -X POST 'http://localhost:9000/auth/manager/emailpass/register' \2-H 'Content-Type: application/json' \3--data-raw '{4    "email": "manager@gmail.com",5    "password": "supersecret"6}'

Copy the returned token to use it in the next request.

  1. Send a POST request to /manager to create a manager:
Code
1curl -X POST 'http://localhost:9000/manager' \2-H 'Content-Type: application/json' \3-H 'Authorization: Bearer {token}' \4--data-raw '{5    "first_name": "John",6    "last_name": "Doe",7    "email": "manager@gmail.com"8}'

Replace {token} with the token returned in the previous step.

  1. Send a POST request to /auth/manager/emailpass again to retrieve an authenticated token for the manager:
Code
1curl -X POST 'http://localhost:9000/auth/manager/emailpass' \2-H 'Content-Type: application/json' \3--data-raw '{4    "email": "manager@gmail.com",5    "password": "supersecret"6}'
  1. You can now send authenticated requests as a manager. For example, send a GET request to /manager/me to retrieve the authenticated manager’s details:
Code
1curl 'http://localhost:9000/manager/me' \2-H 'Authorization: Bearer {token}'

Whenever you want to log in as a manager, use the /auth/manager/emailpass API route, as explained in step 3.


Delete User of Actor Type#

When you delete a user of the actor type, you must update its auth identity to remove the association to the user.

For example, create the following workflow that deletes a manager and updates its auth identity, create the file src/workflows/delete-manager.ts with the following content:

src/workflows/delete-manager.ts
5import ManagerModuleService from "../modules/manager/service"6
7export type DeleteManagerWorkflow = {8  id: string9}10
11const deleteManagerStep = createStep(12  "delete-manager-step",13  async (14    { id }: DeleteManagerWorkflow, 15    { container }) => {16      const managerModuleService: ManagerModuleService = 17        container.resolve("managerModuleService")18
19      const manager = await managerModuleService.retrieve(id)20
21      await managerModuleService.deleteManagers(id)22
23      return new StepResponse(undefined, { manager })24    },25    async ({ manager }, { container }) => {26      const managerModuleService: ManagerModuleService = 27        container.resolve("managerModuleService")28
29      await managerModuleService.createManagers(manager)30    }31  )

You add a step that deletes the manager using the deleteManagers method of the module's main service. In the compensation function, you create the manager again.

Next, in the same file, add the workflow that deletes a manager:

src/workflows/delete-manager.ts
14// ...15
16export const deleteManagerWorkflow = createWorkflow(17  "delete-manager",18  (19    input: WorkflowData<DeleteManagerWorkflow>20  ): WorkflowResponse<string> => {21    deleteManagerStep(input)22
23    const authIdentities = useRemoteQueryStep({24      entry_point: "auth_identity",25      fields: ["id"],26      variables: {27        filters: {28          app_metadata: {29            // the ID is of the format `{actor_type}_id`.30            manager_id: input.id,31          },32        },33      },34    })35
36    const authIdentity = transform(37      { authIdentities },38      ({ authIdentities }) => {39        const authIdentity = authIdentities[0]40
41        if (!authIdentity) {42          throw new MedusaError(43            MedusaError.Types.NOT_FOUND,44            "Auth identity not found"45          )46        }47
48        return authIdentity49      }50    )51
52    setAuthAppMetadataStep({53      authIdentityId: authIdentity.id,54      actorType: "manager",55      value: null,56    })57
58    return new WorkflowResponse(input.id)59  }60)

In the workflow, you:

  1. Use the deleteManagerStep defined earlier to delete the manager.
  2. Retrieve the auth identity of the manager using Query. To do that, you filter the app_metadata property of an auth identity, which holds the user's ID under {actor_type_name}_id. So, in this case, it's manager_id.
  3. Check that the auth identity exist, then, update the auth identity to remove the ID of the manager from it.

You can use this workflow when deleting a manager, such as in an API route.

Was this page helpful?
Edit this page