bluesky viewer in the terminal
1package config
2
3import (
4 "encoding/json"
5 "errors"
6 "os"
7)
8
9// Config represents the application configuration stored in ~/.skycli/.config.json
10// Tokens are encrypted at rest using AES-256-GCM
11type Config struct {
12 Session *SessionConfig `json:"session,omitempty"`
13}
14
15// SessionConfig holds the current session information with encrypted tokens
16type SessionConfig struct {
17 Handle string `json:"handle"`
18 Did string `json:"did"`
19 ServiceURL string `json:"serviceUrl"`
20 EncryptedAccess string `json:"encryptedAccessToken"`
21 EncryptedRefresh string `json:"encryptedRefreshToken"`
22 Email string `json:"email,omitempty"`
23}
24
25// Load reads and decrypts the configuration from ~/.skycli/.config.json
26// Returns a default empty config if the file doesn't exist
27func Load() (*Config, error) {
28 configPath, err := GetConfigFile()
29 if err != nil {
30 return nil, err
31 }
32
33 data, err := os.ReadFile(configPath)
34 if err != nil {
35 if errors.Is(err, os.ErrNotExist) {
36 return &Config{}, nil
37 }
38 return nil, &ConfigError{Op: "ReadFile", Err: err}
39 }
40
41 var cfg Config
42 if err := json.Unmarshal(data, &cfg); err != nil {
43 return nil, &ConfigError{Op: "Unmarshal", Err: err}
44 }
45
46 return &cfg, nil
47}
48
49// Save encrypts tokens and persists the configuration to ~/.skycli/.config.json
50// Creates the config directory if it doesn't exist
51func (c *Config) Save() error {
52 if err := EnsureConfigDir(); err != nil {
53 return err
54 }
55
56 configPath, err := GetConfigFile()
57 if err != nil {
58 return err
59 }
60
61 data, err := json.MarshalIndent(c, "", " ")
62 if err != nil {
63 return &ConfigError{Op: "Marshal", Err: err}
64 }
65
66 if err := os.WriteFile(configPath, data, 0600); err != nil {
67 return &ConfigError{Op: "WriteFile", Err: err}
68 }
69
70 return nil
71}
72
73// GetAccessToken decrypts and returns the access token
74func (s *SessionConfig) GetAccessToken() (string, error) {
75 if s == nil || s.EncryptedAccess == "" {
76 return "", nil
77 }
78 return DecryptToken(s.EncryptedAccess)
79}
80
81// GetRefreshToken decrypts and returns the refresh token
82func (s *SessionConfig) GetRefreshToken() (string, error) {
83 if s == nil || s.EncryptedRefresh == "" {
84 return "", nil
85 }
86 return DecryptToken(s.EncryptedRefresh)
87}
88
89// SetAccessToken encrypts and stores the access token
90func (s *SessionConfig) SetAccessToken(token string) error {
91 encrypted, err := EncryptToken(token)
92 if err != nil {
93 return err
94 }
95 s.EncryptedAccess = encrypted
96 return nil
97}
98
99// SetRefreshToken encrypts and stores the refresh token
100func (s *SessionConfig) SetRefreshToken(token string) error {
101 encrypted, err := EncryptToken(token)
102 if err != nil {
103 return err
104 }
105 s.EncryptedRefresh = encrypted
106 return nil
107}
108
109// ConfigError represents an error that occurred during config operations
110type ConfigError struct {
111 Op string
112 Err error
113}
114
115func (e *ConfigError) Error() string {
116 return "config." + e.Op + ": " + e.Err.Error()
117}
118
119func (e *ConfigError) Unwrap() error {
120 return e.Err
121}