mirror of https://github.com/h44z/wg-portal.git
				
				
				
			
		
			
				
	
	
		
			292 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			292 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Go
		
	
	
	
| package server
 | |
| 
 | |
| import (
 | |
| 	"encoding/gob"
 | |
| 	"errors"
 | |
| 	"html/template"
 | |
| 	"math/rand"
 | |
| 	"net/url"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/h44z/wg-portal/internal/wireguard"
 | |
| 
 | |
| 	"github.com/h44z/wg-portal/internal/common"
 | |
| 
 | |
| 	"github.com/h44z/wg-portal/internal/ldap"
 | |
| 	log "github.com/sirupsen/logrus"
 | |
| 
 | |
| 	"github.com/gin-contrib/sessions"
 | |
| 	"github.com/gin-contrib/sessions/memstore"
 | |
| 	"github.com/gin-gonic/gin"
 | |
| )
 | |
| 
 | |
| const SessionIdentifier = "wgPortalSession"
 | |
| const CacheRefreshDuration = 5 * time.Minute
 | |
| 
 | |
| func init() {
 | |
| 	gob.Register(SessionData{})
 | |
| 	gob.Register(FlashData{})
 | |
| 	gob.Register(User{})
 | |
| 	gob.Register(Device{})
 | |
| 	gob.Register(LdapCreateForm{})
 | |
| }
 | |
| 
 | |
| type SessionData struct {
 | |
| 	LoggedIn      bool
 | |
| 	IsAdmin       bool
 | |
| 	UID           string
 | |
| 	UserName      string
 | |
| 	Firstname     string
 | |
| 	Lastname      string
 | |
| 	Email         string
 | |
| 	SortedBy      string
 | |
| 	SortDirection string
 | |
| 	Search        string
 | |
| 	AlertData     string
 | |
| 	AlertType     string
 | |
| 	FormData      interface{}
 | |
| }
 | |
| 
 | |
| type FlashData struct {
 | |
| 	HasAlert bool
 | |
| 	Message  string
 | |
| 	Type     string
 | |
| }
 | |
| 
 | |
| type StaticData struct {
 | |
| 	WebsiteTitle string
 | |
| 	WebsiteLogo  string
 | |
| 	CompanyName  string
 | |
| 	Year         int
 | |
| 	LdapDisabled bool
 | |
| }
 | |
| 
 | |
| type Server struct {
 | |
| 	// Core components
 | |
| 	config  *common.Config
 | |
| 	server  *gin.Engine
 | |
| 	users   *UserManager
 | |
| 	mailTpl *template.Template
 | |
| 
 | |
| 	// WireGuard stuff
 | |
| 	wg *wireguard.Manager
 | |
| 
 | |
| 	// LDAP stuff
 | |
| 	ldapDisabled     bool
 | |
| 	ldapAuth         ldap.Authentication
 | |
| 	ldapUsers        *ldap.SynchronizedUserCacheHolder
 | |
| 	ldapCacheUpdater *ldap.UserCache
 | |
| }
 | |
| 
 | |
