package auth import ( "crypto/tls" "fmt" "os" "github.com/go-ldap/ldap/v3" ) type LDAPConfig struct { URL string // ldap://host:389 or ldaps://host:636 BindDN string // cn=admin,dc=example,dc=com (for search) BindPass string BaseDN string // dc=example,dc=com UserFilter string // (&(objectClass=inetOrgPerson)(uid=%s)) SkipTLS bool } func LDAPConfigFromEnv() *LDAPConfig { url := os.Getenv("MH_LDAP_URL") if url == "" { return nil } filter := os.Getenv("MH_LDAP_USER_FILTER") if filter == "" { filter = "(&(objectClass=inetOrgPerson)(uid=%s))" } return &LDAPConfig{ URL: url, BindDN: os.Getenv("MH_LDAP_BIND_DN"), BindPass: os.Getenv("MH_LDAP_BIND_PASS"), BaseDN: os.Getenv("MH_LDAP_BASE_DN"), UserFilter: filter, SkipTLS: os.Getenv("MH_LDAP_SKIP_TLS") == "true", } } // LDAPAuth attempts to authenticate a user via LDAP. // Returns (email, displayName, error). func LDAPAuth(cfg *LDAPConfig, username, password string) (string, string, error) { conn, err := ldap.DialURL(cfg.URL, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: cfg.SkipTLS})) if err != nil { return "", "", fmt.Errorf("ldap connect: %w", err) } defer conn.Close() // Bind with service account to search if cfg.BindDN != "" { if err := conn.Bind(cfg.BindDN, cfg.BindPass); err != nil { return "", "", fmt.Errorf("ldap bind: %w", err) } } // Search for user filter := fmt.Sprintf(cfg.UserFilter, ldap.EscapeFilter(username)) sr, err := conn.Search(ldap.NewSearchRequest( cfg.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 10, false, filter, []string{"dn", "mail", "cn", "uid"}, nil, )) if err != nil || len(sr.Entries) == 0 { return "", "", fmt.Errorf("ldap user not found") } entry := sr.Entries[0] userDN := entry.DN // Bind as user to verify password if err := conn.Bind(userDN, password); err != nil { return "", "", fmt.Errorf("ldap auth failed") } email := entry.GetAttributeValue("mail") if email == "" { email = username + "@ldap" } displayName := entry.GetAttributeValue("cn") if displayName == "" { displayName = entry.GetAttributeValue("uid") } return email, displayName, nil }