62 lines
2.1 KiB
TypeScript
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,
|
|
);
|
|
});
|
|
});
|