package socketclients import ( "encoding/base64" "janex/admin-dashboard-backend/modules/cache" "janex/admin-dashboard-backend/modules/database" "janex/admin-dashboard-backend/modules/structs" "janex/admin-dashboard-backend/modules/utils" "time" "github.com/rs/zerolog/log" "golang.org/x/crypto/bcrypt" ) func BroadcastMessage(sendSocketMessage structs.SendSocketMessage) { for _, client := range cache.GetSocketClients() { client.SendMessage(sendSocketMessage) } } func BroadcastMessageExceptUserSessionId(ignoreUserSessionId string, sendSocketMessage structs.SendSocketMessage) { for _, client := range cache.GetSocketClients() { if client.SessionId != ignoreUserSessionId { client.SendMessage(sendSocketMessage) } } } func UpdateConnectedUsers(userId string) { var user structs.User database.DB.First(&user, "id = ?", userId) BroadcastMessage(structs.SendSocketMessage{ Cmd: utils.SentCmdUpdateConnectedUsers, Body: struct { WebSocketUsersCount int UserId string ConnectionStatus uint8 LastOnline time.Time }{ WebSocketUsersCount: len(cache.GetSocketClients()), UserId: userId, ConnectionStatus: isUserGenerallyConnected(userId), LastOnline: user.LastOnline, }, }) } func SendMessageToUser(userId string, ignoreUserSessionId string, sendSocketMessage structs.SendSocketMessage) { for _, client := range cache.GetSocketClients() { if client.UserId == userId && client.SessionId != ignoreUserSessionId { client.SendMessage(sendSocketMessage) } } } // This close all connections that are connected with one session id. // For example when a user has two browser tabs opened func CloseAllUserSessionConnections(sessionId string) { for _, client := range cache.GetSocketClients() { if client.SessionId == sessionId { client.SendSessionClosedMessage() } } } func GetUserSessions(userId string) []structs.UserSessionSocket { var userSessions []structs.UserSession database.DB.Where("user_id = ?", userId).Find(&userSessions) var userSessionsSocket []structs.UserSessionSocket socketClients := cache.GetSocketClients() for _, userSession := range userSessions { userSessionsSocket = append(userSessionsSocket, structs.UserSessionSocket{ IdForDeletion: userSession.IdForDeletion, UserAgent: userSession.UserAgent, ConnectionStatus: isUserSessionConnected(userSession.Id, socketClients), LastUsed: userSession.LastUsed, ExpiresAt: userSession.ExpiresAt, }) } return userSessionsSocket } func UpdateUserSessionsForUser(userId string, ignoreUserSessionId string) { GetUserSessions(userId) SendMessageToUser(userId, ignoreUserSessionId, structs.SendSocketMessage{ Cmd: utils.SentCmdUpdateUserSessions, Body: GetUserSessions(userId), }) } func isUserSessionConnected(userSessionId string, socketClients []*structs.SocketClient) uint8 { for _, socketClient := range socketClients { if socketClient.SessionId == userSessionId { return 1 } } return 0 } // Used to determine if a user is connected regardless of the session used func isUserGenerallyConnected(userId string) uint8 { for _, socketClient := range cache.GetSocketClients() { if socketClient.UserId == userId { return 1 } } return 0 } // Get all users from database. // This is used in the UI to display all users. func GetAllUsers() []structs.AllUsers { var users []structs.User var allUsers []structs.AllUsers database.DB.Find(&users) for _, user := range users { allUsers = append(allUsers, structs.AllUsers{ Id: user.Id, Avatar: user.Avatar, Username: user.Username, ConnectionStatus: isUserGenerallyConnected(user.Id), LastOnline: user.LastOnline, }) } return allUsers } func GetAllScanners() []structs.Scanner { var scanners []structs.Scanner var allScanners []structs.Scanner database.DB.Find(&scanners) for _, scanner := range scanners { // clear session to prevent leaking and sending to ui scanner.Session = "" allScanners = append(allScanners, scanner) } return allScanners } func isUsernameAvailable(username string) bool { var user structs.User database.DB.Select("username").Where("username = ?", username).Find(&user) return user.Username == "" } func isEmailAvailable(email string) bool { var user structs.User database.DB.Select("email").Where("email = ?", email).Find(&user) return user.Email == "" } func UpdateUserProfile(userId string, changes map[string]interface{}) { log.Debug().Msgf("changes: %v", changes) var user structs.User var updates = make(map[string]interface{}) // TODO: validate length of username and email if changes["username"] != nil { username := changes["username"].(string) if isUsernameAvailable(username) { user.Username = username updates["Username"] = username } } if changes["email"] != nil { email := changes["email"].(string) if isEmailAvailable(email) { user.Email = email updates["Email"] = email } } if changes["password"] != nil { log.Debug().Msg("update password") password := changes["password"].(string) decodedPassword, err := base64.StdEncoding.DecodeString(changes["password"].(string)) if err != nil { log.Error().Msg("Failed to decode base64 password, err: " + err.Error()) } if utils.IsPasswordLengthValid(password) { if err := bcrypt.CompareHashAndPassword([]byte(user.Password), decodedPassword); err != nil { log.Error().Msg("Incorrect password") } } // TODO: logout all client user sessions } log.Debug().Msgf("len %v", len(changes)) // TODO: dont sent change message if user changed password if len(changes) > 0 { // TODO: update user last updated timestamp database.DB.Model(&structs.User{}).Where("id = ?", userId).Updates(user) if changes["username"] != nil || changes["email"] != nil { BroadcastMessage(structs.SendSocketMessage{ Cmd: utils.SentCmdUserProfileUpdated, Body: struct { UserId string Changes map[string]interface{} }{ UserId: userId, Changes: updates, }, }) } } // TODO: sent feedback back to user for ui notification message }