mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-11-04 08:05:42 +00:00 
			
		
		
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			060e20e578
			...
			feat/multi
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					7795a989cd | ||
| 
						 | 
					cebce1a92c | ||
| 
						 | 
					120ae2c79d | 
@@ -11,13 +11,15 @@ export function TailscaleIcon(props: SVGProps<SVGSVGElement>) {
 | 
				
			|||||||
      {...props}
 | 
					      {...props}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <path
 | 
					      <path
 | 
				
			||||||
        className="opacity-80 fill-white"
 | 
					        className="opacity-80"
 | 
				
			||||||
 | 
					        fill="currentColor"
 | 
				
			||||||
        d="M65.6 318.1c35.3 0 63.9-28.6 63.9-63.9s-28.6-63.9-63.9-63.9S1.8 219 1.8 254.2s28.6 63.9 63.8 63.9m191.6 0c35.3 0 63.9-28.6 63.9-63.9s-28.6-63.9-63.9-63.9-63.9 28.6-63.9 63.9 28.6 63.9 63.9 63.9m0 193.9c35.3 0 63.9-28.6 63.9-63.9s-28.6-63.9-63.9-63.9-63.9 28.6-63.9 63.9 28.6 63.9 63.9 63.9m189.2-193.9c35.3 0 63.9-28.6 63.9-63.9s-28.6-63.9-63.9-63.9-63.9 28.6-63.9 63.9 28.6 63.9 63.9 63.9"
 | 
					        d="M65.6 318.1c35.3 0 63.9-28.6 63.9-63.9s-28.6-63.9-63.9-63.9S1.8 219 1.8 254.2s28.6 63.9 63.8 63.9m191.6 0c35.3 0 63.9-28.6 63.9-63.9s-28.6-63.9-63.9-63.9-63.9 28.6-63.9 63.9 28.6 63.9 63.9 63.9m0 193.9c35.3 0 63.9-28.6 63.9-63.9s-28.6-63.9-63.9-63.9-63.9 28.6-63.9 63.9 28.6 63.9 63.9 63.9m189.2-193.9c35.3 0 63.9-28.6 63.9-63.9s-28.6-63.9-63.9-63.9-63.9 28.6-63.9 63.9 28.6 63.9 63.9 63.9"
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <path
 | 
					      <path
 | 
				
			||||||
        d="M65.6 127.7c35.3 0 63.9-28.6 63.9-63.9S100.9 0 65.6 0 1.8 28.6 1.8 63.9s28.6 63.8 63.8 63.8m0 384.3c35.3 0 63.9-28.6 63.9-63.9s-28.6-63.9-63.9-63.9-63.8 28.7-63.8 63.9S30.4 512 65.6 512m191.6-384.3c35.3 0 63.9-28.6 63.9-63.9S292.5 0 257.2 0s-63.9 28.6-63.9 63.9 28.6 63.8 63.9 63.8m189.2 0c35.3 0 63.9-28.6 63.9-63.9S481.6 0 446.4 0c-35.3 0-63.9 28.6-63.9 63.9s28.6 63.8 63.9 63.8m0 384.3c35.3 0 63.9-28.6 63.9-63.9s-28.6-63.9-63.9-63.9-63.9 28.6-63.9 63.9 28.6 63.9 63.9 63.9"
 | 
					        d="M65.6 127.7c35.3 0 63.9-28.6 63.9-63.9S100.9 0 65.6 0 1.8 28.6 1.8 63.9s28.6 63.8 63.8 63.8m0 384.3c35.3 0 63.9-28.6 63.9-63.9s-28.6-63.9-63.9-63.9-63.8 28.7-63.8 63.9S30.4 512 65.6 512m191.6-384.3c35.3 0 63.9-28.6 63.9-63.9S292.5 0 257.2 0s-63.9 28.6-63.9 63.9 28.6 63.8 63.9 63.8m189.2 0c35.3 0 63.9-28.6 63.9-63.9S481.6 0 446.4 0c-35.3 0-63.9 28.6-63.9 63.9s28.6 63.8 63.9 63.8m0 384.3c35.3 0 63.9-28.6 63.9-63.9s-28.6-63.9-63.9-63.9-63.9 28.6-63.9 63.9 28.6 63.9 63.9 63.9"
 | 
				
			||||||
        className="opacity-20 fill-white"
 | 
					        className="opacity-20"
 | 
				
			||||||
 | 
					        fill="currentColor"
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
    </svg>
 | 
					    </svg>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,10 @@
 | 
				
			|||||||
ALTER TABLE "sessions" ADD COLUMN "oauth_name" TEXT;
 | 
					ALTER TABLE "sessions" ADD COLUMN "oauth_name" TEXT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
UPDATE
 | 
					UPDATE "sessions"
 | 
				
			||||||
    "sessions"
 | 
					SET "oauth_name" = CASE
 | 
				
			||||||
SET
 | 
					  WHEN LOWER("provider") = 'github' THEN 'GitHub'
 | 
				
			||||||
    "oauth_name" = "Generic"
 | 
					  WHEN LOWER("provider") = 'google' THEN 'Google'
 | 
				
			||||||
WHERE
 | 
					  ELSE UPPER(SUBSTR("provider", 1, 1)) || SUBSTR("provider", 2)
 | 
				
			||||||
    "oauth_name" IS NULL AND "provider" IS NOT NULL;
 | 
					END
 | 
				
			||||||
 | 
					WHERE "oauth_name" IS NULL AND "provider" IS NOT NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,16 +51,16 @@ type Claims struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type OAuthServiceConfig struct {
 | 
					type OAuthServiceConfig struct {
 | 
				
			||||||
	ClientID           string
 | 
						ClientID           string   `key:"client-id"`
 | 
				
			||||||
	ClientSecret       string
 | 
						ClientSecret       string   `key:"client-secret"`
 | 
				
			||||||
	ClientSecretFile   string
 | 
						ClientSecretFile   string   `key:"client-secret-file"`
 | 
				
			||||||
	Scopes             []string
 | 
						Scopes             []string `key:"scopes"`
 | 
				
			||||||
	RedirectURL        string
 | 
						RedirectURL        string   `key:"redirect-url"`
 | 
				
			||||||
	AuthURL            string
 | 
						AuthURL            string   `key:"auth-url"`
 | 
				
			||||||
	TokenURL           string
 | 
						TokenURL           string   `key:"token-url"`
 | 
				
			||||||
	UserinfoURL        string
 | 
						UserinfoURL        string   `key:"user-info-url"`
 | 
				
			||||||
	InsecureSkipVerify bool
 | 
						InsecureSkipVerify bool     `key:"insecure-skip-verify"`
 | 
				
			||||||
	Name               string
 | 
						Name               string   `key:"name"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// User/session related stuff
 | 
					// User/session related stuff
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -142,7 +142,9 @@ func GetOAuthProvidersConfig(env []string, args []string, appUrl string) (map[st
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	for _, e := range env {
 | 
						for _, e := range env {
 | 
				
			||||||
		pair := strings.SplitN(e, "=", 2)
 | 
							pair := strings.SplitN(e, "=", 2)
 | 
				
			||||||
		envMap[pair[0]] = pair[1]
 | 
							if len(pair) == 2 {
 | 
				
			||||||
 | 
								envMap[pair[0]] = pair[1]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	envProviders, err := decoders.DecodeEnv(envMap)
 | 
						envProviders, err := decoders.DecodeEnv(envMap)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										81
									
								
								internal/utils/decoders/decoders.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								internal/utils/decoders/decoders.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					package decoders
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"tinyauth/internal/config"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NormalizeKeys(keys map[string]string, rootName string, sep string) map[string]string {
 | 
				
			||||||
 | 
						normalized := make(map[string]string)
 | 
				
			||||||
 | 
						knownKeys := getKnownKeys()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for k, v := range keys {
 | 
				
			||||||
 | 
							var finalKey []string
 | 
				
			||||||
 | 
							var suffix string
 | 
				
			||||||
 | 
							var camelClientName string
 | 
				
			||||||
 | 
							var camelField string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							finalKey = append(finalKey, rootName)
 | 
				
			||||||
 | 
							finalKey = append(finalKey, "providers")
 | 
				
			||||||
 | 
							cebabKey := strings.ToLower(k)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for _, known := range knownKeys {
 | 
				
			||||||
 | 
								if strings.HasSuffix(cebabKey, strings.ReplaceAll(known, "-", sep)) {
 | 
				
			||||||
 | 
									suffix = known
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if suffix == "" {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							clientNameParts := strings.Split(strings.TrimPrefix(strings.TrimSuffix(cebabKey, sep+strings.ReplaceAll(suffix, "-", sep)), "providers"+sep), sep)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for i, p := range clientNameParts {
 | 
				
			||||||
 | 
								if i == 0 {
 | 
				
			||||||
 | 
									camelClientName += p
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if p == "" {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								camelClientName += strings.ToUpper(string([]rune(p)[0])) + string([]rune(p)[1:])
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							finalKey = append(finalKey, camelClientName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							filedParts := strings.Split(suffix, "-")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for i, p := range filedParts {
 | 
				
			||||||
 | 
								if i == 0 {
 | 
				
			||||||
 | 
									camelField += p
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if p == "" {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								camelField += strings.ToUpper(string([]rune(p)[0])) + string([]rune(p)[1:])
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							finalKey = append(finalKey, camelField)
 | 
				
			||||||
 | 
							normalized[strings.Join(finalKey, ".")] = v
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return normalized
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getKnownKeys() []string {
 | 
				
			||||||
 | 
						var known []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p := config.OAuthServiceConfig{}
 | 
				
			||||||
 | 
						v := reflect.ValueOf(p)
 | 
				
			||||||
 | 
						typeOfP := v.Type()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for field := range typeOfP.NumField() {
 | 
				
			||||||
 | 
							known = append(known, typeOfP.Field(field).Tag.Get("key"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return known
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										44
									
								
								internal/utils/decoders/decoders_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								internal/utils/decoders/decoders_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					package decoders_test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
						"tinyauth/internal/utils/decoders"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gotest.tools/v3/assert"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestNormalizeKeys(t *testing.T) {
 | 
				
			||||||
 | 
						// Test with env
 | 
				
			||||||
 | 
						test := map[string]string{
 | 
				
			||||||
 | 
							"PROVIDERS_CLIENT1_CLIENT_ID":                    "my-client-id",
 | 
				
			||||||
 | 
							"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",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						expected := map[string]string{
 | 
				
			||||||
 | 
							"tinyauth.providers.client1.clientId":                 "my-client-id",
 | 
				
			||||||
 | 
							"tinyauth.providers.client1.clientSecret":             "my-client-secret",
 | 
				
			||||||
 | 
							"tinyauth.providers.myAwesomeClient.clientId":         "my-awesome-client-id",
 | 
				
			||||||
 | 
							"tinyauth.providers.myAwesomeClient.clientSecretFile": "/path/to/secret",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						normalized := decoders.NormalizeKeys(test, "tinyauth", "_")
 | 
				
			||||||
 | 
						assert.DeepEqual(t, normalized, expected)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test with flags (assume -- is already stripped)
 | 
				
			||||||
 | 
						test = map[string]string{
 | 
				
			||||||
 | 
							"providers-client1-client-id":                    "my-client-id",
 | 
				
			||||||
 | 
							"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",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						expected = map[string]string{
 | 
				
			||||||
 | 
							"tinyauth.providers.client1.clientId":                 "my-client-id",
 | 
				
			||||||
 | 
							"tinyauth.providers.client1.clientSecret":             "my-client-secret",
 | 
				
			||||||
 | 
							"tinyauth.providers.myAwesomeClient.clientId":         "my-awesome-client-id",
 | 
				
			||||||
 | 
							"tinyauth.providers.myAwesomeClient.clientSecretFile": "/path/to/secret",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						normalized = decoders.NormalizeKeys(test, "tinyauth", "-")
 | 
				
			||||||
 | 
						assert.DeepEqual(t, normalized, expected)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,37 +1,16 @@
 | 
				
			|||||||
package decoders
 | 
					package decoders
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"slices"
 | 
					 | 
				
			||||||
	"sort"
 | 
					 | 
				
			||||||
	"strings"
 | 
					 | 
				
			||||||
	"tinyauth/internal/config"
 | 
						"tinyauth/internal/config"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/traefik/paerser/parser"
 | 
						"github.com/traefik/paerser/parser"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Based on https://github.com/traefik/paerser/blob/master/parser/labels_decode.go
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func DecodeEnv(env map[string]string) (config.Providers, error) {
 | 
					func DecodeEnv(env map[string]string) (config.Providers, error) {
 | 
				
			||||||
	normalized := normalizeEnv(env, "tinyauth")
 | 
						normalized := NormalizeKeys(env, "tinyauth", "_")
 | 
				
			||||||
 | 
					 | 
				
			||||||
	node, err := decodeEnvsToNode(normalized, "tinyauth", "tinyauth_providers")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return config.Providers{}, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var providers config.Providers
 | 
						var providers config.Providers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	metaOpts := parser.MetadataOpts{TagName: "env", AllowSliceAsStruct: true}
 | 
						err := parser.Decode(normalized, &providers, "tinyauth", "tinyauth.providers")
 | 
				
			||||||
 | 
					 | 
				
			||||||
	err = parser.AddMetadata(&providers, node, metaOpts)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return config.Providers{}, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	err = parser.Fill(&providers, node, parser.FillerOpts{AllowSliceAsStruct: true})
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return config.Providers{}, err
 | 
							return config.Providers{}, err
 | 
				
			||||||
@@ -39,99 +18,3 @@ func DecodeEnv(env map[string]string) (config.Providers, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return providers, nil
 | 
						return providers, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
func decodeEnvsToNode(env map[string]string, rootName string, filters ...string) (*parser.Node, error) {
 | 
					 | 
				
			||||||
	sorted := sortEnvKeys(env, filters)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var node *parser.Node
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for i, k := range sorted {
 | 
					 | 
				
			||||||
		split := strings.SplitN(k, "_", 4) // Something like PROVIDERS_MY_AWESOME_CLIENT is not supported because it will confuse the parser
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if split[0] != rootName {
 | 
					 | 
				
			||||||
			return nil, fmt.Errorf("invalid env root %s", split[0])
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if slices.Contains(split, "") {
 | 
					 | 
				
			||||||
			return nil, fmt.Errorf("invalid element: %s", k)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if i == 0 {
 | 
					 | 
				
			||||||
			node = &parser.Node{}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		decodeEnvToNode(node, split, env[k])
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return node, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func decodeEnvToNode(root *parser.Node, path []string, value string) {
 | 
					 | 
				
			||||||
	if len(root.Name) == 0 {
 | 
					 | 
				
			||||||
		root.Name = path[0]
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if len(path) <= 1 {
 | 
					 | 
				
			||||||
		root.Value = value
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if n := containsEnvNode(root.Children, path[1]); n != nil {
 | 
					 | 
				
			||||||
		decodeEnvToNode(n, path[1:], value)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	child := &parser.Node{Name: path[1]}
 | 
					 | 
				
			||||||
	decodeEnvToNode(child, path[1:], value)
 | 
					 | 
				
			||||||
	root.Children = append(root.Children, child)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func containsEnvNode(node []*parser.Node, name string) *parser.Node {
 | 
					 | 
				
			||||||
	for _, n := range node {
 | 
					 | 
				
			||||||
		if strings.EqualFold(n.Name, name) {
 | 
					 | 
				
			||||||
			return n
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func sortEnvKeys(env map[string]string, filters []string) []string {
 | 
					 | 
				
			||||||
	var sorted []string
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for k := range env {
 | 
					 | 
				
			||||||
		if len(filters) == 0 {
 | 
					 | 
				
			||||||
			sorted = append(sorted, k)
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		for _, f := range filters {
 | 
					 | 
				
			||||||
			if strings.HasPrefix(k, f) {
 | 
					 | 
				
			||||||
				sorted = append(sorted, k)
 | 
					 | 
				
			||||||
				break
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	sort.Strings(sorted)
 | 
					 | 
				
			||||||
	return sorted
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// normalizeEnv converts env vars from PROVIDERS_CLIENT1_CLIENT_ID to tinyauth_providers_client_clientId
 | 
					 | 
				
			||||||
func normalizeEnv(env map[string]string, rootName string) map[string]string {
 | 
					 | 
				
			||||||
	n := make(map[string]string)
 | 
					 | 
				
			||||||
	for k, v := range env {
 | 
					 | 
				
			||||||
		fk := strings.ToLower(k)
 | 
					 | 
				
			||||||
		fks := strings.SplitN(fk, "_", 3)
 | 
					 | 
				
			||||||
		fkb := ""
 | 
					 | 
				
			||||||
		for i, s := range strings.Split(fks[len(fks)-1], "_") {
 | 
					 | 
				
			||||||
			if i == 0 {
 | 
					 | 
				
			||||||
				fkb += s
 | 
					 | 
				
			||||||
				continue
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			fkb += strings.ToUpper(string([]rune(s)[0])) + string([]rune(s)[1:])
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		fk = rootName + "_" + strings.Join(fks[:len(fks)-1], "_") + "_" + fkb
 | 
					 | 
				
			||||||
		n[fk] = v
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return n
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,37 +1,18 @@
 | 
				
			|||||||
package decoders
 | 
					package decoders
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"slices"
 | 
					 | 
				
			||||||
	"sort"
 | 
					 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"tinyauth/internal/config"
 | 
						"tinyauth/internal/config"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/traefik/paerser/parser"
 | 
						"github.com/traefik/paerser/parser"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Based on https://github.com/traefik/paerser/blob/master/parser/labels_decode.go
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func DecodeFlags(flags map[string]string) (config.Providers, error) {
 | 
					func DecodeFlags(flags map[string]string) (config.Providers, error) {
 | 
				
			||||||
	normalized := normalizeFlags(flags, "tinyauth")
 | 
						filtered := filterFlags(flags)
 | 
				
			||||||
 | 
						normalized := NormalizeKeys(filtered, "tinyauth", "-")
 | 
				
			||||||
	node, err := decodeFlagsToNode(normalized, "tinyauth", "tinyauth_providers")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return config.Providers{}, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var providers config.Providers
 | 
						var providers config.Providers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	metaOpts := parser.MetadataOpts{TagName: "flag", AllowSliceAsStruct: true}
 | 
						err := parser.Decode(normalized, &providers, "tinyauth", "tinyauth.providers")
 | 
				
			||||||
 | 
					 | 
				
			||||||
	err = parser.AddMetadata(&providers, node, metaOpts)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return config.Providers{}, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	err = parser.Fill(&providers, node, parser.FillerOpts{AllowSliceAsStruct: true})
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return config.Providers{}, err
 | 
							return config.Providers{}, err
 | 
				
			||||||
@@ -40,98 +21,10 @@ func DecodeFlags(flags map[string]string) (config.Providers, error) {
 | 
				
			|||||||
	return providers, nil
 | 
						return providers, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func decodeFlagsToNode(flags map[string]string, rootName string, filters ...string) (*parser.Node, error) {
 | 
					func filterFlags(flags map[string]string) map[string]string {
 | 
				
			||||||
	sorted := sortFlagKeys(flags, filters)
 | 
						filtered := make(map[string]string)
 | 
				
			||||||
 | 
					 | 
				
			||||||
	var node *parser.Node
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for i, k := range sorted {
 | 
					 | 
				
			||||||
		split := strings.SplitN(k, "_", 4) // Something like --providers-my-awesome-client is not supported because it will confuse the parser
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if split[0] != rootName {
 | 
					 | 
				
			||||||
			return nil, fmt.Errorf("invalid flag root %s", split[0])
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if slices.Contains(split, "") {
 | 
					 | 
				
			||||||
			return nil, fmt.Errorf("invalid element: %s", k)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if i == 0 {
 | 
					 | 
				
			||||||
			node = &parser.Node{}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		decodeFlagToNode(node, split, flags[k])
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return node, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func decodeFlagToNode(root *parser.Node, path []string, value string) {
 | 
					 | 
				
			||||||
	if len(root.Name) == 0 {
 | 
					 | 
				
			||||||
		root.Name = path[0]
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if len(path) <= 1 {
 | 
					 | 
				
			||||||
		root.Value = value
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if n := containsFlagNode(root.Children, path[1]); n != nil {
 | 
					 | 
				
			||||||
		decodeFlagToNode(n, path[1:], value)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	child := &parser.Node{Name: path[1]}
 | 
					 | 
				
			||||||
	decodeFlagToNode(child, path[1:], value)
 | 
					 | 
				
			||||||
	root.Children = append(root.Children, child)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func containsFlagNode(node []*parser.Node, name string) *parser.Node {
 | 
					 | 
				
			||||||
	for _, n := range node {
 | 
					 | 
				
			||||||
		if strings.EqualFold(n.Name, name) {
 | 
					 | 
				
			||||||
			return n
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func sortFlagKeys(flags map[string]string, filters []string) []string {
 | 
					 | 
				
			||||||
	var sorted []string
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for k := range flags {
 | 
					 | 
				
			||||||
		if len(filters) == 0 {
 | 
					 | 
				
			||||||
			sorted = append(sorted, k)
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		for _, f := range filters {
 | 
					 | 
				
			||||||
			if strings.HasPrefix(k, f) {
 | 
					 | 
				
			||||||
				sorted = append(sorted, k)
 | 
					 | 
				
			||||||
				break
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	sort.Strings(sorted)
 | 
					 | 
				
			||||||
	return sorted
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// normalizeFlags converts flags from --providers-client-client-id to tinyauth_providers_client_clientId
 | 
					 | 
				
			||||||
func normalizeFlags(flags map[string]string, rootName string) map[string]string {
 | 
					 | 
				
			||||||
	n := make(map[string]string)
 | 
					 | 
				
			||||||
	for k, v := range flags {
 | 
						for k, v := range flags {
 | 
				
			||||||
		fk := strings.TrimPrefix(k, "--")
 | 
							filtered[strings.TrimPrefix(k, "--")] = v
 | 
				
			||||||
		fks := strings.SplitN(fk, "-", 3)
 | 
					 | 
				
			||||||
		fkb := ""
 | 
					 | 
				
			||||||
		for i, s := range strings.Split(fks[len(fks)-1], "-") {
 | 
					 | 
				
			||||||
			if i == 0 {
 | 
					 | 
				
			||||||
				fkb += s
 | 
					 | 
				
			||||||
				continue
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			fkb += strings.ToUpper(string([]rune(s)[0])) + string([]rune(s)[1:])
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		fk = rootName + "_" + strings.Join(fks[:len(fks)-1], "_") + "_" + fkb
 | 
					 | 
				
			||||||
		n[fk] = v
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return n
 | 
						return filtered
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user