Files
2025-11-29 18:20:21 +08:00

59 KiB

Feature Implementation Operation

Complete full-stack feature implementation across database, backend, and frontend layers with production-ready code, tests, and documentation.

Parameters

Received: $ARGUMENTS (after removing 'implement' operation name)

Expected format: description:"feature details" [scope:"specific-area"] [priority:"high|medium|low"] [tests:"coverage-level"] [framework:"react|vue|angular"]

Workflow

Phase 1: Requirements Understanding

Parse the feature description and clarify:

Functional Requirements:

  • What is the user-facing functionality?
  • What business problem does it solve?
  • What are the acceptance criteria?
  • What are the expected inputs and outputs?

Non-Functional Requirements:

  • Performance expectations (response time, throughput)
  • Security considerations (authentication, authorization, data protection)
  • Scalability requirements (concurrent users, data volume)
  • UI/UX requirements (responsive, accessible, real-time updates)

Ask clarifying questions if:

  • Requirements are ambiguous or incomplete
  • Multiple implementation approaches are possible
  • Technical constraints are unclear
  • Acceptance criteria are not well-defined

Phase 2: Codebase Analysis

Before implementation, examine the existing project:

Project Structure:

# Discover project layout
ls -la
find . -maxdepth 3 -type d | grep -E "(src|app|api|components|models)"

# Identify tech stack
find . -name "package.json" -o -name "requirements.txt" -o -name "go.mod" -o -name "pom.xml"
cat package.json | grep -E "(react|vue|angular|express|fastify|prisma|typeorm)"

Existing Patterns:

# Database patterns
find . -path "*/migrations/*" -o -path "*/models/*" -o -path "*/schemas/*"

# Backend patterns
find . -path "*/services/*" -o -path "*/controllers/*" -o -path "*/routes/*"

# Frontend patterns
find . -path "*/components/*" -o -path "*/hooks/*" -o -path "*/store/*"

# Testing patterns
find . -path "*/__tests__/*" -o -path "*/test/*" -name "*.test.*" -o -name "*.spec.*"

Conventions to Follow:

  • Naming conventions (camelCase, PascalCase, snake_case)
  • File organization patterns
  • Import/export patterns
  • Error handling approaches
  • Testing frameworks and patterns
  • Documentation style

Phase 3: Implementation Design

Design the implementation across all layers:

Database Design

Schema Design:

-- Example: User authentication feature
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email VARCHAR(255) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    email_verified BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE user_sessions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    token_hash VARCHAR(255) NOT NULL,
    expires_at TIMESTAMP NOT NULL,
    created_at TIMESTAMP DEFAULT NOW(),
    INDEX idx_user_sessions_user_id (user_id),
    INDEX idx_user_sessions_token_hash (token_hash),
    INDEX idx_user_sessions_expires_at (expires_at)
);

Index Strategy:

  • Primary keys for unique identification
  • Foreign keys for relationships
  • Indexes on frequently queried columns
  • Composite indexes for multi-column queries
  • Consider query patterns and performance

Migration Planning:

  • Forward migration (up)
  • Rollback migration (down)
  • Data seeding if needed
  • Migration testing strategy

Backend Design

API Endpoint Design:

// Example: Authentication endpoints
POST   /api/auth/register          # Register new user
POST   /api/auth/login             # Login with credentials
POST   /api/auth/logout            # Logout current session
POST   /api/auth/refresh           # Refresh access token
GET    /api/auth/me                # Get current user profile
POST   /api/auth/verify-email      # Verify email address
POST   /api/auth/forgot-password   # Request password reset
POST   /api/auth/reset-password    # Reset password with token

Request/Response Models:

// Register request
interface RegisterRequest {
  email: string;
  password: string;
  name?: string;
}

// Register response
interface RegisterResponse {
  user: {
    id: string;
    email: string;
    name: string | null;
  };
  accessToken: string;
  refreshToken: string;
}

// Error response
interface ErrorResponse {
  error: {
    code: string;
    message: string;
    details?: Record<string, any>;
  };
}

Service Layer Architecture:

  • Business logic separated from controllers
  • Single responsibility per service
  • Dependency injection for testability
  • Error handling with custom exceptions
  • Validation at service boundaries

Data Access Layer:

  • Repository pattern for data operations
  • Query builders or ORM usage
  • Transaction management
  • Connection pooling
  • Caching strategy

Frontend Design

Component Structure:

src/features/auth/
├── components/
│   ├── LoginForm.tsx
│   ├── RegisterForm.tsx
│   ├── ForgotPasswordForm.tsx
│   └── EmailVerification.tsx
├── hooks/
│   ├── useAuth.ts
│   ├── useLogin.ts
│   └── useRegister.ts
├── store/
│   ├── authSlice.ts         # Redux/Zustand
│   └── authSelectors.ts
├── api/
│   └── authApi.ts           # API client
├── types/
│   └── auth.types.ts
└── __tests__/
    ├── LoginForm.test.tsx
    └── useAuth.test.ts

State Management:

  • Local state vs global state decisions
  • Server state management (React Query, SWR)
  • Form state (React Hook Form, Formik)
  • Authentication state persistence
  • Loading and error states

API Integration:

  • HTTP client configuration (axios, fetch)
  • Request/response interceptors
  • Error handling and retry logic
  • Token management and refresh
  • API request cancellation

Phase 4: Incremental Implementation

Implement in phases for robustness and testability:

Phase 4.1 - Data Layer Implementation

Step 1: Create Migration Script

-- migrations/20240101120000_add_user_authentication.up.sql
BEGIN;

CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email VARCHAR(255) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    email_verified BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW(),
    CONSTRAINT email_format CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$')
);

CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_created_at ON users(created_at);

CREATE TABLE user_sessions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    token_hash VARCHAR(255) NOT NULL,
    expires_at TIMESTAMP NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_user_sessions_user_id ON user_sessions(user_id);
CREATE INDEX idx_user_sessions_token_hash ON user_sessions(token_hash);
CREATE INDEX idx_user_sessions_expires_at ON user_sessions(expires_at);

COMMIT;
-- migrations/20240101120000_add_user_authentication.down.sql
BEGIN;

DROP TABLE IF EXISTS user_sessions;
DROP TABLE IF EXISTS users;

COMMIT;

Step 2: Create/Update Models

// models/User.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm';
import { UserSession } from './UserSession';

