Skip to content
quarry Artifact [3614b2d12d]
Login

Artifact [3614b2d12d]

Artifact 3614b2d12dd355ea3af41aa440ccfd0d9d670d8ed477060ffb51ce458bf5c627:


// mirroruv.go is RFC 0020 rung 6's half of the mirror row: whether a
// mirror imports its upstream's unversioned files, the credential the
// import round signs with, and what the last round did.
//
// The password is sealed the way a bucket secret is (secret.go): the
// round replays it into a login card on every pull, so a one-way hash
// cannot hold it, and a copy of oltp.db alone must not yield it.
// MirrorUVLogin is its one reader, and no op returns it.
package oltp

import (
	"database/sql"
	"errors"
	"fmt"
	"time"
)

// SetMirrorUV turns the import round on or off for repo's mirror.
func (d *DB) SetMirrorUV(repo string, on bool) error {
	res, err := d.w.Exec(`UPDATE mirror SET uv = ? WHERE repo = ?`, boolInt(on), repo)
	if err != nil {
		return err
	}
	if n, _ := res.RowsAffected(); n == 0 {
		return fmt.Errorf("oltp: %s is not a mirror", repo)
	}
	return nil
}

// SetMirrorUVCredential stores the login and password the import round
// signs with, sealing the password; an empty user clears both, and the
// round then reads as nobody. The upstream that the credential belongs
// to is the row's URL, and SetMirror drops it when the URL changes.
func (d *DB) SetMirrorUVCredential(repo, user, password string) error {
	sealed := ""
	if user != "" {
		var err error
		if sealed, err = d.seal.seal(password); err != nil {
			return err
		}
	}
	res, err := d.w.Exec(`UPDATE mirror SET uv_user = ?, uv_secret_enc = ? WHERE repo = ?`, user, sealed, repo)
	if err != nil {
		return err
	}
	if n, _ := res.RowsAffected(); n == 0 {
		return fmt.Errorf("oltp: %s is not a mirror", repo)
	}
	return nil
}

// MirrorUVLogin is what an import round signs in with, read from one
// row so the credential always belongs to the URL beside it: SetMirror
// drops the credential in the same statement that changes the URL.
type MirrorUVLogin struct {
	URL     string
	Enabled bool // still tracking
	UV      bool // the switch
	// User and Password are the credential, both empty for nobody.
	User, Password string
}

// MirrorUVLogin answers repo's round login with the password unsealed;
// ok is false when repo has no mirror. Its one caller is the round.
func (d *DB) MirrorUVLogin(repo string) (MirrorUVLogin, bool, error) {
	var l MirrorUVLogin
	var sealed string
	err := d.db.QueryRow(`SELECT url, enabled, uv, uv_user, uv_secret_enc FROM mirror WHERE repo = ?`, repo).
		Scan(&l.URL, &l.Enabled, &l.UV, &l.User, &sealed)
	if errors.Is(err, sql.ErrNoRows) {
		return MirrorUVLogin{}, false, nil
	}
	if err != nil {
		return MirrorUVLogin{}, false, err
	}
	if l.User == "" || sealed == "" {
		l.User = ""
		return l, true, nil
	}
	if l.Password, err = d.seal.open(sealed); err != nil {
		return MirrorUVLogin{}, false, err
	}
	return l, true, nil
}

// RecordMirrorUV stamps an import round: on the mirror row as the last
// result, and on the pull it followed, named by that pull's at (another
// pull may have been recorded while the round ran). changed is how many
// files it applied; msg is what it could not do, empty when nothing.
func (d *DB) RecordMirrorUV(repo, pullAt string, at time.Time, changed int, msg string) error {
	if _, err := d.w.Exec(`UPDATE mirror SET uv_at = ?, uv_changed = ?, uv_err = ? WHERE repo = ?`,
		at.UTC().Format(time.RFC3339), changed, msg, repo); err != nil {
		return err
	}
	_, err := d.w.Exec(`UPDATE mirror_pull SET uv_ran = 1, uv_changed = ?, uv_err = ?
		WHERE repo = ? AND at = ?`, changed, msg, repo, pullAt)
	return err
}