mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-11-04 08:05:42 +00:00 
			
		
		
		
	Compare commits
	
		
			9 Commits
		
	
	
		
			51937906ad
			...
			v4.0.0-alp
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					c307f7eb2e | ||
| 
						 | 
					5dd8526833 | ||
| 
						 | 
					e8558b89b4 | ||
| 
						 | 
					f8047a6c2e | ||
| 
						 | 
					e114bf0943 | ||
| 
						 | 
					c9867ccb76 | ||
| 
						 | 
					866933b3d6 | ||
| 
						 | 
					d70cbea546 | ||
| 
						 | 
					50105e4e9d | 
							
								
								
									
										17
									
								
								.env.example
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								.env.example
									
									
									
									
									
								
							@@ -4,20 +4,6 @@ APP_URL=http://localhost:3000
 | 
			
		||||
USERS=your_user_password_hash
 | 
			
		||||
USERS_FILE=users_file
 | 
			
		||||
SECURE_COOKIE=false
 | 
			
		||||
GITHUB_CLIENT_ID=github_client_id
 | 
			
		||||
GITHUB_CLIENT_SECRET=github_client_secret
 | 
			
		||||
GITHUB_CLIENT_SECRET_FILE=github_client_secret_file
 | 
			
		||||
GOOGLE_CLIENT_ID=google_client_id
 | 
			
		||||
GOOGLE_CLIENT_SECRET=google_client_secret
 | 
			
		||||
GOOGLE_CLIENT_SECRET_FILE=google_client_secret_file
 | 
			
		||||
GENERIC_CLIENT_ID=generic_client_id
 | 
			
		||||
GENERIC_CLIENT_SECRET=generic_client_secret
 | 
			
		||||
GENERIC_CLIENT_SECRET_FILE=generic_client_secret_file
 | 
			
		||||
GENERIC_SCOPES=generic_scopes
 | 
			
		||||
GENERIC_AUTH_URL=generic_auth_url
 | 
			
		||||
GENERIC_TOKEN_URL=generic_token_url
 | 
			
		||||
GENERIC_USER_URL=generic_user_url
 | 
			
		||||
DISABLE_CONTINUE=false
 | 
			
		||||
OAUTH_WHITELIST=
 | 
			
		||||
GENERIC_NAME=My OAuth
 | 
			
		||||
SESSION_EXPIRY=7200
 | 
			
		||||
@@ -31,3 +17,6 @@ BACKGROUND_IMAGE=some_image_url
 | 
			
		||||
GENERIC_SKIP_SSL=false
 | 
			
		||||
RESOURCES_DIR=/data/resources
 | 
			
		||||
DATABASE_PATH=/data/tinyauth.db
 | 
			
		||||
DISABLE_ANALYTICS=false
 | 
			
		||||
DISABLE_RESOURCES=false
 | 
			
		||||
TRUSTED_PROXIES=
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/nightly.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/nightly.yml
									
									
									
									
										vendored
									
									
								
							@@ -80,7 +80,7 @@ jobs:
 | 
			
		||||
      - name: Build
 | 
			
		||||
        run: |
 | 
			
		||||
          cp -r frontend/dist internal/assets/dist
 | 
			
		||||
          go build -ldflags "-s -w -X tinyauth/internal/constants.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/constants.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/constants.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64
 | 
			
		||||
          go build -ldflags "-s -w -X tinyauth/internal/config.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/config.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/config.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64
 | 
			
		||||
        env:
 | 
			
		||||
          CGO_ENABLED: 0
 | 
			
		||||
 | 
			
		||||
@@ -126,7 +126,7 @@ jobs:
 | 
			
		||||
      - name: Build
 | 
			
		||||
        run: |
 | 
			
		||||
          cp -r frontend/dist internal/assets/dist
 | 
			
		||||
          go build -ldflags "-s -w -X tinyauth/internal/constants.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/constants.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/constants.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64
 | 
			
		||||
          go build -ldflags "-s -w -X tinyauth/internal/config.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/config.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/config.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64
 | 
			
		||||
        env:
 | 
			
		||||
          CGO_ENABLED: 0
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -58,7 +58,7 @@ jobs:
 | 
			
		||||
      - name: Build
 | 
			
		||||
        run: |
 | 
			
		||||
          cp -r frontend/dist internal/assets/dist
 | 
			
		||||
          go build -ldflags "-s -w -X tinyauth/internal/constants.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/constants.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/constants.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64
 | 
			
		||||
          go build -ldflags "-s -w -X tinyauth/internal/config.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/config.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/config.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64
 | 
			
		||||
        env:
 | 
			
		||||
          CGO_ENABLED: 0
 | 
			
		||||
 | 
			
		||||
