diff --git a/go.mod b/go.mod index 0f915f6..35c9d86 100644 --- a/go.mod +++ b/go.mod @@ -20,13 +20,16 @@ require ( github.com/jinzhu/copier v0.4.0 github.com/jmoiron/sqlx v1.4.0 github.com/joho/godotenv v1.5.1 + github.com/korylprince/go-ad-auth/v3 v3.3.0 github.com/labstack/echo/v4 v4.13.3 github.com/lib/pq v1.10.9 + github.com/mcnijman/go-emailaddress v1.1.1 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pressly/goose/v3 v3.24.1 github.com/stretchr/testify v1.10.0 github.com/xhit/go-simple-mail/v2 v2.16.0 go.uber.org/mock v0.5.0 + golang.org/x/crypto v0.33.0 gopkg.in/guregu/null.v4 v4.0.0 ) @@ -37,6 +40,8 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/go-asn1-ber/asn1-ber v1.4.1 // indirect + github.com/go-ldap/ldap/v3 v3.1.7 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-test/deep v1.1.1 // indirect @@ -57,7 +62,6 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.33.0 // indirect golang.org/x/net v0.35.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect diff --git a/go.sum b/go.sum index 39276c9..d26c6da 100644 --- a/go.sum +++ b/go.sum @@ -23,6 +23,11 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-asn1-ber/asn1-ber v1.4.1 h1:qP/QDxOtmMoJVgXHCXNzDpA0+wkgYB2x5QoLMVOciyw= +github.com/go-asn1-ber/asn1-ber v1.4.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap/v3 v3.1.7 h1:aHjuWTgZsnxjMgqzx0JHwNqz4jBYZTcNarbPFkW1Oww= +github.com/go-ldap/ldap/v3 v3.1.7/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -70,6 +75,8 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/korylprince/go-ad-auth/v3 v3.3.0 h1:iXuB+sCk4GniHnpUn0BAHH8rKeOLTKuYcBNvERa773Y= +github.com/korylprince/go-ad-auth/v3 v3.3.0/go.mod h1:19M0geaOeNN489k1MO6GCqOCgbruYRQkHRBfhhUZAoE= github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= @@ -88,6 +95,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mcnijman/go-emailaddress v1.1.1 h1:AGhgVDG3tCDaL0/Vc6erlPQjDuDN3dAT7rRdgFtetr0= +github.com/mcnijman/go-emailaddress v1.1.1/go.mod h1:5whZrhS8Xp5LxO8zOD35BC+b76kROtsh+dPomeRt/II= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= @@ -143,6 +152,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -157,10 +167,12 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b h1:FQtJ1MxbXoIIrZHZ33M+w5+dAP9o86rgpjoKr/ZmT7k= diff --git a/main.go b/main.go index 325004e..a9690cd 100644 --- a/main.go +++ b/main.go @@ -91,6 +91,16 @@ func main() { } log.Printf("Connected to CDN: %s", cdnConfig.Endpoint) + adPort, err := strconv.Atoi(os.Getenv("WAUTH_AD_PORT")) + if err != nil { + log.Fatalf("failed to get ad port env: %+v", err) + } + + adSecurity, err := strconv.Atoi(os.Getenv("WAUTH_AD_SECURITY")) + if err != nil { + log.Fatalf("failed to get ad security env: %+v", err) + } + // Generate config conf := &views.Config{ Version: Version, @@ -115,6 +125,16 @@ func main() { AuthenticationKey: os.Getenv("WAUTH_AUTHENTICATION_KEY"), SigningKey: signingKey, }, + AD: views.ADConfig{ + Server: os.Getenv("WAUTH_AD_SERVER"), + Port: adPort, + BaseDN: os.Getenv("WAUTH_AD_BASE_DN"), + Security: adSecurity, + Bind: views.ADBind{ + Username: os.Getenv("WAUTH_AD_BIND_USERNAME"), + Password: os.Getenv("WAUTH_AD_BIND_PASSWORD"), + }, + }, } v := views.New(conf, dbHost, cdn) diff --git a/utils/hash.go b/utils/hash.go index 3954aa8..07f67e7 100644 --- a/utils/hash.go +++ b/utils/hash.go @@ -8,6 +8,7 @@ import ( "math/big" whirl "github.com/balacode/zr-whirl" + "golang.org/x/crypto/scrypt" ) type ( @@ -50,6 +51,14 @@ func HashPass(password string) string { return next } +func HashPassScrypt(password, salt []byte) (string, error) { + hash, err := scrypt.Key(password, salt, 32768, 16, 2, 64) + if err != nil { + return "", fmt.Errorf("failed to generate hash: %w", err) + } + return hex.EncodeToString(hash), nil +} + func GenerateRandomLength(length int, randomType Type) (string, error) { if length < 6 || length > 40 { return "", errors.New("length must be between 6 and 40") diff --git a/views/api.go b/views/api.go index 7910fb4..5b42f74 100644 --- a/views/api.go +++ b/views/api.go @@ -233,6 +233,47 @@ func (v *Views) SetTokenHandler(c echo.Context) error { return c.JSON(http.StatusInternalServerError, data) } + /* + _ = tokenByte + + callback := "" + callbackURL, err := url.Parse(c.QueryParam("callback")) + fmt.Println(callbackURL.String(), err) + if err == nil && strings.HasSuffix(callbackURL.Host, v.conf.BaseDomainName) && callbackURL.String() != "" { + callback = callbackURL.String() + } + // c.Response().Header().Set("Content-Type", "application/json") + c.Response().Header().Set("Authorization", "Bearer "+tokenString) + cookie := new(http.Cookie) + cookie.Name = "token" + cookie.Expires = time.Now().Add(30 * time.Second) + cookie.Value = tokenString + cookie.Secure = false + cookie.HttpOnly = false + cookie.Domain = "localhost" + c.SetCookie(cookie) + http.SetCookie(c.Response().Writer, cookie) + c.Response().Committed = false + // c.Response().Write(tokenByte) + // _, err = c.Response().Write(tokenByte) + // if err != nil { + // log.Printf("failed to write token to http body: %+v", err) + // data := struct { + // Error error `json:"error"` + // }{ + // Error: fmt.Errorf("failed to write token to http body: %w", err), + // } + // return c.JSON(http.StatusInternalServerError, data) + // } + if len(callback) > 0 { + // c.Response().Header().Set("Location", callback) + // c.Response().WriteHeader(http.StatusFound) + return c.Redirect(http.StatusFound, callback+"?token="+tokenString) + // c.Redirect() + } + return nil + */ + c.Response().Header().Set("Content-Type", "application/json") c.Response().WriteHeader(http.StatusCreated) diff --git a/views/login.go b/views/login.go index e905bb3..abfb529 100644 --- a/views/login.go +++ b/views/login.go @@ -1,14 +1,19 @@ package views import ( + "errors" "fmt" "log" "net/http" + "net/mail" "net/url" "strings" + "github.com/go-ldap/ldap/v3" "github.com/google/uuid" + auth "github.com/korylprince/go-ad-auth/v3" "github.com/labstack/echo/v4" + emailParser "github.com/mcnijman/go-emailaddress" "github.com/patrickmn/go-cache" "gopkg.in/guregu/null.v4" @@ -76,6 +81,23 @@ func (v *Views) _loginPost(c echo.Context) error { // Authentication u, resetPw, err := v.user.VerifyUser(c.Request().Context(), u) if err != nil { + address, _ := emailParser.Parse(username) + if address != nil { + u.LDAPUsername = null.StringFrom(address.LocalPart) + ldapUser, err := v.user.GetUser(c.Request().Context(), u) + if err != nil { + return fmt.Errorf("failed to get user ldap: %w", err) + } + if !ldapUser.LDAPUsername.Valid { + return errors.New("failed to get user LDAP username") + } + valid, err := v.LDAPFunc(ldapUser.LDAPUsername.String, password) + if err != nil { + return fmt.Errorf("failed to call LDAP function: %w", err) + } + + fmt.Println("LDAP: ", valid) + } log.Printf("failed login for \"%s\": %v", u.Username, err) err = session.Save(c.Request(), c.Response()) @@ -147,3 +169,66 @@ func (v *Views) _loginPost(c echo.Context) error { return c.Redirect(http.StatusFound, callback) } + +func (v *Views) LDAPFunc(username, password string) (bool, error) { + config := &auth.Config{ + Server: v.conf.AD.Server, + Port: v.conf.AD.Port, + BaseDN: v.conf.AD.BaseDN, + Security: auth.SecurityType(v.conf.AD.Security), + } + + conn, err := config.Connect() + if err != nil { + return false, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("error connecting to server: %w", err)) + } + defer conn.Conn.Close() + + status, err := conn.Bind(v.conf.AD.Bind.Username, v.conf.AD.Bind.Password) + if err != nil { + return false, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("error binding to server: %w", err)) + } + + if !status { + return false, echo.NewHTTPError(http.StatusInternalServerError, errors.New("error binding to server: invalid credentials")) + } + + status1, err := auth.Authenticate(config, username, password) + if err != nil { + return false, echo.NewHTTPError(http.StatusUnauthorized, fmt.Errorf("unable to authenticate %s with error: %w", username, err)) + } + + if status1 { + var entry *ldap.Entry + if _, err = mail.ParseAddress(username); err == nil { + entry, err = conn.GetAttributes("userPrincipalName", username, []string{"memberOf"}) + } else { + entry, err = conn.GetAttributes("samAccountName", username, []string{"memberOf"}) + } + if err != nil { + return false, echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("error getting user groups: %w", err)) + } + + dnGroups := entry.GetAttributeValues("memberOf") + + if len(dnGroups) == 0 { + return false, echo.NewHTTPError(http.StatusUnauthorized, errors.New("BIND_SAM user not member of any groups")) + } + + // stv := false + + for _, group := range dnGroups { + if group == "CN=STV Admin,CN=Users,DC=ystv,DC=local" { + // stv = true + return true, nil + } + } + + // if !stv { + // return false, echo.NewHTTPError(http.StatusUnauthorized, fmt.Errorf("STV not allowed for %s!\n", username)) + // } + log.Printf("%s is authenticated", username) + return true, nil + } + return false, echo.NewHTTPError(http.StatusUnauthorized, fmt.Errorf("user not authenticated: %s", username)) +} diff --git a/views/views.go b/views/views.go index 542ffbd..1faa69d 100644 --- a/views/views.go +++ b/views/views.go @@ -43,6 +43,20 @@ type ( CDNEndpoint string Mail SMTPConfig Security SecurityConfig + AD ADConfig + } + + ADConfig struct { + Server string + Port int + BaseDN string + Security int + Bind ADBind + } + + ADBind struct { + Username string + Password string } // SMTPConfig stores the SMTP Mailer configuration