feat(): Add support for jwt asymetric keys (#12813)
* feat(): Add support for jwt asymetric keys * Create early-chefs-chew.md * fix unit tests * Add verify options support * feedback * fix unit tests
This commit is contained in:
committed by
GitHub
parent
a833c3c98c
commit
d517dbd66a
@@ -4,6 +4,7 @@ import {
|
||||
InferEntityType,
|
||||
InternalModuleDeclaration,
|
||||
ModulesSdkTypes,
|
||||
ProjectConfigOptions,
|
||||
UserTypes,
|
||||
} from "@medusajs/framework/types"
|
||||
import {
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
CommonEvents,
|
||||
EmitEvents,
|
||||
generateEntityId,
|
||||
generateJwtToken,
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
@@ -20,10 +22,11 @@ import {
|
||||
Modules,
|
||||
UserEvents,
|
||||
} from "@medusajs/framework/utils"
|
||||
import jwt, { JwtPayload } from "jsonwebtoken"
|
||||
import jwt, { JwtPayload, SignOptions, VerifyOptions } from "jsonwebtoken"
|
||||
import crypto from "node:crypto"
|
||||
|
||||
import { Invite, User } from "@models"
|
||||
import { getExpiresAt } from "../utils/utils"
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
@@ -51,11 +54,18 @@ export default class UserModuleService
|
||||
protected readonly inviteService_: ModulesSdkTypes.IMedusaInternalService<
|
||||
InferEntityType<typeof Invite>
|
||||
>
|
||||
protected readonly config: { jwtSecret: string; expiresIn: number }
|
||||
protected readonly config: {
|
||||
jwtSecret: string
|
||||
jwtPublicKey?: string
|
||||
jwt_verify_options: ProjectConfigOptions["http"]["jwtVerifyOptions"]
|
||||
jwtOptions: ProjectConfigOptions["http"]["jwtOptions"] & {
|
||||
expiresIn: number
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
{ userService, inviteService, baseRepository }: InjectedDependencies,
|
||||
protected readonly moduleDeclaration: InternalModuleDeclaration
|
||||
moduleDeclaration: InternalModuleDeclaration
|
||||
) {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
@@ -63,11 +73,18 @@ export default class UserModuleService
|
||||
this.baseRepository_ = baseRepository
|
||||
this.userService_ = userService
|
||||
this.inviteService_ = inviteService
|
||||
|
||||
this.config = {
|
||||
jwtSecret: moduleDeclaration["jwt_secret"],
|
||||
expiresIn:
|
||||
parseInt(moduleDeclaration["valid_duration"]) ||
|
||||
DEFAULT_VALID_INVITE_DURATION_SECONDS,
|
||||
jwtPublicKey: moduleDeclaration["jwt_public_key"],
|
||||
jwt_verify_options: moduleDeclaration["jwt_verify_options"],
|
||||
jwtOptions: {
|
||||
...moduleDeclaration["jwt_options"],
|
||||
expiresIn:
|
||||
moduleDeclaration["valid_duration"] ??
|
||||
moduleDeclaration["jwt_options"]?.expiresIn ??
|
||||
DEFAULT_VALID_INVITE_DURATION_SECONDS,
|
||||
},
|
||||
}
|
||||
|
||||
if (!this.config.jwtSecret) {
|
||||
@@ -83,8 +100,21 @@ export default class UserModuleService
|
||||
token: string,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<UserTypes.InviteDTO> {
|
||||
const jwtSecret = this.moduleDeclaration["jwt_secret"]
|
||||
const decoded: JwtPayload = jwt.verify(token, jwtSecret, { complete: true })
|
||||
const options = {
|
||||
...(this.config.jwt_verify_options ?? this.config.jwtOptions),
|
||||
complete: true,
|
||||
} as VerifyOptions & SignOptions
|
||||
|
||||
if (!options.algorithms && options.algorithm) {
|
||||
options.algorithms = [options.algorithm]
|
||||
delete options.algorithm
|
||||
}
|
||||
|
||||
const decoded = jwt.verify(
|
||||
token,
|
||||
this.config.jwtPublicKey ?? this.config.jwtSecret,
|
||||
options
|
||||
) as JwtPayload
|
||||
|
||||
const invite = await this.inviteService_.retrieve(
|
||||
decoded.payload.id,
|
||||
@@ -155,10 +185,11 @@ export default class UserModuleService
|
||||
}
|
||||
}
|
||||
|
||||
const expiresAt = getExpiresAt(this.config.jwtOptions.expiresIn)
|
||||
const updates = invites.map((invite) => {
|
||||
return {
|
||||
id: invite.id,
|
||||
expires_at: new Date(Date.now() + this.config.expiresIn * 1000),
|
||||
expires_at: expiresAt,
|
||||
token: this.generateToken({ id: invite.id, email: invite.email }),
|
||||
}
|
||||
})
|
||||
@@ -317,12 +348,14 @@ export default class UserModuleService
|
||||
)
|
||||
}
|
||||
|
||||
const expiresAt = getExpiresAt(this.config.jwtOptions.expiresIn)
|
||||
|
||||
const toCreate = data.map((invite) => {
|
||||
const id = generateEntityId((invite as { id?: string }).id, "invite")
|
||||
return {
|
||||
...invite,
|
||||
id,
|
||||
expires_at: new Date(Date.now() + this.config.expiresIn * 1000),
|
||||
expires_at: expiresAt,
|
||||
token: this.generateToken({ id, email: invite.email }),
|
||||
}
|
||||
})
|
||||
@@ -375,10 +408,15 @@ export default class UserModuleService
|
||||
}
|
||||
|
||||
private generateToken(data: any): string {
|
||||
const jwtSecret: string = this.moduleDeclaration["jwt_secret"]
|
||||
return jwt.sign(data, jwtSecret, {
|
||||
jwtid: crypto.randomUUID(),
|
||||
expiresIn: this.config.expiresIn,
|
||||
const jwtId = this.config.jwtOptions.jwtid ?? crypto.randomUUID()
|
||||
const token = generateJwtToken(data, {
|
||||
secret: this.config.jwtSecret,
|
||||
jwtOptions: {
|
||||
...this.config.jwtOptions,
|
||||
jwtid: jwtId,
|
||||
},
|
||||
})
|
||||
|
||||
return token
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user