feat: experimental option to run tinyauth in oauth bridge mode (#1027)

This commit is contained in:
Stavros
2026-07-20 00:03:26 +03:00
committed by GitHub
parent 4db1b33034
commit 0e7bdf6cd5
5 changed files with 91 additions and 40 deletions
+5
View File
@@ -223,6 +223,11 @@ TINYAUTH_LDAP_AUTHKEY=
# Cache duration for LDAP group membership in seconds.
TINYAUTH_LDAP_GROUPCACHETTL=900
# experimental config
# Enable the OAuth bridge, uses a new way to format OAuth user information.
TINYAUTH_EXPERIMENTAL_OAUTHBRIDGEENABLED=false
# tailscale config
# Enable Tailscale integration.
+5 -4
View File
@@ -3,6 +3,7 @@ package main
import (
"fmt"
"os"
"reflect"
"strings"
"charm.land/huh/v2"
@@ -32,10 +33,10 @@ func main() {
Resources: loaders,
Run: func(_ []string) error {
// enable this on experimental features
//if !reflect.DeepEqual(model.NewDefaultConfiguration(env).Experimental, tConfig.Experimental) {
// colors := getColors()
// fmt.Println(colors.yellow.Render("⚠") + " Experimental features are enabled, use with caution. Experimental features may change with each release.")
//}
if !reflect.DeepEqual(model.NewDefaultConfiguration(env).Experimental, tConfig.Experimental) {
colors := getColors()
fmt.Println(colors.yellow.Render("⚠") + " Experimental features are enabled, use with caution. Experimental features may change with each release.")
}
return runCmd(*tConfig)
},
}
+64 -27
View File
@@ -220,35 +220,16 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
return
}
var name string
if strings.TrimSpace(user.Name) != "" {
controller.log.App.Debug().Msg("Using name from OAuth provider")
name = user.Name
} else {
controller.log.App.Debug().Msg("No name from OAuth provider, generating from email")
parts := strings.SplitN(user.Email, "@", 2)
if len(parts) == 2 {
name = fmt.Sprintf("%s (%s)", utils.Capitalize(parts[0]), parts[1])
} else {
name = utils.Capitalize(user.Email)
}
}
var username string
if strings.TrimSpace(user.PreferredUsername) != "" {
controller.log.App.Debug().Msg("Using preferred username from OAuth provider")
username = user.PreferredUsername
} else {
controller.log.App.Debug().Msg("No preferred username from OAuth provider, generating from email")
username = strings.Replace(user.Email, "@", "_", 1)
}
oauthUserInfo := controller.createOAuthUserInfo(oauthUserInfo{
Username: user.PreferredUsername,
Email: user.Email,
Name: user.Name,
})
sessionCookie := repository.Session{
Username: username,
Name: name,
Email: user.Email,
Username: oauthUserInfo.Username,
Name: oauthUserInfo.Name,
Email: oauthUserInfo.Email,
Provider: svc.ID(),
OAuthGroups: utils.CoalesceToString(user.Groups),
OAuthName: svc.Name(),
@@ -355,3 +336,59 @@ func (controller *OAuthController) isRedirectSafe(redirectURI string) bool {
return false
}
type oauthUserInfo struct {
Email string
Username string
Name string
}
func (controller *OAuthController) createOAuthUserInfo(input oauthUserInfo) oauthUserInfo {
info := oauthUserInfo{
Email: input.Email,
}
if controller.config.Experimental.OAuthBridgeEnabled {
if input.Username != "" {
info.Username = input.Username
} else {
parts := strings.SplitN(input.Email, "@", 2)
if len(parts) != 2 {
controller.log.App.Error().Str("email", input.Email).Msg("Invalid email address")
} else {
info.Username = parts[0]
}
}
if input.Name != "" {
info.Name = input.Name
} else {
info.Name = utils.Capitalize(info.Username)
}
return info
}
if input.Name != "" {
controller.log.App.Debug().Msg("Using name from OAuth provider")
info.Name = input.Name
} else {
controller.log.App.Debug().Msg("No name from OAuth provider, generating from email")
parts := strings.SplitN(input.Email, "@", 2)
if len(parts) != 2 {
controller.log.App.Error().Str("email", input.Email).Msg("Invalid email address")
} else {
info.Name = fmt.Sprintf("%s (%s)", utils.Capitalize(parts[0]), parts[1])
}
}
if input.Username != "" {
controller.log.App.Debug().Msg("Using preferred username from OAuth provider")
info.Username = input.Username
} else {
controller.log.App.Debug().Msg("No preferred username from OAuth provider, generating from email")
info.Username = strings.Replace(info.Email, "@", "_", 1)
}
return info
}
+9 -3
View File
@@ -40,6 +40,7 @@ var (
type ContextMiddleware struct {
log *logger.Logger
runtime *model.RuntimeConfig
config *model.Config
auth *service.AuthService
broker *service.OAuthBrokerService
tailscale *service.TailscaleService
@@ -50,6 +51,7 @@ type ContextMiddlewareInput struct {
Log *logger.Logger
RuntimeConfig *model.RuntimeConfig
StaticConfig *model.Config
AuthService *service.AuthService
BrokerService *service.OAuthBrokerService
TailscaleService *service.TailscaleService
@@ -59,6 +61,7 @@ func NewContextMiddleware(i ContextMiddlewareInput) *ContextMiddleware {
return &ContextMiddleware{
log: i.Log,
runtime: i.RuntimeConfig,
config: i.StaticConfig,
auth: i.AuthService,
broker: i.BrokerService,
tailscale: i.TailscaleService,
@@ -332,16 +335,19 @@ func (m *ContextMiddleware) tailscaleWhois(ip string) (*model.TailscaleContext,
return nil, nil
}
username := strings.Replace(whois.LoginName, "@", "_", 1)
uctx := model.TailscaleContext{
BaseContext: model.BaseContext{
Username: username,
Email: whois.LoginName,
Name: whois.DisplayName,
},
NodeName: whois.NodeName,
}
if m.config.Experimental.OAuthBridgeEnabled {
uctx.BaseContext.Username = strings.SplitN(whois.LoginName, "@", 2)[0]
} else {
uctx.BaseContext.Username = strings.Replace(whois.LoginName, "@", "_", 1)
}
return &uctx, nil
}
+4 -2
View File
@@ -115,7 +115,7 @@ type Config struct {
UI UIConfig `description:"UI customization." yaml:"ui,omitempty"`
LDAP LDAPConfig `description:"LDAP configuration." yaml:"ldap,omitempty"`
// enable the cli warning on experimental features
//Experimental ExperimentalConfig `description:"Experimental features, use with caution." yaml:"experimental,omitempty"`
Experimental ExperimentalConfig `description:"Experimental features, use with caution." yaml:"experimental,omitempty"`
Tailscale TailscaleConfig `description:"Tailscale configuration." yaml:"tailscale,omitempty"`
Log LogConfig `description:"Logging configuration." yaml:"log,omitempty"`
}
@@ -238,7 +238,9 @@ type LogStreamConfig struct {
Level string `description:"Log level for this stream. Use global if empty." yaml:"level,omitempty"`
}
//type ExperimentalConfig struct{}
type ExperimentalConfig struct {
OAuthBridgeEnabled bool `description:"Enable the OAuth bridge, uses a new way to format OAuth user information." yaml:"oauthBridgeEnabled,omitempty"`
}
type TailscaleConfig struct {
Enabled bool `description:"Enable Tailscale integration." yaml:"enabled,omitempty"`