start minIO, refactored files into packages
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
package ConnectionState
|
||||
|
||||
type ConnectionState uint8
|
||||
|
||||
const (
|
||||
Stranger ConnectionState = iota
|
||||
GroupFellow
|
||||
Friend
|
||||
GroupFriend
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
package WsEventType
|
||||
|
||||
type WsEventType uint8
|
||||
|
||||
const (
|
||||
Authentication WsEventType = iota
|
||||
DirectMessage
|
||||
ConnectionCreated
|
||||
ConnectionDeleted
|
||||
ConnectionElevated
|
||||
)
|
||||
Vendored
+85
@@ -0,0 +1,85 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"go-socket/packages/types"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
var (
|
||||
Mu sync.RWMutex
|
||||
Users = make(map[uuid.UUID]*types.User)
|
||||
)
|
||||
|
||||
func CacheGetUserById(id uuid.UUID) (*types.User, error) {
|
||||
Mu.RLock()
|
||||
defer Mu.RUnlock()
|
||||
|
||||
user, ok := Users[id]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("user %s not found", id)
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func CacheGetUserByName(name string) (*types.User, error) {
|
||||
Mu.RLock()
|
||||
defer Mu.RUnlock()
|
||||
|
||||
for _, user := range Users {
|
||||
if user.Name == name {
|
||||
return user, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("user %s not found", name)
|
||||
}
|
||||
|
||||
func CacheSaveUser(user *types.User) {
|
||||
Mu.Lock()
|
||||
defer Mu.Unlock()
|
||||
|
||||
Users[user.Id] = user
|
||||
}
|
||||
|
||||
func CacheDeleteUser(id uuid.UUID) {
|
||||
Mu.Lock()
|
||||
defer Mu.Unlock()
|
||||
|
||||
delete(Users, id)
|
||||
}
|
||||
|
||||
func CacheAddConnection(a, b *types.User, conn *types.Connection) {
|
||||
first, second := a, b
|
||||
if a.Id.String() > b.Id.String() {
|
||||
first, second = b, a
|
||||
}
|
||||
first.Mu.Lock()
|
||||
second.Mu.Lock()
|
||||
a.Connections[conn.Id] = conn
|
||||
b.Connections[conn.Id] = conn
|
||||
second.Mu.Unlock()
|
||||
first.Mu.Unlock()
|
||||
}
|
||||
|
||||
func CacheDeleteConnection(a, b *types.User, id uuid.UUID) {
|
||||
first, second := a, b
|
||||
if a.Id.String() > b.Id.String() {
|
||||
first, second = b, a
|
||||
}
|
||||
first.Mu.Lock()
|
||||
second.Mu.Lock()
|
||||
delete(a.Connections, id)
|
||||
delete(b.Connections, id)
|
||||
second.Mu.Unlock()
|
||||
first.Mu.Unlock()
|
||||
}
|
||||
|
||||
func CacheGetConnection(user *types.User, id uuid.UUID) (*types.Connection, bool) {
|
||||
user.Mu.RLock()
|
||||
defer user.Mu.RUnlock()
|
||||
conn, ok := user.Connections[id]
|
||||
return conn, ok
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package convertions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go-socket/packages/types"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func ConvertStringUint32(s string) (uint32, error) {
|
||||
v, err := strconv.ParseUint(s, 10, 32)
|
||||
return uint32(v), err
|
||||
}
|
||||
|
||||
func ConvertStringToRgba(str string) (*types.Rgba, error) {
|
||||
parts := strings.SplitN(str, ",", 5)
|
||||
if len(parts) != 4 {
|
||||
return nil, fmt.Errorf("invalid rgba")
|
||||
}
|
||||
rgba := &types.Rgba{}
|
||||
for i, p := range parts {
|
||||
n, err := strconv.ParseUint(strings.TrimSpace(p), 10, 8)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid component %d: %w", i, err)
|
||||
}
|
||||
rgba[i] = uint8(n)
|
||||
}
|
||||
return rgba, nil
|
||||
}
|
||||
|
||||
func RgbaToUint32(r *types.Rgba) uint32 {
|
||||
return uint32(r[0])<<24 | uint32(r[1])<<16 | uint32(r[2])<<8 | uint32(r[3])
|
||||
}
|
||||
|
||||
func Uint32ToRgba(v uint32) *types.Rgba {
|
||||
return &types.Rgba{uint8(v >> 24), uint8(v >> 16), uint8(v >> 8), uint8(v)}
|
||||
}
|
||||
|
||||
func ConvertStringUuid(str string) (uuid.UUID, error) {
|
||||
return uuid.Parse(str)
|
||||
}
|
||||
|
||||
func ConvertStringTimestamp(str string) (time.Time, error) {
|
||||
return time.Parse(time.RFC3339, str)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package globals
|
||||
|
||||
const (
|
||||
MaxDirectMsgCache uint32 = 12
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go-socket/packages/cache"
|
||||
"go-socket/packages/postgresql"
|
||||
"go-socket/packages/tokens"
|
||||
"go-socket/packages/types"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func GetUserById(ctx context.Context, userId uuid.UUID) (*types.User, error) {
|
||||
user, err := cache.CacheGetUserById(userId)
|
||||
if err != nil {
|
||||
user = &types.User{Id: userId}
|
||||
err = postgresql.PgGetWholeUser(ctx, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func GetUserByToken(ctx context.Context, token string) (*types.User, error) {
|
||||
userId, err := tokens.TokenValidateGetId(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return GetUserById(ctx, userId)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func methodAllowed(response *http.ResponseWriter, request *http.Request) bool {
|
||||
if request.Method != http.MethodPost {
|
||||
http.Error(*response, "POST only", http.StatusMethodNotAllowed)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
json2 "encoding/json"
|
||||
"maps"
|
||||
"net/http"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"go-socket/packages/Enums/ConnectionState"
|
||||
"go-socket/packages/Enums/WsEventType"
|
||||
"go-socket/packages/cache"
|
||||
"go-socket/packages/convertions"
|
||||
"go-socket/packages/globals"
|
||||
"go-socket/packages/postgresql"
|
||||
"go-socket/packages/types"
|
||||
"go-socket/packages/wsServer"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func HandleDm(response http.ResponseWriter, request *http.Request) {
|
||||
if !methodAllowed(&response, request) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx := request.Context()
|
||||
user, err := GetUserByToken(ctx, request.FormValue("token"))
|
||||
if err != nil {
|
||||
http.Error(response, "invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
targetConnection, err := convertions.ConvertStringUuid(request.FormValue("connectionid"))
|
||||
if err != nil {
|
||||
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
conn, ok := cache.CacheGetConnection(user, targetConnection)
|
||||
if !ok {
|
||||
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
msgContent := request.FormValue("msgContent")
|
||||
if msgContent == "" {
|
||||
http.Error(response, "empty msgContent", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var target *types.User
|
||||
|
||||
if user.Id == conn.RequestorId {
|
||||
target, err = GetUserById(ctx, conn.RecipientId)
|
||||
} else if user.Id == conn.RecipientId {
|
||||
target, err = GetUserById(ctx, conn.RequestorId)
|
||||
} else {
|
||||
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
message := &types.Message{
|
||||
Id: uuid.New(),
|
||||
Content: msgContent,
|
||||
CreatedAt: time.Now(),
|
||||
Sender: user.Id,
|
||||
Receiver: conn.Id,
|
||||
}
|
||||
|
||||
wsServer.WsSendMessageCloseIfTimeout(target, types.WsEventMessage{
|
||||
Type: WsEventType.DirectMessage,
|
||||
Event: message,
|
||||
})
|
||||
|
||||
err = postgresql.PgConnectionMessageSave(ctx, message)
|
||||
if err != nil {
|
||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
func HandleUserGetConnectionMessages(response http.ResponseWriter, request *http.Request) {
|
||||
if !methodAllowed(&response, request) {
|
||||
return
|
||||
}
|
||||
ctx := request.Context()
|
||||
user, err := GetUserByToken(ctx, request.FormValue("token"))
|
||||
if err != nil {
|
||||
http.Error(response, "invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
connectionId, err := convertions.ConvertStringUuid(request.FormValue("connectionid"))
|
||||
if err != nil {
|
||||
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
before, err := convertions.ConvertStringTimestamp(request.FormValue("before"))
|
||||
if err != nil {
|
||||
before = time.Now()
|
||||
}
|
||||
|
||||
messagesCap, err := convertions.ConvertStringUint32(request.FormValue("messages"))
|
||||
if err != nil {
|
||||
messagesCap = globals.MaxDirectMsgCache
|
||||
}
|
||||
|
||||
conn, ok := cache.CacheGetConnection(user, connectionId)
|
||||
if !ok {
|
||||
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
buffer, bufferSize := conn.GetSortedMessagesBuff()
|
||||
|
||||
var validBufCount uint32
|
||||
for validBufCount < bufferSize && buffer[validBufCount].CreatedAt.Before(before) {
|
||||
validBufCount++
|
||||
}
|
||||
|
||||
var messages []*types.Message
|
||||
|
||||
if validBufCount >= messagesCap {
|
||||
start := validBufCount - messagesCap
|
||||
messages = make([]*types.Message, messagesCap)
|
||||
for i := uint32(0); i < messagesCap; i++ {
|
||||
messages[i] = buffer[start+i]
|
||||
}
|
||||
} else {
|
||||
remaining := messagesCap - validBufCount
|
||||
cutoff := before
|
||||
if validBufCount > 0 {
|
||||
cutoff = buffer[0].CreatedAt
|
||||
}
|
||||
dbMessages, err := postgresql.PgConnectionGetMessagesBefore(ctx, cutoff, connectionId, remaining)
|
||||
if err != nil {
|
||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
messages = make([]*types.Message, 0, uint32(len(dbMessages))+validBufCount)
|
||||
messages = append(messages, dbMessages...)
|
||||
for i := uint32(0); i < validBufCount; i++ {
|
||||
messages = append(messages, buffer[i])
|
||||
}
|
||||
}
|
||||
|
||||
json, err := json2.Marshal(messages)
|
||||
if err != nil {
|
||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusOK)
|
||||
response.Write(json)
|
||||
}
|
||||
|
||||
func HandleUserNewConnection(response http.ResponseWriter, request *http.Request) {
|
||||
if !methodAllowed(&response, request) {
|
||||
return
|
||||
}
|
||||
ctx := request.Context()
|
||||
requestor, err := GetUserByToken(ctx, request.FormValue("token"))
|
||||
if err != nil {
|
||||
http.Error(response, "invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
recipientId, err := convertions.ConvertStringUuid(request.FormValue("recipient"))
|
||||
if err != nil {
|
||||
http.Error(response, "no such user", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
recipient, err := GetUserById(ctx, recipientId)
|
||||
if err != nil {
|
||||
http.Error(response, "no such user", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if requestor.Id == recipient.Id {
|
||||
http.Error(response, "cannot connect to yourself", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
requestor.Mu.RLock()
|
||||
for _, connection := range requestor.Connections {
|
||||
if (connection.RequestorId == requestor.Id && connection.RecipientId == recipient.Id) ||
|
||||
(connection.RecipientId == requestor.Id && connection.RequestorId == recipient.Id) {
|
||||
requestor.Mu.RUnlock()
|
||||
http.Error(response, "connection already exists", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
requestor.Mu.RUnlock()
|
||||
|
||||
connection := &types.Connection{
|
||||
CreatedAt: time.Now(),
|
||||
RequestorId: requestor.Id,
|
||||
RecipientId: recipient.Id,
|
||||
State: ConnectionState.Stranger,
|
||||
}
|
||||
err = postgresql.PgConnectionSave(ctx, connection)
|
||||
if err != nil {
|
||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
cache.CacheAddConnection(requestor, recipient, connection)
|
||||
|
||||
wsServer.WsSendMessageCloseIfTimeout(recipient, types.WsEventMessage{
|
||||
Type: WsEventType.ConnectionCreated,
|
||||
Event: connection,
|
||||
})
|
||||
|
||||
response.WriteHeader(http.StatusCreated)
|
||||
return
|
||||
}
|
||||
|
||||
func HandleUserDeleteConnection(response http.ResponseWriter, request *http.Request) {
|
||||
if !methodAllowed(&response, request) {
|
||||
return
|
||||
}
|
||||
ctx := request.Context()
|
||||
|
||||
user, err := GetUserByToken(ctx, request.FormValue("token"))
|
||||
if err != nil {
|
||||
http.Error(response, "invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
connectionId, err := convertions.ConvertStringUuid(request.FormValue("connectionid"))
|
||||
if err != nil {
|
||||
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
conn, ok := cache.CacheGetConnection(user, connectionId)
|
||||
if !ok {
|
||||
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var user2 *types.User
|
||||
if conn.RequestorId == user.Id {
|
||||
recipient, err := GetUserById(ctx, conn.RecipientId)
|
||||
if err != nil {
|
||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
user2 = recipient
|
||||
} else if conn.RecipientId == user.Id {
|
||||
requestor, err := GetUserById(ctx, conn.RequestorId)
|
||||
if err != nil {
|
||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
user2 = requestor
|
||||
} else {
|
||||
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = postgresql.PgConnectionDelete(ctx, conn)
|
||||
if err != nil {
|
||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
cache.CacheDeleteConnection(user, user2, connectionId)
|
||||
wsServer.WsSendMessageCloseIfTimeout(user2, types.WsEventMessage{
|
||||
Type: WsEventType.ConnectionDeleted,
|
||||
Event: connectionId,
|
||||
})
|
||||
|
||||
response.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
func HandleUserElevateConnection(response http.ResponseWriter, request *http.Request) {
|
||||
if !methodAllowed(&response, request) {
|
||||
return
|
||||
}
|
||||
ctx := request.Context()
|
||||
user, err := GetUserByToken(ctx, request.FormValue("token"))
|
||||
if err != nil {
|
||||
http.Error(response, "invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
connectionId, err := convertions.ConvertStringUuid(request.FormValue("connectionid"))
|
||||
if err != nil {
|
||||
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
conn, ok := cache.CacheGetConnection(user, connectionId)
|
||||
if !ok {
|
||||
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusAccepted)
|
||||
|
||||
if conn.UserWantingToElevate != (uuid.UUID{}) && conn.UserWantingToElevate != user.Id {
|
||||
switch conn.State {
|
||||
case ConnectionState.Stranger:
|
||||
conn.State = ConnectionState.Friend
|
||||
break
|
||||
case ConnectionState.GroupFellow:
|
||||
conn.State = ConnectionState.Stranger
|
||||
break
|
||||
default:
|
||||
http.Error(response, "cannot elevate further", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = postgresql.PgConnectionUpdateState(ctx, conn)
|
||||
if err != nil {
|
||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
response.Write([]byte("elevated"))
|
||||
|
||||
var user2 *types.User
|
||||
if conn.RequestorId == user.Id {
|
||||
user2, err = GetUserById(ctx, conn.RecipientId)
|
||||
} else {
|
||||
user2, err = GetUserById(ctx, conn.RequestorId)
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
wsServer.WsSendMessageCloseIfTimeout(user2, types.WsEventMessage{
|
||||
Type: WsEventType.ConnectionElevated,
|
||||
Event: types.ConnectionElevationData{
|
||||
Id: connectionId,
|
||||
NewState: conn.State,
|
||||
},
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
conn.UserWantingToElevate = user.Id
|
||||
response.Write([]byte("waiting for second user to elevate"))
|
||||
}
|
||||
|
||||
func HandleUserGetConnections(response http.ResponseWriter, request *http.Request) {
|
||||
if !methodAllowed(&response, request) {
|
||||
return
|
||||
}
|
||||
ctx := request.Context()
|
||||
user, err := GetUserByToken(ctx, request.FormValue("token"))
|
||||
if err != nil {
|
||||
http.Error(response, "invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
user.Mu.RLock()
|
||||
connections := slices.Collect(maps.Values(user.Connections))
|
||||
user.Mu.RUnlock()
|
||||
json, err := json2.Marshal(connections)
|
||||
if err != nil {
|
||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusOK)
|
||||
response.Write(json)
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
json2 "encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"go-socket/packages/cache"
|
||||
"go-socket/packages/convertions"
|
||||
"go-socket/packages/passwords"
|
||||
"go-socket/packages/postgresql"
|
||||
"go-socket/packages/tokens"
|
||||
"go-socket/packages/types"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func HandleUserNewToken(response http.ResponseWriter, request *http.Request) {
|
||||
if !methodAllowed(&response, request) {
|
||||
return
|
||||
}
|
||||
|
||||
username := request.FormValue("username")
|
||||
if len(username) < 4 {
|
||||
http.Error(response, "no or short username", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
password := request.FormValue("passwords")
|
||||
|
||||
if len(password) < 8 {
|
||||
http.Error(response, "no or short passwords", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
user *types.User
|
||||
err error
|
||||
ctx = request.Context()
|
||||
)
|
||||
|
||||
user, err = cache.CacheGetUserByName(username)
|
||||
if err != nil {
|
||||
user = &types.User{Name: username}
|
||||
if err = postgresql.PgUserGetStandardInfoByName(ctx, user); err != nil {
|
||||
http.Error(response, "bad login", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if err = postgresql.PgGetWholeUser(ctx, user); err != nil {
|
||||
http.Error(response, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password))
|
||||
if err != nil {
|
||||
http.Error(response, "bad login", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
token, err := tokens.TokenCreate(user.Id)
|
||||
if err != nil {
|
||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
json, err := json2.Marshal(types.LoginReturn{Token: token, UserId: user.Id})
|
||||
if err != nil {
|
||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusCreated)
|
||||
response.Write(json)
|
||||
}
|
||||
|
||||
func HandleUserNew(response http.ResponseWriter, request *http.Request) {
|
||||
if !methodAllowed(&response, request) {
|
||||
return
|
||||
}
|
||||
|
||||
username := request.FormValue("username")
|
||||
if len(username) < 4 {
|
||||
http.Error(response, "no or short username", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
password := request.FormValue("passwords")
|
||||
if len(password) < 8 {
|
||||
http.Error(response, "no or short passwords", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
hashedPassword, err := passwords.PasswordHash(password)
|
||||
if err != nil {
|
||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
newUser := &types.User{
|
||||
Name: username,
|
||||
PasswordHash: hashedPassword,
|
||||
Color: types.Rgba{}.GetRandom(),
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
ctx := request.Context()
|
||||
|
||||
err = postgresql.PgUserSave(ctx, newUser)
|
||||
if err != nil {
|
||||
http.Error(response, "name taken", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
|
||||
func HandleUserDelete(response http.ResponseWriter, request *http.Request) {
|
||||
if !methodAllowed(&response, request) {
|
||||
return
|
||||
}
|
||||
ctx := request.Context()
|
||||
|
||||
userId, err := tokens.TokenValidateGetId(request.FormValue("token"))
|
||||
if err != nil {
|
||||
http.Error(response, "invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
err = postgresql.PgUserDelete(ctx, userId)
|
||||
if err != nil {
|
||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
cache.CacheDeleteUser(userId)
|
||||
response.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
// HandleUserModifyAppearance currently just color
|
||||
func HandleUserModifyAppearance(response http.ResponseWriter, request *http.Request) {
|
||||
if !methodAllowed(&response, request) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx := request.Context()
|
||||
user, err := GetUserByToken(ctx, request.FormValue("token"))
|
||||
if err != nil {
|
||||
http.Error(response, "invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
color, err := convertions.ConvertStringToRgba(request.FormValue("color"))
|
||||
if err != nil {
|
||||
http.Error(response, "invalid color", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
user.Color = color
|
||||
err = postgresql.PgUserSetColor(ctx, user)
|
||||
if err != nil {
|
||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
response.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
// HandleUserModifyAbout currently just pronouns
|
||||
func HandleUserModifyAbout(response http.ResponseWriter, request *http.Request) {
|
||||
if !methodAllowed(&response, request) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx := request.Context()
|
||||
user, err := GetUserByToken(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 = postgresql.PgUserSetPronouns(ctx, user)
|
||||
if err != nil {
|
||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
response.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package minio
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
)
|
||||
|
||||
func MinInit() {
|
||||
ctx := context.Background()
|
||||
|
||||
dbConn, err := minio.New("localhost:9000", &minio.Options{
|
||||
Creds: credentials.NewStaticV4("root", "change_to_env", ""),
|
||||
Secure: false,
|
||||
}) // TODO change in production
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
exists, err := dbConn.BucketExists(ctx, "main")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package passwords
|
||||
|
||||
import "golang.org/x/crypto/bcrypt"
|
||||
|
||||
func PasswordHash(password string) (string, error) {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
func PasswordCheckAgainstHash(password, hash string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
package postgresql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go-socket/packages/cache"
|
||||
"go-socket/packages/convertions"
|
||||
"go-socket/packages/types"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
var dbConn *pgxpool.Pool
|
||||
|
||||
func PgInit(ctx context.Context) {
|
||||
var err error
|
||||
dbConn, err = pgxpool.New(ctx, "postgres://master:secret@localhost:5432") // TODO change to env in production
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = dbConn.Exec(ctx, `CREATE EXTENSION IF NOT EXISTS "pgcrypto";`)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = dbConn.Exec(ctx, `
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
pass_hash TEXT NOT NULL,
|
||||
pronouns TEXT DEFAULT NULL,
|
||||
rgba BIGINT NOT NULL DEFAULT 0 CHECK (rgba BETWEEN 0 AND 4294967295),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
)
|
||||
`)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = dbConn.Exec(ctx, `
|
||||
CREATE TABLE IF NOT EXISTS user_connections (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
requestor_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
recipient_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
state SMALLINT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
)
|
||||
`)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = dbConn.Exec(ctx, `
|
||||
CREATE TABLE IF NOT EXISTS direct_messages (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
sender_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
receiver_id UUID NOT NULL REFERENCES user_connections(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
content TEXT NOT NULL
|
||||
)
|
||||
`)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func PgUserSave(ctx context.Context, user *types.User) error {
|
||||
err := dbConn.QueryRow(ctx, `
|
||||
INSERT INTO users (name, pass_hash, pronouns, rgba, created_at)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id
|
||||
`, user.Name, user.PasswordHash, user.Pronouns, convertions.RgbaToUint32(user.Color), user.CreatedAt).
|
||||
Scan(&user.Id)
|
||||
return err
|
||||
}
|
||||
|
||||
func PgUserDelete(ctx context.Context, id uuid.UUID) error {
|
||||
_, err := dbConn.Exec(ctx, `
|
||||
DELETE FROM users WHERE id = $1
|
||||
`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func PgUserGetStandardInfoByName(ctx context.Context, user *types.User) error {
|
||||
var rgba int64
|
||||
err := dbConn.QueryRow(ctx, `
|
||||
SELECT id, name, pass_hash, COALESCE(pronouns, ''), rgba, created_at FROM users WHERE name = $1
|
||||
`, user.Name).Scan(&user.Id, &user.Name, &user.PasswordHash, &user.Pronouns, &rgba, &user.CreatedAt)
|
||||
if err == nil {
|
||||
user.Color = convertions.Uint32ToRgba(uint32(rgba))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func PgUserGetById(ctx context.Context, user *types.User) error {
|
||||
var rgba int64
|
||||
err := dbConn.QueryRow(ctx, `
|
||||
SELECT name, pass_hash, COALESCE(pronouns, ''), rgba, created_at FROM users WHERE id = $1
|
||||
`, user.Id).Scan(&user.Name, &user.PasswordHash, &user.Pronouns, &rgba, &user.CreatedAt)
|
||||
if err == nil {
|
||||
user.Color = convertions.Uint32ToRgba(uint32(rgba))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func PgUserSetColor(ctx context.Context, user *types.User) error {
|
||||
_, err := dbConn.Exec(ctx, `
|
||||
UPDATE users SET rgba = $1 WHERE id = $2
|
||||
`, convertions.RgbaToUint32(user.Color), user.Id)
|
||||
return err
|
||||
}
|
||||
|
||||
func PgUserSetPronouns(ctx context.Context, user *types.User) error {
|
||||
_, err := dbConn.Exec(ctx, `
|
||||
UPDATE users SET pronouns = $1 WHERE id = $2
|
||||
`, user.Pronouns, user.Id)
|
||||
return err
|
||||
}
|
||||
|
||||
func PgGetWholeUser(ctx context.Context, user *types.User) error {
|
||||
if err := PgUserGetById(ctx, user); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := PgConnectionsGetBelongingToUser(ctx, user); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cache.CacheSaveUser(user)
|
||||
return nil
|
||||
}
|
||||
|
||||
func PgConnectionSave(ctx context.Context, conn *types.Connection) error {
|
||||
return dbConn.QueryRow(ctx, `
|
||||
INSERT INTO user_connections (requestor_id, recipient_id, state, created_at) VALUES ($1, $2, $3, $4)
|
||||
RETURNING id
|
||||
`, conn.RequestorId, conn.RecipientId, conn.State, conn.CreatedAt).Scan(&conn.Id)
|
||||
}
|
||||
|
||||
func PgConnectionDelete(ctx context.Context, conn *types.Connection) error {
|
||||
_, err := dbConn.Exec(ctx, `
|
||||
DELETE FROM user_connections WHERE id = $1
|
||||
`, conn.Id)
|
||||
return err
|
||||
}
|
||||
|
||||
func PgConnectionsGetBelongingToUser(ctx context.Context, user *types.User) error {
|
||||
rows, err := dbConn.Query(ctx, `
|
||||
SELECT id, requestor_id, recipient_id, state, created_at
|
||||
FROM user_connections
|
||||
WHERE requestor_id = $1 OR recipient_id = $1
|
||||
`, user.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
if user.Connections == nil {
|
||||
user.Connections = make(map[uuid.UUID]*types.Connection)
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
conn := &types.Connection{}
|
||||
if err = rows.Scan(&conn.Id, &conn.RequestorId, &conn.RecipientId, &conn.State, &conn.CreatedAt); err != nil {
|
||||
return fmt.Errorf("scanning connection row: %w", err)
|
||||
}
|
||||
user.Connections[conn.Id] = conn
|
||||
}
|
||||
return rows.Err()
|
||||
}
|
||||
|
||||
func PgConnectionUpdateState(ctx context.Context, conn *types.Connection) error {
|
||||
_, err := dbConn.Exec(ctx, `
|
||||
UPDATE user_connections SET state = $1 WHERE id = $2
|
||||
`, conn.State, conn.Id)
|
||||
return err
|
||||
}
|
||||
|
||||
func PgConnectionMessageSave(ctx context.Context, message *types.Message) error {
|
||||
if message.Id != (uuid.UUID{}) {
|
||||
_, err := dbConn.Exec(ctx, `
|
||||
INSERT INTO direct_messages (id, sender_id, receiver_id, created_at, content) VALUES ($1, $2, $3, $4, $5)
|
||||
`, message.Id, message.Sender, message.Receiver, message.CreatedAt, message.Content)
|
||||
return err
|
||||
}
|
||||
return dbConn.QueryRow(ctx, `
|
||||
INSERT INTO direct_messages (sender_id, receiver_id, created_at, content) VALUES ($1, $2, $3, $4)
|
||||
RETURNING id
|
||||
`, message.Sender, message.Receiver, message.CreatedAt, message.Content).Scan(&message.Id)
|
||||
}
|
||||
|
||||
func PgConnectionGetMessagesBefore(ctx context.Context, before time.Time, connection uuid.UUID, cap uint32) ([]*types.Message, error) {
|
||||
rows, err := dbConn.Query(ctx, `
|
||||
SELECT id, sender_id, receiver_id, created_at, content
|
||||
FROM (
|
||||
SELECT id, sender_id, receiver_id, created_at, content
|
||||
FROM direct_messages
|
||||
WHERE receiver_id = $1
|
||||
AND created_at < $2
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $3
|
||||
) sub
|
||||
ORDER BY created_at ASC
|
||||
`, connection, before, cap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
messages := make([]*types.Message, 0, cap)
|
||||
for rows.Next() {
|
||||
msg := &types.Message{}
|
||||
if err = rows.Scan(&msg.Id, &msg.Sender, &msg.Receiver, &msg.CreatedAt, &msg.Content); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
messages = append(messages, msg)
|
||||
}
|
||||
return messages, rows.Err()
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package tokens
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const tokenSecret = "tmp" // TODO delete in production
|
||||
const tokenExpiration = time.Hour
|
||||
|
||||
func TokenCreate(userId uuid.UUID) (string, error) {
|
||||
now := time.Now()
|
||||
signedToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
|
||||
Subject: userId.String(),
|
||||
IssuedAt: jwt.NewNumericDate(now),
|
||||
ExpiresAt: jwt.NewNumericDate(now.Add(tokenExpiration)),
|
||||
}).SignedString([]byte(tokenSecret))
|
||||
return signedToken, err
|
||||
}
|
||||
|
||||
func TokenValidateGetId(tokenString string) (uuid.UUID, error) {
|
||||
token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
|
||||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
|
||||
}
|
||||
return []byte(tokenSecret), nil
|
||||
})
|
||||
if err != nil {
|
||||
return uuid.UUID{}, err
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok || !token.Valid {
|
||||
return uuid.UUID{}, fmt.Errorf("invalid token")
|
||||
}
|
||||
|
||||
exp, ok := claims["exp"].(float64)
|
||||
if !ok || time.Now().Unix() > int64(exp) {
|
||||
return uuid.UUID{}, fmt.Errorf("token expired")
|
||||
}
|
||||
|
||||
sub, ok := claims["sub"].(string)
|
||||
if !ok {
|
||||
return uuid.UUID{}, fmt.Errorf("invalid subject claim")
|
||||
}
|
||||
|
||||
id, err := uuid.Parse(sub)
|
||||
if err != nil {
|
||||
return uuid.UUID{}, fmt.Errorf("invalid subject claim")
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"math/rand/v2"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go-socket/packages/Enums/ConnectionState"
|
||||
"go-socket/packages/Enums/WsEventType"
|
||||
"go-socket/packages/globals"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Rgba [4]uint8
|
||||
|
||||
func (r Rgba) GetRandom() *Rgba {
|
||||
for i := range r {
|
||||
r[i] = uint8(rand.IntN(256))
|
||||
}
|
||||
return &r
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Mu sync.RWMutex
|
||||
Name string
|
||||
Pronouns string
|
||||
PasswordHash string
|
||||
CreatedAt time.Time
|
||||
WsConn *websocket.Conn
|
||||
Id uuid.UUID
|
||||
Connections map[uuid.UUID]*Connection
|
||||
Color *Rgba
|
||||
}
|
||||
|
||||
type Connection struct {
|
||||
Mu sync.RWMutex `json:"-"`
|
||||
Id uuid.UUID `json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
MessagesBuff [globals.MaxDirectMsgCache]*Message `json:"-"`
|
||||
NextBuffIdx uint32 `json:"-"`
|
||||
RequestorId uuid.UUID `json:"requestorId"`
|
||||
RecipientId uuid.UUID `json:"recipientId"`
|
||||
UserWantingToElevate uuid.UUID `json:"userWantingToElevate"` // TODO add to database
|
||||
HaveOverflowed bool `json:"-"`
|
||||
State ConnectionState.ConnectionState `json:"state"`
|
||||
}
|
||||
|
||||
func (conn *Connection) AddMessageToBuff(message *Message) {
|
||||
conn.Mu.Lock()
|
||||
defer conn.Mu.Unlock()
|
||||
|
||||
conn.MessagesBuff[conn.NextBuffIdx%globals.MaxDirectMsgCache] = message
|
||||
conn.NextBuffIdx++
|
||||
if conn.NextBuffIdx >= globals.MaxDirectMsgCache {
|
||||
conn.HaveOverflowed = true
|
||||
}
|
||||
}
|
||||
|
||||
// GetSortedMessagesBuff returns arr, length
|
||||
func (conn *Connection) GetSortedMessagesBuff() (*[globals.MaxDirectMsgCache]*Message, uint32) {
|
||||
conn.Mu.RLock()
|
||||
defer conn.Mu.RUnlock()
|
||||
|
||||
if !conn.HaveOverflowed {
|
||||
return &conn.MessagesBuff, conn.NextBuffIdx
|
||||
}
|
||||
|
||||
sorted := new([globals.MaxDirectMsgCache]*Message)
|
||||
for i := uint32(0); i < globals.MaxDirectMsgCache; i++ {
|
||||
sorted[i] = conn.MessagesBuff[(conn.NextBuffIdx+i)%globals.MaxDirectMsgCache]
|
||||
}
|
||||
return sorted, globals.MaxDirectMsgCache
|
||||
}
|
||||
|
||||
type ConnectionElevationData struct {
|
||||
Id uuid.UUID `json:"id"`
|
||||
NewState ConnectionState.ConnectionState `json:"newState"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Id uuid.UUID `json:"id"`
|
||||
AttachedMedia string `json:"attachedMedia"`
|
||||
Content string `json:"content"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Sender uuid.UUID `json:"sender"`
|
||||
Receiver uuid.UUID `json:"receiver"`
|
||||
}
|
||||
|
||||
type LoginReturn struct {
|
||||
Token string `json:"token"`
|
||||
UserId uuid.UUID `json:"userId"`
|
||||
}
|
||||
|
||||
type WsEventMessage struct {
|
||||
Type WsEventType.WsEventType `json:"types"`
|
||||
Event any `json:"event"`
|
||||
}
|
||||
|
||||
type WsAuthMessage struct {
|
||||
Success bool `json:"success"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package wsServer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"go-socket/packages/Enums/WsEventType"
|
||||
"go-socket/packages/cache"
|
||||
"go-socket/packages/tokens"
|
||||
"go-socket/packages/types"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
"github.com/coder/websocket/wsjson"
|
||||
)
|
||||
|
||||
func ServeWsConnection(responseWriter http.ResponseWriter, request *http.Request) {
|
||||
connection, err := websocket.Accept(responseWriter, request, &websocket.AcceptOptions{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("websocket accept error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
var user = types.User{WsConn: connection}
|
||||
var isAuthenticated bool
|
||||
var ignoreCache bool
|
||||
|
||||
defer func() { closeConnection(&user, ignoreCache) }()
|
||||
for {
|
||||
var userMessage map[string]any
|
||||
err = wsjson.Read(ctx, connection, &userMessage)
|
||||
if err != nil {
|
||||
log.Printf("read error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(userMessage) > 0 {
|
||||
if isAuthenticated {
|
||||
if !handleAuthenticatedMessage(&user, &userMessage) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if !handleUnauthenticatedMessage(ctx, &user, &userMessage) {
|
||||
ignoreCache = true
|
||||
return
|
||||
}
|
||||
isAuthenticated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WsSendMessageCloseIfTimeout(user *types.User, message any) {
|
||||
if user.WsConn == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
defer cancel()
|
||||
|
||||
err := wsjson.Write(ctx, user.WsConn, message)
|
||||
if err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
closeConnection(user, false)
|
||||
}
|
||||
log.Printf("write error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func sendToAllMessageCloseIfTimeout(message *map[string]any) {
|
||||
cache.Mu.RLock()
|
||||
users := make([]*types.User, 0, len(cache.Users))
|
||||
for _, user := range cache.Users {
|
||||
users = append(users, user)
|
||||
}
|
||||
cache.Mu.RUnlock()
|
||||
|
||||
for _, user := range users {
|
||||
WsSendMessageCloseIfTimeout(user, message)
|
||||
}
|
||||
}
|
||||
|
||||
func handleAuthenticatedMessage(user *types.User, userMessage *map[string]any) bool {
|
||||
WsSendMessageCloseIfTimeout(user, userMessage)
|
||||
return true
|
||||
}
|
||||
|
||||
func handleUnauthenticatedMessage(ctx context.Context, user *types.User, userMessage *map[string]any) bool {
|
||||
token, ok := (*userMessage)["token"].(string)
|
||||
response := types.WsEventMessage{Type: WsEventType.Authentication}
|
||||
|
||||
if !ok {
|
||||
response.Event = types.WsAuthMessage{
|
||||
Success: false,
|
||||
Error: "no token in message",
|
||||
}
|
||||
WsSendMessageCloseIfTimeout(user, response)
|
||||
return false
|
||||
}
|
||||
|
||||
userId, err := tokens.TokenValidateGetId(token)
|
||||
if err != nil {
|
||||
response.Event = types.WsAuthMessage{
|
||||
Success: false,
|
||||
Error: "invalid token",
|
||||
}
|
||||
WsSendMessageCloseIfTimeout(user, response)
|
||||
return false
|
||||
}
|
||||
|
||||
userFromCache, err := cache.CacheGetUserById(userId)
|
||||
if err != nil {
|
||||
response.Event = types.WsAuthMessage{
|
||||
Success: false,
|
||||
Error: "user not found",
|
||||
}
|
||||
WsSendMessageCloseIfTimeout(user, response)
|
||||
return false
|
||||
}
|
||||
|
||||
userFromCache.WsConn = user.WsConn
|
||||
*user = *userFromCache
|
||||
|
||||
response.Event = types.WsAuthMessage{
|
||||
Success: true,
|
||||
Error: "",
|
||||
}
|
||||
WsSendMessageCloseIfTimeout(user, response)
|
||||
return true
|
||||
}
|
||||
|
||||
func closeConnection(user *types.User, ignoreCache bool) {
|
||||
if !ignoreCache {
|
||||
cache.CacheDeleteUser(user.Id)
|
||||
}
|
||||
if user.WsConn != nil {
|
||||
user.WsConn.CloseNow()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user