Skip to content
quarry Artifact [589de798f9]
Login

Artifact [589de798f9]

Artifact 589de798f99aca5c9b81724b4ff7d6e73b14e635ab6a1a92f09c88fa5b68c295:


package oltp

import (
	"path/filepath"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/xfer"
)

// tokenDB is a forge that can seal (a real data directory) and can
// answer for a repository's project code.
func tokenDB(t *testing.T, repos ...string) *DB {
	t.Helper()
	dir := t.TempDir()
	db, err := Open(filepath.Join(dir, "oltp.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { db.Close() })
	for _, r := range repos {
		if err := db.AddRepo(r, filepath.Join(dir, r), "", "public"); err != nil {
			t.Fatal(err)
		}
	}
	db.SyncProjectCode = func(repo string) (string, bool) { return "CODE-" + repo, true }
	return db
}

func writerWithToken(t *testing.T, db *DB, user string, repos ...string) string {
	t.Helper()
	if err := db.AddAccount(user, "irrelevant to pushing", false); err != nil {
		t.Fatal(err)
	}
	for _, r := range repos {
		if err := db.Grant(user, r, Write); err != nil {
			t.Fatal(err)
		}
	}
	token, err := db.MintSyncToken(user)
	if err != nil {
		t.Fatal(err)
	}
	return token
}

// The push credential is the token, and nothing else. In particular it
// is NOT the account password: that was the previous design, and it
// planted a fast-hash derivative of every password in a database whose
// whole point is that argon2id makes a stolen copy worthless.
func TestTheSecretComesFromTheTokenNotThePassword(t *testing.T) {
	db := tokenDB(t, "sandbox")
	token := writerWithToken(t, db, "jan", "sandbox")

	got, ok := db.Secret("sandbox", "jan")
	if !ok {
		t.Fatal("a writer holding a token has no push secret")
	}
	if want := xfer.SharedSecret("CODE-sandbox", "jan", token); got != want {
		t.Errorf("secret is not derived from the token:\n got %q\nwant %q", got, want)
	}
	if bad := xfer.SharedSecret("CODE-sandbox", "jan", "irrelevant to pushing"); got == bad {
		t.Error("the account password still derives the push secret")
	}
}

// THE REASON FOR SEALING RATHER THAN STORING PER REPOSITORY.
//
// A repository created after the token was minted needs no re-issue, no
// second sign-in and no admin. The previous design stored a row per
// repository at sign-in, so a repository made five minutes later had no
// credential and its own creator could not push to it — the same shape
// of staleness that locked twenty-three repositories away from their
// owners.
func TestARepositoryMadeAfterTheTokenJustWorks(t *testing.T) {
	db := tokenDB(t, "sandbox")
	token := writerWithToken(t, db, "jan", "sandbox")

	if err := db.AddRepo("later", t.TempDir(), "", "public"); err != nil {
		t.Fatal(err)
	}
	if err := db.Grant("jan", "later", Write); err != nil {
		t.Fatal(err)
	}
	got, ok := db.Secret("later", "jan")
	if !ok {
		t.Fatal("a repository created after the token was minted has no credential for its writer")
	}
	if want := xfer.SharedSecret("CODE-later", "jan", token); got != want {
		t.Errorf("wrong derivation for the later repository:\n got %q\nwant %q", got, want)
	}
}

// The letters are consulted on every call, so revoking write stops a
// push immediately rather than at some next mint.
func TestRevokingWriteStopsThePushAtOnce(t *testing.T) {
	db := tokenDB(t, "sandbox")
	writerWithToken(t, db, "jan", "sandbox")
	if _, ok := db.Secret("sandbox", "jan"); !ok {
		t.Fatal("premise broken: the writer should have a secret")
	}
	if err := db.Revoke("jan", "sandbox"); err != nil {
		t.Fatal(err)
	}
	if _, ok := db.Secret("sandbox", "jan"); ok {
		t.Error("a revoked writer could still prove itself to the sync protocol")
	}
}

// Revoking the token stops every repository at once, which is what
// makes it a credential somebody can actually withdraw.
func TestRevokingTheTokenStopsEverything(t *testing.T) {
	db := tokenDB(t, "sandbox", "other")
	writerWithToken(t, db, "jan", "sandbox", "other")
	if err := db.RevokeSyncToken("jan"); err != nil {
		t.Fatal(err)
	}
	for _, r := range []string{"sandbox", "other"} {
		if _, ok := db.Secret(r, "jan"); ok {
			t.Errorf("%s still accepted a revoked token", r)
		}
	}
}

// Minting again replaces the old token, so a leaked one stops working.
func TestMintingAgainRetiresTheOldToken(t *testing.T) {
	db := tokenDB(t, "sandbox")
	old := writerWithToken(t, db, "jan", "sandbox")
	fresh, err := db.MintSyncToken("jan")
	if err != nil {
		t.Fatal(err)
	}
	if fresh == old {
		t.Fatal("minting returned the same token twice")
	}
	got, _ := db.Secret("sandbox", "jan")
	if got == xfer.SharedSecret("CODE-sandbox", "jan", old) {
		t.Error("the retired token still derives the live secret")
	}
	if want := xfer.SharedSecret("CODE-sandbox", "jan", fresh); got != want {
		t.Error("the fresh token does not derive the live secret")
	}
}

// A hand-issued sync_user row keeps working. Somebody is relying on one
// right now, and a credential change that silently breaks the people
// already using it is the exact mistake this landing exists to undo.
func TestAHandIssuedRowStillWins(t *testing.T) {
	db := tokenDB(t, "sandbox")
	writerWithToken(t, db, "jan", "sandbox")
	if err := db.SetSyncUser("sandbox", "jan", "issued-by-hand"); err != nil {
		t.Fatal(err)
	}
	if got, _ := db.Secret("sandbox", "jan"); got != "issued-by-hand" {
		t.Errorf("the hand-issued row was overridden: %q", got)
	}
}

// The token is never readable back out of the store: a page may say one
// exists and when it was made, never what it is.
func TestTheTokenIsShownOnceAndNeverAgain(t *testing.T) {
	db := tokenDB(t, "sandbox")
	token := writerWithToken(t, db, "jan", "sandbox")
	created, ok := db.SyncTokenIssued("jan")
	if !ok || created == "" {
		t.Fatal("no issue date for a minted token")
	}
	if strings.Contains(created, token) {
		t.Error("the issue record carries the token itself")
	}
	var sealed string
	if err := db.db.QueryRow(`SELECT sealed FROM sync_token WHERE user = ?`, "jan").Scan(&sealed); err != nil {
		t.Fatal(err)
	}
	if strings.Contains(sealed, token) {
		t.Error("the token is stored in the clear")
	}
}

// An account with no token cannot push, even holding every letter.
func TestNoTokenMeansNoPush(t *testing.T) {
	db := tokenDB(t, "sandbox")
	if err := db.AddAccount("jan", "irrelevant", false); err != nil {
		t.Fatal(err)
	}
	if err := db.Grant("jan", "sandbox", Write); err != nil {
		t.Fatal(err)
	}
	if _, ok := db.Secret("sandbox", "jan"); ok {
		t.Error("an account with no token was admitted")
	}
}