Skip to content

feat(people): register people before login and expose role assignment dates - #75

Merged
toto04 merged 2 commits into
feat/people-role-apifrom
feat/people-role-since
Oct 27, 2026
Merged

toto04 merged 2 commits into
feat/people-role-apifrom
feat/people-role-since

Conversation

@BIA3IA

@BIA3IA BIA3IA commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

People can register a person in Auth before their first login, using only first and last name, optional contacts, and initial bound admin/team roles. Auth remains the authority for identity details and role assignments. This extends #73 and supports PoliNetworkOrg/backend#99 and PoliNetworkOrg/admin#136.

  • Migration 0024_people_registration.sql stores declared contacts separately from verified provider evidence. The preliminary Auth subject has no credentials, provider accounts or sessions, and is excluded from the login-account/signing picker.
  • The existing backend-only endpoint accepts idempotent create-person, update-person and link-person commands. Creation/contact editing require admin:access and people:write; every role assignment still passes the original delegation checks. Reused IDs with changed input are refused.
  • Verified app email or Telegram identity produces a review proposal. Homonyms are name-only hints; conflicting identifiers require review. No contact or name automatically links accounts or grants access.
  • A Master Admin approves the identity link. Auth atomically transfers the preliminary person's current bound roles to the existing login account, keeping that account's other roles and provider accounts. A stable registration ID lets People retain its record and history, including after a backend restart or a lost response.
  • Directory responses also expose users[].since, the earliest assignment date conferring each held bound role, including inheritance.

Validation: check, typecheck, all tests and production build/bundle validation passed. 352 tests passed and 117 were skipped, with the People role PostgreSQL suite enabled on a disposable migrated database. New coverage checks contact matching, ambiguity, no pre-login provider account, idempotent creation, rejected non-Master linking, Master approval and canonical directory identity.

Merge into #73 first. Deploy Auth and apply migration 0024 before the matching backend/dashboard feature. Keep the People role bindings and backend idp:people:roles scope configured. Other integration suites requiring their own external configuration remain skipped; no production mutation or real-provider sign-in was performed.

People reads who holds its bound roles from GET /api/internal/people-roles on every operation instead of keeping a copy, so it needs this IdP to say when each role started: since maps every held bound role to the date (YYYY-MM-DD, UTC) of the earliest assignment that confers it, directly or through a parent role.
@coderabbitai

coderabbitai Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e241727d-6788-4d0d-b859-a68e48b267c6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Keep preliminary identities and declared contacts in Auth without credentials or provider accounts. Offer verified identity and name-only review proposals; require Master Admin approval to transfer current bound roles to a real login account while preserving provider accounts and existing roles.

Add the preliminary-person migration and idempotent creation, contact editing and linking commands, with PostgreSQL and matching coverage.
@BIA3IA BIA3IA changed the title feat(rbac): say since when each People role is held feat(people): register people before login and expose role assignment dates Oct 27, 2026
@toto04
toto04 merged commit b57a858 into feat/people-role-api Oct 27, 2026
1 check passed
@toto04

toto04 commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

For the record: we're not going ahead with the preliminary-person model from this PR. It will be removed from #73's branch by resetting it to d698d3c. Tracked in #78; the overall direction is in PoliNetworkOrg/admin#146.

Why:

  • We can't reliably match identities. We often don't know which account someone will sign in with: Entra ID if they're a socio, otherwise any Google account. The name and email we have may not match it.
  • Placeholder user rows cost too much. They have to be excluded across the IdP, and linking or unlinking has to move roles between subjects. That's security-sensitive IdP code we'd rather not carry.
  • Linking only worked for Master Admins. linkPeoplePerson refuses unless the actor is Master Admin (people-registration.ts:106). feat(people)!: manage Auth-backed people before and after their first login backend#99 sends the invitee or the board member as the actor, so both invite acceptance and manual linking would fail with 403.

Instead, people without a linked account stay in the backend with their pending assignments. Linking happens only through an email invite (any account can accept it) or by hand with a new people:link permission. The backend then sends the normal role command, and Auth checks the actor's clearance as before (PoliNetworkOrg/backend#101, PoliNetworkOrg/backend#102).

If you want to keep anything from here, the per-role assignment date (users[].since) can come back on its own; it's listed as optional in #78.

toto04 added a commit that referenced this pull request Oct 30, 2026
…d and drop Soci

