134 lines
4.2 KiB
TypeScript
134 lines
4.2 KiB
TypeScript
import { describe, it, expect, beforeEach } from "vitest";
|
|
|
|
// 앞으로 구현할 인증/보안 유틸리티 모듈을 대상으로 TDD를 진행한다.
|
|
// - 비밀번호 해시/검증 (bcrypt 기반)
|
|
// - JWT 액세스 토큰 발급/검증
|
|
// - 민감정보 JSON 암호화/복호화
|
|
|
|
import {
|
|
hashPassword,
|
|
verifyPassword,
|
|
signAccessToken,
|
|
verifyAccessToken,
|
|
encryptJson,
|
|
decryptJson,
|
|
} from "@/features/auth/authCrypto";
|
|
|
|
interface TestUser {
|
|
id: string;
|
|
email: string;
|
|
tokenVersion: number;
|
|
}
|
|
|
|
describe("authCrypto 유틸리티", () => {
|
|
beforeEach(() => {
|
|
// 테스트 환경에서 사용할 JWT/암호화 키를 설정한다.
|
|
process.env.AUTH_JWT_SECRET = "test-jwt-secret-key";
|
|
// 32바이트 키를 hex 로 표현 (예시)
|
|
process.env.AUTH_ENCRYPTION_KEY = "0123456789abcdef0123456789abcdef";
|
|
});
|
|
|
|
describe("비밀번호 해시/검증", () => {
|
|
it("8자 이상 비밀번호를 bcrypt 해시로 안전하게 저장하고, 검증에 성공해야 한다", async () => {
|
|
const password = "securePass1";
|
|
|
|
const hash = await hashPassword(password);
|
|
|
|
expect(hash).toBeTypeOf("string");
|
|
expect(hash).not.toBe(password);
|
|
|
|
const ok = await verifyPassword(password, hash);
|
|
const fail = await verifyPassword("wrongPass", hash);
|
|
|
|
expect(ok).toBe(true);
|
|
expect(fail).toBe(false);
|
|
});
|
|
|
|
it("8자 미만 비밀번호는 해시 시도 시 오류를 발생시켜야 한다", async () => {
|
|
const shortPassword = "short"; // 5자
|
|
|
|
await expect(hashPassword(shortPassword)).rejects.toThrow();
|
|
});
|
|
});
|
|
|
|
describe("JWT 액세스 토큰", () => {
|
|
it("유저 정보를 기반으로 JWT 액세스 토큰을 발급하고, 검증 시 동일한 정보가 나와야 한다", async () => {
|
|
const user: TestUser = {
|
|
id: "user-1",
|
|
email: "user@example.com",
|
|
tokenVersion: 1,
|
|
};
|
|
|
|
const token = await signAccessToken(user);
|
|
|
|
expect(token).toBeTypeOf("string");
|
|
expect(token.length).toBeGreaterThan(10);
|
|
|
|
const payload = await verifyAccessToken(token);
|
|
|
|
expect(payload).not.toBeNull();
|
|
expect(payload?.sub).toBe(user.id);
|
|
expect(payload?.email).toBe(user.email);
|
|
expect(payload?.tokenVersion).toBe(user.tokenVersion);
|
|
|
|
const anyPayload = payload as any;
|
|
const iat = anyPayload.iat;
|
|
const exp = anyPayload.exp;
|
|
|
|
expect(typeof iat).toBe("number");
|
|
expect(typeof exp).toBe("number");
|
|
expect(exp > iat).toBe(true);
|
|
});
|
|
|
|
it("잘못된 토큰이나 시크릿으로 검증할 경우 null 을 반환해야 한다", async () => {
|
|
const user: TestUser = {
|
|
id: "user-2",
|
|
email: "user2@example.com",
|
|
tokenVersion: 3,
|
|
};
|
|
|
|
const token = await signAccessToken(user);
|
|
|
|
// 시크릿을 바꿔서 검증 실패 상황을 만든다.
|
|
process.env.AUTH_JWT_SECRET = "other-secret";
|
|
|
|
const payload = await verifyAccessToken(token);
|
|
expect(payload).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("민감정보 JSON 암호화/복호화", () => {
|
|
it("JSON 객체를 암호화한 뒤 복호화하면 원래 객체와 동일해야 한다", async () => {
|
|
const original = {
|
|
cardToken: "tok_123",
|
|
customerId: "cus_456",
|
|
meta: { plan: "pro", country: "KR" },
|
|
};
|
|
|
|
const ciphertext = await encryptJson(original);
|
|
|
|
expect(ciphertext).toBeTypeOf("string");
|
|
expect(ciphertext).not.toContain("tok_123");
|
|
expect(ciphertext).not.toContain("cus_456");
|
|
|
|
const decrypted = await decryptJson<typeof original>(ciphertext);
|
|
|
|
expect(decrypted).toEqual(original);
|
|
});
|
|
|
|
it("암호화 문자열이 손상되었거나 키가 잘못된 경우 복호화 시 오류를 발생시켜야 한다", async () => {
|
|
const original = { secret: "value" };
|
|
const ciphertext = await encryptJson(original);
|
|
|
|
// 중간 일부를 잘라 손상시킨다.
|
|
const broken = ciphertext.slice(0, Math.floor(ciphertext.length / 2));
|
|
|
|
await expect(decryptJson(broken as string)).rejects.toThrow();
|
|
|
|
// 키를 바꿔서 복호화 시도
|
|
process.env.AUTH_ENCRYPTION_KEY = "ffffffffffffffffffffffffffffffff";
|
|
await expect(decryptJson(ciphertext)).rejects.toThrow();
|
|
});
|
|
});
|
|
});
|