fix(medusa): Error messages for reset tokens (#3514)

* initial

* reset password token handling

* Create .changeset/old-planes-cross.md

---------

Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Philip Korsholm
2023-03-19 10:52:10 +01:00
committed by GitHub
parent aa690beed7
commit aed7805c0e
6 changed files with 93 additions and 31 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---
fix(medusa): Error messages for reset tokens

View File

@@ -171,6 +171,16 @@ describe("/admin/users", () => {
})
describe("Password reset", () => {
it("Doesn't fail to fetch user when resetting password for an unknown email (unauthorized endpoint)", async () => {
const api = useApi()
const resp = await api.post("/admin/users/password-token", {
email: "test-doesnt-exist@test.com",
})
expect(resp.status).toEqual(204)
})
it("Doesn't fail when generating password reset token (unauthorized endpoint)", async () => {
const api = useApi()

View File

@@ -521,5 +521,40 @@ describe("/store/customers", () => {
expect(response.status).toEqual(204)
})
it("Returns 204 for non-existent customer", async () => {
const api = useApi()
const response = await api.post(`/store/customers/password-token`, {
email: "non-existent@test.com",
})
expect(response.status).toEqual(204)
})
})
describe("POST /store/customers/password-reset", () => {
afterEach(async () => {
await doAfterEach()
})
it("Returns 204 for non-existent customer", async () => {
const api = useApi()
const response = await api
.post(`/store/customers/password-reset`, {
email: "non-existent@test.com",
token: "token",
password: "password",
})
.catch((error) => {
return error
})
expect(response.response.status).toEqual(401)
expect(response.response.data).toEqual({
type: "unauthorized",
message: "Invalid or expired password reset token",
})
})
})
})

View File

@@ -66,15 +66,19 @@ export default async (req, res) => {
const validated = await validator(AdminResetPasswordTokenRequest, req.body)
const userService: UserService = req.scope.resolve("userService")
const user = await userService.retrieveByEmail(validated.email)
const user = await userService
.retrieveByEmail(validated.email)
.catch(() => undefined)
// Should call a email service provider that sends the token to the user
const manager: EntityManager = req.scope.resolve("manager")
await manager.transaction(async (transactionManager) => {
return await userService
.withTransaction(transactionManager)
.generateResetPasswordToken(user.id)
})
if (user) {
// Should call a email service provider that sends the token to the user
const manager: EntityManager = req.scope.resolve("manager")
await manager.transaction(async (transactionManager) => {
return await userService
.withTransaction(transactionManager)
.generateResetPasswordToken(user.id)
})
}
res.sendStatus(204)
}

View File

@@ -66,17 +66,19 @@ export default async (req, res) => {
"customerService"
) as CustomerService
const customer = await customerService.retrieveRegisteredByEmail(
validated.email
)
const customer = await customerService
.retrieveRegisteredByEmail(validated.email)
.catch(() => undefined)
// Will generate a token and send it to the customer via an email provider
const manager: EntityManager = req.scope.resolve("manager")
await manager.transaction(async (transactionManager) => {
return await customerService
.withTransaction(transactionManager)
.generateResetPasswordToken(customer.id)
})
if (customer) {
// Will generate a token and send it to the customer via an email provider
const manager: EntityManager = req.scope.resolve("manager")
await manager.transaction(async (transactionManager) => {
return await customerService
.withTransaction(transactionManager)
.generateResetPasswordToken(customer.id)
})
}
res.sendStatus(204)
}

View File

@@ -4,6 +4,7 @@ import jwt, { JwtPayload } from "jsonwebtoken"
import CustomerService from "../../../../services/customer"
import { validator } from "../../../../utils/validator"
import { EntityManager } from "typeorm"
import { MedusaError } from "medusa-core-utils"
/**
* @oas [post] /store/customers/password-reset
@@ -70,20 +71,25 @@ export default async (req, res) => {
)) as StorePostCustomersResetPasswordReq
const customerService: CustomerService = req.scope.resolve("customerService")
let customer = await customerService.retrieveRegisteredByEmail(
validated.email,
{
select: ["id", "password_hash"],
}
)
let customer
const decodedToken = jwt.verify(
validated.token,
customer.password_hash
) as JwtPayload
if (!decodedToken || customer.id !== decodedToken.customer_id) {
res.status(401).send("Invalid or expired password reset token")
return
customer = await customerService
.retrieveRegisteredByEmail(validated.email, {
select: ["id", "password_hash"],
})
.catch(() => undefined)
const decodedToken = customer
? jwt.verify(validated.token, customer.password_hash)
: undefined
if (
!decodedToken ||
customer.id !== (decodedToken as JwtPayload)?.customer_id
) {
throw new MedusaError(
MedusaError.Types.UNAUTHORIZED,
"Invalid or expired password reset token"
)
}
const manager: EntityManager = req.scope.resolve("manager")