feat: implement identity and security features

- Added account types and privileged account types constants.
- Created admin user controller for updating user security fields.
- Developed role assignment controller for managing user roles.
- Implemented validation middleware for request schemas.
- Defined user role and auth session models for database interactions.
- Created services for authentication, email notifications, and OTP handling.
- Developed OAuth service for Google and Apple authentication.
- Added JWT utility functions for token generation and verification.
- Implemented comprehensive tests for authentication, session management, and password policies.
- Created migration for updating user schema and adding new tables for auth sessions and user identities.
This commit is contained in:
Sathira Sri Sathara
2026-09-03 13:56:18 +05:30
parent 267e80e2ec
commit 9d3d431416
54 changed files with 1389 additions and 1076 deletions
+31
View File
@@ -0,0 +1,31 @@
const mockDb = { User: { findOne: jest.fn() } };
const mockCheckPassword = jest.fn();
const mockCreateChallenge = jest.fn();
const mockSendOtp = jest.fn();
jest.mock("../../app/models", () => mockDb);
jest.mock("../../app/utils/hashPassword.util", () => ({ checkPassword: mockCheckPassword }));
jest.mock("../../app/services/auth/otp.service", () => ({ createLoginChallenge: mockCreateChallenge, verifyLoginChallenge: jest.fn() }));
jest.mock("../../app/services/auth/email.service", () => ({ sendLoginOtp: mockSendOtp }));
jest.mock("../../app/utils/idGen.util", () => ({ generateUserId: jest.fn(), generateId: jest.fn() }));
jest.mock("../../app/services/auth/session.service", () => ({ createSession: jest.fn(), rotateSession: jest.fn() }));
jest.mock("../../app/services/auth/oauth.service", () => ({ verifyGoogleToken: jest.fn(), verifyAppleToken: jest.fn() }));
const { beginPasswordLogin } = require("../../app/services/auth/auth.service");
const req = { get: jest.fn(), ip: "127.0.0.1" };
describe("password login boundary", () => {
beforeEach(() => { jest.clearAllMocks(); mockCheckPassword.mockResolvedValue(true); });
test.each(["PENDING_VERIFICATION", "SUSPENDED", "DEACTIVATED"])("rejects %s accounts", async (status) => {
mockDb.User.findOne.mockResolvedValue({ id: "usr", password: "hash", accountStatus: status, accountType: "customer" });
await expect(beginPasswordLogin({ email: "x@example.com", password: "secret", rememberMe: false }, req)).rejects.toMatchObject({ code: "ACCOUNT_NOT_ACTIVE" });
});
test("uses a generic error for missing users and wrong passwords", async () => {
mockDb.User.findOne.mockResolvedValue(null);
await expect(beginPasswordLogin({ email: "x@example.com", password: "wrong" }, req)).rejects.toMatchObject({ code: "INVALID_CREDENTIALS" });
});
test("creates an OTP challenge but no session for valid credentials", async () => {
const user = { id: "usr", password: "hash", accountStatus: "ACTIVE", accountType: "customer" };
mockDb.User.findOne.mockResolvedValue(user); mockCreateChallenge.mockResolvedValue({ challengeId: "challenge", otp: "123456" });
await expect(beginPasswordLogin({ email: "x@example.com", password: "secret", rememberMe: true }, req)).resolves.toEqual({ challengeId: "challenge" });
expect(mockSendOtp).toHaveBeenCalledWith(user, "123456");
});
});