mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-10-30 21:55:43 +00:00 
			
		
		
		
	Compare commits
	
		
			9 Commits
		
	
	
		
			v4.0.0
			...
			9b76a84ee2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 9b76a84ee2 | ||
|   | ed20d2cf51 | ||
|   | fc7e395e66 | ||
|   | b940d681c3 | ||
|   | a1ec4a69cf | ||
| ![github-actions[bot]](/assets/img/avatar_default.png)  | 4047cea451 | ||
|   | 5a4855c12c | ||
|   | 05d4dbd68e | ||
|   | ae8347fd28 | 
							
								
								
									
										4
									
								
								.github/workflows/nightly.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/nightly.yml
									
									
									
									
										vendored
									
									
								
							| @@ -396,6 +396,8 @@ jobs: | |||||||
|         uses: docker/metadata-action@v5 |         uses: docker/metadata-action@v5 | ||||||
|         with: |         with: | ||||||
|           images: ghcr.io/${{ github.repository_owner }}/tinyauth |           images: ghcr.io/${{ github.repository_owner }}/tinyauth | ||||||
|  |           flavor: | | ||||||
|  |             latest=false | ||||||
|           tags: | |           tags: | | ||||||
|             type=raw,nightly |             type=raw,nightly | ||||||
|  |  | ||||||
| @@ -433,6 +435,8 @@ jobs: | |||||||
|         uses: docker/metadata-action@v5 |         uses: docker/metadata-action@v5 | ||||||
|         with: |         with: | ||||||
|           images: ghcr.io/${{ github.repository_owner }}/tinyauth |           images: ghcr.io/${{ github.repository_owner }}/tinyauth | ||||||
|  |           flavor: | | ||||||
|  |             latest=false | ||||||
|           tags: | |           tags: | | ||||||
|             type=raw,nightly-distroless |             type=raw,nightly-distroless | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -359,10 +359,13 @@ jobs: | |||||||
|         uses: docker/metadata-action@v5 |         uses: docker/metadata-action@v5 | ||||||
|         with: |         with: | ||||||
|           images: ghcr.io/${{ github.repository_owner }}/tinyauth |           images: ghcr.io/${{ github.repository_owner }}/tinyauth | ||||||
|  |           flavor: | | ||||||
|  |             latest=true | ||||||
|  |             prefix=v,onlatest=false | ||||||
|           tags: | |           tags: | | ||||||
|             type=semver,pattern={{version}},prefix=v |             type=semver,pattern={{version}} | ||||||
|             type=semver,pattern={{major}},prefix=v |             type=semver,pattern={{major}} | ||||||
|             type=semver,pattern={{major}}.{{minor}},prefix=v |             type=semver,pattern={{major}}.{{minor}} | ||||||
|  |  | ||||||
|       - name: Create manifest list and push |       - name: Create manifest list and push | ||||||
|         working-directory: ${{ runner.temp }}/digests |         working-directory: ${{ runner.temp }}/digests | ||||||
| @@ -398,10 +401,14 @@ jobs: | |||||||
|         uses: docker/metadata-action@v5 |         uses: docker/metadata-action@v5 | ||||||
|         with: |         with: | ||||||
|           images: ghcr.io/${{ github.repository_owner }}/tinyauth |           images: ghcr.io/${{ github.repository_owner }}/tinyauth | ||||||
|  |           flavor: | | ||||||
|  |             latest=false | ||||||
|  |             prefix=v,onlatest=false | ||||||
|  |             suffix=-distroless,onlatest=false | ||||||
|           tags: | |           tags: | | ||||||
|             type=semver,pattern={{version}},prefix=v,suffix=-distroless |             type=semver,pattern={{version}} | ||||||
|             type=semver,pattern={{major}},prefix=v,suffix=-distroless |             type=semver,pattern={{major}} | ||||||
|             type=semver,pattern={{major}}.{{minor}},prefix=v,suffix=-distroless |             type=semver,pattern={{major}}.{{minor}} | ||||||
|  |  | ||||||
|       - name: Create manifest list and push |       - name: Create manifest list and push | ||||||
|         working-directory: ${{ runner.temp }}/digests |         working-directory: ${{ runner.temp }}/digests | ||||||
|   | |||||||
| @@ -47,6 +47,8 @@ WORKDIR /tinyauth | |||||||
|  |  | ||||||
| COPY --from=builder /tinyauth/tinyauth ./ | COPY --from=builder /tinyauth/tinyauth ./ | ||||||
|  |  | ||||||
|  | RUN mkdir -p /data | ||||||
|  |  | ||||||
| EXPOSE 3000 | EXPOSE 3000 | ||||||
|  |  | ||||||
| VOLUME ["/data"] | VOLUME ["/data"] | ||||||
|   | |||||||
| @@ -38,6 +38,8 @@ COPY ./cmd ./cmd | |||||||
| COPY ./internal ./internal | COPY ./internal ./internal | ||||||
| COPY --from=frontend-builder /frontend/dist ./internal/assets/dist | COPY --from=frontend-builder /frontend/dist ./internal/assets/dist | ||||||
|  |  | ||||||
|  | RUN mkdir -p data | ||||||
|  |  | ||||||
| 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}"  | 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 | # Runner | ||||||
| @@ -47,6 +49,9 @@ WORKDIR /tinyauth | |||||||
|  |  | ||||||
| COPY --from=builder /tinyauth/tinyauth ./ | COPY --from=builder /tinyauth/tinyauth ./ | ||||||
|  |  | ||||||
|  | # Since it's distroless, we need to copy the data directory from the builder stage | ||||||
|  | COPY --from=builder /tinyauth/data /data | ||||||
|  |  | ||||||
| EXPOSE 3000 | EXPOSE 3000 | ||||||
|  |  | ||||||
| VOLUME ["/data"] | VOLUME ["/data"] | ||||||
|   | |||||||
| @@ -53,7 +53,7 @@ Tinyauth is licensed under the GNU General Public License v3.0. TL;DR — You ma | |||||||
|  |  | ||||||
| A big thank you to the following people for providing me with more coffee: | A big thank you to the following people for providing me with more coffee: | ||||||
|  |  | ||||||
| <!-- sponsors --><a href="https://github.com/erwinkramer"><img src="https://github.com/erwinkramer.png" width="64px" alt="User avatar: erwinkramer" /></a>  <a href="https://github.com/nicotsx"><img src="https://github.com/nicotsx.png" width="64px" alt="User avatar: nicotsx" /></a>  <a href="https://github.com/SimpleHomelab"><img src="https://github.com/SimpleHomelab.png" width="64px" alt="User avatar: SimpleHomelab" /></a>  <a href="https://github.com/jmadden91"><img src="https://github.com/jmadden91.png" width="64px" alt="User avatar: jmadden91" /></a>  <a href="https://github.com/tribor"><img src="https://github.com/tribor.png" width="64px" alt="User avatar: tribor" /></a>  <a href="https://github.com/eliasbenb"><img src="https://github.com/eliasbenb.png" width="64px" alt="User avatar: eliasbenb" /></a>  <a href="https://github.com/afunworm"><img src="https://github.com/afunworm.png" width="64px" alt="User avatar: afunworm" /></a>  <!-- sponsors --> | <!-- sponsors --><a href="https://github.com/erwinkramer"><img src="https://github.com/erwinkramer.png" width="64px" alt="User avatar: erwinkramer" /></a>  <a href="https://github.com/nicotsx"><img src="https://github.com/nicotsx.png" width="64px" alt="User avatar: nicotsx" /></a>  <a href="https://github.com/SimpleHomelab"><img src="https://github.com/SimpleHomelab.png" width="64px" alt="User avatar: SimpleHomelab" /></a>  <a href="https://github.com/jmadden91"><img src="https://github.com/jmadden91.png" width="64px" alt="User avatar: jmadden91" /></a>  <a href="https://github.com/tribor"><img src="https://github.com/tribor.png" width="64px" alt="User avatar: tribor" /></a>  <a href="https://github.com/eliasbenb"><img src="https://github.com/eliasbenb.png" width="64px" alt="User avatar: eliasbenb" /></a>  <a href="https://github.com/afunworm"><img src="https://github.com/afunworm.png" width="64px" alt="User avatar: afunworm" /></a>  <a href="https://github.com/chip-well"><img src="https://github.com/chip-well.png" width="64px" alt="User avatar: chip-well" /></a>  <a href="https://github.com/Lancelot-Enguerrand"><img src="https://github.com/Lancelot-Enguerrand.png" width="64px" alt="User avatar: Lancelot-Enguerrand" /></a>  <!-- sponsors --> | ||||||
|  |  | ||||||
| ## Acknowledgements | ## Acknowledgements | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								air.toml
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								air.toml
									
									
									
									
									
								
							| @@ -2,7 +2,7 @@ root = "/tinyauth" | |||||||
