6.6 KiB
6.6 KiB
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
EnforceBatchto check permissions for all affected objects
How It Works
Permission Checking Flow
- Get current object to find its index
- Determine affected objects that will be shifted during reordering
- Check permissions using
EnforceBatchfor all affected objects + target object - Verify all permissions - if any object lacks update permission, return error
- Proceed with reordering only if all permissions are granted
Key Differences from Basic Indexable
- Additional parameter:
accountReffor permission checking - Permission validation: All affected objects must have
ActionUpdatepermission - Security: Prevents unauthorized reordering that could affect other users' data
Usage
1. Using the Generic Auth.Indexable Implementation
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)
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
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
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
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:
- Target object - the object being moved
- Affected objects - all objects whose indices will be shifted:
- Moving down: objects between
A+1andB(shifted up by -1) - Moving up: objects between
BandA-1(shifted down by +1)
- Moving down: objects between
Permission Requirements
- Action:
model.ActionUpdate - Scope: All affected objects must be
PermissionBoundStorable - Result: If any object lacks permission, the entire operation fails
Error Handling
// 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
EnforceBatchfor bulk permission checking
✅ Type Safety
- Generic implementation works with any
Indexablestruct - Compile-time type checking
- No runtime type assertions
✅ Flexible Filtering
- Single
builder.Queryparameter 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
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
- Use Auth.Indexable for user-facing reordering operations
- Use Basic Indexable for internal/system operations
- Always provide account reference for proper permission checking
- Test permission scenarios thoroughly with mock enforcers
- Handle permission errors gracefully in user interfaces
That's it! Secure, type-safe reordering with comprehensive permission checking using EnforceBatch.