Skip to content
quarry Artifact [29d14a86ac]
Login

Artifact [29d14a86ac]

Artifact 29d14a86ac90e36465c2b7a7c4cdac3dc7ccb81b1a84f1b5e752cf992fca4236:


package oltp

import (
	"path/filepath"
	"testing"
)

const cutoverPW = "a-long-enough-password"

// cutoverRig is a store as the cutover finds it: a person, and agents
// that still hold a password from before marking cleared one — a
// person's agent with a native verifier, a forge agent, and a person's
// agent with only an imported fossil credential. The operator columns
// are written by hand, the way a pre-rung-3 mark left them.
func cutoverRig(t *testing.T, path string) *DB {
	t.Helper()
	db, err := Open(path)
	if err != nil {
		t.Fatal(err)
	}
	db.SyncProjectCode = func(string) (string, bool) { return "code", true }
	for _, who := range []string{"dan", "dan-bot", "zeit"} {
		if err := db.AddAccount(who, cutoverPW, false); err != nil {
			t.Fatal(err)
		}
	}
	if err := db.AddRepo("pub", t.TempDir(), "", Public); err != nil {
		t.Fatal(err)
	}
	if _, _, err := db.ImportUsers("pub", "code", []ImportedUser{
		{Login: "old-bot", Caps: "oi", PWHash: legacySecret("code", "old-bot", cutoverPW)},
	}); err != nil {
		t.Fatal(err)
	}
	for _, who := range []string{"dan", "dan-bot", "zeit"} {
		if err := db.SetCaps(who, "pub", "oi"); err != nil {
			t.Fatal(err)
		}
		if err := db.SetSyncUser("pub", who, legacySecret("code", who, cutoverPW)); err != nil {
			t.Fatal(err)
		}
	}
	return db
}

func markByHand(t *testing.T, db *DB, name, operator string) {
	t.Helper()
	if _, err := db.w.Exec(`UPDATE account SET operator = ? WHERE name = ?`, operator, name); err != nil {
		t.Fatal(err)
	}
}

// Every password door is Authenticate or Secrets: an agent's password
// is refused at both even while one is still stored, and its tokens
// are not. A person's password is untouched.
func TestAgentPasswordRefusedAtEveryDoor(t *testing.T) {
	db := cutoverRig(t, filepath.Join(t.TempDir(), "oltp.db"))
	defer db.Close()
	reset, err := db.ResetPassword("dan-bot")
	if err != nil {
		t.Fatal(err)
	}
	botSession, _, ok := db.NewSession("dan-bot", cutoverPW)
	if !ok {
		t.Fatal("no session before the mark")
	}
	danSession, _, ok := db.NewSession("dan", cutoverPW)
	if !ok {
		t.Fatal("no session for the person")
	}
	markByHand(t, db, "dan-bot", "dan")
	markByHand(t, db, "old-bot", "dan")
	markByHand(t, db, "zeit", forgeOperator)

	for _, who := range []string{"dan-bot", "old-bot", "zeit"} {
		if _, ok := db.Authenticate(who, cutoverPW); ok {
			t.Errorf("FAIL: %s signed in with a stored password", who)
		}
		if _, _, ok := db.NewSessionFrom(who, cutoverPW, "", ""); ok {
			t.Errorf("FAIL: %s opened a web session with a password", who)
		}
		if err := db.SetPasswordJournalled(who, cutoverPW, "host", "account passwd"); !IsRefusal(err) {
			t.Errorf("FAIL: the host recovery gave %s a password: %v", who, err)
		}
	}
	if _, _, ok := db.LegacyCredential("old-bot"); !ok {
		t.Error("FAIL: the legacy credential was spent by a refused sign-in")
	}
	if _, err := db.RedeemInvite(reset, cutoverPW); err == nil {
		t.Error("FAIL: a reset minted before marking set the agent's password")
	}
	if _, ok := db.Authenticate("dan", cutoverPW); !ok {
		t.Error("FAIL: a person's password was refused")
	}
	// A session no token opened is a password's, and an agent's is
	// refused even where the row survived.
	if _, _, _, ok := db.SessionPrincipal(botSession); ok {
		t.Error("FAIL: an agent's password session still resolves")
	}
	if _, _, _, ok := db.SessionPrincipal(danSession); !ok {
		t.Error("FAIL: a person's session was refused")
	}

	// The login card: the hand-issued row is a password's secret and
	// goes unanswered for an agent; its sync token signs.
	if creds := db.Secrets("pub", "dan-bot"); len(creds) != 0 {
		t.Errorf("FAIL: an agent's hand-issued row still signs: %d credentials", len(creds))
	}
	if _, _, err := db.MintToken("dan-bot", TokenSpec{Name: "push", Scopes: []string{ScopeSync}}); err != nil {
		t.Fatal(err)
	}
	creds := db.Secrets("pub", "dan-bot")
	if len(creds) != 1 || creds[0].Token == "" {
		t.Errorf("FAIL: an agent's login card credentials = %+v, want its one sync token", creds)
	}
	if creds := db.Secrets("pub", "dan"); len(creds) != 1 || creds[0].Token != "" {
		t.Errorf("FAIL: a person's hand-issued row = %+v, want it answered", creds)
	}
	if !t.Failed() {
		t.Log("ok: password refused for both kinds of agent at sign-in, session, recovery, reset and login card; the person's still works")
	}
}

