feat: Reset password (#8962)
* wip * more work * wip * more work * wrap up first iteration * work on new approach * more work * move middleware func to route * cleanup * more work * wrap up * more work * fix workflow * minor tweaks * finalize * Use JWT secret instead
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { generateResetPasswordTokenWorkflow } from "@medusajs/core-flows"
|
||||
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
||||
import {
|
||||
adminHeaders,
|
||||
@@ -8,120 +9,250 @@ jest.setTimeout(30000)
|
||||
|
||||
medusaIntegrationTestRunner({
|
||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||
let container
|
||||
beforeEach(async () => {
|
||||
await createAdminUser(dbConnection, adminHeaders, getContainer())
|
||||
container = getContainer()
|
||||
await createAdminUser(dbConnection, adminHeaders, container)
|
||||
})
|
||||
|
||||
it("Invite + registration + authentication flow", async () => {
|
||||
// Create invite
|
||||
const { token: inviteToken } = (
|
||||
await api.post(
|
||||
"/admin/invites",
|
||||
{ email: "newadmin@medusa.js" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.invite
|
||||
afterEach(() => {
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
// Register identity
|
||||
const signup = await api.post("/auth/user/emailpass/register", {
|
||||
email: "newadmin@medusa.js",
|
||||
password: "secret_password",
|
||||
})
|
||||
describe("Full authentication lifecycle", () => {
|
||||
it("Invite + registration + authentication flow", async () => {
|
||||
// Create invite
|
||||
const { token: inviteToken } = (
|
||||
await api.post(
|
||||
"/admin/invites",
|
||||
{ email: "newadmin@medusa.js" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.invite
|
||||
|
||||
expect(signup.status).toEqual(200)
|
||||
expect(signup.data).toEqual({ token: expect.any(String) })
|
||||
|
||||
// Accept invite
|
||||
const response = await api.post(
|
||||
`/admin/invites/accept?token=${inviteToken}`,
|
||||
{
|
||||
// Register identity
|
||||
const signup = await api.post("/auth/user/emailpass/register", {
|
||||
email: "newadmin@medusa.js",
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
authorization: `Bearer ${signup.data.token}`,
|
||||
password: "secret_password",
|
||||
})
|
||||
|
||||
expect(signup.status).toEqual(200)
|
||||
expect(signup.data).toEqual({ token: expect.any(String) })
|
||||
|
||||
// Accept invite
|
||||
const response = await api.post(
|
||||
`/admin/invites/accept?token=${inviteToken}`,
|
||||
{
|
||||
email: "newadmin@medusa.js",
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
},
|
||||
}
|
||||
)
|
||||
{
|
||||
headers: {
|
||||
authorization: `Bearer ${signup.data.token}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data).toEqual({
|
||||
user: expect.objectContaining({
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data).toEqual({
|
||||
user: expect.objectContaining({
|
||||
email: "newadmin@medusa.js",
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
}),
|
||||
})
|
||||
|
||||
// Sign in
|
||||
const login = await api.post("/auth/user/emailpass", {
|
||||
email: "newadmin@medusa.js",
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
}),
|
||||
password: "secret_password",
|
||||
})
|
||||
expect(login.status).toEqual(200)
|
||||
expect(login.data).toEqual({ token: expect.any(String) })
|
||||
|
||||
// Convert token to session
|
||||
const createSession = await api.post(
|
||||
"/auth/session",
|
||||
{},
|
||||
{ headers: { authorization: `Bearer ${login.data.token}` } }
|
||||
)
|
||||
expect(createSession.status).toEqual(200)
|
||||
|
||||
// Extract cookie
|
||||
const [cookie] = createSession.headers["set-cookie"][0].split(";")
|
||||
expect(cookie).toEqual(expect.stringContaining("connect.sid"))
|
||||
|
||||
const cookieHeader = {
|
||||
headers: { Cookie: cookie },
|
||||
}
|
||||
|
||||
// Perform cookie authenticated request
|
||||
const authedRequest = await api.get(
|
||||
"/admin/products?limit=1",
|
||||
cookieHeader
|
||||
)
|
||||
expect(authedRequest.status).toEqual(200)
|
||||
|
||||
// Sign out
|
||||
const signOutRequest = await api.delete("/auth/session", cookieHeader)
|
||||
expect(signOutRequest.status).toEqual(200)
|
||||
|
||||
// Attempt to perform authenticated request
|
||||
const unAuthedRequest = await api
|
||||
.get("/admin/products?limit=1", cookieHeader)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(unAuthedRequest.response.status).toEqual(401)
|
||||
})
|
||||
|
||||
// Sign in
|
||||
const login = await api.post("/auth/user/emailpass", {
|
||||
email: "newadmin@medusa.js",
|
||||
password: "secret_password",
|
||||
it("should respond with 401 on register, if email already exists", async () => {
|
||||
const signup = await api
|
||||
.post("/auth/user/emailpass/register", {
|
||||
email: "admin@medusa.js",
|
||||
password: "secret_password",
|
||||
})
|
||||
.catch((e) => e)
|
||||
|
||||
expect(signup.response.status).toEqual(401)
|
||||
expect(signup.response.data.message).toEqual(
|
||||
"Identity with email already exists"
|
||||
)
|
||||
})
|
||||
expect(login.status).toEqual(200)
|
||||
expect(login.data).toEqual({ token: expect.any(String) })
|
||||
|
||||
// Convert token to session
|
||||
const createSession = await api.post(
|
||||
"/auth/session",
|
||||
{},
|
||||
{ headers: { authorization: `Bearer ${login.data.token}` } }
|
||||
)
|
||||
expect(createSession.status).toEqual(200)
|
||||
it("should respond with 401 on sign in, if email does not exist", async () => {
|
||||
const signup = await api
|
||||
.post("/auth/user/emailpass", {
|
||||
email: "john@doe.com",
|
||||
password: "secret_password",
|
||||
})
|
||||
.catch((e) => e)
|
||||
|
||||
// Extract cookie
|
||||
const [cookie] = createSession.headers["set-cookie"][0].split(";")
|
||||
expect(cookie).toEqual(expect.stringContaining("connect.sid"))
|
||||
|
||||
const cookieHeader = {
|
||||
headers: { Cookie: cookie },
|
||||
}
|
||||
|
||||
// Perform cookie authenticated request
|
||||
const authedRequest = await api.get(
|
||||
"/admin/products?limit=1",
|
||||
cookieHeader
|
||||
)
|
||||
expect(authedRequest.status).toEqual(200)
|
||||
|
||||
// Sign out
|
||||
const signOutRequest = await api.delete("/auth/session", cookieHeader)
|
||||
expect(signOutRequest.status).toEqual(200)
|
||||
|
||||
// Attempt to perform authenticated request
|
||||
const unAuthedRequest = await api
|
||||
.get("/admin/products?limit=1", cookieHeader)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(unAuthedRequest.response.status).toEqual(401)
|
||||
expect(signup.response.status).toEqual(401)
|
||||
expect(signup.response.data.message).toEqual(
|
||||
"Invalid email or password"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it("should respond with 401 on register, if email already exists", async () => {
|
||||
const signup = await api
|
||||
.post("/auth/user/emailpass/register", {
|
||||
describe("Reset password flows", () => {
|
||||
it("should generate a reset password token", async () => {
|
||||
const response = await api.post("/auth/user/emailpass/reset-password", {
|
||||
email: "admin@medusa.js",
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(201)
|
||||
})
|
||||
|
||||
it("should fail to generate token for non-existing user, but still respond with 201", async () => {
|
||||
const response = await api.post("/auth/user/emailpass/reset-password", {
|
||||
email: "non-existing-user@medusa.js",
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(201)
|
||||
})
|
||||
|
||||
it("should fail to generate token for existing user but no provider, but still respond with 201", async () => {
|
||||
const response = await api.post(
|
||||
"/auth/user/non-existing-provider/reset-password",
|
||||
{ email: "admin@medusa.js" }
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(201)
|
||||
})
|
||||
|
||||
it("should fail to generate token for existing user but no provider, but still respond with 201", async () => {
|
||||
const response = await api.post(
|
||||
"/auth/user/non-existing-provider/reset-password",
|
||||
{ email: "admin@medusa.js" }
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(201)
|
||||
})
|
||||
|
||||
it("should successfully reset password", async () => {
|
||||
// Register user
|
||||
await api.post("/auth/user/emailpass/register", {
|
||||
email: "test@medusa-commerce.com",
|
||||
password: "secret_password",
|
||||
})
|
||||
.catch((e) => e)
|
||||
|
||||
expect(signup.response.status).toEqual(401)
|
||||
expect(signup.response.data.message).toEqual(
|
||||
"Identity with email already exists"
|
||||
)
|
||||
})
|
||||
// The token won't be part of the Rest API response, so we need to generate it manually
|
||||
const { result } = await generateResetPasswordTokenWorkflow(
|
||||
container
|
||||
).run({
|
||||
input: {
|
||||
entityId: "test@medusa-commerce.com",
|
||||
provider: "emailpass",
|
||||
secret: "test",
|
||||
},
|
||||
})
|
||||
|
||||
it("should respond with 401 on sign in, if email does not exist", async () => {
|
||||
const signup = await api
|
||||
.post("/auth/user/emailpass", {
|
||||
email: "john@doe.com",
|
||||
const response = await api.post(
|
||||
`/auth/user/emailpass/update?token=${result}`,
|
||||
{
|
||||
email: "test@medusa-commerce.com",
|
||||
password: "new_password",
|
||||
}
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data).toEqual({ success: true })
|
||||
|
||||
const failedLogin = await api
|
||||
.post("/auth/user/emailpass", {
|
||||
email: "test@medusa-commerce.com",
|
||||
password: "secret_password",
|
||||
})
|
||||
.catch((e) => e)
|
||||
|
||||
expect(failedLogin.response.status).toEqual(401)
|
||||
expect(failedLogin.response.data.message).toEqual(
|
||||
"Invalid email or password"
|
||||
)
|
||||
|
||||
const login = await api.post("/auth/user/emailpass", {
|
||||
email: "test@medusa-commerce.com",
|
||||
password: "new_password",
|
||||
})
|
||||
|
||||
expect(login.status).toEqual(200)
|
||||
expect(login.data).toEqual({ token: expect.any(String) })
|
||||
})
|
||||
|
||||
it("should fail if token has expired", async () => {
|
||||
jest.useFakeTimers()
|
||||
|
||||
// Register user
|
||||
await api.post("/auth/user/emailpass/register", {
|
||||
email: "test@medusa-commerce.com",
|
||||
password: "secret_password",
|
||||
})
|
||||
.catch((e) => e)
|
||||
|
||||
expect(signup.response.status).toEqual(401)
|
||||
expect(signup.response.data.message).toEqual("Invalid email or password")
|
||||
// The token won't be part of the Rest API response, so we need to generate it manually
|
||||
const { result } = await generateResetPasswordTokenWorkflow(
|
||||
container
|
||||
).run({
|
||||
input: {
|
||||
entityId: "test@medusa-commerce.com",
|
||||
provider: "emailpass",
|
||||
secret: "test",
|
||||
},
|
||||
})
|
||||
|
||||
// Advance time by 15 minutes
|
||||
jest.advanceTimersByTime(15 * 60 * 1000)
|
||||
|
||||
const response = await api
|
||||
.post(`/auth/user/emailpass/update?token=${result}`, {
|
||||
email: "test@medusa-commerce.com",
|
||||
password: "new_password",
|
||||
})
|
||||
.catch((e) => e)
|
||||
|
||||
expect(response.response.status).toEqual(401)
|
||||
expect(response.response.data.message).toEqual("Invalid token")
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user