42 lines
1.5 KiB
TypeScript
42 lines
1.5 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import {
|
|
decodeBase32,
|
|
generateTotp,
|
|
generateTotpSecret,
|
|
totpUri,
|
|
verifyTotp,
|
|
} from "../src/totp/index.ts";
|
|
|
|
test("TOTP matches the RFC 6238 SHA-1 vector", async () => {
|
|
const secret = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ";
|
|
expect(await generateTotp(secret, { timestamp: 59_000, digits: 8 })).toBe("94287082");
|
|
expect(await verifyTotp(secret, "94287082", { timestamp: 59_000, digits: 8, window: 0 })).toEqual(
|
|
{ valid: true, counter: 1 },
|
|
);
|
|
});
|
|
|
|
test("TOTP rejects malformed secrets, tokens, and unsafe options", async () => {
|
|
expect(() => decodeBase32("JBSW0Y3P")).toThrow("Invalid base32 secret");
|
|
expect(() => decodeBase32("====")).toThrow("Invalid base32 secret");
|
|
expect(() => generateTotpSecret(() => new Uint8Array(19))).toThrow(
|
|
"must return exactly 20 bytes",
|
|
);
|
|
await expect(generateTotp("JBSWY3DPEHPK3PXP", { period: 0 })).rejects.toThrow("TOTP period");
|
|
expect(await verifyTotp("JBSWY3DPEHPK3PXP", "12ab56", { timestamp: 59_000 })).toEqual({
|
|
valid: false,
|
|
});
|
|
});
|
|
|
|
test("TOTP URI validates and normalizes configuration", () => {
|
|
const uri = totpUri({
|
|
issuer: " WorkRoot ",
|
|
accountName: " user@example.com ",
|
|
secret: "JBSW Y3DP-EHPK3PXP",
|
|
});
|
|
expect(uri).toContain("secret=JBSWY3DPEHPK3PXP");
|
|
expect(uri).toContain("issuer=WorkRoot");
|
|
expect(() =>
|
|
totpUri({ issuer: "", accountName: "user@example.com", secret: "JBSWY3DPEHPK3PXP" }),
|
|
).toThrow("issuer and account name");
|
|
});
|