Files
auth-tools/tests/otp/profile.test.ts
T

62 lines
2.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { parseOtpAuth, serializeOtpAuth } from "../../src/otp/profile";
describe("otpauth profiles", () => {
it("parses and serializes a TOTP profile without losing extensions", () => {
const parsed = parseOtpAuth(
"otpauth://totp/Example:alice%40example.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&algorithm=SHA256&digits=8&period=45&image=ignored",
);
expect(parsed.profile).toMatchObject({
kind: "totp",
issuer: "Example",
account: "alice@example.com",
algorithm: "SHA-256",
digits: 8,
period: 45,
});
expect(parsed.profile.extensions.get("image")).toBe("ignored");
expect(serializeOtpAuth(parsed.profile)).toContain("image=ignored");
});
it("requires an HOTP counter and preserves 64-bit values", () => {
expect(() =>
parseOtpAuth("otpauth://hotp/Example?secret=JBSWY3DPEHPK3PXP"),
).toThrow(/counter/iu);
const parsed = parseOtpAuth(
"otpauth://hotp/Example?secret=JBSWY3DPEHPK3PXP&counter=18446744073709551615",
);
expect(parsed.profile.counter).toBe((1n << 64n) - 1n);
});
it("rejects duplicate known parameters", () => {
expect(() =>
parseOtpAuth(
"otpauth://totp/Example?secret=JBSWY3DPEHPK3PXP&secret=JBSWY3DPEHPK3PXP",
),
).toThrow(/duplicate/iu);
});
it("rejects partially numeric parameters and unsafe labels", () => {
expect(() =>
parseOtpAuth(
"otpauth://totp/Example?secret=JBSWY3DPEHPK3PXP&digits=6oops",
),
).toThrow(/integer/iu);
const parsed = parseOtpAuth(
"otpauth://totp/Example?secret=JBSWY3DPEHPK3PXP",
);
parsed.profile.account = "unsafe:label";
expect(() => serializeOtpAuth(parsed.profile)).toThrow(/colon/iu);
});
it("warns without silently reconciling issuer mismatch", () => {
const result = parseOtpAuth(
"otpauth://totp/Display:alice?secret=JBSWY3DPEHPK3PXP&issuer=canonical.example",
);
expect(result.profile.issuer).toBe("canonical.example");
expect(result.warnings.some(({ code }) => code === "issuer-mismatch")).toBe(
true,
);
});
});