summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKyren223 <Kyren223@proton.me>2025-07-06 00:51:33 +0300
committerKyren223 <Kyren223@proton.me>2025-07-06 00:51:33 +0300
commite7344492595b26fd7ae21b945d1bdfe2ef115f10 (patch)
treed15598991805ef530be5332cf43f6ea629f7adaf
parent8f8736d09efce46147ff37d4840fb424aa6cb7e4 (diff)
Added terms of service (incomplete!!!!) to the server, and added
authentication separation between API calls, actually authentication (nonce, and verification) is not implemented yet
-rw-r--r--internal/client/ui/core/core.go2
-rw-r--r--internal/packet/protocol.md1
-rw-r--r--internal/packet/types.go3
-rw-r--r--internal/server/ctxkeys/ctxkeys.go16
-rw-r--r--internal/server/server.go97
-rw-r--r--internal/server/session/session.go35
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
}