diff --git a/src/components/profile_photo.go b/src/components/profile_photo.go index 180fc2d..b35ce79 100644 --- a/src/components/profile_photo.go +++ b/src/components/profile_photo.go @@ -24,12 +24,12 @@ var ( var ( sessionManager = session.GetSessionManager() - ldapServer *ldap.LDAPServer + LDAPServer *ldap.LDAPServer - baseDN string + BaseDN string - serviceUserBindDN string - serviceUserPassword string + ServiceUserBindDN string + ServiceUserPassword string ) func ReadBlankPhoto() { @@ -97,8 +97,8 @@ func UploadPhotoHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, "Failed to read file", http.StatusInternalServerError) return } - userDN := fmt.Sprintf("uid=%s,cn=users,cn=accounts,%s", sessionData.UserID, baseDN) - ldapServer.ModifyAttribute(serviceUserBindDN, serviceUserPassword, userDN, "jpegphoto", []string{string(data)}) + userDN := fmt.Sprintf("uid=%s,cn=users,cn=accounts,%s", sessionData.UserID, BaseDN) + LDAPServer.ModifyAttribute(ServiceUserBindDN, ServiceUserPassword, userDN, "jpegphoto", []string{string(data)}) CreateUserPhoto(sessionData.UserID, data) } @@ -124,9 +124,9 @@ func AvatarHandler(w http.ResponseWriter, r *http.Request) { photoCreatedMutex.Unlock() } - userSearch, err := ldapServer.SerchServer( - serviceUserBindDN, serviceUserPassword, - baseDN, + userSearch, err := LDAPServer.SerchServer( + ServiceUserBindDN, ServiceUserPassword, + BaseDN, fmt.Sprintf("(&(objectClass=inetOrgPerson)(uid=%s))", ldap.LDAPEscapeFilter(username)), []string{"jpegphoto"}, ) diff --git a/src/main/main.go b/src/main/main.go index 6136f3c..6e629a6 100644 --- a/src/main/main.go +++ b/src/main/main.go @@ -3,24 +3,22 @@ package main import ( "fmt" "html/template" - "io" "log" "net/http" - "os" - "path/filepath" "strings" "sync" - "time" + "astraltech.xyz/accountmanager/src/components" + "astraltech.xyz/accountmanager/src/helpers" + "astraltech.xyz/accountmanager/src/ldap" "astraltech.xyz/accountmanager/src/logging" "astraltech.xyz/accountmanager/src/session" ) var ( - ldapServer *LDAPServer - ldapServerMutex sync.Mutex - serverConfig *ServerConfig - sessionManager *session.SessionManager + ldapServer ldap.LDAPServer + serverConfig *ServerConfig + sessionManager *session.SessionManager ) type UserData struct { @@ -30,62 +28,35 @@ type UserData struct { } var ( - userData = make(map[string]UserData) + userData = make(map[string]*UserData) userDataMutex sync.RWMutex ) -var ( - photoCreatedTimestamp = make(map[string]time.Time) - photoCreatedMutex sync.Mutex - blankPhotoData []byte -) - -func createUserPhoto(username string, photoData []byte) error { - Mkdir("./avatars", os.ModePerm) - - path := fmt.Sprintf("./avatars/%s.jpeg", username) - cleaned := filepath.Clean(path) - dst, err := CreateFile(cleaned) - - if err != nil { - return fmt.Errorf("Could not save file") - } - photoCreatedMutex.Lock() - photoCreatedTimestamp[username] = time.Now() - photoCreatedMutex.Unlock() - defer dst.Close() - logging.Info("Writing to avarar file") - _, err = dst.Write(photoData) - if err != nil { - return err - } - return nil -} - -func authenticateUser(username, password string) (UserData, error) { +func authenticateUser(username, password string) (*UserData, error) { logging.Event(logging.AuthenticateUser, username) - ldapServerMutex.Lock() - defer ldapServerMutex.Unlock() - if ldapServer.Connection == nil { - return UserData{isAuth: false}, fmt.Errorf("LDAP server not connected") - } userDN := fmt.Sprintf("uid=%s,cn=users,cn=accounts,%s", username, serverConfig.LDAPConfig.BaseDN) - connected := connectAsLDAPUser(ldapServer, userDN, password) - if connected != nil { - return UserData{isAuth: false}, connected - } - userSearch := searchLDAPServer( - ldapServer, + connected, err := ldapServer.AuthenticateUser(userDN, password) + if err != nil { + return nil, err + } + if connected == false { + logging.Debug("Failed to authenticate user") + return nil, fmt.Errorf("Failed to authenticate user %s", username) + } + logging.Info("User authenticated successfully") + + userSearch, err := ldapServer.SerchServer( + userDN, password, serverConfig.LDAPConfig.BaseDN, - fmt.Sprintf("(&(objectClass=inetOrgPerson)(uid=%s))", ldapEscapeFilter(username)), + fmt.Sprintf("(&(objectClass=inetOrgPerson)(uid=%s))", ldap.LDAPEscapeFilter(username)), []string{"displayName", "mail", "jpegphoto"}, ) - if !userSearch.Succeeded { - return UserData{isAuth: false}, fmt.Errorf("user metadata not found") + if err != nil { + return nil, err } - entry := userSearch.LDAPSearch.Entries[0] + entry := userSearch.GetEntry(0) user := UserData{ isAuth: true, DisplayName: entry.GetAttributeValue("displayName"), @@ -94,9 +65,9 @@ func authenticateUser(username, password string) (UserData, error) { photoData := entry.GetRawAttributeValue("jpegphoto") if len(photoData) > 0 { - createUserPhoto(username, photoData) + components.CreateUserPhoto(username, photoData) } - return user, nil + return &user, nil } type LoginPageData struct { @@ -128,7 +99,7 @@ func loginHandler(w http.ResponseWriter, r *http.Request) { userData[username] = newUserData userDataMutex.Unlock() if err != nil { - log.Print(err) + logging.Error(err.Error()) tmpl.Execute(w, LoginPageData{IsHiddenClassList: ""}) } else { if newUserData.isAuth == true { @@ -177,57 +148,6 @@ func profileHandler(w http.ResponseWriter, r *http.Request) { } } -func avatarHandler(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "image/jpeg") - username := r.URL.Query().Get("user") - if strings.Contains(username, "/") { - w.Write(blankPhotoData) - return - } - - filePath := fmt.Sprintf("./avatars/%s.jpeg", username) - cleaned := filepath.Clean(filePath) - value, err := ReadFile(cleaned) - - if err == nil { - photoCreatedMutex.Lock() - if time.Since(photoCreatedTimestamp[username]) <= 5*time.Minute { - photoCreatedMutex.Unlock() - w.Write(value) - return - } - photoCreatedMutex.Unlock() - } - - ldapServerMutex.Lock() - defer ldapServerMutex.Unlock() - connected := connectAsLDAPUser(ldapServer, serverConfig.LDAPConfig.BindDN, serverConfig.LDAPConfig.BindPassword) - if connected != nil { - w.Write(blankPhotoData) - return - } - - userSearch := searchLDAPServer( - ldapServer, - serverConfig.LDAPConfig.BaseDN, - fmt.Sprintf("(&(objectClass=inetOrgPerson)(uid=%s))", ldapEscapeFilter(username)), - []string{"jpegphoto"}, - ) - if !userSearch.Succeeded || len(userSearch.LDAPSearch.Entries) == 0 { - w.Write(blankPhotoData) - return - } - entry := userSearch.LDAPSearch.Entries[0] - bytes := entry.GetRawAttributeValue("jpegphoto") - if len(bytes) == 0 { - w.Write(blankPhotoData) - return - } else { - w.Write(bytes) - createUserPhoto(username, bytes) - } -} - func logoutHandler(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie("session_token") if err != nil { @@ -251,48 +171,6 @@ func logoutHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/login", http.StatusSeeOther) } -func uploadPhotoHandler(w http.ResponseWriter, r *http.Request) { - sessionData, err := sessionManager.GetSession(r) - if err != nil { - logging.Error(err.Error()) - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } - err = r.ParseMultipartForm(10 << 20) // 10MB limit - if err != nil { - http.Error(w, "Bad request", http.StatusBadRequest) - return - } - - if r.FormValue("csrf_token") != sessionData.CSRFToken { - http.Error(w, "CSRF Forbidden", http.StatusForbidden) - return - } - - file, header, err := r.FormFile("photo") - if err != nil { - http.Error(w, "File not found", http.StatusBadRequest) - return - } - defer file.Close() - if header.Size > (10 * 1024 * 1024) { - http.Error(w, "File is to large (limit is 10 MB)", http.StatusBadRequest) - return - } - - // 3. Read file into memory - data, err := io.ReadAll(file) - if err != nil { - http.Error(w, "Failed to read file", http.StatusInternalServerError) - return - } - userDN := fmt.Sprintf("uid=%s,cn=users,cn=accounts,%s", sessionData.UserID, serverConfig.LDAPConfig.BaseDN) - ldapServerMutex.Lock() - defer ldapServerMutex.Unlock() - modifyLDAPAttribute(ldapServer, userDN, "jpegphoto", []string{string(data)}) - createUserPhoto(sessionData.UserID, data) -} - func faviconHandler(w http.ResponseWriter, r *http.Request) { logging.Info("Requesting Favicon") http.ServeFile(w, r, serverConfig.StyleConfig.FaviconPath) @@ -343,7 +221,7 @@ func changePasswordHandler(w http.ResponseWriter, r *http.Request) { serverConfig.LDAPConfig.BaseDN, ) - err = changeLDAPPassword(ldapServer, userDN, oldPassword, newPassword) + err = ldapServer.ChangePassword(userDN, oldPassword, newPassword) if err != nil { w.WriteHeader(http.StatusInternalServerError) @@ -363,37 +241,47 @@ func changePasswordHandler(w http.ResponseWriter, r *http.Request) { func main() { logging.Info("Starting the server") - sessionManager = session.CreateSessionManager(session.InMemory) + sessionManager = session.GetSessionManager() + sessionManager.SetStoreType(session.InMemory) - var err error = nil - blankPhotoData, err = ReadFile("static/blank_profile.jpg") - if err != nil { - logging.Fatal("Could not load blank profile image") - } + var err error serverConfig, err = loadServerConfig("./data/config.json") if err != nil { log.Fatal("Could not load server config") } - ldapServerMutex.Lock() - server := connectToLDAPServer(serverConfig.LDAPConfig.LDAPURL, serverConfig.LDAPConfig.Security == "tls", serverConfig.LDAPConfig.IgnoreInvalidCert) - ldapServer = server - ldapServerMutex.Unlock() - defer closeLDAPServer(ldapServer) + ldapServer = ldap.LDAPServer{ + URL: serverConfig.LDAPConfig.LDAPURL, + StartTLS: serverConfig.LDAPConfig.Security == "tls", + IgnoreInsecureCert: serverConfig.LDAPConfig.IgnoreInvalidCert, + } - HandleFunc("/favicon.ico", faviconHandler) - HandleFunc("/logo", logoHandler) + components.LDAPServer = &ldapServer + components.BaseDN = serverConfig.LDAPConfig.BaseDN + components.ServiceUserBindDN = serverConfig.LDAPConfig.BindDN + components.ServiceUserPassword = serverConfig.LDAPConfig.BindPassword + + connected, err := ldapServer.TestConnection() + if connected != true || err != nil { + if err != nil { + logging.Error(err.Error()) + } + logging.Fatal("Failed to connect to LDAP server") + } + + helpers.HandleFunc("/favicon.ico", faviconHandler) + helpers.HandleFunc("/logo", logoHandler) http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) - HandleFunc("/login", loginHandler) - HandleFunc("/profile", profileHandler) - HandleFunc("/logout", logoutHandler) + helpers.HandleFunc("/login", loginHandler) + helpers.HandleFunc("/profile", profileHandler) + helpers.HandleFunc("/logout", logoutHandler) - HandleFunc("/avatar", avatarHandler) - HandleFunc("/change-photo", uploadPhotoHandler) - HandleFunc("/change-password", changePasswordHandler) + helpers.HandleFunc("/avatar", components.AvatarHandler) + helpers.HandleFunc("/change-photo", components.UploadPhotoHandler) + helpers.HandleFunc("/change-password", changePasswordHandler) - HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + helpers.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/profile", http.StatusFound) // 302 redirect })