Skip to content

Commit 44412b2

Browse files
authored
Merge pull request #1305 from fullstackhero/chore/pre-publish-cleanup
chore: pre-publish cleanup — group-permission fix, fetched-permissions docs, .NET 10 nits
2 parents 66496f8 + bf81106 commit 44412b2

14 files changed

Lines changed: 307 additions & 15 deletions

File tree

.agents/rules/frontend/dashboard.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ Tenant-facing application. Read `frontend/shared.md` first; this file is only th
1010

1111
The dashboard does **not** depend on react-hook-form or zod. Use controlled inputs + local state. Don't add those deps to match admin.
1212

13-
## Permissions — straight from the JWT
13+
## Permissions — fetched, not in the JWT
1414

15-
`auth-context.tsx` reads `claims.permissions` off the decoded JWT — no separate fetch, no `permissionsHydrated`, no permissions cache key. `ProtectedRoute` is **auth-only** (no permission gating). Don't add `RouteGuard`-style gating here.
15+
The JWT carries **only role names**. `auth-context.tsx` fetches the effective permission list from `GET /api/v1/identity/permissions` (`getMyPermissions()` in `src/api/identity.ts`), caches it in `tokenStore` under `fsh.dashboard.permissions`, and exposes a `permissionsHydrated` flag so gated UI doesn't flash while the fetch is in flight (re-fetched on login/impersonation swaps; `refreshPermissions()` for role changes). Nav items are gated via `perm`/`anyPerm` in `src/components/layout/nav-data.ts`. `ProtectedRoute` is still **auth-only** (no per-route permission gating) — don't add `RouteGuard`-style gating here.
1616

1717
## Routing & realtime/SSE
1818

.agents/skills/add-permission/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ So `RouteGuard` passes on first paint, add the new permission to the test seed s
6161

6262
## Dashboard
6363

64-
No mirror, no `RouteGuard`. The permission rides in the JWT (`claims.permissions`) and the server enforces it; a missing permission yields a 403 the UI surfaces. Nothing to add client-side beyond consuming the gated endpoint.
64+
No mirror, no `RouteGuard`. The JWT carries only role names — the app fetches the permission list from `GET /api/v1/identity/permissions` at hydration and the server enforces access; a missing permission yields a 403 the UI surfaces. Routes aren't permission-gated; to hide a nav entry, set `perm`/`anyPerm` on the item in `src/components/layout/nav-data.ts`. Permission-gated specs mock `GET /identity/permissions` with the grants they need (shell mocks stub it to `[]`).
6565

6666
## Checklist
6767

.agents/skills/add-react-page/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ The frontend slice. Read `.agents/rules/frontend/shared.md` plus the app file (`
1717
| Forms | **react-hook-form + zod** | **hand-rolled** controlled inputs (no RHF/zod) |
1818
| List + create | separate routed pages (`list.tsx`, `create.tsx`) | one file with `<Dialog>` editors |
1919
| Route wrapper | `<RouteGuard perms={[…]}>` | `withSuspense(<X/>)` (no permission gate) |
20-
| Permissions | mirror in `src/lib/permissions.ts` | none — JWT claims + server 403 |
20+
| Permissions | mirror in `src/lib/permissions.ts` | fetched from `GET /identity/permissions` (not JWT); nav gating via `perm`/`anyPerm` in `nav-data.ts`; no route guard — server 403 backstops |
2121

2222
Shared everywhere: types are **hand-written** (no codegen); `apiFetch<T>` from `@/lib/api-client`; `cn()` from `@/lib/cn`; `env.apiBase` from runtime `/config.json`; CVA `components/ui` + `components/list` primitives; Tailwind v4 CSS-first (tokens in `src/styles/globals.css`); `toast` from `sonner`; pages are **named exports**; `placeholderData: keepPreviousData` (v5).
2323

.devcontainer/devcontainer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"features": {
55
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
66
},
7-
"postCreateCommand": "dotnet workload install aspire && dotnet restore src/FSH.Starter.slnx",
7+
"postCreateCommand": "dotnet restore src/FSH.Starter.slnx",
88
"forwardPorts": [5030, 7030, 15888],
99
"customizations": {
1010
"vscode": {

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Thanks for helping out. The conventions below keep PRs reviewable.
1010

1111
## Dev setup
1212

13-
Prerequisites: .NET 10 SDK, Docker, Node.js 22+.
13+
Prerequisites: .NET 10 SDK, Docker, Node.js 20+.
1414

1515
```bash
1616
dotnet build src/FSH.Starter.slnx

README-template.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ the shared code lives in `src/BuildingBlocks` and is yours to change.
1212
- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
1313
- [Node.js 20+](https://nodejs.org) — for the React apps
1414
- [Docker](https://www.docker.com/) — Postgres, Redis, MinIO (orchestrated by Aspire)
15-
- .NET Aspire workload: `dotnet workload install aspire`
1615

1716
## Quick start
1817

clients/dashboard/tests/helpers/auth-seed.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,23 @@ import type { Page } from "@playwright/test";
77
* We populate the same localStorage keys the runtime tokenStore writes
88
* (see clients/dashboard/src/auth/token-store.ts). The token value is
99
* a JWT-shaped string that decodes to the supplied user — useAuth's
10-
* decoder reads sub/email/given_name/family_name/tenant/permissions
11-
* out of the payload.
10+
* decoder reads sub/email/given_name/family_name/tenant out of the
11+
* payload. Permissions are NOT read from the JWT: at runtime the app
12+
* fetches them from GET /api/v1/identity/permissions (installShellMocks
13+
* stubs that route to []; permission-gated specs re-mock it with the
14+
* grants they need — see tests/system/trash.spec.ts).
1215
*/
1316
export type SeededUser = {
1417
sub: string;
1518
email: string;
1619
firstName: string;
1720
lastName: string;
1821
tenant: string;
22+
/**
23+
* Inert — written into the fake JWT payload but ignored by the app.
24+
* To grant permissions in a spec, mock GET /identity/permissions
25+
* instead.
26+
*/
1927
permissions?: string[];
2028
};
2129

@@ -36,7 +44,7 @@ function fakeJwt(payload: Record<string, unknown>): string {
3644

3745
export async function seedAuthedSession(page: Page, user: SeededUser) {
3846
// Build the JWT-shaped payload. Claim names match what useAuth's decoder
39-
// looks for in the runtime path.
47+
// looks for in the runtime path (permissions excepted — inert, see SeededUser).
4048
const payload = {
4149
sub: user.sub,
4250
email: user.email,

src/Host/FSH.Starter.DbMigrator/MigratorCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ apply Apply pending migrations (default). Use --seed to also run SeedA
6666
seed Run only the SeedAsync step per tenant.
6767
seed-demo Provision the demo tenants (acme, globex) with users, catalog,
6868
tickets, and chat. Dev-only — refuses to run unless
69-
ASPNETCORE_ENVIRONMENT=Development.
69+
DOTNET_ENVIRONMENT=Development.
7070
list-pending Print pending migrations without applying anything.
7171
7272
Options:

src/Host/FSH.Starter.DbMigrator/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ await Console.Out.WriteLineAsync(
291291
if (!env.IsDevelopment())
292292
{
293293
await Console.Error.WriteLineAsync(
294-
$"[demo-seed] REFUSING to run — ASPNETCORE_ENVIRONMENT is '{env.EnvironmentName}'. "
294+
$"[demo-seed] REFUSING to run — DOTNET_ENVIRONMENT is '{env.EnvironmentName}'. "
295295
+ "seed-demo is dev-only by design.")
296296
.ConfigureAwait(false);
297297
return 1;

src/Host/FSH.Starter.DbMigrator/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ dotnet run --project src/Host/FSH.Starter.DbMigrator -- seed
4343
# Dev only — provision the demo tenants (acme, globex) with users,
4444
# custom roles, sample catalog, tickets, and chat. Hard-refuses outside
4545
# Development. Idempotent: safe to re-run.
46-
ASPNETCORE_ENVIRONMENT=Development \
46+
DOTNET_ENVIRONMENT=Development \
4747
dotnet run --project src/Host/FSH.Starter.DbMigrator -- seed-demo
4848
```
4949

0 commit comments

Comments
 (0)