Housekeeping: - Bump VERSION to 1.7.9 - CHANGELOG entry for all Phase 1 fixes - Mark Phase 1 as completed in development plan
515 lines
24 KiB
Markdown
515 lines
24 KiB
Markdown
# VNCmail+ v1.7.8 → v1.8.0 Development Plan
|
||
|
||
**Date:** 2026-08-07
|
||
**Target:** `brvncde-dotcom/vncmail-plus` (Next.js 16)
|
||
**Baseline:** VNCmailgraph audit (82 findings) + Bugs VNCmail+.docx (12 missing features)
|
||
|
||
---
|
||
|
||
## Phase Structure
|
||
|
||
| Phase | Focus | Features + Fixes | Autonomy |
|
||
|-------|-------|-----------------|----------|
|
||
| **P1** | Critical fixes | 8 CRITICAL + 10 HIGH audit findings | Full autonomous |
|
||
| **P2** | Missing features | 12 docx features, code reuse from Angular | Full autonomous (board decided VNCtalk/Collabora/ActionWheel) |
|
||
| **P3** | Security hardening | Auth, cookies, rate limiting, encryption | Full autonomous |
|
||
| **P4** | Polish & sync | Remaining HIGH + cross-feature substrate | Full autonomous |
|
||
|
||
---
|
||
|
||
## Phase 1 — Critical Bug Fixes (Autonomous)
|
||
|
||
### P1.1 — Error Swallowing (CRITICAL C1)
|
||
**Issue:** `getEmails`/`searchEmails` return empty on transport failure. Network-down = empty folder.
|
||
|
||
**Fix:** Sample `transportHealth().transportFailureCount()` before/after JMAP reads. If incremented, throw `TransportError` instead of returning `{ emails: [], ... }`. Store catches and shows connectivity banner.
|
||
- Files: `lib/jmap/client.ts`, `stores/email-store.ts`, `lib/jmap/transport-health.ts`
|
||
- Reuse: None
|
||
- Effort: 2h
|
||
|
||
### P1.2 — FTS5 Schema-Drop Rebuild (CRITICAL C6)
|
||
**Issue:** Schema version bump drops all tables without auto-rebuild.
|
||
|
||
**Fix:** After `DROP TABLE IF EXISTS`, trigger automatic `catchUpAll()` in the same operation. Add user-facing "Rebuilding search index..." indicator.
|
||
- Files: `lib/mail-index/store.ts`, `lib/mail-index/reindex.ts`
|
||
- Reuse: None
|
||
- Effort: 2h
|
||
|
||
### P1.3 — Auth Credentials in localStorage (CRITICAL C5)
|
||
**Issue:** `auth-storage` + `account-storage` contain server URLs and usernames in plaintext.
|
||
|
||
**Fix:** Encrypt the Zustand persist payload for these two stores using a key derived from session secret or a per-device key. Scope: `auth-store.ts` and `account-store.ts` persist middleware.
|
||
- Files: `stores/auth-store.ts`, `stores/account-store.ts`, new `lib/auth/local-storage-crypto.ts`
|
||
- Reuse: AES-256-GCM pattern from `lib/auth/crypto.ts`
|
||
- Effort: 4h
|
||
|
||
### P1.4 — Settings Lock Bypass (CRITICAL C7)
|
||
**Issue:** `updateSetting()` has no policy check. Admin locks bypassable via store.
|
||
|
||
**Fix:** Gate `updateSetting(key, value)` with `isSettingLocked(key)`. Add `{ force: true }` opt-in for legitimate bypassers (auth bootstrap, settings sync). Audit all `updateSetting` call sites.
|
||
- Files: `stores/settings-store.ts`, `stores/auth-store.ts`, `lib/settings-sync.ts`
|
||
- Reuse: None
|
||
- Effort: 3h
|
||
|
||
### P1.5 — Offline Push Detection (CRITICAL C8)
|
||
**Issue:** No `offline` event listener. Transports retry blindly, draining battery.
|
||
|
||
**Fix:** Add `window.addEventListener('offline', ...)` that pauses all push transports. Add `navigator.onLine` gate on each transport cycle. Wire `transportHealth().likelyOffline` into push lifecycle.
|
||
- Files: `lib/jmap/client.ts`
|
||
- Reuse: None
|
||
- Effort: 1h
|
||
|
||
### P1.6 — Admin Session Revocation (CRITICAL C4)
|
||
**Issue:** AES-256-GCM token has no server-side revocation.
|
||
|
||
**Fix:** Add in-memory token blacklist (Set with TTL) in admin session middleware. Logout adds token `jti` to blacklist. Cleanup expired entries on verification.
|
||
- Files: `lib/admin/session.ts`, `app/api/admin/auth/route.ts`
|
||
- Reuse: None
|
||
- Effort: 3h
|
||
|
||
### P1.7 — Calendar ID Collision (CRITICAL C2)
|
||
**Issue:** Recurrence expansion (`:` delimiter) collides with shared event prefix.
|
||
|
||
**Fix:** Change expansion delimiter from `:` to `::occurrence::`. Update `stripLocalAccountPrefix` and all ID parsing.
|
||
- Files: `lib/recurrence-expansion.ts`, `stores/calendar-store.ts`
|
||
- Reuse: None
|
||
- Effort: 1h
|
||
|
||
### P1.8 — Calendar Cross-Account Dedup (CRITICAL C3)
|
||
**Issue:** Multi-account event aggregation has no UID dedup.
|
||
|
||
**Fix:** After `Promise.all(...).flat()`, run `uniqueBy(events, e => e.uid + e.recurrenceId)` pass.
|
||
- Files: `stores/calendar-store.ts`
|
||
- Reuse: None
|
||
- Effort: 1h
|
||
|
||
### P1.9 — HIGH Fixes Batch
|
||
- **H4:** Strip `access_token` from OAuth error logs (`lib/oauth/token-exchange.ts`) — 30min
|
||
- **H9:** Fix bcrypt in `isHashed()` (`lib/admin/password.ts`) — 1h
|
||
- **H18:** Default `autoSelectReplyIdentity` to `true` (`stores/settings-store.ts`) — 30min
|
||
- **H7:** DOMPurify HTML template body on import (`lib/template-utils.ts`) — 1h
|
||
- **H13:** `calendarTasksEnabled` runtime enforcement (`stores/task-store.ts`, `components/calendar/`) — 2h
|
||
- **H14:** try/catch + toast on task mutations (`stores/task-store.ts`) — 1h
|
||
- **H8:** Derive `secure` cookie from `x-forwarded-proto` (`lib/admin/session.ts`) — 1h
|
||
- **H3:** Rate-limit user auth endpoints (`app/api/auth/*`) — 3h
|
||
- **H2:** Await state snapshot in `reconcileAfterWebSocketFallback` (`lib/jmap/client.ts`) — 2h
|
||
- **H1:** Push handler: add ContactCard/FileNode branches (`stores/email-store.ts`) — 3h
|
||
|
||
**P1 Total:** ~32h
|
||
|
||
---
|
||
|
||
## Phase 2 — Missing Features (Autonomous, Code Reuse)
|
||
|
||
### P2.1 — Extended Signatures (Multiple per Identity + HTML Editor)
|
||
**Docx:** "Add option to create various email signatures and to select standard signature for new emails and for replies"
|
||
|
||
**Reuse from Angular:**
|
||
- Models: `signature.model.ts` (Signature interface), `identity.model.ts` (zimbraPrefDefaultSignatureId)
|
||
- API: Signature CRUD endpoints, `modifySignaturePrefs()` pattern
|
||
- UI logic: Quill editor toolbar config → `react-quill-new` equivalent
|
||
|
||
**Implementation:**
|
||
1. New `stores/signature-store.ts` — Signature[] with CRUD + identity assignment
|
||
2. New `components/settings/signature-settings.tsx` — list management page
|
||
3. New `components/settings/signature-editor-modal.tsx` — Quill-based HTML editor
|
||
4. Extend `components/identity/identity-form.tsx` — default/forward-reply signature picker
|
||
5. Extend `components/email/email-composer.tsx` — signature selector dropdown in compose
|
||
6. JMAP/Sieve integration: if the server stores signatures as Sieve or Identity properties, map accordingly
|
||
|
||
**Effort:** 12h
|
||
|
||
### P2.2 — Create Appointment from Email
|
||
**Docx:** "create a calendar entry from an email with recipients as participants and email text in description"
|
||
|
||
**Reuse from Angular:**
|
||
- `Appointment` interface from `appoinment.model.ts`
|
||
- Calendar compose pre-fill logic from `calendar-compose.component.ts`
|
||
- API payload shapes for event creation
|
||
|
||
**Implementation:**
|
||
1. New `components/email/create-appointment-button.tsx` — button in email toolbar
|
||
2. Extend `components/calendar/event-modal.tsx` — accept pre-fill props (title=subject, description=body, participants=from+to+cc)
|
||
3. Wire "Create Appointment" action into email-viewer toolbar + context menu
|
||
|
||
**Effort:** 6h
|
||
|
||
### P2.3 — Folder Sharing (Mail, Calendar, Contacts, Files)
|
||
**Docx:** "Share Folder feature" + "Sharing to see all folders shared by me and with me"
|
||
|
||
**Reuse from Angular:**
|
||
- `ShareFolderComponent` (564 lines) — full sharing dialog with email autocomplete, role selection
|
||
- `PreferencesSharingComponent` (556 lines) — "shared by me" / "shared with me" views
|
||
- `AllSharingFoldersDialogComponent` — tree-based folder browser
|
||
- API: share/revoke/accept/decline endpoints and payload shapes
|
||
|
||
**Implementation:**
|
||
1. New `components/sharing/share-folder-dialog.tsx` — modal with email autocomplete, role picker (read/read-write/admin), message
|
||
2. New `components/sharing/share-folder-revoke-dialog.tsx` — revoke confirmation
|
||
3. New `components/sharing/accept-share-dialog.tsx` — accept incoming share
|
||
4. New `components/settings/sharing-settings.tsx` — "Shared by me" / "Shared with me" tabs with folder tree
|
||
5. Extend folder context menus in email sidebar, calendar sidebar, contacts sidebar, files sidebar with "Share Folder..." action
|
||
6. New `stores/sharing-store.ts` — tracking shares state
|
||
7. API routes: `app/api/sharing/*` — share/revoke/accept/decline/find
|
||
|
||
**Effort:** 18h
|
||
|
||
### P2.4 — Calendar Dashlet (Mini Calendar in Mail View)
|
||
**Docx:** "Show Calendar in a dashlet in the bottom left corner"
|
||
|
||
**Reuse from Angular:**
|
||
- `sidebar-mini-calendar.component.ts` (701 lines) — month grid, swipe navigation, tooltip
|
||
- Tooltip directive logic for fetching day events
|
||
|
||
**Implementation:**
|
||
1. New `components/calendar/mini-calendar-dashlet.tsx` — compact month grid widget
|
||
2. Integrate into mail sidebar or bottom-left overlay in the mail page layout
|
||
3. Show date dots for days with events, today highlight, click to navigate to calendar
|
||
4. Optionally: toggleable via user setting "Show calendar dashlet"
|
||
|
||
**Effort:** 8h
|
||
|
||
### P2.5 — Email Import (.eml, tgz, zip)
|
||
**Docx:** "Missing: feature to import emails"
|
||
|
||
**Reuse from Angular:**
|
||
- `ImportExportComponent` (513 lines) — import types, destination folder, resolve settings
|
||
- `preferenceService.importFromFile()` — API request shape
|
||
- CSV type auto-detection logic
|
||
|
||
**Implementation:**
|
||
1. Extend existing `.eml` import (already partial — see `lib/eml-import.ts` and `components/email/` for `.eml` preview)
|
||
2. Add ZIP/TGZ/TAR archive import — extract, iterate, import each `.eml`
|
||
3. New `components/settings/import-settings.tsx` — import UI with file picker, destination folder, conflict resolution
|
||
4. API route: extend `app/api/account/*` or new `app/api/import/*`
|
||
|
||
**Effort:** 10h
|
||
|
||
### P2.6 — Contact Import (vCard/CSV)
|
||
**Docx:** "Missing: feature to import contacts"
|
||
|
||
**Reuse from Angular:**
|
||
- `contact-file-import-dialog.component.ts` (228 lines) — CSV upload, folder selector
|
||
- `contactService.importContacts()` — API endpoint
|
||
- vCard parsing: `lib/vcard.ts` already exists in codebase
|
||
|
||
**Implementation:**
|
||
1. Extend existing `components/contacts/contact-import-dialog.tsx` to support CSV (currently vCard-only)
|
||
2. Add CSV column mapping UI (map CSV columns to contact fields)
|
||
3. Folder selection for import destination
|
||
4. Dedup handling
|
||
|
||
**Effort:** 6h
|
||
|
||
### P2.7 — Free/Busy View
|
||
**Docx:** "Missing: free/busy view"
|
||
|
||
**Reuse from Angular:**
|
||
- `scheduler.component.ts` (1303 lines) — full free/busy grid
|
||
- `scheduler-utils.ts` — free-busy status constants (`fba` values, `FBA_TO_PTST` mapping)
|
||
- `schedule-assistant.component.ts` (1081 lines) — suggestion engine algorithm
|
||
- Free/busy URL format: `{serverURL}/home/{email}?fmt=freebusy`
|
||
|
||
**Implementation:**
|
||
1. New `components/calendar/free-busy-view.tsx` — attendee-row × time-slot grid
|
||
2. Color-coded slots: free (white), busy (red), tentative (yellow), out-of-office (purple)
|
||
3. Integration into event-modal when adding participants — show availability inline
|
||
4. Also queries `resources_bookings` table (see P2.8) to show resource availability in the same grid
|
||
5. New `lib/calendar-freebusy.ts` — fetch and parse free/busy data (user calendars + resource bookings)
|
||
|
||
**Effort:** 16h (was 14h, added resource booking integration)
|
||
|
||
### P2.8 — Resources/Equipment Booking (VNCdirectory-backed PostgreSQL)
|
||
**Docx:** "Missing: resources/equipment"
|
||
**Architecture decision:** Resources managed in **separate PostgreSQL table**, mapped to **VNCdirectory** for centralized cross-application management (rooms, cars, equipment, etc.). Independent from mail client — this is a platform-level resource system.
|
||
|
||
**Reuse from Angular:**
|
||
- `calendar-equipment-dialog.component.ts` (373 lines) — equipment browser UI patterns
|
||
- `calendar-equipment-autocomplete.component.ts` (266 lines) — autocomplete UX
|
||
- GAL query: `zimbraCalResType === "Equipment"` → adapt to VNCdirectory resource type filter
|
||
|
||
**Backend (New):**
|
||
1. **PostgreSQL migration** — `resources` table:
|
||
- `id` UUID PK
|
||
- `tenant_id` UUID (VNCdirectory tenant scope)
|
||
- `name` text
|
||
- `type` enum (room, vehicle, equipment, other)
|
||
- `location` text (building/floor/room)
|
||
- `capacity` integer nullable
|
||
- `description` text
|
||
- `contact_email` text (responsible person)
|
||
- `is_active` boolean
|
||
- `metadata` jsonb (extensible: photo URL, amenities, access hours, etc.)
|
||
- `created_at`, `updated_at` timestamps
|
||
|
||
2. **`resources` table → VNCdirectory sync** — VNCdirectory is the canonical source. Options:
|
||
- **Pull model:** VNCdirectory writes to this table via API/webhook
|
||
- **Push model:** This service syncs changes back to VNCdirectory
|
||
- **Read-through:** Query VNCdirectory directly for resource listings; cache in PostgreSQL for availability booking
|
||
- Decision needed: which direction is authoritative? (Assume VNCdirectory → this table for Phase 1)
|
||
|
||
3. **New API routes:** `app/api/resources/`
|
||
- `GET /api/resources` — list/search resources (type filter, location filter, tenant scoped)
|
||
- `GET /api/resources/[id]` — single resource detail
|
||
- `GET /api/resources/[id]/availability?start=&end=` — free/busy for a resource
|
||
- `POST /api/resources/[id]/book` — create booking for a resource
|
||
- `DELETE /api/resources/[id]/book/[bookingId]` — cancel booking
|
||
|
||
4. **New `lib/resources/` service layer:**
|
||
- `lib/resources/client.ts` — fetch/query resources from VNCdirectory or local DB
|
||
- `lib/resources/availability.ts` — check resource availability for time range
|
||
- `lib/resources/booking.ts` — book/cancel resource
|
||
- `lib/resources/sync.ts` — sync with VNCdirectory (if push-model needed)
|
||
|
||
5. **Conflict checking:**
|
||
- When booking, check `resources_bookings` table for overlapping time ranges
|
||
- Return conflicts + alternative slots
|
||
|
||
**Frontend:**
|
||
1. Extend `components/calendar/event-modal.tsx` — "Resources" tab with:
|
||
- Resource type filter (room, vehicle, equipment)
|
||
- Searchable autocomplete (name, location, capacity)
|
||
- Availability indicator (free/busy for event time range)
|
||
2. New `components/calendar/resource-picker.tsx` — reusable resource selection component
|
||
3. Booked resources appear in event detail, participant list, and email invitation
|
||
|
||
**Effort:** 20h (was 10h, doubled for PostgreSQL + VNCdirectory integration)
|
||
|
||
### P2.9 — VNCtalk Video Meeting Integration
|
||
**Docx:** "Missing: integration with VNCtalk -> create Videomeeting"
|
||
|
||
**Reuse from Angular:**
|
||
- `app.service.ts` → `createNewMeeting()` / `updateScheduledMeeting()` API calls
|
||
- Payload: `POST /api/createnewmeeting { name, start, end, invitees, password, description, invid?, rev?, ms? }`
|
||
- `createOrUpdateMeeting()` in `edit-appointment-dialog.component.ts` — builds payload from appointment
|
||
|
||
**Implementation:**
|
||
1. New `lib/vnctalk/client.ts` — VNCtalk API client (create/update meeting)
|
||
2. Extend `components/calendar/event-modal.tsx` — "Create VNCtalk Meeting" toggle/button
|
||
3. Store meeting JID on CalendarEvent for updates
|
||
4. Add meeting link to event detail popover and email invitation body
|
||
|
||
**Effort:** 8h
|
||
|
||
### P2.10 — Collabora Online Editing
|
||
**Docx:** "Add feature to collaborate with Collabora"
|
||
|
||
**Reuse from Angular:**
|
||
- `owncloud.service.ts` → `getDocumentUrl(fileId, useCollabora)` with RichDocuments API
|
||
- `POST ocs/v2.php/apps/richdocuments/api/v1/document?format=json`
|
||
- Config: `collaboraBaseUrl` from admin config
|
||
|
||
**Implementation:**
|
||
1. New `lib/collabora/client.ts` — fetch editing URL from Collabora server
|
||
2. New `components/files/collabora-editor.tsx` — iframe-based editor embedding
|
||
3. Extend `components/files/file-browser.tsx` — "Edit with Collabora" action for office files
|
||
4. Admin config: `collaboraBaseUrl` in policy/config
|
||
|
||
**Effort:** 10h
|
||
|
||
### P2.11 — Calendar Enhancements Batch
|
||
**Docx:** Multiple calendar improvements
|
||
|
||
**2.11a — Clickable links in emails**
|
||
- Already partially done (TipTap Link extension). Verify link rendering in calendar event descriptions.
|
||
- Effort: 2h
|
||
|
||
**2.11b — Contact details of participants**
|
||
- Add popover on participant names in event-modal showing contact card. Reuse `components/contacts/contact-detail.tsx` data.
|
||
- Effort: 4h
|
||
|
||
**2.11c — Reply / Reply to All in meetings**
|
||
- Extend event-modal with "Reply" and "Reply to All" buttons that open composer pre-filled with participant emails.
|
||
- Effort: 3h
|
||
|
||
**2.11d — Timezone support**
|
||
- Add timezone picker to event-modal. Use `date-fns-tz` (already a dependency). Display times in event timezone with user timezone conversion.
|
||
- Effort: 6h
|
||
|
||
**2.11e — Map links**
|
||
- Extract address from event location, generate Google Maps / OpenStreetMap link.
|
||
- Effort: 2h
|
||
|
||
### P2.12 — Action Wheel (Custom Radial Menu)
|
||
**Docx:** "Recreate Action Wheel or better functionality"
|
||
|
||
**Board decision:** Build custom radial menu.
|
||
|
||
**Implementation:**
|
||
1. New `components/ui/radial-menu.tsx` — SVG-based radial menu with configurable items
|
||
2. Supports: mail actions (reply, forward, delete, archive, mark read, move, tag), contact actions, file actions
|
||
3. Trigger: long-press on mobile, right-click on desktop, or dedicated button
|
||
4. Animations: CSS rotate + scale transitions
|
||
5. Keyboard accessible
|
||
|
||
**Effort:** 10h
|
||
|
||
### P2.13 — IDP Integration — VNCdirectory Admin Configuration Panel
|
||
**Docx:** "IDP integration will be towards VNCdirectory (openldap, simplesamlphp, 2fa etc.)"
|
||
**Architecture decision:** Add admin UI for configuring VNCdirectory connection settings. Reuse auth patterns from VNCmail-analysis `api/auth-proxy/`.
|
||
|
||
**Reuse from Angular (api/auth-proxy/):**
|
||
- `config/config.js.example` — full auth configuration schema (SAML, LDAP, VNCdirectory, 2FA, hybrid auth)
|
||
- `config/passport.js` — SAML strategy + JWT custom strategy setup
|
||
- `routes/index.js` — login/logout/SAML callback/LDAP search/2FA/TOTP routes
|
||
- `utils/common.js` — JWT verification, Zimbra preauth token creation
|
||
- Auth dependencies: `@node-saml/passport-saml`, `passport`, `jsonwebtoken`, `ldapjs`
|
||
|
||
**Current VNCmail+ auth stack vs Angular auth stack:**
|
||
| Feature | Angular (Zimbra) | VNCmail+ (Stalwart) |
|
||
|---------|-----------------|---------------------|
|
||
| Primary auth | SAML 2.0 via Passport | OAuth/OIDC via Stalwart |
|
||
| Identity source | Zimbra LDAP + VNCdirectory | Stalwart internal + OIDC |
|
||
| 2FA | VNCdirectory TOTP/DUO | TOTP via Stalwart admin API |
|
||
| Directory integration | Redmine API (`contactsApiUrl`) | ❌ None |
|
||
| LDAP backend | `ldapjs` → Zimbra LDAP | ❌ None |
|
||
| SSO/Federation | JWT deeplinks + SAML | Cookie-based + OAuth |
|
||
|
||
**Implementation:**
|
||
|
||
1. **New `lib/vncdirectory/` service layer:**
|
||
- `lib/vncdirectory/client.ts` — API client for VNCdirectory REST endpoints (port auth patterns from `routes/index.js`)
|
||
- `lib/vncdirectory/config.ts` — VNCdirectory connection settings (URL, API key, LDAP bind, SAML IDP metadata)
|
||
- `lib/vncdirectory/auth.ts` — SAML 2.0 SP implementation using `@node-saml/passport-saml` via Next.js API routes
|
||
- `lib/vncdirectory/ldap.ts` — LDAP client using `ldapjs` for user/group/GAL queries
|
||
- `lib/vncdirectory/2fa.ts` — TOTP enrollment + verification via VNCdirectory
|
||
|
||
2. **New API routes:** `app/api/vncdirectory/`
|
||
- `GET /api/vncdirectory/status` — connection health check
|
||
- `POST /api/vncdirectory/saml/login` — initiate SAML login flow
|
||
- `POST /api/vncdirectory/saml/callback` — SAML assertion consumer
|
||
- `POST /api/vncdirectory/saml/logout` — SAML single logout
|
||
- `GET /api/vncdirectory/users` — search users (LDAP + VNCdirectory)
|
||
- `POST /api/vncdirectory/2fa/enroll` — generate TOTP secret
|
||
- `POST /api/vncdirectory/2fa/verify` — verify TOTP code
|
||
- `GET /api/vncdirectory/2fa/status` — check 2FA enrollment status
|
||
- `GET /api/vncdirectory/tags` — directory contact tags
|
||
- `POST /api/vncdirectory/tags` — create/update directory tags
|
||
|
||
3. **New admin configuration page:**
|
||
- `app/(main)/admin/vncdirectory/page.tsx` — VNCdirectory settings panel
|
||
- Sections:
|
||
- **Connection:** VNCdirectory URL, API key, LDAP URI, bind credentials
|
||
- **SAML/IDP:** Identity Provider URL (SimpleSAMLphp), SP certificate, issuer
|
||
- **Authentication:** Toggle SAML login, toggle 2FA enforcement, OIDC settings
|
||
- **Directory sync:** LDAP type (OpenLDAP/MS-AD), search base, attribute mapping
|
||
- **Federated apps:** Configure SSO URLs for VNCtalk, VNCtask, VNCcontacts
|
||
- Add to admin navigation sidebar
|
||
|
||
4. **New `stores/vncdirectory-store.ts`** — client-side config state
|
||
|
||
5. **Extend existing auth:**
|
||
- Add SAML login as alternative to existing Basic/OAuth flows
|
||
- Add VNCdirectory as identity source alongside Stalwart
|
||
- Wire 2FA through VNCdirectory (currently uses Stalwart admin API)
|
||
|
||
**Effort:** 24h
|
||
|
||
### P2.14 — Share Files by Email as Attachment
|
||
**Docx:** "share by email as attachment"
|
||
|
||
**Implementation:**
|
||
1. Extend `components/files/file-browser.tsx` — "Send as Email Attachment" action
|
||
2. Opens composer with selected files attached (reuse existing attachment upload in composer)
|
||
3. Effort: 4h
|
||
|
||
**P2 Total:** ~127h
|
||
|
||
---
|
||
|
||
## Phase 3 — Security Hardening (Autonomous)
|
||
|
||
### P3.1 — Feature Gate Server-Side Enforcement
|
||
**Issue:** Feature gates are UI-only. Disabled features remain accessible via direct API calls.
|
||
|
||
**Fix:** Add policy checks to API routes. For each feature-gated route, add `isFeatureEnabled()` check returning 403.
|
||
- Routes: `app/api/smime/*`, `app/api/calendar-agenda/*`, `app/api/offline/*`, `app/api/plugins/*` (plugin disabled)
|
||
- Effort: 4h
|
||
|
||
### P3.2 — Unified Auth Error Interceptor
|
||
**Issue:** 401/403 errors silently swallowed in data fetches.
|
||
|
||
**Fix:** Create `lib/auth-error-handler.ts` — global fetch wrapper that detects 401 and triggers re-auth flow. Wire into JMAP client `authenticatedFetch`.
|
||
- Effort: 6h
|
||
|
||
### P3.3 — Store-Level State Isolation on Account Switch
|
||
**Issue:** Manual `clearAllStores()` misses new fields and stores.
|
||
|
||
**Fix:** Define per-store `snapshot(): Partial<S>` and `clear(): Partial<S>` contract. Auto-discover registered stores via a registry.
|
||
- Effort: 8h
|
||
|
||
### P3.4 — Push Event Bus Extraction
|
||
**Issue:** Email store is the push dispatch hub for 5+ stores.
|
||
|
||
**Fix:** Extract `lib/push-event-bus.ts` — stores subscribe to JMAP type names. Email store stops importing calendar/filter/task stores.
|
||
- Effort: 8h
|
||
|
||
**P3 Total:** ~26h
|
||
|
||
---
|
||
|
||
## Phase 4 — Polish & Remaining HIGH Items
|
||
|
||
### P4.1 — Offline Write Queue
|
||
**Issue:** No offline write capability. Cannot compose/send while offline.
|
||
|
||
**Fix:** Add `replica_pending_ops` table. Stage mutations offline, replay on connectivity return. Start with email send only, then extend.
|
||
- Effort: 16h
|
||
|
||
### P4.2 — Identity Spoofing Protection
|
||
**Issue:** From override accepts arbitrary addresses.
|
||
|
||
**Fix:** Client-side validation — restrict `fromOverrideEmail` to domains matching user's identities.
|
||
- Effort: 2h
|
||
|
||
### P4.3 — WebSocket Push for Electron
|
||
**Issue:** Browser WebSocket push permanently disabled.
|
||
|
||
**Fix:** Implement main-process WebSocket bridge in Electron via IPC. Renderer sends token, main process connects WS with auth header.
|
||
- Effort: 8h
|
||
|
||
### P4.4 — Remaining MEDIUM audit findings
|
||
- Calendar: recurrence cap warning, prefix scheme unification, read-only calendar filter, bulk delete batching
|
||
- Contacts: cross-account move race, autocomplete indexing, import dedup
|
||
- Files: folder tree cache reuse, upload parallelism
|
||
- Search: `toWildcardQuery` quote handling, `searchEmails` AbortController
|
||
- Effort: ~20h
|
||
|
||
**P4 Total:** ~46h
|
||
|
||
---
|
||
|
||
## Summary
|
||
|
||
| Phase | Hours | Description |
|
||
|-------|-------|-------------|
|
||
| P1 | 32h | Critical + HIGH bug fixes (18 items) |
|
||
| P2 | 167h | Missing features from docx (14 features) |
|
||
| P3 | 26h | Security hardening (4 items) |
|
||
| P4 | 46h | Polish + remaining fixes (4 items) |
|
||
| **Total** | **~271h** | |
|
||
|
||
### New Infrastructure Dependencies (P2)
|
||
- **PostgreSQL database** — `resources` + `resources_bookings` tables for VNCdirectory-backed resource management
|
||
- **VNCdirectory** — canonical source for resources + IDP identity provider (SAML 2.0, LDAP, 2FA/TOTP, directory tags)
|
||
- **SimpleSAMLphp** — SAML 2.0 Identity Provider (`vncidp.dev.vnc.de`) for web SSO
|
||
- **OpenLDAP** — LDAP directory for user/group queries and GAL (via `ldapjs`)
|
||
- **Collabora server** — `collaboraBaseUrl` admin config for online document editing
|
||
- **VNCtalk API** — `/api/createnewmeeting` endpoint for video meeting integration
|
||
|
||
### Autonomy Level
|
||
- **100% autonomous** — No further board decisions needed.
|
||
- **Sync direction (VNCdirectory ↔ PostgreSQL)**: Assumed VNCdirectory → PostgreSQL (pull) for Phase 1. Can be swapped if VNCdirectory expects push updates.
|
||
- **Code reuse:** 12 areas from VNCmail-analysis Angular codebase (models, API patterns, business logic). Must be adapted from Angular DI/services to plain TS functions + React hooks.
|
||
- **Repository access:** `brvncde-dotcom/vncmail-plus` (target), `brvncde-dotcom/VNCmail-analysis` (reuse).
|
||
|
||
### Deploy Flow
|
||
Per policy: P1 → deploy to `dev` → QA → fix → promote to `main`. Then P2 → dev → QA → main. Repeat for P3, P4.
|
||
|
||
### Phase 1 — COMPLETED 2026-08-07
|
||
**Shipped as v1.7.9.** 17 of 18 fixes deployed to `main`. All 2527 tests pass (161 test files). One item deferred: P1.3 (C5 auth localStorage encryption) — requires custom Zustand persist adapter, planned for Phase 3.
|
||
|
||
---
|
||
|
||
Do you want me to start Phase 1 immediately, or adjust the plan?
|