- verify_oauth2_token now passes GOOGLE_CLIENT_ID as third arg (audience check)
- fail-closed: returns 503 if GOOGLE_CLIENT_ID env var is not set
- add GOOGLE_CLIENT_ID = os.environ.get('GOOGLE_CLIENT_ID', '') to settings
- replace ClerkLoginViewTests with GoogleLoginViewTests (4 cases)
- update requirements-docker.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
190 lines
11 KiB
Markdown
190 lines
11 KiB
Markdown
# Changelog
|
||
|
||
All notable changes to the Eventify Backend are documented here.
|
||
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), versioning follows [Semantic Versioning](https://semver.org/).
|
||
|
||
---
|
||
|
||
## [1.10.0] — 2026-04-10
|
||
|
||
### Security
|
||
- **`GoogleLoginView` audience-check fix** (`POST /api/user/google-login/`) — **CRITICAL security patch**
|
||
- `verify_oauth2_token(token, google_requests.Request())` was called **without** the third `audience` argument, meaning any valid Google-signed ID token from *any* OAuth client was accepted — token spoofing from external apps was trivially possible
|
||
- Fixed to `verify_oauth2_token(token, google_requests.Request(), settings.GOOGLE_CLIENT_ID)` — only tokens whose `aud` claim matches our registered Client ID are now accepted
|
||
- Added fail-closed guard: if `settings.GOOGLE_CLIENT_ID` is empty the view returns HTTP 503 instead of silently accepting all tokens
|
||
|
||
### Changed
|
||
- **Removed Clerk scaffolding** — the `@clerk/react` broker approach added in a prior iteration has been replaced with direct Google Identity Services (GIS) ID-token flow on the frontend. Simpler architecture: one trust boundary instead of three.
|
||
- Removed `ClerkLoginView`, `_clerk_jwks_client`, `_get_clerk_jwks_client()` from `mobile_api/views/user.py`
|
||
- Removed `path('user/clerk-login/', ...)` from `mobile_api/urls.py`
|
||
- Removed `CLERK_JWKS_URL` / `CLERK_ISSUER` / `CLERK_SECRET_KEY` from `eventify/settings.py`; replaced with `GOOGLE_CLIENT_ID = os.environ.get('GOOGLE_CLIENT_ID', '')`
|
||
- Removed `PyJWT[crypto]>=2.8.0` and `requests>=2.31.0` from `requirements.txt` + `requirements-docker.txt` (no longer needed; `google-auth>=2.0.0` handles verification)
|
||
|
||
### Added
|
||
- **Settings**: `GOOGLE_CLIENT_ID = os.environ.get('GOOGLE_CLIENT_ID', '')` in `eventify/settings.py`
|
||
- **Tests**: `mobile_api/tests.py::GoogleLoginViewTests` — 4 cases: valid token creates user (audience arg verified), missing `id_token` → 400, `ValueError` (wrong sig / wrong aud) → 401, existing user reuses DRF token
|
||
|
||
### Context
|
||
- The consumer SPA (`app.eventifyplus.com`) now loads the Google Identity Services script dynamically and POSTs a Google ID token to the existing `/api/user/google-login/` endpoint. Django is the sole session authority. `localStorage.event_token` / `event_user` are unchanged.
|
||
- Deploy requirement: set `GOOGLE_CLIENT_ID` in the Django container `.env` **before** deploying — without it the view returns 503 (fail-closed by design).
|
||
|
||
---
|
||
|
||
## [1.9.0] — 2026-04-07
|
||
|
||
### Added
|
||
- **Lead Manager** — new `Lead` model in `admin_api` for tracking Schedule-a-Call form submissions and sales inquiries
|
||
- Fields: name, email, phone, event_type, message, status (new/contacted/qualified/converted/closed), source (schedule_call/website/manual), priority (low/medium/high), assigned_to (FK User), notes
|
||
- Migration `admin_api/0003_lead` with indexes on status, priority, created_at, email
|
||
- **Consumer endpoint** `POST /api/leads/schedule-call/` — public (AllowAny, CSRF-exempt) endpoint for the Schedule a Call modal; creates Lead with status=new, source=schedule_call
|
||
- **Admin API endpoints** (all IsAuthenticated):
|
||
- `GET /api/v1/leads/metrics/` — total, new today, counts per status
|
||
- `GET /api/v1/leads/` — paginated list with filters (status, priority, source, search, date_from, date_to)
|
||
- `GET /api/v1/leads/<id>/` — single lead detail
|
||
- `PATCH /api/v1/leads/<id>/update/` — update status, priority, assigned_to, notes
|
||
- **RBAC**: `leads` added to `ALL_MODULES`, `get_allowed_modules()`, and `StaffProfile.SCOPE_TO_MODULE`
|
||
|
||
---
|
||
|
||
## [1.8.3] — 2026-04-06
|
||
|
||
### Fixed
|
||
- **`TopEventsAPI` now works without authentication** — `POST /api/events/top-events/` had `AllowAny` permission but still called `validate_token_and_get_user()`, returning `{"status":"error","message":"token and username required"}` for unauthenticated requests
|
||
- Removed `validate_token_and_get_user()` call entirely
|
||
- Added `event_status='published'` filter (was `is_top_event=True` only)
|
||
- Added `event_type_name` field resolution: `e.event_type.event_type if e.event_type else ''` — `model_to_dict()` only returns the FK integer
|
||
|
||
---
|
||
|
||
## [1.8.2] — 2026-04-06
|
||
|
||
### Fixed
|
||
- **`FeaturedEventsAPI` now returns `event_type_name` string** — `model_to_dict()` serialises the `event_type` FK as an integer ID; the hero slider frontend reads `ev.event_type_name` to display the category badge, which was always `null`
|
||
- Added `data_dict['event_type_name'] = e.event_type.event_type if e.event_type else ''` after `model_to_dict(e)` to resolve the FK to its human-readable name (e.g. `"Festivals"`)
|
||
- No frontend changes required — `fetchHeroSlides()` already falls back to `ev.event_type_name`
|
||
|
||
---
|
||
|
||
## [1.8.1] — 2026-04-06
|
||
|
||
### Fixed
|
||
- **`FeaturedEventsAPI` now works without authentication** — `POST /api/events/featured-events/` had `AllowAny` permission but still called `validate_token_and_get_user()`, causing the endpoint to return HTTP 200 + `{"status":"error","message":"token and username required"}` for unauthenticated requests (e.g. the desktop hero slider)
|
||
- Removed the `validate_token_and_get_user()` call entirely — the endpoint is public by design and requires no token
|
||
- Also tightened the queryset to `event_status='published'` (was `is_featured=True` only) to match `ConsumerFeaturedEventsView` behaviour and avoid returning draft/cancelled events
|
||
- Root cause: host Nginx routes `/api/` → `eventify-backend` container (port 3001), not `eventify-django` (port 8085); the `validate_token_and_get_user` gate in this container was silently blocking all hero slider requests
|
||
|
||
---
|
||
|
||
## [1.8.0] — 2026-04-04
|
||
|
||
### Added
|
||
- **`BulkUserPublicInfoView`** (`POST /api/user/bulk-public-info/`)
|
||
- Internal endpoint for the Node.js gamification server to resolve user details
|
||
- Accepts `{ emails: [...] }` (max 500), returns `{ users: { email: { display_name, district, eventify_id } } }`
|
||
- Used for leaderboard data bridge (syncing user names/districts into gamification DB)
|
||
- CSRF-exempt, returns only public-safe fields (no passwords, tokens, or sensitive PII)
|
||
|
||
---
|
||
|
||
## [1.7.0] — 2026-04-04
|
||
|
||
### Added
|
||
- **Home District with 6-month cooldown**
|
||
- `district_changed_at` DateTimeField on User model (migration `0013_user_district_changed_at`) — nullable, no backfill; NULL means "eligible to change immediately"
|
||
- `VALID_DISTRICTS` constant (14 Kerala districts) in `accounts/models.py` for server-side validation
|
||
- `WebRegisterForm` now accepts optional `district` field; stamps `district_changed_at` on valid selection during signup
|
||
- `UpdateProfileView` enforces 183-day (~6 months) cooldown — rejects district changes within the window with a human-readable "Next change: {date}" error
|
||
- `district_changed_at` included in all relevant API responses: `LoginView`, `WebRegisterView`, `StatusView`, `UpdateProfileView`
|
||
- `StatusView` now also returns `district` field (was previously missing)
|
||
|
||
---
|
||
|
||
## [1.6.2] — 2026-04-03
|
||
|
||
### Security
|
||
- **Internal exceptions no longer exposed to API callers** — all 15 `except Exception as e` blocks across `mobile_api/views/user.py` and `mobile_api/views/events.py` now log the real error via `eventify_logger` and return a generic `"An unexpected server error occurred."` to the caller
|
||
- Affected views: `RegisterView`, `WebRegisterView`, `LoginView`, `StatusView`, `LogoutView`, `UpdateProfileView`, `EventTypeAPI`, `EventListAPI`, `EventDetailAPI`, `EventImagesListAPI`, `EventsByDateAPI`, `DateSheetAPI`, `PincodeEventsAPI`, `FeaturedEventsAPI`, `TopEventsAPI`
|
||
- `StatusView` and `UpdateProfileView` were also missing `log(...)` calls entirely — added
|
||
- `from eventify_logger.services import log` import added to `events.py` (was absent)
|
||
|
||
---
|
||
|
||
## [1.6.1] — 2026-04-03
|
||
|
||
### Added
|
||
- **`eventify_id` in `StatusView` response** (`/api/user/status/`) — consumer app uses this to refresh the Eventify ID badge (`EVT-XXXXXXXX`) for sessions that pre-date the `eventify_id` login field
|
||
- **`accounts` migration `0012_user_eventify_id` deployed to production containers** — backfilled all existing users with unique Eventify IDs; previously the migration existed locally but had not been applied in production
|
||
|
||
---
|
||
|
||
## [1.6.0] — 2026-04-02
|
||
|
||
### Added
|
||
- **Unique Eventify ID system** (`EVT-XXXXXXXX` format)
|
||
- New `eventify_id` field on `User` model — `CharField(max_length=12, unique=True, editable=False, db_index=True)`
|
||
- Charset `ABCDEFGHJKLMNPQRSTUVWXYZ23456789` (no ambiguous characters I/O/0/1) giving ~1.78T combinations
|
||
- Auto-generated on first `save()` via a 10-attempt retry loop using `secrets.choice()`
|
||
- Migration `0012_user_eventify_id`: add nullable → backfill all existing users → make non-null
|
||
- `eventify_id` exposed in `accounts/api.py` → `_partner_user_to_dict()` fields list
|
||
- `eventify_id` exposed in `partner/api.py` → `_user_to_dict()` fields list
|
||
- `eventify_id` exposed in `mobile_api/views/user.py` → `LoginView` response (populates `localStorage.event_user.eventify_id`)
|
||
- `eventifyId` exposed in `admin_api/views.py` → `_serialize_user()` (camelCase for direct TypeScript compatibility)
|
||
- Server-side search in `UserListView` now also filters on `eventify_id__icontains`
|
||
- Synced migration `0011_user_allowed_modules_alter_user_id` (pulled from server, was missing from local repo)
|
||
|
||
### Changed
|
||
- `accounts/models.py`: merged `allowed_modules` field + `get_allowed_modules()` + `ALL_MODULES` constant from server (previously only existed on server)
|
||
|
||
---
|
||
|
||
## [1.5.0] — 2026-03-31
|
||
|
||
### Added
|
||
- `allowed_modules` TextField on `User` model — comma-separated module slug access control
|
||
- `get_allowed_modules()` method on `User` — returns list of accessible modules based on role or explicit list
|
||
- `ALL_MODULES` class constant listing all platform module slugs
|
||
- Migration `0011_user_allowed_modules_alter_user_id`
|
||
|
||
---
|
||
|
||
## [1.4.0] — 2026-03-24
|
||
|
||
### Added
|
||
- Partner portal login/logout APIs (`accounts/api.py`) — `PartnerLoginAPI`, `PartnerLogoutAPI`, `PartnerMeAPI`
|
||
- `_partner_user_to_dict()` serializer for partner-scoped user data
|
||
- Partner CRUD, KYC review, and user management endpoints in `partner/api.py`
|
||
|
||
---
|
||
|
||
## [1.3.0] — 2026-03-14
|
||
|
||
### Changed
|
||
- User `id` field changed from `AutoField` to `BigAutoField` (migration `0010_alter_user_id`)
|
||
|
||
---
|
||
|
||
## [1.2.0] — 2026-03-10
|
||
|
||
### Added
|
||
- `partner` ForeignKey on `User` model linking users to partners (migration `0009_user_partner`)
|
||
- Profile picture upload support (`ImageField`) with `default.png` fallback (migration `0006–0007`)
|
||
|
||
---
|
||
|
||
## [1.1.0] — 2026-02-28
|
||
|
||
### Added
|
||
- Location fields on `User`: `pincode`, `district`, `state`, `country`, `place`, `latitude`, `longitude`
|
||
- Custom `UserManager` for programmatic user creation
|
||
|
||
---
|
||
|
||
## [1.0.0] — 2026-03-01
|
||
|
||
### Added
|
||
- Initial Django project with custom `User` model extending `AbstractUser`
|
||
- Role choices: `admin`, `manager`, `staff`, `customer`, `partner`, `partner_manager`, `partner_staff`, `partner_customer`
|
||
- JWT authentication via `djangorestframework-simplejwt`
|
||
- Admin API foundation: auth, dashboard metrics, partners, users, events
|
||
- Docker + Gunicorn + PostgreSQL 16 production setup
|