fix blocker 1 - super admin

This commit is contained in:
hamid
2026-08-02 21:03:26 +03:30
parent 42f38f5a72
commit 2cd1075286
6 changed files with 148 additions and 63 deletions
-17
View File
@@ -14,18 +14,6 @@ Effort is a rough size, not a schedule: **S** = small/contained, **M** = a real
## A. The product is broken or unusable ## A. The product is broken or unusable
### Admin can't do anything
- **No admin account can perform a single real admin action.** Every super-admin/finance account gets
"access denied" on every real admin screen — approving a nurse, moderating a review, processing a refund,
running a payout batch, managing tickets, all of it. The permission system only recognizes one hardcoded
role name that nothing ever actually assigns. *(Effort: M)*
- **On top of that, no demo account holds even that hardcoded role**, so fixing the permission bug alone
still leaves nothing to test it with out of the box. *(Effort: S, bundled with the item above)*
*Why this matters more than it looks: a large share of the other items below — nurse verification,
reviews, refunds, payouts, ticket handling — are gated behind this one fix. Fixing admin access is the
single highest-leverage thing on this list.*
### Payments ### Payments
- **A card payment can never actually complete.** The test/demo payment page redirects to a page that - **A card payment can never actually complete.** The test/demo payment page redirects to a page that
doesn't exist. Nothing about the money logic itself is wrong — it's the very last step, handing off to a doesn't exist. Nothing about the money logic itself is wrong — it's the very last step, handing off to a
@@ -81,11 +69,6 @@ Effort is a rough size, not a schedule: **S** = small/contained, **M** = a real
reach a state where the nurse can actually be paid for the visits she did complete. The "today's visits" reach a state where the nurse can actually be paid for the visits she did complete. The "today's visits"
list nurses see is also unfiltered — it shows a nurse's entire history, not just today. *(Effort: M)* list nurses see is also unfiltered — it shows a nurse's entire history, not just today. *(Effort: M)*
### Reviews
- **A real review can never actually go live.** It gets submitted and saved correctly, but the approval step
that would publish it sits behind the broken admin-access problem above, so nothing ever gets un-stuck.
*(Effort: S once admin access is fixed)*
### Catalog / pricing ### Catalog / pricing
- **Outside of the developer's own local setup, there are zero service categories or pricing options - **Outside of the developer's own local setup, there are zero service categories or pricing options
configured at all**, and there is no admin page to add them — only direct database editing. Deployed as-is, configured at all**, and there is no admin page to add them — only direct database editing. Deployed as-is,
+20 -19
View File
@@ -3,8 +3,9 @@
An index into [`blocker-phases/`](blocker-phases/), an engineering-facing companion to An index into [`blocker-phases/`](blocker-phases/), an engineering-facing companion to
[blockers.md](blockers.md): one numbered file per fix, in a suggested working order. Each file has the exact [blockers.md](blockers.md): one numbered file per fix, in a suggested working order. Each file has the exact
root cause (file:line), the proposed fix, and anywhere the fix depends on a business-rule decision that root cause (file:line), the proposed fix, and anywhere the fix depends on a business-rule decision that
shouldn't be guessed. Written from a full read of the current code (commit `fb58ca5`, 2026-08-02). Nothing has shouldn't be guessed. Written from a full read of the current code (commit `fb58ca5`, 2026-08-02). Phases 01
been applied yet — work through them at your own pace, in whatever order you prefer. and 02 are applied (see Status column); the rest are unapplied — work through them at your own pace, in
whatever order you prefer.
> **Two blockers already got a scope decision, recorded so it isn't re-litigated:** §B.4 (مودیان > **Two blockers already got a scope decision, recorded so it isn't re-litigated:** §B.4 (مودیان
> e-invoicing) gets a note in [forgotten-features.md](forgotten-features.md) instead of a build — no chosen > e-invoicing) gets a note in [forgotten-features.md](forgotten-features.md) instead of a build — no chosen
@@ -13,23 +14,23 @@ been applied yet — work through them at your own pace, in whatever order you p
## Phases ## Phases
| # | File | Blocker | Depends on | | # | File | Blocker | Depends on | Status |
| --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| 01 | [admin-rbac](blocker-phases/01-admin-rbac.md) | Admin can't do anything (the #1 leverage item) | — | | 01 | [admin-rbac](blocker-phases/01-admin-rbac.md) | Admin can't do anything (the #1 leverage item) | — | ✅ Done |
| 02 | [reviews-moderation](blocker-phases/02-reviews-moderation.md) | Reviews can never go live | 01 | | 02 | [reviews-moderation](blocker-phases/02-reviews-moderation.md) | Reviews can never go live | 01 | ✅ Done (no separate fix needed — unblocked by 01) |
| 03 | [address-edit-bug](blocker-phases/03-address-edit-bug.md) | Editing an address wipes recipient name/phone | — | | 03 | [address-edit-bug](blocker-phases/03-address-edit-bug.md) | Editing an address wipes recipient name/phone | — | — |
| 04 | [payment-timezone](blocker-phases/04-payment-timezone.md) | The 30-minute payment countdown can lie | — | | 04 | [payment-timezone](blocker-phases/04-payment-timezone.md) | The 30-minute payment countdown can lie | — | — |
| 05 | [bnpl-setup](blocker-phases/05-bnpl-setup.md) | Installments (BNPL) don't work at all | — | | 05 | [bnpl-setup](blocker-phases/05-bnpl-setup.md) | Installments (BNPL) don't work at all | — | — |
| 06 | [catalog-admin-page](blocker-phases/06-catalog-admin-page.md) | No admin page for service categories/pricing | — | | 06 | [catalog-admin-page](blocker-phases/06-catalog-admin-page.md) | No admin page for service categories/pricing | — | — |
| 07 | [card-payment-redirect](blocker-phases/07-card-payment-redirect.md) | Card payment can never complete | — | | 07 | [card-payment-redirect](blocker-phases/07-card-payment-redirect.md) | Card payment can never complete | — | — |
| 08 | [refunds-demock](blocker-phases/08-refunds-demock.md) | Refunds are demo-only, off by 100× | — | | 08 | [refunds-demock](blocker-phases/08-refunds-demock.md) | Refunds are demo-only, off by 100× | — | — |
| 09 | [nurse-verification-badge](blocker-phases/09-nurse-verification-badge.md) | Verification badge doesn't reflect reality | pairs with 10 | | 09 | [nurse-verification-badge](blocker-phases/09-nurse-verification-badge.md) | Verification badge doesn't reflect reality | pairs with 10 | — |
| 10 | [search-dedup-and-trust](blocker-phases/10-search-dedup-and-trust.md) | Search isn't de-duplicated; trust info hardcoded | pairs with 09 | | 10 | [search-dedup-and-trust](blocker-phases/10-search-dedup-and-trust.md) | Search isn't de-duplicated; trust info hardcoded | pairs with 09 | — |
| 11 | [nurse-payouts](blocker-phases/11-nurse-payouts.md) | Nurse pay/payouts are fake, no "process" action | benefits from 01 | | 11 | [nurse-payouts](blocker-phases/11-nurse-payouts.md) | Nurse pay/payouts are fake, no "process" action | benefits from 01 | — |
| 12 | [patient-records](blocker-phases/12-patient-records.md) | Patient records & visit notes are fake demo data | needs a product decision first | | 12 | [patient-records](blocker-phases/12-patient-records.md) | Patient records & visit notes are fake demo data | needs a product decision first | — |
| 13 | [booking-lifecycle](blocker-phases/13-booking-lifecycle.md) | Stuck bookings; "today's visits" unfiltered | pairs with 04 | | 13 | [booking-lifecycle](blocker-phases/13-booking-lifecycle.md) | Stuck bookings; "today's visits" unfiltered | pairs with 04 | — |
| 14 | [partner-center](blocker-phases/14-partner-center.md) | Partner/business-center accounts are fake | benefits from 01 | | 14 | [partner-center](blocker-phases/14-partner-center.md) | Partner/business-center accounts are fake | benefits from 01 | — |
| 15 | [debug-mode-production](blocker-phases/15-debug-mode-production.md) | Turn off dev mode on the live site (§B.2) | do last, deliberately | | 15 | [debug-mode-production](blocker-phases/15-debug-mode-production.md) | Turn off dev mode on the live site (§B.2) | do last, deliberately | — |
## Suggested order ## Suggested order
+23 -22
View File
@@ -32,8 +32,8 @@ need to touch that directly except for one step below).
| `09120000003` | Nurse — مریم احمدی (f) | **Not verified yet** — should never appear in customer search | | `09120000003` | Nurse — مریم احمدی (f) | **Not verified yet** — should never appear in customer search |
| `09120000010` | Customer — سارا محمدی (f) | 2 people in her care circle, 1 saved address, owns most seeded bookings | | `09120000010` | Customer — سارا محمدی (f) | 2 people in her care circle, 1 saved address, owns most seeded bookings |
| `09120000011` | Customer — رضا حسینی (m) | 1 infant patient, owns the installment-plan bookings | | `09120000011` | Customer — رضا حسینی (m) | 1 infant patient, owns the installment-plan bookings |
| `09120000020` | Admin (full) — نگار مدیری (f) | Can log in, but admin actions don't work yet — see [blockers.md](blockers.md) | | `09120000020` | Admin (super_admin) — نگار مدیری (f) | Real actions now work in review moderation and the support ticket queue; most other admin consoles are still client-side sample data — see [blockers.md](blockers.md) |
| `09120000021` | Admin (finance) — کامران مالی (m) | Same admin caveat as above | | `09120000021` | Admin (finance) — کامران مالی (m) | Scoped to refunds/payouts/platform-settings consoles; same "most consoles still sample data" caveat as above |
| `09120000030` | Customer who owns a partner business center — بهنام رستگار (m) | Logs in as a normal customer, then opens the partner section by typing its address directly — nothing in the app links to it | | `09120000030` | Customer who owns a partner business center — بهنام رستگار (m) | Logs in as a normal customer, then opens the partner section by typing its address directly — nothing in the app links to it |
--- ---
@@ -49,12 +49,12 @@ need to touch that directly except for one step below).
3. Read the code as described above and type it in. Expect it to auto-submit and take you to the family home screen (not the nurse page you originally tried). 3. Read the code as described above and type it in. Expect it to auto-submit and take you to the family home screen (not the nurse page you originally tried).
4. Repeat with `09120000001` (زهرا عزیزی, nurse). This time you should land on the nurse's "Today's Visits" screen instead. 4. Repeat with `09120000001` (زهرا عزیزی, nurse). This time you should land on the nurse's "Today's Visits" screen instead.
5. Try entering a wrong code five times in a row. Expect a lockout message; requesting a new code should still work. 5. Try entering a wrong code five times in a row. Expect a lockout message; requesting a new code should still work.
6. Log in as `09120000020` (نگار مدیری, admin). Login itself succeeds, but admin features don't work (known blocker). 6. Log in as `09120000020` (نگار مدیری, admin). Login succeeds and lands on the admin home; most admin consoles still show sample data (client-side mock, unrelated to login).
**What you should see:** each account type lands on its correct home screen after login, and a page you tried to reach before logging in is remembered and opened afterward. **What you should see:** each account type lands on its correct home screen after login, and a page you tried to reach before logging in is remembered and opened afterward.
**Known issues:** **Known issues:**
- Admin accounts can log in but can't actually do anything yet (known blocker — see [blockers.md](blockers.md)) - Most admin consoles still show client-side sample data regardless of login (see "Admin backoffice" below)
- After too many wrong codes, the "try again later" message is wrongly shown as "wrong code" - After too many wrong codes, the "try again later" message is wrongly shown as "wrong code"
- Logging out on one device signs that account out everywhere, with no option to choose just one device - Logging out on one device signs that account out everywhere, with no option to choose just one device
@@ -344,19 +344,21 @@ The screens you'll see show fake numbers, not what you would actually get back.
- Think twice before pressing the final "confirm cancellation" button on a demo account's only remaining bookings — it permanently cancels them for everyone testing with that account - Think twice before pressing the final "confirm cancellation" button on a demo account's only remaining bookings — it permanently cancels them for everyone testing with that account
### Rating and reviewing a nurse after a visit ### Rating and reviewing a nurse after a visit
**Who tests this:** customers (write a review); admins (approve reviews — currently broken); anyone (reads published reviews) **Who tests this:** customers (write a review); admins (approve reviews); anyone (reads published reviews)
**Status today:** ⚠️ Works, but has known issues **Status today:** ⚠️ Works, but has known issues
1. Log in as سارا محمدی (`09120000010`). 1. Log in as سارا محمدی (`09120000010`).
2. Go to Bookings — only one completed booking shows a "write a review" (ثبت نظر) button; the others already have a review or aren't eligible. 2. Go to Bookings — only one completed booking shows a "write a review" (ثبت نظر) button; the others already have a review or aren't eligible.
3. Tap it, give a 15 star rating, optionally add a note and pick some tags, then submit — a notice warns it must be approved before anyone else can see it. 3. Tap it, give a 15 star rating, optionally add a note and pick some tags, then submit — a notice warns it must be approved before anyone else can see it.
4. The screen then shows your review as "pending review" (در انتظار بررسی) — this part is genuinely real and saved. 4. The screen then shows your review as "pending review" (در انتظار بررسی) — this part is genuinely real and saved.
5. Check a nurse's profile "Reviews" tab — only already-approved reviews show up; yours won't appear, because it's stuck waiting on approval. 5. Check a nurse's profile "Reviews" tab — only already-approved reviews show up; yours won't appear yet.
6. Log in as `09120000020` (نگار مدیری, super_admin) and open the review moderation queue (`/fa/admin/reviews`). Expect your pending review to show up for real, with approve/reject actions that now actually work.
7. Approve it, then re-check the nurse's profile "Reviews" tab — it should now appear.
**What you should see:** submitting a review actually works for real, but it never becomes publicly visible in this demo because the approval step is broken. **What you should see:** submitting a review, approving it as admin, and seeing it go live are all genuinely real and connected end-to-end.
**Known issues:** **Known issues:**
- Reviews can never actually get published yet — stuck waiting on admin approval, which is broken (known blocker — see [blockers.md](blockers.md)) - `ReviewModerationStatus.Rejected` is reachable only through this admin action — the submit-time banned-word filter maps to "hidden," not "rejected"
- A nurse has no screen yet to see reviews written about them - A nurse has no screen yet to see reviews written about them
### Nurse earnings and payouts ### Nurse earnings and payouts
@@ -390,12 +392,11 @@ Everything on these screens is fake, even though real payout numbers do exist be
3. Open the support conversation. Expect just the customer/staff messages — any internal staff-only notes never appear here. 3. Open the support conversation. Expect just the customer/staff messages — any internal staff-only notes never appear here.
4. Type a reply and send it. Expect it to appear immediately, then confirm as sent. 4. Type a reply and send it. Expect it to appear immediately, then confirm as sent.
5. Log in as the nurse, `09120000001` (زهرا عزیزی), and open her support/messages page — same kind of list, nurse's own threads. 5. Log in as the nurse, `09120000001` (زهرا عزیزی), and open her support/messages page — same kind of list, nurse's own threads.
6. Log in as an admin (`09120000020`) and try the admin support-queue page. Expect it to fail with an access-denied error (known blocker). 6. Log in as an admin (`09120000020`, super_admin) and open the admin support-queue page. Expect the real ticket list to load and a conversation to open.
**What you should see:** customers and nurses can view and reply to their own threads normally; the admin/staff side of messaging is currently unreachable. **What you should see:** customers, nurses, and now the admin/staff side all view and reply to threads for real.
**Known issues:** **Known issues:**
- Admin ticket queue and admin view of a conversation are broken (access denied) (known blocker — see [blockers.md](blockers.md))
- The little unread-messages counter in the header never lights up - The little unread-messages counter in the header never lights up
- The inbox list doesn't show a preview of the latest message - The inbox list doesn't show a preview of the latest message
- Auto-created booking conversations start completely empty, with nothing explaining what they're for - Auto-created booking conversations start completely empty, with nothing explaining what they're for
@@ -425,22 +426,22 @@ Everything on these screens is fake, even though real payout numbers do exist be
### Admin backoffice ### Admin backoffice
**Who tests this:** admin accounts (super_admin and finance) **Who tests this:** admin accounts (super_admin and finance)
**Status today:** ❌ Mostly fake/broken — not really testable yet **Status today:** ⚠️ Two consoles are real; the rest is still a client-side mock
Right now every admin account gets an "access denied" error on essentially every real admin action on the The server-side permission bug that blocked every admin action is fixed. Two consoles were already wired to
server — this is one of the biggest known blockers. The admin screens in the app look fully populated and a real (non-admin-mock) domain and now work end-to-end: review moderation and the support ticket queue. The
functional, but that's because almost all of them are showing made-up sample data built into the app, not other admin sections still render from `client/src/services/admin`'s built-in sample data regardless of who's
real information. If that fake layer weren't there, clicking any real action would fail with access denied. logged in — that's a separate, larger de-mock effort tracked per-console in
[fix-plan.md](fix-plan.md) (phases 0914 touch the ones that matter most).
1. Log in as an admin, either `09120000020` (نگار مدیری, super_admin) or `09120000021` (کامران مالی, finance). 1. Log in as an admin, either `09120000020` (نگار مدیری, super_admin) or `09120000021` (کامران مالی, finance).
2. Browse the admin sections: nurse verification, review moderation, payouts, refunds, support tickets/alerts, platform settings, holidays, audit log, and partner centers. 2. Open review moderation (`/fa/admin/reviews`) and the support ticket queue (`/fa/admin/tickets`) — both load real data and their actions (approve/reject a review, reply to/close a ticket) actually work.
3. Expect most sections to show full, editable-looking data, and "saving" a change to appear to work — but this is all fake, local sample data, not connected to the real system. 3. Browse the rest: nurse verification, payouts, refunds, support alerts, platform settings, holidays, audit log, and partner centers. Expect full, editable-looking data, and "saving" a change to appear to work — but this is still fake, local sample data, not connected to the real system.
4. Two sections (the ticket queue and review moderation) actually try to reach the server, and both show a visible error instead of data.
**What you should see:** a good-looking dashboard that mostly isn't real. Only two screens honestly show that something is broken; the rest hide it. **What you should see:** review moderation and the ticket queue behave like a normal, working admin tool; every other section still looks real but isn't connected yet.
**Known issues:** **Known issues:**
- Admin accounts can't do anything for real yet (known blocker — see [blockers.md](blockers.md)) - 19 of 21 admin consoles still show client-side sample data, not the real system (see [fix-plan.md](fix-plan.md))
- The "user directory" and "roles" admin screens have no real data behind them at all - The "user directory" and "roles" admin screens have no real data behind them at all
### Partner center ### Partner center
@@ -473,7 +474,7 @@ This section is 100% fake demo data — there's no real business-center account
5. Edit your personal info (e.g. last name) and save. Expect a "saved" confirmation and the name to update on screen. 5. Edit your personal info (e.g. last name) and save. Expect a "saved" confirmation and the name to update on screen.
6. Tap sign out («خروج از حساب») and confirm. Expect to land on the login screen, with that account fully signed out everywhere, not just this device. 6. Tap sign out («خروج از حساب») and confirm. Expect to land on the login screen, with that account fully signed out everywhere, not just this device.
7. Log in as the nurse, `09120000001` (زهرا عزیزی), and open her account/settings menu. Expect similar settings, but sign-out here happens on a single tap with no confirmation — be careful. 7. Log in as the nurse, `09120000001` (زهرا عزیزی), and open her account/settings menu. Expect similar settings, but sign-out here happens on a single tap with no confirmation — be careful.
8. As an admin, the account page shows a broken, untranslated label for the admin's role, and every admin section link on it leads to an access-denied error (known blocker). 8. As an admin, the account page shows a broken, untranslated label for the admin's role. Admin section links now open normally (most render sample data rather than erroring — see "Admin backoffice" above).
9. The partner account page is only reachable by typing its address manually and shows a fake business name (known blocker). 9. The partner account page is only reachable by typing its address manually and shows a fake business name (known blocker).
**What you should see:** appearance, language switching, editing personal info, and sign-out all genuinely work for a customer and nurse; admin and partner settings inherit the blockers from those areas. **What you should see:** appearance, language switching, editing personal info, and sign-out all genuinely work for a customer and nurse; admin and partner settings inherit the blockers from those areas.
@@ -1,4 +1,5 @@
using System.Security.Claims; using System.Security.Claims;
using Baya.Domain.Entities.User;
namespace Baya.Infrastructure.Identity.Identity.PermissionManager; namespace Baya.Infrastructure.Identity.Identity.PermissionManager;
@@ -6,18 +7,17 @@ public class DynamicPermissionService : IDynamicPermissionService
{ {
public bool CanAccess(ClaimsPrincipal user, string area, string controller, string action) public bool CanAccess(ClaimsPrincipal user, string area, string controller, string action)
{ {
if (user.IsInRole("admin")) // super_admin is definitionally the top role, but RoleNames has no hierarchy — each admin
// sub-role is a sibling, so it must be granted the bypass explicitly, not implied by "admin".
if (user.IsInRole(RoleNames.Admin) || user.IsInRole(RoleNames.SuperAdmin))
{ {
return true; return true;
} }
var key = $"{area}:{controller}:"; var key = $"{area}:{controller}:";
var userClaims = user.FindAll(ConstantPolicies.DynamicPermission); var userClaims = user.FindAll(ConstantPolicies.DynamicPermission);
return userClaims.Any(item => item.Value.Equals(key, StringComparison.OrdinalIgnoreCase)); return userClaims.Any(item => item.Value.Equals(key, StringComparison.OrdinalIgnoreCase));
} }
} }
@@ -1,5 +1,8 @@
using Baya.Application.Contracts.Identity;
using Baya.Application.Models.Identity;
using Baya.Domain.Entities.User; using Baya.Domain.Entities.User;
using Baya.Infrastructure.Identity.Identity.Manager; using Baya.Infrastructure.Identity.Identity.Manager;
using Baya.Infrastructure.Identity.Identity.PermissionManager;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
@@ -15,12 +18,25 @@ public class SeedDataBase : ISeedDataBase
private readonly AppUserManager _userManager; private readonly AppUserManager _userManager;
private readonly AppRoleManager _roleManager; private readonly AppRoleManager _roleManager;
private readonly IConfiguration _configuration; private readonly IConfiguration _configuration;
private readonly IRoleManagerService _roleManagerService;
public SeedDataBase(AppUserManager userManager, AppRoleManager roleManager, IConfiguration configuration) // The admin-console permission matrix owned by each non-admin sub-role — mirrors
// client/src/hooks/capabilities.ts (useAdminCapabilities), the reviewed source of truth for who
// owns which console. Area is always empty: no controller uses [Area] today.
private static readonly IReadOnlyDictionary<string, string[]> AdminSubRolePermissions =
new Dictionary<string, string[]>
{
[RoleNames.Finance] = ["AdminRefunds", "AdminPayouts", "PlatformConfig"],
[RoleNames.Support] = ["AdminVerifications", "AdminVerificationStepTypes", "SupportAlerts", "AdminTickets"],
[RoleNames.Moderation] = ["AdminReviews", "Reviews"],
};
public SeedDataBase(AppUserManager userManager, AppRoleManager roleManager, IConfiguration configuration, IRoleManagerService roleManagerService)
{ {
_userManager = userManager; _userManager = userManager;
_roleManager = roleManager; _roleManager = roleManager;
_configuration = configuration; _configuration = configuration;
_roleManagerService = roleManagerService;
} }
public async Task Seed() public async Task Seed()
@@ -38,9 +54,39 @@ public class SeedDataBase : ISeedDataBase
} }
} }
await SeedAdminSubRolePermissionsAsync();
await SeedBootstrapAdminAsync(); await SeedBootstrapAdminAsync();
} }
// finance/support/moderation are sibling roles, not implied by super_admin — each needs its own
// DynamicPermission claims scoped to the consoles it owns. Skips roles already at the target claim
// set so a normal restart doesn't churn every affected user's security stamp.
private async Task SeedAdminSubRolePermissionsAsync()
{
foreach (var (roleName, controllers) in AdminSubRolePermissions)
{
var role = await _roleManager.FindByNameAsync(roleName);
if (role == null)
continue;
var targetPermissions = controllers.Select(controller => $":{controller}:").ToList();
var existingPermissions = (await _roleManager.GetClaimsAsync(role))
.Where(c => c.Type == ConstantPolicies.DynamicPermission)
.Select(c => c.Value)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
if (existingPermissions.SetEquals(targetPermissions))
continue;
await _roleManagerService.ChangeRolePermissionsAsync(new EditRolePermissionsDto
{
RoleId = role.Id,
Permissions = targetPermissions
});
}
}
// The bootstrap admin is config-driven, never a committed credential: it is created only when both // The bootstrap admin is config-driven, never a committed credential: it is created only when both
// Seed:AdminUsername and Seed:AdminPassword are supplied (via the environment-specific appsettings file, environment // Seed:AdminUsername and Seed:AdminPassword are supplied (via the environment-specific appsettings file, environment
// variables in a deployment). With neither configured — the default for Testing and any fresh boot — // variables in a deployment). With neither configured — the default for Testing and any fresh boot —
@@ -0,0 +1,54 @@
using System.Security.Claims;
using Baya.Domain.Entities.User;
using Baya.Infrastructure.Identity.Identity.PermissionManager;
namespace Baya.Test.Infrastructure.Identity
{
public class DynamicPermissionServiceTest
{
private readonly DynamicPermissionService _sut = new();
[Fact]
public void Admin_Role_Bypasses_Permission_Check()
{
var user = BuildUser(RoleNames.Admin);
Assert.True(_sut.CanAccess(user, area: null, controller: "AdminRefunds", action: "Create"));
}
[Fact]
public void SuperAdmin_Role_Bypasses_Permission_Check()
{
var user = BuildUser(RoleNames.SuperAdmin);
Assert.True(_sut.CanAccess(user, area: null, controller: "AdminRefunds", action: "Create"));
}
[Fact]
public void Role_With_Matching_Claim_Is_Granted_Access()
{
var user = BuildUser(RoleNames.Finance, dynamicPermission: ":AdminRefunds:");
Assert.True(_sut.CanAccess(user, area: null, controller: "AdminRefunds", action: "Create"));
}
[Fact]
public void Role_Without_Matching_Claim_Is_Denied_Access()
{
var user = BuildUser(RoleNames.Finance, dynamicPermission: ":AdminRefunds:");
Assert.False(_sut.CanAccess(user, area: null, controller: "AdminReviews", action: "Status"));
}
private static ClaimsPrincipal BuildUser(string role, string? dynamicPermission = null)
{
var claims = new List<Claim> { new(ClaimTypes.Role, role) };
if (dynamicPermission != null)
claims.Add(new Claim(ConstantPolicies.DynamicPermission, dynamicPermission));
var identity = new ClaimsIdentity(claims, "TestAuth", ClaimTypes.Name, ClaimTypes.Role);
return new ClaimsPrincipal(identity);
}
}
}