Show display name in audit log

This commit is contained in:
Jonathon Leight
2026-07-08 00:15:58 -04:00
parent 7baa7ac4db
commit ca656d6bff
3 changed files with 63 additions and 5 deletions
+9 -4
View File
@@ -56,8 +56,11 @@ func (s *Store) MarkCommandReply(ctx context.Context, logID int64, response stri
// CommandSession groups command-log entries by the console session they were
// sent in.
type CommandSession struct {
ID int64
SenderName string // who held the session
ID int64
// SenderName is who held the session: their current display name (or username),
// falling back to the username snapshotted at session start if they've since
// been deleted.
SenderName string
StartedAt time.Time
EndedAt *time.Time
Entries []*CommandLogEntry // newest first, matching the newest-first session order
@@ -95,8 +98,9 @@ func (s *Store) ListCommandLogSessionsPage(ctx context.Context, repeaterID int64
// behavior of the previous query).
headers, err := s.pool.Query(ctx, `
SELECT cs.id, cs.started_at, cs.ended_at,
COALESCE(cs.sender_username, '(deleted)')
COALESCE(NULLIF(u.display_name, ''), u.username, cs.sender_username, '(deleted)')
FROM console_sessions cs
LEFT JOIN users u ON u.id = cs.user_id
WHERE cs.repeater_id = $1
AND ($2 OR (cs.started_at, cs.id) < ($3, $4))
AND EXISTS (SELECT 1 FROM command_log l WHERE l.session_id = cs.id)
@@ -172,10 +176,11 @@ type OwnerCommandLogEntry struct {
func (s *Store) ListRecentCommandsForOwner(ctx context.Context, ownerID int64, limit int) ([]OwnerCommandLogEntry, error) {
rows, err := s.pool.Query(ctx, `
SELECT l.repeater_id, r.public_id, r.name,
COALESCE(l.sender_username, '(deleted)'),
COALESCE(NULLIF(u.display_name, ''), u.username, l.sender_username, '(deleted)'),
l.command_text, l.sent_at, l.ack_received, l.response_text
FROM command_log l
JOIN repeaters r ON r.id = l.repeater_id
LEFT JOIN users u ON u.id = l.user_id
WHERE r.owner_id = $1
ORDER BY l.sent_at DESC
LIMIT $2`, ownerID, limit)
+52
View File
@@ -6,6 +6,58 @@ import (
"testing"
)
// TestCommandLogSenderName: the log shows the sender's live display name while
// they exist, and falls back to the username snapshot once they're deleted.
func TestCommandLogSenderName(t *testing.T) {
t.Parallel()
st, ctx := orgTestStore(t)
owner, err := st.CreateUser(ctx, "logowner2", "")
if err != nil {
t.Fatal(err)
}
rep, err := st.CreateRepeater(ctx, &Repeater{
OwnerID: owner.ID, Name: "R", PublicKeyHex: strings.Repeat("d", 64),
RadioFreqHz: 1, RadioBwHz: 1, RadioSF: 11, RadioCR: 5,
})
if err != nil {
t.Fatal(err)
}
// A separate sender (not the owner, so deleting them doesn't cascade the repeater).
sender, err := st.CreateUser(ctx, "bobby", "Bob Builder")
if err != nil {
t.Fatal(err)
}
sid, err := st.StartConsoleSession(ctx, rep.ID, sender.ID)
if err != nil {
t.Fatal(err)
}
if _, err := st.LogCommand(ctx, rep.ID, sender.ID, sid, 0, "advert"); err != nil {
t.Fatal(err)
}
senderName := func() string {
page, _, err := st.ListCommandLogSessionsPage(ctx, rep.ID, nil)
if err != nil || len(page) != 1 {
t.Fatalf("page = %d sessions, %v; want 1", len(page), err)
}
return page[0].SenderName
}
// While the sender exists: their display name.
if got := senderName(); got != "Bob Builder" {
t.Fatalf("sender name = %q, want the live display name %q", got, "Bob Builder")
}
// After deletion (user_id → NULL): the username snapshot taken at session start.
if _, err := st.pool.Exec(ctx, `DELETE FROM users WHERE id=$1`, sender.ID); err != nil {
t.Fatalf("delete sender: %v", err)
}
if got := senderName(); got != "bobby" {
t.Fatalf("deleted sender name = %q, want the username snapshot %q", got, "bobby")
}
}
// TestListCommandLogSessionsPage walks the keyset-paginated session log and
// checks every session with commands appears once, newest-first, across pages —
// and that a session with no commands is omitted.
+2 -1
View File
@@ -8,7 +8,8 @@ import (
// StartConsoleSession records the start of a console session and returns its id.
func (s *Store) StartConsoleSession(ctx context.Context, repeaterID, userID int64) (int64, error) {
var id int64
// Snapshot the username so the log stays a point-in-time record after renames.
// Snapshot the username as a fallback so the log still identifies the sender if
// they're later deleted; while they exist the log shows their live display name.
err := s.pool.QueryRow(ctx,
`INSERT INTO console_sessions (repeater_id, user_id, sender_username)
VALUES ($1, $2, (SELECT username FROM users WHERE id = $2)) RETURNING id`,