Commit Graph
149 Commits
Author SHA1 Message Date
Matthieu MALVACHE 05cdb6311a chore: bump version to 1.3.0 2026-03-16 16:30:46 +01:00
Matthieu MALVACHE 3bc2bc0ad8 docs: update ARCHITECTURE.md with v1.2.0 features 2026-03-16 16:24:48 +01:00
Matthieu MALVACHE 77c1133f06 fix: improve mobile/tablet layout transitions and touch interactions
- Remove isMobile/isTablet JS state from layout classes to eliminate
  orientation-change blink (CSS breakpoints apply instantly)
- Extract dismissViewer() helper for return-to-list pattern (was duplicated 4x)
- Convert more-actions dropdown from hover-only to click-toggle (works on touch)
- Add Escape key and role attributes to more-actions menu
- Reset showMoreActions on email change to prevent stale menu
- Move overflow-x:auto to iframe inner body stylesheet (was inert on iframe element)
- Reset tabletListVisible when crossing to desktop breakpoint
2026-03-16 16:14:40 +01:00
Matthieu MALVACHE 0652700256 fix: remove unused error variable in empty folder catch 2026-03-16 15:41:14 +01:00
Matthieu MALVACHE 27911dfe76 feat: add UX & robustness improvements (iframe rendering, retry, mobile, polish)
P0 Core:
- Sandboxed iframe rendering for rich HTML emails (CSS isolation)
- API retry with exponential backoff for transient JMAP failures
- Mobile bottom action bar with touch-friendly email actions
- Long-press context menu + tap-to-expand submenus on touch devices

P1 Polish:
- Tag counts sidebar section with JMAP batch queries
- Empty folder option for Junk/Trash with batch delete
- Extra-compact density option (28px rows, 44px on touch)
- SPF/DKIM/DMARC security tooltips with plain-language explanations
- Resizable sidebars with drag, touch, and keyboard support
- Expandable sender info panel in email viewer

All 8 locales updated (en/fr/ja/es/it/de/nl/pt).
2026-03-16 15:40:24 +01:00
Matthieu MALVACHE 3733a704b7 chore: bump version to 1.2.0 2026-03-16 13:07:46 +01:00
Matthieu MALVACHE 512c97f656 feat: add OAUTH_ONLY mode to hide basic auth form on login page
New OAUTH_ONLY env var hides username/password fields and promotes the
SSO button as the primary login method. Useful for deployments that
want to enforce OAuth-only authentication.

Includes retry button when OAuth discovery fails to avoid dead-end
login pages.

Closes #32
2026-03-16 13:07:08 +01:00
Matthieu MALVACHE 96ddac27da chore: bump version to 1.1.4 2026-03-16 12:58:13 +01:00
Matthieu MALVACHE 2de80badc1 fix: show mobile nav in viewer mode and add folder hierarchy to move dialog
Mobile bottom navigation bar was hidden when viewing an email, trapping
users in the mail section. Now always visible on mobile.

Move-to-folder submenu now shows hierarchical folder structure using
buildMailboxTree/flattenMailboxTree instead of a flat list.

Closes #30, closes #29
2026-03-16 12:58:04 +01:00
Matthieu MALVACHE 17b0df4c2f chore: bump version to 1.1.3 2026-03-16 12:41:37 +01:00
Matthieu MALVACHE cc89763035 chore: update dependencies to fix security vulnerabilities
dompurify 3.3.1 -> 3.3.3 (XSS fix)
undici 7.22.0 -> 7.24.4 (WebSocket crash, CRLF injection, smuggling)
flatted 3.3.3 -> 3.4.1 (DoS via unbounded recursion)
2026-03-16 12:41:26 +01:00
Matthieu MALVACHE 4aaef1ebf1 fix: resolve calendar crash on undefined duration and sieve filter save error
parseDuration() now handles undefined event.duration gracefully instead
of crashing on .match(). CalendarEvent.duration type updated to reflect
that JMAP servers may omit this field.

Sieve filter activation replaced isActive (server-set per RFC 9661) with
onSuccessActivateScript. Create/update calls merged with activation into
single JMAP roundtrips.

Closes #31, closes #21
2026-03-16 12:41:10 +01:00
Matthieu MALVACHE 2790b877c9 chore: bump version to 1.1.2 2026-03-02 09:09:21 +01:00
Matthieu MALVACHE 5623460b82 chore: update patch/minor dependencies
tailwindcss 4.2.1, lucide-react 0.576.0, @tanstack/react-virtual
3.13.19, @typescript-eslint/* 8.56.1, globals 17.4.0, @types/node 25.3.3
2026-03-02 09:08:48 +01:00
Matthieu MALVACHE c787acc34f fix: resolve context menu submenu usability and move-to-folder failures
The context menu's "Move to folder" submenu was practically unusable:
scroll events inside the folder list closed the entire menu, and moving
the mouse from the trigger to the submenu dismissed it instantly.

Additionally, the JMAP moveEmail call silently discarded server errors
and always used the primary account ID, causing moves to appear to
succeed locally while the server rejected them.

Closes root-fr/jmap-webmail#19
2026-03-02 09:08:36 +01:00
Matthieu MALVACHE 2d5b5c09b5 chore: bump version to 1.1.1 2026-02-28 21:01:56 +01:00
Matthieu MALVACHE b4f4be31f6 fix: resolve Edge Runtime warnings in instrumentation
Split instrumentation into two files so the Edge bundler never sees
Node.js-specific imports (fs, process.cwd). The thin entry point
conditionally imports the Node-only module via NEXT_RUNTIME check.
2026-02-28 20:50:19 +01:00
Matthieu MALVACHE 329401b431 fix(security): patch minimatch ReDoS vulnerability (CVE-2026-27903)
Upgrade minimatch 9.0.6→9.0.9 and 3.1.3→3.1.5 to fix high-severity
ReDoS via combinatorial backtracking in matchOne() with multiple
non-adjacent GLOBSTAR segments.
2026-02-28 20:50:09 +01:00
Matthieu MALVACHE 8554b30e5b fix: keep show/hide details toggle in place when expanded
Move the toggle button above the expandable details section so it stays
at the same position regardless of expand/collapse state. Also replace
hardcoded English text with i18n translations.

Closes #18
2026-02-28 20:49:57 +01:00
Matthieu MALVACHE bf3de065b9 chore: bump version to 1.1.0 2026-02-28 14:58:02 +01:00
Matthieu MALVACHE 0a7d266c7c feat: add server-side version update check on startup
Logs current version to server console and checks GitHub for newer
releases in production. Keeps version info out of the client bundle.
2026-02-28 14:57:15 +01:00
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