mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-10-15 07:09:58 +00:00
A small follow-up to parts A to E of #2128: users can download all data the instance holds about them, and the server backs up `users.db`. Stacked on #2140. Review the commits after that branch. ## The situation - An instance with accounts holds personal data, but a user has no way to get a copy (GDPR articles 15 and 20). Self-delete already exists. - `users.db` is the only file the server writes, and nothing backs it up: `GET /api/backup` snapshots the analyzer database only. ## What this PR adds **Data export.** `GET /api/account/export` and a "Download my data" button on the account page give one JSON file with: - the profile, including activation and a pending email change; - sessions (times and user agent); - synced settings, own proposals, notification prefs and watches; - audit rows where the user is actor or target, with other accounts as an id only; - the mail log. Password hash, session and link tokens and the unsubscribe token are left out: they are credentials. Each export is audited as `user.export`. **users.db backup** - Daily `VACUUM INTO` snapshots through `internal/users`, on by default when user management is on. They go to `backups/` next to `users.db`, the 7 newest are kept, the directory is 0700 and the files 0600. Rotation only touches `users-<timestamp>.db` files and never the snapshot it just wrote. Config: `userManagement.backup {enabled, dir, keep}`. - `GET /api/admin/users-backup`: an admin downloads a fresh snapshot to keep off the server; audited as `user.backup`. - `docs/user-guide/accounts.md` gains a restore procedure. It covers what a restore brings back (accounts deleted after the snapshot, old passwords, revoked sessions) and how to clean that up afterwards. Spec: [`docs/specs/2026-10-08-account-export-and-users-backup-design.md`](https://github.com/efiten/CoreScope/blob/feat/account-export-backup/docs/specs/2026-10-08-account-export-and-users-backup-design.md). ## Verification - `internal/users` with `-race` and the `cmd/server` suite pass locally, 30 new Go tests. The file-mode tests skip on Windows. - `sh test-all.sh` exits 0; the XSS gate in diff mode passes. - User-management E2E: 27 of 27 steps locally. - On our production instance since 8 October 2026; the first snapshot there was 143 kB. ## Not in this PR - Importing an export into another instance. - Off-site upload of the snapshots. The admin download covers that by hand. --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
31 lines
928 B
Go
31 lines
928 B
Go
package users
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
)
|
|
|
|
// Snapshot writes a consistent copy of users.db to path with VACUUM INTO
|
|
// (the technique of GET /api/backup) and makes it readable by the owner
|
|
// only: it holds password hashes and addresses. path must not exist; an
|
|
// empty file there is refused too, although VACUUM INTO would accept it.
|
|
// On failure no file is left at path.
|
|
func (s *Store) Snapshot(path string) error {
|
|
if _, err := os.Lstat(path); err == nil {
|
|
return fmt.Errorf("users: snapshot target %s already exists", path)
|
|
} else if !errors.Is(err, fs.ErrNotExist) {
|
|
return fmt.Errorf("users: snapshot target %s: %w", path, err)
|
|
}
|
|
if _, err := s.db.Exec(`VACUUM INTO ?`, path); err != nil {
|
|
os.Remove(path)
|
|
return fmt.Errorf("users: snapshot: %w", err)
|
|
}
|
|
if err := os.Chmod(path, 0o600); err != nil {
|
|
os.Remove(path)
|
|
return fmt.Errorf("users: snapshot mode: %w", err)
|
|
}
|
|
return nil
|
|
}
|