Files
sendico/api/pkg/auth/USAGE.md
Stephan D 62a6631b9a
All checks were successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
service backend
2025-11-07 18:35:26 +01:00

202 lines
6.6 KiB
Markdown

# 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`.