Skip to content

Add groups, roles and permissions management - #130

Open
brionmario wants to merge 10 commits into
thunder-id:mainfrom
brionmario:feat-cp-dp-separation
Open

brionmario wants to merge 10 commits into
thunder-id:mainfrom
brionmario:feat-cp-dp-separation

Conversation

@brionmario

@brionmario brionmario commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Purpose

Adds group, role and permission management to the JavaScript SDKs.

Approach

  • Endpoints: endpoints.groups, endpoints.roles and endpoints.agentTypes join the existing management keys. A collection URL resolves from the call's url, then endpoints.<key>, then {baseUrl}/{collection}.
  • @thunderid/javascript:
    • Group and role APIs: list, get, create, update and delete, plus group members and role assignments.
    • Agent groups, agent roles, agent types and user groups.
    • getTokenPermissions() and hasPermission(): read the permissions an access token grants and match them the way the server does (system:user covers system:user:view).
    • createResourceInvalidator moves here from @thunderid/react. resolveManagedIdentityName and toManagementErrorMessage are new.
  • @thunderid/react and @thunderid/vue:
    • Query and mutation hooks (composables in Vue) for every new resource, and useGrantedPermissions().
    • Admin components for users, groups, roles and agents (list, create, details, members, permissions and assignments), each with a Base* variant.
    • Search and filters: UserList and AgentList search on the server (users by username or email, agents by name), and RoleAssignments filters by assignee type. The server matches one attribute exactly, so the search applies on submit. getAgents gains filter, and toEqualityFilter builds the expression. Groups, roles and group members have no server filter, so they stay unfiltered.
    • Render props on every admin component, the same pattern as SignIn: a children function in React and the default scoped slot in Vue receive the data, loading and error state, and handlers, so the UI can come from any library. A list hands over its rows, default columns, limit, offset, totalResults, onPageChange and onSelect, enough to drive a third-party data grid with server-side paging; a form hands over values, handleInputChange, handleSubmit and isValid.
    • A DataTable primitive and an Attribution component.
  • @thunderid/nextjs: the admin components and hooks, with management calls relayed through server actions (managementRequestAction, getGrantedPermissionsAction), so the browser never calls the management API or holds the token.
  • @thunderid/nuxt: a management module option that auto-imports the admin components and composables, and a /api/auth/management route that relays management calls.
  • @thunderid/node and @thunderid/express: client.management(sessionId) and managementFromRequest(req) bind management operations to a session. The session's token stays on the server and is read on every call.
  • @thunderid/react-router and @thunderid/tanstack-router: admin routes (createAdminRoutes, AdminPage).
  • i18n: the admin strings are translated in every locale.

Playgrounds

Every playground exercises the new APIs:

  • capability checks
  • admin, DataTable and Attribution stories with sample data and live variants
  • hooks and composables views
  • a Custom rendering group: UserList as an MUI DataGrid, GroupCreate as an MUI form and UserDetails as an MUI card in React and Next.js, and the same with PrimeVue in Vue and Nuxt (playground-only dependencies)
  • a card showing where each management collection resolves

They read THUNDERID_ENDPOINT_<KEY> (with VITE_ or NUXT_PUBLIC_ where the framework needs it) to override one management collection's URL. The server playgrounds show management calls going through the server. The SDK gaps the playgrounds hit are listed in each README.

Screenshots

From the React playground's component explorer, with sample data.

Admin components, including the server-side search and the assignee type filter:

UserList UserList, searching by email
UserList UserList, searching by email
UserDetails UserCreate
UserDetails UserCreate
GroupMembers RoleAssignments, filtered to groups
GroupMembers RoleAssignments, filtered to groups

Custom rendering through render props, drawn with MUI. Each admin component has a Custom (MUI) story; the Code tab shows the render function:

UserList as an MUI DataGrid, with search The render function
UserList as an MUI DataGrid, with search The render function
GroupCreate as an MUI form UserDetails as an MUI card
GroupCreate as an MUI form UserDetails as an MUI card
GroupMembers with an MUI picker RoleAssignments with type chips
GroupMembers with an MUI picker RoleAssignments with type chips
RolePermissions as MUI cards AgentCreate as an MUI form
RolePermissions as MUI cards AgentCreate as an MUI form

Vue, through the default scoped slot with PrimeVue:

UserList as a PrimeVue DataTable GroupCreate UserDetails
UserList as a PrimeVue DataTable GroupCreate UserDetails

Verified locally:

  • pnpm build (18/18) and pnpm test across the packages (2,233 passed, 3 skipped).
  • Typecheck, lint and format:check for all seven playgrounds, and builds for React, Vue, Next.js and Nuxt.
  • Admin stories rendered in headless Chromium without errors.
  • Render props: React (459 tests) and Vue (346 passed, 3 skipped) suites include the new render props tests; the MUI and PrimeVue stories rendered headlessly in React, Next.js, Vue and Nuxt, with the grid's paging driving onPageChange.
  • The live management checks have only run signed out so far.

@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • 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.

@github-actions

Copy link
Copy Markdown

🔀 Cross-SDK feature parity

Does this change need to ship in the other ThunderID SDKs too? This check stays red until one of these labels answers that:

Label What it asserts
parity/prs-raised The ports exist in every other SDK this change reaches, or are recorded as not applying there.
parity/prs-not-needed Nothing to port: a fix, refactor, docs, CI, dependency bump, release, or a change specific to this platform.

Ports (DO NOT EDIT)

  • thunder-id/ios-sdks, iOS
  • thunder-id/android-sdks, Android
  • thunder-id/flutter-sdks, Flutter

