Commit Graph

2 Commits

Author SHA1 Message Date
Matt 19eb2be85f Phase 1: Full implementation — security, bugs, utilities, UI/UX, consolidation
Build and Push Docker Image / build (push) Successful in 2m8s Details
28 items across 7 batches. 36 files changed (9 new, 27 modified).
1061 insertions, 406 deletions.

== Batch 1: Critical Security Fixes ==

1.1 — Fix open redirect in /auth/callback
  - src/routes/auth/callback/+server.ts: url.searchParams.get('next')
    was used directly in redirect(303, next). Attacker could set
    next=https://evil.com. Now wrapped through sanitizeRedirectUrl()
    which rejects protocol/host, //, javascript: prefixes; falls back
    to /dashboard.

1.2 — Fix open redirect in /login
  - src/routes/(auth)/login/+page.server.ts: redirectTo param used
    without validation in both load() and form action. Applied
    sanitizeRedirectUrl() to both locations.

1.3 — Fix RLS self-role-escalation
  - supabase/migrations/017_fix_rls_role_escalation.sql (NEW):
    "Users can update own profile" policy had USING(auth.uid()=id)
    but no WITH CHECK clause — users could SET role='admin' on their
    own row. Added WITH CHECK constraining role to current value.
  - deploy/init.sql: updated to match migration 017.

1.4 — Remove hardcoded secrets from docker-compose.yml
  - docker-compose.yml: removed hardcoded SECRET_KEY_BASE fallback.

== Batch 2: Critical & High Bugs ==

2.1 — Fix deleteAvatar wrong argument type
  - src/routes/(app)/settings/+page.server.ts: was passing supabase
    client object as second arg to deleteAvatar(memberId, avatarPath).
    Changed to pass member.avatar_url instead.