@Entity('users')
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ type: 'varchar', length: 255, unique: true })
  email: string;

  @Column({ type: 'varchar', length: 255, select: false })
  passwordHash: string;

  @Column({ type: 'boolean', default: false })
  emailVerified: boolean;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;

  @OneToMany(() => UserSession, session => session.user)
  sessions: UserSession[];
}
// models/UserSession.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, JoinColumn, Index } from 'typeorm';
import { User } from './User';

@Entity('user_sessions')
export class UserSession {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ type: 'uuid' })
  @Index()
  userId: string;

  @Column({ type: 'varchar', length: 255 })
  @Index()
  tokenHash: string;

  @Column({ type: 'timestamp' })
  @Index()
  expiresAt: Date;

  @CreateDateColumn()
  createdAt: Date;

  @ManyToOne(() => User, user => user.sessions, { onDelete: 'CASCADE' })
  @JoinColumn({ name: 'userId' })
  user: User;
}

Step 3: Test Database Operations

// models/__tests__/User.test.ts
import { DataSource } from 'typeorm';
import { User } from '../User';

describe('User Model', () => {
  let dataSource: DataSource;

  beforeAll(async () => {
    dataSource = await createTestDataSource();
  });

  afterAll(async () => {
    await dataSource.destroy();
  });

  it('should create user with valid data', async () => {
    const userRepo = dataSource.getRepository(User);
    const user = userRepo.create({
      email: 'test@example.com',
      passwordHash: 'hashed_password',
    });

    await userRepo.save(user);

    expect(user.id).toBeDefined();
    expect(user.email).toBe('test@example.com');
    expect(user.emailVerified).toBe(false);
  });

  it('should enforce unique email constraint', async () => {
    const userRepo = dataSource.getRepository(User);

    await userRepo.save({
      email: 'duplicate@example.com',
      passwordHash: 'hash1',
    });

    await expect(
      userRepo.save({
        email: 'duplicate@example.com',
        passwordHash: 'hash2',
      })
    ).rejects.toThrow();
  });
});

Phase 4.2 - Backend Layer Implementation

Step 1: Create Repository Layer

// repositories/UserRepository.ts
import { Repository } from 'typeorm';
import { User } from '../models/User';
import { AppDataSource } from '../config/database';

export class UserRepository {
  private repository: Repository<User>;

  constructor() {
    this.repository = AppDataSource.getRepository(User);
  }

  async findByEmail(email: string): Promise<User | null> {
    return this.repository.findOne({
      where: { email: email.toLowerCase() },
      select: ['id', 'email', 'passwordHash', 'emailVerified', 'createdAt', 'updatedAt'],
    });
  }

  async findById(id: string): Promise<User | null> {
    return this.repository.findOne({
      where: { id },
    });
  }

  async create(data: { email: string; passwordHash: string }): Promise<User> {
    const user = this.repository.create({
      email: data.email.toLowerCase(),
      passwordHash: data.passwordHash,
    });
    return this.repository.save(user);
  }

  async updateEmailVerified(userId: string, verified: boolean): Promise<void> {
    await this.repository.update(userId, { emailVerified: verified });
  }

  async updatePassword(userId: string, passwordHash: string): Promise<void> {
    await this.repository.update(userId, { passwordHash });
  }

  async delete(userId: string): Promise<void> {
    await this.repository.delete(userId);
  }
}

Step 2: Create Service Layer

// services/AuthService.ts
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import { UserRepository } from '../repositories/UserRepository';
import { SessionRepository } from '../repositories/SessionRepository';
import {
  UnauthorizedError,
  ConflictError,
  ValidationError
} from '../errors';

export interface RegisterInput {
  email: string;
  password: string;
  name?: string;
}

export interface LoginInput {
  email: string;
  password: string;
}

export interface AuthTokens {
  accessToken: string;
  refreshToken: string;
}

export class AuthService {
  constructor(
    private userRepository: UserRepository,
    private sessionRepository: SessionRepository
  ) {}

  async register(input: RegisterInput): Promise<{ user: User; tokens: AuthTokens }> {
    // Validate input
    this.validateEmail(input.email);
    this.validatePassword(input.password);

    // Check if user exists
    const existingUser = await this.userRepository.findByEmail(input.email);
    if (existingUser) {
      throw new ConflictError('User with this email already exists');
    }

    // Hash password
    const passwordHash = await bcrypt.hash(input.password, 12);

    // Create user
    const user = await this.userRepository.create({
      email: input.email,
      passwordHash,
    });

    // Generate tokens
    const tokens = await this.generateTokens(user.id);

    // Create session
    await this.sessionRepository.create({
      userId: user.id,
      tokenHash: await this.hashToken(tokens.refreshToken),
      expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
    });

    return { user, tokens };
  }

  async login(input: LoginInput): Promise<{ user: User; tokens: AuthTokens }> {
    // Find user
    const user = await this.userRepository.findByEmail(input.email);
    if (!user) {
      throw new UnauthorizedError('Invalid credentials');
    }

    // Verify password
    const isValid = await bcrypt.compare(input.password, user.passwordHash);
    if (!isValid) {
      throw new UnauthorizedError('Invalid credentials');
    }

    // Generate tokens
    const tokens = await this.generateTokens(user.id);

    // Create session
    await this.sessionRepository.create({
      userId: user.id,
      tokenHash: await this.hashToken(tokens.refreshToken),
      expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
    });

    return { user, tokens };
  }

  async logout(refreshToken: string): Promise<void> {
    const tokenHash = await this.hashToken(refreshToken);
    await this.sessionRepository.deleteByTokenHash(tokenHash);
  }

  async refreshTokens(refreshToken: string): Promise<AuthTokens> {
    // Verify refresh token
    const payload = jwt.verify(refreshToken, process.env.JWT_SECRET!) as { userId: string };

    // Check if session exists
    const tokenHash = await this.hashToken(refreshToken);
    const session = await this.sessionRepository.findByTokenHash(tokenHash);

    if (!session || session.expiresAt < new Date()) {
      throw new UnauthorizedError('Invalid or expired refresh token');
    }

    // Generate new tokens
    const tokens = await this.generateTokens(payload.userId);

    // Update session
    await this.sessionRepository.update(session.id, {
      tokenHash: await this.hashToken(tokens.refreshToken),
      expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
    });

    return tokens;
  }

  private async generateTokens(userId: string): Promise<AuthTokens> {
    const accessToken = jwt.sign(
      { userId, type: 'access' },
      process.env.JWT_SECRET!,
      { expiresIn: '15m' }
    );

    const refreshToken = jwt.sign(
      { userId, type: 'refresh' },
      process.env.JWT_SECRET!,
      { expiresIn: '7d' }
    );

    return { accessToken, refreshToken };
  }

