feat(teams): admin toggle to restrict inviting to admins#5032
Conversation
Follow-up to #4994 per review discussion: members can invite by default, but team admins can now turn 'allow non-admin invites' off so only admins/owners may send invites. - New team.allow_non_admin_invites column (default TRUE) + migration - invite_users_to_team returns 403 NonAdminInvitesDisabled for non-admin callers when the setting is off - New admin-only POST /team/non-admin-invites/toggle endpoint (mirrors the auto-join toggle), registered in swagger - Web: 'Members can invite' toggle in team settings; invite button hidden for members when restricted; generated client extended to match the new spec (orval regen will produce the same output) - Replace em-dashes with plain hyphens in PR-added comments/messages - Tests: service gating (member blocked / admin allowed / toggle delegation), repo toggle + default + missing-team, mock updates
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAdds a persistent 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
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. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/teams/src/inbound/axum_router/invite_to_team.rs (1)
57-89: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winEliminate duplicated error response mapping.
InviteUsersToTeamErroralready implementsIntoResponse(as seen incrates/teams/src/inbound/axum_router/mod.rs), so we can avoid duplicating its variant mappings here by delegating directly to it. This prevents the need to make parallel updates across multiple files when adding new domain errors likeNonAdminInvitesDisabled.♻️ Proposed refactor to delegate the response conversion
- InviteToTeamError::InviteUsersToTeamError(e) => match e { - InviteUsersToTeamError::TooManyEmails => ( - StatusCode::BAD_REQUEST, - Json(ErrorResponse { - message: "too many emails".into(), - }), - ), - InviteUsersToTeamError::NonAdminInvitesDisabled => ( - StatusCode::FORBIDDEN, - Json(ErrorResponse { - message: "only team admins may invite users to this team".into(), - }), - ), - InviteUsersToTeamError::NotEnoughOpenSeats => ( - StatusCode::BAD_REQUEST, - Json(ErrorResponse { - message: "free team member limit reached; upgrade to invite more members" - .into(), - }), - ), - InviteUsersToTeamError::CustomerError(_) => ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ErrorResponse { - message: "internal server error".into(), - }), - ), - _ => ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ErrorResponse { - message: "unable to invite users to team".into(), - }), - ), - }, + InviteToTeamError::InviteUsersToTeamError(e) => return e.into_response(),🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/teams/src/inbound/axum_router/invite_to_team.rs` around lines 57 - 89, Replace the nested InviteUsersToTeamError variant-to-response match in the InviteToTeamError::InviteUsersToTeamError arm with direct delegation to InviteUsersToTeamError’s existing IntoResponse implementation. Preserve the outer InviteToTeamError handling and allow all current and future domain error mappings to be handled centrally.
🧹 Nitpick comments (1)
crates/teams/src/outbound/team_repo.rs (1)
1489-1529: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUse
query_scalar!for these bool queries
Both statements return a singlebool, soquery_scalar!can drop the manual row mapping.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/teams/src/outbound/team_repo.rs` around lines 1489 - 1529, Update get_team_allow_non_admin_invites and toggle_allow_non_admin_invites to use sqlx::query_scalar! for their single-boolean SELECT and UPDATE RETURNING queries. Fetch the optional scalar bool directly, preserve the existing TeamDoesNotExist error when no value is returned, and remove the manual row-field mapping.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@crates/teams/src/inbound/axum_router/invite_to_team.rs`:
- Around line 57-89: Replace the nested InviteUsersToTeamError
variant-to-response match in the InviteToTeamError::InviteUsersToTeamError arm
with direct delegation to InviteUsersToTeamError’s existing IntoResponse
implementation. Preserve the outer InviteToTeamError handling and allow all
current and future domain error mappings to be handled centrally.
---
Nitpick comments:
In `@crates/teams/src/outbound/team_repo.rs`:
- Around line 1489-1529: Update get_team_allow_non_admin_invites and
toggle_allow_non_admin_invites to use sqlx::query_scalar! for their
single-boolean SELECT and UPDATE RETURNING queries. Fetch the optional scalar
bool directly, preserve the existing TeamDoesNotExist error when no value is
returned, and remove the manual row-field mapping.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: e6e70dd8-1cd1-44b3-9828-026fa67868e0
⛔ Files ignored due to path filters (8)
.sqlx/query-05f7a9a439044165e4523a2b25261b11f805ba2dcf3f0b0a03422502ee73f304.jsonis excluded by!**/.sqlx/**.sqlx/query-210d2b138dfaed6974dad7ee5cdbf00b90b65abddda29eab93d816e453371bf4.jsonis excluded by!**/.sqlx/**.sqlx/query-5d5cf55d5b9dc0b138684b01620b30dc5908a951020368af47cbbe6ca1052e79.jsonis excluded by!**/.sqlx/**.sqlx/query-8b1e8ff98b7b6cfc0d18fc32bcba5f41392f2f716d7c3ba7c7d50044981a322e.jsonis excluded by!**/.sqlx/**.sqlx/query-edb000afe8b8359996a3623dd495bcc6b3b39fe98fb76bcbccb176ba9e06aaef.jsonis excluded by!**/.sqlx/**apps/web/src/lib/service-clients/service-auth/generated/schemas/index.tsis excluded by!**/generated/**,!apps/web/src/lib/service-clients/**/generated/**apps/web/src/lib/service-clients/service-auth/generated/schemas/team.tsis excluded by!**/generated/**,!apps/web/src/lib/service-clients/**/generated/**apps/web/src/lib/service-clients/service-auth/generated/schemas/toggleNonAdminInvitesResponse.tsis excluded by!**/generated/**,!apps/web/src/lib/service-clients/**/generated/**
📒 Files selected for processing (16)
apps/web/src/features/settings/Team.tsxapps/web/src/lib/queries/team/teams.tsapps/web/src/lib/service-clients/service-auth/client.tscrates/macro_db_client/migrations/20260720145727_add_team_allow_non_admin_invites.sqlcrates/teams/src/domain/model.rscrates/teams/src/domain/team_repo.rscrates/teams/src/domain/team_service.rscrates/teams/src/domain/team_service/test.rscrates/teams/src/inbound/axum_router/create_team.rscrates/teams/src/inbound/axum_router/invite_to_team.rscrates/teams/src/inbound/axum_router/mod.rscrates/teams/src/inbound/axum_router/test.rscrates/teams/src/inbound/axum_router/toggle_non_admin_invites.rscrates/teams/src/outbound/team_repo.rscrates/teams/src/outbound/team_repo/test.rsservices/authentication_service/src/api/swagger.rs
- Regenerate service-auth openapi.json + generated client.ts for the new /team/non-admin-invites/toggle endpoint and Team.allow_non_admin_invites field (CI gen-api check) - Delegate InviteToTeamError's domain-error arm to InviteUsersToTeamError's IntoResponse impl instead of duplicating the variant mappings - Use query_scalar! for the single-bool allow_non_admin_invites queries
The Team type's new required field broke the create-team optimistic update literal; new teams default to allowing member invites.
Follow-up to #4994, implementing the resolution of the invite-permission review thread: @whutchinson98 flagged that member-level invites are asymmetric with admin-only revocation; @jbecke's call was to keep member invites as the default but give admins an off switch. #4994 merged before this landed, so it ships here as its own PR.
What changed
Backend
team.allow_non_admin_invitescolumn,BOOLEAN NOT NULL DEFAULT TRUE(migration20260720145727).invite_users_to_teamnow checks the setting after the member-role extractor: when the team has turned it off, a non-admin caller gets 403NonAdminInvitesDisabled("only team admins may invite users to this team"). Admins/owners are unaffected; teams that never touch the setting behave exactly as on main.POST /team/non-admin-invites/toggle, mirroring the auto-join-domain toggle (same extractor pattern, returns the authoritative new value). Registered in the authentication_service OpenAPI spec.Web
Team.allow_non_admin_invites,ToggleNonAdminInvitesResponse) since codegen can't run in this sandbox — the next orval regen should produce identical output.Review nits from #4994
mod.rscomment).InviteUsersToTeamError's standaloneIntoResponsenow mapsNotEnoughOpenSeatsto the same 400 + message as the/team/invitehandler path (was falling to the 500 catch-all), per CodeRabbit's finding on feat(teams): free teams up to 5 members, member invites, auto-join domain by default #4994.Tests
TeamDoesNotExistfor missing teams from both getter and toggle.cargo test -p teams: 172 passed, 0 failed against local Postgres 16 with all migrations. Clippy clean;authentication_servicecompiles;.sqlxcache regenerated via workspace prepare (minimal diff: 3 reworked team queries + 2 new ones).bun installcan't fetch private git deps in this sandbox).Generated by Claude Code