Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Here is the list of all utilities:
- [WebP Converter](https://jam.dev/utilities/webp-converter)
- [SQL Minifer](https://jam.dev/utilities/sql-minifier)
- [Internet Speed Test](https://jam.dev/utilities/internet-speed-test)
- [Password Generator](https://jam.dev/utilities/password-generator)

### Built With

Expand Down
92 changes: 92 additions & 0 deletions components/seo/PasswordGeneratorSEO.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
export default function PasswordGeneratorSEO() {
return (
<div className="content-wrapper">
<section>
<p>
Generate secure passwords instantly with this free online password
generator. Whether you&apos;re protecting your accounts, apps, or
sensitive data, Jam&apos;s Password Generator creates strong,
randomized passwords to keep you safe.
</p>
</section>

<section>
<h2>How to Use Jam&apos;s Password Generator</h2>
<p>
Quickly create a strong password tailored to your needs. Follow these
simple steps:
</p>
<ul>
<li>
<b>Select options:</b> <br /> Choose to include lowercase,
uppercase, numbers, and/or symbols.
</li>
<li>
<b>Set length:</b> <br /> Pick a password length.
</li>
<li>
<b>Generate:</b> <br /> Click &quot;Generate&quot; to instantly get
a secure password.
</li>
<li>
<b>Copy:</b> <br /> Copy your password safely with one click.
</li>
</ul>
</section>

<section>
<h2>Why Use a Password Generator?</h2>
<p>
Manually created passwords are often weak and easy to guess. A
password generator helps you avoid common mistakes and ensures your
credentials are strong.
</p>
<ul>
<li>
<b>Security:</b> <br /> Random, unpredictable passwords are harder
to crack.
</li>
<li>
<b>Customization:</b> <br /> Adjust the length and types of
characters to meet site requirements.
</li>
<li>
<b>Convenience:</b> <br /> Generate reliable passwords instantly,
without thinking.
</li>
</ul>
</section>

<section>
<h2>FAQs</h2>
<ul>
<li>
<b>Is this password generator free?</b> <br /> Yes. It&apos;s free,
open-source, and ad-free.
</li>
<li>
<b>Are the generated passwords stored?</b> <br />
No. All jam.dev/utilities are local-only browser tools that work
entirely without any backend. Your passwords are generated in your
browser and never leave your device.
</li>
<li>
<b>How long should my password be?</b> <br /> For strong security,
use at least 12 characters. For maximum protection, 20+ characters
are recommended.
</li>
<li>
<b>Can I use these passwords everywhere?</b> <br /> Yes. They are
compatible with any website, app, or system that accepts custom
passwords.
</li>
<li>
<b>How strong are the passwords?</b> <br /> They are generated using
cryptographically secure random values, making them highly resistant
to attacks.
</li>
</ul>
</section>
</div>
);
}
52 changes: 52 additions & 0 deletions components/utils/password-generator.utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { PasswordBuilder } from "./password-generator.utils";

describe("PasswordBuilder", () => {
it("generates a character pool correctly according to options", () => {
const builder = new PasswordBuilder(true, false, false, false, 8);
const result = builder.Build();

expect(result).toMatch(/^[a-z]+$/);
expect(result.length).toBe(8);
});

it("includes at least one character from each selected category", () => {
const builder = new PasswordBuilder(true, true, true, true, 20);
const password = builder.Build();

expect(password).toMatch(/[a-z]/); // lowercase
expect(password).toMatch(/[A-Z]/); // uppercase
expect(password).toMatch(/[0-9]/); // numbers
expect(password).toMatch(/[!@#$%^&*()_+[\]{}|;:,.<>?/~`\-=]/); // symbols
expect(password.length).toBe(20);
});

it("respects the minimum and maximum length constraints", () => {
const builderMin = new PasswordBuilder(true, false, false, false, 0);
expect(builderMin.Build().length).toBeGreaterThanOrEqual(1);

const builderMax = new PasswordBuilder(true, false, false, false, 999);
expect(builderMax.Build().length).toBeLessThanOrEqual(128);
});

it("handles when desired length is smaller than the number of categories", () => {
// 4 categories but desired length 2
const builder = new PasswordBuilder(true, true, true, true, 2);
const password = builder.Build();

// Must include at least one char from each category
expect(password).toMatch(/[a-z]/);
expect(password).toMatch(/[A-Z]/);
expect(password).toMatch(/[0-9]/);
expect(password).toMatch(/[!@#$%^&*()_+[\]{}|;:,.<>?/~`\-=]/);
// And length must be at least 4
expect(password.length).toBeGreaterThanOrEqual(4);
});

it("GetMandatoryChars returns characters from the correct categories", () => {
const builder = new PasswordBuilder(true, true, false, false, 10);
const picks = builder.GetMandatoryChars();
expect(picks.some((c) => /[a-z]/.test(c))).toBeTruthy();
expect(picks.some((c) => /[A-Z]/.test(c))).toBeTruthy();
expect(picks.length).toBe(2); // two categories selected
});
});
82 changes: 82 additions & 0 deletions components/utils/password-generator.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
export const getRandomInt = (max: number) => {
const arr = new Uint32Array(1);
window.crypto.getRandomValues(arr);
return arr[0] % max;
};

export class PasswordBuilder {
private LOWER = "abcdefghijklmnopqrstuvwxyz";
private UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private NUMS = "0123456789";
private SYMBOLS = "!@#$%^&*()_+[]{}|;:,.<>?/~`-=";
private characterPool = "";

constructor(
private includeLowercase: boolean,
private includeUppercase: boolean,
private includeNumbers: boolean,
private includeSymbols: boolean,
private desiredLength: number
) {
let pool = "";
if (this.includeLowercase) pool += this.LOWER;
if (this.includeUppercase) pool += this.UPPER;
if (this.includeNumbers) pool += this.NUMS;
if (this.includeSymbols) pool += this.SYMBOLS;
this.characterPool = pool;
}

GetMandatoryChars() {
const picks: string[] = [];

if (this.includeLowercase) {
const lowers = this.LOWER.split("");
picks.push(lowers[getRandomInt(lowers.length)]);
}
if (this.includeUppercase) {
const uppers = this.UPPER.split("");
picks.push(uppers[getRandomInt(uppers.length)]);
}
if (this.includeNumbers) {
const numbers = this.NUMS.split("");
picks.push(numbers[getRandomInt(numbers.length)]);
}
if (this.includeSymbols) {
const symbols = this.SYMBOLS.split("");
picks.push(symbols[getRandomInt(symbols.length)]);
}

return picks;
}

Build() {
const categoriesAmount = [
this.includeLowercase,
this.includeUppercase,
this.includeNumbers,
this.includeSymbols,
].filter((b) => !!b);

const finalLen = Math.max(1, Math.min(128, this.desiredLength));
const useLen =
finalLen < categoriesAmount.length ? categoriesAmount.length : finalLen;

// start with selected categories mandatory characters
const guaranteed = this.GetMandatoryChars();
const resultChars: string[] = [...guaranteed];

// fill the rest of the input length
for (let i = resultChars.length; i < useLen; i++) {
const idx = getRandomInt(this.characterPool.length);
resultChars.push(this.characterPool[idx]);
}

// Fisher-Yates shuffle algorithm
for (let i = resultChars.length - 1; i > 0; i--) {
const j = getRandomInt(i + 1);
[resultChars[i], resultChars[j]] = [resultChars[j], resultChars[i]];
}

return resultChars.join("");
}
}
6 changes: 6 additions & 0 deletions components/utils/tools-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,10 @@ export const tools = [
"Test your internet connection speed with accurate measurements by using Cloudflare's global network.",
link: "/utilities/internet-speed-test",
},
{
title: "Password Generator",
description:
"Mix uppercase, lowercase, numbers, and symbols to generate bulletproof passwords that will keep your accounts safe.",
link: "/utilities/password-generator",
},
];
Loading