635 lines
15 KiB
Go
635 lines
15 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 getUser(ctx context.Context, token string) (*User, error) {
|
|
userId, err := TokenValidateGetId(token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
user, err := CacheGetUserById(userId)
|
|
if err != nil {
|
|
user = &User{Id: userId}
|
|
if err = DbGetUserById(ctx, user); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = DbGetUserGroups(ctx, user); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = DbGetUserConnections(ctx, user); err != nil {
|
|
return nil, err
|
|
}
|
|
CacheSaveUser(user)
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func getGroup(ctx context.Context, groupId uint32) (*Group, error) {
|
|
group, err := CacheGetGroup(groupId)
|
|
if err != nil {
|
|
group = &Group{Id: groupId}
|
|
if err = DbGetGroupById(ctx, group); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = DbGetGroupMembers(ctx, group); err != nil {
|
|
return nil, err
|
|
}
|
|
CacheSaveGroup(group)
|
|
}
|
|
return group, nil
|
|
}
|
|
|
|
func isOwner(user *User, group *Group) bool {
|
|
if group.OwnerId == user.Id {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func getIfOwnerUserAndGroup(ctx context.Context, response *http.ResponseWriter, request *http.Request) (*User, *Group, error) {
|
|
user, err := getUser(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(user, group) {
|
|
http.Error(*response, "no such group", http.StatusUnauthorized)
|
|
return nil, nil, err
|
|
}
|
|
return user, group, nil
|
|
}
|
|
|
|
func HttpHandleUserNew(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
|
|
}
|
|
|
|
newUser := &User{
|
|
Name: username,
|
|
PasswordHash: hashedPassword,
|
|
Color: color,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
|
|
ctx := request.Context()
|
|
|
|
err = DbUserSave(ctx, newUser)
|
|
if err != nil {
|
|
http.Error(response, "name taken", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
response.WriteHeader(http.StatusCreated)
|
|
}
|
|
|
|
func HttpHandleUserDelete(response http.ResponseWriter, request *http.Request) {
|
|
ctx := request.Context()
|
|
|
|
userId, err := TokenValidateGetId(request.FormValue("token"))
|
|
if err != nil {
|
|
http.Error(response, "invalid token", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
err = DbUserDelete(ctx, userId)
|
|
if err != nil {
|
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
|
}
|
|
|
|
CacheDeleteUser(userId)
|
|
response.WriteHeader(http.StatusAccepted)
|
|
}
|
|
|
|
// HttpHandleUserModifyAppearance currently just color
|
|
func HttpHandleUserModifyAppearance(response http.ResponseWriter, request *http.Request) {
|
|
ctx := request.Context()
|
|
user, err := getUser(ctx, request.FormValue("token"))
|
|
if err != nil {
|
|
http.Error(response, "invalid token", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
color, err := ConvertStringToRgb(request.FormValue("color"))
|
|
if err != nil {
|
|
http.Error(response, "invalid color", http.StatusBadRequest)
|
|
return
|
|
}
|
|
user.Color = color
|
|
err = DbUserSetColor(ctx, user)
|
|
if err != nil {
|
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
response.WriteHeader(http.StatusAccepted)
|
|
}
|
|
|
|
// HttpHandleUserModifyAbout currently just pronouns
|
|
func HttpHandleUserModifyAbout(response http.ResponseWriter, request *http.Request) {
|
|
ctx := request.Context()
|
|
user, err := getUser(ctx, request.FormValue("token"))
|
|
if err != nil {
|
|
http.Error(response, "invalid token", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
pronouns := request.FormValue("pronouns")
|
|
if len(pronouns) > 25 && len(pronouns) < 2 {
|
|
http.Error(response, "invalid pronouns", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
user.Pronouns = pronouns
|
|
err = DbUserSetPronouns(ctx, user)
|
|
if err != nil {
|
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
response.WriteHeader(http.StatusAccepted)
|
|
}
|
|
|
|
func HttpHandleUserMessage(response http.ResponseWriter, request *http.Request) {
|
|
ctx := request.Context()
|
|
user, err := getUser(ctx, request.FormValue("token"))
|
|
if err != nil {
|
|
http.Error(response, "invalid token", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
}
|
|
|
|
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 (
|
|
user *User
|
|
err error
|
|
ctx = request.Context()
|
|
)
|
|
|
|
user, err = CacheGetUserByName(username)
|
|
if err != nil {
|
|
user = &User{Name: username}
|
|
if err = DbGetUserByName(ctx, user); err != nil {
|
|
http.Error(response, "bad login1", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
if err = DbGetUserGroups(ctx, user); err != nil {
|
|
http.Error(response, "bad login1", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
if err = DbGetUserConnections(ctx, user); err != nil {
|
|
http.Error(response, "bad login1", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
CacheSaveUser(user)
|
|
}
|
|
|
|
err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password))
|
|
if err != nil {
|
|
http.Error(response, "bad login2", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
token, err := TokenCreate(user.Id)
|
|
if err != nil {
|
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
response.WriteHeader(http.StatusCreated)
|
|
response.Write([]byte(token))
|
|
}
|
|
|
|
func HttpHandeGroupCreate(response http.ResponseWriter, request *http.Request) {
|
|
if !isMethodAllowed(&response, request) {
|
|
return
|
|
}
|
|
|
|
ctx := request.Context()
|
|
|
|
user, err := getUser(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: user.Id,
|
|
CreatorId: user.Id,
|
|
Color: color,
|
|
Users: map[uint32]struct{}{user.Id: {}},
|
|
}
|
|
|
|
enableUserColors := request.FormValue("enableUserColors")
|
|
if enableUserColors == "1" {
|
|
group.EnableUserColors = true
|
|
}
|
|
|
|
err = DbGroupSaveWithoutUsers(ctx, &group)
|
|
if err != nil {
|
|
http.Error(response, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
response.WriteHeader(http.StatusCreated)
|
|
fmt.Fprintf(response, "%d", group.Id)
|
|
}
|
|
|
|
func HttpHandleGroupDelete(response http.ResponseWriter, request *http.Request) {
|
|
if !isMethodAllowed(&response, request) {
|
|
return
|
|
}
|
|
|
|
ctx := request.Context()
|
|
_, group, err := getIfOwnerUserAndGroup(ctx, &response, request)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
err = DbGroupDelete(ctx, group)
|
|
if err != nil {
|
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
response.WriteHeader(http.StatusAccepted)
|
|
}
|
|
|
|
func HttpHandleGroupAddUser(response http.ResponseWriter, request *http.Request) {
|
|
if !isMethodAllowed(&response, request) {
|
|
return
|
|
}
|
|
|
|
ctx := request.Context()
|
|
|
|
_, group, err := getIfOwnerUserAndGroup(ctx, &response, request)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
usersString := request.FormValue("users")
|
|
var remainingUsersCount = int(MaxUsersInGroup) - len(group.Users)
|
|
if remainingUsersCount < 1 {
|
|
http.Error(response, "max users", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
usersStringSlice := strings.SplitN(usersString, ",", remainingUsersCount+1)
|
|
if len(usersStringSlice) == 0 {
|
|
http.Error(response, "no users to add", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
var ids [MaxUsersInGroup]uint32
|
|
var idx uint32 = 0
|
|
for _, s := range usersStringSlice {
|
|
if idx >= MaxUsersInGroup {
|
|
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 = DbGroupAddUsers(ctx, group.Id, &ids)
|
|
if err != nil {
|
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
for i := uint32(0); i < idx; i++ {
|
|
group.Users[ids[i]] = struct{}{}
|
|
}
|
|
|
|
response.WriteHeader(http.StatusAccepted)
|
|
}
|
|
|
|
func HttpHandleGroupRemoveUser(response http.ResponseWriter, request *http.Request) {
|
|
if !isMethodAllowed(&response, request) {
|
|
return
|
|
}
|
|
|
|
ctx := request.Context()
|
|
|
|
_, group, err := getIfOwnerUserAndGroup(ctx, &response, request)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
usersString := request.FormValue("users")
|
|
|
|
usersStringSlice := strings.SplitN(usersString, ",", int(MaxUsersInGroup)+1)
|
|
|
|
var ids [MaxUsersInGroup]uint32
|
|
var idx uint32 = 0
|
|
for _, s := range usersStringSlice {
|
|
if idx >= MaxUsersInGroup {
|
|
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 := DbGroupRemoveUsers(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.Users, 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 := getIfOwnerUserAndGroup(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 = DbGroupSetColor(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()
|
|
user, group, err := getIfOwnerUserAndGroup(ctx, &response, request)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
newOwnerName := request.FormValue("newOwner")
|
|
|
|
newOwner, err := CacheGetUserByName(newOwnerName)
|
|
if err != nil {
|
|
newOwner = &User{Name: newOwnerName}
|
|
err = DbGetUserByName(ctx, newOwner)
|
|
if err != nil {
|
|
http.Error(response, "user not in group", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
CacheSaveUser(user)
|
|
}
|
|
|
|
_, ok := group.Users[newOwner.Id]
|
|
if !ok {
|
|
http.Error(response, "user not in group", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
group.OwnerId = newOwner.Id
|
|
err = DbGroupSetOwnerId(ctx, group)
|
|
if err != nil {
|
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
response.WriteHeader(http.StatusAccepted)
|
|
}
|
|
|
|
func HttpHandleGroupMessage(response http.ResponseWriter, request *http.Request) {
|
|
if !isMethodAllowed(&response, request) {
|
|
return
|
|
}
|
|
|
|
ctx := request.Context()
|
|
user, err := getUser(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, user.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()
|
|
|
|
user, err := getUser(ctx, request.FormValue("token"))
|
|
if err != nil {
|
|
http.Error(response, "invalid token", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
groups := make([]GroupNoMembers, 0, len(user.Groups))
|
|
|
|
for groupId := range user.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,
|
|
EnableUsersColors: group.EnableUserColors,
|
|
})
|
|
}
|
|
|
|
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()
|
|
user, err := getUser(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 := user.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.Users))
|
|
|
|
json, err := json2.Marshal(groupMembers)
|
|
if err != nil {
|
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
response.WriteHeader(http.StatusAccepted)
|
|
response.Write(json)
|
|
}
|