diff --git a/.env.example b/.env.example index 858636ca..3c899d9d 100644 --- a/.env.example +++ b/.env.example @@ -16,6 +16,9 @@ JWT_EXPIRES_IN=15m JWT_REFRESH_SECRET=your-super-secret-refresh-key-change-this-in-production JWT_REFRESH_EXPIRES_IN=7d +# Security Configuration +BCRYPT_ROUNDS=10 + # Redis Configuration (for Bull Queue) REDIS_HOST=localhost REDIS_PORT=6379 diff --git a/README.md b/README.md index f9b541df..1ab3fb1e 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,22 @@ curl -H "X-API-Version: 1" http://localhost:3000/users ## 📊 Architecture +## ⚙️ Tech Stack + +| Layer | Technology | +| ------------- | -------------------------- | +| Framework | NestJS | +| Database | PostgreSQL + TypeORM | +| Blockchain | Starknet + Starknet.js | +| Realtime | WebSockets (Gateway) | +| Queues/Async | BullMQ + Redis (optional) | +| File Uploads | Cloudinary | +| Config Mgmt | @nestjs/config | +| Testing | Jest + Supertest | +| Auth | JWT + Wallet Sign-In | +| Deployment | Docker, Railway, or Fly.io | +| File Upload | Cloudinary | +| Security | Helmet + bcrypt | Security headers and password hashing | ### System Overview TeachLink Backend follows a **modular microservices architecture** built on NestJS, designed for scalability and maintainability. The system uses a layered approach with clear separation of concerns: @@ -233,7 +249,61 @@ TeachLink Backend follows a **modular microservices architecture** built on Nest | **Validation** | class-validator + class-transformer | DTO validation | | **Security** | Helmet + bcrypt | Security headers and password hashing | -## �️ Database +## 🔐 Security + +### Password Hashing Configuration + +The application uses **bcrypt** for password hashing with configurable rounds via the `BCRYPT_ROUNDS` environment variable. + +#### Recommended Bcrypt Rounds by Environment + +| Environment | Recommended Rounds | Hash Time (ms) | Security Level | Performance Impact | +| ----------- | ----------------- | -------------- | -------------- | ------------------ | +| **Development** | 8-10 | 50-100 | Good | Low | +| **Staging** | 10-12 | 100-300 | High | Medium | +| **Production** | 12-14 | 300-1000 | Very High | High | + +#### Security vs Performance Tradeoffs + +**Lower Rounds (4-8):** +- ✅ Faster authentication +- ✅ Lower CPU usage +- ⚠️ Reduced security against brute force attacks +- ⚠️ May be vulnerable to GPU-based cracking + +**Higher Rounds (12-15):** +- ✅ Strong resistance against brute force attacks +- ✅ Future-proof against computational advances +- ❌ Slower authentication (may impact user experience) +- ❌ Higher CPU usage (may affect scalability) + +#### Configuration Example + +```env +# Development (faster, less secure) +BCRYPT_ROUNDS=8 + +# Production (slower, more secure) +BCRYPT_ROUNDS=12 +``` + +#### Security Best Practices + +1. **Minimum 10 rounds** for production environments +2. **Monitor authentication performance** when increasing rounds +3. **Consider rate limiting** to prevent brute force attacks +4. **Use hardware security modules** for high-security applications +5. **Regular security audits** to assess adequate protection levels + +#### Migration Considerations + +When changing `BCRYPT_ROUNDS`: +- Existing passwords remain valid until users change them +- New passwords will use the configured rounds +- Consider forcing password reset for sensitive accounts +- Gradually increase rounds to monitor performance impact + +## 🗄️ Database ### Index Strategy diff --git a/src/config/env.validation.ts b/src/config/env.validation.ts index 38b141c2..bfd401a6 100644 --- a/src/config/env.validation.ts +++ b/src/config/env.validation.ts @@ -29,6 +29,9 @@ export const envValidationSchema = Joi.object({ // Encryption ENCRYPTION_SECRET: Joi.string().min(32).required(), + // Security Configuration + BCRYPT_ROUNDS: Joi.number().integer().min(4).max(15).default(10), + // Stripe Configuration STRIPE_SECRET_KEY: Joi.string().required(), STRIPE_WEBHOOK_SECRET: Joi.string().required(), diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 8f539532..d08258c0 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -13,6 +13,7 @@ import { GetUsersDto } from './dto/get-users.dto'; import { CachingService } from '../caching/caching.service'; import { CACHE_TTL, CACHE_PREFIXES, CACHE_EVENTS } from '../caching/caching.constants'; import { EventEmitter2 } from '@nestjs/event-emitter'; +import { ConfigService } from '@nestjs/config'; @Injectable() export class UsersService { @@ -21,7 +22,8 @@ export class UsersService { private readonly userRepository: Repository, private readonly cachingService: CachingService, private readonly eventEmitter: EventEmitter2, - ) {} + private readonly configService: ConfigService, + ) { } async create(createUserDto: CreateUserDto): Promise { // Check if user already exists @@ -32,7 +34,8 @@ export class UsersService { ensureUserDoesNotExist(existingUser, 'User with this email already exists'); // Hash password - const hashedPassword = await bcrypt.hash(createUserDto.password, 10); + const bcryptRounds = this.configService.get('BCRYPT_ROUNDS') || 10; + const hashedPassword = await bcrypt.hash(createUserDto.password, bcryptRounds); // Create user const user = this.userRepository.create({ @@ -141,7 +144,8 @@ export class UsersService { // Append current, maintain last 5 entries user.passwordHistory = [...recentPasswords, user.password].slice(-5); - updateUserDto.password = await bcrypt.hash(plainPassword, 10); + const bcryptRounds = this.configService.get('BCRYPT_ROUNDS') || 10; + updateUserDto.password = await bcrypt.hash(plainPassword, bcryptRounds); } Object.assign(user, updateUserDto);