Cybersecurity-Projects/PROJECTS/intermediate/security-news-scraper/internal/setup/credentials.go

146 lines
2.9 KiB
Go

// ©AngelaMos | 2026
// credentials.go
package setup
import (
"bufio"
"fmt"
"os"
"sort"
"strings"
)
const (
EnvProvider = "NADEZHDA_AI_PROVIDER"
EnvQwenURL = "NADEZHDA_QWEN_BASE_URL"
commentMark = "#"
kvSeparator = "="
credentialKeyPrefix = "NADEZHDA_"
credentialKeySuffix = "_API_KEY"
tmpSuffix = ".tmp"
credentialsHeader = "# ©AngelaMos | 2026\n# nadezhda credentials — auto-managed by `nadezhda ai`; keep private, do not commit\n\n"
)
func Load() error {
path, err := CredentialsPath()
if err != nil {
return err
}
kv, err := readCredentials(path)
if err != nil {
return err
}
for k, v := range kv {
if !allowedKey(k) {
continue
}
if _, set := os.LookupEnv(k); set {
continue
}
if err := os.Setenv(k, v); err != nil {
return fmt.Errorf("setup: set %s: %w", k, err)
}
}
return nil
}
func allowedKey(k string) bool {
return strings.HasPrefix(k, credentialKeyPrefix) || strings.HasSuffix(k, credentialKeySuffix)
}
func NonSecretEnviron() []string {
env := os.Environ()
out := make([]string, 0, len(env))
for _, e := range env {
if k, _, ok := strings.Cut(e, kvSeparator); ok && allowedKey(k) {
continue
}
out = append(out, e)
}
return out
}
func Save(updates map[string]string) error {
path, err := CredentialsPath()
if err != nil {
return err
}
dir, err := ConfigDir()
if err != nil {
return err
}
if err := os.MkdirAll(dir, dirPerm); err != nil {
return fmt.Errorf("setup: create config dir: %w", err)
}
kv, err := readCredentials(path)
if err != nil {
return err
}
for k, v := range updates {
kv[k] = v
}
return writeCredentials(path, kv)
}
func readCredentials(path string) (map[string]string, error) {
f, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
return map[string]string{}, nil
}
return nil, fmt.Errorf("setup: read credentials: %w", err)
}
defer f.Close()
kv := map[string]string{}
sc := bufio.NewScanner(f)
for sc.Scan() {
line := strings.TrimSpace(sc.Text())
if line == "" || strings.HasPrefix(line, commentMark) {
continue
}
k, v, ok := strings.Cut(line, kvSeparator)
if !ok {
continue
}
key := strings.TrimSpace(k)
if key == "" {
continue
}
kv[key] = strings.TrimSpace(v)
}
return kv, sc.Err()
}
func writeCredentials(path string, kv map[string]string) error {
keys := make([]string, 0, len(kv))
for k := range kv {
keys = append(keys, k)
}
sort.Strings(keys)
var b strings.Builder
b.WriteString(credentialsHeader)
for _, k := range keys {
fmt.Fprintf(&b, "%s=%s\n", k, kv[k])
}
tmp := path + tmpSuffix
if err := os.WriteFile(tmp, []byte(b.String()), filePerm); err != nil {
return fmt.Errorf("setup: write credentials: %w", err)
}
if err := os.Chmod(tmp, filePerm); err != nil {
os.Remove(tmp)
return fmt.Errorf("setup: secure credentials: %w", err)
}
if err := os.Rename(tmp, path); err != nil {
os.Remove(tmp)
return fmt.Errorf("setup: replace credentials: %w", err)
}
return nil
}