Newer
Older
wg-portal / internal / server / handlers_user.go
package server

import (
	"net/http"
	"net/url"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/h44z/wg-portal/internal/users"
	"github.com/sirupsen/logrus"
	"golang.org/x/crypto/bcrypt"
	"gorm.io/gorm"
)

func (s *Server) GetAdminUsersIndex(c *gin.Context) {
	currentSession := GetSessionData(c)

	sort := c.Query("sort")
	if sort != "" {
		if currentSession.SortedBy["users"] != sort {
			currentSession.SortedBy["users"] = sort
			currentSession.SortDirection["users"] = "asc"
		} else {
			if currentSession.SortDirection["users"] == "asc" {
				currentSession.SortDirection["users"] = "desc"
			} else {
				currentSession.SortDirection["users"] = "asc"
			}
		}

		if err := UpdateSessionData(c, currentSession); err != nil {
			s.GetHandleError(c, http.StatusInternalServerError, "sort error", "failed to save session")
			return
		}
		c.Redirect(http.StatusSeeOther, "/admin/users/")
		return
	}

	search, searching := c.GetQuery("search")
	if searching {
		currentSession.Search["users"] = search

		if err := UpdateSessionData(c, currentSession); err != nil {
			s.GetHandleError(c, http.StatusInternalServerError, "search error", "failed to save session")
			return
		}
		c.Redirect(http.StatusSeeOther, "/admin/users/")
		return
	}

	dbUsers := s.users.GetFilteredAndSortedUsersUnscoped(currentSession.SortedBy["users"], currentSession.SortDirection["users"], currentSession.Search["users"])

	c.HTML(http.StatusOK, "admin_user_index.html", struct {
		Route      string
		Alerts     []FlashData
		Session    SessionData
		Static     StaticData
		Users      []users.User
		TotalUsers int
		Device     Device
	}{
		Route:      c.Request.URL.Path,
		Alerts:     GetFlashes(c),
		Session:    currentSession,
		Static:     s.getStaticData(),
		Users:      dbUsers,
		TotalUsers: len(s.users.GetUsers()),
		Device:     s.peers.GetDevice(),
	})
}

func (s *Server) GetAdminUsersEdit(c *gin.Context) {
	user := s.users.GetUserUnscoped(c.Query("pkey"))

	currentSession, err := s.setFormInSession(c, *user)
	if err != nil {
		s.GetHandleError(c, http.StatusInternalServerError, "Session error", err.Error())
		return
	}

	c.HTML(http.StatusOK, "admin_edit_user.html", struct {
		Route   string
		Alerts  []FlashData
		Session SessionData
		Static  StaticData
		User    users.User
		Device  Device
		Epoch   time.Time
	}{
		Route:   c.Request.URL.Path,
		Alerts:  GetFlashes(c),
		Session: currentSession,
		Static:  s.getStaticData(),
		User:    currentSession.FormData.(users.User),
		Device:  s.peers.GetDevice(),
	})
}

func (s *Server) PostAdminUsersEdit(c *gin.Context) {
	currentUser := s.users.GetUserUnscoped(c.Query("pkey"))
	if currentUser == nil {
		SetFlashMessage(c, "invalid user", "danger")
		c.Redirect(http.StatusSeeOther, "/admin/users/")
		return
	}
	urlEncodedKey := url.QueryEscape(c.Query("pkey"))

	currentSession := GetSessionData(c)
	var formUser users.User
	if currentSession.FormData != nil {
		formUser = currentSession.FormData.(users.User)
	}
	if err := c.ShouldBind(&formUser); err != nil {
		_ = s.updateFormInSession(c, formUser)
		SetFlashMessage(c, "failed to bind form data: "+err.Error(), "danger")
		c.Redirect(http.StatusSeeOther, "/admin/users/edit?pkey="+urlEncodedKey+"&formerr=bind")
		return
	}

	if formUser.Password != "" {
		hashedPassword, err := bcrypt.GenerateFromPassword([]byte(formUser.Password), bcrypt.DefaultCost)
		if err != nil {
			_ = s.updateFormInSession(c, formUser)
			SetFlashMessage(c, "failed to hash admin password", "danger")
			c.Redirect(http.StatusSeeOther, "/admin/users/edit?pkey="+urlEncodedKey+"&formerr=bind")
			return
		}
		formUser.Password = string(hashedPassword)
	} else {
		formUser.Password = currentUser.Password
	}

	disabled := c.PostForm("isdisabled") != ""
	if disabled {
		formUser.DeletedAt = gorm.DeletedAt{
			Time:  time.Now(),
			Valid: true,
		}
	} else {
		formUser.DeletedAt = gorm.DeletedAt{}
	}
	formUser.IsAdmin = c.PostForm("isadmin") == "true"

	// Update peers
	if disabled != currentUser.DeletedAt.Valid {
		if disabled {
			// disable all peers for the given user
			for _, peer := range s.peers.GetPeersByMail(currentUser.Email) {
				now := time.Now()
				peer.DeactivatedAt = &now
				if err := s.UpdatePeer(peer, now); err != nil {
					logrus.Errorf("failed to update deactivated peer %s: %v", peer.PublicKey, err)
				}
			}
		} else {
			// enable all peers for the given user
			for _, peer := range s.peers.GetPeersByMail(currentUser.Email) {
				now := time.Now()
				peer.DeactivatedAt = nil
				if err := s.UpdatePeer(peer, now); err != nil {
					logrus.Errorf("failed to update activated peer %s: %v", peer.PublicKey, err)
				}
			}
		}
	}

	// Update in database
	if err := s.users.UpdateUser(&formUser); err != nil {
		_ = s.updateFormInSession(c, formUser)
		SetFlashMessage(c, "failed to update user: "+err.Error(), "danger")
		c.Redirect(http.StatusSeeOther, "/admin/users/edit?pkey="+urlEncodedKey+"&formerr=update")
		return
	}

	SetFlashMessage(c, "changes applied successfully", "success")
	c.Redirect(http.StatusSeeOther, "/admin/users/edit?pkey="+urlEncodedKey)
}

