Some checks failed
CI Pipeline / Lint Code (push) Has been cancelled
CI Pipeline / Test API Package (push) Has been cancelled
CI Pipeline / Test Web Package (push) Has been cancelled
CI Pipeline / Test Shared Package (push) Has been cancelled
CI Pipeline / Build All Packages (push) Has been cancelled
CI Pipeline / Generate Coverage Report (push) Has been cancelled
Docker Build & Deploy / Build Docker Images (push) Has been cancelled
Docker Build & Deploy / Push Docker Images (push) Has been cancelled
Docker Build & Deploy / Deploy to Staging (push) Has been cancelled
Docker Build & Deploy / Deploy to Production (push) Has been cancelled
E2E Tests / End-to-End Tests (push) Has been cancelled
E2E Tests / E2E Tests (Mobile) (push) Has been cancelled
Security Scanning / NPM Audit (push) Has been cancelled
Security Scanning / Dependency License Check (push) Has been cancelled
Security Scanning / Code Quality Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
Implement a complete authentication system with local email/password authentication, Google OAuth, JWT tokens, and role-based access control. Backend Features: - Database schema with User, RefreshToken, VerificationToken, RecipeShare models - Role-based access control (USER, ADMIN) - Recipe visibility controls (PRIVATE, SHARED, PUBLIC) - Email verification for local accounts - Password reset functionality - JWT access tokens (15min) and refresh tokens (7 days) - Passport.js strategies: Local, JWT, Google OAuth - bcrypt password hashing with 12 salt rounds - Password strength validation (min 8 chars, uppercase, lowercase, number) - Rate limiting on auth endpoints (5 attempts/15min) - Email service with styled HTML templates for verification and password reset API Endpoints: - POST /api/auth/register - Register with email/password - POST /api/auth/login - Login and get tokens - POST /api/auth/logout - Invalidate refresh token - POST /api/auth/refresh - Get new access token - GET /api/auth/verify-email/:token - Verify email address - POST /api/auth/resend-verification - Resend verification email - POST /api/auth/forgot-password - Request password reset - POST /api/auth/reset-password - Reset password with token - GET /api/auth/google - Initiate Google OAuth - GET /api/auth/google/callback - Google OAuth callback - GET /api/auth/me - Get current user info Security Middleware: - requireAuth - Protect routes requiring authentication - requireAdmin - Admin-only route protection - optionalAuth - Routes that work with or without auth - requireOwnership - Check resource ownership Admin Tools: - npm run create-admin - Interactive script to create admin users - verify-user-manual.ts - Helper script for testing Test Coverage: - 49 unit and integration tests (all passing) - Password utility tests (12 tests) - JWT utility tests (17 tests) - Auth middleware tests (12 tests) - Auth routes integration tests (8 tests) Dependencies Added: - passport, passport-local, passport-jwt, passport-google-oauth20 - bcrypt, jsonwebtoken - nodemailer - express-rate-limit, express-validator, cookie-parser Environment Variables Required: - JWT_SECRET, JWT_REFRESH_SECRET - JWT_EXPIRES_IN, JWT_REFRESH_EXPIRES_IN - GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET (optional) - SMTP configuration for email - APP_URL, API_URL 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
106 lines
3.7 KiB
TypeScript
106 lines
3.7 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { hashPassword, comparePassword, validatePasswordStrength } from './password';
|
|
|
|
describe('Password Utilities', () => {
|
|
describe('hashPassword', () => {
|
|
it('should hash a password', async () => {
|
|
const password = 'TestPassword123';
|
|
const hash = await hashPassword(password);
|
|
|
|
expect(hash).toBeDefined();
|
|
expect(hash).not.toBe(password);
|
|
expect(hash.length).toBeGreaterThan(0);
|
|
expect(hash).toMatch(/^\$2[aby]\$/); // bcrypt hash format
|
|
});
|
|
|
|
it('should generate different hashes for the same password', async () => {
|
|
const password = 'TestPassword123';
|
|
const hash1 = await hashPassword(password);
|
|
const hash2 = await hashPassword(password);
|
|
|
|
expect(hash1).not.toBe(hash2); // Different salt
|
|
});
|
|
});
|
|
|
|
describe('comparePassword', () => {
|
|
it('should return true for matching password', async () => {
|
|
const password = 'TestPassword123';
|
|
const hash = await hashPassword(password);
|
|
const isMatch = await comparePassword(password, hash);
|
|
|
|
expect(isMatch).toBe(true);
|
|
});
|
|
|
|
it('should return false for non-matching password', async () => {
|
|
const password = 'TestPassword123';
|
|
const wrongPassword = 'WrongPassword456';
|
|
const hash = await hashPassword(password);
|
|
const isMatch = await comparePassword(wrongPassword, hash);
|
|
|
|
expect(isMatch).toBe(false);
|
|
});
|
|
|
|
it('should be case-sensitive', async () => {
|
|
const password = 'TestPassword123';
|
|
const hash = await hashPassword(password);
|
|
const isMatch = await comparePassword('testpassword123', hash);
|
|
|
|
expect(isMatch).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('validatePasswordStrength', () => {
|
|
it('should accept strong password', () => {
|
|
const result = validatePasswordStrength('StrongPass123');
|
|
|
|
expect(result.valid).toBe(true);
|
|
expect(result.errors).toHaveLength(0);
|
|
});
|
|
|
|
it('should reject password shorter than 8 characters', () => {
|
|
const result = validatePasswordStrength('Short1');
|
|
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toContain('Password must be at least 8 characters long');
|
|
});
|
|
|
|
it('should reject password without uppercase letter', () => {
|
|
const result = validatePasswordStrength('lowercase123');
|
|
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toContain('Password must contain at least one uppercase letter');
|
|
});
|
|
|
|
it('should reject password without lowercase letter', () => {
|
|
const result = validatePasswordStrength('UPPERCASE123');
|
|
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toContain('Password must contain at least one lowercase letter');
|
|
});
|
|
|
|
it('should reject password without number', () => {
|
|
const result = validatePasswordStrength('NoNumbers');
|
|
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toContain('Password must contain at least one number');
|
|
});
|
|
|
|
it('should return multiple errors for weak password', () => {
|
|
const result = validatePasswordStrength('weak');
|
|
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors.length).toBeGreaterThan(1);
|
|
expect(result.errors).toContain('Password must be at least 8 characters long');
|
|
expect(result.errors).toContain('Password must contain at least one uppercase letter');
|
|
expect(result.errors).toContain('Password must contain at least one number');
|
|
});
|
|
|
|
it('should accept password with special characters', () => {
|
|
const result = validatePasswordStrength('Strong!Pass@123');
|
|
|
|
expect(result.valid).toBe(true);
|
|
expect(result.errors).toHaveLength(0);
|
|
});
|
|
});
|
|
});
|