diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/client/ui/core/chat/chat.go | 48 | ||||
| -rw-r--r-- | internal/client/ui/core/core.go | 3 | ||||
| -rw-r--r-- | internal/client/ui/core/memberlist/memberlist.go | 47 | ||||
| -rw-r--r-- | internal/client/ui/core/state/state.go | 13 | ||||
| -rw-r--r-- | internal/client/ui/ui.go | 10 | ||||
| -rw-r--r-- | internal/data/models.go | 2 | ||||
| -rw-r--r-- | internal/data/trusted_and_blocked_users.sql.go | 95 | ||||
| -rw-r--r-- | internal/packet/packet.go | 8 | ||||
| -rw-r--r-- | internal/packet/types.go | 21 | ||||
| -rw-r--r-- | internal/server/api/api.go | 97 | ||||
| -rw-r--r-- | internal/server/server.go | 21 |
11 files changed, 343 insertions, 22 deletions
diff --git a/internal/client/ui/core/chat/chat.go b/internal/client/ui/core/chat/chat.go index d1c9671..3b30c1b 100644 --- a/internal/client/ui/core/chat/chat.go +++ b/internal/client/ui/core/chat/chat.go @@ -1,6 +1,7 @@ package chat import ( + "bytes" "log" "strconv" "strings" @@ -330,6 +331,23 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.style = editStyle } + case "T": + if m.selectedMessage == nil { + return m, nil + } + senderId := m.selectedMessage.SenderID + + if senderId == *state.UserID { + return m, nil + } + + _, isTrusting := state.State.Trusteds[senderId] + + return m, gateway.Send(&packet.TrustUser{ + User: senderId, + Trust: !isTrusting, + }) + // Admin case "K": if m.selectedMessage == nil { @@ -492,8 +510,6 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { Network: *state.NetworkId(m.networkIndex), User: member.UserID, }) - case "T": - // TODO: transfer ownership } } @@ -936,12 +952,30 @@ func (m *Model) renderHeader(message data.Message, selected bool) []byte { members := state.State.Members[*networkId] member := members[message.SenderID] user := state.State.Users[message.SenderID] + trustedPublicKey, isTrusted := state.State.Trusteds[user.ID] + keysMatch := bytes.Equal(trustedPublicKey, user.PublicKey) - senderStyle := ui.NormalMemberStyle - if ownerId == member.UserID { - senderStyle = ui.OwnerMemberStyle - } else if member.IsAdmin { - senderStyle = ui.AdminMemberStyle + if isTrusted && !keysMatch { + buf = append(buf, ui.UntrustedSymbol...) + } + + senderStyle := lipgloss.NewStyle() + if isTrusted && keysMatch { + if ownerId == member.UserID { + senderStyle = ui.TrustedOwnerMemberStyle + } else if member.IsAdmin { + senderStyle = ui.TrustedAdminMemberStyle + } else { + senderStyle = ui.TrustedNormalMemberStyle + } + } else { + if ownerId == member.UserID { + senderStyle = ui.OwnerMemberStyle + } else if member.IsAdmin { + senderStyle = ui.AdminMemberStyle + } else { + senderStyle = ui.NormalMemberStyle + } } sender := senderStyle.Render(user.Name) diff --git a/internal/client/ui/core/core.go b/internal/client/ui/core/core.go index 3d4836c..c099473 100644 --- a/internal/client/ui/core/core.go +++ b/internal/client/ui/core/core.go @@ -288,6 +288,9 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { case *packet.MessagesInfo: state.UpdateMessages(msg) + case *packet.TrustInfo: + state.UpdateTrusteds(msg) + case tea.KeyMsg: switch msg.String() { case "n": diff --git a/internal/client/ui/core/memberlist/memberlist.go b/internal/client/ui/core/memberlist/memberlist.go index 5f380dc..1485e1e 100644 --- a/internal/client/ui/core/memberlist/memberlist.go +++ b/internal/client/ui/core/memberlist/memberlist.go @@ -1,6 +1,7 @@ package memberlist import ( + "bytes" "slices" "strings" @@ -91,14 +92,33 @@ func (m Model) View() string { memberStyle = memberStyle.Background(colors.BackgroundHighlight) } - userStyle := ui.NormalMemberStyle - if ownerId == member.UserID { - userStyle = ui.OwnerMemberStyle - } else if member.IsAdmin { - userStyle = ui.AdminMemberStyle + user := state.State.Users[member.UserID] + trustedPublicKey, isTrusted := state.State.Trusteds[user.ID] + keysMatch := bytes.Equal(trustedPublicKey, user.PublicKey) + + userStyle := lipgloss.NewStyle() + if isTrusted && keysMatch { + if ownerId == member.UserID { + userStyle = ui.TrustedOwnerMemberStyle + } else if member.IsAdmin { + userStyle = ui.TrustedAdminMemberStyle + } else { + userStyle = ui.TrustedNormalMemberStyle + } + } else { + if ownerId == member.UserID { + userStyle = ui.OwnerMemberStyle + } else if member.IsAdmin { + userStyle = ui.AdminMemberStyle + } else { + userStyle = ui.NormalMemberStyle + } } memberName := m.Users()[member.UserID].Name memberName = userStyle.Render(memberName) + if isTrusted && !keysMatch { + memberName = ui.UntrustedSymbol + memberName + } if lipgloss.Width(memberName) <= maxMemberWidth { memberName = lipgloss.NewStyle(). @@ -156,6 +176,21 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { case "p": // TODO: profile + // Normal + case "T": + member := m.Members()[m.index] + + if member.UserID == *state.UserID { + return m, nil + } + + _, isTrusting := state.State.Trusteds[member.UserID] + + return m, gateway.Send(&packet.TrustUser{ + User: member.UserID, + Trust: !isTrusting, + }) + // Admin case "x", "K": networkId := state.NetworkId(m.networkIndex) @@ -298,8 +333,6 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { Network: *state.NetworkId(m.networkIndex), User: member.UserID, }) - case "T": - // TODO: transfer ownership } } diff --git a/internal/client/ui/core/state/state.go b/internal/client/ui/core/state/state.go index 182a957..03b7b9d 100644 --- a/internal/client/ui/core/state/state.go +++ b/internal/client/ui/core/state/state.go @@ -1,6 +1,7 @@ package state import ( + "crypto/ed25519" "encoding/json" "slices" @@ -28,6 +29,7 @@ type state struct { Frequencies map[snowflake.ID][]data.Frequency // key is network id Members map[snowflake.ID]map[snowflake.ID]data.Member // key is network id then user id Users map[snowflake.ID]data.User // key is user id + Trusteds map[snowflake.ID]ed25519.PublicKey // key is user id } var State state = state{ @@ -38,6 +40,7 @@ var State state = state{ Frequencies: map[snowflake.ID][]data.Frequency{}, Members: map[snowflake.ID]map[snowflake.ID]data.Member{}, Users: map[snowflake.ID]data.User{}, + Trusteds: map[snowflake.ID]ed25519.PublicKey{}, } type UserData struct { @@ -195,3 +198,13 @@ func FromJsonUserData(s string) { } Data = data } + +func UpdateTrusteds(info *packet.TrustInfo) { + for _, removed := range info.RemovedTrusteds { + delete(State.Trusteds, removed) + } + + for i, trusted := range info.Trusteds { + State.Trusteds[trusted] = info.TrustedPublicKeys[i] + } +} diff --git a/internal/client/ui/ui.go b/internal/client/ui/ui.go index 26a39b1..61bf7bf 100644 --- a/internal/client/ui/ui.go +++ b/internal/client/ui/ui.go @@ -28,9 +28,13 @@ var ( Height int Program *tea.Program - NormalMemberStyle = lipgloss.NewStyle().Foreground(colors.Purple).SetString("") - AdminMemberStyle = lipgloss.NewStyle().Foreground(colors.Red).Bold(true).SetString("") - OwnerMemberStyle = AdminMemberStyle.Foreground(colors.Gold).SetString("") + NormalMemberStyle = lipgloss.NewStyle().Foreground(colors.Purple).SetString("") + AdminMemberStyle = lipgloss.NewStyle().Foreground(colors.Red).Bold(true).SetString("") + OwnerMemberStyle = AdminMemberStyle.Foreground(colors.Gold).SetString("") + TrustedNormalMemberStyle = NormalMemberStyle.SetString("") + TrustedAdminMemberStyle = AdminMemberStyle.SetString("") + TrustedOwnerMemberStyle = OwnerMemberStyle.SetString("") + UntrustedSymbol = lipgloss.NewStyle().Foreground(colors.Red).Render("") ) var NewAuth func() tea.Model diff --git a/internal/data/models.go b/internal/data/models.go index 8815e0b..9bc2961 100644 --- a/internal/data/models.go +++ b/internal/data/models.go @@ -56,7 +56,7 @@ type Network struct { type TrustedUser struct { TrustingUserID snowflake.ID TrustedUserID snowflake.ID - TrustedPublicKey []byte + TrustedPublicKey ed25519.PublicKey } type User struct { diff --git a/internal/data/trusted_and_blocked_users.sql.go b/internal/data/trusted_and_blocked_users.sql.go new file mode 100644 index 0000000..436b8d9 --- /dev/null +++ b/internal/data/trusted_and_blocked_users.sql.go @@ -0,0 +1,95 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: trusted_and_blocked_users.sql + +package data + +import ( + "context" + + "crypto/ed25519" + "github.com/kyren223/eko/pkg/snowflake" +) + +const getTrustedPublicKey = `-- name: GetTrustedPublicKey :one +SELECT trusted_public_key FROM trusted_users +WHERE trusting_user_id = ? AND trusted_user_id = ? +` + +type GetTrustedPublicKeyParams struct { + TrustingUserID snowflake.ID + TrustedUserID snowflake.ID +} + +func (q *Queries) GetTrustedPublicKey(ctx context.Context, arg GetTrustedPublicKeyParams) (ed25519.PublicKey, error) { + row := q.db.QueryRowContext(ctx, getTrustedPublicKey, arg.TrustingUserID, arg.TrustedUserID) + var trusted_public_key ed25519.PublicKey + err := row.Scan(&trusted_public_key) + return trusted_public_key, err +} + +const getUserTrusteds = `-- name: GetUserTrusteds :many +SELECT trusted_user_id, trusted_public_key FROM trusted_users +WHERE trusting_user_id = ? +` + +type GetUserTrustedsRow struct { + TrustedUserID snowflake.ID + TrustedPublicKey ed25519.PublicKey +} + +func (q *Queries) GetUserTrusteds(ctx context.Context, trustingUserID snowflake.ID) ([]GetUserTrustedsRow, error) { + rows, err := q.db.QueryContext(ctx, getUserTrusteds, trustingUserID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetUserTrustedsRow + for rows.Next() { + var i GetUserTrustedsRow + if err := rows.Scan(&i.TrustedUserID, &i.TrustedPublicKey); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const trustUser = `-- name: TrustUser :exec +INSERT OR IGNORE INTO trusted_users ( + trusting_user_id, trusted_user_id, trusted_public_key +) VALUES (?, ?, ?) +` + +type TrustUserParams struct { + TrustingUserID snowflake.ID + TrustedUserID snowflake.ID + TrustedPublicKey ed25519.PublicKey +} + +func (q *Queries) TrustUser(ctx context.Context, arg TrustUserParams) error { + _, err := q.db.ExecContext(ctx, trustUser, arg.TrustingUserID, arg.TrustedUserID, arg.TrustedPublicKey) + return err +} + +const untrustUser = `-- name: UntrustUser :exec +DELETE FROM trusted_users +WHERE trusting_user_id = ? AND trusted_user_id = ? +` + +type UntrustUserParams struct { + TrustingUserID snowflake.ID + TrustedUserID snowflake.ID +} + +func (q *Queries) UntrustUser(ctx context.Context, arg UntrustUserParams) error { + _, err := q.db.ExecContext(ctx, untrustUser, arg.TrustingUserID, arg.TrustedUserID) + return err +} diff --git a/internal/packet/packet.go b/internal/packet/packet.go index 57260da..e629f32 100644 --- a/internal/packet/packet.go +++ b/internal/packet/packet.go @@ -76,6 +76,9 @@ const ( PacketSetMember PacketMembersInfo + PacketTrustUser + PacketTrustInfo + PacketMax ) @@ -234,6 +237,11 @@ func (p Packet) DecodedPayload() (Payload, error) { case PacketMembersInfo: payload = &MembersInfo{} + case PacketTrustUser: + payload = &TrustUser{} + case PacketTrustInfo: + payload = &TrustInfo{} + default: assert.Never("unexpected packet.PacketType", "type", p.Type()) } diff --git a/internal/packet/types.go b/internal/packet/types.go index f142a48..5dba38b 100644 --- a/internal/packet/types.go +++ b/internal/packet/types.go @@ -1,6 +1,8 @@ package packet import ( + "crypto/ed25519" + "github.com/kyren223/eko/internal/data" "github.com/kyren223/eko/pkg/snowflake" ) @@ -203,3 +205,22 @@ type GetUserData struct{} func (m *GetUserData) Type() PacketType { return PacketGetUserData } + +type TrustUser struct { + User snowflake.ID + Trust bool +} + +func (m *TrustUser) Type() PacketType { + return PacketTrustUser +} + +type TrustInfo struct { + Trusteds []snowflake.ID + TrustedPublicKeys []ed25519.PublicKey + RemovedTrusteds []snowflake.ID +} + +func (m *TrustInfo) Type() PacketType { + return PacketTrustInfo +} diff --git a/internal/server/api/api.go b/internal/server/api/api.go index bddc43d..2fa013a 100644 --- a/internal/server/api/api.go +++ b/internal/server/api/api.go @@ -1016,3 +1016,100 @@ func EditMessage(ctx context.Context, sess *session.Session, request *packet.Edi assert.Never("unreachable") return nil } + +func TrustUser(ctx context.Context, sess *session.Session, request *packet.TrustUser) packet.Payload { + if sess.ID() == request.User { + return &packet.Error{Error: "you cannot trust yourself"} + } + + queries := data.New(db) + + user, err := queries.GetUserById(ctx, request.User) + if err == sql.ErrNoRows { + return &packet.Error{Error: "requested user doesn't exist"} + } + if err != nil { + log.Println("database error 0:", err) + return &ErrInternalError + } + + if request.Trust { + publicKey, err := queries.GetTrustedPublicKey(ctx, data.GetTrustedPublicKeyParams{ + TrustingUserID: sess.ID(), + TrustedUserID: user.ID, + }) + if err == nil { + return &packet.TrustInfo{ + Trusteds: []snowflake.ID{user.ID}, + TrustedPublicKeys: []ed25519.PublicKey{publicKey}, + RemovedTrusteds: nil, + } + } + if err != nil && err != sql.ErrNoRows { + log.Println("database error 1:", err) + return &ErrInternalError + } + + err = queries.TrustUser(ctx, data.TrustUserParams{ + TrustingUserID: sess.ID(), + TrustedUserID: user.ID, + TrustedPublicKey: user.PublicKey, + }) + if err != nil && err != sql.ErrNoRows { + log.Println("database error 2:", err) + return &ErrInternalError + } + + return &packet.TrustInfo{ + Trusteds: []snowflake.ID{user.ID}, + TrustedPublicKeys: []ed25519.PublicKey{user.PublicKey}, + RemovedTrusteds: nil, + } + } else { + err = queries.UntrustUser(ctx, data.UntrustUserParams{ + TrustingUserID: sess.ID(), + TrustedUserID: user.ID, + }) + if err == sql.ErrNoRows { + return &packet.TrustInfo{ + Trusteds: nil, + TrustedPublicKeys: nil, + RemovedTrusteds: []snowflake.ID{user.ID}, + } + } + if err != nil { + log.Println("database error 3:", err) + return &ErrInternalError + } + + return &packet.TrustInfo{ + Trusteds: nil, + TrustedPublicKeys: nil, + RemovedTrusteds: []snowflake.ID{user.ID}, + } + } +} + +func GetUserTrusteds(ctx context.Context, sess *session.Session) packet.Payload { + queries := data.New(db) + + trustedRows, err := queries.GetUserTrusteds(ctx, sess.ID()) + if err != nil && err != sql.ErrNoRows { + log.Println("database error 0:", err) + return &ErrInternalError + } + + trusteds := make([]snowflake.ID, 0, len(trustedRows)) + trustedPublicKeys := make([]ed25519.PublicKey, 0, len(trustedRows)) + + for _, row := range trustedRows { + trusteds = append(trusteds, row.TrustedUserID) + trustedPublicKeys = append(trustedPublicKeys, row.TrustedPublicKey) + } + + return &packet.TrustInfo{ + Trusteds: trusteds, + TrustedPublicKeys: trustedPublicKeys, + RemovedTrusteds: nil, + } +} diff --git a/internal/server/server.go b/internal/server/server.go index 8e69d70..3dcc16b 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -242,15 +242,25 @@ func (server *server) handleConnection(conn net.Conn) { // Send initial packets payload := api.GetUserData(ctx, sess, &packet.GetUserData{}) - dataPacket := packet.NewPacket(packet.NewMsgPackEncoder(payload)) - sess.Write(ctx, dataPacket) + if payload == &api.ErrInternalError { + return // closes the connection + } + pkt := packet.NewPacket(packet.NewMsgPackEncoder(payload)) + sess.Write(ctx, pkt) + + payload = api.GetUserTrusteds(ctx, sess) + if payload == &api.ErrInternalError { + return // closes the connection + } + pkt = packet.NewPacket(packet.NewMsgPackEncoder(payload)) + sess.Write(ctx, pkt) payload, err = api.GetNetworksInfo(ctx, sess) if err != nil { return // closes the connection } - infoPacket := packet.NewPacket(packet.NewMsgPackEncoder(payload)) - sess.Write(ctx, infoPacket) + pkt = packet.NewPacket(packet.NewMsgPackEncoder(payload)) + sess.Write(ctx, pkt) // Infinite read loop buffer := make([]byte, 512) @@ -371,6 +381,9 @@ func processRequest(ctx context.Context, sess *session.Session, request packet.P case *packet.SetMember: response = timeout(50*time.Millisecond, api.SetMember, ctx, sess, request) + case *packet.TrustUser: + response = timeout(10*time.Millisecond, api.TrustUser, ctx, sess, request) + default: response = &packet.Error{Error: "use of disallowed packet type for request"} } |
