Update asset verification and user experience
All checks were successful
renovate / renovate (push) Successful in 2m8s
CI / build (push) Successful in 10m24s

- Added SRI hash injection during frontend build to improve security.
- Updated ESLint configuration to include 'navigator' as a global variable.
- Introduced a new `settingsStore` to manage user preferences for asset verification.
- Enhanced `SoftwareCard` and `VerificationModal` components to display contributor information and security checks.
- Updated `verificationStore` to handle expanded toast notifications for detailed verification steps.
- Implemented a new `CodeBlock` component for displaying code snippets with syntax highlighting.
- Improved API documentation and added new endpoints for fetching software and asset details.
This commit is contained in:
2025-12-27 16:29:05 -06:00
parent 3605710875
commit 4c60e3cf4a
19 changed files with 1209 additions and 97 deletions

View File

@@ -2,15 +2,132 @@ package gitea
import (
"bufio"
"crypto/tls"
"encoding/json"
"fmt"
"net"
"net/http"
"net/url"
"strings"
"time"
"software-station/internal/models"
)
func CheckSourceSecurity(serverURL, token, owner, repo string) *models.SourceSecurity {
u, err := url.Parse(serverURL)
if err != nil {
return nil
}
domain := u.Hostname()
security := &models.SourceSecurity{
Domain: domain,
}
// TLS Check
conf := &tls.Config{
InsecureSkipVerify: false,
MinVersion: tls.VersionTLS12,
}
// Use port 443 for HTTPS domains
port := "443"
if u.Port() != "" {
port = u.Port()
}
dialer := &net.Dialer{Timeout: 5 * time.Second}
conn, err := tls.DialWithDialer(dialer, "tcp", net.JoinHostPort(domain, port), conf)
if err == nil {
security.TLSValid = true
_ = conn.Close()
}
return security
}
func FetchUserGPGKeys(server, token, username string) ([]string, error) {
url := fmt.Sprintf("%s/api/v1/users/%s/gpg_keys", server, username)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
if token != "" {
req.Header.Set("Authorization", "token "+token)
}
client := &http.Client{Timeout: DefaultTimeout}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("gitea gpg api returned status %d", resp.StatusCode)
}
var giteaKeys []struct {
PublicKey string `json:"public_key"`
}
if err := json.NewDecoder(resp.Body).Decode(&giteaKeys); err != nil {
return nil, err
}
keys := make([]string, len(giteaKeys))
for i, k := range giteaKeys {
keys[i] = k.PublicKey
}
return keys, nil
}
func FetchContributors(server, token, owner, repo string) ([]models.Contributor, error) {
url := fmt.Sprintf("%s%s/%s/%s%s", server, RepoAPIPath, owner, repo, ContributorsSuffix)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
if token != "" {
req.Header.Set("Authorization", "token "+token)
}
client := &http.Client{Timeout: DefaultTimeout}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("gitea api returned status %d", resp.StatusCode)
}
var giteaContributors []struct {
Username string `json:"username"`
AvatarURL string `json:"avatar_url"`
}
if err := json.NewDecoder(resp.Body).Decode(&giteaContributors); err != nil {
return nil, err
}
contributors := make([]models.Contributor, len(giteaContributors))
for i, c := range giteaContributors {
keys, _ := FetchUserGPGKeys(server, token, c.Username)
contributors[i] = models.Contributor{
Username: c.Username,
AvatarURL: c.AvatarURL,
GPGKeys: keys,
}
}
return contributors, nil
}
func DetectOS(filename string) string {
lower := strings.ToLower(filename)
@@ -215,6 +332,9 @@ func FetchReleases(server, token, owner, repo string) ([]models.Release, error)
return nil, err
}
contributors, _ := FetchContributors(server, token, owner, repo)
security := CheckSourceSecurity(server, token, owner, repo)
var releases []models.Release
for _, gr := range giteaReleases {
var assets []models.Asset
@@ -246,10 +366,12 @@ func FetchReleases(server, token, owner, repo string) ([]models.Release, error)
}
releases = append(releases, models.Release{
TagName: gr.TagName,
Body: gr.Body,
CreatedAt: gr.CreatedAt,
Assets: assets,
TagName: gr.TagName,
Body: gr.Body,
CreatedAt: gr.CreatedAt,
Assets: assets,
Contributors: contributors,
Security: security,
})
}

View File

@@ -3,7 +3,8 @@ package gitea
import "time"
const (
DefaultTimeout = 10 * time.Second
RepoAPIPath = "/api/v1/repos"
ReleasesSuffix = "/releases"
DefaultTimeout = 10 * time.Second
RepoAPIPath = "/api/v1/repos"
ReleasesSuffix = "/releases"
ContributorsSuffix = "/contributors"
)

View File

@@ -11,11 +11,24 @@ type Asset struct {
IsSBOM bool `json:"is_sbom"`
}
type Contributor struct {
Username string `json:"username"`
AvatarURL string `json:"avatar_url"`
GPGKeys []string `json:"gpg_keys,omitempty"`
}
type SourceSecurity struct {
Domain string `json:"domain"`
TLSValid bool `json:"tls_valid"`
}
type Release struct {
TagName string `json:"tag_name"`
Body string `json:"body,omitempty"`
CreatedAt time.Time `json:"created_at"`
Assets []Asset `json:"assets"`
TagName string `json:"tag_name"`
Body string `json:"body,omitempty"`
CreatedAt time.Time `json:"created_at"`
Assets []Asset `json:"assets"`
Contributors []Contributor `json:"contributors,omitempty"`
Security *SourceSecurity `json:"security,omitempty"`
}
type Software struct {