func (s *Server) GetAdminUsersCreate(c *gin.Context) {
	user := users.User{}

	currentSession, err := s.setFormInSession(c, user)
	if err != nil {
		s.GetHandleError(c, http.StatusInternalServerError, "Session error", err.Error())
		return
	}

	c.HTML(http.StatusOK, "admin_edit_user.html", struct {
		Route   string
		Alerts  []FlashData
		Session SessionData
		Static  StaticData
		User    users.User
		Device  Device
		Epoch   time.Time
	}{
		Route:   c.Request.URL.Path,
		Alerts:  GetFlashes(c),
		Session: currentSession,
		Static:  s.getStaticData(),
		User:    currentSession.FormData.(users.User),
		Device:  s.peers.GetDevice(),
	})
}

func (s *Server) PostAdminUsersCreate(c *gin.Context) {
	currentSession := GetSessionData(c)
	var formUser users.User
	if currentSession.FormData != nil {
		formUser = currentSession.FormData.(users.User)
	}
	if err := c.ShouldBind(&formUser); err != nil {
		_ = s.updateFormInSession(c, formUser)
		SetFlashMessage(c, "failed to bind form data: "+err.Error(), "danger")
		c.Redirect(http.StatusSeeOther, "/admin/users/create?formerr=bind")
		return
	}

	if formUser.Password != "" {
		hashedPassword, err := bcrypt.GenerateFromPassword([]byte(formUser.Password), bcrypt.DefaultCost)
		if err != nil {
			SetFlashMessage(c, "failed to hash admin password", "danger")
			c.Redirect(http.StatusSeeOther, "/admin/users/create?formerr=bind")
			return
		}
		formUser.Password = string(hashedPassword)
	} else {
		_ = s.updateFormInSession(c, formUser)
		SetFlashMessage(c, "invalid password", "danger")
		c.Redirect(http.StatusSeeOther, "/admin/users/create?formerr=create")
		return
	}

	disabled := c.PostForm("isdisabled") != ""
	if disabled {
		formUser.DeletedAt = gorm.DeletedAt{
			Time:  time.Now(),
			Valid: true,
		}
	} else {
		formUser.DeletedAt = gorm.DeletedAt{}
	}
	formUser.IsAdmin = c.PostForm("isadmin") == "true"
	formUser.Source = users.UserSourceDatabase
	if err := s.users.CreateUser(&formUser); err != nil {
		formUser.CreatedAt = time.Time{} // reset created time
		_ = s.updateFormInSession(c, formUser)
		SetFlashMessage(c, "failed to add user: "+err.Error(), "danger")
		c.Redirect(http.StatusSeeOther, "/admin/users/create?formerr=create")
		return
	}

	// Check if user already has a peer setup, if not create one
	if s.config.Core.CreateDefaultPeer {
		peers := s.peers.GetPeersByMail(formUser.Email)
		if len(peers) == 0 { // Create vpn peer
			err := s.CreatePeer(Peer{
				Identifier: formUser.Firstname + " " + formUser.Lastname + " (Default)",
				Email:      formUser.Email,
				CreatedBy:  formUser.Email,
				UpdatedBy:  formUser.Email,
			})
			logrus.Errorf("Failed to automatically create vpn peer for %s: %v", formUser.Email, err)
		}
	}

	SetFlashMessage(c, "user created successfully", "success")
	c.Redirect(http.StatusSeeOther, "/admin/users/")
}