109 lines
4.0 KiB
TypeScript
109 lines
4.0 KiB
TypeScript
import { describe, test, expect } from "bun:test";
|
|
import Password from "../../src/modules/tools/Password";
|
|
|
|
describe("Password", () => {
|
|
// -------------------------------------------------------------------
|
|
// generateSalt
|
|
// -------------------------------------------------------------------
|
|
describe("generateSalt()", () => {
|
|
test("returns a string of default length 12", () => {
|
|
const salt = Password.generateSalt();
|
|
expect(typeof salt).toBe("string");
|
|
expect(salt.length).toBe(12);
|
|
});
|
|
|
|
test("returns a string of custom length", () => {
|
|
const salt = Password.generateSalt({ length: 24 });
|
|
expect(salt.length).toBe(24);
|
|
});
|
|
|
|
test("generates different salts each time", () => {
|
|
const s1 = Password.generateSalt();
|
|
const s2 = Password.generateSalt();
|
|
// Extremely unlikely to be equal
|
|
expect(s1).not.toBe(s2);
|
|
});
|
|
|
|
test("returns hex characters only", () => {
|
|
const salt = Password.generateSalt({ length: 20 });
|
|
expect(/^[0-9a-f]+$/.test(salt)).toBe(true);
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------
|
|
// hash
|
|
// -------------------------------------------------------------------
|
|
describe("hash()", () => {
|
|
test("returns a BLAKE2s prefixed string", () => {
|
|
const hashed = Password.hash("mypassword");
|
|
expect(hashed.startsWith("BLAKE2s$")).toBe(true);
|
|
});
|
|
|
|
test("contains three dollar-sign separated parts", () => {
|
|
const hashed = Password.hash("mypassword", { overrideSalt: "testsalt" });
|
|
const parts = hashed.split("$");
|
|
expect(parts.length).toBe(3);
|
|
expect(parts[0]).toBe("BLAKE2s");
|
|
expect(parts[2]).toBe("testsalt");
|
|
});
|
|
|
|
test("same password + same salt produces same hash", () => {
|
|
const h1 = Password.hash("password", { overrideSalt: "salt123" });
|
|
const h2 = Password.hash("password", { overrideSalt: "salt123" });
|
|
expect(h1).toBe(h2);
|
|
});
|
|
|
|
test("different passwords produce different hashes", () => {
|
|
const h1 = Password.hash("password1", { overrideSalt: "salt" });
|
|
const h2 = Password.hash("password2", { overrideSalt: "salt" });
|
|
expect(h1).not.toBe(h2);
|
|
});
|
|
|
|
test("different salts produce different hashes", () => {
|
|
const h1 = Password.hash("password", { overrideSalt: "salt1" });
|
|
const h2 = Password.hash("password", { overrideSalt: "salt2" });
|
|
expect(h1).not.toBe(h2);
|
|
});
|
|
|
|
test("generates salt when saltOpts provided", () => {
|
|
const hashed = Password.hash("password", { saltOpts: { length: 16 } });
|
|
const parts = hashed.split("$");
|
|
// Should have a 16-char salt
|
|
expect(parts[2]!.length).toBe(16);
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------
|
|
// validate
|
|
// -------------------------------------------------------------------
|
|
describe("validate()", () => {
|
|
test("returns true for matching password", () => {
|
|
const hashed = Password.hash("correctpassword", { overrideSalt: "salt" });
|
|
expect(Password.validate("correctpassword", hashed)).toBe(true);
|
|
});
|
|
|
|
test("returns false for wrong password", () => {
|
|
const hashed = Password.hash("correctpassword", { overrideSalt: "salt" });
|
|
// timingSafeEqual throws if buffers are different lengths, but since
|
|
// the hash output has the same length regardless, a wrong password
|
|
// with same-length output will return false.
|
|
// However if the buffers are different lengths it throws — in that
|
|
// case we just check the behaviour is consistent:
|
|
try {
|
|
const result = Password.validate("wrongpassword", hashed);
|
|
expect(result).toBe(false);
|
|
} catch {
|
|
// timingSafeEqual may throw on different lengths, which is acceptable
|
|
expect(true).toBe(true);
|
|
}
|
|
});
|
|
|
|
test("round-trips correctly with generated salt", () => {
|
|
const hashed = Password.hash("securePass123!", {
|
|
saltOpts: { length: 12 },
|
|
});
|
|
expect(Password.validate("securePass123!", hashed)).toBe(true);
|
|
});
|
|
});
|
|
});
|