feat: Admin/Viewer 역할 분리 (설정 vs 로그 탐색)#1
Conversation
HPCNT fork: first registrant and ADMIN_EMAILS are admin; invites default to viewer. Mutations on sources/connections/team/alerts/webhooks require admin; Team Settings UI is hidden for viewers. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 4 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit a4a1c5f. Configure here.
| void router.replace('/search'); | ||
| } | ||
| }, [hasAdminAccess, router]); | ||
|
|
There was a problem hiding this comment.
Team page shows settings to viewers
High Severity
After switching hasAdminAccess to useIsAdmin(), only the team-name edit control is gated. Tabs still render all Team Settings sections (sources, connections, members, etc.) whenever team is loaded, while the redirect runs in useEffect. Viewers who open /team can see admin UI and trigger those fetches before navigation finishes.
Reviewed by Cursor Bugbot for commit a4a1c5f. Configure here.
| return true; | ||
| } | ||
| // Missing role (legacy) → admin so we don't lock out existing users. | ||
| return (me?.role ?? 'admin') === 'admin'; |
There was a problem hiding this comment.
Admin UI shown while loading
Medium Severity
useIsAdmin() treats a missing /me payload the same as legacy users without a role: (me?.role ?? 'admin') === 'admin'. While useMe() is still loading, me is undefined, so viewers briefly count as admin and see Team Settings in nav and Spotlight until /me returns role: 'viewer'.
Reviewed by Cursor Bugbot for commit a4a1c5f. Configure here.
| return next(); | ||
| } | ||
| return requireAdmin(req, res, next); | ||
| } |
There was a problem hiding this comment.
Viewers read team secrets via GET
High Severity
Mounting requireAdminForMutations on /team lets viewers pass all GET requests. GET /team returns the team apiKey, and GET /team/invitations returns invite URLs built from secret tokens—data meant for admins, not log-only viewers.
Reviewed by Cursor Bugbot for commit a4a1c5f. Configure here.
| isUserAuthenticated, | ||
| requireAdminForMutations, | ||
| sourcesRouter, | ||
| ); |
There was a problem hiding this comment.
External API skips role checks
High Severity
Session routes for /sources, /connections, /team, /alerts, and /webhooks now use requireAdminForMutations, but /api/v2 still authenticates with access keys only. Viewers receive an accessKey from /me and can mutate those resources through v2 while the UI returns 403 on the internal routes.
Reviewed by Cursor Bugbot for commit a4a1c5f. Configure here.


Summary
role: admin | viewer추가. 팀 최초 가입자·ADMIN_EMAILS는 admin, invite 기본은 viewer./team은 viewer에게 숨김/리다이렉트.Test plan
ADMIN_EMAILS없이 첫 register → admin,/me.role=adminADMIN_EMAILS에 넣은 이메일 invite → admin상세:
FORK.mdMade with Cursor