forked from tangled.org/core
this repo has no description

appview: repo: show verified commits

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

Changed files
+185 -32
.air
appview
types
+2 -2
.air/knotserver.toml
···
[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 = [""]
+3 -3
appview/db/pubkeys.go
···
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
···
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
···
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)
+3
appview/pages/pages.go
···
HTMLReadme template.HTML
Raw bool
EmailToDidOrHandle map[string]string
+
VerifiedCommits map[string]bool
Languages *types.RepoLanguageResponse
types.RepoIndexResponse
}
···
types.RepoLogResponse
Active string
EmailToDidOrHandle map[string]string
+
VerifiedCommits map[string]bool
}
func (p *Pages) RepoLog(w io.Writer, params RepoLogParams) error {
···
RepoInfo repoinfo.RepoInfo
Active string
EmailToDidOrHandle map[string]string
+
Verified bool
types.RepoCommitResponse
}
+11 -2
appview/pages/templates/repo/commit.html
···
{{ define "extrameta" }}
{{ $title := printf "commit %s &middot; %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 }}
···
</div>
</div>
-
<div class="flex items-center">
+
<div class="flex items-center space-x-2">
<p class="text-sm text-gray-500 dark:text-gray-300">
{{ $didOrHandle := index $.EmailToDidOrHandle $commit.Author.Email }}
···
<a href="/{{ $repo }}/commit/{{ $commit.Parent }}" class="no-underline hover:underline text-gray-500 dark:text-gray-300">{{ slice $commit.Parent 0 8 }}</a>
{{ end }}
</p>
+
+
{{ if .Verified }}
+
<span class="text-sm bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 px-2 py-1 rounded">
+
<div class="flex items-center gap-2">
+
{{ i "shield-check" "w-4 h-4" }}
+
verified
+
</div>
+
</span>
+
{{ end }}
</div>
</section>
+16 -8
appview/pages/templates/repo/index.html
···
</div>
</div>
-
<div class="text-xs text-gray-500 dark:text-gray-400">
-
<span class="font-mono">
-
<a
-
href="/{{ $.RepoInfo.FullName }}/commit/{{ .Hash.String }}"
-
class="text-gray-500 dark:text-gray-400 no-underline hover:underline"
-
>{{ slice .Hash.String 0 8 }}</a
-
></span
-
>
+
<div class="text-xs mt-2 text-gray-500 dark:text-gray-400 flex items-center">
+
{{ $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 }}
+
<span class="font-mono">
+
<a href="/{{ $.RepoInfo.FullName }}/commit/{{ .Hash.String }}"
+
class="no-underline hover:underline {{ $hashStyle }} px-2 py-1 rounded flex items-center gap-2">
+
{{ slice .Hash.String 0 8 }}
+
{{ if $verified }}
+
{{ i "shield-check" "w-3 h-3" }}
+
{{ end }}
+
</a>
+
</span>
<span
class="mx-2 before:content-['·'] before:select-none"
></span>
+26 -6
appview/pages/templates/repo/log.html
···
<a href="mailto:{{ $commit.Author.Email }}" class="text-gray-700 dark:text-gray-300 no-underline hover:underline">{{ $commit.Author.Name }}</a>
{{ end }}
</td>
-
<td class=" py-3 align-top font-mono flex items-end">
-
<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>
-
<div class="inline-flex">
+
<td class="py-3 align-top font-mono flex items-center">
+
{{ $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 }}
+
<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">
+
{{ slice $commit.Hash.String 0 8 }}
+
{{ if $verified }}
+
{{ i "shield-check" "w-4 h-4" }}
+
{{ end }}
+
</a>
+
<div class="{{ if not $verified }} ml-6 {{ end }}inline-flex">
<button class="p-1 mx-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
title="Copy SHA"
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)">
···
<!-- mobile view (visible only on small screens) -->
<div class="md:hidden">
{{ range $index, $commit := .Commits }}
-
<div class="relative p-2 {{ if ne $index (sub (len $.Commits) 1) }}border-b border-gray-200 dark:border-gray-700{{ end }}">
+
<div class="relative p-2 mb-2 {{ if ne $index (sub (len $.Commits) 1) }}border-b border-gray-200 dark:border-gray-700{{ end }}">
<div id="commit-message">
{{ $messageParts := splitN $commit.Message "\n\n" 2 }}
<div class="text-base cursor-pointer">
···
</div>
</div>
-
<div class="text-xs text-gray-500 dark:text-gray-400">
+
<div class="text-xs mt-2 text-gray-500 dark:text-gray-400 flex items-center">
+
{{ $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 }}
<span class="font-mono">
<a href="/{{ $.RepoInfo.FullName }}/commit/{{ $commit.Hash.String }}"
-
class="text-gray-500 dark:text-gray-400 no-underline hover:underline">
+
class="no-underline hover:underline {{ $hashStyle }} px-2 py-1 rounded flex items-center gap-2">
{{ slice $commit.Hash.String 0 8 }}
+
{{ if $verified }}
+
{{ i "shield-check" "w-3 h-3" }}
+
{{ end }}
</a>
</span>
<span class="mx-2 before:content-['·'] before:select-none"></span>
+36 -3
appview/repo/repo.go
···
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)
···
TagsTrunc: tagsTrunc,
ForkInfo: forkInfo,
BranchesTrunc: branchesTrunc,
-
EmailToDidOrHandle: EmailToDidOrHandle(rp, emails),
+
EmailToDidOrHandle: emailToDidOrHandle(rp, emailToDidMap),
+
VerifiedCommits: vc,
Languages: repoLanguages,
})
return
···
}
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
}
···
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
}
+56 -6
appview/repo/repo_util.go
···
"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 {
···
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)
···
// 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 {
+1 -1
appview/settings/settings.go
···
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)
}
+1 -1
appview/state/state.go
···
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
+30
types/diff.go
···
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
+
}