Skip to content

Commit 9578e40

Browse files
committed
chore: fix golangci-lint errors and clean code formatting
1 parent 68a6d3f commit 9578e40

File tree

3 files changed

+398
-218
lines changed

3 files changed

+398
-218
lines changed

internal/auth/ldapauth/ldapauth.go

Lines changed: 203 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,159 +1,264 @@
11
package ldapauth
22

33
import (
4-
"fmt"
5-
"time"
4+
"errors"
5+
"fmt"
6+
"log"
7+
"time"
68

7-
"github.com/go-ldap/ldap/v3"
8-
"github.com/golang-jwt/jwt/v4"
9+
"github.com/go-ldap/ldap/v3"
10+
"github.com/golang-jwt/jwt/v4"
11+
)
12+
13+
var (
14+
ErrUserNotFound = errors.New("user not found or multiple entries returned")
15+
16+
ErrTokenSigningEmptyKey = errors.New("token signing error: no secret configured")
17+
18+
ErrUnexpectedSigningMethod = errors.New("unexpected signing method")
19+
20+
ErrInvalidToken = errors.New("invalid token")
21+
22+
ErrInvalidClaims = errors.New("invalid claims")
23+
24+
ErrMissingSubClaim = errors.New("missing sub claim")
925
)
1026

1127
// Conn abstracts ldap.Conn methods for easier testing.
28+
1229
type Conn interface {
13-
Bind(username, password string) error
14-
Search(searchReq *ldap.SearchRequest) (*ldap.SearchResult, error)
15-
Close() error
30+
Bind(username, password string) error
31+
32+
Search(searchReq *ldap.SearchRequest) (*ldap.SearchResult, error)
33+
34+
Close() error
1635
}
1736

1837
// dialerFunc connects to LDAP and returns a Conn.
38+
1939
type dialerFunc func(addr string, opts ...ldap.DialOpt) (Conn, error)
2040

2141
// defaultDialer uses ldap.DialURL under the hood.
42+
2243
func defaultDialer(addr string, opts ...ldap.DialOpt) (Conn, error) {
23-
return ldap.DialURL(addr, opts...)
44+
45+
return ldap.DialURL(addr, opts...)
46+
2447
}
2548

2649
// Config holds LDAP and JWT settings.
50+
2751
type Config struct {
28-
Addr string // LDAP server address (e.g., "localhost:389" or "ldap.example.com:389")
29-
BaseDN string // Base distinguished name (DN) used to search for users (e.g., "dc=example,dc=com")
30-
BindUserDN string // Optional: DN of a service account to perform search operations.
31-
// Required if anonymous search is disabled on the LDAP server.
32-
33-
BindPassword string // Optional: Password for the service account specified in BindUserDN.
34-
// Required only if BindUserDN is set.
35-
JWTSecret string // Secret key used to sign JWT tokens issued after successful authentication.
52+
Addr string // LDAP server address (e.g., "localhost:389" or "ldap.example.com:389")
53+
54+
BaseDN string // Base distinguished name (DN) used to search for users (e.g., "dc=example,dc=com")
55+
56+
BindUserDN string // Optional: DN of a service account to perform search operations.
57+
58+
// Required if anonymous search is disabled on the LDAP server.
59+
60+
BindPassword string // Optional: Password for the service account specified in BindUserDN.
61+
62+
// Required only if BindUserDN is set.
63+
64+
JWTSecret string // Secret key used to sign JWT tokens issued after successful authentication.
65+
3666
}
3767

3868
// Authenticator handles LDAP authentication and JWT issuance.
69+
3970
type Authenticator struct {
40-
cfg Config
41-
dialFn dialerFunc
71+
cfg Config
72+
73+
dialFn dialerFunc
4274
}
4375

4476
// New returns an Authenticator with the default dialer.
77+
4578
func New(cfg Config) *Authenticator {
46-
return &Authenticator{
47-
cfg: cfg,
48-
dialFn: defaultDialer,
49-
}
79+
80+
return &Authenticator{
81+
82+
cfg: cfg,
83+
84+
dialFn: defaultDialer,
85+
}
86+
5087
}
5188

5289
// WithDialer allows injecting a custom dialer (for tests).
90+
5391
func (a *Authenticator) WithDialer(d dialerFunc) {
54-
a.dialFn = d
92+
93+
a.dialFn = d
94+
5595
}
5696

