diff --git a/app/server/modules/auth/__tests__/helpers.test.ts b/app/server/modules/auth/__tests__/helpers.test.ts new file mode 100644 index 00000000..cd8f86d3 --- /dev/null +++ b/app/server/modules/auth/__tests__/helpers.test.ts @@ -0,0 +1,63 @@ +import { beforeEach, describe, expect, test, vi } from "vitest"; +import { db } from "~/server/db/db"; +import { account, usersTable } from "~/server/db/schema"; +import { createUser, randomId, randomSlug } from "~/test/helpers/user-org"; +import { verifyUserPassword } from "../helpers"; + +const { verifyPassword } = vi.hoisted(() => ({ + verifyPassword: vi.fn(async ({ hash }: { hash: string }) => hash === "credential-password-hash"), +})); + +vi.mock("better-auth/crypto", () => ({ + verifyPassword, +})); + +async function createAccount({ + password, + providerId, + userId, +}: { + password: string | null; + providerId: string; + userId: string; +}) { + await db.insert(account).values({ + id: randomId(), + accountId: randomSlug("account"), + providerId, + userId, + password, + }); +} + +describe("verifyUserPassword", () => { + beforeEach(async () => { + verifyPassword.mockClear(); + await db.delete(account); + await db.delete(usersTable); + }); + + test("verifies against the credential account when the user also has SSO accounts", async () => { + const userId = await createUser(`${randomSlug("user")}@example.com`); + await createAccount({ userId, providerId: "oidc-acme", password: null }); + await createAccount({ userId, providerId: "credential", password: "credential-password-hash" }); + + const result = await verifyUserPassword({ userId, password: "correct-password" }); + + expect(result).toBe(true); + expect(verifyPassword).toHaveBeenCalledWith({ + password: "correct-password", + hash: "credential-password-hash", + }); + }); + + test("returns false when the user has no credential password account", async () => { + const userId = await createUser(`${randomSlug("user")}@example.com`); + await createAccount({ userId, providerId: "oidc-acme", password: null }); + + const result = await verifyUserPassword({ userId, password: "correct-password" }); + + expect(result).toBe(false); + expect(verifyPassword).not.toHaveBeenCalled(); + }); +}); diff --git a/app/server/modules/auth/helpers.ts b/app/server/modules/auth/helpers.ts index 1a3733b9..6442aacd 100644 --- a/app/server/modules/auth/helpers.ts +++ b/app/server/modules/auth/helpers.ts @@ -8,7 +8,7 @@ type PasswordVerificationBody = { export const verifyUserPassword = async ({ password, userId }: PasswordVerificationBody) => { const userAccount = await db.query.account.findFirst({ - where: { userId }, + where: { AND: [{ userId }, { providerId: "credential" }] }, }); if (!userAccount || !userAccount.password) {