Two unrelated production blockers came up while exercising the live
deploy:
1. Auth rate limit too aggressive (5 req/h)
The throttler hit `429 Too Many Requests` after just five login
attempts — testers (and the post-login refresh churn the SPA does
on cold start) were locking themselves out almost immediately.
- `auth.controller.ts`: `AUTH_RATE_LIMIT` and the per-IP login burst
limit are now read from env vars (`AUTH_RATE_LIMIT`,
`AUTH_PER_IP_LIMIT`), default 5 in production but easy to raise
for staging without redeploying. Cluster ConfigMap now sets
200 / 100 respectively.
- `throttler-behind-proxy.guard.ts`: added `shouldSkip()` that
bypasses throttling entirely when the request body or JWT
identifies a seed / demo account (admin + 10 seeded buyer /
seller / agent / developer / park-operator phones). Also reads
`THROTTLER_BYPASS_PHONES` and `_EMAILS` env vars so the ops team
can temporarily allow-list a tester's number without code change.
2. `/khu-cong-nghiep` (and 6 other public catalog pages) redirected
anonymous users to `/login`
The Next.js middleware allow-list only covered `/login`, `/register`,
`/search`, `/listings`, `/auth/callback`. Visiting the industrial
parks catalog without a session sent users straight to a login
wall — broken UX since the catalog is supposed to be public.
Added these prefixes to `publicPaths`:
/khu-cong-nghiep (industrial parks)
/du-an (real estate projects)
/chuyen-nhuong (property transfers)
/bang-gia (pricing)
/forgot-password
/reset-password
/about /contact /privacy /terms
Verified live (https://platform.goodgo.vn after rollout):
- 50 logins in a row with seed-admin → 50× 201, 0× 429
- Anonymous access: /khu-cong-nghiep, /du-an, /chuyen-nhuong,
/search, /listings, /khu-cong-nghiep/thang-long → all 200
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Unauthenticated requests to /listings were being 302-redirected to /login
because '/listings' was missing from the publicPaths allowlist. /listings
is the public marketplace board and must be accessible without auth.
Unblocks 5 Playwright DataTable specs + smoke test (TEC-3040).
Co-Authored-By: Paperclip <noreply@paperclip.ing>
The i18n architecture (config, routing, translation files, locale pages) was
already built but non-functional due to three missing pieces:
1. next-intl not listed in package.json
2. middleware.ts not using createMiddleware from next-intl/middleware
3. next.config.js not wrapped with createNextIntlPlugin
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Enable prefer-inline for import-x/no-duplicates to support barrel
import patterns (value + type imports from same module)
- Inline duplicate type imports in middleware.ts and listing-form-steps.tsx
- Fix import ordering across API test files and MCP controller
- Add next-intl mock to search spec (FilterBar uses useTranslations)
- Exclude [locale] test duplicates from vitest (need proper i18n test setup)
All 801 tests passing (653 API + 119 web + 29 MCP). Zero lint errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add /auth/callback/google and /auth/callback/zalo pages that extract
tokens from query params and persist them via the auth store
- Add handleOAuthCallback method to Zustand auth store
- Update middleware to allow /auth/callback/* as public routes
- Show OAuth error messages on login page when redirected back
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Create (public) route group with landing page (hero, featured listings, district links, stats, CTA)
- Create search page with filter sidebar, list/map/split view modes, URL-synced filters, pagination
- Build ListingMap component with CSS-based marker visualization and popup details
- Build FilterBar with transaction type, property type, city, price range, area, bedrooms filters
- Build PropertyCard and SearchResults components with responsive grid layout
- Update middleware to allow public access to / and /search routes
- Move dashboard home to /dashboard to avoid route conflict
- All content in Vietnamese, mobile responsive
Co-Authored-By: Paperclip <noreply@paperclip.ing>