rework of hubs

This commit is contained in:
2026-04-29 19:32:26 +02:00
parent 6378966267
commit 909d222a89
5 changed files with 80 additions and 334 deletions
+9 -9
View File
@@ -65,20 +65,20 @@ func getHubByIdStr(ctx context.Context, hubId string) (*types.Hub, error) {
return hub, nil
}
func getHubUserIfValidWithResponseOnFail(ctx context.Context, response http.ResponseWriter, token string, hubId string) (
func getHubUserIfValidWithResponseOnFail(ctx context.Context, response http.ResponseWriter, request *http.Request) (
*types.User, *types.HubUser, *types.Hub, error) {
hub, err := getHubByIdStr(ctx, hubId)
if err != nil {
http.Error(response, "invalid hubid", http.StatusBadRequest)
return nil, nil, nil, errors.New("no such hub")
}
user, err := getUserByToken(ctx, token)
user, err := getUserByToken(ctx, request.Header.Get("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusBadRequest)
return nil, nil, nil, errors.New("invalid token")
}
hub, err := getHubByIdStr(ctx, request.Header.Get("hubid"))
if err != nil {
http.Error(response, "invalid hubid", http.StatusBadRequest)
return nil, nil, nil, errors.New("no such hub")
}
hub.Mu.RLock()
hubUser, ok := hub.Users[user.Id]
hub.Mu.RUnlock()
@@ -103,7 +103,7 @@ func getHubChannelIfValidWithResponseOnFail(ctx context.Context, response http.R
return nil, errors.New("invalid channelid")
}
if !haveHubUserPermissionsOnChannel(types.CachedUserCanView, hubUser, channel) {
if !haveHubUserCachedPermissions(types.CachedUserCanView, hubUser, channel) {
return nil, errors.New("invalid channelid")
}
+3 -3
View File
@@ -8,16 +8,16 @@ import (
"go-socket/packages/config"
)
type postType uint8
type requestType uint8
const (
normal postType = iota
normal requestType = iota
file
avatar
profileBg
)
func validCheckWithResponseOnFail(response *http.ResponseWriter, request *http.Request, pt postType) bool {
func validCheckWithResponseOnFail(response *http.ResponseWriter, request *http.Request, pt requestType) bool {
var maxSize int64
switch pt {
case file:
+55 -283
View File
@@ -2,7 +2,6 @@ package httpRequest
import (
"encoding/json"
"go-socket/packages/convertions"
"maps"
"net/http"
"slices"
@@ -16,7 +15,24 @@ import (
"github.com/google/uuid"
)
func haveHubUserPermissionsOnChannel(needed types.CachedUserPermissions, user *types.HubUser, channel *types.HubChannel) bool {
func haveHubUserPermission(u *types.HubUser, h *types.Hub, needed types.Permissions) bool {
h.Mu.Lock()
defer h.Mu.Unlock()
for _, role := range h.Roles {
if role == nil {
continue
}
if !u.Roles.Has(role.Id) {
continue
}
if (needed & role.Permissions) == needed {
return true
}
}
return false
}
func haveHubUserCachedPermissions(needed types.CachedUserPermissions, user *types.HubUser, channel *types.HubChannel) bool {
channel.Mu.RLock()
checkAgainst, ok := channel.UsersCachedPermissions[user.OriginalId]
channel.Mu.RUnlock()
@@ -26,61 +42,6 @@ func haveHubUserPermissionsOnChannel(needed types.CachedUserPermissions, user *t
return true
}
func haveUserPermissions(needed types.Permissions, scope uint8, hu *types.HubUser, h *types.Hub) bool {
h.Mu.RLock()
defer h.Mu.RUnlock()
for _, role := range h.Roles {
if role == nil {
continue
}
if scope != 0 && role.BoundedGroup != scope {
continue
}
if !hu.Roles.Has(role.Id) {
continue
}
if (needed & role.Permissions) != needed {
return true
}
}
return true
}
func addHubUserToPermissionCache(hub *types.Hub, user *types.HubUser) {
user.Mu.RLock()
roles := user.Roles
userId := user.OriginalId
user.Mu.RUnlock()
for _, group := range hub.Groups {
if group == nil {
continue
}
if !roles.HasSameId(group.RolesCanView) {
continue
}
group.UsersCachedPermissions[userId] = types.CachedUserCanView
for _, channel := range group.Channels {
var perms types.CachedUserPermissions
if roles.HasSameId(channel.RolesCanView) {
perms |= types.CachedUserCanView
if roles.HasSameId(channel.RolesCanReadHistory) {
perms |= types.CachedUserCanReadHistory
}
if roles.HasSameId(channel.RolesCanMessage) {
perms |= types.CachedUserCanMessage
}
}
channel.Mu.Lock()
channel.UsersCachedPermissions[userId] = perms
channel.Mu.Unlock()
}
}
}
func HandleHubCreate(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(&response, request, normal) {
return
@@ -132,31 +93,20 @@ func HandleHubCreate(response http.ResponseWriter, request *http.Request) {
hub.Roles[memberRole.Id] = memberRole
creator.Roles.Add(memberRole.Id)
rootGroup := types.NewHubGroup()
rootGroup.Name = "root"
rootGroup.Id = uint8(1)
rootGroup.Color = types.Rgba{}.GetRandom()
rootGroup.CreatedAt = hub.CreatedAt
rootGroup.RolesCanView.Add(rootRole.Id)
rootGroup.RolesCanView.Add(memberRole.Id)
hub.Groups[rootGroup.Id] = rootGroup
channel := types.NewHubChannel()
channel.Name = "main channel"
channel.Position = uint8(0)
channel.Id = uuid.New()
channel.ParentId = rootGroup.Id
channel.Description = "The fist channel!"
channel.CreatedAt = hub.CreatedAt
channel.RolesCanMessage.Add(rootGroup.Id)
channel.RolesCanMessage.Add(rootRole.Id)
channel.RolesCanMessage.Add(memberRole.Id)
channel.RolesCanView.Add(rootGroup.Id)
channel.RolesCanView.Add(rootRole.Id)
channel.RolesCanView.Add(memberRole.Id)
channel.RolesCanReadHistory.Add(rootGroup.Id)
channel.RolesCanReadHistory.Add(rootRole.Id)
channel.RolesCanReadHistory.Add(memberRole.Id)
channel.UsersCachedPermissions[creator.OriginalId] = types.CachedUserPermissionsAll
hub.Channels[channel.Id] = channel
rootGroup.Channels[channel.Id] = channel
cache.SaveHub(hub)
}
@@ -182,7 +132,6 @@ func HandleHubJoin(response http.ResponseWriter, request *http.Request) {
hubUser.Roles.Add(hub.JoinRole.Id)
hubUser.CreatedAt = time.Now()
hub.Users[hubUser.OriginalId] = hubUser
addHubUserToPermissionCache(hub, hubUser)
}
func HandleHubMessage(response http.ResponseWriter, request *http.Request) {
@@ -190,7 +139,7 @@ func HandleHubMessage(response http.ResponseWriter, request *http.Request) {
return
}
ctx := request.Context()
user, hubUser, hub, err := getHubUserIfValidWithResponseOnFail(ctx, response, request.Header.Get("token"), request.FormValue("hubid"))
user, hubUser, hub, err := getHubUserIfValidWithResponseOnFail(ctx, response, request)
if err != nil {
return
}
@@ -199,7 +148,7 @@ func HandleHubMessage(response http.ResponseWriter, request *http.Request) {
return
}
if hubUser.IsGlobalMuted {
if hubUser.IsMuted {
http.Error(response, "muted", http.StatusForbidden)
}
@@ -269,215 +218,38 @@ func HandleGetHubs(response http.ResponseWriter, request *http.Request) {
response.Write(converted)
}
func HandleHubPermissionActionCore(
response http.ResponseWriter, request *http.Request,
takenVarName string, rt requestType, needed types.Permissions,
performedAction func(usr *types.HubUser, hub *types.Hub, takenVar *string)) {
if !validCheckWithResponseOnFail(&response, request, rt) {
return
}
ctx := request.Context()
_, hubUser, hub, err := getHubUserIfValidWithResponseOnFail(ctx, response, request)
if err != nil {
return
}
if !haveHubUserPermission(hubUser, hub, needed) {
http.Error(response, "", http.StatusForbidden)
}
var takenVar *string = nil
if takenVarName != "" {
takenVar = new(request.FormValue(takenVarName))
}
performedAction(hubUser, hub, takenVar)
}
func HandleHubSetName(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(&response, request, normal) {
return
}
ctx := request.Context()
_, hubUser, hub, err := getHubUserIfValidWithResponseOnFail(ctx, response, request.Header.Get("token"), request.FormValue("hubid"))
if err != nil {
return
}
newName := request.FormValue("name")
if newName == "" {
http.Error(response, "empty name", http.StatusBadRequest)
return
}
if !haveUserPermissions(types.PermissionSetHubName, 0, hubUser, hub) {
http.Error(response, "no permission", http.StatusForbidden)
return
}
hub.Name = newName
response.WriteHeader(http.StatusAccepted)
}
func HandleHubSetColor(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(&response, request, normal) {
return
}
ctx := request.Context()
_, hubUser, hub, err := getHubUserIfValidWithResponseOnFail(ctx, response, request.Header.Get("token"), request.FormValue("hubid"))
if err != nil {
return
}
newColor, err := convertions.StringToRgba(request.FormValue("color"))
if err != nil {
http.Error(response, "bad color", http.StatusBadRequest)
return
}
if !haveUserPermissions(types.PermissionSetHubColor, 0, hubUser, hub) {
http.Error(response, "no permission", http.StatusForbidden)
return
}
hub.Color = newColor
response.WriteHeader(http.StatusAccepted)
}
func HandleHubRemove(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(&response, request, normal) {
return
}
ctx := request.Context()
_, hubUser, hub, err := getHubUserIfValidWithResponseOnFail(ctx, response, request.Header.Get("token"), request.FormValue("hubid"))
if err != nil {
return
}
if !haveUserPermissions(types.PermissionRemoveHub, 0, hubUser, hub) {
http.Error(response, "no permission", http.StatusForbidden)
return
}
cache.DeleteHub(hub)
}
func HandleHubSetAllowUserColor(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(&response, request, normal) {
return
}
ctx := request.Context()
_, hubUser, hub, err := getHubUserIfValidWithResponseOnFail(ctx, response, request.Header.Get("token"), request.FormValue("hubid"))
if err != nil {
return
}
if !haveUserPermissions(types.PermissionSetUserColorAllowed, 0, hubUser, hub) {
http.Error(response, "no permission", http.StatusForbidden)
return
}
cache.DeleteHub(hub)
}
func HandleHubRemoveUser(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(&response, request, normal) {
return
}
ctx := request.Context()
_, hubUser, hub, err := getHubUserIfValidWithResponseOnFail(ctx, response, request.Header.Get("token"), request.FormValue("hubid"))
if err != nil {
return
}
targetId, err := convertions.StringToUuid(request.FormValue("target"))
if err != nil {
http.Error(response, "invalid targetid", http.StatusBadRequest)
return
}
hub.Mu.RLock()
_, ok := hub.Users[targetId]
if !ok {
http.Error(response, "target not found", http.StatusNotFound)
return
}
if !haveUserPermissions(types.PermissionRemoveUser, 0, hubUser, hub) {
http.Error(response, "no permission", http.StatusForbidden)
return
}
hub.Mu.Lock()
delete(hub.Users, targetId)
hub.Mu.Unlock()
response.WriteHeader(http.StatusAccepted)
}
func HandleHubMuteUserToggle(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(&response, request, normal) {
return
}
ctx := request.Context()
_, hubUser, hub, err := getHubUserIfValidWithResponseOnFail(ctx, response, request.Header.Get("token"), request.FormValue("hubid"))
if err != nil {
return
}
targetId, err := convertions.StringToUuid(request.FormValue("target"))
if err != nil {
http.Error(response, "invalid targetid", http.StatusBadRequest)
return
}
scope, err := convertions.StringToUint32(request.FormValue("scope"))
if err != nil {
http.Error(response, "invalid scope (set 0 for no scope)", http.StatusBadRequest)
return
}
hub.Mu.RLock()
target, ok := hub.Users[targetId]
hub.Mu.RUnlock()
if !ok {
http.Error(response, "target not found", http.StatusNotFound)
return
}
if !haveUserPermissions(types.PermissionMuteUser, uint8(scope), hubUser, hub) {
http.Error(response, "no permission", http.StatusForbidden)
return
}
if scope == 0 {
target.IsGlobalMuted = !target.IsGlobalMuted
} else {
hub.Mu.Lock()
users := hub.Groups[scope].MutedUsers
_, ok = users[targetId]
if ok {
delete(users, targetId)
} else {
users[targetId] = struct{}{}
HandleHubPermissionActionCore(response, request, "newname", normal, types.PermissionSetHubName, func(usr *types.HubUser, hub *types.Hub, takenVar *string) {
newName := *takenVar
if newName == "" {
http.Error(response, "empty name", http.StatusBadRequest)
return
}
}
}
func HandleHubMuteUserToggle(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(&response, request, normal) {
return
}
ctx := request.Context()
_, hubUser, hub, err := getHubUserIfValidWithResponseOnFail(ctx, response, request.Header.Get("token"), request.FormValue("hubid"))
if err != nil {
return
}
targetId, err := convertions.StringToUuid(request.FormValue("target"))
if err != nil {
http.Error(response, "invalid targetid", http.StatusBadRequest)
return
}
scope, err := convertions.StringToUint32(request.FormValue("scope"))
if err != nil {
http.Error(response, "invalid scope (set 0 for no scope)", http.StatusBadRequest)
return
}
hub.Mu.RLock()
target, ok := hub.Users[targetId]
hub.Mu.RUnlock()
if !ok {
http.Error(response, "target not found", http.StatusNotFound)
return
}
if !haveUserPermissions(types.PermissionMuteUser, uint8(scope), hubUser, hub) {
http.Error(response, "no permission", http.StatusForbidden)
return
}
if scope == 0 {
target.IsGlobalMuted = !target.IsGlobalMuted
} else {
hub.Mu.Lock()
users := hub.Groups[scope].MutedUsers
_, ok = users[targetId]
if ok {
delete(users, targetId)
} else {
users[targetId] = struct{}{}
}
}
hub.Name = newName
})
}
+2 -2
View File
@@ -86,8 +86,8 @@ func HandleUserNew(response http.ResponseWriter, request *http.Request) {
}
username := request.FormValue("username")
if len(username) < 4 {
http.Error(response, "no or short username", http.StatusBadRequest)
if len(username) < 4 || len(username) > 30 {
http.Error(response, "no or short/too long username", http.StatusBadRequest)
return
}
+11 -37
View File
@@ -152,25 +152,18 @@ const (
PermissionAddUser
PermissionRemoveUser
PermissionRenameUser
PermissionSelfRename
PermissionMuteUser
// Role permissions
PermissionAddRole
PermissionRemoveRole
PermissionChangeRoleName
PermissionChangeRoleColor
PermissionChangeRoleGlobals
PermissionSetRoleName
PermissionSetRoleColor
PermissionNoSelfRoleRemove
PermissionSelfRoleRemove
PermissionOnlySelfRoleRemove
// Channel group permissions
PermissionAddChannelGroup
PermissionRemoveChannelGroup
PermissionSetChannelGroupName
PermissionSetChannelGroupColor
PermissionSetChannelGroupPermittedVisibleRoles
// Channel permissions
PermissionAddChannel
PermissionRemoveChannel
@@ -238,7 +231,6 @@ type Hub struct {
CreatedAt time.Time `json:"createdAt"`
Roles [256]*HubRole `json:"-"`
Users map[uuid.UUID]*HubUser `json:"-"`
Groups [256]*HubGroup `json:"-"`
Channels map[uuid.UUID]*HubChannel `json:"-"`
Name string `json:"name"`
IconUrl string `json:"iconUrl"`
@@ -264,6 +256,7 @@ type HubRole struct {
Color Rgba `json:"color"`
Id uint8 `json:"id"`
BoundedGroup uint8 `json:"boundedGroup"` // BoundedGroup 0 for global
IsDeleted bool `json:"-"`
}
func (h *HubRole) GrantPermission(r Permissions) {
@@ -277,36 +270,18 @@ func (h *HubRole) HasPermission(r Permissions) bool {
}
type HubUser struct {
Mu sync.RWMutex `json:"mu"`
CreatedAt time.Time `json:"createdAt"`
Roles HubBoundRoles `json:"-"`
Name string `json:"name"` // Name empty = original name
OriginalId uuid.UUID `json:"originalId"`
IsGlobalMuted bool `json:"isGlobalMuted"`
Mu sync.RWMutex `json:"mu"`
CreatedAt time.Time `json:"createdAt"`
Roles HubBoundRoles `json:"-"`
Name string `json:"name"` // Name empty = original name
OriginalId uuid.UUID `json:"originalId"`
IsMuted bool `json:"isMuted"`
}
func NewHubUser() *HubUser {
return &HubUser{}
}
type HubGroup struct {
Name string `json:"name"`
CreatedAt time.Time `json:"createdAt"`
Color Rgba `json:"color"`
RolesCanView HubBoundRoles `json:"rolesCanView"`
UsersCachedPermissions map[uuid.UUID]CachedUserPermissions `json:"-"`
Channels map[uuid.UUID]*HubChannel `json:"-"`
MutedUsers map[uuid.UUID]struct{} `json:"-"`
Id uint8 `json:"id"` // Id 0 for unused
}
func NewHubGroup() *HubGroup {
return &HubGroup{
UsersCachedPermissions: make(map[uuid.UUID]CachedUserPermissions),
Channels: make(map[uuid.UUID]*HubChannel),
}
}
type HubChannel struct {
Mu sync.RWMutex `json:"-"`
MessagesBuff []*Message `json:"-"`
@@ -320,7 +295,6 @@ type HubChannel struct {
UsersCachedPermissions map[uuid.UUID]CachedUserPermissions `json:"-"`
NextBuffIdx uint32 `json:"-"`
Id uuid.UUID `json:"id"`
ParentId uint8 `json:"parentId"`
Position uint8 `json:"position"`
HaveOverflowed bool `json:"-"`
}