// The cutover migration clears every agent's stored password, native
// and imported, and the sessions it opened; a token-bound session and
// the tokens themselves survive, and so does the person.
func TestCutoverRetiresAgentPasswords(t *testing.T) {
	path := filepath.Join(t.TempDir(), "oltp.db")
	db := cutoverRig(t, path)
	byPassword, _, ok := db.NewSession("dan-bot", cutoverPW)
	if !ok {
		t.Fatal("no session before the mark")
	}
	markByHand(t, db, "dan-bot", "dan")
	markByHand(t, db, "old-bot", "dan")
	markByHand(t, db, "zeit", forgeOperator)
	value, tok, err := db.MintToken("dan-bot", TokenSpec{Name: "web", Scopes: []string{ScopeAPI, ScopeWeb}})
	if err != nil {
		t.Fatal(err)
	}
	link, _, err := db.MintSigninLink("dan-bot", tok.ID, "")
	if err != nil {
		t.Fatal(err)
	}
	byToken, _, _, ok := db.RedeemSigninLink(link, "", "")
	if !ok {
		t.Fatal("the sign-in link did not open a session")
	}
	db.Close()

	db, err = Open(path)
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	var left int
	if err := db.db.QueryRow(`SELECT count(*) FROM account WHERE operator <> ''
		AND (pwhash <> '' OR legacy_pw <> '' OR legacy_code <> '')`).Scan(&left); err != nil {
		t.Fatal(err)
	}
	if left != 0 {
		t.Errorf("FAIL: %d agents still hold a password after the cutover", left)
	}
	if _, _, _, ok := db.SessionPrincipal(byPassword); ok {
		t.Error("FAIL: the agent's password session outlived the cutover")
	}
	if _, _, bound, ok := db.SessionPrincipal(byToken); !ok || !bound {
		t.Error("FAIL: the agent's token-bound session was ended by the cutover")
	}
	if _, ok := db.ResolveToken("dan-bot", value, ScopeAPI); !ok {
		t.Error("FAIL: the agent's token stopped resolving")
	}
	var hash string
	if err := db.db.QueryRow(`SELECT pwhash FROM account WHERE name = 'dan'`).Scan(&hash); err != nil || hash == "" {
		t.Errorf("FAIL: the person's password went with the agents' (%v)", err)
	}
	if _, ok := db.Authenticate("dan", cutoverPW); !ok {
		t.Error("FAIL: a person's password was refused after the cutover")
	}
	if rows, _ := db.LegacySyncUsers("dan-bot"); len(rows) != 0 {
		t.Errorf("FAIL: the agent's hand-issued push rows survived the cutover: %v", rows)
	}
	if rows, _ := db.LegacySyncUsers("dan"); len(rows) != 1 {
		t.Errorf("FAIL: the person's hand-issued push row = %v, want it kept", rows)
	}
	// Clearing the mark leaves a person with nothing to sign with until
	// an invite is redeemed: the old secret does not come back.
	if err := db.SetOperator("dan-bot", ""); err != nil {
		t.Fatal(err)
	}
	if creds := db.Secrets("pub", "dan-bot"); len(creds) != 0 {
		t.Errorf("FAIL: the unmarked account signs with %d old credentials", len(creds))
	}
	if !t.Failed() {
		t.Log("ok: agents' passwords, push rows and password sessions retired; token, token session and person intact")
	}
}

// Marking, as a person's agent or the forge's, drops the account's
// hand-issued push rows in the same act as its password.
func TestMarkingDropsHandIssuedPushRows(t *testing.T) {
	db := testDB(t)
	if err := db.AddAccount("root", cutoverPW, true); err != nil {
		t.Fatal(err)
	}
	for _, who := range []string{"dan", "dan-bot", "zeit"} {
		if err := db.AddAccount(who, cutoverPW, false); err != nil {
			t.Fatal(err)
		}
		if err := db.SetSyncUser("pub", who, legacySecret("code", who, cutoverPW)); err != nil {
			t.Fatal(err)
		}
	}
	if err := db.SetOperator("dan-bot", "dan"); err != nil {
		t.Fatal(err)
	}
	if err := db.SetForgeAgent("zeit", "root"); err != nil {
		t.Fatal(err)
	}
	for who, want := range map[string]int{"dan-bot": 0, "zeit": 0, "dan": 1} {
		if rows, _ := db.LegacySyncUsers(who); len(rows) != want {
			t.Errorf("FAIL: %s holds %d hand-issued push rows, want %d", who, len(rows), want)
		}
	}
	if !t.Failed() {
		t.Log("ok: marking drops the agent's push rows and leaves the person's")
	}
}