  private async hashToken(token: string): Promise<string> {
    return bcrypt.hash(token, 10);
  }

  private validateEmail(email: string): void {
    const emailRegex = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$/;
    if (!emailRegex.test(email)) {
      throw new ValidationError('Invalid email format');
    }
  }

  private validatePassword(password: string): void {
    if (password.length < 8) {
      throw new ValidationError('Password must be at least 8 characters');
    }
    if (!/[A-Z]/.test(password)) {
      throw new ValidationError('Password must contain at least one uppercase letter');
    }
    if (!/[a-z]/.test(password)) {
      throw new ValidationError('Password must contain at least one lowercase letter');
    }
    if (!/[0-9]/.test(password)) {
      throw new ValidationError('Password must contain at least one number');
    }
  }
}

Step 3: Create API Controllers

// controllers/AuthController.ts
import { Request, Response, NextFunction } from 'express';
import { AuthService } from '../services/AuthService';

export class AuthController {
  constructor(private authService: AuthService) {}

  register = async (req: Request, res: Response, next: NextFunction) => {
    try {
      const { email, password, name } = req.body;

      const result = await this.authService.register({
        email,
        password,
        name,
      });

      res.status(201).json({
        user: {
          id: result.user.id,
          email: result.user.email,
          emailVerified: result.user.emailVerified,
        },
        accessToken: result.tokens.accessToken,
        refreshToken: result.tokens.refreshToken,
      });
    } catch (error) {
      next(error);
    }
  };

  login = async (req: Request, res: Response, next: NextFunction) => {
    try {
      const { email, password } = req.body;

      const result = await this.authService.login({ email, password });

      res.json({
        user: {
          id: result.user.id,
          email: result.user.email,
          emailVerified: result.user.emailVerified,
        },
        accessToken: result.tokens.accessToken,
        refreshToken: result.tokens.refreshToken,
      });
    } catch (error) {
      next(error);
    }
  };

  logout = async (req: Request, res: Response, next: NextFunction) => {
    try {
      const { refreshToken } = req.body;

      await this.authService.logout(refreshToken);

      res.status(204).send();
    } catch (error) {
      next(error);
    }
  };

  refresh = async (req: Request, res: Response, next: NextFunction) => {
    try {
      const { refreshToken } = req.body;

      const tokens = await this.authService.refreshTokens(refreshToken);

      res.json(tokens);
    } catch (error) {
      next(error);
    }
  };

  me = async (req: Request, res: Response, next: NextFunction) => {
    try {
      // User is attached by auth middleware
      const user = req.user!;

      res.json({
        id: user.id,
        email: user.email,
        emailVerified: user.emailVerified,
        createdAt: user.createdAt,
      });
    } catch (error) {
      next(error);
    }
  };
}

Step 4: Create Routes

// routes/auth.routes.ts
import { Router } from 'express';
import { AuthController } from '../controllers/AuthController';
import { AuthService } from '../services/AuthService';
import { UserRepository } from '../repositories/UserRepository';
import { SessionRepository } from '../repositories/SessionRepository';
import { authenticate } from '../middlewares/auth.middleware';
import { validateRequest } from '../middlewares/validation.middleware';
import { registerSchema, loginSchema, refreshSchema } from '../schemas/auth.schemas';

const router = Router();

// Initialize dependencies
const userRepository = new UserRepository();
const sessionRepository = new SessionRepository();
const authService = new AuthService(userRepository, sessionRepository);
const authController = new AuthController(authService);

// Public routes
router.post('/register', validateRequest(registerSchema), authController.register);
router.post('/login', validateRequest(loginSchema), authController.login);
router.post('/refresh', validateRequest(refreshSchema), authController.refresh);

// Protected routes
router.post('/logout', authenticate, authController.logout);
router.get('/me', authenticate, authController.me);

export default router;

Step 5: Write Tests

// services/__tests__/AuthService.test.ts
import { AuthService } from '../AuthService';
import { UserRepository } from '../../repositories/UserRepository';
import { SessionRepository } from '../../repositories/SessionRepository';
import { ConflictError, UnauthorizedError, ValidationError } from '../../errors';

describe('AuthService', () => {
  let authService: AuthService;
  let userRepository: jest.Mocked<UserRepository>;
  let sessionRepository: jest.Mocked<SessionRepository>;

  beforeEach(() => {
    userRepository = {
      findByEmail: jest.fn(),
      create: jest.fn(),
      findById: jest.fn(),
    } as any;

    sessionRepository = {
      create: jest.fn(),
      findByTokenHash: jest.fn(),
      update: jest.fn(),
      deleteByTokenHash: jest.fn(),
    } as any;

    authService = new AuthService(userRepository, sessionRepository);
  });

  describe('register', () => {
    it('should register new user successfully', async () => {
      const input = {
        email: 'test@example.com',
        password: 'Password123',
      };

      userRepository.findByEmail.mockResolvedValue(null);
      userRepository.create.mockResolvedValue({
        id: 'user-id',
        email: input.email,
        emailVerified: false,
        createdAt: new Date(),
        updatedAt: new Date(),
      } as any);

      const result = await authService.register(input);

      expect(result.user).toBeDefined();
      expect(result.tokens.accessToken).toBeDefined();
      expect(result.tokens.refreshToken).toBeDefined();
      expect(userRepository.create).toHaveBeenCalled();
      expect(sessionRepository.create).toHaveBeenCalled();
    });

    it('should throw ConflictError if user exists', async () => {
      userRepository.findByEmail.mockResolvedValue({ id: 'existing-id' } as any);

      await expect(
        authService.register({
          email: 'existing@example.com',
          password: 'Password123',
        })
      ).rejects.toThrow(ConflictError);
    });

    it('should throw ValidationError for invalid email', async () => {
      await expect(
        authService.register({
          email: 'invalid-email',
          password: 'Password123',
        })
      ).rejects.toThrow(ValidationError);
    });

    it('should throw ValidationError for weak password', async () => {
      await expect(
        authService.register({
          email: 'test@example.com',
          password: 'weak',
        })
      ).rejects.toThrow(ValidationError);
    });
  });

  describe('login', () => {
    it('should login user successfully', async () => {
      const user = {
        id: 'user-id',
        email: 'test@example.com',
        passwordHash: await bcrypt.hash('Password123', 12),
      };

      userRepository.findByEmail.mockResolvedValue(user as any);

      const result = await authService.login({
        email: 'test@example.com',
        password: 'Password123',
      });

      expect(result.user).toBeDefined();
      expect(result.tokens).toBeDefined();
      expect(sessionRepository.create).toHaveBeenCalled();
    });

    it('should throw UnauthorizedError for invalid credentials', async () => {
      userRepository.findByEmail.mockResolvedValue(null);

      await expect(
        authService.login({
          email: 'test@example.com',
          password: 'Password123',
        })
      ).rejects.toThrow(UnauthorizedError);
    });
  });
});
// controllers/__tests__/AuthController.test.ts
import request from 'supertest';
import { app } from '../../app';
import { UserRepository } from '../../repositories/UserRepository';

