mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-11-03 23:55:44 +00:00 
			
		
		
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			v4.0.0-bet
			...
			feat/analy
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					902574e501 | ||
| 
						 | 
					aac19d4d5a | 
@@ -94,6 +94,7 @@ func init() {
 | 
				
			|||||||
		{"resources-dir", "/data/resources", "Path to a directory containing custom resources (e.g. background image)."},
 | 
							{"resources-dir", "/data/resources", "Path to a directory containing custom resources (e.g. background image)."},
 | 
				
			||||||
		{"database-path", "/data/tinyauth.db", "Path to the Sqlite database file."},
 | 
							{"database-path", "/data/tinyauth.db", "Path to the Sqlite database file."},
 | 
				
			||||||
		{"trusted-proxies", "", "Comma separated list of trusted proxies (IP addresses or CIDRs) for correct client IP detection."},
 | 
							{"trusted-proxies", "", "Comma separated list of trusted proxies (IP addresses or CIDRs) for correct client IP detection."},
 | 
				
			||||||
 | 
							{"disable-analytics", false, "Disable anonymous version collection."},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, opt := range configOptions {
 | 
						for _, opt := range configOptions {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,14 @@
 | 
				
			|||||||
package bootstrap
 | 
					package bootstrap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
	"tinyauth/internal/config"
 | 
						"tinyauth/internal/config"
 | 
				
			||||||
	"tinyauth/internal/controller"
 | 
						"tinyauth/internal/controller"
 | 
				
			||||||
	"tinyauth/internal/middleware"
 | 
						"tinyauth/internal/middleware"
 | 
				
			||||||
@@ -29,40 +33,43 @@ type Service interface {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type BootstrapApp struct {
 | 
					type BootstrapApp struct {
 | 
				
			||||||
	Config config.Config
 | 
						config config.Config
 | 
				
			||||||
 | 
						uuid   string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewBootstrapApp(config config.Config) *BootstrapApp {
 | 
					func NewBootstrapApp(config config.Config) *BootstrapApp {
 | 
				
			||||||
	return &BootstrapApp{
 | 
						return &BootstrapApp{
 | 
				
			||||||
		Config: config,
 | 
							config: config,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (app *BootstrapApp) Setup() error {
 | 
					func (app *BootstrapApp) Setup() error {
 | 
				
			||||||
	// Parse users
 | 
						// Parse users
 | 
				
			||||||
	users, err := utils.GetUsers(app.Config.Users, app.Config.UsersFile)
 | 
						users, err := utils.GetUsers(app.config.Users, app.config.UsersFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Get OAuth configs
 | 
						// Get OAuth configs
 | 
				
			||||||
	oauthProviders, err := utils.GetOAuthProvidersConfig(os.Environ(), os.Args, app.Config.AppURL)
 | 
						oauthProviders, err := utils.GetOAuthProvidersConfig(os.Environ(), os.Args, app.config.AppURL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Get cookie domain
 | 
						// Get cookie domain
 | 
				
			||||||
	cookieDomain, err := utils.GetCookieDomain(app.Config.AppURL)
 | 
						cookieDomain, err := utils.GetCookieDomain(app.config.AppURL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Cookie names
 | 
						// Cookie names
 | 
				
			||||||
	appUrl, _ := url.Parse(app.Config.AppURL) // Already validated
 | 
						appUrl, _ := url.Parse(app.config.AppURL) // Already validated
 | 
				
			||||||
	cookieId := utils.GenerateIdentifier(appUrl.Hostname())
 | 
						uuid := utils.GenerateUUID(appUrl.Hostname())
 | 
				
			||||||
 | 
						app.uuid = uuid
 | 
				
			||||||
 | 
						cookieId := strings.Split(uuid, "-")[0]
 | 
				
			||||||
	sessionCookieName := fmt.Sprintf("%s-%s", config.SessionCookieName, cookieId)
 | 
						sessionCookieName := fmt.Sprintf("%s-%s", config.SessionCookieName, cookieId)
 | 
				
			||||||
	csrfCookieName := fmt.Sprintf("%s-%s", config.CSRFCookieName, cookieId)
 | 
						csrfCookieName := fmt.Sprintf("%s-%s", config.CSRFCookieName, cookieId)
 | 
				
			||||||
	redirectCookieName := fmt.Sprintf("%s-%s", config.RedirectCookieName, cookieId)
 | 
						redirectCookieName := fmt.Sprintf("%s-%s", config.RedirectCookieName, cookieId)
 | 
				
			||||||
@@ -70,26 +77,26 @@ func (app *BootstrapApp) Setup() error {
 | 
				
			|||||||
	// Create configs
 | 
						// Create configs
 | 
				
			||||||
	authConfig := service.AuthServiceConfig{
 | 
						authConfig := service.AuthServiceConfig{
 | 
				
			||||||
		Users:             users,
 | 
							Users:             users,
 | 
				
			||||||
		OauthWhitelist:    app.Config.OAuthWhitelist,
 | 
							OauthWhitelist:    app.config.OAuthWhitelist,
 | 
				
			||||||
		SessionExpiry:     app.Config.SessionExpiry,
 | 
							SessionExpiry:     app.config.SessionExpiry,
 | 
				
			||||||
		SecureCookie:      app.Config.SecureCookie,
 | 
							SecureCookie:      app.config.SecureCookie,
 | 
				
			||||||
		CookieDomain:      cookieDomain,
 | 
							CookieDomain:      cookieDomain,
 | 
				
			||||||
		LoginTimeout:      app.Config.LoginTimeout,
 | 
							LoginTimeout:      app.config.LoginTimeout,
 | 
				
			||||||
		LoginMaxRetries:   app.Config.LoginMaxRetries,
 | 
							LoginMaxRetries:   app.config.LoginMaxRetries,
 | 
				
			||||||
		SessionCookieName: sessionCookieName,
 | 
							SessionCookieName: sessionCookieName,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Setup services
 | 
						// Setup services
 | 
				
			||||||
	var ldapService *service.LdapService
 | 
						var ldapService *service.LdapService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if app.Config.LdapAddress != "" {
 | 
						if app.config.LdapAddress != "" {
 | 
				
			||||||
		ldapConfig := service.LdapServiceConfig{
 | 
							ldapConfig := service.LdapServiceConfig{
 | 
				
			||||||
			Address:      app.Config.LdapAddress,
 | 
								Address:      app.config.LdapAddress,
 | 
				
			||||||
			BindDN:       app.Config.LdapBindDN,
 | 
								BindDN:       app.config.LdapBindDN,
 | 
				
			||||||
			BindPassword: app.Config.LdapBindPassword,
 | 
								BindPassword: app.config.LdapBindPassword,
 | 
				
			||||||
			BaseDN:       app.Config.LdapBaseDN,
 | 
								BaseDN:       app.config.LdapBaseDN,
 | 
				
			||||||
			Insecure:     app.Config.LdapInsecure,
 | 
								Insecure:     app.config.LdapInsecure,
 | 
				
			||||||
			SearchFilter: app.Config.LdapSearchFilter,
 | 
								SearchFilter: app.config.LdapSearchFilter,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ldapService = service.NewLdapService(ldapConfig)
 | 
							ldapService = service.NewLdapService(ldapConfig)
 | 
				
			||||||
@@ -104,7 +111,7 @@ func (app *BootstrapApp) Setup() error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Bootstrap database
 | 
						// Bootstrap database
 | 
				
			||||||
	databaseService := service.NewDatabaseService(service.DatabaseServiceConfig{
 | 
						databaseService := service.NewDatabaseService(service.DatabaseServiceConfig{
 | 
				
			||||||
		DatabasePath: app.Config.DatabasePath,
 | 
							DatabasePath: app.config.DatabasePath,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Debug().Str("service", fmt.Sprintf("%T", databaseService)).Msg("Initializing service")
 | 
						log.Debug().Str("service", fmt.Sprintf("%T", databaseService)).Msg("Initializing service")
 | 
				
			||||||
@@ -183,8 +190,8 @@ func (app *BootstrapApp) Setup() error {
 | 
				
			|||||||
	// Create engine
 | 
						// Create engine
 | 
				
			||||||
	engine := gin.New()
 | 
						engine := gin.New()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(app.Config.TrustedProxies) > 0 {
 | 
						if len(app.config.TrustedProxies) > 0 {
 | 
				
			||||||
		engine.SetTrustedProxies(strings.Split(app.Config.TrustedProxies, ","))
 | 
							engine.SetTrustedProxies(strings.Split(app.config.TrustedProxies, ","))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if config.Version != "development" {
 | 
						if config.Version != "development" {
 | 
				
			||||||
@@ -219,24 +226,24 @@ func (app *BootstrapApp) Setup() error {
 | 
				
			|||||||
	// Create controllers
 | 
						// Create controllers
 | 
				
			||||||
	contextController := controller.NewContextController(controller.ContextControllerConfig{
 | 
						contextController := controller.NewContextController(controller.ContextControllerConfig{
 | 
				
			||||||
		Providers:             configuredProviders,
 | 
							Providers:             configuredProviders,
 | 
				
			||||||
		Title:                 app.Config.Title,
 | 
							Title:                 app.config.Title,
 | 
				
			||||||
		AppURL:                app.Config.AppURL,
 | 
							AppURL:                app.config.AppURL,
 | 
				
			||||||
		CookieDomain:          cookieDomain,
 | 
							CookieDomain:          cookieDomain,
 | 
				
			||||||
		ForgotPasswordMessage: app.Config.ForgotPasswordMessage,
 | 
							ForgotPasswordMessage: app.config.ForgotPasswordMessage,
 | 
				
			||||||
		BackgroundImage:       app.Config.BackgroundImage,
 | 
							BackgroundImage:       app.config.BackgroundImage,
 | 
				
			||||||
		OAuthAutoRedirect:     app.Config.OAuthAutoRedirect,
 | 
							OAuthAutoRedirect:     app.config.OAuthAutoRedirect,
 | 
				
			||||||
	}, apiRouter)
 | 
						}, apiRouter)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	oauthController := controller.NewOAuthController(controller.OAuthControllerConfig{
 | 
						oauthController := controller.NewOAuthController(controller.OAuthControllerConfig{
 | 
				
			||||||
		AppURL:             app.Config.AppURL,
 | 
							AppURL:             app.config.AppURL,
 | 
				
			||||||
		SecureCookie:       app.Config.SecureCookie,
 | 
							SecureCookie:       app.config.SecureCookie,
 | 
				
			||||||
		CSRFCookieName:     csrfCookieName,
 | 
							CSRFCookieName:     csrfCookieName,
 | 
				
			||||||
		RedirectCookieName: redirectCookieName,
 | 
							RedirectCookieName: redirectCookieName,
 | 
				
			||||||
		CookieDomain:       cookieDomain,
 | 
							CookieDomain:       cookieDomain,
 | 
				
			||||||
	}, apiRouter, authService, oauthBrokerService)
 | 
						}, apiRouter, authService, oauthBrokerService)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	proxyController := controller.NewProxyController(controller.ProxyControllerConfig{
 | 
						proxyController := controller.NewProxyController(controller.ProxyControllerConfig{
 | 
				
			||||||
		AppURL: app.Config.AppURL,
 | 
							AppURL: app.config.AppURL,
 | 
				
			||||||
	}, apiRouter, dockerService, authService)
 | 
						}, apiRouter, dockerService, authService)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	userController := controller.NewUserController(controller.UserControllerConfig{
 | 
						userController := controller.NewUserController(controller.UserControllerConfig{
 | 
				
			||||||
@@ -244,7 +251,7 @@ func (app *BootstrapApp) Setup() error {
 | 
				
			|||||||
	}, apiRouter, authService)
 | 
						}, apiRouter, authService)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	resourcesController := controller.NewResourcesController(controller.ResourcesControllerConfig{
 | 
						resourcesController := controller.NewResourcesController(controller.ResourcesControllerConfig{
 | 
				
			||||||
		ResourcesDir: app.Config.ResourcesDir,
 | 
							ResourcesDir: app.config.ResourcesDir,
 | 
				
			||||||
	}, mainRouter)
 | 
						}, mainRouter)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	healthController := controller.NewHealthController(apiRouter)
 | 
						healthController := controller.NewHealthController(apiRouter)
 | 
				
			||||||
@@ -264,8 +271,14 @@ func (app *BootstrapApp) Setup() error {
 | 
				
			|||||||
		ctrl.SetupRoutes()
 | 
							ctrl.SetupRoutes()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If analytics are not disabled, start heartbeat
 | 
				
			||||||
 | 
						if !app.config.DisableAnalytics {
 | 
				
			||||||
 | 
							log.Debug().Msg("Starting heartbeat routine")
 | 
				
			||||||
 | 
							go app.heartbeat()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Start server
 | 
						// Start server
 | 
				
			||||||
	address := fmt.Sprintf("%s:%d", app.Config.Address, app.Config.Port)
 | 
						address := fmt.Sprintf("%s:%d", app.config.Address, app.config.Port)
 | 
				
			||||||
	log.Info().Msgf("Starting server on %s", address)
 | 
						log.Info().Msgf("Starting server on %s", address)
 | 
				
			||||||
	if err := engine.Run(address); err != nil {
 | 
						if err := engine.Run(address); err != nil {
 | 
				
			||||||
		log.Fatal().Err(err).Msg("Failed to start server")
 | 
							log.Fatal().Err(err).Msg("Failed to start server")
 | 
				
			||||||
@@ -273,3 +286,55 @@ func (app *BootstrapApp) Setup() error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (app *BootstrapApp) heartbeat() {
 | 
				
			||||||
 | 
						ticker := time.NewTicker(time.Duration(12) * time.Hour)
 | 
				
			||||||
 | 
						defer ticker.Stop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type heartbeat struct {
 | 
				
			||||||
 | 
							UUID    string `json:"uuid"`
 | 
				
			||||||
 | 
							Version string `json:"version"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var body heartbeat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						body.UUID = app.uuid
 | 
				
			||||||
 | 
						body.Version = config.Version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bodyJson, err := json.Marshal(body)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error().Err(err).Msg("Failed to marshal heartbeat body")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client := &http.Client{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						heartbeatURL := config.ApiServer + "/v1/instances/heartbeat"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for ; true; <-ticker.C {
 | 
				
			||||||
 | 
							log.Debug().Msg("Sending heartbeat")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							req, err := http.NewRequest(http.MethodPost, heartbeatURL, bytes.NewReader(bodyJson))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error().Err(err).Msg("Failed to create heartbeat request")
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							req.Header.Add("Content-Type", "application/json")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							res, err := client.Do(req)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error().Err(err).Msg("Failed to send heartbeat")
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							res.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if res.StatusCode != 200 {
 | 
				
			||||||
 | 
								log.Debug().Str("status", res.Status).Msg("Heartbeat returned non-200 status")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,6 +39,7 @@ type Config struct {
 | 
				
			|||||||
	ResourcesDir          string `mapstructure:"resources-dir"`
 | 
						ResourcesDir          string `mapstructure:"resources-dir"`
 | 
				
			||||||
	DatabasePath          string `mapstructure:"database-path" validate:"required"`
 | 
						DatabasePath          string `mapstructure:"database-path" validate:"required"`
 | 
				
			||||||
	TrustedProxies        string `mapstructure:"trusted-proxies"`
 | 
						TrustedProxies        string `mapstructure:"trusted-proxies"`
 | 
				
			||||||
 | 
						DisableAnalytics      bool   `mapstructure:"disable-analytics"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// OAuth/OIDC config
 | 
					// OAuth/OIDC config
 | 
				
			||||||
@@ -169,3 +170,7 @@ type AppPath struct {
 | 
				
			|||||||
type Providers struct {
 | 
					type Providers struct {
 | 
				
			||||||
	Providers map[string]OAuthServiceConfig
 | 
						Providers map[string]OAuthServiceConfig
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// API server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var ApiServer = "https://api.tinyauth.app"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -101,8 +101,7 @@ func CheckFilter(filter string, str string) bool {
 | 
				
			|||||||
	return false
 | 
						return false
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GenerateIdentifier(str string) string {
 | 
					func GenerateUUID(str string) string {
 | 
				
			||||||
	uuid := uuid.NewSHA1(uuid.NameSpaceURL, []byte(str))
 | 
						uuid := uuid.NewSHA1(uuid.NameSpaceURL, []byte(str))
 | 
				
			||||||
	uuidString := uuid.String()
 | 
						return uuid.String()
 | 
				
			||||||
	return strings.Split(uuidString, "-")[0]
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -136,16 +136,13 @@ func TestCheckFilter(t *testing.T) {
 | 
				
			|||||||
	assert.Equal(t, false, utils.CheckFilter("apple, banana, cherry", "grape"))
 | 
						assert.Equal(t, false, utils.CheckFilter("apple, banana, cherry", "grape"))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestGenerateIdentifier(t *testing.T) {
 | 
					func TestGenerateUUID(t *testing.T) {
 | 
				
			||||||
	// Consistent output for same input
 | 
						// Consistent output for same input
 | 
				
			||||||
	id1 := utils.GenerateIdentifier("teststring")
 | 
						id1 := utils.GenerateUUID("teststring")
 | 
				
			||||||
	id2 := utils.GenerateIdentifier("teststring")
 | 
						id2 := utils.GenerateUUID("teststring")
 | 
				
			||||||
	assert.Equal(t, id1, id2)
 | 
						assert.Equal(t, id1, id2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Different output for different input
 | 
						// Different output for different input
 | 
				
			||||||
	id3 := utils.GenerateIdentifier("differentstring")
 | 
						id3 := utils.GenerateUUID("differentstring")
 | 
				
			||||||
	assert.Assert(t, id1 != id3)
 | 
						assert.Assert(t, id1 != id3)
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Check length (should be 8 characters from first segment of UUID)
 | 
					 | 
				
			||||||
	assert.Equal(t, 8, len(id1))
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user