Skip to content
quarry Artifact [d425f146f6]
Login

Artifact [d425f146f6]

Artifact d425f146f60f7a0c53a645cdc62f1dbc0f698d05f740a21f56174bca7a01ea49:


package oltp

import (
	"testing"
	"time"
)

// #319: an admin who re-sends an invite holds a second link. Once the
// owner claims the account with either one, the other must not set the
// owner's password — or the admin signs in as them.
func TestAStaleInviteCannotTakeOverAClaimedAccount(t *testing.T) {
	db := testDB(t)
	first, err := db.InviteAccount("bob", "[email protected]", false)
	if err != nil {
		t.Fatal(err)
	}
	second, err := db.InviteAccount("bob", "[email protected]", false)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := db.RedeemInvite(second, "bobs-own-pw"); err != nil {
		t.Fatalf("bob could not claim his account: %v", err)
	}
	if _, err := db.RedeemInvite(first, "admins-pw"); err == nil {
		t.Error("FAIL: the older invite set a claimed account's password")
	}
	if _, ok := db.Authenticate("bob", "admins-pw"); ok {
		t.Error("FAIL: the admin signs in as bob")
	}
	if _, ok := db.Authenticate("bob", "bobs-own-pw"); !ok {
		t.Error("FAIL: bob's own password stopped working")
	}
}

// Re-inviting supersedes, as ResetPassword does: one live link per
// pending account, the newest.
func TestReinviteSupersedesTheOlderLink(t *testing.T) {
	db := testDB(t)
	first, err := db.InviteAccount("bob", "", false)
	if err != nil {
		t.Fatal(err)
	}
	second, err := db.InviteAccount("bob", "", false)
	if err != nil {
		t.Fatal(err)
	}
	if _, ok := db.InviteFor(first); ok {
		t.Error("FAIL: the superseded invite is still live")
	}
	if name, ok := db.InviteFor(second); !ok || name != "bob" {
		t.Errorf("FAIL: the newest invite is not live: %q %v", name, ok)
	}
}

// mintRaw writes a token row the way a build before the supersede did,
// so a database carrying such rows across the upgrade is covered too.
func mintRaw(t *testing.T, db *DB, token, account, kind string) {
	t.Helper()
	now := time.Now().UTC()
	if _, err := db.w.Exec(`INSERT INTO invite (token, account, created, expires, kind) VALUES (?, ?, ?, ?, ?)`,
		hashToken(token), account, now.Format(time.RFC3339), now.Add(InviteTTL).Format(time.RFC3339), kind); err != nil {
		t.Fatal(err)
	}
}

// An invite token is only ever good while the account is unclaimed,
// whatever else is true of it.
func TestAnInviteForAClaimedAccountIsRefused(t *testing.T) {
	db := testDB(t)
	if err := db.AddAccount("bob", "bobs-own-pw", false); err != nil {
		t.Fatal(err)
	}
	mintRaw(t, db, "left-over", "bob", "invite")
	if _, err := db.RedeemInvite("left-over", "admins-pw"); err == nil {
		t.Error("FAIL: an invite set a claimed account's password")
	}
	if _, ok := db.Authenticate("bob", "bobs-own-pw"); !ok {
		t.Error("FAIL: bob's own password stopped working")
	}
}

// Redeeming any token spends every other unspent one the account has,
// invite or reset alike.
func TestRedeemingSpendsTheAccountsOtherTokens(t *testing.T) {
	db := testDB(t)
	if _, err := db.InviteAccount("bob", "", false); err != nil {
		t.Fatal(err)
	}
	mintRaw(t, db, "inv-a", "bob", "invite")
	mintRaw(t, db, "inv-b", "bob", "invite")
	if _, err := db.RedeemInvite("inv-a", "bobs-own-pw"); err != nil {
		t.Fatal(err)
	}
	if _, ok := db.InviteFor("inv-b"); ok {
		t.Error("FAIL: a second invite survived the claim")
	}
	mintRaw(t, db, "rst-a", "bob", "reset")
	mintRaw(t, db, "rst-b", "bob", "reset")
	if _, err := db.RedeemInvite("rst-a", "bobs-next-pw"); err != nil {
		t.Fatal(err)
	}
	if _, ok := db.InviteFor("rst-b"); ok {
		t.Error("FAIL: a second reset survived the redemption")
	}
}

// Setting a password retires an imported fossil credential: left in
// place, the old fossil password still signs in and Authenticate's
// upgrade then writes it over the password just set.
func TestSetPasswordRetiresTheLegacyCredential(t *testing.T) {
	for _, self := range []bool{false, true} {
		db := testDB(t)
		const code, login, old = "code", "alice", "fossil-pw"
		if _, _, err := db.ImportUsers("repo", code, []ImportedUser{
			{Login: login, PWHash: legacySecret(code, login, old)},
		}); err != nil {
			t.Fatal(err)
		}
		set := db.SetPassword
		if self {
			set = func(name, pw string) error { return db.SetPasswordSelf(name, pw, "") }
		}
		if err := set(login, "brand-new-pw"); err != nil {
			t.Fatal(err)
		}
		if _, _, ok := db.LegacyCredential(login); ok {
			t.Errorf("FAIL (self=%v): the legacy credential survived a password change", self)
		}
		if _, ok := db.Authenticate(login, old); ok {
			t.Errorf("FAIL (self=%v): the old fossil password still signs in", self)
		}
		if _, ok := db.Authenticate(login, "brand-new-pw"); !ok {
			t.Errorf("FAIL (self=%v): the new password does not sign in", self)
		}
	}
}