mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-11-08 01:55:43 +00:00
Compare commits
3 Commits
refactor/e
...
feat/light
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95dced96ae | ||
|
|
2c3b72353a | ||
|
|
f5f18bc2f6 |
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -29,10 +29,10 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo testing > internal/assets/version
|
echo testing > internal/assets/version
|
||||||
|
|
||||||
- name: Lint frontend
|
- name: Build frontend
|
||||||
run: |
|
run: |
|
||||||
cd frontend
|
cd frontend
|
||||||
bun run lint
|
bun run build
|
||||||
|
|
||||||
- name: Copy frontend
|
- name: Copy frontend
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const isValidUrl = (url: string) => {
|
|||||||
try {
|
try {
|
||||||
new URL(url);
|
new URL(url);
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -76,14 +76,7 @@ export const ContinuePage = () => {
|
|||||||
clearTimeout(auto);
|
clearTimeout(auto);
|
||||||
clearTimeout(reveal);
|
clearTimeout(reveal);
|
||||||
};
|
};
|
||||||
}, [
|
}, []);
|
||||||
handleRedirect,
|
|
||||||
isAllowedRedirectProto,
|
|
||||||
isHttpsDowngrade,
|
|
||||||
isLoggedIn,
|
|
||||||
isTrustedRedirectUri,
|
|
||||||
isValidRedirectUri,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -119,8 +119,6 @@ export const LoginPage = () => {
|
|||||||
!isLoggedIn &&
|
!isLoggedIn &&
|
||||||
redirectUri
|
redirectUri
|
||||||
) {
|
) {
|
||||||
// Not sure of a better way to do this
|
|
||||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
||||||
setOauthAutoRedirectHandover(true);
|
setOauthAutoRedirectHandover(true);
|
||||||
oauthMutation.mutate(oauthAutoRedirect);
|
oauthMutation.mutate(oauthAutoRedirect);
|
||||||
redirectButtonTimer.current = window.setTimeout(() => {
|
redirectButtonTimer.current = window.setTimeout(() => {
|
||||||
@@ -128,15 +126,7 @@ export const LoginPage = () => {
|
|||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, []);
|
||||||
isMounted,
|
|
||||||
oauthProviders.length,
|
|
||||||
providers,
|
|
||||||
isLoggedIn,
|
|
||||||
redirectUri,
|
|
||||||
oauthAutoRedirect,
|
|
||||||
oauthMutation,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() => () => {
|
() => () => {
|
||||||
|
|||||||
@@ -62,13 +62,6 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get access controls
|
|
||||||
acls, err := utils.GetACLS(os.Environ(), os.Args)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get cookie domain
|
// Get cookie domain
|
||||||
cookieDomain, err := utils.GetCookieDomain(app.config.AppURL)
|
cookieDomain, err := utils.GetCookieDomain(app.config.AppURL)
|
||||||
|
|
||||||
@@ -146,7 +139,7 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
|
|
||||||
// Create services
|
// Create services
|
||||||
dockerService := service.NewDockerService()
|
dockerService := service.NewDockerService()
|
||||||
aclsService := service.NewAccessControlsService(dockerService, acls)
|
aclsService := service.NewAccessControlsService(dockerService)
|
||||||
authService := service.NewAuthService(authConfig, dockerService, ldapService, database)
|
authService := service.NewAuthService(authConfig, dockerService, ldapService, database)
|
||||||
oauthBrokerService := service.NewOAuthBrokerService(oauthProviders)
|
oauthBrokerService := service.NewOAuthBrokerService(oauthProviders)
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func setupProxyController(t *testing.T, middlewares *[]gin.HandlerFunc) (*gin.En
|
|||||||
assert.NilError(t, dockerService.Init())
|
assert.NilError(t, dockerService.Init())
|
||||||
|
|
||||||
// Access controls
|
// Access controls
|
||||||
accessControlsService := service.NewAccessControlsService(dockerService, config.Apps{})
|
accessControlsService := service.NewAccessControlsService(dockerService)
|
||||||
|
|
||||||
assert.NilError(t, accessControlsService.Init())
|
assert.NilError(t, accessControlsService.Init())
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,82 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"tinyauth/internal/config"
|
"tinyauth/internal/config"
|
||||||
|
"tinyauth/internal/utils/decoders"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AccessControlsService struct {
|
type AccessControlsService struct {
|
||||||
docker *DockerService
|
docker *DockerService
|
||||||
nonDocker config.Apps
|
envACLs config.Apps
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAccessControlsService(docker *DockerService, nonDocker config.Apps) *AccessControlsService {
|
func NewAccessControlsService(docker *DockerService) *AccessControlsService {
|
||||||
return &AccessControlsService{
|
return &AccessControlsService{
|
||||||
docker: docker,
|
docker: docker,
|
||||||
nonDocker: nonDocker,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (acls *AccessControlsService) Init() error {
|
func (acls *AccessControlsService) Init() error {
|
||||||
|
acls.envACLs = config.Apps{}
|
||||||
|
env := os.Environ()
|
||||||
|
appEnvVars := []string{}
|
||||||
|
|
||||||
|
for _, e := range env {
|
||||||
|
if strings.HasPrefix(e, "TINYAUTH_APPS_") {
|
||||||
|
appEnvVars = append(appEnvVars, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := acls.loadEnvACLs(appEnvVars)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (acls *AccessControlsService) lookupNonDockerACLs(appDomain string) *config.App {
|
func (acls *AccessControlsService) loadEnvACLs(appEnvVars []string) error {
|
||||||
if len(acls.nonDocker.Apps) == 0 {
|
if len(appEnvVars) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for appName, appACLs := range acls.nonDocker.Apps {
|
envAcls := map[string]string{}
|
||||||
|
|
||||||
|
for _, e := range appEnvVars {
|
||||||
|
parts := strings.SplitN(e, "=", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize key, this should use the same normalization logic as in utils/decoders/decoders.go
|
||||||
|
key := parts[0]
|
||||||
|
key = strings.ToLower(key)
|
||||||
|
key = strings.ReplaceAll(key, "_", ".")
|
||||||
|
value := parts[1]
|
||||||
|
envAcls[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
apps, err := decoders.DecodeLabels(envAcls)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
acls.envACLs = apps
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (acls *AccessControlsService) lookupEnvACLs(appDomain string) *config.App {
|
||||||
|
if len(acls.envACLs.Apps) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for appName, appACLs := range acls.envACLs.Apps {
|
||||||
if appACLs.Config.Domain == appDomain {
|
if appACLs.Config.Domain == appDomain {
|
||||||
return &appACLs
|
return &appACLs
|
||||||
}
|
}
|
||||||
@@ -42,11 +90,11 @@ func (acls *AccessControlsService) lookupNonDockerACLs(appDomain string) *config
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (acls *AccessControlsService) GetAccessControls(appDomain string) (config.App, error) {
|
func (acls *AccessControlsService) GetAccessControls(appDomain string) (config.App, error) {
|
||||||
// First check non-docker apps
|
// First check environment variables
|
||||||
envACLs := acls.lookupNonDockerACLs(appDomain)
|
envACLs := acls.lookupEnvACLs(appDomain)
|
||||||
|
|
||||||
if envACLs != nil {
|
if envACLs != nil {
|
||||||
log.Debug().Str("domain", appDomain).Msg("Found matching access controls in environment variables or flags")
|
log.Debug().Str("domain", appDomain).Msg("Found matching access controls in environment variables")
|
||||||
return *envACLs, nil
|
return *envACLs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ func (docker *DockerService) GetLabels(appDomain string) (config.App, error) {
|
|||||||
return config.App{}, err
|
return config.App{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
labels, err := decoders.DecodeLabels[config.Apps](inspect.Config.Labels)
|
labels, err := decoders.DecodeLabels(inspect.Config.Labels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return config.App{}, err
|
return config.App{}, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,11 +134,20 @@ func GetLogLevel(level string) zerolog.Level {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOAuthProvidersConfig(environ []string, args []string, appUrl string) (map[string]config.OAuthServiceConfig, error) {
|
func GetOAuthProvidersConfig(env []string, args []string, appUrl string) (map[string]config.OAuthServiceConfig, error) {
|
||||||
providers := make(map[string]config.OAuthServiceConfig)
|
providers := make(map[string]config.OAuthServiceConfig)
|
||||||
|
|
||||||
// Get from environment variables
|
// Get from environment variables
|
||||||
envProviders, err := decoders.DecodeEnv[config.Providers](environ)
|
envMap := make(map[string]string)
|
||||||
|
|
||||||
|
for _, e := range env {
|
||||||
|
pair := strings.SplitN(e, "=", 2)
|
||||||
|
if len(pair) == 2 {
|
||||||
|
envMap[pair[0]] = pair[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
envProviders, err := decoders.DecodeEnv[config.Providers, config.OAuthServiceConfig](envMap, "providers")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -146,14 +155,25 @@ func GetOAuthProvidersConfig(environ []string, args []string, appUrl string) (ma
|
|||||||
|
|
||||||
maps.Copy(providers, envProviders.Providers)
|
maps.Copy(providers, envProviders.Providers)
|
||||||
|
|
||||||
// Get from args
|
// Get from flags
|
||||||
argProviders, err := decoders.DecodeFlags[config.Providers](args)
|
flagsMap := make(map[string]string)
|
||||||
|
|
||||||
|
for _, arg := range args[1:] {
|
||||||
|
if strings.HasPrefix(arg, "--") {
|
||||||
|
pair := strings.SplitN(arg[2:], "=", 2)
|
||||||
|
if len(pair) == 2 {
|
||||||
|
flagsMap[pair[0]] = pair[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flagProviders, err := decoders.DecodeFlags[config.Providers, config.OAuthServiceConfig](flagsMap, "providers")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
maps.Copy(providers, argProviders.Providers)
|
maps.Copy(providers, flagProviders.Providers)
|
||||||
|
|
||||||
// For every provider get correct secret from file if set
|
// For every provider get correct secret from file if set
|
||||||
for name, provider := range providers {
|
for name, provider := range providers {
|
||||||
@@ -188,28 +208,3 @@ func GetOAuthProvidersConfig(environ []string, args []string, appUrl string) (ma
|
|||||||
// Return combined providers
|
// Return combined providers
|
||||||
return providers, nil
|
return providers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetACLS(environ []string, args []string) (config.Apps, error) {
|
|
||||||
acls := config.Apps{}
|
|
||||||
acls.Apps = make(map[string]config.App)
|
|
||||||
|
|
||||||
// Get from environment variables
|
|
||||||
envACLs, err := decoders.DecodeEnv[config.Apps](environ)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return config.Apps{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
maps.Copy(acls.Apps, envACLs.Apps)
|
|
||||||
|
|
||||||
// Get from args
|
|
||||||
argACLs, err := decoders.DecodeFlags[config.Apps](args)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return config.Apps{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
maps.Copy(acls.Apps, argACLs.Apps)
|
|
||||||
|
|
||||||
return acls, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -238,8 +238,8 @@ func TestIsRedirectSafeMultiLevel(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetOAuthProvidersConfig(t *testing.T) {
|
func TestGetOAuthProvidersConfig(t *testing.T) {
|
||||||
env := []string{"PROVIDERS_CLIENT1_CLIENTID=client1-id", "PROVIDERS_CLIENT1_CLIENTSECRET=client1-secret"}
|
env := []string{"PROVIDERS_CLIENT1_CLIENT_ID=client1-id", "PROVIDERS_CLIENT1_CLIENT_SECRET=client1-secret"}
|
||||||
args := []string{"/tinyauth/tinyauth", "--providers-client2-clientid=client2-id", "--providers-client2-clientsecret=client2-secret"}
|
args := []string{"/tinyauth/tinyauth", "--providers-client2-client-id=client2-id", "--providers-client2-client-secret=client2-secret"}
|
||||||
|
|
||||||
expected := map[string]config.OAuthServiceConfig{
|
expected := map[string]config.OAuthServiceConfig{
|
||||||
"client1": {
|
"client1": {
|
||||||
@@ -278,7 +278,7 @@ func TestGetOAuthProvidersConfig(t *testing.T) {
|
|||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
defer os.Remove("/tmp/tinyauth_test_file")
|
defer os.Remove("/tmp/tinyauth_test_file")
|
||||||
|
|
||||||
env = []string{"PROVIDERS_CLIENT1_CLIENTID=client1-id", "PROVIDERS_CLIENT1_CLIENTSECRETFILE=/tmp/tinyauth_test_file"}
|
env = []string{"PROVIDERS_CLIENT1_CLIENT_ID=client1-id", "PROVIDERS_CLIENT1_CLIENT_SECRET_FILE=/tmp/tinyauth_test_file"}
|
||||||
args = []string{"/tinyauth/tinyauth"}
|
args = []string{"/tinyauth/tinyauth"}
|
||||||
expected = map[string]config.OAuthServiceConfig{
|
expected = map[string]config.OAuthServiceConfig{
|
||||||
"client1": {
|
"client1": {
|
||||||
@@ -293,7 +293,7 @@ func TestGetOAuthProvidersConfig(t *testing.T) {
|
|||||||
assert.DeepEqual(t, expected, result)
|
assert.DeepEqual(t, expected, result)
|
||||||
|
|
||||||
// Case with google provider and no redirect URL
|
// Case with google provider and no redirect URL
|
||||||
env = []string{"PROVIDERS_GOOGLE_CLIENTID=google-id", "PROVIDERS_GOOGLE_CLIENTSECRET=google-secret"}
|
env = []string{"PROVIDERS_GOOGLE_CLIENT_ID=google-id", "PROVIDERS_GOOGLE_CLIENT_SECRET=google-secret"}
|
||||||
args = []string{"/tinyauth/tinyauth"}
|
args = []string{"/tinyauth/tinyauth"}
|
||||||
expected = map[string]config.OAuthServiceConfig{
|
expected = map[string]config.OAuthServiceConfig{
|
||||||
"google": {
|
"google": {
|
||||||
@@ -308,39 +308,3 @@ func TestGetOAuthProvidersConfig(t *testing.T) {
|
|||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.DeepEqual(t, expected, result)
|
assert.DeepEqual(t, expected, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetACLS(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
env := []string{"TINYAUTH_APPS_APP1_CONFIG_DOMAIN=app1.com", "TINYAUTH_APPS_APP2_CONFIG_DOMAIN=app2.com"}
|
|
||||||
args := []string{"--apps-app3-config-domain=app3.com", "--apps-app4-config-domain=app4.com"}
|
|
||||||
|
|
||||||
expected := config.Apps{
|
|
||||||
Apps: map[string]config.App{
|
|
||||||
"app1": {
|
|
||||||
Config: config.AppConfig{
|
|
||||||
Domain: "app1.com",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"app2": {
|
|
||||||
Config: config.AppConfig{
|
|
||||||
Domain: "app2.com",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"app3": {
|
|
||||||
Config: config.AppConfig{
|
|
||||||
Domain: "app3.com",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"app4": {
|
|
||||||
Config: config.AppConfig{
|
|
||||||
Domain: "app4.com",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test
|
|
||||||
result, err := utils.GetACLS(env, args)
|
|
||||||
assert.NilError(t, err)
|
|
||||||
assert.DeepEqual(t, expected, result)
|
|
||||||
}
|
|
||||||
|
|||||||
76
internal/utils/decoders/decoders.go
Normal file
76
internal/utils/decoders/decoders.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package decoders
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/stoewer/go-strcase"
|
||||||
|
)
|
||||||
|
|
||||||
|
func normalizeKeys[T any](input map[string]string, root string, sep string) map[string]string {
|
||||||
|
knownKeys := getKnownKeys[T]()
|
||||||
|
normalized := make(map[string]string)
|
||||||
|
|
||||||
|
for k, v := range input {
|
||||||
|
parts := []string{"tinyauth"}
|
||||||
|
|
||||||
|
key := strings.ToLower(k)
|
||||||
|
key = strings.ReplaceAll(key, sep, "-")
|
||||||
|
|
||||||
|
suffix := ""
|
||||||
|
|
||||||
|
for _, known := range knownKeys {
|
||||||
|
if strings.HasSuffix(key, known) {
|
||||||
|
suffix = known
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if suffix == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = append(parts, root)
|
||||||
|
|
||||||
|
id := strings.TrimPrefix(key, root+"-")
|
||||||
|
id = strings.TrimSuffix(id, "-"+suffix)
|
||||||
|
|
||||||
|
if id == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = append(parts, id)
|
||||||
|
parts = append(parts, suffix)
|
||||||
|
|
||||||
|
final := ""
|
||||||
|
|
||||||
|
for i, part := range parts {
|
||||||
|
if i > 0 {
|
||||||
|
final += "."
|
||||||
|
}
|
||||||
|
final += strcase.LowerCamelCase(part)
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized[final] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
func getKnownKeys[T any]() []string {
|
||||||
|
var keys []string
|
||||||
|
var t T
|
||||||
|
|
||||||
|
v := reflect.ValueOf(t)
|
||||||
|
typeOfT := v.Type()
|
||||||
|
|
||||||
|
for field := range typeOfT.NumField() {
|
||||||
|
if typeOfT.Field(field).Tag.Get("field") != "" {
|
||||||
|
keys = append(keys, typeOfT.Field(field).Tag.Get("field"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keys = append(keys, strcase.KebabCase(typeOfT.Field(field).Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys
|
||||||
|
}
|
||||||
@@ -1,17 +1,19 @@
|
|||||||
package decoders
|
package decoders
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/traefik/paerser/env"
|
"github.com/traefik/paerser/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DecodeEnv[T any](environ []string) (T, error) {
|
func DecodeEnv[T any, C any](env map[string]string, subName string) (T, error) {
|
||||||
var target T
|
var result T
|
||||||
|
|
||||||
err := env.Decode(environ, "TINYAUTH_", &target)
|
normalized := normalizeKeys[C](env, subName, "_")
|
||||||
|
|
||||||
|
err := parser.Decode(normalized, &result, "tinyauth", "tinyauth."+subName)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return target, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return target, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ import (
|
|||||||
|
|
||||||
func TestDecodeEnv(t *testing.T) {
|
func TestDecodeEnv(t *testing.T) {
|
||||||
// Setup
|
// Setup
|
||||||
env := []string{
|
env := map[string]string{
|
||||||
"TINYAUTH_PROVIDERS_GOOGLE_CLIENTID=google-client-id",
|
"PROVIDERS_GOOGLE_CLIENT_ID": "google-client-id",
|
||||||
"TINYAUTH_PROVIDERS_GOOGLE_CLIENTSECRET=google-client-secret",
|
"PROVIDERS_GOOGLE_CLIENT_SECRET": "google-client-secret",
|
||||||
"TINYAUTH_PROVIDERS_GITHUB_CLIENTID=github-client-id",
|
"PROVIDERS_MY_GITHUB_CLIENT_ID": "github-client-id",
|
||||||
"TINYAUTH_PROVIDERS_GITHUB_CLIENTSECRET=github-client-secret",
|
"PROVIDERS_MY_GITHUB_CLIENT_SECRET": "github-client-secret",
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := config.Providers{
|
expected := config.Providers{
|
||||||
@@ -23,7 +23,7 @@ func TestDecodeEnv(t *testing.T) {
|
|||||||
ClientID: "google-client-id",
|
ClientID: "google-client-id",
|
||||||
ClientSecret: "google-client-secret",
|
ClientSecret: "google-client-secret",
|
||||||
},
|
},
|
||||||
"github": {
|
"myGithub": {
|
||||||
ClientID: "github-client-id",
|
ClientID: "github-client-id",
|
||||||
ClientSecret: "github-client-secret",
|
ClientSecret: "github-client-secret",
|
||||||
},
|
},
|
||||||
@@ -31,7 +31,7 @@ func TestDecodeEnv(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Execute
|
// Execute
|
||||||
result, err := decoders.DecodeEnv[config.Providers](env)
|
result, err := decoders.DecodeEnv[config.Providers, config.OAuthServiceConfig](env, "providers")
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.DeepEqual(t, result, expected)
|
assert.DeepEqual(t, result, expected)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,32 +3,28 @@ package decoders
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/traefik/paerser/flag"
|
"github.com/traefik/paerser/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DecodeFlags[T any](args []string) (T, error) {
|
func DecodeFlags[T any, C any](flags map[string]string, subName string) (T, error) {
|
||||||
var target T
|
var result T
|
||||||
var formatted = []string{}
|
|
||||||
|
|
||||||
for _, arg := range args {
|
filtered := filterFlags(flags)
|
||||||
argFmt := strings.TrimPrefix(arg, "--")
|
normalized := normalizeKeys[C](filtered, subName, "_")
|
||||||
argParts := strings.SplitN(argFmt, "=", 2)
|
|
||||||
|
|
||||||
if len(argParts) != 2 {
|
err := parser.Decode(normalized, &result, "tinyauth", "tinyauth."+subName)
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
key := argParts[0]
|
|
||||||
value := argParts[1]
|
|
||||||
|
|
||||||
formatted = append(formatted, "--"+strings.ReplaceAll(key, "-", ".")+"="+value)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := flag.Decode(formatted, &target)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return target, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return target, nil
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterFlags(flags map[string]string) map[string]string {
|
||||||
|
filtered := make(map[string]string)
|
||||||
|
for k, v := range flags {
|
||||||
|
filtered[strings.TrimPrefix(k, "--")] = v
|
||||||
|
}
|
||||||
|
return filtered
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ import (
|
|||||||
|
|
||||||
func TestDecodeFlags(t *testing.T) {
|
func TestDecodeFlags(t *testing.T) {
|
||||||
// Setup
|
// Setup
|
||||||
args := []string{
|
flags := map[string]string{
|
||||||
"--providers-google-clientid=google-client-id",
|
"--providers-google-client-id": "google-client-id",
|
||||||
"--providers-google-clientsecret=google-client-secret",
|
"--providers-google-client-secret": "google-client-secret",
|
||||||
"--providers-github-clientid=github-client-id",
|
"--providers-my-github-client-id": "github-client-id",
|
||||||
"--providers-github-clientsecret=github-client-secret",
|
"--providers-my-github-client-secret": "github-client-secret",
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := config.Providers{
|
expected := config.Providers{
|
||||||
@@ -23,7 +23,7 @@ func TestDecodeFlags(t *testing.T) {
|
|||||||
ClientID: "google-client-id",
|
ClientID: "google-client-id",
|
||||||
ClientSecret: "google-client-secret",
|
ClientSecret: "google-client-secret",
|
||||||
},
|
},
|
||||||
"github": {
|
"myGithub": {
|
||||||
ClientID: "github-client-id",
|
ClientID: "github-client-id",
|
||||||
ClientSecret: "github-client-secret",
|
ClientSecret: "github-client-secret",
|
||||||
},
|
},
|
||||||
@@ -31,7 +31,7 @@ func TestDecodeFlags(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Execute
|
// Execute
|
||||||
result, err := decoders.DecodeFlags[config.Providers](args)
|
result, err := decoders.DecodeFlags[config.Providers, config.OAuthServiceConfig](flags, "providers")
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.DeepEqual(t, result, expected)
|
assert.DeepEqual(t, result, expected)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
package decoders
|
package decoders
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"tinyauth/internal/config"
|
||||||
|
|
||||||
"github.com/traefik/paerser/parser"
|
"github.com/traefik/paerser/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DecodeLabels[T any](labels map[string]string) (T, error) {
|
func DecodeLabels(labels map[string]string) (config.Apps, error) {
|
||||||
var target T
|
var appLabels config.Apps
|
||||||
|
|
||||||
err := parser.Decode(labels, &target, "tinyauth")
|
err := parser.Decode(labels, &appLabels, "tinyauth", "tinyauth.apps")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return target, err
|
return config.Apps{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return target, nil
|
return appLabels, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,24 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestDecodeLabels(t *testing.T) {
|
func TestDecodeLabels(t *testing.T) {
|
||||||
// Setup
|
// Variables
|
||||||
labels := map[string]string{
|
|
||||||
"tinyauth.apps.foo.config.domain": "example.com",
|
|
||||||
"tinyauth.apps.foo.users.allow": "user1,user2",
|
|
||||||
"tinyauth.apps.foo.users.block": "user3",
|
|
||||||
"tinyauth.apps.foo.oauth.whitelist": "somebody@example.com",
|
|
||||||
"tinyauth.apps.foo.oauth.groups": "group3",
|
|
||||||
"tinyauth.apps.foo.ip.allow": "10.71.0.1/24,10.71.0.2",
|
|
||||||
"tinyauth.apps.foo.ip.block": "10.10.10.10,10.0.0.0/24",
|
|
||||||
"tinyauth.apps.foo.ip.bypass": "192.168.1.1",
|
|
||||||
"tinyauth.apps.foo.response.headers": "X-Foo=Bar,X-Baz=Qux",
|
|
||||||
"tinyauth.apps.foo.response.basicauth.username": "admin",
|
|
||||||
"tinyauth.apps.foo.response.basicauth.password": "password",
|
|
||||||
"tinyauth.apps.foo.response.basicauth.passwordfile": "/path/to/passwordfile",
|
|
||||||
"tinyauth.apps.foo.path.allow": "/public",
|
|
||||||
"tinyauth.apps.foo.path.block": "/private",
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := config.Apps{
|
expected := config.Apps{
|
||||||
Apps: map[string]config.App{
|
Apps: map[string]config.App{
|
||||||
"foo": {
|
"foo": {
|
||||||
@@ -61,9 +44,25 @@ func TestDecodeLabels(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
test := map[string]string{
|
||||||
|
"tinyauth.apps.foo.config.domain": "example.com",
|
||||||
|
"tinyauth.apps.foo.users.allow": "user1,user2",
|
||||||
|
"tinyauth.apps.foo.users.block": "user3",
|
||||||
|
"tinyauth.apps.foo.oauth.whitelist": "somebody@example.com",
|
||||||
|
"tinyauth.apps.foo.oauth.groups": "group3",
|
||||||
|
"tinyauth.apps.foo.ip.allow": "10.71.0.1/24,10.71.0.2",
|
||||||
|
"tinyauth.apps.foo.ip.block": "10.10.10.10,10.0.0.0/24",
|
||||||
|
"tinyauth.apps.foo.ip.bypass": "192.168.1.1",
|
||||||
|
"tinyauth.apps.foo.response.headers": "X-Foo=Bar,X-Baz=Qux",
|
||||||
|
"tinyauth.apps.foo.response.basicauth.username": "admin",
|
||||||
|
"tinyauth.apps.foo.response.basicauth.password": "password",
|
||||||
|
"tinyauth.apps.foo.response.basicauth.passwordfile": "/path/to/passwordfile",
|
||||||
|
"tinyauth.apps.foo.path.allow": "/public",
|
||||||
|
"tinyauth.apps.foo.path.block": "/private",
|
||||||
|
}
|
||||||
|
|
||||||
// Test
|
// Test
|
||||||
result, err := decoders.DecodeLabels[config.Apps](labels)
|
result, err := decoders.DecodeLabels(test)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.DeepEqual(t, expected, result)
|
assert.DeepEqual(t, expected, result)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user