LuxBrief - Auth & Weather Architecture¶
Safety Ground Rules¶
LuxBrief is safety-critical aviation software. Flight planning errors, missing weather data, or incorrect compliance documentation can endanger lives.
Core Principles¶
- Safety over speed — Never prioritize time-to-market over correctness
- Reliability over features — Ship less, ship solid
- Never delete without asking — Soft-delete by default; no destructive migrations
- Ask when in doubt — Stop and ask on ambiguous safety/compliance decisions
- Aviation data is sacred — Weather, compliance, flights must never be lost
Development Rules¶
- No destructive migrations (no DROP TABLE/COLUMN)
- No silent failures (all errors logged with context)
- Fail open for reads, fail closed for writes
- EASA compliance is non-negotiable (12+ month retention)
- Test weather parsing thoroughly (CAVOK, NSC, VRB, SPECI, TEMPO/BECMG edge cases)
- Auth must be defense-in-depth (frontend + backend)
- Multi-tenancy isolation is mandatory (all queries scoped by operator ID)
Data Retention¶
| Data Type | Retention | Mutability | Deletion |
|---|---|---|---|
| Weather readings | 36 months min | Immutable | Never |
| Compliance reports | 36 months min | Immutable | Never |
| Flight records | 36 months min | Editable (audit log) | Soft-delete only |
| Aircraft records | Indefinite | Editable | Soft-delete only |
| Audit logs | 36 months min | Immutable | Never |
Full safety rules are version-controlled in ~/dev/luxbrief/AGENTS.md.
Authentication Architecture¶
Stack¶
- Identity Provider: Clerk (Cloud OIDC)
- Frontend: Clerk Next.js SDK (
@clerk/nextjsv7) - Backend: JWKS-based JWT validation (Go,
golang-jwt/jwt/v5) - Architecture: Auth is a separate bounded context (
internal/auth/), not just middleware
Auth Flow¶
1. User signs in via Clerk (frontend)
2. Clerk issues JWT with claims: sub, org_id, org_role, org_slug
3. Frontend sends: Authorization: Bearer <jwt> on every API call
4. Backend middleware:
a. Extracts Bearer token from header
b. Validates JWT signature via JWKS (cached RSA keys from Clerk)
c. Checks exp, nbf, iss claims
d. Extracts org_id → resolves to internal operator UUID via DB lookup
e. Sets OperatorContext in Echo request context
5. Handler reads OperatorContext for tenant-scoped queries Package Structure¶
internal/auth/
service.go # AuthService — ValidateToken(), OperatorResolver interface
claims.go # OperatorContext struct, ClerkClaims struct
jwks.go # JWKSCache — thread-safe RSA key cache (1hr TTL)
middleware.go # Echo middleware using AuthService
errors.go # ErrInvalidToken, ErrExpiredToken, etc. Key Types¶
- OperatorContext:
OperatorID(uuid),UserID(string),OrgRole(string) - AuthService: Validates tokens, resolves Clerk org_id to internal operator UUID
- JWKSCache:
sync.RWMutex-protected map ofkid→*rsa.PublicKey, 1-hour TTL, refresh on unknown kid - OperatorResolver: Interface:
GetByClerkOrgID(ctx, clerkOrgID) (uuid.UUID, error)
Roles (Clerk Organization Roles)¶
| Role | Scope | Description |
|---|---|---|
org:admin | Platform admin | Manages organizations (you) |
org:billing | Billing | Manage billing, invoices, payment methods |
org:chiefpilot | Operations | Full operational access, manage aircraft/users |
org:dispatcher | Operations | Create flights, generate briefings |
org:pilot | Flight crew | View briefings, acknowledge alerts |
org:readonly | Audit | View only (auditors, trainees) |
Current phase: Auth + identity only. RBAC enforcement deferred.
Clerk Organization Setup (Testing)¶
1. Enable Organizations¶
Clerk Dashboard → Organizations → Enable → Set "Allow users to create organizations" to OFF
2. Create Custom Roles¶
Dashboard → Organizations → Roles → Add: org:chiefpilot, org:dispatcher, org:pilot, org:billing, org:readonly
3. Create Test Organization¶
Dashboard → Organizations → Create: Name "Gestair", slug gestair. Note the org_2abc... ID.
4. Add Yourself¶
Organization → Members → Invite your email as org:admin
5. Seed Database¶
UPDATE operators SET clerk_org_id = 'org_YOUR_CLERK_ORG_ID'
WHERE id = '00000000-0000-0000-0000-000000000001'; 6. Backend Environment¶
CLERK_ISSUER=https://your-clerk-domain.clerk.accounts.dev
CLERK_JWKS_URL=https://your-clerk-domain.clerk.accounts.dev/.well-known/jwks.json 7. Verify JWT Claims¶
Clerk JWTs must include: sub, org_id, org_role, org_slug. Check in Dashboard → JWT Templates.
Troubleshooting¶
- No active org error: User must select org. Use
<OrganizationSwitcher />or set default. - JWT missing
org_id: User not in active org session. - 401 from backend: Check
CLERK_ISSUERmatches JWTissclaim. Decode at jwt.io. - Operator not found:
operators.clerk_org_idmust match JWTorg_idexactly.
Weather Architecture¶
Data Sources¶
Integrated¶
| Source | Data | Pricing | Status |
|---|---|---|---|
| AviationWeather.gov | METAR, TAF, SIGMET | FREE (NOAA) | Primary — integrated, parsing incomplete |
Planned¶
| Source | Data | Pricing | Status |
|---|---|---|---|
| FlightAware AeroAPI | Weather obs/forecast, airport search, flight tracking | Enterprise (per-query, $0.002/wx) | Planned — airport autocomplete + weather enrichment |
| CheckWX | METAR, TAF, G-AIRMET, AIRSIGMET | Free: 3K/day, Pro: $7/mo (50K/day) | Planned fallback |
Evaluated, Deferred¶
| Source | Data | Pricing | Reason Deferred |
|---|---|---|---|
| AVWX REST | METAR, TAF, PIREP, NOTAM (parsed + translated) | Free basic | Good parser but AWG already provides parsed fields |
| Windy.com | Point forecasts (wind, temp, precip) | 990 EUR/yr | Not aviation-specific; useful for route wx visualization later |
| Open-Meteo | General weather forecasts | Free / 150 EUR/mo | Same — supplemental route weather, not METAR/TAF |
Non-Weather Data (Available)¶
| Source | Purpose | Access |
|---|---|---|
| FlightAware AeroAPI | Flight tracking, ETAs, airport search, delays | Enterprise |
| Flightradar24 | Flight tracking | Contributor level |
| OpenSky Network | ADS-B state vectors | Available |
Weather Persistence¶
Current State¶
weather_readingstable exists in DB (migration 001) — never written toWeatherReadingRepositoryinterface defined inports/repository.go— no implementation- Weather fetched live on every request (4 HTTP calls per briefing), never cached
WeatherDatastruct defined butParsedfield always nil (fields received but not mapped)
Target Architecture¶
Request → Check cache (GetLatestByStation)
→ Fresh? Return cached reading
→ Stale? Fetch live from AWG → Parse → Persist → Return
→ AWG down? Return cached with staleness warning
→ No cache, no AWG? Return error (do NOT show empty as "no weather") Cache TTL¶
| Type | TTL | Reasoning |
|---|---|---|
| METAR | 30 minutes | Issued hourly, SPECI possible anytime |
| TAF | 6 hours | Issued every 6 hours |
| SIGMET | 1 hour | When implemented |
METAR Parsing (to implement)¶
Map AWG JSON fields → WeatherData struct: - Temp → Temperature - Dewp → DewPoint - Wspd → WindSpeed - Wdir → WindDirection - Wgst → WindGust - Visibility → Visibility - Altim → Pressure - Clouds → []Cloud - WxString → Weather - FlightCategory → new field (VFR/MVFR/IFR/LIFR)
FlightAware AeroAPI Integration (Planned)¶
- Airport autocomplete:
GET /airports?query=...→ typeahead in flight creation form - Weather enrichment:
GET /airports/{id}/weather/observations($0.002/query),GET /airports/{id}/weather/forecast($0.002/query) - Airport data: Name, ICAO, IATA, city, country, coordinates, delays
- Base client:
internal/adapters/flightaware/client.gowithx-apikeyheader and rate limiting
Implementation Phases¶
Phase 1: Sidebar Fix (completed planning)¶
Remove "N" avatar (Clerk floating widget). Replace sidebar bottom with minimal: brand icon + version + theme toggle.
Phase 2: Auth Service¶
- 2a: Create
internal/auth/package (Go) - 2b: Activate Clerk middleware (rename
proxy.ts→middleware.ts), add Bearer token to API client - 2c: Wire middleware in
main.go, replace hardcoded UUIDs, add env vars to docker-compose
Phase 3: METAR/TAF Parsing¶
Map AWG response fields into WeatherData struct. Expand response structs. Add FlightCategory. Fix silent time parse errors. Unit tests.
Phase 4: Weather Persistence¶
Implement postgres/weather_repository.go. Add cache-before-fetch logic. Add weather history endpoint. Wire into services.
Phase 5: FlightAware AeroAPI¶
Airport autocomplete (search endpoint + frontend typeahead). Weather enrichment adapter. Rate limiting.