This document describes the resolution for four GitHub issues assigned to YaronZaki, all closed in a single PR.
Priority: High
File: src/common/interceptors/response-time.interceptor.ts
API response time degradation was not tracked or alerted on.
A NestJS NestInterceptor measures the elapsed time for every request using process.hrtime.bigint() and attaches it as an X-Response-Time header. Slow requests (configurable threshold, default 1000 ms) are logged as warnings via the existing Logger.
The interceptor is registered globally in AppModule.
GET /api/courses
→ X-Response-Time: 42ms
Slow-request warning in logs:
[ResponseTimeInterceptor] SLOW REQUEST: GET /api/courses — 1234ms
Priority: High
Files:
src/auth/entities/user.entity.ts— addedrolecolumnsrc/auth/guards/roles.guard.ts— new guardsrc/auth/decorators/roles.decorator.ts—@Roles()decoratorsrc/auth/enums/role.enum.ts—Roleenum
No granular permission system existed; all authenticated users had the same access level.
A Role enum (admin, instructor, student) is stored on the User entity. A RolesGuard reads the @Roles() metadata set on a route handler and compares it against request.user.role. The guard is designed to compose with the existing JwtCookieGuard.
@UseGuards(JwtCookieGuard, RolesGuard)
@Roles(Role.Admin)
@Delete(':id')
deleteUser(@Param('id') id: string) { ... }Priority: High
Files:
src/auth/entities/user.entity.ts— addedmfaSecret,mfaEnabledcolumnssrc/auth/services/mfa.service.ts— TOTP generation & verificationsrc/auth/controllers/mfa.controller.ts— REST endpointssrc/auth/dtos/mfa.dto.ts— request DTOs
User accounts had no second factor; a compromised password was sufficient for full access.
TOTP-based MFA using the speakeasy library (already in node_modules):
- Enroll —
POST /auth/mfa/setupgenerates a TOTP secret and returns aotpauth://URI for QR-code display. - Verify & enable —
POST /auth/mfa/verifyconfirms the first TOTP code and setsmfaEnabled = true. - Login flow — after password validation, if
mfaEnabledthe client must supply atotpCode; the auth service validates it before issuing tokens. - Disable —
POST /auth/mfa/disablerequires a valid TOTP code.
Secrets are stored encrypted (base32) in the database; they are never returned after initial setup.
POST /auth/mfa/setup → { otpauthUrl, secret }
POST /auth/mfa/verify { token: "123456" }
POST /auth/login { email, password, totpCode?: "123456" }
POST /auth/mfa/disable { token: "123456" }
Priority: Low
Files:
src/common/middleware/certificate-pinning.middleware.ts— server-side header middlewaredocs/CERTIFICATE_PINNING.md— mobile client implementation guide
Mobile clients were vulnerable to MITM attacks because they did not pin the server's TLS certificate.
Certificate pinning is a client-side concern. The backend contribution is:
Public-Key-Pins-Report-Only/Expect-CTheaders — the middleware injects these HTTP headers so clients and browsers can detect certificate mismatches and report them.- Documentation —
docs/CERTIFICATE_PINNING.mdprovides step-by-step instructions for pinning in React Native (iOS/Android) using thereact-native-ssl-pinninglibrary, including how to extract the certificate hash and handle pin rotation.
Note: Full SSL pinning enforcement (
Public-Key-Pins) is intentionally set to report-only mode to avoid bricking clients during certificate rotation. Upgrade to enforcement only after validating the pin hash in production.
# Build
npm run build
# Unit tests
npm test
# Manual smoke test (requires running server)
curl -I http://localhost:3000/health | grep X-Response-Time- Closes #820
- Closes #825
- Closes #826
- Closes #829