Skip to content

Commit 2060134

Browse files
committed
feat: multi-index monitoring, security hardening, changelog & bug fixes
1 parent 93ef830 commit 2060134

48 files changed

Lines changed: 2458 additions & 713 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Changelog
2+
3+
All notable changes to OpenFarm will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6+
7+
## [Unreleased]
8+
9+
### Added
10+
- Changelog page visible in-app under sidebar navigation.
11+
- SAVI L factor displayed on layer cards in field detail sidebar.
12+
13+
### Fixed
14+
- Index tab order on field detail page now matches sidebar order (NDVI → EVI → SAVI → NDWI).
15+
- "Start Processing" button no longer stays disabled after a completed analysis job.
16+
- SAVI L factor now persisted in `params_json` on raster layers during pipeline processing.
17+
- API response for raster layers now includes `params_json` field.
18+
19+
---
20+
21+
## [0.3.0] - 2026-03-16
22+
23+
### Added
24+
- Multi-index vegetation analysis: EVI, SAVI (configurable L factor), and NDWI alongside existing NDVI.
25+
- Index-specific colormaps and rescale ranges powered by a centralized `INDEX_REGISTRY`.
26+
- Per-index alert thresholds and severity rules.
27+
- Index type selector on field detail page, share page, and alert filters.
28+
- Job step progress labels for all four index types.
29+
- English and Spanish translations for all new index-related UI strings.
30+
31+
### Changed
32+
- Raster layer unique constraint now includes `layer_type` (migration `0003`).
33+
- Alerts table extended with `index_type` column (migration `0002`).
34+
- Share page reports grouped by index type with toggle selector.
35+
- Tile URLs use per-index colormap/rescale from registry instead of hardcoded NDVI values.
36+
37+
### Fixed
38+
- Containers no longer run as root — both API and Tiler Dockerfiles use a dedicated `appuser`.
39+
- Upload endpoint now whitelists `image/jpeg`, `image/png`, `image/webp` content types only.
40+
- `httpx.AsyncClient` lifecycle managed via FastAPI lifespan (created on startup, closed on shutdown) instead of leaked global.
41+
- Farm soft-delete cascade is now atomic (single `UPDATE` statement instead of select-and-loop).
42+
- Shapely `is_valid` check added to field geometry creation/update to reject invalid polygons.
43+
- Pagination offset capped at 100,000 to prevent expensive sequential scans.
44+
- Next.js database pool increased from 3 to 10 connections.
45+
- Organization context errors now show user-facing toast instead of silent `console.error`.
46+
47+
### Security
48+
- Documented JWT-in-tile-URL trade-off in `SECURITY.md` with mitigations.
49+
- Added rate limits (`10/minute`) to `create_invite`, `remove_member`, and `cancel_invite` org endpoints.
50+
- Added Alembic migration `0004` with database indexes on `alerts.field_id`, `field_stats.field_id`, `field_stats.layer_id`, `jobs.field_id`, and `scouting_observations.field_id`.
51+
- Web service healthcheck added to `docker-compose.yml`.
52+
53+
---
54+
55+
## [0.2.0] - 2026-02-01
56+
57+
### Added
58+
- Share links with public field reports and tile proxy.
59+
- Scouting observations with photo uploads via presigned MinIO URLs.
60+
- Organization invites with email notifications (Resend).
61+
- Audit event logging for all sensitive operations.
62+
- Role-based access control: owner, admin, member, viewer.
63+
- Rate limiting on job creation and upload endpoints.
64+
65+
### Changed
66+
- CORS configuration moved to environment variable.
67+
68+
---
69+
70+
## [0.1.0] - 2025-12-15
71+
72+
### Added
73+
- Initial release of OpenFarm platform.
74+
- Google OAuth authentication with NextAuth and JWT bridge to FastAPI.
75+
- Multi-tenant organization support with workspace switching.
76+
- Farm and field CRUD with GeoJSON geometry and PostGIS storage.
77+
- NDVI vegetation index pipeline: Sentinel-2 STAC search, band download, COG generation, zonal statistics.
78+
- TiTiler integration for COG tile serving with colormap rendering.
79+
- MapLibre GL JS map with PMTiles basemap and field/raster overlays.
80+
- Time-series charts with Apache ECharts.
81+
- Alert system with threshold and drop-percentage rules.
82+
- Celery worker for async satellite data processing.
83+
- Docker Compose stack: PostgreSQL+PostGIS, Redis, MinIO, TiTiler, Caddy.
84+
- Internationalization with next-intl (English, Spanish).
85+
- Structured logging with structlog (API) and pino (web).