describe('AuthController', () => {
  let userRepository: UserRepository;

  beforeEach(async () => {
    await clearDatabase();
    userRepository = new UserRepository();
  });

  describe('POST /api/auth/register', () => {
    it('should register new user', async () => {
      const response = await request(app)
        .post('/api/auth/register')
        .send({
          email: 'newuser@example.com',
          password: 'Password123',
        })
        .expect(201);

      expect(response.body).toHaveProperty('user');
      expect(response.body).toHaveProperty('accessToken');
      expect(response.body).toHaveProperty('refreshToken');
      expect(response.body.user.email).toBe('newuser@example.com');
    });

    it('should return 409 for duplicate email', async () => {
      await userRepository.create({
        email: 'existing@example.com',
        passwordHash: 'hash',
      });

      await request(app)
        .post('/api/auth/register')
        .send({
          email: 'existing@example.com',
          password: 'Password123',
        })
        .expect(409);
    });

    it('should return 400 for invalid input', async () => {
      await request(app)
        .post('/api/auth/register')
        .send({
          email: 'invalid-email',
          password: 'weak',
        })
        .expect(400);
    });
  });

  describe('POST /api/auth/login', () => {
    it('should login existing user', async () => {
      // Create user first
      await request(app)
        .post('/api/auth/register')
        .send({
          email: 'test@example.com',
          password: 'Password123',
        });

      const response = await request(app)
        .post('/api/auth/login')
        .send({
          email: 'test@example.com',
          password: 'Password123',
        })
        .expect(200);

      expect(response.body).toHaveProperty('accessToken');
      expect(response.body).toHaveProperty('refreshToken');
    });

    it('should return 401 for invalid credentials', async () => {
      await request(app)
        .post('/api/auth/login')
        .send({
          email: 'nonexistent@example.com',
          password: 'Password123',
        })
        .expect(401);
    });
  });

  describe('GET /api/auth/me', () => {
    it('should return current user', async () => {
      // Register and login
      const registerResponse = await request(app)
        .post('/api/auth/register')
        .send({
          email: 'test@example.com',
          password: 'Password123',
        });

      const { accessToken } = registerResponse.body;

      const response = await request(app)
        .get('/api/auth/me')
        .set('Authorization', `Bearer ${accessToken}`)
        .expect(200);

      expect(response.body.email).toBe('test@example.com');
    });

    it('should return 401 without token', async () => {
      await request(app)
        .get('/api/auth/me')
        .expect(401);
    });
  });
});

Phase 4.3 - Frontend Layer Implementation

Step 1: Create API Client

// src/features/auth/api/authApi.ts
import axios, { AxiosInstance } from 'axios';

export interface RegisterRequest {
  email: string;
  password: string;
  name?: string;
}

export interface LoginRequest {
  email: string;
  password: string;
}

export interface AuthResponse {
  user: {
    id: string;
    email: string;
    emailVerified: boolean;
  };
  accessToken: string;
  refreshToken: string;
}

export interface RefreshTokenResponse {
  accessToken: string;
  refreshToken: string;
}

export interface UserProfile {
  id: string;
  email: string;
  emailVerified: boolean;
  createdAt: string;
}

export class AuthApi {
  private client: AxiosInstance;

  constructor(baseURL: string = '/api') {
    this.client = axios.create({
      baseURL,
      timeout: 10000,
      headers: {
        'Content-Type': 'application/json',
      },
    });

    // Request interceptor to add auth token
    this.client.interceptors.request.use((config) => {
      const token = localStorage.getItem('accessToken');
      if (token) {
        config.headers.Authorization = `Bearer ${token}`;
      }
      return config;
    });

    // Response interceptor to handle token refresh
    this.client.interceptors.response.use(
      (response) => response,
      async (error) => {
        const originalRequest = error.config;

        // If 401 and not already retried, try to refresh token
        if (error.response?.status === 401 && !originalRequest._retry) {
          originalRequest._retry = true;

          try {
            const refreshToken = localStorage.getItem('refreshToken');
            if (refreshToken) {
              const response = await this.refreshTokens({ refreshToken });
              localStorage.setItem('accessToken', response.accessToken);
              localStorage.setItem('refreshToken', response.refreshToken);

              // Retry original request with new token
              originalRequest.headers.Authorization = `Bearer ${response.accessToken}`;
              return this.client(originalRequest);
            }
          } catch (refreshError) {
            // Refresh failed, clear tokens and redirect to login
            localStorage.removeItem('accessToken');
            localStorage.removeItem('refreshToken');
            window.location.href = '/login';
            throw refreshError;
          }
        }

        throw error;
      }
    );
  }

  async register(data: RegisterRequest): Promise<AuthResponse> {
    const response = await this.client.post<AuthResponse>('/auth/register', data);
    return response.data;
  }

  async login(data: LoginRequest): Promise<AuthResponse> {
    const response = await this.client.post<AuthResponse>('/auth/login', data);
    return response.data;
  }

  async logout(): Promise<void> {
    const refreshToken = localStorage.getItem('refreshToken');
    await this.client.post('/auth/logout', { refreshToken });
  }

  async refreshTokens(data: { refreshToken: string }): Promise<RefreshTokenResponse> {
    const response = await this.client.post<RefreshTokenResponse>('/auth/refresh', data);
    return response.data;
  }

  async getCurrentUser(): Promise<UserProfile> {
    const response = await this.client.get<UserProfile>('/auth/me');
    return response.data;
  }
}

export const authApi = new AuthApi();

Step 2: Create React Hooks

// src/features/auth/hooks/useAuth.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { authApi } from '../api/authApi';

interface User {
  id: string;
  email: string;
  emailVerified: boolean;
}

