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 := mux.NewRouter()
router.Use(middlewares.Session(sqlite.SessionRepository))
router.Use(middlewares.Session(sqlite.SessionRepository, sqlite.UserRepository))
groupController.Mount(router, "/api/group/")
userController.Mount(router, "/api/user/")

1
go.mod

@ -3,6 +3,7 @@ module git.aiterp.net/lucifer/lucifer
require (
github.com/collinux/GoHue v0.0.0-20181229002551-d259041d5eb8 // indirect
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/mux v1.6.2
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/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
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/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
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 (
"net/http"
"strings"
"time"
"git.aiterp.net/lucifer/lucifer/internal/httperr"
"git.aiterp.net/lucifer/lucifer/models"
"github.com/gorilla/mux"
)
// Session is a middleware that adds a Session to the request context if there
// is one.
func Session(repo models.SessionRepository) mux.MiddlewareFunc {
func Session(sessions models.SessionRepository, users models.UserRepository) mux.MiddlewareFunc {
clearCookie := &http.Cookie{
Name: "lucifer_session",
Value: "",
@ -20,32 +22,51 @@ func Session(repo models.SessionRepository) mux.MiddlewareFunc {
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 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Find cookie
cookie, err := r.Cookie("lucifer_session")
if err != nil || cookie == nil {
next.ServeHTTP(w, r)
redirectFailure(next, w, r)
return
}
// 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 {
http.SetCookie(w, clearCookie)
next.ServeHTTP(w, r)
redirectFailure(next, w, r)
return
}
ctx = user.InContext(ctx)
// Check if session has expired
if session.Expired() {
http.SetCookie(w, clearCookie)
next.ServeHTTP(w, r)
redirectFailure(next, w, r)
return
}
// 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"
)
type userCtxKeyType string
const userCtxKey = userCtxKeyType("luciter_user")
// The User model represents a registered user.
type User struct {
ID int `db:"id" json:"id"`
@ -42,6 +46,21 @@ func (user *User) CheckPassword(attempt string) error {
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
// the user model makes.
type UserRepository interface {

Loading…
Cancel
Save