Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

is-common updated: now supports an additional common passwords datasource #1263

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,19 @@ if (passwordHashNeedsToBeRefreshed(user.password)) {
npm install @foal/password
```

To prevent users from using very weak passwords such as `123456` or `password`, you can call the `isCommon` function. This utility checks if the given password is part of the 10000 most common passwords listed [here](https://github.com/danielmiessler/SecLists/blob/master/Passwords/Common-Credentials/10-million-password-list-top-10000.txt).
To prevent users from using very weak passwords such as `123456` or `password`, you can call the `isCommon` function. This utility checks if the given password is part of the most common passwords listed [here](https://github.com/danielmiessler/SecLists/tree/master/Passwords/Common-Credentials).

Example 1: Check if the given password is part of the `10,000 most common passwords list`.
```typescript
const isPasswordTooCommon = await isCommon(password);
```
```

Example 2: Check if the given password is part of the `10,000 most common passwords list`.
```typescript
const isPasswordTooCommon = await isCommon(password, 'TenMillionListTop10k');
```

Example 3: Check if the given password is part of the `100,000 most common passwords list`.
```typescript
const isPasswordTooCommon = await isCommon(password, 'TenMillionListTop100k');
```
4 changes: 4 additions & 0 deletions packages/password/DATASOURCES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# DataSources origin

* https://github.com/danielmiessler/SecLists/blob/master/Passwords/Common-Credentials/10-million-password-list-top-10000.txt
* https://github.com/danielmiessler/SecLists/blob/master/Passwords/Common-Credentials/10-million-password-list-top-100000.txt
Binary file not shown.
1 change: 1 addition & 0 deletions packages/password/src/common-password-datasource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type CommonPasswordDataSource = 'TenMillionListTop10k' | 'TenMillionListTop100k';
43 changes: 35 additions & 8 deletions packages/password/src/is-common.util.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,43 @@
import { strictEqual } from 'assert';

// 3p
import { isCommon } from './is-common.util';
import { clearCommonPasswordsCache, isCommon } from './is-common.util';

describe('isCommon', () => {

it('should return true if the given password is part of the 10000 most common passwords.', async () => {
strictEqual(await isCommon('12345'), true);
describe('isCommon util', () => {
describe('isCommon', () => {
describe('default datasource', () => {
it('should return true if the given password is part of the 10000 most common passwords.', async () => {
strictEqual(await isCommon('12345'), true);
});
it('should return false if the given password is not part of the 10000 most common passwords.', async () => {
strictEqual(await isCommon('a bird in the sky'), false);
});
});
describe('10k datasource', () => {
it('should return true if the given password is part of the 100000 most common passwords.', async () => {
strictEqual(await isCommon('12345', 'TenMillionListTop10k'), true);
});
it('should return false if the given password is not part of the 100000 most common passwords.', async () => {
strictEqual(await isCommon('a bird in the sky', 'TenMillionListTop10k'), false);
});
});
describe('100k datasource', () => {
it('should return true if the given password is part of the 100000 most common passwords.', async () => {
strictEqual(await isCommon('Cinder', 'TenMillionListTop100k'), true);
});
it('should return false if the given password is not part of the 100000 most common passwords.', async () => {
strictEqual(await isCommon('a bird in the sky', 'TenMillionListTop100k'), false);
});
});
});

it('should return false if the given password is not part of the 10000 most common passwords.', async () => {
strictEqual(await isCommon('a bird in the sky'), false);
describe('clearCommonPasswordsCache', () => {
it('should not fail if it is empty', () => {
clearCommonPasswordsCache();
});
it('should not fail if it is full', async () => {
await isCommon('12345', 'TenMillionListTop10k');
clearCommonPasswordsCache();
});
});

});
30 changes: 23 additions & 7 deletions packages/password/src/is-common.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,36 @@ import { readFile } from 'node:fs/promises';
import { join } from 'path';
import { promisify } from 'util';
import { gunzip, InputType } from 'zlib';
import { CommonPasswordDataSource } from './common-password-datasource';

let list: string[];
const dataSourcesCache: Record<string, string[]> = {};

const dataSources: Record<CommonPasswordDataSource, string> = {
'TenMillionListTop10k': '10-million-password-list-top-10000.txt.gz',
'TenMillionListTop100k': '10-million-password-list-top-100000.txt.gz',
};

/**
* Test if a password belongs to a list of 10k common passwords.
* Test if a password belongs to a list of common passwords.
*
* @export
* @param {string} password - The password to test.
* @param {CommonPasswordDataSource} database - Data source.
* @returns {Promise<boolean>} - True if the password is found in the list. False otherwise.
*/
export async function isCommon(password: string): Promise<boolean> {
if (!list) {
const fileContent = await readFile(join(__dirname, './10-million-password-list-top-10000.txt.gz'));
list = (await promisify<InputType, Buffer>(gunzip)(fileContent)).toString().split('\n');
export async function isCommon(password: string, dataSource: CommonPasswordDataSource = 'TenMillionListTop10k'): Promise<boolean> {
if (!dataSourcesCache[dataSource]) {
const file = dataSources[dataSource];
const fileContent = await readFile(join(__dirname, './' + file));
dataSourcesCache[dataSource] = (await promisify<InputType, Buffer>(gunzip)(fileContent)).toString().split('\n');
}
return list.includes(password);

return dataSourcesCache[dataSource].includes(password);
}

/**
* Clear the common passwords cache.
*/
export function clearCommonPasswordsCache() {
Object.keys(dataSources).forEach(ds => delete dataSourcesCache[ds]);
}
Loading