Newer
Older
wg-portal / internal / server / core.go
@Christoph Haas Christoph Haas on 6 Nov 2020 5 KB initial commit
package server

import (
	"encoding/gob"
	"errors"
	"math/rand"
	"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-gonic/contrib/sessions"
	"github.com/gin-gonic/gin"
)

const SessionIdentifier = "wgPortalSession"
const CacheRefreshDuration = 5 * time.Minute

func init() {
	gob.Register(SessionData{})
}

type SessionData struct {
	LoggedIn      bool
	IsAdmin       bool
	UID           string
	UserName      string
	Firstname     string
	Lastname      string
	SortedBy      string
	SortDirection string
	Search        string
	AlertData     string
	AlertType     string
}

type AlertData struct {
	HasAlert bool
	Message  string
	Type     string
}

type StaticData struct {
	WebsiteTitle string
	WebsiteLogo  string
	LoginURL     string
	LogoutURL    string
}

type Server struct {
	// Core components
	config *common.Config
	server *gin.Engine
	users  *UserManager

	// WireGuard stuff
	wg *wireguard.Manager

	// LDAP stuff
	ldapAuth         ldap.Authentication
	ldapUsers        *ldap.SynchronizedUserCacheHolder
	ldapCacheUpdater *ldap.UserCache
}

func (s *Server) Setup() error {
	// 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 {
		return s.ldapCacheUpdater.LastError
	}

	// Setup WireGuard stuff
	s.wg = &wireguard.Manager{Cfg: &s.config.WG}
	if err := s.wg.Init(); err != nil {
		return err
	}

	// Setup user manager
	s.users = NewUserManager()
	if s.users == nil {
		return errors.New("unable to setup user manager")
	}
	s.users.InitWithDevice(s.wg.GetDeviceInfo())
	s.users.InitWithPeers(s.wg.GetPeerList())

	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)

	// Setup http server
	s.server = gin.Default()

	// 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", sessions.NewCookieStore([]byte("secret"))))

	// 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
	go func(s *Server) {
		for {
			time.Sleep(CacheRefreshDuration)
			if err := s.ldapCacheUpdater.Update(true); err != nil {
				log.Warnf("Failed to update ldap group cache: %v", err)
			}
			log.Debugf("Refreshed LDAP permissions!")
		}
	}(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:      "sn",
			SortDirection: "asc",
			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) getAlertData(c *gin.Context) AlertData {
	currentSession := s.getSessionData(c)
	alertData := AlertData{
		HasAlert: currentSession.AlertData != "",
		Message:  currentSession.AlertData,
		Type:     currentSession.AlertType,
	}
	// Reset alerts
	_ = s.setAlert(c, "", "")

	return alertData
}

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,
		LoginURL:     s.config.AuthRoutePrefix + "/login",
		LogoutURL:    s.config.AuthRoutePrefix + "/logout",
	}
}

func (s *Server) setAlert(c *gin.Context, message, typ string) SessionData {
	currentSession := s.getSessionData(c)
	currentSession.AlertData = message
	currentSession.AlertType = typ
	_ = s.updateSessionData(c, currentSession)

	return currentSession
}

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"
	}
}