mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-21 21:26:16 +01:00
refactor(hub): harden/enforce pb api rules and add tests
- separate collection related code from hub.go - ensure hub is bootstrapped and collections updated automatically when calling NewHub
This commit is contained in:
@@ -28,7 +28,7 @@ func main() {
|
||||
}
|
||||
|
||||
baseApp := getBaseApp()
|
||||
h := hub.NewHub(baseApp)
|
||||
h, _ := hub.NewHub(baseApp)
|
||||
if err := h.StartHub(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -32,7 +32,8 @@ func createTestHub(t testing.TB) (*Hub, *pbtests.TestApp, error) {
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return NewHub(testApp), testApp, nil
|
||||
h, err := NewHub(testApp)
|
||||
return h, testApp, err
|
||||
}
|
||||
|
||||
// cleanupTestHub stops background system goroutines before tearing down the app.
|
||||
|
||||
128
internal/hub/collections.go
Normal file
128
internal/hub/collections.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package hub
|
||||
|
||||
import "github.com/pocketbase/pocketbase/core"
|
||||
|
||||
type collectionRules struct {
|
||||
list *string
|
||||
view *string
|
||||
create *string
|
||||
update *string
|
||||
delete *string
|
||||
}
|
||||
|
||||
// setCollectionAuthSettings applies Beszel's collection auth settings.
|
||||
func setCollectionAuthSettings(app core.App) error {
|
||||
usersCollection, err := app.FindCollectionByNameOrId("users")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
superusersCollection, err := app.FindCollectionByNameOrId(core.CollectionNameSuperusers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// disable email auth if DISABLE_PASSWORD_AUTH env var is set
|
||||
disablePasswordAuth, _ := GetEnv("DISABLE_PASSWORD_AUTH")
|
||||
usersCollection.PasswordAuth.Enabled = disablePasswordAuth != "true"
|
||||
usersCollection.PasswordAuth.IdentityFields = []string{"email"}
|
||||
// allow oauth user creation if USER_CREATION is set
|
||||
if userCreation, _ := GetEnv("USER_CREATION"); userCreation == "true" {
|
||||
cr := "@request.context = 'oauth2'"
|
||||
usersCollection.CreateRule = &cr
|
||||
} else {
|
||||
usersCollection.CreateRule = nil
|
||||
}
|
||||
|
||||
// enable mfaOtp mfa if MFA_OTP env var is set
|
||||
mfaOtp, _ := GetEnv("MFA_OTP")
|
||||
usersCollection.OTP.Length = 6
|
||||
superusersCollection.OTP.Length = 6
|
||||
usersCollection.OTP.Enabled = mfaOtp == "true"
|
||||
usersCollection.MFA.Enabled = mfaOtp == "true"
|
||||
superusersCollection.OTP.Enabled = mfaOtp == "true" || mfaOtp == "superusers"
|
||||
superusersCollection.MFA.Enabled = mfaOtp == "true" || mfaOtp == "superusers"
|
||||
if err := app.Save(superusersCollection); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := app.Save(usersCollection); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// When SHARE_ALL_SYSTEMS is enabled, any authenticated user can read
|
||||
// system-scoped data. Write rules continue to block readonly users.
|
||||
shareAllSystems, _ := GetEnv("SHARE_ALL_SYSTEMS")
|
||||
|
||||
authenticatedRule := "@request.auth.id != \"\""
|
||||
systemsMemberRule := authenticatedRule + " && users.id ?= @request.auth.id"
|
||||
systemMemberRule := authenticatedRule + " && system.users.id ?= @request.auth.id"
|
||||
|
||||
systemsReadRule := systemsMemberRule
|
||||
systemScopedReadRule := systemMemberRule
|
||||
if shareAllSystems == "true" {
|
||||
systemsReadRule = authenticatedRule
|
||||
systemScopedReadRule = authenticatedRule
|
||||
}
|
||||
systemsWriteRule := systemsReadRule + " && @request.auth.role != \"readonly\""
|
||||
systemScopedWriteRule := systemScopedReadRule + " && @request.auth.role != \"readonly\""
|
||||
|
||||
if err := applyCollectionRules(app, []string{"systems"}, collectionRules{
|
||||
list: &systemsReadRule,
|
||||
view: &systemsReadRule,
|
||||
create: &systemsWriteRule,
|
||||
update: &systemsWriteRule,
|
||||
delete: &systemsWriteRule,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := applyCollectionRules(app, []string{"containers", "container_stats", "system_stats", "systemd_services"}, collectionRules{
|
||||
list: &systemScopedReadRule,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := applyCollectionRules(app, []string{"smart_devices"}, collectionRules{
|
||||
list: &systemScopedReadRule,
|
||||
view: &systemScopedReadRule,
|
||||
delete: &systemScopedWriteRule,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := applyCollectionRules(app, []string{"fingerprints"}, collectionRules{
|
||||
list: &systemScopedReadRule,
|
||||
view: &systemScopedReadRule,
|
||||
create: &systemScopedWriteRule,
|
||||
update: &systemScopedWriteRule,
|
||||
delete: &systemScopedWriteRule,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := applyCollectionRules(app, []string{"system_details"}, collectionRules{
|
||||
list: &systemScopedReadRule,
|
||||
view: &systemScopedReadRule,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyCollectionRules(app core.App, collectionNames []string, rules collectionRules) error {
|
||||
for _, collectionName := range collectionNames {
|
||||
collection, err := app.FindCollectionByNameOrId(collectionName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
collection.ListRule = rules.list
|
||||
collection.ViewRule = rules.view
|
||||
collection.CreateRule = rules.create
|
||||
collection.UpdateRule = rules.update
|
||||
collection.DeleteRule = rules.delete
|
||||
if err := app.Save(collection); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
527
internal/hub/collections_test.go
Normal file
527
internal/hub/collections_test.go
Normal file
@@ -0,0 +1,527 @@
|
||||
//go:build testing
|
||||
|
||||
package hub_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
beszelTests "github.com/henrygd/beszel/internal/tests"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
pbTests "github.com/pocketbase/pocketbase/tests"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCollectionRulesDefault(t *testing.T) {
|
||||
hub, _ := beszelTests.NewTestHub(t.TempDir())
|
||||
defer hub.Cleanup()
|
||||
|
||||
const isUserMatchesUser = `@request.auth.id != "" && user = @request.auth.id`
|
||||
|
||||
const isUserInUsers = `@request.auth.id != "" && users.id ?= @request.auth.id`
|
||||
const isUserInUsersNotReadonly = `@request.auth.id != "" && users.id ?= @request.auth.id && @request.auth.role != "readonly"`
|
||||
|
||||
const isUserInSystemUsers = `@request.auth.id != "" && system.users.id ?= @request.auth.id`
|
||||
const isUserInSystemUsersNotReadonly = `@request.auth.id != "" && system.users.id ?= @request.auth.id && @request.auth.role != "readonly"`
|
||||
|
||||
// users collection
|
||||
usersCollection, err := hub.FindCollectionByNameOrId("users")
|
||||
assert.NoError(t, err, "Failed to find users collection")
|
||||
assert.True(t, usersCollection.PasswordAuth.Enabled)
|
||||
assert.Equal(t, usersCollection.PasswordAuth.IdentityFields, []string{"email"})
|
||||
assert.Nil(t, usersCollection.CreateRule)
|
||||
assert.False(t, usersCollection.MFA.Enabled)
|
||||
|
||||
// superusers collection
|
||||
superusersCollection, err := hub.FindCollectionByNameOrId(core.CollectionNameSuperusers)
|
||||
assert.NoError(t, err, "Failed to find superusers collection")
|
||||
assert.True(t, superusersCollection.PasswordAuth.Enabled)
|
||||
assert.Equal(t, superusersCollection.PasswordAuth.IdentityFields, []string{"email"})
|
||||
assert.Nil(t, superusersCollection.CreateRule)
|
||||
assert.False(t, superusersCollection.MFA.Enabled)
|
||||
|
||||
// alerts collection
|
||||
alertsCollection, err := hub.FindCollectionByNameOrId("alerts")
|
||||
require.NoError(t, err, "Failed to find alerts collection")
|
||||
assert.Equal(t, isUserMatchesUser, *alertsCollection.ListRule)
|
||||
assert.Nil(t, alertsCollection.ViewRule)
|
||||
assert.Equal(t, isUserMatchesUser, *alertsCollection.CreateRule)
|
||||
assert.Equal(t, isUserMatchesUser, *alertsCollection.UpdateRule)
|
||||
assert.Equal(t, isUserMatchesUser, *alertsCollection.DeleteRule)
|
||||
|
||||
// alerts_history collection
|
||||
alertsHistoryCollection, err := hub.FindCollectionByNameOrId("alerts_history")
|
||||
require.NoError(t, err, "Failed to find alerts_history collection")
|
||||
assert.Equal(t, isUserMatchesUser, *alertsHistoryCollection.ListRule)
|
||||
assert.Nil(t, alertsHistoryCollection.ViewRule)
|
||||
assert.Nil(t, alertsHistoryCollection.CreateRule)
|
||||
assert.Nil(t, alertsHistoryCollection.UpdateRule)
|
||||
assert.Equal(t, isUserMatchesUser, *alertsHistoryCollection.DeleteRule)
|
||||
|
||||
// containers collection
|
||||
containersCollection, err := hub.FindCollectionByNameOrId("containers")
|
||||
require.NoError(t, err, "Failed to find containers collection")
|
||||
assert.Equal(t, isUserInSystemUsers, *containersCollection.ListRule)
|
||||
assert.Nil(t, containersCollection.ViewRule)
|
||||
assert.Nil(t, containersCollection.CreateRule)
|
||||
assert.Nil(t, containersCollection.UpdateRule)
|
||||
assert.Nil(t, containersCollection.DeleteRule)
|
||||
|
||||
// container_stats collection
|
||||
containerStatsCollection, err := hub.FindCollectionByNameOrId("container_stats")
|
||||
require.NoError(t, err, "Failed to find container_stats collection")
|
||||
assert.Equal(t, isUserInSystemUsers, *containerStatsCollection.ListRule)
|
||||
assert.Nil(t, containerStatsCollection.ViewRule)
|
||||
assert.Nil(t, containerStatsCollection.CreateRule)
|
||||
assert.Nil(t, containerStatsCollection.UpdateRule)
|
||||
assert.Nil(t, containerStatsCollection.DeleteRule)
|
||||
|
||||
// fingerprints collection
|
||||
fingerprintsCollection, err := hub.FindCollectionByNameOrId("fingerprints")
|
||||
require.NoError(t, err, "Failed to find fingerprints collection")
|
||||
assert.Equal(t, isUserInSystemUsers, *fingerprintsCollection.ListRule)
|
||||
assert.Equal(t, isUserInSystemUsers, *fingerprintsCollection.ViewRule)
|
||||
assert.Equal(t, isUserInSystemUsersNotReadonly, *fingerprintsCollection.CreateRule)
|
||||
assert.Equal(t, isUserInSystemUsersNotReadonly, *fingerprintsCollection.UpdateRule)
|
||||
assert.Equal(t, isUserInSystemUsersNotReadonly, *fingerprintsCollection.DeleteRule)
|
||||
|
||||
// quiet_hours collection
|
||||
quietHoursCollection, err := hub.FindCollectionByNameOrId("quiet_hours")
|
||||
require.NoError(t, err, "Failed to find quiet_hours collection")
|
||||
assert.Equal(t, isUserMatchesUser, *quietHoursCollection.ListRule)
|
||||
assert.Equal(t, isUserMatchesUser, *quietHoursCollection.ViewRule)
|
||||
assert.Equal(t, isUserMatchesUser, *quietHoursCollection.CreateRule)
|
||||
assert.Equal(t, isUserMatchesUser, *quietHoursCollection.UpdateRule)
|
||||
assert.Equal(t, isUserMatchesUser, *quietHoursCollection.DeleteRule)
|
||||
|
||||
// smart_devices collection
|
||||
smartDevicesCollection, err := hub.FindCollectionByNameOrId("smart_devices")
|
||||
require.NoError(t, err, "Failed to find smart_devices collection")
|
||||
assert.Equal(t, isUserInSystemUsers, *smartDevicesCollection.ListRule)
|
||||
assert.Equal(t, isUserInSystemUsers, *smartDevicesCollection.ViewRule)
|
||||
assert.Nil(t, smartDevicesCollection.CreateRule)
|
||||
assert.Nil(t, smartDevicesCollection.UpdateRule)
|
||||
assert.Equal(t, isUserInSystemUsersNotReadonly, *smartDevicesCollection.DeleteRule)
|
||||
|
||||
// system_details collection
|
||||
systemDetailsCollection, err := hub.FindCollectionByNameOrId("system_details")
|
||||
require.NoError(t, err, "Failed to find system_details collection")
|
||||
assert.Equal(t, isUserInSystemUsers, *systemDetailsCollection.ListRule)
|
||||
assert.Equal(t, isUserInSystemUsers, *systemDetailsCollection.ViewRule)
|
||||
assert.Nil(t, systemDetailsCollection.CreateRule)
|
||||
assert.Nil(t, systemDetailsCollection.UpdateRule)
|
||||
assert.Nil(t, systemDetailsCollection.DeleteRule)
|
||||
|
||||
// system_stats collection
|
||||
systemStatsCollection, err := hub.FindCollectionByNameOrId("system_stats")
|
||||
require.NoError(t, err, "Failed to find system_stats collection")
|
||||
assert.Equal(t, isUserInSystemUsers, *systemStatsCollection.ListRule)
|
||||
assert.Nil(t, systemStatsCollection.ViewRule)
|
||||
assert.Nil(t, systemStatsCollection.CreateRule)
|
||||
assert.Nil(t, systemStatsCollection.UpdateRule)
|
||||
assert.Nil(t, systemStatsCollection.DeleteRule)
|
||||
|
||||
// systemd_services collection
|
||||
systemdServicesCollection, err := hub.FindCollectionByNameOrId("systemd_services")
|
||||
require.NoError(t, err, "Failed to find systemd_services collection")
|
||||
assert.Equal(t, isUserInSystemUsers, *systemdServicesCollection.ListRule)
|
||||
assert.Nil(t, systemdServicesCollection.ViewRule)
|
||||
assert.Nil(t, systemdServicesCollection.CreateRule)
|
||||
assert.Nil(t, systemdServicesCollection.UpdateRule)
|
||||
assert.Nil(t, systemdServicesCollection.DeleteRule)
|
||||
|
||||
// systems collection
|
||||
systemsCollection, err := hub.FindCollectionByNameOrId("systems")
|
||||
require.NoError(t, err, "Failed to find systems collection")
|
||||
assert.Equal(t, isUserInUsers, *systemsCollection.ListRule)
|
||||
assert.Equal(t, isUserInUsers, *systemsCollection.ViewRule)
|
||||
assert.Equal(t, isUserInUsersNotReadonly, *systemsCollection.CreateRule)
|
||||
assert.Equal(t, isUserInUsersNotReadonly, *systemsCollection.UpdateRule)
|
||||
assert.Equal(t, isUserInUsersNotReadonly, *systemsCollection.DeleteRule)
|
||||
|
||||
// universal_tokens collection
|
||||
universalTokensCollection, err := hub.FindCollectionByNameOrId("universal_tokens")
|
||||
require.NoError(t, err, "Failed to find universal_tokens collection")
|
||||
assert.Nil(t, universalTokensCollection.ListRule)
|
||||
assert.Nil(t, universalTokensCollection.ViewRule)
|
||||
assert.Nil(t, universalTokensCollection.CreateRule)
|
||||
assert.Nil(t, universalTokensCollection.UpdateRule)
|
||||
assert.Nil(t, universalTokensCollection.DeleteRule)
|
||||
|
||||
// user_settings collection
|
||||
userSettingsCollection, err := hub.FindCollectionByNameOrId("user_settings")
|
||||
require.NoError(t, err, "Failed to find user_settings collection")
|
||||
assert.Equal(t, isUserMatchesUser, *userSettingsCollection.ListRule)
|
||||
assert.Nil(t, userSettingsCollection.ViewRule)
|
||||
assert.Equal(t, isUserMatchesUser, *userSettingsCollection.CreateRule)
|
||||
assert.Equal(t, isUserMatchesUser, *userSettingsCollection.UpdateRule)
|
||||
assert.Nil(t, userSettingsCollection.DeleteRule)
|
||||
}
|
||||
|
||||
func TestCollectionRulesShareAllSystems(t *testing.T) {
|
||||
t.Setenv("SHARE_ALL_SYSTEMS", "true")
|
||||
hub, _ := beszelTests.NewTestHub(t.TempDir())
|
||||
defer hub.Cleanup()
|
||||
|
||||
const isUser = `@request.auth.id != ""`
|
||||
const isUserNotReadonly = `@request.auth.id != "" && @request.auth.role != "readonly"`
|
||||
|
||||
const isUserMatchesUser = `@request.auth.id != "" && user = @request.auth.id`
|
||||
|
||||
// alerts collection
|
||||
alertsCollection, err := hub.FindCollectionByNameOrId("alerts")
|
||||
require.NoError(t, err, "Failed to find alerts collection")
|
||||
assert.Equal(t, isUserMatchesUser, *alertsCollection.ListRule)
|
||||
assert.Nil(t, alertsCollection.ViewRule)
|
||||
assert.Equal(t, isUserMatchesUser, *alertsCollection.CreateRule)
|
||||
assert.Equal(t, isUserMatchesUser, *alertsCollection.UpdateRule)
|
||||
assert.Equal(t, isUserMatchesUser, *alertsCollection.DeleteRule)
|
||||
|
||||
// alerts_history collection
|
||||
alertsHistoryCollection, err := hub.FindCollectionByNameOrId("alerts_history")
|
||||
require.NoError(t, err, "Failed to find alerts_history collection")
|
||||
assert.Equal(t, isUserMatchesUser, *alertsHistoryCollection.ListRule)
|
||||
assert.Nil(t, alertsHistoryCollection.ViewRule)
|
||||
assert.Nil(t, alertsHistoryCollection.CreateRule)
|
||||
assert.Nil(t, alertsHistoryCollection.UpdateRule)
|
||||
assert.Equal(t, isUserMatchesUser, *alertsHistoryCollection.DeleteRule)
|
||||
|
||||
// containers collection
|
||||
containersCollection, err := hub.FindCollectionByNameOrId("containers")
|
||||
require.NoError(t, err, "Failed to find containers collection")
|
||||
assert.Equal(t, isUser, *containersCollection.ListRule)
|
||||
assert.Nil(t, containersCollection.ViewRule)
|
||||
assert.Nil(t, containersCollection.CreateRule)
|
||||
assert.Nil(t, containersCollection.UpdateRule)
|
||||
assert.Nil(t, containersCollection.DeleteRule)
|
||||
|
||||
// container_stats collection
|
||||
containerStatsCollection, err := hub.FindCollectionByNameOrId("container_stats")
|
||||
require.NoError(t, err, "Failed to find container_stats collection")
|
||||
assert.Equal(t, isUser, *containerStatsCollection.ListRule)
|
||||
assert.Nil(t, containerStatsCollection.ViewRule)
|
||||
assert.Nil(t, containerStatsCollection.CreateRule)
|
||||
assert.Nil(t, containerStatsCollection.UpdateRule)
|
||||
assert.Nil(t, containerStatsCollection.DeleteRule)
|
||||
|
||||
// fingerprints collection
|
||||
fingerprintsCollection, err := hub.FindCollectionByNameOrId("fingerprints")
|
||||
require.NoError(t, err, "Failed to find fingerprints collection")
|
||||
assert.Equal(t, isUser, *fingerprintsCollection.ListRule)
|
||||
assert.Equal(t, isUser, *fingerprintsCollection.ViewRule)
|
||||
assert.Equal(t, isUserNotReadonly, *fingerprintsCollection.CreateRule)
|
||||
assert.Equal(t, isUserNotReadonly, *fingerprintsCollection.UpdateRule)
|
||||
assert.Equal(t, isUserNotReadonly, *fingerprintsCollection.DeleteRule)
|
||||
|
||||
// quiet_hours collection
|
||||
quietHoursCollection, err := hub.FindCollectionByNameOrId("quiet_hours")
|
||||
require.NoError(t, err, "Failed to find quiet_hours collection")
|
||||
assert.Equal(t, isUserMatchesUser, *quietHoursCollection.ListRule)
|
||||
assert.Equal(t, isUserMatchesUser, *quietHoursCollection.ViewRule)
|
||||
assert.Equal(t, isUserMatchesUser, *quietHoursCollection.CreateRule)
|
||||
assert.Equal(t, isUserMatchesUser, *quietHoursCollection.UpdateRule)
|
||||
assert.Equal(t, isUserMatchesUser, *quietHoursCollection.DeleteRule)
|
||||
|
||||
// smart_devices collection
|
||||
smartDevicesCollection, err := hub.FindCollectionByNameOrId("smart_devices")
|
||||
require.NoError(t, err, "Failed to find smart_devices collection")
|
||||
assert.Equal(t, isUser, *smartDevicesCollection.ListRule)
|
||||
assert.Equal(t, isUser, *smartDevicesCollection.ViewRule)
|
||||
assert.Nil(t, smartDevicesCollection.CreateRule)
|
||||
assert.Nil(t, smartDevicesCollection.UpdateRule)
|
||||
assert.Equal(t, isUserNotReadonly, *smartDevicesCollection.DeleteRule)
|
||||
|
||||
// system_details collection
|
||||
systemDetailsCollection, err := hub.FindCollectionByNameOrId("system_details")
|
||||
require.NoError(t, err, "Failed to find system_details collection")
|
||||
assert.Equal(t, isUser, *systemDetailsCollection.ListRule)
|
||||
assert.Equal(t, isUser, *systemDetailsCollection.ViewRule)
|
||||
assert.Nil(t, systemDetailsCollection.CreateRule)
|
||||
assert.Nil(t, systemDetailsCollection.UpdateRule)
|
||||
assert.Nil(t, systemDetailsCollection.DeleteRule)
|
||||
|
||||
// system_stats collection
|
||||
systemStatsCollection, err := hub.FindCollectionByNameOrId("system_stats")
|
||||
require.NoError(t, err, "Failed to find system_stats collection")
|
||||
assert.Equal(t, isUser, *systemStatsCollection.ListRule)
|
||||
assert.Nil(t, systemStatsCollection.ViewRule)
|
||||
assert.Nil(t, systemStatsCollection.CreateRule)
|
||||
assert.Nil(t, systemStatsCollection.UpdateRule)
|
||||
assert.Nil(t, systemStatsCollection.DeleteRule)
|
||||
|
||||
// systemd_services collection
|
||||
systemdServicesCollection, err := hub.FindCollectionByNameOrId("systemd_services")
|
||||
require.NoError(t, err, "Failed to find systemd_services collection")
|
||||
assert.Equal(t, isUser, *systemdServicesCollection.ListRule)
|
||||
assert.Nil(t, systemdServicesCollection.ViewRule)
|
||||
assert.Nil(t, systemdServicesCollection.CreateRule)
|
||||
assert.Nil(t, systemdServicesCollection.UpdateRule)
|
||||
assert.Nil(t, systemdServicesCollection.DeleteRule)
|
||||
|
||||
// systems collection
|
||||
systemsCollection, err := hub.FindCollectionByNameOrId("systems")
|
||||
require.NoError(t, err, "Failed to find systems collection")
|
||||
assert.Equal(t, isUser, *systemsCollection.ListRule)
|
||||
assert.Equal(t, isUser, *systemsCollection.ViewRule)
|
||||
assert.Equal(t, isUserNotReadonly, *systemsCollection.CreateRule)
|
||||
assert.Equal(t, isUserNotReadonly, *systemsCollection.UpdateRule)
|
||||
assert.Equal(t, isUserNotReadonly, *systemsCollection.DeleteRule)
|
||||
|
||||
// universal_tokens collection
|
||||
universalTokensCollection, err := hub.FindCollectionByNameOrId("universal_tokens")
|
||||
require.NoError(t, err, "Failed to find universal_tokens collection")
|
||||
assert.Nil(t, universalTokensCollection.ListRule)
|
||||
assert.Nil(t, universalTokensCollection.ViewRule)
|
||||
assert.Nil(t, universalTokensCollection.CreateRule)
|
||||
assert.Nil(t, universalTokensCollection.UpdateRule)
|
||||
assert.Nil(t, universalTokensCollection.DeleteRule)
|
||||
|
||||
// user_settings collection
|
||||
userSettingsCollection, err := hub.FindCollectionByNameOrId("user_settings")
|
||||
require.NoError(t, err, "Failed to find user_settings collection")
|
||||
assert.Equal(t, isUserMatchesUser, *userSettingsCollection.ListRule)
|
||||
assert.Nil(t, userSettingsCollection.ViewRule)
|
||||
assert.Equal(t, isUserMatchesUser, *userSettingsCollection.CreateRule)
|
||||
assert.Equal(t, isUserMatchesUser, *userSettingsCollection.UpdateRule)
|
||||
assert.Nil(t, userSettingsCollection.DeleteRule)
|
||||
}
|
||||
|
||||
func TestDisablePasswordAuth(t *testing.T) {
|
||||
t.Setenv("DISABLE_PASSWORD_AUTH", "true")
|
||||
hub, _ := beszelTests.NewTestHub(t.TempDir())
|
||||
defer hub.Cleanup()
|
||||
|
||||
usersCollection, err := hub.FindCollectionByNameOrId("users")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, usersCollection.PasswordAuth.Enabled)
|
||||
}
|
||||
|
||||
func TestUserCreation(t *testing.T) {
|
||||
t.Setenv("USER_CREATION", "true")
|
||||
hub, _ := beszelTests.NewTestHub(t.TempDir())
|
||||
defer hub.Cleanup()
|
||||
|
||||
usersCollection, err := hub.FindCollectionByNameOrId("users")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "@request.context = 'oauth2'", *usersCollection.CreateRule)
|
||||
}
|
||||
|
||||
func TestMFAOtp(t *testing.T) {
|
||||
t.Setenv("MFA_OTP", "true")
|
||||
hub, _ := beszelTests.NewTestHub(t.TempDir())
|
||||
defer hub.Cleanup()
|
||||
|
||||
usersCollection, err := hub.FindCollectionByNameOrId("users")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, usersCollection.OTP.Enabled)
|
||||
assert.True(t, usersCollection.MFA.Enabled)
|
||||
|
||||
superusersCollection, err := hub.FindCollectionByNameOrId(core.CollectionNameSuperusers)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, superusersCollection.OTP.Enabled)
|
||||
assert.True(t, superusersCollection.MFA.Enabled)
|
||||
}
|
||||
|
||||
func TestApiCollectionsAuthRules(t *testing.T) {
|
||||
hub, _ := beszelTests.NewTestHub(t.TempDir())
|
||||
defer hub.Cleanup()
|
||||
|
||||
hub.StartHub()
|
||||
|
||||
user1, _ := beszelTests.CreateUser(hub, "user1@example.com", "password")
|
||||
user1Token, _ := user1.NewAuthToken()
|
||||
|
||||
user2, _ := beszelTests.CreateUser(hub, "user2@example.com", "password")
|
||||
// user2Token, _ := user2.NewAuthToken()
|
||||
|
||||
userReadonly, _ := beszelTests.CreateUserWithRole(hub, "userreadonly@example.com", "password", "readonly")
|
||||
userReadonlyToken, _ := userReadonly.NewAuthToken()
|
||||
|
||||
userOneSystem, _ := beszelTests.CreateRecord(hub, "systems", map[string]any{
|
||||
"name": "system1",
|
||||
"users": []string{user1.Id},
|
||||
"host": "127.0.0.1",
|
||||
})
|
||||
|
||||
sharedSystem, _ := beszelTests.CreateRecord(hub, "systems", map[string]any{
|
||||
"name": "system2",
|
||||
"users": []string{user1.Id, user2.Id},
|
||||
"host": "127.0.0.2",
|
||||
})
|
||||
|
||||
userTwoSystem, _ := beszelTests.CreateRecord(hub, "systems", map[string]any{
|
||||
"name": "system3",
|
||||
"users": []string{user2.Id},
|
||||
"host": "127.0.0.2",
|
||||
})
|
||||
|
||||
userRecords, _ := hub.CountRecords("users")
|
||||
assert.EqualValues(t, 3, userRecords, "all users should be created")
|
||||
|
||||
systemRecords, _ := hub.CountRecords("systems")
|
||||
assert.EqualValues(t, 3, systemRecords, "all systems should be created")
|
||||
|
||||
testAppFactory := func(t testing.TB) *pbTests.TestApp {
|
||||
return hub.TestApp
|
||||
}
|
||||
|
||||
scenarios := []beszelTests.ApiScenario{
|
||||
{
|
||||
Name: "Unauthorized user cannot list systems",
|
||||
Method: http.MethodGet,
|
||||
URL: "/api/collections/systems/records",
|
||||
ExpectedStatus: 200, // https://github.com/pocketbase/pocketbase/discussions/1570
|
||||
TestAppFactory: testAppFactory,
|
||||
ExpectedContent: []string{`"items":[]`, `"totalItems":0`},
|
||||
NotExpectedContent: []string{userOneSystem.Id, sharedSystem.Id, userTwoSystem.Id},
|
||||
},
|
||||
{
|
||||
Name: "Unauthorized user cannot delete a system",
|
||||
Method: http.MethodDelete,
|
||||
URL: fmt.Sprintf("/api/collections/systems/records/%s", userOneSystem.Id),
|
||||
ExpectedStatus: 404,
|
||||
TestAppFactory: testAppFactory,
|
||||
ExpectedContent: []string{"resource wasn't found"},
|
||||
NotExpectedContent: []string{userOneSystem.Id},
|
||||
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
|
||||
systemsCount, _ := app.CountRecords("systems")
|
||||
assert.EqualValues(t, 3, systemsCount, "should have 3 systems before deletion")
|
||||
},
|
||||
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
|
||||
systemsCount, _ := app.CountRecords("systems")
|
||||
assert.EqualValues(t, 3, systemsCount, "should still have 3 systems after failed deletion")
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "User 1 can list their own systems",
|
||||
Method: http.MethodGet,
|
||||
URL: "/api/collections/systems/records",
|
||||
Headers: map[string]string{
|
||||
"Authorization": user1Token,
|
||||
},
|
||||
ExpectedStatus: 200,
|
||||
ExpectedContent: []string{userOneSystem.Id, sharedSystem.Id},
|
||||
NotExpectedContent: []string{userTwoSystem.Id},
|
||||
TestAppFactory: testAppFactory,
|
||||
},
|
||||
{
|
||||
Name: "User 1 cannot list user 2's system",
|
||||
Method: http.MethodGet,
|
||||
URL: "/api/collections/systems/records",
|
||||
Headers: map[string]string{
|
||||
"Authorization": user1Token,
|
||||
},
|
||||
ExpectedStatus: 200,
|
||||
ExpectedContent: []string{userOneSystem.Id, sharedSystem.Id},
|
||||
NotExpectedContent: []string{userTwoSystem.Id},
|
||||
TestAppFactory: testAppFactory,
|
||||
},
|
||||
{
|
||||
Name: "User 1 can see user 2's system if SHARE_ALL_SYSTEMS is enabled",
|
||||
Method: http.MethodGet,
|
||||
URL: "/api/collections/systems/records",
|
||||
Headers: map[string]string{
|
||||
"Authorization": user1Token,
|
||||
},
|
||||
ExpectedStatus: 200,
|
||||
ExpectedContent: []string{userOneSystem.Id, sharedSystem.Id, userTwoSystem.Id},
|
||||
TestAppFactory: testAppFactory,
|
||||
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
|
||||
t.Setenv("SHARE_ALL_SYSTEMS", "true")
|
||||
hub.SetCollectionAuthSettings()
|
||||
},
|
||||
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
|
||||
t.Setenv("SHARE_ALL_SYSTEMS", "")
|
||||
hub.SetCollectionAuthSettings()
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "User 1 can delete their own system",
|
||||
Method: http.MethodDelete,
|
||||
URL: fmt.Sprintf("/api/collections/systems/records/%s", userOneSystem.Id),
|
||||
Headers: map[string]string{
|
||||
"Authorization": user1Token,
|
||||
},
|
||||
ExpectedStatus: 204,
|
||||
TestAppFactory: testAppFactory,
|
||||
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
|
||||
systemsCount, _ := app.CountRecords("systems")
|
||||
assert.EqualValues(t, 3, systemsCount, "should have 3 systems before deletion")
|
||||
},
|
||||
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
|
||||
systemsCount, _ := app.CountRecords("systems")
|
||||
assert.EqualValues(t, 2, systemsCount, "should have 2 systems after deletion")
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "User 1 cannot delete user 2's system",
|
||||
Method: http.MethodDelete,
|
||||
URL: fmt.Sprintf("/api/collections/systems/records/%s", userTwoSystem.Id),
|
||||
Headers: map[string]string{
|
||||
"Authorization": user1Token,
|
||||
},
|
||||
ExpectedStatus: 404,
|
||||
TestAppFactory: testAppFactory,
|
||||
ExpectedContent: []string{"resource wasn't found"},
|
||||
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
|
||||
systemsCount, _ := app.CountRecords("systems")
|
||||
assert.EqualValues(t, 2, systemsCount)
|
||||
},
|
||||
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
|
||||
systemsCount, _ := app.CountRecords("systems")
|
||||
assert.EqualValues(t, 2, systemsCount)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Readonly cannot delete a system even if SHARE_ALL_SYSTEMS is enabled",
|
||||
Method: http.MethodDelete,
|
||||
URL: fmt.Sprintf("/api/collections/systems/records/%s", sharedSystem.Id),
|
||||
Headers: map[string]string{
|
||||
"Authorization": userReadonlyToken,
|
||||
},
|
||||
ExpectedStatus: 404,
|
||||
ExpectedContent: []string{"resource wasn't found"},
|
||||
TestAppFactory: testAppFactory,
|
||||
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
|
||||
t.Setenv("SHARE_ALL_SYSTEMS", "true")
|
||||
hub.SetCollectionAuthSettings()
|
||||
systemsCount, _ := app.CountRecords("systems")
|
||||
assert.EqualValues(t, 2, systemsCount)
|
||||
},
|
||||
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
|
||||
t.Setenv("SHARE_ALL_SYSTEMS", "")
|
||||
hub.SetCollectionAuthSettings()
|
||||
systemsCount, _ := app.CountRecords("systems")
|
||||
assert.EqualValues(t, 2, systemsCount)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "User 1 can delete user 2's system if SHARE_ALL_SYSTEMS is enabled",
|
||||
Method: http.MethodDelete,
|
||||
URL: fmt.Sprintf("/api/collections/systems/records/%s", userTwoSystem.Id),
|
||||
Headers: map[string]string{
|
||||
"Authorization": user1Token,
|
||||
},
|
||||
ExpectedStatus: 204,
|
||||
TestAppFactory: testAppFactory,
|
||||
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
|
||||
t.Setenv("SHARE_ALL_SYSTEMS", "true")
|
||||
hub.SetCollectionAuthSettings()
|
||||
systemsCount, _ := app.CountRecords("systems")
|
||||
assert.EqualValues(t, 2, systemsCount)
|
||||
},
|
||||
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
|
||||
t.Setenv("SHARE_ALL_SYSTEMS", "")
|
||||
hub.SetCollectionAuthSettings()
|
||||
systemsCount, _ := app.CountRecords("systems")
|
||||
assert.EqualValues(t, 1, systemsCount)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
scenario.Test(t)
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ package hub
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -45,20 +46,17 @@ type Hub struct {
|
||||
var containerIDPattern = regexp.MustCompile(`^[a-fA-F0-9]{12,64}$`)
|
||||
|
||||
// NewHub creates a new Hub instance with default configuration
|
||||
func NewHub(app core.App) *Hub {
|
||||
hub := &Hub{}
|
||||
hub.App = app
|
||||
|
||||
func NewHub(app core.App) (*Hub, error) {
|
||||
hub := &Hub{App: app}
|
||||
hub.AlertManager = alerts.NewAlertManager(hub)
|
||||
hub.um = users.NewUserManager(hub)
|
||||
hub.rm = records.NewRecordManager(hub)
|
||||
hub.sm = systems.NewSystemManager(hub)
|
||||
hub.appURL, _ = GetEnv("APP_URL")
|
||||
hub.hb = heartbeat.New(app, GetEnv)
|
||||
if hub.hb != nil {
|
||||
hub.hbStop = make(chan struct{})
|
||||
}
|
||||
return hub
|
||||
return hub, initialize(hub)
|
||||
}
|
||||
|
||||
// GetEnv retrieves an environment variable with a "BESZEL_HUB_" prefix, or falls back to the unprefixed key.
|
||||
@@ -72,10 +70,6 @@ func GetEnv(key string) (value string, exists bool) {
|
||||
|
||||
func (h *Hub) StartHub() error {
|
||||
h.App.OnServe().BindFunc(func(e *core.ServeEvent) error {
|
||||
// initialize settings / collections
|
||||
if err := h.initialize(e); err != nil {
|
||||
return err
|
||||
}
|
||||
// sync systems with config
|
||||
if err := config.SyncSystems(e); err != nil {
|
||||
return err
|
||||
@@ -110,132 +104,32 @@ func (h *Hub) StartHub() error {
|
||||
h.App.OnRecordCreate("users").BindFunc(h.um.InitializeUserRole)
|
||||
h.App.OnRecordCreate("user_settings").BindFunc(h.um.InitializeUserSettings)
|
||||
|
||||
if pb, ok := h.App.(*pocketbase.PocketBase); ok {
|
||||
// log.Println("Starting pocketbase")
|
||||
err := pb.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pb, ok := h.App.(*pocketbase.PocketBase)
|
||||
if !ok {
|
||||
return errors.New("not a pocketbase app")
|
||||
}
|
||||
|
||||
return nil
|
||||
return pb.Start()
|
||||
}
|
||||
|
||||
// initialize sets up initial configuration (collections, settings, etc.)
|
||||
func (h *Hub) initialize(e *core.ServeEvent) error {
|
||||
// set general settings
|
||||
settings := e.App.Settings()
|
||||
// batch requests (for global alerts)
|
||||
settings.Batch.Enabled = true
|
||||
// set URL if BASE_URL env is set
|
||||
if h.appURL != "" {
|
||||
settings.Meta.AppURL = h.appURL
|
||||
func initialize(hub *Hub) error {
|
||||
if !hub.App.IsBootstrapped() {
|
||||
hub.App.Bootstrap()
|
||||
}
|
||||
if err := e.App.Save(settings); err != nil {
|
||||
// set general settings
|
||||
settings := hub.App.Settings()
|
||||
// batch requests (for alerts)
|
||||
settings.Batch.Enabled = true
|
||||
// set URL if APP_URL env is set
|
||||
if appURL, isSet := GetEnv("APP_URL"); isSet {
|
||||
hub.appURL = appURL
|
||||
settings.Meta.AppURL = hub.appURL
|
||||
}
|
||||
if err := hub.App.Save(settings); err != nil {
|
||||
return err
|
||||
}
|
||||
// set auth settings
|
||||
if err := setCollectionAuthSettings(e.App); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setCollectionAuthSettings sets up default authentication settings for the app
|
||||
func setCollectionAuthSettings(app core.App) error {
|
||||
usersCollection, err := app.FindCollectionByNameOrId("users")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
superusersCollection, err := app.FindCollectionByNameOrId(core.CollectionNameSuperusers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// disable email auth if DISABLE_PASSWORD_AUTH env var is set
|
||||
disablePasswordAuth, _ := GetEnv("DISABLE_PASSWORD_AUTH")
|
||||
usersCollection.PasswordAuth.Enabled = disablePasswordAuth != "true"
|
||||
usersCollection.PasswordAuth.IdentityFields = []string{"email"}
|
||||
// allow oauth user creation if USER_CREATION is set
|
||||
if userCreation, _ := GetEnv("USER_CREATION"); userCreation == "true" {
|
||||
cr := "@request.context = 'oauth2'"
|
||||
usersCollection.CreateRule = &cr
|
||||
} else {
|
||||
usersCollection.CreateRule = nil
|
||||
}
|
||||
|
||||
// enable mfaOtp mfa if MFA_OTP env var is set
|
||||
mfaOtp, _ := GetEnv("MFA_OTP")
|
||||
usersCollection.OTP.Length = 6
|
||||
superusersCollection.OTP.Length = 6
|
||||
usersCollection.OTP.Enabled = mfaOtp == "true"
|
||||
usersCollection.MFA.Enabled = mfaOtp == "true"
|
||||
superusersCollection.OTP.Enabled = mfaOtp == "true" || mfaOtp == "superusers"
|
||||
superusersCollection.MFA.Enabled = mfaOtp == "true" || mfaOtp == "superusers"
|
||||
if err := app.Save(superusersCollection); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := app.Save(usersCollection); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shareAllSystems, _ := GetEnv("SHARE_ALL_SYSTEMS")
|
||||
|
||||
// allow all users to access systems if SHARE_ALL_SYSTEMS is set
|
||||
systemsCollection, err := app.FindCollectionByNameOrId("systems")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var systemsReadRule string
|
||||
if shareAllSystems == "true" {
|
||||
systemsReadRule = "@request.auth.id != \"\""
|
||||
} else {
|
||||
systemsReadRule = "@request.auth.id != \"\" && users.id ?= @request.auth.id"
|
||||
}
|
||||
updateDeleteRule := systemsReadRule + " && @request.auth.role != \"readonly\""
|
||||
systemsCollection.ListRule = &systemsReadRule
|
||||
systemsCollection.ViewRule = &systemsReadRule
|
||||
systemsCollection.UpdateRule = &updateDeleteRule
|
||||
systemsCollection.DeleteRule = &updateDeleteRule
|
||||
if err := app.Save(systemsCollection); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// allow all users to access all containers if SHARE_ALL_SYSTEMS is set
|
||||
containersCollection, err := app.FindCollectionByNameOrId("containers")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
containersListRule := strings.Replace(systemsReadRule, "users.id", "system.users.id", 1)
|
||||
containersCollection.ListRule = &containersListRule
|
||||
if err := app.Save(containersCollection); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// allow all users to access system-related collections if SHARE_ALL_SYSTEMS is set
|
||||
// these collections all have a "system" relation field
|
||||
systemRelatedCollections := []string{"system_details", "smart_devices", "systemd_services"}
|
||||
for _, collectionName := range systemRelatedCollections {
|
||||
collection, err := app.FindCollectionByNameOrId(collectionName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
collection.ListRule = &containersListRule
|
||||
// set viewRule for collections that need it (system_details, smart_devices)
|
||||
if collection.ViewRule != nil {
|
||||
collection.ViewRule = &containersListRule
|
||||
}
|
||||
// set deleteRule for smart_devices (allows user to dismiss disk warnings)
|
||||
if collectionName == "smart_devices" {
|
||||
deleteRule := containersListRule + " && @request.auth.role != \"readonly\""
|
||||
collection.DeleteRule = &deleteRule
|
||||
}
|
||||
if err := app.Save(collection); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return setCollectionAuthSettings(hub.App)
|
||||
}
|
||||
|
||||
// registerCronJobs sets up scheduled tasks
|
||||
|
||||
@@ -961,3 +961,21 @@ func TestTrustedHeaderMiddleware(t *testing.T) {
|
||||
scenario.Test(t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppUrl(t *testing.T) {
|
||||
t.Run("no APP_URL does't change app url", func(t *testing.T) {
|
||||
hub, _ := beszelTests.NewTestHub(t.TempDir())
|
||||
defer hub.Cleanup()
|
||||
|
||||
settings := hub.Settings()
|
||||
assert.Equal(t, "http://localhost:8090", settings.Meta.AppURL)
|
||||
})
|
||||
t.Run("APP_URL changes app url", func(t *testing.T) {
|
||||
t.Setenv("APP_URL", "http://example.com/app")
|
||||
hub, _ := beszelTests.NewTestHub(t.TempDir())
|
||||
defer hub.Cleanup()
|
||||
|
||||
settings := hub.Settings()
|
||||
assert.Equal(t, "http://example.com/app", settings.Meta.AppURL)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
package hub
|
||||
|
||||
import "github.com/henrygd/beszel/internal/hub/systems"
|
||||
import (
|
||||
"github.com/henrygd/beszel/internal/hub/systems"
|
||||
)
|
||||
|
||||
// TESTING ONLY: GetSystemManager returns the system manager
|
||||
func (h *Hub) GetSystemManager() *systems.SystemManager {
|
||||
@@ -18,3 +20,7 @@ func (h *Hub) GetPubkey() string {
|
||||
func (h *Hub) SetPubkey(pubkey string) {
|
||||
h.pubKey = pubkey
|
||||
}
|
||||
|
||||
func (h *Hub) SetCollectionAuthSettings() error {
|
||||
return setCollectionAuthSettings(h)
|
||||
}
|
||||
|
||||
@@ -11,11 +11,11 @@ func init() {
|
||||
jsonData := `[
|
||||
{
|
||||
"id": "elngm8x1l60zi2v",
|
||||
"listRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
|
||||
"viewRule": "",
|
||||
"createRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
|
||||
"updateRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
|
||||
"deleteRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
|
||||
"listRule": "@request.auth.id != \"\" && user = @request.auth.id",
|
||||
"viewRule": null,
|
||||
"createRule": "@request.auth.id != \"\" && user = @request.auth.id",
|
||||
"updateRule": "@request.auth.id != \"\" && user = @request.auth.id",
|
||||
"deleteRule": "@request.auth.id != \"\" && user = @request.auth.id",
|
||||
"name": "alerts",
|
||||
"type": "base",
|
||||
"fields": [
|
||||
@@ -143,11 +143,11 @@ func init() {
|
||||
},
|
||||
{
|
||||
"id": "pbc_1697146157",
|
||||
"listRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
|
||||
"viewRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
|
||||
"listRule": "@request.auth.id != \"\" && user = @request.auth.id",
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
|
||||
"deleteRule": "@request.auth.id != \"\" && user = @request.auth.id",
|
||||
"name": "alerts_history",
|
||||
"type": "base",
|
||||
"fields": [
|
||||
@@ -261,7 +261,7 @@ func init() {
|
||||
},
|
||||
{
|
||||
"id": "juohu4jipgc13v7",
|
||||
"listRule": "@request.auth.id != \"\"",
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
@@ -351,10 +351,10 @@ func init() {
|
||||
},
|
||||
{
|
||||
"id": "pbc_3663931638",
|
||||
"listRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id",
|
||||
"viewRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id",
|
||||
"createRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id && @request.auth.role != \"readonly\"",
|
||||
"updateRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id && @request.auth.role != \"readonly\"",
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"name": "fingerprints",
|
||||
"type": "base",
|
||||
@@ -433,7 +433,7 @@ func init() {
|
||||
},
|
||||
{
|
||||
"id": "ej9oowivz8b2mht",
|
||||
"listRule": "@request.auth.id != \"\"",
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
@@ -523,10 +523,10 @@ func init() {
|
||||
},
|
||||
{
|
||||
"id": "4afacsdnlu8q8r2",
|
||||
"listRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
|
||||
"listRule": "@request.auth.id != \"\" && user = @request.auth.id",
|
||||
"viewRule": null,
|
||||
"createRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
|
||||
"updateRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
|
||||
"createRule": "@request.auth.id != \"\" && user = @request.auth.id",
|
||||
"updateRule": "@request.auth.id != \"\" && user = @request.auth.id",
|
||||
"deleteRule": null,
|
||||
"name": "user_settings",
|
||||
"type": "base",
|
||||
@@ -596,11 +596,11 @@ func init() {
|
||||
},
|
||||
{
|
||||
"id": "2hz5ncl8tizk5nx",
|
||||
"listRule": "@request.auth.id != \"\" && users.id ?= @request.auth.id",
|
||||
"viewRule": "@request.auth.id != \"\" && users.id ?= @request.auth.id",
|
||||
"createRule": "@request.auth.id != \"\" && users.id ?= @request.auth.id && @request.auth.role != \"readonly\"",
|
||||
"updateRule": "@request.auth.id != \"\" && users.id ?= @request.auth.id && @request.auth.role != \"readonly\"",
|
||||
"deleteRule": "@request.auth.id != \"\" && users.id ?= @request.auth.id && @request.auth.role != \"readonly\"",
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"name": "systems",
|
||||
"type": "base",
|
||||
"fields": [
|
||||
@@ -866,7 +866,7 @@ func init() {
|
||||
},
|
||||
{
|
||||
"id": "pbc_1864144027",
|
||||
"listRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id",
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
@@ -1159,7 +1159,7 @@ func init() {
|
||||
"CREATE INDEX ` + "`" + `idx_4Z7LuLNdQb` + "`" + ` ON ` + "`" + `systemd_services` + "`" + ` (` + "`" + `system` + "`" + `)",
|
||||
"CREATE INDEX ` + "`" + `idx_pBp1fF837e` + "`" + ` ON ` + "`" + `systemd_services` + "`" + ` (` + "`" + `updated` + "`" + `)"
|
||||
],
|
||||
"listRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id",
|
||||
"listRule": null,
|
||||
"name": "systemd_services",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
@@ -1167,8 +1167,8 @@ func init() {
|
||||
"viewRule": null
|
||||
},
|
||||
{
|
||||
"createRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
|
||||
"deleteRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
|
||||
"createRule": "@request.auth.id != \"\" && user = @request.auth.id",
|
||||
"deleteRule": "@request.auth.id != \"\" && user = @request.auth.id",
|
||||
"fields": [
|
||||
{
|
||||
"autogeneratePattern": "[a-z0-9]{10}",
|
||||
@@ -1252,16 +1252,16 @@ func init() {
|
||||
"CREATE INDEX ` + "`" + `idx_q0iKnRP9v8` + "`" + ` ON ` + "`" + `quiet_hours` + "`" + ` (\n ` + "`" + `user` + "`" + `,\n ` + "`" + `system` + "`" + `\n)",
|
||||
"CREATE INDEX ` + "`" + `idx_6T7ljT7FJd` + "`" + ` ON ` + "`" + `quiet_hours` + "`" + ` (\n ` + "`" + `type` + "`" + `,\n ` + "`" + `end` + "`" + `\n)"
|
||||
],
|
||||
"listRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
|
||||
"listRule": "@request.auth.id != \"\" && user = @request.auth.id",
|
||||
"name": "quiet_hours",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
|
||||
"viewRule": "@request.auth.id != \"\" && user.id = @request.auth.id"
|
||||
"updateRule": "@request.auth.id != \"\" && user = @request.auth.id",
|
||||
"viewRule": "@request.auth.id != \"\" && user = @request.auth.id"
|
||||
},
|
||||
{
|
||||
"createRule": null,
|
||||
"deleteRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id",
|
||||
"deleteRule": null,
|
||||
"fields": [
|
||||
{
|
||||
"autogeneratePattern": "[a-z0-9]{10}",
|
||||
@@ -1447,16 +1447,16 @@ func init() {
|
||||
"indexes": [
|
||||
"CREATE INDEX ` + "`" + `idx_DZ9yhvgl44` + "`" + ` ON ` + "`" + `smart_devices` + "`" + ` (` + "`" + `system` + "`" + `)"
|
||||
],
|
||||
"listRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id",
|
||||
"listRule": null,
|
||||
"name": "smart_devices",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": null,
|
||||
"viewRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id"
|
||||
"viewRule": null
|
||||
},
|
||||
{
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"createRule": null,
|
||||
"deleteRule": null,
|
||||
"fields": [
|
||||
{
|
||||
"autogeneratePattern": "[a-z0-9]{15}",
|
||||
@@ -1625,12 +1625,12 @@ func init() {
|
||||
],
|
||||
"id": "pbc_3116237454",
|
||||
"indexes": [],
|
||||
"listRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id",
|
||||
"name": "system_details",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id"
|
||||
"updateRule": null,
|
||||
"listRule": null,
|
||||
"viewRule": null
|
||||
},
|
||||
{
|
||||
"createRule": null,
|
||||
|
||||
@@ -52,7 +52,10 @@ func NewTestHubWithConfig(config core.BaseAppConfig) (*TestHub, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hub := hub.NewHub(testApp)
|
||||
hub, err := hub.NewHub(testApp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t := &TestHub{
|
||||
App: testApp,
|
||||
@@ -77,6 +80,16 @@ func CreateUser(app core.App, email string, password string) (*core.Record, erro
|
||||
return user, app.Save(user)
|
||||
}
|
||||
|
||||
func CreateUserWithRole(app core.App, email string, password string, roleName string) (*core.Record, error) {
|
||||
user, err := CreateUser(app, email, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user.Set("role", roleName)
|
||||
return user, app.Save(user)
|
||||
}
|
||||
|
||||
// Helper function to create a test record
|
||||
func CreateRecord(app core.App, collectionName string, fields map[string]any) (*core.Record, error) {
|
||||
collection, err := app.FindCachedCollectionByNameOrId(collectionName)
|
||||
|
||||
Reference in New Issue
Block a user