diff --git a/server/server.go b/server/server.go index be9a910..8bd1972 100644 --- a/server/server.go +++ b/server/server.go @@ -53,13 +53,7 @@ import ( rate limiting: - login/account endpoints Tests: - - APIs - - CRUD tokens - - Expire tokens - - userManager can be nil - visitor with/without user - - userManager. - Later: - Password reset - Pricing @@ -569,7 +563,7 @@ func (s *Server) handlePublishWithoutResponse(r *http.Request, v *visitor) (*mes } } v.IncrMessages() - if v.user != nil { + if s.userManager != nil && v.user != nil { s.userManager.EnqueueStats(v.user) } s.mu.Lock() @@ -1342,7 +1336,7 @@ func (s *Server) sendDelayedMessages() error { } for _, m := range messages { var v *visitor - if m.User != "" { + if s.userManager != nil && m.User != "" { user, err := s.userManager.User(m.User) if err != nil { log.Warn("%s Error sending delayed message: %s", logMessagePrefix(v, m), err.Error()) diff --git a/server/server_account.go b/server/server_account.go index 4213386..d0e3789 100644 --- a/server/server_account.go +++ b/server/server_account.go @@ -8,7 +8,8 @@ import ( ) const ( - jsonBodyBytesLimit = 4096 + jsonBodyBytesLimit = 4096 + subscriptionIDLength = 16 ) func (s *Server) handleAccountCreate(w http.ResponseWriter, r *http.Request, v *visitor) error { @@ -232,7 +233,7 @@ func (s *Server) handleAccountSubscriptionAdd(w http.ResponseWriter, r *http.Req } } if newSubscription.ID == "" { - newSubscription.ID = util.RandomString(16) + newSubscription.ID = util.RandomString(subscriptionIDLength) v.user.Prefs.Subscriptions = append(v.user.Prefs.Subscriptions, newSubscription) if err := s.userManager.ChangeSettings(v.user); err != nil { return err diff --git a/user/manager.go b/user/manager.go index 17a8e0d..a5f6a37 100644 --- a/user/manager.go +++ b/user/manager.go @@ -80,7 +80,7 @@ const ( FROM user u JOIN user_token t on u.id = t.user_id LEFT JOIN plan p on p.id = u.plan_id - WHERE t.token = ? + WHERE t.token = ? AND t.expires >= ? ` selectTopicPermsQuery = ` SELECT read, write @@ -138,17 +138,23 @@ const ( // Manager is an implementation of Manager. It stores users and access control list // in a SQLite database. type Manager struct { - db *sql.DB - defaultRead bool - defaultWrite bool - statsQueue map[string]*User // Username -> User, for "unimportant" user updates - mu sync.Mutex + db *sql.DB + defaultRead bool // Default read permission if no ACL matches + defaultWrite bool // Default write permission if no ACL matches + statsQueue map[string]*User // Username -> User, for "unimportant" user updates + tokenExpiryInterval time.Duration // Duration after which tokens expire, and by which tokens are extended + mu sync.Mutex } var _ Auther = (*Manager)(nil) // NewManager creates a new Manager instance func NewManager(filename string, defaultRead, defaultWrite bool) (*Manager, error) { + return newManager(filename, defaultRead, defaultWrite, userTokenExpiryDuration, userStatsQueueWriterInterval) +} + +// NewManager creates a new Manager instance +func newManager(filename string, defaultRead, defaultWrite bool, tokenExpiryDuration, statsWriterInterval time.Duration) (*Manager, error) { db, err := sql.Open("sqlite3", filename) if err != nil { return nil, err @@ -157,12 +163,13 @@ func NewManager(filename string, defaultRead, defaultWrite bool) (*Manager, erro return nil, err } manager := &Manager{ - db: db, - defaultRead: defaultRead, - defaultWrite: defaultWrite, - statsQueue: make(map[string]*User), + db: db, + defaultRead: defaultRead, + defaultWrite: defaultWrite, + statsQueue: make(map[string]*User), + tokenExpiryInterval: tokenExpiryDuration, } - go manager.userStatsQueueWriter() + go manager.userStatsQueueWriter(statsWriterInterval) return manager, nil } @@ -263,8 +270,8 @@ func (a *Manager) EnqueueStats(user *User) { a.statsQueue[user.Name] = user } -func (a *Manager) userStatsQueueWriter() { - ticker := time.NewTicker(userStatsQueueWriterInterval) +func (a *Manager) userStatsQueueWriter(interval time.Duration) { + ticker := time.NewTicker(interval) for range ticker.C { if err := a.writeUserStatsQueue(); err != nil { log.Warn("UserManager: Writing user stats queue failed: %s", err.Error()) @@ -409,7 +416,7 @@ func (a *Manager) User(username string) (*User, error) { } func (a *Manager) userByToken(token string) (*User, error) { - rows, err := a.db.Query(selectUserByTokenQuery, token) + rows, err := a.db.Query(selectUserByTokenQuery, token, time.Now().Unix()) if err != nil { return nil, err } diff --git a/user/manager_test.go b/user/manager_test.go index 91557b6..c3a7e5e 100644 --- a/user/manager_test.go +++ b/user/manager_test.go @@ -1,8 +1,7 @@ -package user_test +package user import ( "github.com/stretchr/testify/require" - "heckel.io/ntfy/user" "path/filepath" "strings" "testing" @@ -13,29 +12,29 @@ const minBcryptTimingMillis = int64(50) // Ideally should be >100ms, but this sh func TestManager_FullScenario_Default_DenyAll(t *testing.T) { a := newTestManager(t, false, false) - require.Nil(t, a.AddUser("phil", "phil", user.RoleAdmin)) - require.Nil(t, a.AddUser("ben", "ben", user.RoleUser)) + require.Nil(t, a.AddUser("phil", "phil", RoleAdmin)) + require.Nil(t, a.AddUser("ben", "ben", RoleUser)) require.Nil(t, a.AllowAccess("ben", "mytopic", true, true)) require.Nil(t, a.AllowAccess("ben", "readme", true, false)) require.Nil(t, a.AllowAccess("ben", "writeme", false, true)) require.Nil(t, a.AllowAccess("ben", "everyonewrite", false, false)) // How unfair! - require.Nil(t, a.AllowAccess(user.Everyone, "announcements", true, false)) - require.Nil(t, a.AllowAccess(user.Everyone, "everyonewrite", true, true)) - require.Nil(t, a.AllowAccess(user.Everyone, "up*", false, true)) // Everyone can write to /up* + require.Nil(t, a.AllowAccess(Everyone, "announcements", true, false)) + require.Nil(t, a.AllowAccess(Everyone, "everyonewrite", true, true)) + require.Nil(t, a.AllowAccess(Everyone, "up*", false, true)) // 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, user.RoleAdmin, phil.Role) - require.Equal(t, []user.Grant{}, phil.Grants) + require.Equal(t, RoleAdmin, phil.Role) + require.Equal(t, []Grant{}, phil.Grants) 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, user.RoleUser, ben.Role) - require.Equal(t, []user.Grant{ + require.Equal(t, RoleUser, ben.Role) + require.Equal(t, []Grant{ {"mytopic", true, true}, {"writeme", false, true}, {"readme", true, false}, @@ -44,62 +43,62 @@ func TestManager_FullScenario_Default_DenyAll(t *testing.T) { notben, err := a.Authenticate("ben", "this is wrong") require.Nil(t, notben) - require.Equal(t, user.ErrUnauthenticated, err) + require.Equal(t, ErrUnauthenticated, err) // Admin can do everything - require.Nil(t, a.Authorize(phil, "sometopic", user.PermissionWrite)) - require.Nil(t, a.Authorize(phil, "mytopic", user.PermissionRead)) - require.Nil(t, a.Authorize(phil, "readme", user.PermissionWrite)) - require.Nil(t, a.Authorize(phil, "writeme", user.PermissionWrite)) - require.Nil(t, a.Authorize(phil, "announcements", user.PermissionWrite)) - require.Nil(t, a.Authorize(phil, "everyonewrite", user.PermissionWrite)) + 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", user.PermissionWrite)) - require.Nil(t, a.Authorize(ben, "mytopic", user.PermissionRead)) - require.Nil(t, a.Authorize(ben, "readme", user.PermissionRead)) - require.Equal(t, user.ErrUnauthorized, a.Authorize(ben, "readme", user.PermissionWrite)) - require.Equal(t, user.ErrUnauthorized, a.Authorize(ben, "writeme", user.PermissionRead)) - require.Nil(t, a.Authorize(ben, "writeme", user.PermissionWrite)) - require.Nil(t, a.Authorize(ben, "writeme", user.PermissionWrite)) - require.Equal(t, user.ErrUnauthorized, a.Authorize(ben, "everyonewrite", user.PermissionRead)) - require.Equal(t, user.ErrUnauthorized, a.Authorize(ben, "everyonewrite", user.PermissionWrite)) - require.Nil(t, a.Authorize(ben, "announcements", user.PermissionRead)) - require.Equal(t, user.ErrUnauthorized, a.Authorize(ben, "announcements", user.PermissionWrite)) + 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)) // Everyone else can do barely anything - require.Equal(t, user.ErrUnauthorized, a.Authorize(nil, "sometopicnotinthelist", user.PermissionRead)) - require.Equal(t, user.ErrUnauthorized, a.Authorize(nil, "sometopicnotinthelist", user.PermissionWrite)) - require.Equal(t, user.ErrUnauthorized, a.Authorize(nil, "mytopic", user.PermissionRead)) - require.Equal(t, user.ErrUnauthorized, a.Authorize(nil, "mytopic", user.PermissionWrite)) - require.Equal(t, user.ErrUnauthorized, a.Authorize(nil, "readme", user.PermissionRead)) - require.Equal(t, user.ErrUnauthorized, a.Authorize(nil, "readme", user.PermissionWrite)) - require.Equal(t, user.ErrUnauthorized, a.Authorize(nil, "writeme", user.PermissionRead)) - require.Equal(t, user.ErrUnauthorized, a.Authorize(nil, "writeme", user.PermissionWrite)) - require.Equal(t, user.ErrUnauthorized, a.Authorize(nil, "announcements", user.PermissionWrite)) - require.Nil(t, a.Authorize(nil, "announcements", user.PermissionRead)) - require.Nil(t, a.Authorize(nil, "everyonewrite", user.PermissionRead)) - require.Nil(t, a.Authorize(nil, "everyonewrite", user.PermissionWrite)) - require.Nil(t, a.Authorize(nil, "up1234", user.PermissionWrite)) // Wildcard permission - require.Nil(t, a.Authorize(nil, "up5678", user.PermissionWrite)) + 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_AddUser_Invalid(t *testing.T) { a := newTestManager(t, false, false) - require.Equal(t, user.ErrInvalidArgument, a.AddUser(" invalid ", "pass", user.RoleAdmin)) - require.Equal(t, user.ErrInvalidArgument, a.AddUser("validuser", "pass", "invalid-role")) + require.Equal(t, ErrInvalidArgument, a.AddUser(" invalid ", "pass", RoleAdmin)) + require.Equal(t, ErrInvalidArgument, a.AddUser("validuser", "pass", "invalid-role")) } func TestManager_AddUser_Timing(t *testing.T) { a := newTestManager(t, false, false) start := time.Now().UnixMilli() - require.Nil(t, a.AddUser("user", "pass", user.RoleAdmin)) + require.Nil(t, a.AddUser("user", "pass", RoleAdmin)) require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis) } func TestManager_Authenticate_Timing(t *testing.T) { a := newTestManager(t, false, false) - require.Nil(t, a.AddUser("user", "pass", user.RoleAdmin)) + require.Nil(t, a.AddUser("user", "pass", RoleAdmin)) // Timing a correct attempt start := time.Now().UnixMilli() @@ -110,53 +109,53 @@ func TestManager_Authenticate_Timing(t *testing.T) { // Timing an incorrect attempt start = time.Now().UnixMilli() _, err = a.Authenticate("user", "INCORRECT") - require.Equal(t, user.ErrUnauthenticated, err) + require.Equal(t, ErrUnauthenticated, err) require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis) // Timing a non-existing user attempt start = time.Now().UnixMilli() _, err = a.Authenticate("DOES-NOT-EXIST", "hithere") - require.Equal(t, user.ErrUnauthenticated, err) + require.Equal(t, ErrUnauthenticated, err) require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis) } func TestManager_UserManagement(t *testing.T) { a := newTestManager(t, false, false) - require.Nil(t, a.AddUser("phil", "phil", user.RoleAdmin)) - require.Nil(t, a.AddUser("ben", "ben", user.RoleUser)) + require.Nil(t, a.AddUser("phil", "phil", RoleAdmin)) + require.Nil(t, a.AddUser("ben", "ben", RoleUser)) require.Nil(t, a.AllowAccess("ben", "mytopic", true, true)) require.Nil(t, a.AllowAccess("ben", "readme", true, false)) require.Nil(t, a.AllowAccess("ben", "writeme", false, true)) require.Nil(t, a.AllowAccess("ben", "everyonewrite", false, false)) // How unfair! - require.Nil(t, a.AllowAccess(user.Everyone, "announcements", true, false)) - require.Nil(t, a.AllowAccess(user.Everyone, "everyonewrite", true, true)) + require.Nil(t, a.AllowAccess(Everyone, "announcements", true, false)) + require.Nil(t, a.AllowAccess(Everyone, "everyonewrite", true, true)) // 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$10$")) - require.Equal(t, user.RoleAdmin, phil.Role) - require.Equal(t, []user.Grant{}, phil.Grants) + require.Equal(t, RoleAdmin, phil.Role) + require.Equal(t, []Grant{}, phil.Grants) ben, err := a.User("ben") require.Nil(t, err) require.Equal(t, "ben", ben.Name) require.True(t, strings.HasPrefix(ben.Hash, "$2a$10$")) - require.Equal(t, user.RoleUser, ben.Role) - require.Equal(t, []user.Grant{ + require.Equal(t, RoleUser, ben.Role) + require.Equal(t, []Grant{ {"mytopic", true, true}, {"writeme", false, true}, {"readme", true, false}, {"everyonewrite", false, false}, }, ben.Grants) - everyone, err := a.User(user.Everyone) + everyone, err := a.User(Everyone) require.Nil(t, err) require.Equal(t, "*", everyone.Name) require.Equal(t, "", everyone.Hash) - require.Equal(t, user.RoleAnonymous, everyone.Role) - require.Equal(t, []user.Grant{ + require.Equal(t, RoleAnonymous, everyone.Role) + require.Equal(t, []Grant{ {"everyonewrite", true, true}, {"announcements", true, false}, }, everyone.Grants) @@ -165,22 +164,22 @@ func TestManager_UserManagement(t *testing.T) { require.Nil(t, a.AllowAccess("ben", "mytopic", true, true)) // Overwrite! require.Nil(t, a.AllowAccess("ben", "readme", true, false)) require.Nil(t, a.AllowAccess("ben", "writeme", false, true)) - require.Nil(t, a.Authorize(ben, "mytopic", user.PermissionRead)) - require.Nil(t, a.Authorize(ben, "mytopic", user.PermissionWrite)) - require.Nil(t, a.Authorize(ben, "readme", user.PermissionRead)) - require.Nil(t, a.Authorize(ben, "writeme", user.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, user.ErrUnauthorized, a.Authorize(ben, "mytopic", user.PermissionWrite)) // Revoked - require.Equal(t, user.ErrUnauthorized, a.Authorize(ben, "mytopic", user.PermissionRead)) // Revoked - require.Nil(t, a.Authorize(ben, "readme", user.PermissionRead)) // Unchanged - require.Nil(t, a.Authorize(ben, "writeme", user.PermissionWrite)) // Unchanged + 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, user.ErrUnauthorized, a.Authorize(ben, "readme", user.PermissionRead)) // Revoked - require.Equal(t, user.ErrUnauthorized, a.Authorize(ben, "wrtiteme", user.PermissionWrite)) // Revoked + 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() @@ -193,7 +192,7 @@ func TestManager_UserManagement(t *testing.T) { // Remove user require.Nil(t, a.RemoveUser("ben")) _, err = a.User("ben") - require.Equal(t, user.ErrNotFound, err) + require.Equal(t, ErrNotFound, err) users, err = a.Users() require.Nil(t, err) @@ -204,40 +203,40 @@ func TestManager_UserManagement(t *testing.T) { func TestManager_ChangePassword(t *testing.T) { a := newTestManager(t, false, false) - require.Nil(t, a.AddUser("phil", "phil", user.RoleAdmin)) + require.Nil(t, a.AddUser("phil", "phil", RoleAdmin)) _, err := a.Authenticate("phil", "phil") require.Nil(t, err) require.Nil(t, a.ChangePassword("phil", "newpass")) _, err = a.Authenticate("phil", "phil") - require.Equal(t, user.ErrUnauthenticated, err) + require.Equal(t, ErrUnauthenticated, err) _, err = a.Authenticate("phil", "newpass") require.Nil(t, err) } func TestManager_ChangeRole(t *testing.T) { a := newTestManager(t, false, false) - require.Nil(t, a.AddUser("ben", "ben", user.RoleUser)) + require.Nil(t, a.AddUser("ben", "ben", RoleUser)) require.Nil(t, a.AllowAccess("ben", "mytopic", true, true)) require.Nil(t, a.AllowAccess("ben", "readme", true, false)) ben, err := a.User("ben") require.Nil(t, err) - require.Equal(t, user.RoleUser, ben.Role) + require.Equal(t, RoleUser, ben.Role) require.Equal(t, 2, len(ben.Grants)) - require.Nil(t, a.ChangeRole("ben", user.RoleAdmin)) + require.Nil(t, a.ChangeRole("ben", RoleAdmin)) ben, err = a.User("ben") require.Nil(t, err) - require.Equal(t, user.RoleAdmin, ben.Role) + require.Equal(t, RoleAdmin, ben.Role) require.Equal(t, 0, len(ben.Grants)) } func TestManager_Token_Valid(t *testing.T) { a := newTestManager(t, false, false) - require.Nil(t, a.AddUser("ben", "ben", user.RoleUser)) + require.Nil(t, a.AddUser("ben", "ben", RoleUser)) u, err := a.User("ben") require.Nil(t, err) @@ -256,26 +255,103 @@ func TestManager_Token_Valid(t *testing.T) { // Remove token and auth again require.Nil(t, a.RemoveToken(u2)) u3, err := a.AuthenticateToken(token.Value) - require.Equal(t, user.ErrUnauthenticated, err) + require.Equal(t, ErrUnauthenticated, err) require.Nil(t, u3) } func TestManager_Token_Invalid(t *testing.T) { a := newTestManager(t, false, false) - require.Nil(t, a.AddUser("ben", "ben", user.RoleUser)) + require.Nil(t, a.AddUser("ben", "ben", RoleUser)) u, err := a.AuthenticateToken(strings.Repeat("x", 32)) // 32 == token length require.Nil(t, u) - require.Equal(t, user.ErrUnauthenticated, err) + require.Equal(t, ErrUnauthenticated, err) u, err = a.AuthenticateToken("not long enough anyway") require.Nil(t, u) - require.Equal(t, user.ErrUnauthenticated, err) + require.Equal(t, ErrUnauthenticated, err) } -func newTestManager(t *testing.T, defaultRead, defaultWrite bool) *user.Manager { - filename := filepath.Join(t.TempDir(), "user.db") - a, err := user.NewManager(filename, defaultRead, defaultWrite) +func TestManager_Token_Expire(t *testing.T) { + a := newTestManager(t, false, false) + require.Nil(t, a.AddUser("ben", "ben", RoleUser)) + + u, err := a.User("ben") + require.Nil(t, err) + + // Create tokens for user + token1, err := a.CreateToken(u) + 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) + 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) + + _, err = a.AuthenticateToken(token2.Value) + require.Nil(t, err) + + // Modify token expiration in database + _, err = a.db.Exec("UPDATE user_token SET expires = 1 WHERE token = ?", token1.Value) + require.Nil(t, err) + + // Now token1 shouldn't work anymore + _, err = a.AuthenticateToken(token1.Value) + require.Equal(t, ErrUnauthenticated, err) + + result, err := a.db.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()) + + // Expire tokens and check database rows + require.Nil(t, a.RemoveExpiredTokens()) + + result, err = a.db.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()) +} + +func TestManager_EnqueueStats(t *testing.T) { + a, err := newManager(filepath.Join(t.TempDir(), "db"), true, true, time.Hour, 1500*time.Millisecond) + require.Nil(t, err) + require.Nil(t, a.AddUser("ben", "ben", RoleUser)) + + // 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) + + u.Stats.Messages = 11 + u.Stats.Emails = 2 + a.EnqueueStats(u) + + // 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) +} + +func newTestManager(t *testing.T, defaultRead, defaultWrite bool) *Manager { + a, err := NewManager(filepath.Join(t.TempDir(), "db"), defaultRead, defaultWrite) require.Nil(t, err) return a } diff --git a/util/util.go b/util/util.go index abc0df9..1966a86 100644 --- a/util/util.go +++ b/util/util.go @@ -31,6 +31,7 @@ var ( noQuotesRegex = regexp.MustCompile(`^[-_./:@a-zA-Z0-9]+$`) ) +// Errors for ReadJSON and ReadJSONWithLimit functions var ( ErrInvalidJSON = errors.New("invalid JSON") ErrTooLargeJSON = errors.New("too large JSON")