| 
							
							
							
						 |  |  | @@ -1,10 +1,14 @@ | 
		
	
		
			
				|  |  |  |  | package bootstrap | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | import ( | 
		
	
		
			
				|  |  |  |  | 	"bytes" | 
		
	
		
			
				|  |  |  |  | 	"encoding/json" | 
		
	
		
			
				|  |  |  |  | 	"fmt" | 
		
	
		
			
				|  |  |  |  | 	"net/http" | 
		
	
		
			
				|  |  |  |  | 	"net/url" | 
		
	
		
			
				|  |  |  |  | 	"os" | 
		
	
		
			
				|  |  |  |  | 	"strings" | 
		
	
		
			
				|  |  |  |  | 	"time" | 
		
	
		
			
				|  |  |  |  | 	"tinyauth/internal/config" | 
		
	
		
			
				|  |  |  |  | 	"tinyauth/internal/controller" | 
		
	
		
			
				|  |  |  |  | 	"tinyauth/internal/middleware" | 
		
	
	
		
			
				
					
					|  |  |  | @@ -29,40 +33,43 @@ type Service interface { | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | type BootstrapApp struct { | 
		
	
		
			
				|  |  |  |  | 	Config config.Config | 
		
	
		
			
				|  |  |  |  | 	config config.Config | 
		
	
		
			
				|  |  |  |  | 	uuid   string | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | func NewBootstrapApp(config config.Config) *BootstrapApp { | 
		
	
		
			
				|  |  |  |  | 	return &BootstrapApp{ | 
		
	
		
			
				|  |  |  |  | 		Config: config, | 
		
	
		
			
				|  |  |  |  | 		config: config, | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | func (app *BootstrapApp) Setup() error { | 
		
	
		
			
				|  |  |  |  | 	// 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 { | 
		
	
		
			
				|  |  |  |  | 		return err | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	// 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 { | 
		
	
		
			
				|  |  |  |  | 		return err | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	// Get cookie domain | 
		
	
		
			
				|  |  |  |  | 	cookieDomain, err := utils.GetCookieDomain(app.Config.AppURL) | 
		
	
		
			
				|  |  |  |  | 	cookieDomain, err := utils.GetCookieDomain(app.config.AppURL) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	if err != nil { | 
		
	
		
			
				|  |  |  |  | 		return err | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	// Cookie names | 
		
	
		
			
				|  |  |  |  | 	appUrl, _ := url.Parse(app.Config.AppURL) // Already validated | 
		
	
		
			
				|  |  |  |  | 	cookieId := utils.GenerateIdentifier(appUrl.Hostname()) | 
		
	
		
			
				|  |  |  |  | 	appUrl, _ := url.Parse(app.config.AppURL) // Already validated | 
		
	
		
			
				|  |  |  |  | 	uuid := utils.GenerateUUID(appUrl.Hostname()) | 
		
	
		
			
				|  |  |  |  | 	app.uuid = uuid | 
		
	
		
			
				|  |  |  |  | 	cookieId := strings.Split(uuid, "-")[0] | 
		
	
		
			
				|  |  |  |  | 	sessionCookieName := fmt.Sprintf("%s-%s", config.SessionCookieName, cookieId) | 
		
	
		
			
				|  |  |  |  | 	csrfCookieName := fmt.Sprintf("%s-%s", config.CSRFCookieName, cookieId) | 
		
	
		
			
				|  |  |  |  | 	redirectCookieName := fmt.Sprintf("%s-%s", config.RedirectCookieName, cookieId) | 
		
	
	
		
			
				
					
					|  |  |  | @@ -70,26 +77,26 @@ func (app *BootstrapApp) Setup() error { | 
		
	
		
			
				|  |  |  |  | 	// Create configs | 
		
	
		
			
				|  |  |  |  | 	authConfig := service.AuthServiceConfig{ | 
		
	
		
			
				|  |  |  |  | 		Users:             users, | 
		
	
		
			
				|  |  |  |  | 		OauthWhitelist:    app.Config.OAuthWhitelist, | 
		
	
		
			
				|  |  |  |  | 		SessionExpiry:     app.Config.SessionExpiry, | 
		
	
		
			
				|  |  |  |  | 		SecureCookie:      app.Config.SecureCookie, | 
		
	
		
			
				|  |  |  |  | 		OauthWhitelist:    app.config.OAuthWhitelist, | 
		
	
		
			
				|  |  |  |  | 		SessionExpiry:     app.config.SessionExpiry, | 
		
	
		
			
				|  |  |  |  | 		SecureCookie:      app.config.SecureCookie, | 
		
	
		
			
				|  |  |  |  | 		CookieDomain:      cookieDomain, | 
		
	
		
			
				|  |  |  |  | 		LoginTimeout:      app.Config.LoginTimeout, | 
		
	
		
			
				|  |  |  |  | 		LoginMaxRetries:   app.Config.LoginMaxRetries, | 
		
	
		
			
				|  |  |  |  | 		LoginTimeout:      app.config.LoginTimeout, | 
		
	
		
			
				|  |  |  |  | 		LoginMaxRetries:   app.config.LoginMaxRetries, | 
		
	
		
			
				|  |  |  |  | 		SessionCookieName: sessionCookieName, | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	// Setup services | 
		
	
		
			
				|  |  |  |  | 	var ldapService *service.LdapService | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	if app.Config.LdapAddress != "" { | 
		
	
		
			
				|  |  |  |  | 	if app.config.LdapAddress != "" { | 
		
	
		
			
				|  |  |  |  | 		ldapConfig := service.LdapServiceConfig{ | 
		
	
		
			
				|  |  |  |  | 			Address:      app.Config.LdapAddress, | 
		
	
		
			
				|  |  |  |  | 			BindDN:       app.Config.LdapBindDN, | 
		
	
		
			
				|  |  |  |  | 			BindPassword: app.Config.LdapBindPassword, | 
		
	
		
			
				|  |  |  |  | 			BaseDN:       app.Config.LdapBaseDN, | 
		
	
		
			
				|  |  |  |  | 			Insecure:     app.Config.LdapInsecure, | 
		
	
		
			
				|  |  |  |  | 			SearchFilter: app.Config.LdapSearchFilter, | 
		
	
		
			
				|  |  |  |  | 			Address:      app.config.LdapAddress, | 
		
	
		
			
				|  |  |  |  | 			BindDN:       app.config.LdapBindDN, | 
		
	
		
			
				|  |  |  |  | 			BindPassword: app.config.LdapBindPassword, | 
		
	
		
			
				|  |  |  |  | 			BaseDN:       app.config.LdapBaseDN, | 
		
	
		
			
				|  |  |  |  | 			Insecure:     app.config.LdapInsecure, | 
		
	
		
			
				|  |  |  |  | 			SearchFilter: app.config.LdapSearchFilter, | 
		
	
		
			
				|  |  |  |  | 		} | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 		ldapService = service.NewLdapService(ldapConfig) | 
		
	
	
		
			
				
					
					|  |  |  | @@ -104,7 +111,7 @@ func (app *BootstrapApp) Setup() error { | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	// Bootstrap database | 
		
	
		
			
				|  |  |  |  | 	databaseService := service.NewDatabaseService(service.DatabaseServiceConfig{ | 
		
	
		
			
				|  |  |  |  | 		DatabasePath: app.Config.DatabasePath, | 
		
	
		
			
				|  |  |  |  | 		DatabasePath: app.config.DatabasePath, | 
		
	
		
			
				|  |  |  |  | 	}) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	log.Debug().Str("service", fmt.Sprintf("%T", databaseService)).Msg("Initializing service") | 
		
	
	
		
			
				
					
					|  |  |  | @@ -183,8 +190,8 @@ func (app *BootstrapApp) Setup() error { | 
		
	
		
			
				|  |  |  |  | 	// Create engine | 
		
	
		
			
				|  |  |  |  | 	engine := gin.New() | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	if len(app.Config.TrustedProxies) > 0 { | 
		
	
		
			
				|  |  |  |  | 		engine.SetTrustedProxies(strings.Split(app.Config.TrustedProxies, ",")) | 
		
	
		
			
				|  |  |  |  | 	if len(app.config.TrustedProxies) > 0 { | 
		
	
		
			
				|  |  |  |  | 		engine.SetTrustedProxies(strings.Split(app.config.TrustedProxies, ",")) | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	if config.Version != "development" { | 
		
	
	
		
			
				
					
					|  |  |  | @@ -219,24 +226,24 @@ func (app *BootstrapApp) Setup() error { | 
		
	
		
			
				|  |  |  |  | 	// Create controllers | 
		
	
		
			
				|  |  |  |  | 	contextController := controller.NewContextController(controller.ContextControllerConfig{ | 
		
	
		
			
				|  |  |  |  | 		Providers:             configuredProviders, | 
		
	
		
			
				|  |  |  |  | 		Title:                 app.Config.Title, | 
		
	
		
			
				|  |  |  |  | 		AppURL:                app.Config.AppURL, | 
		
	
		
			
				|  |  |  |  | 		Title:                 app.config.Title, | 
		
	
		
			
				|  |  |  |  | 		AppURL:                app.config.AppURL, | 
		
	
		
			
				|  |  |  |  | 		CookieDomain:          cookieDomain, | 
		
	
		
			
				|  |  |  |  | 		ForgotPasswordMessage: app.Config.ForgotPasswordMessage, | 
		
	
		
			
				|  |  |  |  | 		BackgroundImage:       app.Config.BackgroundImage, | 
		
	
		
			
				|  |  |  |  | 		OAuthAutoRedirect:     app.Config.OAuthAutoRedirect, | 
		
	
		
			
				|  |  |  |  | 		ForgotPasswordMessage: app.config.ForgotPasswordMessage, | 
		
	
		
			
				|  |  |  |  | 		BackgroundImage:       app.config.BackgroundImage, | 
		
	
		
			
				|  |  |  |  | 		OAuthAutoRedirect:     app.config.OAuthAutoRedirect, | 
		
	
		
			
				|  |  |  |  | 	}, apiRouter) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	oauthController := controller.NewOAuthController(controller.OAuthControllerConfig{ | 
		
	
		
			
				|  |  |  |  | 		AppURL:             app.Config.AppURL, | 
		
	
		
			
				|  |  |  |  | 		SecureCookie:       app.Config.SecureCookie, | 
		
	
		
			
				|  |  |  |  | 		AppURL:             app.config.AppURL, | 
		
	
		
			
				|  |  |  |  | 		SecureCookie:       app.config.SecureCookie, | 
		
	
		
			
				|  |  |  |  | 		CSRFCookieName:     csrfCookieName, | 
		
	
		
			
				|  |  |  |  | 		RedirectCookieName: redirectCookieName, | 
		
	
		
			
				|  |  |  |  | 		CookieDomain:       cookieDomain, | 
		
	
		
			
				|  |  |  |  | 	}, apiRouter, authService, oauthBrokerService) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	proxyController := controller.NewProxyController(controller.ProxyControllerConfig{ | 
		
	
		
			
				|  |  |  |  | 		AppURL: app.Config.AppURL, | 
		
	
		
			
				|  |  |  |  | 		AppURL: app.config.AppURL, | 
		
	
		
			
				|  |  |  |  | 	}, apiRouter, dockerService, authService) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	userController := controller.NewUserController(controller.UserControllerConfig{ | 
		
	
	
		
			
				
					
					|  |  |  | @@ -244,7 +251,7 @@ func (app *BootstrapApp) Setup() error { | 
		
	
		
			
				|  |  |  |  | 	}, apiRouter, authService) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	resourcesController := controller.NewResourcesController(controller.ResourcesControllerConfig{ | 
		
	
		
			
				|  |  |  |  | 		ResourcesDir: app.Config.ResourcesDir, | 
		
	
		
			
				|  |  |  |  | 		ResourcesDir: app.config.ResourcesDir, | 
		
	
		
			
				|  |  |  |  | 	}, mainRouter) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	healthController := controller.NewHealthController(apiRouter) | 
		
	
	
		
			
				
					
					|  |  |  | @@ -264,8 +271,14 @@ func (app *BootstrapApp) Setup() error { | 
		
	
		
			
				|  |  |  |  | 		ctrl.SetupRoutes() | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	// If analytics are not disabled, start heartbeat | 
		
	
		
			
				|  |  |  |  | 	if !app.config.DisableAnalytics { | 
		
	
		
			
				|  |  |  |  | 		log.Debug().Msg("Starting heartbeat routine") | 
		
	
		
			
				|  |  |  |  | 		go app.heartbeat() | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	// 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) | 
		
	
		
			
				|  |  |  |  | 	if err := engine.Run(address); err != nil { | 
		
	
		
			
				|  |  |  |  | 		log.Fatal().Err(err).Msg("Failed to start server") | 
		
	
	
		
			
				
					
					|  |  |  | @@ -273,3 +286,55 @@ func (app *BootstrapApp) Setup() error { | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	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") | 
		
	
		
			
				|  |  |  |  | 		} | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  | } | 
		
	
	
		
			
				
					
					| 
							
							
							
						 |  |  |   |