package main import ( "encoding/binary" "fmt" "net/http" "strconv" "strings" "time" "golang.org/x/crypto/bcrypt" ) func isMethodAllowed(response *http.ResponseWriter, request *http.Request) bool { if request.Method != http.MethodPost { http.Error(*response, "POST only", http.StatusMethodNotAllowed) return false } return true } func parseRgb(str string) ([3]uint8, error) { parts := strings.SplitN(str, ",", 4) if len(parts) != 3 { return [3]uint8{}, fmt.Errorf("invalid rgb") } var rgb [3]uint8 for i, p := range parts { n, err := strconv.ParseUint(strings.TrimSpace(p), 10, 8) if err != nil { return [3]uint8{}, fmt.Errorf("invalid component %d: %w", i, err) } rgb[i] = uint8(n) } return rgb, nil } func HttpHandleNewUser(response http.ResponseWriter, request *http.Request) { if !isMethodAllowed(&response, request) { return } username := request.FormValue("username") if len(username) < 4 { http.Error(response, "no or short username", http.StatusBadRequest) return } password := request.FormValue("password") if len(password) < 8 { http.Error(response, "no or short password", http.StatusBadRequest) return } color, err := parseRgb(request.FormValue("color")) if err != nil { http.Error(response, "bad color", http.StatusBadRequest) return } hashedPassword, err := PasswordHash(password) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return } newClient := &Client{ Name: username, PasswordHash: hashedPassword, Color: color, CreatedAt: time.Now(), } ctx := request.Context() err = DbSaveClientWithoutGroups(ctx, newClient) if err != nil { http.Error(response, "name taken", http.StatusUnauthorized) return } response.WriteHeader(http.StatusAccepted) response.Write([]byte("created")) } func HttpHandleNewToken(response http.ResponseWriter, request *http.Request) { if !isMethodAllowed(&response, request) { return } username := request.FormValue("username") if len(username) < 4 { http.Error(response, "no or short username", http.StatusBadRequest) return } password := request.FormValue("password") if len(password) < 8 { http.Error(response, "no or short password", http.StatusBadRequest) return } var ( client *Client err error ctx = request.Context() ) client, err = CacheGetClientByName(username) if err != nil { client = &Client{Name: username} err := DbSetClientByName(ctx, client) if err != nil { http.Error(response, "bad login1", http.StatusUnauthorized) return } CacheSaveClient(client) } err = bcrypt.CompareHashAndPassword([]byte(client.PasswordHash), []byte(password)) if err != nil { http.Error(response, "bad login2", http.StatusUnauthorized) return } token, err := TokenCreate(client.Id) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return } response.WriteHeader(http.StatusAccepted) response.Write([]byte(token)) } func HttpHandeNewGroup(response http.ResponseWriter, request *http.Request) { if !isMethodAllowed(&response, request) { return } token := request.FormValue("token") clientId, err := TokenValidateGetId(token) if err != nil { http.Error(response, "invalid token", http.StatusUnauthorized) return } name := request.FormValue("name") if name == "" { name = "Best group ever" } colorString := request.FormValue("color") color, err := parseRgb(colorString) if err != nil { var ok bool color, ok = Colors[colorString] if !ok { color = Colors["default"] } } ctx := request.Context() client := Client{Id: clientId} cacheClient, err := CacheGetClientById(clientId) if err == nil { client = *cacheClient } else { err = DbSetClientByIdWithoutGroups(ctx, &client) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return } } group := Group{ Name: name, CreatedAt: time.Now(), OwnerId: clientId, CreatorId: clientId, Color: color, Clients: map[uint32]struct{}{clientId: {}}, } enableClientColors := request.FormValue("enableClientColors") if enableClientColors == "1" { group.EnableClientColors = true } err = DbSaveGroupWithoutClients(ctx, &group) if err != nil { http.Error(response, err.Error(), http.StatusInternalServerError) return } groupIdBytes := make([]byte, 4) binary.BigEndian.PutUint32(groupIdBytes, group.Id) response.WriteHeader(http.StatusCreated) response.Write(groupIdBytes) } func HttpHandleGroupAddClient(response http.ResponseWriter, request *http.Request) { if !isMethodAllowed(&response, request) { return } token := request.FormValue("token") clientId, err := TokenValidateGetId(token) if err != nil { http.Error(response, "invalid token", http.StatusUnauthorized) return } affectedGroupId, err := ConvertStringUint32(request.FormValue("groupid")) if err != nil { http.Error(response, "no such group", http.StatusUnauthorized) return } ctx := request.Context() var group Group group.Id = affectedGroupId groupPtr, err := CacheGetGroup(affectedGroupId) if err == nil { group = *groupPtr } else { err = DbSetGroupByIdWithoutClients(ctx, &group) if err != nil { http.Error(response, "no such group", http.StatusUnauthorized) return } } if group.OwnerId != clientId { http.Error(response, "no such group", http.StatusUnauthorized) return } usersToAddString := request.FormValue("users") var remainingUsersCount = int(MaxClientsInGroup) - len(group.Clients) if remainingUsersCount < 1 { http.Error(response, "max users", http.StatusUnauthorized) return } userIdStrings := strings.SplitN(usersToAddString, ",", remainingUsersCount+1) if len(userIdStrings) == 0 { http.Error(response, "no users to add", http.StatusBadRequest) return } clientIds := make([]uint32, 0, len(userIdStrings)) for _, s := range userIdStrings { id, err := ConvertStringUint32(strings.TrimSpace(s)) if err != nil { continue } clientIds = append(clientIds, id) } if len(clientIds) == 0 { http.Error(response, "no valid users", http.StatusBadRequest) return } err = DbAddClientsToGroup(ctx, group.Id, clientIds) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return } response.WriteHeader(http.StatusAccepted) _, err = response.Write([]byte("ok")) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return } } func HttpHandleNewMessage(response http.ResponseWriter, request *http.Request) { if !isMethodAllowed(&response, request) { return } token := request.FormValue("token") if token == "" { http.Error(response, "invalid token", http.StatusUnauthorized) return } targetStr := request.FormValue("subject") if targetStr == "" { http.Error(response, "invalid subject", http.StatusBadRequest) return } targetId, err := ConvertStringUint32(targetStr) if err != nil { http.Error(response, "invalid subject", http.StatusBadRequest) return } content := request.FormValue("content") if content == "" { http.Error(response, "invalid content", http.StatusBadRequest) return } clientId, err := TokenValidateGetId(token) if err != nil { http.Error(response, "invalid token", http.StatusUnauthorized) return } ctx := request.Context() err = WsSendToGroup(ctx, targetId, clientId, content) if err != nil { http.Error(response, err.Error(), http.StatusBadRequest) return } response.WriteHeader(http.StatusAccepted) response.Write([]byte("sent")) }