Browse Source

lucifer-server, middlewares, httperr: Made session middleware sensible.

login_bugfix
Gisle Aune 5 years ago
parent
commit
22dddc749e
  1. 2
      cmd/lucifer-server/main.go
  2. 1
      go.mod
  3. 2
      go.sum
  4. 55
      internal/httperr/error.go
  5. 33
      middlewares/session.go
  6. 19
      models/user.go

2
cmd/lucifer-server/main.go

@ -47,7 +47,7 @@ func main() {
// Router // Router
router := mux.NewRouter() router := mux.NewRouter()
router.Use(middlewares.Session(sqlite.SessionRepository))
router.Use(middlewares.Session(sqlite.SessionRepository, sqlite.UserRepository))
groupController.Mount(router, "/api/group/") groupController.Mount(router, "/api/group/")
userController.Mount(router, "/api/user/") userController.Mount(router, "/api/user/")

1
go.mod

@ -3,6 +3,7 @@ module git.aiterp.net/lucifer/lucifer
require ( require (
github.com/collinux/GoHue v0.0.0-20181229002551-d259041d5eb8 // indirect github.com/collinux/GoHue v0.0.0-20181229002551-d259041d5eb8 // indirect
github.com/collinux/gohue v0.0.0-20181229002551-d259041d5eb8 github.com/collinux/gohue v0.0.0-20181229002551-d259041d5eb8
github.com/google/uuid v1.1.0
github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/mux v1.6.2 github.com/gorilla/mux v1.6.2
github.com/jmoiron/sqlx v1.2.0 github.com/jmoiron/sqlx v1.2.0

2
go.sum

@ -5,6 +5,8 @@ github.com/collinux/gohue v0.0.0-20181229002551-d259041d5eb8/go.mod h1:HFm7vkh/1
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/uuid v1.1.0 h1:Jf4mxPC/ziBnoPIdpQdPJ9OeiomAUHLvxmPRSPH9m4s=
github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=

55
internal/httperr/error.go

@ -0,0 +1,55 @@
package httperr
import (
"fmt"
"log"
"net/http"
"strconv"
"time"
"git.aiterp.net/lucifer/lucifer/internal/respond"
"github.com/google/uuid"
)
// Error is an error that can be used.
type Error struct {
Status int
Kind string
Message string
}
func (err Error) Error() string {
return err.Kind + ": " + err.Message
}
// ErrLoginRequired is a common error for when a session is expected, but none is found.
var ErrLoginRequired = Error{Status: 401, Kind: "login_required", Message: "You are not logged in."}
// NotFound generates a 404 error.
func NotFound(model string) Error {
return Error{Status: 404, Kind: "not_found", Message: model + " not found"}
}
// Respond responds with the error using the format in the `respond` package. If
// the error is not a httperr.Error, then it'll be logged and a 500 will be returned.
func Respond(w http.ResponseWriter, err error) {
if httpErr, ok := err.(Error); ok {
respond.Error(w, httpErr.Status, httpErr.Kind, httpErr.Message)
} else {
errIDStr := ""
errID, err2 := uuid.NewRandom()
if err2 != nil {
errID, err2 = uuid.NewUUID()
if err2 != nil {
errIDStr = strconv.FormatInt(time.Now().UnixNano(), 36)
} else {
errIDStr = errID.String()
}
} else {
errIDStr = errID.String()
}
log.Printf("ERROR [%s]: %s", errIDStr, err.Error())
respond.Error(w, 500, "internal_error", fmt.Sprintf("Something went wrong. It has been logged with the ID %s", errIDStr))
}
}

33
middlewares/session.go

@ -2,15 +2,17 @@ package middlewares
import ( import (
"net/http" "net/http"
"strings"
"time" "time"
"git.aiterp.net/lucifer/lucifer/internal/httperr"
"git.aiterp.net/lucifer/lucifer/models" "git.aiterp.net/lucifer/lucifer/models"
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
// Session is a middleware that adds a Session to the request context if there // Session is a middleware that adds a Session to the request context if there
// is one. // is one.
func Session(repo models.SessionRepository) mux.MiddlewareFunc {
func Session(sessions models.SessionRepository, users models.UserRepository) mux.MiddlewareFunc {
clearCookie := &http.Cookie{ clearCookie := &http.Cookie{
Name: "lucifer_session", Name: "lucifer_session",
Value: "", Value: "",
@ -20,32 +22,51 @@ func Session(repo models.SessionRepository) mux.MiddlewareFunc {
HttpOnly: true, HttpOnly: true,
} }
redirectFailure := func(next http.Handler, w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/api/user/") {
next.ServeHTTP(w, r)
} else {
httperr.Respond(w, httperr.ErrLoginRequired)
}
}
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Find cookie // Find cookie
cookie, err := r.Cookie("lucifer_session") cookie, err := r.Cookie("lucifer_session")
if err != nil || cookie == nil { if err != nil || cookie == nil {
next.ServeHTTP(w, r)
redirectFailure(next, w, r)
return return
} }
// Check session existence // Check session existence
session, err := repo.FindByID(r.Context(), cookie.Value)
session, err := sessions.FindByID(r.Context(), cookie.Value)
if err != nil {
http.SetCookie(w, clearCookie)
redirectFailure(next, w, r)
return
}
ctx = session.InContext(ctx)
user, err := users.FindByID(r.Context(), session.UserID)
if err != nil { if err != nil {
http.SetCookie(w, clearCookie) http.SetCookie(w, clearCookie)
next.ServeHTTP(w, r)
redirectFailure(next, w, r)
return return
} }
ctx = user.InContext(ctx)
// Check if session has expired // Check if session has expired
if session.Expired() { if session.Expired() {
http.SetCookie(w, clearCookie) http.SetCookie(w, clearCookie)
next.ServeHTTP(w, r)
redirectFailure(next, w, r)
return return
} }
// Proceed. // Proceed.
next.ServeHTTP(w, r.WithContext(session.InContext(r.Context())))
next.ServeHTTP(w, r.WithContext(ctx))
}) })
} }
} }

19
models/user.go

@ -8,6 +8,10 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
type userCtxKeyType string
const userCtxKey = userCtxKeyType("luciter_user")
// The User model represents a registered user. // The User model represents a registered user.
type User struct { type User struct {
ID int `db:"id" json:"id"` ID int `db:"id" json:"id"`
@ -42,6 +46,21 @@ func (user *User) CheckPassword(attempt string) error {
return nil return nil
} }
// InContext returns a child context with the value.
func (user *User) InContext(parent context.Context) context.Context {
return context.WithValue(parent, userCtxKey, user)
}
// UserFromContext gets the user from context, or `nil` if no user is available.
func UserFromContext(ctx context.Context) *User {
v := ctx.Value(userCtxKey)
if v == nil {
return nil
}
return v.(*User)
}
// UserRepository is an interface for all database operations // UserRepository is an interface for all database operations
// the user model makes. // the user model makes.
type UserRepository interface { type UserRepository interface {

Loading…
Cancel
Save