Commit Graph
128 Commits
Author SHA1 Message Date
Matthieu MALVACHE d3d0776a50 chore: bump version to 1.0.2 2026-02-26 13:36:22 +01:00
Matthieu MALVACHE b97de51262 fix(docker): resolve 4 CVEs in production image
Remove npm/npx from runner stage (unused at runtime) to eliminate
minimatch and tar CVEs. Upgrade Alpine packages to patch busybox
and zlib vulnerabilities.
2026-02-26 13:35:45 +01:00
Matthieu MALVACHE 98bee76bcf chore: disable Next.js telemetry in Docker image 2026-02-26 01:15:55 +01:00
Matthieu MALVACHE f17ebbf14b feat(ci): add Docker Hub publishing and versioning policy
Push images to both Docker Hub (rootfr/jmap-webmail) and GHCR on
release. Add semver versioning guidelines to CLAUDE.md and bump
version to 1.0.0.
2026-02-26 01:12:13 +01:00
Matthieu MALVACHE 1c924bf988 docs: mark OAuth2 Stalwart testing as complete 2026-02-26 00:46:25 +01:00
Matthieu MALVACHE 8d229580b9 docs: update TODO with remember-me completion 2026-02-26 00:45:55 +01:00
Matthieu MALVACHE 6a49514f7a feat(auth): add "Remember me" session persistence and simplify 2FA UX
Basic Auth sessions now persist across page refreshes via an encrypted
httpOnly cookie (AES-256-GCM). The feature is opt-in: admin sets
SESSION_SECRET env var, user checks "Remember me" at login.

Rework the 2FA input from a checkbox+animated panel to a discreet
"I have a 2FA code" text link that reveals the TOTP field on click.
2026-02-26 00:45:26 +01:00
Matthieu MALVACHE 2f20aa835b feat(auth): add RP-initiated logout and OAuth unit tests
Logout now revokes the refresh token and redirects to the IdP's
end_session_endpoint so the SSO session is fully terminated. The
end_session_url is validated server-side (HTTPS only) before being
returned to the client, preventing open redirect attacks.

Also adds 14 unit tests covering PKCE (including RFC 7636 test vector)
and OAuth discovery (fallback, caching, required field validation).
2026-02-26 00:07:38 +01:00
Matthieu MALVACHE d948ac3900 Merge branch 'feature/oauth2' 2026-02-25 23:38:45 +01:00
Matthieu MALVACHE d235403d4c feat(auth): add explicit OAuth issuer URL and improve discovery diagnostics
Support external IdPs (Keycloak, Authentik) that handle auth separately
from the JMAP server via OAUTH_ISSUER_URL. Also surfaces discovery
failures with logged errors and a visible warning banner instead of
silently hiding the SSO button.
2026-02-25 23:38:27 +01:00
Matthieu MALVACHE d3ffe31c3f fix(spam): use originalId when moving emails out of junk folder
undoSpam and batchUndoSpam were passing the store's composite mailbox
ID instead of the JMAP originalId, which could cause the move to
silently fail for shared mailboxes.
2026-02-25 16:12:53 +01:00
Matthieu MALVACHE 2ae0520026 feat(auth): add OAuth2/OIDC with PKCE for SSO login
Add OAuth2 authorization code flow with PKCE as an alternative to
basic auth. Supports Stalwart's built-in OAuth provider with automatic
token refresh, session persistence via httpOnly cookies, and graceful
fallback to basic auth when OAuth is not configured.
2026-02-25 16:04:17 +01:00
Matthieu MALVACHE f9813347a4 refactor(auth): extract helpers to reduce duplication in auth store
Extract shared logic into standalone functions:
- ERROR_PATTERNS + classifyLoginError: data-driven error classification
- loadIdentities: identity sorting and store sync
- markSessionExpired: sessionStorage wrapper
- initializeFeatureStores: feature store initialization
- Normalize console.error → debug.error, use optional chaining
2026-02-25 16:03:15 +01:00
Matthieu MALVACHE 5581ac768b refactor(jmap): extract shared constants and simplify client patterns
DRY: extract DEFAULT_MAILBOX_RIGHTS, EMAIL_LIST_PROPERTIES,
namespaceMailboxIds(), computeHasMore(), buildStatePollingRequest(),
STATE_TYPE_MAP, and parseEmailHeaders() to eliminate duplication
across multiple methods.

Simplify getContacts, batchMarkAsRead, batchMoveEmails, connect,
getBlobDownloadUrl, getEventSourceUrl, and ping with cleaner patterns.

