Skip to content

Commit 848956b

Browse files
cfsmp3claude
andauthored
security: add authentication to logs endpoint and filter by user (#409)
* security: add authentication to logs endpoint and filter by user Add session authentication to /sync/logs endpoint and filter logs by user UUID so users can only see their own logs. - Require valid session to access logs endpoint - Add GetLogsByUser() to filter logs by user UUID - Return 401 Unauthorized for unauthenticated requests - Update SyncLogsHandler signature to accept session store Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: add hard cap of 20 to logs endpoint - Change default from 100 to 20 - Enforce maximum of 20 entries regardless of request - Prevents resource exhaustion from large requests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 679b006 commit 848956b

3 files changed

Lines changed: 77 additions & 24 deletions

File tree

backend/controllers/get_logs.go

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,43 +5,69 @@ import (
55
"encoding/json"
66
"net/http"
77
"strconv"
8+
9+
"github.com/gorilla/sessions"
810
)
911

1012
// SyncLogsHandler godoc
1113
// @Summary Get sync logs
12-
// @Description Fetch the latest sync operation logs
14+
// @Description Fetch the latest sync operation logs for the authenticated user
1315
// @Tags Logs
1416
// @Accept json
1517
// @Produce json
16-
// @Param last query int false "Number of latest log entries to return (default: 100)"
18+
// @Param last query int false "Number of latest log entries to return (default: 20, max: 20)"
1719
// @Success 200 {array} models.LogEntry "List of log entries"
1820
// @Failure 400 {string} string "Invalid last parameter"
21+
// @Failure 401 {string} string "Authentication required"
1922
// @Router /sync/logs [get]
20-
func SyncLogsHandler(w http.ResponseWriter, r *http.Request) {
21-
if r.Method != http.MethodGet {
22-
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
23-
return
24-
}
23+
func SyncLogsHandler(store *sessions.CookieStore) http.HandlerFunc {
24+
return func(w http.ResponseWriter, r *http.Request) {
25+
if r.Method != http.MethodGet {
26+
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
27+
return
28+
}
2529

26-
// Get the 'last' query parameter, default to 100
27-
lastParam := r.URL.Query().Get("last")
28-
last := 100
29-
if lastParam != "" {
30-
parsedLast, err := strconv.Atoi(lastParam)
31-
if err != nil || parsedLast < 0 {
32-
http.Error(w, "Invalid 'last' parameter", http.StatusBadRequest)
30+
// Validate session - user must be authenticated to view logs
31+
session, err := store.Get(r, "session-name")
32+
if err != nil {
33+
http.Error(w, "Authentication required", http.StatusUnauthorized)
3334
return
3435
}
35-
last = parsedLast
36-
}
3736

38-
// Get the log store and retrieve logs
39-
logStore := models.GetLogStore()
40-
logs := logStore.GetLogs(last)
37+
userInfo, ok := session.Values["user"].(map[string]interface{})
38+
if !ok || userInfo == nil {
39+
http.Error(w, "Authentication required", http.StatusUnauthorized)
40+
return
41+
}
42+
43+
// Get user's UUID to filter logs
44+
userUUID, _ := userInfo["uuid"].(string)
45+
46+
// Get the 'last' query parameter, default to 20, max 20
47+
const maxLogs = 20
48+
lastParam := r.URL.Query().Get("last")
49+
last := maxLogs
50+
if lastParam != "" {
51+
parsedLast, err := strconv.Atoi(lastParam)
52+
if err != nil || parsedLast < 0 {
53+
http.Error(w, "Invalid 'last' parameter", http.StatusBadRequest)
54+
return
55+
}
56+
last = parsedLast
57+
}
58+
// Enforce hard cap to prevent resource exhaustion
59+
if last > maxLogs {
60+
last = maxLogs
61+
}
4162

42-
w.Header().Set("Content-Type", "application/json")
43-
if err := json.NewEncoder(w).Encode(logs); err != nil {
44-
http.Error(w, "Failed to encode logs", http.StatusInternalServerError)
45-
return
63+
// Get the log store and retrieve logs filtered by user UUID
64+
logStore := models.GetLogStore()
65+
logs := logStore.GetLogsByUser(last, userUUID)
66+
67+
w.Header().Set("Content-Type", "application/json")
68+
if err := json.NewEncoder(w).Encode(logs); err != nil {
69+
http.Error(w, "Failed to encode logs", http.StatusInternalServerError)
70+
return
71+
}
4672
}
4773
}

backend/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ func main() {
109109
mux.Handle("/modify-task", rateLimitedHandler(http.HandlerFunc(controllers.ModifyTaskHandler)))
110110
mux.Handle("/complete-task", rateLimitedHandler(http.HandlerFunc(controllers.CompleteTaskHandler)))
111111
mux.Handle("/delete-task", rateLimitedHandler(http.HandlerFunc(controllers.DeleteTaskHandler)))
112-
mux.Handle("/sync/logs", rateLimitedHandler(http.HandlerFunc(controllers.SyncLogsHandler)))
112+
mux.Handle("/sync/logs", rateLimitedHandler(controllers.SyncLogsHandler(store)))
113113
mux.Handle("/complete-tasks", rateLimitedHandler(http.HandlerFunc(controllers.BulkCompleteTaskHandler)))
114114
mux.Handle("/delete-tasks", rateLimitedHandler(http.HandlerFunc(controllers.BulkDeleteTaskHandler)))
115115

backend/models/logs.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,30 @@ func (ls *LogStore) GetLogs(last int) []LogEntry {
9595
}
9696
return result
9797
}
98+
99+
// GetLogsByUser returns the last N log entries for a specific user (filtered by SyncID/UUID)
100+
func (ls *LogStore) GetLogsByUser(last int, userUUID string) []LogEntry {
101+
ls.mu.RLock()
102+
defer ls.mu.RUnlock()
103+
104+
// Filter entries by user UUID
105+
var userEntries []LogEntry
106+
for _, entry := range ls.entries {
107+
if entry.SyncID == userUUID {
108+
userEntries = append(userEntries, entry)
109+
}
110+
}
111+
112+
// Determine how many to return
113+
count := last
114+
if count <= 0 || count > len(userEntries) {
115+
count = len(userEntries)
116+
}
117+
118+
// Return last N entries in reverse order (newest first)
119+
result := make([]LogEntry, count)
120+
for i := 0; i < count; i++ {
121+
result[i] = userEntries[len(userEntries)-1-i]
122+
}
123+
return result
124+
}

0 commit comments

Comments
 (0)