import type { User } from "next-auth"; import { describe, expect, it, vi, beforeEach } from "vitest"; import { IdentityProvider, UserPermissionRole } from "@calcom/prisma/enums"; import { ErrorCode } from "./ErrorCode"; // Mock dependencies vi.mock("@calcom/prisma", () => ({ prisma: { user: { update: vi.fn(), }, }, default: { user: { update: vi.fn(), }, }, })); const mockFindByEmailAndIncludeProfilesAndPassword = vi.fn(); vi.mock("@calcom/features/users/repositories/UserRepository", () => { return { UserRepository: vi.fn().mockImplementation(function () { return { findByEmailAndIncludeProfilesAndPassword: mockFindByEmailAndIncludeProfilesAndPassword, }; }), }; }); vi.mock("./verifyPassword", () => ({ verifyPassword: vi.fn(), })); vi.mock("@calcom/lib/checkRateLimitAndThrowError", () => ({ checkRateLimitAndThrowError: vi.fn().mockResolvedValue(undefined), })); vi.mock("@calcom/lib/server/PiiHasher", () => ({ hashEmail: vi.fn((email: string) => `hashed_${email}`), })); vi.mock("@calcom/lib/totp", () => ({ totpAuthenticatorCheck: vi.fn(), })); vi.mock("@calcom/lib/crypto", () => ({ symmetricDecrypt: vi.fn(), symmetricEncrypt: vi.fn(), })); vi.mock("@calcom/lib/auth/isPasswordValid", () => ({ isPasswordValid: vi.fn(), })); vi.mock("@calcom/lib/env", () => ({ isENVDev: false, })); vi.mock("@calcom/lib/constants", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, IS_TEAM_BILLING_ENABLED: false, ENABLE_PROFILE_SWITCHER: false, WEBAPP_URL: "http://localhost:3000", }; }); vi.mock("@calcom/lib/logger", () => ({ default: { getSubLogger: vi.fn(() => ({ debug: vi.fn(), error: vi.fn(), info: vi.fn(), })), }, })); vi.mock("@calcom/lib/safeStringify", () => ({ safeStringify: vi.fn((obj) => JSON.stringify(obj)), })); vi.mock("./next-auth-custom-adapter", () => ({ default: vi.fn(() => ({ linkAccount: vi.fn(), })), })); vi.mock("@calcom/features/profile/repositories/ProfileRepository", () => ({ ProfileRepository: { findAllProfilesForUserIncludingMovedUser: vi.fn(), findByUpIdWithAuth: vi.fn(), }, })); describe("CredentialsProvider authorize", () => { let authorizeCredentials: typeof import("./next-auth-options").authorizeCredentials; let verifyPassword: any; beforeEach(async () => { vi.clearAllMocks(); mockFindByEmailAndIncludeProfilesAndPassword.mockReset(); const verifyPasswordModule = await import("./verifyPassword"); verifyPassword = verifyPasswordModule.verifyPassword; // Import the exported authorize function directly const authModule = await import("./next-auth-options"); authorizeCredentials = authModule.authorizeCredentials; }); const createMockUser = (overrides: Partial = {}) => ({ id: 1, email: "test@example.com", name: "Test User", username: "testuser", role: UserPermissionRole.USER, locked: false, identityProvider: IdentityProvider.CAL, twoFactorEnabled: false, twoFactorSecret: null, backupCodes: null, password: { hash: "$2a$10$hashedpassword", }, allProfiles: [ { id: 1, upId: "usr_123", username: "testuser", }, ], teams: [], ...overrides, }); describe("Password validation", () => { it("should throw error when user has no password hash with CAL identity provider", async () => { const mockUser = createMockUser({ password: null, identityProvider: IdentityProvider.CAL, }); mockFindByEmailAndIncludeProfilesAndPassword.mockResolvedValue(mockUser); await expect( authorizeCredentials({ email: "test@example.com", password: "password123", } as any) ).rejects.toThrow(ErrorCode.IncorrectEmailPassword); }); it("should throw error when user has no password hash with Google identity provider", async () => { const mockUser = createMockUser({ password: null, identityProvider: IdentityProvider.GOOGLE, }); mockFindByEmailAndIncludeProfilesAndPassword.mockResolvedValue(mockUser); await expect( authorizeCredentials({ email: "test@example.com", password: "password123", } as any) ).rejects.toThrow(ErrorCode.IncorrectEmailPassword); }); it("should throw error when user has no password hash with SAML identity provider", async () => { const mockUser = createMockUser({ password: null, identityProvider: IdentityProvider.SAML, }); mockFindByEmailAndIncludeProfilesAndPassword.mockResolvedValue(mockUser); await expect( authorizeCredentials({ email: "test@example.com", password: "password123", } as any) ).rejects.toThrow(ErrorCode.IncorrectEmailPassword); }); it("should throw error when user has no password hash even with TOTP code provided", async () => { const mockUser = createMockUser({ password: null, identityProvider: IdentityProvider.CAL, }); mockFindByEmailAndIncludeProfilesAndPassword.mockResolvedValue(mockUser); await expect( authorizeCredentials({ email: "test@example.com", password: "password123", totpCode: "123456", } as any) ).rejects.toThrow(ErrorCode.IncorrectEmailPassword); }); it("should throw error when user has no password hash with Google identity provider and TOTP code", async () => { const mockUser = createMockUser({ password: null, identityProvider: IdentityProvider.GOOGLE, }); mockFindByEmailAndIncludeProfilesAndPassword.mockResolvedValue(mockUser); await expect( authorizeCredentials({ email: "test@example.com", password: "password123", totpCode: "123456", } as any) ).rejects.toThrow(ErrorCode.IncorrectEmailPassword); }); it("should throw error when user has no password hash with SAML identity provider and TOTP code", async () => { const mockUser = createMockUser({ password: null, identityProvider: IdentityProvider.SAML, }); mockFindByEmailAndIncludeProfilesAndPassword.mockResolvedValue(mockUser); await expect( authorizeCredentials({ email: "test@example.com", password: "password123", totpCode: "123456", } as any) ).rejects.toThrow(ErrorCode.IncorrectEmailPassword); }); }); });