Fix virtual account filtering and permission inheritance

Two critical fixes for user account access:

1. **Permission inheritance for ALL permission types**
   - Previously only checked READ permission inheritance
   - Now checks ALL permission types (read, submit_expense, manage)
   - Fixes issue where users with submit_expense on parent virtual accounts
     couldn't see child expense accounts

2. **Virtual account filtering after permission check**
   - Virtual accounts are now filtered AFTER permission inheritance logic
   - This allows permission inheritance to work correctly for virtual parents
   - Virtual accounts are still excluded from final results for users

3. **User-specific account filtering**
   - Frontend now passes filter_by_user=true to only show permitted accounts
   - Prevents users from seeing accounts they don't have access to

Flow now works correctly:
- Admin grants user submit_expense permission on virtual 'Expenses:Supplies'
- Permission inheritance checks ALL permission types (not just read)
- User sees all 'Expenses:Supplies:*' child accounts (Food, Kitchen, etc.)
- Virtual parent 'Expenses:Supplies' is filtered out from final results
- User only sees real expense accounts they can submit to

Fixes loading hang and empty account list in Add Expense dialog.

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
padreug 2025-11-11 03:25:49 +01:00
parent b97e899983
commit 0e6fe3e3cd
2 changed files with 24 additions and 6 deletions

View file

@ -405,7 +405,7 @@ window.app = Vue.createApp({
try {
const response = await LNbits.api.request(
'GET',
'/castle/api/v1/accounts',
'/castle/api/v1/accounts?filter_by_user=true&exclude_virtual=true',
this.g.user.wallets[0].inkey
)
this.accounts = response.data

View file

@ -130,21 +130,27 @@ async def api_get_currencies() -> list[str]:
@castle_api_router.get("/api/v1/accounts")
async def api_get_accounts(
filter_by_user: bool = False,
exclude_virtual: bool = True,
wallet: WalletTypeInfo = Depends(require_invoice_key),
) -> list[Account] | list[AccountWithPermissions]:
"""
Get all accounts in the chart of accounts.
- filter_by_user: If true, only return accounts the user has permissions for
- exclude_virtual: If true, exclude virtual parent accounts (default True)
- Returns AccountWithPermissions objects when filter_by_user=true, otherwise Account objects
"""
all_accounts = await get_all_accounts()
if not filter_by_user:
# Return all accounts without filtering
# Filter out virtual accounts if requested (default behavior for user views)
if exclude_virtual:
all_accounts = [acc for acc in all_accounts if not acc.is_virtual]
# Return all accounts without filtering by permissions
return all_accounts
# Filter by user permissions
# NOTE: Do NOT filter out virtual accounts yet - they're needed for inheritance logic
user_id = wallet.wallet.user
user_permissions = await get_user_permissions(user_id)
@ -160,10 +166,14 @@ async def api_get_accounts(
perm for perm in user_permissions if perm.account_id == account.id
]
# Check if user has inherited permission from parent account
inherited_perms = await get_user_permissions_with_inheritance(
user_id, account.name, PermissionType.READ
)
# Check if user has inherited permission from parent account (any permission type)
# Try each permission type to see if user has inherited access
inherited_perms = []
for perm_type in [PermissionType.READ, PermissionType.SUBMIT_EXPENSE, PermissionType.MANAGE]:
perms = await get_user_permissions_with_inheritance(
user_id, account.name, perm_type
)
inherited_perms.extend(perms)
# Determine if account should be included
has_access = bool(account_perms) or bool(inherited_perms)
@ -197,6 +207,8 @@ async def api_get_accounts(
description=account.description,
user_id=account.user_id,
created_at=account.created_at,
is_active=account.is_active,
is_virtual=account.is_virtual,
user_permissions=permission_types if permission_types else None,
inherited_from=inherited_from,
parent_account=parent_account,
@ -205,6 +217,12 @@ async def api_get_accounts(
)
)
# Filter out virtual accounts if requested (after permission inheritance logic)
if exclude_virtual:
accounts_with_permissions = [
acc for acc in accounts_with_permissions if not acc.is_virtual
]
return accounts_with_permissions