diff --git a/go-socket b/go-socket index be5b708..1134ef2 100755 Binary files a/go-socket and b/go-socket differ diff --git a/main.go b/main.go index 057ec06..17316f0 100644 --- a/main.go +++ b/main.go @@ -69,6 +69,10 @@ func main() { http.HandleFunc("PUT /hub/join", withCORS(httpRequest.HandleHubJoin)) http.HandleFunc("PATCH /hub/name", withCORS(httpRequest.HandleHubSetName)) http.HandleFunc("PATCH /hub/color", withCORS(httpRequest.HandleHubSetColor)) + http.HandleFunc("PATCH /hub/icon", withCORS(httpRequest.HandleHubSetIcon)) + http.HandleFunc("GET /hub/icon", withCORS(httpRequest.HandleGetHubIcon)) + http.HandleFunc("PATCH /hub/bg", withCORS(httpRequest.HandleHubSetBg)) + http.HandleFunc("GET /hub/bg", withCORS(httpRequest.HandleGetHubBg)) http.HandleFunc("DELETE /hub", withCORS(httpRequest.HandleHubRemove)) http.HandleFunc("PATCH /hub/usercolorallowed", withCORS(httpRequest.HandleHubToggleUserColorAllowed)) http.HandleFunc("DELETE /hub/user", withCORS(httpRequest.HandleHubUserRemove)) @@ -93,7 +97,7 @@ func main() { http.HandleFunc("POST /connection/message", withCORS(httpRequest.HandleDm)) http.HandleFunc("GET /ws", wsServer.ServeWsConnection) - http.Handle("GET /client/", http.StripPrefix("/client/", http.FileServer(http.Dir("machine-client")))) + http.Handle("GET /client/", http.StripPrefix("/client/", http.FileServer(http.Dir("test-client")))) log.Println("beep boop; server server started") log.Fatal(http.ListenAndServe(":"+strconv.Itoa(int(config.Port)), nil)) diff --git a/packages/httpRequest/files.go b/packages/httpRequest/files.go index 4a50a0b..9f2c97d 100644 --- a/packages/httpRequest/files.go +++ b/packages/httpRequest/files.go @@ -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 } diff --git a/packages/httpRequest/get.go b/packages/httpRequest/get.go index eeb5014..1ed2682 100644 --- a/packages/httpRequest/get.go +++ b/packages/httpRequest/get.go @@ -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 { diff --git a/packages/httpRequest/helper.go b/packages/httpRequest/helper.go index fc3f78b..9653f2b 100644 --- a/packages/httpRequest/helper.go +++ b/packages/httpRequest/helper.go @@ -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) diff --git a/packages/httpRequest/hubs.go b/packages/httpRequest/hubs.go index 79ab776..1ecfc06 100644 --- a/packages/httpRequest/hubs.go +++ b/packages/httpRequest/hubs.go @@ -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 } diff --git a/packages/minio/minio.go b/packages/minio/minio.go index c5dad4f..58d256a 100644 --- a/packages/minio/minio.go +++ b/packages/minio/minio.go @@ -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 { diff --git a/packages/postgresql/postgresql.go b/packages/postgresql/postgresql.go index c01b3e8..9a34b70 100644 --- a/packages/postgresql/postgresql.go +++ b/packages/postgresql/postgresql.go @@ -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 diff --git a/packages/types/types.go b/packages/types/types.go index f03e29e..f9a8976 100644 --- a/packages/types/types.go +++ b/packages/types/types.go @@ -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) { diff --git a/packages/wsServer/wsServer.go b/packages/wsServer/wsServer.go index f4dae95..83a89d8 100644 --- a/packages/wsServer/wsServer.go +++ b/packages/wsServer/wsServer.go @@ -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 { diff --git a/machine-client/.gitignore b/test-client/.gitignore similarity index 100% rename from machine-client/.gitignore rename to test-client/.gitignore diff --git a/machine-client/index.html b/test-client/index.html similarity index 92% rename from machine-client/index.html rename to test-client/index.html index 94868c7..b4d5140 100644 --- a/machine-client/index.html +++ b/test-client/index.html @@ -53,6 +53,10 @@ + + + + @@ -178,7 +182,7 @@