README.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ Open source self-hostable and reproducible Crop Intelligence Platform
3636

3737
## Why OpenFarm
3838
- Self-hostable stack with clear service boundaries (Next.js ↔ FastAPI ↔ TiTiler ↔ MinIO ↔ PostGIS)
39-
- Reproducible NDVI pipeline with provenance (Element84 STAC → COG → TiTiler tiles)
39+
- Multi-index vegetation monitoring — NDVI, EVI, SAVI (configurable L factor), and NDWI from Sentinel-2 imagery
40+
- Reproducible pipeline with provenance (Element84 STAC → COG → TiTiler tiles)
4041
- Tenant isolation via `X-Org-Id` + JWT; RBAC (`owner`/`admin`/`member`/`viewer`)
4142
- MapLibre + PMTiles (no Mapbox token needed), ECharts for time series
4243
- Open, permissive BSD-3-Clause license
@@ -50,7 +51,7 @@ Open source self-hostable and reproducible Crop Intelligence Platform
5051
## Architecture
5152
```
5253
apps/web/ → Next.js 14 + NextAuth (Google OAuth) + Tailwind + shadcn/ui + MapLibre + ECharts
53-
services/api/ → FastAPI + SQLAlchemy 2.0 (async) + Alembic + Celery tasks (NDVI)
54+
services/api/ → FastAPI + SQLAlchemy 2.0 (async) + Alembic + Celery tasks (NDVI/EVI/SAVI/NDWI)
5455
services/tiler/ → TiTiler COG tile server (shared JWT auth)
5556
docker-compose.yml → Postgres/PostGIS, Redis, MinIO, API, Celery worker, TiTiler, Web
5657
```
@@ -138,14 +139,15 @@ ruff format --check .
138139
- Geometry stored as `MultiPolygon(4326)`; polygons auto-wrapped
139140
- Audit events on key actions (e.g., field_created)
140141

141-
## Feature Overview (MVP)
142-
- Auth: Google OAuth via NextAuth → JWT bridge (`/api/auth/token`)
143-
- Orgs & RBAC: owner/admin/member/viewer
144-
- Farms & Fields: draw/upload GeoJSON/KML, area calc, soft delete
145-
- NDVI Monitoring: STAC search → NDVI COG → TiTiler tiles → time-series stats
146-
- Alerts: ndvi_drop / ndvi_threshold
147-
- Scouting: geotagged notes, optional photo
148-
- Sharing: read-only field health report via share links
142+
## Feature Overview
143+
- **Auth**: Google OAuth via NextAuth → JWT bridge (`/api/auth/token`)
144+
- **Orgs & RBAC**: owner/admin/member/viewer with audit logging
145+
- **Farms & Fields**: draw/upload GeoJSON/KML, area calc, soft delete
146+
- **Vegetation Monitoring**: NDVI, EVI, SAVI (configurable L factor), NDWI — STAC search → COG → TiTiler tiles → time-series stats
147+
- **Per-Index Alerts**: configurable threshold and drop-percentage rules for each index
148+
- **Scouting**: geotagged observations with optional photo upload
149+
- **Sharing**: read-only field health reports via share links with multi-index toggle
150+
- **Changelog**: in-app changelog page with version history
149151

150152
## Quality & CI
151153
- GitHub Actions: lint + type-check (`.github/workflows/ci.yml`)

