# Auth.Indexable Usage Guide ## Secure Reordering with Permission Checking The `auth.Indexable` implementation adds **permission checking** to the generic reordering functionality using `EnforceBatch`. - **Core Implementation**: `api/pkg/auth/indexable.go` - generic implementation with permission checking - **Project Factory**: `api/pkg/auth/project_indexable.go` - convenient factory for projects - **Key Feature**: Uses `EnforceBatch` to check permissions for all affected objects ## How It Works ### Permission Checking Flow 1. **Get current object** to find its index 2. **Determine affected objects** that will be shifted during reordering 3. **Check permissions** using `EnforceBatch` for all affected objects + target object 4. **Verify all permissions** - if any object lacks update permission, return error 5. **Proceed with reordering** only if all permissions are granted ### Key Differences from Basic Indexable - **Additional parameter**: `accountRef` for permission checking - **Permission validation**: All affected objects must have `ActionUpdate` permission - **Security**: Prevents unauthorized reordering that could affect other users' data ## Usage ### 1. Using the Generic Auth.Indexable Implementation ```go import "github.com/tech/sendico/pkg/auth" // For any type that embeds model.Indexable, define helper functions: createEmpty := func() *YourType { return &YourType{} } getIndexable := func(obj *YourType) *model.Indexable { return &obj.Indexable } // Create auth.IndexableDB with enforcer indexableDB := auth.NewIndexableDB(repo, logger, enforcer, createEmpty, getIndexable) // Use with account reference for permission checking err := indexableDB.Reorder(ctx, accountRef, objectID, newIndex, filter) ``` ### 2. Using the Project Factory (Recommended for Projects) ```go import "github.com/tech/sendico/pkg/auth" // Create auth.ProjectIndexableDB (automatically applies org filter) projectDB := auth.NewProjectIndexableDB(repo, logger, enforcer, organizationRef) // Reorder project with permission checking err := projectDB.Reorder(ctx, accountRef, projectID, newIndex, repository.Query()) // Reorder with additional filters (combined with org filter) additionalFilter := repository.Query().Comparison(repository.Field("state"), builder.Eq, "active") err := projectDB.Reorder(ctx, accountRef, projectID, newIndex, additionalFilter) ``` ## Examples for Different Types ### Project Auth.IndexableDB ```go createEmpty := func() *model.Project { return &model.Project{} } getIndexable := func(p *model.Project) *model.Indexable { return &p.Indexable } projectDB := auth.NewIndexableDB(repo, logger, enforcer, createEmpty, getIndexable) orgFilter := repository.OrgFilter(organizationRef) projectDB.Reorder(ctx, accountRef, projectID, 2, orgFilter) ``` ### Status Auth.IndexableDB ```go createEmpty := func() *model.Status { return &model.Status{} } getIndexable := func(s *model.Status) *model.Indexable { return &s.Indexable } statusDB := auth.NewIndexableDB(repo, logger, enforcer, createEmpty, getIndexable) projectFilter := repository.Query().Comparison(repository.Field("projectRef"), builder.Eq, projectRef) statusDB.Reorder(ctx, accountRef, statusID, 1, projectFilter) ``` ### Task Auth.IndexableDB ```go createEmpty := func() *model.Task { return &model.Task{} } getIndexable := func(t *model.Task) *model.Indexable { return &t.Indexable } taskDB := auth.NewIndexableDB(repo, logger, enforcer, createEmpty, getIndexable) statusFilter := repository.Query().Comparison(repository.Field("statusRef"), builder.Eq, statusRef) taskDB.Reorder(ctx, accountRef, taskID, 3, statusFilter) ``` ## Permission Checking Details ### What Gets Checked When reordering an object from index `A` to index `B`: 1. **Target object** - the object being moved 2. **Affected objects** - all objects whose indices will be shifted: - Moving down: objects between `A+1` and `B` (shifted up by -1) - Moving up: objects between `B` and `A-1` (shifted down by +1) ### Permission Requirements - **Action**: `model.ActionUpdate` - **Scope**: All affected objects must be `PermissionBoundStorable` - **Result**: If any object lacks permission, the entire operation fails ### Error Handling ```go // Permission denied error if err != nil { if strings.Contains(err.Error(), "accessDenied") { // Handle permission denied } } ``` ## Security Benefits ### ✅ **Comprehensive Permission Checking** - Checks permissions for **all affected objects**, not just the target - Prevents unauthorized reordering that could affect other users' data - Uses efficient `EnforceBatch` for bulk permission checking ### ✅ **Type Safety** - Generic implementation works with any `Indexable` struct - Compile-time type checking - No runtime type assertions ### ✅ **Flexible Filtering** - Single `builder.Query` parameter for scoping - Can combine organization filters with additional criteria - Project factory automatically applies organization filtering ### ✅ **Clean Architecture** - Separates permission logic from reordering logic - Easy to test with mock enforcers - Follows existing auth patterns ## Testing ### Mock Enforcer Setup ```go mockEnforcer := &MockEnforcer{} // Grant all permissions permissions := map[primitive.ObjectID]bool{ objectID1: true, objectID2: true, } mockEnforcer.On("EnforceBatch", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return(permissions, nil) // Deny specific permission permissions[objectID2] = false mockEnforcer.On("EnforceBatch", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return(permissions, nil) ``` ### Test Scenarios - ✅ **Permission granted** - reordering succeeds - ❌ **Permission denied** - reordering fails with access denied error - 🔄 **No change needed** - early return, minimal permission checking - 🏢 **Organization filtering** - automatic org scope for projects ## Comparison: Basic vs Auth.Indexable | Feature | Basic Indexable | Auth.Indexable | |---------|----------------|----------------| | Permission checking | ❌ No | ✅ Yes | | Account parameter | ❌ No | ✅ Required | | Security | ❌ None | ✅ Comprehensive | | Performance | ✅ Fast | ⚠️ Slower (permission checks) | | Use case | Internal operations | User-facing operations | ## Best Practices 1. **Use Auth.Indexable** for user-facing reordering operations 2. **Use Basic Indexable** for internal/system operations 3. **Always provide account reference** for proper permission checking 4. **Test permission scenarios** thoroughly with mock enforcers 5. **Handle permission errors** gracefully in user interfaces That's it! **Secure, type-safe reordering** with comprehensive permission checking using `EnforceBatch`.