Skip to content

Commit

Permalink
Improved user update behaviour
Browse files Browse the repository at this point in the history
  • Loading branch information
svera authored Apr 25, 2024
1 parent 1842f42 commit f334eef
Show file tree
Hide file tree
Showing 14 changed files with 117 additions and 69 deletions.
1 change: 1 addition & 0 deletions internal/webserver/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func SetupControllers(cfg Config, db *gorm.DB, metadataReaders map[string]metada
usersCfg := user.Config{
MinPasswordLength: cfg.MinPasswordLength,
WordsPerMinute: cfg.WordsPerMinute,
Secret: cfg.JwtSecret,
}

documentsCfg := document.Config{
Expand Down
40 changes: 23 additions & 17 deletions internal/webserver/controller/auth/signin.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,30 +31,17 @@ func (a *Controller) SignIn(c *fiber.Ctx) error {
}

// Send back JWT as a cookie.
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"userdata": model.User{
ID: user.ID,
Name: user.Name,
Username: user.Username,
Email: user.Email,
Role: user.Role,
Uuid: user.Uuid,
SendToEmail: user.SendToEmail,
WordsPerMinute: user.WordsPerMinute,
},
"exp": jwt.NewNumericDate(time.Now().Add(a.config.SessionTimeout)),
},
)

signedToken, err := token.SignedString(a.config.Secret)
expiration := time.Now().Add(a.config.SessionTimeout)
signedToken, err := GenerateToken(c, user, expiration, a.config.Secret)
if err != nil {
return fiber.ErrInternalServerError
}

c.Cookie(&fiber.Cookie{
Name: "coreander",
Value: signedToken,
Path: "/",
Expires: time.Now().Add(a.config.SessionTimeout),
Expires: expiration,
Secure: false,
HTTPOnly: true,
})
Expand All @@ -66,3 +53,22 @@ func (a *Controller) SignIn(c *fiber.Ctx) error {

return c.Redirect(fmt.Sprintf("/%s", c.Params("lang")))
}

func GenerateToken(c *fiber.Ctx, user *model.User, expiration time.Time, secret []byte) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"userdata": model.User{
ID: user.ID,
Name: user.Name,
Username: user.Username,
Email: user.Email,
Role: user.Role,
Uuid: user.Uuid,
SendToEmail: user.SendToEmail,
WordsPerMinute: user.WordsPerMinute,
},
"exp": jwt.NewNumericDate(expiration),
},
)

return token.SignedString(secret)
}
4 changes: 2 additions & 2 deletions internal/webserver/controller/document/detail.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ func (d *Controller) Detail(c *fiber.Ctx) error {
emailSendingConfigured = false
}

var session model.User
if val, ok := c.Locals("Session").(model.User); ok {
var session model.Session
if val, ok := c.Locals("Session").(model.Session); ok {
session = val
}

Expand Down
4 changes: 2 additions & 2 deletions internal/webserver/controller/document/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ func (d *Controller) Search(c *fiber.Ctx) error {
page = 1
}

var session model.User
if val, ok := c.Locals("Session").(model.User); ok {
var session model.Session
if val, ok := c.Locals("Session").(model.Session); ok {
session = val
}

Expand Down
2 changes: 1 addition & 1 deletion internal/webserver/controller/highlight/highlight.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
)

func (h *Controller) Highlight(c *fiber.Ctx) error {
user := c.Locals("Session").(model.User)
user := c.Locals("Session").(model.Session)

document, err := h.idx.Document(c.FormValue("slug"))
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions internal/webserver/controller/highlight/highlights.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ func (h *Controller) Highlights(c *fiber.Ctx) error {
page = 1
}

var session model.User
if val, ok := c.Locals("Session").(model.User); ok {
var session model.Session
if val, ok := c.Locals("Session").(model.Session); ok {
session = val
}

Expand Down
2 changes: 1 addition & 1 deletion internal/webserver/controller/highlight/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
)

func (h *Controller) Remove(c *fiber.Ctx) error {
user := c.Locals("Session").(model.User)
user := c.Locals("Session").(model.Session)

document, err := h.idx.Document(c.FormValue("slug"))
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions internal/webserver/controller/user/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type usersRepository interface {
type Config struct {
MinPasswordLength int
WordsPerMinute float64
Secret []byte
}

type Controller struct {
Expand Down
4 changes: 2 additions & 2 deletions internal/webserver/controller/user/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ func (u *Controller) Edit(c *fiber.Ctx) error {
return fiber.ErrNotFound
}

var session model.User
if val, ok := c.Locals("Session").(model.User); ok {
var session model.Session
if val, ok := c.Locals("Session").(model.Session); ok {
session = val
}

Expand Down
88 changes: 58 additions & 30 deletions internal/webserver/controller/user/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"log"
"strconv"
"strings"
"time"

"github.com/gofiber/fiber/v2"
"github.com/svera/coreander/v3/internal/webserver/controller/auth"
"github.com/svera/coreander/v3/internal/webserver/model"
)

Expand All @@ -20,8 +22,8 @@ func (u *Controller) Update(c *fiber.Ctx) error {
return fiber.ErrNotFound
}

var session model.User
if val, ok := c.Locals("Session").(model.User); ok {
var session model.Session
if val, ok := c.Locals("Session").(model.Session); ok {
session = val
}

Expand All @@ -30,66 +32,92 @@ func (u *Controller) Update(c *fiber.Ctx) error {
}

if c.FormValue("password-tab") == "true" {
return u.updateUserPassword(c, session, *user)
return u.updateUserPassword(c, *user, session)
}

return u.updateUserData(user, c, session)
return u.updateUserData(c, user, session)
}

func (u *Controller) updateUserData(user *model.User, c *fiber.Ctx, session model.User) error {
func (u *Controller) updateUserData(c *fiber.Ctx, user *model.User, session model.Session) error {
user.Name = c.FormValue("name")
user.Username = strings.ToLower(c.FormValue("username"))
user.Email = c.FormValue("email")
user.SendToEmail = c.FormValue("send-to-email")
user.WordsPerMinute, _ = strconv.ParseFloat(c.FormValue("words-per-minute"), 64)

errs := user.Validate(u.config.MinPasswordLength)

exists, err := u.usernameExists(c, session)
if err != nil {
log.Println(err.Error())
return fiber.ErrInternalServerError
}

if exists {
errs["username"] = "A user with this username already exists"
}

exists, err = u.emailExists(c, session)
validationErrs, err := u.validate(c, user, session)
if err != nil {
log.Println(err.Error())
return fiber.ErrInternalServerError
}

if exists {
errs["email"] = "A user with this email address already exists"
return err
}

if len(errs) > 0 {
if len(validationErrs) > 0 {
return c.Render("users/edit", fiber.Map{
"Title": "Edit user",
"User": user,
"MinPasswordLength": u.config.MinPasswordLength,
"UsernamePattern": model.UsernamePattern,
"Errors": errs,
"Errors": validationErrs,
}, "layout")
}

if err := u.repository.Update(user); err != nil {
return fiber.ErrInternalServerError
}

if session.Uuid == user.Uuid {
expiration := time.Unix(int64(session.Exp), 0)
signedToken, err := auth.GenerateToken(c, user, expiration, u.config.Secret)
if err != nil {
return fiber.ErrInternalServerError
}

c.Cookie(&fiber.Cookie{
Name: "coreander",
Value: signedToken,
Path: "/",
Expires: expiration,
Secure: false,
HTTPOnly: true,
})
c.Locals("Session", user)
}

return c.Render("users/edit", fiber.Map{
"Title": "Edit user",
"User": user,
"MinPasswordLength": u.config.MinPasswordLength,
"UsernamePattern": model.UsernamePattern,
"Errors": errs,
"Errors": validationErrs,
"Message": "Profile updated",
}, "layout")
}

func (u *Controller) usernameExists(c *fiber.Ctx, session model.User) (bool, error) {
func (u *Controller) validate(c *fiber.Ctx, user *model.User, session model.Session) (map[string]string, error) {
errs := user.Validate(u.config.MinPasswordLength)

exists, err := u.usernameExists(c, session)
if err != nil {
log.Println(err.Error())
return nil, fiber.ErrInternalServerError
}

if exists {
errs["username"] = "A user with this username already exists"
}

exists, err = u.emailExists(c, session)
if err != nil {
log.Println(err.Error())
return nil, fiber.ErrInternalServerError
}

if exists {
errs["email"] = "A user with this email address already exists"
}
return errs, nil
}

func (u *Controller) usernameExists(c *fiber.Ctx, session model.Session) (bool, error) {
user, err := u.repository.FindByUsername(c.FormValue("username"))
if err != nil {
return true, fiber.ErrInternalServerError
Expand All @@ -103,7 +131,7 @@ func (u *Controller) usernameExists(c *fiber.Ctx, session model.User) (bool, err
return false, nil
}

func (u *Controller) emailExists(c *fiber.Ctx, session model.User) (bool, error) {
func (u *Controller) emailExists(c *fiber.Ctx, session model.Session) (bool, error) {
user, err := u.repository.FindByEmail(c.FormValue("email"))
if err != nil {
return true, fiber.ErrInternalServerError
Expand All @@ -118,7 +146,7 @@ func (u *Controller) emailExists(c *fiber.Ctx, session model.User) (bool, error)
}

// updateUserPassword gathers information from the edit user form and updates user password
func (u *Controller) updateUserPassword(c *fiber.Ctx, session, user model.User) error {
func (u *Controller) updateUserPassword(c *fiber.Ctx, user model.User, session model.Session) error {
user.Password = c.FormValue("password")

errs := user.Validate(u.config.MinPasswordLength)
Expand Down
25 changes: 14 additions & 11 deletions internal/webserver/jwtclaimsreader.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,39 @@ import (
"github.com/svera/coreander/v3/internal/webserver/model"
)

func sessionData(c *fiber.Ctx) model.User {
var user model.User
func sessionData(c *fiber.Ctx) model.Session {
var session model.Session

if t, ok := c.Locals("user").(*jwt.Token); ok {
claims := t.Claims.(jwt.MapClaims)
userDataMap := claims["userdata"].(map[string]interface{})
if value, ok := userDataMap["ID"].(float64); ok {
user.ID = uint(value)
session.ID = uint(value)
}
if value, ok := userDataMap["Name"].(string); ok {
user.Name = value
session.Name = value
}
if value, ok := userDataMap["Username"].(string); ok {
user.Username = value
session.Username = value
}
if value, ok := userDataMap["Email"].(string); ok {
user.Email = value
session.Email = value
}
if value, ok := userDataMap["Role"].(float64); ok {
user.Role = int(value)
session.Role = int(value)
}
if value, ok := userDataMap["Uuid"].(string); ok {
user.Uuid = value
session.Uuid = value
}
if value, ok := userDataMap["SendToEmail"].(string); ok {
user.SendToEmail = value
session.SendToEmail = value
}
if value, ok := userDataMap["WordsPerMinute"].(float64); ok {
user.WordsPerMinute = value
session.WordsPerMinute = value
}

session.Exp = claims["exp"].(float64)
}

return user
return session
}
2 changes: 1 addition & 1 deletion internal/webserver/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func RequireAdmin(c *fiber.Ctx) error {
return fiber.ErrForbidden
}

session := c.Locals("Session").(model.User)
session := c.Locals("Session").(model.Session)

if session.Role != model.RoleAdmin {
return fiber.ErrForbidden
Expand Down
6 changes: 6 additions & 0 deletions internal/webserver/model/session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package model

type Session struct {
User
Exp float64
}
3 changes: 3 additions & 0 deletions internal/webserver/webserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/svera/coreander/v3/internal/i18n"
"github.com/svera/coreander/v3/internal/index"
"github.com/svera/coreander/v3/internal/webserver/infrastructure"
"github.com/svera/coreander/v3/internal/webserver/model"
"golang.org/x/exp/slices"
"golang.org/x/text/message"
)
Expand Down Expand Up @@ -175,13 +176,15 @@ func errorHandler(c *fiber.Ctx, err error) error {
code = e.Code
}

session, _ := c.Locals("Session").(model.Session)
// Send custom error page
err = c.Status(code).Render(
fmt.Sprintf("errors/%d", code),
fiber.Map{
"Lang": chooseBestLanguage(c),
"Title": "Coreander",
"Version": c.App().Config().AppName,
"Session": session,
},
"layout")

Expand Down

0 comments on commit f334eef

Please sign in to comment.