mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-10-31 14:15:50 +00:00 
			
		
		
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			97639ae903
			...
			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