Update asset verification and user experience
- 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:
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user