interface AuthState {
  user: User | null;
  isAuthenticated: boolean;
  isLoading: boolean;
  error: string | null;

  login: (email: string, password: string) => Promise<void>;
  register: (email: string, password: string, name?: string) => Promise<void>;
  logout: () => Promise<void>;
  refreshUser: () => Promise<void>;
  clearError: () => void;
}

export const useAuth = create<AuthState>()(
  persist(
    (set, get) => ({
      user: null,
      isAuthenticated: false,
      isLoading: false,
      error: null,

      login: async (email: string, password: string) => {
        set({ isLoading: true, error: null });

        try {
          const response = await authApi.login({ email, password });

          localStorage.setItem('accessToken', response.accessToken);
          localStorage.setItem('refreshToken', response.refreshToken);

          set({
            user: response.user,
            isAuthenticated: true,
            isLoading: false,
          });
        } catch (error: any) {
          const errorMessage = error.response?.data?.error?.message || 'Login failed';
          set({ error: errorMessage, isLoading: false });
          throw error;
        }
      },

      register: async (email: string, password: string, name?: string) => {
        set({ isLoading: true, error: null });

        try {
          const response = await authApi.register({ email, password, name });

          localStorage.setItem('accessToken', response.accessToken);
          localStorage.setItem('refreshToken', response.refreshToken);

          set({
            user: response.user,
            isAuthenticated: true,
            isLoading: false,
          });
        } catch (error: any) {
          const errorMessage = error.response?.data?.error?.message || 'Registration failed';
          set({ error: errorMessage, isLoading: false });
          throw error;
        }
      },

      logout: async () => {
        set({ isLoading: true });

        try {
          await authApi.logout();
        } catch (error) {
          console.error('Logout error:', error);
        } finally {
          localStorage.removeItem('accessToken');
          localStorage.removeItem('refreshToken');

          set({
            user: null,
            isAuthenticated: false,
            isLoading: false,
            error: null,
          });
        }
      },

      refreshUser: async () => {
        if (!get().isAuthenticated) return;

        try {
          const user = await authApi.getCurrentUser();
          set({ user });
        } catch (error) {
          console.error('Failed to refresh user:', error);
        }
      },

      clearError: () => set({ error: null }),
    }),
    {
      name: 'auth-storage',
      partialize: (state) => ({
        user: state.user,
        isAuthenticated: state.isAuthenticated,
      }),
    }
  )
);

Step 3: Create Components

// src/features/auth/components/LoginForm.tsx
import React from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useAuth } from '../hooks/useAuth';
import { useNavigate } from 'react-router-dom';

const loginSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
});

type LoginFormData = z.infer<typeof loginSchema>;

export const LoginForm: React.FC = () => {
  const { login, isLoading, error, clearError } = useAuth();
  const navigate = useNavigate();

  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<LoginFormData>({
    resolver: zodResolver(loginSchema),
  });

  const onSubmit = async (data: LoginFormData) => {
    try {
      clearError();
      await login(data.email, data.password);
      navigate('/dashboard');
    } catch (error) {
      // Error is handled by the store
    }
  };

  return (
    <div className="w-full max-w-md mx-auto p-6">
      <h2 className="text-2xl font-bold mb-6">Login</h2>

      <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
        {error && (
          <div className="p-3 bg-red-50 border border-red-200 text-red-700 rounded">
            {error}
          </div>
        )}

        <div>
          <label htmlFor="email" className="block text-sm font-medium mb-1">
            Email
          </label>
          <input
            id="email"
            type="email"
            {...register('email')}
            className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
            disabled={isLoading}
          />
          {errors.email && (
            <p className="mt-1 text-sm text-red-600">{errors.email.message}</p>
          )}
        </div>

        <div>
          <label htmlFor="password" className="block text-sm font-medium mb-1">
            Password
          </label>
          <input
            id="password"
            type="password"
            {...register('password')}
            className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
            disabled={isLoading}
          />
          {errors.password && (
            <p className="mt-1 text-sm text-red-600">{errors.password.message}</p>
          )}
        </div>

        <button
          type="submit"
          disabled={isLoading}
          className="w-full py-2 px-4 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
        >
          {isLoading ? 'Logging in...' : 'Login'}
        </button>
      </form>
    </div>
  );
};
// src/features/auth/components/RegisterForm.tsx
import React from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useAuth } from '../hooks/useAuth';
import { useNavigate } from 'react-router-dom';

const registerSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z
    .string()
    .min(8, 'Password must be at least 8 characters')
    .regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
    .regex(/[a-z]/, 'Password must contain at least one lowercase letter')
    .regex(/[0-9]/, 'Password must contain at least one number'),
  confirmPassword: z.string(),
  name: z.string().optional(),
}).refine((data) => data.password === data.confirmPassword, {
  message: "Passwords don't match",
  path: ['confirmPassword'],
});

type RegisterFormData = z.infer<typeof registerSchema>;

export const RegisterForm: React.FC = () => {
  const { register: registerUser, isLoading, error, clearError } = useAuth();
  const navigate = useNavigate();

  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<RegisterFormData>({
    resolver: zodResolver(registerSchema),
  });

  const onSubmit = async (data: RegisterFormData) => {
    try {
      clearError();
      await registerUser(data.email, data.password, data.name);
      navigate('/dashboard');
    } catch (error) {
      // Error is handled by the store
    }
  };

  return (
    <div className="w-full max-w-md mx-auto p-6">
      <h2 className="text-2xl font-bold mb-6">Create Account</h2>

      <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
        {error && (
          <div className="p-3 bg-red-50 border border-red-200 text-red-700 rounded">
            {error}
          </div>
        )}

        <div>
          <label htmlFor="name" className="block text-sm font-medium mb-1">
            Name (Optional)
          </label>
          <input
            id="name"
            type="text"
            {...register('name')}
            className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
            disabled={isLoading}
          />
        </div>

        <div>
          <label htmlFor="email" className="block text-sm font-medium mb-1">
            Email
          </label>
          <input
            id="email"
            type="email"
            {...register('email')}
            className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
            disabled={isLoading}
          />
          {errors.email && (
            <p className="mt-1 text-sm text-red-600">{errors.email.message}</p>
          )}
        </div>

        <div>
          <label htmlFor="password" className="block text-sm font-medium mb-1">
            Password
          </label>
          <input
            id="password"
            type="password"
            {...register('password')}
            className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
            disabled={isLoading}
          />
          {errors.password && (
            <p className="mt-1 text-sm text-red-600">{errors.password.message}</p>
          )}
        </div>

        <div>
          <label htmlFor="confirmPassword" className="block text-sm font-medium mb-1">
            Confirm Password
          </label>
          <input
            id="confirmPassword"
            type="password"
            {...register('confirmPassword')}
            className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
            disabled={isLoading}
          />
          {errors.confirmPassword && (
            <p className="mt-1 text-sm text-red-600">{errors.confirmPassword.message}</p>
          )}
        </div>

        <button
          type="submit"
          disabled={isLoading}
          className="w-full py-2 px-4 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
        >
          {isLoading ? 'Creating account...' : 'Register'}
        </button>
      </form>
    </div>
  );
};
// src/features/auth/components/ProtectedRoute.tsx
import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';

