diff --git a/auth/auth.go b/auth/auth.go index 58bda33..8b4230e 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -25,6 +25,7 @@ type UserBackend interface { // User interface for user type User interface { GetHash() string + HasRole(string) bool } // Authorizer handle sesssion diff --git a/auth/middleware.go b/auth/middleware.go new file mode 100644 index 0000000..237e682 --- /dev/null +++ b/auth/middleware.go @@ -0,0 +1,61 @@ +package auth + +import ( + "net/http" + + "github.com/gorilla/context" +) + +type key int + +const ukey key = 0 + +// AuthMiddleware get User from session and put it in context +type Middleware struct { + authorizer *Authorizer +} + +func NewMiddleware(authorizer *Authorizer) *Middleware { + return &Middleware{authorizer} +} + +func (m *Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { + user, err := m.authorizer.CurrentUser(w, r) + if err != nil { + panic(err) + } + context.Set(r, ukey, user) + next(w, r) +} + +type MiddlewareRole struct { + authorizer *Authorizer + role string +} + +func NewMiddlewareRole(authorizer *Authorizer, role string) *MiddlewareRole { + return &MiddlewareRole{authorizer, role} +} + +func (m *MiddlewareRole) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { + user := GetCurrentUser(r) + + if user == nil || !user.HasRole(m.role) { + //TODO: redirect to login page and save wanted page + return + } + + next(w, r) +} + +func GetCurrentUser(r *http.Request) User { + u := context.Get(r, ukey) + if u == nil { + return nil + } + user, ok := u.(User) + if !ok { + panic("Invalid user type") + } + return user +} diff --git a/main.go b/main.go index eb9f497..6f02535 100644 --- a/main.go +++ b/main.go @@ -10,7 +10,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/codegangsta/negroni" - "github.com/gorilla/mux" "github.com/jmoiron/sqlx" _ "github.com/lib/pq" ) @@ -35,13 +34,17 @@ func main() { uBackend := &UserBackend{db} authorizer := auth.New(uBackend, "peeper", "cookieName", "cookieKey", 10) - env := web.NewEnv(db, authorizer, log, "/templates") + env := web.NewEnv(db, authorizer, log, "./templates") + authMiddleware := auth.NewMiddleware(env.Auth) - router := mux.NewRouter() + env.Handle("users.login", "/users/login", users.LoginHandler) + env.Handle("users.logout", "users/logout", users.LogoutHandler) + env.HandleRole("users.details", "/users/details", users.DetailsHandler, users.UserRole) - router.Handle("/", env.Handler(movies.PolochonMovies)) + env.HandleRole("movies.polochon", "/", movies.PolochonMovies, users.UserRole) n := negroni.Classic() - n.UseHandler(router) + n.Use(authMiddleware) + n.UseHandler(env.Router) n.Run(":3000") } diff --git a/movies/handlers.go b/movies/handlers.go index c0c3697..0b72884 100644 --- a/movies/handlers.go +++ b/movies/handlers.go @@ -4,7 +4,7 @@ import ( "net/http" "github.com/odwrtw/polochon/lib" - "github.com/odwrtw/polochon/modules/tmdb" + "github.com/odwrtw/polochon/modules/mock" "gitlab.quimbo.fr/odwrtw/canape-sql/web" "gitlab.quimbo.fr/odwrtw/papi" @@ -27,7 +27,8 @@ func PolochonMovies(env *web.Env, w http.ResponseWriter, r *http.Request) error movies := []*Movie{} //TODO use configurable detailer - detailer, err := tmdb.New(&tmdb.Params{"57be344f84917b3f32c68a678f1482eb"}) + // detailer, err := tmdb.New(&tmdb.Params{"57be344f84917b3f32c68a678f1482eb"}) + detailer, _ := mock.NewDetailer(nil) if err != nil { return err } diff --git a/sql/0001_initial.up.sql b/sql/0001_initial.up.sql index f43c2ea..a8c4213 100644 --- a/sql/0001_initial.up.sql +++ b/sql/0001_initial.up.sql @@ -17,6 +17,7 @@ CREATE TABLE users ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), name text NOT NULL UNIQUE, hash text NOT NULL, + admin boolean, LIKE base INCLUDING DEFAULTS ); CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); diff --git a/sqltest/001_data.down.sql b/sqltest/001_data.down.sql new file mode 100644 index 0000000..6f114a7 --- /dev/null +++ b/sqltest/001_data.down.sql @@ -0,0 +1 @@ +DELETE FROM users; diff --git a/sqltest/001_data.up.sql b/sqltest/001_data.up.sql new file mode 100644 index 0000000..c025886 --- /dev/null +++ b/sqltest/001_data.up.sql @@ -0,0 +1,2 @@ +INSERT INTO users (name, hash, admin) VALUES ('test', '$2a$10$DPsyngE6ccXzzE38.JJv3OIpvU/lSjfMyg9CR68F8h6krKIyVJYrW', false); +INSERT INTO users (name, hash, admin) VALUES ('admin', '$2a$10$e3564lLAh.0tIHQu8kfzsunViwd56AvGPeUypuCUcE3Vh09RBZci.', true); diff --git a/templates/layout.tmpl b/templates/layout.tmpl index 1f5bdfe..fe84f02 100644 --- a/templates/layout.tmpl +++ b/templates/layout.tmpl @@ -3,16 +3,8 @@ My Layout - - - - {{ template "navbar" $ }} {{ yield }} - - - - diff --git a/templates/navbar.tmpl b/templates/navbar.tmpl deleted file mode 100644 index a63a60f..0000000 --- a/templates/navbar.tmpl +++ /dev/null @@ -1,161 +0,0 @@ - diff --git a/templates/users/login.tmpl b/templates/users/login.tmpl index e00f89a..60267df 100644 --- a/templates/users/login.tmpl +++ b/templates/users/login.tmpl @@ -1 +1,5 @@ -hello wolrd +
+ + + +
diff --git a/users/handlers.go b/users/handlers.go index 5037202..91dc4cf 100644 --- a/users/handlers.go +++ b/users/handlers.go @@ -4,85 +4,55 @@ import ( "fmt" "net/http" - "github.com/gorilla/mux" - "github.com/mholt/binding" + "github.com/gorilla/Schema" + "gitlab.quimbo.fr/odwrtw/canape-sql/auth" "gitlab.quimbo.fr/odwrtw/canape-sql/web" ) -// FormErrors map[field name]message -type FormErrors map[string]string - -// HandleFormErrors translate binding.Erros to FormErrors -func HandleFormErrors(errs binding.Errors) FormErrors { - ferrs := FormErrors{} - for _, err := range errs { - for _, field := range err.FieldNames { - if ferrs[field] == "" { - ferrs[field] = err.Message - } - } - } - return ferrs -} - -type loginForm struct { - Username string - Password string -} - -func (f *loginForm) FieldMap(r *http.Request) binding.FieldMap { - return binding.FieldMap{ - &f.Username: "username", - &f.Password: "password", - } -} - -func (f *loginForm) Validate(r *http.Request, errs binding.Errors) binding.Errors { - if f.Username == "" { - errs = append(errs, binding.Error{ - FieldNames: []string{"username"}, - Classification: "InvalidValues", - Message: "Specify a username", - }) - } - if f.Password == "" { - errs = append(errs, binding.Error{ - FieldNames: []string{"password"}, - Classification: "InvalidValues", - Message: "Specify a password", - }) - } - return errs -} - -func loginSubmitHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error { - form := new(loginForm) - - errs := binding.Bind(r, form) - if errs != nil { - formErrs := HandleFormErrors(errs) - web.SetData(r, "FormErrors", formErrs) +func LoginHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error { + if r.Method == "GET" { return e.Rends(w, r, "users/login") } - err := e.Auth.Login(w, r, form.Username, form.Password) - if err != nil { - if err == auth.ErrInvalidPassword || err == ErrUnknownUser { - web.SetData(r, "FormErrors", FormErrors{"password": "Invalid username or password"}) - return e.Rends(w, r, "users/login") - } - return web.InternalError(err) + type loginForm struct { + Username string + Password string } - return nil -} - -func userDetailsHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error { - u, err := e.Auth.CurrentUser(w, r) + err := r.ParseForm() if err != nil { return err } + + form := new(loginForm) + decoder := schema.NewDecoder() + err = decoder.Decode(form, r.PostForm) + if err != nil { + return err + } + + err = e.Auth.Login(w, r, form.Username, form.Password) + if err != nil { + if err == auth.ErrInvalidPassword || err == ErrUnknownUser { + web.SetData(r, "FormErrors", "Error invalid user or password") + return e.Rends(w, r, "users/login") + } + return err + } + + //TODO: redirect to user details or to previous location + return nil +} + +func LogoutHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error { + e.Auth.Logout(w, r) + //TODO: redirect to login page + return nil +} + +func DetailsHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error { + u := auth.GetCurrentUser(r) if u == nil { return nil } @@ -92,9 +62,3 @@ func userDetailsHandler(e *web.Env, w http.ResponseWriter, r *http.Request) erro } return nil } - -// SetRoutes adds users routes to the router -func SetRoutes(r *mux.Router, e *web.Env) { - r.Handle("/login", e.Handler(loginSubmitHandler)).Methods("POST").Name("loginPost") - r.Handle("/", e.Handler(userDetailsHandler)).Methods("GET").Name("UserDetails") -} diff --git a/users/users.go b/users/users.go index ec9fdf7..dcca860 100644 --- a/users/users.go +++ b/users/users.go @@ -9,9 +9,9 @@ import ( ) const ( - addUserQuery = `INSERT INTO users (name, hash) VALUES ($1, $2) RETURNING id;` + addUserQuery = `INSERT INTO users (name, hash, admin) VALUES ($1, $2, $3) RETURNING id;` getUserQuery = `SELECT * FROM users WHERE name=$1;` - updateUserQuery = `UPDATE users SET name=:name, hash=:hash RETURNING *;` + updateUserQuery = `UPDATE users SET name=:name, hash=:hash, admin=:admin RETURNING *;` deleteUseQuery = `DELETE FROM users WHERE id=:id;` addTokenQuery = `INSERT INTO tokens (value, user_id) VALUES ($1, $2) RETURNING id;` @@ -20,14 +20,20 @@ const ( deleteTokenQuery = `DELETE FROM tokens WHERE user_id=$1 AND value=$2;` ) +const ( + UserRole = "user" + AdminRole = "admin" +) + // ErrUnknownUser returned web a user does'nt exist var ErrUnknownUser = fmt.Errorf("users: user does'nt exist") // User represents an user type User struct { sqly.BaseModel - Name string - Hash string + Name string + Hash string + Admin bool } // Token represents a token @@ -52,7 +58,7 @@ func Get(q sqlx.Queryer, name string) (*User, error) { // Add user to database or raises an error func (u *User) Add(q sqlx.Queryer) error { var id string - err := q.QueryRowx(addUserQuery, u.Name, u.Hash).Scan(&id) + err := q.QueryRowx(addUserQuery, u.Name, u.Hash, u.Admin).Scan(&id) if err != nil { return err } @@ -136,3 +142,10 @@ func (u *User) DeleteToken(ex *sqlx.DB, value string) error { func (u *User) GetHash() string { return u.Hash } + +func (u *User) HasRole(role string) bool { + if role == AdminRole && !u.Admin { + return false + } + return true +} diff --git a/web/env.go b/web/env.go index 41f8576..37d5c9e 100644 --- a/web/env.go +++ b/web/env.go @@ -7,6 +7,7 @@ import ( "gitlab.quimbo.fr/odwrtw/canape-sql/auth" "github.com/Sirupsen/logrus" + "github.com/codegangsta/negroni" "github.com/gorilla/mux" "github.com/jmoiron/sqlx" "github.com/unrolled/render" @@ -62,9 +63,17 @@ func (e *Env) GetURL(name string, pairs ...string) (string, error) { return URL.String(), nil } -// Handler create a new handler -func (e *Env) Handler(H func(e *Env, w http.ResponseWriter, r *http.Request) error) Handler { - return Handler{e, H} +type HandlerFunc func(e *Env, w http.ResponseWriter, r *http.Request) error + +func (e *Env) Handle(name, route string, H HandlerFunc) { + e.Router.Handle(route, Handler{e, H}) +} + +func (e *Env) HandleRole(name, route string, H HandlerFunc, role string) { + e.Router.Handle(route, negroni.New( + auth.NewMiddlewareRole(e.Auth, role), + negroni.Wrap(Handler{e, H}), + )) } // The Handler struct that takes a configured Env and a function matching our @@ -85,8 +94,3 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.StatusInternalServerError) } } - -// InternalError create a internal error -func InternalError(err error) error { - return nil -}