@@ -101,7 +101,7 @@ jobs:
 | 
			
		||||
      - name: Build
 | 
			
		||||
        run: |
 | 
			
		||||
          cp -r frontend/dist internal/assets/dist
 | 
			
		||||
          go build -ldflags "-s -w -X tinyauth/internal/constants.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/constants.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/constants.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64
 | 
			
		||||
          go build -ldflags "-s -w -X tinyauth/internal/config.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/config.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/config.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64
 | 
			
		||||
        env:
 | 
			
		||||
          CGO_ENABLED: 0
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@ COPY ./cmd ./cmd
 | 
			
		||||
COPY ./internal ./internal
 | 
			
		||||
COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
 | 
			
		||||
 | 
			
		||||
RUN CGO_ENABLED=0 go build -ldflags "-s -w -X tinyauth/internal/constants.Version=${VERSION} -X tinyauth/internal/constants.CommitHash=${COMMIT_HASH} -X tinyauth/internal/constants.BuildTimestamp=${BUILD_TIMESTAMP}" 
 | 
			
		||||
RUN CGO_ENABLED=0 go build -ldflags "-s -w -X tinyauth/internal/config.Version=${VERSION} -X tinyauth/internal/config.CommitHash=${COMMIT_HASH} -X tinyauth/internal/config.BuildTimestamp=${BUILD_TIMESTAMP}" 
 | 
			
		||||
 
 | 
			
		||||
# Runner
 | 
			
		||||
FROM alpine:3.22 AS runner
 | 
			
		||||
 
 | 
			
		||||
