arblant
firefox-vpn-client / core / internal / accounts / password_test.go
password_test.go63 lines · 2.1 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
package accounts

import (
	"errors"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/armanlife/firefox-vpn-client/core/internal/fxa"
)

func TestPasswordIsEncryptedAndSurvivesTokenUpdates(t *testing.T) {
	dir := t.TempDir()
	store, err := OpenStore(dir)
	if err != nil {
		t.Fatal(err)
	}
	acc, _, err := store.Add("user@example.com", &fxa.SavedTokens{AccessToken: "a1", RefreshToken: "r1", ObtainedAt: time.Now(), ExpiresIn: 3600}, time.Now())
	if err != nil {
		t.Fatal(err)
	}
	if _, err := store.Password(acc.ID); !errors.Is(err, ErrNoPassword) || store.HasPassword(acc.ID) {
		t.Fatalf("a new account has no password, got %v", err)
	}
	const secret = "correct horse battery staple"
	if err := store.SetPassword(acc.ID, secret); err != nil {
		t.Fatal(err)
	}
	raw, err := os.ReadFile(filepath.Join(dir, "accounts", acc.ID, "credentials.json"))
	if err != nil {
		t.Fatal(err)
	}
	if strings.Contains(string(raw), secret) || !strings.Contains(string(raw), `"password": "v1:`) {
		t.Fatalf("the password must be stored encrypted:\n%s", raw)
	}

	// Refreshed tokens keep the password, and the tokens still read back.
	if err := store.SaveCredentials(acc.ID, &fxa.SavedTokens{AccessToken: "a2", RefreshToken: "r2", ObtainedAt: time.Now(), ExpiresIn: 3600}); err != nil {
		t.Fatal(err)
	}
	if got, err := store.Password(acc.ID); err != nil || got != secret {
		t.Fatalf("password after a token update: %q %v", got, err)
	}
	if creds, err := store.Credentials(acc.ID); err != nil || creds.RefreshToken != "r2" {
		t.Fatalf("tokens after setting a password: %+v %v", creds, err)
	}

	// Another key cannot decrypt it.
	if err := os.WriteFile(filepath.Join(dir, "accounts", secretKeyFileName), []byte(strings.Repeat("ab", 32)), 0o600); err != nil {
		t.Fatal(err)
	}
	if _, err := store.Password(acc.ID); err == nil {
		t.Fatal("a different key must not decrypt the password")
	}
	if err := store.SetPassword(acc.ID, ""); err != nil || store.HasPassword(acc.ID) {
		t.Fatalf("clearing the password: %v", err)
	}
	if list, err := store.List(); err != nil || len(list) != 1 {
		t.Fatalf("the key file must not appear as an account: %v %v", list, err)
	}
}