1113 lines
33 KiB
Go
1113 lines
33 KiB
Go
package socketclients
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"jannex/admin-dashboard-backend/modules/cache"
|
|
"jannex/admin-dashboard-backend/modules/database"
|
|
"jannex/admin-dashboard-backend/modules/logger"
|
|
"jannex/admin-dashboard-backend/modules/requestclient"
|
|
"jannex/admin-dashboard-backend/modules/structs"
|
|
"jannex/admin-dashboard-backend/modules/systempermissions"
|
|
"jannex/admin-dashboard-backend/modules/utils"
|
|
"os"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.ex.umbach.dev/Alex/roese-utils/rslogger"
|
|
"git.ex.umbach.dev/Alex/roese-utils/rsutils"
|
|
"github.com/gofiber/websocket/v2"
|
|
"github.com/google/uuid"
|
|
"github.com/rs/zerolog/log"
|
|
"golang.org/x/crypto/bcrypt"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func BroadcastMessageToTopic(topic string, sendSocketMessage structs.SendSocketMessage) {
|
|
for _, client := range cache.GetSocketClients() {
|
|
if hasClientSubscribedToTopic(topic, client.SubscribedTopic) {
|
|
client.SendMessage(sendSocketMessage)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BroadcastMessageToTopicStartsWith(topic string, sendSocketMessage structs.SendSocketMessage) {
|
|
for _, client := range cache.GetSocketClients() {
|
|
if strings.HasPrefix(client.SubscribedTopic, topic) {
|
|
client.SendMessage(sendSocketMessage)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BroadcastMessageToTopics(topics []string, sendSocketMessage structs.SendSocketMessage) {
|
|
for _, topic := range topics {
|
|
BroadcastMessageToTopic(topic, sendSocketMessage)
|
|
}
|
|
}
|
|
|
|
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 BroadcastMessageToTopicExceptUserSessionId(topic string, ignoreUserSessionId string, sendSocketMessage structs.SendSocketMessage) {
|
|
for _, client := range cache.GetSocketClients() {
|
|
if client.SessionId != ignoreUserSessionId && hasClientSubscribedToTopic(topic, client.SubscribedTopic) {
|
|
client.SendMessage(sendSocketMessage)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BroadcastMessageToTopicsExceptUserSessionId(topics []string, ignoreUserSessionId string, sendSocketMessage structs.SendSocketMessage) {
|
|
for _, topic := range topics {
|
|
BroadcastMessageToTopicExceptUserSessionId(topic, ignoreUserSessionId, sendSocketMessage)
|
|
}
|
|
}
|
|
|
|
func hasClientSubscribedToTopic(topic string, clientTopic string) bool {
|
|
return clientTopic == topic || strings.HasPrefix(clientTopic, topic)
|
|
}
|
|
|
|
func BroadcastMessageExceptUserId(ignoreUserId string, sendSocketMessage structs.SendSocketMessage) {
|
|
for _, client := range cache.GetSocketClients() {
|
|
if client.UserId != ignoreUserId {
|
|
client.SendMessage(sendSocketMessage)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BroadcastMessageToTopicExceptUserId(topic string, ignoreUserId string, sendSocketMessage structs.SendSocketMessage) {
|
|
for _, client := range cache.GetSocketClients() {
|
|
if client.UserId != ignoreUserId && hasClientSubscribedToTopic(topic, client.SubscribedTopic) {
|
|
client.SendMessage(sendSocketMessage)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BroadcastMessageToTopicsExceptUserId(topics []string, ignoreUserId string, sendSocketMessage structs.SendSocketMessage) {
|
|
for _, topic := range topics {
|
|
BroadcastMessageToTopicExceptUserId(topic, ignoreUserId, sendSocketMessage)
|
|
}
|
|
}
|
|
|
|
func BroadcastMessageToUsersWithPermission(neededPermission string, sendSocketMessage structs.SendSocketMessage) {
|
|
var rolePermissions []structs.RolePermission
|
|
|
|
database.DB.Model(&structs.RolePermission{}).Where("permission_id = ?", neededPermission).Find(&rolePermissions)
|
|
|
|
var foundUsers []structs.User
|
|
|
|
for _, rolePermission := range rolePermissions {
|
|
var users []structs.User
|
|
|
|
database.DB.Where("role_id = ?", rolePermission.RoleId).Select("id").Find(&users)
|
|
|
|
foundUsers = append(foundUsers, users...)
|
|
}
|
|
|
|
for _, foundUser := range foundUsers {
|
|
SendMessageToUser(foundUser.Id, "", sendSocketMessage)
|
|
}
|
|
}
|
|
|
|
func BroadcastMessageToUsersWithPermissionAndTopic(neededPermission string, topic string, sendSocketMessage structs.SendSocketMessage) {
|
|
var rolePermissions []structs.RolePermission
|
|
|
|
database.DB.Model(&structs.RolePermission{}).Where("permission_id = ?", neededPermission).Find(&rolePermissions)
|
|
|
|
var foundUsers []structs.User
|
|
|
|
for _, rolePermission := range rolePermissions {
|
|
var users []structs.User
|
|
|
|
database.DB.Where("role_id = ?", rolePermission.RoleId).Select("id").Find(&users)
|
|
|
|
foundUsers = append(foundUsers, users...)
|
|
}
|
|
|
|
for _, foundUser := range foundUsers {
|
|
SendMessageToUserWithTopic(foundUser.Id, topic, "", sendSocketMessage)
|
|
}
|
|
}
|
|
|
|
func SendMessageToUser(userId string, ignoreUserSessionId string, sendSocketMessage structs.SendSocketMessage) {
|
|
for _, client := range cache.GetSocketClients() {
|
|
if client.UserId == userId && client.SessionId != ignoreUserSessionId {
|
|
client.SendMessage(sendSocketMessage)
|
|
}
|
|
}
|
|
}
|
|
|
|
func SendMessageToUserWithTopic(userId string, topic string, ignoreUserSessionId string, sendSocketMessage structs.SendSocketMessage) {
|
|
for _, client := range cache.GetSocketClients() {
|
|
if client.UserId == userId && client.SessionId != ignoreUserSessionId && hasClientSubscribedToTopic(topic, client.SubscribedTopic) {
|
|
client.SendMessage(sendSocketMessage)
|
|
}
|
|
}
|
|
}
|
|
|
|
func SendMessageOnlyToSessionId(sessionId string, sendSocketMessage structs.SendSocketMessage) {
|
|
for _, client := range cache.GetSocketClients() {
|
|
if client.SessionId == sessionId {
|
|
client.SendMessage(sendSocketMessage)
|
|
}
|
|
}
|
|
}
|
|
|
|
func SendMessageToUsersByRoleId(roleId string, sendSocketMessage structs.SendSocketMessage) {
|
|
for _, client := range cache.GetSocketClients() {
|
|
var user structs.User
|
|
|
|
database.DB.Where("id = ?", client.UserId).First(&user)
|
|
|
|
if user.RoleId == roleId {
|
|
client.SendMessage(sendSocketMessage)
|
|
}
|
|
}
|
|
}
|
|
|
|
func SendMessageToUserExceptBrowserTabSession(userId string, ignoreBrowserTabSession string, sendSocketMessage structs.SendSocketMessage) {
|
|
for _, client := range cache.GetSocketClients() {
|
|
if client.UserId == userId && client.BrowserTabSession != ignoreBrowserTabSession {
|
|
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()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Used to close all user connections
|
|
// For example when a user changed his password
|
|
func CloseAndDeleteAllUserConnections(userId string) {
|
|
for _, client := range cache.GetSocketClients() {
|
|
if client.UserId == userId {
|
|
client.SendSessionClosedMessage()
|
|
}
|
|
}
|
|
|
|
database.DB.Where("user_id = ?", userId).Delete(&structs.UserSession{})
|
|
}
|
|
|
|
func UpdateConnectedUsers(userId string) {
|
|
var user structs.User
|
|
|
|
database.DB.First(&user, "id = ?", userId)
|
|
|
|
BroadcastMessage(structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdUpdateConnectedUsers,
|
|
Body: len(cache.GetSocketClients()),
|
|
})
|
|
|
|
BroadcastMessageToTopic(utils.SubscribedTopicUsers, structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdUpdateUsers,
|
|
Body: struct {
|
|
UserId string
|
|
ConnectionStatus uint8
|
|
LastOnline time.Time
|
|
}{
|
|
UserId: userId,
|
|
ConnectionStatus: isUserGenerallyConnected(userId),
|
|
LastOnline: user.LastOnline,
|
|
},
|
|
})
|
|
}
|
|
|
|
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) {
|
|
SendMessageToUserWithTopic(userId, utils.SubscribedTopicUserProfile, 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 utils.ConnectionStateOnline
|
|
}
|
|
}
|
|
|
|
return utils.ConnectionStateOffline
|
|
}
|
|
|
|
// 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,
|
|
RoleId: user.RoleId,
|
|
Avatar: user.Avatar,
|
|
Username: user.Username,
|
|
ConnectionStatus: isUserGenerallyConnected(user.Id),
|
|
Deactivated: user.Deactivated,
|
|
LastOnline: user.LastOnline,
|
|
})
|
|
}
|
|
|
|
return allUsers
|
|
}
|
|
|
|
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(conn *websocket.Conn, changes map[string]interface{}) {
|
|
sessionId := conn.Locals("sessionId").(string)
|
|
userId := conn.Locals("userId").(string)
|
|
|
|
var user structs.User
|
|
var updates = make(map[string]interface{})
|
|
|
|
if changes["username"] != nil {
|
|
username := changes["username"].(string)
|
|
|
|
if isValueLenValid(username, utils.MinUsername, utils.MaxUsername) { // only affected if username was manipulated as min and max is provided in web ui
|
|
if isUsernameAvailable(username) {
|
|
user.Username = username
|
|
updates["Username"] = username
|
|
} else {
|
|
SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdUserProfileUpdated,
|
|
Body: struct {
|
|
UserId string
|
|
Result uint8
|
|
}{
|
|
UserId: userId,
|
|
Result: 0,
|
|
},
|
|
})
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
if changes["email"] != nil {
|
|
email := changes["email"].(string)
|
|
|
|
if isEmailAvailable(email) {
|
|
user.Email = email
|
|
updates["Email"] = email
|
|
} else {
|
|
SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdUserProfileUpdated,
|
|
Body: struct {
|
|
UserId string
|
|
Result uint8
|
|
}{
|
|
UserId: userId,
|
|
Result: 1,
|
|
},
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
if changes["oldPassword"] != nil && changes["newPassword"] != nil {
|
|
oldPassword := changes["oldPassword"].(string)
|
|
newPassword := changes["newPassword"].(string)
|
|
|
|
decodedOldPassword, err := base64.StdEncoding.DecodeString(oldPassword)
|
|
decodedNewPassword, err1 := base64.StdEncoding.DecodeString(newPassword)
|
|
|
|
if err != nil || err1 != nil {
|
|
log.Error().Msgf("Error decoding old or new password %s %s", err.Error(), err1.Error())
|
|
|
|
SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdUserProfileUpdated,
|
|
Body: struct {
|
|
UserId string
|
|
Result uint8
|
|
}{
|
|
UserId: userId,
|
|
Result: 2,
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
if utils.IsPasswordLengthValid(string(decodedOldPassword)) { // only affected if username was manipulated as min and max is provided in web ui
|
|
database.DB.Select("password").First(&user, "id = ?", userId)
|
|
|
|
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), decodedOldPassword); err == nil {
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(decodedNewPassword), bcrypt.DefaultCost)
|
|
|
|
if err == nil {
|
|
user.Password = string(hashedPassword)
|
|
} else {
|
|
log.Error().Msgf("Failed to generate hash password %s", err.Error())
|
|
}
|
|
} else {
|
|
log.Error().Msg("Incorrect password")
|
|
|
|
SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdUserProfileUpdated,
|
|
Body: struct {
|
|
UserId string
|
|
Result uint8
|
|
}{
|
|
UserId: userId,
|
|
Result: 2,
|
|
},
|
|
})
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(changes) > 0 {
|
|
user.UpdatedAt = time.Now()
|
|
|
|
database.DB.Model(&structs.User{}).Where("id = ?", userId).Updates(user)
|
|
|
|
if changes["username"] != nil || changes["email"] != nil || changes["oldPassword"] != nil && changes["newPassword"] != nil {
|
|
if changes["oldPassword"] != nil && changes["newPassword"] != nil {
|
|
// user has changed password - logout all his sessions
|
|
CloseAndDeleteAllUserConnections(userId)
|
|
|
|
logger.AddSystemLog(rslogger.LogTypeInfo, "%s has changed his password", userId)
|
|
return
|
|
}
|
|
|
|
SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdUserProfileUpdated,
|
|
Body: struct {
|
|
UserId string
|
|
Changes map[string]interface{}
|
|
}{
|
|
UserId: userId,
|
|
Changes: updates,
|
|
},
|
|
})
|
|
|
|
BroadcastMessageExceptUserSessionId(sessionId, structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdUserProfileUpdated,
|
|
Body: struct {
|
|
UserId string
|
|
Changes map[string]interface{}
|
|
}{
|
|
UserId: userId,
|
|
Changes: updates,
|
|
},
|
|
})
|
|
|
|
logger.AddSystemLog(rslogger.LogTypeInfo, "User %s has updated his account with the following changes: %v", userId, rsutils.MarshalJson(updates))
|
|
}
|
|
}
|
|
}
|
|
|
|
func isValueLenValid(value string, min int, max int) bool {
|
|
l := len(value)
|
|
return l >= min && l <= max
|
|
}
|
|
|
|
func GetAllRoles() []structs.Role {
|
|
var roles []structs.Role
|
|
|
|
database.DB.Order("sorting_order").Find(&roles)
|
|
|
|
return roles
|
|
}
|
|
|
|
func GetPermissionsByRoleId(roleId string) []string {
|
|
var rolePermissions []structs.RolePermission
|
|
|
|
database.DB.Where("role_id = ?", roleId).Find(&rolePermissions)
|
|
|
|
var permissions []string
|
|
|
|
for _, rolePermission := range rolePermissions {
|
|
permissions = append(permissions, rolePermission.PermissionId)
|
|
}
|
|
|
|
return permissions
|
|
}
|
|
|
|
// Retrieve all roles with a list of all permissions for each role
|
|
func GetAdminAreaRolesPermissions() []structs.RolePermissions {
|
|
roles := GetAllRoles()
|
|
var rolesPermissions []structs.RolePermission
|
|
|
|
database.DB.Find(&rolesPermissions)
|
|
|
|
var rolePermissions []structs.RolePermissions
|
|
|
|
for _, role := range roles {
|
|
var permissions []string
|
|
|
|
for _, rolePermission := range rolesPermissions {
|
|
if rolePermission.RoleId == role.Id {
|
|
permissions = append(permissions, rolePermission.PermissionId)
|
|
}
|
|
}
|
|
|
|
rolePermissions = append(rolePermissions, structs.RolePermissions{
|
|
RoleId: role.Id,
|
|
Permissions: permissions,
|
|
})
|
|
}
|
|
|
|
return rolePermissions
|
|
}
|
|
|
|
func isRoleDisplayNameAvailable(roleDisplayName string) bool {
|
|
var role structs.Role
|
|
|
|
database.DB.Select("display_name").Where("display_name = ?", roleDisplayName).Find(&role)
|
|
|
|
return role.DisplayName == ""
|
|
}
|
|
|
|
func AdminAreaUpdateRole(conn *websocket.Conn, body map[string]interface{}) {
|
|
if body["RoleId"] == nil {
|
|
log.Error().Msgf("No role id specified in update role %v", body)
|
|
return
|
|
}
|
|
|
|
roleId := body["RoleId"].(string)
|
|
changes := body["Changes"].(map[string]interface{})
|
|
|
|
// user has nothing changed
|
|
if changes["DisplayName"] == nil &&
|
|
changes["Description"] == nil &&
|
|
changes["AddedPermissions"] == nil &&
|
|
changes["RemovedPermissions"] == nil {
|
|
log.Error().Msgf("User has not specified anything to update the role %v", body)
|
|
return
|
|
}
|
|
|
|
updatedRole := structs.Role{
|
|
Id: roleId,
|
|
}
|
|
|
|
var changesResult = make(map[string]uint8)
|
|
var updates = make(map[string]interface{})
|
|
|
|
if changes["DisplayName"] != nil && isValueLenValid(changes["DisplayName"].(string), utils.MinRoleDisplayName, utils.MaxRoleDisplayName) {
|
|
if isRoleDisplayNameAvailable(changes["DisplayName"].(string)) {
|
|
updatedRole.DisplayName = changes["DisplayName"].(string)
|
|
updates["DisplayName"] = changes["DisplayName"].(string)
|
|
} else {
|
|
changesResult["DisplayName"] = 1
|
|
}
|
|
}
|
|
|
|
if changes["Description"] != nil {
|
|
updatedRole.Description = changes["Description"].(string)
|
|
updates["Description"] = changes["Description"].(string)
|
|
}
|
|
|
|
if changes["AddedPermissions"] != nil {
|
|
addedPermissions := changes["AddedPermissions"].([]interface{})
|
|
|
|
updates["AddedPermissions"] = addedPermissions
|
|
|
|
var dbAddedPermissions []structs.RolePermission
|
|
|
|
for _, addedPermission := range addedPermissions {
|
|
dbAddedPermissions = append(dbAddedPermissions, structs.RolePermission{
|
|
RoleId: roleId,
|
|
PermissionId: addedPermission.(string),
|
|
})
|
|
}
|
|
|
|
database.DB.Create(dbAddedPermissions)
|
|
}
|
|
|
|
if changes["RemovedPermissions"] != nil {
|
|
removedPermissions := changes["RemovedPermissions"].([]interface{})
|
|
|
|
updates["RemovedPermissions"] = removedPermissions
|
|
|
|
for _, removedPermission := range removedPermissions {
|
|
database.DB.Where("role_id = ?", roleId).Where("permission_id = ?", removedPermission.(string)).Delete(&structs.RolePermission{})
|
|
}
|
|
}
|
|
|
|
database.DB.Model(&structs.Role{}).Where("id = ?", roleId).Updates(&updatedRole)
|
|
|
|
sessionId := conn.Locals("sessionId").(string)
|
|
availableCategories := GetAvailableCategories(conn.Locals("userId").(string))
|
|
|
|
SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdAdminAreaRoleUpdated,
|
|
Body: struct {
|
|
RoleId string
|
|
Changes map[string]interface{}
|
|
Result map[string]uint8
|
|
AvailableCategories []string
|
|
}{
|
|
RoleId: roleId,
|
|
Changes: updates,
|
|
Result: changesResult,
|
|
AvailableCategories: availableCategories,
|
|
},
|
|
})
|
|
|
|
BroadcastMessageToTopicsExceptUserSessionId(
|
|
[]string{utils.SubscribedTopicAdminAreaRoles, utils.SubscribedTopicUsers},
|
|
sessionId,
|
|
structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdAdminAreaRoleUpdated,
|
|
Body: struct {
|
|
RoleId string
|
|
Changes map[string]interface{}
|
|
AvailableCategories []string
|
|
}{
|
|
RoleId: roleId,
|
|
Changes: updates,
|
|
AvailableCategories: availableCategories,
|
|
},
|
|
})
|
|
|
|
SendMessageToUsersByRoleId(roleId, structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdRolePermissionsUpdated,
|
|
Body: struct {
|
|
Changes map[string]interface{}
|
|
AvailableCategories []string
|
|
}{
|
|
Changes: updates,
|
|
AvailableCategories: availableCategories,
|
|
},
|
|
})
|
|
|
|
logger.AddSystemLog(rslogger.LogTypeInfo, "User %s has updated the role %s with the following changes: %v",
|
|
conn.Locals("userId").(string), roleId, rsutils.MarshalJson(updates))
|
|
}
|
|
|
|
func AdminAreaMoveRoleToSortingOrder(conn *websocket.Conn, body map[string]interface{}) {
|
|
roleId := body["RoleId"].(string)
|
|
direction := int(body["Direction"].(float64))
|
|
|
|
var role structs.Role
|
|
|
|
if err := database.DB.First(&role, "id = ?", roleId).Error; err != nil {
|
|
log.Error().Msgf("Failed to get role %s", err.Error())
|
|
return
|
|
}
|
|
|
|
currentSortingOrder := role.SortingOrder
|
|
var newSortingOrder int
|
|
|
|
if direction == 1 {
|
|
newSortingOrder = currentSortingOrder + 1
|
|
|
|
if newSortingOrder > systempermissions.GetRoleSortingOrder()-1 {
|
|
return
|
|
}
|
|
} else {
|
|
newSortingOrder = currentSortingOrder - 1
|
|
|
|
if newSortingOrder < 0 {
|
|
return
|
|
}
|
|
}
|
|
|
|
// If the new sorting order number is smaller than the current sorting order number,
|
|
// increase the sorting order numbers of all roles between the new and current sorting order by 1
|
|
if newSortingOrder < currentSortingOrder {
|
|
if err := database.DB.Model(&structs.Role{}).Where("sorting_order >= ? AND sorting_order < ?", newSortingOrder, currentSortingOrder).Update("sorting_order", gorm.Expr("sorting_order + 1")).Error; err != nil {
|
|
log.Error().Msgf("Failed to update sorting order upwards %s", err.Error())
|
|
return
|
|
}
|
|
} else if newSortingOrder > currentSortingOrder {
|
|
// If the new sorting order number is larger than the current sorting order number,
|
|
// decrease the sorting order numbers of all roles between the current and new sorting order by 1.
|
|
if err := database.DB.Model(&structs.Role{}).Where("sorting_order > ? AND sorting_order <= ?", currentSortingOrder, newSortingOrder).Update("sorting_order", gorm.Expr("sorting_order - 1")).Error; err != nil {
|
|
log.Error().Msgf("Failed to update sorting order downwards %s", err.Error())
|
|
return
|
|
}
|
|
}
|
|
|
|
// Update the sorting order number of the moved role
|
|
if err := database.DB.Model(&role).Update("sorting_order", newSortingOrder).Error; err != nil {
|
|
log.Debug().Msgf("Failed to update sorting order %s", err.Error())
|
|
return
|
|
}
|
|
|
|
BroadcastMessageToTopics(
|
|
[]string{utils.SubscribedTopicAdminAreaRoles, utils.SubscribedTopicUsers},
|
|
structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdAdminAreaUpdateRoleSortingOrder,
|
|
Body: body,
|
|
})
|
|
|
|
logger.AddSystemLog(rslogger.LogTypeInfo, "User %s has changed the sorting order of role %s to %s",
|
|
conn.Locals("userId").(string), roleId, strconv.Itoa(newSortingOrder))
|
|
}
|
|
|
|
func AdminAreaDeleteRole(conn *websocket.Conn, body map[string]interface{}) {
|
|
roleId := body["RoleId"].(string)
|
|
|
|
var role structs.Role
|
|
|
|
database.DB.Where("id = ?", roleId).First(&role)
|
|
|
|
if err := database.DB.Model(&structs.Role{}).Where("sorting_order > ?", role.SortingOrder).Update("sorting_order", gorm.Expr("sorting_order - 1")).Error; err != nil {
|
|
log.Error().Msgf("Failed to update role sorting order after delete role %s", err.Error())
|
|
return
|
|
}
|
|
|
|
database.DB.Where("id = ?", roleId).Delete(&structs.Role{})
|
|
database.DB.Where("role_id = ?", roleId).Delete(&structs.RolePermission{})
|
|
|
|
BroadcastMessageToTopic(utils.SubscribedTopicAdminAreaRoles,
|
|
structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdAdminAreaRoleDeleted,
|
|
Body: body,
|
|
})
|
|
|
|
logger.AddSystemLog(rslogger.LogTypeInfo, "User %s has deleted the role %s", conn.Locals("userId").(string), role.Id)
|
|
}
|
|
|
|
func UpdateUserRole(conn *websocket.Conn, userId string, roleId string) {
|
|
database.DB.Model(&structs.User{}).Where("id = ?", userId).Updates(structs.User{
|
|
RoleId: roleId,
|
|
})
|
|
|
|
SendMessageToUser(userId, "", structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdAllUsersUserRoleUpdated,
|
|
Body: struct {
|
|
UserId string
|
|
RoleId string
|
|
Permissions []string
|
|
}{
|
|
UserId: userId,
|
|
RoleId: roleId,
|
|
Permissions: GetPermissionsByRoleId(roleId),
|
|
},
|
|
})
|
|
|
|
BroadcastMessageToTopicsExceptUserId(
|
|
[]string{utils.SubscribedTopicUsers, utils.SubscribedTopicAdminAreaRoles},
|
|
userId, structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdAllUsersUserRoleUpdated,
|
|
Body: struct {
|
|
UserId string
|
|
RoleId string
|
|
}{
|
|
UserId: userId,
|
|
RoleId: roleId,
|
|
},
|
|
})
|
|
|
|
logger.AddSystemLog(rslogger.LogTypeInfo, "User %s has assigned the role %s to %s",
|
|
conn.Locals("userId").(string), roleId, userId)
|
|
}
|
|
|
|
func HasPermission(userId string, permission string) bool {
|
|
var user structs.User
|
|
|
|
database.DB.Where("id = ?", userId).First(&user)
|
|
|
|
var rolePermission structs.RolePermission
|
|
|
|
database.DB.Where("role_id = ?", user.RoleId).Where("permission_id = ?", permission).Find(&rolePermission)
|
|
|
|
return rolePermission.PermissionId == permission
|
|
}
|
|
|
|
// This function is used to check if a user has one of the provided permissions
|
|
// For example if a user has one of the permissions "user.create" or "user.delete"
|
|
func HasOnePermission(userId string, permissions []string) bool {
|
|
var user structs.User
|
|
|
|
database.DB.Where("id = ?", userId).First(&user)
|
|
|
|
var userPermissions []structs.RolePermission
|
|
|
|
database.DB.Where("role_id = ?", user.RoleId).Find(&userPermissions)
|
|
|
|
// loop through all provided permissions
|
|
for _, permission := range permissions {
|
|
// loop through all user permissions
|
|
for _, userPermission := range userPermissions {
|
|
// check if the user has the permission
|
|
if userPermission.PermissionId == permission {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func HasXYPermission(userId string, permission string, category string) bool {
|
|
return HasPermission(userId, systempermissions.ConvertXYPermission(permission, category))
|
|
}
|
|
|
|
func SendErrorMessageNoPermissions(sessionId string) {
|
|
SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdErrorNoPermissions,
|
|
})
|
|
}
|
|
|
|
func AllUsersCreateNewUser(conn *websocket.Conn, body map[string]interface{}) {
|
|
if body["Username"] == nil ||
|
|
body["Email"] == nil ||
|
|
body["Password"] == nil ||
|
|
body["RoleId"] == nil {
|
|
log.Error().Msgf("Invalid body provided for user creation: %v", body)
|
|
return
|
|
}
|
|
|
|
username := body["Username"].(string)
|
|
email := body["Email"].(string)
|
|
password := body["Password"].(string)
|
|
roleId := body["RoleId"].(string)
|
|
|
|
if !isValueLenValid(username, utils.MinUsername, utils.MaxUsername) {
|
|
log.Error().Msgf("Invalid username length: %s", username)
|
|
return
|
|
}
|
|
|
|
sessionId := conn.Locals("sessionId").(string)
|
|
|
|
if !isUsernameAvailable(username) {
|
|
SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdAllUsersNewUserCreated,
|
|
Body: struct {
|
|
Result uint8
|
|
}{
|
|
Result: 0,
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
if !isEmailAvailable(email) {
|
|
SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdAllUsersNewUserCreated,
|
|
Body: struct {
|
|
Result uint8
|
|
}{
|
|
Result: 1,
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
decodedPassword, err := base64.StdEncoding.DecodeString(password)
|
|
|
|
if err != nil {
|
|
log.Error().Msgf("Failed to decode base64 password, err: %s", err.Error())
|
|
return
|
|
}
|
|
|
|
hashedPassword, err := bcrypt.GenerateFromPassword(decodedPassword, bcrypt.DefaultCost)
|
|
|
|
if err != nil {
|
|
log.Error().Msgf("Failed to generate password: %s", err.Error())
|
|
return
|
|
}
|
|
|
|
newUser := structs.User{
|
|
Id: uuid.New().String(),
|
|
RoleId: roleId,
|
|
Username: username,
|
|
Email: email,
|
|
Password: string(hashedPassword),
|
|
CreatedAt: time.Now(),
|
|
}
|
|
|
|
database.DB.Create(&newUser)
|
|
|
|
BroadcastMessageToTopics(
|
|
[]string{utils.SubscribedTopicUsers, utils.SubscribedTopicAdminAreaRoles},
|
|
structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdAllUsersNewUserCreated,
|
|
Body: struct {
|
|
Id string
|
|
Username string
|
|
RoleId string
|
|
ConnectionStatus uint8
|
|
Deactivated bool
|
|
}{
|
|
Id: newUser.Id,
|
|
Username: username,
|
|
RoleId: roleId,
|
|
ConnectionStatus: utils.ConnectionStateOffline,
|
|
Deactivated: false,
|
|
},
|
|
})
|
|
|
|
logger.AddSystemLog(rslogger.LogTypeInfo, "User %s has created the user %s with the assigned role %s",
|
|
conn.Locals("userId").(string), newUser.Id, roleId)
|
|
}
|
|
|
|
func AllUsersDeleteUser(conn *websocket.Conn, userId string) {
|
|
var user structs.User
|
|
|
|
database.DB.Select("avatar").Where("id = ?", userId).Find(&user)
|
|
|
|
if user.Avatar != "" {
|
|
os.Remove("./public/avatars/" + user.Avatar)
|
|
}
|
|
|
|
database.DB.Where("id = ?", userId).Delete(&structs.User{})
|
|
database.DB.Where("user_id = ?", userId).Delete(&structs.UserSession{})
|
|
|
|
CloseAndDeleteAllUserConnections(userId)
|
|
|
|
BroadcastMessageToTopics(
|
|
[]string{utils.SubscribedTopicUsers, utils.SubscribedTopicAdminAreaRoles},
|
|
structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdAllUsersUserDeleted,
|
|
Body: struct {
|
|
UserId string
|
|
}{
|
|
UserId: userId,
|
|
},
|
|
})
|
|
|
|
logger.AddSystemLog(rslogger.LogTypeInfo, "User %s has deleted the user %s",
|
|
conn.Locals("userId").(string), userId)
|
|
|
|
requestclient.TelegramBotManagerDeleteUserRequestClient(userId)
|
|
}
|
|
|
|
func AllUsersUserDeactivation(conn *websocket.Conn, userId string, deactivate bool) {
|
|
database.DB.Model(&structs.User{}).Select("deactivated").Where("id = ?", userId).Updates(structs.User{
|
|
Deactivated: deactivate,
|
|
})
|
|
|
|
if deactivate {
|
|
CloseAndDeleteAllUserConnections(userId)
|
|
}
|
|
|
|
BroadcastMessageToTopic(utils.SubscribedTopicUsers, structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdAllUsersUserDeactivation,
|
|
Body: struct {
|
|
UserId string
|
|
Deactivated bool
|
|
}{
|
|
UserId: userId,
|
|
Deactivated: deactivate,
|
|
},
|
|
})
|
|
|
|
logger.AddSystemLog(rslogger.LogTypeInfo, "User %s has changed the deactivation status of user %s to %v",
|
|
conn.Locals("userId").(string), userId, strconv.FormatBool(deactivate))
|
|
}
|
|
|
|
func GetUserApiKeys(userId string) []structs.UserApiKey {
|
|
var apiKeys []structs.UserApiKey
|
|
|
|
database.DB.Where("user_id = ?", userId).Find(&apiKeys)
|
|
|
|
return apiKeys
|
|
}
|
|
|
|
func CreateNewUserApiKey(userId string, apiName string) {
|
|
if len(apiName) < utils.MinUserApiKeyNameLength || len(apiName) > utils.MaxUserApiKeyNameLength {
|
|
return
|
|
}
|
|
|
|
token, err := rsutils.GenerateSession()
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
newApiKey := structs.UserApiKey{
|
|
Id: uuid.New().String(),
|
|
Token: token,
|
|
UserId: userId,
|
|
Name: apiName,
|
|
UsageCount: 0,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
|
|
database.DB.Create(&newApiKey)
|
|
|
|
SendMessageToUserWithTopic(userId, utils.SubscribedTopicUserProfile, "", structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdNewUserApiKeyCreated,
|
|
Body: newApiKey,
|
|
})
|
|
|
|
logger.AddSystemLog(rslogger.LogTypeInfo, "User %s has created an API key", userId)
|
|
}
|
|
|
|
func DeleteUserApiKey(userId string, apiKey string) {
|
|
database.DB.Where("id = ?", apiKey).Where("user_id = ?", userId).Delete(&structs.UserApiKey{})
|
|
/*
|
|
SendMessageToUser(userId, "", structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdDeletedUserApiKey,
|
|
Body: apiKey,
|
|
}) */
|
|
|
|
SendMessageToUserWithTopic(userId, utils.SubscribedTopicUserProfile, "", structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdDeletedUserApiKey,
|
|
Body: apiKey,
|
|
})
|
|
|
|
logger.AddSystemLog(rslogger.LogTypeInfo, "User %s has deleted one of its api keys", userId)
|
|
}
|
|
|
|
func GetAvailableCategories(userId string) []string {
|
|
var categories []string
|
|
|
|
for _, categoryGroup := range cache.GetCategoryGroups() {
|
|
categories = append(categories, categoryGroup.Category)
|
|
}
|
|
|
|
// sort categories alphabetically
|
|
slices.Sort(categories)
|
|
|
|
return categories
|
|
}
|
|
|
|
func HandleCheckWhichCategoriesAreAvailable() {
|
|
// this will add/remove permissions for the master and returns the role id of the master role
|
|
masterRoleId := systempermissions.HandleMasterRolePermissions()
|
|
masterRolePermissions := GetPermissionsByRoleId(masterRoleId)
|
|
|
|
// we need to send the master role permissions to the user who is
|
|
// member of the master role as the master role has access to all
|
|
// categories but without this message the member of the master role
|
|
// wouldn't see new categories
|
|
// web ui will set the permissions to appContext userPermissions
|
|
SendMessageToUsersByRoleId(masterRoleId, structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdAdminAreaManageCheckedForAvailableCategories,
|
|
Body: struct {
|
|
UserPermissions []string
|
|
}{
|
|
UserPermissions: masterRolePermissions,
|
|
},
|
|
})
|
|
|
|
// send response the admin area manage topic that the check is finished
|
|
BroadcastMessageToTopic(
|
|
utils.SubscribedTopicAdminAreaManage,
|
|
structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdAdminAreaManageCheckedForAvailableCategories,
|
|
},
|
|
)
|
|
|
|
// send the current available categories to all users - so they can remove the no longer existing categories from the sidebar
|
|
BroadcastMessage(
|
|
structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdAdminAreaManageCheckedForAvailableCategories,
|
|
Body: struct {
|
|
AvailableCategories []string
|
|
}{
|
|
AvailableCategories: GetAvailableCategories(""),
|
|
},
|
|
},
|
|
)
|
|
|
|
// send message to admin area roles topic to update master role permissions
|
|
// web ui will save the master permissions into adminAreaRoles context
|
|
BroadcastMessageToTopic(
|
|
utils.SubscribedTopicAdminAreaRoles,
|
|
structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdAdminAreaManageCheckedForAvailableCategories,
|
|
Body: struct {
|
|
MasterRoleId string
|
|
MasterRolePermissions []string
|
|
}{
|
|
MasterRoleId: masterRoleId,
|
|
MasterRolePermissions: masterRolePermissions,
|
|
},
|
|
},
|
|
)
|
|
}
|
|
|
|
func AddLogManagerServerConnection(userId string, displayName string, address string) {
|
|
newConnection := structs.LogManagerServerConnection{
|
|
Id: uuid.New().String(),
|
|
DisplayName: displayName,
|
|
Address: address,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
|
|
database.DB.Create(&newConnection)
|
|
|
|
BroadcastMessageToTopics(
|
|
[]string{utils.SubscribedTopicAdminAreaManage, utils.SubscribedTopicConsoles},
|
|
structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdAdminAreaManageLogManagerServerConnectionAdded,
|
|
Body: newConnection,
|
|
})
|
|
|
|
logger.AddSystemLog(rslogger.LogTypeInfo, "User %s has added log manager server connection with id %s, display name %s and address %s", userId, newConnection.Id, displayName, address)
|
|
}
|
|
|
|
func DeleteLogManagerServerConnection(userId string, id string) {
|
|
database.DB.Where("id = ?", id).Delete(&structs.LogManagerServerConnection{})
|
|
|
|
BroadcastMessageToTopics(
|
|
[]string{utils.SubscribedTopicAdminAreaManage, utils.SubscribedTopicConsoles},
|
|
structs.SendSocketMessage{
|
|
Cmd: utils.SentCmdAdminAreaManageLogManagerServerConnectionRemoved,
|
|
Body: id,
|
|
})
|
|
|
|
logger.AddSystemLog(rslogger.LogTypeInfo, "User %s has removed log manager server connection with id %s", userId, id)
|
|
}
|