@@ -94,6 +94,8 @@ func init() {
 | 
			
		||||
		{"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."},
 | 
			
		||||
		{"trusted-proxies", "", "Comma separated list of trusted proxies (IP addresses or CIDRs) for correct client IP detection."},
 | 
			
		||||
		{"disable-analytics", false, "Disable anonymous version collection."},
 | 
			
		||||
		{"disable-resources", false, "Disable the resources server."},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, opt := range configOptions {
 | 
			
		||||
 
 | 
			
		||||
@@ -70,7 +70,7 @@ var VerifyCmd = &cobra.Command{
 | 
			
		||||
 | 
			
		||||
		err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(iPassword))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatal().Msg("Ppassword is incorrect")
 | 
			
		||||
			log.Fatal().Msg("Password is incorrect")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if user.TotpSecret == "" {
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,10 @@ services:
 | 
			
		||||
    build:
 | 
			
		||||
      context: .
 | 
			
		||||
      dockerfile: Dockerfile.dev
 | 
			
		||||
      args:
 | 
			
		||||
        - VERSION=development
 | 
			
		||||
        - COMMIT_HASH=development
 | 
			
		||||
        - BUILD_TIMESTAMP=000-00-00T00:00:00Z
 | 
			
		||||
    env_file: .env
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./internal:/tinyauth/internal
 | 
			
		||||
 
 | 
			
		||||
@@ -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")
 | 
			
		||||
@@ -181,16 +188,20 @@ func (app *BootstrapApp) Setup() error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Create engine
 | 
			
		||||
	engine := gin.New()
 | 
			
		||||
 | 
			
		||||
	if len(app.Config.TrustedProxies) > 0 {
 | 
			
		||||
		engine.SetTrustedProxies(strings.Split(app.Config.TrustedProxies, ","))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if config.Version != "development" {
 | 
			
		||||
		gin.SetMode(gin.ReleaseMode)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	engine := gin.New()
 | 
			
		||||
 | 
			
		||||
	if len(app.config.TrustedProxies) > 0 {
 | 
			
		||||
		err := engine.SetTrustedProxies(strings.Split(app.config.TrustedProxies, ","))
 | 
			
		||||
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("failed to set trusted proxies: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Create middlewares
 | 
			
		||||
	var middlewares []Middleware
 | 
			
		||||
 | 
			
		||||
@@ -219,24 +230,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 +255,8 @@ func (app *BootstrapApp) Setup() error {
 | 
			
		||||
	}, apiRouter, authService)
 | 
			
		||||
 | 
			
		||||
	resourcesController := controller.NewResourcesController(controller.ResourcesControllerConfig{
 | 
			
		||||
		ResourcesDir: app.Config.ResourcesDir,
 | 
			
		||||
		ResourcesDir:      app.config.ResourcesDir,
 | 
			
		||||
		ResourcesDisabled: app.config.DisableResources,
 | 
			
		||||
	}, mainRouter)
 | 
			
		||||
 | 
			
		||||
	healthController := controller.NewHealthController(apiRouter)
 | 
			
		||||
@@ -264,8 +276,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 +291,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 && res.StatusCode != 201 {
 | 
			
		||||
			log.Debug().Str("status", res.Status).Msg("Heartbeat returned non-200/201 status")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,8 +3,8 @@ package config
 | 
			
		||||
// Version information, set at build time
 | 
			
		||||
 | 
			
		||||
var Version = "development"
 | 
			
		||||
var CommitHash = "n/a"
 | 
			
		||||
var BuildTimestamp = "n/a"
 | 
			
		||||
var CommitHash = "development"
 | 
			
		||||
var BuildTimestamp = "0000-00-00T00:00:00Z"
 | 
			
		||||
 | 
			
		||||
// Cookie name templates
 | 
			
		||||
 | 
			
		||||
@@ -39,6 +39,8 @@ type Config struct {
 | 
			
		||||
	ResourcesDir          string `mapstructure:"resources-dir"`
 | 
			
		||||
	DatabasePath          string `mapstructure:"database-path" validate:"required"`
 | 
			
		||||
	TrustedProxies        string `mapstructure:"trusted-proxies"`
 | 
			
		||||
	DisableAnalytics      bool   `mapstructure:"disable-analytics"`
 | 
			
		||||
	DisableResources      bool   `mapstructure:"disable-resources"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OAuth/OIDC config
 | 
			
		||||
@@ -169,3 +171,7 @@ type AppPath struct {
 | 
			
		||||
type Providers struct {
 | 
			
		||||
	Providers map[string]OAuthServiceConfig
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// API server
 | 
			
		||||
 | 
			
		||||
var ApiServer = "https://api.tinyauth.app"
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ import (
 | 
			
		||||
 | 
			
		||||
type ResourcesControllerConfig struct {
 | 
			
		||||
	ResourcesDir      string
 | 
			
		||||
	ResourcesDisabled bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ResourcesController struct {
 | 
			
		||||
@@ -38,5 +39,12 @@ func (controller *ResourcesController) resourcesHandler(c *gin.Context) {
 | 
			
		||||
		})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if controller.config.ResourcesDisabled {
 | 
			
		||||
		c.JSON(403, gin.H{
 | 
			
		||||
			"status":  403,
 | 
			
		||||
			"message": "Resources are disabled",
 | 
			
		||||
		})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	controller.fileServer.ServeHTTP(c.Writer, c.Request)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,7 @@ func (broker *OAuthBrokerService) Init() error {
 | 
			
		||||
			log.Error().Err(err).Msgf("Failed to initialize OAuth service: %T", name)
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		log.Info().Msgf("Initialized OAuth service: %T", name)
 | 
			
		||||
		log.Info().Str("service", service.GetName()).Msg("Initialized OAuth service")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
 
 | 
			
		||||
@@ -18,10 +18,14 @@ func NormalizeKeys(keys map[string]string, rootName string, sep string) map[stri
 | 
			
		||||
 | 
			
		||||
		finalKey = append(finalKey, rootName)
 | 
			
		||||
		finalKey = append(finalKey, "providers")
 | 
			
		||||
		cebabKey := strings.ToLower(k)
 | 
			
		||||
		lowerKey := strings.ToLower(k)
 | 
			
		||||
 | 
			
		||||
		if !strings.HasPrefix(lowerKey, "providers"+sep) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, known := range knownKeys {
 | 
			
		||||
			if strings.HasSuffix(cebabKey, strings.ReplaceAll(known, "-", sep)) {
 | 
			
		||||
			if strings.HasSuffix(lowerKey, strings.ReplaceAll(known, "-", sep)) {
 | 
			
		||||
				suffix = known
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
@@ -31,7 +35,11 @@ func NormalizeKeys(keys map[string]string, rootName string, sep string) map[stri
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		clientNameParts := strings.Split(strings.TrimPrefix(strings.TrimSuffix(cebabKey, sep+strings.ReplaceAll(suffix, "-", sep)), "providers"+sep), sep)
 | 
			
		||||
		if strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(lowerKey, "providers"+sep), strings.ReplaceAll(suffix, "-", sep))) == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		clientNameParts := strings.Split(strings.TrimPrefix(strings.TrimSuffix(lowerKey, sep+strings.ReplaceAll(suffix, "-", sep)), "providers"+sep), sep)
 | 
			
		||||
 | 
			
		||||
		for i, p := range clientNameParts {
 | 
			
		||||
			if i == 0 {
 | 
			
		||||
@@ -46,9 +54,9 @@ func NormalizeKeys(keys map[string]string, rootName string, sep string) map[stri
 | 
			
		||||
 | 
			
		||||
		finalKey = append(finalKey, camelClientName)
 | 
			
		||||
 | 
			
		||||
		filedParts := strings.Split(suffix, "-")
 | 
			
		||||
		fieldParts := strings.Split(suffix, "-")
 | 
			
		||||
 | 
			
		||||
		for i, p := range filedParts {
 | 
			
		||||
		for i, p := range fieldParts {
 | 
			
		||||
			if i == 0 {
 | 
			
		||||
				camelField += p
 | 
			
		||||
				continue
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,8 @@ func TestNormalizeKeys(t *testing.T) {
 | 
			
		||||
		"PROVIDERS_CLIENT1_CLIENT_SECRET":                "my-client-secret",
 | 
			
		||||
		"PROVIDERS_MY_AWESOME_CLIENT_CLIENT_ID":          "my-awesome-client-id",
 | 
			
		||||
		"PROVIDERS_MY_AWESOME_CLIENT_CLIENT_SECRET_FILE": "/path/to/secret",
 | 
			
		||||
		"I_LOOK_LIKE_A_KEY_CLIENT_ID":                    "should-not-appear",
 | 
			
		||||
		"PROVIDERS_CLIENT_ID":                            "should-not-appear",
 | 
			
		||||
	}
 | 
			
		||||
	expected := map[string]string{
 | 
			
		||||
		"tinyauth.providers.client1.clientId":                 "my-client-id",
 | 
			
		||||
@@ -31,6 +33,9 @@ func TestNormalizeKeys(t *testing.T) {
 | 
			
		||||
		"providers-client1-client-secret":                "my-client-secret",
 | 
			
		||||
		"providers-my-awesome-client-client-id":          "my-awesome-client-id",
 | 
			
		||||
		"providers-my-awesome-client-client-secret-file": "/path/to/secret",
 | 
			
		||||
		"providers-should-not-appear-client":             "should-not-appear",
 | 
			
		||||
		"i-look-like-a-key-client-id":                    "should-not-appear",
 | 
			
		||||
		"providers-client-id":                            "should-not-appear",
 | 
			
		||||
	}
 | 
			
		||||
	expected = map[string]string{
 | 
			
		||||
		"tinyauth.providers.client1.clientId":                 "my-client-id",
 | 
			
		||||
 
 | 
			
		||||
@@ -101,8 +101,7 @@ func CheckFilter(filter string, str string) bool {
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GenerateIdentifier(str string) string {
 | 
			
		||||
func GenerateUUID(str string) string {
 | 
			
		||||
	uuid := uuid.NewSHA1(uuid.NameSpaceURL, []byte(str))
 | 
			
		||||
	uuidString := uuid.String()
 | 
			
		||||
	return strings.Split(uuidString, "-")[0]
 | 
			
		||||
	return uuid.String()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -136,16 +136,13 @@ func TestCheckFilter(t *testing.T) {
 | 
			
		||||
	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
 | 
			
		||||
	id1 := utils.GenerateIdentifier("teststring")
 | 
			
		||||
	id2 := utils.GenerateIdentifier("teststring")
 | 
			
		||||
	id1 := utils.GenerateUUID("teststring")
 | 
			
		||||
	id2 := utils.GenerateUUID("teststring")
 | 
			
		||||
	assert.Equal(t, id1, id2)
 | 
			
		||||
 | 
			
		||||
	// Different output for different input
 | 
			
		||||
	id3 := utils.GenerateIdentifier("differentstring")
 | 
			
		||||
	id3 := utils.GenerateUUID("differentstring")
 | 
			
		||||
	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