113 lines
2.6 KiB
TypeScript
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/,
|
|
);
|
|
});
|