export const ProtectedRoute: React.FC = () => {
  const { isAuthenticated, isLoading } = useAuth();

  if (isLoading) {
    return (
      <div className="flex items-center justify-center min-h-screen">
        <div className="text-lg">Loading...</div>
      </div>
    );
  }

  return isAuthenticated ? <Outlet /> : <Navigate to="/login" replace />;
};

Step 4: Write Component Tests

// src/features/auth/components/__tests__/LoginForm.test.tsx
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { LoginForm } from '../LoginForm';
import { useAuth } from '../../hooks/useAuth';
import { BrowserRouter } from 'react-router-dom';

jest.mock('../../hooks/useAuth');

const mockUseAuth = useAuth as jest.MockedFunction<typeof useAuth>;

describe('LoginForm', () => {
  const mockLogin = jest.fn();
  const mockClearError = jest.fn();

  beforeEach(() => {
    mockUseAuth.mockReturnValue({
      login: mockLogin,
      isLoading: false,
      error: null,
      clearError: mockClearError,
    } as any);
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  const renderLoginForm = () => {
    return render(
      <BrowserRouter>
        <LoginForm />
      </BrowserRouter>
    );
  };

  it('should render login form', () => {
    renderLoginForm();

    expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument();
  });

  it('should show validation errors for invalid input', async () => {
    renderLoginForm();

    const submitButton = screen.getByRole('button', { name: /login/i });
    fireEvent.click(submitButton);

    await waitFor(() => {
      expect(screen.getByText(/invalid email address/i)).toBeInTheDocument();
    });
  });

  it('should call login with valid credentials', async () => {
    mockLogin.mockResolvedValue(undefined);
    renderLoginForm();

    fireEvent.change(screen.getByLabelText(/email/i), {
      target: { value: 'test@example.com' },
    });
    fireEvent.change(screen.getByLabelText(/password/i), {
      target: { value: 'Password123' },
    });

    const submitButton = screen.getByRole('button', { name: /login/i });
    fireEvent.click(submitButton);

    await waitFor(() => {
      expect(mockLogin).toHaveBeenCalledWith('test@example.com', 'Password123');
    });
  });

  it('should display error message on login failure', () => {
    mockUseAuth.mockReturnValue({
      login: mockLogin,
      isLoading: false,
      error: 'Invalid credentials',
      clearError: mockClearError,
    } as any);

    renderLoginForm();

    expect(screen.getByText(/invalid credentials/i)).toBeInTheDocument();
  });

  it('should disable form during loading', () => {
    mockUseAuth.mockReturnValue({
      login: mockLogin,
      isLoading: true,
      error: null,
      clearError: mockClearError,
    } as any);

    renderLoginForm();

    expect(screen.getByLabelText(/email/i)).toBeDisabled();
    expect(screen.getByLabelText(/password/i)).toBeDisabled();
    expect(screen.getByRole('button', { name: /logging in/i })).toBeDisabled();
  });
});

Phase 4.4 - Integration & Polish

Step 1: End-to-End Tests

// e2e/auth.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Authentication Flow', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('http://localhost:3000');
  });

  test('should complete full registration flow', async ({ page }) => {
    // Navigate to register page
    await page.click('text=Register');

    // Fill registration form
    await page.fill('input[name="name"]', 'Test User');
    await page.fill('input[name="email"]', `test-${Date.now()}@example.com`);
    await page.fill('input[name="password"]', 'Password123');
    await page.fill('input[name="confirmPassword"]', 'Password123');

    // Submit form
    await page.click('button:has-text("Register")');

    // Should redirect to dashboard
    await expect(page).toHaveURL(/\/dashboard/);
    await expect(page.locator('text=Welcome')).toBeVisible();
  });

  test('should complete full login flow', async ({ page }) => {
    // Assume user already registered
    await page.click('text=Login');

    // Fill login form
    await page.fill('input[name="email"]', 'existing@example.com');
    await page.fill('input[name="password"]', 'Password123');

    // Submit form
    await page.click('button:has-text("Login")');

    // Should redirect to dashboard
    await expect(page).toHaveURL(/\/dashboard/);
  });

  test('should handle logout correctly', async ({ page }) => {
    // Login first
    await page.click('text=Login');
    await page.fill('input[name="email"]', 'existing@example.com');
    await page.fill('input[name="password"]', 'Password123');
    await page.click('button:has-text("Login")');
    await expect(page).toHaveURL(/\/dashboard/);

    // Logout
    await page.click('button:has-text("Logout")');

    // Should redirect to home/login
    await expect(page).toHaveURL(/\/(login)?$/);
  });

  test('should protect routes when not authenticated', async ({ page }) => {
    // Try to access protected route
    await page.goto('http://localhost:3000/dashboard');

    // Should redirect to login
    await expect(page).toHaveURL(/\/login/);
  });

  test('should refresh token automatically', async ({ page, context }) => {
    // Login
    await page.click('text=Login');
    await page.fill('input[name="email"]', 'existing@example.com');
    await page.fill('input[name="password"]', 'Password123');
    await page.click('button:has-text("Login")');

    // Get initial access token
    const initialStorage = await context.storageState();
    const initialToken = initialStorage.origins[0]?.localStorage.find(
      (item) => item.name === 'accessToken'
    )?.value;

    // Wait for token to expire (in real scenario, this would be 15 minutes)
    // For testing, you might mock the token expiration
    await page.waitForTimeout(16 * 60 * 1000); // 16 minutes

    // Make an API request that should trigger token refresh
    await page.reload();

    // Get new access token
    const newStorage = await context.storageState();
    const newToken = newStorage.origins[0]?.localStorage.find(
      (item) => item.name === 'accessToken'
    )?.value;

    // Tokens should be different
    expect(newToken).not.toBe(initialToken);
  });
});

