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 +1,2 @@
|
||||
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,
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
ModulesSdkUtils,
|
||||
} from "@medusajs/utils"
|
||||
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
||||
|
||||
import { Invite, User } from "@models"
|
||||
import InviteService from "./invite"
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
userService: ModulesSdkTypes.InternalModuleService<any>
|
||||
inviteService: ModulesSdkTypes.InternalModuleService<any>
|
||||
inviteService: InviteService<any>
|
||||
}
|
||||
|
||||
const generateMethodForModels = [Invite]
|
||||
@@ -46,7 +48,7 @@ export default class UserModuleService<
|
||||
protected baseRepository_: DAL.RepositoryService
|
||||
|
||||
protected readonly userService_: ModulesSdkTypes.InternalModuleService<TUser>
|
||||
protected readonly inviteService_: ModulesSdkTypes.InternalModuleService<TInvite>
|
||||
protected readonly inviteService_: InviteService<TInvite>
|
||||
|
||||
constructor(
|
||||
{ userService, inviteService, baseRepository }: InjectedDependencies,
|
||||
@@ -57,7 +59,17 @@ export default class UserModuleService<
|
||||
|
||||
this.baseRepository_ = baseRepository
|
||||
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(
|
||||
@@ -146,14 +158,11 @@ export default class UserModuleService<
|
||||
data: UserTypes.CreateInviteDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TInvite[]> {
|
||||
// expiration date in 10 days
|
||||
const expirationDate = new Date().setDate(new Date().getDate() + 10)
|
||||
|
||||
const toCreate = data.map((invite) => {
|
||||
return {
|
||||
...invite,
|
||||
expires_at: new Date(expirationDate),
|
||||
token: "placeholder", // TODO: generate token
|
||||
expires_at: new Date(),
|
||||
token: "placeholder",
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user