Skip to content

FlightLog RPAS Chile: multi-tenant backend, dual auth, GIS, authenticated frontend - #2

Draft
PyCHile wants to merge 7 commits into
mainfrom
claude/flightlog-rpas-full-stack-7fogbx
Draft

FlightLog RPAS Chile: multi-tenant backend, dual auth, GIS, authenticated frontend#2
PyCHile wants to merge 7 commits into
mainfrom
claude/flightlog-rpas-full-stack-7fogbx

Conversation

@PyCHile

@PyCHile PyCHile commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Summary

Builds a working (not mocked) vertical slice of FlightLog RPAS Chile: a multi-tenant NestJS + Prisma + PostgreSQL/PostGIS backend and a Next.js authenticated frontend, per the task brief. Full scope (~30 screens, live Azure validation, zero-tolerance security gate) was not achievable in one session — this PR is honest about what's real vs. pending. See docs/ for the complete accounting.

  • Auth: local (bcrypt, lockout, email verify, password reset, session revocation) and Microsoft Entra ID (OIDC Authorization Code + PKCE, JWKS validation) behind a shared IdentityProvider interface. Entra disabled by default (ENTRA_ENABLED=false) — never validated against a real Azure tenant (no credentials available); tested with locally-signed simulated JWTs.
  • Multi-tenancy: tenantId always comes from the verified JWT (JwtAuthGuard), never from client input. 13 integration tests attempt the exact cross-tenant attacks listed in the task spec — all correctly denied.
  • RBAC: 9 roles, enforced server-side via RolesGuard, independent of what the frontend renders.
  • GIS/telemetry: real PostGIS (ST_MakePoint, ST_MakeLine, ST_Length), GeoJSON endpoints, MapLibre rendering in the frontend.
  • Core modules: clients, projects, missions, flights, drones, batteries/sensors/maintenance, documents (MinIO), incidents, costs, privacy requests, audit log — all tenant-scoped, RBAC-guarded.
  • Frontend: authenticated app (dashboard, clients, projects, missions, flights+map, drones, documents, users/roles, audit, GIS map) wired to real endpoints — loading/empty/error states, RBAC-aware rendering, pagination/search, RHF+Zod forms.
  • Tests: 23 backend unit tests, 22 integration tests (incl. multi-tenant isolation), 3 Playwright E2E — all passing against a live PostgreSQL+PostGIS instance.
  • Security: 3 Semgrep scans (initial/intermediate/final) — 0 critical/high/warning findings in the final pass, all INFO findings triaged with written justification in docs/security/semgrep-report.md.

Known limitations (see final report for full detail)

  • Docker daemon unavailable in the dev sandbox — docker-compose.yml/Dockerfiles authored and YAML-validated, but never actually run. Full native equivalent (PostgreSQL+PostGIS, migrations, seed, backend, frontend, all tests) was verified instead.
  • Entra ID adapter implemented and unit-tested with simulated tokens only — never validated against a real Azure tenant.
  • Several modules (contacts, quotes/contracts, batteries/sensors/maintenance, incidents, costs, privacy requests) have complete, tested, RBAC-guarded APIs but no dedicated frontend screen yet — usable today via Swagger (/api/docs).
  • No client portal, auditor portal, settings screen, or reports screen.

Test plan

  • npm run build (backend, frontend) — clean
  • npm run lint / typecheck (backend, frontend) — clean
  • npx prisma migrate deploy + seed against real PostgreSQL 16 + PostGIS 3
  • Backend unit tests (23/23) and integration/multi-tenant tests (22/22)
  • Playwright E2E (3/3) against real backend + frontend + browser
  • Semgrep initial/intermediate/final scans — 0 blocking findings
  • docker compose up --buildnot run (no Docker daemon in this environment); recommended before considering this deployable to any shared environment

🤖 Generated with Claude Code

https://claude.ai/code/session_01DUGiezdNx1ZvZWBgGazzQd


Generated by Claude Code

claude added 6 commits August 5, 2026 05:11
Backend (NestJS + Prisma + PostgreSQL/PostGIS):
- Dual auth: LocalIdentityProvider (bcrypt, lockout, email verify,
  password reset) and MicrosoftEntraIdentityProvider (OIDC Authorization
  Code + PKCE, JWKS validation), both behind a shared IdentityProvider
  interface. Entra disabled by default (ENTRA_ENABLED=false).
- App-issued JWT access/refresh sessions with revocation, entra_role_mappings
  table for external-group -> internal-role mapping.
- Multi-tenant isolation enforced server-side: tenantId always comes from
  the verified JWT via JwtAuthGuard, never from client input. RBAC via
  RolesGuard + 9 SystemRole values. Audit log interceptor on every mutation.
- Core modules: tenants, users, clients, contacts, projects, missions,
  flights, drones, batteries, sensors, maintenance, documents (MinIO),
  incidents, costs, privacy requests.
- Real PostGIS: telemetry import derives flight paths (ST_MakeLine) and
  distance, GIS endpoints return GeoJSON for missions/flights.
- Verified end-to-end against a live PostgreSQL+PostGIS instance: migration
  applies, seed persists, login works, cross-tenant GET/DELETE correctly
  denied (404/403), telemetry import + GIS path retrieval work.