Step 2: Performance Optimization

// Performance considerations for authentication feature

// 1. Database Indexes (already included in migration)
// - idx_users_email for login lookups
// - idx_user_sessions_user_id for session queries
// - idx_user_sessions_token_hash for token validation
// - idx_user_sessions_expires_at for cleanup queries

// 2. Caching Strategy
// Example: Redis caching for user sessions
import { Redis } from 'ioredis';

const redis = new Redis(process.env.REDIS_URL);

// Cache user data after login
async function cacheUserSession(userId: string, userData: any) {
  await redis.setex(
    `user:${userId}`,
    15 * 60, // 15 minutes (access token lifetime)
    JSON.stringify(userData)
  );
}

// Get cached user data
async function getCachedUser(userId: string) {
  const cached = await redis.get(`user:${userId}`);
  return cached ? JSON.parse(cached) : null;
}

// 3. Password Hashing Optimization
// Use bcrypt with work factor 12 (balance security and performance)
const BCRYPT_ROUNDS = 12;

// 4. Token Generation Optimization
// Use JWT with short expiration for access tokens (15 minutes)
// Use longer expiration for refresh tokens (7 days)

// 5. Connection Pooling
// Configure database connection pool
{
  type: 'postgres',
  host: process.env.DB_HOST,
  port: Number(process.env.DB_PORT),
  username: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  extra: {
    max: 20,           // Maximum pool size
    min: 5,            // Minimum pool size
    idle: 10000,       // Idle timeout
    acquire: 30000,    // Acquire timeout
  }
}

// 6. Frontend Optimization
// - Use React.memo for authentication components
// - Lazy load protected routes
// - Implement request deduplication

// Example: Lazy loading
const Dashboard = React.lazy(() => import('./pages/Dashboard'));
const Profile = React.lazy(() => import('./pages/Profile'));

// Example: Request deduplication
import { useQuery } from '@tanstack/react-query';

function useCurrentUser() {
  return useQuery({
    queryKey: ['currentUser'],
    queryFn: () => authApi.getCurrentUser(),
    staleTime: 5 * 60 * 1000, // 5 minutes
    cacheTime: 10 * 60 * 1000, // 10 minutes
  });
}

Step 3: Security Hardening

// Security measures for authentication feature

// 1. Rate Limiting
import rateLimit from 'express-rate-limit';

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // 5 requests per window
  message: 'Too many login attempts, please try again later',
  standardHeaders: true,
  legacyHeaders: false,
});

app.use('/api/auth/login', loginLimiter);

// 2. Input Sanitization
import validator from 'validator';
import xss from 'xss';

function sanitizeInput(input: string): string {
  return xss(validator.trim(input));
}

// 3. SQL Injection Prevention (using parameterized queries with TypeORM)
// TypeORM automatically uses parameterized queries

// 4. XSS Prevention
// - Set security headers
import helmet from 'helmet';
app.use(helmet());

// - Content Security Policy
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "'unsafe-inline'"],
    styleSrc: ["'self'", "'unsafe-inline'"],
    imgSrc: ["'self'", 'data:', 'https:'],
  },
}));

// 5. CSRF Protection
import csrf from 'csurf';
const csrfProtection = csrf({ cookie: true });
app.use(csrfProtection);

// 6. Secure Cookie Configuration
app.use(session({
  secret: process.env.SESSION_SECRET!,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict',
    maxAge: 24 * 60 * 60 * 1000, // 24 hours
  },
}));

// 7. Password Policy Enforcement
const PASSWORD_REQUIREMENTS = {
  minLength: 8,
  requireUppercase: true,
  requireLowercase: true,
  requireNumber: true,
  requireSpecialChar: false, // Optional
};

// 8. Account Lockout (after failed attempts)
async function checkAccountLockout(email: string): Promise<boolean> {
  const attempts = await redis.get(`login_attempts:${email}`);
  if (attempts && parseInt(attempts) >= 5) {
    const ttl = await redis.ttl(`login_attempts:${email}`);
    if (ttl > 0) {
      throw new Error(`Account locked. Try again in ${Math.ceil(ttl / 60)} minutes`);
    }
  }
  return false;
}

async function recordFailedLogin(email: string): Promise<void> {
  const key = `login_attempts:${email}`;
  const attempts = await redis.incr(key);
  if (attempts === 1) {
    await redis.expire(key, 15 * 60); // 15 minutes
  }
}

// 9. Session Management
// - Implement session rotation on privilege escalation
// - Invalidate sessions on password change
// - Implement "logout all devices" functionality

async function invalidateAllUserSessions(userId: string): Promise<void> {
  await sessionRepository.deleteAllByUserId(userId);
  await redis.del(`user:${userId}`);
}

Step 4: Documentation

# Authentication Feature Documentation

## Overview

Complete user authentication system with email/password login, JWT tokens, session management, and secure password handling.

## Features

- User registration with email verification
- Login with email and password
- JWT-based authentication (access + refresh tokens)
- Token refresh mechanism
- Secure session management
- Password hashing with bcrypt
- Rate limiting on authentication endpoints
- CSRF protection
- Account lockout after failed attempts

## API Endpoints

### POST /api/auth/register

Register a new user account.

