Conversation
- Add permission_ids field to team member profile schemas (client/server) - Update team member profiles API to include permission_ids in responses - Modify team member list UI to display roles based on permission_ids - Update team invitation endpoints to handle permission_ids - Add role-permissions endpoint for fetching available permissions - Update documentation with permission_ids examples and type definitions - Add conceptual documentation for role-based team invitations
…s API - Add permission_ids field to team-member-profiles schema (client & server) - Update CRUD operations to fetch and include direct permission_ids - Fix team-member-list-section to use permission_ids for role display - Optimize permission fetching to avoid N+1 queries - Add atomic permission granting in team-invitations/accept - Add e2e test coverage for permission_ids in API responses - Fix naming convention violations (snake_case to camelCase) - Remove debug console.log statements Resolves broken member list roles functionality
- Changed the return statement in the `get` method to use `await res.json()` instead of `res.data` for proper JSON handling. This change ensures that the API response is correctly parsed as JSON, improving data handling in the client interface.
…n_ids validation - Added tests for fetching role permissions from the role-permissions endpoint. - Implemented tests for sending invitations with specific permission_ids and verifying their application upon acceptance. - Updated team member profile tests to ensure permission_ids are included in responses for both the inviting and invited users. - Refactored permission fetching logic in the frontend to utilize the new project.listTeamPermissionDefinitions method.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
- Introduced tests for handling invalid, malformed, and mixed permission_ids in team invitations, ensuring proper error responses. - Added tests for permission escalation scenarios, verifying that users without invite permissions cannot grant elevated permissions. - Implemented tests for backwards compatibility with empty and omitted permission_ids. - Included server access tests to confirm that invitations can be sent with permission_ids bypassing user permissions. - Enhanced role-permissions endpoint tests for consistent responses between client and server access.
- Updated logic to ensure that only non-default roles are included in permissionIds when inviting users to a team, enhancing the accuracy of permission assignments during the invitation process.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
- Updated test cases to use `receiveMailbox.emailAddress` for email field in team invitation requests. - Simplified error response assertions by removing unnecessary nested property checks for 'error', focusing directly on 'code' and 'message' properties. - Enhanced consistency in error handling across various invitation scenarios, ensuring clearer validation of permission-related errors.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds permission_ids support across team invitations and member profiles: new role-permissions endpoint, permission_ids accepted on send-code, preserved on invitation records, applied when accepting invitations, propagated through CRUD/SDK/server layers, plus UI/docs/tests updates and permission validation/guards. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as UI/Client
participant SDK as SDK
participant API as Backend
participant DB as Database
UI->>SDK: GET listTeamPermissionDefinitions()
SDK->>API: GET /team-invitations/role-permissions
API->>DB: Query permission definitions (scope: team)
DB-->>API: Permission definitions
API-->>SDK: { items: [...], is_paginated: false }
SDK-->>UI: available roles shown
UI->>SDK: inviteUser(email, callbackUrl, permissionIds)
SDK->>API: POST /team-invitations/send-code { permission_ids }
API->>API: Validate permission_ids (server-only guard)
API->>DB: Create invitation code with permission_ids
DB-->>API: Stored code
API-->>SDK: Invitation created
sequenceDiagram
participant Inviter as Inviter (client/server)
participant CodeHandler as Send-Code Handler
participant Accepter as Accept Handler
participant PermSvc as Permission Granting
participant DB as Database
Inviter->>CodeHandler: POST send-code (email, permission_ids?)
CodeHandler->>DB: Store code.data.permission_ids
Note right of DB: invitation code saved with permission_ids
Accepter->>CodeHandler: POST accept (code)
CodeHandler->>DB: Create team membership (within tx)
CodeHandler->>PermSvc: For each deduped permission_id -> grantTeamPermission (within same tx)
PermSvc-->>DB: Insert grants
DB-->>Accepter: Membership + permissions persisted
Accepter-->>Inviter: Acceptance complete
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Areas to focus on:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
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 |
Greptile Summary
Confidence Score: 2/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant Client
participant API as Team Invitations API
participant Handler as Verification Handler
participant DB as Database
participant Email as Email Service
User->>Client: Send invitation with role
Client->>API: POST /team-invitations/send-code (server-side)
API->>API: Validate permission_ids
API->>Handler: Create verification code
Handler->>DB: Store invitation with permission_ids
Handler->>Email: Send invitation email
Email-->>User: Invitation email with link
User->>Client: Click invitation link
Client->>Handler: POST /accept (with code)
Handler->>DB: Verify invitation code
Handler->>DB: Create team membership
Handler->>DB: Grant permissions from permission_ids
Handler-->>Client: Success response
|
There was a problem hiding this comment.
22 files reviewed, 8 comments
Edit Code Review Agent Settings | Greptile
React with 👍 or 👎 to share your feedback on this new summary format
| if (response.body.items.length > 0) { | ||
| const item = response.body.items[0]; | ||
| expect(item).toHaveProperty('id'); | ||
| expect(item).toHaveProperty('contained_permission_ids'); |
There was a problem hiding this comment.
logic: test will fail - the security fix on line 41-43 of apps/backend/src/app/api/latest/team-invitations/send-code/route.tsx blocks client-side requests from setting permission_ids, but this test expects status 200 instead of 403
| expect(item).toHaveProperty('contained_permission_ids'); | |
| expect(sendResponse.status).toBe(403); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts
Line: 576:576
Comment:
**logic:** test will fail - the security fix on line 41-43 of `apps/backend/src/app/api/latest/team-invitations/send-code/route.tsx` blocks client-side requests from setting `permission_ids`, but this test expects status 200 instead of 403
```suggestion
expect(sendResponse.status).toBe(403);
```
How can I resolve this? If you propose a fix, please make it concise.| // Grant invite permission | ||
| await niceBackendFetch(`/api/v1/team-permissions/${teamId}/${userId}/$invite_members`, { | ||
| accessType: "server", | ||
| method: "POST", | ||
| body: {}, | ||
| }); |
There was a problem hiding this comment.
logic: these assertions will never be reached since the test now expects a 403 error from the client-side security check - remove or update these assertions
| // Grant invite permission | |
| await niceBackendFetch(`/api/v1/team-permissions/${teamId}/${userId}/$invite_members`, { | |
| accessType: "server", | |
| method: "POST", | |
| body: {}, | |
| }); | |
| // Verify that permission_ids cannot be set from client-side | |
| expect(sendResponse.body).toHaveProperty('code'); | |
| expect(sendResponse.body.message).toMatch(/permission_ids.*server-side/i); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts
Line: 586:591
Comment:
**logic:** these assertions will never be reached since the test now expects a 403 error from the client-side security check - remove or update these assertions
```suggestion
// Verify that permission_ids cannot be set from client-side
expect(sendResponse.body).toHaveProperty('code');
expect(sendResponse.body.message).toMatch(/permission_ids.*server-side/i);
```
How can I resolve this? If you propose a fix, please make it concise.|
|
||
| const sendResponse = await niceBackendFetch("/api/v1/team-invitations/send-code", { | ||
| method: "POST", | ||
| accessType: "client", | ||
| body: { | ||
| email: receiveMailbox.emailAddress, | ||
| team_id: teamId, | ||
| callback_url: "http://localhost:12345/some-callback-url", | ||
| permission_ids: ["team_admin"], | ||
| }, | ||
| }); | ||
|
|
||
| expect(sendResponse.status).toBe(200); | ||
|
|
||
| // Verify invitation was created with permission_ids | ||
| const listResponse = await niceBackendFetch(`/api/v1/team-invitations?team_id=${teamId}`, { | ||
| accessType: "server", | ||
| method: "GET", | ||
| }); | ||
|
|
||
| expect(listResponse.status).toBe(200); | ||
| expect(listResponse.body.items.length).toBeGreaterThan(0); | ||
| expect(listResponse.body.items[0]).toHaveProperty('permission_ids'); | ||
| expect(listResponse.body.items[0].permission_ids).toEqual(["team_admin"]); | ||
| }); | ||
|
|
||
| it("applies permission_ids when accepting invitation", async ({ expect }) => { | ||
| await Project.createAndSwitch({ config: { magic_link_enabled: true } }); | ||
| const { userId } = await Auth.Otp.signIn(); | ||
| const { teamId } = await Team.create(); | ||
|
|
||
| // Grant invite permission | ||
| await niceBackendFetch(`/api/v1/team-permissions/${teamId}/${userId}/$invite_members`, { | ||
| accessType: "server", | ||
| method: "POST", | ||
| body: {}, |
There was a problem hiding this comment.
logic: test logic is broken - client-side requests with permission_ids are now blocked by the security fix, so this test will fail at line 609 with a 403 error before reaching the invitation acceptance logic
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts
Line: 594:629
Comment:
**logic:** test logic is broken - client-side requests with `permission_ids` are now blocked by the security fix, so this test will fail at line 609 with a 403 error before reaching the invitation acceptance logic
How can I resolve this? If you propose a fix, please make it concise.| // Send invitation with permission_ids | ||
| await niceBackendFetch("/api/v1/team-invitations/send-code", { | ||
| method: "POST", | ||
| accessType: "client", | ||
| body: { | ||
| email: receiveMailbox.emailAddress, | ||
| team_id: teamId, | ||
| callback_url: "http://localhost:12345/some-callback-url", | ||
| permission_ids: ["team_admin"], | ||
| }, | ||
| }); | ||
|
|
||
| // Accept invitation as new user | ||
| backendContext.set({ mailbox: receiveMailbox }); | ||
| await Auth.Otp.signIn(); | ||
| await Team.acceptInvitation(); | ||
|
|
||
| // Verify user received the permission | ||
| const newUserId = (await User.getCurrent()).id; | ||
| const permissionResponse = await niceBackendFetch( | ||
| `/api/v1/team-permissions/${teamId}/${newUserId}/team_admin`, | ||
| { | ||
| accessType: "server", |
There was a problem hiding this comment.
logic: test uses client-side accessType with permission_ids, which is now blocked - change to server-side access or expect 403 error
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts
Line: 634:656
Comment:
**logic:** test uses client-side `accessType` with `permission_ids`, which is now blocked - change to server-side access or expect 403 error
How can I resolve this? If you propose a fix, please make it concise.| } | ||
| ); | ||
|
|
||
| expect(permissionResponse.status).toBe(200); | ||
| expect(permissionResponse.body).toHaveProperty('id', 'team_admin'); | ||
| }); | ||
|
|
||
| // Edge case tests for invalid permission_ids | ||
| it("returns 400 error for invitation with non-existent permission_ids", async ({ expect }) => { | ||
| await Project.createAndSwitch({ config: { magic_link_enabled: true } }); | ||
| const { userId } = await Auth.Otp.signIn(); | ||
| const { teamId } = await Team.create(); | ||
|
|
||
| // Grant invite permission | ||
| await niceBackendFetch(`/api/v1/team-permissions/${teamId}/${userId}/$invite_members`, { | ||
| accessType: "server", | ||
| method: "POST", | ||
| body: {}, | ||
| }); | ||
|
|
||
| const receiveMailbox = createMailbox(); |
There was a problem hiding this comment.
logic: test uses client-side accessType with permission_ids (malformed as number), but will now fail with 403 before schema validation - change to server-side or adjust expectations
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts
Line: 658:678
Comment:
**logic:** test uses client-side `accessType` with `permission_ids` (malformed as number), but will now fail with 403 before schema validation - change to server-side or adjust expectations
How can I resolve this? If you propose a fix, please make it concise.| const response = await niceBackendFetch("/api/v1/team-invitations/send-code", { | ||
| method: "POST", | ||
| accessType: "client", | ||
| body: { | ||
| email: receiveMailbox.emailAddress, | ||
| team_id: teamId, | ||
| callback_url: "http://localhost:12345/some-callback-url", | ||
| permission_ids: ["non_existent_role"], | ||
| }, | ||
| }); | ||
|
|
||
| expect(response.status).toBe(400); | ||
| expect(response.body).toHaveProperty('code'); | ||
| expect(response.body).toHaveProperty('message'); | ||
| expect(response.body.message).toMatch(/invalid permission|permission.*not found/i); | ||
| }); | ||
|
|
||
| it("returns 400 error for invitation with malformed permission_ids", async ({ expect }) => { | ||
| await Project.createAndSwitch({ config: { magic_link_enabled: true } }); | ||
| const { userId } = await Auth.Otp.signIn(); | ||
| const { teamId } = await Team.create(); | ||
|
|
There was a problem hiding this comment.
logic: same issue - client-side request with permission_ids will return 403 before validation
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts
Line: 680:701
Comment:
**logic:** same issue - client-side request with `permission_ids` will return 403 before validation
How can I resolve this? If you propose a fix, please make it concise.| const { teamId } = await Team.create(); | ||
|
|
||
| // Grant invite permission | ||
| await niceBackendFetch(`/api/v1/team-permissions/${teamId}/${userId}/$invite_members`, { | ||
| accessType: "server", | ||
| method: "POST", | ||
| body: {}, | ||
| }); | ||
|
|
||
| const receiveMailbox = createMailbox(); | ||
|
|
||
| const response = await niceBackendFetch("/api/v1/team-invitations/send-code", { | ||
| method: "POST", | ||
| accessType: "client", | ||
| body: { | ||
| email: receiveMailbox.emailAddress, | ||
| team_id: teamId, | ||
| callback_url: "http://localhost:12345/some-callback-url", | ||
| permission_ids: ["team_admin", "invalid_permission"], | ||
| }, | ||
| }); |
There was a problem hiding this comment.
logic: client-side request with empty permission_ids array will fail with 403 - the security check blocks ANY permission_ids value (including empty arrays) from client-side
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts
Line: 730:750
Comment:
**logic:** client-side request with empty `permission_ids` array will fail with 403 - the security check blocks ANY `permission_ids` value (including empty arrays) from client-side
How can I resolve this? If you propose a fix, please make it concise.| await Project.createAndSwitch({ config: { magic_link_enabled: true } }); | ||
| const { userId } = await Auth.Otp.signIn(); | ||
| const { teamId } = await Team.create(); | ||
|
|
||
| // Grant invite permission | ||
| await niceBackendFetch(`/api/v1/team-permissions/${teamId}/${userId}/$invite_members`, { | ||
| accessType: "server", | ||
| method: "POST", | ||
| body: {}, | ||
| }); | ||
|
|
||
| const receiveMailbox = createMailbox(); | ||
|
|
||
| const response = await niceBackendFetch("/api/v1/team-invitations/send-code", { | ||
| method: "POST", | ||
| accessType: "client", | ||
| body: { | ||
| email: receiveMailbox.emailAddress, | ||
| team_id: teamId, | ||
| callback_url: "http://localhost:12345/some-callback-url", | ||
| permission_ids: [], // Empty array |
There was a problem hiding this comment.
logic: client-side request with permission_ids: null will return 403 before schema validation runs
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts
Line: 810:830
Comment:
**logic:** client-side request with `permission_ids: null` will return 403 before schema validation runs
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
docs/templates/sdk/types/team.mdx (1)
212-237: Align Team invitation docs with newpermissionIdsfieldThe
listInvitations()“Returns” text still showsPromise<{ id: string, email: string, expiresAt: Date }[]>, but the signature and examples now includepermissionIds: string[]. Also, theinviteUserparameter docs don’t mention the new optionalpermissionIds?: string[]field even though the signature and examples use it. Please update the “Returns” description and add apermissionIdsproperty to the inviteUser parameter accordion so the docs match the actual API.Also applies to: 324-339, 357-371
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (1)
919-928: ClientTeam.inviteUsercurrently ignorespermissionIdsYou’ve correctly plumbed
permissionIdsfrom CRUD into:
_clientTeamUserFromCrud→teamProfile.permissionIds_clientTeamInvitationFromCrud→permissionIds_editableTeamProfileFromCrud→permissionIdsand the
Teamtype now exposesinviteUser(options: { email, callbackUrl?: string, permissionIds?: string[] }). However, the concrete implementation returned by_clientTeamFromCrudstill has:async inviteUser(options: { email: string, callbackUrl?: string }) { await app._interface.sendTeamInvitation({ teamId: crud.id, email: options.email, session, callbackUrl: options.callbackUrl ?? constructRedirectUrl(app.urls.teamInvitation, "callbackUrl"), }); await app._teamInvitationsCache.refresh([session, crud.id]); }so any
permissionIdspassed by callers are silently dropped and never reach the backend.Please update this implementation to accept and forward
permissionIdsso the public API actually supports role-based invitations, e.g.:- async inviteUser(options: { email: string, callbackUrl?: string }) { + async inviteUser(options: { email: string, callbackUrl?: string, permissionIds?: string[] }) { await app._interface.sendTeamInvitation({ teamId: crud.id, email: options.email, - session, - callbackUrl: options.callbackUrl ?? constructRedirectUrl(app.urls.teamInvitation, "callbackUrl"), + session, + callbackUrl: options.callbackUrl ?? constructRedirectUrl(app.urls.teamInvitation, "callbackUrl"), + permissionIds: options.permissionIds, }); await app._teamInvitationsCache.refresh([session, crud.id]); },Also applies to: 930-941, 993-1010, 1192-1209
apps/backend/src/app/api/latest/team-member-profiles/crud.tsx (1)
191-216: Fix user ID used for last-active lookup inonUpdateIn
onUpdate, the call togetUserLastActiveAtMillisuses:return prismaToCrud( db, await getUserLastActiveAtMillis(auth.project.id, auth.branchId, db.projectUser.projectUserId) ?? db.projectUser.createdAt.getTime(), perms.map(p => p.permissionId), );
dbis aTeamMemberwith aprojectUserIdfield; the nesteddb.projectUseris the related record, which is very unlikely to have aprojectUserIdproperty. This will either fail type-checking or passundefinedas the user ID, breaking the last-active computation.It should mirror the other call sites that use
db.projectUserIdas the user identifier, e.g.:- return prismaToCrud(db, await getUserLastActiveAtMillis(auth.project.id, auth.branchId, db.projectUser.projectUserId) ?? db.projectUser.createdAt.getTime(), perms.map(p => p.permissionId)); + const lastActiveAt = + (await getUserLastActiveAtMillis(auth.project.id, auth.branchId, db.projectUserId)) + ?? db.projectUser.createdAt.getTime(); + return prismaToCrud(db, lastActiveAt, perms.map((p) => p.permissionId));
🧹 Nitpick comments (3)
packages/template/src/components-page/account-settings/teams/team-member-list-section.tsx (1)
38-45: Consider handling multiple role permissions.The current logic only uses the first non-system permission (
filteredPermissions[0]). If a user has multiple role-based permissions assigned, only the first one will be displayed.Consider whether this is the intended behavior or if you should:
- Display all roles (e.g., "Admin, Manager")
- Prioritize certain roles over others
- Display the "highest" permission level
packages/stack-shared/src/interface/crud/team-member-profiles.ts (1)
12-12: Consider usingpermissionDefinitionIdSchemafor consistency.The
team-invitation.tsfile usespermissionDefinitionIdSchemafor itspermission_idsfield, while this file usesyupString(). For consistency and better type safety, consider using the same schema type:- permission_ids: schemaFields.yupArray(schemaFields.yupString().defined()).defined(), + permission_ids: schemaFields.yupArray(schemaFields.permissionDefinitionIdSchema.defined()).defined(),This ensures consistent validation rules across all permission ID fields.
apps/backend/src/app/api/latest/team-member-profiles/crud.tsx (1)
15-37: Permission fetching helper is good; consider reusing it and tightening typesThe new
fetchTeamMemberPermissionshelper and its use inonListandonReadnicely avoid an N+1 pattern and keep permission aggregation in one place. InonUpdateyou reimplement a narrower query againstteamMemberDirectPermission; for consistency and maintainability you might want to reuse the helper there as well (passing a singleprojectUserId) instead of duplicating query logic.Also, the helper currently types
txasany. If possible, tightening this to the appropriate Prisma transaction client type would catch more mistakes at compile time, especially as this query evolves.Also applies to: 105-121, 160-172, 206-215
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (22)
apps/backend/src/app/api/latest/team-invitations/accept/verification-code-handler.tsx(3 hunks)apps/backend/src/app/api/latest/team-invitations/crud.tsx(1 hunks)apps/backend/src/app/api/latest/team-invitations/role-permissions/route.tsx(1 hunks)apps/backend/src/app/api/latest/team-invitations/send-code/route.tsx(4 hunks)apps/backend/src/app/api/latest/team-member-profiles/crud.tsx(4 hunks)apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts(1 hunks)apps/e2e/tests/backend/endpoints/api/v1/team-member-profiles.test.ts(2 hunks)docs/concepts/role-based-team-invitations.mdx(1 hunks)docs/templates-python/concepts/teams-management.mdx(8 hunks)docs/templates/sdk/types/team-profile.mdx(2 hunks)docs/templates/sdk/types/team.mdx(4 hunks)packages/stack-shared/src/interface/client-interface.ts(1 hunks)packages/stack-shared/src/interface/crud/team-invitation.ts(1 hunks)packages/stack-shared/src/interface/crud/team-member-profiles.ts(1 hunks)packages/stack-shared/src/interface/server-interface.ts(2 hunks)packages/template/src/components-page/account-settings/teams/team-member-invitation-section.tsx(3 hunks)packages/template/src/components-page/account-settings/teams/team-member-list-section.tsx(3 hunks)packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts(1 hunks)packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts(4 hunks)packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts(4 hunks)packages/template/src/lib/stack-app/projects/index.ts(1 hunks)packages/template/src/lib/stack-app/teams/index.ts(4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-11T04:13:19.308Z
Learnt from: N2D4
Repo: stack-auth/stack-auth PR: 943
File: examples/convex/app/action/page.tsx:23-28
Timestamp: 2025-10-11T04:13:19.308Z
Learning: In the stack-auth codebase, use `runAsynchronouslyWithAlert` from `stackframe/stack-shared/dist/utils/promises` for async button click handlers and form submissions instead of manual try/catch blocks. This utility automatically handles errors and shows alerts to users.
Applied to files:
packages/template/src/components-page/account-settings/teams/team-member-invitation-section.tsx
🧬 Code graph analysis (8)
apps/backend/src/app/api/latest/team-invitations/role-permissions/route.tsx (3)
apps/backend/src/route-handlers/smart-route-handler.tsx (1)
createSmartRouteHandler(209-294)packages/stack-shared/src/schema-fields.ts (7)
yupObject(247-251)clientOrHigherAuthTypeSchema(497-497)adaptSchema(330-330)yupNumber(191-194)yupString(187-190)yupArray(213-216)yupBoolean(195-198)apps/backend/src/lib/permissions.tsx (1)
listPermissionDefinitions(183-193)
apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts (4)
apps/e2e/tests/helpers.ts (1)
it(12-12)apps/e2e/tests/backend/backend-helpers.ts (3)
niceBackendFetch(109-173)createMailbox(59-66)backendContext(35-57)packages/template/src/lib/stack-app/projects/index.ts (1)
Project(10-15)packages/template/src/lib/stack-app/teams/index.ts (1)
Team(38-52)
apps/backend/src/app/api/latest/team-invitations/accept/verification-code-handler.tsx (4)
packages/stack-shared/src/schema-fields.ts (2)
yupArray(213-216)permissionDefinitionIdSchema(729-738)apps/backend/src/prisma-client.tsx (1)
retryTransaction(151-226)apps/backend/src/app/api/latest/team-memberships/crud.tsx (1)
teamMembershipsCrudHandlers(43-160)apps/backend/src/lib/permissions.tsx (1)
grantTeamPermission(96-138)
apps/e2e/tests/backend/endpoints/api/v1/team-member-profiles.test.ts (3)
apps/e2e/tests/backend/backend-helpers.ts (3)
niceBackendFetch(109-173)createMailbox(59-66)backendContext(35-57)packages/template/src/lib/stack-app/projects/index.ts (1)
Project(10-15)packages/template/src/lib/stack-app/teams/index.ts (1)
Team(38-52)
packages/stack-shared/src/interface/client-interface.ts (1)
packages/stack-shared/src/sessions.ts (1)
InternalSession(71-241)
apps/backend/src/app/api/latest/team-member-profiles/crud.tsx (1)
apps/backend/src/app/api/latest/users/crud.tsx (2)
getUsersLastActiveAtMillis(220-243)getUserLastActiveAtMillis(188-194)
packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts (1)
packages/template/src/utils/url.ts (1)
constructRedirectUrl(4-20)
apps/backend/src/app/api/latest/team-invitations/send-code/route.tsx (2)
packages/stack-shared/src/schema-fields.ts (2)
yupArray(213-216)permissionDefinitionIdSchema(729-738)packages/stack-shared/src/utils/errors.tsx (1)
StatusError(152-261)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: Vercel Agent Review
- GitHub Check: check_prisma_migrations (22.x)
- GitHub Check: docker
- GitHub Check: setup-tests
- GitHub Check: restart-dev-and-test
- GitHub Check: restart-dev-and-test-with-custom-base-port
- GitHub Check: build (22.x)
- GitHub Check: all-good
- GitHub Check: build (22.x)
- GitHub Check: build (22.x)
- GitHub Check: lint_and_build (latest)
🔇 Additional comments (19)
packages/stack-shared/src/interface/crud/team-invitation.ts (1)
10-10: LGTM! Schema definition is correct.The
permission_idsfield is properly defined as a required array of permission definition IDs, aligning with the role-based invitation feature.packages/template/src/lib/stack-app/projects/index.ts (1)
14-14: LGTM! Method signature is well-defined.The
listTeamPermissionDefinitions()method provides a clean API for fetching team permission definitions with appropriate typing.apps/backend/src/app/api/latest/team-invitations/crud.tsx (1)
56-56: LGTM! Proper fallback handling.The fallback to an empty array ensures that
permission_idsalways returns a defined array, maintaining consistency with the client schema definition.apps/e2e/tests/backend/endpoints/api/v1/team-member-profiles.test.ts (1)
311-372: LGTM! Comprehensive test coverage.The new tests effectively validate:
- Presence of
permission_idsfield in team member profiles- Correct propagation of granted permissions to the field
The test flow (create team → invite → accept → grant permission → verify) provides solid end-to-end validation.
packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts (1)
174-181: LGTM! Clean implementation.The method properly delegates to the interface layer and maps the response to the expected public API shape.
packages/stack-shared/src/interface/client-interface.ts (1)
727-736: LGTM!The implementation is clean and follows existing patterns in the codebase. The method correctly fetches role permissions and returns a well-typed response.
packages/stack-shared/src/interface/server-interface.ts (1)
657-679: LGTM!The optional
permissionIdsparameter is correctly added and properly propagated to the request body. This is a non-breaking change that extends the invitation functionality.packages/template/src/components-page/account-settings/teams/team-member-invitation-section.tsx (2)
32-43: Simple role mapping is appropriate for current use case.The
getRoleDisplayNamefunction uses a straightforward approach to determine role display names. It filters system permissions and maps known IDs to friendly names, which is suitable for the current invitation display context.
45-86: LGTM!The table structure changes are consistent and properly implemented. The new "Role" column is correctly added, and the
colspanis appropriately updated to match the new column count.docs/templates/sdk/types/team-profile.mdx (1)
54-66: LGTM!The documentation for
permissionIdsis clear, well-structured, and follows the established documentation patterns in the file.apps/backend/src/app/api/latest/team-invitations/accept/verification-code-handler.tsx (1)
119-130: Good deduplication of permission IDs.The code correctly deduplicates permission IDs before granting them, which is appropriate defensive programming to handle potential duplicates in the input.
docs/templates-python/concepts/teams-management.mdx (3)
220-276: LGTM!The documentation for
send_team_invitationis comprehensive and includes clear examples for different role scenarios (admin, member, and default). The optionalpermission_idsparameter is well-documented and properly handled.
298-322: LGTM!The documentation for
get_team_role_permissionsis clear and provides a useful example of how to retrieve and display available permissions.
195-211: LGTM!The documentation updates consistently integrate permission IDs throughout the teams management examples. The role determination logic is clear and the examples are helpful for understanding how to work with team permissions.
Also applies to: 343-348, 456-488
apps/backend/src/app/api/latest/team-invitations/send-code/route.tsx (1)
36-76: Good security measure to prevent client-side permission assignment.The guard at lines 41-43 correctly prevents client-side requests from setting
permission_ids, which is an important security control. Only server-side requests (with proper authentication) can assign specific permissions during invitations.apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts (1)
559-992: Strong coverage forpermission_ids& role-permissions, but verify membership and error shapesThe new tests do a good job exercising role-permissions,
permission_idsvalidation, escalation, and backwards-compat scenarios end to end. Two things to double-check:
Several tests (
"can send invitation with permission_ids","applies permission_ids when accepting invitation", and some error cases) callTeam.create()and then grant$invite_membersvia/api/v1/team-permissions/...without explicitly adding the inviter as a team member (unlike earlier tests that usecreateAndAddCurrentUserWithoutMemberPermission()). If team permissions are meant to apply only to members, consider either using that helper or confirming thatTeam.create()always enrolls the creator as a member.New negative-path tests assert
response.body.message(and match regexes against it), whereas earlier errors in this file typically exposedcode/details/errorwithx-stack-known-errorheaders. Please confirm that the updated endpoints actually return amessagefield in these cases; otherwise it may be safer to assert againsterroror a well-definedcode.packages/template/src/lib/stack-app/teams/index.ts (1)
10-14: Type surface forpermissionIdson team members & invitations looks consistentAdding
permissionIdstoTeamMemberProfile, optionalpermissionIdstoTeamInvitation, and extending bothTeam.inviteUserandServerTeam.inviteUserto acceptpermissionIds?: string[]aligns with the surrounding client/server implementations and tests. The new types are coherent and should make the permissions data available where expected.Also applies to: 30-36, 38-52, 86-96
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (1)
888-910: ConfirmgetTeamRolePermissionsinterface contract forlistTeamPermissionDefinitions
_clientProjectFromCrudwiresProject.listTeamPermissionDefinitionsto:const session = await this._getSession(); const result = await this._interface.getTeamRolePermissions({ session }); return result.items;This assumes
getTeamRolePermissionstakes a single options object containingsessionand returns an object with anitemsarray (and likelyis_paginated). Please confirm this matches the actualStackClientInterfacemethod signature and return shape; if the interface instead follows the usual(options, session)pattern, you may need to passsessionas a separate argument.packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts (1)
273-291: Server-sidepermissionIdswiring andinviteUserupdates look correctThe server app now:
- Exposes
permissionIdson editable team profiles, server team users, and team invitations.- Extends
ServerTeam.inviteUserto acceptpermissionIds?: string[]and forwards them to_interface.sendServerTeamInvitation, then refreshes the invitations cache.This keeps the server-side API in sync with the shared types and backend CRUD and should support role-based invitations as intended.
Also applies to: 772-781, 786-797, 853-861
| await retryTransaction(prisma, async (tx) => { | ||
| await teamMembershipsCrudHandlers.adminCreate({ | ||
| tenancy, | ||
| team_id: data.team_id, | ||
| user_id: user.id, | ||
| data: {}, | ||
| }); | ||
|
|
||
| // Apply additional specific permissions if provided (with deduplication) | ||
| if (data.permission_ids && data.permission_ids.length > 0) { | ||
| const uniquePermissionIds = [...new Set(data.permission_ids)]; | ||
| for (const permissionId of uniquePermissionIds) { | ||
| await grantTeamPermission(tx, { | ||
| tenancy, | ||
| teamId: data.team_id, | ||
| userId: user.id, | ||
| permissionId, | ||
| }); | ||
| } | ||
| } | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
Verify transaction safety for adminCreate.
The implementation correctly wraps membership creation and permission granting in a transaction for atomicity. However, calling adminCreate inside retryTransaction may have side effects (e.g., webhooks) that could be triggered multiple times if the transaction is retried.
Consider either:
- Verifying that
adminCreateonly performs database operations within the transaction - Using lower-level functions that don't trigger side effects, similar to how
addUserToTeamis used in team-memberships/crud.tsx
Run the following to check how adminCreate is implemented and whether it triggers webhooks:
🏁 Script executed:
#!/bin/bash
# Check the implementation of adminCreate in team memberships CRUD
ast-grep --pattern $'adminCreate: async ({ $$$, data }) => {
$$$
}'Length of output: 0
🏁 Script executed:
#!/bin/bash
# Search for adminCreate function definition more broadly
rg -nP 'adminCreate\s*:\s*async|function\s+adminCreate' --type=ts --type=tsx -A5Length of output: 27
🏁 Script executed:
#!/bin/bash
# Find team memberships CRUD file and search for adminCreate there
fd -e tsx -e ts -path '*team*membership*crud*' | head -20Length of output: 168
🏁 Script executed:
#!/bin/bash
# Search for where adminCreate is exported or defined
rg -nP 'export.*adminCreate|const\s+adminCreate|adminCreate\s*=' --type=ts --type=tsx -B2 -A10Length of output: 27
🏁 Script executed:
#!/bin/bash
# Search for adminCreate definition across the codebase
rg -n 'adminCreate.*async.*{' -A15 | head -50Length of output: 105
🏁 Script executed:
#!/bin/bash
# Find files in team-memberships directory
fd 'crud' apps/backend/srcLength of output: 1692
🏁 Script executed:
#!/bin/bash
# Search for adminCreate and webhook-related patterns
rg -n -i 'adminCreate|webhook' apps/backend/src/app/api --max-count=100Length of output: 6174
🏁 Script executed:
#!/bin/bash
# Read the team-memberships/crud.tsx file to see adminCreate implementation
cat apps/backend/src/app/api/latest/team-memberships/crud.tsxLength of output: 4992
🏁 Script executed:
#!/bin/bash
# Find the crud-handler file to understand how adminCreate is generated
cat apps/backend/src/route-handlers/crud-handler.tsx | head -200Length of output: 9264
🏁 Script executed:
#!/bin/bash
# Search for how adminCreate is used elsewhere with retryTransaction
rg -n 'retryTransaction.*adminCreate|adminCreate.*retryTransaction' apps/backend/src -B3 -A3Length of output: 0
🏁 Script executed:
#!/bin/bash
# Look for any version of adminCreate that accepts transaction parameter
rg -n 'adminCreate.*tx\|adminCreate.*transaction' apps/backend/src -B2 -A5Length of output: 0
🏁 Script executed:
#!/bin/bash
# Look at the verification-code-handler full context around the adminCreate call
cat apps/backend/src/app/api/latest/team-invitations/accept/verification-code-handler.tsxLength of output: 5151
🏁 Script executed:
#!/bin/bash
# Search for patterns where addUserToTeam is used directly (lower-level function)
rg -n 'addUserToTeam' apps/backend/src -B2 -A2Length of output: 2291
Replace adminCreate call with direct addUserToTeam to avoid nested transactions and repeated webhook triggers.
Calling adminCreate inside retryTransaction creates a nested transaction problem. The adminCreate handler internally creates its own transaction and triggers webhooks via runAsynchronouslyAndWaitUntil after completion. On outer transaction retry, these webhooks fire again, and the permission grants in the outer transaction don't coordinate with the membership created in the nested one.
Use the lower-level addUserToTeam function directly (as done in integrations/custom/projects/transfer/confirm/verification-code-handler.tsx) to keep all operations atomic within a single transaction:
await retryTransaction(prisma, async (tx) => {
await addUserToTeam(tx, {
tenancy,
teamId: data.team_id,
userId: user.id,
type: 'member',
});
if (data.permission_ids && data.permission_ids.length > 0) {
const uniquePermissionIds = [...new Set(data.permission_ids)];
for (const permissionId of uniquePermissionIds) {
await grantTeamPermission(tx, {
tenancy,
teamId: data.team_id,
userId: user.id,
permissionId,
});
}
}
});
// Send webhooks after transaction succeeds
await runAsynchronouslyAndWaitUntil(/* webhook calls */);
🤖 Prompt for AI Agents
In
apps/backend/src/app/api/latest/team-invitations/accept/verification-code-handler.tsx
around lines 111 to 131, replace the call to
teamMembershipsCrudHandlers.adminCreate inside the retryTransaction with the
lower-level addUserToTeam(tx, { tenancy, teamId: data.team_id, userId: user.id,
type: 'member' }) so all DB changes (membership + permission grants) occur
within the same transaction and avoid nested transactions/webhook duplication;
keep the permission deduplication and grantTeamPermission calls using the
transaction object, and move any webhook or async post-commit work
(runAsynchronouslyAndWaitUntil) to after the retryTransaction completes
successfully.
| export const GET = createSmartRouteHandler({ | ||
| metadata: { | ||
| summary: "Get role-based permissions for team invitations", | ||
| description: "Fetch available role-based permissions that can be assigned to team members during invitations. Only returns role-based permissions, not system permissions.", | ||
| tags: ["Teams"], | ||
| }, | ||
| request: yupObject({ | ||
| auth: yupObject({ | ||
| type: clientOrHigherAuthTypeSchema, | ||
| tenancy: adaptSchema.defined(), | ||
| user: adaptSchema.optional(), | ||
| }).defined(), | ||
| }), | ||
| response: yupObject({ | ||
| statusCode: yupNumber().oneOf([200]).defined(), | ||
| bodyType: yupString().oneOf(["json"]).defined(), | ||
| body: yupObject({ | ||
| items: yupArray(yupObject({ | ||
| id: yupString().defined(), | ||
| description: yupString().optional(), | ||
| contained_permission_ids: yupArray(yupString().defined()).defined(), | ||
| }).defined()).defined(), | ||
| is_paginated: yupBoolean().oneOf([false]).defined(), | ||
| }).defined(), | ||
| }), | ||
| async handler({ auth }) { | ||
| const allPermissions = await listPermissionDefinitions({ | ||
| scope: "team", | ||
| tenancy: auth.tenancy, | ||
| }); | ||
|
|
||
| // Return all permissions including system permissions (starting with $) | ||
| return { | ||
| statusCode: 200, | ||
| bodyType: "json", | ||
| body: { | ||
| items: allPermissions, | ||
| is_paginated: false, | ||
| }, | ||
| }; | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Clarify whether system permissions should be included.
There's an inconsistency between the metadata description and the implementation:
- Line 8 states: "Only returns role-based permissions, not system permissions"
- Line 36 comment states: "Return all permissions including system permissions"
- The code returns
allPermissionswithout filtering
Please clarify the intended behavior and update either:
- The description (line 8) if system permissions should be included, or
- The implementation (lines 36-41) to filter out system permissions (those starting with
$) if they should be excluded
🤖 Prompt for AI Agents
In apps/backend/src/app/api/latest/team-invitations/role-permissions/route.tsx
around lines 5 to 46: the metadata says this endpoint should return only
role-based permissions (no system permissions) but the handler returns
allPermissions and the inline comment says it returns system permissions—either
update the metadata to state system permissions are included, or filter out
permissions whose ids start with "$" before returning; to fix, decide desired
behavior and then either change the metadata/description/tags to reflect
inclusion, or modify the handler to filter allPermissions (remove items with id
starting with "$") and ensure the response body and response schema still match.
| - Only users with `$invite_members` permission can send team invitations | ||
| - All permissions (both role-based and system) are validated against your project configuration | ||
| - Invalid or non-existent permission IDs are rejected | ||
| - System permissions can now be directly assigned through invitations |
There was a problem hiding this comment.
Resolve contradictory statements about system permissions.
The documentation contains conflicting statements:
- Line 214 states: "System permissions can now be directly assigned through invitations"
- Line 275 states: "System permissions cannot be directly assigned through invitations"
Please clarify which statement is correct and update the documentation accordingly to avoid user confusion.
Also applies to: 275-275
🤖 Prompt for AI Agents
In docs/concepts/role-based-team-invitations.mdx around lines 214 and 275, there
are contradictory statements about whether system permissions can be assigned
via invitations; pick the correct behavior (confirm with product/engineering if
unsure) and make both lines consistent: if system permissions are allowed,
change line 275 to match line 214 and add a short clarifying sentence about
which permissions are assignable and any required roles/flags; if they are not
allowed, change line 214 to match line 275 and add a short note explaining how
system permissions must be granted instead (e.g., via admin console or API).
Ensure both instances use identical phrasing and update any nearby examples or
links to reflect the chosen behavior.
packages/template/src/components-page/account-settings/teams/team-member-list-section.tsx
Show resolved
Hide resolved
apps/backend/src/app/api/latest/team-invitations/role-permissions/route.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
apps/backend/src/app/api/latest/team-invitations/accept/verification-code-handler.tsx (1)
34-35: permission_ids acceptance and granting logic is soundThe verification-code handler correctly treats
permission_idsas optional, deduplicates them vianew Set, and applies each viagrantTeamPermission, which centralizes existence/scope validation. Note that permissions are only granted on first-time membership creation (!oldMembership), so re-accepting an invite for an already-member won’t adjust permissions—this matches the “bootstrap-on-join” semantics implied by the tests.If you ever need invitations to adjust permissions for existing members, this
if (!oldMembership)guard will need to be revisited or extended.Also applies to: 118-129
apps/e2e/tests/backend/endpoints/api/v1/team-member-profiles.test.ts (1)
318-380: New permission_ids-focused tests provide targeted coverage for profile endpointsThe tests
includes permission_ids in team member profiles responseandreturns correct permission_ids for team members with granted permissionseffectively verify that both/team-member-profiles/:team_id/meand/team-member-profiles/:team_id/:user_idsurfacepermission_idsand reflect granted permissions liketeam_admin. This nicely bridges the invite/permission flows with the profile APIs.If you start returning more metadata on these endpoints, consider factoring out small helpers to reduce duplication across these tests when asserting profile shapes.
apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts (2)
6-6: Double-check the auth type used in “requires $invite_members permission to send invitation”This test now calls
/team-invitations/send-codewithaccessType: "server", but the route’s explicit$invite_memberscheck currently only runs whenauth.type === "client", and a later test (allows server access invitation with permission_ids bypassing user permissions) deliberately relies on server access bypassing user-level permission checks. It’s worth confirming whether this first test is still exercising the intended code path or should be using client access (or be renamed to reflect a different guarantee).Also applies to: 32-32
583-668: permission_ids send/accept, validation, escalation, and compatibility tests comprehensively cover the new behaviorThe added tests around sending invitations with
permission_ids, applying them on acceptance, handling invalid/malformed/mixed IDs (400 vs 404), blocking client-side elevation attempts with 403, and maintaining backwards compatibility for empty/omittedpermission_idscollectively mirror the logic in the send-code and verification handlers. They should catch regressions both in validation and in who is allowed to attach permissions to invites.If these flows grow further, consider extracting small helper utilities (e.g., “sendInviteWithPermissions”, “expectInvalidPermissionIds404”) to reduce duplication and keep individual tests focused on behavior rather than setup.
Also applies to: 671-760, 762-841, 844-920, 923-955, 999-1027
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/backend/src/app/api/latest/team-invitations/accept/verification-code-handler.tsx(5 hunks)apps/backend/src/app/api/latest/team-invitations/send-code/route.tsx(4 hunks)apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts(7 hunks)apps/e2e/tests/backend/endpoints/api/v1/team-member-profiles.test.ts(8 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
apps/backend/src/app/api/latest/team-invitations/send-code/route.tsx (4)
packages/stack-shared/src/schema-fields.ts (2)
yupArray(213-216)permissionDefinitionIdSchema(729-738)packages/stack-shared/src/utils/errors.tsx (1)
StatusError(152-261)apps/backend/src/lib/permissions.tsx (1)
listPermissionDefinitionsFromConfig(161-181)apps/backend/src/app/api/latest/team-invitations/accept/verification-code-handler.tsx (1)
teamInvitationCodeHandler(13-155)
apps/e2e/tests/backend/endpoints/api/v1/team-member-profiles.test.ts (4)
apps/e2e/tests/helpers.ts (1)
it(12-12)apps/e2e/tests/backend/backend-helpers.ts (3)
niceBackendFetch(109-173)createMailbox(59-66)backendContext(35-57)packages/template/src/lib/stack-app/projects/index.ts (1)
Project(10-15)packages/template/src/lib/stack-app/teams/index.ts (1)
Team(38-52)
apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts (3)
packages/template/src/lib/stack-app/teams/index.ts (1)
Team(38-52)apps/e2e/tests/backend/backend-helpers.ts (3)
niceBackendFetch(109-173)createMailbox(59-66)backendContext(35-57)packages/template/src/lib/stack-app/projects/index.ts (1)
Project(10-15)
apps/backend/src/app/api/latest/team-invitations/accept/verification-code-handler.tsx (2)
packages/stack-shared/src/schema-fields.ts (2)
yupArray(213-216)permissionDefinitionIdSchema(729-738)apps/backend/src/lib/permissions.tsx (1)
grantTeamPermission(96-138)
🪛 Biome (2.1.2)
apps/backend/src/app/api/latest/team-invitations/accept/verification-code-handler.tsx
[error] 72-72: Unexpected empty object pattern.
(lint/correctness/noEmptyPattern)
[error] 138-138: Unexpected empty object pattern.
(lint/correctness/noEmptyPattern)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: Vercel Agent Review
- GitHub Check: restart-dev-and-test-with-custom-base-port
- GitHub Check: build (22.x)
- GitHub Check: all-good
- GitHub Check: build (22.x)
- GitHub Check: build (22.x)
- GitHub Check: check_prisma_migrations (22.x)
- GitHub Check: restart-dev-and-test
- GitHub Check: setup-tests
- GitHub Check: lint_and_build (latest)
- GitHub Check: docker
🔇 Additional comments (5)
apps/backend/src/app/api/latest/team-invitations/send-code/route.tsx (2)
5-6: permission_ids schema, validation, and forwarding look consistent with the new invite flowThe request schema, config-based validation, and forwarding into
teamInvitationCodeHandler.sendCodeare wired coherently:permission_idsis optional, validated against the team-scoped definitions (raisingKnownErrors.PermissionNotFoundfor unknown IDs), and always passed as an array (defaulting to[]), which matches the acceptance handler’s expectations and the new e2e tests (404 for invalid IDs, 200 for valid server-side IDs, and 200 when omitted).Also applies to: 26-27, 57-69, 75-75
40-44: Client-side restriction on permission_ids matches the updated security modelThe explicit 403 guard when
auth.type === "client"andbody.permission_idsis defined aligns with the new tests that assert client requests withpermission_idsare blocked, while client requests withoutpermission_idscontinue to work and server access can set them. No change requested; just confirming this is now the intended contract.If the long-term goal is to eventually allow some client flows to set
permission_ids, we should capture that in documentation or a follow-up issue since the current behavior is now enforced and covered by tests.apps/e2e/tests/backend/endpoints/api/v1/team-member-profiles.test.ts (1)
151-175: Expanded team-member profile snapshots and permission_ids assertions look consistentThe updated snapshots and assertions for
permission_ids(both in list responses and single-profile endpoints) align with the new backend behavior: default members show[], creator/admin users show elevated permissions, and the values remain stable across profile updates. The two additional tests that check for the presence and correctness ofpermission_idsadd good coverage without over-constraining the response shape.Also applies to: 193-223, 233-245, 260-272, 303-315, 165-165, 200-201, 207-207, 214-214, 238-238, 265-265, 308-308
apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts (2)
170-171: Invitation list snapshots with permission_ids align with the new API contractThe updated list-invitations snapshots now include
permission_ids: [], which matches the server behavior of always returning apermission_idsfield (empty array when none are set) and keeps the response shape stable for consumers and follow-up tests.Also applies to: 241-242
561-581: Role-permissions endpoint tests are well scopedThe tests around
/team-invitations/role-permissionsvalidate both structure (items, contained_permission_ids, is_paginated) and behavior for client vs server vs unauthenticated access, and assert that client/server responses are identical. This gives good confidence that the UI can rely on this endpoint for permission selection.Also applies to: 958-997
apps/backend/src/app/api/latest/team-invitations/accept/verification-code-handler.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts (2)
763-801: Client-side elevation attempt correctly expects 403This test now uses
accessType: "client"withpermission_ids: ["team_admin"]and asserts a 403 plus a clear error string thatpermission_idsmust be server-side. That matches the intended hardening where any client attempt to setpermission_idsshould be rejected up front.
803-841: Empty client-sidepermission_idsstill rejected with 403Using
permission_ids: []from a client request and expecting 403 ensures the security check also covers “default/empty” arrays, closing a subtle bypass vector. The string-based assertion on the error message is consistent with the previous test.
🧹 Nitpick comments (1)
apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts (1)
970-997: Client/server parity test for role-permissions is strong but strictVerifying that client and server responses are both 200 and structurally correct, and then asserting
clientResponse.body === serverResponse.body, gives strong guarantees about parity. It is slightly brittle if extra metadata fields diverge in the future; if that happens, you may want to relax this to a subset comparison (e.g., items only), but for now it’s fine.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts (3)
apps/e2e/tests/backend/backend-helpers.ts (3)
niceBackendFetch(109-173)createMailbox(59-66)backendContext(35-57)packages/template/src/lib/stack-app/projects/index.ts (1)
Project(10-15)packages/template/src/lib/stack-app/teams/index.ts (1)
Team(38-52)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: Vercel Agent Review
- GitHub Check: restart-dev-and-test
- GitHub Check: build (22.x)
- GitHub Check: setup-tests
- GitHub Check: build (22.x)
- GitHub Check: lint_and_build (latest)
- GitHub Check: check_prisma_migrations (22.x)
- GitHub Check: restart-dev-and-test-with-custom-base-port
- GitHub Check: all-good
- GitHub Check: docker
- GitHub Check: build (22.x)
🔇 Additional comments (12)
apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts (12)
161-178: Snapshot updates forpermission_idslook consistentIncluding
"permission_ids": []in the list-invitations snapshots aligns with the new behavior of always exposing apermission_idsfield and keeps older tests compatible with the extended schema. No issues here.Also applies to: 233-249
561-581: Role-permissions fetch test is clear and sufficiently scopedThis test exercises the
/team-invitations/role-permissionsendpoint with client access, checks pagination flags, and validates the basic item shape (id,contained_permission_idsas array). Looks correct and focused without over-specifying the schema.
583-620: Server-side invitation withpermission_idsis correctly modeledUsing
accessType: "server"for sending an invitation withpermission_ids: ["team_admin"]matches the security model, and the follow-up list check verifies both presence and exact value ofpermission_ids. The flow and assertions look sound.
622-668: End-to-end test for applyingpermission_idson accept is solidThis test covers the full lifecycle: server-side invitation with
permission_ids, accepting via mailbox, then asserting the new member has the expected permission via/team-permissions. The use ofsome(item.id === "team_admin")is robust enough for unordered results. No issues spotted.
670-700: 404 behavior for non-existentpermission_idsis well coveredThe test uses server access, sends an obviously invalid permission ID, and verifies a 404 plus
details.permission_id. This cleanly documents the error contract for unknown roles and should help prevent regressions.
702-730: Malformedpermission_idsvalidation test is appropriateUsing
permission_ids: 123with server access and asserting a 400 plus an error message mentioningpermission_idsand array-ness is a good balance between strictness and flexibility. The regex is broad enough to tolerate minor wording changes while still catching regressions in validation behavior.
732-760: Mixed valid/invalidpermission_idshandling looks correctThe test ensures that when
permission_idsincludes both a valid role and an invalid one, the API responds with 404 and surfaces the offendingpermission_id. This is a clear, deterministic contract for partial failures and is nicely captured here.
843-881: Back-compat: server-side emptypermission_idsis allowedThis test confirms that
permission_ids: []is valid when usingaccessType: "server", and that invitations get created and listed withpermission_ids: []. That gives a clear story: client cannot setpermission_ids, but server can explicitly set empty arrays for backward compatibility. Looks good.
883-920: Back-compat: omittedpermission_idsfrom client is preservedHere, a client request omits
permission_idsentirely and is expected to succeed with 200, then list invitations wherepermission_idsexists but its exact shape is not over-specified. This matches the intended behavior that only explicit client-sidepermission_idsare blocked, not legacy calls that never send the field.
923-955: Server-access bypass test accurately captures intended powerThis test demonstrates that server access can create invitations with
permission_idseven if the current user lacks invite permissions, which is consistent with the “server is trusted” model. Listing afterward and assertingpermission_ids: ["team_admin"]verifies that the bypass works as designed.
958-968: Unauthenticated access to role-permissions is explicitly specifiedBy setting
backendContext.userAuthto null and still expecting 200 and a validitemsarray, this test documents that the role-permissions endpoint is public/read-only. That’s useful both for client contracts and for catching accidental auth tightening later.
999-1027: Double-check behavior forpermission_ids: nullon client requestsThis test expects a 400 with a validation-style error for
permission_ids: nullsent from a client request. Earlier security hardening aroundpermission_idsaimed to reject any client-providedpermission_idsup front with a 403 before schema validation. If the route now explicitly allowspermission_ids: nullto reach validation (to return 400), that’s fine, but it’s worth confirming this matches the latest backend implementation and security intent so tests don’t quietly drift from the guard logic.
There was a problem hiding this comment.
This is the final PR Bugbot will review for you during this billing cycle
Your free Bugbot reviews will reset on December 10
Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
| permissionId, | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
Bug: Permissions not granted for existing team members
When an existing team member accepts an invitation with permission_ids, those permissions are not granted because the permission granting logic is inside the if (!oldMembership) block. This means invitations with specific permissions only work for new members, not for re-inviting existing members with different permissions. If re-inviting existing members is intended to update their permissions, this logic prevents that from working.
Summary by CodeRabbit
Release Notes
New Features
Documentation
Tests
✏️ Tip: You can customize this high-level summary in your review settings.
Note
Adds role-based permissions to team invitations (applied on accept), exposes
permission_idsin invitations and team member profiles, and introduces an endpoint to list available role permissions, with SDK/UI/docs updates.permission_ids; validated server-side and applied on accept (grantTeamPermission).GET /api/v1/team-invitations/role-permissionsto list team permission definitions.permission_ids; acceptance enforces limits and assigns roles.permission_ids(batch-fetched to avoid N+1).permissionIdson invite and exposepermission_idsin invitations and team profiles.listTeamPermissionDefinitions().Team,TeamInvitation,TeamProfiletypings accordingly.permissionIds.permission_ids.permission_idsflows, application on accept, validation errors, and profilepermission_ids.Written by Cursor Bugbot for commit d697810. This will update automatically on new commits. Configure here.