Remove redundant comments and leftover console.log debug statements.
2026-02-25 14:15:51 +01:00
Matthieu MALVACHE 8843b70add fix(jmap): use path extraction instead of origin-length slicing for URL rewrite
When Stalwart returns session URLs with explicit :443 port, the URL
parser normalizes it out but the original string keeps it, causing
string slicing to produce wrong offsets. Now extracts the path by
finding the first / after // instead of relying on origin length.
2026-02-25 12:44:41 +01:00
Matthieu MALVACHE 602d50b703 fix(email): include identity display name in From header when sending
Sent emails only included the bare email address in the From field,
so recipients saw <user@example.com> with no display name. Now the
identity name from the JMAP server is included in the From header
for composed emails, quick replies, and saved drafts.

Also sorts identities so the one matching the login username is
selected by default in the composer dropdown.
2026-02-25 12:35:00 +01:00
Matthieu MALVACHE 97ffb72d04 fix(jmap): rewrite session URLs to match user-configured server origin
JMAP servers (e.g. Stalwart behind Docker) may return internal hostnames
in their session response (https://jmap/ instead of the actual server URL).
Replace the origin of all session URLs (apiUrl, downloadUrl, uploadUrl,
eventSourceUrl) with the user-configured server origin, using string
slicing to preserve RFC 6570 template placeholders without encoding.

Fixes #16
2026-02-24 15:17:03 +01:00
Matthieu MALVACHE 403eb565c9 fix(login): detect CORS errors and show actionable message instead of generic network failure
When the JMAP server is reachable but lacks CORS headers, users saw
"Unable to reach the server" which is misleading. Now a no-cors probe
distinguishes CORS blocking from true network failure, surfacing a
specific message that points users to their server's CORS config.
2026-02-23 18:19:28 +01:00
Matthieu MALVACHE 3516f030dc chore(deps): remove unused packages and explicit phantom dependencies
Remove jmap-jam (unused), @types/dompurify (bundled in dompurify 3.x),
eslint-config-next (not used in flat config), and lint-staged (unreferenced).
Add @eslint/js and eslint-plugin-react-hooks as explicit devDependencies
since they were phantom deps that would break without their former hosts.
2026-02-22 22:54:13 +01:00
Matthieu MALVACHE 66ff74a292 fix(calendar): inline isEdit checks to satisfy exhaustive-deps rule
Replace isEdit references with direct event checks inside the useCallback,
since isEdit is derived from event which is already in the dependency array.
2026-02-22 18:06:25 +01:00
Matthieu MALVACHE 2b599bbbb3 fix(email): skip redundant state update in polling refresh to prevent list flicker
The 15-second polling refresh was replacing the entire emails array on every
state change detection, even when the fetched data was identical. This caused
unnecessary re-renders of the virtualized thread list. Now compares email IDs,
threads, and keywords before updating state.
2026-02-22 18:05:17 +01:00
Matthieu MALVACHE 312f336407 fix(calendar): add missing timeString dependency to useCallback 2026-02-22 18:04:04 +01:00
Matthieu MALVACHE 7d18d2c3b7 chore(deps): update dependencies and fix ajv security vulnerability
Bump all semver-compatible packages including tailwindcss 4.2.0,
lucide-react 0.575.0, @typescript-eslint 8.56.0, and eslint 9.39.3.
Remaining minimatch advisories are ESLint-only dev deps awaiting
eslint-plugin-react ESLint 10 support.
2026-02-22 18:03:47 +01:00
Matthieu MALVACHE 7e42d2dedf fix(calendar): produce correct JMAP patch when removing recurrence rules
When editing a recurring event and clearing its recurrence, the update
patch was omitting recurrenceRules entirely instead of setting it to null.
The server never received the change. Also clear recurrenceOverrides and
excludedRecurrenceRules so orphaned exceptions don't persist. Same fix
applied to locations and alerts removal.

Fix "this_and_future" scope overriding recurrence removal by reordering
the spread so user updates take precedence over master's original rules.

Closes root-fr/jmap-webmail#14
2026-02-22 17:50:53 +01:00
Matthieu MALVACHE 82a27a1f52 feat(calendar): add drag-create, resize, recurring scope, quick-create, and duplication
Address GitHub issue #13 (events not spanning real duration, hour label alignment)
and add five standard calendar UX features:

- Click-drag on empty time slots to create events with pre-filled time range
- Resize events by dragging bottom edge handle (15-min snap, optimistic JMAP update)
- Recurring event edit/delete scope dialog (this/following/all occurrences)
- Double-click quick create with inline title input (PT1H default)
- Event duplication button in modal (clones +1 day, opens for editing)

Shared interaction logic extracted to hooks/use-time-grid-interactions.ts.
All features include i18n (8 locales), accessibility (ARIA, pointer events),
and proper error handling with toast feedback.
2026-02-22 17:31:16 +01:00
Matthieu MALVACHE da52389b8e docs: update TODO with GHCR and CI/CD deployment items 2026-02-22 00:00:30 +01:00
Matthieu MALVACHE 51660481a0 fix(ci): add path filters to Docker workflow to avoid unnecessary rebuilds 2026-02-21 23:59:31 +01:00
Matthieu MALVACHE 05f339283d feat(docker): add GHCR publish workflow and OCI image labels
Automate multi-arch Docker image builds (amd64+arm64) on push to main
or version tags via GitHub Actions. Add OCI metadata labels for GHCR
discoverability. Update release script to sync infra files to
public-release branch.
2026-02-21 23:28:51 +01:00
Matthieu MALVACHE 40c7fa3649 feat: add UI/UX polish, templates settings, confirm dialog, welcome banner, and navigation rail
- Add ConfirmDialog component with promise-based useConfirmDialog hook
- Add WelcomeBanner onboarding component with localStorage persistence
- Add NavigationRail (desktop icon rail + mobile bottom tab bar)
- Polish login form (shake on error, TOTP slide, password visibility, session expired banner)
- Add inline form validation with shake animation in email composer and contacts
- Add empty state patterns for contacts (no data vs no search results)
- Improve toast system with undo action support and typed durations
- Add template settings tab to settings page
- Refactor sidebar (cleaner code, remove unused imports)
- Add WCAG AA reduced-motion, safe area insets, sr-only live region
- Add shake/slide animations in globals.css
- Update i18n for all 8 locales
2026-02-17 02:30:09 +01:00
Matthieu MALVACHE 7da9fa61e2 feat(templates): add email templates with placeholder variables and composer integration
Local-storage templates with dynamic {{variable}} placeholders that auto-fill
from composer context (recipient, sender, date/time). Includes template manager,
category filtering, quick picker in composer toolbar, and settings tab.
48 tests covering placeholder extraction, filling, and variable suggestions.
2026-02-17 01:34:13 +01:00
Matthieu MALVACHE 5d2f5dcf7e feat(calendar): add participant scheduling with iTIP invitations and inline calendar invitation banner
Add organizer/attendee UI with RSVP buttons (accept/maybe/decline),
contact autocomplete in participant input, scheduling message support
via sendSchedulingMessages flag, and DnD notification for participant
events. Add inline calendar invitation banner in email viewer that
auto-detects .ics attachments, parses event details, and allows RSVP
or import to calendar. Includes cancellation display and 51 tests.
2026-02-17 01:09:03 +01:00
Matthieu MALVACHE 83198d46dc fix(contacts): resolve useMemo dependency warnings
Inline the contact filtering directly in useMemo instead of calling
store selectors, so the dependency array accurately reflects what
triggers recomputation.
2026-02-17 00:40:07 +01:00
Matthieu MALVACHE 613eb6ce84 fix(email): improve dark mode readability and fix long subject overflow
Dark mode color transform had 3 bugs preventing it from working:
- Hook only ran when external content was blocked, not for all emails
- Checked `theme` ('system') instead of `resolvedTheme` ('dark')
- Only transformed CSS style attrs, missing <font color> and bgcolor

Replaced invert+boost algorithm with blend-toward-white for text
(preserves hue, brighter results) and added background darkening
for light email backgrounds. Also handles color/bgcolor HTML attrs.

Fixed long email subjects pushing action buttons off-screen by adding
min-w-0 to the email viewer flex container in the page layout.
2026-02-17 00:38:46 +01:00
Matthieu MALVACHE be78603c48 fix(filters): show hierarchical folder structure in rule mailbox selector
Reuse buildMailboxTree/flattenMailboxTree to display nested folders
with indentation instead of a flat list. Exclude shared folders since
Sieve rules only operate on the user's own mailboxes.
2026-02-17 00:06:52 +01:00
Matthieu MALVACHE e80df576cf fix(scripts): make release.sh non-interactive and more reliable
- Remove interactive read prompt that blocked non-interactive shells
- Exclude .md files and .claude/ directory from Claude reference scan
- Also check for "anthropic" references
- Hard-fail instead of prompting on Claude references found
- Remove fragile final diff grep (was matching harmless content)
- Improve forbidden file check to report all matches before aborting
2026-02-16 23:47:45 +01:00
Matthieu MALVACHE a603ee496b feat(calendar): add event notifications with client-side alert evaluation
Evaluate JMAP CalendarEventAlert triggers client-side and display toast
notifications when alert times are reached. Extracts notification sound
into shared utility and mounts ToastContainer globally (fixes silent
toast failures across all components).

- Pure alert utilities: offset parsing, fire time computation (start/end),
  effective alerts resolution with useDefaultAlerts, pending alert filtering
- Zustand persist store for acknowledged alert deduplication (24h retention)
- Global hook with 60s interval check, proactive 24h event fetch via ref
  (isolated from calendar store), stale closure protection
- Settings: calendarNotificationsEnabled, calendarNotificationSound toggles
- Toast icon customization, CalendarAlertProvider in layout
- i18n: all 8 locales (EN/FR/JA/ES/IT/DE/NL/PT)
- 44 tests (36 alert utils + 8 notification store), 558 total passing
2026-02-16 23:46:12 +01:00
Matthieu MALVACHE 9be02e8dd7 feat(filters): add email filters with Sieve rules, review fixes and hardening
Implement JMAP Sieve Scripts (RFC 9661) with visual rule builder and raw
Sieve editor. Includes post-review fixes: parser validation guards,
generator empty-rule skipping, JMAP client error hardening, focus trap
accessibility, mailbox name fix, toast validation feedback, auto-save with
rollback, and "Reset to visual builder" for opaque scripts. 514 tests pass.
2026-02-16 23:07:06 +01:00
Matthieu MALVACHE b84e543e2e feat(calendar): add drag-and-drop rescheduling and iCalendar import
Calendar phase 2 features with review-driven fixes:
- Drag events in week/day views to reschedule (15-min snap intervals)
- Drag events in month view to change date (preserves time)
- Visual snap indicators with time labels during drag
- iCalendar (.ics) import via JMAP CalendarEvent/parse
- Import modal with file upload, event preview, calendar selector
- File validation (5MB max), bulk import with progress
- i18n: calendar.import.* and event_move_error keys (8 locales)
2026-02-16 21:07:44 +01:00
Matthieu MALVACHE a27196bdeb feat(calendar): add JMAP Calendar integration with full review fixes
Calendar phase 1 with comprehensive review-driven hardening:
- JMAP CalendarEvent CRUD (RFC 8984 types, capability detection)
- Month/week/day/agenda views with multi-day event spanning
- Column-based overlap layout for concurrent events
- Event modal with create/edit/delete, recurrence, reminders
- Mini-calendar sidebar with calendar visibility toggles
- Settings: firstDayOfWeek and timeFormat wired to all views
- Locale-aware date formatting via next-intl useFormatter (8 locales)
- Push notification handling for Calendar/CalendarEvent state changes
- ARIA grid roles, event card labels, focus trap, 44px touch targets
- Input validation, color sanitization, timezone auto-detection
- Error handling with toast feedback, capability page guard
- ICU pluralization for alert translations, debug logger integration
2026-02-16 20:32:06 +01:00
Matthieu MALVACHE 83e01ae551 feat: add contacts phase 2, advanced search, vacation responder, Docker & TOTP 2FA
Contacts:
- Contact groups/lists with JMAP members map and composer expansion
- vCard import/export with RFC 6350 parser and duplicate detection
- Bulk operations (multi-select, delete, group add, export)

Search:
- Advanced search panel with JMAP filter fields
- Search chips for active filters visualization
- Debounced inputs with AbortController deduplication

Vacation:
- JMAP VacationResponse singleton management
- Settings tab with date range and message configuration
- Sidebar indicator when vacation responder is active

Auth:
- TOTP 2FA support with Stalwart-compatible password$totp format

Infrastructure:
- Docker multi-stage build with standalone Next.js output
- Structured server-side logger with text/JSON format
- CSP Report-Only and security headers via proxy middleware
- Layout refactoring (HTML structure in root layout)
- Playwright E2E framework setup

Testing: 450+ tests (identity, contacts, vCard, threads, headers, components)
i18n: All new strings added to all 8 locales
2026-02-16 18:46:33 +01:00
Matthieu MALVACHE 3796119da5 docs: update TODO with email layout fix and dependency upgrade notes 2026-02-16 17:23:25 +01:00
Matthieu MALVACHE 416b0e985e chore(deps): upgrade @types/node 22→25, jsdom 27→28, lucide-react 0.564
Major version bumps for dev dependencies. eslint 10 skipped — ecosystem
plugins (typescript-eslint, eslint-config-next) don't support it yet.
2026-02-16 17:21:59 +01:00
Matthieu MALVACHE 4499c83a37 chore(deps): update all packages to latest compatible versions
Update next 16.1.5→16.1.6, react 19.2.3→19.2.4, next-intl 4.7→4.8.3,
zustand 5.0.9→5.0.11, lucide-react 0.562→0.564, vitest 4.0.16→4.0.18,
typescript-eslint 8.50→8.55, and other minor/patch dependencies.
2026-02-16 17:20:39 +01:00
Matthieu MALVACHE dddbcf75db fix(email): resolve layout overflow and blocked image empty spaces
Remove width: max-content and display: inline-block from email content
CSS that caused horizontal scroll and left-side text clipping. Collapse
empty table cells/containers when external images are blocked to prevent
large blank areas in newsletter emails.
2026-02-16 17:18:38 +01:00
Matthieu MALVACHE 17abe7ff56 feat(contacts): add address book with JMAP sync and local fallback
Implement Phase 1 of contacts support — the biggest missing feature
on the roadmap. Uses RFC 9553 (JSContact) data model and RFC 9610
(JMAP for Contacts) when the server advertises the capability,
falling back to localStorage for servers without contact support.

- JMAP types for ContactCard, AddressBook, NameComponent, etc.
- JMAP client methods: CRUD, search, address book listing
- Refactored request() to accept dynamic `using` capabilities
- Zustand contact store with dual-mode persistence
- Full contacts page with two-column layout (list + detail/edit)
- Contact form with multi-email/phone, context labels, validation
- Composer autocomplete on To/Cc/Bcc with keyboard nav and ARIA
- Sidebar navigation link to contacts
- Auth integration: fetch on login, clear on logout
- i18n: contacts.* namespace across all 8 languages
2026-02-16 17:07:45 +01:00
Matthieu MALVACHE b18906b8fa fix(security): upgrade Next.js to 16.1.5 to patch 3 CVEs
Fixes CVE-2026-23864 (HIGH - RSC deserialization DoS),
CVE-2025-59471 (Image Optimizer DoS), and
CVE-2025-59472 (PPR resume endpoint memory exhaustion).
2026-02-16 16:15:21 +01:00
Matthieu MALVACHE 3fc8ccf116 feat(i18n): expand language support from 3 to 8 languages
Add support for Spanish, Italian, German, Dutch, and Portuguese with complete translations for all features including identity management, newsletter unsubscribe, and accessibility improvements.

Changes:
- Add 5 new locale directories with full translations
- Update IntlProvider to load all 8 language message files
- Redesign language switcher from button group to dropdown for better scalability
- Update routing and request configuration to handle new locales
- Improve Select component with dir="auto" for RTL support
- Reorganize notification keys in translation files for consistency
- Update all documentation to reflect expanded language support
2026-01-08 22:12:13 +01:00
Matthieu MALVACHE cfcf0083f6 feat(i18n): add Japanese language support with complete translations
Add Japanese (ja) as the third supported language alongside English and French. Includes 650+ translation keys covering all UI sections: login, sidebar, email operations, settings (all 8 tabs), context menus, keyboard shortcuts, threads, identity management, and error messages.

Uses formal Japanese (敬語 keigo) appropriate for business email communication. All client and server-side i18n infrastructure updated to load Japanese translations.
2026-01-08 21:40:13 +01:00
Matthieu MALVACHE b0946f5e3f chore: add git hook to prevent AI attribution in commits
Added commit-msg hook that blocks commits containing Claude/Anthropic
references. Hook is excluded from version control via .gitignore.
2026-01-08 20:39:50 +01:00
Matthieu MALVACHE a98b48e7b4 chore: upgrade dependencies to latest stable versions
Remove unused next-auth dependency (zero imports in codebase).
Update minor/patch versions for security fixes and bug improvements:
- dompurify 3.2.7 → 3.3.1 (XSS protection improvements)
- tailwind-merge 3.3.1 → 3.4.0 (bug fixes)
- TypeScript to 5.9.3 (performance and type inference improvements)
- globals 16.5.0 → 17.0.0 (ESLint global definitions)
- @types packages to latest for React 19 compatibility

All tests passing (124/124), no breaking changes.
2026-01-08 19:21:43 +01:00