**Request Body:**
```json
{
  "email": "user@example.com",
  "password": "SecurePassword123",
  "name": "John Doe"
}

Response (201 Created):

{
  "user": {
    "id": "uuid",
    "email": "user@example.com",
    "emailVerified": false
  },
  "accessToken": "jwt-access-token",
  "refreshToken": "jwt-refresh-token"
}

Error Responses:

  • 400 Bad Request - Invalid input
  • 409 Conflict - Email already exists

POST /api/auth/login

Login with existing credentials.

Request Body:

{
  "email": "user@example.com",
  "password": "SecurePassword123"
}

Response (200 OK):

{
  "user": {
    "id": "uuid",
    "email": "user@example.com",
    "emailVerified": true
  },
  "accessToken": "jwt-access-token",
  "refreshToken": "jwt-refresh-token"
}

Error Responses:

  • 401 Unauthorized - Invalid credentials
  • 429 Too Many Requests - Rate limit exceeded

POST /api/auth/refresh

Refresh access token using refresh token.

Request Body:

{
  "refreshToken": "jwt-refresh-token"
}

Response (200 OK):

{
  "accessToken": "new-jwt-access-token",
  "refreshToken": "new-jwt-refresh-token"
}

POST /api/auth/logout

Logout and invalidate current session.

Headers:

Authorization: Bearer {accessToken}

Request Body:

{
  "refreshToken": "jwt-refresh-token"
}

Response (204 No Content)

GET /api/auth/me

Get current user profile.

Headers:

Authorization: Bearer {accessToken}

Response (200 OK):

{
  "id": "uuid",
  "email": "user@example.com",
  "emailVerified": true,
  "createdAt": "2024-01-01T00:00:00Z"
}

Frontend Usage

Using the Auth Hook

import { useAuth } from '@/features/auth/hooks/useAuth';

function MyComponent() {
  const { user, isAuthenticated, login, logout } = useAuth();

  const handleLogin = async () => {
    try {
      await login('user@example.com', 'password');
      // User is now logged in
    } catch (error) {
      // Handle error
    }
  };

  return (
    <div>
      {isAuthenticated ? (
        <>
          <p>Welcome, {user?.email}</p>
          <button onClick={logout}>Logout</button>
        </>
      ) : (
        <button onClick={handleLogin}>Login</button>
      )}
    </div>
  );
}

Protecting Routes

import { ProtectedRoute } from '@/features/auth/components/ProtectedRoute';

<Routes>
  <Route path="/login" element={<LoginPage />} />
  <Route path="/register" element={<RegisterPage />} />

  <Route element={<ProtectedRoute />}>
    <Route path="/dashboard" element={<Dashboard />} />
    <Route path="/profile" element={<Profile />} />
  </Route>
</Routes>

Security Considerations

  1. Password Requirements:

    • Minimum 8 characters
    • At least one uppercase letter
    • At least one lowercase letter
    • At least one number
  2. Token Lifetimes:

    • Access token: 15 minutes
    • Refresh token: 7 days
  3. Rate Limiting:

    • Login: 5 attempts per 15 minutes
    • Register: 3 attempts per hour
  4. Session Management:

    • Sessions are invalidated on logout
    • Sessions are automatically cleaned up after expiration
    • Multiple sessions per user are allowed

Testing

Run tests with:

# Unit tests
npm run test

# Integration tests
npm run test:integration

# E2E tests
npm run test:e2e

Environment Variables

# Database
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=password
DB_NAME=myapp

# JWT
JWT_SECRET=your-secret-key-change-in-production

# Session
SESSION_SECRET=your-session-secret

# Redis (for rate limiting and caching)
REDIS_URL=redis://localhost:6379

# SMTP (for email verification)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your-email@example.com
SMTP_PASSWORD=your-smtp-password

Deployment

  1. Run migrations:

    npm run migrate
    
  2. Build the application:

    npm run build
    
  3. Start the server:

    npm start
    

Troubleshooting

Token expired error

If you receive "Token expired" errors, ensure:

  • The JWT_SECRET is correctly set
  • System clocks are synchronized
  • Token refresh is working correctly

Cannot login

If login fails:

  • Verify database connection
  • Check password hashing (bcrypt rounds)
  • Verify rate limiting isn't blocking requests
  • Check error logs for details

CORS issues

If frontend cannot connect to backend:

  • Verify CORS configuration in backend
  • Ensure credentials are included in requests
  • Check allowed origins match frontend URL

## Output Format

Provide complete implementation organized by layer:

```markdown
# Feature Implementation: {Feature Name}

## Overview
{Brief description and purpose}

## Implementation Summary
{High-level approach and key decisions}

## Phase 1: Database Layer

### Migration Script
{SQL migration with up/down}

### Models/Schemas
{Data models with relationships}

### Testing
{Database operation tests}

## Phase 2: Backend Layer

### Data Access Layer
{Repositories and queries}

### Business Logic Layer
{Services with business rules}

### API Layer
{Controllers and routes}

### API Documentation
{Endpoint specs with examples}

### Testing
{Unit and integration tests}

## Phase 3: Frontend Layer

### API Client
{HTTP client with interceptors}

### State Management
{Hooks and stores}

### Components
{React/Vue/Angular components}

### Testing
{Component tests}

## Phase 4: Integration & Polish

### E2E Tests
{End-to-end test scenarios}

### Performance Optimizations
{Caching, indexing, lazy loading}

### Security Hardening
{Rate limiting, validation, CSRF}

### Documentation
{API docs, usage guide, troubleshooting}

## Configuration Changes

### Environment Variables
{New env vars needed}

### Dependencies
{New packages to install}

## Deployment Considerations
{Migration steps, monitoring, rollback}

## Follow-up Tasks
{Future improvements}

Quality Checklist

Before considering the feature complete:

  • All layers implemented (database, backend, frontend)
  • Database migrations tested (up and down)
  • Unit tests written and passing (>80% coverage)
  • Integration tests written and passing
  • E2E tests for critical flows
  • Error handling comprehensive
  • Validation on both frontend and backend
  • Security measures implemented (auth, rate limiting, CSRF)
  • Performance optimized (indexes, caching, lazy loading)
  • Accessibility considered (WCAG 2.1 AA)
  • Documentation complete (API docs, usage guide)
  • No hardcoded secrets or sensitive data
  • Code follows project conventions
  • Ready for code review

Error Handling

Unclear Requirements:

  • Ask specific questions about acceptance criteria
  • Request clarification on edge cases
  • Provide examples to confirm understanding
  • Suggest sensible defaults if context missing

Missing Context:

  • List needed information (tech stack, patterns)
  • Attempt to discover from codebase
  • Document assumptions made
  • Provide alternatives if context unclear

Implementation Blockers:

  • Clearly identify the blocker
  • Suggest alternative approaches
  • Provide workarounds if available
  • Document issue for resolution
  • Continue with unblocked portions

Examples by Feature Type

Real-time Features (WebSocket/SSE)

  • WebSocket connection management
  • Event broadcasting and subscriptions
  • Presence tracking
  • Conflict resolution
  • Reconnection handling

Payment Features (Stripe/PayPal)

  • Checkout flow integration
  • Webhook handling
  • Payment method management
  • Subscription management
  • Invoice generation
  • Refund processing

File Upload Features

  • Multipart form handling
  • File validation (type, size)
  • Storage integration (S3, GCS)
  • Progress tracking
  • Image optimization
  • Virus scanning

Search Features

  • Full-text search implementation
  • Filter and sorting
  • Pagination
  • Search suggestions
  • Relevance scoring
  • Query optimization