diff --git a/user/manager_test.go b/user/manager_test.go index e59e3cff..4b929d39 100644 --- a/user/manager_test.go +++ b/user/manager_test.go @@ -4,6 +4,8 @@ import ( "database/sql" "fmt" "net/netip" + "net/url" + "os" "path/filepath" "strings" "testing" @@ -16,139 +18,188 @@ import ( const minBcryptTimingMillis = int64(40) // Ideally should be >100ms, but this should also run on a Raspberry Pi without massive resources +// newStoreFunc creates a Store. Calling it multiple times within the same test +// returns a new Store object pointing at the same underlying data (same SQLite +// file / same PostgreSQL schema), enabling close-and-reopen tests. +type newStoreFunc func() Store + +func forEachBackend(t *testing.T, f func(t *testing.T, newStore newStoreFunc)) { + t.Run("sqlite", func(t *testing.T) { + dir := t.TempDir() + f(t, func() Store { + store, err := NewSQLiteStore(filepath.Join(dir, "user.db"), "") + require.Nil(t, err) + return store + }) + }) + t.Run("postgres", func(t *testing.T) { + dsn := os.Getenv("NTFY_TEST_DATABASE_URL") + if dsn == "" { + t.Skip("NTFY_TEST_DATABASE_URL not set") + } + schema := fmt.Sprintf("test_%s", util.RandomString(10)) + setupDB, err := sql.Open("pgx", dsn) + require.Nil(t, err) + _, err = setupDB.Exec(fmt.Sprintf("CREATE SCHEMA %s", schema)) + require.Nil(t, err) + require.Nil(t, setupDB.Close()) + u, err := url.Parse(dsn) + require.Nil(t, err) + q := u.Query() + q.Set("search_path", schema) + u.RawQuery = q.Encode() + schemaDSN := u.String() + t.Cleanup(func() { + cleanDB, _ := sql.Open("pgx", dsn) + cleanDB.Exec(fmt.Sprintf("DROP SCHEMA %s CASCADE", schema)) + cleanDB.Close() + }) + f(t, func() Store { + store, err := NewPostgresStore(schemaDSN) + require.Nil(t, err) + return store + }) + }) +} + func TestManager_FullScenario_Default_DenyAll(t *testing.T) { - a := newTestManagerFromFile(t, filepath.Join(t.TempDir(), "user.db"), "", PermissionDenyAll, DefaultUserPasswordBcryptCost, DefaultUserStatsQueueWriterInterval) - require.Nil(t, a.AddUser("phil", "phil", RoleAdmin, false)) - require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) - require.Nil(t, a.AddUser("john", "john", RoleUser, false)) - require.Nil(t, a.AllowAccess("ben", "mytopic", PermissionReadWrite)) - require.Nil(t, a.AllowAccess("ben", "readme", PermissionRead)) - require.Nil(t, a.AllowAccess("ben", "writeme", PermissionWrite)) - require.Nil(t, a.AllowAccess("ben", "everyonewrite", PermissionDenyAll)) // How unfair! - require.Nil(t, a.AllowAccess("john", "*", PermissionRead)) - require.Nil(t, a.AllowAccess("john", "mytopic*", PermissionReadWrite)) - require.Nil(t, a.AllowAccess("john", "mytopic_ro*", PermissionRead)) - require.Nil(t, a.AllowAccess("john", "mytopic_deny*", PermissionDenyAll)) - require.Nil(t, a.AllowAccess(Everyone, "announcements", PermissionRead)) - require.Nil(t, a.AllowAccess(Everyone, "everyonewrite", PermissionReadWrite)) - require.Nil(t, a.AllowAccess(Everyone, "up*", PermissionWrite)) // Everyone can write to /up* + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) + require.Nil(t, a.AddUser("phil", "phil", RoleAdmin, false)) + require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) + require.Nil(t, a.AddUser("john", "john", RoleUser, false)) + require.Nil(t, a.AllowAccess("ben", "mytopic", PermissionReadWrite)) + require.Nil(t, a.AllowAccess("ben", "readme", PermissionRead)) + require.Nil(t, a.AllowAccess("ben", "writeme", PermissionWrite)) + require.Nil(t, a.AllowAccess("ben", "everyonewrite", PermissionDenyAll)) // How unfair! + require.Nil(t, a.AllowAccess("john", "*", PermissionRead)) + require.Nil(t, a.AllowAccess("john", "mytopic*", PermissionReadWrite)) + require.Nil(t, a.AllowAccess("john", "mytopic_ro*", PermissionRead)) + require.Nil(t, a.AllowAccess("john", "mytopic_deny*", PermissionDenyAll)) + require.Nil(t, a.AllowAccess(Everyone, "announcements", PermissionRead)) + require.Nil(t, a.AllowAccess(Everyone, "everyonewrite", PermissionReadWrite)) + require.Nil(t, a.AllowAccess(Everyone, "up*", PermissionWrite)) // Everyone can write to /up* - phil, err := a.Authenticate("phil", "phil") - require.Nil(t, err) - require.Equal(t, "phil", phil.Name) - require.True(t, strings.HasPrefix(phil.Hash, "$2a$10$")) - require.Equal(t, RoleAdmin, phil.Role) + phil, err := a.Authenticate("phil", "phil") + require.Nil(t, err) + require.Equal(t, "phil", phil.Name) + require.True(t, strings.HasPrefix(phil.Hash, "$2a$04$")) + require.Equal(t, RoleAdmin, phil.Role) - philGrants, err := a.Grants("phil") - require.Nil(t, err) - require.Equal(t, []Grant{}, philGrants) + philGrants, err := a.Grants("phil") + require.Nil(t, err) + require.Equal(t, []Grant{}, philGrants) - ben, err := a.Authenticate("ben", "ben") - require.Nil(t, err) - require.Equal(t, "ben", ben.Name) - require.True(t, strings.HasPrefix(ben.Hash, "$2a$10$")) - require.Equal(t, RoleUser, ben.Role) + ben, err := a.Authenticate("ben", "ben") + require.Nil(t, err) + require.Equal(t, "ben", ben.Name) + require.True(t, strings.HasPrefix(ben.Hash, "$2a$04$")) + require.Equal(t, RoleUser, ben.Role) - benGrants, err := a.Grants("ben") - require.Nil(t, err) - require.Equal(t, []Grant{ - {"everyonewrite", PermissionDenyAll, false}, - {"mytopic", PermissionReadWrite, false}, - {"writeme", PermissionWrite, false}, - {"readme", PermissionRead, false}, - }, benGrants) + benGrants, err := a.Grants("ben") + require.Nil(t, err) + require.Equal(t, []Grant{ + {"everyonewrite", PermissionDenyAll, false}, + {"mytopic", PermissionReadWrite, false}, + {"writeme", PermissionWrite, false}, + {"readme", PermissionRead, false}, + }, benGrants) - john, err := a.Authenticate("john", "john") - require.Nil(t, err) - require.Equal(t, "john", john.Name) - require.True(t, strings.HasPrefix(john.Hash, "$2a$10$")) - require.Equal(t, RoleUser, john.Role) + john, err := a.Authenticate("john", "john") + require.Nil(t, err) + require.Equal(t, "john", john.Name) + require.True(t, strings.HasPrefix(john.Hash, "$2a$04$")) + require.Equal(t, RoleUser, john.Role) - johnGrants, err := a.Grants("john") - require.Nil(t, err) - require.Equal(t, []Grant{ - {"mytopic_deny*", PermissionDenyAll, false}, - {"mytopic_ro*", PermissionRead, false}, - {"mytopic*", PermissionReadWrite, false}, - {"*", PermissionRead, false}, - }, johnGrants) + johnGrants, err := a.Grants("john") + require.Nil(t, err) + require.Equal(t, []Grant{ + {"mytopic_deny*", PermissionDenyAll, false}, + {"mytopic_ro*", PermissionRead, false}, + {"mytopic*", PermissionReadWrite, false}, + {"*", PermissionRead, false}, + }, johnGrants) - notben, err := a.Authenticate("ben", "this is wrong") - require.Nil(t, notben) - require.Equal(t, ErrUnauthenticated, err) + notben, err := a.Authenticate("ben", "this is wrong") + require.Nil(t, notben) + require.Equal(t, ErrUnauthenticated, err) - // Admin can do everything - require.Nil(t, a.Authorize(phil, "sometopic", PermissionWrite)) - require.Nil(t, a.Authorize(phil, "mytopic", PermissionRead)) - require.Nil(t, a.Authorize(phil, "readme", PermissionWrite)) - require.Nil(t, a.Authorize(phil, "writeme", PermissionWrite)) - require.Nil(t, a.Authorize(phil, "announcements", PermissionWrite)) - require.Nil(t, a.Authorize(phil, "everyonewrite", PermissionWrite)) + // Admin can do everything + require.Nil(t, a.Authorize(phil, "sometopic", PermissionWrite)) + require.Nil(t, a.Authorize(phil, "mytopic", PermissionRead)) + require.Nil(t, a.Authorize(phil, "readme", PermissionWrite)) + require.Nil(t, a.Authorize(phil, "writeme", PermissionWrite)) + require.Nil(t, a.Authorize(phil, "announcements", PermissionWrite)) + require.Nil(t, a.Authorize(phil, "everyonewrite", PermissionWrite)) - // User cannot do everything - require.Nil(t, a.Authorize(ben, "mytopic", PermissionWrite)) - require.Nil(t, a.Authorize(ben, "mytopic", PermissionRead)) - require.Nil(t, a.Authorize(ben, "readme", PermissionRead)) - require.Equal(t, ErrUnauthorized, a.Authorize(ben, "readme", PermissionWrite)) - require.Equal(t, ErrUnauthorized, a.Authorize(ben, "writeme", PermissionRead)) - require.Nil(t, a.Authorize(ben, "writeme", PermissionWrite)) - require.Nil(t, a.Authorize(ben, "writeme", PermissionWrite)) - require.Equal(t, ErrUnauthorized, a.Authorize(ben, "everyonewrite", PermissionRead)) - require.Equal(t, ErrUnauthorized, a.Authorize(ben, "everyonewrite", PermissionWrite)) - require.Nil(t, a.Authorize(ben, "announcements", PermissionRead)) - require.Equal(t, ErrUnauthorized, a.Authorize(ben, "announcements", PermissionWrite)) + // User cannot do everything + require.Nil(t, a.Authorize(ben, "mytopic", PermissionWrite)) + require.Nil(t, a.Authorize(ben, "mytopic", PermissionRead)) + require.Nil(t, a.Authorize(ben, "readme", PermissionRead)) + require.Equal(t, ErrUnauthorized, a.Authorize(ben, "readme", PermissionWrite)) + require.Equal(t, ErrUnauthorized, a.Authorize(ben, "writeme", PermissionRead)) + require.Nil(t, a.Authorize(ben, "writeme", PermissionWrite)) + require.Nil(t, a.Authorize(ben, "writeme", PermissionWrite)) + require.Equal(t, ErrUnauthorized, a.Authorize(ben, "everyonewrite", PermissionRead)) + require.Equal(t, ErrUnauthorized, a.Authorize(ben, "everyonewrite", PermissionWrite)) + require.Nil(t, a.Authorize(ben, "announcements", PermissionRead)) + require.Equal(t, ErrUnauthorized, a.Authorize(ben, "announcements", PermissionWrite)) - // User john should have - // "deny" to mytopic_deny*, - // "ro" to mytopic_ro*, - // "rw" to mytopic*, - // "ro" to the rest - require.Equal(t, ErrUnauthorized, a.Authorize(john, "mytopic_deny_case", PermissionRead)) - require.Equal(t, ErrUnauthorized, a.Authorize(john, "mytopic_deny_case", PermissionWrite)) - require.Nil(t, a.Authorize(john, "mytopic_ro_test_case", PermissionRead)) - require.Equal(t, ErrUnauthorized, a.Authorize(john, "mytopic_ro_test_case", PermissionWrite)) - require.Nil(t, a.Authorize(john, "mytopic_case1", PermissionRead)) - require.Nil(t, a.Authorize(john, "mytopic_case1", PermissionWrite)) - require.Nil(t, a.Authorize(john, "readme", PermissionRead)) - require.Equal(t, ErrUnauthorized, a.Authorize(john, "writeme", PermissionWrite)) + // User john should have + // "deny" to mytopic_deny*, + // "ro" to mytopic_ro*, + // "rw" to mytopic*, + // "ro" to the rest + require.Equal(t, ErrUnauthorized, a.Authorize(john, "mytopic_deny_case", PermissionRead)) + require.Equal(t, ErrUnauthorized, a.Authorize(john, "mytopic_deny_case", PermissionWrite)) + require.Nil(t, a.Authorize(john, "mytopic_ro_test_case", PermissionRead)) + require.Equal(t, ErrUnauthorized, a.Authorize(john, "mytopic_ro_test_case", PermissionWrite)) + require.Nil(t, a.Authorize(john, "mytopic_case1", PermissionRead)) + require.Nil(t, a.Authorize(john, "mytopic_case1", PermissionWrite)) + require.Nil(t, a.Authorize(john, "readme", PermissionRead)) + require.Equal(t, ErrUnauthorized, a.Authorize(john, "writeme", PermissionWrite)) - // Everyone else can do barely anything - require.Equal(t, ErrUnauthorized, a.Authorize(nil, "sometopicnotinthelist", PermissionRead)) - require.Equal(t, ErrUnauthorized, a.Authorize(nil, "sometopicnotinthelist", PermissionWrite)) - require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopic", PermissionRead)) - require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopic", PermissionWrite)) - require.Equal(t, ErrUnauthorized, a.Authorize(nil, "readme", PermissionRead)) - require.Equal(t, ErrUnauthorized, a.Authorize(nil, "readme", PermissionWrite)) - require.Equal(t, ErrUnauthorized, a.Authorize(nil, "writeme", PermissionRead)) - require.Equal(t, ErrUnauthorized, a.Authorize(nil, "writeme", PermissionWrite)) - require.Equal(t, ErrUnauthorized, a.Authorize(nil, "announcements", PermissionWrite)) - require.Nil(t, a.Authorize(nil, "announcements", PermissionRead)) - require.Nil(t, a.Authorize(nil, "everyonewrite", PermissionRead)) - require.Nil(t, a.Authorize(nil, "everyonewrite", PermissionWrite)) - require.Nil(t, a.Authorize(nil, "up1234", PermissionWrite)) // Wildcard permission - require.Nil(t, a.Authorize(nil, "up5678", PermissionWrite)) + // Everyone else can do barely anything + require.Equal(t, ErrUnauthorized, a.Authorize(nil, "sometopicnotinthelist", PermissionRead)) + require.Equal(t, ErrUnauthorized, a.Authorize(nil, "sometopicnotinthelist", PermissionWrite)) + require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopic", PermissionRead)) + require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopic", PermissionWrite)) + require.Equal(t, ErrUnauthorized, a.Authorize(nil, "readme", PermissionRead)) + require.Equal(t, ErrUnauthorized, a.Authorize(nil, "readme", PermissionWrite)) + require.Equal(t, ErrUnauthorized, a.Authorize(nil, "writeme", PermissionRead)) + require.Equal(t, ErrUnauthorized, a.Authorize(nil, "writeme", PermissionWrite)) + require.Equal(t, ErrUnauthorized, a.Authorize(nil, "announcements", PermissionWrite)) + require.Nil(t, a.Authorize(nil, "announcements", PermissionRead)) + require.Nil(t, a.Authorize(nil, "everyonewrite", PermissionRead)) + require.Nil(t, a.Authorize(nil, "everyonewrite", PermissionWrite)) + require.Nil(t, a.Authorize(nil, "up1234", PermissionWrite)) // Wildcard permission + require.Nil(t, a.Authorize(nil, "up5678", PermissionWrite)) + }) } func TestManager_Access_Order_LengthWriteRead(t *testing.T) { // This test validates issue #914 / #917, i.e. that write permissions are prioritized over read permissions, // and longer ACL rules are prioritized as well. + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) + require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) + require.Nil(t, a.AllowAccess("ben", "test*", PermissionReadWrite)) + require.Nil(t, a.AllowAccess("ben", "*", PermissionRead)) - a := newTestManagerFromFile(t, filepath.Join(t.TempDir(), "user.db"), "", PermissionDenyAll, DefaultUserPasswordBcryptCost, DefaultUserStatsQueueWriterInterval) - require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) - require.Nil(t, a.AllowAccess("ben", "test*", PermissionReadWrite)) - require.Nil(t, a.AllowAccess("ben", "*", PermissionRead)) - - ben, err := a.Authenticate("ben", "ben") - require.Nil(t, err) - require.Nil(t, a.Authorize(ben, "any-topic-can-be-read", PermissionRead)) - require.Nil(t, a.Authorize(ben, "this-too", PermissionRead)) - require.Nil(t, a.Authorize(ben, "test123", PermissionWrite)) + ben, err := a.Authenticate("ben", "ben") + require.Nil(t, err) + require.Nil(t, a.Authorize(ben, "any-topic-can-be-read", PermissionRead)) + require.Nil(t, a.Authorize(ben, "this-too", PermissionRead)) + require.Nil(t, a.Authorize(ben, "test123", PermissionWrite)) + }) } func TestManager_AddUser_Invalid(t *testing.T) { - a := newTestManager(t, PermissionDenyAll) - require.Equal(t, ErrInvalidArgument, a.AddUser(" invalid ", "pass", RoleAdmin, false)) - require.Equal(t, ErrInvalidArgument, a.AddUser("validuser", "pass", "invalid-role", false)) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) + require.Equal(t, ErrInvalidArgument, a.AddUser(" invalid ", "pass", RoleAdmin, false)) + require.Equal(t, ErrInvalidArgument, a.AddUser("validuser", "pass", "invalid-role", false)) + }) } func TestManager_AddUser_Timing(t *testing.T) { @@ -159,1157 +210,1206 @@ func TestManager_AddUser_Timing(t *testing.T) { } func TestManager_AddUser_And_Query(t *testing.T) { - a := newTestManagerFromFile(t, filepath.Join(t.TempDir(), "user.db"), "", PermissionDenyAll, DefaultUserPasswordBcryptCost, DefaultUserStatsQueueWriterInterval) - require.Nil(t, a.AddUser("user", "pass", RoleAdmin, false)) - require.Nil(t, a.ChangeBilling("user", &Billing{ - StripeCustomerID: "acct_123", - StripeSubscriptionID: "sub_123", - StripeSubscriptionStatus: "active", - StripeSubscriptionInterval: "month", - StripeSubscriptionPaidUntil: time.Now().Add(time.Hour), - StripeSubscriptionCancelAt: time.Unix(0, 0), - })) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) + require.Nil(t, a.AddUser("user", "pass", RoleAdmin, false)) + require.Nil(t, a.ChangeBilling("user", &Billing{ + StripeCustomerID: "acct_123", + StripeSubscriptionID: "sub_123", + StripeSubscriptionStatus: "active", + StripeSubscriptionInterval: "month", + StripeSubscriptionPaidUntil: time.Now().Add(time.Hour), + StripeSubscriptionCancelAt: time.Unix(0, 0), + })) - u, err := a.User("user") - require.Nil(t, err) - require.Equal(t, "user", u.Name) + u, err := a.User("user") + require.Nil(t, err) + require.Equal(t, "user", u.Name) - u2, err := a.UserByID(u.ID) - require.Nil(t, err) - require.Equal(t, u.Name, u2.Name) + u2, err := a.UserByID(u.ID) + require.Nil(t, err) + require.Equal(t, u.Name, u2.Name) - u3, err := a.UserByStripeCustomer("acct_123") - require.Nil(t, err) - require.Equal(t, u.ID, u3.ID) + u3, err := a.UserByStripeCustomer("acct_123") + require.Nil(t, err) + require.Equal(t, u.ID, u3.ID) + }) } func TestManager_MarkUserRemoved_RemoveDeletedUsers(t *testing.T) { - a := newTestManager(t, PermissionDenyAll) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) - // Create user, add reservations and token - require.Nil(t, a.AddUser("user", "pass", RoleAdmin, false)) - require.Nil(t, a.AddReservation("user", "mytopic", PermissionRead)) + // Create user, add reservations and token + require.Nil(t, a.AddUser("user", "pass", RoleAdmin, false)) + require.Nil(t, a.AddReservation("user", "mytopic", PermissionRead)) - u, err := a.User("user") - require.Nil(t, err) - require.False(t, u.Deleted) + u, err := a.User("user") + require.Nil(t, err) + require.False(t, u.Deleted) - token, err := a.CreateToken(u.ID, "", time.Now().Add(time.Hour), netip.IPv4Unspecified(), false) - require.Nil(t, err) + token, err := a.CreateToken(u.ID, "", time.Now().Add(time.Hour), netip.IPv4Unspecified(), false) + require.Nil(t, err) - u, err = a.Authenticate("user", "pass") - require.Nil(t, err) + u, err = a.Authenticate("user", "pass") + require.Nil(t, err) - _, err = a.AuthenticateToken(token.Value) - require.Nil(t, err) + _, err = a.AuthenticateToken(token.Value) + require.Nil(t, err) - reservations, err := a.Reservations("user") - require.Nil(t, err) - require.Equal(t, 1, len(reservations)) + reservations, err := a.Reservations("user") + require.Nil(t, err) + require.Equal(t, 1, len(reservations)) - // Mark deleted: cannot auth anymore, and all reservations are gone - require.Nil(t, a.MarkUserRemoved(u)) + // Mark deleted: cannot auth anymore, and all reservations are gone + require.Nil(t, a.MarkUserRemoved(u)) - _, err = a.Authenticate("user", "pass") - require.Equal(t, ErrUnauthenticated, err) + _, err = a.Authenticate("user", "pass") + require.Equal(t, ErrUnauthenticated, err) - _, err = a.AuthenticateToken(token.Value) - require.Equal(t, ErrUnauthenticated, err) + _, err = a.AuthenticateToken(token.Value) + require.Equal(t, ErrUnauthenticated, err) - reservations, err = a.Reservations("user") - require.Nil(t, err) - require.Equal(t, 0, len(reservations)) + reservations, err = a.Reservations("user") + require.Nil(t, err) + require.Equal(t, 0, len(reservations)) - // Make sure user is still there - u, err = a.User("user") - require.Nil(t, err) - require.True(t, u.Deleted) + // Make sure user is still there + u, err = a.User("user") + require.Nil(t, err) + require.True(t, u.Deleted) - _, err = testDB(a).Exec("UPDATE user SET deleted = ? WHERE id = ?", time.Now().Add(-1*(userHardDeleteAfterDuration+time.Hour)).Unix(), u.ID) - require.Nil(t, err) - require.Nil(t, a.RemoveDeletedUsers()) + // Backdate the deleted timestamp so RemoveDeletedUsers will prune the user + q := a.store.(*commonStore).queries.updateUserDeleted + _, err = testDB(a).Exec(q, time.Now().Add(-1*(userHardDeleteAfterDuration+time.Hour)).Unix(), u.ID) + require.Nil(t, err) + require.Nil(t, a.RemoveDeletedUsers()) - _, err = a.User("user") - require.Equal(t, ErrUserNotFound, err) + _, err = a.User("user") + require.Equal(t, ErrUserNotFound, err) + }) } func TestManager_CreateToken_Only_Lower(t *testing.T) { - a := newTestManager(t, PermissionDenyAll) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) - // Create user, add reservations and token - require.Nil(t, a.AddUser("user", "pass", RoleAdmin, false)) - u, err := a.User("user") - require.Nil(t, err) + // Create user, add reservations and token + require.Nil(t, a.AddUser("user", "pass", RoleAdmin, false)) + u, err := a.User("user") + require.Nil(t, err) - token, err := a.CreateToken(u.ID, "", time.Now().Add(time.Hour), netip.IPv4Unspecified(), false) - require.Nil(t, err) - require.Equal(t, token.Value, strings.ToLower(token.Value)) + token, err := a.CreateToken(u.ID, "", time.Now().Add(time.Hour), netip.IPv4Unspecified(), false) + require.Nil(t, err) + require.Equal(t, token.Value, strings.ToLower(token.Value)) + }) } func TestManager_UserManagement(t *testing.T) { - a := newTestManager(t, PermissionDenyAll) - require.Nil(t, a.AddUser("phil", "phil", RoleAdmin, false)) - require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) - require.Nil(t, a.AllowAccess("ben", "mytopic", PermissionReadWrite)) - require.Nil(t, a.AllowAccess("ben", "readme", PermissionRead)) - require.Nil(t, a.AllowAccess("ben", "writeme", PermissionWrite)) - require.Nil(t, a.AllowAccess("ben", "everyonewrite", PermissionDenyAll)) // How unfair! - require.Nil(t, a.AllowAccess(Everyone, "announcements", PermissionRead)) - require.Nil(t, a.AllowAccess(Everyone, "everyonewrite", PermissionReadWrite)) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) + require.Nil(t, a.AddUser("phil", "phil", RoleAdmin, false)) + require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) + require.Nil(t, a.AllowAccess("ben", "mytopic", PermissionReadWrite)) + require.Nil(t, a.AllowAccess("ben", "readme", PermissionRead)) + require.Nil(t, a.AllowAccess("ben", "writeme", PermissionWrite)) + require.Nil(t, a.AllowAccess("ben", "everyonewrite", PermissionDenyAll)) // How unfair! + require.Nil(t, a.AllowAccess(Everyone, "announcements", PermissionRead)) + require.Nil(t, a.AllowAccess(Everyone, "everyonewrite", PermissionReadWrite)) - // Query user details - phil, err := a.User("phil") - require.Nil(t, err) - require.Equal(t, "phil", phil.Name) - require.True(t, strings.HasPrefix(phil.Hash, "$2a$04$")) // Min cost for testing - require.Equal(t, RoleAdmin, phil.Role) + // Query user details + phil, err := a.User("phil") + require.Nil(t, err) + require.Equal(t, "phil", phil.Name) + require.True(t, strings.HasPrefix(phil.Hash, "$2a$04$")) // Min cost for testing + require.Equal(t, RoleAdmin, phil.Role) - philGrants, err := a.Grants("phil") - require.Nil(t, err) - require.Equal(t, []Grant{}, philGrants) + philGrants, err := a.Grants("phil") + require.Nil(t, err) + require.Equal(t, []Grant{}, philGrants) - ben, err := a.User("ben") - require.Nil(t, err) - require.Equal(t, "ben", ben.Name) - require.True(t, strings.HasPrefix(ben.Hash, "$2a$04$")) // Min cost for testing - require.Equal(t, RoleUser, ben.Role) + ben, err := a.User("ben") + require.Nil(t, err) + require.Equal(t, "ben", ben.Name) + require.True(t, strings.HasPrefix(ben.Hash, "$2a$04$")) // Min cost for testing + require.Equal(t, RoleUser, ben.Role) - benGrants, err := a.Grants("ben") - require.Nil(t, err) - require.Equal(t, []Grant{ - {"everyonewrite", PermissionDenyAll, false}, - {"mytopic", PermissionReadWrite, false}, - {"writeme", PermissionWrite, false}, - {"readme", PermissionRead, false}, - }, benGrants) + benGrants, err := a.Grants("ben") + require.Nil(t, err) + require.Equal(t, []Grant{ + {"everyonewrite", PermissionDenyAll, false}, + {"mytopic", PermissionReadWrite, false}, + {"writeme", PermissionWrite, false}, + {"readme", PermissionRead, false}, + }, benGrants) - everyone, err := a.User(Everyone) - require.Nil(t, err) - require.Equal(t, "*", everyone.Name) - require.Equal(t, "", everyone.Hash) - require.Equal(t, RoleAnonymous, everyone.Role) + everyone, err := a.User(Everyone) + require.Nil(t, err) + require.Equal(t, "*", everyone.Name) + require.Equal(t, "", everyone.Hash) + require.Equal(t, RoleAnonymous, everyone.Role) - everyoneGrants, err := a.Grants(Everyone) - require.Nil(t, err) - require.Equal(t, []Grant{ - {"everyonewrite", PermissionReadWrite, false}, - {"announcements", PermissionRead, false}, - }, everyoneGrants) + everyoneGrants, err := a.Grants(Everyone) + require.Nil(t, err) + require.Equal(t, []Grant{ + {"everyonewrite", PermissionReadWrite, false}, + {"announcements", PermissionRead, false}, + }, everyoneGrants) - // Ben: Before revoking - require.Nil(t, a.AllowAccess("ben", "mytopic", PermissionReadWrite)) // Overwrite! - require.Nil(t, a.AllowAccess("ben", "readme", PermissionRead)) - require.Nil(t, a.AllowAccess("ben", "writeme", PermissionWrite)) - require.Nil(t, a.Authorize(ben, "mytopic", PermissionRead)) - require.Nil(t, a.Authorize(ben, "mytopic", PermissionWrite)) - require.Nil(t, a.Authorize(ben, "readme", PermissionRead)) - require.Nil(t, a.Authorize(ben, "writeme", PermissionWrite)) + // Ben: Before revoking + require.Nil(t, a.AllowAccess("ben", "mytopic", PermissionReadWrite)) // Overwrite! + require.Nil(t, a.AllowAccess("ben", "readme", PermissionRead)) + require.Nil(t, a.AllowAccess("ben", "writeme", PermissionWrite)) + require.Nil(t, a.Authorize(ben, "mytopic", PermissionRead)) + require.Nil(t, a.Authorize(ben, "mytopic", PermissionWrite)) + require.Nil(t, a.Authorize(ben, "readme", PermissionRead)) + require.Nil(t, a.Authorize(ben, "writeme", PermissionWrite)) - // Revoke access for "ben" to "mytopic", then check again - require.Nil(t, a.ResetAccess("ben", "mytopic")) - require.Equal(t, ErrUnauthorized, a.Authorize(ben, "mytopic", PermissionWrite)) // Revoked - require.Equal(t, ErrUnauthorized, a.Authorize(ben, "mytopic", PermissionRead)) // Revoked - require.Nil(t, a.Authorize(ben, "readme", PermissionRead)) // Unchanged - require.Nil(t, a.Authorize(ben, "writeme", PermissionWrite)) // Unchanged + // Revoke access for "ben" to "mytopic", then check again + require.Nil(t, a.ResetAccess("ben", "mytopic")) + require.Equal(t, ErrUnauthorized, a.Authorize(ben, "mytopic", PermissionWrite)) // Revoked + require.Equal(t, ErrUnauthorized, a.Authorize(ben, "mytopic", PermissionRead)) // Revoked + require.Nil(t, a.Authorize(ben, "readme", PermissionRead)) // Unchanged + require.Nil(t, a.Authorize(ben, "writeme", PermissionWrite)) // Unchanged - // Revoke rest of the access - require.Nil(t, a.ResetAccess("ben", "")) - require.Equal(t, ErrUnauthorized, a.Authorize(ben, "readme", PermissionRead)) // Revoked - require.Equal(t, ErrUnauthorized, a.Authorize(ben, "wrtiteme", PermissionWrite)) // Revoked + // Revoke rest of the access + require.Nil(t, a.ResetAccess("ben", "")) + require.Equal(t, ErrUnauthorized, a.Authorize(ben, "readme", PermissionRead)) // Revoked + require.Equal(t, ErrUnauthorized, a.Authorize(ben, "wrtiteme", PermissionWrite)) // Revoked - // User list - users, err := a.Users() - require.Nil(t, err) - require.Equal(t, 3, len(users)) - require.Equal(t, "phil", users[0].Name) - require.Equal(t, "ben", users[1].Name) - require.Equal(t, "*", users[2].Name) + // User list + users, err := a.Users() + require.Nil(t, err) + require.Equal(t, 3, len(users)) + require.Equal(t, "phil", users[0].Name) + require.Equal(t, "ben", users[1].Name) + require.Equal(t, "*", users[2].Name) - // Remove user - require.Nil(t, a.RemoveUser("ben")) - _, err = a.User("ben") - require.Equal(t, ErrUserNotFound, err) + // Remove user + require.Nil(t, a.RemoveUser("ben")) + _, err = a.User("ben") + require.Equal(t, ErrUserNotFound, err) - users, err = a.Users() - require.Nil(t, err) - require.Equal(t, 2, len(users)) - require.Equal(t, "phil", users[0].Name) - require.Equal(t, "*", users[1].Name) + users, err = a.Users() + require.Nil(t, err) + require.Equal(t, 2, len(users)) + require.Equal(t, "phil", users[0].Name) + require.Equal(t, "*", users[1].Name) + }) } func TestManager_ChangePassword(t *testing.T) { - a := newTestManager(t, PermissionDenyAll) - require.Nil(t, a.AddUser("phil", "phil", RoleAdmin, false)) - require.Nil(t, a.AddUser("jane", "$2a$10$OyqU72muEy7VMd1SAU2Iru5IbeSMgrtCGHu/fWLmxL1MwlijQXWbG", RoleUser, true)) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) + require.Nil(t, a.AddUser("phil", "phil", RoleAdmin, false)) + require.Nil(t, a.AddUser("jane", "$2a$10$OyqU72muEy7VMd1SAU2Iru5IbeSMgrtCGHu/fWLmxL1MwlijQXWbG", RoleUser, true)) - _, err := a.Authenticate("phil", "phil") - require.Nil(t, err) + _, err := a.Authenticate("phil", "phil") + require.Nil(t, err) - _, err = a.Authenticate("jane", "jane") - require.Nil(t, err) + _, err = a.Authenticate("jane", "jane") + require.Nil(t, err) - require.Nil(t, a.ChangePassword("phil", "newpass", false)) - _, err = a.Authenticate("phil", "phil") - require.Equal(t, ErrUnauthenticated, err) - _, err = a.Authenticate("phil", "newpass") - require.Nil(t, err) + require.Nil(t, a.ChangePassword("phil", "newpass", false)) + _, err = a.Authenticate("phil", "phil") + require.Equal(t, ErrUnauthenticated, err) + _, err = a.Authenticate("phil", "newpass") + require.Nil(t, err) - require.Nil(t, a.ChangePassword("jane", "$2a$10$CNaCW.q1R431urlbQ5Drh.zl48TiiOeJSmZgfcswkZiPbJGQ1ApSS", true)) - _, err = a.Authenticate("jane", "jane") - require.Equal(t, ErrUnauthenticated, err) - _, err = a.Authenticate("jane", "newpass") - require.Nil(t, err) + require.Nil(t, a.ChangePassword("jane", "$2a$10$CNaCW.q1R431urlbQ5Drh.zl48TiiOeJSmZgfcswkZiPbJGQ1ApSS", true)) + _, err = a.Authenticate("jane", "jane") + require.Equal(t, ErrUnauthenticated, err) + _, err = a.Authenticate("jane", "newpass") + require.Nil(t, err) + }) } func TestManager_ChangeRole(t *testing.T) { - a := newTestManager(t, PermissionDenyAll) - require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) - require.Nil(t, a.AllowAccess("ben", "mytopic", PermissionReadWrite)) - require.Nil(t, a.AllowAccess("ben", "readme", PermissionRead)) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) + require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) + require.Nil(t, a.AllowAccess("ben", "mytopic", PermissionReadWrite)) + require.Nil(t, a.AllowAccess("ben", "readme", PermissionRead)) - ben, err := a.User("ben") - require.Nil(t, err) - require.Equal(t, RoleUser, ben.Role) + ben, err := a.User("ben") + require.Nil(t, err) + require.Equal(t, RoleUser, ben.Role) - benGrants, err := a.Grants("ben") - require.Nil(t, err) - require.Equal(t, 2, len(benGrants)) + benGrants, err := a.Grants("ben") + require.Nil(t, err) + require.Equal(t, 2, len(benGrants)) - require.Nil(t, a.ChangeRole("ben", RoleAdmin)) + require.Nil(t, a.ChangeRole("ben", RoleAdmin)) - ben, err = a.User("ben") - require.Nil(t, err) - require.Equal(t, RoleAdmin, ben.Role) + ben, err = a.User("ben") + require.Nil(t, err) + require.Equal(t, RoleAdmin, ben.Role) - benGrants, err = a.Grants("ben") - require.Nil(t, err) - require.Equal(t, 0, len(benGrants)) + benGrants, err = a.Grants("ben") + require.Nil(t, err) + require.Equal(t, 0, len(benGrants)) + }) } func TestManager_Reservations(t *testing.T) { - a := newTestManager(t, PermissionDenyAll) - require.Nil(t, a.AddUser("phil", "phil", RoleUser, false)) - require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) - require.Nil(t, a.AddReservation("ben", "ztopic_", PermissionDenyAll)) - require.Nil(t, a.AddReservation("ben", "readme", PermissionRead)) - require.Nil(t, a.AllowAccess("ben", "something-else", PermissionRead)) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) + require.Nil(t, a.AddUser("phil", "phil", RoleUser, false)) + require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) + require.Nil(t, a.AddReservation("ben", "ztopic_", PermissionDenyAll)) + require.Nil(t, a.AddReservation("ben", "readme", PermissionRead)) + require.Nil(t, a.AllowAccess("ben", "something-else", PermissionRead)) - reservations, err := a.Reservations("ben") - require.Nil(t, err) - require.Equal(t, 2, len(reservations)) - require.Equal(t, Reservation{ - Topic: "readme", - Owner: PermissionReadWrite, - Everyone: PermissionRead, - }, reservations[0]) - require.Equal(t, Reservation{ - Topic: "ztopic_", - Owner: PermissionReadWrite, - Everyone: PermissionDenyAll, - }, reservations[1]) + reservations, err := a.Reservations("ben") + require.Nil(t, err) + require.Equal(t, 2, len(reservations)) + require.Equal(t, Reservation{ + Topic: "readme", + Owner: PermissionReadWrite, + Everyone: PermissionRead, + }, reservations[0]) + require.Equal(t, Reservation{ + Topic: "ztopic_", + Owner: PermissionReadWrite, + Everyone: PermissionDenyAll, + }, reservations[1]) - b, err := a.HasReservation("ben", "readme") - require.Nil(t, err) - require.True(t, b) + b, err := a.HasReservation("ben", "readme") + require.Nil(t, err) + require.True(t, b) - b, err = a.HasReservation("ben", "ztopic_") - require.Nil(t, err) - require.True(t, b) + b, err = a.HasReservation("ben", "ztopic_") + require.Nil(t, err) + require.True(t, b) - b, err = a.HasReservation("ben", "ztopicX") // _ != X (used to be a SQL wildcard issue) - require.Nil(t, err) - require.False(t, b) + b, err = a.HasReservation("ben", "ztopicX") // _ != X (used to be a SQL wildcard issue) + require.Nil(t, err) + require.False(t, b) - b, err = a.HasReservation("notben", "readme") - require.Nil(t, err) - require.False(t, b) + b, err = a.HasReservation("notben", "readme") + require.Nil(t, err) + require.False(t, b) - b, err = a.HasReservation("ben", "something-else") - require.Nil(t, err) - require.False(t, b) + b, err = a.HasReservation("ben", "something-else") + require.Nil(t, err) + require.False(t, b) - count, err := a.ReservationsCount("ben") - require.Nil(t, err) - require.Equal(t, int64(2), count) + count, err := a.ReservationsCount("ben") + require.Nil(t, err) + require.Equal(t, int64(2), count) - count, err = a.ReservationsCount("phil") - require.Nil(t, err) - require.Equal(t, int64(0), count) + count, err = a.ReservationsCount("phil") + require.Nil(t, err) + require.Equal(t, int64(0), count) - err = a.AllowReservation("phil", "readme") - require.Equal(t, errTopicOwnedByOthers, err) + err = a.AllowReservation("phil", "readme") + require.Equal(t, errTopicOwnedByOthers, err) - err = a.AllowReservation("phil", "ztopic_") - require.Equal(t, errTopicOwnedByOthers, err) + err = a.AllowReservation("phil", "ztopic_") + require.Equal(t, errTopicOwnedByOthers, err) - err = a.AllowReservation("phil", "ztopicX") - require.Nil(t, err) + err = a.AllowReservation("phil", "ztopicX") + require.Nil(t, err) - err = a.AllowReservation("phil", "not-reserved") - require.Nil(t, err) + err = a.AllowReservation("phil", "not-reserved") + require.Nil(t, err) - // Now remove them again - require.Nil(t, a.RemoveReservations("ben", "ztopic_", "readme")) + // Now remove them again + require.Nil(t, a.RemoveReservations("ben", "ztopic_", "readme")) - count, err = a.ReservationsCount("ben") - require.Nil(t, err) - require.Equal(t, int64(0), count) + count, err = a.ReservationsCount("ben") + require.Nil(t, err) + require.Equal(t, int64(0), count) + }) } func TestManager_ChangeRoleFromTierUserToAdmin(t *testing.T) { - a := newTestManager(t, PermissionDenyAll) - require.Nil(t, a.AddTier(&Tier{ - Code: "pro", - Name: "ntfy Pro", - StripeMonthlyPriceID: "price123", - MessageLimit: 5_000, - MessageExpiryDuration: 3 * 24 * time.Hour, - EmailLimit: 50, - ReservationLimit: 5, - AttachmentFileSizeLimit: 52428800, - AttachmentTotalSizeLimit: 524288000, - AttachmentExpiryDuration: 24 * time.Hour, - })) - require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) - require.Nil(t, a.ChangeTier("ben", "pro")) - require.Nil(t, a.AddReservation("ben", "mytopic", PermissionDenyAll)) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) + require.Nil(t, a.AddTier(&Tier{ + Code: "pro", + Name: "ntfy Pro", + StripeMonthlyPriceID: "price123", + MessageLimit: 5_000, + MessageExpiryDuration: 3 * 24 * time.Hour, + EmailLimit: 50, + ReservationLimit: 5, + AttachmentFileSizeLimit: 52428800, + AttachmentTotalSizeLimit: 524288000, + AttachmentExpiryDuration: 24 * time.Hour, + })) + require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) + require.Nil(t, a.ChangeTier("ben", "pro")) + require.Nil(t, a.AddReservation("ben", "mytopic", PermissionDenyAll)) - ben, err := a.User("ben") - require.Nil(t, err) - require.Equal(t, RoleUser, ben.Role) - require.Equal(t, "pro", ben.Tier.Code) - require.Equal(t, int64(5000), ben.Tier.MessageLimit) - require.Equal(t, 3*24*time.Hour, ben.Tier.MessageExpiryDuration) - require.Equal(t, int64(50), ben.Tier.EmailLimit) - require.Equal(t, int64(5), ben.Tier.ReservationLimit) - require.Equal(t, int64(52428800), ben.Tier.AttachmentFileSizeLimit) - require.Equal(t, int64(524288000), ben.Tier.AttachmentTotalSizeLimit) - require.Equal(t, 24*time.Hour, ben.Tier.AttachmentExpiryDuration) + ben, err := a.User("ben") + require.Nil(t, err) + require.Equal(t, RoleUser, ben.Role) + require.Equal(t, "pro", ben.Tier.Code) + require.Equal(t, int64(5000), ben.Tier.MessageLimit) + require.Equal(t, 3*24*time.Hour, ben.Tier.MessageExpiryDuration) + require.Equal(t, int64(50), ben.Tier.EmailLimit) + require.Equal(t, int64(5), ben.Tier.ReservationLimit) + require.Equal(t, int64(52428800), ben.Tier.AttachmentFileSizeLimit) + require.Equal(t, int64(524288000), ben.Tier.AttachmentTotalSizeLimit) + require.Equal(t, 24*time.Hour, ben.Tier.AttachmentExpiryDuration) - benGrants, err := a.Grants("ben") - require.Nil(t, err) - require.Equal(t, 1, len(benGrants)) - require.Equal(t, PermissionReadWrite, benGrants[0].Permission) + benGrants, err := a.Grants("ben") + require.Nil(t, err) + require.Equal(t, 1, len(benGrants)) + require.Equal(t, PermissionReadWrite, benGrants[0].Permission) - everyoneGrants, err := a.Grants(Everyone) - require.Nil(t, err) - require.Equal(t, 1, len(everyoneGrants)) - require.Equal(t, PermissionDenyAll, everyoneGrants[0].Permission) + everyoneGrants, err := a.Grants(Everyone) + require.Nil(t, err) + require.Equal(t, 1, len(everyoneGrants)) + require.Equal(t, PermissionDenyAll, everyoneGrants[0].Permission) - benReservations, err := a.Reservations("ben") - require.Nil(t, err) - require.Equal(t, 1, len(benReservations)) - require.Equal(t, "mytopic", benReservations[0].Topic) - require.Equal(t, PermissionReadWrite, benReservations[0].Owner) - require.Equal(t, PermissionDenyAll, benReservations[0].Everyone) + benReservations, err := a.Reservations("ben") + require.Nil(t, err) + require.Equal(t, 1, len(benReservations)) + require.Equal(t, "mytopic", benReservations[0].Topic) + require.Equal(t, PermissionReadWrite, benReservations[0].Owner) + require.Equal(t, PermissionDenyAll, benReservations[0].Everyone) - // Switch to admin, this should remove all grants and owned ACL entries - require.Nil(t, a.ChangeRole("ben", RoleAdmin)) + // Switch to admin, this should remove all grants and owned ACL entries + require.Nil(t, a.ChangeRole("ben", RoleAdmin)) - benGrants, err = a.Grants("ben") - require.Nil(t, err) - require.Equal(t, 0, len(benGrants)) + benGrants, err = a.Grants("ben") + require.Nil(t, err) + require.Equal(t, 0, len(benGrants)) - everyoneGrants, err = a.Grants(Everyone) - require.Nil(t, err) - require.Equal(t, 0, len(everyoneGrants)) + everyoneGrants, err = a.Grants(Everyone) + require.Nil(t, err) + require.Equal(t, 0, len(everyoneGrants)) + }) } func TestManager_Token_Valid(t *testing.T) { - a := newTestManager(t, PermissionDenyAll) - require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) + require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) - u, err := a.User("ben") - require.Nil(t, err) + u, err := a.User("ben") + require.Nil(t, err) - // Create token for user - token, err := a.CreateToken(u.ID, "some label", time.Now().Add(72*time.Hour), netip.IPv4Unspecified(), false) - require.Nil(t, err) - require.NotEmpty(t, token.Value) - require.Equal(t, "some label", token.Label) - require.True(t, time.Now().Add(71*time.Hour).Unix() < token.Expires.Unix()) + // Create token for user + token, err := a.CreateToken(u.ID, "some label", time.Now().Add(72*time.Hour), netip.IPv4Unspecified(), false) + require.Nil(t, err) + require.NotEmpty(t, token.Value) + require.Equal(t, "some label", token.Label) + require.True(t, time.Now().Add(71*time.Hour).Unix() < token.Expires.Unix()) - u2, err := a.AuthenticateToken(token.Value) - require.Nil(t, err) - require.Equal(t, u.Name, u2.Name) - require.Equal(t, token.Value, u2.Token) + u2, err := a.AuthenticateToken(token.Value) + require.Nil(t, err) + require.Equal(t, u.Name, u2.Name) + require.Equal(t, token.Value, u2.Token) - token2, err := a.Token(u.ID, token.Value) - require.Nil(t, err) - require.Equal(t, token.Value, token2.Value) - require.Equal(t, "some label", token2.Label) + token2, err := a.Token(u.ID, token.Value) + require.Nil(t, err) + require.Equal(t, token.Value, token2.Value) + require.Equal(t, "some label", token2.Label) - tokens, err := a.Tokens(u.ID) - require.Nil(t, err) - require.Equal(t, 1, len(tokens)) - require.Equal(t, "some label", tokens[0].Label) + tokens, err := a.Tokens(u.ID) + require.Nil(t, err) + require.Equal(t, 1, len(tokens)) + require.Equal(t, "some label", tokens[0].Label) - tokens, err = a.Tokens("u_notauser") - require.Nil(t, err) - require.Equal(t, 0, len(tokens)) + tokens, err = a.Tokens("u_notauser") + require.Nil(t, err) + require.Equal(t, 0, len(tokens)) - // Remove token and auth again - require.Nil(t, a.RemoveToken(u2.ID, u2.Token)) - u3, err := a.AuthenticateToken(token.Value) - require.Equal(t, ErrUnauthenticated, err) - require.Nil(t, u3) + // Remove token and auth again + require.Nil(t, a.RemoveToken(u2.ID, u2.Token)) + u3, err := a.AuthenticateToken(token.Value) + require.Equal(t, ErrUnauthenticated, err) + require.Nil(t, u3) - tokens, err = a.Tokens(u.ID) - require.Nil(t, err) - require.Equal(t, 0, len(tokens)) + tokens, err = a.Tokens(u.ID) + require.Nil(t, err) + require.Equal(t, 0, len(tokens)) + }) } func TestManager_Token_Invalid(t *testing.T) { - a := newTestManager(t, PermissionDenyAll) - require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) + require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) - u, err := a.AuthenticateToken(strings.Repeat("x", 32)) // 32 == token length - require.Nil(t, u) - require.Equal(t, ErrUnauthenticated, err) + u, err := a.AuthenticateToken(strings.Repeat("x", 32)) // 32 == token length + require.Nil(t, u) + require.Equal(t, ErrUnauthenticated, err) - u, err = a.AuthenticateToken("not long enough anyway") - require.Nil(t, u) - require.Equal(t, ErrUnauthenticated, err) + u, err = a.AuthenticateToken("not long enough anyway") + require.Nil(t, u) + require.Equal(t, ErrUnauthenticated, err) + }) } func TestManager_Token_NotFound(t *testing.T) { - a := newTestManager(t, PermissionDenyAll) - _, err := a.Token("u_bla", "notfound") - require.Equal(t, ErrTokenNotFound, err) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) + _, err := a.Token("u_bla", "notfound") + require.Equal(t, ErrTokenNotFound, err) + }) } func TestManager_Token_Expire(t *testing.T) { - a := newTestManager(t, PermissionDenyAll) - require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) + require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) - u, err := a.User("ben") - require.Nil(t, err) + u, err := a.User("ben") + require.Nil(t, err) - // Create tokens for user - token1, err := a.CreateToken(u.ID, "", time.Now().Add(72*time.Hour), netip.IPv4Unspecified(), false) - require.Nil(t, err) - require.NotEmpty(t, token1.Value) - require.True(t, time.Now().Add(71*time.Hour).Unix() < token1.Expires.Unix()) + // Create tokens for user + token1, err := a.CreateToken(u.ID, "", time.Now().Add(72*time.Hour), netip.IPv4Unspecified(), false) + require.Nil(t, err) + require.NotEmpty(t, token1.Value) + require.True(t, time.Now().Add(71*time.Hour).Unix() < token1.Expires.Unix()) - token2, err := a.CreateToken(u.ID, "", time.Now().Add(72*time.Hour), netip.IPv4Unspecified(), false) - require.Nil(t, err) - require.NotEmpty(t, token2.Value) - require.NotEqual(t, token1.Value, token2.Value) - require.True(t, time.Now().Add(71*time.Hour).Unix() < token2.Expires.Unix()) + token2, err := a.CreateToken(u.ID, "", time.Now().Add(72*time.Hour), netip.IPv4Unspecified(), false) + require.Nil(t, err) + require.NotEmpty(t, token2.Value) + require.NotEqual(t, token1.Value, token2.Value) + require.True(t, time.Now().Add(71*time.Hour).Unix() < token2.Expires.Unix()) - // See that tokens work - _, err = a.AuthenticateToken(token1.Value) - require.Nil(t, err) + // See that tokens work + _, err = a.AuthenticateToken(token1.Value) + require.Nil(t, err) - _, err = a.AuthenticateToken(token2.Value) - require.Nil(t, err) + _, err = a.AuthenticateToken(token2.Value) + require.Nil(t, err) - // Modify token expiration in database - _, err = testDB(a).Exec("UPDATE user_token SET expires = 1 WHERE token = ?", token1.Value) - require.Nil(t, err) + // Expire token1 via the API + _, err = a.ChangeToken(u.ID, token1.Value, nil, util.Time(time.Unix(1, 0))) + require.Nil(t, err) - // Now token1 shouldn't work anymore - _, err = a.AuthenticateToken(token1.Value) - require.Equal(t, ErrUnauthenticated, err) + // Now token1 shouldn't work anymore + _, err = a.AuthenticateToken(token1.Value) + require.Equal(t, ErrUnauthenticated, err) - result, err := testDB(a).Query("SELECT * from user_token WHERE token = ?", token1.Value) - require.Nil(t, err) - require.True(t, result.Next()) // Still a matching row - require.Nil(t, result.Close()) + // But the token row should still exist + tokens, err := a.Tokens(u.ID) + require.Nil(t, err) + require.Equal(t, token1.Value, tokens[0].Value) + require.Equal(t, 2, len(tokens)) - // Expire tokens and check database rows - require.Nil(t, a.RemoveExpiredTokens()) + // Expire tokens and check that token1 is gone + require.Nil(t, a.RemoveExpiredTokens()) - result, err = testDB(a).Query("SELECT * from user_token WHERE token = ?", token1.Value) - require.Nil(t, err) - require.False(t, result.Next()) // No matching row! - require.Nil(t, result.Close()) + tokens, err = a.Tokens(u.ID) + require.Nil(t, err) + require.Equal(t, 1, len(tokens)) + require.Equal(t, token2.Value, tokens[0].Value) + }) } func TestManager_Token_Extend(t *testing.T) { - a := newTestManager(t, PermissionDenyAll) - require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) + require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) - // Try to extend token for user without token - u, err := a.User("ben") - require.Nil(t, err) + // Try to extend token for user without token + u, err := a.User("ben") + require.Nil(t, err) - _, err = a.ChangeToken(u.ID, u.Token, util.String("some label"), util.Time(time.Now().Add(time.Hour))) - require.Equal(t, errNoTokenProvided, err) + _, err = a.ChangeToken(u.ID, u.Token, util.String("some label"), util.Time(time.Now().Add(time.Hour))) + require.Equal(t, errNoTokenProvided, err) - // Create token for user - token, err := a.CreateToken(u.ID, "", time.Now().Add(72*time.Hour), netip.IPv4Unspecified(), false) - require.Nil(t, err) - require.NotEmpty(t, token.Value) + // Create token for user + token, err := a.CreateToken(u.ID, "", time.Now().Add(72*time.Hour), netip.IPv4Unspecified(), false) + require.Nil(t, err) + require.NotEmpty(t, token.Value) - userWithToken, err := a.AuthenticateToken(token.Value) - require.Nil(t, err) + userWithToken, err := a.AuthenticateToken(token.Value) + require.Nil(t, err) - extendedToken, err := a.ChangeToken(userWithToken.ID, userWithToken.Token, util.String("changed label"), util.Time(time.Now().Add(100*time.Hour))) - require.Nil(t, err) - require.Equal(t, token.Value, extendedToken.Value) - require.Equal(t, "changed label", extendedToken.Label) - require.True(t, token.Expires.Unix() < extendedToken.Expires.Unix()) - require.True(t, time.Now().Add(99*time.Hour).Unix() < extendedToken.Expires.Unix()) + extendedToken, err := a.ChangeToken(userWithToken.ID, userWithToken.Token, util.String("changed label"), util.Time(time.Now().Add(100*time.Hour))) + require.Nil(t, err) + require.Equal(t, token.Value, extendedToken.Value) + require.Equal(t, "changed label", extendedToken.Label) + require.True(t, token.Expires.Unix() < extendedToken.Expires.Unix()) + require.True(t, time.Now().Add(99*time.Hour).Unix() < extendedToken.Expires.Unix()) + }) } func TestManager_Token_MaxCount_AutoDelete(t *testing.T) { // Tests that tokens are automatically deleted when the maximum number of tokens is reached + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) + require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) + require.Nil(t, a.AddUser("phil", "phil", RoleUser, false)) - a := newTestManager(t, PermissionDenyAll) - require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) - require.Nil(t, a.AddUser("phil", "phil", RoleUser, false)) + ben, err := a.User("ben") + require.Nil(t, err) - ben, err := a.User("ben") - require.Nil(t, err) + phil, err := a.User("phil") + require.Nil(t, err) - phil, err := a.User("phil") - require.Nil(t, err) - - // Create 2 tokens for phil - philTokens := make([]string, 0) - token, err := a.CreateToken(phil.ID, "", time.Now().Add(72*time.Hour), netip.IPv4Unspecified(), false) - require.Nil(t, err) - require.NotEmpty(t, token.Value) - philTokens = append(philTokens, token.Value) - - token, err = a.CreateToken(phil.ID, "", time.Unix(0, 0), netip.IPv4Unspecified(), false) - require.Nil(t, err) - require.NotEmpty(t, token.Value) - philTokens = append(philTokens, token.Value) - - // Create 62 tokens for ben (only 60 allowed!) - baseTime := time.Now().Add(24 * time.Hour) - benTokens := make([]string, 0) - for i := 0; i < 62; i++ { // - token, err := a.CreateToken(ben.ID, "", time.Now().Add(72*time.Hour), netip.IPv4Unspecified(), false) + // Create 2 tokens for phil + philTokens := make([]string, 0) + token, err := a.CreateToken(phil.ID, "", time.Now().Add(72*time.Hour), netip.IPv4Unspecified(), false) require.Nil(t, err) require.NotEmpty(t, token.Value) - benTokens = append(benTokens, token.Value) + philTokens = append(philTokens, token.Value) - // Manually modify expiry date to avoid sorting issues (this is a hack) - _, err = testDB(a).Exec(`UPDATE user_token SET expires=? WHERE token=?`, baseTime.Add(time.Duration(i)*time.Minute).Unix(), token.Value) + token, err = a.CreateToken(phil.ID, "", time.Unix(0, 0), netip.IPv4Unspecified(), false) require.Nil(t, err) - } + require.NotEmpty(t, token.Value) + philTokens = append(philTokens, token.Value) - // Ben: The first 2 tokens should have been wiped and should not work anymore! - _, err = a.AuthenticateToken(benTokens[0]) - require.Equal(t, ErrUnauthenticated, err) + // Create 62 tokens for ben (only 60 allowed!) + baseTime := time.Now().Add(24 * time.Hour) + benTokens := make([]string, 0) + for i := 0; i < 62; i++ { // + token, err := a.CreateToken(ben.ID, "", time.Now().Add(72*time.Hour), netip.IPv4Unspecified(), false) + require.Nil(t, err) + require.NotEmpty(t, token.Value) + benTokens = append(benTokens, token.Value) - _, err = a.AuthenticateToken(benTokens[1]) - require.Equal(t, ErrUnauthenticated, err) + // Manually modify expiry date to avoid sorting issues (this is a hack) + _, err = a.ChangeToken(ben.ID, token.Value, nil, util.Time(baseTime.Add(time.Duration(i)*time.Minute))) + require.Nil(t, err) + } - // Ben: The other tokens should still work - for i := 2; i < 62; i++ { - userWithToken, err := a.AuthenticateToken(benTokens[i]) - require.Nil(t, err, "token[%d]=%s failed", i, benTokens[i]) - require.Equal(t, "ben", userWithToken.Name) - require.Equal(t, benTokens[i], userWithToken.Token) - } + // Ben: The first 2 tokens should have been wiped and should not work anymore! + _, err = a.AuthenticateToken(benTokens[0]) + require.Equal(t, ErrUnauthenticated, err) - // Phil: All tokens should still work - for i := 0; i < 2; i++ { - userWithToken, err := a.AuthenticateToken(philTokens[i]) - require.Nil(t, err, "token[%d]=%s failed", i, philTokens[i]) - require.Equal(t, "phil", userWithToken.Name) - require.Equal(t, philTokens[i], userWithToken.Token) - } + _, err = a.AuthenticateToken(benTokens[1]) + require.Equal(t, ErrUnauthenticated, err) - var benCount int - rows, err := testDB(a).Query(`SELECT COUNT(*) FROM user_token WHERE user_id=?`, ben.ID) - require.Nil(t, err) - require.True(t, rows.Next()) - require.Nil(t, rows.Scan(&benCount)) - require.Equal(t, 60, benCount) + // Ben: The other tokens should still work + for i := 2; i < 62; i++ { + userWithToken, err := a.AuthenticateToken(benTokens[i]) + require.Nil(t, err, "token[%d]=%s failed", i, benTokens[i]) + require.Equal(t, "ben", userWithToken.Name) + require.Equal(t, benTokens[i], userWithToken.Token) + } - var philCount int - rows, err = testDB(a).Query(`SELECT COUNT(*) FROM user_token WHERE user_id=?`, phil.ID) - require.Nil(t, err) - require.True(t, rows.Next()) - require.Nil(t, rows.Scan(&philCount)) - require.Equal(t, 2, philCount) + // Phil: All tokens should still work + for i := 0; i < 2; i++ { + userWithToken, err := a.AuthenticateToken(philTokens[i]) + require.Nil(t, err, "token[%d]=%s failed", i, philTokens[i]) + require.Equal(t, "phil", userWithToken.Name) + require.Equal(t, philTokens[i], userWithToken.Token) + } + + benTokensList, err := a.Tokens(ben.ID) + require.Nil(t, err) + require.Equal(t, 60, len(benTokensList)) + + philTokensList, err := a.Tokens(phil.ID) + require.Nil(t, err) + require.Equal(t, 2, len(philTokensList)) + }) } func TestManager_EnqueueStats_ResetStats(t *testing.T) { - filename := filepath.Join(t.TempDir(), "db") - conf := &Config{ - DefaultAccess: PermissionReadWrite, - BcryptCost: bcrypt.MinCost, - QueueWriterInterval: 1500 * time.Millisecond, - } - a := newTestManagerFromStoreConfig(t, filename, conf) - require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + conf := &Config{ + DefaultAccess: PermissionReadWrite, + BcryptCost: bcrypt.MinCost, + QueueWriterInterval: 1500 * time.Millisecond, + } + a := newTestManagerFromStoreConfig(t, newStore, conf) + require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) - // Baseline: No messages or emails - u, err := a.User("ben") - require.Nil(t, err) - require.Equal(t, int64(0), u.Stats.Messages) - require.Equal(t, int64(0), u.Stats.Emails) - a.EnqueueUserStats(u.ID, &Stats{ - Messages: 11, - Emails: 2, + // Baseline: No messages or emails + u, err := a.User("ben") + require.Nil(t, err) + require.Equal(t, int64(0), u.Stats.Messages) + require.Equal(t, int64(0), u.Stats.Emails) + a.EnqueueUserStats(u.ID, &Stats{ + Messages: 11, + Emails: 2, + }) + + // Still no change, because it's queued asynchronously + u, err = a.User("ben") + require.Nil(t, err) + require.Equal(t, int64(0), u.Stats.Messages) + require.Equal(t, int64(0), u.Stats.Emails) + + // After 2 seconds they should be persisted + time.Sleep(2 * time.Second) + + u, err = a.User("ben") + require.Nil(t, err) + require.Equal(t, int64(11), u.Stats.Messages) + require.Equal(t, int64(2), u.Stats.Emails) + + // Now reset stats (enqueued stats will be thrown out) + a.EnqueueUserStats(u.ID, &Stats{ + Messages: 99, + Emails: 23, + }) + require.Nil(t, a.ResetStats()) + + u, err = a.User("ben") + require.Nil(t, err) + require.Equal(t, int64(0), u.Stats.Messages) + require.Equal(t, int64(0), u.Stats.Emails) }) - - // Still no change, because it's queued asynchronously - u, err = a.User("ben") - require.Nil(t, err) - require.Equal(t, int64(0), u.Stats.Messages) - require.Equal(t, int64(0), u.Stats.Emails) - - // After 2 seconds they should be persisted - time.Sleep(2 * time.Second) - - u, err = a.User("ben") - require.Nil(t, err) - require.Equal(t, int64(11), u.Stats.Messages) - require.Equal(t, int64(2), u.Stats.Emails) - - // Now reset stats (enqueued stats will be thrown out) - a.EnqueueUserStats(u.ID, &Stats{ - Messages: 99, - Emails: 23, - }) - require.Nil(t, a.ResetStats()) - - u, err = a.User("ben") - require.Nil(t, err) - require.Equal(t, int64(0), u.Stats.Messages) - require.Equal(t, int64(0), u.Stats.Emails) } func TestManager_EnqueueTokenUpdate(t *testing.T) { - filename := filepath.Join(t.TempDir(), "db") - conf := &Config{ - DefaultAccess: PermissionReadWrite, - BcryptCost: bcrypt.MinCost, - QueueWriterInterval: 500 * time.Millisecond, - } - a := newTestManagerFromStoreConfig(t, filename, conf) - require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + conf := &Config{ + DefaultAccess: PermissionReadWrite, + BcryptCost: bcrypt.MinCost, + QueueWriterInterval: 500 * time.Millisecond, + } + a := newTestManagerFromStoreConfig(t, newStore, conf) + require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) - // Create user and token - u, err := a.User("ben") - require.Nil(t, err) + // Create user and token + u, err := a.User("ben") + require.Nil(t, err) - token, err := a.CreateToken(u.ID, "", time.Now().Add(time.Hour), netip.IPv4Unspecified(), false) - require.Nil(t, err) + token, err := a.CreateToken(u.ID, "", time.Now().Add(time.Hour), netip.IPv4Unspecified(), false) + require.Nil(t, err) - // Queue token update - a.EnqueueTokenUpdate(token.Value, &TokenUpdate{ - LastAccess: time.Unix(111, 0).UTC(), - LastOrigin: netip.MustParseAddr("1.2.3.3"), + // Queue token update + a.EnqueueTokenUpdate(token.Value, &TokenUpdate{ + LastAccess: time.Unix(111, 0).UTC(), + LastOrigin: netip.MustParseAddr("1.2.3.3"), + }) + + // Token has not changed yet. + token2, err := a.Token(u.ID, token.Value) + require.Nil(t, err) + require.Equal(t, token.LastAccess.Unix(), token2.LastAccess.Unix()) + require.Equal(t, token.LastOrigin, token2.LastOrigin) + + // After a second or so they should be persisted + time.Sleep(time.Second) + + token3, err := a.Token(u.ID, token.Value) + require.Nil(t, err) + require.Equal(t, time.Unix(111, 0).UTC().Unix(), token3.LastAccess.Unix()) + require.Equal(t, netip.MustParseAddr("1.2.3.3"), token3.LastOrigin) }) - - // Token has not changed yet. - token2, err := a.Token(u.ID, token.Value) - require.Nil(t, err) - require.Equal(t, token.LastAccess.Unix(), token2.LastAccess.Unix()) - require.Equal(t, token.LastOrigin, token2.LastOrigin) - - // After a second or so they should be persisted - time.Sleep(time.Second) - - token3, err := a.Token(u.ID, token.Value) - require.Nil(t, err) - require.Equal(t, time.Unix(111, 0).UTC().Unix(), token3.LastAccess.Unix()) - require.Equal(t, netip.MustParseAddr("1.2.3.3"), token3.LastOrigin) } func TestManager_ChangeSettings(t *testing.T) { - filename := filepath.Join(t.TempDir(), "db") - conf := &Config{ - DefaultAccess: PermissionReadWrite, - BcryptCost: bcrypt.MinCost, - QueueWriterInterval: 1500 * time.Millisecond, - } - a := newTestManagerFromStoreConfig(t, filename, conf) - require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + conf := &Config{ + DefaultAccess: PermissionReadWrite, + BcryptCost: bcrypt.MinCost, + QueueWriterInterval: 1500 * time.Millisecond, + } + a := newTestManagerFromStoreConfig(t, newStore, conf) + require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) - // No settings - u, err := a.User("ben") - require.Nil(t, err) - require.Nil(t, u.Prefs.Subscriptions) - require.Nil(t, u.Prefs.Notification) - require.Nil(t, u.Prefs.Language) + // No settings + u, err := a.User("ben") + require.Nil(t, err) + require.Nil(t, u.Prefs.Subscriptions) + require.Nil(t, u.Prefs.Notification) + require.Nil(t, u.Prefs.Language) - // Save with new settings - prefs := &Prefs{ - Language: util.String("de"), - Notification: &NotificationPrefs{ - Sound: util.String("ding"), - MinPriority: util.Int(2), - }, - Subscriptions: []*Subscription{ - { - BaseURL: "https://ntfy.sh", - Topic: "mytopic", - DisplayName: util.String("My Topic"), + // Save with new settings + prefs := &Prefs{ + Language: util.String("de"), + Notification: &NotificationPrefs{ + Sound: util.String("ding"), + MinPriority: util.Int(2), }, - }, - } - require.Nil(t, a.ChangeSettings(u.ID, prefs)) + Subscriptions: []*Subscription{ + { + BaseURL: "https://ntfy.sh", + Topic: "mytopic", + DisplayName: util.String("My Topic"), + }, + }, + } + require.Nil(t, a.ChangeSettings(u.ID, prefs)) - // Read again - u, err = a.User("ben") - require.Nil(t, err) - require.Equal(t, util.String("de"), u.Prefs.Language) - require.Equal(t, util.String("ding"), u.Prefs.Notification.Sound) - require.Equal(t, util.Int(2), u.Prefs.Notification.MinPriority) - require.Nil(t, u.Prefs.Notification.DeleteAfter) - require.Equal(t, "https://ntfy.sh", u.Prefs.Subscriptions[0].BaseURL) - require.Equal(t, "mytopic", u.Prefs.Subscriptions[0].Topic) - require.Equal(t, util.String("My Topic"), u.Prefs.Subscriptions[0].DisplayName) + // Read again + u, err = a.User("ben") + require.Nil(t, err) + require.Equal(t, util.String("de"), u.Prefs.Language) + require.Equal(t, util.String("ding"), u.Prefs.Notification.Sound) + require.Equal(t, util.Int(2), u.Prefs.Notification.MinPriority) + require.Nil(t, u.Prefs.Notification.DeleteAfter) + require.Equal(t, "https://ntfy.sh", u.Prefs.Subscriptions[0].BaseURL) + require.Equal(t, "mytopic", u.Prefs.Subscriptions[0].Topic) + require.Equal(t, util.String("My Topic"), u.Prefs.Subscriptions[0].DisplayName) + }) } func TestManager_Tier_Create_Update_List_Delete(t *testing.T) { - a := newTestManager(t, PermissionDenyAll) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) - // Create tier and user - require.Nil(t, a.AddTier(&Tier{ - Code: "supporter", - Name: "Supporter", - MessageLimit: 1, - MessageExpiryDuration: time.Second, - EmailLimit: 1, - ReservationLimit: 1, - AttachmentFileSizeLimit: 1, - AttachmentTotalSizeLimit: 1, - AttachmentExpiryDuration: time.Second, - AttachmentBandwidthLimit: 1, - StripeMonthlyPriceID: "price_1", - })) - require.Nil(t, a.AddTier(&Tier{ - Code: "pro", - Name: "Pro", - MessageLimit: 123, - MessageExpiryDuration: 86400 * time.Second, - EmailLimit: 32, - ReservationLimit: 2, - AttachmentFileSizeLimit: 1231231, - AttachmentTotalSizeLimit: 123123, - AttachmentExpiryDuration: 10800 * time.Second, - AttachmentBandwidthLimit: 21474836480, - StripeMonthlyPriceID: "price_2", - })) - require.Nil(t, a.AddUser("phil", "phil", RoleUser, false)) - require.Nil(t, a.ChangeTier("phil", "pro")) + // Create tier and user + require.Nil(t, a.AddTier(&Tier{ + Code: "supporter", + Name: "Supporter", + MessageLimit: 1, + MessageExpiryDuration: time.Second, + EmailLimit: 1, + ReservationLimit: 1, + AttachmentFileSizeLimit: 1, + AttachmentTotalSizeLimit: 1, + AttachmentExpiryDuration: time.Second, + AttachmentBandwidthLimit: 1, + StripeMonthlyPriceID: "price_1", + })) + require.Nil(t, a.AddTier(&Tier{ + Code: "pro", + Name: "Pro", + MessageLimit: 123, + MessageExpiryDuration: 86400 * time.Second, + EmailLimit: 32, + ReservationLimit: 2, + AttachmentFileSizeLimit: 1231231, + AttachmentTotalSizeLimit: 123123, + AttachmentExpiryDuration: 10800 * time.Second, + AttachmentBandwidthLimit: 21474836480, + StripeMonthlyPriceID: "price_2", + })) + require.Nil(t, a.AddUser("phil", "phil", RoleUser, false)) + require.Nil(t, a.ChangeTier("phil", "pro")) - ti, err := a.Tier("pro") - require.Nil(t, err) + ti, err := a.Tier("pro") + require.Nil(t, err) - u, err := a.User("phil") - require.Nil(t, err) + u, err := a.User("phil") + require.Nil(t, err) - // These are populated by different SQL queries - require.Equal(t, ti, u.Tier) + // These are populated by different SQL queries + require.Equal(t, ti, u.Tier) - // Fields - require.True(t, strings.HasPrefix(ti.ID, "ti_")) - require.Equal(t, "pro", ti.Code) - require.Equal(t, "Pro", ti.Name) - require.Equal(t, int64(123), ti.MessageLimit) - require.Equal(t, 86400*time.Second, ti.MessageExpiryDuration) - require.Equal(t, int64(32), ti.EmailLimit) - require.Equal(t, int64(2), ti.ReservationLimit) - require.Equal(t, int64(1231231), ti.AttachmentFileSizeLimit) - require.Equal(t, int64(123123), ti.AttachmentTotalSizeLimit) - require.Equal(t, 10800*time.Second, ti.AttachmentExpiryDuration) - require.Equal(t, int64(21474836480), ti.AttachmentBandwidthLimit) - require.Equal(t, "price_2", ti.StripeMonthlyPriceID) + // Fields + require.True(t, strings.HasPrefix(ti.ID, "ti_")) + require.Equal(t, "pro", ti.Code) + require.Equal(t, "Pro", ti.Name) + require.Equal(t, int64(123), ti.MessageLimit) + require.Equal(t, 86400*time.Second, ti.MessageExpiryDuration) + require.Equal(t, int64(32), ti.EmailLimit) + require.Equal(t, int64(2), ti.ReservationLimit) + require.Equal(t, int64(1231231), ti.AttachmentFileSizeLimit) + require.Equal(t, int64(123123), ti.AttachmentTotalSizeLimit) + require.Equal(t, 10800*time.Second, ti.AttachmentExpiryDuration) + require.Equal(t, int64(21474836480), ti.AttachmentBandwidthLimit) + require.Equal(t, "price_2", ti.StripeMonthlyPriceID) - // Update tier - ti.EmailLimit = 999999 - require.Nil(t, a.UpdateTier(ti)) + // Update tier + ti.EmailLimit = 999999 + require.Nil(t, a.UpdateTier(ti)) - // List tiers - tiers, err := a.Tiers() - require.Nil(t, err) - require.Equal(t, 2, len(tiers)) + // List tiers + tiers, err := a.Tiers() + require.Nil(t, err) + require.Equal(t, 2, len(tiers)) - ti = tiers[0] - require.Equal(t, "supporter", ti.Code) - require.Equal(t, "Supporter", ti.Name) - require.Equal(t, int64(1), ti.MessageLimit) - require.Equal(t, time.Second, ti.MessageExpiryDuration) - require.Equal(t, int64(1), ti.EmailLimit) - require.Equal(t, int64(1), ti.ReservationLimit) - require.Equal(t, int64(1), ti.AttachmentFileSizeLimit) - require.Equal(t, int64(1), ti.AttachmentTotalSizeLimit) - require.Equal(t, time.Second, ti.AttachmentExpiryDuration) - require.Equal(t, int64(1), ti.AttachmentBandwidthLimit) - require.Equal(t, "price_1", ti.StripeMonthlyPriceID) + ti = tiers[0] + require.Equal(t, "supporter", ti.Code) + require.Equal(t, "Supporter", ti.Name) + require.Equal(t, int64(1), ti.MessageLimit) + require.Equal(t, time.Second, ti.MessageExpiryDuration) + require.Equal(t, int64(1), ti.EmailLimit) + require.Equal(t, int64(1), ti.ReservationLimit) + require.Equal(t, int64(1), ti.AttachmentFileSizeLimit) + require.Equal(t, int64(1), ti.AttachmentTotalSizeLimit) + require.Equal(t, time.Second, ti.AttachmentExpiryDuration) + require.Equal(t, int64(1), ti.AttachmentBandwidthLimit) + require.Equal(t, "price_1", ti.StripeMonthlyPriceID) - ti = tiers[1] - require.Equal(t, "pro", ti.Code) - require.Equal(t, "Pro", ti.Name) - require.Equal(t, int64(123), ti.MessageLimit) - require.Equal(t, 86400*time.Second, ti.MessageExpiryDuration) - require.Equal(t, int64(999999), ti.EmailLimit) // Updatedd! - require.Equal(t, int64(2), ti.ReservationLimit) - require.Equal(t, int64(1231231), ti.AttachmentFileSizeLimit) - require.Equal(t, int64(123123), ti.AttachmentTotalSizeLimit) - require.Equal(t, 10800*time.Second, ti.AttachmentExpiryDuration) - require.Equal(t, int64(21474836480), ti.AttachmentBandwidthLimit) - require.Equal(t, "price_2", ti.StripeMonthlyPriceID) + ti = tiers[1] + require.Equal(t, "pro", ti.Code) + require.Equal(t, "Pro", ti.Name) + require.Equal(t, int64(123), ti.MessageLimit) + require.Equal(t, 86400*time.Second, ti.MessageExpiryDuration) + require.Equal(t, int64(999999), ti.EmailLimit) // Updatedd! + require.Equal(t, int64(2), ti.ReservationLimit) + require.Equal(t, int64(1231231), ti.AttachmentFileSizeLimit) + require.Equal(t, int64(123123), ti.AttachmentTotalSizeLimit) + require.Equal(t, 10800*time.Second, ti.AttachmentExpiryDuration) + require.Equal(t, int64(21474836480), ti.AttachmentBandwidthLimit) + require.Equal(t, "price_2", ti.StripeMonthlyPriceID) - ti, err = a.TierByStripePrice("price_1") - require.Nil(t, err) - require.Equal(t, "supporter", ti.Code) - require.Equal(t, "Supporter", ti.Name) - require.Equal(t, int64(1), ti.MessageLimit) - require.Equal(t, time.Second, ti.MessageExpiryDuration) - require.Equal(t, int64(1), ti.EmailLimit) - require.Equal(t, int64(1), ti.ReservationLimit) - require.Equal(t, int64(1), ti.AttachmentFileSizeLimit) - require.Equal(t, int64(1), ti.AttachmentTotalSizeLimit) - require.Equal(t, time.Second, ti.AttachmentExpiryDuration) - require.Equal(t, int64(1), ti.AttachmentBandwidthLimit) - require.Equal(t, "price_1", ti.StripeMonthlyPriceID) + ti, err = a.TierByStripePrice("price_1") + require.Nil(t, err) + require.Equal(t, "supporter", ti.Code) + require.Equal(t, "Supporter", ti.Name) + require.Equal(t, int64(1), ti.MessageLimit) + require.Equal(t, time.Second, ti.MessageExpiryDuration) + require.Equal(t, int64(1), ti.EmailLimit) + require.Equal(t, int64(1), ti.ReservationLimit) + require.Equal(t, int64(1), ti.AttachmentFileSizeLimit) + require.Equal(t, int64(1), ti.AttachmentTotalSizeLimit) + require.Equal(t, time.Second, ti.AttachmentExpiryDuration) + require.Equal(t, int64(1), ti.AttachmentBandwidthLimit) + require.Equal(t, "price_1", ti.StripeMonthlyPriceID) - // Cannot remove tier, since user has this tier - require.Error(t, a.RemoveTier("pro")) + // Cannot remove tier, since user has this tier + require.Error(t, a.RemoveTier("pro")) - // CAN remove this tier - require.Nil(t, a.RemoveTier("supporter")) + // CAN remove this tier + require.Nil(t, a.RemoveTier("supporter")) - tiers, err = a.Tiers() - require.Nil(t, err) - require.Equal(t, 1, len(tiers)) - require.Equal(t, "pro", tiers[0].Code) - require.Equal(t, "pro", tiers[0].Code) + tiers, err = a.Tiers() + require.Nil(t, err) + require.Equal(t, 1, len(tiers)) + require.Equal(t, "pro", tiers[0].Code) + require.Equal(t, "pro", tiers[0].Code) + }) } func TestAccount_Tier_Create_With_ID(t *testing.T) { - a := newTestManager(t, PermissionDenyAll) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) - require.Nil(t, a.AddTier(&Tier{ - ID: "ti_123", - Code: "pro", - })) + require.Nil(t, a.AddTier(&Tier{ + ID: "ti_123", + Code: "pro", + })) - ti, err := a.Tier("pro") - require.Nil(t, err) - require.Equal(t, "ti_123", ti.ID) + ti, err := a.Tier("pro") + require.Nil(t, err) + require.Equal(t, "ti_123", ti.ID) + }) } func TestManager_Tier_Change_And_Reset(t *testing.T) { - a := newTestManager(t, PermissionDenyAll) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) - // Create tier and user - require.Nil(t, a.AddTier(&Tier{ - Code: "supporter", - Name: "Supporter", - ReservationLimit: 3, - })) - require.Nil(t, a.AddTier(&Tier{ - Code: "pro", - Name: "Pro", - ReservationLimit: 4, - })) - require.Nil(t, a.AddUser("phil", "phil", RoleUser, false)) - require.Nil(t, a.ChangeTier("phil", "pro")) + // Create tier and user + require.Nil(t, a.AddTier(&Tier{ + Code: "supporter", + Name: "Supporter", + ReservationLimit: 3, + })) + require.Nil(t, a.AddTier(&Tier{ + Code: "pro", + Name: "Pro", + ReservationLimit: 4, + })) + require.Nil(t, a.AddUser("phil", "phil", RoleUser, false)) + require.Nil(t, a.ChangeTier("phil", "pro")) - // Add 10 reservations (pro tier allows that) - for i := 0; i < 4; i++ { - require.Nil(t, a.AddReservation("phil", fmt.Sprintf("topic%d", i), PermissionWrite)) - } + // Add 10 reservations (pro tier allows that) + for i := 0; i < 4; i++ { + require.Nil(t, a.AddReservation("phil", fmt.Sprintf("topic%d", i), PermissionWrite)) + } - // Downgrading will not work (too many reservations) - require.Equal(t, ErrTooManyReservations, a.ChangeTier("phil", "supporter")) + // Downgrading will not work (too many reservations) + require.Equal(t, ErrTooManyReservations, a.ChangeTier("phil", "supporter")) - // Downgrade after removing a reservation - require.Nil(t, a.RemoveReservations("phil", "topic0")) - require.Nil(t, a.ChangeTier("phil", "supporter")) + // Downgrade after removing a reservation + require.Nil(t, a.RemoveReservations("phil", "topic0")) + require.Nil(t, a.ChangeTier("phil", "supporter")) - // Resetting will not work (too many reservations) - require.Equal(t, ErrTooManyReservations, a.ResetTier("phil")) + // Resetting will not work (too many reservations) + require.Equal(t, ErrTooManyReservations, a.ResetTier("phil")) - // Resetting after removing all reservations - require.Nil(t, a.RemoveReservations("phil", "topic1", "topic2", "topic3")) - require.Nil(t, a.ResetTier("phil")) + // Resetting after removing all reservations + require.Nil(t, a.RemoveReservations("phil", "topic1", "topic2", "topic3")) + require.Nil(t, a.ResetTier("phil")) + }) } func TestUser_PhoneNumberAddListRemove(t *testing.T) { - a := newTestManager(t, PermissionDenyAll) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) - require.Nil(t, a.AddUser("phil", "phil", RoleUser, false)) - phil, err := a.User("phil") - require.Nil(t, err) - require.Nil(t, a.AddPhoneNumber(phil.ID, "+1234567890")) + require.Nil(t, a.AddUser("phil", "phil", RoleUser, false)) + phil, err := a.User("phil") + require.Nil(t, err) + require.Nil(t, a.AddPhoneNumber(phil.ID, "+1234567890")) - phoneNumbers, err := a.PhoneNumbers(phil.ID) - require.Nil(t, err) - require.Equal(t, 1, len(phoneNumbers)) - require.Equal(t, "+1234567890", phoneNumbers[0]) + phoneNumbers, err := a.PhoneNumbers(phil.ID) + require.Nil(t, err) + require.Equal(t, 1, len(phoneNumbers)) + require.Equal(t, "+1234567890", phoneNumbers[0]) - require.Nil(t, a.RemovePhoneNumber(phil.ID, "+1234567890")) - phoneNumbers, err = a.PhoneNumbers(phil.ID) - require.Nil(t, err) - require.Equal(t, 0, len(phoneNumbers)) + require.Nil(t, a.RemovePhoneNumber(phil.ID, "+1234567890")) + phoneNumbers, err = a.PhoneNumbers(phil.ID) + require.Nil(t, err) + require.Equal(t, 0, len(phoneNumbers)) - // Paranoia check: We do NOT want to keep phone numbers in there - rows, err := testDB(a).Query(`SELECT * FROM user_phone`) - require.Nil(t, err) - require.False(t, rows.Next()) - require.Nil(t, rows.Close()) + // Paranoia check: We do NOT want to keep phone numbers in there + rows, err := testDB(a).Query(`SELECT * FROM user_phone`) + require.Nil(t, err) + require.False(t, rows.Next()) + require.Nil(t, rows.Close()) + }) } func TestUser_PhoneNumberAdd_Multiple_Users_Same_Number(t *testing.T) { - a := newTestManager(t, PermissionDenyAll) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) - require.Nil(t, a.AddUser("phil", "phil", RoleUser, false)) - require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) - phil, err := a.User("phil") - require.Nil(t, err) - ben, err := a.User("ben") - require.Nil(t, err) - require.Nil(t, a.AddPhoneNumber(phil.ID, "+1234567890")) - require.Nil(t, a.AddPhoneNumber(ben.ID, "+1234567890")) + require.Nil(t, a.AddUser("phil", "phil", RoleUser, false)) + require.Nil(t, a.AddUser("ben", "ben", RoleUser, false)) + phil, err := a.User("phil") + require.Nil(t, err) + ben, err := a.User("ben") + require.Nil(t, err) + require.Nil(t, a.AddPhoneNumber(phil.ID, "+1234567890")) + require.Nil(t, a.AddPhoneNumber(ben.ID, "+1234567890")) + }) } func TestManager_Topic_Wildcard_With_Asterisk_Underscore(t *testing.T) { - f := filepath.Join(t.TempDir(), "user.db") - a := newTestManagerFromFile(t, f, "", PermissionDenyAll, DefaultUserPasswordBcryptCost, DefaultUserStatsQueueWriterInterval) - require.Nil(t, a.AllowAccess(Everyone, "*_", PermissionRead)) - require.Nil(t, a.AllowAccess(Everyone, "__*_", PermissionRead)) - require.Nil(t, a.Authorize(nil, "allowed_", PermissionRead)) - require.Nil(t, a.Authorize(nil, "__allowed_", PermissionRead)) - require.Nil(t, a.Authorize(nil, "_allowed_", PermissionRead)) // The "%" in "%\_" matches the first "_" - require.Equal(t, ErrUnauthorized, a.Authorize(nil, "notallowed", PermissionRead)) - require.Equal(t, ErrUnauthorized, a.Authorize(nil, "_notallowed", PermissionRead)) - require.Equal(t, ErrUnauthorized, a.Authorize(nil, "__notallowed", PermissionRead)) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) + require.Nil(t, a.AllowAccess(Everyone, "*_", PermissionRead)) + require.Nil(t, a.AllowAccess(Everyone, "__*_", PermissionRead)) + require.Nil(t, a.Authorize(nil, "allowed_", PermissionRead)) + require.Nil(t, a.Authorize(nil, "__allowed_", PermissionRead)) + require.Nil(t, a.Authorize(nil, "_allowed_", PermissionRead)) // The "%" in "%\_" matches the first "_" + require.Equal(t, ErrUnauthorized, a.Authorize(nil, "notallowed", PermissionRead)) + require.Equal(t, ErrUnauthorized, a.Authorize(nil, "_notallowed", PermissionRead)) + require.Equal(t, ErrUnauthorized, a.Authorize(nil, "__notallowed", PermissionRead)) + }) } func TestManager_Topic_Wildcard_With_Underscore(t *testing.T) { - f := filepath.Join(t.TempDir(), "user.db") - a := newTestManagerFromFile(t, f, "", PermissionDenyAll, DefaultUserPasswordBcryptCost, DefaultUserStatsQueueWriterInterval) - require.Nil(t, a.AllowAccess(Everyone, "mytopic_", PermissionReadWrite)) - require.Nil(t, a.Authorize(nil, "mytopic_", PermissionRead)) - require.Nil(t, a.Authorize(nil, "mytopic_", PermissionWrite)) - require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopicX", PermissionRead)) - require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopicX", PermissionWrite)) + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + a := newTestManager(t, newStore, PermissionDenyAll) + require.Nil(t, a.AllowAccess(Everyone, "mytopic_", PermissionReadWrite)) + require.Nil(t, a.Authorize(nil, "mytopic_", PermissionRead)) + require.Nil(t, a.Authorize(nil, "mytopic_", PermissionWrite)) + require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopicX", PermissionRead)) + require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopicX", PermissionWrite)) + }) } func TestManager_WithProvisionedUsers(t *testing.T) { - f := filepath.Join(t.TempDir(), "user.db") - conf := &Config{ - DefaultAccess: PermissionReadWrite, - ProvisionEnabled: true, - Users: []*User{ - {Name: "philuser", Hash: "$2a$10$YLiO8U21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C", Role: RoleUser}, - {Name: "philadmin", Hash: "$2a$10$YLiO8U21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C", Role: RoleAdmin}, - }, - Access: map[string][]*Grant{ - "philuser": { - {TopicPattern: "stats", Permission: PermissionReadWrite}, - {TopicPattern: "secret", Permission: PermissionRead}, + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + conf := &Config{ + DefaultAccess: PermissionReadWrite, + ProvisionEnabled: true, + Users: []*User{ + {Name: "philuser", Hash: "$2a$10$YLiO8U21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C", Role: RoleUser}, + {Name: "philadmin", Hash: "$2a$10$YLiO8U21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C", Role: RoleAdmin}, }, - }, - Tokens: map[string][]*Token{ - "philuser": { - {Value: "tk_op56p8lz5bf3cxkz9je99v9oc37lo", Label: "Alerts token"}, + Access: map[string][]*Grant{ + "philuser": { + {TopicPattern: "stats", Permission: PermissionReadWrite}, + {TopicPattern: "secret", Permission: PermissionRead}, + }, }, - }, - } - a := newTestManagerFromStoreConfig(t, f, conf) + Tokens: map[string][]*Token{ + "philuser": { + {Value: "tk_op56p8lz5bf3cxkz9je99v9oc37lo", Label: "Alerts token"}, + }, + }, + } + a := newTestManagerFromStoreConfig(t, newStore, conf) - // Manually add user - require.Nil(t, a.AddUser("philmanual", "manual", RoleUser, false)) + // Manually add user + require.Nil(t, a.AddUser("philmanual", "manual", RoleUser, false)) - // Check that the provisioned users are there - users, err := a.Users() - require.Nil(t, err) - require.Len(t, users, 4) - require.Equal(t, "philadmin", users[0].Name) - require.Equal(t, RoleAdmin, users[0].Role) - require.Equal(t, "philmanual", users[1].Name) - require.Equal(t, RoleUser, users[1].Role) - require.Equal(t, "philuser", users[2].Name) - require.Equal(t, RoleUser, users[2].Role) - require.Equal(t, "*", users[3].Name) - provisionedUserID := users[2].ID // "philuser" is the provisioned user + // Check that the provisioned users are there + users, err := a.Users() + require.Nil(t, err) + require.Len(t, users, 4) + require.Equal(t, "philadmin", users[0].Name) + require.Equal(t, RoleAdmin, users[0].Role) + require.Equal(t, "philmanual", users[1].Name) + require.Equal(t, RoleUser, users[1].Role) + require.Equal(t, "philuser", users[2].Name) + require.Equal(t, RoleUser, users[2].Role) + require.Equal(t, "*", users[3].Name) + provisionedUserID := users[2].ID // "philuser" is the provisioned user - grants, err := a.Grants("philuser") - require.Nil(t, err) - require.Equal(t, 2, len(grants)) - require.Equal(t, "secret", grants[0].TopicPattern) - require.Equal(t, PermissionRead, grants[0].Permission) - require.Equal(t, "stats", grants[1].TopicPattern) - require.Equal(t, PermissionReadWrite, grants[1].Permission) + grants, err := a.Grants("philuser") + require.Nil(t, err) + require.Equal(t, 2, len(grants)) + require.Equal(t, "secret", grants[0].TopicPattern) + require.Equal(t, PermissionRead, grants[0].Permission) + require.Equal(t, "stats", grants[1].TopicPattern) + require.Equal(t, PermissionReadWrite, grants[1].Permission) - tokens, err := a.Tokens(provisionedUserID) - require.Nil(t, err) - require.Equal(t, 1, len(tokens)) - require.Equal(t, "tk_op56p8lz5bf3cxkz9je99v9oc37lo", tokens[0].Value) - require.Equal(t, "Alerts token", tokens[0].Label) - require.True(t, tokens[0].Provisioned) + tokens, err := a.Tokens(provisionedUserID) + require.Nil(t, err) + require.Equal(t, 1, len(tokens)) + require.Equal(t, "tk_op56p8lz5bf3cxkz9je99v9oc37lo", tokens[0].Value) + require.Equal(t, "Alerts token", tokens[0].Label) + require.True(t, tokens[0].Provisioned) - // Update the token last access time and origin (so we can check that it is persisted) - lastAccessTime := time.Now().Add(time.Hour) - lastOrigin := netip.MustParseAddr("1.1.9.9") - err = a.store.UpdateTokenLastAccess(tokens[0].Value, lastAccessTime, lastOrigin) - require.Nil(t, err) + // Update the token last access time and origin (so we can check that it is persisted) + lastAccessTime := time.Now().Add(time.Hour) + lastOrigin := netip.MustParseAddr("1.1.9.9") + err = a.store.UpdateTokenLastAccess(tokens[0].Value, lastAccessTime, lastOrigin) + require.Nil(t, err) - // Re-open the DB (second app start) - require.Nil(t, a.Close()) - conf.Users = []*User{ - {Name: "philuser", Hash: "$2a$10$AAAAU21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C", Role: RoleUser}, - } - conf.Access = map[string][]*Grant{ - "philuser": { - {TopicPattern: "stats12", Permission: PermissionReadWrite}, - {TopicPattern: "secret12", Permission: PermissionRead}, - }, - } - conf.Tokens = map[string][]*Token{ - "philuser": { - {Value: "tk_op56p8lz5bf3cxkz9je99v9oc37lo", Label: "Alerts token updated"}, - {Value: "tk_u48wqendnkx9er21pqqcadlytbutx", Label: "Another token"}, - }, - } - a = newTestManagerFromStoreConfig(t, f, conf) + // Re-open the DB (second app start) + require.Nil(t, a.Close()) + conf.Users = []*User{ + {Name: "philuser", Hash: "$2a$10$AAAAU21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C", Role: RoleUser}, + } + conf.Access = map[string][]*Grant{ + "philuser": { + {TopicPattern: "stats12", Permission: PermissionReadWrite}, + {TopicPattern: "secret12", Permission: PermissionRead}, + }, + } + conf.Tokens = map[string][]*Token{ + "philuser": { + {Value: "tk_op56p8lz5bf3cxkz9je99v9oc37lo", Label: "Alerts token updated"}, + {Value: "tk_u48wqendnkx9er21pqqcadlytbutx", Label: "Another token"}, + }, + } + a = newTestManagerFromStoreConfig(t, newStore, conf) - // Check that the provisioned users are there - users, err = a.Users() - require.Nil(t, err) - require.Len(t, users, 3) - require.Equal(t, "philmanual", users[0].Name) - require.Equal(t, "philuser", users[1].Name) - require.Equal(t, RoleUser, users[1].Role) - require.Equal(t, RoleUser, users[0].Role) - require.Equal(t, "*", users[2].Name) + // Check that the provisioned users are there + users, err = a.Users() + require.Nil(t, err) + require.Len(t, users, 3) + require.Equal(t, "philmanual", users[0].Name) + require.Equal(t, "philuser", users[1].Name) + require.Equal(t, RoleUser, users[1].Role) + require.Equal(t, RoleUser, users[0].Role) + require.Equal(t, "*", users[2].Name) - grants, err = a.Grants("philuser") - require.Nil(t, err) - require.Equal(t, 2, len(grants)) - require.Equal(t, "secret12", grants[0].TopicPattern) - require.Equal(t, PermissionRead, grants[0].Permission) - require.Equal(t, "stats12", grants[1].TopicPattern) - require.Equal(t, PermissionReadWrite, grants[1].Permission) + grants, err = a.Grants("philuser") + require.Nil(t, err) + require.Equal(t, 2, len(grants)) + require.Equal(t, "secret12", grants[0].TopicPattern) + require.Equal(t, PermissionRead, grants[0].Permission) + require.Equal(t, "stats12", grants[1].TopicPattern) + require.Equal(t, PermissionReadWrite, grants[1].Permission) - tokens, err = a.Tokens(provisionedUserID) - require.Nil(t, err) - require.Equal(t, 2, len(tokens)) - require.Equal(t, "tk_op56p8lz5bf3cxkz9je99v9oc37lo", tokens[0].Value) - require.Equal(t, "Alerts token updated", tokens[0].Label) - require.Equal(t, lastAccessTime.Unix(), tokens[0].LastAccess.Unix()) - require.Equal(t, lastOrigin, tokens[0].LastOrigin) - require.True(t, tokens[0].Provisioned) - require.Equal(t, "tk_u48wqendnkx9er21pqqcadlytbutx", tokens[1].Value) - require.Equal(t, "Another token", tokens[1].Label) + tokens, err = a.Tokens(provisionedUserID) + require.Nil(t, err) + require.Equal(t, 2, len(tokens)) + require.Equal(t, "tk_op56p8lz5bf3cxkz9je99v9oc37lo", tokens[0].Value) + require.Equal(t, "Alerts token updated", tokens[0].Label) + require.Equal(t, lastAccessTime.Unix(), tokens[0].LastAccess.Unix()) + require.Equal(t, lastOrigin, tokens[0].LastOrigin) + require.True(t, tokens[0].Provisioned) + require.Equal(t, "tk_u48wqendnkx9er21pqqcadlytbutx", tokens[1].Value) + require.Equal(t, "Another token", tokens[1].Label) - // Try changing provisioned user's password - require.Error(t, a.ChangePassword("philuser", "new-pass", false)) + // Try changing provisioned user's password + require.Error(t, a.ChangePassword("philuser", "new-pass", false)) - // Re-open the DB again (third app start) - require.Nil(t, a.Close()) - conf.Users = []*User{} - conf.Access = map[string][]*Grant{} - conf.Tokens = map[string][]*Token{} - a = newTestManagerFromStoreConfig(t, f, conf) + // Re-open the DB again (third app start) + require.Nil(t, a.Close()) + conf.Users = []*User{} + conf.Access = map[string][]*Grant{} + conf.Tokens = map[string][]*Token{} + a = newTestManagerFromStoreConfig(t, newStore, conf) - // Check that the provisioned users are all gone - users, err = a.Users() - require.Nil(t, err) - require.Len(t, users, 2) + // Check that the provisioned users are all gone + users, err = a.Users() + require.Nil(t, err) + require.Len(t, users, 2) - require.Equal(t, "philmanual", users[0].Name) - require.Equal(t, RoleUser, users[0].Role) - require.Equal(t, "*", users[1].Name) + require.Equal(t, "philmanual", users[0].Name) + require.Equal(t, RoleUser, users[0].Role) + require.Equal(t, "*", users[1].Name) - grants, err = a.Grants("philuser") - require.Nil(t, err) - require.Equal(t, 0, len(grants)) + grants, err = a.Grants("philuser") + require.Nil(t, err) + require.Equal(t, 0, len(grants)) - tokens, err = a.Tokens(provisionedUserID) - require.Nil(t, err) - require.Equal(t, 0, len(tokens)) + tokens, err = a.Tokens(provisionedUserID) + require.Nil(t, err) + require.Equal(t, 0, len(tokens)) - var count int - testDB(a).QueryRow("SELECT COUNT(*) FROM user WHERE provisioned = 1").Scan(&count) - require.Equal(t, 0, count) - testDB(a).QueryRow("SELECT COUNT(*) FROM user_access WHERE provisioned = 1").Scan(&count) - require.Equal(t, 0, count) - testDB(a).QueryRow("SELECT COUNT(*) FROM user_token WHERE provisioned = 1").Scan(&count) + // Verify no provisioned data remains + for _, u := range users { + require.False(t, u.Provisioned) + userGrants, err := a.Grants(u.Name) + require.Nil(t, err) + for _, g := range userGrants { + require.False(t, g.Provisioned) + } + userTokens, err := a.Tokens(u.ID) + require.Nil(t, err) + for _, tk := range userTokens { + require.False(t, tk.Provisioned) + } + } + }) } func TestManager_UpdateNonProvisionedUsersToProvisionedUsers(t *testing.T) { - f := filepath.Join(t.TempDir(), "user.db") - conf := &Config{ - DefaultAccess: PermissionReadWrite, - ProvisionEnabled: true, - Users: []*User{}, - Access: map[string][]*Grant{ - Everyone: { - {TopicPattern: "food", Permission: PermissionRead}, + forEachBackend(t, func(t *testing.T, newStore newStoreFunc) { + conf := &Config{ + DefaultAccess: PermissionReadWrite, + ProvisionEnabled: true, + Users: []*User{}, + Access: map[string][]*Grant{ + Everyone: { + {TopicPattern: "food", Permission: PermissionRead}, + }, }, - }, - } - a := newTestManagerFromStoreConfig(t, f, conf) + } + a := newTestManagerFromStoreConfig(t, newStore, conf) - // Manually add user - require.Nil(t, a.AddUser("philuser", "manual", RoleUser, false)) - require.Nil(t, a.AllowAccess("philuser", "stats", PermissionReadWrite)) - require.Nil(t, a.AllowAccess("philuser", "food", PermissionReadWrite)) + // Manually add user + require.Nil(t, a.AddUser("philuser", "manual", RoleUser, false)) + require.Nil(t, a.AllowAccess("philuser", "stats", PermissionReadWrite)) + require.Nil(t, a.AllowAccess("philuser", "food", PermissionReadWrite)) - users, err := a.Users() - require.Nil(t, err) - require.Len(t, users, 2) - require.Equal(t, "philuser", users[0].Name) - require.Equal(t, RoleUser, users[0].Role) - require.False(t, users[0].Provisioned) // Manually added + users, err := a.Users() + require.Nil(t, err) + require.Len(t, users, 2) + require.Equal(t, "philuser", users[0].Name) + require.Equal(t, RoleUser, users[0].Role) + require.False(t, users[0].Provisioned) // Manually added - grants, err := a.Grants("philuser") - require.Nil(t, err) - require.Equal(t, 2, len(grants)) - require.Equal(t, "stats", grants[0].TopicPattern) - require.Equal(t, PermissionReadWrite, grants[0].Permission) - require.False(t, grants[0].Provisioned) // Manually added - require.Equal(t, "food", grants[1].TopicPattern) - require.Equal(t, PermissionReadWrite, grants[1].Permission) - require.False(t, grants[1].Provisioned) // Manually added + grants, err := a.Grants("philuser") + require.Nil(t, err) + require.Equal(t, 2, len(grants)) + require.Equal(t, "stats", grants[0].TopicPattern) + require.Equal(t, PermissionReadWrite, grants[0].Permission) + require.False(t, grants[0].Provisioned) // Manually added + require.Equal(t, "food", grants[1].TopicPattern) + require.Equal(t, PermissionReadWrite, grants[1].Permission) + require.False(t, grants[1].Provisioned) // Manually added - grants, err = a.Grants(Everyone) - require.Nil(t, err) - require.Equal(t, 1, len(grants)) - require.Equal(t, "food", grants[0].TopicPattern) - require.Equal(t, PermissionRead, grants[0].Permission) - require.True(t, grants[0].Provisioned) // Provisioned entry + grants, err = a.Grants(Everyone) + require.Nil(t, err) + require.Equal(t, 1, len(grants)) + require.Equal(t, "food", grants[0].TopicPattern) + require.Equal(t, PermissionRead, grants[0].Permission) + require.True(t, grants[0].Provisioned) // Provisioned entry - // Re-open the DB (second app start) - require.Nil(t, a.Close()) - conf.Users = []*User{ - {Name: "philuser", Hash: "$2a$10$AAAAU21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C", Role: RoleUser}, - } - conf.Access = map[string][]*Grant{ - "philuser": { - {TopicPattern: "stats", Permission: PermissionReadWrite}, - }, - } - a = newTestManagerFromStoreConfig(t, f, conf) + // Re-open the DB (second app start) + require.Nil(t, a.Close()) + conf.Users = []*User{ + {Name: "philuser", Hash: "$2a$10$AAAAU21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C", Role: RoleUser}, + } + conf.Access = map[string][]*Grant{ + "philuser": { + {TopicPattern: "stats", Permission: PermissionReadWrite}, + }, + } + a = newTestManagerFromStoreConfig(t, newStore, conf) - // Check that the user was "upgraded" to a provisioned user - users, err = a.Users() - require.Nil(t, err) - require.Len(t, users, 2) - require.Equal(t, "philuser", users[0].Name) - require.Equal(t, RoleUser, users[0].Role) - require.Equal(t, "$2a$10$AAAAU21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C", users[0].Hash) - require.True(t, users[0].Provisioned) // Updated to provisioned! + // Check that the user was "upgraded" to a provisioned user + users, err = a.Users() + require.Nil(t, err) + require.Len(t, users, 2) + require.Equal(t, "philuser", users[0].Name) + require.Equal(t, RoleUser, users[0].Role) + require.Equal(t, "$2a$10$AAAAU21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C", users[0].Hash) + require.True(t, users[0].Provisioned) // Updated to provisioned! - grants, err = a.Grants("philuser") - require.Nil(t, err) - require.Equal(t, 2, len(grants)) - require.Equal(t, "stats", grants[0].TopicPattern) - require.Equal(t, PermissionReadWrite, grants[0].Permission) - require.True(t, grants[0].Provisioned) // Updated to provisioned! - require.Equal(t, "food", grants[1].TopicPattern) - require.Equal(t, PermissionReadWrite, grants[1].Permission) - require.False(t, grants[1].Provisioned) // Manually added grants stay! + grants, err = a.Grants("philuser") + require.Nil(t, err) + require.Equal(t, 2, len(grants)) + require.Equal(t, "stats", grants[0].TopicPattern) + require.Equal(t, PermissionReadWrite, grants[0].Permission) + require.True(t, grants[0].Provisioned) // Updated to provisioned! + require.Equal(t, "food", grants[1].TopicPattern) + require.Equal(t, PermissionReadWrite, grants[1].Permission) + require.False(t, grants[1].Provisioned) // Manually added grants stay! - grants, err = a.Grants(Everyone) - require.Nil(t, err) - require.Empty(t, grants) + grants, err = a.Grants(Everyone) + require.Nil(t, err) + require.Empty(t, grants) + }) } func TestToFromSQLWildcard(t *testing.T) { @@ -1340,7 +1440,7 @@ func TestMigrationFrom1(t *testing.T) { role TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS access ( - user TEXT NOT NULL, + user TEXT NOT NULL, topic TEXT NOT NULL, read INT NOT NULL, write INT NOT NULL, @@ -1351,7 +1451,7 @@ func TestMigrationFrom1(t *testing.T) { version INT NOT NULL ); INSERT INTO schemaVersion (id, version) VALUES (1, 1); - COMMIT; + COMMIT; `) require.Nil(t, err) @@ -1363,7 +1463,7 @@ func TestMigrationFrom1(t *testing.T) { INSERT INTO access (user, topic, read, write) VALUES ('ben', 'stats', 1, 1); INSERT INTO access (user, topic, read, write) VALUES ('ben', 'secret', 1, 0); INSERT INTO access (user, topic, read, write) VALUES ('*', 'stats', 1, 0); - COMMIT; + COMMIT; `) require.Nil(t, err) @@ -1495,7 +1595,7 @@ func TestMigrationFrom4(t *testing.T) { INSERT INTO user (id, user, pass, role, sync_topic, created) VALUES ('u_everyone', '*', '', 'anonymous', '', UNIXEPOCH()) ON CONFLICT (id) DO NOTHING; - INSERT INTO schemaVersion (id, version) VALUES (1, 4); + INSERT INTO schemaVersion (id, version) VALUES (1, 4); COMMIT; `) require.Nil(t, err) @@ -1506,7 +1606,7 @@ func TestMigrationFrom4(t *testing.T) { INSERT INTO user_access (user_id, topic, read, write) values ('u_everyone', 'mytopic_', 1, 1); INSERT INTO user_access (user_id, topic, read, write) values ('u_everyone', 'up%', 1, 1); INSERT INTO user_access (user_id, topic, read, write) values ('u_everyone', 'down_%', 1, 1); - COMMIT; + COMMIT; `) require.Nil(t, err) @@ -1568,8 +1668,16 @@ func checkSchemaVersion(t *testing.T, db *sql.DB) { require.Nil(t, rows.Close()) } -func newTestManager(t *testing.T, defaultAccess Permission) *Manager { - return newTestManagerFromFile(t, filepath.Join(t.TempDir(), "user.db"), "", defaultAccess, bcrypt.MinCost, DefaultUserStatsQueueWriterInterval) +func newTestManager(t *testing.T, newStore newStoreFunc, defaultAccess Permission) *Manager { + store := newStore() + a, err := NewManager(store, &Config{ + DefaultAccess: defaultAccess, + BcryptCost: bcrypt.MinCost, + QueueWriterInterval: DefaultUserStatsQueueWriterInterval, + }) + require.Nil(t, err) + t.Cleanup(func() { a.Close() }) + return a } func newTestManagerFromFile(t *testing.T, filename, startupQueries string, defaultAccess Permission, bcryptCost int, statsWriterInterval time.Duration) *Manager { @@ -1585,14 +1693,14 @@ func newTestManagerFromFile(t *testing.T, filename, startupQueries string, defau return a } +func newTestManagerFromStoreConfig(t *testing.T, newStore newStoreFunc, conf *Config) *Manager { + store := newStore() + a, err := NewManager(store, conf) + require.Nil(t, err) + t.Cleanup(func() { a.Close() }) + return a +} + func testDB(a *Manager) *sql.DB { return a.store.(*commonStore).db } - -func newTestManagerFromStoreConfig(t *testing.T, filename string, conf *Config) *Manager { - store, err := NewSQLiteStore(filename, "") - require.Nil(t, err) - a, err := NewManager(store, conf) - require.Nil(t, err) - return a -} diff --git a/user/store.go b/user/store.go index bde62658..4dbc2bad 100644 --- a/user/store.go +++ b/user/store.go @@ -19,6 +19,7 @@ type Store interface { User(username string) (*User, error) UserByToken(token string) (*User, error) UserByStripeCustomer(customerID string) (*User, error) + UserIDByUsername(username string) (string, error) Users() ([]*User, error) UsersCount() (int64, error) AddUser(username, hash string, role Role, provisioned bool) error @@ -33,6 +34,7 @@ type Store interface { ResetTier(username string) error UpdateStats(userID string, stats *Stats) error ResetStats() error + // Token operations CreateToken(userID, token, label string, lastAccess time.Time, lastOrigin netip.Addr, expires time.Time, provisioned bool) (*Token, error) Token(userID, token string) (*Token, error) @@ -45,6 +47,7 @@ type Store interface { RemoveExpiredTokens() error TokenCount(userID string) (int, error) RemoveExcessTokens(userID string, maxCount int) error + // Access operations AuthorizeTopicAccess(usernameOrEveryone, topic string) (read, write, found bool, err error) AllGrants() (map[string][]Grant, error) @@ -57,6 +60,7 @@ type Store interface { ReservationsCount(username string) (int64, error) ReservationOwner(topic string) (string, error) OtherAccessCount(username, topic string) (int, error) + // Tier operations AddTier(tier *Tier) error UpdateTier(tier *Tier) error @@ -64,15 +68,14 @@ type Store interface { Tiers() ([]*Tier, error) Tier(code string) (*Tier, error) TierByStripePrice(priceID string) (*Tier, error) + // Phone operations PhoneNumbers(userID string) ([]string, error) AddPhoneNumber(userID, phoneNumber string) error RemovePhoneNumber(userID, phoneNumber string) error - // Billing + + // Other stuff ChangeBilling(username string, billing *Billing) error - // Internal helpers - UserIDByUsername(username string) (string, error) - // System Close() error }