Commit graph

4 commits

Author SHA1 Message Date
SkateSpots
f5ce4002e9 feat(lab,spots): validate contact details + drag & drop photo upload
- Lab forms: a non-empty email must be valid and website/portfolio must
  resolve to an https URL; invalid input shows an inline hint and blocks
  publish. Adds isValidEmail() (reused by sanitizeEmail). social/instagram
  are exempt since they may be bare @handles.
- New PhotoDropzone component: drag & drop plus multi-select image input,
  emitting File[] to the caller. Wired into CreateSpotPage + EditSpotPage
  (drop several photos at once) and AddPhotoToSpot, replacing the old
  one-file-at-a-time inputs.
- Spot photo uploads run sequentially and dedupe URLs.
2026-09-27 09:46:37 +07:00
SkateSpots
f73a360983 fix(lab): complete offer-a-service audit — data preservation, rendering, stats, input handling
- Preserve the geo 'g' tag and paid 'featured' tag when editing any Lab
  profile (all 7 craft roles via CraftProfileForm + 5 bespoke pages).
- Render previously collected-but-hidden fields on the Lab profile page:
  judge 'events_judged', organizer 'events_hosted', editor hardware/software,
  and spot references.
- Include Event Organizer (35013) in Community Stats + counts.
- Cap Title inputs at 100 (was 120) so profiles no longer fail the parser
  and silently vanish; MIN_TITLE_LEN 2 -> 1 for 1-char display names.
- Normalize bare-domain URLs (example.com) to https:// before storing instead
  of dropping/hiding them (new normalizeUrlInput helper; fixes extra url fields).
- No crew strip for judging/organizing (was wrongly showing Photo & Film);
  scrollable Offer Services FAB; live bio counters.
2026-09-27 09:38:03 +07:00
shakespeare.diy
40e4e53400 Hardening: https-only URLs, route-level crash resilience, cleaner errors (1.4.3)
Security & reliability audit follow-up. The app was already well-hardened
(strict CSP synced across index.html/_headers/netlify.toml, careful
textContent/escapeHtml in the Leaflet DOM, broad sanitizeUrl usage,
consistent author-filtering on trust-sensitive queries). This closes the
remaining gaps:

- sanitizeUrl is now HTTPS-only: bare http:// links are upgraded to
  https:// (not silently dropped); every other scheme (javascript:,
  data:, file:, relative) is rejected. Removes a downgrade/mixed-content/
  IP-leak vector and matches the native HTTPS-only WebView. Added a
  sanitizeCssString() allowlist helper for future CSS interpolation.

- Route-level ErrorBoundary (RouteErrorBoundary) wraps the routed pages,
  keyed by pathname: a crash in one page now shows a recoverable
  "this page slammed → go back" message while the header/nav/rest of the
  app keep working, instead of the single top-level boundary blanking the
  ENTIRE app. Resets automatically on navigation.

- ErrorBoundary fallback no longer leaks stack traces to users (full
  detail still logged to console for us); redesigned on-brand ("Slammed",
  neon, reassures that account + data are safe). Updated its test.

Author-filtering spot-check across admin/newsletter/featured/crew/stash
paths found no gaps — all trust-sensitive queries correctly constrain
authors.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-09 12:40:43 +07:00
shakespeare.diy
f19851da92 security: implement all 8 protection measures
## 1. sanitizeUrl() (already existed, verified applied)
- Applied to ShopDetailPage, CoachDetailPage with urlDomain() tooltip hints
- Blocks javascript:, data:, vbscript: and all non-http(s) schemes
- Returns null for invalid URLs — link simply doesn't render

## 2. Geohash validation in parsers
- Added isValidGeohash() in types.ts using ngeohash.decode()
- Min 4 chars (country-level precision rejected), max 12 chars
- Rejects lat/lng outside Earth bounds
- Rejects 0,0 "null island" — classic bot artifact
- VPN/Tor unaffected: geohash comes from map pin, not user IP

## 3. Content length limits in all parsers
- MAX_TITLE_LEN=100, MAX_DESCRIPTION_LEN=3000, MAX_LOCATION_LEN=200
- MIN_TITLE_LEN=2, MIN_GEOHASH_LEN=4
- Applied to parseSkateSpot, parseSkateShop, parseSkateCoach,
  parseSkateFilmer, parseSkateEditor, parseSkateEvent

## 4. Smart WoT default (already in codebase, verified correct)
- Logged out / <5 follows → 'all' (don't punish new/anon users)
- Logged in with ≥5 follows → 'trusted' (sort trusted first)
- Soft mode: trusted content floats to top, nothing hidden
- Active in MapPage and EventsPage

## 5. Trusted count stats in useGrowthStats
- Added trustedSpots and trustedMappers to GrowthStats type
- Heuristic: pubkeys with ≥2 published items = organic community member
- CommunityStatsPage now shows "🛡 X from active accounts" below spot count
- Purely additive — both numbers always visible

## 6. OfficialBadge component (already existed, wired up)
- Added to SpotCard, EventCard, ShopDetailPage, CoachDetailPage
- Only renders for FOUNDER_PUBKEY (20e7c953...)
- Zero-friction: invisible for all other pubkeys
- Impersonation attempts immediately visible

## 7. Leaflet marker clustering
- Installed leaflet.markercluster v1.5.3 + @types
- MarkerClusterGroup replaces direct marker.addTo(map)
- Custom cluster icon: red/orange gradient circle with count
- maxClusterRadius: 60px, spiderfyOnMaxZoom: true
- MarkerCluster CSS loaded from CDN in index.html

## 8. DM phishing warning banner
- PhishingWarningBanner component in DirectMessagesPage
- One-time dismissible (localStorage key: dm-phishing-warning-dismissed)
- Message: SkateSpots will never DM you asking for keys/payments
- Uses useLocalStorage hook, Shield icon, X dismiss button

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 14:03:15 +07:00