CI / test (push) Failing after 35s
Add a cache-optimized multi-stage Dockerfile with non-root runtime, compose service definitions for local/dev execution, CI Go version alignment, and docs/path cleanup updates. Made-with: Cursor
159 lines
7.3 KiB
Markdown
159 lines
7.3 KiB
Markdown
---
|
|
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 this repository root, so this plan assumes a greenfield implementation.
|
|
- 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:
|
|
- `cmd/api/main.go`
|
|
- `internal/http/routes.go`
|
|
- `internal/config/config.go`
|
|
- Auth/invitation domain:
|
|
- `internal/auth/challenge.go`
|
|
- `internal/auth/ed25519_verify.go`
|
|
- `internal/invite/token.go`
|
|
- `internal/invite/service.go`
|
|
- Geo domain:
|
|
- `internal/geo/collections_service.go`
|
|
- `internal/geo/features_service.go`
|
|
- Database:
|
|
- `migrations/0001_init_users_invites.sql`
|
|
- `migrations/0002_geo_collections_features.sql`
|
|
- Frontend (no npm/bundling):
|
|
- `web/index.html`
|
|
- `web/app.js`
|
|
- `web/api.js`
|
|
- Reusable TypeScript client library (separate package):
|
|
- `libs/geo-api-client/package.json`
|
|
- `libs/geo-api-client/tsconfig.json`
|
|
- `libs/geo-api-client/src/GeoApiClient.ts`
|
|
- `libs/geo-api-client/src/keys.ts`
|
|
- `libs/geo-api-client/src/storage.ts`
|
|
- `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.
|