internal/store/audit.go

419f6dfdc5489a0c6374e36dd1ebbfca68040056
gitbay/internal/store/audit.go history · blame · raw

75 lines · 2096 bytes

 1package store
 2
 3import "encoding/json"
 4
 5// Audit appends to the security feed. Events are the product feed; this
 6// records who did what, from where, for an operator. actorID 0 means the
 7// host admin (gitbayd admin commands) or an unauthenticated source.
 8func (s *Store) Audit(actorID int64, action string, data map[string]any) {
 9	var actor any
10	if actorID != 0 {
11		actor = actorID
12	}
13	raw, err := json.Marshal(data)
14	if err != nil {
15		raw = []byte("{}")
16	}
17	s.DB.Exec("INSERT INTO audit_log (actor_id, action, data_json) VALUES (?, ?, ?)",
18		actor, action, string(raw))
19}
20
21type AuditEntry struct {
22	ID        int64  `json:"id"`
23	Actor     string `json:"actor,omitempty"`
24	Action    string `json:"action"`
25	Data      string `json:"data"`
26	CreatedAt string `json:"created_at"`
27}
28
29// AuditFilter narrows AuditEntries. Actor is a username, or "-" for rows
30// with no actor (host commands, auth failures). ActionPrefix matches the
31// start of the action. Since is an ISO timestamp in the log's own format.
32type AuditFilter struct {
33	Actor        string
34	ActionPrefix string
35	Since        string
36	Limit        int
37}
38
39func (s *Store) AuditEntries(f AuditFilter) ([]AuditEntry, error) {
40	q := `SELECT a.id, COALESCE(u.username, ''), a.action, a.data_json, a.created_at
41		FROM audit_log a LEFT JOIN users u ON u.id = a.actor_id WHERE 1 = 1`
42	var args []any
43	switch f.Actor {
44	case "":
45	case "-":
46		q += " AND a.actor_id IS NULL"
47	default:
48		q += " AND u.username = ?"
49		args = append(args, f.Actor)
50	}
51	if f.ActionPrefix != "" {
52		q += " AND substr(a.action, 1, length(?)) = ?"
53		args = append(args, f.ActionPrefix, f.ActionPrefix)
54	}
55	if f.Since != "" {
56		q += " AND a.created_at >= ?"
57		args = append(args, f.Since)
58	}
59	q += " ORDER BY a.id DESC LIMIT ?"
60	args = append(args, f.Limit)
61	rows, err := s.DB.Query(q, args...)
62	if err != nil {
63		return nil, err
64	}
65	defer rows.Close()
66	var out []AuditEntry
67	for rows.Next() {
68		var e AuditEntry
69		if err := rows.Scan(&e.ID, &e.Actor, &e.Action, &e.Data, &e.CreatedAt); err != nil {
70			return nil, err
71		}
72		out = append(out, e)
73	}
74	return out, rows.Err()
75}