Skip to content
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
60 changes: 57 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,21 @@ A JavaScript development environment starter kit built on the MEAN stack toolcha
npm install
```

2. **Start the development server:**
2. **Set the MongoDB connection string:**

```bash
export MONGODB_URI="mongodb+srv://<user>:<password>@<cluster>/?appName=Cluster0"
```

3. **Seed user passwords** (generates a unique random password per user and stores the bcrypt hash):

```bash
npx babel-node buildScripts/seedPasswords.js > credentials.csv
```

The plaintext credentials are printed to stdout in CSV format (`email,name,password`). Redirect to a file to save them. Only the bcrypt hashes are stored in the database.

4. **Start the development server:**

```bash
npm start
Expand All @@ -38,18 +52,58 @@ A JavaScript development environment starter kit built on the MEAN stack toolcha
- Starts ESLint in watch mode
- Starts Mocha tests in watch mode

3. **Run linting manually:**
5. **Run linting manually:**

```bash
npm run lint
```

4. **Run tests manually:**
6. **Run tests manually:**

```bash
npm test
```

## Login Credentials

After running the seed script, each of the 186 users in `sample_mflix.users` has a unique random password. Check your `credentials.csv` output file for the full list. Example entries:

| Email | Name |
|---|---|
| `sean_bean@gameofthron.es` | Ned Stark |
| `lena_headey@gameofthron.es` | Cersei Lannister |
| `emilia_clarke@gameofthron.es` | Daenerys Targaryen |
| `kit_harington@gameofthron.es` | Jon Snow |

> **Note:** Run the seed script again at any time to regenerate all passwords. The previous passwords will be overwritten.

## Password Hashing Methodology

This application uses **bcrypt** for password hashing, implemented via the `bcryptjs` library.

| Property | Value |
|---|---|
| **Algorithm** | bcrypt (`$2b$` prefix) |
| **Cost factor** | 12 rounds (2^12 = 4,096 iterations) |
| **Salt** | Unique 128-bit salt auto-generated per hash |
| **Hash output** | 60 characters: `$2b$12$<22-char salt><31-char hash>` |
| **Password generation** | `crypto.randomBytes(12)` → base64 → 16-character string |
| **CSPRNG** | Node.js `crypto` module (cryptographically secure) |

### Why bcrypt?

- **Purpose-built for passwords** — unlike SHA-256 or MD5, bcrypt is intentionally slow to resist brute-force attacks
- **Built-in salt** — each hash includes a unique random salt, preventing rainbow table attacks
- **Tunable cost factor** — the work factor can be increased as hardware improves (currently set to 12 rounds)
- **Constant-time comparison** — `bcrypt.compare()` mitigates timing attacks

### How it works

1. The seed script generates a 16-character random password per user using `crypto.randomBytes()` (CSPRNG)
2. Each password is hashed with `bcrypt.hash(password, 12)` — bcrypt auto-generates a unique salt
3. Only the 60-character hash string is stored in the `password` field of `sample_mflix.users`
4. At login, `bcrypt.compare(inputPassword, storedHash)` verifies the password without ever decrypting the hash

## Available Scripts

| Script | Description |
Expand Down
78 changes: 78 additions & 0 deletions buildScripts/seedPasswords.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import crypto from 'crypto';
import { MongoClient } from 'mongodb';
import bcrypt from 'bcryptjs';

/*eslint-disable no-console*/

/**
* Password Hashing Methodology
* =============================
* 1. Random password generation:
* - Uses Node.js crypto.randomBytes() (CSPRNG) to generate 12 bytes
* - Encodes as base64 and takes 16 characters for a strong random password
* - Each user receives a unique password
*
* 2. Password hashing:
* - Algorithm: bcrypt (via bcryptjs library)
* - Cost factor: 12 rounds (2^12 = 4096 iterations)
* - bcrypt automatically generates a unique 128-bit salt per hash
* - Output format: $2b$12$<22-char salt><31-char hash> (60 chars total)
*
* 3. Storage:
* - Only the bcrypt hash is stored in the database
* - Plaintext passwords are printed to stdout for initial setup only
* - Output can be redirected to a file: npx babel-node buildScripts/seedPasswords.js > credentials.csv
*
* Why bcrypt?
* - Designed specifically for password hashing (unlike SHA-256, MD5)
* - Intentionally slow to resist brute-force attacks
* - Built-in salt prevents rainbow table attacks
* - Cost factor is tunable to increase work as hardware improves
*/

const BCRYPT_ROUNDS = 12;
const PASSWORD_LENGTH = 16;

const uri = process.env.MONGODB_URI || 'mongodb://localhost:27017';
const dbName = process.env.MONGODB_DB || 'sample_mflix';

function generatePassword() {
return crypto.randomBytes(12).toString('base64').slice(0, PASSWORD_LENGTH);
}

async function seedPasswords() {
const client = new MongoClient(uri);
try {
await client.connect();
const db = client.db(dbName);

const users = await db.collection('users').find(
{},
{ projection: { _id: 1, name: 1, email: 1 } }
).toArray();

console.log('email,name,password');

for (var i = 0; i < users.length; i++) {
var user = users[i];
var plaintext = generatePassword();
var hash = await bcrypt.hash(plaintext, BCRYPT_ROUNDS);

await db.collection('users').updateOne(
{ _id: user._id },
{ $set: { password: hash } }
);

console.log(user.email + ',' + (user.name || '') + ',' + plaintext);
}

console.error('Seeded ' + users.length + ' users with unique random passwords (bcrypt, ' + BCRYPT_ROUNDS + ' rounds).');
} catch (err) {
console.error('Error seeding passwords:', err.message);
process.exit(1);
} finally {
await client.close();
}
}

seedPasswords();