| tmp_dir = "tmp" | tmp_dir = "tmp" | ||||||
|  |  | ||||||
| [build] | [build] | ||||||
| pre_cmd = ["mkdir -p internal/assets/dist", "echo 'backend running' > internal/assets/dist/index.html", "go install github.com/go-delve/delve/cmd/dlv@v1.25.0"] | pre_cmd = ["mkdir -p internal/assets/dist", "mkdir -p /data", "echo 'backend running' > internal/assets/dist/index.html", "go install github.com/go-delve/delve/cmd/dlv@v1.25.0"] | ||||||
| cmd = "CGO_ENABLED=0 go build -gcflags=\"all=-N -l\" -o tmp/tinyauth ." | cmd = "CGO_ENABLED=0 go build -gcflags=\"all=-N -l\" -o tmp/tinyauth ." | ||||||
| bin = "/go/bin/dlv --listen :4000 --headless=true --api-version=2 --accept-multiclient --log=true exec tmp/tinyauth --continue --check-go-version=false" | bin = "/go/bin/dlv --listen :4000 --headless=true --api-version=2 --accept-multiclient --log=true exec tmp/tinyauth --continue --check-go-version=false" | ||||||
| include_ext = ["go"] | include_ext = ["go"] | ||||||
|   | |||||||
| @@ -112,6 +112,10 @@ func (c *rootCmd) run(cmd *cobra.Command, args []string) { | |||||||
| 	log.Logger = log.Level(zerolog.Level(utils.GetLogLevel(conf.LogLevel))) | 	log.Logger = log.Level(zerolog.Level(utils.GetLogLevel(conf.LogLevel))) | ||||||
| 	log.Info().Str("version", strings.TrimSpace(config.Version)).Msg("Starting Tinyauth") | 	log.Info().Str("version", strings.TrimSpace(config.Version)).Msg("Starting Tinyauth") | ||||||
|  |  | ||||||
|  | 	if log.Logger.GetLevel() == zerolog.TraceLevel { | ||||||
|  | 		log.Warn().Msg("Log level set to trace, this will log sensitive information!") | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	app := bootstrap.NewBootstrapApp(conf) | 	app := bootstrap.NewBootstrapApp(conf) | ||||||
|  |  | ||||||
| 	err = app.Setup() | 	err = app.Setup() | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ import ( | |||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"sort" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 	"tinyauth/internal/config" | 	"tinyauth/internal/config" | ||||||
| @@ -157,6 +158,10 @@ func (app *BootstrapApp) Setup() error { | |||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	sort.Slice(configuredProviders, func(i, j int) bool { | ||||||
|  | 		return configuredProviders[i].Name < configuredProviders[j].Name | ||||||
|  | 	}) | ||||||
|  |  | ||||||
| 	if authService.UserAuthConfigured() || ldapService != nil { | 	if authService.UserAuthConfigured() || ldapService != nil { | ||||||
| 		configuredProviders = append(configuredProviders, controller.Provider{ | 		configuredProviders = append(configuredProviders, controller.Provider{ | ||||||
| 			Name:  "Username", | 			Name:  "Username", | ||||||
| @@ -173,6 +178,7 @@ func (app *BootstrapApp) Setup() error { | |||||||
|  |  | ||||||
| 	// Create engine | 	// Create engine | ||||||
| 	engine := gin.New() | 	engine := gin.New() | ||||||
|  | 	engine.Use(gin.Recovery()) | ||||||
|  |  | ||||||
| 	if len(app.config.TrustedProxies) > 0 { | 	if len(app.config.TrustedProxies) > 0 { | ||||||
| 		err := engine.SetTrustedProxies(strings.Split(app.config.TrustedProxies, ",")) | 		err := engine.SetTrustedProxies(strings.Split(app.config.TrustedProxies, ",")) | ||||||
|   | |||||||
| @@ -162,7 +162,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) { | |||||||
|  |  | ||||||
| 	var name string | 	var name string | ||||||
|  |  | ||||||
| 	if user.Name != "" { | 	if strings.TrimSpace(user.Name) != "" { | ||||||
| 		log.Debug().Msg("Using name from OAuth provider") | 		log.Debug().Msg("Using name from OAuth provider") | ||||||
| 		name = user.Name | 		name = user.Name | ||||||
| 	} else { | 	} else { | ||||||
| @@ -172,7 +172,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) { | |||||||
|  |  | ||||||
| 	var username string | 	var username string | ||||||
|  |  | ||||||
| 	if user.PreferredUsername != "" { | 	if strings.TrimSpace(user.PreferredUsername) != "" { | ||||||
| 		log.Debug().Msg("Using preferred username from OAuth provider") | 		log.Debug().Msg("Using preferred username from OAuth provider") | ||||||
| 		username = user.PreferredUsername | 		username = user.PreferredUsername | ||||||
| 	} else { | 	} else { | ||||||
|   | |||||||
| @@ -84,6 +84,8 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	log.Trace().Interface("labels", labels).Msg("Labels for resource") | ||||||
|  |  | ||||||
| 	clientIP := c.ClientIP() | 	clientIP := c.ClientIP() | ||||||
|  |  | ||||||
| 	if controller.auth.IsBypassedIP(labels.IP, clientIP) { | 	if controller.auth.IsBypassedIP(labels.IP, clientIP) { | ||||||
| @@ -150,6 +152,8 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) { | |||||||
| 		userContext = context | 		userContext = context | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	log.Trace().Interface("context", userContext).Msg("User context from request") | ||||||
|  |  | ||||||
| 	if userContext.Provider == "basic" && userContext.TotpEnabled { | 	if userContext.Provider == "basic" && userContext.TotpEnabled { | ||||||
| 		log.Debug().Msg("User has TOTP enabled, denying basic auth access") | 		log.Debug().Msg("User has TOTP enabled, denying basic auth access") | ||||||
| 		userContext.IsLoggedIn = false | 		userContext.IsLoggedIn = false | ||||||
|   | |||||||
| @@ -318,6 +318,7 @@ func (auth *AuthService) IsInOAuthGroup(c *gin.Context, context config.UserConte | |||||||
|  |  | ||||||
| 	for userGroup := range strings.SplitSeq(context.OAuthGroups, ",") { | 	for userGroup := range strings.SplitSeq(context.OAuthGroups, ",") { | ||||||
| 		if utils.CheckFilter(requiredGroups, strings.TrimSpace(userGroup)) { | 		if utils.CheckFilter(requiredGroups, strings.TrimSpace(userGroup)) { | ||||||
|  | 			log.Trace().Str("group", userGroup).Str("required", requiredGroups).Msg("User group matched") | ||||||
| 			return true | 			return true | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ import ( | |||||||
| type DockerService struct { | type DockerService struct { | ||||||
| 	client      *client.Client | 	client      *client.Client | ||||||
| 	context     context.Context | 	context     context.Context | ||||||
|  | 	isConnected bool | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewDockerService() *DockerService { | func NewDockerService() *DockerService { | ||||||
| @@ -31,10 +32,24 @@ func (docker *DockerService) Init() error { | |||||||
|  |  | ||||||
| 	docker.client = client | 	docker.client = client | ||||||
| 	docker.context = ctx | 	docker.context = ctx | ||||||
|  |  | ||||||
|  | 	_, err = docker.client.Ping(docker.context) | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Debug().Err(err).Msg("Docker not connected") | ||||||
|  | 		docker.isConnected = false | ||||||
|  | 		docker.client = nil | ||||||
|  | 		docker.context = nil | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	docker.isConnected = true | ||||||
|  | 	log.Debug().Msg("Docker connected") | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (docker *DockerService) GetContainers() ([]container.Summary, error) { | func (docker *DockerService) getContainers() ([]container.Summary, error) { | ||||||
| 	containers, err := docker.client.ContainerList(docker.context, container.ListOptions{}) | 	containers, err := docker.client.ContainerList(docker.context, container.ListOptions{}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -42,7 +57,7 @@ func (docker *DockerService) GetContainers() ([]container.Summary, error) { | |||||||
| 	return containers, nil | 	return containers, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (docker *DockerService) InspectContainer(containerId string) (container.InspectResponse, error) { | func (docker *DockerService) inspectContainer(containerId string) (container.InspectResponse, error) { | ||||||
| 	inspect, err := docker.client.ContainerInspect(docker.context, containerId) | 	inspect, err := docker.client.ContainerInspect(docker.context, containerId) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return container.InspectResponse{}, err | 		return container.InspectResponse{}, err | ||||||
| @@ -50,26 +65,19 @@ func (docker *DockerService) InspectContainer(containerId string) (container.Ins | |||||||
| 	return inspect, nil | 	return inspect, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (docker *DockerService) DockerConnected() bool { |  | ||||||
| 	_, err := docker.client.Ping(docker.context) |  | ||||||
| 	return err == nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (docker *DockerService) GetLabels(appDomain string) (config.App, error) { | func (docker *DockerService) GetLabels(appDomain string) (config.App, error) { | ||||||
| 	isConnected := docker.DockerConnected() | 	if !docker.isConnected { | ||||||
|  |  | ||||||
| 	if !isConnected { |  | ||||||
| 		log.Debug().Msg("Docker not connected, returning empty labels") | 		log.Debug().Msg("Docker not connected, returning empty labels") | ||||||
| 		return config.App{}, nil | 		return config.App{}, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	containers, err := docker.GetContainers() | 	containers, err := docker.getContainers() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return config.App{}, err | 		return config.App{}, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, ctr := range containers { | 	for _, ctr := range containers { | ||||||
| 		inspect, err := docker.InspectContainer(ctr.ID) | 		inspect, err := docker.inspectContainer(ctr.ID) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return config.App{}, err | 			return config.App{}, err | ||||||
| 		} | 		} | ||||||
| @@ -81,12 +89,12 @@ func (docker *DockerService) GetLabels(appDomain string) (config.App, error) { | |||||||
|  |  | ||||||
| 		for appName, appLabels := range labels.Apps { | 		for appName, appLabels := range labels.Apps { | ||||||
| 			if appLabels.Config.Domain == appDomain { | 			if appLabels.Config.Domain == appDomain { | ||||||
| 				log.Debug().Str("id", inspect.ID).Msg("Found matching container by domain") | 				log.Debug().Str("id", inspect.ID).Str("name", inspect.Name).Msg("Found matching container by domain") | ||||||
| 				return appLabels, nil | 				return appLabels, nil | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if strings.TrimPrefix(inspect.Name, "/") == appName { | 			if strings.TrimPrefix(inspect.Name, "/") == appName { | ||||||
| 				log.Debug().Str("id", inspect.ID).Msg("Found matching container by app name") | 				log.Debug().Str("id", inspect.ID).Str("name", inspect.Name).Msg("Found matching container by app name") | ||||||
| 				return appLabels, nil | 				return appLabels, nil | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
| 	"tinyauth/internal/config" | 	"tinyauth/internal/config" | ||||||
|  |  | ||||||
|  | 	"github.com/rs/zerolog/log" | ||||||
| 	"golang.org/x/oauth2" | 	"golang.org/x/oauth2" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -110,6 +111,8 @@ func (generic *GenericOAuthService) Userinfo() (config.Claims, error) { | |||||||
| 		return user, err | 		return user, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	log.Trace().Str("body", string(body)).Msg("Userinfo response body") | ||||||
|  |  | ||||||
| 	err = json.Unmarshal(body, &user) | 	err = json.Unmarshal(body, &user) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return user, err | 		return user, err | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user