user no stays in cache forever, fix some hubs bugs and add new enpoints

This commit is contained in:
2026-05-04 14:03:02 +02:00
parent 22e2d18810
commit 015c79bf09
14 changed files with 260 additions and 34 deletions
+44 -16
View File
@@ -29,18 +29,12 @@ func HandleAttachmentFileUpload(response http.ResponseWriter, request *http.Requ
return
}
request.Body = http.MaxBytesReader(response, request.Body, int64(config.MaxRequestWithFileBytes))
if err = request.ParseMultipartForm(int64(config.MaxRequestBytes)); err != nil {
http.Error(response, "invalid multipart form", http.StatusBadRequest)
return
}
conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connectionid"), user)
if !ok {
return
}
target := request.FormValue("target_id")
file, header, err := request.FormFile("file")
if err != nil {
http.Error(response, "missing file", http.StatusBadRequest)
@@ -49,11 +43,31 @@ func HandleAttachmentFileUpload(response http.ResponseWriter, request *http.Requ
defer file.Close()
contentType := header.Header.Get("Content-Type")
key := minio.GetKey(&minio.GetKeyOptions{
ConnectionId: conn.Id,
MimeType: contentType,
UploadType: minio.ConnectionFile,
})
var key string
if conn, ok := getConnection(ctx, target, user); ok {
key = minio.GetKey(&minio.GetKeyOptions{
ConnectionId: conn.Id,
MimeType: contentType,
UploadType: minio.ConnectionFile,
})
} else if channel, ok := getChannelFromUser(user, target); ok {
channel.Mu.RLock()
perms := channel.UsersCachedPermissions[user.Id]
channel.Mu.RUnlock()
if !perms.CanMessage() {
http.Error(response, "forbidden", http.StatusForbidden)
return
}
key = minio.GetKey(&minio.GetKeyOptions{
ChannelId: channel.Id,
MimeType: contentType,
UploadType: minio.HubChannelFile,
})
} else {
http.Error(response, "cannot find target", http.StatusBadRequest)
return
}
if err = minio.Upload(ctx, key, file, header.Size, contentType, map[string]string{
"originalName": header.Filename,
@@ -322,13 +336,27 @@ func HandleAttachmentFileDownload(response http.ResponseWriter, request *http.Re
return
}
conn, ok := getConnectionWithResponseOnFail(response, request.FormValue("connectionid"), user)
if !ok {
target := request.URL.Query().Get("target_id")
key := request.URL.Query().Get("key")
var validPrefix string
if conn, ok := getConnection(ctx, target, user); ok {
validPrefix = string(minio.ConnectionFilePrefix) + conn.Id.String() + "/"
} else if channel, ok := getChannelFromUser(user, target); ok {
channel.Mu.RLock()
perms := channel.UsersCachedPermissions[user.Id]
channel.Mu.RUnlock()
if !perms.CanReadHistory() {
http.Error(response, "forbidden", http.StatusForbidden)
return
}
validPrefix = string(minio.HubChannelFilePrefix) + channel.Id.String() + "/"
} else {
http.Error(response, "cannot find target", http.StatusBadRequest)
return
}
key := request.URL.Query().Get("key")
if !strings.HasPrefix(key, string(minio.ConnectionFilePrefix)+conn.Id.String()+"/") {
if !strings.HasPrefix(key, validPrefix) {
http.Error(response, "no such file", http.StatusUnauthorized)
return
}
+39
View File
@@ -36,6 +36,45 @@ func getUserByToken(ctx context.Context, token string) (*types.User, error) {
return getUserById(ctx, userId)
}
func getConnection(ctx context.Context, connectionIdStr string, user *types.User) (*types.Connection, bool) {
connectionId, err := convertions.StringToUuid(connectionIdStr)
if err != nil {
return nil, false
}
if conn, ok := cache.GetConnection(user, connectionId); ok {
return conn, true
}
conn, err := postgresql.ConnectionGetById(ctx, connectionId)
if err != nil {
return nil, false
}
if conn.RequestorId != user.Id && conn.RecipientId != user.Id {
return nil, false
}
user.Mu.Lock()
user.Connections[conn.Id] = conn
user.Mu.Unlock()
return conn, true
}
func getChannelFromUser(user *types.User, channelIdStr string) (*types.HubChannel, bool) {
channelId, err := convertions.StringToUuid(channelIdStr)
if err != nil {
return nil, false
}
user.Mu.RLock()
defer user.Mu.RUnlock()
for _, hub := range user.Hubs {
hub.Mu.RLock()
ch, ok := hub.Channels[channelId]
hub.Mu.RUnlock()
if ok {
return ch, true
}
}
return nil, false
}
func getConnectionWithResponseOnFail(response http.ResponseWriter, connectionIdStr string, user *types.User) (*types.Connection, bool) {
connectionId, err := convertions.StringToUuid(connectionIdStr)
if err != nil {
+2
View File
@@ -41,6 +41,8 @@ func validCheckWithResponseOnFail(response http.ResponseWriter, request *http.Re
maxSize = int64(config.MaxRequestBytes)
}
request.Body = http.MaxBytesReader(response, request.Body, maxSize)
if request.ContentLength > maxSize {
io.Copy(io.Discard, request.Body)
http.Error(response, "Request too large", http.StatusRequestEntityTooLarge)
+69 -1
View File
@@ -551,6 +551,74 @@ func HandleHubSetBg(response http.ResponseWriter, request *http.Request) {
response.WriteHeader(http.StatusCreated)
}
func HandleGetHubIcon(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(response, request, normal) {
return
}
ctx := request.Context()
_, _, hub, err := getHubUserIfValidWithResponseOnFail(ctx, response, request)
if err != nil {
return
}
if hub.IconUrl == "" {
http.Error(response, "hub has no icon", http.StatusNoContent)
return
}
url, meta, err := minio.GetDownloadUrlAndMetadata(ctx, hub.IconUrl)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
iconData, err := json.Marshal(map[string]any{
"url": url.String(),
"metadata": meta,
})
if err != nil {
http.Error(response, "json error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusOK)
response.Write(iconData)
}
func HandleGetHubBg(response http.ResponseWriter, request *http.Request) {
if !validCheckWithResponseOnFail(response, request, normal) {
return
}
ctx := request.Context()
_, _, hub, err := getHubUserIfValidWithResponseOnFail(ctx, response, request)
if err != nil {
return
}
if hub.BgUrl == "" {
http.Error(response, "hub has no background", http.StatusNoContent)
return
}
url, meta, err := minio.GetDownloadUrlAndMetadata(ctx, hub.BgUrl)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
bgData, err := json.Marshal(map[string]any{
"url": url.String(),
"metadata": meta,
})
if err != nil {
http.Error(response, "json error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusOK)
response.Write(bgData)
}
func HandleHubRemove(response http.ResponseWriter, request *http.Request) {
_, hub, _, _, ok := hubPermissionContext(response, request, normal, types.PermissionRemoveHub)
if !ok {
@@ -852,7 +920,7 @@ func HandleRoleSetColor(response http.ResponseWriter, request *http.Request) {
}
color, err := convertions.StringToRgba(request.FormValue("new_color"))
if err != nil {
http.Error(response, "invalid newcolor", http.StatusBadRequest)
http.Error(response, "invalid new_color", http.StatusBadRequest)
return
}
+3 -1
View File
@@ -26,17 +26,19 @@ const (
UserProfileBg
HubIcon
HubBackground
ChannelIcon
)
type DataTypePrefix string
const (
ConnectionFilePrefix DataTypePrefix = "connection/"
HubChannelFilePrefix DataTypePrefix = "hub/"
HubChannelFilePrefix DataTypePrefix = "hubChannel/"
UserAvatarPrefix DataTypePrefix = "userAvatar/"
UserProfileBgPrefix DataTypePrefix = "userProfileBg/"
HubIconPrefix DataTypePrefix = "hubIcon/"
HubBackgroundPrefix DataTypePrefix = "hubBackground/"
ChannelIconPrefix DataTypePrefix = "channelIcon/"
)
type GetKeyOptions struct {
+13
View File
@@ -181,6 +181,19 @@ func ConnectionDelete(ctx context.Context, conn *types.Connection) error {
return err
}
func ConnectionGetById(ctx context.Context, id uuid.UUID) (*types.Connection, error) {
conn := types.NewConn()
err := dbConn.QueryRow(ctx, `
SELECT id, requestor_id, recipient_id, state, created_at
FROM user_connections
WHERE id = $1
`, id).Scan(&conn.Id, &conn.RequestorId, &conn.RecipientId, &conn.State, &conn.CreatedAt)
if err != nil {
return nil, err
}
return conn, nil
}
func ConnectionsGetBelongingToUser(ctx context.Context, user *types.User) error {
rows, err := dbConn.Query(ctx, `
SELECT id, requestor_id, recipient_id, state, created_at
+7 -8
View File
@@ -38,7 +38,7 @@ type User struct {
WsConn *websocket.Conn `json:"-"`
Id uuid.UUID `json:"-"`
Connections map[uuid.UUID]*Connection `json:"-"`
Hubs map[uuid.UUID]*Hub `json:"-"`
Hubs map[uuid.UUID]*Hub `json:"hubs-to-delete"`
Color Rgba `json:"color"`
}
@@ -249,7 +249,7 @@ const (
CachedUserCanMessage
)
const CachedUserPermissionsAll = CachedUserCanMessage | CachedUserCanReadHistory | CachedUserCanReadHistory
const CachedUserPermissionsAll = CachedUserCanView | CachedUserCanReadHistory | CachedUserCanMessage
func (p *CachedUserPermissions) SetCanView() { *p |= CachedUserCanView }
func (p *CachedUserPermissions) ClearCanView() { *p &^= CachedUserCanView }
@@ -287,12 +287,11 @@ func NewHub() *Hub {
}
type HubRole struct {
Name string `json:"role"`
CreatedAt time.Time `json:"createdAt"`
Permissions Permissions `json:"permissions"`
Color Rgba `json:"color"`
Id uint8 `json:"id"`
BoundedGroup uint8 `json:"boundedGroup"` // BoundedGroup 0 for global
Name string `json:"role"`
CreatedAt time.Time `json:"createdAt"`
Permissions Permissions `json:"permissions"`
Color Rgba `json:"color"`
Id uint8 `json:"id"`
}
func (h *HubRole) GrantPermission(r Permissions) {
+1 -1
View File
@@ -32,7 +32,7 @@ func ServeWsConnection(responseWriter http.ResponseWriter, request *http.Request
var user = types.User{WsConn: connection}
var isAuthenticated bool
var ignoreCache bool
var ignoreCache bool = true
defer func() { closeConnection(&user, ignoreCache) }()
for {