ROADMAP.md

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
# Roadmap
22

3-
> Last updated: February 2026
3+
> Last updated: March 2026
44
55
This document outlines where OpenFarm is today and where it's headed. If you'd like to contribute to any of these areas, check the [Contributing Guide](CONTRIBUTING.md) and look for issues labeled [`help wanted`](https://github.com/superzero11/OpenFarm/labels/help%20wanted) or [`good first issue`](https://github.com/superzero11/OpenFarm/labels/good%20first%20issue).
66

77
---
88

99
## Current Status
1010

11-
OpenFarm **Phase 1 MVP is complete**. The platform delivers end-to-end satellite-powered crop intelligence: auth, org management, farm/field CRUD, NDVI monitoring pipeline, alerts, scouting observations, shareable field health reports, and production-grade security hardening — all functional and deployed.
12-
13-
**179 of 182 tasks complete (98%).** The only remaining items are automated testing (API, frontend, E2E).
11+
OpenFarm **Phase 2 (Multi-Index) is complete**. The platform delivers end-to-end satellite-powered crop intelligence with four vegetation indices (NDVI, EVI, SAVI, NDWI), auth, org management, farm/field CRUD, configurable monitoring pipelines, per-index alerts, scouting observations, shareable field health reports, and production-grade security hardening — all functional and deployed. The focus now shifts to testing, documentation, and expanding the platform with new data sources and intelligence capabilities. See [Future Ideas](#future-ideas-post-mvp) for what's next.
1412

1513
---
1614

@@ -57,19 +55,49 @@ OpenFarm **Phase 1 MVP is complete**. The platform delivers end-to-end satellite
5755

5856
## Milestone 4 — Polish, Security & QA ✅
5957

60-
- [x] Viewer role enforcement — write endpoints restricted to member+ across all routers
61-
- [x] Rate limiting (slowapi) — 120 req/min default, tighter limits on jobs and uploads, Redis-backed
58+
- [x] Viewer role enforcement across all routers
59+
- [x] Rate limiting (slowapi) — Redis-backed, per-endpoint limits
6260
- [x] Pagination consistency — all list endpoints use `PaginatedResponse` envelope
6361
- [x] Audit log UI in settings page — event icons, search, pagination
6462
- [x] Frontend structured logging (pino)
6563
- [x] Celery worker health check in Docker Compose
6664
- [x] Backup script (`deploy/backup.sh`) — automated daily pg_dump, 7-day retention
67-
- [x] MinIO bucket versioning documentation
68-
- [x] WAL archiving / PITR documentation
65+
- [x] MinIO bucket versioning + WAL archiving/PITR documentation
6966
- [ ] API unit/integration tests
7067
- [ ] Frontend component tests
7168
- [ ] E2E acceptance tests
7269

70+
## Milestone 5 — Multi-Index Vegetation Analysis ✅
71+
72+
- [x] Index registry (`INDEX_REGISTRY`) — pluggable formula, bands, colormap, rescale, alert defaults
73+
- [x] EVI pipeline: `2.5 × (B08−B04) / (B08 + 6×B04 − 7.5×B02 + 1)`
74+
- [x] SAVI pipeline: `((B08−B04)/(B08+B04+L)) × (1+L)` with configurable L factor (0–1)
75+
- [x] NDWI pipeline: `(B03−B08)/(B03+B08)` for water stress detection
76+
- [x] Per-index colormaps (rdylgn for NDVI/EVI/SAVI, rdbu for NDWI)
77+
- [x] Per-index alert rules with configurable thresholds and drop percentages
78+
- [x] Generalized job endpoint (`POST /fields/{id}/jobs/index`) + backward-compatible NDVI alias
79+
- [x] Index selector UI on field detail page with floating map toggle
80+
- [x] Multi-index job submission from single form
81+
- [x] Per-index chart, legend, layer list, and map overlay
82+
- [x] Share page with index toggle for multi-index reports
83+
- [x] Full i18n translations (English + Spanish) for all index UI strings
84+
85+
## Milestone 6 — Security Hardening & Code Quality ✅
86+
87+
- [x] Non-root containers (API + Tiler Dockerfiles)
88+
- [x] Upload content-type whitelist (JPEG, PNG, WebP only)
89+
- [x] JWT-in-tile-URL security trade-off documented in SECURITY.md
90+
- [x] httpx client lifecycle via FastAPI lifespan
91+
- [x] Missing FK indexes added (alerts, field_stats, jobs, scouting)
92+
- [x] Shapely geometry validation with descriptive error messages
93+
- [x] Atomic farm soft-delete cascade
94+
- [x] Pagination offset cap (100,000)
95+
- [x] Rate limits on invitation/member management endpoints
96+
- [x] Shared `wkb_to_geojson()` utility to reduce code duplication
97+
- [x] Index task map derived from registry (single source of truth)
98+
- [x] SAVI L factor stored in layer `params_json` and displayed in UI
99+
- [x] In-app changelog page with parsed Keep a Changelog rendering
100+
73101
---
74102

75103
## Future Ideas (Post-MVP)
@@ -81,7 +109,6 @@ These are under consideration but not yet committed. Grouped by theme and roughl
81109
- **Direct API integration** — stable, versioned public API with API keys for integrating OpenFarm into existing farm management software
82110
- **Boundary detection** — automatic field boundary detection from satellite imagery
83111
- **Crop detection and classification** — ML-based crop type identification from spectral data
84-
- **Additional vegetation indices** — EVI, SAVI, NDWI alongside NDVI
85112
- **Multi-satellite support** — Landsat, Planet (currently Sentinel-2 only)
86113

87114
### Agricultural Intelligence
@@ -94,7 +121,7 @@ These are under consideration but not yet committed. Grouped by theme and roughl
94121
- **Advanced analytics and reporting framework** — customizable dashboards, scheduled reports, and data export
95122
- **Field comparison** — side-by-side health comparison across fields
96123
- **Historical analytics** — season-over-season trend analysis
97-
- **Advanced workflows** — rule-based automation (e.g., auto-trigger NDVI on new imagery, scheduled monitoring)
124+
- **Advanced workflows** — rule-based automation (e.g., auto-trigger analysis on new imagery, scheduled monitoring)
98125
- **Webhook/notification system** — email, Slack, or SMS on alerts
99126

100127
### Ecosystem & Integrations

SECURITY.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,24 @@ We will not pursue legal action against researchers who:
3434
- Engage in good faith to test and report vulnerabilities
3535
- Avoid privacy violations, data destruction, and service disruption
3636
- Provide us a reasonable time to remediate before public disclosure
37+
38+
## Known Limitations & Mitigations
39+
40+
### JWT in Tile URL Query Parameters
41+
42+
MapLibre GL JS does not support `Authorization` headers on tile requests. As a
43+
result, tile endpoints receive the JWT via `?access_token=<token>` query
44+
parameter (see `apps/web/src/lib/map-auth.ts`).
45+
46+
**Risk:** Tokens may appear in server access logs and browser history.
47+
48+
**Mitigations in place:**
49+
- JWTs have a 1-hour TTL; leaked tokens expire quickly.
50+
- The share-link tile proxy mints its own 5-minute service JWT, so public share
51+
pages never expose user tokens.
52+
- Caddy reverse proxy is configured not to log query strings in production.
53+
- Tile endpoints only serve read-only raster data; no mutation is possible.
54+
55+
**Planned improvements:**
56+
- Evaluate short-lived, tile-specific tokens with a narrower scope (e.g., per
57+
field, read-only) to reduce exposure if a token is logged.

apps/web/Dockerfile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ FROM node:20-alpine AS base
33
# Install deps
44
FROM base AS deps
55
WORKDIR /app
6-
COPY package.json ./
6+
COPY apps/web/package.json ./
77
RUN npm install
88

99
# Build
@@ -19,7 +19,9 @@ ENV NEXT_PUBLIC_TITILER_URL=$NEXT_PUBLIC_TITILER_URL
1919
ENV NEXT_PUBLIC_PROTOMAPS_URL=$NEXT_PUBLIC_PROTOMAPS_URL
2020

2121
COPY --from=deps /app/node_modules ./node_modules
22-
COPY . .
22+
COPY apps/web/ .
23+
# Copy CHANGELOG.md into public/ so the API route can read it at runtime
24+
COPY CHANGELOG.md ./public/CHANGELOG.md
2325
RUN npm run build
2426

2527
# Production

apps/web/messages/en.json

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"dashboard": "Dashboard",
1616
"farms": "Farms",
1717
"alerts": "Alerts",
18-
"settings": "Settings"
18+
"settings": "Settings",
19+
"changelog": "Changelog"
1920
},
2021
"sidebar": {
2122
"workspaces": "Workspaces",
@@ -209,7 +210,7 @@
209210
"edit": "Edit",
210211
"delete": "Delete",
211212
"tabInfo": "Info",
212-
"tabNdvi": "NDVI",
213+
"tabNdvi": "Monitor",
213214
"tabAlerts": "Alerts",
214215
"tabScouting": "Scouting",
215216
"tabShare": "Share",
@@ -283,6 +284,36 @@
283284
"title": "NDVI",
284285
"fieldRange": "Field range"
285286
},
287+
"monitoring": {
288+
"indexName": {
289+
"NDVI": "NDVI",
290+
"EVI": "EVI",
291+
"SAVI": "SAVI",
292+
"NDWI": "NDWI"
293+
},
294+
"indexDesc": {
295+
"NDVI": "Normalized Difference Vegetation Index",
296+
"EVI": "Enhanced Vegetation Index",
297+
"SAVI": "Soil-Adjusted Vegetation Index",
298+
"NDWI": "Normalized Difference Water Index"
299+
},
300+
"jobStep": {
301+
"computeNdvi": "Computing NDVI",
302+
"computeEvi": "Computing EVI",
303+
"computeSavi": "Computing SAVI",
304+
"computeNdwi": "Computing NDWI"
305+
},
306+
"alertMessage": {
307+
"ndviDrop": "NDVI dropped {pct}% below rolling average",
308+
"ndviThreshold": "NDVI fell below threshold ({value})",
309+
"eviDrop": "EVI dropped {pct}% below rolling average",
310+
"eviThreshold": "EVI fell below threshold ({value})",
311+
"saviDrop": "SAVI dropped {pct}% below rolling average",
312+
"saviThreshold": "SAVI fell below threshold ({value})",
313+
"ndwiDrop": "NDWI dropped {pct}% below rolling average",
314+
"ndwiThreshold": "NDWI fell below threshold ({value})"
315+
}
316+
},
286317
"scoutingTab": {
287318
"title": "Scouting Observations",
288319
"addObservation": "Add Observation",
@@ -355,12 +386,14 @@
355386
"fieldInfo": "Field Information",
356387
"area": "Area",
357388
"cropType": "Crop Type",
389+
"timeSeries": "Time Series",
358390
"ndviTimeSeries": "NDVI Time Series",
359391
"recentAlerts": "Recent Alerts",
360392
"recentScouting": "Recent Scouting Notes",
361393
"noAlerts": "No alerts",
362394
"noScouting": "No scouting observations",
363395
"noNdviData": "No NDVI data available",
396+
"noIndexData": "No {index} data available",
364397
"linkExpired": "This share link has expired or been revoked.",
365398
"linkNotFound": "Share link not found.",
366399
"loading": "Loading report...",
@@ -371,5 +404,10 @@
371404
"label": "Language",
372405
"en": "English",
373406
"es": "Español"
407+
},
408+
"changelog": {
409+
"title": "Changelog",
410+
"description": "Latest updates and improvements to OpenFarm",
411+
"unreleased": "Unreleased"
374412
}
375413
}

0 commit comments

Comments
 (0)