config.go raw
1 package config
2
3 import (
4 "fmt"
5 "os"
6 "strconv"
7 "strings"
8 "time"
9
10 "gopkg.in/yaml.v3"
11 )
12
13 type Config struct {
14 Server ServerConfig `yaml:"server"`
15 OAuth2 OAuth2Config `yaml:"oauth2"`
16 Nostr NostrConfig `yaml:"nostr"`
17 }
18
19 type ServerConfig struct {
20 Port int `yaml:"port"`
21 Host string `yaml:"host"`
22 BaseURL string `yaml:"base_url"`
23 }
24
25 func (s ServerConfig) Address() string {
26 host := s.Host
27 if host == "" {
28 host = "0.0.0.0"
29 }
30 port := s.Port
31 if port == 0 {
32 port = 8080
33 }
34 return fmt.Sprintf("%s:%d", host, port)
35 }
36
37 type OAuth2Config struct {
38 Clients []ClientConfig `yaml:"clients"`
39 }
40
41 type ClientConfig struct {
42 ClientID string `yaml:"client_id"`
43 ClientSecret string `yaml:"client_secret"`
44 RedirectURIs []string `yaml:"redirect_uris"`
45 }
46
47 type NostrConfig struct {
48 ChallengeTTL time.Duration `yaml:"challenge_ttl"`
49 FallbackRelays []string `yaml:"fallback_relays"`
50 }
51
52 func Load(path string) (*Config, error) {
53 data, err := os.ReadFile(path)
54 if err != nil {
55 return nil, err
56 }
57
58 var cfg Config
59 if err := yaml.Unmarshal(data, &cfg); err != nil {
60 return nil, fmt.Errorf("failed to parse config: %w", err)
61 }
62
63 cfg.setDefaults()
64 return &cfg, nil
65 }
66
67 func FromEnv() *Config {
68 cfg := &Config{}
69
70 // Server config
71 if port := os.Getenv("PORT"); port != "" {
72 cfg.Server.Port, _ = strconv.Atoi(port)
73 }
74 cfg.Server.Host = os.Getenv("HOST")
75 cfg.Server.BaseURL = os.Getenv("BASE_URL")
76
77 // OAuth2 client config (single client from env)
78 clientID := os.Getenv("OAUTH2_CLIENT_ID")
79 clientSecret := os.Getenv("OAUTH2_CLIENT_SECRET")
80 redirectURIs := os.Getenv("OAUTH2_REDIRECT_URIS")
81
82 if clientID != "" {
83 cfg.OAuth2.Clients = []ClientConfig{{
84 ClientID: clientID,
85 ClientSecret: clientSecret,
86 RedirectURIs: strings.Split(redirectURIs, ","),
87 }}
88 }
89
90 // Nostr config
91 if ttl := os.Getenv("NOSTR_CHALLENGE_TTL"); ttl != "" {
92 cfg.Nostr.ChallengeTTL, _ = time.ParseDuration(ttl)
93 }
94 if relays := os.Getenv("NOSTR_FALLBACK_RELAYS"); relays != "" {
95 cfg.Nostr.FallbackRelays = strings.Split(relays, ",")
96 }
97
98 cfg.setDefaults()
99 return cfg
100 }
101
102 // DefaultFallbackRelays are well-known relays that aggregate profile data
103 var DefaultFallbackRelays = []string{
104 "wss://relay.nostr.band/",
105 "wss://nostr.wine/",
106 "wss://nos.lol/",
107 "wss://relay.primal.net/",
108 "wss://purplepag.es/",
109 }
110
111 func (c *Config) setDefaults() {
112 if c.Server.Port == 0 {
113 c.Server.Port = 8080
114 }
115 if c.Server.BaseURL == "" {
116 c.Server.BaseURL = fmt.Sprintf("http://localhost:%d", c.Server.Port)
117 }
118 if c.Nostr.ChallengeTTL == 0 {
119 c.Nostr.ChallengeTTL = 60 * time.Second
120 }
121 if len(c.Nostr.FallbackRelays) == 0 {
122 c.Nostr.FallbackRelays = DefaultFallbackRelays
123 }
124 }
125
126 func (c *Config) GetClient(clientID string) *ClientConfig {
127 for i := range c.OAuth2.Clients {
128 if c.OAuth2.Clients[i].ClientID == clientID {
129 return &c.OAuth2.Clients[i]
130 }
131 }
132 return nil
133 }
134
135 func (c *Config) ValidateRedirectURI(clientID, redirectURI string) bool {
136 client := c.GetClient(clientID)
137 if client == nil {
138 return false
139 }
140 for _, uri := range client.RedirectURIs {
141 if uri == redirectURI {
142 return true
143 }
144 }
145 return false
146 }
147