Files
gw/members-area.md
T
ponzischeme89 6d44e05de4 v1
2026-04-18 07:23:55 +12:00

9.8 KiB

Members Area

A password-protected portal for Goodwalk clients. It supports an onboarding lifecycle before full activation: admin creates the client, the client claims the account, completes onboarding details, signs the contract, and only then is the account activated for the normal members area.


Overview

Area URL
Login /members/login
Login 2FA verify /members/login/verify
Claim account /members/claim
Claim verify + set password /members/claim/verify
Onboarding /members/onboarding
Dashboard /members/dashboard
Book a walk /members/book
Walk history /members/walks
Profile /members/profile
Contract / onboarding /members/contract
Messages /members/messages

Authentication Flow

Account Claim (first-time setup)

Members are pre-registered by the admin via POST /api/v1/admin/members. They do not have a password yet.

  1. Member visits /members/claim and enters their email.
  2. Backend checks the email exists AND is unclaimed — sends a 6-character code.
  3. Member visits /members/claim/verify, enters the code and chooses a password.
  4. Account is claimed (is_claimed = true, password set) and moves into the onboarding lifecycle.

The claim code expires in 15 minutes. A generic response is always returned from /members/claim/request to prevent email enumeration.

Onboarding Lifecycle

Member accounts move through these states:

  1. invited
  2. onboarding
  3. pending_contract
  4. pending_review
  5. active

Only active members can use the normal members area routes like dashboard, bookings, walks, profile, and messages.

Login (returning and onboarding members)

  1. Member visits /members/login, enters email + password.
  2. If credentials are valid, a 2FA code is emailed.
  3. Member visits /members/login/verify, enters the code.
  4. On success, a JWT access token (15 min) and refresh token (7 days) are issued and stored in localStorage.
  5. If the member is not yet active, the frontend routes them into /members/onboarding instead of the dashboard.

The 2FA code expires in 10 minutes. Both codes are SHA-256 hashed before storage.

Token Refresh

The frontend memberApi.js transparently retries failed 401 responses with a token refresh via POST /api/v1/members/auth/refresh. If refresh fails, tokens are cleared and the user is redirected to login.


Backend API

All endpoints are under /api/v1/ prefix.

Public (rate-limited)

Method Path Description
POST /members/claim/request Send claim code (5/min)
POST /members/claim/complete Verify code + set password (10/min)
POST /members/auth/login Password check → send 2FA (10/min)
POST /members/auth/login/verify Verify 2FA → issue JWT pair (10/min)
POST /members/auth/refresh Rotate refresh token (10/min)

Member-authenticated (Bearer JWT with role: "member")

Method Path Description
GET /members/me Own profile
PUT /members/me Update contact details
GET /members/onboarding Read onboarding lifecycle + onboarding data
PUT /members/onboarding Save onboarding details and mark onboarding complete
POST /members/onboarding/contract Sign the service agreement
GET /members/walks Completed walks (newest first)
GET /members/bookings All bookings
POST /members/bookings Request a new booking
GET /members/contract Onboarding data + contract info
GET /members/messages Admin messages — excludes soft-deleted (newest first)
PUT /members/messages/{id}/read Mark a message as read
DELETE /members/messages/{id} Soft-delete (dismiss) a message
POST /members/messages/{id}/reply Send a reply to an admin message

Admin-authenticated (Bearer JWT with admin role)

Method Path Description
POST /admin/members Pre-register a member
GET /admin/members List all members
POST /admin/members/{member_id}/activate Activate a fully completed onboarding account
POST /admin/walks Record a completed walk
POST /admin/messages Send a message to a member
GET /admin/messages List all admin-sent messages with per-member read status

Database Tables

Table Purpose
members Member profiles plus lifecycle fields such as member_status, claimed_at, onboarding_completed_at, contract_signed_at, and activated_at
member_verification_codes Claim and 2FA codes (hashed, with expiry and used_at)
member_refresh_tokens Member JWT refresh tokens (hashed, revoked on rotation)
walks Completed walks recorded by admin
bookings Walk booking requests from members
admin_messages Messages sent from admin to members. Also stores member replies (direction = "outbound", reply_to_id links back to original). Soft-deleted via deleted_at.

Email

In development, set EMAIL_BACKEND=console (default) in .env — codes are printed to the backend stdout. No SMTP server required.

