docs(auth): document User policies and bypass pattern
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Add bypass vs HasPermission pattern documentation Update architecture and implementation plan docs
This commit is contained in:
parent
63d8c4668d
commit
5506b5b2dc
4 changed files with 757 additions and 65 deletions
|
|
@ -524,61 +524,68 @@ Add authorization policies to the Member resource using the new `HasPermission`
|
|||
**Size:** M (2 days)
|
||||
**Dependencies:** #6 (HasPermission check)
|
||||
**Can work in parallel:** Yes (parallel with #7, #9, #10)
|
||||
**Assignable to:** Backend Developer
|
||||
**Assignable to:** Backend Developer
|
||||
**Status:** ✅ **COMPLETED**
|
||||
|
||||
**Description:**
|
||||
|
||||
Add authorization policies to the User resource. Special case: Users can always read/update their own credentials.
|
||||
Add authorization policies to the User resource. Users can always read their own credentials (via bypass), and update their own credentials (via HasPermission with scope :own).
|
||||
|
||||
**Implementation Pattern:**
|
||||
|
||||
Following the same pattern as Member resource:
|
||||
- **Bypass for READ** - Handles list queries (auto_filter)
|
||||
- **HasPermission for UPDATE** - Handles updates with scope :own
|
||||
|
||||
**Tasks:**
|
||||
|
||||
1. Open `lib/mv/accounts/user.ex`
|
||||
2. Add `policies` block
|
||||
3. Add special policy: Allow user to always access their own account (before general policy)
|
||||
1. ✅ Open `lib/mv/accounts/user.ex`
|
||||
2. ✅ Add `policies` block
|
||||
3. ✅ Add AshAuthentication bypass (registration/login without actor)
|
||||
4. ✅ Add NoActor bypass (test environment only)
|
||||
5. ✅ Add bypass for READ: Allow user to always read their own account
|
||||
```elixir
|
||||
policy action_type([:read, :update]) do
|
||||
bypass action_type(:read) do
|
||||
description "Users can always read their own account"
|
||||
authorize_if expr(id == ^actor(:id))
|
||||
end
|
||||
```
|
||||
4. Add general policy: Check HasPermission for all actions
|
||||
5. Ensure :destroy is admin-only (via HasPermission)
|
||||
6. Preload :role relationship for actor
|
||||
6. ✅ Add general policy: Check HasPermission for all actions (including UPDATE with scope :own)
|
||||
7. ✅ Ensure :destroy is admin-only (via HasPermission)
|
||||
8. ✅ Preload :role relationship for actor in tests
|
||||
|
||||
**Policy Order:**
|
||||
1. Allow user to read/update own account (id == actor.id)
|
||||
2. Check HasPermission (for admin operations)
|
||||
3. Default: Forbid
|
||||
1. ✅ AshAuthentication bypass (registration/login)
|
||||
2. ✅ NoActor bypass (test environment)
|
||||
3. ✅ Bypass: User can READ own account (id == actor.id)
|
||||
4. ✅ HasPermission: General permission check (UPDATE uses scope :own, admin uses scope :all)
|
||||
5. ✅ Default: Ash implicitly forbids (fail-closed)
|
||||
|
||||
**Why Bypass for READ but not UPDATE?**
|
||||
|
||||
- **READ list queries**: No record at strict_check time → bypass with `expr()` needed for auto_filter ✅
|
||||
- **UPDATE operations**: Changeset contains record → HasPermission evaluates `scope :own` correctly ✅
|
||||
|
||||
This ensures `scope :own` in PermissionSets is actually used (not redundant).
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] User can always read/update own credentials
|
||||
- [ ] Only admin can read/update other users
|
||||
- [ ] Only admin can destroy users
|
||||
- [ ] Policy order is correct
|
||||
- [ ] Actor preloads :role relationship
|
||||
- ✅ User can always read own credentials (via bypass)
|
||||
- ✅ User can always update own credentials (via HasPermission with scope :own)
|
||||
- ✅ Only admin can read/update other users (scope :all)
|
||||
- ✅ Only admin can destroy users (scope :all)
|
||||
- ✅ Policy order is correct (AshAuth → NoActor → Bypass READ → HasPermission)
|
||||
- ✅ Actor preloads :role relationship
|
||||
- ✅ All tests pass (30/31 pass, 1 skipped)
|
||||
|
||||
**Test Strategy (TDD):**
|
||||
|
||||
**Own Data Tests (All Roles):**
|
||||
- User with :own_data can read own user record
|
||||
- User with :own_data can update own email/password
|
||||
- User with :own_data cannot read other users
|
||||
- User with :read_only can read own data
|
||||
- User with :normal_user can read own data
|
||||
- Verify special policy takes precedence
|
||||
|
||||
**Admin Tests:**
|
||||
- Admin can read all users
|
||||
- Admin can update any user's credentials
|
||||
- Admin can destroy users
|
||||
- Admin has unrestricted access
|
||||
|
||||
**Forbidden Tests:**
|
||||
- Non-admin cannot read other users
|
||||
- Non-admin cannot update other users
|
||||
- Non-admin cannot destroy users
|
||||
**Test Results:**
|
||||
|
||||
**Test File:** `test/mv/accounts/user_policies_test.exs`
|
||||
- ✅ 31 tests total: 30 passing, 1 skipped (AshAuthentication edge case)
|
||||
- ✅ Tests for all 4 permission sets: own_data, read_only, normal_user, admin
|
||||
- ✅ Tests for AshAuthentication bypass (registration/login)
|
||||
- ✅ Tests for NoActor bypass (test environment)
|
||||
- ✅ Tests verify scope :own is used for UPDATE (not redundant)
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue