diff --git a/config.yml.exemple b/config.yml.exemple index 07a4295..ae281e5 100644 --- a/config.yml.exemple +++ b/config.yml.exemple @@ -1,11 +1,9 @@ authorizer: - cookie_name: auth - cookie_key: mysecretkey + secret: my_secret_app_key pepper: pepper cost: 10 pgdsn: postgres://test:test@127.0.0.1:5432/dev?sslmode=disable trakttv_client_id: my_trakttv_client_id tmdb_api_key: my_tmdb_key listen_port: 3000 -templates_dir: build/templates public_dir: build/public diff --git a/src/internal/auth/auth.go b/src/internal/auth/auth.go index 7144a68..e9457c6 100644 --- a/src/internal/auth/auth.go +++ b/src/internal/auth/auth.go @@ -3,8 +3,10 @@ package auth import ( "fmt" "net/http" + "strings" + + jwt "github.com/dgrijalva/jwt-go" - "github.com/gorilla/sessions" "golang.org/x/crypto/bcrypt" ) @@ -13,8 +15,8 @@ var ( ErrInvalidPassword = fmt.Errorf("Invalid password") // ErrInvalidSecret returned when cookie's secret is don't match ErrInvalidSecret = fmt.Errorf("Invalid secret") - // ErrCorrupted returned when session have been corrupted - ErrCorrupted = fmt.Errorf("Corrupted session") + // ErrInvalidToken returned when the jwt token is invalid + ErrInvalidToken = fmt.Errorf("Invalid token") ) // UserBackend interface for user backend @@ -27,6 +29,7 @@ type User interface { GetName() string GetHash() string HasRole(string) bool + IsAdmin() bool } // Authorizer handle sesssion @@ -36,11 +39,10 @@ type Authorizer struct { // Params for Authorizer creation type Params struct { - Backend UserBackend - Cookiejar *sessions.CookieStore - CookieName string - Pepper string - Cost int + Backend UserBackend + Pepper string + Cost int + Secret string } // New Authorizer pepper is like a salt but not stored in database, @@ -60,112 +62,56 @@ func (a *Authorizer) GenHash(password string) (string, error) { return string(b), nil } -// Login cheks password and updates cookie info -func (a *Authorizer) Login(rw http.ResponseWriter, req *http.Request, username, password string) error { - cookie, err := a.Cookiejar.Get(req, a.CookieName) - if err != nil { - return err - } - +// Login cheks password and creates a jwt token +func (a *Authorizer) Login(rw http.ResponseWriter, req *http.Request, username, password string) (User, error) { u, err := a.Backend.Get(username) if err != nil { - return err + return nil, err } + // Compare the password err = bcrypt.CompareHashAndPassword([]byte(u.GetHash()), []byte(password+a.Pepper)) if err != nil { - return ErrInvalidPassword - } - - cookie.Values["username"] = username - - // genereate secret - b, err := bcrypt.GenerateFromPassword([]byte(u.GetHash()), a.Cost) - if err != nil { - return err - } - cookie.Values["secret"] = string(b) - - err = cookie.Save(req, rw) - if err != nil { - return err - } - return nil -} - -// RegenSecret update secret in cookie with user info, usefull when updating password -func (a *Authorizer) RegenSecret(user User, w http.ResponseWriter, r *http.Request) error { - cookie, err := a.Cookiejar.Get(r, a.CookieName) - if err != nil { - return err - } - // genereate secret - b, err := bcrypt.GenerateFromPassword([]byte(user.GetHash()), a.Cost) - if err != nil { - return err - } - cookie.Values["secret"] = string(b) - - err = cookie.Save(r, w) - if err != nil { - return err - } - return nil -} - -// Logout remove cookie info -func (a *Authorizer) Logout(rw http.ResponseWriter, req *http.Request) error { - cookie, err := a.Cookiejar.Get(req, a.CookieName) - if err != nil { - return err - } - cookie.Values["username"] = nil - cookie.Values["secret"] = nil - cookie.Options.MaxAge = -1 // kill the cookie - err = cookie.Save(req, rw) - if err != nil { - return err - } - return nil -} - -// CurrentUser returns the logged in username from session -func (a *Authorizer) CurrentUser(rw http.ResponseWriter, req *http.Request) (User, error) { - cookie, err := a.Cookiejar.Get(req, a.CookieName) - if err != nil { - return nil, err - } - if cookie.IsNew { - return nil, nil - } - - usernameTmp := cookie.Values["username"] - if usernameTmp == nil { - return nil, nil - } - username, ok := usernameTmp.(string) - if !ok { - return nil, ErrCorrupted - } - - u, err := a.Backend.Get(username) - if err != nil { - return nil, err - } - - // Check secret - hash := u.GetHash() - secretTmp := cookie.Values["secret"] - if secretTmp == nil { - return nil, nil - } - secret, ok := secretTmp.(string) - if !ok { - return nil, ErrCorrupted - } - err = bcrypt.CompareHashAndPassword([]byte(secret), []byte(hash)) - if err != nil { - return nil, ErrInvalidSecret + return nil, ErrInvalidPassword + } + + return u, nil +} + +// CurrentUser returns the logged in username from session and verifies the token +func (a *Authorizer) CurrentUser(rw http.ResponseWriter, req *http.Request) (User, error) { + h := req.Header.Get("Authorization") + // No user logged + if h == "" { + return nil, nil + } + + // Get the token from the header + tokenStr := strings.Replace(h, "Bearer ", "", -1) + + // Keyfunc to decode the token + var keyfunc jwt.Keyfunc = func(token *jwt.Token) (interface{}, error) { + return []byte(a.Secret), nil + } + + var tokenClaims struct { + Username string `json:"username"` + jwt.StandardClaims + } + token, err := jwt.ParseWithClaims(tokenStr, &tokenClaims, keyfunc) + if err != nil { + return nil, err + } + + // Check the token validity + if !token.Valid { + return nil, ErrInvalidToken + } + + // Get the user + u, err := a.Backend.Get(tokenClaims.Username) + if err != nil { + return nil, err } return u, nil diff --git a/src/internal/auth/auth_test.go b/src/internal/auth/auth_test.go deleted file mode 100644 index 7a9c51c..0000000 --- a/src/internal/auth/auth_test.go +++ /dev/null @@ -1,199 +0,0 @@ -package auth - -import ( - "fmt" - "io/ioutil" - "net/http" - "net/http/cookiejar" - "net/http/httptest" - "testing" - - "github.com/gorilla/mux" - "github.com/kr/pretty" -) - -const ( - pepper = "polp" - ckey = "plop" - cookieName = "auth" - cost = 10 - - username = "plop" - password = "ploppwd" - hash = "$2a$10$eVye8xbs6nj4TWnlTmifRuBsAU3F2hkxEcFz9WXdYjUuE6uKLVuzK" -) - -type user struct { - username string - password string - hash string -} - -func (u *user) GetHash() string { - return u.hash -} - -type Backend struct { - user *user -} - -func (b *Backend) Get(username string) (User, error) { - if username == b.user.username { - return b.user, nil - } - return nil, fmt.Errorf("invalid username") -} - -func getBackend() *Backend { - return &Backend{ - user: &user{ - username: username, - password: password, - hash: hash, - }, - } -} - -func login(w http.ResponseWriter, r *http.Request) { - a := New(getBackend(), pepper, cookieName, ckey, cost) - err := a.Login(w, r, username, password) - if err != nil { - fmt.Fprintf(w, "%s", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - w.WriteHeader(http.StatusOK) - -} -func logout(w http.ResponseWriter, r *http.Request) { - a := New(getBackend(), pepper, cookieName, ckey, cost) - err := a.Logout(w, r) - if err != nil { - fmt.Fprintf(w, "%s", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - w.WriteHeader(http.StatusOK) -} -func check(w http.ResponseWriter, r *http.Request) { - a := New(getBackend(), pepper, cookieName, key, cost) - u, err := a.CurrentUser(w, r) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, "%s", err) - return - } - w.WriteHeader(http.StatusOK) - if u != nil { - usr, ok := u.(*user) - if !ok { - fmt.Fprintf(w, "Invalid user type") - return - } - fmt.Fprintf(w, "%s", usr.username) - } - -} - -func handlers() *mux.Router { - r := mux.NewRouter() - r.HandleFunc("/login", login).Methods("GET") - r.HandleFunc("/logout", logout).Methods("GET") - r.HandleFunc("/check", check).Methods("GET") - return r -} - -func TestAuth(t *testing.T) { - ts := httptest.NewServer(handlers()) - defer ts.Close() - - cookieJar, _ := cookiejar.New(nil) - client := &http.Client{ - Jar: cookieJar, - } - - // Check no user logged in = - res, err := client.Get(ts.URL + "/check") - if err != nil { - t.Fatal(err) - } - - body, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - t.Fatal(err) - } - if res.StatusCode != http.StatusOK { - t.Fatal(body) - } - if string(body) != "" { - t.Fatalf("No user logged in expected but found: %s", body) - } - - // Login - res, err = client.Get(ts.URL + "/login") - if err != nil { - t.Fatal(err) - } - - body, err = ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - t.Fatal(err) - } - if res.StatusCode != http.StatusOK { - t.Fatal(string(body)) - } - - // Checks we are logged in - res, err = client.Get(ts.URL + "/check") - if err != nil { - t.Fatal(err) - } - - body, err = ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - t.Fatal(err) - } - if res.StatusCode != http.StatusOK { - pretty.Println(res.StatusCode) - t.Fatal(body) - } - if string(body) != username { - t.Fatalf("We expect be logged in as %s but we got: %s", username, body) - } - - // Logout - res, err = client.Get(ts.URL + "/logout") - if err != nil { - t.Fatal(err) - } - - body, err = ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - t.Fatal(err) - } - if res.StatusCode != http.StatusOK { - t.Fatal(string(body)) - } - - // Check no username logged in anymore - res, err = client.Get(ts.URL + "/check") - if err != nil { - t.Fatal(err) - } - - body, err = ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - t.Fatal(err) - } - if res.StatusCode != http.StatusOK { - t.Fatal(body) - } - if string(body) != "" { - t.Fatalf("No user logged in expected but found: %s", body) - } -} diff --git a/src/internal/auth/middleware.go b/src/internal/auth/middleware.go index e5cf922..a810288 100644 --- a/src/internal/auth/middleware.go +++ b/src/internal/auth/middleware.go @@ -2,11 +2,8 @@ package auth import ( "context" - "fmt" "net/http" - "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/data" - "github.com/Sirupsen/logrus" ) @@ -27,13 +24,12 @@ func NewMiddleware(authorizer *Authorizer, log *logrus.Entry) *Middleware { 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) + http.Error(w, err.Error(), http.StatusUnauthorized) + return } if user != nil { - name := user.GetName() - m.log.Debugf("setting user %s in the context", name) - data.SetData(r, "user", user) + m.log.Debugf("setting user %s in the context", user.GetName()) } else { m.log.Debugf("got a nil user in the context") } @@ -46,19 +42,17 @@ func (m *Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http // MiddlewareRole handles the role checking for the current user type MiddlewareRole struct { - authorizer *Authorizer - log *logrus.Entry - role string - loginPageGetter func() string + authorizer *Authorizer + log *logrus.Entry + role string } // NewMiddlewareRole returns a new MiddlewareRole -func NewMiddlewareRole(authorizer *Authorizer, log *logrus.Entry, loginPageGetter func() string, role string) *MiddlewareRole { +func NewMiddlewareRole(authorizer *Authorizer, log *logrus.Entry, role string) *MiddlewareRole { return &MiddlewareRole{ - authorizer: authorizer, - log: log.WithField("middleware", "role"), - role: role, - loginPageGetter: loginPageGetter, + authorizer: authorizer, + log: log.WithField("middleware", "role"), + role: role, } } @@ -72,16 +66,8 @@ func (m *MiddlewareRole) ServeHTTP(w http.ResponseWriter, r *http.Request, next m.log.Debug("user doesn't have the role") } - cookie, err := m.authorizer.Cookiejar.Get(r, "rlogin") - if err != nil { - panic(err) - } - cookie.Values["redirect"] = r.URL.Path - err = cookie.Save(r, w) - if err != nil { - panic(err) - } - http.Redirect(w, r, m.loginPageGetter(), http.StatusTemporaryRedirect) + // return unauthorized + http.Error(w, "Invalid user role", http.StatusUnauthorized) return } @@ -90,30 +76,6 @@ func (m *MiddlewareRole) ServeHTTP(w http.ResponseWriter, r *http.Request, next next(w, r) } -// GetPostLoginRedirect returns the location of the page requested before the -// users was redirected to the login page -func GetPostLoginRedirect(a *Authorizer, w http.ResponseWriter, r *http.Request) (string, error) { - cookie, err := a.Cookiejar.Get(r, "rlogin") - if err != nil { - return "", err - } - val := cookie.Values["redirect"] - if val == nil { - return "", nil - } - path, ok := val.(string) - if !ok { - return "", fmt.Errorf("invalid redirect type") - } - cookie.Values["rlogin"] = "" - err = cookie.Save(r, w) - if err != nil { - return "", err - } - return path, nil - -} - // GetCurrentUser gets the current user from the request context func GetCurrentUser(r *http.Request, log *logrus.Entry) User { log.Debug("getting user from context") diff --git a/src/internal/config/canape.go b/src/internal/config/canape.go index 55ff3bf..43dc397 100644 --- a/src/internal/config/canape.go +++ b/src/internal/config/canape.go @@ -15,7 +15,6 @@ type Config struct { PGDSN string `yaml:"pgdsn"` Port string `yaml:"listen_port"` TraktTVClientID string `yaml:"trakttv_client_id"` - TemplatesDir string `yaml:"templates_dir"` PublicDir string `yaml:"public_dir"` // TODO improve the detailers configurations @@ -24,10 +23,9 @@ type Config struct { } type AuthorizerConfig struct { - CookieName string `yaml:"cookie_name"` - Key string `yaml:"cookie_key"` - Pepper string `yaml:"pepper"` - Cost int `yaml:"cost"` + Pepper string `yaml:"pepper"` + Cost int `yaml:"cost"` + Secret string `yaml:"secret"` } func Load(path string) (*Config, error) { diff --git a/src/internal/config/polochon.go b/src/internal/config/polochon.go index 30f5644..cbc1cbf 100644 --- a/src/internal/config/polochon.go +++ b/src/internal/config/polochon.go @@ -2,6 +2,6 @@ package config // UserPolochon is polochon access parameter for a user type UserPolochon struct { - URL string - Token string + URL string `json:"url"` + Token string `json:"token"` } diff --git a/src/internal/data/data.go b/src/internal/data/data.go deleted file mode 100644 index 2a2a8cd..0000000 --- a/src/internal/data/data.go +++ /dev/null @@ -1,42 +0,0 @@ -package data - -import ( - "fmt" - "net/http" - - "context" -) - -type key int - -// dKey is key for access to response data in context -const dKey key = 0 - -// GetAllData return response's data -func GetAllData(r *http.Request) map[string]interface{} { - data := GetData(r, "data") - if data == nil { - data = make(map[string]interface{}) - } - mapData, ok := data.(map[string]interface{}) - if !ok { - fmt.Printf("something wrong with data") - } - // log.Printf("got all data %+v", mapData) - return mapData -} - -// SetData sets some response's data for access in template -func SetData(r *http.Request, key string, val interface{}) { - allData := GetAllData(r) - allData[key] = val - - ctx := context.WithValue(r.Context(), "data", allData) - *r = *r.WithContext(ctx) - allData = GetAllData(r) -} - -// GetData gets some response's data for access in template -func GetData(r *http.Request, key string) interface{} { - return r.Context().Value(key) -} diff --git a/src/internal/movies/handlers.go b/src/internal/movies/handlers.go index e08adbb..b6f270c 100644 --- a/src/internal/movies/handlers.go +++ b/src/internal/movies/handlers.go @@ -15,13 +15,14 @@ import ( "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth" "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/config" - "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/data" "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users" "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web" ) +// ErrPolochonUnavailable is an error returned if the polochon server is not available var ErrPolochonUnavailable = fmt.Errorf("Invalid polochon address") +// SortByTitle helps sort the movies type SortByTitle []*Movie func (a SortByTitle) Len() int { return len(a) } @@ -115,18 +116,10 @@ func FromPolochon(env *web.Env, w http.ResponseWriter, r *http.Request) error { if !ok { return fmt.Errorf("invalid user type") } - data.SetData(r, "user", user) movies, err := getPolochonMovies(user) - if err != nil { - // Catch network error for accessing specified polochon address - if err == ErrPolochonUnavailable { - data.SetData(r, "error", "Invalid address") - return env.Rends(w, r, "movies/library") - } - - return err + return env.RenderError(w, err) } var polochonConfig config.UserPolochon @@ -168,10 +161,10 @@ func FromPolochon(env *web.Env, w http.ResponseWriter, r *http.Request) error { sort.Sort(smovies) - data.SetData(r, "movies", movies[params.Start:params.Start+params.Limit]) - return env.Rends(w, r, "movies/library") + return env.RenderJSON(w, movies) } +// ExplorePopular returns the popular movies func ExplorePopular(env *web.Env, w http.ResponseWriter, r *http.Request) error { log := env.Log.WithField("function", "movies.ExplorePopular") @@ -206,7 +199,6 @@ func ExplorePopular(env *web.Env, w http.ResponseWriter, r *http.Request) error movies = append(movies, movie) ids = append(ids, m.IDs.ImDB) } - data.SetData(r, "movies", movies) - return env.Rends(w, r, "movies/library") + return env.RenderJSON(w, movies) } diff --git a/src/internal/users/handlers.go b/src/internal/users/handlers.go index 122a254..7f95366 100644 --- a/src/internal/users/handlers.go +++ b/src/internal/users/handlers.go @@ -1,58 +1,43 @@ package users import ( + "encoding/json" "fmt" "net/http" + "time" - "github.com/gorilla/Schema" - "github.com/kr/pretty" + jwt "github.com/dgrijalva/jwt-go" "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth" "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/config" - "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/data" "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web" ) -func LoginGETHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error { - return e.Rends(w, r, "users/login") -} - -func SignupGETHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error { - return e.Rends(w, r, "users/signup") -} - func SignupPOSTHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error { - type newForm struct { - Username string - Password string - PasswordVerify string + var data struct { + Username string `json:"username"` + Password string `json:"password"` + PasswordConfirm string `json:"password_confirm"` + } + if err := json.NewDecoder(r.Body).Decode(&data); err != nil { + return err } e.Log.Debugf("creating new user ...") - err := r.ParseForm() - if err != nil { - return err + if data.Password == "" && data.PasswordConfirm != "" { + return e.RenderError(w, fmt.Errorf("Empty password")) } - form := new(newForm) - decoder := schema.NewDecoder() - err = decoder.Decode(form, r.PostForm) - if err != nil { - return err + if data.Password != data.PasswordConfirm { + return e.RenderError(w, fmt.Errorf("Passwords missmatch")) } - user := User{ - Name: form.Username, - } - if form.Password != "" || form.PasswordVerify != "" { - if form.Password != form.PasswordVerify { - data.SetData(r, "Errors", "Password mismatch") - return e.Rends(w, r, "users/signup") - } - user.Hash, err = e.Auth.GenHash(form.Password) - if err != nil { - return err - } + user := User{Name: data.Username} + + var err error + user.Hash, err = e.Auth.GenHash(data.Password) + if err != nil { + return err } err = user.NewConfig() @@ -60,75 +45,58 @@ func SignupPOSTHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error return err } - err = user.Add(e.Database) - if err != nil { - pretty.Println(err) + if err = user.Add(e.Database); err != nil { return err } - e.Log.Debugf("new user %s created ...", form.Username) + e.Log.Debugf("new user %s created ...", data.Username) - err = e.Auth.Login(w, r, form.Username, form.Password) - if err != nil { - if err == auth.ErrInvalidPassword || err == ErrUnknownUser { - data.SetData(r, "Errors", "Error invalid user or password") - return e.Rends(w, r, "users/signup") - } - return err - } - - http.Redirect(w, r, "/", http.StatusTemporaryRedirect) - return nil + return e.RenderOK(w, "User created") } +// LoginPOSTHandler handles the login proccess func LoginPOSTHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error { - type loginForm struct { - Username string - Password string + var data struct { + Username string `json:"username"` + Password string `json:"password"` } - err := r.ParseForm() - if err != nil { + if err := json.NewDecoder(r.Body).Decode(&data); 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) + user, err := e.Auth.Login(w, r, data.Username, data.Password) if err != nil { if err == auth.ErrInvalidPassword || err == ErrUnknownUser { - data.SetData(r, "Errors", "Error invalid user or password") - return e.Rends(w, r, "users/login") + return e.RenderError(w, fmt.Errorf("Error invalid user or password")) } return err } - e.Log.Debug("logged") + e.Log.Debugf("logged %s", user.GetName()) - path, err := auth.GetPostLoginRedirect(e.Auth, w, r) + // Create a jwt token + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + // Not before + "nbf": time.Now().Unix(), + // Issued at + "iat": time.Now().Unix(), + // Private claims + "username": user.GetName(), + "isAdmin": user.IsAdmin(), + }) + + // Sign the token + ss, err := token.SignedString([]byte(e.Auth.Secret)) if err != nil { return err } - e.Log.Debugf("redirecting to %s", path) - if path != "" { - http.Redirect(w, r, path, http.StatusTemporaryRedirect) - return nil + var out = struct { + Token string `json:"token"` + }{ + Token: ss, } - e.Log.Debugf("got no path, redirecting to /") - http.Redirect(w, r, "/", http.StatusTemporaryRedirect) - return nil -} -// LogoutHandler just logout -func LogoutHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error { - e.Auth.Logout(w, r) - - http.Redirect(w, r, "/", http.StatusTemporaryRedirect) - return nil + return e.RenderJSON(w, out) } // DetailsHandler show user details @@ -145,8 +113,7 @@ func DetailsHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error { return err } - data.SetData(r, "polochon", polochonConfig) - return e.Rends(w, r, "users/details") + return e.RenderJSON(w, polochonConfig) } // EditHandler allow editing user info and configuration @@ -156,70 +123,44 @@ func EditHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error { if !ok { return fmt.Errorf("invalid user type") } - var polochonConfig config.UserPolochon - err := user.GetConfig("polochon", &polochonConfig) - if err != nil { + + var data struct { + PolochonURL string `json:"polochon_url"` + PolochonToken string `json:"polochon_token"` + Password string `json:"password"` + PasswordConfirm string `json:"password_confirm"` + } + if err := json.NewDecoder(r.Body).Decode(&data); err != nil { return err } - if r.Method == "GET" { - data.SetData(r, "polochon", polochonConfig) - return e.Rends(w, r, "users/edit") - } - - type editForm struct { - PolochonURL string - PolochonToken string - Password string - PasswordVerify string - } - - err = r.ParseForm() - if err != nil { - return err - } - - form := new(editForm) - decoder := schema.NewDecoder() - err = decoder.Decode(form, r.PostForm) - if err != nil { - return err - } - - polochonConfig.URL = form.PolochonURL - polochonConfig.Token = form.PolochonToken - - err = user.SetConfig("polochon", polochonConfig) - if err != nil { - return err - } - - if form.Password != "" || form.PasswordVerify != "" { - if form.Password != form.PasswordVerify { - // TODO: manage form error + if data.Password == "" && data.PasswordConfirm != "" { + if data.Password != data.PasswordConfirm { + return e.RenderError(w, fmt.Errorf("Passwords empty or missmatch")) } - user.Hash, err = e.Auth.GenHash(form.Password) + + // Update the user config + var err error + user.Hash, err = e.Auth.GenHash(data.Password) if err != nil { return err } + + if err := user.Update(e.Database); err != nil { + return err + } } - err = user.Update(e.Database) - if err != nil { - pretty.Println(err) + // Update the polochon config + var polochonConfig config.UserPolochon + if err := user.GetConfig("polochon", &polochonConfig); err != nil { + return err + } + polochonConfig.URL = data.PolochonURL + polochonConfig.Token = data.PolochonToken + if err := user.SetConfig("polochon", polochonConfig); err != nil { return err } - err = e.Auth.RegenSecret(user, w, r) - if err != nil { - return err - } - - url, err := e.GetURL("users.details") - if err != nil { - return err - } - - http.Redirect(w, r, url, http.StatusTemporaryRedirect) - return nil + return e.RenderOK(w, "user updated") } diff --git a/src/internal/users/users.go b/src/internal/users/users.go index a0f036b..ad1b224 100644 --- a/src/internal/users/users.go +++ b/src/internal/users/users.go @@ -204,3 +204,7 @@ func (u *User) HasRole(role string) bool { } return true } + +func (u *User) IsAdmin() bool { + return u.HasRole(AdminRole) +} diff --git a/src/internal/web/env.go b/src/internal/web/env.go index cabc99c..3b80e54 100644 --- a/src/internal/web/env.go +++ b/src/internal/web/env.go @@ -1,8 +1,6 @@ package web import ( - "fmt" - "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth" "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/config" @@ -34,28 +32,14 @@ type EnvParams struct { // NewEnv returns a new *Env func NewEnv(p EnvParams) *Env { - e := &Env{ + return &Env{ Database: p.Database, Log: p.Log, Router: mux.NewRouter(), Auth: p.Auth, Config: p.Config, + Render: render.New(), } - - tmplFuncs = append(tmplFuncs, map[string]interface{}{ - "URL": func(name string, pairs ...string) (string, error) { - return e.GetURL(name, pairs...) - }, - }) - - e.Render = render.New(render.Options{ - Directory: p.Config.TemplatesDir, - Layout: "layout", - Funcs: tmplFuncs, - DisableHTTPErrorRendering: true, - RequirePartials: true, - }) - return e } type Route struct { @@ -76,7 +60,7 @@ func (r *Route) Methods(methods ...string) *Route { func (r *Route) WithRole(role string) *Route { handler := r.mRoute.GetHandler() newHandler := negroni.New( - auth.NewMiddlewareRole(r.env.Auth, r.env.Log, r.env.GetLoginRouteGetter(), role), + auth.NewMiddlewareRole(r.env.Auth, r.env.Log, role), negroni.Wrap(handler)) r.mRoute.Handler(newHandler) return r @@ -90,39 +74,3 @@ func (e *Env) Handle(route string, H HandlerFunc) *Route { mRoute: mRoute, } } - -// GetURL returns URL string from URL name and params -// Usefull for redirection and templates -func (e *Env) GetURL(name string, pairs ...string) (string, error) { - route := e.Router.Get(name) - if route == nil { - return "", fmt.Errorf("No route find for the given name: %s", name) - } - URL, err := route.URL(pairs...) - if err != nil { - return "", err - } - return URL.String(), nil -} - -// SetLoginRoute sets the route name of login page for further use -// with GetLoginRouteGetter -func (e *Env) SetLoginRoute(name string) error { - route, err := e.GetURL(name) - if err != nil { - return err - } - e.loginRoute = route - return nil -} - -// GetLoginRouteGetter allow some code parts like middleware access -// to login route name -func (e *Env) GetLoginRouteGetter() func() string { - return func() string { - if e.loginRoute == "" { - panic("Env: login route not set") - } - return e.loginRoute - } -} diff --git a/src/internal/web/render.go b/src/internal/web/render.go index 7750799..49aa863 100644 --- a/src/internal/web/render.go +++ b/src/internal/web/render.go @@ -1,48 +1,36 @@ package web -import ( - "html/template" - "net/http" +import "net/http" - "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/data" - - "github.com/gorilla/mux" -) - -// TmplFuncs handles global template functions -var tmplFuncs = []template.FuncMap{ - map[string]interface{}{ - "safeURL": func(s string) template.URL { - return template.URL(s) - }, - }, +// RenderError renders an error +func (e *Env) RenderError(w http.ResponseWriter, err error) error { + return e.render(w, "error", err.Error()) } -// AddTmplFunc adds a template function -func AddTmplFunc(name string, f interface{}) error { - tmplFuncs = append(tmplFuncs, map[string]interface{}{ - name: f, - }) - return nil +// RenderOK renders a message +func (e *Env) RenderOK(w http.ResponseWriter, message string) error { + return e.render(w, "ok", message) } -// TemplateData represents object passed to template renderer -type TemplateData struct { - Route string - Data map[string]interface{} -} - -// Rends a view -func (e *Env) Rends(w http.ResponseWriter, r *http.Request, template string) error { - if r.Header.Get("Accept") == "application/json" { - return e.Render.JSON(w, http.StatusOK, TemplateData{ - Route: mux.CurrentRoute(r).GetName(), - Data: data.GetAllData(r), - }) +func (e *Env) render(w http.ResponseWriter, status, message string) error { + var out = struct { + Status string `json:"status"` + Message string `json:"message"` + }{ + Status: status, + Message: message, } - - return e.Render.HTML(w, http.StatusOK, template, TemplateData{ - Route: mux.CurrentRoute(r).GetName(), - Data: data.GetAllData(r), - }) + return e.Render.JSON(w, http.StatusOK, out) +} + +// RenderJSON renders the data in JSON format +func (e *Env) RenderJSON(w http.ResponseWriter, data interface{}) error { + var out = struct { + Status string `json:"status"` + Data interface{} `json:"data"` + }{ + Status: "ok", + Data: data, + } + return e.Render.JSON(w, http.StatusOK, out) } diff --git a/src/internal/web/templates.go b/src/internal/web/templates.go deleted file mode 100644 index 7750799..0000000 --- a/src/internal/web/templates.go +++ /dev/null @@ -1,48 +0,0 @@ -package web - -import ( - "html/template" - "net/http" - - "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/data" - - "github.com/gorilla/mux" -) - -// TmplFuncs handles global template functions -var tmplFuncs = []template.FuncMap{ - map[string]interface{}{ - "safeURL": func(s string) template.URL { - return template.URL(s) - }, - }, -} - -// AddTmplFunc adds a template function -func AddTmplFunc(name string, f interface{}) error { - tmplFuncs = append(tmplFuncs, map[string]interface{}{ - name: f, - }) - return nil -} - -// TemplateData represents object passed to template renderer -type TemplateData struct { - Route string - Data map[string]interface{} -} - -// Rends a view -func (e *Env) Rends(w http.ResponseWriter, r *http.Request, template string) error { - if r.Header.Get("Accept") == "application/json" { - return e.Render.JSON(w, http.StatusOK, TemplateData{ - Route: mux.CurrentRoute(r).GetName(), - Data: data.GetAllData(r), - }) - } - - return e.Render.HTML(w, http.StatusOK, template, TemplateData{ - Route: mux.CurrentRoute(r).GetName(), - Data: data.GetAllData(r), - }) -} diff --git a/src/main.go b/src/main.go index 16ac02c..71c76a6 100644 --- a/src/main.go +++ b/src/main.go @@ -11,7 +11,6 @@ import ( "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web" "github.com/Sirupsen/logrus" - "github.com/gorilla/sessions" "github.com/jmoiron/sqlx" _ "github.com/lib/pq" "github.com/urfave/negroni" @@ -52,11 +51,10 @@ func main() { uBackend := &UserBackend{Database: db} authParams := auth.Params{ - Backend: uBackend, - Pepper: cf.Authorizer.Pepper, - CookieName: cf.Authorizer.CookieName, - Cookiejar: sessions.NewCookieStore([]byte(cf.Authorizer.Key)), - Cost: cf.Authorizer.Cost, + Backend: uBackend, + Pepper: cf.Authorizer.Pepper, + Cost: cf.Authorizer.Cost, + Secret: cf.Authorizer.Secret, } authorizer := auth.New(authParams) @@ -69,22 +67,13 @@ func main() { authMiddleware := auth.NewMiddleware(env.Auth, log) - env.Handle("/", movies.ExplorePopular).Name("movies.home") - env.Handle("/users/login", users.LoginGETHandler).Name("users.login").Methods("GET") - env.Handle("/users/login", users.LoginPOSTHandler).Name("users.login").Methods("POST") - env.Handle("/users/signup", users.SignupGETHandler).Name("users.signup").Methods("GET") - env.Handle("/users/signup", users.SignupPOSTHandler).Name("users.signup").Methods("POST") - env.Handle("/users/logout", users.LogoutHandler).Name("users.logout") - env.Handle("/users/details", users.DetailsHandler).Name("users.details").WithRole(users.UserRole) - env.Handle("/users/edit", users.EditHandler).Name("users.edit").WithRole(users.UserRole) + env.Handle("/users/login", users.LoginPOSTHandler).Methods("POST") + env.Handle("/users/signup", users.SignupPOSTHandler).Methods("POST") + env.Handle("/users/details", users.DetailsHandler).WithRole(users.UserRole) + env.Handle("/users/edit", users.EditHandler).WithRole(users.UserRole) - env.Handle("/movies/polochon", movies.FromPolochon).Name("movies.polochon").WithRole(users.UserRole) - env.Handle("/movies/explore/popular", movies.ExplorePopular).Name("movies.explore.popular").WithRole(users.UserRole) - - err = env.SetLoginRoute("users.login") - if err != nil { - log.Panic(err) - } + env.Handle("/movies/polochon", movies.FromPolochon).WithRole(users.UserRole) + env.Handle("/movies/explore/popular", movies.ExplorePopular).WithRole(users.UserRole) n := negroni.Classic() n.Use(authMiddleware) diff --git a/src/templates/errors.tmpl b/src/templates/errors.tmpl deleted file mode 100644 index 56877aa..0000000 --- a/src/templates/errors.tmpl +++ /dev/null @@ -1,9 +0,0 @@ -{{ if $.Data.Errors }} -
-
-
-

