Feat(medusa, user, core-flows): User creation with invites (#6413)
**What** - add accept invite endpoint **Invite accept flow** - authenticate using the auth endpoint to create and auth-user - invoke accept endpoint with token and info to create user
This commit is contained in:
1 parent
5977a38ef4
commit
f6d38163bb
28 files changed
+399
-40
No files matched your search
@@ -5,6 +5,7 @@ import { startBootstrapApp } from "../../../environment-helpers/bootstrap-app"
|
|||||||
import { useApi } from "../../../environment-helpers/use-api"
|
import { useApi } from "../../../environment-helpers/use-api"
|
||||||
import adminSeeder from "../../../helpers/admin-seeder"
|
import adminSeeder from "../../../helpers/admin-seeder"
|
||||||
import { AxiosInstance } from "axios"
|
import { AxiosInstance } from "axios"
|
||||||
|
import { createAdminUser } from "../../helpers/create-admin-user"
|
||||||
|
|
||||||
jest.setTimeout(50000)
|
jest.setTimeout(50000)
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ describe("POST /admin/invites", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await adminSeeder(dbConnection)
|
await createAdminUser(dbConnection, adminHeaders)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { startBootstrapApp } from "../../../environment-helpers/bootstrap-app"
|
|||||||
import { useApi } from "../../../environment-helpers/use-api"
|
import { useApi } from "../../../environment-helpers/use-api"
|
||||||
import adminSeeder from "../../../helpers/admin-seeder"
|
import adminSeeder from "../../../helpers/admin-seeder"
|
||||||
import { AxiosInstance } from "axios"
|
import { AxiosInstance } from "axios"
|
||||||
|
import { createAdminUser } from "../../helpers/create-admin-user"
|
||||||
|
|
||||||
jest.setTimeout(50000)
|
jest.setTimeout(50000)
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ describe("DELETE /admin/invites/:id", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await adminSeeder(dbConnection)
|
await createAdminUser(dbConnection, adminHeaders)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { startBootstrapApp } from "../../../environment-helpers/bootstrap-app"
|
|||||||
import { useApi } from "../../../environment-helpers/use-api"
|
import { useApi } from "../../../environment-helpers/use-api"
|
||||||
import adminSeeder from "../../../helpers/admin-seeder"
|
import adminSeeder from "../../../helpers/admin-seeder"
|
||||||
import { AxiosInstance } from "axios"
|
import { AxiosInstance } from "axios"
|
||||||
|
import { createAdminUser } from "../../helpers/create-admin-user"
|
||||||
|
|
||||||
jest.setTimeout(50000)
|
jest.setTimeout(50000)
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ describe("GET /admin/invites", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await adminSeeder(dbConnection)
|
await createAdminUser(dbConnection, adminHeaders)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { initDb, useDb } from "../../../environment-helpers/use-db"
|
import { initDb, useDb } from "../../../environment-helpers/use-db"
|
||||||
|
|
||||||
import { IUserModuleService } from "@medusajs/types"
|
import { IAuthModuleService, IUserModuleService } from "@medusajs/types"
|
||||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
import { getContainer } from "../../../environment-helpers/use-container"
|
import { getContainer } from "../../../environment-helpers/use-container"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { startBootstrapApp } from "../../../environment-helpers/bootstrap-app"
|
import { startBootstrapApp } from "../../../environment-helpers/bootstrap-app"
|
||||||
import { useApi } from "../../../environment-helpers/use-api"
|
import { useApi } from "../../../environment-helpers/use-api"
|
||||||
import adminSeeder from "../../../helpers/admin-seeder"
|
|
||||||
import { AxiosInstance } from "axios"
|
import { AxiosInstance } from "axios"
|
||||||
|
import { createAdminUser } from "../../helpers/create-admin-user"
|
||||||
|
|
||||||
jest.setTimeout(50000)
|
jest.setTimeout(50000)
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ describe("GET /admin/invites/:id", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await adminSeeder(dbConnection)
|
await createAdminUser(dbConnection, adminHeaders)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@@ -48,8 +48,6 @@ describe("GET /admin/invites/:id", () => {
|
|||||||
it("should retrieve a single invite", async () => {
|
it("should retrieve a single invite", async () => {
|
||||||
const invite = await userModuleService.createInvites({
|
const invite = await userModuleService.createInvites({
|
||||||
email: "potential_member@test.com",
|
email: "potential_member@test.com",
|
||||||
token: "test",
|
|
||||||
expires_at: new Date(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const api = useApi()! as AxiosInstance
|
const api = useApi()! as AxiosInstance
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { IAuthModuleService } from "@medusajs/types"
|
||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import adminSeeder from "../../helpers/admin-seeder"
|
||||||
|
import jwt from "jsonwebtoken"
|
||||||
|
import { getContainer } from "../../environment-helpers/use-container"
|
||||||
|
|
||||||
|
export const createAdminUser = async (dbConnection, adminHeaders) => {
|
||||||
|
await adminSeeder(dbConnection)
|
||||||
|
const appContainer = getContainer()!
|
||||||
|
|
||||||
|
const authModule: IAuthModuleService = appContainer.resolve(
|
||||||
|
ModuleRegistrationName.AUTH
|
||||||
|
)
|
||||||
|
const authUser = await authModule.create({
|
||||||
|
provider: "emailpass",
|
||||||
|
entity_id: "admin@medusa.js",
|
||||||
|
scope: "admin",
|
||||||
|
app_metadata: {
|
||||||
|
user_id: "admin_user",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const token = jwt.sign(authUser, "test")
|
||||||
|
adminHeaders.headers["authorization"] = `Bearer ${token}`
|
||||||
|
}
|
||||||
@@ -48,6 +48,9 @@ module.exports = {
|
|||||||
scope: "internal",
|
scope: "internal",
|
||||||
resources: "shared",
|
resources: "shared",
|
||||||
resolve: "@medusajs/user",
|
resolve: "@medusajs/user",
|
||||||
|
options: {
|
||||||
|
jwt_secret: "test",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
[Modules.STOCK_LOCATION]: {
|
[Modules.STOCK_LOCATION]: {
|
||||||
scope: "internal",
|
scope: "internal",
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { InviteDTO } from "@medusajs/types"
|
||||||
|
import { IUserModuleService } from "@medusajs/types"
|
||||||
|
import { StepResponse } from "@medusajs/workflows-sdk"
|
||||||
|
import { createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
export const validateTokenStepId = "validate-invite-token-step"
|
||||||
|
export const validateTokenStep = createStep(
|
||||||
|
validateTokenStepId,
|
||||||
|
async (input: string, { container }) => {
|
||||||
|
const userModuleService: IUserModuleService = container.resolve(
|
||||||
|
ModuleRegistrationName.USER
|
||||||
|
)
|
||||||
|
|
||||||
|
const invite = await userModuleService.validateInviteToken(input)
|
||||||
|
|
||||||
|
return new StepResponse(invite)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { UserDTO } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
WorkflowData,
|
||||||
|
createWorkflow,
|
||||||
|
transform,
|
||||||
|
} from "@medusajs/workflows-sdk"
|
||||||
|
import { createUsersStep } from "../../user"
|
||||||
|
import { validateTokenStep } from "../steps/validate-token"
|
||||||
|
import { setAuthAppMetadataStep } from "../../auth"
|
||||||
|
import { InviteWorkflow } from "@medusajs/types"
|
||||||
|
import { deleteInvitesStep } from "../steps"
|
||||||
|
|
||||||
|
export const acceptInviteWorkflowId = "accept-invite-workflow"
|
||||||
|
export const acceptInviteWorkflow = createWorkflow(
|
||||||
|
acceptInviteWorkflowId,
|
||||||
|
(
|
||||||
|
input: WorkflowData<InviteWorkflow.AcceptInviteWorkflowInputDTO>
|
||||||
|
): WorkflowData<UserDTO[]> => {
|
||||||
|
// validate token
|
||||||
|
const invite = validateTokenStep(input.invite_token)
|
||||||
|
|
||||||
|
const createUserInput = transform(
|
||||||
|
{ input, invite },
|
||||||
|
({ input, invite }) => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
...input.user,
|
||||||
|
email: invite.email,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const users = createUsersStep(createUserInput)
|
||||||
|
|
||||||
|
const authUserInput = transform({ input, users }, ({ input, users }) => {
|
||||||
|
const createdUser = users[0]
|
||||||
|
|
||||||
|
return {
|
||||||
|
authUserId: input.auth_user_id,
|
||||||
|
key: "user_id",
|
||||||
|
value: createdUser.id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
setAuthAppMetadataStep(authUserInput)
|
||||||
|
|
||||||
|
deleteInvitesStep([invite.id])
|
||||||
|
|
||||||
|
return users
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from "./create-invites"
|
export * from "./create-invites"
|
||||||
export * from "./delete-invites"
|
export * from "./delete-invites"
|
||||||
|
export * from "./accept-invite"
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { acceptInviteWorkflow } from "@medusajs/core-flows"
|
||||||
|
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
|
||||||
|
import { InviteWorkflow } from "@medusajs/types"
|
||||||
|
import { AdminPostInvitesInviteAcceptReq } from "../validators"
|
||||||
|
import { IUserModuleService } from "@medusajs/types"
|
||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
|
||||||
|
export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
|
if (req.auth_user?.app_metadata?.user_id) {
|
||||||
|
const moduleService: IUserModuleService = req.scope.resolve(
|
||||||
|
ModuleRegistrationName.USER
|
||||||
|
)
|
||||||
|
const user = moduleService.retrieve(req.auth_user.app_metadata.user_id)
|
||||||
|
res.status(200).json({ user })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const workflow = acceptInviteWorkflow(req.scope)
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
invite_token: req.filterableFields.token as string,
|
||||||
|
auth_user_id: req.auth_user!.id,
|
||||||
|
user: req.validatedBody as AdminPostInvitesInviteAcceptReq,
|
||||||
|
} as InviteWorkflow.AcceptInviteWorkflowInputDTO
|
||||||
|
|
||||||
|
const { result: users } = await workflow.run({ input })
|
||||||
|
|
||||||
|
// Set customer_id on session user if we are in session
|
||||||
|
if (req.session.auth_user) {
|
||||||
|
req.session.auth_user.app_metadata.user_id = users[0].id
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ user: users[0] })
|
||||||
|
}
|
||||||
@@ -3,11 +3,19 @@ import {
|
|||||||
AdminCreateInviteRequest,
|
AdminCreateInviteRequest,
|
||||||
AdminGetInvitesParams,
|
AdminGetInvitesParams,
|
||||||
AdminGetInvitesInviteParams,
|
AdminGetInvitesInviteParams,
|
||||||
|
AdminPostInvitesInviteAcceptReq,
|
||||||
|
AdminPostInvitesInviteAcceptParams,
|
||||||
} from "./validators"
|
} from "./validators"
|
||||||
import * as QueryConfig from "./query-config"
|
import * as QueryConfig from "./query-config"
|
||||||
import { MiddlewareRoute } from "../../../types/middlewares"
|
import { MiddlewareRoute } from "../../../types/middlewares"
|
||||||
|
import { authenticate } from "../../../utils/authenticate-middleware"
|
||||||
|
|
||||||
export const adminInviteRoutesMiddlewares: MiddlewareRoute[] = [
|
export const adminInviteRoutesMiddlewares: MiddlewareRoute[] = [
|
||||||
|
{
|
||||||
|
method: "ALL",
|
||||||
|
matcher: "/admin/invites*",
|
||||||
|
middlewares: [authenticate("admin", ["session", "bearer"])],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
method: ["GET"],
|
method: ["GET"],
|
||||||
matcher: "/admin/invites",
|
matcher: "/admin/invites",
|
||||||
@@ -23,6 +31,14 @@ export const adminInviteRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
matcher: "/admin/invites",
|
matcher: "/admin/invites",
|
||||||
middlewares: [transformBody(AdminCreateInviteRequest)],
|
middlewares: [transformBody(AdminCreateInviteRequest)],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
matcher: "/admin/invites/accept",
|
||||||
|
middlewares: [
|
||||||
|
transformBody(AdminPostInvitesInviteAcceptReq),
|
||||||
|
transformQuery(AdminPostInvitesInviteAcceptParams),
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
method: ["GET"],
|
method: ["GET"],
|
||||||
matcher: "/admin/invites/:id",
|
matcher: "/admin/invites/:id",
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { Type } from "class-transformer"
|
import { Type } from "class-transformer"
|
||||||
import { IsEmail, IsOptional, IsString, ValidateNested } from "class-validator"
|
import {
|
||||||
|
IsEmail,
|
||||||
|
IsNotEmpty,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
ValidateNested,
|
||||||
|
} from "class-validator"
|
||||||
import {
|
import {
|
||||||
DateComparisonOperator,
|
DateComparisonOperator,
|
||||||
FindParams,
|
FindParams,
|
||||||
@@ -70,3 +76,63 @@ export class AdminCreateInviteRequest {
|
|||||||
@IsEmail()
|
@IsEmail()
|
||||||
email: string
|
email: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Details of the use accepting the invite.
|
||||||
|
*/
|
||||||
|
export class AdminPostInvitesInviteAcceptUserReq {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @schema AdminPostInvitesInviteAcceptReq
|
||||||
|
* type: object
|
||||||
|
* description: "The details of the invite to be accepted."
|
||||||
|
* required:
|
||||||
|
* - token
|
||||||
|
* - user
|
||||||
|
* properties:
|
||||||
|
* token:
|
||||||
|
* description: "The token of the invite to accept. This is a unique token generated when the invite was created or resent."
|
||||||
|
* type: string
|
||||||
|
* user:
|
||||||
|
* description: "The details of the user to create."
|
||||||
|
* type: object
|
||||||
|
* required:
|
||||||
|
* - first_name
|
||||||
|
* - last_name
|
||||||
|
* - password
|
||||||
|
* properties:
|
||||||
|
* first_name:
|
||||||
|
* type: string
|
||||||
|
* description: the first name of the User
|
||||||
|
* last_name:
|
||||||
|
* type: string
|
||||||
|
* description: the last name of the User
|
||||||
|
* password:
|
||||||
|
* description: The password for the User
|
||||||
|
* type: string
|
||||||
|
* format: password
|
||||||
|
*/
|
||||||
|
export class AdminPostInvitesInviteAcceptReq {
|
||||||
|
/**
|
||||||
|
* The invite's first name.
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
first_name: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The invite's last name.
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
last_name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminPostInvitesInviteAcceptParams {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
token: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
expand = undefined
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ export interface CreateInviteDTO {
|
|||||||
metadata?: Record<string, unknown> | null
|
metadata?: Record<string, unknown> | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateInviteDTO extends Partial<CreateInviteDTO> {
|
export interface UpdateInviteDTO
|
||||||
|
extends Partial<Omit<CreateInviteDTO, "email">> {
|
||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,11 @@ import { IModuleService } from "../modules-sdk"
|
|||||||
import { RestoreReturn, SoftDeleteReturn } from "../dal"
|
import { RestoreReturn, SoftDeleteReturn } from "../dal"
|
||||||
|
|
||||||
export interface IUserModuleService extends IModuleService {
|
export interface IUserModuleService extends IModuleService {
|
||||||
|
validateInviteToken(
|
||||||
|
token: string,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<InviteDTO>
|
||||||
|
|
||||||
retrieve(
|
retrieve(
|
||||||
id: string,
|
id: string,
|
||||||
config?: FindConfig<UserDTO>,
|
config?: FindConfig<UserDTO>,
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export interface AcceptInviteWorkflowInputDTO {
|
||||||
|
invite_token: string
|
||||||
|
auth_user_id: string
|
||||||
|
user: {
|
||||||
|
first_name?: string | null
|
||||||
|
last_name?: string | null
|
||||||
|
avatar_url?: string | null
|
||||||
|
metadata?: Record<string, unknown> | null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
export * from "./create-invite"
|
export * from "./create-invite"
|
||||||
export * from "./update-invite"
|
|
||||||
export * from "./delete-invite"
|
export * from "./delete-invite"
|
||||||
|
export * from "./accept-invite"
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { UpdateInviteDTO } from "../../user"
|
|
||||||
|
|
||||||
export interface UpdateInvitesWorkflowInputDTO {
|
|
||||||
updates: UpdateInviteDTO[]
|
|
||||||
}
|
|
||||||
@@ -10,6 +10,7 @@ export function getInitModuleConfig() {
|
|||||||
schema: process.env.MEDUSA_USER_DB_SCHEMA,
|
schema: process.env.MEDUSA_USER_DB_SCHEMA,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
jwt_secret: "test",
|
||||||
}
|
}
|
||||||
|
|
||||||
const injectedDependencies = {}
|
const injectedDependencies = {}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
"@mikro-orm/postgresql": "5.9.7",
|
"@mikro-orm/postgresql": "5.9.7",
|
||||||
"awilix": "^8.0.0",
|
"awilix": "^8.0.0",
|
||||||
"dotenv": "16.3.1",
|
"dotenv": "16.3.1",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
"knex": "2.4.2"
|
"knex": "2.4.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1 +1,2 @@
|
|||||||
export { default as UserModuleService } from "./user-module"
|
export { default as UserModuleService } from "./user-module"
|
||||||
|
export { default as InviteService } from "./invite"
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import { Context, DAL } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
InjectTransactionManager,
|
||||||
|
MedusaError,
|
||||||
|
ModulesSdkUtils,
|
||||||
|
} from "@medusajs/utils"
|
||||||
|
import { Invite } from "@models"
|
||||||
|
import { InviteServiceTypes } from "@types"
|
||||||
|
import jwt, { JwtPayload } from "jsonwebtoken"
|
||||||
|
|
||||||
|
type InjectedDependencies = {
|
||||||
|
inviteRepository: DAL.RepositoryService
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7 days
|
||||||
|
const DEFAULT_VALID_INVITE_DURATION = 1000 * 60 * 60 * 24 * 7
|
||||||
|
|
||||||
|
export default class InviteService<
|
||||||
|
TEntity extends Invite = Invite
|
||||||
|
> extends ModulesSdkUtils.internalModuleServiceFactory<InjectedDependencies>(
|
||||||
|
Invite
|
||||||
|
)<TEntity> {
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
protected readonly inviteRepository_: DAL.RepositoryService<TEntity>
|
||||||
|
protected options_: { jwt_secret: string; valid_duration: number } | undefined
|
||||||
|
|
||||||
|
constructor(container: InjectedDependencies) {
|
||||||
|
super(container)
|
||||||
|
this.inviteRepository_ = container.inviteRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
public withModuleOptions(options: any) {
|
||||||
|
const service = new InviteService<TEntity>(this.__container__)
|
||||||
|
|
||||||
|
service.options_ = options
|
||||||
|
|
||||||
|
return service
|
||||||
|
}
|
||||||
|
|
||||||
|
private getOption(key: string) {
|
||||||
|
if (!this.options_) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.UNEXPECTED_STATE,
|
||||||
|
`Options are not configured for InviteService, call "withModuleOptions" and provide options`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return this.options_[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
create(
|
||||||
|
data: InviteServiceTypes.CreateInviteDTO,
|
||||||
|
context?: Context
|
||||||
|
): Promise<TEntity>
|
||||||
|
create(
|
||||||
|
data: InviteServiceTypes.CreateInviteDTO[],
|
||||||
|
context?: Context
|
||||||
|
): Promise<TEntity[]>
|
||||||
|
|
||||||
|
@InjectTransactionManager("inviteRepository_")
|
||||||
|
async create(
|
||||||
|
data:
|
||||||
|
| InviteServiceTypes.CreateInviteDTO
|
||||||
|
| InviteServiceTypes.CreateInviteDTO[],
|
||||||
|
context: Context = {}
|
||||||
|
): Promise<TEntity | TEntity[]> {
|
||||||
|
const data_ = Array.isArray(data) ? data : [data]
|
||||||
|
|
||||||
|
const invites = await super.create(data_, context)
|
||||||
|
|
||||||
|
const expiresIn: number =
|
||||||
|
parseInt(this.getOption("valid_duration")) ||
|
||||||
|
DEFAULT_VALID_INVITE_DURATION
|
||||||
|
|
||||||
|
const updates = invites.map((invite) => {
|
||||||
|
return {
|
||||||
|
id: invite.id,
|
||||||
|
expires_at: new Date().setMilliseconds(
|
||||||
|
new Date().getMilliseconds() + expiresIn
|
||||||
|
),
|
||||||
|
token: this.generateToken({ id: invite.id }),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return await super.update(updates, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("inviteRepository_")
|
||||||
|
async validateInviteToken(
|
||||||
|
token: string,
|
||||||
|
context?: Context
|
||||||
|
): Promise<TEntity> {
|
||||||
|
const decoded = this.validateToken(token)
|
||||||
|
|
||||||
|
return await super.retrieve(decoded.payload.id, {}, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateToken(data: any): string {
|
||||||
|
const jwtSecret: string = this.getOption("jwt_secret")
|
||||||
|
const expiresIn: number =
|
||||||
|
parseInt(this.getOption("valid_duration")) ||
|
||||||
|
DEFAULT_VALID_INVITE_DURATION
|
||||||
|
|
||||||
|
if (!jwtSecret) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"No jwt_secret was provided in the UserModule's options. Please add one."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return jwt.sign(data, jwtSecret, {
|
||||||
|
expiresIn,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
private validateToken(data: any): JwtPayload {
|
||||||
|
const jwtSecret = this.getOption("jwt_secret")
|
||||||
|
|
||||||
|
if (!jwtSecret) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"No jwt_secret was provided in the UserModule's options. Please add one."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return jwt.verify(data, jwtSecret, { complete: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,16 +10,18 @@ import {
|
|||||||
InjectManager,
|
InjectManager,
|
||||||
InjectTransactionManager,
|
InjectTransactionManager,
|
||||||
MedusaContext,
|
MedusaContext,
|
||||||
|
MedusaError,
|
||||||
ModulesSdkUtils,
|
ModulesSdkUtils,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
||||||
|
|
||||||
import { Invite, User } from "@models"
|
import { Invite, User } from "@models"
|
||||||
|
import InviteService from "./invite"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
baseRepository: DAL.RepositoryService
|
baseRepository: DAL.RepositoryService
|
||||||
userService: ModulesSdkTypes.InternalModuleService<any>
|
userService: ModulesSdkTypes.InternalModuleService<any>
|
||||||
inviteService: ModulesSdkTypes.InternalModuleService<any>
|
inviteService: InviteService<any>
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateMethodForModels = [Invite]
|
const generateMethodForModels = [Invite]
|
||||||
@@ -46,7 +48,7 @@ export default class UserModuleService<
|
|||||||
protected baseRepository_: DAL.RepositoryService
|
protected baseRepository_: DAL.RepositoryService
|
||||||
|
|
||||||
protected readonly userService_: ModulesSdkTypes.InternalModuleService<TUser>
|
protected readonly userService_: ModulesSdkTypes.InternalModuleService<TUser>
|
||||||
protected readonly inviteService_: ModulesSdkTypes.InternalModuleService<TInvite>
|
protected readonly inviteService_: InviteService<TInvite>
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{ userService, inviteService, baseRepository }: InjectedDependencies,
|
{ userService, inviteService, baseRepository }: InjectedDependencies,
|
||||||
@@ -57,7 +59,17 @@ export default class UserModuleService<
|
|||||||
|
|
||||||
this.baseRepository_ = baseRepository
|
this.baseRepository_ = baseRepository
|
||||||
this.userService_ = userService
|
this.userService_ = userService
|
||||||
this.inviteService_ = inviteService
|
this.inviteService_ = inviteService.withModuleOptions(
|
||||||
|
this.moduleDeclaration
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("baseRepository_")
|
||||||
|
async validateInviteToken(
|
||||||
|
token: string,
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<UserTypes.InviteDTO> {
|
||||||
|
return await this.inviteService_.validateInviteToken(token, sharedContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
create(
|
create(
|
||||||
@@ -146,14 +158,11 @@ export default class UserModuleService<
|
|||||||
data: UserTypes.CreateInviteDTO[],
|
data: UserTypes.CreateInviteDTO[],
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<TInvite[]> {
|
): Promise<TInvite[]> {
|
||||||
// expiration date in 10 days
|
|
||||||
const expirationDate = new Date().setDate(new Date().getDate() + 10)
|
|
||||||
|
|
||||||
const toCreate = data.map((invite) => {
|
const toCreate = data.map((invite) => {
|
||||||
return {
|
return {
|
||||||
...invite,
|
...invite,
|
||||||
expires_at: new Date(expirationDate),
|
expires_at: new Date(),
|
||||||
token: "placeholder", // TODO: generate token
|
token: "placeholder",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ export type InitializeModuleInjectableDependencies = {
|
|||||||
logger?: Logger
|
logger?: Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
export * as ServiceTypes from "./services"
|
export * from "./services"
|
||||||
@@ -1 +1 @@
|
|||||||
export * from "./user"
|
export * as InviteServiceTypes from "./invite"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export interface CreateInviteDTO {
|
||||||
|
email: string
|
||||||
|
accepted?: boolean
|
||||||
|
metadata?: Record<string, unknown> | null
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
export type UserDTO = {
|
|
||||||
id: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CreateUserDTO = {
|
|
||||||
id?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type UpdateUserDTO = {
|
|
||||||
id: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FilterableUserProps = {}
|
|
||||||
@@ -46,7 +46,7 @@ export abstract class AbstractAuthModuleProvider {
|
|||||||
|
|
||||||
const cloned = new (this.constructor as any)(this.container_)
|
const cloned = new (this.constructor as any)(this.container_)
|
||||||
cloned.scope_ = scope
|
cloned.scope_ = scope
|
||||||
cloned.scopeConfg_ = this.scopes_[scope]
|
cloned.scopeConfig_ = this.scopes_[scope]
|
||||||
|
|
||||||
return cloned
|
return cloned
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user