No sibling SDKs accounted for on this thread yet.

Important

Reply to this comment with this block, filled in. The boxes above tick
themselves when you post it.

- thunder-id/ios-sdks: <paste the port link here>
- thunder-id/android-sdks: <paste the port link here>
- thunder-id/flutter-sdks: <paste the port link here>

A link can be a pull request or a tracked issue, written in full or as thunder-id/ios-sdks#123.
Where the change does not reach an SDK, replace the placeholder with N/A and a short
reason, as in - thunder-id/ios-sdks: N/A, the capability has no equivalent there.

The contract is in the SDK development specification.

Add group and role management to every package: list, get, create, update and delete groups and roles, group members and role assignments, plus agent groups, agent roles, agent types, user groups and user types. The new resources resolve through endpoints.groups, endpoints.roles and endpoints.agentTypes, alongside the existing management collections.

Add getTokenPermissions() and hasPermission(), which read the permissions an access token grants and match them the way the server does, and useGrantedPermissions() in React, Vue and Next.js.

Add admin presentation components for users, groups, roles and agents (list, create, details, members, permissions and assignments, each with a Base variant), a DataTable primitive and an Attribution component to React, Vue and Next.js, and admin routes to react-router and tanstack-router.

On the server, add management() to the Node client and managementFromRequest() to Express, which bind management operations to a session without exposing its token. Next.js relays management calls through server actions and Nuxt through a /api/auth/management route, so the browser never calls the management API directly.

Move createResourceInvalidator into @thunderid/javascript and translate the new admin strings in every locale.

Signed-off-by: Brion <info@brionmario.com>
Every playground now covers the new management APIs: capability checks for groups, roles, role assignments, group members, agent and user types and granted permissions, explorer stories for the admin components, DataTable and Attribution with sample data and live variants, hooks and composables views, and a card showing where each management collection resolves.

Each playground reads THUNDERID_ENDPOINT_<KEY>, which overrides one management collection's URL, with the framework prefix where one applies. The Express, Node, Next.js and Nuxt playgrounds also show management calls going through the server.

The SDK gaps the playgrounds hit are listed in each README.

Signed-off-by: Brion <info@brionmario.com>
@brionmario
brionmario force-pushed the feat-cp-dp-separation branch from 5bb8d0d to 51f46d4 Compare October 22, 2026 11:33
@brionmario brionmario changed the title Add groups, roles and permissions management, and let management APIs live on a separate control plane Add groups, roles and permissions management Oct 22, 2026
Every admin component in React and Vue can now hand its UI to the application, the same way SignIn does. In React each Base and connected component takes a children render function; in Vue, the default scoped slot. The function receives the component's data, loading and error state, and handlers: a list gets its rows, the default columns, limit, offset, totalResults, onPageChange and onSelect, so a third-party data grid can take over with server-side paging; a create form gets its values, handleInputChange, handleSubmit, isValid and isSubmitting; details, members, assignments and permissions get the record and the actions that are allowed. Connected components pass the slot or function through to their Base component, and Next.js re-exports the render props types.

Signed-off-by: Brion <info@brionmario.com>
The React and Next.js explorers draw UserList as an MUI DataGrid with server-side paging, GroupCreate as an MUI form and UserDetails as an MUI card, all through the components' render props. The Vue and Nuxt explorers do the same with PrimeVue's DataTable, inputs and card through the default scoped slot. MUI and PrimeVue are playground dependencies only, and each playground README explains the pattern.

Signed-off-by: Brion <info@brionmario.com>
Instead of a separate Custom rendering group, each admin component's stories now end with a Custom (MUI) story, and the source control gains a custom option. The story renders the Base component with sample data through its children render function: lists as an MUI DataGrid with server-side paging, create forms with MUI inputs, details and permissions as MUI cards, and group members and role assignments as a DataGrid with an MUI picker. The Code panel shows the render props version for each.

Signed-off-by: Brion <info@brionmario.com>
The admin components' story lists now hold the sample-data stories and the Custom (MUI) story. The live connected components are still reachable through each component's source control.

Signed-off-by: Brion <info@brionmario.com>
DataGrid sets each cell's line-height to the row height, which pushed the SDK's avatar and name cell out of place and clipped the avatar. Each cell the custom stories hand to the grid is now wrapped in a flex box that resets the line-height and centers the content.

Signed-off-by: Brion <info@brionmario.com>
The management list endpoints accept a single exact-match filter on users (attribute eq "value") and on custom agent attributes, and an assignee type on role assignments; groups, roles and group members take no filter. The agents API now passes filter through, and a new equality filter helper builds the expression, trimming the value and rejecting blank values or invalid attribute names.

UserList and AgentList show a search form that applies on submit: users by username or email, agents by name, configurable through searchAttributes and switchable with searchable. A fixed filter on UserList hides the form, since the server takes one expression. An empty page while searching says nothing matched. RoleAssignments shows an assignee type filter that narrows the request, switchable with filterable. The Base components take the search or type filter as props, and the render props carry them so a custom UI can draw its own search box or filter.

Signed-off-by: Brion <info@brionmario.com>
The UserList and AgentList sample stories search their sample rows the way the server does, an exact match on the chosen attribute, and RoleAssignments narrows its sample assignments by type. The Custom (MUI) stories draw the search as MUI fields with a clearable chip, and the assignee type filter as MUI chips, from the render props.

Signed-off-by: Brion <info@brionmario.com>
The React Select drew its arrow inside the right padding, which matched the left, so in a narrow select such as the admin search attribute picker the selected label ran under the arrow. The right padding now leaves room for it.

Signed-off-by: Brion <info@brionmario.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.

1 participant