summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorKyren223 <Kyren223@proton.me>2025-01-22 17:23:18 +0200
committerKyren223 <Kyren223@proton.me>2025-01-22 17:23:18 +0200
commita6ea05146050a8b2ae5963d4ea96434dc3005f37 (patch)
tree09128a6f90a0aae8159e0575e11562409f525a1d /internal
parent645247f4ef489c9084a7d8d30db89b083287f0c6 (diff)
Added the ability to trust/untrust users
Diffstat (limited to 'internal')
-rw-r--r--internal/client/ui/core/chat/chat.go48
-rw-r--r--internal/client/ui/core/core.go3
-rw-r--r--internal/client/ui/core/memberlist/memberlist.go47
-rw-r--r--internal/client/ui/core/state/state.go13
-rw-r--r--internal/client/ui/ui.go10
-rw-r--r--internal/data/models.go2
-rw-r--r--internal/data/trusted_and_blocked_users.sql.go95
-rw-r--r--internal/packet/packet.go8
-rw-r--r--internal/packet/types.go21
-rw-r--r--internal/server/api/api.go97
-rw-r--r--internal/server/server.go21
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"}
}