service backend
This commit is contained in:
202
api/pkg/auth/USAGE.md
Normal file
202
api/pkg/auth/USAGE.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# 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`.
|
||||
Reference in New Issue
Block a user