diff --git a/.air/knotserver.toml b/.air/knotserver.toml --- a/.air/knotserver.toml +++ b/.air/knotserver.toml @@ -1,6 +1,6 @@ [build] -cmd = 'go build -ldflags "-X tangled.sh/tangled.sh/core/knotserver.version=$(git describe --tags --long)" -o .bin/knot ./cmd/knotserver/main.go' -bin = ".bin/knot" +cmd = 'go build -ldflags "-X tangled.sh/tangled.sh/core/knotserver.version=$(git describe --tags --long)" -o .bin/knot ./cmd/knot/' +bin = ".bin/knot server" root = "." exclude_regex = [""] diff --git a/types/diff.go b/types/diff.go --- a/types/diff.go +++ b/types/diff.go @@ -77,3 +77,33 @@ return files } + +// ObjectCommitToNiceDiff is a compatibility function to convert a +// commit object into a NiceDiff structure. +func ObjectCommitToNiceDiff(c *object.Commit) NiceDiff { + var niceDiff NiceDiff + + // set commit information + niceDiff.Commit.Message = c.Message + niceDiff.Commit.Author = c.Author + niceDiff.Commit.This = c.Hash.String() + niceDiff.Commit.Committer = c.Committer + niceDiff.Commit.Tree = c.TreeHash.String() + niceDiff.Commit.PGPSignature = c.PGPSignature + + changeId, ok := c.ExtraHeaders["change-id"] + if ok { + niceDiff.Commit.ChangedId = string(changeId) + } + + // set parent hash if available + if len(c.ParentHashes) > 0 { + niceDiff.Commit.Parent = c.ParentHashes[0].String() + } + + // XXX: Stats and Diff fields are typically populated + // after fetching the actual diff information, which isn't + // directly available in the commit object itself. + + return niceDiff +} diff --git a/appview/db/pubkeys.go b/appview/db/pubkeys.go --- a/appview/db/pubkeys.go +++ b/appview/db/pubkeys.go @@ -15,7 +15,7 @@ func DeletePublicKey(e Execer, did, name, key string) error { _, err := e.Exec(` - delete from public_keys + delete from public_keys where did = ? and name = ? and key = ?`, did, name, key) return err @@ -23,7 +23,7 @@ func DeletePublicKeyByRkey(e Execer, did, rkey string) error { _, err := e.Exec(` - delete from public_keys + delete from public_keys where did = ? and rkey = ?`, did, rkey) return err @@ -75,7 +75,7 @@ return keys, nil } -func GetPublicKeys(e Execer, did string) ([]PublicKey, error) { +func GetPublicKeysForDid(e Execer, did string) ([]PublicKey, error) { var keys []PublicKey rows, err := e.Query(`select did, key, name, rkey, created from public_keys where did = ?`, did) diff --git a/appview/pages/pages.go b/appview/pages/pages.go --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -414,6 +414,7 @@ HTMLReadme template.HTML Raw bool EmailToDidOrHandle map[string]string + VerifiedCommits map[string]bool Languages *types.RepoLanguageResponse types.RepoIndexResponse } @@ -452,6 +453,7 @@ types.RepoLogResponse Active string EmailToDidOrHandle map[string]string + VerifiedCommits map[string]bool } func (p *Pages) RepoLog(w io.Writer, params RepoLogParams) error { @@ -464,6 +466,7 @@ RepoInfo repoinfo.RepoInfo Active string EmailToDidOrHandle map[string]string + Verified bool types.RepoCommitResponse } diff --git a/appview/repo/repo.go b/appview/repo/repo.go --- a/appview/repo/repo.go +++ b/appview/repo/repo.go @@ -138,6 +138,15 @@ branchesTrunc := result.Branches[:min(branchCount, len(result.Branches))] emails := uniqueEmails(commitsTrunc) + emailToDidMap, err := db.GetEmailToDid(rp.db, emails, true) + if err != nil { + log.Println("failed to get email to did map", err) + } + + vc, err := verifiedObjectCommits(rp, emailToDidMap, commitsTrunc) + if err != nil { + log.Println(err) + } user := rp.oauth.GetUser(r) repoInfo := f.RepoInfo(user) @@ -178,7 +187,8 @@ TagsTrunc: tagsTrunc, ForkInfo: forkInfo, BranchesTrunc: branchesTrunc, - EmailToDidOrHandle: EmailToDidOrHandle(rp, emails), + EmailToDidOrHandle: emailToDidOrHandle(rp, emailToDidMap), + VerifiedCommits: vc, Languages: repoLanguages, }) return @@ -293,12 +303,24 @@ } user := rp.oauth.GetUser(r) + + emailToDidMap, err := db.GetEmailToDid(rp.db, uniqueEmails(repolog.Commits), true) + if err != nil { + log.Println("failed to fetch email to did mapping", err) + } + + vc, err := verifiedObjectCommits(rp, emailToDidMap, repolog.Commits) + if err != nil { + log.Println(err) + } + rp.pages.RepoLog(w, pages.RepoLogParams{ LoggedInUser: user, TagMap: tagMap, RepoInfo: f.RepoInfo(user), RepoLogResponse: *repolog, - EmailToDidOrHandle: EmailToDidOrHandle(rp, uniqueEmails(repolog.Commits)), + EmailToDidOrHandle: emailToDidOrHandle(rp, emailToDidMap), + VerifiedCommits: vc, }) return } @@ -438,12 +460,23 @@ return } + emailToDidMap, err := db.GetEmailToDid(rp.db, []string{result.Diff.Commit.Author.Email}, true) + if err != nil { + log.Println("failed to get email to did mapping:", err) + } + + vc, err := verifiedCommits(rp, emailToDidMap, []types.NiceDiff{*result.Diff}) + if err != nil { + log.Println(err) + } + user := rp.oauth.GetUser(r) rp.pages.RepoCommit(w, pages.RepoCommitParams{ LoggedInUser: user, RepoInfo: f.RepoInfo(user), RepoCommitResponse: result, - EmailToDidOrHandle: EmailToDidOrHandle(rp, []string{result.Diff.Commit.Author.Email}), + EmailToDidOrHandle: emailToDidOrHandle(rp, emailToDidMap), + Verified: vc[result.Diff.Commit.This], }) return } diff --git a/appview/repo/repo_util.go b/appview/repo/repo_util.go --- a/appview/repo/repo_util.go +++ b/appview/repo/repo_util.go @@ -9,6 +9,8 @@ "github.com/go-git/go-git/v5/plumbing/object" "tangled.sh/tangled.sh/core/appview/db" + "tangled.sh/tangled.sh/core/crypto" + "tangled.sh/tangled.sh/core/types" ) func uniqueEmails(commits []*object.Commit) []string { @@ -56,15 +58,15 @@ return } -func EmailToDidOrHandle(r *Repo, emails []string) map[string]string { - emailToDid, err := db.GetEmailToDid(r.db, emails, true) // only get verified emails for mapping - if err != nil { - log.Printf("error fetching dids for emails: %v", err) +// emailToDidOrHandle takes an emailToDidMap from db.GetEmailToDid +// and resolves all dids to handles and returns a new map[string]string +func emailToDidOrHandle(r *Repo, emailToDidMap map[string]string) map[string]string { + if emailToDidMap == nil { return nil } var dids []string - for _, v := range emailToDid { + for _, v := range emailToDidMap { dids = append(dids, v) } resolvedIdents := r.idResolver.ResolveIdents(context.Background(), dids) @@ -80,13 +82,61 @@ // Create map of email to didOrHandle for commit display emailToDidOrHandle := make(map[string]string) - for email, did := range emailToDid { + for email, did := range emailToDidMap { if didOrHandle, ok := didHandleMap[did]; ok { emailToDidOrHandle[email] = didOrHandle } } return emailToDidOrHandle +} + +func verifiedObjectCommits(r *Repo, emailToDid map[string]string, commits []*object.Commit) (map[string]bool, error) { + ndCommits := []types.NiceDiff{} + for _, commit := range commits { + ndCommits = append(ndCommits, types.ObjectCommitToNiceDiff(commit)) + } + return verifiedCommits(r, emailToDid, ndCommits) +} + +func verifiedCommits(r *Repo, emailToDid map[string]string, ndCommits []types.NiceDiff) (map[string]bool, error) { + hashToVerified := make(map[string]bool) + + didPubkeyCache := make(map[string][]db.PublicKey) + + for _, commit := range ndCommits { + c := commit.Commit + + committerEmail := c.Committer.Email + if did, exists := emailToDid[committerEmail]; exists { + // check if we've already fetched public keys for this did + pubKeys, ok := didPubkeyCache[did] + if !ok { + // fetch and cache public keys + keys, err := db.GetPublicKeysForDid(r.db, did) + if err != nil { + log.Printf("failed to fetch pubkey for %s: %v", committerEmail, err) + continue + } + pubKeys = keys + didPubkeyCache[did] = pubKeys + } + + verified := false + + // try to verify with any associated pubkeys + for _, pk := range pubKeys { + if _, ok := crypto.VerifyCommitSignature(pk.Key, commit); ok { + verified = true + break + } + } + + hashToVerified[c.This] = verified + } + } + + return hashToVerified, nil } func randomString(n int) string { diff --git a/appview/settings/settings.go b/appview/settings/settings.go --- a/appview/settings/settings.go +++ b/appview/settings/settings.go @@ -58,7 +58,7 @@ func (s *Settings) settings(w http.ResponseWriter, r *http.Request) { user := s.OAuth.GetUser(r) - pubKeys, err := db.GetPublicKeys(s.Db, user.Did) + pubKeys, err := db.GetPublicKeysForDid(s.Db, user.Did) if err != nil { log.Println(err) } diff --git a/appview/state/state.go b/appview/state/state.go --- a/appview/state/state.go +++ b/appview/state/state.go @@ -219,7 +219,7 @@ return } - pubKeys, err := db.GetPublicKeys(s.db, id.DID.String()) + pubKeys, err := db.GetPublicKeysForDid(s.db, id.DID.String()) if err != nil { w.WriteHeader(http.StatusNotFound) return diff --git a/appview/pages/templates/repo/commit.html b/appview/pages/templates/repo/commit.html --- a/appview/pages/templates/repo/commit.html +++ b/appview/pages/templates/repo/commit.html @@ -3,7 +3,7 @@ {{ define "extrameta" }} {{ $title := printf "commit %s · %s" .Diff.Commit.This .RepoInfo.FullName }} {{ $url := printf "https://tangled.sh/%s/commit/%s" .RepoInfo.FullName .Diff.Commit.This }} - + {{ template "repo/fragments/og" (dict "RepoInfo" .RepoInfo "Title" $title "Url" $url) }} {{ end }} @@ -24,7 +24,7 @@ -
+

