diff --git a/README.md b/README.md index f82e696..18aca06 100644 --- a/README.md +++ b/README.md @@ -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://:@/?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 @@ -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 | diff --git a/buildScripts/seedPasswords.js b/buildScripts/seedPasswords.js new file mode 100644 index 0000000..45ca211 --- /dev/null +++ b/buildScripts/seedPasswords.js @@ -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();