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:
1 parent
aa690beed7
commit
aed7805c0e
6 files changed
+79
-17
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix(medusa): Error messages for reset tokens
|
||||||
@@ -171,6 +171,16 @@ describe("/admin/users", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("Password reset", () => {
|
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 () => {
|
it("Doesn't fail when generating password reset token (unauthorized endpoint)", async () => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
|
|||||||
@@ -521,5 +521,40 @@ describe("/store/customers", () => {
|
|||||||
|
|
||||||
expect(response.status).toEqual(204)
|
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",
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -66,8 +66,11 @@ export default async (req, res) => {
|
|||||||
const validated = await validator(AdminResetPasswordTokenRequest, req.body)
|
const validated = await validator(AdminResetPasswordTokenRequest, req.body)
|
||||||
|
|
||||||
const userService: UserService = req.scope.resolve("userService")
|
const userService: UserService = req.scope.resolve("userService")
|
||||||
const user = await userService.retrieveByEmail(validated.email)
|
const user = await userService
|
||||||
|
.retrieveByEmail(validated.email)
|
||||||
|
.catch(() => undefined)
|
||||||
|
|
||||||
|
if (user) {
|
||||||
// Should call a email service provider that sends the token to the user
|
// Should call a email service provider that sends the token to the user
|
||||||
const manager: EntityManager = req.scope.resolve("manager")
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
await manager.transaction(async (transactionManager) => {
|
await manager.transaction(async (transactionManager) => {
|
||||||
@@ -75,6 +78,7 @@ export default async (req, res) => {
|
|||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.generateResetPasswordToken(user.id)
|
.generateResetPasswordToken(user.id)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
res.sendStatus(204)
|
res.sendStatus(204)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,10 +66,11 @@ export default async (req, res) => {
|
|||||||
"customerService"
|
"customerService"
|
||||||
) as CustomerService
|
) as CustomerService
|
||||||
|
|
||||||
const customer = await customerService.retrieveRegisteredByEmail(
|
const customer = await customerService
|
||||||
validated.email
|
.retrieveRegisteredByEmail(validated.email)
|
||||||
)
|
.catch(() => undefined)
|
||||||
|
|
||||||
|
if (customer) {
|
||||||
// Will generate a token and send it to the customer via an email provider
|
// Will generate a token and send it to the customer via an email provider
|
||||||
const manager: EntityManager = req.scope.resolve("manager")
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
await manager.transaction(async (transactionManager) => {
|
await manager.transaction(async (transactionManager) => {
|
||||||
@@ -77,6 +78,7 @@ export default async (req, res) => {
|
|||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.generateResetPasswordToken(customer.id)
|
.generateResetPasswordToken(customer.id)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
res.sendStatus(204)
|
res.sendStatus(204)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import jwt, { JwtPayload } from "jsonwebtoken"
|
|||||||
import CustomerService from "../../../../services/customer"
|
import CustomerService from "../../../../services/customer"
|
||||||
import { validator } from "../../../../utils/validator"
|
import { validator } from "../../../../utils/validator"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [post] /store/customers/password-reset
|
* @oas [post] /store/customers/password-reset
|
||||||
@@ -70,20 +71,25 @@ export default async (req, res) => {
|
|||||||
)) as StorePostCustomersResetPasswordReq
|
)) as StorePostCustomersResetPasswordReq
|
||||||
|
|
||||||
const customerService: CustomerService = req.scope.resolve("customerService")
|
const customerService: CustomerService = req.scope.resolve("customerService")
|
||||||
let customer = await customerService.retrieveRegisteredByEmail(
|
let customer
|
||||||
validated.email,
|
|
||||||
{
|
|
||||||
select: ["id", "password_hash"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const decodedToken = jwt.verify(
|
customer = await customerService
|
||||||
validated.token,
|
.retrieveRegisteredByEmail(validated.email, {
|
||||||
customer.password_hash
|
select: ["id", "password_hash"],
|
||||||
) as JwtPayload
|
})
|
||||||
if (!decodedToken || customer.id !== decodedToken.customer_id) {
|
.catch(() => undefined)
|
||||||
res.status(401).send("Invalid or expired password reset token")
|
|
||||||
return
|
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")
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
|
|||||||
Reference in new issue
Block a user