Add Env.Handle and Env.HandleRole functions
This commit is contained in:
parent
054f5103e6
commit
5778fd45df
@ -25,6 +25,7 @@ type UserBackend interface {
|
||||
// User interface for user
|
||||
type User interface {
|
||||
GetHash() string
|
||||
HasRole(string) bool
|
||||
}
|
||||
|
||||
// Authorizer handle sesssion
|
||||
|
61
auth/middleware.go
Normal file
61
auth/middleware.go
Normal file
@ -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
|
||||
}
|
13
main.go
13
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")
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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();
|
||||
|
1
sqltest/001_data.down.sql
Normal file
1
sqltest/001_data.down.sql
Normal file
@ -0,0 +1 @@
|
||||
DELETE FROM users;
|
2
sqltest/001_data.up.sql
Normal file
2
sqltest/001_data.up.sql
Normal file
@ -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);
|
@ -3,16 +3,8 @@
|
||||
<title>My Layout</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/vendors/bootstrap/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/vendors/sweetalert/lib/sweet-alert.css">
|
||||
<link rel="stylesheet" href="/css/app.css">
|
||||
</head>
|
||||
<body>
|
||||
{{ template "navbar" $ }}
|
||||
{{ yield }}
|
||||
<script src="/vendors/jquery/dist/jquery.min.js"></script>
|
||||
<script src="/vendors/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||
<script src="/vendors/sweetalert/lib/sweet-alert.min.js"></script>
|
||||
<script src="/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,161 +0,0 @@
|
||||
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="#">Canapé</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
|
||||
{{ if .Data.user.IsLogged }}
|
||||
<li {{ if eq .Name "MoviesLibrary" }} class="active" {{ end }}><a href="{{ .Env.GetURL "MoviesLibrary" }}">Movies</a></li>
|
||||
<li {{ if eq .Name "TVShowsLibrary" }} class="active" {{ end }}><a href="{{ .Env.GetURL "TVShowsLibrary" }}">Shows</a></li>
|
||||
{{ end }}
|
||||
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fa fa-list"></i>
|
||||
Explore
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li>
|
||||
<a href="{{.Env.GetURL "TVShowsExplore" }}">
|
||||
TVShows
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ .Env.GetURL "MoviesExplore" "sort" "seeds" }}">
|
||||
Movies
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
{{ if .Data.user.IsLogged }}
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fa fa-list"></i>
|
||||
My Watchlist
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li>
|
||||
<a href="{{.Env.GetURL "TVShowsWichlist" }}">
|
||||
TVShows
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ .Env.GetURL "MoviesWichlist" }}">
|
||||
Movies
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
|
||||
{{ if .Data.user.Username }}
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fa fa-user"></i>
|
||||
{{ .Data.user.Username }}
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li>
|
||||
<a href="{{ .Env.GetURL "UsersDetails" }}" rel="nofollow">
|
||||
<i class="fa fa-user"></i> My Account
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a data-method="delete" href="{{ .Env.GetURL "logout" }}" rel="nofollow">
|
||||
<i class="fa fa-sign-out"></i> Sign out
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
{{ else }}
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li>
|
||||
<a href="{{ .Env.GetURL "login" }}">Sign in</a>
|
||||
</li>
|
||||
</ul>
|
||||
{{ end }}
|
||||
|
||||
|
||||
{{ if or (eq $.Name "MoviesLibrary") (eq $.Name "MoviesExplore") }}
|
||||
<div class="hidden-sm hidden-md">
|
||||
<form class="navbar-form navbar-right" role="search" action="{{ $.Env.GetURL "MoviesSearch" }}" method="get">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" size="13" placeholder="search movies" name="keyword" /><br/>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default">search</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="visible-sm visible-md">
|
||||
<ul class="nav navbar-nav navbar-right"> <li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fa fa-search"></i>
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<form class="navbar-form" role="search" action="{{ $.Env.GetURL "MoviesSearch" }}" method="get">
|
||||
<li>
|
||||
<input type="text" class="form-control" size="13" placeholder="search movies" name="keyword" /><br/>
|
||||
</li>
|
||||
<button type="submit" class="btn btn-default col-xs-12">search</button>
|
||||
</form>
|
||||
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
|
||||
{{ if or (eq $.Name "TVShowsLibrary") (eq $.Name "TVShowsExplore") }}
|
||||
<div class="hidden-sm hidden-md">
|
||||
<form class="navbar-form navbar-right" role="search" action="{{ $.Env.GetURL "TVShowsSearch" }}" method="GET">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" size="13" placeholder="Search TV Shows" name="keyword" /><br/>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default">Search</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="visible-sm visible-md">
|
||||
<ul class="nav navbar-nav navbar-right"> <li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fa fa-search"></i>
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<form class="navbar-form" role="search" action="{{ $.Env.GetURL "TVShowsSearch" }}" method="GET">
|
||||
<li>
|
||||
<input type="text" class="form-control" size="13" placeholder="Search TV Shows" name="keyword" /><br/>
|
||||
</li>
|
||||
<button type="submit" class="btn btn-default col-xs-12">Search</button>
|
||||
</form>
|
||||
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
@ -1 +1,5 @@
|
||||
hello wolrd
|
||||
<form action="/users/login" method="post">
|
||||
<input type="text" name="Username">
|
||||
<input type="text" name="Password">
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
|
@ -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
|
||||
func LoginHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
if r.Method == "GET" {
|
||||
return e.Rends(w, r, "users/login")
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
@ -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,6 +20,11 @@ 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")
|
||||
|
||||
@ -28,6 +33,7 @@ type User struct {
|
||||
sqly.BaseModel
|
||||
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
|
||||
}
|
||||
|
20
web/env.go
20
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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user