Rework of #73 for #78, after dropping #75's preliminary people.

- Add the `capo` binding for Capo Admins: pattern, check constraint, parser and
  `{ kind: "capo", active }` commands. Appointing or ending one needs
  `admin:access` + `people:appoint`; `capo-admin` inherits `admin`, so
  `people:write` is required too.
- Binding a role to People needs the new managed permission `idp:people:bind`
  (always implies `idp:roles:read`) instead of Master Admin, bounded like role
  edits: only Master Admin binds a role granting permissions they don't hold.
  The role page card and the MCP tool follow it.
- Restructure delegation as one rule per binding kind (`PEOPLE_RULES`), so a
  later "Capo Admins manage their admins" rule is a local change.
- `GET /api/internal/people-roles` lists only holders of bound roles: no
  Microsoft 365 accounts or `azureId`. It reports `since` per held role.
- README: People roles and the deployment steps from #79.

Folded into the unreleased migration 0023.

Closes #78
BIA3IA pushed a commit that referenced this pull request Oct 30, 2026
* feat(rbac): let People manage bound roles through the backend

Master Admin can bind a hand-made role to the admin dashboard's People
register (admin, team:<key>:member, team:<key>:lead) on its page. Roles
carrying idp:* permissions can't be bound, and role or permission saves
that would give a bound role one are refused.

- GET /api/internal/people-roles (backend client, idp:access:read) lists
  the bound roles and their holders with name, directory email and
  Telegram, including roles that grant no permission.
- POST /api/internal/people-roles (backend client, new scope
  idp:people:roles) gives or takes back a bound role on behalf of the
  dashboard user the backend authenticated. This IdP resolves the actor's
  own access: no self-grants, people:write (or leading the team) for
  admin and member roles, people:appoint for Leads, admin:access, and
  bounded delegation unless Master Admin. Audited with via: "people".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(mcp): bind roles to People from an agent

Master Admin can call set_role_people_binding, so an agent connected to
the MCP server can set up People roles end to end alongside
create_permission, create_role and assign_role.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(rbac): refuse unknown People actors and skip audits for no-op changes

- An unknown actor got a 500 from the membership refresh; it is now the
  intended 403.
- A People change that alters nothing (already held, or held only
  through a parent role) no longer writes an audit row claiming it did.
- The directory says which bound roles each holder was given directly,
  the only ones a change can take back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(rbac)!: apply People role commands atomically and idempotently

People now sends one command per person with the desired state of each
changed concept. Auth plans the concrete assignments against its own graph,
checks the actor for every one of them before writing, and applies all or
none. A command that would leave the requested state held through another
role is refused with 409 instead of reporting a revocation that never
happened. The command is stored as a `people.roles` audit receipt, so a
retry after a lost response applies nothing again and returns the current
roles.

The actor check now covers every bound role a role inherits, so a member
role wrapping a Lead role needs people:appoint.

The People directory also lists accounts with a PoliNetwork Microsoft 365
login and their object ID, so People can match Microsoft 365 members to
linked accounts without relying on an address.

BREAKING CHANGE: POST /api/internal/people-roles takes
`{ id, actor, sub, reason, changes }` and answers with the person's bound
roles, instead of `{ actor, sub, role, action, reason }` and 204. Deploy
before the backend.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(people)!: bind Capo Admins, delegate binding with idp:people:bind and drop Soci

Rework of #73 for #78, after dropping #75's preliminary people.

- Add the `capo` binding for Capo Admins: pattern, check constraint, parser and
  `{ kind: "capo", active }` commands. Appointing or ending one needs
  `admin:access` + `people:appoint`; `capo-admin` inherits `admin`, so
  `people:write` is required too.
- Binding a role to People needs the new managed permission `idp:people:bind`
  (always implies `idp:roles:read`) instead of Master Admin, bounded like role
  edits: only Master Admin binds a role granting permissions they don't hold.
  The role page card and the MCP tool follow it.
- Restructure delegation as one rule per binding kind (`PEOPLE_RULES`), so a
  later "Capo Admins manage their admins" rule is a local change.
- `GET /api/internal/people-roles` lists only holders of bound roles: no
  Microsoft 365 accounts or `azureId`. It reports `since` per held role.
- README: People roles and the deployment steps from #79.

Folded into the unreleased migration 0023.

Closes #78

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Tommaso Morganti <tommaso.morganti01@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants