Commit Graph

16 Commits

Author SHA1 Message Date
cesnimda 1e15b84dce fix(search): move pagination clamp to controller (fixes cleanup regression)
Phase 5 re-validation caught a functional regression: the V-10 clamp in
SearchService.SearchAsync also capped CleanupService's internal target resolution
(pageSize 10000 -> 200), silently limiting bulk cleanup-by-query to 200 emails.

The clamp belongs at the user-facing trust boundary, not the shared service: move
MaxPageSize (200) enforcement into SearchController (both the POST body and GET query
paths). Internal callers of ISearchService now request large pages unhindered, while
user requests are still bounded. Adds a regression test proving SearchService returns
a 250-row page uncapped. No security regressions per Phase 5. All 39 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 00:29:45 +02:00
cesnimda e6a0239436 fix(security): systemic IDOR safeguard via EF global query filters
Defense-in-depth tenant isolation: every user-owned entity (Email, Sender,
MailThread, MailDomain, Attachment, Label, SyncState, AnalyticsAggregate,
WidgetLayout, UnsubscribeItem) gets a global query filter restricting reads to the
authenticated user. AppDbContext takes an optional ICurrentUser; CurrentUserId is
Guid.Empty for background workers / design-time, which DISABLES the filter so sync
and tooling (which already scope by an explicit userId) are unaffected. On the HTTP
attack surface a forgotten manual `WHERE UserId ==` can no longer leak another
tenant's rows.

Phase 1 confirmed no active IDOR; this is preventive, and prioritised now because the
upcoming automation engine will add many new queries.

Also: moved the Npgsql-only tsvector FTS mapping out of EmailConfiguration into
AppDbContext.OnModelCreating, guarded by Database.IsRelational() (Ignored otherwise),
so non-relational test providers work — honouring the existing Email.SearchVector
comment. Production (Npgsql) model is unchanged; no migration needed.

Adds 3 cross-user tenant-isolation integration tests. All 38 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 00:20:56 +02:00
cesnimda 626a9f8454 fix(security): Phase 4 edge hardening + SSRF egress guard
Backend security fixes from the Phase 1 register / Phase 2 roadmap (PR1 + V-01):

- V-01 SSRF: new SafeHttpGuard validates outbound unsubscribe URLs (scheme allowlist
  + DNS-resolve-and-block private/loopback/link-local/ULA/metadata ranges), wired into
  UnsubscribeService; the "unsubscribe" HttpClient now disables auto-redirect so a
  validated external URL can't 3xx into an internal target. +33 unit tests.
- V-04: session cookie SecurePolicy=Always in non-dev (SameAsRequest in dev).
- V-06: UseExceptionHandler/ProblemDetails in prod; Cleanup/Unsubscribe no longer
  echo ex.Message to clients (logged server-side, generic message returned).
- V-08: ForwardedHeaders trusted only from configurable KnownNetworks (default private
  ranges) + ForwardLimit, instead of trusting any client. New ForwardedHeaders config.
- V-09: returnUrl validated with Url.IsLocalUrl (no open redirect via OAuth flow).
- V-10: SearchService clamps Page/PageSize (<=200); Analytics clamps take/days.
- V-11: baseline security headers (nosniff, X-Frame-Options DENY, Referrer-Policy,
  COOP) + HSTS in prod.
- V-13: /app/info discloses only devMode to anonymous callers unless dev mode is on.
- V-12: API container runs as non-root 'app' user (keys dir pre-owned).
- V-03: Postgres + API ports bound to 127.0.0.1; POSTGRES_PASSWORD now required (no
  weak default fallback).

API compatibility preserved (clamps not rejections; error-body shape changes only on
failure paths). No DB migrations. Build + all 33 unit tests green. V-15 (MailKit
NU1902) persists across versions and the SMTP path is default-off — tracked, not bumped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 00:15:04 +02:00
cesnimda 64f835f719 feat: AI categorisation fallback, unsubscribe confidence scoring, one-line summaries
Heuristic classifier falls back to AI only when it can't determine a category;
unsubscribe confidence blends method reliability with an AI safety opinion and
surfaces it in the Unsubscribe Manager; emails get an on-demand AI one-line
summary in the detail pane. All AI calls degrade gracefully when AI is disabled
or the provider errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 22:21:10 +02:00
cesnimda 0163165beb feat: notification digests via SMTP
Adds a periodic inbox-summary email per opted-in user. SMTP is
configured via appsettings (Smtp section); the digest is built from
existing analytics (health score, top senders, recommendations) and
sent by a new hourly DigestWorker, mirroring the GmailSyncWorker
pattern. Frequency and send hour are configurable (Digest section).

Backend: IEmailSender (SmtpEmailSender, MailKit), IDigestService,
DigestWorker, new User.DigestEnabled/LastDigestSentUtc fields +
migration, SettingsController for the per-user toggle and a
send-now test endpoint, all scoped to the authenticated UserId.

Frontend: SettingsApi + a bell/no-bell toggle button in the topbar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 22:11:39 +02:00
cesnimda 8c92263cc3 feat: sync status indicator, unsubscribe queue UI, email body preview
SyncStatus widget polls /sync/status and shows a live progress bar in
the topbar while syncing, or last-synced time / error otherwise.

Unsubscribe page reworked into a proper queue: status filter tabs
(All/Pending/Succeeded/Failed), select-all, sorted by volume, status
badges, and a result toast after processing.

