mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-10-31 06:05:43 +00:00 
			
		
		
		
	Compare commits
	
		
			13 Commits
		
	
	
		
			feat/analy
			...
			dad0718091
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | dad0718091 | ||
|   | d4069900bc | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | a54996d72d | ||
|   | 085f6257c5 | ||
|   | c307f7eb2e | ||
|   | 5dd8526833 | ||
|   | e8558b89b4 | ||
|   | f8047a6c2e | ||
|   | e114bf0943 | ||
|   | c9867ccb76 | ||
|   | 866933b3d6 | ||
|   | d70cbea546 | ||
|   | 50105e4e9d | 
							
								
								
									
										19
									
								
								.env.example
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								.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 | ||||
| @@ -30,4 +16,7 @@ OAUTH_AUTO_REDIRECT=none | ||||
| BACKGROUND_IMAGE=some_image_url | ||||
| GENERIC_SKIP_SSL=false | ||||
| RESOURCES_DIR=/data/resources | ||||
| DATABASE_PATH=/data/tinyauth.db | ||||
| 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 | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| # Site builder | ||||
| FROM oven/bun:1.2.22-alpine AS frontend-builder | ||||
| FROM oven/bun:1.2.23-alpine AS frontend-builder | ||||
|  | ||||
| WORKDIR /frontend | ||||
|  | ||||
| @@ -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 | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
|     <link rel="shortcut icon" href="/favicon.ico" /> | ||||
|     <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> | ||||
|     <meta name="apple-mobile-web-app-title" content="Tinyauth" /> | ||||
|     <meta name="robots" content="noindex" /> | ||||
|     <link rel="manifest" href="/site.webmanifest" /> | ||||
|     <title>Tinyauth</title> | ||||
|   </head> | ||||
|   | ||||
| @@ -44,6 +44,7 @@ export const TotpForm = (props: Props) => { | ||||
|                   disabled={loading} | ||||
|                   {...field} | ||||
|                   autoComplete="one-time-code" | ||||
|                   autoFocus | ||||
|                 > | ||||
|                   <InputOTPGroup> | ||||
|                     <InputOTPSlot index={0} /> | ||||
|   | ||||
| @@ -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") | ||||
| @@ -140,10 +147,6 @@ func (app *BootstrapApp) Setup() error { | ||||
| 	} | ||||
|  | ||||
| 	// Configured providers | ||||
| 	babysit := map[string]string{ | ||||
| 		"google": "Google", | ||||
| 		"github": "GitHub", | ||||
| 	} | ||||
| 	configuredProviders := make([]controller.Provider, 0) | ||||
|  | ||||
| 	for id, provider := range oauthProviders { | ||||
| @@ -152,7 +155,7 @@ func (app *BootstrapApp) Setup() error { | ||||
| 		} | ||||
|  | ||||
| 		if provider.Name == "" { | ||||
| 			if name, ok := babysit[id]; ok { | ||||
| 			if name, ok := config.OverrideProviders[id]; ok { | ||||
| 				provider.Name = name | ||||
| 			} else { | ||||
| 				provider.Name = utils.Capitalize(id) | ||||
| @@ -181,16 +184,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 +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,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 +272,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 +287,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 | ||||
| @@ -63,6 +65,11 @@ type OAuthServiceConfig struct { | ||||
| 	Name               string   `key:"name"` | ||||
| } | ||||
|  | ||||
| var OverrideProviders = map[string]string{ | ||||
| 	"google": "Google", | ||||
| 	"github": "GitHub", | ||||
| } | ||||
|  | ||||
| // User/session related stuff | ||||
|  | ||||
| type User struct { | ||||
| @@ -169,3 +176,7 @@ type AppPath struct { | ||||
| type Providers struct { | ||||
| 	Providers map[string]OAuthServiceConfig | ||||
| } | ||||
|  | ||||
| // API server | ||||
|  | ||||
| var ApiServer = "https://api.tinyauth.app" | ||||
|   | ||||
| @@ -7,7 +7,8 @@ import ( | ||||
| ) | ||||
|  | ||||
| type ResourcesControllerConfig struct { | ||||
| 	ResourcesDir string | ||||
| 	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) | ||||
| } | ||||
|   | ||||
| @@ -309,12 +309,14 @@ func (auth *AuthService) IsInOAuthGroup(c *gin.Context, context config.UserConte | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	if context.Provider != "generic" { | ||||
| 		log.Debug().Msg("Not using generic provider, skipping group check") | ||||
| 		return true | ||||
| 	for id := range config.OverrideProviders { | ||||
| 		if context.Provider == id { | ||||
| 			log.Info().Str("provider", id).Msg("OAuth groups not supported for this provider") | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for _, userGroup := range strings.Split(context.OAuthGroups, ",") { | ||||
| 	for userGroup := range strings.SplitSeq(context.OAuthGroups, ",") { | ||||
| 		if utils.CheckFilter(requiredGroups, strings.TrimSpace(userGroup)) { | ||||
| 			return true | ||||
| 		} | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -183,14 +183,13 @@ func GetOAuthProvidersConfig(env []string, args []string, appUrl string) (map[st | ||||
| 		providers[name] = provider | ||||
| 	} | ||||
|  | ||||
| 	// If we have google/github providers and no redirect URL babysit them | ||||
| 	babysitProviders := []string{"google", "github"} | ||||
| 	// If we have google/github providers and no redirect URL then set a default | ||||
|  | ||||
| 	for _, name := range babysitProviders { | ||||
| 		if provider, exists := providers[name]; exists { | ||||
| 	for id := range config.OverrideProviders { | ||||
| 		if provider, exists := providers[id]; exists { | ||||
| 			if provider.RedirectURL == "" { | ||||
| 				provider.RedirectURL = appUrl + "/api/oauth/callback/" + name | ||||
| 				providers[name] = provider | ||||
| 				provider.RedirectURL = appUrl + "/api/oauth/callback/" + id | ||||
| 				providers[id] = provider | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -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