For production, configure:

EMAIL_BACKEND=smtp
SMTP_HOST=smtp.yourprovider.com
SMTP_PORT=587
SMTP_USE_TLS=true
SMTP_USER=your@email.com
SMTP_PASSWORD=yourpassword
EMAIL_FROM=noreply@goodwalk.co.nz

Database Migration

Run the Alembic migrations to create the member tables and onboarding lifecycle columns:

cd backend
alembic upgrade head

Migration files (in order):

  • alembic/versions/a1b2c3d4e5f6_add_members.py
  • alembic/versions/c7d2b6f4a9e1_add_member_onboarding_lifecycle.py
  • alembic/versions/a7f3e2c1b8d4_add_message_soft_delete_and_reply.py

Creating an Onboarding Client (admin workflow)

Use the admin JWT to create an onboarding account:

curl -X POST http://localhost:8000/api/v1/admin/members \
  -H "Authorization: Bearer <admin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jane@example.com",
    "first_name": "Jane",
    "last_name": "Smith",
    "phone": "021 234 5678",
    "address": "123 Main St, Auckland",
    "emergency_contact": "John Smith 021 987 6543",
    "onboarding_data": {
      "dog_name": "Buddy",
      "dog_breed": "Labrador",
      "vet_name": "Auckland Vets",
      "vet_phone": "09 123 4567",
      "vaccinations_up_to_date": true,
      "service": "Pack Walk"
    }
  }'

The client then:

  1. goes to /members/claim
  2. claims the account and sets a password
  3. logs in
  4. completes /members/onboarding
  5. signs the contract
  6. waits for admin activation

Admin then activates the member with:

curl -X POST http://localhost:8000/api/v1/admin/members/<member-uuid>/activate \
  -H "Authorization: Bearer <admin_token>"

Recording a Walk (admin workflow)

curl -X POST http://localhost:8000/api/v1/admin/walks \
  -H "Authorization: Bearer <admin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "member_id": "<member-uuid>",
    "walked_at": "2026-03-31T09:00:00+13:00",
    "service_type": "pack_walk",
    "duration_minutes": 60,
    "notes": "Buddy was a superstar today!"
  }'

Frontend Architecture

  • Layout: frontend/src/routes/members/+layout.svelte — sticky nav bar, auth guard, lifecycle-aware routing, logout button.
  • Lifecycle guard: the layout reads /api/v1/members/onboarding after login and routes non-active users to /members/onboarding.
  • Token storage: localStorage keys member_access_token / member_refresh_token.
  • API client: frontend/src/lib/memberApi.js — wraps all API calls, handles 401 retry with token refresh.
  • Root layout: Updated to skip the main site Header/Footer for /members/* routes.

Page design system

All members area pages share a consistent visual language (dark green #213021, yellow #FFD100, warm neutral surfaces). Key patterns:

Pattern Usage
Dark hero header Dashboard, Walks — full-bleed dark green gradient with yellow CTA
Light card header Book, Profile, Messages — white/cream card with yellow radial accent
Step accordion Book a walk — collapsible numbered steps with animated open/close
Split workspace Messages — inbox list (300 px) + reader panel, collapses to single-column on mobile
Skeleton loaders All pages — shape-matched placeholder surfaces while data loads

Messages — Member Features

Members can manage their inbox directly from /members/messages:

  • Read tracking — messages are automatically marked as read when opened.
  • Delete — members can dismiss messages. A confirmation step is shown before deletion. Deletion is a soft-delete on the backend (deleted_at timestamp); the message is hidden from the member but retained for admin records.
  • Reply — members can reply to any message from within the reader panel. Replies are stored as direction = "outbound" records linked via reply_to_id and appear in a thread below the original message body.

Security Notes

  • Member tokens carry role: "member" in the JWT payload.
  • get_authenticated_member allows signed-in onboarding users to access onboarding-only routes.
  • get_current_member only allows members with member_status = "active" to access the full members area.
  • Verification codes are SHA-256 hashed before storage (same as refresh tokens). Plaintext is only ever sent via email.
  • All verification codes have explicit expires_at and used_at columns — replay attacks are blocked.
  • Claim requests always return a generic message to prevent email enumeration.
  • All auth endpoints are rate-limited via slowapi.
  • Message delete and reply endpoints verify member_id ownership before acting — members cannot affect each other's messages.