Skip to content

Security Documentation ‐ HelloKitchen API

Xantass edited this page Nov 15, 2024 · 1 revision

Table of Contents

  1. Security Architecture
  2. Authentication
  3. Data Protection
  4. Security Tests
  5. Recommendations

1. Security Architecture

1.1 Overview

The HelloKitchen API uses a secure architecture based on NestJS with multiple protection layers:

  • Global input validation
  • Protection against common attacks with Helmet
  • Rate limiting to prevent brute force attacks
  • JWT for authentication
const app = await NestFactory.create(AppModule, { abortOnError: false });
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true, // Remove undefined properties in the DTO
      forbidNonWhitelisted: true, // Throw an error if undefined properties are present
      transform: true, // Transform the values according to the type specified in the DTO
    }),
  );
  app.use(helmet());
  app.use(
    rateLimit({
      windowMs: 15 * 60 * 1000, // 15 min
      max: 100, // IP Limit
    }),
  );

1.2 Security Middlewares

The application implements several essential security middlewares:

  • Helmet : HTTP headers protection
  • Rate Limiting : 100 requests per IP on 15 minutes
  • Validation Pipe : Cleaning and validating incoming data

2. Authentication

2.1 Login System

The login system is managed by the login module:

@Module({
  imports: [
    PassportModule.register({ defaultStrategy: 'jwt' }),
    JwtModule.register({
      global: true,
      signOptions: { expiresIn: '1h' }, // life of the token
      secret: 'secret',
    }),
  ],
  controllers: [
    LoginController /**< The controller responsible for handling HTTP requests related to login items */,
  ],
  providers: [
    LoginService /**< The service that contains the business logic for managing login items */,
    JwtStrategy,
  ],
  exports: [JwtModule, PassportModule],
})

2.2 JWT Protection

The JWT strategy is configured for maximum security:

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: 'secret',
    });
  }
}

2.3 Authentication Guard

The JWT guard protects sensitive routes:

@Injectable()
export class AuthGuard extends AuthGuard('jwt') {}

3. Data Protection

3.1 User Schema

Le modèle de données utilisateur définit la structure sécurisée :

@Prop({
    type: Number,
    required: true,
    description: 'Must be an integer and is required',
  })
  id: number;

  @Prop({ type: Number, required: true, description: 'Must be an integer' })
  id_restaurant: number;

  @Prop({
    type: String,
    required: true,
    description: 'Must be a string',
  })
  username: string;

  @Prop({
    type: String,
    required: true,
    description: 'Must be a string',
  })
  password: string;

3.2 Validation of Requests

Example of validation in the restaurant controller:

@Post()
@UseGuards(AuthGuard)
@UsePipes(ValidationPipe)

4. Security Tests

4.1 Authentication Tests

The tests check the security of the authentication process:

describe('login', () => {
    const loginCredentials = {
      username: 'testuser',
      password: 'testpass',
      idRestaurant: 1,
    };

    const mockUser = {
      id: 1,
      username: 'testuser',
      role: 'user',
    };

    const mockToken = {
      access_token: 'mock.jwt.token',
    };

    it('should successfully authenticate and return a token', async () => {
      mockLoginService.authenticateUser.mockResolvedValue(mockUser);
      mockLoginService.login.mockResolvedValue(mockToken);

      const result = await controller.login(
        loginCredentials.password,
        loginCredentials.username,
        loginCredentials.idRestaurant,
      );

      expect(mockLoginService.authenticateUser).toHaveBeenCalledWith(
        loginCredentials.idRestaurant,
        loginCredentials.username,
        loginCredentials.password,
      );

      expect(mockLoginService.login).toHaveBeenCalledWith(mockUser);

      expect(result).toEqual(mockToken);
    });

    it('should throw BadRequestException when authentication fails', async () => {
      mockLoginService.authenticateUser.mockRejectedValue(
        new BadRequestException(),
      );

      await expect(
        controller.login(
          loginCredentials.password,
          loginCredentials.username,
          loginCredentials.idRestaurant,
        ),
      ).rejects.toThrow(BadRequestException);
    });

5. Recommendations

5.1 Prioritized Improvements

  1. Securing Secrets

    • Move secrets to environment variables
    • Use a secrets manager (ex: Vault)
  2. Strengthening Authentication

    • Implement password hashing
    • Add a refresh tokens system
    • Implement a strong password policy
  3. Logging and Monitoring

    • Implement a secure logging system
    • Set up monitoring of authentication attempts
    • Configure security alerts

5.2 Recommended Configuration

Add to the .env file:

JWT_SECRET=your_complex_secret
JWT_EXPIRATION=3600
MONGODB_URI=your_mongodb_uri
RATE_LIMIT_MAX=100
RATE_LIMIT_WINDOW=900000

5.3 Good Development Practices

  1. Data Validation

    • Use DTOs systematically
    • Validate all user inputs
    • Escape sensitive data
  2. Error Management

    • Do not expose technical details in error messages
    • Log security errors
    • Implement centralized error management
  3. Tests

    • Maintain a high test coverage
    • Include security tests
    • Test error cases

Conclusion

This security documentation should be regularly updated and shared with the development team. The recommendations should be implemented according to their priority to maintain an optimal security level.