Files
Inboxintel/docs/specs/feature-privacy-monitor.md
T
cesnimda be6cbf90d7 docs: full specs for automation engine, sender policy, activity log, privacy monitor, UI overhaul
Clean.Email-parity feature build-out plus a Stripe/Notion-style UI rebuild on
Tailwind + shadcn-style primitives. Locks the hybrid-automation, Gmail-only,
light+dark, incremental-rollout decisions and lays out the backend/frontend
build sequence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 23:04:52 +02:00

92 lines
4.1 KiB
Markdown

# Spec: Privacy Monitor (data-breach checking)
Checks the user's email address (and optionally addresses they've corresponded with)
against known data breaches. Spec'd around **Have I Been Pwned (HIBP)**, shipped behind
a config flag that stays **off until an API key is provided** — zero cost until then.
## 1. Configuration
`PrivacyOptions` (new, `Configuration/Options.cs`):
```csharp
public class PrivacyOptions
{
public const string SectionName = "Privacy";
public bool Enabled { get; set; } = false; // master flag
public string Provider { get; set; } = "Hibp"; // "Hibp" | "None"
public string? HibpApiKey { get; set; } // required for Hibp
public int CacheHours { get; set; } = 24; // don't hammer the API
}
```
`appsettings.json` gains a `Privacy` section with `Enabled: false`, empty key.
`IsEnabled => Enabled && Provider == "Hibp" && !string.IsNullOrWhiteSpace(HibpApiKey)`.
## 2. Provider abstraction
```csharp
public record BreachDto(string Name, string Title, string Domain, DateOnly BreachDate,
IReadOnlyList<string> DataClasses, bool IsVerified, string? Description);
public interface IBreachProvider
{
bool IsEnabled { get; }
/// <summary>Breaches for an address; empty list if clean; throws only on hard errors.</summary>
Task<IReadOnlyList<BreachDto>> CheckAsync(string emailAddress, CancellationToken ct = default);
}
```
- `HibpBreachProvider``GET https://haveibeenpwned.com/api/v3/breachedaccount/{account}?truncateResponse=false`,
header `hibp-api-key`, a descriptive `user-agent`. Handle: `404` = no breaches (return
empty), `401` = misconfig (log, treat as disabled), `429` = rate-limited (respect
`Retry-After`, return cached/empty). Uses a named `HttpClient` "hibp" with a sane timeout.
- `NullBreachProvider``IsEnabled => false`, returns empty. Registered when the flag is off
(mirrors the `NullAiProvider` pattern in `DependencyInjection`).
## 3. Service
```csharp
public interface IPrivacyService
{
bool IsEnabled { get; }
/// <summary>Check the signed-in user's own address. Cached per PrivacyOptions.CacheHours.</summary>
Task<PrivacyReportDto> CheckSelfAsync(Guid userId, CancellationToken ct = default);
}
public record PrivacyReportDto(string Address, bool Checked, int BreachCount,
IReadOnlyList<BreachDto> Breaches, DateTimeOffset? LastCheckedUtc);
```
- Resolve the user's address from `User`/identity.
- Cache the last result + timestamp (new `User.LastBreachCheckUtc` + a small
`BreachCheck`/JSON column, or a dedicated table if we later check multiple addresses).
For v1, store on `User`: `LastBreachCheckUtc`, `BreachCountCached`.
- Respect `CacheHours`: return cached unless stale or `force` requested.
## 4. API (`PrivacyController : ApiControllerBase`)
| Method | Route | Purpose |
|--------|-------|---------|
| GET | `/privacy/status` | `{ enabled }` so the UI can hide the feature when off |
| GET | `/privacy/self` | cached report for the signed-in user |
| POST | `/privacy/self/refresh` | force a fresh check (rate-limit aware) |
All scoped to `UserId`. We **only** check the authenticated user's own address in v1 —
never arbitrary addresses (avoids turning the app into a breach-lookup tool for others).
## 5. Frontend
- **Privacy page** (`/app/privacy`), hidden from nav when `GET /privacy/status` is disabled.
- Shows: address checked, breach count, and a card per breach (title, date, what leaked,
verified badge), plus a "Re-check" button and "what this means / next steps" guidance.
- Dashboard widget (optional): a small "Privacy" tile with breach count + link, also
hidden when disabled.
## 6. Security & safety
- [ ] Only the authenticated user's own address is ever checked (no lookup of others).
- [ ] API key read from config/secrets, never logged, never sent to the client.
- [ ] Feature fully inert (endpoints return `{ enabled:false }`, nav hidden) until a key is set.
- [ ] Rate-limit/backoff respected; failures degrade to cached/empty, never crash.
- [ ] HIBP responses cached to minimize external calls and avoid leaking usage patterns.