Environment
@btst/stack@3.0.0-rc.2
@btst/better-auth-ui@2.0.0-rc.1
- Next.js 16.2.1 / React 19.2.1
- Existing application migrated from
@btst/stack@2.12.2
Reproduction
Configure v3 auth so a signed-in non-admin may post/edit/like comments but may not delete them, and enforce the same policy at the server hook:
// client auth provider
can: ({ resource, action, identity }) => {
if (resource !== "comments:comment") return false
if (action === "delete") return identity?.role === "admin"
return identity !== null
}
// comments backend
onBeforeDelete: async (_commentId, context) => {
await assertAdminFromContext(context, "Unauthorized to delete comment")
}
- Sign in as a regular user.
- Open a
CommentThread containing a comment owned by that user (approved and pending comments both reproduce it).
- Click the rendered Delete button and accept the confirmation.
Observed in the RC2 real-world validation:
CommentThread renders Delete solely from isOwn; it does not consult <CanAccess resource="comments:comment" action="delete">.
- The request reaches
DELETE /api/data/comments/:id and correctly returns 403.
- The click handler awaits
mutateAsync without handling the rejection, producing both a page error and a server-side unhandledRejection: Error: Unauthorized to delete comment in development.
- The comment remains in place with no useful user-facing recovery.
The released source confirms the inconsistency: the moderation page wraps delete controls in CanAccess, while client/components/comment-thread.mjs gates the owner controls only with isOwn.
Expected
Permission-sensitive thread controls should use the v3 auth contract before rendering, matching the acceptance criteria from #128/#136 and the moderation page. A denied server response should also be handled through the notification/error path instead of escaping as an unhandled rejection.
Why this matters for v3
Centralized permissions are a core developer-experience promise of v3. An application currently has to keep its server policy secure and discover that the first-party thread UI advertises an action the policy explicitly denies.
Environment
@btst/stack@3.0.0-rc.2@btst/better-auth-ui@2.0.0-rc.1@btst/stack@2.12.2Reproduction
Configure v3 auth so a signed-in non-admin may post/edit/like comments but may not delete them, and enforce the same policy at the server hook:
CommentThreadcontaining a comment owned by that user (approved and pending comments both reproduce it).Observed in the RC2 real-world validation:
CommentThreadrenders Delete solely fromisOwn; it does not consult<CanAccess resource="comments:comment" action="delete">.DELETE /api/data/comments/:idand correctly returns 403.mutateAsyncwithout handling the rejection, producing both a page error and a server-sideunhandledRejection: Error: Unauthorized to delete commentin development.The released source confirms the inconsistency: the moderation page wraps delete controls in
CanAccess, whileclient/components/comment-thread.mjsgates the owner controls only withisOwn.Expected
Permission-sensitive thread controls should use the v3 auth contract before rendering, matching the acceptance criteria from #128/#136 and the moderation page. A denied server response should also be handled through the notification/error path instead of escaping as an unhandled rejection.
Why this matters for v3
Centralized permissions are a core developer-experience promise of v3. An application currently has to keep its server policy secure and discover that the first-party thread UI advertises an action the policy explicitly denies.