go: implement session auth middleware
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
* simplify protection of endpoints * role discernment still occures in respective handlers * db client needs to be passed into handlers as a global var now
This commit is contained in:
parent
1f11b71341
commit
dbd0e9d01d
@ -19,6 +19,7 @@ func (a *App) SetupRoutes() {
|
||||
tmpls := a.getTemplates()
|
||||
|
||||
modtmpl.Init(setting, tmpls)
|
||||
handlers.SetDBClient(a.db)
|
||||
// run this before declaring any handler funcs.
|
||||
handlers.InitHandlers(setting)
|
||||
|
||||
@ -48,10 +49,10 @@ func (a *App) SetupRoutes() {
|
||||
e.POST("/signup", handlers.SignupPost(a.db))
|
||||
e.GET("/home", handlers.Home(a.db))
|
||||
|
||||
e.GET("/manage/users", handlers.ManageUsers(a.db))
|
||||
e.GET("/manage/users/new", handlers.ManageUsers(a.db))
|
||||
e.GET("/manage/users/:id", handlers.ViewUser(a.db))
|
||||
e.POST("/manage/users/create", handlers.CreateUser(a.db))
|
||||
e.GET("/manage/users", handlers.ManageUsers(), handlers.MiddlewareSession)
|
||||
e.GET("/manage/users/new", handlers.ManageUsers(), handlers.MiddlewareSession)
|
||||
e.GET("/manage/users/:id", handlers.ViewUser(), handlers.MiddlewareSession)
|
||||
e.POST("/manage/users/create", handlers.CreateUser(), handlers.MiddlewareSession)
|
||||
|
||||
e.GET("/logout", handlers.Logout())
|
||||
e.POST("/logout", handlers.Logout())
|
||||
|
@ -5,17 +5,23 @@ package handlers
|
||||
|
||||
import (
|
||||
"git.dotya.ml/mirre-mt/pcmt/app/settings"
|
||||
"git.dotya.ml/mirre-mt/pcmt/ent"
|
||||
"git.dotya.ml/mirre-mt/pcmt/slogging"
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
var (
|
||||
setting *settings.Settings
|
||||
appver string
|
||||
slogger *slogging.Slogger
|
||||
log slogging.Slogger
|
||||
setting *settings.Settings
|
||||
appver string
|
||||
slogger *slogging.Slogger
|
||||
log slogging.Slogger
|
||||
dbclient *ent.Client
|
||||
)
|
||||
|
||||
func SetDBClient(client *ent.Client) {
|
||||
dbclient = client
|
||||
}
|
||||
|
||||
func InitHandlers(s *settings.Settings) {
|
||||
slogger = slogging.Logger()
|
||||
log = *slogger // have a local copy.
|
||||
|
@ -4,12 +4,18 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoSession = errors.New("No session found, please log in")
|
||||
ErrSessionExpired = errors.New("Session expired, log in again")
|
||||
)
|
||||
|
||||
func renderErrorPage(c echo.Context, status int, statusText, error string) error {
|
||||
addHeaders(c)
|
||||
|
||||
|
@ -11,63 +11,39 @@ import (
|
||||
|
||||
"git.dotya.ml/mirre-mt/pcmt/ent"
|
||||
moduser "git.dotya.ml/mirre-mt/pcmt/modules/user"
|
||||
"github.com/labstack/echo-contrib/session"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func ManageUsers(client *ent.Client) echo.HandlerFunc { //nolint:gocognit
|
||||
func ManageUsers() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
addHeaders(c)
|
||||
|
||||
sess, _ := session.Get(setting.SessionCookieName(), c)
|
||||
if sess == nil {
|
||||
c.Logger().Info("No session found, unauthorised.")
|
||||
|
||||
u, ok := c.Get("sessUsr").(moduser.User)
|
||||
if !ok {
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusUnauthorized,
|
||||
http.StatusText(http.StatusUnauthorized),
|
||||
"No session found, please log in",
|
||||
"it appears there is no user",
|
||||
)
|
||||
}
|
||||
|
||||
uname := sess.Values["username"]
|
||||
if uname == nil {
|
||||
c.Logger().Debugf("%d - %s", http.StatusUnauthorized, "session expired or invalid")
|
||||
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusUnauthorized,
|
||||
http.StatusText(http.StatusUnauthorized)+": Log in again",
|
||||
"Session expired, log in again.",
|
||||
)
|
||||
}
|
||||
|
||||
log.Info("gorilla session", "username", sess.Values["username"].(string))
|
||||
|
||||
username := sess.Values["username"].(string)
|
||||
|
||||
var u moduser.User
|
||||
|
||||
ctx := context.WithValue(context.Background(), moduser.CtxKey{}, slogger)
|
||||
if usr, err := moduser.QueryUser(ctx, client, username); err == nil && usr != nil {
|
||||
u.ID = usr.ID
|
||||
u.Username = usr.Username
|
||||
u.IsAdmin = usr.IsAdmin
|
||||
u.CreatedAt = usr.CreatedAt
|
||||
u.IsActive = usr.IsActive
|
||||
u.IsLoggedIn = true
|
||||
} else {
|
||||
c.Logger().Error(http.StatusText(http.StatusInternalServerError) + " - " + err.Error())
|
||||
|
||||
sess, ok := c.Get("sess").(*sessions.Session)
|
||||
if !ok {
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusInternalServerError,
|
||||
http.StatusText(http.StatusInternalServerError),
|
||||
err.Error(),
|
||||
"missing the session",
|
||||
)
|
||||
}
|
||||
|
||||
ctx, ok := c.Get("sloggerCtx").(context.Context)
|
||||
if !ok {
|
||||
ctx = context.WithValue(context.Background(), moduser.CtxKey{}, slogger)
|
||||
}
|
||||
|
||||
if !u.IsAdmin {
|
||||
c.Logger().Debug("this is a restricted endpoint")
|
||||
|
||||
@ -121,7 +97,7 @@ func ManageUsers(client *ent.Client) echo.HandlerFunc { //nolint:gocognit
|
||||
|
||||
var allUsers []*moduser.User
|
||||
|
||||
if users, err := moduser.ListAll(ctx, client); err == nil && users != nil {
|
||||
if users, err := moduser.ListAll(ctx, dbclient); err == nil && users != nil {
|
||||
for _, u := range users {
|
||||
usr := &moduser.User{
|
||||
Username: u.Username,
|
||||
@ -185,57 +161,23 @@ func ManageUsers(client *ent.Client) echo.HandlerFunc { //nolint:gocognit
|
||||
}
|
||||
}
|
||||
|
||||
func CreateUser(client *ent.Client) echo.HandlerFunc { //nolint:gocognit
|
||||
func CreateUser() echo.HandlerFunc { //nolint:gocognit
|
||||
return func(c echo.Context) error {
|
||||
addHeaders(c)
|
||||
|
||||
sess, _ := session.Get(setting.SessionCookieName(), c)
|
||||
if sess == nil {
|
||||
c.Logger().Info("No session found, unauthorised.")
|
||||
|
||||
u, ok := c.Get("sessUsr").(moduser.User)
|
||||
if !ok {
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusUnauthorized,
|
||||
http.StatusText(http.StatusUnauthorized),
|
||||
"No session found, please log in",
|
||||
"username was nil",
|
||||
)
|
||||
}
|
||||
|
||||
uname := sess.Values["username"]
|
||||
if uname == nil {
|
||||
c.Logger().Debugf("%d - %s", http.StatusUnauthorized, "session expired or invalid")
|
||||
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusUnauthorized,
|
||||
http.StatusText(http.StatusUnauthorized)+": Log in again",
|
||||
"Session expired, log in again.",
|
||||
)
|
||||
}
|
||||
|
||||
log.Info("gorilla session", "username", sess.Values["username"].(string))
|
||||
|
||||
username := sess.Values["username"].(string)
|
||||
|
||||
var u moduser.User
|
||||
|
||||
ctx := context.WithValue(context.Background(), moduser.CtxKey{}, slogger)
|
||||
if usr, err := moduser.QueryUser(ctx, client, username); err == nil && usr != nil {
|
||||
u.ID = usr.ID
|
||||
u.Username = usr.Username
|
||||
u.IsAdmin = usr.IsAdmin
|
||||
u.CreatedAt = usr.CreatedAt
|
||||
u.IsActive = usr.IsActive
|
||||
u.IsLoggedIn = true
|
||||
} else {
|
||||
c.Logger().Error(http.StatusText(http.StatusInternalServerError) + " - " + err.Error())
|
||||
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusInternalServerError,
|
||||
http.StatusText(http.StatusInternalServerError),
|
||||
err.Error(),
|
||||
)
|
||||
ctx, ok := c.Get("sloggerCtx").(context.Context)
|
||||
if !ok {
|
||||
ctx = context.WithValue(context.Background(), moduser.CtxKey{}, slogger)
|
||||
}
|
||||
|
||||
if !u.IsAdmin {
|
||||
@ -249,6 +191,14 @@ func CreateUser(client *ent.Client) echo.HandlerFunc { //nolint:gocognit
|
||||
)
|
||||
}
|
||||
|
||||
p := page{
|
||||
AppName: setting.AppName(),
|
||||
AppVer: appver,
|
||||
Title: "Manage Users",
|
||||
DevelMode: setting.IsDevel(),
|
||||
Current: "manage-user",
|
||||
}
|
||||
data := make(map[string]any)
|
||||
uc := new(userCreate)
|
||||
|
||||
if err := c.Bind(uc); err != nil {
|
||||
@ -269,33 +219,27 @@ func CreateUser(client *ent.Client) echo.HandlerFunc { //nolint:gocognit
|
||||
msg += "; password needs to be passed the same twice"
|
||||
}
|
||||
|
||||
data := make(map[string]any)
|
||||
|
||||
data["flash"] = msg
|
||||
data["form"] = uc
|
||||
p.Data = data
|
||||
|
||||
return c.Render(
|
||||
http.StatusBadRequest,
|
||||
"manage/user-new.tmpl",
|
||||
page{
|
||||
AppName: setting.AppName(),
|
||||
AppVer: appver,
|
||||
Title: "Manage Users - New User",
|
||||
DevelMode: setting.IsDevel(),
|
||||
Current: "manage-user-new",
|
||||
Data: data,
|
||||
},
|
||||
p,
|
||||
)
|
||||
}
|
||||
|
||||
var msg string
|
||||
|
||||
usr, err := moduser.CreateUser(ctx, client, uc.Email, uc.Username, uc.Password, uc.IsAdmin)
|
||||
usr, err := moduser.CreateUser(ctx, dbclient, uc.Email, uc.Username, uc.Password, uc.IsAdmin)
|
||||
if err == nil && usr != nil {
|
||||
msg = "created user '" + usr.Username + "'!"
|
||||
|
||||
sess.Values["flash"] = msg
|
||||
_ = sess.Save(c.Request(), c.Response())
|
||||
if sess, ok := c.Get("sess").(*sessions.Session); ok {
|
||||
sess.Values["flash"] = msg
|
||||
_ = sess.Save(c.Request(), c.Response())
|
||||
}
|
||||
|
||||
return c.Redirect(http.StatusSeeOther, "/manage/users")
|
||||
}
|
||||
@ -308,98 +252,35 @@ func CreateUser(client *ent.Client) echo.HandlerFunc { //nolint:gocognit
|
||||
msg = "Error: " + err.Error()
|
||||
}
|
||||
|
||||
data := make(map[string]any)
|
||||
|
||||
data["flash"] = msg
|
||||
data["form"] = uc
|
||||
p.Data = data
|
||||
|
||||
return c.Render(
|
||||
http.StatusInternalServerError,
|
||||
"manage/user-new.tmpl",
|
||||
page{
|
||||
AppName: setting.AppName(),
|
||||
AppVer: appver,
|
||||
Title: "Manage Users",
|
||||
DevelMode: setting.IsDevel(),
|
||||
Current: "manage-user",
|
||||
Data: data,
|
||||
},
|
||||
p,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func ViewUser(client *ent.Client) echo.HandlerFunc {
|
||||
func ViewUser() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
addHeaders(c)
|
||||
|
||||
sess, _ := session.Get(setting.SessionCookieName(), c)
|
||||
if sess == nil {
|
||||
c.Logger().Info("No session found, unauthorised.")
|
||||
|
||||
u, ok := c.Get("sessUsr").(moduser.User)
|
||||
if !ok {
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusUnauthorized,
|
||||
http.StatusText(http.StatusUnauthorized),
|
||||
"No session found, please log in",
|
||||
"username was nil",
|
||||
)
|
||||
}
|
||||
|
||||
uname := sess.Values["username"]
|
||||
if uname == nil {
|
||||
c.Logger().Debugf("%d - %s", http.StatusUnauthorized, "session expired or invalid")
|
||||
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusUnauthorized,
|
||||
http.StatusText(http.StatusUnauthorized)+": Log in again",
|
||||
"Session expired, log in again.",
|
||||
)
|
||||
}
|
||||
|
||||
log.Info("gorilla session", "username", sess.Values["username"].(string))
|
||||
|
||||
username := sess.Values["username"].(string)
|
||||
|
||||
var u moduser.User
|
||||
|
||||
ctx := context.WithValue(context.Background(), moduser.CtxKey{}, slogger)
|
||||
if usr, err := moduser.QueryUser(ctx, client, username); err == nil && usr != nil {
|
||||
u.ID = usr.ID
|
||||
u.Username = usr.Username
|
||||
u.IsAdmin = usr.IsAdmin
|
||||
u.CreatedAt = usr.CreatedAt
|
||||
u.IsActive = usr.IsActive
|
||||
u.IsLoggedIn = true
|
||||
} else {
|
||||
c.Logger().Error(http.StatusText(http.StatusInternalServerError) + " - " + err.Error())
|
||||
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusInternalServerError,
|
||||
http.StatusText(http.StatusInternalServerError),
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
refreshSession(
|
||||
sess,
|
||||
"/",
|
||||
// setting.SessionMaxAge,
|
||||
86400,
|
||||
true,
|
||||
c.Request().URL.Scheme == "https", //nolint:goconst
|
||||
http.SameSiteStrictMode,
|
||||
)
|
||||
|
||||
if err := sess.Save(c.Request(), c.Response()); err != nil {
|
||||
c.Logger().Error("failed to save session")
|
||||
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusInternalServerError,
|
||||
http.StatusText(http.StatusInternalServerError)+" (make sure you've got cookies enabled)",
|
||||
err.Error(),
|
||||
)
|
||||
ctx, ok := c.Get("sloggerCtx").(context.Context)
|
||||
if !ok {
|
||||
ctx = context.WithValue(context.Background(), moduser.CtxKey{}, slogger)
|
||||
}
|
||||
|
||||
if !u.IsAdmin {
|
||||
@ -426,7 +307,7 @@ func ViewUser(client *ent.Client) echo.HandlerFunc {
|
||||
|
||||
err := c.Bind(uid)
|
||||
if err == nil {
|
||||
usr, err := getUserByID(ctx, client, uid.ID)
|
||||
usr, err := getUserByID(ctx, dbclient, uid.ID)
|
||||
if err != nil {
|
||||
if errors.Is(err, moduser.ErrUserNotFound) { //nolint:gocritic
|
||||
c.Logger().Errorf("user not found by ID: '%s'", uid.ID)
|
||||
|
99
handlers/middleware.go
Normal file
99
handlers/middleware.go
Normal file
@ -0,0 +1,99 @@
|
||||
// Copyright 2023 wanderer <a_mirre at utb dot cz>
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
moduser "git.dotya.ml/mirre-mt/pcmt/modules/user"
|
||||
"github.com/labstack/echo-contrib/session"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func MiddlewareSession(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
sess, _ := session.Get(setting.SessionCookieName(), c)
|
||||
if sess == nil {
|
||||
c.Logger().Info("No session found, unauthorised.")
|
||||
|
||||
// return a 404 instead of 401 to not disclose the existence of
|
||||
// resources for unauthenticated users with no past sessions.
|
||||
return echo.NewHTTPError(http.StatusNotFound).SetInternal(ErrNoSession)
|
||||
}
|
||||
|
||||
uname := sess.Values["username"]
|
||||
if uname == nil {
|
||||
c.Logger().Debugf("%d - %s", http.StatusUnauthorized, "seassion expired or invalid")
|
||||
// return echo.NewHTTPError(http.StatusUnauthorized).SetInternal(ErrSessionExpired)
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusUnauthorized,
|
||||
http.StatusText(http.StatusUnauthorized),
|
||||
ErrSessionExpired.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
username, ok := sess.Values["username"].(string)
|
||||
if !ok {
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusUnauthorized,
|
||||
http.StatusText(http.StatusUnauthorized),
|
||||
"username was nil",
|
||||
)
|
||||
}
|
||||
|
||||
log.Info("gorilla session", "username", username)
|
||||
|
||||
refreshSession(
|
||||
sess,
|
||||
"/",
|
||||
// setting.SessionMaxAge,
|
||||
86400,
|
||||
true,
|
||||
c.Request().URL.Scheme == "https", //nolint:goconst
|
||||
http.SameSiteStrictMode,
|
||||
)
|
||||
|
||||
if err := sess.Save(c.Request(), c.Response()); err != nil {
|
||||
c.Logger().Error("failed to save session")
|
||||
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusInternalServerError,
|
||||
http.StatusText(http.StatusInternalServerError)+" (make sure you've got cookies enabled)",
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
c.Set("sess", sess)
|
||||
|
||||
var u moduser.User
|
||||
|
||||
ctx := context.WithValue(context.Background(), moduser.CtxKey{}, slogger)
|
||||
if usr, err := moduser.QueryUser(ctx, dbclient, username); err == nil && usr != nil {
|
||||
u.ID = usr.ID
|
||||
u.Username = usr.Username
|
||||
u.IsAdmin = usr.IsAdmin
|
||||
u.CreatedAt = usr.CreatedAt
|
||||
u.IsActive = usr.IsActive
|
||||
u.IsLoggedIn = true
|
||||
} else {
|
||||
c.Logger().Error(http.StatusText(http.StatusInternalServerError) + " - " + err.Error())
|
||||
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusInternalServerError,
|
||||
http.StatusText(http.StatusInternalServerError),
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
c.Set("sloggerCtx", ctx)
|
||||
c.Set("sessUsr", u)
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user