Skip to content
Open
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: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,12 @@ During a sign-in the login page names the requesting application. The consent pa

Future consumers should use authorization code with PKCE, validate token signatures, issuer, audience, and expiration, and request `polinetwork:identity` only when needed. APIs must check their own permissions. An ID token or a linked Telegram ID alone is not permission to moderate a group.

### People roles

Master Admin can bind a hand-made role to the admin dashboard's People register on the role's page (People register card): its holders are admins of the Telegram groups (`admin`), or members or the Lead of a team (`team:<key>:member`, `team:<key>:lead`, where the key is the dashboard's team key: `it`, `hr`, `design`, `intl` or `events`). Managed roles, and roles that carry any `idp:*` permission directly, through a parent or through an implication, can't be bound, and saving a role or permission that would give a bound role one is refused.

`GET /api/internal/people-roles` (backend client, `idp:access:read`) lists the bound roles, everyone holding them by hand, and everyone with a PoliNetwork Microsoft 365 account (whose roles may be empty), with their name, directory email, Microsoft 365 object ID, Telegram ID and username, and which of those roles they were given directly; unlike the access snapshot it includes roles that grant no permission. `POST /api/internal/people-roles` (backend client, `idp:people:roles`) applies one People command `{ id, actor, sub, reason, changes }` on behalf of the dashboard user the backend authenticated, where each change names a desired state: `{ kind: "admin", active }` or `{ kind: "team", team, role: "member" | "lead" | null }`. This IdP plans the concrete assignments against its own role graph and applies all of them or none: a concept with no bound role stays People's own and is skipped; it refuses with 409 when the mapping is ambiguous (not exactly one bound role to give), or when the requested state would still be held through another role, rather than reporting a change that did not happen. The backend only names the actor; this IdP resolves the actor's own access and refuses every assignment, before any write, unless: the role and every bound role it inherits are allowed to the actor (admin and team member roles need `admin:access` and `people:write`, or for team members leading that team; Lead roles need `admin:access` and `people:appoint`); the actor isn't giving themselves a role (giving one up is allowed); and, unless Master Admin, the actor holds every permission the role grants. The response is the person's bound roles after the command (`held`, `assigned`) and the bound role list. Each assignment is audited as `role.assign`/`role.unassign` with `via: "people"`, the reason and the command ID, and the command itself as `people.roles` under `people-command:<id>`: repeating an ID with the same command, for example after a lost response, applies nothing again and returns the current roles, and repeating it with a different command is refused with 409. Grant `idp:people:roles` to the backend client on its application page to enable writes.

## MCP server

`/api/mcp` is an MCP server, so an AI agent such as Claude Code or Codex can read and change this identity provider for you: debug an application that cannot sign people in, register or edit applications, and set up roles, permissions and who holds them. It only works for people holding `idp:mcp`, and the agent can only do what that person could do on the website.
Expand Down Expand Up @@ -371,7 +377,7 @@ Every tool calls the same code as the matching page, which checks the permission
| `idp:roles:write` | `create_role`, `update_role`, `delete_role` |
| `idp:roles:assign` | `assign_role`, `unassign_role` |
| `idp:permissions:write` | `create_permission`, `update_permission`, `delete_permission` |
| Master Admin | `list_audit_events` |
| Master Admin | `list_audit_events`, `set_role_people_binding` |

Deleting people is left to the website. Deletes ask the agent to repeat the name or key. New client secrets go back to the agent once, like on the page, so it can hand them over.

Expand Down
2 changes: 2 additions & 0 deletions drizzle/0023_role_people_binding.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE "role" ADD COLUMN "people_binding" text;--> statement-breakpoint
ALTER TABLE "role" ADD CONSTRAINT "role_people_binding_check" CHECK ("role"."people_binding" is null or (not "role"."managed" and "role"."people_binding" ~ '^(admin|team:[a-z0-9-]{1,32}:(member|lead))$'));
Loading
Loading