Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,9 @@ What the app is and how to run it: [`README.md`](README.md). UI rules: [`docs/de
backend's `people.*` responses with `people.contract.ts` and, in agent mode, uses the in-memory fake
(`AGENT_PERMISSIONS`, `AGENT_PERSON` pick the viewer). `/dashboard` admits `people:self` without `admin:access`;
Capo Admin and Team Lead come from the person's own record (`peopleRoles` in the route context) and only open their
sections. Fees and receipts (`docs/people/fees.md`): `people:fees:write` reviews, marks paid and reminds;
`people:receipts:sign` downloads and uploads signed receipts for the officer on the caller's record.
Preparing CIE requests additionally requires `esign:sign` and reuses the existing eSign UI and agent; the backend
owns the durable receipt/request association and completion (the People backend contract is still pending). Handoff notes,
decisions and the plan: `docs/people/`.
sections. Member register, Receipts, Census and My area's fee card are unconnected mockups (#138): static sample data
from `mockup-data.ts`, no server functions, disabled actions and a `MockupBanner`, gated by `people:read`, to be
rebuilt in Phase 2 (#144) and Phase 3 (#145). Handoff notes, decisions and the plan: `docs/people/`.
- **Shared UI**: `src/components/shell`, `src/components/primitives`, `src/components/ui`.

## Conventions
Expand Down
49 changes: 24 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@ read-only list of groups. Directory access requires `azure:members:read`; creati
`azure:members:create`, and changing member IDs `azure:members:write`. Groups require `azure:groups:read`. Group
membership is never edited: creating a member adds only that new user to Soci, which gives them the Socio role.

**People** keeps one record per volunteer and member (Overview, Member register, Admins, Teams, Census, Linking), plus
Telegram › Capo Admin and Account › My area for volunteers themselves. The dashboard talks to a typed `people.*`
contract (`src/features/people/people.contract.ts`, documented in `docs/people/contract.md`); until the backend
implements it, agent mode serves sample data from `people.fake.server.ts`. Permissions: `people:read`, `people:write`,
`people:members:read`, `people:members:write`, `people:appoint`, `people:fees:write` (review transfer receipts, mark fees
paid, reminders), `people:receipts:sign` (the President and Vice-president sign receipts), and `people:self`, which alone
admits non-admins to My area and, by the role on their record, Capo Admin or their team. See `docs/people/README.md`
and, for fees and receipts, `docs/people/fees.md`.
**People** keeps one record per volunteer (Overview, Admins, Teams, Linking), plus Telegram › Capo Admin and Account ›
My area for volunteers themselves. The dashboard talks to a typed `people.*` contract
(`src/features/people/people.contract.ts`, documented in `docs/people/contract.md`); agent mode serves sample data from
`people.fake.server.ts`. Member register, Receipts and Census are unconnected UI mockups with static sample data, to be
rebuilt in Phase 2 (#144) and Phase 3 (#145). Permissions: `people:read` (which also opens the mockups),
`people:write`, `people:appoint`, and `people:self`, which alone admits non-admins to My area and, by the role on their
record, Capo Admin or their team. See `docs/people/README.md`.

Built with TanStack Start (React 19), Vite+, Nitro, Tailwind CSS v4 and shadcn/Base UI. The server is an OIDC BFF:
only it holds the IdP access/refresh/ID tokens. Browsers hold an opaque, HttpOnly session cookie; sessions live in a
Expand All @@ -33,23 +32,23 @@ Signing in as a real user needs a development client registered in the IdP.

Set the following runtime variables (the build does not need secrets):

| Variable | Meaning |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `APP_URL` | Dashboard's public origin. Required outside development; HTTPS in production. Development defaults to `http://localhost:$PORT` (3001). Used for exact Origin CSRF validation and callback/logout URLs. |
| `BACKEND_URL` | tRPC backend origin; use the in-cluster service address in production. Defaults to `http://localhost:3000` in development. |
| `IDP_URL` | Public IdP origin; defaults to `https://auth.polinetwork.org`. Issuer is `$IDP_URL/api/auth`. Browser redirects retain this public origin. |
| `IDP_INTERNAL_URL` | Optional internal IdP origin, e.g. `http://auth-service.auth.svc.cluster.local:3000`. Rewrites discovery, token, JWKS and revocation transport only; issuer, JWT assertion audiences (public token/revoke endpoints) and browser URLs remain public. |
| `OIDC_CLIENT_ID` | Registered confidential dashboard client, with authorization code and refresh grants, scopes `openid profile email offline_access backend:admin`, and backend resource link. |
| `OIDC_CLIENT_PRIVATE_JWK` | Private signing JWK JSON with `kid` and `alg` (`EdDSA`/Ed25519 or `ES256`/P-256); inject from this service's Key Vault. Register only its public JWKS in the IdP. |
| `OIDC_CLIENT_SECRET` | Alternative `client_secret_basic` credential; set exactly one client credential mode. |
| `BACKEND_RESOURCE` | Backend OAuth resource/audience; defaults to `https://backend.internal.polinetwork.org`. Sent on every authorize, code exchange and refresh request. |
| `REDIS_URL` | Dedicated dashboard Redis connection. Required outside development; development defaults to `redis://localhost:6379`. Never point to shared backend Redis. |
| `SESSION_SECRET` | Independent random secret of at least 32 characters, encrypting the 10-minute state/nonce/PKCE cookie. Rotation invalidates pending login attempts. |
| `AGENT_PERMISSIONS` | Development only. Comma-separated permissions for the agent-mode user (default: all). `people:self` alone previews the restricted People view. |
| `AGENT_PERSON` | Development only. The agent-mode user's People record (default `P-0007`, Direttivo and President). `P-0098` is a Capo Admin and the Vice-president, `P-0034` the IT Team Lead, `P-0115` an IT member, `P-0045` has a fee receipt to review, `P-0021` a rejected one; `match:P-0115` previews the first sign-in, `none` a sign-in without a record. Sending an invite prints its link in the dev server's console. |
| `AGENT_MODE` | Fake all-permission user in development only. It has no access token, so the backend refuses its calls; People serves sample data. Production ignores this mode. |
| `PORT` | Local listening port (3001 by default); use a port ≥10000 for agent previews. |
| `TEST_REDIS_URL` | Opt-in disposable Redis for integration tests. Tests create/delete only UUID session keys. |
| Variable | Meaning |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `APP_URL` | Dashboard's public origin. Required outside development; HTTPS in production. Development defaults to `http://localhost:$PORT` (3001). Used for exact Origin CSRF validation and callback/logout URLs. |
| `BACKEND_URL` | tRPC backend origin; use the in-cluster service address in production. Defaults to `http://localhost:3000` in development. |
| `IDP_URL` | Public IdP origin; defaults to `https://auth.polinetwork.org`. Issuer is `$IDP_URL/api/auth`. Browser redirects retain this public origin. |
| `IDP_INTERNAL_URL` | Optional internal IdP origin, e.g. `http://auth-service.auth.svc.cluster.local:3000`. Rewrites discovery, token, JWKS and revocation transport only; issuer, JWT assertion audiences (public token/revoke endpoints) and browser URLs remain public. |
| `OIDC_CLIENT_ID` | Registered confidential dashboard client, with authorization code and refresh grants, scopes `openid profile email offline_access backend:admin`, and backend resource link. |
| `OIDC_CLIENT_PRIVATE_JWK` | Private signing JWK JSON with `kid` and `alg` (`EdDSA`/Ed25519 or `ES256`/P-256); inject from this service's Key Vault. Register only its public JWKS in the IdP. |
| `OIDC_CLIENT_SECRET` | Alternative `client_secret_basic` credential; set exactly one client credential mode. |
| `BACKEND_RESOURCE` | Backend OAuth resource/audience; defaults to `https://backend.internal.polinetwork.org`. Sent on every authorize, code exchange and refresh request. |
| `REDIS_URL` | Dedicated dashboard Redis connection. Required outside development; development defaults to `redis://localhost:6379`. Never point to shared backend Redis. |
| `SESSION_SECRET` | Independent random secret of at least 32 characters, encrypting the 10-minute state/nonce/PKCE cookie. Rotation invalidates pending login attempts. |
| `AGENT_PERMISSIONS` | Development only. Comma-separated permissions for the agent-mode user (default: all). `people:self` alone previews the restricted People view. |
| `AGENT_PERSON` | Development only. The agent-mode user's People record (default `P-0007`, Direttivo). `P-0098` is a Capo Admin, `P-0034` the IT Team Lead, `P-0115` an IT member; `none` previews a sign-in without a record. Sending an invite prints its link in the dev server's console. |
| `AGENT_MODE` | Fake all-permission user in development only. It has no access token, so the backend refuses its calls; People serves sample data. Production ignores this mode. |
| `PORT` | Local listening port (3001 by default); use a port ≥10000 for agent previews. |
| `TEST_REDIS_URL` | Opt-in disposable Redis for integration tests. Tests create/delete only UUID session keys. |

Before review run `pnpm check`, `pnpm typecheck`, `pnpm test` and `pnpm build`. Run real Redis tests with
`TEST_REDIS_URL=redis://127.0.0.1:<test-port> pnpm test`. The OIDC integration test uses a local fake provider with
Expand Down
29 changes: 1 addition & 28 deletions docs/esign/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,7 @@ closes its dialog, so signing again is one click away.
## Backend tRPC (`esign.*`)

Permissions: `esign:sign` (see own inbox, sign when it is your turn), `esign:requests:manage` (create, cancel, see all,
browse OneDrive). Requests are visible to their signers and creator. People receipt requests are visible to
`admin:access` + `people:members:read`, or to the officer currently assigned to the receipt.
browse OneDrive). Requests are visible to their signers and creator.

