From d5cf076444db1864ec5762093db0943b709098e5 Mon Sep 17 00:00:00 2001 From: Ilyas Date: Sun, 3 May 2026 18:03:18 +0200 Subject: [PATCH] fix(ldap): pass through LDAP mail attribute instead of crafting email TinyAuth was constructing LDAP user emails as username@CookieDomain instead of using the mail attribute stored in the directory. This caused OIDC clients like Grafana to receive a synthetic email rather than the real one. Rename GetUserDN to GetUserInfo and extend it to also fetch the mail attribute in the same LDAP query. Thread the result through UserSearch and use it in both the login flow and the basic auth middleware, falling back to the crafted email only when LDAP returns no mail value. Co-Authored-By: Claude Sonnet 4.6 --- internal/config/config.go | 1 + internal/controller/user_controller.go | 3 +++ internal/middleware/context_middleware.go | 7 ++++++- internal/service/auth_service.go | 3 ++- internal/service/ldap_service.go | 13 ++++++------- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index e364b458..b0dcf2b2 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -268,6 +268,7 @@ type LdapUser struct { type UserSearch struct { Username string Type string // local, ldap or unknown + Email string } type UserContext struct { diff --git a/internal/controller/user_controller.go b/internal/controller/user_controller.go index 187b33b9..2277c959 100644 --- a/internal/controller/user_controller.go +++ b/internal/controller/user_controller.go @@ -172,6 +172,9 @@ func (controller *UserController) loginHandler(c *gin.Context) { if userSearch.Type == "ldap" { sessionCookie.Provider = "ldap" + if userSearch.Email != "" { + sessionCookie.Email = userSearch.Email + } } tlog.App.Trace().Interface("session_cookie", sessionCookie).Msg("Creating session cookie") diff --git a/internal/middleware/context_middleware.go b/internal/middleware/context_middleware.go index 651d9d85..27ab72e7 100644 --- a/internal/middleware/context_middleware.go +++ b/internal/middleware/context_middleware.go @@ -240,10 +240,15 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc { return } + email := utils.CompileUserEmail(basic.Username, m.config.CookieDomain) + if userSearch.Email != "" { + email = userSearch.Email + } + c.Set("context", &config.UserContext{ Username: basic.Username, Name: utils.Capitalize(basic.Username), - Email: utils.CompileUserEmail(basic.Username, m.config.CookieDomain), + Email: email, Provider: "ldap", IsLoggedIn: true, LdapGroups: strings.Join(ldapUser.Groups, ","), diff --git a/internal/service/auth_service.go b/internal/service/auth_service.go index 0311229d..332c7efd 100644 --- a/internal/service/auth_service.go +++ b/internal/service/auth_service.go @@ -123,7 +123,7 @@ func (auth *AuthService) SearchUser(username string) config.UserSearch { } if auth.ldap.IsConfigured() { - userDN, err := auth.ldap.GetUserDN(username) + userDN, email, err := auth.ldap.GetUserInfo(username) if err != nil { tlog.App.Warn().Err(err).Str("username", username).Msg("Failed to search for user in LDAP") @@ -135,6 +135,7 @@ func (auth *AuthService) SearchUser(username string) config.UserSearch { return config.UserSearch{ Username: userDN, Type: "ldap", + Email: email, } } diff --git a/internal/service/ldap_service.go b/internal/service/ldap_service.go index 0963ebf5..9d2ffdae 100644 --- a/internal/service/ldap_service.go +++ b/internal/service/ldap_service.go @@ -143,8 +143,7 @@ func (ldap *LdapService) connect() (*ldapgo.Conn, error) { return ldap.conn, nil } -func (ldap *LdapService) GetUserDN(username string) (string, error) { - // Escape the username to prevent LDAP injection +func (ldap *LdapService) GetUserInfo(username string) (dn string, email string, err error) { escapedUsername := ldapgo.EscapeFilter(username) filter := fmt.Sprintf(ldap.config.SearchFilter, escapedUsername) @@ -152,7 +151,7 @@ func (ldap *LdapService) GetUserDN(username string) (string, error) { ldap.config.BaseDN, ldapgo.ScopeWholeSubtree, ldapgo.NeverDerefAliases, 0, 0, false, filter, - []string{"dn"}, + []string{"dn", "mail"}, nil, ) @@ -161,15 +160,15 @@ func (ldap *LdapService) GetUserDN(username string) (string, error) { searchResult, err := ldap.conn.Search(searchRequest) if err != nil { - return "", err + return "", "", err } if len(searchResult.Entries) != 1 { - return "", fmt.Errorf("multiple or no entries found for user %s", username) + return "", "", fmt.Errorf("multiple or no entries found for user %s", username) } - userDN := searchResult.Entries[0].DN - return userDN, nil + entry := searchResult.Entries[0] + return entry.DN, entry.GetAttributeValue("mail"), nil } func (ldap *LdapService) GetUserGroups(userDN string) ([]string, error) {