diff options
| -rw-r--r-- | internal/client/ui/core/core.go | 2 | ||||
| -rw-r--r-- | internal/packet/protocol.md | 1 | ||||
| -rw-r--r-- | internal/packet/types.go | 3 | ||||
| -rw-r--r-- | internal/server/ctxkeys/ctxkeys.go | 16 | ||||
| -rw-r--r-- | internal/server/server.go | 97 | ||||
| -rw-r--r-- | internal/server/session/session.go | 35 |
6 files changed, 120 insertions, 34 deletions
diff --git a/internal/client/ui/core/core.go b/internal/client/ui/core/core.go index 37ce613..8ff7810 100644 --- a/internal/client/ui/core/core.go +++ b/internal/client/ui/core/core.go @@ -280,7 +280,7 @@ func (m *Model) updateConnected(message tea.Msg) tea.Cmd { case *packet.Error: err := "new connection from another location, closing this one" - if msg.PktType == packet.PacketError && err == msg.Error { + if err == msg.Error { return ui.Transition(ui.NewAuth()) } diff --git a/internal/packet/protocol.md b/internal/packet/protocol.md index 6a50475..f013196 100644 --- a/internal/packet/protocol.md +++ b/internal/packet/protocol.md @@ -34,6 +34,7 @@ The first time a connection is established, the following packets are exchanged. - If the client sends any other type of packet, or the boolean is false, the server may close the connection After the handshake, the client may send any unauthenticated packets. +And the server may stream any additional packets. ### Authentication diff --git a/internal/packet/types.go b/internal/packet/types.go index 156fa5f..213e5ce 100644 --- a/internal/packet/types.go +++ b/internal/packet/types.go @@ -8,8 +8,7 @@ import ( ) type Error struct { - Error string - PktType PacketType + Error string } func (m *Error) Type() PacketType { diff --git a/internal/server/ctxkeys/ctxkeys.go b/internal/server/ctxkeys/ctxkeys.go index 1fb0fa6..827e6ea 100644 --- a/internal/server/ctxkeys/ctxkeys.go +++ b/internal/server/ctxkeys/ctxkeys.go @@ -14,15 +14,23 @@ const ( IpAddr Evicted EvictedBy + Request + RequestType KeyMax ) var keyNames = map[key]string{ - UserID: "user_id", - IpAddr: "ip_addr", - Evicted: "evicted", - EvictedBy: "evicted_by", + UserID: "user_id", + IpAddr: "ip_addr", + Evicted: "evicted", + EvictedBy: "evicted_by", + Request: "request", + RequestType: "request_type", +} + +func (k key) String() string { + return keyNames[k] } func init() { diff --git a/internal/server/server.go b/internal/server/server.go index 32c1205..cdb9313 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -83,11 +83,15 @@ func (s *server) AddSession(session *session.Session, userId snowflake.ID, pubKe if sess, ok := s.sessions[session.ID()]; ok { EvictSession(sess) // last connection wins slog.Info("closed due to new connection from another location", - ctxkeys.IpAddr, sess.Addr(), ctxkeys.UserID, sess.ID(), - ctxkeys.EvictedBy, session.Addr()) + ctxkeys.IpAddr.String(), sess.Addr(), + ctxkeys.UserID.String(), sess.ID(), + ctxkeys.EvictedBy.String(), session.Addr(), + ) slog.Info("this session evicted another session", - ctxkeys.IpAddr, session.Addr(), ctxkeys.UserID, session.ID(), - ctxkeys.Evicted, sess.Addr()) + ctxkeys.IpAddr.String(), session.Addr(), + ctxkeys.UserID.String(), session.ID(), + ctxkeys.Evicted.String(), sess.Addr(), + ) } s.sessions[session.ID()] = session @@ -98,8 +102,7 @@ func EvictSession(sess *session.Session) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() payload := &packet.Error{ - Error: "new connection from another location, closing this one", - PktType: packet.PacketError, + Error: "new connection from another location, closing this one", } pkt := packet.NewPacket(packet.NewMsgPackEncoder(payload)) sess.Write(ctx, pkt) @@ -206,6 +209,7 @@ func (server *server) handleConnection(conn net.Conn) { // Writer go func() { defer close(done) + defer conn.Close() // To unblock reader writeQueue := sess.Read() for packet := range writeQueue { @@ -242,6 +246,10 @@ func (server *server) handleConnection(conn net.Conn) { } }() + // NOTE: IMPROTANT LEGAL STUFF + // Sending this first thing, before client sends us any data + sendTosInfo(ctx, sess) + // Reader buffer := make([]byte, 512) for { @@ -328,11 +336,60 @@ func processPacket(ctx context.Context, sess *session.Session, pkt packet.Packet } func processRequest(ctx context.Context, sess *session.Session, request packet.Payload) packet.Payload { - log.Println(sess.Addr(), "processing", request.Type(), "request:", request) + slog.InfoContext(ctx, "processing request", + ctxkeys.RequestType.String(), + request.Type(), ctxkeys.Request.String(), request, + ) + + if !sess.IsTosAccepted() { + if acceptTos, ok := request.(*packet.AcceptTos); ok && acceptTos.IAgreeToTheTermsOfServiceAndPrivacyPolicy { + sess.ReceivedTosAcceptance() + slog.InfoContext(ctx, "terms of service accepted, continuing...") + return &api.ErrSuccess + } + + slog.InfoContext(ctx, "refused terms of service, refusing service...") + sess.Close() // Refuse to receive any more requests + return &packet.Error{Error: "Terms of Service not accepted, refusing service"} + } + + assert.Assert(sess.IsTosAccepted(), "justified paranoia") // Just in case // TODO: add a way to measure the time each request/response took and log it // Potentially even separate time for code vs DB operations + var response packet.Payload + + if sess.IsAuthenticated() { + // Authentication only requests, others will be handled without auth even if authenticated + authCtx := ctxkeys.WithValue(ctx, ctxkeys.UserID, sess.ID()) + response = processAuthenticatedRequests(authCtx, sess, request) + } + + if response != nil { + return response + } + + switch request := request.(type) { + // TODO: + // case *packet.GetNonce: + // response = timeout(5*time.Millisecond, api.SetUserData, ctx, sess, request) + + // TODO: + // case *packet.Authenticate: + // response = timeout(5*time.Millisecond, api.SetUserData, ctx, sess, request) + + default: + _ = request // FIXME: remove + response = &packet.Error{Error: "use of disallowed packet type for request"} + } + + return response +} + +func processAuthenticatedRequests(ctx context.Context, sess *session.Session, request packet.Payload) packet.Payload { + var response packet.Payload + switch request := request.(type) { case *packet.SetUserData: @@ -383,13 +440,7 @@ func processRequest(ctx context.Context, sess *session.Session, request packet.P response = timeout(10*time.Millisecond, api.GetUsers, ctx, sess, request) default: - response = &packet.Error{Error: "use of disallowed packet type for request"} - } - - // TODO: Isn't this weird? shouldn't it always be packet.ErrorPacket type? - // rather than copying the pkt type of the request? - if response, ok := response.(*packet.Error); ok { - response.PktType = request.Type() + response = nil } return response @@ -422,7 +473,23 @@ func timeout[T packet.Payload]( } } -func (server *server) sendInitialPackets(ctx context.Context, sess *session.Session) bool { +func sendTosInfo(ctx context.Context, sess *session.Session) bool { + // FIXME: ===== REPLACE WITH ACTUAL TOS ===== + // TODO: Get TOS and privacy policy + tos := "WIP Terms of Service" + privacy := "WIP Privacy Policy" + date := "2025-07-03" + + payload := &packet.TosInfo{ + Tos: tos, + PrivacyPolicy: privacy, + Date: date, + } + pkt := packet.NewPacket(packet.NewMsgPackEncoder(payload)) + return sess.Write(ctx, pkt) +} + +func (server *server) sendInitialAuthPackets(ctx context.Context, sess *session.Session) bool { payload := api.GetUserData(ctx, sess, &packet.GetUserData{}) if payload == &api.ErrInternalError { return false diff --git a/internal/server/session/session.go b/internal/server/session/session.go index ffe4e13..013ff71 100644 --- a/internal/server/session/session.go +++ b/internal/server/session/session.go @@ -36,8 +36,9 @@ type Session struct { issuedTime time.Time challenge []byte - pubKey ed25519.PublicKey - id snowflake.ID + isTosAccepted bool + pubKey ed25519.PublicKey + id snowflake.ID mu sync.Mutex } @@ -50,16 +51,18 @@ func NewSession( assert.NotNil(addr, "tcp address should be valid") assert.NotNil(manager, "session manager should be valid") session := &Session{ - manager: manager, - addr: addr, - cancel: cancel, - writeQueue: make(chan packet.Packet, WriteQueueSize), - writerWg: writerWg, - issuedTime: time.Time{}, - challenge: make([]byte, 32), - pubKey: ed25519.PublicKey{}, - id: snowflake.InvalidID, - mu: sync.Mutex{}, + manager: manager, + addr: addr, + cancel: cancel, + writeQueue: make(chan packet.Packet, WriteQueueSize), + writerWg: writerWg, + writeMu: sync.RWMutex{}, + issuedTime: time.Time{}, + challenge: make([]byte, 32), + pubKey: ed25519.PublicKey{}, + id: snowflake.InvalidID, + mu: sync.Mutex{}, + isTosAccepted: false, } return session } @@ -69,6 +72,14 @@ func (s *Session) Addr() *net.TCPAddr { return s.addr } +func (s *Session) IsTosAccepted() bool { + return s.isTosAccepted +} + +func (s *Session) ReceivedTosAcceptance() { + s.isTosAccepted = true +} + func (s *Session) IsAuthenticated() bool { return s.id != snowflake.InvalidID } |
