Monorepo for Tangled tangled.org

appview: repo: show verified commits

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.sh>

anirudh.fi a492f6aa cb4cce7a

verified
Changed files
+185 -32
.air
appview
types
+2 -2
.air/knotserver.toml
··· 1 1 [build] 2 - cmd = 'go build -ldflags "-X tangled.sh/tangled.sh/core/knotserver.version=$(git describe --tags --long)" -o .bin/knot ./cmd/knotserver/main.go' 3 - bin = ".bin/knot" 2 + cmd = 'go build -ldflags "-X tangled.sh/tangled.sh/core/knotserver.version=$(git describe --tags --long)" -o .bin/knot ./cmd/knot/' 3 + bin = ".bin/knot server" 4 4 root = "." 5 5 6 6 exclude_regex = [""]
+3 -3
appview/db/pubkeys.go
··· 15 15 16 16 func DeletePublicKey(e Execer, did, name, key string) error { 17 17 _, err := e.Exec(` 18 - delete from public_keys 18 + delete from public_keys 19 19 where did = ? and name = ? and key = ?`, 20 20 did, name, key) 21 21 return err ··· 23 23 24 24 func DeletePublicKeyByRkey(e Execer, did, rkey string) error { 25 25 _, err := e.Exec(` 26 - delete from public_keys 26 + delete from public_keys 27 27 where did = ? and rkey = ?`, 28 28 did, rkey) 29 29 return err ··· 75 75 return keys, nil 76 76 } 77 77 78 - func GetPublicKeys(e Execer, did string) ([]PublicKey, error) { 78 + func GetPublicKeysForDid(e Execer, did string) ([]PublicKey, error) { 79 79 var keys []PublicKey 80 80 81 81 rows, err := e.Query(`select did, key, name, rkey, created from public_keys where did = ?`, did)
+3
appview/pages/pages.go
··· 414 414 HTMLReadme template.HTML 415 415 Raw bool 416 416 EmailToDidOrHandle map[string]string 417 + VerifiedCommits map[string]bool 417 418 Languages *types.RepoLanguageResponse 418 419 types.RepoIndexResponse 419 420 } ··· 452 453 types.RepoLogResponse 453 454 Active string 454 455 EmailToDidOrHandle map[string]string 456 + VerifiedCommits map[string]bool 455 457 } 456 458 457 459 func (p *Pages) RepoLog(w io.Writer, params RepoLogParams) error { ··· 464 466 RepoInfo repoinfo.RepoInfo 465 467 Active string 466 468 EmailToDidOrHandle map[string]string 469 + Verified bool 467 470 468 471 types.RepoCommitResponse 469 472 }
+11 -2
appview/pages/templates/repo/commit.html
··· 3 3 {{ define "extrameta" }} 4 4 {{ $title := printf "commit %s &middot; %s" .Diff.Commit.This .RepoInfo.FullName }} 5 5 {{ $url := printf "https://tangled.sh/%s/commit/%s" .RepoInfo.FullName .Diff.Commit.This }} 6 - 6 + 7 7 {{ template "repo/fragments/og" (dict "RepoInfo" .RepoInfo "Title" $title "Url" $url) }} 8 8 {{ end }} 9 9 ··· 24 24 </div> 25 25 </div> 26 26 27 - <div class="flex items-center"> 27 + <div class="flex items-center space-x-2"> 28 28 <p class="text-sm text-gray-500 dark:text-gray-300"> 29 29 {{ $didOrHandle := index $.EmailToDidOrHandle $commit.Author.Email }} 30 30 ··· 45 45 <a href="/{{ $repo }}/commit/{{ $commit.Parent }}" class="no-underline hover:underline text-gray-500 dark:text-gray-300">{{ slice $commit.Parent 0 8 }}</a> 46 46 {{ end }} 47 47 </p> 48 + 49 + {{ if .Verified }} 50 + <span class="text-sm bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 px-2 py-1 rounded"> 51 + <div class="flex items-center gap-2"> 52 + {{ i "shield-check" "w-4 h-4" }} 53 + verified 54 + </div> 55 + </span> 56 + {{ end }} 48 57 </div> 49 58 50 59 </section>
+16 -8
appview/pages/templates/repo/index.html
··· 222 222 </div> 223 223 </div> 224 224 225 - <div class="text-xs text-gray-500 dark:text-gray-400"> 226 - <span class="font-mono"> 227 - <a 228 - href="/{{ $.RepoInfo.FullName }}/commit/{{ .Hash.String }}" 229 - class="text-gray-500 dark:text-gray-400 no-underline hover:underline" 230 - >{{ slice .Hash.String 0 8 }}</a 231 - ></span 232 - > 225 + <div class="text-xs mt-2 text-gray-500 dark:text-gray-400 flex items-center"> 226 + {{ $verified := false }} 227 + {{ $verified = index $.VerifiedCommits .Hash.String }} 228 + {{ $hashStyle := "text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-900" }} 229 + {{ if $verified }} 230 + {{ $hashStyle = "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 px-2 rounded" }} 231 + {{ end }} 232 + <span class="font-mono"> 233 + <a href="/{{ $.RepoInfo.FullName }}/commit/{{ .Hash.String }}" 234 + class="no-underline hover:underline {{ $hashStyle }} px-2 py-1 rounded flex items-center gap-2"> 235 + {{ slice .Hash.String 0 8 }} 236 + {{ if $verified }} 237 + {{ i "shield-check" "w-3 h-3" }} 238 + {{ end }} 239 + </a> 240 + </span> 233 241 <span 234 242 class="mx-2 before:content-['·'] before:select-none" 235 243 ></span>
+26 -6
appview/pages/templates/repo/log.html
··· 35 35 <a href="mailto:{{ $commit.Author.Email }}" class="text-gray-700 dark:text-gray-300 no-underline hover:underline">{{ $commit.Author.Name }}</a> 36 36 {{ end }} 37 37 </td> 38 - <td class=" py-3 align-top font-mono flex items-end"> 39 - <a href="/{{ $.RepoInfo.FullName }}/commit/{{ $commit.Hash.String }}" class="text-gray-700 dark:text-gray-300 no-underline hover:underline">{{ slice $commit.Hash.String 0 8 }}</a> 40 - <div class="inline-flex"> 38 + <td class="py-3 align-top font-mono flex items-center"> 39 + {{ $verified := false }} 40 + {{ $verified = index $.VerifiedCommits $commit.Hash.String }} 41 + {{ $hashStyle := "text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-900" }} 42 + {{ if $verified }} 43 + {{ $hashStyle = "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 px-2 rounded" }} 44 + {{ end }} 45 + <a href="/{{ $.RepoInfo.FullName }}/commit/{{ $commit.Hash.String }}" class="no-underline hover:underline {{ $hashStyle }} px-2 py-1/2 rounded flex items-center gap-2"> 46 + {{ slice $commit.Hash.String 0 8 }} 47 + {{ if $verified }} 48 + {{ i "shield-check" "w-4 h-4" }} 49 + {{ end }} 50 + </a> 51 + <div class="{{ if not $verified }} ml-6 {{ end }}inline-flex"> 41 52 <button class="p-1 mx-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded" 42 53 title="Copy SHA" 43 54 onclick="navigator.clipboard.writeText('{{ $commit.Hash.String }}'); this.innerHTML=`{{ i "copy-check" "w-4 h-4" }}`; setTimeout(() => this.innerHTML=`{{ i "copy" "w-4 h-4" }}`, 1500)"> ··· 80 91 <!-- mobile view (visible only on small screens) --> 81 92 <div class="md:hidden"> 82 93 {{ range $index, $commit := .Commits }} 83 - <div class="relative p-2 {{ if ne $index (sub (len $.Commits) 1) }}border-b border-gray-200 dark:border-gray-700{{ end }}"> 94 + <div class="relative p-2 mb-2 {{ if ne $index (sub (len $.Commits) 1) }}border-b border-gray-200 dark:border-gray-700{{ end }}"> 84 95 <div id="commit-message"> 85 96 {{ $messageParts := splitN $commit.Message "\n\n" 2 }} 86 97 <div class="text-base cursor-pointer"> ··· 125 136 </div> 126 137 </div> 127 138 128 - <div class="text-xs text-gray-500 dark:text-gray-400"> 139 + <div class="text-xs mt-2 text-gray-500 dark:text-gray-400 flex items-center"> 140 + {{ $verified := false }} 141 + {{ $verified = index $.VerifiedCommits $commit.Hash.String }} 142 + {{ $hashStyle := "text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-900" }} 143 + {{ if $verified }} 144 + {{ $hashStyle = "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 px-2 rounded" }} 145 + {{ end }} 129 146 <span class="font-mono"> 130 147 <a href="/{{ $.RepoInfo.FullName }}/commit/{{ $commit.Hash.String }}" 131 - class="text-gray-500 dark:text-gray-400 no-underline hover:underline"> 148 + class="no-underline hover:underline {{ $hashStyle }} px-2 py-1 rounded flex items-center gap-2"> 132 149 {{ slice $commit.Hash.String 0 8 }} 150 + {{ if $verified }} 151 + {{ i "shield-check" "w-3 h-3" }} 152 + {{ end }} 133 153 </a> 134 154 </span> 135 155 <span class="mx-2 before:content-['·'] before:select-none"></span>
+36 -3
appview/repo/repo.go
··· 138 138 branchesTrunc := result.Branches[:min(branchCount, len(result.Branches))] 139 139 140 140 emails := uniqueEmails(commitsTrunc) 141 + emailToDidMap, err := db.GetEmailToDid(rp.db, emails, true) 142 + if err != nil { 143 + log.Println("failed to get email to did map", err) 144 + } 145 + 146 + vc, err := verifiedObjectCommits(rp, emailToDidMap, commitsTrunc) 147 + if err != nil { 148 + log.Println(err) 149 + } 141 150 142 151 user := rp.oauth.GetUser(r) 143 152 repoInfo := f.RepoInfo(user) ··· 178 187 TagsTrunc: tagsTrunc, 179 188 ForkInfo: forkInfo, 180 189 BranchesTrunc: branchesTrunc, 181 - EmailToDidOrHandle: EmailToDidOrHandle(rp, emails), 190 + EmailToDidOrHandle: emailToDidOrHandle(rp, emailToDidMap), 191 + VerifiedCommits: vc, 182 192 Languages: repoLanguages, 183 193 }) 184 194 return ··· 293 303 } 294 304 295 305 user := rp.oauth.GetUser(r) 306 + 307 + emailToDidMap, err := db.GetEmailToDid(rp.db, uniqueEmails(repolog.Commits), true) 308 + if err != nil { 309 + log.Println("failed to fetch email to did mapping", err) 310 + } 311 + 312 + vc, err := verifiedObjectCommits(rp, emailToDidMap, repolog.Commits) 313 + if err != nil { 314 + log.Println(err) 315 + } 316 + 296 317 rp.pages.RepoLog(w, pages.RepoLogParams{ 297 318 LoggedInUser: user, 298 319 TagMap: tagMap, 299 320 RepoInfo: f.RepoInfo(user), 300 321 RepoLogResponse: *repolog, 301 - EmailToDidOrHandle: EmailToDidOrHandle(rp, uniqueEmails(repolog.Commits)), 322 + EmailToDidOrHandle: emailToDidOrHandle(rp, emailToDidMap), 323 + VerifiedCommits: vc, 302 324 }) 303 325 return 304 326 } ··· 438 460 return 439 461 } 440 462 463 + emailToDidMap, err := db.GetEmailToDid(rp.db, []string{result.Diff.Commit.Author.Email}, true) 464 + if err != nil { 465 + log.Println("failed to get email to did mapping:", err) 466 + } 467 + 468 + vc, err := verifiedCommits(rp, emailToDidMap, []types.NiceDiff{*result.Diff}) 469 + if err != nil { 470 + log.Println(err) 471 + } 472 + 441 473 user := rp.oauth.GetUser(r) 442 474 rp.pages.RepoCommit(w, pages.RepoCommitParams{ 443 475 LoggedInUser: user, 444 476 RepoInfo: f.RepoInfo(user), 445 477 RepoCommitResponse: result, 446 - EmailToDidOrHandle: EmailToDidOrHandle(rp, []string{result.Diff.Commit.Author.Email}), 478 + EmailToDidOrHandle: emailToDidOrHandle(rp, emailToDidMap), 479 + Verified: vc[result.Diff.Commit.This], 447 480 }) 448 481 return 449 482 }
+56 -6
appview/repo/repo_util.go
··· 9 9 10 10 "github.com/go-git/go-git/v5/plumbing/object" 11 11 "tangled.sh/tangled.sh/core/appview/db" 12 + "tangled.sh/tangled.sh/core/crypto" 13 + "tangled.sh/tangled.sh/core/types" 12 14 ) 13 15 14 16 func uniqueEmails(commits []*object.Commit) []string { ··· 56 58 return 57 59 } 58 60 59 - func EmailToDidOrHandle(r *Repo, emails []string) map[string]string { 60 - emailToDid, err := db.GetEmailToDid(r.db, emails, true) // only get verified emails for mapping 61 - if err != nil { 62 - log.Printf("error fetching dids for emails: %v", err) 61 + // emailToDidOrHandle takes an emailToDidMap from db.GetEmailToDid 62 + // and resolves all dids to handles and returns a new map[string]string 63 + func emailToDidOrHandle(r *Repo, emailToDidMap map[string]string) map[string]string { 64 + if emailToDidMap == nil { 63 65 return nil 64 66 } 65 67 66 68 var dids []string 67 - for _, v := range emailToDid { 69 + for _, v := range emailToDidMap { 68 70 dids = append(dids, v) 69 71 } 70 72 resolvedIdents := r.idResolver.ResolveIdents(context.Background(), dids) ··· 80 82 81 83 // Create map of email to didOrHandle for commit display 82 84 emailToDidOrHandle := make(map[string]string) 83 - for email, did := range emailToDid { 85 + for email, did := range emailToDidMap { 84 86 if didOrHandle, ok := didHandleMap[did]; ok { 85 87 emailToDidOrHandle[email] = didOrHandle 86 88 } 87 89 } 88 90 89 91 return emailToDidOrHandle 92 + } 93 + 94 + func verifiedObjectCommits(r *Repo, emailToDid map[string]string, commits []*object.Commit) (map[string]bool, error) { 95 + ndCommits := []types.NiceDiff{} 96 + for _, commit := range commits { 97 + ndCommits = append(ndCommits, types.ObjectCommitToNiceDiff(commit)) 98 + } 99 + return verifiedCommits(r, emailToDid, ndCommits) 100 + } 101 + 102 + func verifiedCommits(r *Repo, emailToDid map[string]string, ndCommits []types.NiceDiff) (map[string]bool, error) { 103 + hashToVerified := make(map[string]bool) 104 + 105 + didPubkeyCache := make(map[string][]db.PublicKey) 106 + 107 + for _, commit := range ndCommits { 108 + c := commit.Commit 109 + 110 + committerEmail := c.Committer.Email 111 + if did, exists := emailToDid[committerEmail]; exists { 112 + // check if we've already fetched public keys for this did 113 + pubKeys, ok := didPubkeyCache[did] 114 + if !ok { 115 + // fetch and cache public keys 116 + keys, err := db.GetPublicKeysForDid(r.db, did) 117 + if err != nil { 118 + log.Printf("failed to fetch pubkey for %s: %v", committerEmail, err) 119 + continue 120 + } 121 + pubKeys = keys 122 + didPubkeyCache[did] = pubKeys 123 + } 124 + 125 + verified := false 126 + 127 + // try to verify with any associated pubkeys 128 + for _, pk := range pubKeys { 129 + if _, ok := crypto.VerifyCommitSignature(pk.Key, commit); ok { 130 + verified = true 131 + break 132 + } 133 + } 134 + 135 + hashToVerified[c.This] = verified 136 + } 137 + } 138 + 139 + return hashToVerified, nil 90 140 } 91 141 92 142 func randomString(n int) string {
+1 -1
appview/settings/settings.go
··· 58 58 59 59 func (s *Settings) settings(w http.ResponseWriter, r *http.Request) { 60 60 user := s.OAuth.GetUser(r) 61 - pubKeys, err := db.GetPublicKeys(s.Db, user.Did) 61 + pubKeys, err := db.GetPublicKeysForDid(s.Db, user.Did) 62 62 if err != nil { 63 63 log.Println(err) 64 64 }
+1 -1
appview/state/state.go
··· 219 219 return 220 220 } 221 221 222 - pubKeys, err := db.GetPublicKeys(s.db, id.DID.String()) 222 + pubKeys, err := db.GetPublicKeysForDid(s.db, id.DID.String()) 223 223 if err != nil { 224 224 w.WriteHeader(http.StatusNotFound) 225 225 return
+30
types/diff.go
··· 77 77 78 78 return files 79 79 } 80 + 81 + // ObjectCommitToNiceDiff is a compatibility function to convert a 82 + // commit object into a NiceDiff structure. 83 + func ObjectCommitToNiceDiff(c *object.Commit) NiceDiff { 84 + var niceDiff NiceDiff 85 + 86 + // set commit information 87 + niceDiff.Commit.Message = c.Message 88 + niceDiff.Commit.Author = c.Author 89 + niceDiff.Commit.This = c.Hash.String() 90 + niceDiff.Commit.Committer = c.Committer 91 + niceDiff.Commit.Tree = c.TreeHash.String() 92 + niceDiff.Commit.PGPSignature = c.PGPSignature 93 + 94 + changeId, ok := c.ExtraHeaders["change-id"] 95 + if ok { 96 + niceDiff.Commit.ChangedId = string(changeId) 97 + } 98 + 99 + // set parent hash if available 100 + if len(c.ParentHashes) > 0 { 101 + niceDiff.Commit.Parent = c.ParentHashes[0].String() 102 + } 103 + 104 + // XXX: Stats and Diff fields are typically populated 105 + // after fetching the actual diff information, which isn't 106 + // directly available in the commit object itself. 107 + 108 + return niceDiff 109 + }