github.com/10ego/gthp@v0.0.0-20241025155251-e1514fa71fbb/internal/auth/ldap.go (about) 1 package auth 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/go-ldap/ldap/v3" 9 ) 10 11 type Client struct { 12 Host string 13 Port int 14 BaseDN string 15 UserFilter string 16 GroupDN string 17 } 18 19 func NewClient(host string, port int, baseDN, userFilter, groupDN string) *Client { 20 return &Client{ 21 Host: host, 22 Port: port, 23 BaseDN: baseDN, 24 UserFilter: userFilter, 25 GroupDN: groupDN, 26 } 27 } 28 29 func (c *Client) Authenticate(ctx context.Context, username, password string) (bool, error) { 30 // Create a new LDAP connection 31 l, err := ldap.DialURL(fmt.Sprintf("ldap://%s:%d", c.Host, c.Port)) 32 if err != nil { 33 return false, fmt.Errorf("failed to connect to LDAP server: %v", err) 34 } 35 defer l.Close() 36 37 // Construct the user's DN 38 userDN := fmt.Sprintf("uid=%s,%s", username, c.UserFilter) 39 40 // Set a timeout for the connection 41 ldapTimeout := 10 * time.Second 42 l.SetTimeout(ldapTimeout) 43 44 // Attempt to bind as the user 45 err = l.Bind(userDN, password) 46 if err != nil { 47 // If binding fails, the credentials are invalid 48 return false, nil 49 } 50 51 // Search for the user to check group membership 52 searchRequest := ldap.NewSearchRequest( 53 c.BaseDN, 54 ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, int(ldapTimeout.Seconds()), false, 55 fmt.Sprintf("(&(objectClass=user)(uid=%s)(memberOf=%s))", ldap.EscapeFilter(username), c.GroupDN), 56 []string{"dn"}, 57 nil, 58 ) 59 60 // Perform the search 61 sr, err := l.Search(searchRequest) 62 if err != nil { 63 return false, fmt.Errorf("LDAP search error: %v", err) 64 } 65 66 // Check if the search was interrupted by context cancellation 67 select { 68 case <-ctx.Done(): 69 return false, ctx.Err() 70 default: 71 // If we found an entry, the user is authenticated and in the correct group 72 return len(sr.Entries) > 0, nil 73 } 74 }