mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-11-03 23:55:44 +00:00 
			
		
		
		
	* wip: add middlewares * refactor: use context fom middleware in handlers * refactor: use controller approach in handlers * refactor: move oauth providers into services (non-working) * feat: create oauth broker service * refactor: use a boostrap service to bootstrap the app * refactor: split utils into smaller files * refactor: use more clear name for frontend assets * feat: allow customizability of resources dir * fix: fix typo in ui middleware * fix: validate resource file paths in ui middleware * refactor: move resource handling to a controller * feat: add some logging * fix: configure middlewares before groups * fix: use correct api path in login mutation * fix: coderabbit suggestions * fix: further coderabbit suggestions
		
			
				
	
	
		
			156 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			156 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package service
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"crypto/tls"
 | 
						|
	"fmt"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/cenkalti/backoff/v5"
 | 
						|
	ldapgo "github.com/go-ldap/ldap/v3"
 | 
						|
	"github.com/rs/zerolog/log"
 | 
						|
)
 | 
						|
 | 
						|
type LdapServiceConfig struct {
 | 
						|
	Address      string
 | 
						|
	BindDN       string
 | 
						|
	BindPassword string
 | 
						|
	BaseDN       string
 | 
						|
	Insecure     bool
 | 
						|
	SearchFilter string
 | 
						|
}
 | 
						|
 | 
						|
type LdapService struct {
 | 
						|
	Config LdapServiceConfig
 | 
						|
	Conn   *ldapgo.Conn
 | 
						|
}
 | 
						|
 | 
						|
func NewLdapService(config LdapServiceConfig) *LdapService {
 | 
						|
	return &LdapService{
 | 
						|
		Config: config,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (ldap *LdapService) Init() error {
 | 
						|
	_, err := ldap.connect()
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("failed to connect to LDAP server: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	go func() {
 | 
						|
		for range time.Tick(time.Duration(5) * time.Minute) {
 | 
						|
			err := ldap.heartbeat()
 | 
						|
			if err != nil {
 | 
						|
				log.Error().Err(err).Msg("LDAP connection heartbeat failed")
 | 
						|
				if reconnectErr := ldap.reconnect(); reconnectErr != nil {
 | 
						|
					log.Error().Err(reconnectErr).Msg("Failed to reconnect to LDAP server")
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				log.Info().Msg("Successfully reconnected to LDAP server")
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (ldap *LdapService) connect() (*ldapgo.Conn, error) {
 | 
						|
	conn, err := ldapgo.DialURL(ldap.Config.Address, ldapgo.DialWithTLSConfig(&tls.Config{
 | 
						|
		InsecureSkipVerify: ldap.Config.Insecure,
 | 
						|
		MinVersion:         tls.VersionTLS12,
 | 
						|
	}))
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	err = conn.Bind(ldap.Config.BindDN, ldap.Config.BindPassword)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Set and return the connection
 | 
						|
	ldap.Conn = conn
 | 
						|
	return conn, nil
 | 
						|
}
 | 
						|
 | 
						|
func (ldap *LdapService) Search(username string) (string, error) {
 | 
						|
	// Escape the username to prevent LDAP injection
 | 
						|
	escapedUsername := ldapgo.EscapeFilter(username)
 | 
						|
	filter := fmt.Sprintf(ldap.Config.SearchFilter, escapedUsername)
 | 
						|
 | 
						|
	searchRequest := ldapgo.NewSearchRequest(
 | 
						|
		ldap.Config.BaseDN,
 | 
						|
		ldapgo.ScopeWholeSubtree, ldapgo.NeverDerefAliases, 0, 0, false,
 | 
						|
		filter,
 | 
						|
		[]string{"dn"},
 | 
						|
		nil,
 | 
						|
	)
 | 
						|
 | 
						|
	searchResult, err := ldap.Conn.Search(searchRequest)
 | 
						|
	if err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
 | 
						|
	if len(searchResult.Entries) != 1 {
 | 
						|
		return "", fmt.Errorf("multiple or no entries found for user %s", username)
 | 
						|
	}
 | 
						|
 | 
						|
	userDN := searchResult.Entries[0].DN
 | 
						|
	return userDN, nil
 | 
						|
}
 | 
						|
 | 
						|
func (ldap *LdapService) Bind(userDN string, password string) error {
 | 
						|
	err := ldap.Conn.Bind(userDN, password)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (ldap *LdapService) heartbeat() error {
 | 
						|
	log.Debug().Msg("Performing LDAP connection heartbeat")
 | 
						|
 | 
						|
	searchRequest := ldapgo.NewSearchRequest(
 | 
						|
		"",
 | 
						|
		ldapgo.ScopeBaseObject, ldapgo.NeverDerefAliases, 0, 0, false,
 | 
						|
		"(objectClass=*)",
 | 
						|
		[]string{},
 | 
						|
		nil,
 | 
						|
	)
 | 
						|
 | 
						|
	_, err := ldap.Conn.Search(searchRequest)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	// No error means the connection is alive
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (ldap *LdapService) reconnect() error {
 | 
						|
	log.Info().Msg("Reconnecting to LDAP server")
 | 
						|
 | 
						|
	exp := backoff.NewExponentialBackOff()
 | 
						|
	exp.InitialInterval = 500 * time.Millisecond
 | 
						|
	exp.RandomizationFactor = 0.1
 | 
						|
	exp.Multiplier = 1.5
 | 
						|
	exp.Reset()
 | 
						|
 | 
						|
	operation := func() (*ldapgo.Conn, error) {
 | 
						|
		ldap.Conn.Close()
 | 
						|
		conn, err := ldap.connect()
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		return conn, nil
 | 
						|
	}
 | 
						|
 | 
						|
	_, err := backoff.Retry(context.TODO(), operation, backoff.WithBackOff(exp), backoff.WithMaxTries(3))
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 |