From fd5c64dddeef8a336594aeaa9ce978e954895556 Mon Sep 17 00:00:00 2001 From: ssobolewski Date: Tue, 23 Jan 2018 19:26:26 -0700 Subject: [PATCH] snap server base --- auth/auth.go | 7 +++ auth/basic.go | 67 ++++++++++++++++++++++++++++ server.go | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 auth/auth.go create mode 100644 auth/basic.go create mode 100644 server.go diff --git a/auth/auth.go b/auth/auth.go new file mode 100644 index 0000000..9829f05 --- /dev/null +++ b/auth/auth.go @@ -0,0 +1,7 @@ +package auth + +import "net/http" + +type AuthManager interface { + DoAuth(h http.HandlerFunc) http.HandlerFunc +} diff --git a/auth/basic.go b/auth/basic.go new file mode 100644 index 0000000..48ca41f --- /dev/null +++ b/auth/basic.go @@ -0,0 +1,67 @@ +package auth + +import ( + "net/http" + "strings" + "encoding/base64" +) + +type BasicAuth struct { + users map[string]string +} + + +func (ba *BasicAuth) authenticate(user, password string) bool { + pass,ok := ba.users[user] + if !ok { + return false + } + + if pass == password { + return true + } + + return false +} + +func (ba *BasicAuth) DoAuth(h http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) + + s := strings.SplitN(r.Header.Get("Authorization"), " ", 2) + if len(s) != 2 { + http.Error(w, "Not authorized", 401) + return + } + + b, err := base64.StdEncoding.DecodeString(s[1]) + if err != nil { + http.Error(w, err.Error(), 401) + return + } + + pair := strings.SplitN(string(b), ":", 2) + if len(pair) != 2 { + http.Error(w, "Not authorized", 401) + return + } + + if !ba.authenticate(pair[0], pair[1]) { + http.Error(w, "Not authorized", 401) + return + } + + h.ServeHTTP(w, r) + } +} + + +func (ba *BasicAuth) AddUser(user, password string) { + ba.users[user] = password +} + +func NewBasicAuth() *BasicAuth { + return &BasicAuth{ + users: make(map[string]string), + } +} diff --git a/server.go b/server.go new file mode 100644 index 0000000..f283bba --- /dev/null +++ b/server.go @@ -0,0 +1,118 @@ +package snap + +import ( + "github.com/gorilla/mux" + "html/template" + "net/http" + "os" + "path/filepath" + "strings" + "time" + + "fmt" + "git.thirdmartini.com/pub/fancylog" + "git.thirdmartini.com/pub/snap-serve/auth" + "path" +) + +type Server interface { + Serve() error + ServeTLS(keyPath string, certPath string) error + HandleFunc(path string, f func(http.ResponseWriter, *http.Request)) error + HandleFuncAuthenticated(path string, f func(http.ResponseWriter, *http.Request)) error +} + +type server struct { + address string + path string + debug bool + auth auth.AuthManager + router *mux.Router + cachedTmpl *template.Template +} + +type ApplicationContext struct { + Username string +} + +func (s *server) loadTemplates() *template.Template { + tmpl := template.New("") + + err := filepath.Walk(path.Join(s.path, "templates"), func(path string, info os.FileInfo, err error) error { + if strings.Contains(path, ".html") { + _, err = tmpl.ParseFiles(path) + if err != nil { + log.Println(err) + } + } + return err + }) + + if err != nil { + log.Fatal(err) + } + + return tmpl +} + +func (s *server) getTemplates() *template.Template { + if s.debug { + return s.loadTemplates() + } + + if s.cachedTmpl == nil { + s.cachedTmpl = s.loadTemplates() + } + return s.cachedTmpl +} + +func (s *server) HandleFunc(path string, f func(http.ResponseWriter, *http.Request)) error { + s.router.HandleFunc(path, f) + return nil +} + +func (s *server) HandleFuncAuthenticated(path string, f func(http.ResponseWriter, *http.Request)) error { + if s.auth == nil { + return fmt.Errorf("no auth manager provided") + } + + s.router.HandleFunc(path, s.auth.DoAuth(f)) + return nil +} + +func (s *server) ServeTLS(keyPath string, certPath string) error { + srv := &http.Server{ + Handler: s.router, + Addr: s.address, + // Good practice: enforce timeouts for servers you create! + WriteTimeout: 15 * time.Second, + ReadTimeout: 15 * time.Second, + } + + return srv.ListenAndServeTLS(keyPath, certPath) +} + +// Serve serve content forever +func (s *server) Serve() error { + srv := &http.Server{ + Handler: s.router, + Addr: s.address, + // Good practice: enforce timeouts for servers you create! + WriteTimeout: 15 * time.Second, + ReadTimeout: 15 * time.Second, + } + + return srv.ListenAndServe() +} + +func New(address string, path string, auth auth.AuthManager) Server { + s := server{ + router: mux.NewRouter(), + auth: auth, + address: address, + path: path, + } + + s.router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(s.ServerPath+"static/")))) + return &s +}