Add secure password generator
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
function assert(condition: unknown, message = "assertion failed"): asserts condition {
|
||||
if (!condition) throw new Error(message);
|
||||
}
|
||||
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import PasswordField from "../src/components/PasswordField";
|
||||
import {
|
||||
DEFAULT_PASSWORD_GENERATOR_OPTIONS,
|
||||
generateSecurePassword,
|
||||
type SecureRandomFill
|
||||
} from "../src/components/passwordGenerator";
|
||||
import { PlatformLanguageProvider } from "../src/i18n/LanguageContext";
|
||||
|
||||
function sequenceFill(sequence: number[]): SecureRandomFill {
|
||||
let index = 0;
|
||||
return (values) => {
|
||||
values[0] = sequence[index % sequence.length] >>> 0;
|
||||
index += 1;
|
||||
return values;
|
||||
};
|
||||
}
|
||||
|
||||
const password = generateSecurePassword(
|
||||
DEFAULT_PASSWORD_GENERATOR_OPTIONS,
|
||||
sequenceFill(Array.from({ length: 64 }, (_, index) => index * 7919))
|
||||
);
|
||||
assert(password.length === 20, "the requested password length is preserved");
|
||||
assert(/[a-z]/.test(password), "selected lowercase characters are represented");
|
||||
assert(/[A-Z]/.test(password), "selected uppercase characters are represented");
|
||||
assert(/[0-9]/.test(password), "selected digits are represented");
|
||||
assert(/[!#$%&*+\-=?@^_]/.test(password), "selected symbols are represented");
|
||||
|
||||
let rejectedDraws = 0;
|
||||
const rejectionAware = generateSecurePassword(
|
||||
{ length: 12, lowercase: true, uppercase: false, digits: false, symbols: false },
|
||||
(values) => {
|
||||
values[0] = rejectedDraws++ % 2 === 0 ? 0xffff_ffff : 7;
|
||||
return values;
|
||||
}
|
||||
);
|
||||
assert(rejectionAware.length === 12, "out-of-range random draws are rejected without shortening output");
|
||||
assert(rejectedDraws > 12, "modulo-bias rejection consumes a replacement secure draw");
|
||||
|
||||
for (const [options, expected] of [
|
||||
[{ ...DEFAULT_PASSWORD_GENERATOR_OPTIONS, length: 11 }, "between 12 and 128"],
|
||||
[{ ...DEFAULT_PASSWORD_GENERATOR_OPTIONS, lowercase: false, uppercase: false, digits: false, symbols: false }, "at least one character set"]
|
||||
] as const) {
|
||||
let message = "";
|
||||
try {
|
||||
generateSecurePassword(options, sequenceFill([1]));
|
||||
} catch (error) {
|
||||
message = error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
assert(message.includes(expected), `invalid generator options should explain ${expected}`);
|
||||
}
|
||||
|
||||
const markup = renderToStaticMarkup(
|
||||
<PlatformLanguageProvider>
|
||||
<PasswordField value="" onValueChange={() => undefined} generator />
|
||||
</PlatformLanguageProvider>
|
||||
);
|
||||
assert(markup.includes('aria-label="Generate password"'), "the opt-in generator action is accessible");
|
||||
assert(markup.includes("lucide-dice-5"), "the familiar generator icon is used");
|
||||
assert(!markup.includes("password-generator-dialog"), "the generator dialog stays closed until explicitly requested");
|
||||
|
||||
console.log("Password generator contract passed.");
|
||||
Reference in New Issue
Block a user