{{ $didOrHandle := index $.EmailToDidOrHandle $commit.Author.Email }} @@ -45,6 +45,15 @@ {{ slice $commit.Parent 0 8 }} {{ end }}

+ + {{ if .Verified }} + +
+ {{ i "shield-check" "w-4 h-4" }} + verified +
+
+ {{ end }}
diff --git a/appview/pages/templates/repo/index.html b/appview/pages/templates/repo/index.html --- a/appview/pages/templates/repo/index.html +++ b/appview/pages/templates/repo/index.html @@ -222,14 +222,22 @@
-
- - {{ slice .Hash.String 0 8 }} +
+ {{ $verified := false }} + {{ $verified = index $.VerifiedCommits .Hash.String }} + {{ $hashStyle := "text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-900" }} + {{ if $verified }} + {{ $hashStyle = "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 px-2 rounded" }} + {{ end }} + + + {{ slice .Hash.String 0 8 }} + {{ if $verified }} + {{ i "shield-check" "w-3 h-3" }} + {{ end }} + + diff --git a/appview/pages/templates/repo/log.html b/appview/pages/templates/repo/log.html --- a/appview/pages/templates/repo/log.html +++ b/appview/pages/templates/repo/log.html @@ -35,9 +35,20 @@ {{ $commit.Author.Name }} {{ end }} - - {{ slice $commit.Hash.String 0 8 }} -
+ + {{ $verified := false }} + {{ $verified = index $.VerifiedCommits $commit.Hash.String }} + {{ $hashStyle := "text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-900" }} + {{ if $verified }} + {{ $hashStyle = "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 px-2 rounded" }} + {{ end }} + + {{ slice $commit.Hash.String 0 8 }} + {{ if $verified }} + {{ i "shield-check" "w-4 h-4" }} + {{ end }} + +