Signed-off-by: Hadi <[email protected]>
This commit is contained in:
Hadi
2026-08-27 00:05:08 +02:00
commit 624a723b16
29 changed files with 1989 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
package creds
import (
"fmt"
"strings"
)
type Entry struct {
Manufacturer string
Name string
Icon string
Tags []string
Version string
User string
Pass string
Comment string
}
func (e Entry) Title() string { return e.Name }
func (e Entry) Description() string { return fmt.Sprintf("%s %s", e.Manufacturer, e.Version) }
func (e Entry) FilterValue() string { return e.searchString() }
func (e Entry) searchString() string {
return strings.ToLower(strings.Join([]string{
e.Manufacturer, e.Name, e.Comment, strings.Join(e.Tags, " "),
}, " "))
}
func (e Entry) matches(tokens []string) bool {
s := e.searchString()
for _, t := range tokens {
if !strings.Contains(s, t) {
return false
}
}
return true
}
func queryTokens(query string) []string {
return strings.Fields(strings.ToLower(strings.TrimSpace(query)))
}
+104
View File
@@ -0,0 +1,104 @@
package creds
import (
"fmt"
"os"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
type manufacturerFile struct {
Name string `yaml:"name"`
Icon string `yaml:"icon"`
Tags []string `yaml:"tags"`
Entries []struct {
Name string `yaml:"name"`
Version string `yaml:"version"`
User string `yaml:"user"`
Pass string `yaml:"pass"`
Comment string `yaml:"comment"`
} `yaml:"entries"`
}
func LocalDataDir() string {
cacheDir, err := os.UserCacheDir()
if err != nil {
cacheDir = "."
}
return filepath.Join(cacheDir, "default-creds", "data")
}
func HasLocalData() bool {
entries, err := os.ReadDir(LocalDataDir())
if err != nil {
return false
}
for _, e := range entries {
if !e.IsDir() && strings.HasSuffix(e.Name(), ".yaml") {
return true
}
}
return false
}
func LoadLocal(dir string) ([]Entry, error) {
files, err := os.ReadDir(dir)
if err != nil {
return nil, fmt.Errorf("read local data dir: %w", err)
}
var entries []Entry
for _, f := range files {
if f.IsDir() || !strings.HasSuffix(f.Name(), ".yaml") {
continue
}
data, err := os.ReadFile(filepath.Join(dir, f.Name()))
if err != nil {
continue
}
var doc manufacturerFile
if err := yaml.Unmarshal(data, &doc); err != nil {
continue
}
if doc.Name == "" {
continue
}
for _, e := range doc.Entries {
entries = append(entries, Entry{
Manufacturer: doc.Name,
Name: e.Name,
Icon: doc.Icon,
Tags: doc.Tags,
Version: e.Version,
User: e.User,
Pass: e.Pass,
Comment: e.Comment,
})
}
}
return entries, nil
}
func Filter(entries []Entry, query string) []Entry {
tokens := queryTokens(query)
if len(tokens) == 0 {
return nil
}
var out []Entry
for _, e := range entries {
if e.matches(tokens) {
out = append(out, e)
}
}
return out
}
func LocalStats(entries []Entry) (manufacturers, credentials int) {
seen := make(map[string]bool)
for _, e := range entries {
seen[e.Manufacturer] = true
}
return len(seen), len(entries)
}
+30
View File
@@ -0,0 +1,30 @@
package creds
import "fmt"
type Mode string
const (
Auto Mode = "auto"
Online Mode = "online"
Local Mode = "local"
)
func ParseMode(s string) (Mode, error) {
switch Mode(s) {
case Auto, Online, Local:
return Mode(s), nil
default:
return "", fmt.Errorf("invalid mode %q: want \"auto\", \"online\" or \"local\"", s)
}
}
func Resolve(mode Mode) Mode {
if mode != Auto {
return mode
}
if HasLocalData() {
return Local
}
return Online
}
+91
View File
@@ -0,0 +1,91 @@
package creds
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
)
const httpTimeout = 8 * time.Second
type apiEntry struct {
Manufacturer string `json:"manufacturer"`
Name string `json:"name"`
Icon string `json:"icon"`
Tags []string `json:"tags"`
Version string `json:"version"`
User string `json:"user"`
Pass string `json:"pass"`
Comment string `json:"comment"`
}
type apiSearchResponse struct {
Results []apiEntry `json:"results"`
Pagination struct {
TotalResults int `json:"totalResults"`
} `json:"pagination"`
}
type apiStatsResponse struct {
TotalApps int `json:"totalApps"`
TotalPasswords int `json:"totalPasswords"`
}
func FetchOnline(apiURL, query string, size int) (results []Entry, total int, err error) {
u := apiURL + "/api/search?" + url.Values{
"q": {query},
"size": {strconv.Itoa(size)},
}.Encode()
var resp apiSearchResponse
if err := getJSON(u, &resp); err != nil {
return nil, 0, err
}
entries := make([]Entry, len(resp.Results))
for i, r := range resp.Results {
entries[i] = Entry{
Manufacturer: r.Manufacturer,
Name: r.Name,
Icon: r.Icon,
Tags: r.Tags,
Version: r.Version,
User: r.User,
Pass: r.Pass,
Comment: r.Comment,
}
}
return entries, resp.Pagination.TotalResults, nil
}
func FetchStats(apiURL string) (manufacturers, credentials int, err error) {
var resp apiStatsResponse
if err := getJSON(apiURL+"/api/stats", &resp); err != nil {
return 0, 0, err
}
return resp.TotalApps, resp.TotalPasswords, nil
}
func getJSON(u string, out any) error {
ctx, cancel := context.WithTimeout(context.Background(), httpTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("%s: unexpected status %s", u, resp.Status)
}
return json.NewDecoder(resp.Body).Decode(out)
}