Files
calendar/packages/features/auth/lib/next-auth-options.test.ts
T
2025-12-14 19:14:15 +09:00

234 lines
6.5 KiB
TypeScript

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(() => ({
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<typeof import("@calcom/lib/constants")>();
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<any> = {}) => ({
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);
});
});
});