| func (s *Server) Setup() error {
 | |
| 	dir := s.getExecutableDirectory()
 | |
| 	rDir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
 | |
| 	log.Infof("Real working directory: %s", rDir)
 | |
| 	log.Infof("Current working directory: %s", dir)
 | |
| 
 | |
| 	// Init rand
 | |
| 	rand.Seed(time.Now().UnixNano())
 | |
| 
 | |
| 	s.config = common.NewConfig()
 | |
| 
 | |
| 	// Setup LDAP stuff
 | |
| 	s.ldapAuth = ldap.NewAuthentication(s.config.LDAP)
 | |
| 	s.ldapUsers = &ldap.SynchronizedUserCacheHolder{}
 | |
| 	s.ldapUsers.Init()
 | |
| 	s.ldapCacheUpdater = ldap.NewUserCache(s.config.LDAP, s.ldapUsers)
 | |
| 	if s.ldapCacheUpdater.LastError != nil {
 | |
| 		log.Warnf("LDAP error: %v", s.ldapCacheUpdater.LastError)
 | |
| 		log.Warnf("LDAP features disabled!")
 | |
| 		s.ldapDisabled = true
 | |
| 	}
 | |
| 
 | |
| 	// Setup WireGuard stuff
 | |
| 	s.wg = &wireguard.Manager{Cfg: &s.config.WG}
 | |
| 	if err := s.wg.Init(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// Setup user manager
 | |
| 	if s.users = NewUserManager(filepath.Join(dir, s.config.Core.DatabasePath), s.wg, s.ldapUsers); s.users == nil {
 | |
| 		return errors.New("unable to setup user manager")
 | |
| 	}
 | |
| 	if err := s.users.InitFromCurrentInterface(); err != nil {
 | |
| 		return errors.New("unable to initialize user manager")
 | |
| 	}
 | |
| 	if err := s.RestoreWireGuardInterface(); err != nil {
 | |
| 		return errors.New("unable to restore wirguard state")
 | |
| 	}
 | |
| 
 | |
| 	// Setup mail template
 | |
| 	var err error
 | |
| 	s.mailTpl, err = template.New("email.html").ParseFiles(filepath.Join(dir, "/assets/tpl/email.html"))
 | |
| 	if err != nil {
 | |
| 		return errors.New("unable to pare mail template")
 | |
| 	}
 | |
| 
 | |
| 	// Setup http server
 | |
| 	s.server = gin.Default()
 | |
| 	s.server.SetFuncMap(template.FuncMap{
 | |
| 		"formatBytes": common.ByteCountSI,
 | |
| 		"urlEncode":   url.QueryEscape,
 | |
| 	})
 | |
| 
 | |
| 	// Setup templates
 | |
| 	log.Infof("Loading templates from: %s", filepath.Join(dir, "/assets/tpl/*.html"))
 | |
| 	s.server.LoadHTMLGlob(filepath.Join(dir, "/assets/tpl/*.html"))
 | |
| 	s.server.Use(sessions.Sessions("authsession", memstore.NewStore([]byte("secret")))) // TODO: change key?
 | |
| 
 | |
| 	// Serve static files
 | |
| 	s.server.Static("/css", filepath.Join(dir, "/assets/css"))
 | |
| 	s.server.Static("/js", filepath.Join(dir, "/assets/js"))
 | |
| 	s.server.Static("/img", filepath.Join(dir, "/assets/img"))
 | |
| 	s.server.Static("/fonts", filepath.Join(dir, "/assets/fonts"))
 | |
| 
 | |
| 	// Setup all routes
 | |
| 	SetupRoutes(s)
 | |
| 
 | |
| 	log.Infof("Setup of service completed!")
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (s *Server) Run() {
 | |
| 	// Start ldap group watcher
 | |
| 	if !s.ldapDisabled {
 | |
| 		go func(s *Server) {
 | |
| 			for {
 | |
| 				time.Sleep(CacheRefreshDuration)
 | |
| 				if err := s.ldapCacheUpdater.Update(true, true); err != nil {
 | |
| 					log.Warnf("Failed to update ldap group cache: %v", err)
 | |
| 				}
 | |
| 				log.Debugf("Refreshed LDAP permissions!")
 | |
| 			}
 | |
| 		}(s)
 | |
| 	}
 | |
| 
 | |
| 	if !s.ldapDisabled && s.config.Core.SyncLdapStatus {
 | |
| 		go func(s *Server) {
 | |
| 			for {
 | |
| 				time.Sleep(CacheRefreshDuration)
 | |
| 				if err := s.SyncLdapAttributesWithWireGuard(); err != nil {
 | |
| 					log.Warnf("Failed to synchronize ldap attributes: %v", err)
 | |
| 				}
 | |
| 				log.Debugf("Synced LDAP attributes!")
 | |
| 			}
 | |
| 		}(s)
 | |
| 	}
 | |
| 
 | |
| 	// Run web service
 | |
| 	err := s.server.Run(s.config.Core.ListeningAddress)
 | |
| 	if err != nil {
 | |
| 		log.Errorf("Failed to listen and serve on %s: %v", s.config.Core.ListeningAddress, err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (s *Server) getExecutableDirectory() string {
 | |
| 	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
 | |
| 	if err != nil {
 | |
| 		log.Errorf("Failed to get executable directory: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	if _, err := os.Stat(filepath.Join(dir, "assets")); os.IsNotExist(err) {
 | |
| 		return "." // assets directory not found -> we are developing in goland =)
 | |
| 	}
 | |
| 
 | |
| 	return dir
 | |
| }
 | |
| 
 | |
| func (s *Server) getSessionData(c *gin.Context) SessionData {
 | |
| 	session := sessions.Default(c)
 | |
| 	rawSessionData := session.Get(SessionIdentifier)
 | |
| 
 | |
| 	var sessionData SessionData
 | |
| 	if rawSessionData != nil {
 | |
| 		sessionData = rawSessionData.(SessionData)
 | |
| 	} else {
 | |
| 		sessionData = SessionData{
 | |
| 			SortedBy:      "mail",
 | |
| 			SortDirection: "asc",
 | |
| 			Email:         "",
 | |
| 			Firstname:     "",
 | |
| 			Lastname:      "",
 | |
| 			IsAdmin:       false,
 | |
| 			LoggedIn:      false,
 | |
| 		}
 | |
| 		session.Set(SessionIdentifier, sessionData)
 | |
| 		if err := session.Save(); err != nil {
 | |
| 			log.Errorf("Failed to store session: %v", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return sessionData
 | |
| }
 | |
| 
 | |
| func (s *Server) getFlashes(c *gin.Context) []FlashData {
 | |
| 	session := sessions.Default(c)
 | |
| 	flashes := session.Flashes()
 | |
| 	if err := session.Save(); err != nil {
 | |
| 		log.Errorf("Failed to store session after setting flash: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	flashData := make([]FlashData, len(flashes))
 | |
| 	for i := range flashes {
 | |
| 		flashData[i] = flashes[i].(FlashData)
 | |
| 	}
 | |
| 
 | |
| 	return flashData
 | |
| }
 | |
| 
 | |
| func (s *Server) updateSessionData(c *gin.Context, data SessionData) error {
 | |
| 	session := sessions.Default(c)
 | |
| 	session.Set(SessionIdentifier, data)
 | |
| 	if err := session.Save(); err != nil {
 | |
| 		log.Errorf("Failed to store session: %v", err)
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (s *Server) destroySessionData(c *gin.Context) error {
 | |
| 	session := sessions.Default(c)
 | |
| 	session.Delete(SessionIdentifier)
 | |
| 	if err := session.Save(); err != nil {
 | |
| 		log.Errorf("Failed to destroy session: %v", err)
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (s *Server) getStaticData() StaticData {
 | |
| 	return StaticData{
 | |
| 		WebsiteTitle: s.config.Core.Title,
 | |
| 		WebsiteLogo:  "/img/header-logo.png",
 | |
| 		CompanyName:  s.config.Core.CompanyName,
 | |
| 		LdapDisabled: s.ldapDisabled,
 | |
| 		Year:         time.Now().Year(),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (s *Server) setFlashMessage(c *gin.Context, message, typ string) {
 | |
| 	session := sessions.Default(c)
 | |
| 	session.AddFlash(FlashData{
 | |
| 		Message: message,
 | |
| 		Type:    typ,
 | |
| 	})
 | |
| 	if err := session.Save(); err != nil {
 | |
| 		log.Errorf("Failed to store session after setting flash: %v", err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (s SessionData) GetSortIcon(field string) string {
 | |
| 	if s.SortedBy != field {
 | |
| 		return "fa-sort"
 | |
| 	}
 | |
| 	if s.SortDirection == "asc" {
 | |
| 		return "fa-sort-alpha-down"
 | |
| 	} else {
 | |
| 		return "fa-sort-alpha-up"
 | |
| 	}
 | |
| }
 |