{{ $.Data.Errors }}

-
-
-
-{{ end }} diff --git a/src/templates/layout.tmpl b/src/templates/layout.tmpl deleted file mode 100644 index f00cec8..0000000 --- a/src/templates/layout.tmpl +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - Canape - - - - - - {{ template "errors" $ }} - {{ template "success" $ }} - {{ template "navbar" $ }} -
-
- {{ yield }} -
-
- - - diff --git a/src/templates/movies/library.tmpl b/src/templates/movies/library.tmpl deleted file mode 100644 index ee7a38b..0000000 --- a/src/templates/movies/library.tmpl +++ /dev/null @@ -1,61 +0,0 @@ -{{ if $.Data.error }} - - {{ if eq $.Data.error "Invalid address"}} - - {{ end }} - -{{ else }} - -
- -
-
- {{ range $.Data.movies }} -
- - - -
- {{ end }} -
-
- -
- {{ range $.Data.movies}} - - {{ end}} -
- -
- -{{ end }} diff --git a/src/templates/navbar.tmpl b/src/templates/navbar.tmpl deleted file mode 100644 index 89c5d12..0000000 --- a/src/templates/navbar.tmpl +++ /dev/null @@ -1,71 +0,0 @@ - diff --git a/src/templates/success.tmpl b/src/templates/success.tmpl deleted file mode 100644 index cd05d1f..0000000 --- a/src/templates/success.tmpl +++ /dev/null @@ -1,9 +0,0 @@ -{{ if $.Data.Success }} -
-
-
- {{ $.Data.Success }} -
-
-
-{{ end }} diff --git a/src/templates/users/details.tmpl b/src/templates/users/details.tmpl deleted file mode 100644 index 77aceb5..0000000 --- a/src/templates/users/details.tmpl +++ /dev/null @@ -1,26 +0,0 @@ -
-
- -
-