- `esign.profile.sync({name, email})` → upserts the caller's signer profile (BFF passes ID-token claims).
- `esign.signers.list()` → registered IdP users with `esign:sign`, including those who have not opened Signatures; each includes eligible drives (manage).
Expand Down Expand Up @@ -185,29 +184,3 @@ permissions}, updatedBySub, updatedAt}], catalog: {roles: [{key, name}], permiss

Placement: `{page (0-based), x, y, width, height}` as fractions of the displayed page (crop box after rotation),
origin top-left.

## People fee receipts

People › Receipts reuses this signing flow through `people.receipts.prepareSignatures({receiptIds})` (1–10 distinct
IDs), then passes the returned `requestId`s to `esign.jobs.create`. The bridge requires the People receipt-signing
permission, the actual President/Vice-president and `esign:sign`; it does not require general request management.
The backend fixes the issued PDF, officer and placement, persists receipt/request ownership and completes/delivers
receipts after eSign completion without a browser callback. Retrying preparation reuses the request. Manual
ZIP/download/upload remains supported; both paths share completion and concurrency guards. The backend implements it
in the `people.*` router (PoliNetworkOrg/backend#83): see [`../people/contract.md`](../people/contract.md#fees-and-receipts).

Receipt request provenance: `esign.requests.list` rows and `esign.requests.get().request` include
`peopleReceipt: { receiptId: string, number: string, year: "YYYY/YY" }` for the bridge's requests. Other requests omit
it or return null; absence remains supported for deployed eSign versions. The backend fixes the title to
`Receipt {number} · {member name}` and owns this metadata; generic create inputs cannot set it. The dashboard links
the reference to `/dashboard/people/receipts?year={year}&q={number}` when the viewer can read the register.

For these system-owned requests, jobs/create, signed-file uploads and completion additionally enforce
`people:receipts:sign`, the confirmed officer and receipt assignment/version; `myTurn` reflects that eligibility.
Reject custom placement changes, generic cancellation, deletion and delivery retry: People emails the receipt, so
eSign records no output or email for it. Voiding the receipt cancels its request and logs a `people_invalidated`
event. The signed-file upload also accepts a CAdES `.p7m` around the issued PDF; eSign keeps the PDF inside it, so
the signed copy is the one in People › Receipts. People voiding owns request/job invalidation;
manual upload or CIE completion uses the same transaction and exactly-once delivery guard. Decline/failure leaves
the receipt `toSign`, allowing preparation to reuse an active request or create its replacement. No browser callback
completes a receipt. The agent protocol is unchanged.
Loading
Loading