From 046b17959f2296a32a62d3b4f6174d47cdfbd4e9 Mon Sep 17 00:00:00 2001 From: Kyren223 Date: Tue, 8 Jul 2025 19:10:26 +0300 Subject: Finished implementing authentication on the client-side --- internal/client/gateway/gateway.go | 52 ----------------------------- internal/client/ui/auth/auth.go | 3 ++ internal/client/ui/core/core.go | 68 ++++++++++++++++++++++++++++---------- 3 files changed, 53 insertions(+), 70 deletions(-) (limited to 'internal') diff --git a/internal/client/gateway/gateway.go b/internal/client/gateway/gateway.go index f98a3fb..20ca777 100644 --- a/internal/client/gateway/gateway.go +++ b/internal/client/gateway/gateway.go @@ -2,10 +2,8 @@ package gateway import ( "context" - "crypto/ed25519" "crypto/tls" "crypto/x509" - "encoding/binary" "errors" "log" "net" @@ -103,56 +101,6 @@ func connect(ctx context.Context) error { return nil } -func handleAuth(ctx context.Context, conn net.Conn, privKey ed25519.PrivateKey) (snowflake.ID, error) { - const nonceSize = 32 - const packetSize = 1 + nonceSize // For version byte - challengeRequest := make([]byte, packetSize) - - deadline, _ := ctx.Deadline() - err := conn.SetDeadline(deadline) - assert.NoError(err, "setting deadline should not error") - defer func() { - err := conn.SetDeadline(time.Time{}) - assert.NoError(err, "unsetting deadline should not error") - }() - - bytesRead := 0 - for bytesRead < packetSize { - n, err := conn.Read(challengeRequest[bytesRead:]) - if err != nil { - return 0, err - } - bytesRead += n - } - - assert.Assert(challengeRequest[0] == packet.VERSION, "client should always have the same version as the server") - - challengeResponse := make([]byte, 1+ed25519.PublicKeySize+ed25519.SignatureSize) - challengeResponse[0] = packet.VERSION - copy(challengeResponse[1:1+ed25519.PublicKeySize], privKey.Public().(ed25519.PublicKey)) - signedNonce := ed25519.Sign(privKey, challengeRequest[1:]) - n := copy(challengeResponse[1+ed25519.PublicKeySize:], signedNonce) - assert.Assert(n == ed25519.SignatureSize, "copy should've copied the entire signature exactly") - - _, err = conn.Write(challengeResponse) - if err != nil { - return 0, err - } - - var idBytes [8]byte - bytesRead = 0 - for bytesRead < 8 { - n, err := conn.Read(idBytes[:]) - if err != nil { - return 0, err - } - bytesRead += n - } - id := snowflake.ID(binary.BigEndian.Uint64(idBytes[:])) // #nosec G115 - - return id, nil -} - func readForever(conn net.Conn) { buffer := make([]byte, 512) for { diff --git a/internal/client/ui/auth/auth.go b/internal/client/ui/auth/auth.go index 54ce96b..8e6dffb 100644 --- a/internal/client/ui/auth/auth.go +++ b/internal/client/ui/auth/auth.go @@ -142,6 +142,9 @@ func New() Model { } m.SetSignup(false) + + m.Update(nil) // HACK: force an update + return m } diff --git a/internal/client/ui/core/core.go b/internal/client/ui/core/core.go index 61810cd..5a0f8db 100644 --- a/internal/client/ui/core/core.go +++ b/internal/client/ui/core/core.go @@ -247,23 +247,6 @@ func (m *Model) updateDisconnected(msg tea.Msg) tea.Cmd { case gateway.ConnectionEstablished: m.state = Connected m.timeout = InitialTimeout - - // TODO: - // // state.UserID = (*snowflake.ID)(&msg) - // var setName tea.Cmd - // if m.name != "" { - // setName = gateway.Send(&packet.SetUserData{ - // Data: nil, - // User: &data.User{ - // Name: m.name, - // Description: "", - // IsPublicDM: true, - // }, - // }) - // m.name = "" - // } - // - // return tea.Batch(m.timer.Stop(), setName) return m.timer.Stop() case gateway.ConnectionFailed: @@ -326,12 +309,61 @@ func (m *Model) updateConnected(message tea.Msg) tea.Cmd { // // return tea.Batch(m.timer.Stop(), setName) + case *packet.Error: + if m.state == ConnectedAcceptedTos { + if msg.Error == "success" { + return gateway.Send(&packet.GetNonce{}) + } else { + log.Println("received error:", msg.Error) + gateway.Disconnect() + return ui.Transition(ui.NewAuth()) + } + } + + case *packet.NonceInfo: + if m.state == ConnectedAcceptedTos { + pubKey := ed25519.PublicKey(make([]byte, ed25519.PublicKeySize)) + copy(pubKey, m.privKey[32:]) + + signature := ed25519.Sign(m.privKey, msg.Nonce) + + return gateway.Send(&packet.Authenticate{ + PubKey: pubKey, + Signature: signature, + }) + } + + case *packet.UsersInfo: + if m.state == ConnectedAcceptedTos { + assert.Assert(len(msg.Users) == 1, "as per the spec, server must send a UsersInfo with exactly a single user", "users", msg.Users) + + m.state = Authenticated + state.UserID = &msg.Users[0].ID + + var setName tea.Cmd + if m.name != "" { + setName = gateway.Send(&packet.SetUserData{ + Data: nil, + User: &data.User{ + Name: m.name, + Description: "", + IsPublicDM: true, + }, + }) + m.name = "" + } + + return setName + } + case tea.KeyMsg: switch msg.String() { case "enter": if m.state == ConnectedReceivedTos { - // TODO: m.state = ConnectedAcceptedTos + return gateway.Send(&packet.AcceptTos{ + IAgreeToTheTermsOfServiceAndPrivacyPolicy: true, + }) } } -- cgit v1.3.1