mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-10-30 21:55:43 +00:00 
			
		
		
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			060e20e578
			...
			7795a989cd
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 7795a989cd | ||
|   | cebce1a92c | ||
|   | 120ae2c79d | 
| @@ -11,13 +11,15 @@ export function TailscaleIcon(props: SVGProps<SVGSVGElement>) { | ||||
|       {...props} | ||||
|     > | ||||
|       <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" | ||||
|       /> | ||||
|  | ||||
|       <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" | ||||
|         className="opacity-20 fill-white" | ||||
|         className="opacity-20" | ||||
|         fill="currentColor" | ||||
|       /> | ||||
|     </svg> | ||||
|   ); | ||||
|   | ||||
| @@ -1,8 +1,10 @@ | ||||
| ALTER TABLE "sessions" ADD COLUMN "oauth_name" TEXT; | ||||
|  | ||||
| UPDATE | ||||
|     "sessions" | ||||
| SET | ||||
|     "oauth_name" = "Generic" | ||||
| WHERE | ||||
|     "oauth_name" IS NULL AND "provider" IS NOT NULL; | ||||
| UPDATE "sessions" | ||||
| SET "oauth_name" = CASE | ||||
|   WHEN LOWER("provider") = 'github' THEN 'GitHub' | ||||
|   WHEN LOWER("provider") = 'google' THEN 'Google' | ||||
|   ELSE UPPER(SUBSTR("provider", 1, 1)) || SUBSTR("provider", 2) | ||||
| END | ||||
| WHERE "oauth_name" IS NULL AND "provider" IS NOT NULL; | ||||
|  | ||||
|   | ||||
| @@ -51,16 +51,16 @@ type Claims struct { | ||||
| } | ||||
|  | ||||
| type OAuthServiceConfig struct { | ||||
| 	ClientID           string | ||||
| 	ClientSecret       string | ||||
| 	ClientSecretFile   string | ||||
| 	Scopes             []string | ||||
| 	RedirectURL        string | ||||
| 	AuthURL            string | ||||
| 	TokenURL           string | ||||
| 	UserinfoURL        string | ||||
| 	InsecureSkipVerify bool | ||||
| 	Name               string | ||||
| 	ClientID           string   `key:"client-id"` | ||||
| 	ClientSecret       string   `key:"client-secret"` | ||||
| 	ClientSecretFile   string   `key:"client-secret-file"` | ||||
| 	Scopes             []string `key:"scopes"` | ||||
| 	RedirectURL        string   `key:"redirect-url"` | ||||
| 	AuthURL            string   `key:"auth-url"` | ||||
| 	TokenURL           string   `key:"token-url"` | ||||
| 	UserinfoURL        string   `key:"user-info-url"` | ||||
| 	InsecureSkipVerify bool     `key:"insecure-skip-verify"` | ||||
| 	Name               string   `key:"name"` | ||||
| } | ||||
|  | ||||
| // User/session related stuff | ||||
|   | ||||
| @@ -142,7 +142,9 @@ func GetOAuthProvidersConfig(env []string, args []string, appUrl string) (map[st | ||||
|  | ||||
| 	for _, e := range env { | ||||
| 		pair := strings.SplitN(e, "=", 2) | ||||
| 		envMap[pair[0]] = pair[1] | ||||
| 		if len(pair) == 2 { | ||||
| 			envMap[pair[0]] = pair[1] | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	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 | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"slices" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| 	"tinyauth/internal/config" | ||||
|  | ||||
| 	"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) { | ||||
| 	normalized := normalizeEnv(env, "tinyauth") | ||||
|  | ||||
| 	node, err := decodeEnvsToNode(normalized, "tinyauth", "tinyauth_providers") | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return config.Providers{}, err | ||||
| 	} | ||||
|  | ||||
| 	normalized := NormalizeKeys(env, "tinyauth", "_") | ||||
| 	var providers config.Providers | ||||
|  | ||||
| 	metaOpts := parser.MetadataOpts{TagName: "env", AllowSliceAsStruct: true} | ||||
|  | ||||
| 	err = parser.AddMetadata(&providers, node, metaOpts) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return config.Providers{}, err | ||||
| 	} | ||||
|  | ||||
| 	err = parser.Fill(&providers, node, parser.FillerOpts{AllowSliceAsStruct: true}) | ||||
| 	err := parser.Decode(normalized, &providers, "tinyauth", "tinyauth.providers") | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return config.Providers{}, err | ||||
| @@ -39,99 +18,3 @@ func DecodeEnv(env map[string]string) (config.Providers, error) { | ||||
|  | ||||
| 	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 | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"slices" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| 	"tinyauth/internal/config" | ||||
|  | ||||
| 	"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) { | ||||
| 	normalized := normalizeFlags(flags, "tinyauth") | ||||
|  | ||||
| 	node, err := decodeFlagsToNode(normalized, "tinyauth", "tinyauth_providers") | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return config.Providers{}, err | ||||
| 	} | ||||
|  | ||||
| 	filtered := filterFlags(flags) | ||||
| 	normalized := NormalizeKeys(filtered, "tinyauth", "-") | ||||
| 	var providers config.Providers | ||||
|  | ||||
| 	metaOpts := parser.MetadataOpts{TagName: "flag", AllowSliceAsStruct: true} | ||||
|  | ||||
| 	err = parser.AddMetadata(&providers, node, metaOpts) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return config.Providers{}, err | ||||
| 	} | ||||
|  | ||||
| 	err = parser.Fill(&providers, node, parser.FillerOpts{AllowSliceAsStruct: true}) | ||||
| 	err := parser.Decode(normalized, &providers, "tinyauth", "tinyauth.providers") | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return config.Providers{}, err | ||||
| @@ -40,98 +21,10 @@ func DecodeFlags(flags map[string]string) (config.Providers, error) { | ||||
| 	return providers, nil | ||||
| } | ||||
|  | ||||
| func decodeFlagsToNode(flags map[string]string, rootName string, filters ...string) (*parser.Node, error) { | ||||
| 	sorted := sortFlagKeys(flags, 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 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) | ||||
| func filterFlags(flags map[string]string) map[string]string { | ||||
| 	filtered := make(map[string]string) | ||||
| 	for k, v := range flags { | ||||
| 		fk := strings.TrimPrefix(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 | ||||
| 		filtered[strings.TrimPrefix(k, "--")] = v | ||||
| 	} | ||||
| 	return n | ||||
| 	return filtered | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user