5797
// Authenticate binds to LDAP, validates credentials, and returns a signed JWT.
98+
5899
func (a *Authenticator) Authenticate(username, password string) (string, error) {
59-
conn, err := a.dialFn("ldap://" + a.cfg.Addr)
60-
if err != nil {
61-
return "", fmt.Errorf("failed to connect LDAP: %w", err)
62-
}
63-
defer conn.Close()
64100

65-
if a.cfg.BindUserDN != "" {
66-
if err := conn.Bind(a.cfg.BindUserDN, a.cfg.BindPassword); err != nil {
67-
return "", fmt.Errorf("service bind failed: %w", err)
101+
conn, err := a.dialFn("ldap://" + a.cfg.Addr)
102+
103+
if err != nil {
104+
105+
return "", fmt.Errorf("failed to connect LDAP: %w", err)
106+
107+
}
108+
109+
defer conn.Close()
110+
111+
if a.cfg.BindUserDN != "" {
112+
113+
bindErr := conn.Bind(a.cfg.BindUserDN, a.cfg.BindPassword)
114+
if bindErr != nil {
115+
return "", fmt.Errorf("service bind failed: %w", bindErr)
68116
}
69-
}
70117

71-
userDN, err := a.lookupUserDN(conn, username)
72-
if err != nil {
73-
return "", err
74-
}
75118

76-
if err := a.bindUser(conn, userDN, password); err != nil {
77-
return "", err
78-
}
119+
}
120+
121+
userDN, err := a.lookupUserDN(conn, username)
122+
123+
if err != nil {
124+
125+
return "", err
79126

80-
token, err := a.generateToken(username)
81-
if err != nil {
82-
return "", err
127+
}
128+
129+
authErr := a.bindUser(conn, userDN, password)
130+
if authErr != nil {
131+
return "", authErr
83132
}
84133

85-
return token, nil
86-
}
87134

135+
token, err := a.generateToken(username)
136+
137+
if err != nil {
138+
139+
return "", err
140+
141+
}
142+
143+
return token, nil
144+
145+
}
88146

89147
func (a *Authenticator) lookupUserDN(conn Conn, username string) (string, error) {
90-
searchReq := ldap.NewSearchRequest(
91-
a.cfg.BaseDN,
92-
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
93-
fmt.Sprintf("(uid=%s)", ldap.EscapeFilter(username)),
94-
[]string{"dn"},
95-
nil,
96-
)
97-
98-
result, err := conn.Search(searchReq)
99-
if err != nil {
100-
return "", fmt.Errorf("search error: %w", err)
101-
}
102148

103-
if len(result.Entries) != 1 {
104-
return "", fmt.Errorf("user not found or multiple entries returned")
105-
}
149+
searchReq := ldap.NewSearchRequest(
150+
151+
a.cfg.BaseDN,
152+
153+
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
154+
155+
fmt.Sprintf("(uid=%s)", ldap.EscapeFilter(username)),
156+
157+
[]string{"dn"},
158+
159+
nil,
160+
)
161+
162+
result, err := conn.Search(searchReq)
163+
164+
if err != nil {
165+
166+
return "", fmt.Errorf("search error: %w", err)
167+
168+
}
169+
170+
if len(result.Entries) != 1 {
171+
172+
return "", ErrUserNotFound
173+
174+
}
175+
176+
return result.Entries[0].DN, nil
106177

107-
return result.Entries[0].DN, nil
108178
}
109179

110180
func (a *Authenticator) bindUser(conn Conn, userDN, password string) error {
111-
if err := conn.Bind(userDN, password); err != nil {
112-
return fmt.Errorf("invalid credentials: %w", err)
113-
}
114-
return nil
181+
182+
if err := conn.Bind(userDN, password); err != nil {
183+
184+
return fmt.Errorf("invalid credentials: %w", err)
185+
186+
}
187+
188+
return nil
189+
115190
}
116191

117192
func (a *Authenticator) generateToken(username string) (string, error) {
118-
if a.cfg.JWTSecret == "" {
119-
return "", fmt.Errorf("token signing error: no secret configured")
120-
}
121193

122-
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
123-
"sub": username,
124-
"exp": time.Now().Add(1 * time.Hour).Unix(),
125-
})
194+
if a.cfg.JWTSecret == "" {
126195

127-
signed, err := token.SignedString([]byte(a.cfg.JWTSecret))
128-
if err != nil {
129-
return "", fmt.Errorf("token signing error: %w", err)
130-
}
196+
return "", ErrTokenSigningEmptyKey
131197

132-
return signed, nil
133-
}
198+
}
199+
200+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
201+
202+
"sub": username,
203+
204+
"exp": time.Now().Add(1 * time.Hour).Unix(),
205+
})
206+
207+
signed, err := token.SignedString([]byte(a.cfg.JWTSecret))
208+
209+
if err != nil {
210+
211+
log.Printf("token signing error: %v", err)
134212

213+
return "", ErrTokenSigningEmptyKey
214+
215+
}
216+
217+
return signed, nil
218+
219+
}
135220

136221
// ValidateToken parses and validates a JWT, returning the 'sub' claim.
222+
137223
func (a *Authenticator) ValidateToken(tokenStr string) (string, error) {
138-
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
139-
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
140-
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
141-
}
142-
return []byte(a.cfg.JWTSecret), nil
143-
})
144-
if err != nil {
145-
return "", fmt.Errorf("token parse error: %w", err)
146-
}
147-
if !token.Valid {
148-
return "", fmt.Errorf("invalid token")
149-
}
150-
claims, ok := token.Claims.(jwt.MapClaims)
151-
if !ok {
152-
return "", fmt.Errorf("invalid claims")
153-
}
154-
sub, ok := claims["sub"].(string)
155-
if !ok {
156-
return "", fmt.Errorf("missing sub claim")
224+
225+
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (any, error) {
226+
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
227+
log.Printf("unexpected signing method: %v", token.Header["alg"])
228+
return nil, ErrUnexpectedSigningMethod
157229
}
158-
return sub, nil
159-
}
230+
return []byte(a.cfg.JWTSecret), nil
231+
})
232+
233+
234+
if err != nil {
235+
236+
return "", fmt.Errorf("token parse error: %w", err)
237+
238+
}
239+
240+
if !token.Valid {
241+
242+
return "", ErrInvalidToken
243+
244+
}
245+
246+
claims, ok := token.Claims.(jwt.MapClaims)
247+
248+
if !ok {
249+
250+
return "", ErrInvalidClaims
251+
252+
}
253+
254+
sub, ok := claims["sub"].(string)
255+
256+
if !ok {
257+
258+
return "", ErrMissingSubClaim
259+
260+
}
261+
262+
return sub, nil
263+
264+
}

0 commit comments

Comments
 (0)