Frontend (Next.js App Router + TanStack Query + RHF/Zod + Tailwind):
- Auth flows (local + Entra button gated by env), company registration,
  email verification, password reset.
- Authenticated app: dashboard, clients, projects, missions, flights
  (with MapLibre flight-path map), drones, documents (upload/download),
  users/roles admin, audit log, GIS map — all wired to real endpoints
  with loading/empty/error states, RBAC-aware rendering, pagination/search.
- Design tokens per the specified operational dark palette + type system.

Tests:
- Backend unit tests: local auth lockout/verification paths, RBAC guard,
  tenant-scoped CRUD cross-tenant denial (caught and fixed two latent
  tenantId-override bugs in list()/create()), Entra token validation using
  locally-signed simulated JWTs (invalid signature, expired, unauthorized
  tenant, nonce replay, missing company mapping) since no real Azure
  credentials exist in this environment.
- Playwright E2E against the real stack: login -> create client -> create
  project -> persisted; unauthenticated redirect; wrong-password error.
  Caught and fixed a real bug (Input component missing React.forwardRef,
  which silently broke every RHF-registered form field).

Security:
- Installed and applied entra-app-registration, frontend-design, and
  semgrep skills (see docs/skills-applied.md).
- Ran initial and intermediate Semgrep scans (semgrep.dev registry is
  blocked by this environment's egress policy; worked around by running
  the same rule packs from a local clone of semgrep/semgrep-rules).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DUGiezdNx1ZvZWBgGazzQd
…m, CI workflow

- Backend integration tests (supertest-style, via fetch, against a live
  instance): auth.e2e-spec.ts covers login success/failure, /auth/me,
  refresh token rotation, session revocation, password reset request.
  tenant-isolation.e2e-spec.ts runs the exact attack list from the task
  spec (cross-tenant read/delete/modify by direct id, telemetry import,
  GIS path, client-supplied tenantId in the body, missing/forged token,
  role-without-permission) and asserts every one is denied. All 22 pass
  against a real PostgreSQL/PostGIS instance with the seeded two-tenant
  dataset.
- Fixed the Entra provider unit tests: jose's Node runtime fetches remote
  JWKS via raw http/https, not the global fetch, so mocking fetch never
  actually intercepted it (tests were silently making real network calls
  and failing). Added a protected createJwksResolver() seam so tests can
  inject jose.createLocalJWKSet() bound to a locally-generated test
  keypair instead. All 7 Entra scenarios (valid token, untrusted signature,
  expired, unauthorized tenant, nonce replay, missing company mapping,
  adapter disabled) now genuinely exercise the real validation code path.
- Bumped the login endpoint's per-IP throttle from a hardcoded 10/min to
  an env-configurable LOGIN_RATE_LIMIT (default 20/min) — the account-level
  lockout (5 failed attempts) is the precise defense; the IP throttle is a
  coarse secondary one and was tripping during legitimate CI test runs.
- Added .github/workflows/ci.yml: install, env validation, lint, typecheck,
  migrations + seed on a Postgres/PostGIS service container, unit tests,
  backend+frontend build, integration + multi-tenant tests against the
  running app, Semgrep with SARIF upload to Code Scanning (fails the build
  on any unapproved error-level finding), Playwright E2E, report artifacts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DUGiezdNx1ZvZWBgGazzQd
- docs/authentication/{local-authentication,entra-setup,entra-role-mapping}.md
- docs/frontend/{design-system,component-inventory,accessibility-report,api-integration-map}.md
- docs/security/{semgrep-report,docker-compose-verification}.md — honest
  accounting of what was verified natively (real PostgreSQL/PostGIS, real
  test suite, real curl-level manual QA) vs. what could not be verified
  because the Docker daemon is unavailable in this sandbox.
- docker-compose.yml: added security_opt no-new-privileges to all 5
  services and read_only + tmpfs to postgres/redis/minio, fixing the 6
  WARNING-level Semgrep findings from the intermediate scan.
- frontend/Dockerfile: added (was missing), multi-stage, non-root user.
- .github/workflows/ci.yml: pinned actions/checkout, actions/setup-node,
  github/codeql-action/upload-sarif, actions/upload-artifact to resolved
  commit SHAs (git ls-remote), fixing 4 mutable-action-tag findings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DUGiezdNx1ZvZWBgGazzQd
All accepted with technical justification in docs/security/semgrep-report.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DUGiezdNx1ZvZWBgGazzQd
The services: block doesn't support command overrides, so MinIO's image
(which requires `server /data` as an argument) started with no command,
printed its usage text, and failed health checks — aborting the whole
job before any steps ran. Start it via `docker run` instead, with an
explicit readiness wait loop.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DUGiezdNx1ZvZWBgGazzQd
@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semgrep OSS found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

wait-on's http-get mode only treats 2xx as "ready". /auth/me correctly
returns 401 without a token (that's the route working as designed), so
wait-on timed out waiting for a status code it will never see, failing
the job even though the backend had started successfully.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DUGiezdNx1ZvZWBgGazzQd
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants