diff options
| -rw-r--r-- | internal/api/handlers.go | 3 | ||||
| -rw-r--r-- | internal/api/handlers_admin.go | 8 | ||||
| -rw-r--r-- | internal/api/handlers_more_test.go | 4 | ||||
| -rw-r--r-- | internal/api/handlers_test.go | 7 | ||||
| -rw-r--r-- | internal/service/admin.go | 4 | ||||
| -rw-r--r-- | internal/service/admin_test.go | 2 | ||||
| -rw-r--r-- | internal/service/mock.go | 6 | ||||
| -rw-r--r-- | internal/service/mock_test.go | 6 | ||||
| -rw-r--r-- | internal/service/service.go | 3 | ||||
| -rw-r--r-- | internal/service/user.go | 5 |
10 files changed, 29 insertions, 19 deletions
diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 029d78b..b276b10 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -56,7 +56,8 @@ func handleError(w http.ResponseWriter, err error) { case errors.Is(err, service.ErrInvalidCredentials): writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid credentials"}) case errors.Is(err, service.ErrUnsupportedExtension), - errors.Is(err, service.ErrInvalidFeed): + errors.Is(err, service.ErrInvalidFeed), + errors.Is(err, service.ErrCannotDeleteSelf): badRequest(w, err.Error()) default: writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) diff --git a/internal/api/handlers_admin.go b/internal/api/handlers_admin.go index 6d10c67..d68b07c 100644 --- a/internal/api/handlers_admin.go +++ b/internal/api/handlers_admin.go @@ -83,11 +83,11 @@ func (s *Server) handleDeleteUser(w http.ResponseWriter, r *http.Request) { return } adminUser, _ := r.Context().Value(userCtxKey).(*model.User) - if adminUser != nil && adminUser.ID == id { - badRequest(w, "cannot delete self") - return + var callerID int64 + if adminUser != nil { + callerID = adminUser.ID } - if err := s.adminSvc.DeleteUser(r.Context(), id); err != nil { + if err := s.adminSvc.DeleteUser(r.Context(), callerID, id); err != nil { handleError(w, err) return } diff --git a/internal/api/handlers_more_test.go b/internal/api/handlers_more_test.go index 57158fc..8f7817b 100644 --- a/internal/api/handlers_more_test.go +++ b/internal/api/handlers_more_test.go @@ -1730,7 +1730,7 @@ func TestServer_AdminDeleteUser(t *testing.T) { wantCode int }{ {"nil service", "2", true, nil, http.StatusNotImplemented}, - {"self delete", "1", false, nil, http.StatusBadRequest}, + {"self delete", "1", false, service.ErrCannotDeleteSelf, http.StatusBadRequest}, {"invalid id zero", "0", false, nil, http.StatusBadRequest}, {"service error", "2", false, errors.New("boom"), http.StatusInternalServerError}, {"ok", "2", false, nil, http.StatusOK}, @@ -1741,7 +1741,7 @@ func TestServer_AdminDeleteUser(t *testing.T) { var as service.AdminService if !tt.svcNil { as = &service.MockAdminService{ - DeleteUserFunc: func(ctx context.Context, id int64) error { return tt.svcErr }, + DeleteUserFunc: func(ctx context.Context, callerID, id int64) error { return tt.svcErr }, } } srv := newTestServer(t, store, nil, sm, cfg, nil, nil, nil, nil, nil, nil, as, nil, nil, nil) diff --git a/internal/api/handlers_test.go b/internal/api/handlers_test.go index 363b6ba..cf109c3 100644 --- a/internal/api/handlers_test.go +++ b/internal/api/handlers_test.go @@ -1207,7 +1207,12 @@ func TestServer_AdminRoutes(t *testing.T) { CreateUserFunc: func(ctx context.Context, username, password string, isAdmin bool) (*model.User, error) { return &model.User{ID: 2, Username: username, IsAdmin: isAdmin}, nil }, - DeleteUserFunc: func(ctx context.Context, id int64) error { return nil }, + DeleteUserFunc: func(ctx context.Context, callerID, id int64) error { + if callerID == id { + return service.ErrCannotDeleteSelf + } + return nil + }, ListPermissionsFunc: func(ctx context.Context) (*service.PermissionsMatrix, error) { return nil, nil }, GrantPermissionFunc: func(ctx context.Context, setID, userID int64, role model.Role) error { return nil }, RevokePermissionFunc: func(ctx context.Context, setID, userID int64) error { return nil }, diff --git a/internal/service/admin.go b/internal/service/admin.go index 1b1a352..37fda19 100644 --- a/internal/service/admin.go +++ b/internal/service/admin.go @@ -61,8 +61,8 @@ func (s *adminService) CreateUser(ctx context.Context, username, password string } // DeleteUser delegates to userAdminService. -func (s *adminService) DeleteUser(ctx context.Context, id int64) error { - return s.userAdminService.DeleteUser(ctx, id) +func (s *adminService) DeleteUser(ctx context.Context, callerID, id int64) error { + return s.userAdminService.DeleteUser(ctx, callerID, id) } // ListPermissions delegates to permissionAdminService. diff --git a/internal/service/admin_test.go b/internal/service/admin_test.go index 2ab2157..0e4d309 100644 --- a/internal/service/admin_test.go +++ b/internal/service/admin_test.go @@ -229,7 +229,7 @@ func TestAdminService_DeleteUser(t *testing.T) { }, } svc := NewAdminService(store, newMockClock(), &fakeHasher{fixed: "hash"}, nil, "", ctx) - if err := svc.DeleteUser(ctx, 1); err != nil { + if err := svc.DeleteUser(ctx, 2, 1); err != nil { t.Fatalf("unexpected error: %v", err) } if !called { diff --git a/internal/service/mock.go b/internal/service/mock.go index 5b3c96b..f585c52 100644 --- a/internal/service/mock.go +++ b/internal/service/mock.go @@ -283,7 +283,7 @@ type MockAdminService struct { ScanProgressFunc func(ctx context.Context) model.ScanProgress ListUsersFunc func(ctx context.Context) ([]model.User, error) CreateUserFunc func(ctx context.Context, username, password string, isAdmin bool) (*model.User, error) - DeleteUserFunc func(ctx context.Context, id int64) error + DeleteUserFunc func(ctx context.Context, callerID, id int64) error ListPermissionsFunc func(ctx context.Context) (*PermissionsMatrix, error) GrantPermissionFunc func(ctx context.Context, setID, userID int64, role model.Role) error RevokePermissionFunc func(ctx context.Context, setID, userID int64) error @@ -330,9 +330,9 @@ func (m *MockAdminService) CreateUser(ctx context.Context, username, password st } // DeleteUser calls DeleteUserFunc or returns nil. -func (m *MockAdminService) DeleteUser(ctx context.Context, id int64) error { +func (m *MockAdminService) DeleteUser(ctx context.Context, callerID, id int64) error { if m.DeleteUserFunc != nil { - return m.DeleteUserFunc(ctx, id) + return m.DeleteUserFunc(ctx, callerID, id) } return nil } diff --git a/internal/service/mock_test.go b/internal/service/mock_test.go index bb2525c..c90bac6 100644 --- a/internal/service/mock_test.go +++ b/internal/service/mock_test.go @@ -117,7 +117,7 @@ func TestMockAdminService_Defaults(t *testing.T) { m.ListTrash(ctx) m.TriggerRescan(ctx) m.ListUsers(ctx) - m.DeleteUser(ctx, 1) + m.DeleteUser(ctx, 2, 1) m.ListPermissions(ctx) m.GrantPermission(ctx, 1, 2, model.RoleViewer) m.RevokePermission(ctx, 1, 2) @@ -136,7 +136,7 @@ func TestMockAdminService_WithFuncs(t *testing.T) { CreateUserFunc: func(ctx context.Context, username, password string, isAdmin bool) (*model.User, error) { return nil, nil }, - DeleteUserFunc: func(ctx context.Context, id int64) error { return nil }, + DeleteUserFunc: func(ctx context.Context, callerID, id int64) error { return nil }, ListPermissionsFunc: func(ctx context.Context) (*PermissionsMatrix, error) { return nil, nil }, GrantPermissionFunc: func(ctx context.Context, setID, userID int64, role model.Role) error { return nil }, RevokePermissionFunc: func(ctx context.Context, setID, userID int64) error { return nil }, @@ -146,7 +146,7 @@ func TestMockAdminService_WithFuncs(t *testing.T) { m.TriggerRescan(ctx) m.ListUsers(ctx) m.CreateUser(ctx, "alice", "secret", false) - m.DeleteUser(ctx, 1) + m.DeleteUser(ctx, 2, 1) m.ListPermissions(ctx) m.GrantPermission(ctx, 1, 2, model.RoleViewer) m.RevokePermission(ctx, 1, 2) diff --git a/internal/service/service.go b/internal/service/service.go index 6a02d98..ba826be 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -21,6 +21,7 @@ var ( ErrAlreadyBootstrapped = errors.New("already bootstrapped") ErrInvalidCredentials = errors.New("invalid credentials") ErrInvalidFeed = errors.New("invalid feed") + ErrCannotDeleteSelf = errors.New("cannot delete self") ) // MediaQueryFilter defines query parameters for listing media from the API layer. @@ -191,7 +192,7 @@ type AdminService interface { // CreateUser creates a user account. CreateUser(ctx context.Context, username, password string, isAdmin bool) (*model.User, error) // DeleteUser removes a user account. - DeleteUser(ctx context.Context, id int64) error + DeleteUser(ctx context.Context, callerID, id int64) error // ListPermissions returns the set permission matrix. ListPermissions(ctx context.Context) (*PermissionsMatrix, error) // GrantPermission grants a user access to a set. diff --git a/internal/service/user.go b/internal/service/user.go index be2ac71..01467ed 100644 --- a/internal/service/user.go +++ b/internal/service/user.go @@ -47,6 +47,9 @@ func (s *userAdminService) CreateUser(ctx context.Context, username, password st return user, nil } -func (s *userAdminService) DeleteUser(ctx context.Context, id int64) error { +func (s *userAdminService) DeleteUser(ctx context.Context, callerID, id int64) error { + if callerID == id { + return ErrCannotDeleteSelf + } return s.store.DeleteUser(ctx, id) } |