2.2 — Fix event.start_time typo -> event.start_datetime
  - src/routes/(app)/board/events/[id]/attendees/+page.server.ts:
    referenced event.start_time (doesn't exist on type). Caused
    "Invalid Date" in invitation/roll-call emails. Replaced both
    occurrences with event.start_datetime.

2.3 — Fix landing page CTA buttons missing href
  - src/routes/+page.svelte: Sign In and Join Us buttons had no href
    attribute — completely non-functional for visitors. Added
    href="/login" and href="/join" respectively.

2.4 — Fix auth pages logo inconsistency
  - src/routes/auth/reset-password/+page.svelte: hardcoded "M" letter
    in colored box replaced with actual Monaco USA logo image
    (MONACOUSA-Flags_376x376.png) matching login/layout.

2.5 — Fix currency USD -> EUR everywhere
  - src/routes/(app)/board/reports/+page.svelte: USD -> EUR, locale
    to fr-MC.
  - src/routes/public/events/[id]/+page.svelte: USD -> EUR, locale
    to fr-MC.
  - src/routes/(app)/admin/dashboard/+page.svelte: USD -> EUR, locale
    to fr-MC.

== Batch 3: High Security Fixes ==

3.1 — Sanitize HTML in email template rendering
  - src/lib/server/email.ts: added escapeHtml() utility that escapes
    &, <, >, ", '. Applied to all template variable values in
    sendTemplatedEmail() before substitution. URL-type keys
    (logo_url, site_url) exempted. Prevents XSS in emails.

3.2 — Add file upload MIME type validation
  - src/lib/server/storage.ts: added MAGIC_BYTES constant and
    validateFileMagicBytes() function checking PNG (89504E47),
    JPEG (FFD8FF), PDF (25504446), WebP (52494646), GIF (47494638)
    magic bytes against declared MIME. Applied in uploadAvatar and
    uploadDocument before storing.

3.3 — Docker container hardening
  - docker-compose.yml portal service: added security_opt
    [no-new-privileges:true], read_only: true with tmpfs for /tmp,
    deploy.resources.limits (memory: 512M, cpus: 1.0). Dockerfile
    already had USER sveltekit (non-root).

3.4 — Restrict board endpoints data exposure
  - src/routes/(app)/board/members/+page.server.ts: replaced
    .select('*') with explicit column list returning only fields
    the board UI actually displays. Removed sensitive columns.

== Batch 4: Shared Utilities ==

4.1 — Extract getVisibleLevels to shared utility
  - src/lib/server/visibility.ts (NEW): exports getVisibleLevels(role)
    returning appropriate visibility levels per role.
  - Replaced 4 duplicate definitions in:
    src/routes/(app)/dashboard/+page.server.ts
    src/routes/(app)/documents/+page.server.ts
    src/routes/(app)/events/+page.server.ts
    src/routes/(app)/events/[id]/+page.server.ts

4.3 — Fix N+1 query in getReminderEffectiveness
  - src/lib/server/dues.ts: rewrote loop executing individual DB
    queries per reminder into single batch query with IN filter.
    Maps results in JS instead of N+1 round-trips.

== Batch 5: Shared UI Components ==

5.1 — Create reusable EmptyState component
  - src/lib/components/ui/empty-state.svelte (NEW): accepts icon,
    title, description props and optional children snippet. Consistent
    muted-text centered layout matching design system.
  - Applied in DocumentPreviewModal and NotificationCenter.

5.2 — Move LoadingSpinner to shared ui/
  - src/lib/components/ui/LoadingSpinner.svelte (NEW): copied from
    auth/ to ui/ for general use. Original kept for compatibility.

  - src/lib/components/ui/index.ts: added barrel exports for
    EmptyState and LoadingSpinner.

== Batch 6: UX Standardization ==

6.4 — Add skip-to-content link
  - src/routes/(app)/+layout.svelte: added visually-hidden-until-
    focused skip link as first focusable element:
    <a href="#main-content" class="sr-only focus:not-sr-only ...">
    Added id="main-content" to <main> element.

6.5 — Add navigation loading indicator
  - src/routes/(app)/+layout.svelte: imported SvelteKit $navigating
    store. Shows thin animated progress bar at page top during
    transitions. CSS-only animation, no external dependencies.

== Batch 7: Code Consolidation ==

7.1 — Consolidate profile/settings pages
  - src/lib/server/member-profile.ts (NEW, 283 lines): shared helpers
    handleAvatarUpload(), handleAvatarRemoval(), handleProfileUpdate().
    Supports admin mode (supabaseAdmin) and user mode (scoped client).
  - src/routes/(app)/profile/+page.server.ts: simplified from ~167
    to ~88 lines using shared helpers.
  - src/routes/(app)/settings/+page.server.ts: simplified from ~219
    to ~106 lines using shared helpers.

7.2 — Consolidate registration flows
  - src/lib/server/registration.ts (NEW, 201 lines): shared helpers
    createMemberRecord(), cleanupAuthUser(), sendWelcomeEmail().
  - src/routes/(auth)/signup/+page.server.ts: simplified from ~167
    to ~85 lines using shared helpers.
  - src/routes/join/+page.server.ts: simplified from ~209 to ~117
    lines using shared helpers.

7.3 — Create status badge utility
  - src/lib/utils/status-badges.ts (NEW, 55 lines): centralized
    STATUS_MAP for all status types (membership, dues, payment,
    RSVP, event, roles). Exports getStatusConfig(),
    getStatusBadgeClasses(), getStatusLabel().

7.4 — Create rate limiting utility
  - src/lib/server/rate-limit.ts (NEW, 73 lines): in-memory
    Map-based rate limiter with TTL cleanup. Exports
    checkRateLimit(key, maxAttempts, windowMs) and resetRateLimit().
  - Applied to login: 5 attempts per 15 min by email.
  - Applied to forgot-password: 3 attempts per 15 min by email.
  - src/routes/(auth)/login/+page.server.ts: added rate limit check
    before signInWithPassword, reset on success.
  - src/routes/(auth)/forgot-password/+page.server.ts: added rate
    limit check before resetPasswordForEmail.

== New Files (9) ==
  src/lib/server/auth-utils.ts
  src/lib/server/visibility.ts
  src/lib/server/member-profile.ts
  src/lib/server/registration.ts
  src/lib/server/rate-limit.ts
  src/lib/server/email.ts (escapeHtml addition)
  src/lib/server/storage.ts (validateFileMagicBytes addition)
  src/lib/utils/status-badges.ts
  src/lib/components/ui/empty-state.svelte
  src/lib/components/ui/LoadingSpinner.svelte
  supabase/migrations/017_fix_rls_role_escalation.sql

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 07:55:19 +01:00
Matt e7338d1a70 Initial production deployment setup
- Production docker-compose with nginx support
- Nginx configuration for portal.monacousa.org
- Deployment script with backup/restore
- Gitea CI/CD workflow
- Fix CountryFlag reactivity for dropdown flags

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 02:19:49 +01:00