GET /email/{id} returns full BodyText; Senders detail view fetches
and renders it instead of just the snippet.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 21:50:59 +02:00
cesnimda 21606c5f91 feat: add 14 new smart folder categories (locale-agnostic)
New EmailCategory enum values: Travel, Subscriptions, Parcels,
Recruitment, Events, SecurityAlerts, Healthcare, Education,
NewsMedia, PropertyUtilities, Charity, Government, CryptoInvesting,
FamilySchool.

HeuristicClassifier: locale-agnostic rules using global brand
domains + TLD patterns (.gov.*, .edu, .ac.*) + English subject
keywords — works for international users without relying on
country-specific domains (e.g. gov.uk).

Priority order ensures SecurityAlerts and Government take
precedence over Finance, and Travel/RideSharing/FoodDelivery
fire before Finance to prevent receipt mis-classification.

AnalyticsService: maps all 14 new slugs to category counts.
client.js: folderToRequest() handles all 14 new slugs.
Layout.jsx: SMART_FOLDERS extended with icons and labels.
widgets.jsx: CAT_SLUG updated for dashboard drillthrough.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 20:44:21 +02:00
cesnimda 95dc835651 feat: expanded smart folder category coverage (Gaming, RideSharing, FoodDelivery, SeasonalSales, Wellness, Shopping)
- Added 6 new EmailCategory enum values
- HeuristicClassifier rewritten with domain and subject-keyword rules:
  - RideSharing: Uber, Lyft, Bolt, Free Now, Grab etc.
  - FoodDelivery: DoorDash, Uber Eats, Deliveroo, Just Eat, Grubhub etc.
  - Gaming: Steam, Epic, EA, Xbox, PlayStation, Nintendo, Blizzard, Riot etc.
  - Shopping: Amazon, eBay, Etsy, ASOS, Zalando, Shein etc.
  - SeasonalSales: subject keywords (sale, % off, promo, black friday etc.)
  - Wellness: Strava, Garmin, Peloton, MyFitnessPal, Headspace, Nike etc. + subject hints
  - Priority ordering prevents ride-share/food receipts misclassifying as Finance
- SidebarCounts maps all new slugs to their EmailCategory
- frontend folderToRequest() and CAT_SLUG map all new categories
- Existing emails will pick up new categories on next sync

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 20:24:07 +02:00
cesnimda 2c9b402b08 feat: inline unsubscribe button on email rows
- ✉✕ button appears on rows where the email has a List-Unsubscribe header
- One-click POST / HTTP GET executed automatically; shows ✓ on success
- mailto: targets open the user mail client (we never auto-send email)
- POST /email/{id}/unsubscribe: detects sender, then processes — UserId scoped
- EmailSummaryDto gains IsStarred, HasListUnsubscribe, SupportsOneClick fields
- Both SearchService and CleanupService updated to populate new DTO fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 20:22:28 +02:00
cesnimda 2f6d6abdd3 feat: quick actions on email rows (read/unread, star, trash)
- EmailRow shared component with hover action buttons: mark read/unread, star/unstar, trash
- Optimistic UI — local state patches immediately on click; row fades and becomes non-interactive during the API call
- Trashed rows disappear from the current folder view via onRemove callback
- Backend: EmailController single-email endpoints (POST /email/{id}/read|unread|star|unstar|trash|untrash)
- Star/Unstar added to CleanupActionType enum and CleanupService (maps to STARRED Gmail label via BatchModifyAsync)
- UserId scope enforced in ResolveTargetsAsync — a user can only act on their own emails
- action buttons use stopPropagation so clicking them does not open Gmail

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 20:18:44 +02:00
cesnimda 20d51b2c15 feat: folder view — clicking sidebar items shows filtered email list
- New FolderView page at /app/folder/:slug renders a paginated email table
- Sidebar links now route to their respective folder views
- Extended SearchRequestDto with IsInInbox, IsStarred, IsTrashed, GmailLabel, Category, MinSizeBytes
- SearchService applies the new filters; GmailLabel does a label-id lookup (SENT/DRAFT/SPAM)
- Frontend folderToRequest() maps every slug to the correct search payload
- Email rows show sender, subject+snippet, attachment icon, size, date; bold for unread
- Pagination controls for large folders

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 19:59:01 +02:00
cesnimda ca02f40841 feat: Smart Folders sidebar, category heatmap, sidebar counts API
- Replace plain activity heatmap with CategoryHeatmapWidget on dashboard
- Add collapsible Smart Folders sidebar with Favorites, Mailbox, Smart Folders sections
- Favorites: customisable pinned shortcuts (default: Inbox, Unread, Large, Old, Read Later); pin/unpin smart folders via buttons; persists to localStorage
- Mailbox section: 11 Gmail mailbox items with icons and live count badges
- Smart Folders section: 10 category folders sorted by count desc
- Live count badges via new GET /api/v1/analytics/sidebar-counts endpoint
- Backend: SidebarCountsDto, GetSidebarCountsAsync with label lookups for SENT/DRAFT/SPAM, size/age filters, EmailCategory mapping
- Sidebar collapses to icon-only; section states persist to localStorage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 19:40:54 +02:00
cesnimda fe5920919f feat: landing page + logo, dev-mode banner + sync cap, non-blocking sync with progress splash 2026-06-30 16:58:35 +02:00
cesnimda dcb939e4f2 deploy: one-command scripts to build+run from deploy/.env 2026-06-30 16:16:16 +02:00
cesnimda 29a4aa2212 scaffold: EF design ref + initial migration 2026-06-30 16:08:26 +02:00
cesnimda f43ef5f945 chore: init project 2026-06-30 15:53:32 +02:00