Compare commits
4 commits
71805cf65c
...
e6bca3ac8f
| Author | SHA1 | Date | |
|---|---|---|---|
| e6bca3ac8f | |||
| f517be8afb | |||
| 3e6b95bf40 | |||
| 4a68d0700d |
7 changed files with 123 additions and 69 deletions
2
addy.go
2
addy.go
|
|
@ -115,6 +115,7 @@ func addyAliasAPI(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
err = conn.ServiceBind()
|
||||
if err != nil {
|
||||
|
|
@ -197,6 +198,7 @@ func addyAliasAPIDelete(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
err = conn.ServiceBind()
|
||||
if err != nil {
|
||||
|
|
|
|||
72
change.go
72
change.go
|
|
@ -67,31 +67,51 @@ func changePassword(w http.ResponseWriter, r *http.Request) {
|
|||
// Check the two new passwords are identical
|
||||
if r.PostFormValue("newpassword") != r.PostFormValue("new2password") {
|
||||
renderError(http.StatusNotAcceptable, "New passwords are not identical. Please retry.")
|
||||
} else if len(r.PostFormValue("login")) == 0 {
|
||||
renderError(http.StatusNotAcceptable, "Please provide a valid login")
|
||||
} else if err := checkPasswdConstraint(r.PostFormValue("newpassword")); err != nil {
|
||||
renderError(http.StatusNotAcceptable, "The password you chose doesn't respect all constraints: "+err.Error())
|
||||
} else {
|
||||
conn, err := myLDAP.Connect()
|
||||
if err != nil || conn == nil {
|
||||
log.Println(err)
|
||||
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
|
||||
} else if err := conn.ServiceBind(); err != nil {
|
||||
log.Println(err)
|
||||
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
|
||||
} else if dn, err := conn.SearchDN(r.PostFormValue("login"), true); err != nil {
|
||||
log.Println(err)
|
||||
// User not found: perform a dummy bind to prevent username enumeration via timing.
|
||||
conn.Bind("cn=dummy,"+myLDAP.BaseDN, r.PostFormValue("password"))
|
||||
renderError(http.StatusUnauthorized, "Invalid login or password.")
|
||||
} else if err := conn.Bind(dn, r.PostFormValue("password")); err != nil {
|
||||
log.Println(err)
|
||||
renderError(http.StatusUnauthorized, "Invalid login or password.")
|
||||
} else if err := conn.ChangePassword(dn, r.PostFormValue("newpassword")); err != nil {
|
||||
log.Println(err)
|
||||
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
|
||||
} else {
|
||||
displayMsg(w, "Password successfully changed!", http.StatusOK)
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(r.PostFormValue("login")) == 0 {
|
||||
renderError(http.StatusNotAcceptable, "Please provide a valid login")
|
||||
return
|
||||
}
|
||||
if err := checkPasswdConstraint(r.PostFormValue("newpassword")); err != nil {
|
||||
renderError(http.StatusNotAcceptable, "The password you chose doesn't respect all constraints: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := myLDAP.Connect()
|
||||
if err != nil || conn == nil {
|
||||
log.Println(err)
|
||||
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
if err := conn.ServiceBind(); err != nil {
|
||||
log.Println(err)
|
||||
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
|
||||
return
|
||||
}
|
||||
|
||||
dn, err := conn.SearchDN(r.PostFormValue("login"), true)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
// User not found: perform a dummy bind to prevent username enumeration via timing.
|
||||
conn.Bind("cn=dummy,"+myLDAP.BaseDN, r.PostFormValue("password"))
|
||||
renderError(http.StatusUnauthorized, "Invalid login or password.")
|
||||
return
|
||||
}
|
||||
|
||||
if err := conn.Bind(dn, r.PostFormValue("password")); err != nil {
|
||||
log.Println(err)
|
||||
renderError(http.StatusUnauthorized, "Invalid login or password.")
|
||||
return
|
||||
}
|
||||
|
||||
if err := conn.ChangePassword(dn, r.PostFormValue("newpassword")); err != nil {
|
||||
log.Println(err)
|
||||
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
|
||||
return
|
||||
}
|
||||
|
||||
displayMsg(w, "Password successfully changed!", http.StatusOK)
|
||||
}
|
||||
|
|
|
|||
84
ldap.go
84
ldap.go
|
|
@ -4,8 +4,10 @@ import (
|
|||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/amoghe/go-crypt"
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
|
|
@ -19,38 +21,45 @@ type LDAP struct {
|
|||
BaseDN string
|
||||
ServiceDN string
|
||||
ServicePassword string
|
||||
MailHost string
|
||||
MailPort int
|
||||
MailUser string
|
||||
MailPassword string
|
||||
MailFrom string
|
||||
}
|
||||
|
||||
type SMTPConfig struct {
|
||||
MailHost string
|
||||
MailPort int
|
||||
MailUser string
|
||||
MailPassword string
|
||||
MailFrom string
|
||||
}
|
||||
|
||||
func (l LDAP) Connect() (*LDAPConn, error) {
|
||||
if l.Ssl {
|
||||
if c, err := ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", l.Host, l.Port), &tls.Config{ServerName: l.Host}); err != nil {
|
||||
return nil, errors.New("unable to establish LDAPS connection to " + fmt.Sprintf("%s:%d", l.Host, l.Port) + ": " + err.Error())
|
||||
} else {
|
||||
return &LDAPConn{
|
||||
LDAP: l,
|
||||
connection: c,
|
||||
}, nil
|
||||
}
|
||||
} else if c, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", l.Host, l.Port)); err != nil {
|
||||
return nil, errors.New("unable to establish LDAP connection to " + fmt.Sprintf("%s:%d", l.Host, l.Port) + ": " + err.Error())
|
||||
} else {
|
||||
if l.Starttls {
|
||||
if err = c.StartTLS(&tls.Config{ServerName: l.Host}); err != nil {
|
||||
c.Close()
|
||||
return nil, errors.New("unable to StartTLS: " + err.Error())
|
||||
}
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", l.Host, l.Port)
|
||||
|
||||
return &LDAPConn{
|
||||
LDAP: l,
|
||||
connection: c,
|
||||
}, nil
|
||||
var opts []ldap.DialOpt
|
||||
if l.Ssl {
|
||||
opts = append(opts, ldap.DialWithTLSConfig(&tls.Config{ServerName: l.Host}))
|
||||
}
|
||||
|
||||
scheme := "ldap"
|
||||
if l.Ssl {
|
||||
scheme = "ldaps"
|
||||
}
|
||||
|
||||
c, err := ldap.DialURL(fmt.Sprintf("%s://%s", scheme, addr), opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to establish %s connection to %s: %w", strings.ToUpper(scheme), addr, err)
|
||||
}
|
||||
|
||||
if l.Starttls {
|
||||
if err = c.StartTLS(&tls.Config{ServerName: l.Host}); err != nil {
|
||||
c.Close()
|
||||
return nil, fmt.Errorf("unable to StartTLS: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &LDAPConn{
|
||||
LDAP: l,
|
||||
connection: c,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type LDAPConn struct {
|
||||
|
|
@ -58,6 +67,10 @@ type LDAPConn struct {
|
|||
connection *ldap.Conn
|
||||
}
|
||||
|
||||
func (l LDAPConn) Close() {
|
||||
l.connection.Close()
|
||||
}
|
||||
|
||||
func (l LDAPConn) ServiceBind() error {
|
||||
return l.connection.Bind(l.ServiceDN, l.ServicePassword)
|
||||
}
|
||||
|
|
@ -85,8 +98,11 @@ func (l LDAPConn) SearchDN(username string, person bool) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
if len(sr.Entries) != 1 {
|
||||
return "", errors.New("User does not exist or too many entries returned")
|
||||
if len(sr.Entries) == 0 {
|
||||
return "", fmt.Errorf("user %q not found", username)
|
||||
}
|
||||
if len(sr.Entries) > 1 {
|
||||
return "", fmt.Errorf("multiple entries (%d) found for user %q", len(sr.Entries), username)
|
||||
}
|
||||
|
||||
return sr.Entries[0].DN, nil
|
||||
|
|
@ -104,8 +120,11 @@ func (l LDAPConn) GetEntry(dn string) ([]*ldap.EntryAttribute, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if len(sr.Entries) != 1 {
|
||||
return nil, errors.New("User does not exist or too many entries returned")
|
||||
if len(sr.Entries) == 0 {
|
||||
return nil, fmt.Errorf("entry not found for DN %q", dn)
|
||||
}
|
||||
if len(sr.Entries) > 1 {
|
||||
return nil, fmt.Errorf("multiple entries (%d) found for DN %q", len(sr.Entries), dn)
|
||||
}
|
||||
|
||||
return sr.Entries[0].Attributes, nil
|
||||
|
|
@ -133,6 +152,7 @@ func (l LDAPConn) ChangePassword(dn string, rawpassword string) error {
|
|||
|
||||
modify := ldap.NewModifyRequest(dn, nil)
|
||||
modify.Replace("userPassword", []string{"{CRYPT}" + hashedpasswd})
|
||||
modify.Replace("shadowLastChange", []string{strconv.FormatInt(time.Now().Unix()/86400, 10)})
|
||||
|
||||
return l.connection.Modify(modify)
|
||||
}
|
||||
|
|
|
|||
1
login.go
1
login.go
|
|
@ -78,6 +78,7 @@ func login(login string, password string) ([]*ldap.EntryAttribute, error) {
|
|||
if err != nil || conn == nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
if err = conn.ServiceBind(); err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
7
lost.go
7
lost.go
|
|
@ -129,6 +129,7 @@ func lostPassword(w http.ResponseWriter, r *http.Request) {
|
|||
displayTmplError(w, http.StatusInternalServerError, "lost.html", map[string]any{"error": "Unable to process your request. Please try again later."})
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Generate the token
|
||||
token, dn, err := lostPasswordToken(conn, r.PostFormValue("login"))
|
||||
|
|
@ -166,14 +167,14 @@ func lostPassword(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// Send the email
|
||||
m := gomail.NewMessage()
|
||||
m.SetHeader("From", myLDAP.MailFrom)
|
||||
m.SetHeader("From", mySMTP.MailFrom)
|
||||
m.SetHeader("To", email)
|
||||
m.SetHeader("Subject", "SSO nemunai.re: password recovery")
|
||||
m.SetBody("text/plain", "Hello "+cn+"!\n\nSomeone, and we hope it's you, requested to reset your account password. \nIn order to continue, go to:\n"+myPublicURL+"/reset?l="+r.PostFormValue("login")+"&t="+token+"\n\nThis link expires in 1 hour and can only be used once.\n\nBest regards,\n-- \nnemunai.re SSO")
|
||||
|
||||
var s gomail.Sender
|
||||
if myLDAP.MailHost != "" {
|
||||
d := gomail.NewDialer(myLDAP.MailHost, myLDAP.MailPort, myLDAP.MailUser, myLDAP.MailPassword)
|
||||
if mySMTP.MailHost != "" {
|
||||
d := gomail.NewDialer(mySMTP.MailHost, mySMTP.MailPort, mySMTP.MailUser, mySMTP.MailPassword)
|
||||
s, err = d.Dial()
|
||||
if err != nil {
|
||||
log.Println("Unable to connect to email server: " + err.Error())
|
||||
|
|
|
|||
31
main.go
31
main.go
|
|
@ -31,9 +31,12 @@ var dockerRegistrySecret string
|
|||
var allowedAliasDomains []string
|
||||
|
||||
var myLDAP = LDAP{
|
||||
Host: "localhost",
|
||||
Port: 389,
|
||||
BaseDN: "dc=example,dc=com",
|
||||
Host: "localhost",
|
||||
Port: 389,
|
||||
BaseDN: "dc=example,dc=com",
|
||||
}
|
||||
|
||||
var mySMTP = SMTPConfig{
|
||||
MailPort: 587,
|
||||
MailFrom: "noreply@example.com",
|
||||
}
|
||||
|
|
@ -115,8 +118,13 @@ func main() {
|
|||
log.Fatal(err)
|
||||
} else if cnt, err := io.ReadAll(fd); err != nil {
|
||||
log.Fatal(err)
|
||||
} else if err := json.Unmarshal(cnt, &myLDAP); err != nil {
|
||||
log.Fatal(err)
|
||||
} else {
|
||||
if err := json.Unmarshal(cnt, &myLDAP); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := json.Unmarshal(cnt, &mySMTP); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -156,17 +164,17 @@ func main() {
|
|||
}
|
||||
|
||||
if val, ok := os.LookupEnv("SMTP_HOST"); ok {
|
||||
myLDAP.MailHost = val
|
||||
mySMTP.MailHost = val
|
||||
}
|
||||
if val, ok := os.LookupEnv("SMTP_PORT"); ok {
|
||||
if port, err := strconv.Atoi(val); err == nil {
|
||||
myLDAP.MailPort = port
|
||||
mySMTP.MailPort = port
|
||||
} else {
|
||||
log.Println("Invalid value for SMTP_PORT:", val)
|
||||
}
|
||||
}
|
||||
if val, ok := os.LookupEnv("SMTP_USER"); ok {
|
||||
myLDAP.MailUser = val
|
||||
mySMTP.MailUser = val
|
||||
}
|
||||
if val, ok := os.LookupEnv("SMTP_PASSWORD_FILE"); ok {
|
||||
if fd, err := os.Open(val); err != nil {
|
||||
|
|
@ -176,13 +184,13 @@ func main() {
|
|||
log.Fatal(err)
|
||||
} else {
|
||||
fd.Close()
|
||||
myLDAP.MailPassword = string(cnt)
|
||||
mySMTP.MailPassword = string(cnt)
|
||||
}
|
||||
} else if val, ok := os.LookupEnv("SMTP_PASSWORD"); ok {
|
||||
myLDAP.MailPassword = val
|
||||
mySMTP.MailPassword = val
|
||||
}
|
||||
if val, ok := os.LookupEnv("SMTP_FROM"); ok {
|
||||
myLDAP.MailFrom = val
|
||||
mySMTP.MailFrom = val
|
||||
}
|
||||
if val, ok := os.LookupEnv("PUBLIC_URL"); ok {
|
||||
myPublicURL = val
|
||||
|
|
@ -207,6 +215,7 @@ func main() {
|
|||
if err != nil || conn == nil {
|
||||
log.Fatalf("Unable to connect to LDAP: %s", err.Error())
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
token, dn, err := lostPasswordToken(conn, login)
|
||||
if err != nil {
|
||||
|
|
|
|||
1
reset.go
1
reset.go
|
|
@ -79,6 +79,7 @@ func resetPassword(w http.ResponseWriter, r *http.Request) {
|
|||
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Bind as service to perform the password change
|
||||
err = conn.ServiceBind()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue