backup-reauth: the account password before a backup or an export
Agreed with the owner on 2026-10-13, taken up on 2026-10-16 ("Да"); the
mockup is 1888dce. Both secret downloads - the full backup on Server ›
Backup and a domain's export on its settings - ask for "Your account
password": reauth (handlers_backup.go) checks the signed-in user's own
password with bcrypt, as a password change does, under the same limiter
(auth.AllowPasswordChange: ten in fifteen minutes per user, shared with the
Account form). A wrong or missing one is 401 on the same page with "Your
account password is incorrect.", logged; nothing is sent. The import and
the selfpost-backup CLI are not asked.
Also, the owner's phone screenshot of 2026-10-16 ("кнопка выбора файла не
такая, как в макете. Это косяк?"): the import's file field was a bare
<input type="file"> drawn by the browser ("Browse…"), where the mockup has
Bulma's .file - "Choose a file…" and the name chosen. The template now
follows the mockup; panel.js writes the name into .file-name on change and
at load (testdata/filename_test.js, run by TestPanelJS). No other file
field exists.
Tests: TestSecretDownloadsAskForTheAccountPassword (missing, wrong, right,
the limiter), the export refusals of the settings page with the password,
cmd/panel's restore tests and e2e's backups with it, e2e's refusal of a
backup without and with a wrong password. go vet + go test (26 packages)
and make e2e on the test server for this tree.
Docs: security.md § Backup and domain export, guide § Full backup and
restore, help (word for word with the mockup); CHANGELOG [Unreleased];
roadmap row done.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ package e2e
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -76,7 +77,7 @@ func testCertbotLayout(t *testing.T, sc *scenario) {
|
||||
t.Fatalf("Health names a renewal waiting, but Postfix serves what certs/ leads to")
|
||||
}
|
||||
|
||||
resp, archived, err := sc.panel.postForm("/server/backup", nil)
|
||||
resp, archived, err := sc.panel.postForm("/server/backup", url.Values{"account_password": {adminPassword}})
|
||||
if err != nil || resp.StatusCode != 200 {
|
||||
t.Fatalf("full backup over certbot's layout: %v, status %v\n%.600s", err, resp, archived)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,19 @@ func testFullBackupRestore(t *testing.T, sc *scenario) {
|
||||
if err := os.Chmod(keyPath, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp, body, err := sc.panel.postForm("/server/backup", url.Values{})
|
||||
// A session is not enough (backup-reauth): without the account password,
|
||||
// or with a wrong one, nothing is sent.
|
||||
for _, pw := range []string{"", "not-the-password"} {
|
||||
resp, body, err := sc.panel.postForm("/server/backup", url.Values{"account_password": {pw}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.StatusCode != 401 || resp.Header.Get("Content-Disposition") != "" || !strings.Contains(body, "Your account password is incorrect.") {
|
||||
t.Fatalf("full backup with account password %q: status %d, want a refusal:\n%.600s", pw, resp.StatusCode, body)
|
||||
}
|
||||
}
|
||||
|
||||
resp, body, err := sc.panel.postForm("/server/backup", url.Values{"account_password": {adminPassword}})
|
||||
if err != nil {
|
||||
t.Fatalf("full backup with an unreadable key: %v", err)
|
||||
}
|
||||
@@ -51,7 +63,7 @@ func testFullBackupRestore(t *testing.T, sc *scenario) {
|
||||
t.Fatalf("write an extra map: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
resp, archive, err := sc.panel.postForm("/server/backup", url.Values{})
|
||||
resp, archive, err := sc.panel.postForm("/server/backup", url.Values{"account_password": {adminPassword}})
|
||||
if err != nil {
|
||||
t.Fatalf("full backup: %v", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user