From b560a965282765b7a4eb5512242d165319c1e61a Mon Sep 17 00:00:00 2001 From: umang01-hash Date: Wed, 9 Jul 2025 11:45:59 +0530 Subject: [PATCH 1/9] udpdate release version to v1.42.3 --- pkg/gofr/version/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/gofr/version/version.go b/pkg/gofr/version/version.go index afb143154..0169db196 100644 --- a/pkg/gofr/version/version.go +++ b/pkg/gofr/version/version.go @@ -1,3 +1,3 @@ package version -const Framework = "v1.42.2" +const Framework = "v1.42.3" From 4894ffb502588c2178f21fc5531776d36aa8c06d Mon Sep 17 00:00:00 2001 From: mundele2004 Date: Thu, 17 Jul 2025 19:44:58 +0530 Subject: [PATCH 2/9] feat: add LDAP authentication module with JWT issuance --- internal/auth/ldapauth/ldapauth.go | 85 +++++++++++++++++++++++++ internal/auth/ldapauth/ldapauth_test.go | 27 ++++++++ mainFile/main.go | 57 +++++++++++++++++ mainFile/testuser.ldif | 10 +++ 4 files changed, 179 insertions(+) create mode 100644 internal/auth/ldapauth/ldapauth.go create mode 100644 internal/auth/ldapauth/ldapauth_test.go create mode 100644 mainFile/main.go create mode 100644 mainFile/testuser.ldif diff --git a/internal/auth/ldapauth/ldapauth.go b/internal/auth/ldapauth/ldapauth.go new file mode 100644 index 000000000..5fe117353 --- /dev/null +++ b/internal/auth/ldapauth/ldapauth.go @@ -0,0 +1,85 @@ +package ldapauth + +import ( + // "crypto/tls" + "fmt" + "time" + + "github.com/go-ldap/ldap/v3" + "github.com/golang-jwt/jwt/v4" +) + +type Config struct { + Addr string + BaseDN string + BindUserDN string // optional + BindPassword string // optional + JWTSecret string +} + +type dialerFunc func(addr string, opts ...ldap.DialOpt) (*ldap.Conn, error) + + +type Authenticator struct { + cfg Config + dialFn dialerFunc +} + +func New(cfg Config) *Authenticator { + return &Authenticator{ + cfg: cfg, + dialFn: ldap.DialURL, + } +} + +func (a *Authenticator) Authenticate(username, password string) (string, error) { + // l, err := ldap.DialURL("ldap://" + a.cfg.Addr) + l, err := a.dialFn("ldap://" + a.cfg.Addr) + + if err != nil { + return "", fmt.Errorf("failed to connect LDAP: %w", err) + } + defer l.Close() + + // err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) + // if err != nil { + // return "", fmt.Errorf("TLS error: %w", err) + // } + + if a.cfg.BindUserDN != "" { + if err := l.Bind(a.cfg.BindUserDN, a.cfg.BindPassword); err != nil { + return "", fmt.Errorf("bind failed: %w", err) + } + } + + searchReq := ldap.NewSearchRequest( + a.cfg.BaseDN, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + fmt.Sprintf("(uid=%s)", ldap.EscapeFilter(username)), + []string{"dn"}, + nil, + ) + + sr, err := l.Search(searchReq) + if err != nil || len(sr.Entries) != 1 { + return "", fmt.Errorf("user not found") + } + + userDN := sr.Entries[0].DN + + if err := l.Bind(userDN, password); err != nil { + return "", fmt.Errorf("invalid credentials") + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "sub": username, + "exp": time.Now().Add(1 * time.Hour).Unix(), + }) + + signed, err := token.SignedString([]byte(a.cfg.JWTSecret)) + if err != nil { + return "", fmt.Errorf("token signing error: %w", err) + } + + return signed, nil +} diff --git a/internal/auth/ldapauth/ldapauth_test.go b/internal/auth/ldapauth/ldapauth_test.go new file mode 100644 index 000000000..b630f55bd --- /dev/null +++ b/internal/auth/ldapauth/ldapauth_test.go @@ -0,0 +1,27 @@ +package ldapauth + +import ( + "errors" + "testing" + + "github.com/go-ldap/ldap/v3" +) + +func TestAuthenticate_InvalidUser(t *testing.T) { + auth := &Authenticator{ + cfg: Config{ + Addr: "fakehost", + BaseDN: "dc=example,dc=com", + JWTSecret: "testsecret", + }, + dialFn: func(addr string, opts ...ldap.DialOpt) (*ldap.Conn, error) { + return nil, errors.New("dial error") + }, + + } + + _, err := auth.Authenticate("testuser", "testpass") + if err == nil { + t.Fatal("expected error for invalid dial") + } +} diff --git a/mainFile/main.go b/mainFile/main.go new file mode 100644 index 000000000..45fd046d0 --- /dev/null +++ b/mainFile/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + + "ldap-auth-go/internal/ldapauth" +) + +var authenticator *ldapauth.Authenticator + +func main() { + config := ldapauth.Config{ + Addr: "localhost:389", // change this to your LDAP server + BaseDN: "dc=example,dc=com", // adjust as per your directory + BindUserDN: "cn=admin,dc=example,dc=com", // service account (optional) + BindPassword: "admin", // service password + JWTSecret: "your-secret-key", // replace with strong key + } + + authenticator = ldapauth.New(config) + + http.HandleFunc("/login", loginHandler) + log.Println("Server running on http://localhost:8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +} + +func loginHandler(w http.ResponseWriter, r *http.Request) { + var creds struct { + Username string `json:"username"` + Password string `json:"password"` + } + + if err := json.NewDecoder(r.Body).Decode(&creds); err != nil { + http.Error(w, "Invalid JSON", http.StatusBadRequest) + return + } + + token, err := authenticator.Authenticate(creds.Username, creds.Password) + if err != nil { + http.Error(w, fmt.Sprintf("Login failed: %v", err), http.StatusUnauthorized) + return + } + + // json.NewEncoder(w).Encode(map[string]string{ + // "token": token, + // }) + if err := json.NewEncoder(w).Encode(map[string]string{ + "token": token, +}); err != nil { + http.Error(w, "Failed to write response", http.StatusInternalServerError) + return +} + +} diff --git a/mainFile/testuser.ldif b/mainFile/testuser.ldif new file mode 100644 index 000000000..3d40a3035 --- /dev/null +++ b/mainFile/testuser.ldif @@ -0,0 +1,10 @@ +dn: ou=users,dc=example,dc=com +objectClass: organizationalUnit +ou: users + +dn: uid=testuser,ou=users,dc=example,dc=com +objectClass: inetOrgPerson +uid: testuser +sn: User +cn: Test User +userPassword: testpass From 57ecc902bd825572019a2dee1ddc4b40faf6bd5d Mon Sep 17 00:00:00 2001 From: mundele2004 Date: Mon, 21 Jul 2025 18:08:04 +0530 Subject: [PATCH 3/9] test: add edge case tests for LDAP failures and update main.go after final testing --- go.mod | 4 + go.sum | 24 +++++ go.work.sum | 5 + internal/auth/ldapauth/ldapauth.go | 130 +++++++++++++++++++----- internal/auth/ldapauth/ldapauth_test.go | 127 +++++++++++++++++++---- mainFile/main.go | 46 +++------ 6 files changed, 257 insertions(+), 79 deletions(-) diff --git a/go.mod b/go.mod index 343603b2d..473fe093a 100644 --- a/go.mod +++ b/go.mod @@ -9,9 +9,11 @@ require ( github.com/alicebob/miniredis/v2 v2.35.0 github.com/dgraph-io/dgo/v210 v210.0.0-20230328113526-b66f8ae53a2d github.com/eclipse/paho.mqtt.golang v1.5.0 + github.com/go-ldap/ldap/v3 v3.4.11 github.com/go-redis/redismock/v9 v9.2.0 github.com/go-sql-driver/mysql v1.9.3 github.com/gogo/protobuf v1.3.2 + github.com/golang-jwt/jwt/v4 v4.5.2 github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 @@ -53,6 +55,7 @@ require ( cloud.google.com/go/compute/metadata v0.7.0 // indirect cloud.google.com/go/iam v1.5.2 // indirect filippo.io/edwards25519 v1.1.0 // indirect + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v5 v5.0.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -60,6 +63,7 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect diff --git a/go.sum b/go.sum index f0c17b009..3bb33288a 100644 --- a/go.sum +++ b/go.sum @@ -17,11 +17,15 @@ cloud.google.com/go/pubsub v1.49.0 h1:5054IkbslnrMCgA2MAEPcsN3Ky+AyMpEZcii/DoySP cloud.google.com/go/pubsub v1.49.0/go.mod h1:K1FswTWP+C1tI/nfi3HQecoVeFvL4HUOB1tdaNXKhUY= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/XSAM/otelsql v0.39.0 h1:4o374mEIMweaeevL7fd8Q3C710Xi2Jh/c8G4Qy9bvCY= github.com/XSAM/otelsql v0.39.0/go.mod h1:uMOXLUX+wkuAuP0AR3B45NXX7E9lJS2mERa8gqdU8R0= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI= github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -57,7 +61,11 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= +github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU= +github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= @@ -71,6 +79,8 @@ github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI6 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -117,6 +127,20 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDa github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= diff --git a/go.work.sum b/go.work.sum index ec87d4cd5..31056a830 100644 --- a/go.work.sum +++ b/go.work.sum @@ -865,6 +865,7 @@ github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= @@ -1144,6 +1145,7 @@ go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0. go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= @@ -1151,6 +1153,7 @@ go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6c go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= @@ -1163,6 +1166,7 @@ go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzau go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= @@ -1175,6 +1179,7 @@ go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06F go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= diff --git a/internal/auth/ldapauth/ldapauth.go b/internal/auth/ldapauth/ldapauth.go index 5fe117353..2beb229eb 100644 --- a/internal/auth/ldapauth/ldapauth.go +++ b/internal/auth/ldapauth/ldapauth.go @@ -1,7 +1,6 @@ package ldapauth import ( - // "crypto/tls" "fmt" "time" @@ -9,49 +8,85 @@ import ( "github.com/golang-jwt/jwt/v4" ) -type Config struct { - Addr string - BaseDN string - BindUserDN string // optional - BindPassword string // optional - JWTSecret string +// Conn abstracts ldap.Conn methods for easier testing. +type Conn interface { + Bind(username, password string) error + Search(searchReq *ldap.SearchRequest) (*ldap.SearchResult, error) + Close() error } -type dialerFunc func(addr string, opts ...ldap.DialOpt) (*ldap.Conn, error) +// dialerFunc connects to LDAP and returns a Conn. +type dialerFunc func(addr string, opts ...ldap.DialOpt) (Conn, error) + +// defaultDialer uses ldap.DialURL under the hood. +func defaultDialer(addr string, opts ...ldap.DialOpt) (Conn, error) { + return ldap.DialURL(addr, opts...) +} +// Config holds LDAP and JWT settings. +type Config struct { + Addr string // LDAP server address (e.g., "localhost:389" or "ldap.example.com:389") + BaseDN string // Base distinguished name (DN) used to search for users (e.g., "dc=example,dc=com") + BindUserDN string // Optional: DN of a service account to perform search operations. + // Required if anonymous search is disabled on the LDAP server. + + BindPassword string // Optional: Password for the service account specified in BindUserDN. + // Required only if BindUserDN is set. + JWTSecret string // Secret key used to sign JWT tokens issued after successful authentication. +} +// Authenticator handles LDAP authentication and JWT issuance. type Authenticator struct { - cfg Config + cfg Config dialFn dialerFunc } +// New returns an Authenticator with the default dialer. func New(cfg Config) *Authenticator { return &Authenticator{ - cfg: cfg, - dialFn: ldap.DialURL, + cfg: cfg, + dialFn: defaultDialer, } } -func (a *Authenticator) Authenticate(username, password string) (string, error) { - // l, err := ldap.DialURL("ldap://" + a.cfg.Addr) - l, err := a.dialFn("ldap://" + a.cfg.Addr) +// WithDialer allows injecting a custom dialer (for tests). +func (a *Authenticator) WithDialer(d dialerFunc) { + a.dialFn = d +} +// Authenticate binds to LDAP, validates credentials, and returns a signed JWT. +func (a *Authenticator) Authenticate(username, password string) (string, error) { + conn, err := a.dialFn("ldap://" + a.cfg.Addr) if err != nil { return "", fmt.Errorf("failed to connect LDAP: %w", err) } - defer l.Close() - - // err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) - // if err != nil { - // return "", fmt.Errorf("TLS error: %w", err) - // } + defer conn.Close() if a.cfg.BindUserDN != "" { - if err := l.Bind(a.cfg.BindUserDN, a.cfg.BindPassword); err != nil { - return "", fmt.Errorf("bind failed: %w", err) + if err := conn.Bind(a.cfg.BindUserDN, a.cfg.BindPassword); err != nil { + return "", fmt.Errorf("service bind failed: %w", err) } } + userDN, err := a.lookupUserDN(conn, username) + if err != nil { + return "", err + } + + if err := a.bindUser(conn, userDN, password); err != nil { + return "", err + } + + token, err := a.generateToken(username) + if err != nil { + return "", err + } + + return token, nil +} + + +func (a *Authenticator) lookupUserDN(conn Conn, username string) (string, error) { searchReq := ldap.NewSearchRequest( a.cfg.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, @@ -60,15 +95,28 @@ func (a *Authenticator) Authenticate(username, password string) (string, error) nil, ) - sr, err := l.Search(searchReq) - if err != nil || len(sr.Entries) != 1 { - return "", fmt.Errorf("user not found") + result, err := conn.Search(searchReq) + if err != nil { + return "", fmt.Errorf("search error: %w", err) + } + + if len(result.Entries) != 1 { + return "", fmt.Errorf("user not found or multiple entries returned") } - userDN := sr.Entries[0].DN + return result.Entries[0].DN, nil +} + +func (a *Authenticator) bindUser(conn Conn, userDN, password string) error { + if err := conn.Bind(userDN, password); err != nil { + return fmt.Errorf("invalid credentials: %w", err) + } + return nil +} - if err := l.Bind(userDN, password); err != nil { - return "", fmt.Errorf("invalid credentials") +func (a *Authenticator) generateToken(username string) (string, error) { + if a.cfg.JWTSecret == "" { + return "", fmt.Errorf("token signing error: no secret configured") } token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ @@ -83,3 +131,29 @@ func (a *Authenticator) Authenticate(username, password string) (string, error) return signed, nil } + + +// ValidateToken parses and validates a JWT, returning the 'sub' claim. +func (a *Authenticator) ValidateToken(tokenStr string) (string, error) { + token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return []byte(a.cfg.JWTSecret), nil + }) + if err != nil { + return "", fmt.Errorf("token parse error: %w", err) + } + if !token.Valid { + return "", fmt.Errorf("invalid token") + } + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + return "", fmt.Errorf("invalid claims") + } + sub, ok := claims["sub"].(string) + if !ok { + return "", fmt.Errorf("missing sub claim") + } + return sub, nil +} \ No newline at end of file diff --git a/internal/auth/ldapauth/ldapauth_test.go b/internal/auth/ldapauth/ldapauth_test.go index b630f55bd..cf7f55e3e 100644 --- a/internal/auth/ldapauth/ldapauth_test.go +++ b/internal/auth/ldapauth/ldapauth_test.go @@ -1,27 +1,114 @@ package ldapauth import ( - "errors" - "testing" + "errors" + "strings" + "testing" + "time" - "github.com/go-ldap/ldap/v3" + "github.com/go-ldap/ldap/v3" + "github.com/golang-jwt/jwt/v4" ) -func TestAuthenticate_InvalidUser(t *testing.T) { - auth := &Authenticator{ - cfg: Config{ - Addr: "fakehost", - BaseDN: "dc=example,dc=com", - JWTSecret: "testsecret", - }, - dialFn: func(addr string, opts ...ldap.DialOpt) (*ldap.Conn, error) { - return nil, errors.New("dial error") - }, - - } - - _, err := auth.Authenticate("testuser", "testpass") - if err == nil { - t.Fatal("expected error for invalid dial") - } +// mockConn implements Conn for controlled test behavior. +type mockConn struct { + bindErr map[string]error + searchResult *ldap.SearchResult + searchErr error + closed bool } + +func (m *mockConn) Bind(username, password string) error { + if err, ok := m.bindErr[username+"|"+password]; ok { + return err + } + return nil +} + +func (m *mockConn) Search(req *ldap.SearchRequest) (*ldap.SearchResult, error) { + return m.searchResult, m.searchErr +} + +func (m *mockConn) Close() error { + m.closed = true + return nil +} + +func TestAuthenticate(t *testing.T) { + goodEntry := &ldap.Entry{DN: "uid=jdoe,dc=ex,dc=com"} + + cases := []struct { + name string + setupConn func() *mockConn + password string + wantErrSubstr string + }{ + {"LDAP destination does not exist", func() *mockConn { return nil }, "ignored", "connect LDAP"}, + {"Service bind unauthorized", func() *mockConn { + return &mockConn{bindErr: map[string]error{"cn=admin,dc=ex,dc=com|bad": errors.New("uhoh")}, searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{goodEntry}}} + }, "secret", "service bind failed"}, + {"Invalid credentials", func() *mockConn { + return &mockConn{bindErr: map[string]error{"uid=jdoe,dc=ex,dc=com|wrong": errors.New("bad")}, searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{goodEntry}}} + }, "wrong", "invalid credentials"}, + {"Expired password", func() *mockConn { + errExpired := ldap.NewError(ldap.LDAPResultInvalidCredentials, errors.New("expired")) + return &mockConn{bindErr: map[string]error{"uid=jdoe,dc=ex,dc=com|secret": errExpired}, searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{goodEntry}}} + }, "secret", "invalid credentials"}, + {"BindSuccess but info retrieval failed", func() *mockConn { return &mockConn{searchErr: errors.New("search down")} }, "secret", "search error"}, + {"App unable to generate JWT", func() *mockConn { return &mockConn{searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{goodEntry}}} }, "secret", "token signing error"}, + {"Successful authentication", func() *mockConn { return &mockConn{searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{goodEntry}}} }, "secret", ""}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + var auth *Authenticator + if tc.name == "LDAP destination does not exist" { + auth = New(Config{Addr: "x", BaseDN: "d", JWTSecret: "s"}) + auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { + return nil, errors.New("dial error") + }) + } else { + mc := tc.setupConn() + auth = New(Config{Addr: "x", BaseDN: "d", JWTSecret: "secret"}) + auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) + if tc.name == "Service bind unauthorized" { + auth.cfg.BindUserDN = "cn=admin,dc=ex,dc=com" + auth.cfg.BindPassword = "bad" + } + if tc.name == "App unable to generate JWT" { + auth.cfg.JWTSecret = "" + } + } + + token, err := auth.Authenticate("jdoe", tc.password) + if tc.wantErrSubstr != "" { + if err == nil || !strings.Contains(err.Error(), tc.wantErrSubstr) { + t.Fatalf("expected error containing %q, got %v", tc.wantErrSubstr, err) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + sub, err := auth.ValidateToken(token) + if err != nil { + t.Fatalf("token validation failed: %v", err) + } + if sub != "jdoe" { + t.Fatalf("expected sub=jdoe; got %s", sub) + } + }) + } +} + +func TestValidateTokenExpired(t *testing.T) { + cfg := Config{JWTSecret: "secret"} + auth := New(cfg) + expired := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{"sub": "joe", "exp": time.Now().Add(-1 * time.Hour).Unix()}) + tok, _ := expired.SignedString([]byte(cfg.JWTSecret)) + + _, err := auth.ValidateToken(tok) + if err == nil || (!strings.Contains(err.Error(), "token parse error") && !strings.Contains(err.Error(), "invalid token")) { + t.Fatalf("expected expired-token error, got %v", err) + } +} \ No newline at end of file diff --git a/mainFile/main.go b/mainFile/main.go index 45fd046d0..c6ae0e08f 100644 --- a/mainFile/main.go +++ b/mainFile/main.go @@ -6,52 +6,36 @@ import ( "log" "net/http" - "ldap-auth-go/internal/ldapauth" + "gofr.dev/internal/auth/ldapauth" ) var authenticator *ldapauth.Authenticator func main() { - config := ldapauth.Config{ - Addr: "localhost:389", // change this to your LDAP server - BaseDN: "dc=example,dc=com", // adjust as per your directory - BindUserDN: "cn=admin,dc=example,dc=com", // service account (optional) - BindPassword: "admin", // service password - JWTSecret: "your-secret-key", // replace with strong key - } - - authenticator = ldapauth.New(config) + cfg := ldapauth.Config{ + Addr: "localhost:389", // ← use localhost (or 127.0.0.1) + BaseDN: "dc=example,dc=com", + BindUserDN: "cn=admin,dc=example,dc=com", + BindPassword: "admin", + JWTSecret: "super-secret-key", +} + authenticator = ldapauth.New(cfg) http.HandleFunc("/login", loginHandler) - log.Println("Server running on http://localhost:8080") + fmt.Println("Listening on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) } func loginHandler(w http.ResponseWriter, r *http.Request) { - var creds struct { - Username string `json:"username"` - Password string `json:"password"` - } - + var creds struct{ Username, Password string } if err := json.NewDecoder(r.Body).Decode(&creds); err != nil { - http.Error(w, "Invalid JSON", http.StatusBadRequest) + http.Error(w, "invalid request", http.StatusBadRequest) return } - token, err := authenticator.Authenticate(creds.Username, creds.Password) if err != nil { - http.Error(w, fmt.Sprintf("Login failed: %v", err), http.StatusUnauthorized) + http.Error(w, err.Error(), http.StatusUnauthorized) return } - - // json.NewEncoder(w).Encode(map[string]string{ - // "token": token, - // }) - if err := json.NewEncoder(w).Encode(map[string]string{ - "token": token, -}); err != nil { - http.Error(w, "Failed to write response", http.StatusInternalServerError) - return -} - -} + json.NewEncoder(w).Encode(map[string]string{"token": token}) +} \ No newline at end of file From 9578e4013589c18b77fd3bbc41bbea2812ca1889 Mon Sep 17 00:00:00 2001 From: mundele2004 Date: Thu, 31 Jul 2025 00:39:16 +0530 Subject: [PATCH 4/9] chore: fix golangci-lint errors and clean code formatting --- internal/auth/ldapauth/ldapauth.go | 301 ++++++++++++++++-------- internal/auth/ldapauth/ldapauth_test.go | 232 +++++++++++------- mainFile/main.go | 83 ++++--- 3 files changed, 398 insertions(+), 218 deletions(-) diff --git a/internal/auth/ldapauth/ldapauth.go b/internal/auth/ldapauth/ldapauth.go index 2beb229eb..22c032d42 100644 --- a/internal/auth/ldapauth/ldapauth.go +++ b/internal/auth/ldapauth/ldapauth.go @@ -1,159 +1,264 @@ package ldapauth import ( - "fmt" - "time" + "errors" + "fmt" + "log" + "time" - "github.com/go-ldap/ldap/v3" - "github.com/golang-jwt/jwt/v4" + "github.com/go-ldap/ldap/v3" + "github.com/golang-jwt/jwt/v4" +) + +var ( + ErrUserNotFound = errors.New("user not found or multiple entries returned") + + ErrTokenSigningEmptyKey = errors.New("token signing error: no secret configured") + + ErrUnexpectedSigningMethod = errors.New("unexpected signing method") + + ErrInvalidToken = errors.New("invalid token") + + ErrInvalidClaims = errors.New("invalid claims") + + ErrMissingSubClaim = errors.New("missing sub claim") ) // Conn abstracts ldap.Conn methods for easier testing. + type Conn interface { - Bind(username, password string) error - Search(searchReq *ldap.SearchRequest) (*ldap.SearchResult, error) - Close() error + Bind(username, password string) error + + Search(searchReq *ldap.SearchRequest) (*ldap.SearchResult, error) + + Close() error } // dialerFunc connects to LDAP and returns a Conn. + type dialerFunc func(addr string, opts ...ldap.DialOpt) (Conn, error) // defaultDialer uses ldap.DialURL under the hood. + func defaultDialer(addr string, opts ...ldap.DialOpt) (Conn, error) { - return ldap.DialURL(addr, opts...) + + return ldap.DialURL(addr, opts...) + } // Config holds LDAP and JWT settings. + type Config struct { - Addr string // LDAP server address (e.g., "localhost:389" or "ldap.example.com:389") - BaseDN string // Base distinguished name (DN) used to search for users (e.g., "dc=example,dc=com") - BindUserDN string // Optional: DN of a service account to perform search operations. - // Required if anonymous search is disabled on the LDAP server. - - BindPassword string // Optional: Password for the service account specified in BindUserDN. - // Required only if BindUserDN is set. - JWTSecret string // Secret key used to sign JWT tokens issued after successful authentication. + Addr string // LDAP server address (e.g., "localhost:389" or "ldap.example.com:389") + + BaseDN string // Base distinguished name (DN) used to search for users (e.g., "dc=example,dc=com") + + BindUserDN string // Optional: DN of a service account to perform search operations. + + // Required if anonymous search is disabled on the LDAP server. + + BindPassword string // Optional: Password for the service account specified in BindUserDN. + + // Required only if BindUserDN is set. + + JWTSecret string // Secret key used to sign JWT tokens issued after successful authentication. + } // Authenticator handles LDAP authentication and JWT issuance. + type Authenticator struct { - cfg Config - dialFn dialerFunc + cfg Config + + dialFn dialerFunc } // New returns an Authenticator with the default dialer. + func New(cfg Config) *Authenticator { - return &Authenticator{ - cfg: cfg, - dialFn: defaultDialer, - } + + return &Authenticator{ + + cfg: cfg, + + dialFn: defaultDialer, + } + } // WithDialer allows injecting a custom dialer (for tests). + func (a *Authenticator) WithDialer(d dialerFunc) { - a.dialFn = d + + a.dialFn = d + } // Authenticate binds to LDAP, validates credentials, and returns a signed JWT. + func (a *Authenticator) Authenticate(username, password string) (string, error) { - conn, err := a.dialFn("ldap://" + a.cfg.Addr) - if err != nil { - return "", fmt.Errorf("failed to connect LDAP: %w", err) - } - defer conn.Close() - if a.cfg.BindUserDN != "" { - if err := conn.Bind(a.cfg.BindUserDN, a.cfg.BindPassword); err != nil { - return "", fmt.Errorf("service bind failed: %w", err) + conn, err := a.dialFn("ldap://" + a.cfg.Addr) + + if err != nil { + + return "", fmt.Errorf("failed to connect LDAP: %w", err) + + } + + defer conn.Close() + + if a.cfg.BindUserDN != "" { + + bindErr := conn.Bind(a.cfg.BindUserDN, a.cfg.BindPassword) + if bindErr != nil { + return "", fmt.Errorf("service bind failed: %w", bindErr) } - } - userDN, err := a.lookupUserDN(conn, username) - if err != nil { - return "", err - } - if err := a.bindUser(conn, userDN, password); err != nil { - return "", err - } + } + + userDN, err := a.lookupUserDN(conn, username) + + if err != nil { + + return "", err - token, err := a.generateToken(username) - if err != nil { - return "", err + } + + authErr := a.bindUser(conn, userDN, password) + if authErr != nil { + return "", authErr } - return token, nil -} + token, err := a.generateToken(username) + + if err != nil { + + return "", err + + } + + return token, nil + +} func (a *Authenticator) lookupUserDN(conn Conn, username string) (string, error) { - searchReq := ldap.NewSearchRequest( - a.cfg.BaseDN, - ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - fmt.Sprintf("(uid=%s)", ldap.EscapeFilter(username)), - []string{"dn"}, - nil, - ) - - result, err := conn.Search(searchReq) - if err != nil { - return "", fmt.Errorf("search error: %w", err) - } - if len(result.Entries) != 1 { - return "", fmt.Errorf("user not found or multiple entries returned") - } + searchReq := ldap.NewSearchRequest( + + a.cfg.BaseDN, + + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + + fmt.Sprintf("(uid=%s)", ldap.EscapeFilter(username)), + + []string{"dn"}, + + nil, + ) + + result, err := conn.Search(searchReq) + + if err != nil { + + return "", fmt.Errorf("search error: %w", err) + + } + + if len(result.Entries) != 1 { + + return "", ErrUserNotFound + + } + + return result.Entries[0].DN, nil - return result.Entries[0].DN, nil } func (a *Authenticator) bindUser(conn Conn, userDN, password string) error { - if err := conn.Bind(userDN, password); err != nil { - return fmt.Errorf("invalid credentials: %w", err) - } - return nil + + if err := conn.Bind(userDN, password); err != nil { + + return fmt.Errorf("invalid credentials: %w", err) + + } + + return nil + } func (a *Authenticator) generateToken(username string) (string, error) { - if a.cfg.JWTSecret == "" { - return "", fmt.Errorf("token signing error: no secret configured") - } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "sub": username, - "exp": time.Now().Add(1 * time.Hour).Unix(), - }) + if a.cfg.JWTSecret == "" { - signed, err := token.SignedString([]byte(a.cfg.JWTSecret)) - if err != nil { - return "", fmt.Errorf("token signing error: %w", err) - } + return "", ErrTokenSigningEmptyKey - return signed, nil -} + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + + "sub": username, + + "exp": time.Now().Add(1 * time.Hour).Unix(), + }) + + signed, err := token.SignedString([]byte(a.cfg.JWTSecret)) + + if err != nil { + + log.Printf("token signing error: %v", err) + return "", ErrTokenSigningEmptyKey + + } + + return signed, nil + +} // ValidateToken parses and validates a JWT, returning the 'sub' claim. + func (a *Authenticator) ValidateToken(tokenStr string) (string, error) { - token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) - } - return []byte(a.cfg.JWTSecret), nil - }) - if err != nil { - return "", fmt.Errorf("token parse error: %w", err) - } - if !token.Valid { - return "", fmt.Errorf("invalid token") - } - claims, ok := token.Claims.(jwt.MapClaims) - if !ok { - return "", fmt.Errorf("invalid claims") - } - sub, ok := claims["sub"].(string) - if !ok { - return "", fmt.Errorf("missing sub claim") + + token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (any, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + log.Printf("unexpected signing method: %v", token.Header["alg"]) + return nil, ErrUnexpectedSigningMethod } - return sub, nil -} \ No newline at end of file + return []byte(a.cfg.JWTSecret), nil +}) + + + if err != nil { + + return "", fmt.Errorf("token parse error: %w", err) + + } + + if !token.Valid { + + return "", ErrInvalidToken + + } + + claims, ok := token.Claims.(jwt.MapClaims) + + if !ok { + + return "", ErrInvalidClaims + + } + + sub, ok := claims["sub"].(string) + + if !ok { + + return "", ErrMissingSubClaim + + } + + return sub, nil + +} diff --git a/internal/auth/ldapauth/ldapauth_test.go b/internal/auth/ldapauth/ldapauth_test.go index cf7f55e3e..67460c5dd 100644 --- a/internal/auth/ldapauth/ldapauth_test.go +++ b/internal/auth/ldapauth/ldapauth_test.go @@ -1,114 +1,166 @@ package ldapauth import ( - "errors" - "strings" - "testing" - "time" + "errors" + "strings" + "testing" + "time" - "github.com/go-ldap/ldap/v3" - "github.com/golang-jwt/jwt/v4" + "github.com/go-ldap/ldap/v3" + "github.com/golang-jwt/jwt/v4" ) // mockConn implements Conn for controlled test behavior. + type mockConn struct { - bindErr map[string]error - searchResult *ldap.SearchResult - searchErr error - closed bool + bindErr map[string]error + + searchResult *ldap.SearchResult + + searchErr error + + closed bool } func (m *mockConn) Bind(username, password string) error { - if err, ok := m.bindErr[username+"|"+password]; ok { - return err - } - return nil + + if err, ok := m.bindErr[username+"|"+password]; ok { + + return err + + } + + return nil + } func (m *mockConn) Search(req *ldap.SearchRequest) (*ldap.SearchResult, error) { - return m.searchResult, m.searchErr + + return m.searchResult, m.searchErr + } func (m *mockConn) Close() error { - m.closed = true - return nil + + m.closed = true + + return nil + } func TestAuthenticate(t *testing.T) { - goodEntry := &ldap.Entry{DN: "uid=jdoe,dc=ex,dc=com"} - - cases := []struct { - name string - setupConn func() *mockConn - password string - wantErrSubstr string - }{ - {"LDAP destination does not exist", func() *mockConn { return nil }, "ignored", "connect LDAP"}, - {"Service bind unauthorized", func() *mockConn { - return &mockConn{bindErr: map[string]error{"cn=admin,dc=ex,dc=com|bad": errors.New("uhoh")}, searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{goodEntry}}} - }, "secret", "service bind failed"}, - {"Invalid credentials", func() *mockConn { - return &mockConn{bindErr: map[string]error{"uid=jdoe,dc=ex,dc=com|wrong": errors.New("bad")}, searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{goodEntry}}} - }, "wrong", "invalid credentials"}, - {"Expired password", func() *mockConn { - errExpired := ldap.NewError(ldap.LDAPResultInvalidCredentials, errors.New("expired")) - return &mockConn{bindErr: map[string]error{"uid=jdoe,dc=ex,dc=com|secret": errExpired}, searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{goodEntry}}} - }, "secret", "invalid credentials"}, - {"BindSuccess but info retrieval failed", func() *mockConn { return &mockConn{searchErr: errors.New("search down")} }, "secret", "search error"}, - {"App unable to generate JWT", func() *mockConn { return &mockConn{searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{goodEntry}}} }, "secret", "token signing error"}, - {"Successful authentication", func() *mockConn { return &mockConn{searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{goodEntry}}} }, "secret", ""}, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - var auth *Authenticator - if tc.name == "LDAP destination does not exist" { - auth = New(Config{Addr: "x", BaseDN: "d", JWTSecret: "s"}) - auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { - return nil, errors.New("dial error") - }) - } else { - mc := tc.setupConn() - auth = New(Config{Addr: "x", BaseDN: "d", JWTSecret: "secret"}) - auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) - if tc.name == "Service bind unauthorized" { - auth.cfg.BindUserDN = "cn=admin,dc=ex,dc=com" - auth.cfg.BindPassword = "bad" - } - if tc.name == "App unable to generate JWT" { - auth.cfg.JWTSecret = "" - } - } - - token, err := auth.Authenticate("jdoe", tc.password) - if tc.wantErrSubstr != "" { - if err == nil || !strings.Contains(err.Error(), tc.wantErrSubstr) { - t.Fatalf("expected error containing %q, got %v", tc.wantErrSubstr, err) - } - return - } - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - sub, err := auth.ValidateToken(token) - if err != nil { - t.Fatalf("token validation failed: %v", err) - } - if sub != "jdoe" { - t.Fatalf("expected sub=jdoe; got %s", sub) - } + t.Run("LDAP destination does not exist", func(t *testing.T) { + auth := New(Config{Addr: "x", BaseDN: "d", JWTSecret: "s"}) + auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { + return nil, errors.New("dial error") + }) + _, err := auth.Authenticate("jdoe", "ignored") + if err == nil || !strings.Contains(err.Error(), "connect LDAP") { + t.Fatalf("expected connection error, got %v", err) + } + }) + + t.Run("Service bind unauthorized", func(t *testing.T) { + mc := &mockConn{ + bindErr: map[string]error{"cn=admin,dc=ex,dc=com|bad": errors.New("uhoh")}, + searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{{DN: "uid=jdoe,dc=ex,dc=com"}}}, + } + auth := New(Config{ + Addr: "x", + BaseDN: "d", + JWTSecret: "secret", + BindUserDN: "cn=admin,dc=ex,dc=com", + BindPassword: "bad", }) - } + auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) + _, err := auth.Authenticate("jdoe", "secret") + if err == nil || !strings.Contains(err.Error(), "service bind failed") { + t.Fatalf("expected bind error, got %v", err) + } + }) + + t.Run("Invalid credentials", func(t *testing.T) { + mc := &mockConn{ + bindErr: map[string]error{"uid=jdoe,dc=ex,dc=com|wrong": errors.New("bad")}, + searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{{DN: "uid=jdoe,dc=ex,dc=com"}}}, + } + auth := New(Config{Addr: "x", BaseDN: "d", JWTSecret: "secret"}) + auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) + _, err := auth.Authenticate("jdoe", "wrong") + if err == nil || !strings.Contains(err.Error(), "invalid credentials") { + t.Fatalf("expected invalid credentials error, got %v", err) + } + }) + + t.Run("Expired password", func(t *testing.T) { + errExpired := ldap.NewError(ldap.LDAPResultInvalidCredentials, errors.New("expired")) + mc := &mockConn{ + bindErr: map[string]error{"uid=jdoe,dc=ex,dc=com|secret": errExpired}, + searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{{DN: "uid=jdoe,dc=ex,dc=com"}}}, + } + auth := New(Config{Addr: "x", BaseDN: "d", JWTSecret: "secret"}) + auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) + _, err := auth.Authenticate("jdoe", "secret") + if err == nil || !strings.Contains(err.Error(), "invalid credentials") { + t.Fatalf("expected expired password error, got %v", err) + } + }) + + t.Run("BindSuccess but info retrieval failed", func(t *testing.T) { + mc := &mockConn{searchErr: errors.New("search down")} + auth := New(Config{Addr: "x", BaseDN: "d", JWTSecret: "secret"}) + auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) + _, err := auth.Authenticate("jdoe", "secret") + if err == nil || !strings.Contains(err.Error(), "search error") { + t.Fatalf("expected search failure error, got %v", err) + } + }) + + t.Run("App unable to generate JWT", func(t *testing.T) { + mc := &mockConn{searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{{DN: "uid=jdoe,dc=ex,dc=com"}}}} + auth := New(Config{Addr: "x", BaseDN: "d", JWTSecret: ""}) // no secret = JWT signing fails + auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) + _, err := auth.Authenticate("jdoe", "secret") + if err == nil || !strings.Contains(err.Error(), "token signing error") { + t.Fatalf("expected JWT signing error, got %v", err) + } + }) + + t.Run("Successful authentication", func(t *testing.T) { + mc := &mockConn{searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{{DN: "uid=jdoe,dc=ex,dc=com"}}}} + auth := New(Config{Addr: "x", BaseDN: "d", JWTSecret: "secret"}) + auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) + token, err := auth.Authenticate("jdoe", "secret") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + sub, err := auth.ValidateToken(token) + if err != nil { + t.Fatalf("token validation failed: %v", err) + } + if sub != "jdoe" { + t.Fatalf("expected sub=jdoe; got %s", sub) + } + }) } + func TestValidateTokenExpired(t *testing.T) { - cfg := Config{JWTSecret: "secret"} - auth := New(cfg) - expired := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{"sub": "joe", "exp": time.Now().Add(-1 * time.Hour).Unix()}) - tok, _ := expired.SignedString([]byte(cfg.JWTSecret)) - - _, err := auth.ValidateToken(tok) - if err == nil || (!strings.Contains(err.Error(), "token parse error") && !strings.Contains(err.Error(), "invalid token")) { - t.Fatalf("expected expired-token error, got %v", err) - } -} \ No newline at end of file + + cfg := Config{JWTSecret: "secret"} + + auth := New(cfg) + + expired := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{"sub": "joe", "exp": time.Now().Add(-1 * time.Hour).Unix()}) + + tok, _ := expired.SignedString([]byte(cfg.JWTSecret)) + + _, err := auth.ValidateToken(tok) + + if err == nil || (!strings.Contains(err.Error(), "token parse error") && !strings.Contains(err.Error(), "invalid token")) { + + t.Fatalf("expected expired-token error, got %v", err) + + } + +} diff --git a/mainFile/main.go b/mainFile/main.go index c6ae0e08f..62b499846 100644 --- a/mainFile/main.go +++ b/mainFile/main.go @@ -1,41 +1,64 @@ package main import ( - "encoding/json" - "fmt" - "log" - "net/http" + "encoding/json" + "fmt" + "log" + "net/http" + "time" - "gofr.dev/internal/auth/ldapauth" + "gofr.dev/internal/auth/ldapauth" ) -var authenticator *ldapauth.Authenticator - func main() { - cfg := ldapauth.Config{ - Addr: "localhost:389", // ← use localhost (or 127.0.0.1) - BaseDN: "dc=example,dc=com", - BindUserDN: "cn=admin,dc=example,dc=com", - BindPassword: "admin", - JWTSecret: "super-secret-key", + cfg := ldapauth.Config{ + Addr: "localhost:389", + BaseDN: "dc=example,dc=com", + BindUserDN: "cn=admin,dc=example,dc=com", + BindPassword: "admin", + JWTSecret: "super-secret-key", + } + + authenticator := ldapauth.New(cfg) + + http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { + loginHandler(w, r, authenticator) + }) + + fmt.Println("Listening on :8080") + + // Add HTTP Timeouts + srv := &http.Server{ + Addr: ":8080", + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 120 * time.Second, + } + log.Fatal(srv.ListenAndServe()) } - authenticator = ldapauth.New(cfg) - http.HandleFunc("/login", loginHandler) - fmt.Println("Listening on :8080") - log.Fatal(http.ListenAndServe(":8080", nil)) + +func loginHandler(w http.ResponseWriter, r *http.Request, authenticator *ldapauth.Authenticator) { + var creds struct { + Username string `json:"username"` + Password string `json:"password"` + } + + if err := json.NewDecoder(r.Body).Decode(&creds); err != nil { + http.Error(w, "invalid request", http.StatusBadRequest) + return + } + + token, err := authenticator.Authenticate(creds.Username, creds.Password) + if err != nil { + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + + if err := json.NewEncoder(w).Encode(map[string]string{"token": token}); err != nil { + log.Println("encode error:", err) + http.Error(w, "failed to write response", http.StatusInternalServerError) + } } -func loginHandler(w http.ResponseWriter, r *http.Request) { - var creds struct{ Username, Password string } - if err := json.NewDecoder(r.Body).Decode(&creds); err != nil { - http.Error(w, "invalid request", http.StatusBadRequest) - return - } - token, err := authenticator.Authenticate(creds.Username, creds.Password) - if err != nil { - http.Error(w, err.Error(), http.StatusUnauthorized) - return - } - json.NewEncoder(w).Encode(map[string]string{"token": token}) -} \ No newline at end of file + From 466a46b2454f5560c94e7edd9aca5d786383ef01 Mon Sep 17 00:00:00 2001 From: mundele2004 Date: Sun, 10 Aug 2025 11:59:40 +0530 Subject: [PATCH 5/9] Backup before running gci --- internal/auth/jwt/jwt.go | 123 +++++++++ internal/auth/ldapauth/ldapauth.go | 306 ++++++++++---------- internal/auth/ldapauth/ldapauth_test.go | 352 +++++++++++++++--------- mainFile/main.go | 85 ++++-- testuser.ldif | 10 + 5 files changed, 572 insertions(+), 304 deletions(-) create mode 100644 internal/auth/jwt/jwt.go create mode 100644 testuser.ldif diff --git a/internal/auth/jwt/jwt.go b/internal/auth/jwt/jwt.go new file mode 100644 index 000000000..7d57bb77e --- /dev/null +++ b/internal/auth/jwt/jwt.go @@ -0,0 +1,123 @@ +package jwt + +import ( + // Standard library + "errors" + "fmt" + "log" + "time" + + // Third-party + "github.com/golang-jwt/jwt/v4" +) + +var ( + ErrTokenSigningEmptyKey = errors.New("token signing error: no secret configured") + ErrUnexpectedSigningMethod = errors.New("unexpected signing method") + ErrInvalidToken = errors.New("invalid token") + ErrInvalidClaims = errors.New("invalid claims") + ErrMissingSubClaim = errors.New("missing sub claim") +) + +// User interface to avoid circular import. +type User interface { + GetDN() string + GetUsername() string + GetEmail() string + GetFullName() string + GetGroups() []string + GetDepartment() string +} + +// Issuer implements TokenIssuer interface for JWT tokens. +type Issuer struct { + secret string + ttl time.Duration + includeUserInfo bool +} + +// NewIssuer creates a new JWT token issuer. +func NewIssuer(secret string, ttl time.Duration, includeUserInfo bool) *Issuer { + return &Issuer{ + secret: secret, + ttl: ttl, + includeUserInfo: includeUserInfo, + } +} + +// IssueToken creates and signs a JWT token for the given user. +func (j *Issuer) IssueToken(user User) (string, error) { + if j.secret == "" { + return "", ErrTokenSigningEmptyKey + } + + claims := jwt.MapClaims{ + "sub": user.GetUsername(), + "exp": time.Now().Add(j.ttl).Unix(), + "iat": time.Now().Unix(), + } + + // Optionally include user information in token claims. + if j.includeUserInfo { + if email := user.GetEmail(); email != "" { + claims["email"] = email + } + + if fullName := user.GetFullName(); fullName != "" { + claims["name"] = fullName + } + + if dept := user.GetDepartment(); dept != "" { + claims["department"] = dept + } + + if groups := user.GetGroups(); len(groups) > 0 { + claims["groups"] = groups + } + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + signed, err := token.SignedString([]byte(j.secret)) + if err != nil { + log.Printf("token signing error: %v", err) + return "", ErrTokenSigningEmptyKey + } + + return signed, nil +} + +// ValidateToken parses and validates a JWT, returning the 'sub' claim. +func (j *Issuer) ValidateToken(tokenStr string) (string, error) { + keyFn := func(token *jwt.Token) (any, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + log.Printf("unexpected signing method: %v", token.Header["alg"]) + return nil, ErrUnexpectedSigningMethod + } + + secret := []byte(j.secret) + + return secret, nil + } + + token, err := jwt.Parse(tokenStr, keyFn) + if err != nil { + return "", fmt.Errorf("token parse error: %w", err) + } + + if !token.Valid { + return "", ErrInvalidToken + } + + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + return "", ErrInvalidClaims + } + + sub, ok := claims["sub"].(string) + if !ok { + return "", ErrMissingSubClaim + } + + return sub, nil +} diff --git a/internal/auth/ldapauth/ldapauth.go b/internal/auth/ldapauth/ldapauth.go index 22c032d42..2695d816b 100644 --- a/internal/auth/ldapauth/ldapauth.go +++ b/internal/auth/ldapauth/ldapauth.go @@ -3,262 +3,252 @@ package ldapauth import ( "errors" "fmt" - "log" - "time" "github.com/go-ldap/ldap/v3" - "github.com/golang-jwt/jwt/v4" + "gofr.dev/internal/auth/jwt" ) var ( - ErrUserNotFound = errors.New("user not found or multiple entries returned") + ErrUserNotFound = errors.New("user not found") + ErrMultipleUsersFound = errors.New("multiple users found - LDAP configuration error") +) - ErrTokenSigningEmptyKey = errors.New("token signing error: no secret configured") +// User represents LDAP user information. +type User struct { + DN string `json:"dn"` + Username string `json:"username"` + Email string `json:"email,omitempty"` + FullName string `json:"full_name,omitempty"` + Groups []string `json:"groups,omitempty"` + Department string `json:"department,omitempty"` + Attributes map[string]string `json:"attributes,omitempty"` +} - ErrUnexpectedSigningMethod = errors.New("unexpected signing method") +// GetDN returns the distinguished name. +func (u *User) GetDN() string { return u.DN } - ErrInvalidToken = errors.New("invalid token") +// GetUsername returns the username. +func (u *User) GetUsername() string { return u.Username } - ErrInvalidClaims = errors.New("invalid claims") +// GetEmail returns the email. +func (u *User) GetEmail() string { return u.Email } - ErrMissingSubClaim = errors.New("missing sub claim") -) +// GetFullName returns the full name. +func (u *User) GetFullName() string { return u.FullName } -// Conn abstracts ldap.Conn methods for easier testing. +// GetGroups returns the groups. +func (u *User) GetGroups() []string { return u.Groups } +// GetDepartment returns the department. +func (u *User) GetDepartment() string { return u.Department } + +// Conn abstracts ldap.Conn methods for easier testing. type Conn interface { Bind(username, password string) error - Search(searchReq *ldap.SearchRequest) (*ldap.SearchResult, error) - Close() error } -// dialerFunc connects to LDAP and returns a Conn. +// TokenIssuer interface for token issuance - allows different implementations. +type TokenIssuer interface { + IssueToken(user jwt.User) (string, error) + ValidateToken(token string) (string, error) +} +// dialerFunc connects to LDAP and returns a Conn. type dialerFunc func(addr string, opts ...ldap.DialOpt) (Conn, error) // defaultDialer uses ldap.DialURL under the hood. - func defaultDialer(addr string, opts ...ldap.DialOpt) (Conn, error) { - return ldap.DialURL(addr, opts...) - } -// Config holds LDAP and JWT settings. - +// Config holds LDAP settings and token issuer. type Config struct { - Addr string // LDAP server address (e.g., "localhost:389" or "ldap.example.com:389") - - BaseDN string // Base distinguished name (DN) used to search for users (e.g., "dc=example,dc=com") - - BindUserDN string // Optional: DN of a service account to perform search operations. - - // Required if anonymous search is disabled on the LDAP server. - - BindPassword string // Optional: Password for the service account specified in BindUserDN. - - // Required only if BindUserDN is set. - - JWTSecret string // Secret key used to sign JWT tokens issued after successful authentication. - + Addr string + BaseDN string + BindUserDN string + BindPassword string + TokenIssuer TokenIssuer + UserAttributes []string + UsernameAttr string + EmailAttr string + FullNameAttr string + DepartmentAttr string } -// Authenticator handles LDAP authentication and JWT issuance. - +// Authenticator handles LDAP authentication and token issuance. type Authenticator struct { - cfg Config - + cfg Config dialFn dialerFunc } // New returns an Authenticator with the default dialer. +func New(cfg *Config) *Authenticator { + // Set default attribute mappings if not specified. + if cfg.UsernameAttr == "" { + cfg.UsernameAttr = "uid" + } -func New(cfg Config) *Authenticator { + if cfg.EmailAttr == "" { + cfg.EmailAttr = "mail" + } - return &Authenticator{ + if cfg.FullNameAttr == "" { + cfg.FullNameAttr = "cn" + } - cfg: cfg, + if cfg.DepartmentAttr == "" { + cfg.DepartmentAttr = "ou" + } - dialFn: defaultDialer, + // Default attributes to retrieve if not specified. + if len(cfg.UserAttributes) == 0 { + cfg.UserAttributes = []string{ + "dn", cfg.UsernameAttr, cfg.EmailAttr, + cfg.FullNameAttr, cfg.DepartmentAttr, "memberOf", + } } + return &Authenticator{ + cfg: *cfg, + dialFn: defaultDialer, + } } // WithDialer allows injecting a custom dialer (for tests). - func (a *Authenticator) WithDialer(d dialerFunc) { - a.dialFn = d - } -// Authenticate binds to LDAP, validates credentials, and returns a signed JWT. - +// Authenticate binds to LDAP, validates credentials, and returns a signed token. func (a *Authenticator) Authenticate(username, password string) (string, error) { - conn, err := a.dialFn("ldap://" + a.cfg.Addr) - if err != nil { - return "", fmt.Errorf("failed to connect LDAP: %w", err) - } - defer conn.Close() if a.cfg.BindUserDN != "" { - bindErr := conn.Bind(a.cfg.BindUserDN, a.cfg.BindPassword) - if bindErr != nil { - return "", fmt.Errorf("service bind failed: %w", bindErr) - } - - + if bindErr != nil { + return "", fmt.Errorf("service bind failed: %w", bindErr) + } } - userDN, err := a.lookupUserDN(conn, username) - + user, err := a.lookupUser(conn, username) if err != nil { - return "", err - } - authErr := a.bindUser(conn, userDN, password) - if authErr != nil { - return "", authErr - } - - - token, err := a.generateToken(username) + authErr := bindUser(conn, user.DN, password) + if authErr != nil { + return "", authErr + } + token, err := a.cfg.TokenIssuer.IssueToken(user) if err != nil { + return "", fmt.Errorf("token issuance failed: %w", err) + } - return "", err + return token, nil +} +// GetUserInfo retrieves user information without authentication. +func (a *Authenticator) GetUserInfo(username string) (*User, error) { + conn, err := a.dialFn("ldap://" + a.cfg.Addr) + if err != nil { + return nil, fmt.Errorf("failed to connect LDAP: %w", err) } + defer conn.Close() - return token, nil + if a.cfg.BindUserDN != "" { + bindErr := conn.Bind(a.cfg.BindUserDN, a.cfg.BindPassword) + if bindErr != nil { + return nil, fmt.Errorf("service bind failed: %w", bindErr) + } + } + return a.lookupUser(conn, username) } -func (a *Authenticator) lookupUserDN(conn Conn, username string) (string, error) { +// ValidateToken validates a token using the configured token issuer. +func (a *Authenticator) ValidateToken(token string) (string, error) { + return a.cfg.TokenIssuer.ValidateToken(token) +} +// lookupUser retrieves comprehensive user information from LDAP. +func (a *Authenticator) lookupUser(conn Conn, username string) (*User, error) { searchReq := ldap.NewSearchRequest( - a.cfg.BaseDN, - ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - - fmt.Sprintf("(uid=%s)", ldap.EscapeFilter(username)), - - []string{"dn"}, - + fmt.Sprintf("(%s=%s)", a.cfg.UsernameAttr, ldap.EscapeFilter(username)), + a.cfg.UserAttributes, nil, ) result, err := conn.Search(searchReq) - if err != nil { - - return "", fmt.Errorf("search error: %w", err) - + return nil, fmt.Errorf("search error: %w", err) } - if len(result.Entries) != 1 { - - return "", ErrUserNotFound - + // Handle different result scenarios separately. + switch len(result.Entries) { + case 0: + return nil, ErrUserNotFound + case 1: + return a.buildUserFromEntry(result.Entries[0], username), nil + default: + return nil, ErrMultipleUsersFound } - - return result.Entries[0].DN, nil - } -func (a *Authenticator) bindUser(conn Conn, userDN, password string) error { - - if err := conn.Bind(userDN, password); err != nil { - - return fmt.Errorf("invalid credentials: %w", err) - +// buildUserFromEntry constructs a User object from LDAP entry. +func (a *Authenticator) buildUserFromEntry(entry *ldap.Entry, username string) *User { + user := &User{ + DN: entry.DN, + Username: username, + Attributes: make(map[string]string), } - return nil - -} - -func (a *Authenticator) generateToken(username string) (string, error) { - - if a.cfg.JWTSecret == "" { - - return "", ErrTokenSigningEmptyKey - + // Extract standard attributes. + if email := entry.GetAttributeValue(a.cfg.EmailAttr); email != "" { + user.Email = email } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - - "sub": username, - - "exp": time.Now().Add(1 * time.Hour).Unix(), - }) - - signed, err := token.SignedString([]byte(a.cfg.JWTSecret)) - - if err != nil { - - log.Printf("token signing error: %v", err) - - return "", ErrTokenSigningEmptyKey - + if fullName := entry.GetAttributeValue(a.cfg.FullNameAttr); fullName != "" { + user.FullName = fullName } - return signed, nil - -} - -// ValidateToken parses and validates a JWT, returning the 'sub' claim. - -func (a *Authenticator) ValidateToken(tokenStr string) (string, error) { - - token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (any, error) { - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - log.Printf("unexpected signing method: %v", token.Header["alg"]) - return nil, ErrUnexpectedSigningMethod - } - return []byte(a.cfg.JWTSecret), nil -}) - - - if err != nil { - - return "", fmt.Errorf("token parse error: %w", err) - + if dept := entry.GetAttributeValue(a.cfg.DepartmentAttr); dept != "" { + user.Department = dept } - if !token.Valid { - - return "", ErrInvalidToken - + // Extract group memberships. + if groups := entry.GetAttributeValues("memberOf"); len(groups) > 0 { + user.Groups = groups } - claims, ok := token.Claims.(jwt.MapClaims) - - if !ok { - - return "", ErrInvalidClaims - + // Extract all other attributes into the Attributes map. + for _, attr := range entry.Attributes { + // Skip attributes we've already processed. + switch attr.Name { + case a.cfg.EmailAttr, a.cfg.FullNameAttr, a.cfg.DepartmentAttr, "memberOf": + continue + default: + if len(attr.Values) > 0 { + user.Attributes[attr.Name] = attr.Values[0] + } + } } - sub, ok := claims["sub"].(string) - - if !ok { - - return "", ErrMissingSubClaim + return user +} +func bindUser(conn Conn, userDN, password string) error { + if err := conn.Bind(userDN, password); err != nil { + return fmt.Errorf("invalid credentials: %w", err) } - return sub, nil - + return nil } diff --git a/internal/auth/ldapauth/ldapauth_test.go b/internal/auth/ldapauth/ldapauth_test.go index 67460c5dd..ea35276bb 100644 --- a/internal/auth/ldapauth/ldapauth_test.go +++ b/internal/auth/ldapauth/ldapauth_test.go @@ -2,165 +2,259 @@ package ldapauth import ( "errors" - "strings" "testing" - "time" "github.com/go-ldap/ldap/v3" - "github.com/golang-jwt/jwt/v4" + "gofr.dev/internal/auth/jwt" ) -// mockConn implements Conn for controlled test behavior. +// Static sentinel errors for tests (avoid dynamic errors per err113). +var ( + errDial = errors.New("dial error") + errBind = errors.New("uhoh") + errBad = errors.New("bad") + errExpiredCause = errors.New("expired") + errSearchDown = errors.New("search down") + errTokenIssueFailed = errors.New("token issue failed") +) -type mockConn struct { - bindErr map[string]error +// mockTokenIssuer for testing. +type mockTokenIssuer struct { + issueErr error + validateErr error + token string + username string +} - searchResult *ldap.SearchResult +func (m *mockTokenIssuer) IssueToken(_ jwt.User) (string, error) { + if m.issueErr != nil { + return "", m.issueErr + } + + return m.token, nil +} - searchErr error +func (m *mockTokenIssuer) ValidateToken(_ string) (string, error) { + if m.validateErr != nil { + return "", m.validateErr + } - closed bool + return m.username, nil } -func (m *mockConn) Bind(username, password string) error { +// mockConn implements Conn for controlled test behavior. +type mockConn struct { + bindErr map[string]error + searchResult *ldap.SearchResult + searchErr error + closed bool +} +func (m *mockConn) Bind(username, password string) error { if err, ok := m.bindErr[username+"|"+password]; ok { - return err - } return nil +} +func (m *mockConn) Search(_ *ldap.SearchRequest) (*ldap.SearchResult, error) { + return m.searchResult, m.searchErr } -func (m *mockConn) Search(req *ldap.SearchRequest) (*ldap.SearchResult, error) { +func (m *mockConn) Close() error { + m.closed = true + return nil +} - return m.searchResult, m.searchErr +func TestAuthenticate_LDAPDestinationDoesNotExist(t *testing.T) { + tokenIssuer := &mockTokenIssuer{token: "test-token"} + auth := New(&Config{ + Addr: "x", + BaseDN: "d", + TokenIssuer: tokenIssuer, + }) + auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { + return nil, errDial + }) + + _, err := auth.Authenticate("jdoe", "ignored") + if err == nil || err.Error() != "failed to connect LDAP: dial error" { + t.Fatalf("expected connection error, got %v", err) + } +} + +func TestAuthenticate_ServiceBindUnauthorized(t *testing.T) { + mc := &mockConn{ + bindErr: map[string]error{"cn=admin,dc=ex,dc=com|bad": errBind}, + searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{{DN: "uid=jdoe,dc=ex,dc=com"}}}, + } + tokenIssuer := &mockTokenIssuer{token: "test-token"} + auth := New(&Config{ + Addr: "x", + BaseDN: "d", + TokenIssuer: tokenIssuer, + BindUserDN: "cn=admin,dc=ex,dc=com", + BindPassword: "bad", + }) + auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) + + _, err := auth.Authenticate("jdoe", "secret") + if err == nil || err.Error() != "service bind failed: uhoh" { + t.Fatalf("expected bind error, got %v", err) + } } -func (m *mockConn) Close() error { +func TestAuthenticate_UserNotFound(t *testing.T) { + mc := &mockConn{ + searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{}}, + } - m.closed = true + tokenIssuer := &mockTokenIssuer{token: "test-token"} + auth := New(&Config{ + Addr: "x", + BaseDN: "d", + TokenIssuer: tokenIssuer, + }) + auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) + + _, err := auth.Authenticate("nonexistent", "secret") + if !errors.Is(err, ErrUserNotFound) { + t.Fatalf("expected ErrUserNotFound, got %v", err) + } +} - return nil +func TestAuthenticate_MultipleUsersFound(t *testing.T) { + mc := &mockConn{ + searchResult: &ldap.SearchResult{ + Entries: []*ldap.Entry{ + {DN: "uid=jdoe1,dc=ex,dc=com"}, + {DN: "uid=jdoe2,dc=ex,dc=com"}, + }, + }, + } + + tokenIssuer := &mockTokenIssuer{token: "test-token"} + auth := New(&Config{ + Addr: "x", + BaseDN: "d", + TokenIssuer: tokenIssuer, + }) + auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) + + _, err := auth.Authenticate("jdoe", "secret") + if !errors.Is(err, ErrMultipleUsersFound) { + t.Fatalf("expected ErrMultipleUsersFound, got %v", err) + } +} + +func TestAuthenticate_InvalidCredentials(t *testing.T) { + mc := &mockConn{ + bindErr: map[string]error{"uid=jdoe,dc=ex,dc=com|wrong": errBad}, + searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{{DN: "uid=jdoe,dc=ex,dc=com"}}}, + } + + tokenIssuer := &mockTokenIssuer{token: "test-token"} + auth := New(&Config{ + Addr: "x", + BaseDN: "d", + TokenIssuer: tokenIssuer, + }) + auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) + + _, err := auth.Authenticate("jdoe", "wrong") + if err == nil || err.Error() != "invalid credentials: bad" { + t.Fatalf("expected invalid credentials error, got %v", err) + } +} + +func TestAuthenticate_ExpiredPassword(t *testing.T) { + errExpired := ldap.NewError(ldap.LDAPResultInvalidCredentials, errExpiredCause) + + mc := &mockConn{ + bindErr: map[string]error{"uid=jdoe,dc=ex,dc=com|secret": errExpired}, + searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{{DN: "uid=jdoe,dc=ex,dc=com"}}}, + } + + tokenIssuer := &mockTokenIssuer{token: "test-token"} + auth := New(&Config{ + Addr: "x", + BaseDN: "d", + TokenIssuer: tokenIssuer, + }) + auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) + + _, err := auth.Authenticate("jdoe", "secret") + if err == nil || err.Error() != "invalid credentials: LDAP Result Code 49 \"Invalid Credentials\": expired" { + t.Fatalf("expected expired password error, got %v", err) + } +} + +func TestAuthenticate_SearchError(t *testing.T) { + mc := &mockConn{searchErr: errSearchDown} + + tokenIssuer := &mockTokenIssuer{token: "test-token"} + auth := New(&Config{ + Addr: "x", + BaseDN: "d", + TokenIssuer: tokenIssuer, + }) + auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) + + _, err := auth.Authenticate("jdoe", "secret") + if err == nil || err.Error() != "search error: search down" { + t.Fatalf("expected search failure error, got %v", err) + } +} + +func TestAuthenticate_TokenIssuanceFailed(t *testing.T) { + mc := &mockConn{ + searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{{DN: "uid=jdoe,dc=ex,dc=com"}}}, + } + tokenIssuer := &mockTokenIssuer{issueErr: errTokenIssueFailed} + auth := New(&Config{ + Addr: "x", + BaseDN: "d", + TokenIssuer: tokenIssuer, + }) + auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) + + _, err := auth.Authenticate("jdoe", "secret") + if err == nil || err.Error() != "token issuance failed: token issue failed" { + t.Fatalf("expected token issuance error, got %v", err) + } } -func TestAuthenticate(t *testing.T) { - t.Run("LDAP destination does not exist", func(t *testing.T) { - auth := New(Config{Addr: "x", BaseDN: "d", JWTSecret: "s"}) - auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { - return nil, errors.New("dial error") - }) - _, err := auth.Authenticate("jdoe", "ignored") - if err == nil || !strings.Contains(err.Error(), "connect LDAP") { - t.Fatalf("expected connection error, got %v", err) - } - }) - - t.Run("Service bind unauthorized", func(t *testing.T) { - mc := &mockConn{ - bindErr: map[string]error{"cn=admin,dc=ex,dc=com|bad": errors.New("uhoh")}, - searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{{DN: "uid=jdoe,dc=ex,dc=com"}}}, - } - auth := New(Config{ - Addr: "x", - BaseDN: "d", - JWTSecret: "secret", - BindUserDN: "cn=admin,dc=ex,dc=com", - BindPassword: "bad", - }) - auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) - _, err := auth.Authenticate("jdoe", "secret") - if err == nil || !strings.Contains(err.Error(), "service bind failed") { - t.Fatalf("expected bind error, got %v", err) - } - }) - - t.Run("Invalid credentials", func(t *testing.T) { - mc := &mockConn{ - bindErr: map[string]error{"uid=jdoe,dc=ex,dc=com|wrong": errors.New("bad")}, - searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{{DN: "uid=jdoe,dc=ex,dc=com"}}}, - } - auth := New(Config{Addr: "x", BaseDN: "d", JWTSecret: "secret"}) - auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) - _, err := auth.Authenticate("jdoe", "wrong") - if err == nil || !strings.Contains(err.Error(), "invalid credentials") { - t.Fatalf("expected invalid credentials error, got %v", err) - } - }) - - t.Run("Expired password", func(t *testing.T) { - errExpired := ldap.NewError(ldap.LDAPResultInvalidCredentials, errors.New("expired")) - mc := &mockConn{ - bindErr: map[string]error{"uid=jdoe,dc=ex,dc=com|secret": errExpired}, - searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{{DN: "uid=jdoe,dc=ex,dc=com"}}}, - } - auth := New(Config{Addr: "x", BaseDN: "d", JWTSecret: "secret"}) - auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) - _, err := auth.Authenticate("jdoe", "secret") - if err == nil || !strings.Contains(err.Error(), "invalid credentials") { - t.Fatalf("expected expired password error, got %v", err) - } - }) - - t.Run("BindSuccess but info retrieval failed", func(t *testing.T) { - mc := &mockConn{searchErr: errors.New("search down")} - auth := New(Config{Addr: "x", BaseDN: "d", JWTSecret: "secret"}) - auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) - _, err := auth.Authenticate("jdoe", "secret") - if err == nil || !strings.Contains(err.Error(), "search error") { - t.Fatalf("expected search failure error, got %v", err) - } - }) - - t.Run("App unable to generate JWT", func(t *testing.T) { - mc := &mockConn{searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{{DN: "uid=jdoe,dc=ex,dc=com"}}}} - auth := New(Config{Addr: "x", BaseDN: "d", JWTSecret: ""}) // no secret = JWT signing fails - auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) - _, err := auth.Authenticate("jdoe", "secret") - if err == nil || !strings.Contains(err.Error(), "token signing error") { - t.Fatalf("expected JWT signing error, got %v", err) - } - }) - - t.Run("Successful authentication", func(t *testing.T) { - mc := &mockConn{searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{{DN: "uid=jdoe,dc=ex,dc=com"}}}} - auth := New(Config{Addr: "x", BaseDN: "d", JWTSecret: "secret"}) - auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) - token, err := auth.Authenticate("jdoe", "secret") - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - sub, err := auth.ValidateToken(token) - if err != nil { - t.Fatalf("token validation failed: %v", err) - } - if sub != "jdoe" { - t.Fatalf("expected sub=jdoe; got %s", sub) - } - }) -} - - -func TestValidateTokenExpired(t *testing.T) { - - cfg := Config{JWTSecret: "secret"} - - auth := New(cfg) - - expired := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{"sub": "joe", "exp": time.Now().Add(-1 * time.Hour).Unix()}) - - tok, _ := expired.SignedString([]byte(cfg.JWTSecret)) - - _, err := auth.ValidateToken(tok) - - if err == nil || (!strings.Contains(err.Error(), "token parse error") && !strings.Contains(err.Error(), "invalid token")) { - - t.Fatalf("expected expired-token error, got %v", err) +func TestAuthenticate_Successful(t *testing.T) { + mc := &mockConn{ + searchResult: &ldap.SearchResult{Entries: []*ldap.Entry{{DN: "uid=jdoe,dc=ex,dc=com"}}}, + } + + tokenIssuer := &mockTokenIssuer{token: "test-token", username: "jdoe"} + auth := New(&Config{ + Addr: "x", + BaseDN: "d", + TokenIssuer: tokenIssuer, + }) + auth.WithDialer(func(_ string, _ ...ldap.DialOpt) (Conn, error) { return mc, nil }) + + token, err := auth.Authenticate("jdoe", "secret") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if token != "test-token" { + t.Fatalf("expected token=test-token; got %s", token) + } + sub, err := auth.ValidateToken(token) + if err != nil { + t.Fatalf("token validation failed: %v", err) } + if sub != "jdoe" { + t.Fatalf("expected sub=jdoe; got %s", sub) + } } diff --git a/mainFile/main.go b/mainFile/main.go index 62b499846..2fddda7ba 100644 --- a/mainFile/main.go +++ b/mainFile/main.go @@ -2,63 +2,114 @@ package main import ( "encoding/json" + "errors" "fmt" "log" "net/http" "time" + "gofr.dev/internal/auth/jwt" "gofr.dev/internal/auth/ldapauth" ) func main() { + jwtIssuer := jwt.NewIssuer("super-secret-key", 1*time.Hour, true) + cfg := ldapauth.Config{ Addr: "localhost:389", BaseDN: "dc=example,dc=com", BindUserDN: "cn=admin,dc=example,dc=com", BindPassword: "admin", - JWTSecret: "super-secret-key", + TokenIssuer: jwtIssuer, + UserAttributes: []string{ + "dn", "uid", "mail", "cn", "ou", "memberOf", "telephoneNumber", + }, + UsernameAttr: "uid", + EmailAttr: "mail", + FullNameAttr: "cn", + DepartmentAttr: "ou", } - authenticator := ldapauth.New(cfg) + authenticator := ldapauth.New(&cfg) http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { - loginHandler(w, r, authenticator) + if r.Method != http.MethodPost { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + var creds struct { + Username string `json:"username"` + Password string `json:"password"` + } + + if err := json.NewDecoder(r.Body).Decode(&creds); err != nil { + http.Error(w, "invalid request body", http.StatusBadRequest) + return + } + + token, err := authenticator.Authenticate(creds.Username, creds.Password) + if err != nil { + switch { + case errors.Is(err, ldapauth.ErrUserNotFound): + http.Error(w, err.Error(), http.StatusUnauthorized) // 401 + case errors.Is(err, ldapauth.ErrMultipleUsersFound): + http.Error(w, err.Error(), http.StatusInternalServerError) // 500 + default: + http.Error(w, err.Error(), http.StatusUnauthorized) // default to 401 for bad creds + } + + return + } + + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]string{"token": token}) + }) + + http.HandleFunc("/userinfo", func(w http.ResponseWriter, r *http.Request) { + userInfoHandler(w, r, authenticator) }) fmt.Println("Listening on :8080") - // Add HTTP Timeouts srv := &http.Server{ Addr: ":8080", ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 120 * time.Second, } + log.Fatal(srv.ListenAndServe()) } - -func loginHandler(w http.ResponseWriter, r *http.Request, authenticator *ldapauth.Authenticator) { - var creds struct { - Username string `json:"username"` - Password string `json:"password"` - } - - if err := json.NewDecoder(r.Body).Decode(&creds); err != nil { - http.Error(w, "invalid request", http.StatusBadRequest) +func userInfoHandler(w http.ResponseWriter, r *http.Request, authenticator *ldapauth.Authenticator) { + username := r.URL.Query().Get("username") + if username == "" { + http.Error(w, "username parameter required", http.StatusBadRequest) return } - token, err := authenticator.Authenticate(creds.Username, creds.Password) + user, err := authenticator.GetUserInfo(username) if err != nil { - http.Error(w, err.Error(), http.StatusUnauthorized) + statusCode := getHTTPStatusForError(err) + http.Error(w, err.Error(), statusCode) + return } - if err := json.NewEncoder(w).Encode(map[string]string{"token": token}); err != nil { + if err := json.NewEncoder(w).Encode(user); err != nil { log.Println("encode error:", err) http.Error(w, "failed to write response", http.StatusInternalServerError) } } - +func getHTTPStatusForError(err error) int { + switch { + case errors.Is(err, ldapauth.ErrUserNotFound): + return http.StatusUnauthorized + case errors.Is(err, ldapauth.ErrMultipleUsersFound): + return http.StatusInternalServerError + default: + return http.StatusUnauthorized + } +} diff --git a/testuser.ldif b/testuser.ldif new file mode 100644 index 000000000..3d40a3035 --- /dev/null +++ b/testuser.ldif @@ -0,0 +1,10 @@ +dn: ou=users,dc=example,dc=com +objectClass: organizationalUnit +ou: users + +dn: uid=testuser,ou=users,dc=example,dc=com +objectClass: inetOrgPerson +uid: testuser +sn: User +cn: Test User +userPassword: testpass From 2ea5d3bed60de4310aa5c6d5d66ad4cd6855a224 Mon Sep 17 00:00:00 2001 From: mundele2004 Date: Sun, 10 Aug 2025 15:21:59 +0530 Subject: [PATCH 6/9] fix all linter issue --- internal/auth/jwt/jwt.go | 192 +++++++++++++++++++-------------------- mainFile/main.go | 2 +- 2 files changed, 97 insertions(+), 97 deletions(-) diff --git a/internal/auth/jwt/jwt.go b/internal/auth/jwt/jwt.go index 7d57bb77e..c287471a0 100644 --- a/internal/auth/jwt/jwt.go +++ b/internal/auth/jwt/jwt.go @@ -1,123 +1,123 @@ package jwt import ( - // Standard library - "errors" - "fmt" - "log" - "time" - - // Third-party - "github.com/golang-jwt/jwt/v4" + // Standard library. + "errors" + "fmt" + "log" + "time" + + // Third-party. + "github.com/golang-jwt/jwt/v4" ) var ( - ErrTokenSigningEmptyKey = errors.New("token signing error: no secret configured") - ErrUnexpectedSigningMethod = errors.New("unexpected signing method") - ErrInvalidToken = errors.New("invalid token") - ErrInvalidClaims = errors.New("invalid claims") - ErrMissingSubClaim = errors.New("missing sub claim") + ErrTokenSigningEmptyKey = errors.New("token signing error: no secret configured") + ErrUnexpectedSigningMethod = errors.New("unexpected signing method") + ErrInvalidToken = errors.New("invalid token") + ErrInvalidClaims = errors.New("invalid claims") + ErrMissingSubClaim = errors.New("missing sub claim") ) // User interface to avoid circular import. type User interface { - GetDN() string - GetUsername() string - GetEmail() string - GetFullName() string - GetGroups() []string - GetDepartment() string + GetDN() string + GetUsername() string + GetEmail() string + GetFullName() string + GetGroups() []string + GetDepartment() string } // Issuer implements TokenIssuer interface for JWT tokens. type Issuer struct { - secret string - ttl time.Duration - includeUserInfo bool + secret string + ttl time.Duration + includeUserInfo bool } // NewIssuer creates a new JWT token issuer. func NewIssuer(secret string, ttl time.Duration, includeUserInfo bool) *Issuer { - return &Issuer{ - secret: secret, - ttl: ttl, - includeUserInfo: includeUserInfo, - } + return &Issuer{ + secret: secret, + ttl: ttl, + includeUserInfo: includeUserInfo, + } } // IssueToken creates and signs a JWT token for the given user. func (j *Issuer) IssueToken(user User) (string, error) { - if j.secret == "" { - return "", ErrTokenSigningEmptyKey - } - - claims := jwt.MapClaims{ - "sub": user.GetUsername(), - "exp": time.Now().Add(j.ttl).Unix(), - "iat": time.Now().Unix(), - } - - // Optionally include user information in token claims. - if j.includeUserInfo { - if email := user.GetEmail(); email != "" { - claims["email"] = email - } - - if fullName := user.GetFullName(); fullName != "" { - claims["name"] = fullName - } - - if dept := user.GetDepartment(); dept != "" { - claims["department"] = dept - } - - if groups := user.GetGroups(); len(groups) > 0 { - claims["groups"] = groups - } - } - - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - - signed, err := token.SignedString([]byte(j.secret)) - if err != nil { - log.Printf("token signing error: %v", err) - return "", ErrTokenSigningEmptyKey - } - - return signed, nil + if j.secret == "" { + return "", ErrTokenSigningEmptyKey + } + + claims := jwt.MapClaims{ + "sub": user.GetUsername(), + "exp": time.Now().Add(j.ttl).Unix(), + "iat": time.Now().Unix(), + } + + // Optionally include user information in token claims. + if j.includeUserInfo { + if email := user.GetEmail(); email != "" { + claims["email"] = email + } + + if fullName := user.GetFullName(); fullName != "" { + claims["name"] = fullName + } + + if dept := user.GetDepartment(); dept != "" { + claims["department"] = dept + } + + if groups := user.GetGroups(); len(groups) > 0 { + claims["groups"] = groups + } + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + signed, err := token.SignedString([]byte(j.secret)) + if err != nil { + log.Printf("token signing error: %v", err) + return "", ErrTokenSigningEmptyKey + } + + return signed, nil } // ValidateToken parses and validates a JWT, returning the 'sub' claim. func (j *Issuer) ValidateToken(tokenStr string) (string, error) { - keyFn := func(token *jwt.Token) (any, error) { - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - log.Printf("unexpected signing method: %v", token.Header["alg"]) - return nil, ErrUnexpectedSigningMethod - } - - secret := []byte(j.secret) - - return secret, nil - } - - token, err := jwt.Parse(tokenStr, keyFn) - if err != nil { - return "", fmt.Errorf("token parse error: %w", err) - } - - if !token.Valid { - return "", ErrInvalidToken - } - - claims, ok := token.Claims.(jwt.MapClaims) - if !ok { - return "", ErrInvalidClaims - } - - sub, ok := claims["sub"].(string) - if !ok { - return "", ErrMissingSubClaim - } - - return sub, nil + keyFn := func(token *jwt.Token) (any, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + log.Printf("unexpected signing method: %v", token.Header["alg"]) + return nil, ErrUnexpectedSigningMethod + } + + secret := []byte(j.secret) + + return secret, nil + } + + token, err := jwt.Parse(tokenStr, keyFn) + if err != nil { + return "", fmt.Errorf("token parse error: %w", err) + } + + if !token.Valid { + return "", ErrInvalidToken + } + + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + return "", ErrInvalidClaims + } + + sub, ok := claims["sub"].(string) + if !ok { + return "", ErrMissingSubClaim + } + + return sub, nil } diff --git a/mainFile/main.go b/mainFile/main.go index 2fddda7ba..4b19fc930 100644 --- a/mainFile/main.go +++ b/mainFile/main.go @@ -58,7 +58,7 @@ func main() { default: http.Error(w, err.Error(), http.StatusUnauthorized) // default to 401 for bad creds } - + return } From 81699e5ea8013eb89955d4e27310271dce04c94e Mon Sep 17 00:00:00 2001 From: mundele2004 Date: Sun, 10 Aug 2025 15:43:55 +0530 Subject: [PATCH 7/9] Fix import formatting to comply with gci linter --- go.mod | 3 +- go.sum | 2 ++ go.work.sum | 82 ++++++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 72 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 8ebd0be07..b272f6acb 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,8 @@ require ( github.com/go-redis/redismock/v9 v9.2.0 github.com/go-sql-driver/mysql v1.9.3 github.com/gogo/protobuf v1.3.2 - github.com/golang-jwt/jwt/v5 v5.2.3 + github.com/golang-jwt/jwt/v4 v4.5.2 + github.com/golang-jwt/jwt/v5 v5.2.3 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.3 diff --git a/go.sum b/go.sum index 78c5f794b..3bd15c929 100644 --- a/go.sum +++ b/go.sum @@ -79,6 +79,8 @@ github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI6 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0= github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= diff --git a/go.work.sum b/go.work.sum index 31056a830..f4f83399d 100644 --- a/go.work.sum +++ b/go.work.sum @@ -11,6 +11,7 @@ cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI= cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cel.dev/expr v0.23.0 h1:wUb94w6OYQS4uXraxo9U+wUAs9jT47Xvl4iPgAwM2ss= cel.dev/expr v0.23.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= @@ -31,6 +32,8 @@ cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmW cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms= cloud.google.com/go v0.118.3/go.mod h1:Lhs3YLnBlwJ4KA6nuObNMZ/fCbOQBPuWKPoE0Wa/9Vc= +cloud.google.com/go v0.121.1 h1:S3kTQSydxmu1JfLRLpKtxRPA7rSrYPRPEUmL/PavVUw= +cloud.google.com/go v0.121.1/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw= cloud.google.com/go/accessapproval v1.8.2/go.mod h1:aEJvHZtpjqstffVwF/2mCXXSQmpskyzvw6zKLvLutZM= cloud.google.com/go/accessapproval v1.8.3 h1:axlU03FRiXDNupsmPG7LKzuS4Enk1gf598M62lWVB74= cloud.google.com/go/accessapproval v1.8.3/go.mod h1:3speETyAv63TDrDmo5lIkpVueFkQcQchkiw/TAMbBo4= @@ -49,6 +52,7 @@ cloud.google.com/go/aiplatform v1.74.0 h1:rE2P5H7FOAFISAZilmdkapbk4CVgwfVs6FDWlh cloud.google.com/go/aiplatform v1.74.0/go.mod h1:hVEw30CetNut5FrblYd1AJUWRVSIjoyIvp0EVUh51HA= cloud.google.com/go/aiplatform v1.85.0 h1:80/GqdP8Tovaaw9Qr6fYZNDvwJeA9rLk8mYkqBJNIJQ= cloud.google.com/go/aiplatform v1.85.0/go.mod h1:S4DIKz3TFLSt7ooF2aCRdAqsUR4v/YDXUoHqn5P0EFc= +cloud.google.com/go/aiplatform v1.89.0/go.mod h1:TzZtegPkinfXTtXVvZZpxx7noINFMVDrLkE7cEWhYEk= cloud.google.com/go/analytics v0.25.2/go.mod h1:th0DIunqrhI1ZWVlT3PH2Uw/9ANX8YHfFDEPqf/+7xM= cloud.google.com/go/analytics v0.25.3 h1:hX6JAsNbXd2uVjqjIuMcKpmhIybKrEunBiGxK4SwEFI= cloud.google.com/go/analytics v0.25.3/go.mod h1:pWoYg4yEr0iYg83LZRAicjDDdv54+Z//RyhzWwKbavI= @@ -56,6 +60,7 @@ cloud.google.com/go/analytics v0.26.0 h1:O2kWr2Sd4ep3I+YJ4aiY0G4+zWz6sp4eTce+JVn cloud.google.com/go/analytics v0.26.0/go.mod h1:KZWJfs8uX/+lTjdIjvT58SFa86V9KM6aPXwZKK6uNVI= cloud.google.com/go/analytics v0.28.0 h1:Bs17XtOjd+BhJtn+4QsCo8huMt7Zzziqn0umPz8ov2A= cloud.google.com/go/analytics v0.28.0/go.mod h1:hNT09bdzGB3HsL7DBhZkoPi4t5yzZPZROoFv+JzGR7I= +cloud.google.com/go/analytics v0.28.1/go.mod h1:iPaIVr5iXPB3JzkKPW1JddswksACRFl3NSHgVHsuYC4= cloud.google.com/go/apigateway v1.7.2/go.mod h1:+weId+9aR9J6GRwDka7jIUSrKEX60XGcikX7dGU8O7M= cloud.google.com/go/apigateway v1.7.3 h1:Mn7cC5iWJz+cSMS/Hb+N2410CpZ6c8XpJKaexBl0Gxs= cloud.google.com/go/apigateway v1.7.3/go.mod h1:uK0iRHdl2rdTe79bHW/bTsKhhXPcFihjUdb7RzhTPf4= @@ -91,6 +96,7 @@ cloud.google.com/go/asset v1.20.4 h1:6oNgjcs5KCPGBD71G0IccK6TfeFsEtBTyQ3Q+Dn09bs cloud.google.com/go/asset v1.20.4/go.mod h1:DP09pZ+SoFWUZyPZx26xVroHk+6+9umnQv+01yfJxbM= cloud.google.com/go/asset v1.21.0 h1:AtsFIJU1gH3jXHf+2cyugTkpOPT8VYyjCK2yNmQltvg= cloud.google.com/go/asset v1.21.0/go.mod h1:0lMJ0STdyImZDSCB8B3i/+lzIquLBpJ9KZ4pyRvzccM= +cloud.google.com/go/asset v1.21.1/go.mod h1:7AzY1GCC+s1O73yzLM1IpHFLHz3ws2OigmCpOQHwebk= cloud.google.com/go/assuredworkloads v1.12.2/go.mod h1:/WeRr/q+6EQYgnoYrqCVgw7boMoDfjXZZev3iJxs2Iw= cloud.google.com/go/assuredworkloads v1.12.3 h1:RU1WhF1zMggdXAZ+ezYTn4Eh/FdiX7sz8lLXGERn4Po= cloud.google.com/go/assuredworkloads v1.12.3/go.mod h1:iGBkyMGdtlsxhCi4Ys5SeuvIrPTeI6HeuEJt7qJgJT8= @@ -101,6 +107,8 @@ cloud.google.com/go/auth v0.12.1/go.mod h1:BFMu+TNpF3DmvfBO9ClqTR/SiqVIm7LukKF9m cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= cloud.google.com/go/auth v0.14.1/go.mod h1:4JHUxlGXisL0AW8kXPtUF6ztuOksyfUQNFjfsOCXkPM= cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= +cloud.google.com/go/auth v0.16.0/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= +cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= cloud.google.com/go/automl v1.14.2/go.mod h1:mIat+Mf77W30eWQ/vrhjXsXaRh8Qfu4WiymR0hR6Uxk= cloud.google.com/go/automl v1.14.4 h1:vkD+hQ75SMINMgJBT/KDpFYvfQLzJbtIQZdw0AWq8Rs= @@ -139,6 +147,7 @@ cloud.google.com/go/bigquery v1.66.2 h1:EKOSqjtO7jPpJoEzDmRctGea3c2EOGoexy8VyY9d cloud.google.com/go/bigquery v1.66.2/go.mod h1:+Yd6dRyW8D/FYEjUGodIbu0QaoEmgav7Lwhotup6njo= cloud.google.com/go/bigquery v1.67.0 h1:GXleMyn/cu5+DPLy9Rz5f5IULWTLrepwbQnP/5qrVbY= cloud.google.com/go/bigquery v1.67.0/go.mod h1:HQeP1AHFuAz0Y55heDSb0cjZIhnEkuwFRBGo6EEKHug= +cloud.google.com/go/bigquery v1.69.0/go.mod h1:TdGLquA3h/mGg+McX+GsqG9afAzTAcldMjqhdjHTLew= cloud.google.com/go/bigtable v1.33.0/go.mod h1:HtpnH4g25VT1pejHRtInlFPnN5sjTxbQlsYBjh9t5l0= cloud.google.com/go/bigtable v1.34.0 h1:eIgi3QLcN4aq8p6n9U/zPgmHeBP34sm9FiKq4ik/ZoY= cloud.google.com/go/bigtable v1.34.0/go.mod h1:p94uLf6cy6D73POkudMagaFF3x9c7ktZjRnOUVGjZAw= @@ -195,6 +204,8 @@ cloud.google.com/go/compute v1.34.0 h1:+k/kmViu4TEi97NGaxAATYtpYBviOWJySPZ+ekA95 cloud.google.com/go/compute v1.34.0/go.mod h1:zWZwtLwZQyonEvIQBuIa0WvraMYK69J5eDCOw9VZU4g= cloud.google.com/go/compute v1.37.0 h1:XxtZlXYkZXub3LNaLu90TTemcFqIU1yZ4E4q9VlR39A= cloud.google.com/go/compute v1.37.0/go.mod h1:AsK4VqrSyXBo4SMbRtfAO1VfaMjUEjEwv1UB/AwVp5Q= +cloud.google.com/go/compute v1.38.0 h1:MilCLYQW2m7Dku8hRIIKo4r0oKastlD74sSu16riYKs= +cloud.google.com/go/compute v1.38.0/go.mod h1:oAFNIuXOmXbK/ssXm3z4nZB8ckPdjltJ7xhHCdbWFZM= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= @@ -210,6 +221,7 @@ cloud.google.com/go/container v1.42.2 h1:8ncSEBjkng6ucCICauaUGzBomoM2VyYzleAum1O cloud.google.com/go/container v1.42.2/go.mod h1:y71YW7uR5Ck+9Vsbst0AF2F3UMgqmsN4SP8JR9xEsR8= cloud.google.com/go/container v1.42.4 h1:N8I+GiImhrSMUcKSOYTd8D6wBWyWSgPa4IJkSdlR2jk= cloud.google.com/go/container v1.42.4/go.mod h1:wf9lKc3ayWVbbV/IxKIDzT7E+1KQgzkzdxEJpj1pebE= +cloud.google.com/go/container v1.43.0/go.mod h1:ETU9WZ1KM9ikEKLzrhRVao7KHtalDQu6aPqM34zDr/U= cloud.google.com/go/containeranalysis v0.13.2/go.mod h1:AiKvXJkc3HiqkHzVIt6s5M81wk+q7SNffc6ZlkTDgiE= cloud.google.com/go/containeranalysis v0.13.3 h1:1D8U75BeotZxrG4jR6NYBtOt+uAeBsWhpBZmSYLakQw= cloud.google.com/go/containeranalysis v0.13.3/go.mod h1:0SYnagA1Ivb7qPqKNYPkCtphhkJn3IzgaSp3mj+9XAY= @@ -227,11 +239,13 @@ cloud.google.com/go/dataflow v0.10.3 h1:+7IfIXzYWSybIIDGK9FN2uqBsP/5b/Y0pBYzNhcm cloud.google.com/go/dataflow v0.10.3/go.mod h1:5EuVGDh5Tg4mDePWXMMGAG6QYAQhLNyzxdNQ0A1FfW4= cloud.google.com/go/dataflow v0.10.6 h1:UKUD8I7So3H646JHZWcrYVgf2nuEB27l015zUErPnow= cloud.google.com/go/dataflow v0.10.6/go.mod h1:Vi0pTYCVGPnM2hWOQRyErovqTu2xt2sr8Rp4ECACwUI= +cloud.google.com/go/dataflow v0.11.0/go.mod h1:gNHC9fUjlV9miu0hd4oQaXibIuVYTQvZhMdPievKsPk= cloud.google.com/go/dataform v0.10.2/go.mod h1:oZHwMBxG6jGZCVZqqMx+XWXK+dA/ooyYiyeRbUxI15M= cloud.google.com/go/dataform v0.10.3 h1:ZpGkZV8OyhUhvN/tfLffU2ki5ERTtqOunkIaiVAhmw0= cloud.google.com/go/dataform v0.10.3/go.mod h1:8SruzxHYCxtvG53gXqDZvZCx12BlsUchuV/JQFtyTCw= cloud.google.com/go/dataform v0.11.2 h1:poGCMWMvu/t2SooaWDHJAJiUyAtWYzKy+SGDNez2RI0= cloud.google.com/go/dataform v0.11.2/go.mod h1:IMmueJPEKpptT2ZLWlvIYjw6P/mYHHxA7/SUBiXqZUY= +cloud.google.com/go/dataform v0.12.0/go.mod h1:PuDIEY0lSVuPrZqcFji1fmr5RRvz3DGz4YP/cONc8g4= cloud.google.com/go/datafusion v1.8.2/go.mod h1:XernijudKtVG/VEvxtLv08COyVuiYPraSxm+8hd4zXA= cloud.google.com/go/datafusion v1.8.3 h1:FTMtsf2nfGGlDCuE84/RvVaCcTIYE7WQSB0noeO0cwI= cloud.google.com/go/datafusion v1.8.3/go.mod h1:hyglMzE57KRf0Rf/N2VRPcHCwKfZAAucx+LATY6Jc6Q= @@ -249,6 +263,7 @@ cloud.google.com/go/dataplex v1.22.0 h1:j4hD6opb+gq9CJNPFIlIggoW8Kjymg8Wmy2mdHmQ cloud.google.com/go/dataplex v1.22.0/go.mod h1:g166QMCGHvwc3qlTG4p34n+lHwu7JFfaNpMfI2uO7b8= cloud.google.com/go/dataplex v1.25.2 h1:jgfG6iqPVJxNPSpVCxH4diHMFb87wNd0F1kDgU3XJCk= cloud.google.com/go/dataplex v1.25.2/go.mod h1:AH2/a7eCYvFP58scJGR7YlSY9qEhM8jq5IeOA/32IZ0= +cloud.google.com/go/dataplex v1.25.3/go.mod h1:wOJXnOg6bem0tyslu4hZBTncfqcPNDpYGKzed3+bd+E= cloud.google.com/go/dataproc/v2 v2.10.0/go.mod h1:HD16lk4rv2zHFhbm8gGOtrRaFohMDr9f0lAUMLmg1PM= cloud.google.com/go/dataproc/v2 v2.10.1 h1:2vOv471LrcSn91VNzijcH+OkDRLa3kdyymOfKqbwZ4c= cloud.google.com/go/dataproc/v2 v2.10.1/go.mod h1:fq+LSN/HYUaaV2EnUPFVPxfe1XpzGVqFnL0TTXs8juk= @@ -261,6 +276,7 @@ cloud.google.com/go/dataqna v0.9.3 h1:lGUj2FYs650EUPDMV6plWBAoh8qH9Bu1KCz1PUYF2V cloud.google.com/go/dataqna v0.9.3/go.mod h1:PiAfkXxa2LZYxMnOWVYWz3KgY7txdFg9HEMQPb4u1JA= cloud.google.com/go/dataqna v0.9.6 h1:ymqgCzymbsVgBvD4jhdt7HN9cVwg9x60jkozpp/omFQ= cloud.google.com/go/dataqna v0.9.6/go.mod h1:rjnNwjh8l3ZsvrANy6pWseBJL2/tJpCcBwJV8XCx4kU= +cloud.google.com/go/dataqna v0.9.7/go.mod h1:4ac3r7zm7Wqm8NAc8sDIDM0v7Dz7d1e/1Ka1yMFanUM= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/datastore v1.20.0 h1:NNpXoyEqIJmZFc0ACcwBEaXnmscUpcG4NkKnbCePmiM= @@ -279,6 +295,7 @@ cloud.google.com/go/deploy v1.26.2 h1:1c2Cd3jdb0mrKHHfyzSQ5DRmxgYd07tIZZzuMNrwDx cloud.google.com/go/deploy v1.26.2/go.mod h1:XpS3sG/ivkXCfzbzJXY9DXTeCJ5r68gIyeOgVGxGNEs= cloud.google.com/go/deploy v1.27.1 h1:Rs8v4J68cZ45RfimX0wjraXaF4WZl1SIR+hkmGaK6Ag= cloud.google.com/go/deploy v1.27.1/go.mod h1:il2gxiMgV3AMlySoQYe54/xpgVDoEh185nj4XjJ+GRk= +cloud.google.com/go/deploy v1.27.2/go.mod h1:4NHWE7ENry2A4O1i/4iAPfXHnJCZ01xckAKpZQwhg1M= cloud.google.com/go/dialogflow v1.60.0/go.mod h1:PjsrI+d2FI4BlGThxL0+Rua/g9vLI+2A1KL7s/Vo3pY= cloud.google.com/go/dialogflow v1.64.1 h1:6fU4IKLpvgpXqiUCE8gUp8eV5u629SCtiyXMudXtZSg= cloud.google.com/go/dialogflow v1.64.1/go.mod h1:jkv4vTiGhEUPBzmk1sJ+S1Duu2epCOBNHoWUImHkO5U= @@ -293,6 +310,7 @@ cloud.google.com/go/dlp v1.21.0 h1:9kz7+gaB/0gBZsDUnNT1asDihNZSrRFSeUTBcBdUAkk= cloud.google.com/go/dlp v1.21.0/go.mod h1:Y9HOVtPoArpL9sI1O33aN/vK9QRwDERU9PEJJfM8DvE= cloud.google.com/go/dlp v1.22.1 h1:aZvDXCSNmPjhawF/thQa/GNIoW16JGNlI5L5N/HNXGU= cloud.google.com/go/dlp v1.22.1/go.mod h1:Gc7tGo1UJJTBRt4OvNQhm8XEQ0i9VidAiGXBVtsftjM= +cloud.google.com/go/dlp v1.23.0/go.mod h1:vVT4RlyPMEMcVHexdPT6iMVac3seq3l6b8UPdYpgFrg= cloud.google.com/go/documentai v1.35.0/go.mod h1:ZotiWUlDE8qXSUqkJsGMQqVmfTMYATwJEYqbPXTR9kk= cloud.google.com/go/documentai v1.35.1 h1:52RfiUsoblXcE57CfKJGnITWLxRM30BcqNk/BKZl2LI= cloud.google.com/go/documentai v1.35.1/go.mod h1:WJjwUAQfwQPJORW8fjz7RODprMULDzEGLA2E6WxenFw= @@ -342,6 +360,7 @@ cloud.google.com/go/gkebackup v1.6.3 h1:djdExe/QgoKdp1gnIO1G5BoO1o/yGQOQJJEZ4QKT cloud.google.com/go/gkebackup v1.6.3/go.mod h1:JJzGsA8/suXpTDtqI7n9RZW97PXa2CIp+n8aRC/y57k= cloud.google.com/go/gkebackup v1.7.0 h1:9nDcyMJvTEmsWhJv+sIqMLRIJaEmpkpirxt+cOlaDjM= cloud.google.com/go/gkebackup v1.7.0/go.mod h1:oPHXUc6X6tg6Zf/7QmKOfXOFaVzBEgMWpLDb4LqngWA= +cloud.google.com/go/gkebackup v1.8.0/go.mod h1:FjsjNldDilC9MWKEHExnK3kKJyTDaSdO1vF0QeWSOPU= cloud.google.com/go/gkeconnect v0.12.0/go.mod h1:zn37LsFiNZxPN4iO7YbUk8l/E14pAJ7KxpoXoxt7Ly0= cloud.google.com/go/gkeconnect v0.12.1 h1:YVpR0vlHSP/wD74PXEbKua4Aamud+wiYm4TiewNjD3M= cloud.google.com/go/gkeconnect v0.12.1/go.mod h1:L1dhGY8LjINmWfR30vneozonQKRSIi5DWGIHjOqo58A= @@ -361,6 +380,7 @@ cloud.google.com/go/gkemulticloud v1.5.3 h1:334aZmOzIt3LVBpguCof8IHaLaftcZlx+L0T cloud.google.com/go/gkemulticloud v1.5.3/go.mod h1:KPFf+/RcfvmuScqwS9/2MF5exZAmXSuoSLPuaQ98Xlk= cloud.google.com/go/grafeas v0.3.11 h1:CobnwnyeY1j1Defi5vbEircI+jfrk3ci5m004ZjiFP4= cloud.google.com/go/grafeas v0.3.11/go.mod h1:dcQyG2+T4tBgG0MvJAh7g2wl/xHV2w+RZIqivwuLjNg= +cloud.google.com/go/grafeas v0.3.15/go.mod h1:irwcwIQOBlLBotGdMwme8PipnloOPqILfIvMwlmu8Pk= cloud.google.com/go/gsuiteaddons v1.7.2/go.mod h1:GD32J2rN/4APilqZw4JKmwV84+jowYYMkEVwQEYuAWc= cloud.google.com/go/gsuiteaddons v1.7.3 h1:QafYhVhyFGpidBUUlVhy6lUHFogFOycVYm9DV7MinhA= cloud.google.com/go/gsuiteaddons v1.7.3/go.mod h1:0rR+LC21v1Sx1Yb6uohHI/F8DF3h2arSJSHvfi3GmyQ= @@ -375,6 +395,7 @@ cloud.google.com/go/iap v1.10.3 h1:OWNYFHPyIBNHEAEFdVKOltYWe0g3izSrpFJW6Iidovk= cloud.google.com/go/iap v1.10.3/go.mod h1:xKgn7bocMuCFYhzRizRWP635E2LNPnIXT7DW0TlyPJ8= cloud.google.com/go/iap v1.11.1 h1:RWWGRaPe/icBqNLTk83hfLkBZOh5TPufUTyWDWRldFo= cloud.google.com/go/iap v1.11.1/go.mod h1:qFipMJ4nOIv4yDHZxn31PiS8QxJJH2FlxgH9aFauejw= +cloud.google.com/go/iap v1.11.2/go.mod h1:Bh99DMUpP5CitL9lK0BC8MYgjjYO4b3FbyhgW1VHJvg= cloud.google.com/go/ids v1.5.2/go.mod h1:P+ccDD96joXlomfonEdCnyrHvE68uLonc7sJBPVM5T0= cloud.google.com/go/ids v1.5.3 h1:wbFF7twu0XScFr+dtsVxTTttbFIRYt/SJjZiHFidtYE= cloud.google.com/go/ids v1.5.3/go.mod h1:a2MX8g18Eqs7yxD/pnEdid42SyBUm9LIzSWf8Jux9OY= @@ -400,6 +421,7 @@ cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn9GqyjaBT8/mA= +cloud.google.com/go/longrunning v0.6.6/go.mod h1:hyeGJUrPHcx0u2Uu1UFSoYZLn4lkMrccJig0t4FI7yw= cloud.google.com/go/managedidentities v1.7.2/go.mod h1:t0WKYzagOoD3FNtJWSWcU8zpWZz2i9cw2sKa9RiPx5I= cloud.google.com/go/managedidentities v1.7.3 h1:b9xGs24BIjfyvLgCtJoClOZpPi8d8owPgWe5JEINgaY= cloud.google.com/go/managedidentities v1.7.3/go.mod h1:H9hO2aMkjlpY+CNnKWRh+WoQiUIDO8457wWzUGsdtLA= @@ -412,6 +434,7 @@ cloud.google.com/go/maps v1.19.0 h1:deVm1ZFyCrUwxG11CdvtBz350VG5JUQ/LHTLnQrBgrM= cloud.google.com/go/maps v1.19.0/go.mod h1:goHUXrmzoZvQjUVd0KGhH8t3AYRm17P8b+fsyR1UAmQ= cloud.google.com/go/maps v1.20.4 h1:vShJlIzVc3MSUcvdH1j2plmDP/KyWc9e0Th73mY4Kt0= cloud.google.com/go/maps v1.20.4/go.mod h1:Act0Ws4HffrECH+pL8YYy1scdSLegov7+0c6gvKqRzI= +cloud.google.com/go/maps v1.21.0/go.mod h1:cqzZ7+DWUKKbPTgqE+KuNQtiCRyg/o7WZF9zDQk+HQs= cloud.google.com/go/mediatranslation v0.9.2/go.mod h1:1xyRoDYN32THzy+QaU62vIMciX0CFexplju9t30XwUc= cloud.google.com/go/mediatranslation v0.9.3 h1:nRBjeaMLipw05Br+qDAlSCcCQAAlat4mvpafztbEVgc= cloud.google.com/go/mediatranslation v0.9.3/go.mod h1:KTrFV0dh7duYKDjmuzjM++2Wn6yw/I5sjZQVV5k3BAA= @@ -427,6 +450,7 @@ cloud.google.com/go/metastore v1.14.3 h1:jDqeCw6NGDRAPT9+2Y/EjnWAB0BfCcUfmPLOyhB cloud.google.com/go/metastore v1.14.3/go.mod h1:HlbGVOvg0ubBLVFRk3Otj3gtuzInuzO/TImOBwsKlG4= cloud.google.com/go/metastore v1.14.6 h1:X/eWwRv83ACfRPVrXlFM4DfJ7gwXRC1Tziv6w5MGxLU= cloud.google.com/go/metastore v1.14.6/go.mod h1:iDbuGwlDr552EkWA5E1Y/4hHme3cLv3ZxArKHXjS2OU= +cloud.google.com/go/metastore v1.14.7/go.mod h1:0dka99KQofeUgdfu+K/Jk1KeT9veWZlxuZdJpZPtuYU= cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= cloud.google.com/go/monitoring v1.22.1 h1:KQbnAC4IAH+5x3iWuPZT5iN9VXqKMzzOgqcYB6fqPDE= cloud.google.com/go/monitoring v1.22.1/go.mod h1:AuZZXAoN0WWWfsSvET1Cpc4/1D8LXq8KRDU87fMS6XY= @@ -480,6 +504,7 @@ cloud.google.com/go/osconfig v1.14.3 h1:cyf1PMK5c2/WOIr5r2lxjH/XBJMA9P4zC8Tm10i0 cloud.google.com/go/osconfig v1.14.3/go.mod h1:9D2MS1Etne18r/mAeW5jtto3toc9H1qu9wLNDG3NvQg= cloud.google.com/go/osconfig v1.14.5 h1:r3enRq2DarWyiE/BhHjZf1Yc/iC2YBsyvqqtEGD+upk= cloud.google.com/go/osconfig v1.14.5/go.mod h1:XH+NjBVat41I/+xgQzKOJEhuC4xI7lX2INE5SWnVr9U= +cloud.google.com/go/osconfig v1.14.6/go.mod h1:LS39HDBH0IJDFgOUkhSZUHFQzmcWaCpYXLrc3A4CVzI= cloud.google.com/go/oslogin v1.14.2/go.mod h1:M7tAefCr6e9LFTrdWRQRrmMeKHbkvc4D9g6tHIjHySA= cloud.google.com/go/oslogin v1.14.3 h1:yomxnFPk+ye0zd0mJ15nn9fH4Ns7ex4xA3ll+u2q59A= cloud.google.com/go/oslogin v1.14.3/go.mod h1:fDEGODTG/W9ZGUTHTlMh8euXWC1fTcgjJ9Kcxxy14a8= @@ -546,6 +571,7 @@ cloud.google.com/go/retail v1.19.2 h1:PT6CUlazIFIOLLJnV+bPBtiSH8iusKZ+FZRzZYFt2v cloud.google.com/go/retail v1.19.2/go.mod h1:71tRFYAcR4MhrZ1YZzaJxr030LvaZiIcupH7bXfFBcY= cloud.google.com/go/retail v1.20.0 h1:SbvW4zrmY+2sN76xU9syXzOGC9496TZ6r3euIyCn7Nw= cloud.google.com/go/retail v1.20.0/go.mod h1:1CXWDZDJTOsK6lPjkv67gValP9+h1TMadTC9NpFFr9s= +cloud.google.com/go/retail v1.21.0/go.mod h1:LuG+QvBdLfKfO+7nnF3eA3l1j4TQw3Sg+UqlUorquRc= cloud.google.com/go/run v1.7.0/go.mod h1:IvJOg2TBb/5a0Qkc6crn5yTy5nkjcgSWQLhgO8QL8PQ= cloud.google.com/go/run v1.8.1 h1:aeVLygw0BGLH+Zbj8v3K3nEHvKlgoq+j8fcRJaYZtxY= cloud.google.com/go/run v1.8.1/go.mod h1:wR5IG8Nujk9pyyNai187K4p8jzSLeqCKCAFBrZ2Sd4c= @@ -553,6 +579,7 @@ cloud.google.com/go/run v1.9.0 h1:9WeTqeEcriXqRViXMNwczjFJjixOSBlSlk/fW3lfKPg= cloud.google.com/go/run v1.9.0/go.mod h1:Dh0+mizUbtBOpPEzeXMM22t8qYQpyWpfmUiWQ0+94DU= cloud.google.com/go/run v1.9.3 h1:BrB0Y/BlsyWKdHebDp3CpbV9knwcWqqQI4RWYElf1zQ= cloud.google.com/go/run v1.9.3/go.mod h1:Si9yDIkUGr5vsXE2QVSWFmAjJkv/O8s3tJ1eTxw3p1o= +cloud.google.com/go/run v1.10.0/go.mod h1:z7/ZidaHOCjdn5dV0eojRbD+p8RczMk3A7Qi2L+koHg= cloud.google.com/go/scheduler v1.11.2/go.mod h1:GZSv76T+KTssX2I9WukIYQuQRf7jk1WI+LOcIEHUUHk= cloud.google.com/go/scheduler v1.11.3 h1:p6+h8BoYJC+TvUijGBfORN6nuhOvJ3EwZ2H84CZ1ZEU= cloud.google.com/go/scheduler v1.11.3/go.mod h1:Io2+gcvUjLX1GdymwaSPJ6ZYxHN9/NNGL5kIV3Ax5+Q= @@ -595,6 +622,7 @@ cloud.google.com/go/spanner v1.76.1 h1:vYbVZuXfnFwvNcvH3lhI2PeUA+kHyqKmLC7mJWaC4 cloud.google.com/go/spanner v1.76.1/go.mod h1:YtwoE+zObKY7+ZeDCBtZ2ukM+1/iPaMfUM+KnTh/sx0= cloud.google.com/go/spanner v1.80.0 h1:4B2hoN1TF0qghiK7CYjYzjRt0/EEacIlS/UJl0k2hKA= cloud.google.com/go/spanner v1.80.0/go.mod h1:XQWUqx9r8Giw6gNh0Gu8xYfz7O+dAKouAkFCxG/mZC8= +cloud.google.com/go/spanner v1.82.0/go.mod h1:BzybQHFQ/NqGxvE/M+/iU29xgutJf7Q85/4U9RWMto0= cloud.google.com/go/speech v1.25.2/go.mod h1:KPFirZlLL8SqPaTtG6l+HHIFHPipjbemv4iFg7rTlYs= cloud.google.com/go/speech v1.26.0 h1:qvURtJs7BQzQhbxWxwai0pT79S8KLVKJ/4W8igVkt1Y= cloud.google.com/go/speech v1.26.0/go.mod h1:78bqDV2SgwFlP/M4n3i3PwLthFq6ta7qmyG6lUV7UCA= @@ -610,11 +638,13 @@ cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyX cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= cloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6QJs= cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY= +cloud.google.com/go/storage v1.53.0/go.mod h1:7/eO2a/srr9ImZW9k5uufcNahT2+fPb8w5it1i5boaA= cloud.google.com/go/storagetransfer v1.11.2/go.mod h1:FcM29aY4EyZ3yVPmW5SxhqUdhjgPBUOFyy4rqiQbias= cloud.google.com/go/storagetransfer v1.12.1 h1:W3v9A7MGBN7H9sAFstyciwP/1XEQhUhZfrjclmDnpMs= cloud.google.com/go/storagetransfer v1.12.1/go.mod h1:hQqbfs8/LTmObJyCC0KrlBw8yBJ2bSFlaGila0qBMk4= cloud.google.com/go/storagetransfer v1.12.4 h1:2gFmZvD6G0qC57IIQ1Uga5TjvRwDyMW8lGLv9a8+tC4= cloud.google.com/go/storagetransfer v1.12.4/go.mod h1:p1xLKvpt78aQFRJ8lZGYArgFuL4wljFzitPZoYjl/8A= +cloud.google.com/go/storagetransfer v1.13.0/go.mod h1:+aov7guRxXBYgR3WCqedkyibbTICdQOiXOdpPcJCKl8= cloud.google.com/go/talent v1.7.2/go.mod h1:k1sqlDgS9gbc0gMTRuRQpX6C6VB7bGUxSPcoTRWJod8= cloud.google.com/go/talent v1.7.3 h1:mbN4dqACYBf8FIurOOTT4JXfFPkqWtOZccfMG9w03hY= cloud.google.com/go/talent v1.7.3/go.mod h1:6HhwxYxAtL6eKzcUMJ8reliQPUpay3/L6JZll4cS/vE= @@ -629,6 +659,7 @@ cloud.google.com/go/texttospeech v1.11.0 h1:YF/RdNb+jUEp22cIZCvqiFjfA5OxGE+Dxss3 cloud.google.com/go/texttospeech v1.11.0/go.mod h1:7M2ro3I2QfIEvArFk1TJ+pqXJqhszDtxUpnIv/150As= cloud.google.com/go/texttospeech v1.12.1 h1:IdYOIwagXmSjBuACNC86KTB3E/b7vgwyXzYzlLLxDhM= cloud.google.com/go/texttospeech v1.12.1/go.mod h1:f8vrD3OXAKTRr4eL0TPjZgYQhiN6ti/tKM3i1Uub5X0= +cloud.google.com/go/texttospeech v1.13.0/go.mod h1:g/tW/m0VJnulGncDrAoad6WdELMTes8eb77Idz+4HCo= cloud.google.com/go/tpu v1.7.2/go.mod h1:0Y7dUo2LIbDUx0yQ/vnLC6e18FK6NrDfAhYS9wZ/2vs= cloud.google.com/go/tpu v1.7.3 h1:PszqG+pvC7u/cv51GWQIN9M++jciIBr5vVn6/MWzU8I= cloud.google.com/go/tpu v1.7.3/go.mod h1:jZJET6Hp4VKRFHf+ABHVXW4mq1az4ZYHDLBKb5mYAWE= @@ -652,6 +683,7 @@ cloud.google.com/go/video v1.23.3 h1:C2FH+6yr6LCZC4fP0gm9FwJB/SRh5Ul88O5Sc/bL83I cloud.google.com/go/video v1.23.3/go.mod h1:Kvh/BheubZxGZDXSb0iO6YX7ZNcaYHbLjnnaC8Qyy3g= cloud.google.com/go/video v1.23.5 h1:leLw8LyDCR6K7HZkbIie3d45t0Z75BdJVC3WYP+MWy0= cloud.google.com/go/video v1.23.5/go.mod h1:ZSpGFCpfTOTmb1IkmHNGC/9yI3TjIa/vkkOKBDo0Vpo= +cloud.google.com/go/video v1.24.0/go.mod h1:h6Bw4yUbGNEa9dH4qMtUMnj6cEf+OyOv/f2tb70G6Fk= cloud.google.com/go/videointelligence v1.12.2/go.mod h1:8xKGlq0lNVyT8JgTkkCUCpyNJnYYEJVWGdqzv+UcwR8= cloud.google.com/go/videointelligence v1.12.3 h1:zNTOUQyatGQtnCJ2dR3faRtpWQOlC8wszJqwG5CtwVM= cloud.google.com/go/videointelligence v1.12.3/go.mod h1:dUA6V+NH7CVgX6TePq0IelVeBMGzvehxKPR4FGf1dtw= @@ -725,14 +757,17 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0/go.mod h1:6fTWu4m3jocfUZLYF5KsZC1TUfRvEjs7lM4crme/irw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0 h1:5IT7xOdq17MtcdtL/vtl6mGfzhaq4m4vpollPRmlsBQ= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0/go.mod h1:ZV4VOm0/eHR06JLrXWe09068dHpr3TRpY9Uo7T+anuA= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.49.0 h1:jJKWl98inONJAr/IZrdFQUWcwUO95DLY1XMD1ZIut+g= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.49.0/go.mod h1:l2fIqmwB+FKSfvn3bAD/0i+AXAxhIZjTK2svT/mgUXs= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.50.0 h1:nNMpRpnkWDAaqcpxMJvxa/Ud98gjbYwayJY4/9bdjiU= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.50.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 h1:GYUJLfvd++4DMuMhCFLgLXvFwofIxh/qOwoGuS/LTew= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0/go.mod h1:wRbFgBQUVm1YXrvWKofAEmq9HNJTDphbAaJSSX01KUI= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0 h1:ig/FpDD2JofP/NExKQUbn7uOSZzJAQqogfqluZK4ed4= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= github.com/IBM/sarama v1.43.1 h1:Z5uz65Px7f4DhI/jQqEm/tV9t8aU+JUdTyW/K/fCXpA= github.com/IBM/sarama v1.43.1/go.mod h1:GG5q1RURtDNPz8xxJs3mgX6Ytak8Z9eLhAkJPObe2xE= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= @@ -786,6 +821,7 @@ github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k= github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= @@ -820,6 +856,7 @@ github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswgg github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw= github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.3.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -832,6 +869,7 @@ github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE= @@ -888,6 +926,7 @@ github.com/golang/glog v1.2.3 h1:oDTdz9f5VGVVNGu/Q7UXKWYsD0873HXLHdJUNBsSEKM= github.com/golang/glog v1.2.3/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -946,10 +985,9 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8 h1:tlyzajkF3030q6M8SvmJSemC9DTHL/xaMa18b65+JM4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= -github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= @@ -972,16 +1010,6 @@ github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= -github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= -github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= -github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= -github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= -github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= -github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= -github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= -github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= -github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -1086,6 +1114,7 @@ github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11 github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs= github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI= +github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -1105,6 +1134,7 @@ github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5 github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8= github.com/testcontainers/testcontainers-go v0.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg= github.com/testcontainers/testcontainers-go v0.37.0/go.mod h1:QPzbxZhQ6Bclip9igjLFj6z0hs01bU8lrl2dHQmgFGM= +github.com/testcontainers/testcontainers-go v0.38.0/go.mod h1:C52c9MoHpWO+C4aqmgSU+hxlR5jlEayWtgYrb8Pzz1w= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= @@ -1138,13 +1168,16 @@ go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//sn go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= go.opentelemetry.io/contrib/detectors/gcp v1.35.0 h1:bGvFt68+KTiAKFlacHW6AhA56GF2rS0bdD3aJYEnmzA= go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0/go.mod h1:3qi2EEwMgB4xnKgPLqsDP3j9qxnHDZeHsnAxfjQqTko= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= @@ -1158,6 +1191,7 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMey go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0/go.mod h1:U2R3XyVPzn0WX7wOIypPuptulsMcPDPs/oiSVOMVnHY= go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= @@ -1171,7 +1205,9 @@ go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxt go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= @@ -1197,7 +1233,10 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= @@ -1265,7 +1304,7 @@ golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1291,6 +1330,7 @@ golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1340,12 +1380,13 @@ golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -1422,6 +1463,9 @@ google.golang.org/api v0.219.0/go.mod h1:K6OmjGm+NtLrIkHxv1U3a0qIf/0JOvAHd5O/6Ao google.golang.org/api v0.222.0/go.mod h1:efZia3nXpWELrwMlN5vyQrD4GmJN1Vw0x68Et3r+a9c= google.golang.org/api v0.224.0/go.mod h1:3V39my2xAGkodXy0vEqcEtkqgw2GtrFL5WuBZlCTCOQ= google.golang.org/api v0.227.0/go.mod h1:EIpaG6MbTgQarWF5xJvX0eOJPK9n/5D4Bynb9j2HXvQ= +google.golang.org/api v0.229.0/go.mod h1:wyDfmq5g1wYJWn29O22FDWN48P7Xcz0xz+LBpptYvB0= +google.golang.org/api v0.232.0/go.mod h1:p9QCfBWZk1IJETUdbTKloR5ToFdKbYh2fkjsUL6vNoY= +google.golang.org/api v0.239.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= @@ -1464,6 +1508,7 @@ google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY= google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4/go.mod h1:qbZzneIOXSq+KFAFut9krLfRLZiFLzZL5u2t8SV83EE= +google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE= google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6/go.mod h1:10yRODfgim2/T8csjQsMPgZOMvtytXKTDRzH6HRGzRw= google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= @@ -1478,6 +1523,9 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go. google.golang.org/genproto/googleapis/api v0.0.0-20250219182151-9fdb1cabc7b2/go.mod h1:W9ynFDP/shebLB1Hl/ESTOap2jHd6pmLXPNZC7SVDbA= google.golang.org/genproto/googleapis/api v0.0.0-20250227231956-55c901821b1e/go.mod h1:Xsh8gBVxGCcbV8ZeTB9wI5XPyZ5RvC6V3CTeeplHbiA= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= +google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e/go.mod h1:085qFyf2+XaZlRdCgKNCIZ3afY2p4HHZdoIRpId8F4A= +google.golang.org/genproto/googleapis/api v0.0.0-20250425173222-7b384671a197/go.mod h1:Cd8IzgPo5Akum2c9R6FsXNaZbH3Jpa2gpHlW89FqlyQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250106144421-5f5ef82da422 h1:w6g+P/ZscmNlGxVVXGaPVQOLu1q19ubsTOZKwaDqm4k= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250106144421-5f5ef82da422/go.mod h1:s4mHJ3FfG8P6A3O+gZ8TVqB3ufjOl9UG3ANCMMwCHmo= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250124145028-65684f501c47 h1:zYSVZD88HgcYTPowSo35t8Gdxpz+SYJ1CM0Kd/yugGw= @@ -1494,6 +1542,7 @@ google.golang.org/genproto/googleapis/bytestream v0.0.0-20250512202823-5a2f75b73 google.golang.org/genproto/googleapis/bytestream v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:h6yxum/C2qRb4txaZRLDHK8RyS0H/o2oEDeKY4onY/Y= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250603155806-513f23925822 h1:zWFRixYR5QlotL+Uv3YfsPRENIrQFXiGs+iwqel6fOQ= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250603155806-513f23925822/go.mod h1:h6yxum/C2qRb4txaZRLDHK8RyS0H/o2oEDeKY4onY/Y= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20250715232539-7130f93afb79/go.mod h1:h6yxum/C2qRb4txaZRLDHK8RyS0H/o2oEDeKY4onY/Y= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= @@ -1512,7 +1561,10 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20250227231956-55c901821b1e/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -1532,6 +1584,8 @@ google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe0 google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20 h1:MLBCGN1O7GzIx+cBiwfYPwtmZ41U3Mn/cotLJciaArI= google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20/go.mod h1:Nr5H8+MlGWr5+xX/STzdoEqJrO+YteqFbMyCsrb6mH0= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= From 9d6318dd111b8c7a8e1fe39bd5e128afad677084 Mon Sep 17 00:00:00 2001 From: mundele2004 Date: Tue, 12 Aug 2025 22:27:57 +0530 Subject: [PATCH 8/9] Fix gci import order in ldapauth package --- internal/auth/ldapauth/ldapauth.go | 1 + internal/auth/ldapauth/ldapauth_test.go | 1 + 2 files changed, 2 insertions(+) diff --git a/internal/auth/ldapauth/ldapauth.go b/internal/auth/ldapauth/ldapauth.go index 2695d816b..a2fea2f1d 100644 --- a/internal/auth/ldapauth/ldapauth.go +++ b/internal/auth/ldapauth/ldapauth.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/go-ldap/ldap/v3" + "gofr.dev/internal/auth/jwt" ) diff --git a/internal/auth/ldapauth/ldapauth_test.go b/internal/auth/ldapauth/ldapauth_test.go index ea35276bb..39808038c 100644 --- a/internal/auth/ldapauth/ldapauth_test.go +++ b/internal/auth/ldapauth/ldapauth_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/go-ldap/ldap/v3" + "gofr.dev/internal/auth/jwt" ) From b3e36101e434671fe2edda417378c27f32b0ce82 Mon Sep 17 00:00:00 2001 From: mundele2004 Date: Tue, 12 Aug 2025 22:42:41 +0530 Subject: [PATCH 9/9] Fix gci import order in ldapauth package --- go.mod | 1 + go.sum | 2 ++ go.work.sum | 1 + 3 files changed, 4 insertions(+) diff --git a/go.mod b/go.mod index d159606e8..993e06b36 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/go-redis/redismock/v9 v9.2.0 github.com/go-sql-driver/mysql v1.9.3 github.com/gogo/protobuf v1.3.2 + github.com/golang-jwt/jwt/v4 v4.5.2 github.com/golang-jwt/jwt/v5 v5.3.0 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 diff --git a/go.sum b/go.sum index 4e7f0829a..9bbc07fab 100644 --- a/go.sum +++ b/go.sum @@ -79,6 +79,8 @@ github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI6 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= diff --git a/go.work.sum b/go.work.sum index f4f83399d..3dc840025 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1543,6 +1543,7 @@ google.golang.org/genproto/googleapis/bytestream v0.0.0-20250512202823-5a2f75b73 google.golang.org/genproto/googleapis/bytestream v0.0.0-20250603155806-513f23925822 h1:zWFRixYR5QlotL+Uv3YfsPRENIrQFXiGs+iwqel6fOQ= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250603155806-513f23925822/go.mod h1:h6yxum/C2qRb4txaZRLDHK8RyS0H/o2oEDeKY4onY/Y= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250715232539-7130f93afb79/go.mod h1:h6yxum/C2qRb4txaZRLDHK8RyS0H/o2oEDeKY4onY/Y= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20250728155136-f173205681a0/go.mod h1:h6yxum/C2qRb4txaZRLDHK8RyS0H/o2oEDeKY4onY/Y= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=