feat(PROJ-70): Self-Service IMAP-Rückholung (Backend)

Neuer Opt-in-Endpunkt, mit dem User archivierte Mails per IMAP APPEND
zurück in ihr eigenes externes Postfach (INBOX) kopieren können. Das
Archiv selbst bleibt read-only (nur storage.Load(), kein Schreibzugriff
auf internal/imapserver).

- PATCH /api/auth/imap-restore: Opt-in-Flag umschalten, Aktivierung
  erfordert Passwort-Reverifikation.
- POST /api/mails/{id}/restore: lädt Mail lesend, prüft Mail- und
  Account-Ownership in restoreAccessAllowed() (PROJ-61-Muster), APPEND
  via neuer internal/imap/append.go, kein Admin-Override.
- Audit-Log-Eintrag (EventRestore) pro Versuch, erfolgreich und
  fehlgeschlagen.
- imap_restore_enabled-Spalte (default false) via idempotenter
  initSchema-Migration.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-07-07 00:36:44 +02:00
co-authored by Claude Sonnet 5
parent 7aa63e30ca
commit da79a56b3e
7 changed files with 379 additions and 5 deletions
+29
View File
@@ -144,9 +144,38 @@ func (s *Store) initSchema(ctx context.Context) error {
_, err = s.pool.Exec(ctx, `
ALTER TABLE users ADD COLUMN IF NOT EXISTS tokens_valid_after TIMESTAMPTZ;
`)
if err != nil {
return err
}
// PROJ-70: Opt-in-Flag für die Self-Service-IMAP-Rückholung (Archiv-Mail zurück
// ins eigene Postfach). Default false — der Nutzer muss die Funktion selbst per
// Schieberegler mit Passwort-Bestätigung freischalten.
_, err = s.pool.Exec(ctx, `
ALTER TABLE users ADD COLUMN IF NOT EXISTS imap_restore_enabled BOOLEAN NOT NULL DEFAULT false;
`)
return err
}
// GetRestoreEnabled reports whether the user has opted in to the self-service
// IMAP restore feature (PROJ-70).
func (s *Store) GetRestoreEnabled(ctx context.Context, userID int64) (bool, error) {
var enabled bool
err := s.pool.QueryRow(ctx, `SELECT imap_restore_enabled FROM users WHERE id = $1`, userID).Scan(&enabled)
if err != nil {
return false, fmt.Errorf("userstore: get restore enabled: %w", err)
}
return enabled, nil
}
// SetRestoreEnabled toggles the self-service IMAP restore opt-in flag (PROJ-70).
func (s *Store) SetRestoreEnabled(ctx context.Context, userID int64, enabled bool) error {
_, err := s.pool.Exec(ctx, `UPDATE users SET imap_restore_enabled = $1 WHERE id = $2`, enabled, userID)
if err != nil {
return fmt.Errorf("userstore: set restore enabled: %w", err)
}
return nil
}
// Close closes the underlying connection pool.
func (s *Store) Close() error {
s.pool.Close()