mirror of
https://github.com/anotherhadi/default-creds-tui.git
synced 2026-10-05 10:58:25 +02:00
@@ -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)))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user