signature.go

  1package config
  2
  3import (
  4	"os"
  5	"path/filepath"
  6)
  7
  8// signatureFile returns the full path to the global signature file.
  9func signatureFile() (string, error) {
 10	dir, err := configDir()
 11	if err != nil {
 12		return "", err
 13	}
 14	return filepath.Join(dir, "signature.txt"), nil
 15}
 16
 17// accountSignatureFile returns the path to the per-account signature file.
 18func accountSignatureFile(accountID string) (string, error) {
 19	dir, err := configDir()
 20	if err != nil {
 21		return "", err
 22	}
 23	return filepath.Join(dir, "signatures", accountID+".txt"), nil
 24}
 25
 26// LoadSignature loads the signature from the global signature file.
 27func LoadSignature() (string, error) {
 28	path, err := signatureFile()
 29	if err != nil {
 30		return "", err
 31	}
 32	data, err := SecureReadFile(path)
 33	if err != nil {
 34		if os.IsNotExist(err) {
 35			return "", nil
 36		}
 37		return "", err
 38	}
 39	return string(data), nil
 40}
 41
 42// LoadRawAccountSignature loads the per-account signature if one exists,
 43// without falling back to the global signature.
 44func LoadRawAccountSignature(account *Account) (string, error) {
 45	if account == nil || account.ID == "" {
 46		return "", nil
 47	}
 48
 49	// Check for per-account signature file first
 50	path, err := accountSignatureFile(account.ID)
 51	if err != nil {
 52		return "", err
 53	}
 54	data, err := SecureReadFile(path)
 55	if err == nil && len(data) > 0 {
 56		return string(data), nil
 57	}
 58
 59	// Fall back to inline account signature
 60	if account.Signature != "" {
 61		return account.Signature, nil
 62	}
 63
 64	return "", nil
 65}
 66
 67// LoadSignatureForAccount loads the per-account signature if one exists,
 68// otherwise falls back to the global signature.
 69func LoadSignatureForAccount(account *Account) (string, error) {
 70	sig, err := LoadRawAccountSignature(account)
 71	if err == nil && sig != "" {
 72		return sig, nil
 73	}
 74	// Fall back to global signature
 75	return LoadSignature()
 76}
 77
 78// SaveSignature saves the signature to the global signature file.
 79func SaveSignature(signature string) error {
 80	path, err := signatureFile()
 81	if err != nil {
 82		return err
 83	}
 84	if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
 85		return err
 86	}
 87	return SecureWriteFile(path, []byte(signature), 0600)
 88}
 89
 90// SaveSignatureForAccount saves a per-account signature file.
 91func SaveSignatureForAccount(accountID, signature string) error {
 92	path, err := accountSignatureFile(accountID)
 93	if err != nil {
 94		return err
 95	}
 96	if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
 97		return err
 98	}
 99	if signature == "" {
100		// Remove the file to fall back to global
101		os.Remove(path) //nolint:errcheck,gosec
102		return nil
103	}
104	return SecureWriteFile(path, []byte(signature), 0600)
105}
106
107// HasSignature checks if a global signature file exists and is non-empty.
108func HasSignature() bool {
109	sig, err := LoadSignature()
110	if err != nil {
111		return false
112	}
113	return sig != ""
114}
115
116// HasAccountSignature checks if an account has its own signature (file or inline).
117func HasAccountSignature(account *Account) bool {
118	sig, err := LoadRawAccountSignature(account)
119	if err != nil {
120		return false
121	}
122	return sig != ""
123}