Files
MineGeo/tests/initial-password.test.ts

113 lines
2.6 KiB
TypeScript

import { test } from "node:test";
import assert from "node:assert/strict";
import {
authActionSchema,
initialPasswordSchema,
newPasswordSchema,
} from "../shared/account";
import {
generateInitialPassword,
issueInitialPassword,
} from "../shared/initial-password";
test("email-link sign-in is rejected, password login and recovery remain supported", () => {
assert.equal(
authActionSchema.safeParse({ action: "magic", email: "owner@example.com" })
.success,
false,
);
assert.equal(
authActionSchema.safeParse({
action: "recover",
email: "owner@example.com",
}).success,
true,
);
assert.equal(
authActionSchema.safeParse({
action: "login",
email: "owner@example.com",
password: "existing-password",
}).success,
true,
);
});
test("generated first passwords are strong, unique and cannot target another account", () => {
const passwords = new Set(
Array.from({ length: 100 }, generateInitialPassword),
);
assert.equal(passwords.size, 100);
for (const password of passwords) {
assert.equal(newPasswordSchema.safeParse(password).success, true);
assert.ok(
/[A-Z]/.test(password) &&
/[a-z]/.test(password) &&
/\d/.test(password) &&
/[!]/.test(password),
);
}
assert.equal(
initialPasswordSchema.safeParse({
password: generateInitialPassword(),
userId: "someone-else",
}).success,
false,
);
});
test("first password issuance cannot overwrite a password or replay an uncertain Auth update", async () => {
let claimed = false,
updates = 0;
const operations = {
claim: async () => {
if (claimed) return false;
claimed = true;
return true;
},
update: async (id: string) => {
assert.equal(id, "verified-owner");
updates++;
},
};
await issueInitialPassword(
"verified-owner",
generateInitialPassword(),
operations,
);
await assert.rejects(
issueInitialPassword(
"verified-owner",
generateInitialPassword(),
operations,
),
/already/,
);
assert.equal(updates, 1);
let uncertainClaimed = false;
const uncertain = {
claim: async () => {
if (uncertainClaimed) return false;
uncertainClaimed = true;
return true;
},
update: async () => {
throw new Error("network interrupted");
},
};
await assert.rejects(
issueInitialPassword(
"verified-owner",
generateInitialPassword(),
uncertain,
),
/network interrupted/,
);
await assert.rejects(
issueInitialPassword(
"verified-owner",
generateInitialPassword(),
uncertain,
),
/already/,
);
});