commit 5c73295ce50ac274fc4f3ce8942abfa02bb2a3ba Author: Andriy Oblivantsev Date: Sun Mar 1 11:35:34 2026 +0000 Add geo app architecture and delivery plan. Document the backend auth, invitation flow, GeoJSON storage model, and TS client-library integration for frontend key management. Made-with: Cursor diff --git a/docs/geo-auth-backend-plan.md b/docs/geo-auth-backend-plan.md new file mode 100644 index 0000000..8c512a3 --- /dev/null +++ b/docs/geo-auth-backend-plan.md @@ -0,0 +1,158 @@ +--- +name: geo-auth-backend-plan +overview: Design and implement a Go + PostgreSQL(PostGIS) backend with Ed25519 auth, hybrid invitation onboarding, and per-user GeoJSON feature-collection management; plus a no-build Vue/Vuetify frontend for key lifecycle and API usage. +todos: + - id: bootstrap-go-api + content: Create greenfield Go API skeleton with config, routing, and health endpoints + status: pending + - id: auth-ed25519 + content: Implement challenge-response auth with Ed25519 verification and token issuance + status: pending + - id: hybrid-invitations + content: Implement hybrid invitation issuance/verification with lineage and replay protection + status: pending + - id: geo-storage + content: Add PostGIS-backed models/migrations for collections and point features + status: pending + - id: geo-crud-endpoints + content: Expose authenticated per-user GeoJSON collection/feature CRUD endpoints + status: pending + - id: frontend-static-vue + content: Build no-bundler Vue+Vuetify static frontend with key generation/import/export in localStorage + status: pending + - id: tests-and-hardening + content: Add tests for auth/invites/geo ownership and enforce validation/security checks + status: pending + - id: ts-api-client-lib + content: Create reusable TypeScript API client library with @noble/ed25519 for key generation, key storage helpers, and signed API communication + status: pending + - id: js-frontend-wrapper + content: Add plain JavaScript wrapper adapter so no-build frontend can consume the TypeScript-built client bundle + status: pending +isProject: false +--- + +# Geo App Auth + GeoJSON Service Plan + +## Current Project Context + +- No existing backend source files were found in `/home/ano/projects/produktor.io/tenerife.baby/backend`, so this plan assumes a greenfield implementation in that directory. +- Stack confirmed: **Go + PostgreSQL (+ PostGIS)**. +- Invitation model confirmed: **Hybrid** (signed token + inviter lineage tracking). +- Client scope confirmed: **both** a reusable TS library and a plain JS frontend integration. +- Build policy confirmed: **npm/build allowed for library package only**; frontend remains no npm/no bundling. + +## Architecture + +- **Auth identity:** user identity = Ed25519 public key (`base64url`); server never stores private keys. +- **Login/auth:** wallet-style challenge-response: + - client requests nonce + - client signs nonce with private key + - server verifies signature against registered public key + - server returns short-lived access token + optional refresh token. +- **Registration via invitation:** + - inviter generates signed invitation payload (invitee pubkey optional at creation time) + - invite token includes inviter key, expiry, nonce/jti, usage constraints, and lineage metadata + - invitee submits invite token + own pubkey + signature proof + - server verifies inviter trust chain + token signature + replay protection. +- **Data model:** users own feature collections; each collection owns GeoJSON Features (initially Point geometry). +- **Storage:** PostGIS geometry column (`geometry(Point, 4326)`) plus raw GeoJSON JSONB for roundtrip fidelity. + +```mermaid +flowchart TD + inviter[ExistingUserInviter] -->|"signs invite token"| inviteToken[InviteToken] + invitee[NewUserInvitee] -->|"submits pubkey + signed challenge + token"| api[GoAPI] + api -->|"verify inviter + token + signature"| users[(Users)] + api -->|"issue access token"| session[(Sessions)] + invitee -->|"CRUD collections/features"| api + api --> collections[(FeatureCollections)] + api --> features[(FeaturesPostGIS)] +``` + +## Planned Files and Components + +- Backend bootstrap: + - `/home/ano/projects/produktor.io/tenerife.baby/backend/cmd/api/main.go` + - `/home/ano/projects/produktor.io/tenerife.baby/backend/internal/http/routes.go` + - `/home/ano/projects/produktor.io/tenerife.baby/backend/internal/config/config.go` +- Auth/invitation domain: + - `/home/ano/projects/produktor.io/tenerife.baby/backend/internal/auth/challenge.go` + - `/home/ano/projects/produktor.io/tenerife.baby/backend/internal/auth/ed25519_verify.go` + - `/home/ano/projects/produktor.io/tenerife.baby/backend/internal/invite/token.go` + - `/home/ano/projects/produktor.io/tenerife.baby/backend/internal/invite/service.go` +- Geo domain: + - `/home/ano/projects/produktor.io/tenerife.baby/backend/internal/geo/collections_service.go` + - `/home/ano/projects/produktor.io/tenerife.baby/backend/internal/geo/features_service.go` +- Database: + - `/home/ano/projects/produktor.io/tenerife.baby/backend/migrations/0001_init_users_invites.sql` + - `/home/ano/projects/produktor.io/tenerife.baby/backend/migrations/0002_geo_collections_features.sql` +- Frontend (no npm/bundling): + - `/home/ano/projects/produktor.io/tenerife.baby/backend/web/index.html` + - `/home/ano/projects/produktor.io/tenerife.baby/backend/web/app.js` + - `/home/ano/projects/produktor.io/tenerife.baby/backend/web/api.js` +- Reusable TypeScript client library (separate package): + - `/home/ano/projects/produktor.io/tenerife.baby/backend/libs/geo-api-client/package.json` + - `/home/ano/projects/produktor.io/tenerife.baby/backend/libs/geo-api-client/tsconfig.json` + - `/home/ano/projects/produktor.io/tenerife.baby/backend/libs/geo-api-client/src/GeoApiClient.ts` + - `/home/ano/projects/produktor.io/tenerife.baby/backend/libs/geo-api-client/src/keys.ts` + - `/home/ano/projects/produktor.io/tenerife.baby/backend/libs/geo-api-client/src/storage.ts` + - `/home/ano/projects/produktor.io/tenerife.baby/backend/libs/geo-api-client/dist/` (build output consumed by frontend) + +## API Surface (v1) + +- `POST /v1/auth/challenge` -> nonce +- `POST /v1/auth/login` -> verify signature, issue token +- `POST /v1/auth/register` -> invite validation + pubkey registration +- `POST /v1/invitations` -> inviter creates signed invite payload +- `GET /v1/me/keys` -> registered pubkey metadata +- `POST /v1/collections` / `GET /v1/collections` +- `POST /v1/collections/{id}/features` / `GET /v1/collections/{id}/features` +- `PATCH/DELETE /v1/features/{id}` + +## Security and Validation + +- Enforce strict canonical payload format before signature verification. +- Replay protection with nonce/jti tables and TTL. +- Invite expiration + max use + optional binding to target pubkey. +- Per-user row-level authorization in service layer (and optionally DB RLS later). +- Input validation for GeoJSON (`Feature`, `Point`, lon/lat ranges, SRID 4326). + +## Frontend Constraints Implementation + +- Serve Vue + Vuetify from CDN (no npm, no bundling). +- Key lifecycle in browser via client library: + - generate Ed25519 keypair using `@noble/ed25519` in TS client + - precreate key material when first profile/session is initialized + - store key material in `localStorage` through client helper API (with optional encryption passphrase layer) + - import/export profile key material from user settings page. +- UI pages: Login/Register (invite), Collections list, Mapless Feature editor (Point first), Profile key management. + +## TypeScript API Client Library Scope + +- Export `GeoApiClient` class for all API communication: + - `createChallenge()`, `loginWithSignature()`, `registerWithInvitation()` + - `createInvitation()` + - collection/feature CRUD methods. +- Export key-management helpers: + - `generateKeyPair()`, `signChallenge()`, `exportKeys()`, `importKeys()` + - `ensureKeysInStorage()` for precreate-on-first-use behavior. +- Add request signing conventions compatible with backend challenge/invitation verification. +- Build to browser-consumable ESM/UMD artifacts; frontend uses built JS file directly without bundling. + +## TDD-first Delivery Slices + +1. Auth challenge + login signature verification tests. +2. Invitation token verification + replay/expiry tests. +3. Registration tests (happy path + invalid inviter chain). +4. Geo collection ownership tests. +5. GeoJSON point validation + CRUD tests. +6. TS client unit tests (`@noble/ed25519` flows, storage, API error handling). +7. Frontend key-management smoke tests (manual + scripted API checks via JS wrapper). + +## Acceptance Criteria + +- Only registered public keys can authenticate and access their own data. +- New user registration succeeds only with valid inviter lineage and signed token. +- Users can create/read/update/delete their own feature collections and Point features as GeoJSON. +- Reusable TS API client class handles auth + geo API communication and key lifecycle with `@noble/ed25519`. +- Frontend runs as static files without build tooling, consuming prebuilt client bundle, and supports key import/export + local persistence.