From 3b91b699e361fea649cd1765c61f6ce7588553a6 Mon Sep 17 00:00:00 2001 From: Daniel Heath Date: Sun, 27 May 2018 21:51:33 +1000 Subject: [PATCH] Add 'tomlo', configure cowyo via TOML --- cmd/tomlo/tomlo.go | 14 ++++++ config/config.go | 55 ++++++++++++++++++++++ config/defaults.go | 102 ++++++++++++++++++++++++++++++++++++++++ config/http.go | 105 ++++++++++++++++++++++++++++++++++++++++++ multisite_sample.toml | 40 ++++++++++++++++ server/debug.go | 3 ++ server/handlers.go | 1 + 7 files changed, 320 insertions(+) create mode 100644 cmd/tomlo/tomlo.go create mode 100644 config/config.go create mode 100644 config/defaults.go create mode 100644 config/http.go create mode 100644 multisite_sample.toml diff --git a/cmd/tomlo/tomlo.go b/cmd/tomlo/tomlo.go new file mode 100644 index 0000000..67846e9 --- /dev/null +++ b/cmd/tomlo/tomlo.go @@ -0,0 +1,14 @@ +package main + +import ( + "github.com/schollz/cowyo/config" +) + +func main() { + c, err := config.ParseFile("multisite_sample.toml") + if err != nil { + panic(err) + } + + panic(c.ListenAndServe()) +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..1209ecf --- /dev/null +++ b/config/config.go @@ -0,0 +1,55 @@ +package config + +import ( + "github.com/BurntSushi/toml" +) + +func ParseFile(path string) (Config, error) { + c := Config{} + if _, err := toml.DecodeFile("multisite_sample.toml", &c); err != nil { + // handle error + return c, err + } + c.SetDefaults() + c.Validate() + return c, nil +} + +type Config struct { + Default SiteConfig + Sites []SiteConfig +} + +type SiteConfig struct { + Host *string + Port *int + DataDir *string + DefaultPage *string + AllowInsecureMarkup *bool + Lock *string + DebounceSave *int + Diary *bool + AccessCode *string + FileUploadsAllowed *bool + MaxFileUploadMb *uint + MaxDocumentLength *uint + TLS *TLSConfig + CookieKeys []CookieKey +} + +type TLSConfig struct { + CertPath string + KeyPath string + Port int +} + +type CookieKey struct { + AuthenticateBase64 string + EncryptBase64 string +} + +func (c Config) Validate() { + for _, v := range c.Sites { + v.sessionStore() + } +} \ No newline at end of file diff --git a/config/defaults.go b/config/defaults.go new file mode 100644 index 0000000..e7e8c73 --- /dev/null +++ b/config/defaults.go @@ -0,0 +1,102 @@ +package config + +import ( +"encoding/base64" + "crypto/rand" +) +var DefaultSiteConfig SiteConfig + + +func makeAuthKey() string { + secret := make([]byte, 32) + _, err := rand.Read(secret) + if err != nil { + panic(err) + } + return base64.StdEncoding.EncodeToString(secret) +} + +func init() { + host := "*" + port := 8050 + debounce := 500 + dataDir := "data" + empty := "" + zer := uint(0) + lots := uint(100000000) + fal := false + + ck := CookieKey{ + AuthenticateBase64: "", + EncryptBase64: "", + } + + DefaultSiteConfig = SiteConfig{ + Host:&host, +Port:&port, +DataDir:&dataDir, +DebounceSave:&debounce, +CookieKeys: []CookieKey{ck}, +DefaultPage:&empty, +AllowInsecureMarkup:&fal, +Lock:&empty, +Diary:&fal, +AccessCode:&empty, +FileUploadsAllowed:&fal, +MaxFileUploadMb:&zer, +MaxDocumentLength:&lots, + } +} + + +func copyDefaults(base, defaults *SiteConfig) { + if base.Host == nil { + base.Host = defaults.Host + } + if base.Port == nil { + base.Port = defaults.Port + } + if base.DataDir == nil { + base.DataDir = defaults.DataDir + } + if base.DefaultPage == nil { + base.DefaultPage = defaults.DefaultPage + } + if base.AllowInsecureMarkup == nil { + base.AllowInsecureMarkup = defaults.AllowInsecureMarkup + } + if base.Lock == nil { + base.Lock = defaults.Lock + } + if base.DebounceSave == nil { + base.DebounceSave = defaults.DebounceSave + } + if base.Diary == nil { + base.Diary = defaults.Diary + } + if base.AccessCode == nil { + base.AccessCode = defaults.AccessCode + } + if base.FileUploadsAllowed == nil { + base.FileUploadsAllowed = defaults.FileUploadsAllowed + } + if base.MaxFileUploadMb == nil { + base.MaxFileUploadMb = defaults.MaxFileUploadMb + } + if base.MaxDocumentLength == nil { + base.MaxDocumentLength = defaults.MaxDocumentLength + } + if base.TLS == nil { + base.TLS = defaults.TLS + } + if base.CookieKeys == nil { + base.CookieKeys = defaults.CookieKeys + } +} + +func (c *Config) SetDefaults() { + copyDefaults(&c.Default, &DefaultSiteConfig) + for i := range c.Sites { + copyDefaults(&c.Sites[i], &c.Default) + } +} diff --git a/config/http.go b/config/http.go new file mode 100644 index 0000000..18b04ba --- /dev/null +++ b/config/http.go @@ -0,0 +1,105 @@ +package config + +import ( + "fmt" + "log" +"encoding/base64" + "net/http" + "github.com/gin-contrib/sessions" + "github.com/jcelliott/lumber" + "github.com/schollz/cowyo/server" + "strings" +) +func (c Config) ListenAndServe() error { + insecurePorts := map[int]bool{} + securePorts := map[int]bool{} + err := make(chan error) + for _, s := range c.Sites { + if !insecurePorts[*s.Port] { + insecurePorts[*s.Port] = true + go func(s SiteConfig) { + err <- http.ListenAndServe(fmt.Sprintf("localhost:%d", *s.Port), c) + }(s) + } + if s.TLS != nil && !securePorts[s.TLS.Port] { + securePorts[s.TLS.Port] = true + go func(s SiteConfig) { + err <- http.ListenAndServeTLS( + fmt.Sprintf("localhost:%d", s.TLS.Port), + s.TLS.CertPath, + s.TLS.KeyPath, + c, + ) + }(s) + } + } + for { + return <- err + } + return nil +} + +func (c Config) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + for i := range c.Sites { + if c.Sites[i].MatchesRequest(r) { + c.Sites[i].Handle(rw, r) + return + } + } + http.NotFound(rw, r) +} + +func (s SiteConfig) MatchesRequest(r *http.Request) bool { + sh := *s.Host + if strings.HasPrefix(sh, "*") { + return strings.HasSuffix(r.Host, sh[1:]) + } + return sh == r.Host +} + +func (s SiteConfig) sessionStore() sessions.Store { + keys := [][]byte{} + for _, k := range s.CookieKeys { + key, err := base64.StdEncoding.DecodeString(k.AuthenticateBase64) + if err != nil { + panic(err) + } + if len(key) != 32 { + log.Panicf("AuthenticateBase64 key %s must be 32 bytes; suggest %s", k.AuthenticateBase64, makeAuthKey()) + } + + keys = append(keys, key) + key, err = base64.StdEncoding.DecodeString(k.EncryptBase64) + if err != nil { + panic(err) + } + + if len(key) != 32 { + log.Panicf("EncryptBase64 key %s must be 32 bytes, suggest %s", k.EncryptBase64, makeAuthKey()) + } + keys = append(keys, key) + } + return sessions.NewCookieStore(keys...) +} + +func (s SiteConfig) Handle(rw http.ResponseWriter, r *http.Request) { + dataDir := strings.Replace(*s.DataDir, "${HOST}", r.Host, -1) + + router := server.Site{ + PathToData: dataDir, + Css: []byte{}, + DefaultPage: *s.DefaultPage, + DefaultPassword: *s.Lock, + Debounce: *s.DebounceSave, + Diary: *s.Diary, + SessionStore: s.sessionStore(), + SecretCode: *s.AccessCode, + AllowInsecure: *s.AllowInsecureMarkup, + Fileuploads: *s.MaxFileUploadMb > 0, + MaxUploadSize: *s.MaxFileUploadMb, + Logger: lumber.NewConsoleLogger(server.LogLevel), + MaxDocumentSize: *s.MaxDocumentLength, + }.Router() + + router.ServeHTTP(rw, r) +} diff --git a/multisite_sample.toml b/multisite_sample.toml new file mode 100644 index 0000000..b28e0fe --- /dev/null +++ b/multisite_sample.toml @@ -0,0 +1,40 @@ +[default] +dataDir = "root_data/${HOST}" +maxDocumentLength = 100000000 + +# Specify multiple times to change keys without expiring sessions +[[default.CookieKeys]] +authenticateBase64 = "RpW4LjGCPNOx75G8DrywmzlEHLB/ISXCAAayZ47Ifkc=" +encryptBase64 = "ofCKkrfosQb5T4cvz7R5IMP4BQUDHOPsLSMZZy2CUOA=" + +[[sites]] +host = "nerdy.party" +dataDir = "somewhere else" +# theme = "custom.css" # TODO: Theme support. Would prefer to move to a complete directory replacement. +defaultPage = "welcome" +allowInsecureMarkup = true +lock = "1234" +debounceSave = 600 +diary = true +accessCode = "correct horse battery staple" +fileUploadsAllowed = true +maxFileUploadMb = 6 +port = 8090 + +#[sites.TLS] +# TODO: ACME support eg letsencrypt +#certPath = "path.crt" +#keyPath = "path.key" +#port = 8443 + +[[sites]] +host = "cowyo.com" +allowInsecureMarkup = false +fileUploadsAllowed = false +port = 8090 + +# Catchall config +[[sites]] +host = "*" +port = 8100 +cookieSecret = "ASADFGKLJSH+4t4cC2X3f7GzsLZ+wtST67qoLuErpugJz06ZIpdDHEjcMxR+XOLM" diff --git a/server/debug.go b/server/debug.go index 900744c..189d60e 100644 --- a/server/debug.go +++ b/server/debug.go @@ -2,6 +2,9 @@ package server +import "github.com/jcelliott/lumber" + func init() { hotTemplateReloading = true + LogLevel = lumber.TRACE } diff --git a/server/handlers.go b/server/handlers.go index 0173956..504937c 100755 --- a/server/handlers.go +++ b/server/handlers.go @@ -51,6 +51,7 @@ func (s *Site) defaultLock() string { } var hotTemplateReloading bool +var LogLevel int = lumber.WARN func Serve( filepathToData,