diff --git a/auth/auth.go b/auth/auth.go index 4f05c5b..ed96972 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -4,6 +4,28 @@ import ( "net/http" ) -type AuthManager interface { - DoAuth(w http.ResponseWriter,r *http.Request) (string,bool) + +type AuthData struct { + User string + Group string } + +type AuthManager interface { + AddUser(user string, group string, password string) error + DeleteUser(user string) error + DoAuth(w http.ResponseWriter, r *http.Request) (*AuthData, bool) +} + +//---------------------------------------------------------------------------------------------------------------------- +func NewAuth(kind string) AuthManager { + switch kind { + case "basic": + return NewBasicAuth() + + case "token": + return NewTokenAuth() + + default: + return NewNoAuth() + } +} \ No newline at end of file diff --git a/auth/basic.go b/auth/basic.go index 990d89b..47d5687 100644 --- a/auth/basic.go +++ b/auth/basic.go @@ -6,53 +6,69 @@ import ( "encoding/base64" ) -type BasicAuth struct { - users map[string]string +type BasicAuthInfo struct { + Group string + Password string } +type BasicAuth struct { + users map[string]BasicAuthInfo +} func (ba *BasicAuth) authenticate(user, password string) bool { - pass,ok := ba.users[user] + rec, ok := ba.users[user] if !ok { return false } - if pass == password { + if rec.Password == password { return true } return false } -func (ba *BasicAuth) DoAuth(w http.ResponseWriter,r *http.Request) (string, bool) { +func (ba *BasicAuth) DoAuth(w http.ResponseWriter, r *http.Request) (*AuthData, bool) { w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) s := strings.SplitN(r.Header.Get("Authorization"), " ", 2) if len(s) != 2 { - return "", false + return nil, false } b, err := base64.StdEncoding.DecodeString(s[1]) if err != nil { - return "", false + return nil, false } pair := strings.SplitN(string(b), ":", 2) if len(pair) != 2 { http.Error(w, "Not authorized", 401) - return "", false + return nil, false } - return pair[0], ba.authenticate(pair[0], pair[1]) + if ba.authenticate(pair[0], pair[1]) { + return &AuthData{User: pair[0], Group: ""}, true + } + + return nil, false } - -func (ba *BasicAuth) AddUser(user, password string) { - ba.users[user] = password +func (ba *BasicAuth) AddUser(user, group, password string) error { + ba.users[user] = BasicAuthInfo{ + Password: password, + Group: group, + } + return nil } -func NewBasicAuth() *BasicAuth { +func (ba *BasicAuth) DeleteUser(user string) error { + delete(ba.users, user) + return nil +} + +func NewBasicAuth() AuthManager { return &BasicAuth{ - users: make(map[string]string), + users: make(map[string]BasicAuthInfo), } } diff --git a/auth/noauth.go b/auth/noauth.go new file mode 100644 index 0000000..58f6e11 --- /dev/null +++ b/auth/noauth.go @@ -0,0 +1,29 @@ +package auth + +import "net/http" + +//---------------------------------------------------------------------------------------------------------------------- +type NoAuth struct { + User AuthData +} + +func (a *NoAuth) DoAuth(w http.ResponseWriter, r *http.Request) (*AuthData, bool) { + return &a.User, true +} + +func (a *NoAuth) AddUser(user string, group string, password string) error { + return nil +} + +func (a *NoAuth) DeleteUser(user string) error { + return nil +} + +func NewNoAuth() AuthManager { + return &NoAuth{ + User: AuthData{ + User: "admin", + Group: "admin", + }, + } +} diff --git a/auth/token.go b/auth/token.go new file mode 100644 index 0000000..38c0a57 --- /dev/null +++ b/auth/token.go @@ -0,0 +1,45 @@ +package auth + +import "net/http" + +//---------------------------------------------------------------------------------------------------------------------- +type TokenAuth struct { + users map[string]TokenUser +} + +type TokenUser struct { + User string + Group string +} + +func (a *TokenAuth) DoAuth(w http.ResponseWriter, r *http.Request) (*AuthData, bool) { + token := r.Header.Get("token") + + user, ok := a.users[token] + if !ok { + return nil, false + } + + return &AuthData{ + User: user.User, + Group: user.Group, + }, true +} + +func (a *TokenAuth) AddUser(user string, group string, password string) error { + a.users[password] = TokenUser{ + User: user, + Group: group, + } + return nil +} + +func (a *TokenAuth) DeleteUser(user string) error { + return nil +} + +func NewTokenAuth() *TokenAuth { + return &TokenAuth{ + users: make(map[string]TokenUser), + } +} diff --git a/context.go b/context.go index 1ffa903..2e124f6 100644 --- a/context.go +++ b/context.go @@ -1,18 +1,24 @@ package snap -import "net/http" +import ( + "net/http" + "encoding/json" + "git.thirdmartini.com/pub/snap/auth" +) type Context struct { - Username string - srv *server + srv *Server + auth *auth.AuthData + w http.ResponseWriter r *http.Request vars map[string]string } type SnapContent struct { - Username string - AppData interface{} + User string + Theme string + Content interface{} } func (c *Context) GetRequest() *http.Request { @@ -25,7 +31,10 @@ func (c *Context) Writer() http.ResponseWriter { func (c *Context) GetUser() string { - return c.Username + if c.auth == nil { + return "" + } + return c.auth.User } func (c *Context) Error(code int, msg string) { @@ -33,6 +42,20 @@ func (c *Context) Error(code int, msg string) { } +func (c *Context) Reply(msg string) { + c.srv.reply(c.w, msg) +} + + +func (c *Context) ReplyObject(obj interface{}) { + msg, err := json.Marshal(obj) + if err != nil { + c.Error(400, "Internal Server Error") + } + c.Reply(string(msg)) +} + + func (c *Context) Render(tmpl string, content interface{}) { c.srv.render(c.w, tmpl, content) @@ -41,10 +64,10 @@ func (c *Context) Render(tmpl string, content interface{}) { func (c *Context) RenderEx(tmpl string, content interface{}) { cnt := SnapContent { - Username:c.Username, - AppData:content, + User: c.GetUser(), + Theme: c.srv.theme, + Content:content, } - c.srv.render(c.w, tmpl, &cnt) } diff --git a/examples/simple/main.go b/examples/simple/main.go new file mode 100644 index 0000000..9140cf8 --- /dev/null +++ b/examples/simple/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "git.thirdmartini.com/pub/snap" + "git.thirdmartini.com/pub/snap/auth" +) + +func handler(c *snap.Context) { + c.Reply("snap/example/simple 1.0") +} + +func handlerAuthenticated(c *snap.Context) { + c.Reply("snap/example/simple 1.0 (authenticated)") +} + + +func mustRunServer(auth auth.AuthManager) { + srv := snap.New("127.0.0.1:9000", "./", nil) + srv.SetDebug(true) + + srv.HandleFunc("/", handler) + srv.HandleFuncCustomAuth(auth,"/auth", handlerAuthenticated) + + srv.Serve() +} + + +func main() { + auth := auth.NewAuth("basic") + auth.AddUser("admin", "admin", "password") + mustRunServer(auth) +} diff --git a/server.go b/server.go index 583ca24..b436feb 100644 --- a/server.go +++ b/server.go @@ -15,17 +15,11 @@ import ( "git.thirdmartini.com/pub/snap/auth" ) -type Server interface { - Serve() error - ServeTLS(keyPath string, certPath string) error - SetDebug(enable bool) - HandleFunc(path string, f func(c *Context)) error - HandleFuncAuthenticated(path string, f func(c *Context)) *mux.Route -} -type server struct { +type Server struct { address string path string + theme string debug bool auth auth.AuthManager router *mux.Router @@ -33,55 +27,53 @@ type server struct { } -func (s *server) plain(f func(c *Context)) http.HandlerFunc { +type SnapBaseContent struct { + Theme string + Content interface{} +} + +func (s *Server) makeContext( auth *auth.AuthData, w http.ResponseWriter, r *http.Request ) (*Context) { + c := &Context{ + r: r, + w: w, + srv: s, + auth: auth, + vars: mux.Vars(r), + } + return c +} + + +func (s *Server) plain(f func(c *Context)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - c := &Context{ - Username: "", - r: r, - w: w, - srv: s, - } + c := s.makeContext( nil, w,r) f(c) } } -func (s *server) authenticated(handle func(c *Context)) http.HandlerFunc { +func (s *Server) authenticated(auth auth.AuthManager, handle func(c *Context)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - name, ok := s.auth.DoAuth( w, r ) + rec, ok := auth.DoAuth( w, r ) if !ok { http.Error(w, "Not authorized", 401) } else { - c:= &Context{ - Username: name, - r: r, - w: w, - srv: s, - vars: mux.Vars(r), - } + c := s.makeContext( rec, w,r) handle(c) } } } -func (s *server) wrapper(handle func(c *Context)) http.HandlerFunc { +func (s *Server) wrapper(handle func(c *Context)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - c:= &Context{ - Username: "", - r: r, - w: w, - srv: s, - vars: mux.Vars(r), - } + c := s.makeContext( nil, w,r) handle(c) } - } - -func (s *server) loadTemplates() *template.Template { +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 { @@ -101,7 +93,7 @@ func (s *server) loadTemplates() *template.Template { return tmpl } -func (s *server) getTemplates() *template.Template { +func (s *Server) getTemplates() *template.Template { if s.debug { return s.loadTemplates() } @@ -112,37 +104,57 @@ func (s *server) getTemplates() *template.Template { return s.cachedTmpl } -func (s *server) render(w http.ResponseWriter, tmpl string, content interface{} ) { +func (s *Server) render(w http.ResponseWriter, tmpl string, content interface{} ) { s.getTemplates().ExecuteTemplate(w, tmpl, content) } -func (s *server) renderError(w http.ResponseWriter, code int, msg string) { +func (s *Server) reply(w http.ResponseWriter, msg string) { + w.Write([]byte(msg)) +} + +func (s *Server) renderError(w http.ResponseWriter, code int, msg string) { w.WriteHeader(code) w.Write([]byte(msg)) } -func (s *server) HandleFuncAuthenticated(path string, f func(c *Context)) *mux.Route { +func (s *Server) HandleFuncAuthenticated(path string, f func(c *Context)) *mux.Route { if s.auth == nil { return nil } - return s.router.HandleFunc(path, s.authenticated(f)) + return s.router.HandleFunc(path, s.authenticated(s.auth, f)) +} + +func (s *Server) HandleFuncCustomAuth(auth auth.AuthManager, path string, f func(c *Context)) *mux.Route { + if auth == nil { + log.Warn("Nil auth on", path) + return nil + } + return s.router.HandleFunc(path, s.authenticated(auth, f)) } - -func (s *server) HandleFunc(path string, f func(c *Context)) error { +func (s *Server) HandleFunc(path string, f func(c *Context)) error { s.router.HandleFunc(path, s.wrapper(f)) return nil } -func (s *server) SetDebug(enable bool) { +func (s *Server) SetDebug(enable bool) { s.debug = enable } -func (s *server) ServeTLS(keyPath string, certPath string) error { +func (s *Server) EnableStatus(path string) { +} + + +func (s *Server) SetTheme(theme string) { + s.theme = theme +} + + +func (s *Server) ServeTLS(keyPath string, certPath string) error { srv := &http.Server{ Handler: s.router, Addr: s.address, @@ -155,7 +167,7 @@ func (s *server) ServeTLS(keyPath string, certPath string) error { } // Serve serve content forever -func (s *server) Serve() error { +func (s *Server) Serve() error { srv := &http.Server{ Handler: s.router, Addr: s.address, @@ -167,12 +179,13 @@ func (s *server) Serve() error { return srv.ListenAndServe() } -func New(address string, path string, auth auth.AuthManager) Server { - s := server{ +func New(address string, path string, auth auth.AuthManager) *Server { + s := Server{ router: mux.NewRouter(), auth: auth, address: address, path: path, + theme: "/static/css/default.css", } s.router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(s.path+"static/"))))