mirror of https://github.com/h44z/wg-portal.git
				
				
				
			
		
			
				
	
	
		
			374 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			374 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
| package server
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"net"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/gin-gonic/gin"
 | |
| 	"github.com/h44z/wg-portal/internal/common"
 | |
| 	"github.com/h44z/wg-portal/internal/ldap"
 | |
| 	log "github.com/sirupsen/logrus"
 | |
| 	"github.com/tatsushid/go-fastping"
 | |
| )
 | |
| 
 | |
| type LdapCreateForm struct {
 | |
| 	Emails     string `form:"email" binding:"required"`
 | |
| 	Identifier string `form:"identifier" binding:"required,lte=20"`
 | |
| }
 | |
| 
 | |
| func (s *Server) GetAdminEditPeer(c *gin.Context) {
 | |
| 	device := s.users.GetDevice()
 | |
| 	user := s.users.GetUserByKey(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_client.html", struct {
 | |
| 		Route        string
 | |
| 		Alerts       []FlashData
 | |
| 		Session      SessionData
 | |
| 		Static       StaticData
 | |
| 		Peer         User
 | |
| 		Device       Device
 | |
| 		EditableKeys bool
 | |
| 	}{
 | |
| 		Route:        c.Request.URL.Path,
 | |
| 		Alerts:       s.getFlashes(c),
 | |
| 		Session:      currentSession,
 | |
| 		Static:       s.getStaticData(),
 | |
| 		Peer:         currentSession.FormData.(User),
 | |
| 		Device:       device,
 | |
| 		EditableKeys: s.config.Core.EditableKeys,
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func (s *Server) PostAdminEditPeer(c *gin.Context) {
 | |
| 	currentUser := s.users.GetUserByKey(c.Query("pkey"))
 | |
| 	urlEncodedKey := url.QueryEscape(c.Query("pkey"))
 | |
| 
 | |
| 	currentSession := s.getSessionData(c)
 | |
| 	var formUser User
 | |
| 	if currentSession.FormData != nil {
 | |
| 		formUser = currentSession.FormData.(User)
 | |
| 	}
 | |
| 	if err := c.ShouldBind(&formUser); err != nil {
 | |
| 		_ = s.updateFormInSession(c, formUser)
 | |
| 		s.setFlashMessage(c, "failed to bind form data: "+err.Error(), "danger")
 | |
| 		c.Redirect(http.StatusSeeOther, "/admin/peer/edit?pkey="+urlEncodedKey+"&formerr=bind")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Clean list input
 | |
| 	formUser.IPs = common.ParseStringList(formUser.IPsStr)
 | |
| 	formUser.AllowedIPs = common.ParseStringList(formUser.AllowedIPsStr)
 | |
| 	formUser.IPsStr = common.ListToString(formUser.IPs)
 | |
| 	formUser.AllowedIPsStr = common.ListToString(formUser.AllowedIPs)
 | |
| 
 | |
| 	disabled := c.PostForm("isdisabled") != ""
 | |
| 	now := time.Now()
 | |
| 	if disabled && currentUser.DeactivatedAt == nil {
 | |
| 		formUser.DeactivatedAt = &now
 | |
| 	} else if !disabled {
 | |
| 		formUser.DeactivatedAt = nil
 | |
| 	}
 | |
| 
 | |
| 	// Update in database
 | |
| 	if err := s.UpdateUser(formUser, now); err != nil {
 | |
| 		_ = s.updateFormInSession(c, formUser)
 | |
| 		s.setFlashMessage(c, "failed to update user: "+err.Error(), "danger")
 | |
| 		c.Redirect(http.StatusSeeOther, "/admin/peer/edit?pkey="+urlEncodedKey+"&formerr=update")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	s.setFlashMessage(c, "changes applied successfully", "success")
 | |
| 	c.Redirect(http.StatusSeeOther, "/admin/peer/edit?pkey="+urlEncodedKey)
 | |
| }
 | |
| 
 | |
| func (s *Server) GetAdminCreatePeer(c *gin.Context) {
 | |
| 	device := s.users.GetDevice()
 | |
| 
 | |
| 	currentSession, err := s.setNewUserFormInSession(c)
 | |
| 	if err != nil {
 | |
| 		s.GetHandleError(c, http.StatusInternalServerError, "Session error", err.Error())
 | |
| 		return
 | |
| 	}
 | |
| 	c.HTML(http.StatusOK, "admin_edit_client.html", struct {
 | |
| 		Route        string
 | |
| 		Alerts       []FlashData
 | |
| 		Session      SessionData
 | |
| 		Static       StaticData
 | |
| 		Peer         User
 | |
| 		Device       Device
 | |
| 		EditableKeys bool
 | |
| 	}{
 | |
| 		Route:        c.Request.URL.Path,
 | |
| 		Alerts:       s.getFlashes(c),
 | |
| 		Session:      currentSession,
 | |
| 		Static:       s.getStaticData(),
 | |
| 		Peer:         currentSession.FormData.(User),
 | |
| 		Device:       device,
 | |
| 		EditableKeys: s.config.Core.EditableKeys,
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func (s *Server) PostAdminCreatePeer(c *gin.Context) {
 | |
| 	currentSession := s.getSessionData(c)
 | |
| 	var formUser User
 | |
| 	if currentSession.FormData != nil {
 | |
| 		formUser = currentSession.FormData.(User)
 | |
| 	}
 | |
| 	if err := c.ShouldBind(&formUser); err != nil {
 | |
| 		_ = s.updateFormInSession(c, formUser)
 | |
| 		s.setFlashMessage(c, "failed to bind form data: "+err.Error(), "danger")
 | |
| 		c.Redirect(http.StatusSeeOther, "/admin/peer/create?formerr=bind")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Clean list input
 | |
| 	formUser.IPs = common.ParseStringList(formUser.IPsStr)
 | |
| 	formUser.AllowedIPs = common.ParseStringList(formUser.AllowedIPsStr)
 | |
| 	formUser.IPsStr = common.ListToString(formUser.IPs)
 | |
| 	formUser.AllowedIPsStr = common.ListToString(formUser.AllowedIPs)
 | |
| 
 | |
| 	disabled := c.PostForm("isdisabled") != ""
 | |
| 	now := time.Now()
 | |
| 	if disabled {
 | |
| 		formUser.DeactivatedAt = &now
 | |
| 	}
 | |
| 
 | |
| 	if err := s.CreateUser(formUser); err != nil {
 | |
| 		_ = s.updateFormInSession(c, formUser)
 | |
| 		s.setFlashMessage(c, "failed to add user: "+err.Error(), "danger")
 | |
| 		c.Redirect(http.StatusSeeOther, "/admin/peer/create?formerr=create")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	s.setFlashMessage(c, "client created successfully", "success")
 | |
| 	c.Redirect(http.StatusSeeOther, "/admin")
 | |
| }
 | |
| 
 | |
| func (s *Server) GetAdminCreateLdapPeers(c *gin.Context) {
 | |
| 	currentSession, err := s.setFormInSession(c, LdapCreateForm{Identifier: "Default"})
 | |
| 	if err != nil {
 | |
| 		s.GetHandleError(c, http.StatusInternalServerError, "Session error", err.Error())
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	c.HTML(http.StatusOK, "admin_create_clients.html", struct {
 | |
| 		Route    string
 | |
| 		Alerts   []FlashData
 | |
| 		Session  SessionData
 | |
| 		Static   StaticData
 | |
| 		Users    []*ldap.UserCacheHolderEntry
 | |
| 		FormData LdapCreateForm
 | |
| 		Device   Device
 | |
| 	}{
 | |
| 		Route:    c.Request.URL.Path,
 | |
| 		Alerts:   s.getFlashes(c),
 | |
| 		Session:  currentSession,
 | |
| 		Static:   s.getStaticData(),
 | |
| 		Users:    s.ldapUsers.GetSortedUsers("sn", "asc"),
 | |
| 		FormData: currentSession.FormData.(LdapCreateForm),
 | |
| 		Device:   s.users.GetDevice(),
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func (s *Server) PostAdminCreateLdapPeers(c *gin.Context) {
 | |
| 	currentSession := s.getSessionData(c)
 | |
| 	var formData LdapCreateForm
 | |
| 	if currentSession.FormData != nil {
 | |
| 		formData = currentSession.FormData.(LdapCreateForm)
 | |
| 	}
 | |
| 	if err := c.ShouldBind(&formData); err != nil {
 | |
| 		_ = s.updateFormInSession(c, formData)
 | |
| 		s.setFlashMessage(c, "failed to bind form data: "+err.Error(), "danger")
 | |
| 		c.Redirect(http.StatusSeeOther, "/admin/peer/createldap?formerr=bind")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	emails := common.ParseStringList(formData.Emails)
 | |
| 	for i := range emails {
 | |
| 		// TODO: also check email addr for validity?
 | |
| 		if !strings.ContainsRune(emails[i], '@') || s.ldapUsers.GetUserDNByMail(emails[i]) == "" {
 | |
| 			_ = s.updateFormInSession(c, formData)
 | |
| 			s.setFlashMessage(c, "invalid email address: "+emails[i], "danger")
 | |
| 			c.Redirect(http.StatusSeeOther, "/admin/peer/createldap?formerr=mail")
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	log.Infof("creating %d ldap peers", len(emails))
 | |
| 
 | |
| 	for i := range emails {
 | |
| 		if err := s.CreateUserByEmail(emails[i], formData.Identifier, false); err != nil {
 | |
| 			_ = s.updateFormInSession(c, formData)
 | |
| 			s.setFlashMessage(c, "failed to add user: "+err.Error(), "danger")
 | |
| 			c.Redirect(http.StatusSeeOther, "/admin/peer/createldap?formerr=create")
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	s.setFlashMessage(c, "client(s) created successfully", "success")
 | |
| 	c.Redirect(http.StatusSeeOther, "/admin/peer/createldap")
 | |
| }
 | |
| 
 | |
| func (s *Server) GetAdminDeletePeer(c *gin.Context) {
 | |
| 	currentUser := s.users.GetUserByKey(c.Query("pkey"))
 | |
| 	if err := s.DeleteUser(currentUser); err != nil {
 | |
| 		s.GetHandleError(c, http.StatusInternalServerError, "Deletion error", err.Error())
 | |
| 		return
 | |
| 	}
 | |
| 	s.setFlashMessage(c, "user deleted successfully", "success")
 | |
| 	c.Redirect(http.StatusSeeOther, "/admin")
 | |
| }
 | |
| 
 | |
| func (s *Server) GetPeerQRCode(c *gin.Context) {
 | |
| 	user := s.users.GetUserByKey(c.Query("pkey"))
 | |
| 	currentSession := s.getSessionData(c)
 | |
| 	if !currentSession.IsAdmin && user.Email != currentSession.Email {
 | |
| 		s.GetHandleError(c, http.StatusUnauthorized, "No permissions", "You don't have permissions to view this resource!")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	png, err := user.GetQRCode()
 | |
| 	if err != nil {
 | |
| 		s.GetHandleError(c, http.StatusInternalServerError, "QRCode error", err.Error())
 | |
| 		return
 | |
| 	}
 | |
| 	c.Data(http.StatusOK, "image/png", png)
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (s *Server) GetPeerConfig(c *gin.Context) {
 | |
| 	user := s.users.GetUserByKey(c.Query("pkey"))
 | |
| 	currentSession := s.getSessionData(c)
 | |
| 	if !currentSession.IsAdmin && user.Email != currentSession.Email {
 | |
| 		s.GetHandleError(c, http.StatusUnauthorized, "No permissions", "You don't have permissions to view this resource!")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	cfg, err := user.GetClientConfigFile(s.users.GetDevice())
 | |
| 	if err != nil {
 | |
| 		s.GetHandleError(c, http.StatusInternalServerError, "ConfigFile error", err.Error())
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	c.Header("Content-Disposition", "attachment; filename="+user.GetConfigFileName())
 | |
| 	c.Data(http.StatusOK, "application/config", cfg)
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (s *Server) GetPeerConfigMail(c *gin.Context) {
 | |
| 	user := s.users.GetUserByKey(c.Query("pkey"))
 | |
| 	currentSession := s.getSessionData(c)
 | |
| 	if !currentSession.IsAdmin && user.Email != currentSession.Email {
 | |
| 		s.GetHandleError(c, http.StatusUnauthorized, "No permissions", "You don't have permissions to view this resource!")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	cfg, err := user.GetClientConfigFile(s.users.GetDevice())
 | |
| 	if err != nil {
 | |
| 		s.GetHandleError(c, http.StatusInternalServerError, "ConfigFile error", err.Error())
 | |
| 		return
 | |
| 	}
 | |
| 	png, err := user.GetQRCode()
 | |
| 	if err != nil {
 | |
| 		s.GetHandleError(c, http.StatusInternalServerError, "QRCode error", err.Error())
 | |
| 		return
 | |
| 	}
 | |
| 	// Apply mail template
 | |
| 	var tplBuff bytes.Buffer
 | |
| 	if err := s.mailTpl.Execute(&tplBuff, struct {
 | |
| 		Client        User
 | |
| 		QrcodePngName string
 | |
| 		PortalUrl     string
 | |
| 	}{
 | |
| 		Client:        user,
 | |
| 		QrcodePngName: "wireguard-config.png",
 | |
| 		PortalUrl:     s.config.Core.ExternalUrl,
 | |
| 	}); err != nil {
 | |
| 		s.GetHandleError(c, http.StatusInternalServerError, "Template error", err.Error())
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Send mail
 | |
| 	attachments := []common.MailAttachment{
 | |
| 		{
 | |
| 			Name:        user.GetConfigFileName(),
 | |
| 			ContentType: "application/config",
 | |
| 			Data:        bytes.NewReader(cfg),
 | |
| 		},
 | |
| 		{
 | |
| 			Name:        "wireguard-config.png",
 | |
| 			ContentType: "image/png",
 | |
| 			Data:        bytes.NewReader(png),
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	if err := common.SendEmailWithAttachments(s.config.Email, s.config.Core.MailFrom, "", "WireGuard VPN Configuration",
 | |
| 		"Your mail client does not support HTML. Please find the configuration attached to this mail.", tplBuff.String(),
 | |
| 		[]string{user.Email}, attachments); err != nil {
 | |
| 		s.GetHandleError(c, http.StatusInternalServerError, "Email error", err.Error())
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	s.setFlashMessage(c, "mail sent successfully", "success")
 | |
| 	c.Redirect(http.StatusSeeOther, "/admin")
 | |
| }
 | |
| 
 | |
| func (s *Server) GetPeerStatus(c *gin.Context) {
 | |
| 	user := s.users.GetUserByKey(c.Query("pkey"))
 | |
| 	currentSession := s.getSessionData(c)
 | |
| 	if !currentSession.IsAdmin && user.Email != currentSession.Email {
 | |
| 		s.GetHandleError(c, http.StatusUnauthorized, "No permissions", "You don't have permissions to view this resource!")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if user.Peer == nil { // no peer means disabled
 | |
| 		c.JSON(http.StatusOK, false)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	isOnline := false
 | |
| 	ping := make(chan bool)
 | |
| 	defer close(ping)
 | |
| 	for _, cidr := range user.IPs {
 | |
| 		ip, _, _ := net.ParseCIDR(cidr)
 | |
| 		var ra *net.IPAddr
 | |
| 		if common.IsIPv6(ip.String()) {
 | |
| 			ra, _ = net.ResolveIPAddr("ip6:ipv6-icmp", ip.String())
 | |
| 		} else {
 | |
| 
 | |
| 			ra, _ = net.ResolveIPAddr("ip4:icmp", ip.String())
 | |
| 		}
 | |
| 
 | |
| 		p := fastping.NewPinger()
 | |
| 		p.AddIPAddr(ra)
 | |
| 		p.OnRecv = func(addr *net.IPAddr, rtt time.Duration) {
 | |
| 			ping <- true
 | |
| 			p.Stop()
 | |
| 		}
 | |
| 		p.OnIdle = func() {
 | |
| 			ping <- false
 | |
| 			p.Stop()
 | |
| 		}
 | |
| 		p.MaxRTT = 500 * time.Millisecond
 | |
| 		p.RunLoop()
 | |
| 
 | |
| 		if <-ping {
 | |
| 			isOnline = true
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	c.JSON(http.StatusOK, isOnline)
 | |
| 	return
 | |
| }
 |