diff --git a/server/server.go b/server/server.go index 63479191..2747f0bf 100644 --- a/server/server.go +++ b/server/server.go @@ -36,13 +36,15 @@ import ( /* TODO + limits: + message cache duration + Keep 10000 messages or keep X days? + Attachment expiration based on plan database migration reserve topics purge accounts that were not logged into in X reset daily limits for users Account usage not updated "in real time" - Attachment expiration based on plan - Plan: Keep 10000 messages or keep X days? Sync: - "mute" setting - figure out what settings are "web" or "phone" diff --git a/server/server_account.go b/server/server_account.go index 1568ccf4..f832cd99 100644 --- a/server/server_account.go +++ b/server/server_account.go @@ -159,7 +159,7 @@ func (s *Server) handleAccountTokenIssue(w http.ResponseWriter, r *http.Request, w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this response := &apiAccountTokenResponse{ Token: token.Value, - Expires: token.Expires, + Expires: token.Expires.Unix(), } if err := json.NewEncoder(w).Encode(response); err != nil { return err @@ -182,7 +182,7 @@ func (s *Server) handleAccountTokenExtend(w http.ResponseWriter, r *http.Request w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this response := &apiAccountTokenResponse{ Token: token.Value, - Expires: token.Expires, + Expires: token.Expires.Unix(), } if err := json.NewEncoder(w).Encode(response); err != nil { return err diff --git a/user/manager.go b/user/manager.go index d014440f..7a5e46a7 100644 --- a/user/manager.go +++ b/user/manager.go @@ -188,6 +188,9 @@ func (a *Manager) Authenticate(username, password string) (*User, error) { // AuthenticateToken checks if the token exists and returns the associated User if it does. // The method sets the User.Token value to the token that was used for authentication. func (a *Manager) AuthenticateToken(token string) (*User, error) { + if len(token) != tokenLength { + return nil, ErrUnauthenticated + } user, err := a.userByToken(token) if err != nil { return nil, ErrUnauthenticated @@ -205,7 +208,7 @@ func (a *Manager) CreateToken(user *User) (*Token, error) { } return &Token{ Value: token, - Expires: expires.Unix(), + Expires: expires, }, nil } @@ -217,7 +220,7 @@ func (a *Manager) ExtendToken(user *User) (*Token, error) { } return &Token{ Value: user.Token, - Expires: newExpires.Unix(), + Expires: newExpires, }, nil } @@ -390,17 +393,6 @@ func (a *Manager) Users() ([]*User, error) { } users = append(users, user) } - /*sort.Slice(users, func(i, j int) bool { - if users[i].Role != users[j].Role { - return true - } - if users[i].Name == Everyone || users[j].Name == Everyone { - return users[i].Name != Everyone - } else if string(users[i].Role) < string(users[j].Role) { - return true - } - return users[i].Name < users[j].Name - })*/ return users, nil } diff --git a/user/manager_test.go b/user/manager_test.go index 484cf2b7..1e55bcfe 100644 --- a/user/manager_test.go +++ b/user/manager_test.go @@ -11,8 +11,8 @@ import ( const minBcryptTimingMillis = int64(50) // Ideally should be >100ms, but this should also run on a Raspberry Pi without massive resources -func TestSQLiteAuth_FullScenario_Default_DenyAll(t *testing.T) { - a := newTestAuth(t, false, false) +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.AllowAccess("ben", "mytopic", true, true)) @@ -84,21 +84,21 @@ func TestSQLiteAuth_FullScenario_Default_DenyAll(t *testing.T) { require.Nil(t, a.Authorize(nil, "up5678", user.PermissionWrite)) } -func TestSQLiteAuth_AddUser_Invalid(t *testing.T) { - a := newTestAuth(t, false, false) +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")) } -func TestSQLiteAuth_AddUser_Timing(t *testing.T) { - a := newTestAuth(t, false, false) +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.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis) } -func TestSQLiteAuth_Authenticate_Timing(t *testing.T) { - a := newTestAuth(t, false, false) +func TestManager_Authenticate_Timing(t *testing.T) { + a := newTestManager(t, false, false) require.Nil(t, a.AddUser("user", "pass", user.RoleAdmin)) // Timing a correct attempt @@ -120,8 +120,8 @@ func TestSQLiteAuth_Authenticate_Timing(t *testing.T) { require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis) } -func TestSQLiteAuth_UserManagement(t *testing.T) { - a := newTestAuth(t, false, false) +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.AllowAccess("ben", "mytopic", true, true)) @@ -202,8 +202,8 @@ func TestSQLiteAuth_UserManagement(t *testing.T) { require.Equal(t, "*", users[1].Name) } -func TestSQLiteAuth_ChangePassword(t *testing.T) { - a := newTestAuth(t, false, false) +func TestManager_ChangePassword(t *testing.T) { + a := newTestManager(t, false, false) require.Nil(t, a.AddUser("phil", "phil", user.RoleAdmin)) _, err := a.Authenticate("phil", "phil") @@ -216,8 +216,8 @@ func TestSQLiteAuth_ChangePassword(t *testing.T) { require.Nil(t, err) } -func TestSQLiteAuth_ChangeRole(t *testing.T) { - a := newTestAuth(t, false, false) +func TestManager_ChangeRole(t *testing.T) { + a := newTestManager(t, false, false) require.Nil(t, a.AddUser("ben", "ben", user.RoleUser)) require.Nil(t, a.AllowAccess("ben", "mytopic", true, true)) require.Nil(t, a.AllowAccess("ben", "readme", true, false)) @@ -235,7 +235,44 @@ func TestSQLiteAuth_ChangeRole(t *testing.T) { require.Equal(t, 0, len(ben.Grants)) } -func newTestAuth(t *testing.T, defaultRead, defaultWrite bool) *user.Manager { +func TestManager_Token_Valid(t *testing.T) { + a := newTestManager(t, false, false) + require.Nil(t, a.AddUser("ben", "ben", user.RoleUser)) + + u, err := a.User("ben") + require.Nil(t, err) + + // Create token for user + token, err := a.CreateToken(u) + require.NotEmpty(t, token.Value) + 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) + + // Remove token and auth again + require.Nil(t, a.RemoveToken(u2)) + u3, err := a.AuthenticateToken(token.Value) + require.Equal(t, user.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)) + + u, err := a.AuthenticateToken(strings.Repeat("x", 32)) // 32 == token length + require.Nil(t, u) + require.Equal(t, user.ErrUnauthenticated, err) + + u, err = a.AuthenticateToken("not long enough anyway") + require.Nil(t, u) + require.Equal(t, user.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) require.Nil(t, err) diff --git a/user/types.go b/user/types.go index b257822b..7cea41e7 100644 --- a/user/types.go +++ b/user/types.go @@ -4,6 +4,7 @@ package user import ( "errors" "regexp" + "time" ) type Auther interface { @@ -31,7 +32,7 @@ type User struct { type Token struct { Value string - Expires int64 + Expires time.Time } type Prefs struct {