Files
go-socket/http.go
T

548 lines
12 KiB
Go

package main
import (
"context"
json2 "encoding/json"
"fmt"
"maps"
"net/http"
"slices"
"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 getClient(ctx context.Context, token string) (*Client, error) {
clientId, err := TokenValidateGetId(token)
if err != nil {
return nil, err
}
client, err := CacheGetClientById(clientId)
if err != nil {
client = &Client{Id: clientId}
err = DbSetClientById(ctx, client)
if err != nil {
return nil, err
}
CacheSaveClient(client)
}
return client, nil
}
func getGroup(ctx context.Context, groupId uint32) (*Group, error) {
group, err := CacheGetGroup(groupId)
if err != nil {
group = &Group{Id: groupId}
err = DbSetGroupById(ctx, group)
if err != nil {
return nil, err
}
CacheSaveGroup(group)
}
return group, nil
}
func isOwner(client *Client, group *Group) bool {
if group.OwnerId == client.Id {
return true
}
return false
}
func getIfOwnerClientAndGroup(ctx context.Context, response *http.ResponseWriter, request *http.Request) (*Client, *Group, error) {
client, err := getClient(ctx, request.FormValue("token"))
if err != nil {
http.Error(*response, "invalid token", http.StatusUnauthorized)
return nil, nil, err
}
affectedGroupId, err := ConvertStringUint32(request.FormValue("groupid"))
if err != nil {
http.Error(*response, "no such group", http.StatusUnauthorized)
return nil, nil, err
}
group, err := getGroup(ctx, affectedGroupId)
if err != nil {
http.Error(*response, "no such group", http.StatusUnauthorized)
return nil, nil, err
}
if !isOwner(client, group) {
http.Error(*response, "no such group", http.StatusUnauthorized)
return nil, nil, err
}
return client, group, nil
}
func HttpHandleNewClient(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 := ConvertStringToRgb(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.StatusCreated)
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 HttpHandeGroupCreate(response http.ResponseWriter, request *http.Request) {
if !isMethodAllowed(&response, request) {
return
}
ctx := request.Context()
client, err := getClient(ctx, request.FormValue("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 := ConvertStringToRgb(colorString)
group := Group{
Name: name,
CreatedAt: time.Now(),
OwnerId: client.Id,
CreatorId: client.Id,
Color: color,
Clients: map[uint32]struct{}{client.Id: {}},
}
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
}
response.WriteHeader(http.StatusCreated)
fmt.Fprintf(response, "%d", group.Id)
}
func HttpHandleGroupRemove(response http.ResponseWriter, request *http.Request) {
if !isMethodAllowed(&response, request) {
return
}
ctx := request.Context()
_, group, err := getIfOwnerClientAndGroup(ctx, &response, request)
if err != nil {
return
}
err = DbDeleteGroup(ctx, group)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusAccepted)
}
func HttpHandleGroupAddClient(response http.ResponseWriter, request *http.Request) {
if !isMethodAllowed(&response, request) {
return
}
ctx := request.Context()
_, group, err := getIfOwnerClientAndGroup(ctx, &response, request)
if err != nil {
return
}
clientsString := request.FormValue("clients")
var remainingUsersCount = int(MaxClientsInGroup) - len(group.Clients)
if remainingUsersCount < 1 {
http.Error(response, "max users", http.StatusUnauthorized)
return
}
clientsStringSlice := strings.SplitN(clientsString, ",", remainingUsersCount+1)
if len(clientsStringSlice) == 0 {
http.Error(response, "no users to add", http.StatusBadRequest)
return
}
var ids [MaxClientsInGroup]uint32
var idx uint32 = 0
for _, s := range clientsStringSlice {
if idx >= MaxClientsInGroup {
break
}
id, err := ConvertStringUint32(strings.TrimSpace(s))
if err != nil {
continue
}
ids[idx] = id
idx++
}
if idx == 0 {
http.Error(response, "no valid users", http.StatusBadRequest)
return
}
err = DbAddClientsToGroup(ctx, group.Id, &ids)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
for i := uint32(0); i < idx; i++ {
group.Clients[ids[i]] = struct{}{}
}
response.WriteHeader(http.StatusAccepted)
}
func HttpHandleGroupRemoveClient(response http.ResponseWriter, request *http.Request) {
if !isMethodAllowed(&response, request) {
return
}
ctx := request.Context()
_, group, err := getIfOwnerClientAndGroup(ctx, &response, request)
if err != nil {
return
}
clientsString := request.FormValue("clients")
clientsStringSlice := strings.SplitN(clientsString, ",", int(MaxClientsInGroup)+1)
var ids [MaxClientsInGroup]uint32
var idx uint32 = 0
for _, s := range clientsStringSlice {
if idx >= MaxClientsInGroup {
break
}
id, err := ConvertStringUint32(strings.TrimSpace(s))
if err != nil {
continue
}
ids[idx] = id
idx++
}
if idx == 0 {
http.Error(response, "no valid users", http.StatusBadRequest)
return
}
count, err := DbRemoveClientsFromGroup(ctx, group.Id, &ids)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
for i := uint32(0); i < idx; i++ {
delete(group.Clients, ids[i])
}
response.WriteHeader(http.StatusAccepted)
response.Write([]byte(strconv.Itoa(count)))
}
func HttpHandleGroupChangeColor(response http.ResponseWriter, request *http.Request) {
if !isMethodAllowed(&response, request) {
return
}
ctx := request.Context()
_, group, err := getIfOwnerClientAndGroup(ctx, &response, request)
if err != nil {
return
}
color, err := ConvertStringToRgb(request.FormValue("color"))
if err != nil {
http.Error(response, "invalid color", http.StatusBadRequest)
return
}
group.Color = color
err = DbSetGroupColor(ctx, group)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusAccepted)
response.Write([]byte("changed"))
}
func HttpHandleGroupChangeOwner(response http.ResponseWriter, request *http.Request) {
if !isMethodAllowed(&response, request) {
return
}
ctx := request.Context()
client, group, err := getIfOwnerClientAndGroup(ctx, &response, request)
if err != nil {
return
}
newOwnerName := request.FormValue("newOwner")
newOwner, err := CacheGetClientByName(newOwnerName)
if err != nil {
newOwner = &Client{Name: newOwnerName}
err = DbSetClientByName(ctx, newOwner)
if err != nil {
http.Error(response, "client not in group", http.StatusBadRequest)
return
}
CacheSaveClient(client)
}
_, ok := group.Clients[newOwner.Id]
if !ok {
http.Error(response, "client not in group", http.StatusBadRequest)
return
}
group.OwnerId = newOwner.Id
err = DbSetGroupOwnerId(ctx, group)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusAccepted)
}
func HttpHandleNewMessage(response http.ResponseWriter, request *http.Request) {
if !isMethodAllowed(&response, request) {
return
}
ctx := request.Context()
client, err := getClient(ctx, request.FormValue("token"))
if err != nil {
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
}
err = WsSendToGroup(ctx, targetId, client.Id, content)
if err != nil {
http.Error(response, err.Error(), http.StatusBadRequest)
return
}
response.WriteHeader(http.StatusAccepted)
}
func HttpHandleGroupsGetWithoutMembers(response http.ResponseWriter, request *http.Request) {
if !isMethodAllowed(&response, request) {
return
}
ctx := request.Context()
client, err := getClient(ctx, request.FormValue("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
groups := make([]GroupNoMembers, 0, len(client.Groups))
for groupId := range client.Groups {
group, err := getGroup(ctx, groupId)
if err != nil {
continue
}
groups = append(groups, GroupNoMembers{
Id: groupId,
Name: group.Name,
CreatedAt: group.CreatedAt,
CreatorId: group.CreatorId,
OwnerId: group.OwnerId,
Color: group.Color,
EnableClientsColors: group.EnableClientColors,
})
}
json, err := json2.Marshal(groups)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusAccepted)
response.Write(json)
}
func HttpHandleGroupMembersGet(response http.ResponseWriter, request *http.Request) {
if !isMethodAllowed(&response, request) {
return
}
ctx := request.Context()
client, err := getClient(ctx, request.FormValue("token"))
if err != nil {
http.Error(response, "invalid token", http.StatusUnauthorized)
return
}
groupStr := request.FormValue("group")
groupId, err := ConvertStringUint32(groupStr)
if err != nil {
http.Error(response, "invalid group", http.StatusBadRequest)
return
}
_, ok := client.Groups[groupId]
if !ok {
http.Error(response, "no such group", http.StatusUnauthorized)
return
}
group, err := getGroup(ctx, groupId)
if err != nil {
http.Error(response, "no such group", http.StatusUnauthorized)
return
}
groupMembers := slices.Collect(maps.Keys(group.Clients))
json, err := json2.Marshal(groupMembers)
if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusAccepted)
response.Write(json)
}