mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-10-30 21:55:43 +00:00 
			
		
		
		
	Compare commits
	
		
			4 Commits
		
	
	
		
			v3.6.2-bet
			...
			refactor/p
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 6157f75659 | ||
|   | a621135ac0 | ||
|   | aeb93da378 | ||
|   | 282cabf4dd | 
| @@ -1,5 +1,5 @@ | |||||||
| # Site builder | # Site builder | ||||||
| FROM oven/bun:1.2.15-alpine AS frontend-builder | FROM oven/bun:1.2.16-alpine AS frontend-builder | ||||||
|  |  | ||||||
| WORKDIR /frontend | WORKDIR /frontend | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| FROM oven/bun:1.1.45-alpine | FROM oven/bun:1.2.16-alpine | ||||||
|  |  | ||||||
| WORKDIR /frontend | WORKDIR /frontend | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							| @@ -24,6 +24,7 @@ require ( | |||||||
| 	github.com/moby/sys/atomicwriter v0.1.0 // indirect | 	github.com/moby/sys/atomicwriter v0.1.0 // indirect | ||||||
| 	github.com/moby/term v0.5.2 // indirect | 	github.com/moby/term v0.5.2 // indirect | ||||||
| 	github.com/morikuni/aec v1.0.0 // indirect | 	github.com/morikuni/aec v1.0.0 // indirect | ||||||
|  | 	github.com/traefik/paerser v0.2.2 // indirect | ||||||
| 	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect | 	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect | ||||||
| 	go.opentelemetry.io/auto/sdk v1.1.0 // indirect | 	go.opentelemetry.io/auto/sdk v1.1.0 // indirect | ||||||
| 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect | 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							| @@ -238,6 +238,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf | |||||||
| github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||||
| github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= | ||||||
| github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= | ||||||
|  | github.com/traefik/paerser v0.2.2 h1:cpzW/ZrQrBh3mdwD/jnp6aXASiUFKOVr6ldP+keJTcQ= | ||||||
|  | github.com/traefik/paerser v0.2.2/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24= | ||||||
| github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= | ||||||
| github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= | ||||||
| github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= | ||||||
|   | |||||||
| @@ -264,11 +264,11 @@ func (auth *Auth) UserAuthConfigured() bool { | |||||||
| 	return len(auth.Config.Users) > 0 | 	return len(auth.Config.Users) > 0 | ||||||
| } | } | ||||||
|  |  | ||||||
| func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext, labels types.TinyauthLabels) bool { | func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext, labels types.Labels) bool { | ||||||
| 	// Check if oauth is allowed | 	// Check if oauth is allowed | ||||||
| 	if context.OAuth { | 	if context.OAuth { | ||||||
| 		log.Debug().Msg("Checking OAuth whitelist") | 		log.Debug().Msg("Checking OAuth whitelist") | ||||||
| 		return utils.CheckWhitelist(labels.OAuthWhitelist, context.Email) | 		return utils.CheckWhitelist(labels.OAuth.Whitelist, context.Email) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Check users | 	// Check users | ||||||
| @@ -277,9 +277,9 @@ func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext, lab | |||||||
| 	return utils.CheckWhitelist(labels.Users, context.Username) | 	return utils.CheckWhitelist(labels.Users, context.Username) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels types.TinyauthLabels) bool { | func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels types.Labels) bool { | ||||||
| 	// Check if groups are required | 	// Check if groups are required | ||||||
| 	if labels.OAuthGroups == "" { | 	if labels.OAuth.Groups == "" { | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -294,7 +294,7 @@ func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels t | |||||||
|  |  | ||||||
| 	// For every group check if it is in the required groups | 	// For every group check if it is in the required groups | ||||||
| 	for _, group := range oauthGroups { | 	for _, group := range oauthGroups { | ||||||
| 		if utils.CheckWhitelist(labels.OAuthGroups, group) { | 		if utils.CheckWhitelist(labels.OAuth.Groups, group) { | ||||||
| 			log.Debug().Str("group", group).Msg("Group is in required groups") | 			log.Debug().Str("group", group).Msg("Group is in required groups") | ||||||
| 			return true | 			return true | ||||||
| 		} | 		} | ||||||
| @@ -307,7 +307,7 @@ func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels t | |||||||
| 	return false | 	return false | ||||||
| } | } | ||||||
|  |  | ||||||
| func (auth *Auth) AuthEnabled(c *gin.Context, labels types.TinyauthLabels) (bool, error) { | func (auth *Auth) AuthEnabled(c *gin.Context, labels types.Labels) (bool, error) { | ||||||
| 	// Get headers | 	// Get headers | ||||||
| 	uri := c.Request.Header.Get("X-Forwarded-Uri") | 	uri := c.Request.Header.Get("X-Forwarded-Uri") | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,14 +1,5 @@ | |||||||
| package constants | package constants | ||||||
|  |  | ||||||
| // TinyauthLabels is a list of labels that can be used in a tinyauth protected container |  | ||||||
| var TinyauthLabels = []string{ |  | ||||||
| 	"tinyauth.oauth.whitelist", |  | ||||||
| 	"tinyauth.users", |  | ||||||
| 	"tinyauth.allowed", |  | ||||||
| 	"tinyauth.headers", |  | ||||||
| 	"tinyauth.oauth.groups", |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Claims are the OIDC supported claims (including preferd username for some reason) | // Claims are the OIDC supported claims (including preferd username for some reason) | ||||||
| type Claims struct { | type Claims struct { | ||||||
| 	Name              string   `json:"name"` | 	Name              string   `json:"name"` | ||||||
|   | |||||||
| @@ -74,14 +74,14 @@ func (docker *Docker) DockerConnected() bool { | |||||||
| 	return err == nil | 	return err == nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (docker *Docker) GetLabels(appId string) (types.TinyauthLabels, error) { | func (docker *Docker) GetLabels(appId string) (types.Labels, error) { | ||||||
| 	// Check if we have access to the Docker API | 	// Check if we have access to the Docker API | ||||||
| 	isConnected := docker.DockerConnected() | 	isConnected := docker.DockerConnected() | ||||||
|  |  | ||||||
| 	// If we don't have access, return an empty struct | 	// If we don't have access, return an empty struct | ||||||
| 	if !isConnected { | 	if !isConnected { | ||||||
| 		log.Debug().Msg("Docker not connected, returning empty labels") | 		log.Debug().Msg("Docker not connected, returning empty labels") | ||||||
| 		return types.TinyauthLabels{}, nil | 		return types.Labels{}, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Get the containers | 	// Get the containers | ||||||
| @@ -89,7 +89,7 @@ func (docker *Docker) GetLabels(appId string) (types.TinyauthLabels, error) { | |||||||
|  |  | ||||||
| 	// If there is an error, return false | 	// If there is an error, return false | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return types.TinyauthLabels{}, err | 		return types.Labels{}, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	log.Debug().Msg("Got containers") | 	log.Debug().Msg("Got containers") | ||||||
| @@ -99,9 +99,10 @@ func (docker *Docker) GetLabels(appId string) (types.TinyauthLabels, error) { | |||||||
| 		// Inspect the container | 		// Inspect the container | ||||||
| 		inspect, err := docker.InspectContainer(container.ID) | 		inspect, err := docker.InspectContainer(container.ID) | ||||||
|  |  | ||||||
| 		// If there is an error, return false | 		// Check if there was an error | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return types.TinyauthLabels{}, err | 			log.Warn().Str("id", container.ID).Err(err).Msg("Error inspecting container, skipping") | ||||||
|  | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Get the container name (for some reason it is /name) | 		// Get the container name (for some reason it is /name) | ||||||
| @@ -112,7 +113,13 @@ func (docker *Docker) GetLabels(appId string) (types.TinyauthLabels, error) { | |||||||
| 			log.Debug().Str("container", containerName).Msg("Found container") | 			log.Debug().Str("container", containerName).Msg("Found container") | ||||||
|  |  | ||||||
| 			// Get only the tinyauth labels in a struct | 			// Get only the tinyauth labels in a struct | ||||||
| 			labels := utils.GetTinyauthLabels(inspect.Config.Labels) | 			labels, err := utils.GetLabels(inspect.Config.Labels) | ||||||
|  |  | ||||||
|  | 			// Check if there was an error | ||||||
|  | 			if err != nil { | ||||||
|  | 				log.Error().Err(err).Msg("Error parsing labels") | ||||||
|  | 				return types.Labels{}, err | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			log.Debug().Msg("Got labels") | 			log.Debug().Msg("Got labels") | ||||||
|  |  | ||||||
| @@ -125,5 +132,5 @@ func (docker *Docker) GetLabels(appId string) (types.TinyauthLabels, error) { | |||||||
| 	log.Debug().Msg("No matching container found, returning empty labels") | 	log.Debug().Msg("No matching container found, returning empty labels") | ||||||
|  |  | ||||||
| 	// If no matching container is found, return empty labels | 	// If no matching container is found, return empty labels | ||||||
| 	return types.TinyauthLabels{}, nil | 	return types.Labels{}, nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -114,7 +114,8 @@ func (h *Handlers) AuthHandler(c *gin.Context) { | |||||||
|  |  | ||||||
| 	// If auth is not enabled, return 200 | 	// If auth is not enabled, return 200 | ||||||
| 	if !authEnabled { | 	if !authEnabled { | ||||||
| 		for key, value := range labels.Headers { | 		headersParsed := utils.ParseHeaders(labels.Headers) | ||||||
|  | 		for key, value := range headersParsed { | ||||||
| 			log.Debug().Str("key", key).Str("value", value).Msg("Setting header") | 			log.Debug().Str("key", key).Str("value", value).Msg("Setting header") | ||||||
| 			c.Header(key, utils.SanitizeHeader(value)) | 			c.Header(key, utils.SanitizeHeader(value)) | ||||||
| 		} | 		} | ||||||
| @@ -236,7 +237,8 @@ func (h *Handlers) AuthHandler(c *gin.Context) { | |||||||
| 		c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups)) | 		c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups)) | ||||||
|  |  | ||||||
| 		// Set the rest of the headers | 		// Set the rest of the headers | ||||||
| 		for key, value := range labels.Headers { | 		parsedHeaders := utils.ParseHeaders(labels.Headers) | ||||||
|  | 		for key, value := range parsedHeaders { | ||||||
| 			log.Debug().Str("key", key).Str("value", value).Msg("Setting header") | 			log.Debug().Str("key", key).Str("value", value).Msg("Setting header") | ||||||
| 			c.Header(key, utils.SanitizeHeader(value)) | 			c.Header(key, utils.SanitizeHeader(value)) | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -92,3 +92,17 @@ type AuthConfig struct { | |||||||
| type HooksConfig struct { | type HooksConfig struct { | ||||||
| 	Domain string | 	Domain string | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // OAuthLabels is a list of labels that can be used in a tinyauth protected container | ||||||
|  | type OAuthLabels struct { | ||||||
|  | 	Whitelist string | ||||||
|  | 	Groups    string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Labels is a struct that contains the labels for a tinyauth protected container | ||||||
|  | type Labels struct { | ||||||
|  | 	Users   string | ||||||
|  | 	Allowed string | ||||||
|  | 	Headers []string | ||||||
|  | 	OAuth   OAuthLabels | ||||||
|  | } | ||||||
|   | |||||||
| @@ -32,15 +32,6 @@ type SessionCookie struct { | |||||||
| 	OAuthGroups string | 	OAuthGroups string | ||||||
| } | } | ||||||
|  |  | ||||||
| // TinyauthLabels is the labels for the tinyauth container |  | ||||||
| type TinyauthLabels struct { |  | ||||||
| 	OAuthWhitelist string |  | ||||||
| 	Users          string |  | ||||||
| 	Allowed        string |  | ||||||
| 	Headers        map[string]string |  | ||||||
| 	OAuthGroups    string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // UserContext is the context for the user | // UserContext is the context for the user | ||||||
| type UserContext struct { | type UserContext struct { | ||||||
| 	Username    string | 	Username    string | ||||||
|   | |||||||
| @@ -5,11 +5,11 @@ import ( | |||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"os" | 	"os" | ||||||
| 	"regexp" | 	"regexp" | ||||||
| 	"slices" |  | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"tinyauth/internal/constants" |  | ||||||
| 	"tinyauth/internal/types" | 	"tinyauth/internal/types" | ||||||
|  |  | ||||||
|  | 	"github.com/traefik/paerser/parser" | ||||||
|  |  | ||||||
| 	"github.com/google/uuid" | 	"github.com/google/uuid" | ||||||
| 	"github.com/rs/zerolog/log" | 	"github.com/rs/zerolog/log" | ||||||
| ) | ) | ||||||
| @@ -174,45 +174,43 @@ func GetUsers(conf string, file string) (types.Users, error) { | |||||||
| 	return ParseUsers(users) | 	return ParseUsers(users) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Parse the docker labels to the tinyauth labels struct | // Parse the headers in a map[string]string format | ||||||
| func GetTinyauthLabels(labels map[string]string) types.TinyauthLabels { | func ParseHeaders(headers []string) map[string]string { | ||||||
| 	// Create a new tinyauth labels struct | 	// Create a map to store the headers | ||||||
| 	var tinyauthLabels types.TinyauthLabels | 	headerMap := make(map[string]string) | ||||||
|  |  | ||||||
| 	// Loop through the labels | 	// Loop through the headers | ||||||
| 	for label, value := range labels { | 	for _, header := range headers { | ||||||
|  | 		split := strings.SplitN(header, "=", 2) | ||||||
| 		// Check if the label is in the tinyauth labels | 		if len(split) != 2 { | ||||||
| 		if slices.Contains(constants.TinyauthLabels, label) { | 			log.Warn().Str("header", header).Msg("Invalid header format, skipping") | ||||||
|  | 			continue | ||||||
| 			log.Debug().Str("label", label).Msg("Found label") |  | ||||||
|  |  | ||||||
| 			// Add the label value to the tinyauth labels struct |  | ||||||
| 			switch label { |  | ||||||
| 			case "tinyauth.oauth.whitelist": |  | ||||||
| 				tinyauthLabels.OAuthWhitelist = value |  | ||||||
| 			case "tinyauth.users": |  | ||||||
| 				tinyauthLabels.Users = value |  | ||||||
| 			case "tinyauth.allowed": |  | ||||||
| 				tinyauthLabels.Allowed = value |  | ||||||
| 			case "tinyauth.headers": |  | ||||||
| 				tinyauthLabels.Headers = make(map[string]string) |  | ||||||
| 				headers := strings.Split(value, ",") |  | ||||||
| 				for _, header := range headers { |  | ||||||
| 					headerSplit := strings.Split(header, "=") |  | ||||||
| 					if len(headerSplit) != 2 { |  | ||||||
| 						continue |  | ||||||
| 					} |  | ||||||
| 					tinyauthLabels.Headers[headerSplit[0]] = headerSplit[1] |  | ||||||
| 				} |  | ||||||
| 			case "tinyauth.oauth.groups": |  | ||||||
| 				tinyauthLabels.OAuthGroups = value |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
|  | 		key := SanitizeHeader(strings.TrimSpace(split[0])) | ||||||
|  | 		value := SanitizeHeader(strings.TrimSpace(split[1])) | ||||||
|  | 		headerMap[key] = value | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Return the tinyauth labels | 	// Return the header map | ||||||
| 	return tinyauthLabels | 	return headerMap | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Get labels parses a map of labels into a struct with only the needed labels | ||||||
|  | func GetLabels(labels map[string]string) (types.Labels, error) { | ||||||
|  | 	// Create a new labels struct | ||||||
|  | 	var labelsParsed types.Labels | ||||||
|  |  | ||||||
|  | 	// Decode the labels into the labels struct | ||||||
|  | 	err := parser.Decode(labels, &labelsParsed, "tinyauth", "tinyauth.users", "tinyauth.allowed", "tinyauth.headers", "tinyauth.oauth") | ||||||
|  |  | ||||||
|  | 	// Check if there was an error | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Error().Err(err).Msg("Error parsing labels") | ||||||
|  | 		return types.Labels{}, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Return the labels struct | ||||||
|  | 	return labelsParsed, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // Check if any of the OAuth providers are configured based on the client id and secret | // Check if any of the OAuth providers are configured based on the client id and secret | ||||||
|   | |||||||
| @@ -279,29 +279,35 @@ func TestGetUsers(t *testing.T) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // Test the tinyauth labels function | // Test the get labels function | ||||||
| func TestGetTinyauthLabels(t *testing.T) { | func TestGetLabels(t *testing.T) { | ||||||
| 	t.Log("Testing get tinyauth labels with a valid map") | 	t.Log("Testing get labels with a valid map") | ||||||
|  |  | ||||||
| 	// Test the get tinyauth labels function with a valid map | 	// Test the get tinyauth labels function with a valid map | ||||||
| 	labels := map[string]string{ | 	labels := map[string]string{ | ||||||
| 		"tinyauth.users":           "user1,user2", | 		"tinyauth.users":           "user1,user2", | ||||||
| 		"tinyauth.oauth.whitelist": "/regex/", | 		"tinyauth.oauth.whitelist": "/regex/", | ||||||
| 		"tinyauth.allowed":         "random", | 		"tinyauth.allowed":         "random", | ||||||
| 		"random":                   "random", |  | ||||||
| 		"tinyauth.headers":         "X-Header=value", | 		"tinyauth.headers":         "X-Header=value", | ||||||
|  | 		"tinyauth.oauth.groups":    "group1,group2", | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	expected := types.TinyauthLabels{ | 	expected := types.Labels{ | ||||||
| 		Users:          "user1,user2", | 		Users:   "user1,user2", | ||||||
| 		OAuthWhitelist: "/regex/", | 		Allowed: "random", | ||||||
| 		Allowed:        "random", | 		Headers: []string{"X-Header=value"}, | ||||||
| 		Headers: map[string]string{ | 		OAuth: types.OAuthLabels{ | ||||||
| 			"X-Header": "value", | 			Whitelist: "/regex/", | ||||||
|  | 			Groups:    "group1,group2", | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	result := utils.GetTinyauthLabels(labels) | 	result, err := utils.GetLabels(labels) | ||||||
|  |  | ||||||
|  | 	// Check if there was an error | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("Error getting labels: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// Check if the result is equal to the expected | 	// Check if the result is equal to the expected | ||||||
| 	if !reflect.DeepEqual(expected, result) { | 	if !reflect.DeepEqual(expected, result) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user