- Self-heal consent desync: if the server's consent cookie and the
client's localStorage flag fall out of sync, the client now notices
and stops silently sending events into a void.
- Stop double-counting hard page loads in hit/visitor stats.
- Flush pending disk writes on SIGTERM/SIGINT so redeploys don't drop
the last few events.
- Log rate-limiter drops once per window (diagnoses proxy misconfig).
- Widen the bot-detection allowlist to avoid flagging real browsers
(Chrome-iOS, Samsung Browser, etc.) as bots.
- Stop merging distinct visitors who share an IP when a real session
cookie is already present — only fall back to IP correlation when no
cookie was sent at all.
- Include analyticsEvents/episodePlays in backup snapshots and restore
them, so a restore no longer wipes engagement analytics.
- Regroup admin dashboard KPI cards by time scope (range vs all-time)
and remove a duplicated Top Countries/States block.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Community posts/replies fell back to the local part of the student's email
(e.g. "student.test" from student.test@versebyverse.local) whenever they
hadn't set a display name. The per-lesson discussion comments were worse —
they fell back to the FULL email address, and that endpoint has no auth
requirement, so it was publicly readable.
Adds getCommunityDisplayName() in study-helpers.js: uses the student's
display name when set, otherwise assigns a stable "Student XXXX" handle
derived from their account id — never their email. Both the community board
and lesson comments now resolve names live from the author's current
account at read time (falling back to the stored snapshot only if the
account was deleted), so this also fixes any already-posted content
without a data migration.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds forgot/reset password, signup email verification, sign-out-of-all-devices,
note-usage and certificate display in account settings, and password-changed
security emails. Fixes study sessions being lost on server restart by
persisting them to disk, and fixes sanitizeStudyUsers silently dropping the
new pending-token fields on load. Also surfaces the lesson discussion board
in the main site nav, trims the sign-in page for returning users, and renames
colossiansStudy.tsx/colossiansStudyData.ts to studyPages.tsx/legacyColossiansData.ts
now that the study system covers more than Colossians.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Day thresholds and per-tier subject/body/CTA are now editable in the
Email Templates panel. Tier keys are position-based (t1/t2/t3) so
changing day values never re-sends emails already fired.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sends 7/14/30-day inactivity emails to study users who haven't logged in,
with one-click HMAC-signed unsubscribe and automatic re-arm on next login.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Critical fixes:
- sanitizeLoadedHitStats/VisitorStats: restore full state shape so a
snapshot restore no longer crashes hit-counting middleware (missing
byPathReal, byPathBot, byDayReal, byDayBot, botReasons, ipHashIndex)
- /questions/share/🆔 read state.questions only, not draft questions
- inbound-email: validate date with Number.isFinite before toISOString
- study-reminders: wrap each send in try/catch so one failure doesn't
block remaining users; persist sent-markers after each success
Security:
- getClientIp: use req.ip (trust-proxy-resolved) instead of raw
x-forwarded-for header to prevent IP spoofing
- env-snapshot.env: delete immediately after backup tar stream ends
so secrets don't linger on disk between exports
Correctness / UX:
- contact form: email failures no longer 500 the user after the
submission is already saved; log and fall through instead
- study-account profile: cap data URI avatar at 6 MB
- admin enrollment PATCH: validate slug against study catalog
- signup: return 503 at MAX_STUDY_USERS instead of silently dropping
oldest accounts
Memory leaks:
- contactHits, downloadHits Maps: prune stale entries at 5000 entries
- resendEmailSubmissionIndex: trim to 2000 entries (oldest first)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Browsers drop Secure cookies on plain-http origins, so logging into the
admin over a LAN/VPN IP (e.g. during server migration, before TLS is in
front) silently failed every authenticated request. Setting
ALLOW_INSECURE_COOKIES=true omits the Secure flag; default behavior in
production is unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Lessons release at 1:00 AM, but reminders fired immediately, landing
around 9 PM the prior evening. Delay sends by 8 hours so they go out
at 9:00 AM on the release day.