2017-11-08 03:48:22 +00:00
|
|
|
package core
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2017-11-24 19:56:32 +00:00
|
|
|
"errors"
|
2017-11-08 03:48:22 +00:00
|
|
|
"net/http"
|
2017-12-23 21:22:51 +00:00
|
|
|
"time"
|
2017-11-08 03:48:22 +00:00
|
|
|
|
2017-11-24 19:56:32 +00:00
|
|
|
"github.com/google/uuid"
|
2017-11-15 14:55:15 +00:00
|
|
|
"github.com/gorilla/sessions"
|
2017-11-08 03:48:22 +00:00
|
|
|
"github.com/kirsle/blog/core/models/users"
|
|
|
|
)
|
|
|
|
|
2017-11-15 14:55:15 +00:00
|
|
|
type key int
|
|
|
|
|
|
|
|
const (
|
|
|
|
sessionKey key = iota
|
|
|
|
userKey
|
2017-12-23 21:22:51 +00:00
|
|
|
requestTimeKey
|
2017-11-15 14:55:15 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// SessionLoader gets the Gorilla session store and makes it available on the
|
|
|
|
// Request context.
|
2017-12-23 21:22:51 +00:00
|
|
|
//
|
|
|
|
// SessionLoader is the first custom middleware applied, so it takes the current
|
|
|
|
// datetime to make available later in the request and stores it on the request
|
|
|
|
// context.
|
2017-11-15 14:55:15 +00:00
|
|
|
func (b *Blog) SessionLoader(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
2017-12-23 21:22:51 +00:00
|
|
|
// Store the current datetime on the request context.
|
|
|
|
ctx := context.WithValue(r.Context(), requestTimeKey, time.Now())
|
|
|
|
|
|
|
|
// Get the Gorilla session and make it available in the request context.
|
2017-11-15 14:55:15 +00:00
|
|
|
session, _ := b.store.Get(r, "session")
|
2017-12-23 21:22:51 +00:00
|
|
|
ctx = context.WithValue(ctx, sessionKey, session)
|
|
|
|
|
2017-11-15 14:55:15 +00:00
|
|
|
next(w, r.WithContext(ctx))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Session returns the current request's session.
|
|
|
|
func (b *Blog) Session(r *http.Request) *sessions.Session {
|
|
|
|
ctx := r.Context()
|
|
|
|
if session, ok := ctx.Value(sessionKey).(*sessions.Session); ok {
|
|
|
|
return session
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Error(
|
|
|
|
"Session(): didn't find session in request context! Getting it " +
|
|
|
|
"from the session store instead.",
|
|
|
|
)
|
|
|
|
session, _ := b.store.Get(r, "session")
|
|
|
|
return session
|
|
|
|
}
|
|
|
|
|
2017-11-24 19:56:32 +00:00
|
|
|
// CSRFMiddleware enforces CSRF tokens on all POST requests.
|
|
|
|
func (b *Blog) CSRFMiddleware(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
|
|
|
if r.Method == "POST" {
|
|
|
|
session := b.Session(r)
|
2017-11-27 03:44:36 +00:00
|
|
|
token := b.GenerateCSRFToken(w, r, session)
|
|
|
|
if token != r.FormValue("_csrf") {
|
|
|
|
log.Error("CSRF Mismatch: expected %s, got %s", r.FormValue("_csrf"), token)
|
2017-11-24 19:56:32 +00:00
|
|
|
b.Forbidden(w, r, "Failed to validate CSRF token. Please try your request again.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
next(w, r)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GenerateCSRFToken generates a CSRF token for the user and puts it in their session.
|
|
|
|
func (b *Blog) GenerateCSRFToken(w http.ResponseWriter, r *http.Request, session *sessions.Session) string {
|
|
|
|
token, ok := session.Values["csrf"].(string)
|
|
|
|
if !ok {
|
|
|
|
token := uuid.New()
|
|
|
|
session.Values["csrf"] = token.String()
|
|
|
|
session.Save(r, w)
|
|
|
|
}
|
|
|
|
return token
|
|
|
|
}
|
|
|
|
|
|
|
|
// CurrentUser returns the current user's object.
|
|
|
|
func (b *Blog) CurrentUser(r *http.Request) (*users.User, error) {
|
2017-11-15 14:55:15 +00:00
|
|
|
session := b.Session(r)
|
2017-11-08 03:48:22 +00:00
|
|
|
if loggedIn, ok := session.Values["logged-in"].(bool); ok && loggedIn {
|
|
|
|
id := session.Values["user-id"].(int)
|
2017-11-24 19:56:32 +00:00
|
|
|
u, err := users.LoadReadonly(id)
|
2017-11-26 23:53:10 +00:00
|
|
|
u.IsAuthenticated = true
|
2017-11-24 19:56:32 +00:00
|
|
|
return u, err
|
|
|
|
}
|
|
|
|
|
2017-11-24 20:53:13 +00:00
|
|
|
return &users.User{
|
|
|
|
Admin: false,
|
|
|
|
}, errors.New("not authenticated")
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoggedIn returns whether the current user is logged in to an account.
|
|
|
|
func (b *Blog) LoggedIn(r *http.Request) bool {
|
|
|
|
session := b.Session(r)
|
|
|
|
if loggedIn, ok := session.Values["logged-in"].(bool); ok && loggedIn {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
2017-11-24 19:56:32 +00:00
|
|
|
}
|
2017-11-08 03:48:22 +00:00
|
|
|
|
2017-11-24 19:56:32 +00:00
|
|
|
// AuthMiddleware loads the user's authentication state.
|
|
|
|
func (b *Blog) AuthMiddleware(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
|
|
|
u, err := b.CurrentUser(r)
|
|
|
|
if err != nil {
|
|
|
|
next(w, r)
|
2017-11-15 14:55:15 +00:00
|
|
|
return
|
2017-11-08 03:48:22 +00:00
|
|
|
}
|
2017-11-24 19:56:32 +00:00
|
|
|
|
|
|
|
ctx := context.WithValue(r.Context(), userKey, u)
|
|
|
|
next(w, r.WithContext(ctx))
|
2017-11-08 03:48:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// LoginRequired is a middleware that requires a logged-in user.
|
|
|
|
func (b *Blog) LoginRequired(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
|
|
|
ctx := r.Context()
|
|
|
|
if user, ok := ctx.Value(userKey).(*users.User); ok {
|
|
|
|
if user.ID > 0 {
|
|
|
|
next(w, r)
|
2017-11-15 14:55:15 +00:00
|
|
|
return
|
2017-11-08 03:48:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-15 14:55:15 +00:00
|
|
|
log.Info("Redirect away!")
|
2017-11-08 03:48:22 +00:00
|
|
|
b.Redirect(w, "/login?next="+r.URL.Path)
|
|
|
|
}
|