fix(hub): user alerts idor

fixes very unlikely scenario where user guesses another user's 15
character random system id and adds alerts for it
This commit is contained in:
henrygd
2026-08-21 17:25:09 -04:00
parent 946f2e6be1
commit 6f92b9396d
4 changed files with 139 additions and 1 deletions

View File

@@ -11,6 +11,7 @@ import (
beszelTests "github.com/henrygd/beszel/internal/tests"
"github.com/henrygd/beszel/internal/migrations"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
pbTests "github.com/pocketbase/pocketbase/tests"
"github.com/stretchr/testify/require"
@@ -55,7 +56,7 @@ func TestApiRoutesAuthentication(t *testing.T) {
// Create test system
system, err := beszelTests.CreateRecord(hub, "systems", map[string]any{
"name": "test-system",
"users": []string{user.Id},
"users": []string{user.Id, readOnlyUser.Id},
"host": "127.0.0.1",
})
require.NoError(t, err, "Failed to create test system")
@@ -277,6 +278,24 @@ func TestApiRoutesAuthentication(t *testing.T) {
"systems": []string{system.Id},
}),
},
{
Name: "POST /user-alerts - readonly user can create own alert",
Method: http.MethodPost,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": readOnlyUserToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"success\":true"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU", "value": 80, "min": 10, "systems": []string{system.Id},
}),
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
alerts, _ := app.CountRecords("alerts", dbx.HashExp{"user": readOnlyUser.Id})
require.EqualValues(t, 1, alerts)
},
},
{
Name: "DELETE /user-alerts - no auth should fail",
Method: http.MethodDelete,
@@ -314,6 +333,29 @@ func TestApiRoutesAuthentication(t *testing.T) {
})
},
},
{
Name: "DELETE /user-alerts - readonly user can delete own alert",
Method: http.MethodDelete,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": readOnlyUserToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"count\":1", "\"success\":true"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU", "systems": []string{system.Id},
}),
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
beszelTests.CreateRecord(app, "alerts", map[string]any{
"name": "CPU", "system": system.Id, "user": readOnlyUser.Id, "value": 80,
})
},
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
alerts, _ := app.CountRecords("alerts", dbx.HashExp{"user": readOnlyUser.Id})
require.Zero(t, alerts)
},
},
{
Name: "GET /containers/logs - no auth should fail",
Method: http.MethodGet,

View File

@@ -357,6 +357,13 @@ func TestApiCollectionsAuthRules(t *testing.T) {
"host": "127.0.0.2",
})
userOneAlert, _ := beszelTests.CreateRecord(hub, "alerts", map[string]any{
"name": "CPU", "system": userOneSystem.Id, "user": user1.Id, "value": 80,
})
userTwoAlert, _ := beszelTests.CreateRecord(hub, "alerts", map[string]any{
"name": "CPU", "system": userTwoSystem.Id, "user": user2.Id, "value": 80,
})
userRecords, _ := hub.CountRecords("users")
assert.EqualValues(t, 3, userRecords, "all users should be created")
@@ -368,6 +375,30 @@ func TestApiCollectionsAuthRules(t *testing.T) {
}
scenarios := []beszelTests.ApiScenario{
{
Name: "Users can only list their own alerts",
Method: http.MethodGet,
URL: "/api/collections/alerts/records",
Headers: map[string]string{
"Authorization": user1Token,
},
ExpectedStatus: 200,
ExpectedContent: []string{userOneAlert.Id},
NotExpectedContent: []string{userTwoAlert.Id},
TestAppFactory: testAppFactory,
},
{
Name: "Users cannot view another user's alert by id",
Method: http.MethodGet,
URL: fmt.Sprintf("/api/collections/alerts/records/%s", userTwoAlert.Id),
Headers: map[string]string{
"Authorization": user1Token,
},
ExpectedStatus: 403,
ExpectedContent: []string{"Only superusers"},
NotExpectedContent: []string{userTwoAlert.Id},
TestAppFactory: testAppFactory,
},
{
Name: "Unauthorized user cannot list systems",
Method: http.MethodGet,