feat: parse apps acl flags and env dynamically

This commit is contained in:
Nicolas Meienberger
2025-10-28 19:14:57 +01:00
parent 0227af6d2b
commit f978ae155a
8 changed files with 316 additions and 53 deletions

View File

@@ -0,0 +1,92 @@
package decoders_test
import (
"testing"
"tinyauth/internal/config"
"tinyauth/internal/utils/decoders"
"gotest.tools/v3/assert"
)
func TestDecodeACLEnv(t *testing.T) {
env := map[string]string{
"TINYAUTH_APPS_MY_COOL_APP_CONFIG_DOMAIN": "example.com",
"TINYAUTH_APPS_MY_COOL_APP_USERS_ALLOW": "user1,user2",
"TINYAUTH_APPS_MY_COOL_APP_USERS_BLOCK": "user3",
"TINYAUTH_APPS_MY_COOL_APP_OAUTH_WHITELIST": "provider1",
"TINYAUTH_APPS_MY_COOL_APP_OAUTH_GROUPS": "group1,group2",
"TINYAUTH_APPS_OTHERAPP_CONFIG_DOMAIN": "test.com",
"TINYAUTH_APPS_OTHERAPP_USERS_ALLOW": "admin",
}
expected := config.Apps{
Apps: map[string]config.App{
"my_cool_app": {
Config: config.AppConfig{
Domain: "example.com",
},
Users: config.AppUsers{
Allow: "user1,user2",
Block: "user3",
},
OAuth: config.AppOAuth{
Whitelist: "provider1",
Groups: "group1,group2",
},
},
"otherapp": {
Config: config.AppConfig{
Domain: "test.com",
},
Users: config.AppUsers{
Allow: "admin",
},
},
},
}
// Execute
result, err := decoders.DecodeACLEnv[config.Apps](env, "apps")
assert.NilError(t, err)
assert.DeepEqual(t, result, expected)
}
func TestDecodeACLFlags(t *testing.T) {
// Setup
flags := map[string]string{
"tinyauth-apps-webapp-config-domain": "webapp.example.com",
"tinyauth-apps-webapp-users-allow": "alice,bob",
"tinyauth-apps-webapp-oauth-whitelist": "google",
"tinyauth-apps-api-config-domain": "api.example.com",
"tinyauth-apps-api-users-block": "banned",
}
expected := config.Apps{
Apps: map[string]config.App{
"webapp": {
Config: config.AppConfig{
Domain: "webapp.example.com",
},
Users: config.AppUsers{
Allow: "alice,bob",
},
OAuth: config.AppOAuth{
Whitelist: "google",
},
},
"api": {
Config: config.AppConfig{
Domain: "api.example.com",
},
Users: config.AppUsers{
Block: "banned",
},
},
},
}
// Execute
result, err := decoders.DecodeACLFlags[config.Apps](flags, "apps")
assert.NilError(t, err)
assert.DeepEqual(t, result, expected)
}

View File

@@ -7,6 +7,60 @@ import (
"github.com/stoewer/go-strcase"
)
func ParsePath(parts []string, idx int, t reflect.Type) []string {
if idx >= len(parts) {
return []string{}
}
if t.Kind() == reflect.Map {
mapName := strings.ToLower(parts[idx])
if idx+1 >= len(parts) {
return []string{mapName}
}
elemType := t.Elem()
keyEndIdx := idx + 1
if elemType.Kind() == reflect.Struct {
for i := idx + 1; i < len(parts); i++ {
found := false
for j := 0; j < elemType.NumField(); j++ {
field := elemType.Field(j)
if strings.EqualFold(parts[i], field.Name) {
keyEndIdx = i
found = true
break
}
}
if found {
break
}
}
}
keyParts := parts[idx+1 : keyEndIdx]
keyName := strings.ToLower(strings.Join(keyParts, "_"))
rest := ParsePath(parts, keyEndIdx, elemType)
return append([]string{mapName, keyName}, rest...)
}
if t.Kind() == reflect.Struct {
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if strings.EqualFold(parts[idx], field.Name) {
rest := ParsePath(parts, idx+1, field.Type)
return append([]string{strings.ToLower(field.Name)}, rest...)
}
}
}
return []string{}
}
func normalizeKeys[T any](input map[string]string, root string, sep string) map[string]string {
knownKeys := getKnownKeys[T]()
normalized := make(map[string]string)
@@ -74,3 +128,44 @@ func getKnownKeys[T any]() []string {
return keys
}
func normalizeACLKeys[T any](input map[string]string, root string, sep string) map[string]string {
normalized := make(map[string]string)
var t T
rootType := reflect.TypeOf(t)
for k, v := range input {
parts := strings.Split(strings.ToLower(k), sep)
if len(parts) < 2 {
continue
}
if parts[0] != "tinyauth" {
continue
}
if parts[1] != root {
continue
}
if len(parts) > 2 {
parsedParts := ParsePath(parts[2:], 0, rootType)
if len(parsedParts) == 0 {
continue
}
final := "tinyauth"
final += "." + root
for _, part := range parsedParts {
final += "." + strcase.LowerCamelCase(part)
}
normalized[final] = v
}
}
return normalized
}

View File

@@ -17,3 +17,17 @@ func DecodeEnv[T any, C any](env map[string]string, subName string) (T, error) {
return result, nil
}
func DecodeACLEnv[T any](env map[string]string, subName string) (T, error) {
var result T
normalized := normalizeACLKeys[T](env, subName, "_")
err := parser.Decode(normalized, &result, "tinyauth", "tinyauth."+subName)
if err != nil {
return result, err
}
return result, nil
}

View File

@@ -21,6 +21,21 @@ func DecodeFlags[T any, C any](flags map[string]string, subName string) (T, erro
return result, nil
}
func DecodeACLFlags[T any](flags map[string]string, subName string) (T, error) {
var result T
filtered := filterFlags(flags)
normalized := normalizeACLKeys[T](filtered, subName, "-")
err := parser.Decode(normalized, &result, "tinyauth", "tinyauth."+subName)
if err != nil {
return result, err
}
return result, nil
}
func filterFlags(flags map[string]string) map[string]string {
filtered := make(map[string]string)
for k, v := range flags {