diff --git a/config/confpar/confpar.go b/config/confpar/confpar.go index 7019a967..5a1e085e 100644 --- a/config/confpar/confpar.go +++ b/config/confpar/confpar.go @@ -1,7 +1,10 @@ // Package confpar provide the core parameters of the config package confpar -import "time" +import ( + "encoding/json" + "time" +) // Access provides rules around any access type Access struct { @@ -19,7 +22,7 @@ type Access struct { type AccessesWebhook struct { URL string `json:"url"` // URL to call Headers map[string]string `json:"headers"` // Token to use in the - Timeout time.Duration `json:"timeout"` // Max time request can take + Timeout Duration `json:"timeout"` // Max time request can take } // SyncAndDelete provides @@ -65,7 +68,7 @@ type Content struct { PublicHost string `json:"public_host"` // Public host to listen on MaxClients int `json:"max_clients"` // Maximum clients who can connect HashPlaintextPasswords bool `json:"hash_plaintext_passwords"` // Overwrite plain-text passwords with hashed equivalents - IdleTimeout time.Duration `json:"idle_timeout"` // Maximum idle time for client connections + IdleTimeout Duration `json:"idle_timeout"` // Maximum idle time for client connections Accesses []*Access `json:"accesses"` // Accesses offered to users PassiveTransferPortRange *PortRange `json:"passive_transfer_port_range"` // Listen port range Extensions Extensions `json:"extensions"` // Extended features @@ -74,3 +77,21 @@ type Content struct { TLSRequired string `json:"tls_required"` AccessesWebhook *AccessesWebhook `json:"accesses_webhook"` // Webhook to call when accesses are updated } + +// Duration wraps time.Duration to allow unmarshaling from JSON strings +// in Go duration format (e.g., "5m", "30s", "1h") +type Duration struct { + time.Duration +} + +func (d *Duration) MarshalJSON() ([]byte, error) { + return json.Marshal(d.String()) +} + +func (d *Duration) UnmarshalJSON(b []byte) (err error) { + var s string + if err = json.Unmarshal(b, &s); err == nil { + d.Duration, err = time.ParseDuration(s) + } + return +} diff --git a/config/confpar/confpar_test.go b/config/confpar/confpar_test.go new file mode 100644 index 00000000..7170b2c9 --- /dev/null +++ b/config/confpar/confpar_test.go @@ -0,0 +1,54 @@ +package confpar + +import ( + "bytes" + "encoding/json" + "testing" + "time" +) + +func TestDurationMarshalJSON(t *testing.T) { + for _, item := range []struct { + duration time.Duration + want string + }{ + {0, `0s`}, + {31 * time.Second, `31s`}, + {5 * time.Minute, `5m0s`}, + {1 * time.Hour, `1h0m0s`}, + } { + have, err := json.Marshal(&Duration{item.duration}) + if err != nil { + t.Fatalf("json.Marshal(): %v", err) + } + if !bytes.Equal(have, []byte(`"`+item.want+`"`)) { + t.Fatalf("have:%s want:%q", string(have), item.want) + } + } +} + +func TestDurationUnmarshalJSON(t *testing.T) { + for _, item := range []struct { + input string + want time.Duration + wantErr bool + }{ + {`5m`, 5 * time.Minute, false}, + {`30s`, 30 * time.Second, false}, + {`1h`, time.Hour, false}, + {`0s`, 0, false}, + {`invalid`, 0, true}, + } { + var have Duration + err := json.Unmarshal([]byte(`"`+item.input+`"`), &have) + if err == nil && item.wantErr { + t.Fatalf("expecting error for invalid input") + } + if err != nil && !item.wantErr { + t.Fatalf("json.Unmarshal(): %v", err) + } + if have.Duration != item.want { + t.Fatalf("have:%v want:%v", have.Duration, item.want) + } + } +} diff --git a/server/server.go b/server/server.go index 96d53a28..8fdcfac4 100644 --- a/server/server.go +++ b/server/server.go @@ -201,7 +201,7 @@ func (s *Server) getAccessFromWebhook(user, pass string) (*confpar.Access, error } // Timeout is implemented with context termination - ctx, cancel := context.WithTimeout(context.Background(), s.config.Content.AccessesWebhook.Timeout) + ctx, cancel := context.WithTimeout(context.Background(), s.config.Content.AccessesWebhook.Timeout.Duration) defer cancel() // Create a new HTTP request