{{ $.Data.user.Name }}'s informations

-
- -
-
- Polochon URL -
-
{{ $.Data.polochon.URL }}
-
-
-
- Polochon token -
-
{{ $.Data.polochon.Token }}
-
- -
- Edit -
-
-
-
diff --git a/src/templates/users/edit.tmpl b/src/templates/users/edit.tmpl deleted file mode 100644 index 3d3f719..0000000 --- a/src/templates/users/edit.tmpl +++ /dev/null @@ -1,40 +0,0 @@ -
-
-
-

Edit user

-
- -
- -
- - -
- -
- - -
- -
- -
- - -
- -
- - -
- -
- - Cancel -
-
-
-
-
- - diff --git a/src/templates/users/login.tmpl b/src/templates/users/login.tmpl deleted file mode 100644 index 9872d7b..0000000 --- a/src/templates/users/login.tmpl +++ /dev/null @@ -1,27 +0,0 @@ -
-
-
-

Log in

-
-
-
- -
- -

-
-
- -
- -

-
-
- -
-
- Cancel -
-
-
-
diff --git a/src/templates/users/signup.tmpl b/src/templates/users/signup.tmpl deleted file mode 100644 index 76a9058..0000000 --- a/src/templates/users/signup.tmpl +++ /dev/null @@ -1,39 +0,0 @@ -
-
-
-

Sign up

-
-
-
-

- -
- - -

-
-
-

- -
- - -

-
-
-

- -
- - -

-
-
- -
-
- Cancel -
-
-
-