feat: Role based Cadence-web#1108
Conversation
319273d to
42b3e1c
Compare
165520e to
b1131fc
Compare
84cb2e7 to
9bd8868
Compare
|
@Assem-Hafez @demirkayaender, when you available, please review my initial version of the feature to ship with this PR. |
|
@Assem-Uber, yes it is 😃 |
|
Hello, is it possible to get a review on this PR? Thanks! |
There was a problem hiding this comment.
Pull request overview
This PR adds comprehensive Role-Based Access Control (RBAC) support to the Cadence Web UI. The implementation aligns with Cadence backend JWT authentication by extracting tokens from cookies, forwarding them via gRPC metadata, and using JWT claims (Admin flag, groups) to control UI visibility and enable/disable actions.
Changes:
- Authentication infrastructure with JWT cookie handling (
cadence-authorization), token validation, and expiration tracking - Domain access control filtering domains by READ_GROUPS/WRITE_GROUPS metadata and disabling workflow actions based on write permissions
- Login/logout UI in the navigation bar with automatic session expiration handling and token refresh support
Reviewed changes
Copilot reviewed 33 out of 33 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| src/utils/auth/auth-context.ts | Server-side auth context resolution with JWT decoding and cookie reading |
| src/utils/auth/auth-shared.ts | Shared auth types and domain access logic based on group membership |
| src/utils/auth/tests/auth-context.test.ts | Comprehensive tests for auth utilities |
| src/app/api/auth/token/route.ts | POST/DELETE endpoints for setting/clearing auth cookie |
| src/app/api/auth/me/route.ts | GET endpoint exposing public auth context |
| src/utils/route-handlers-middleware/middlewares/user-info.ts | Middleware to populate gRPC metadata from auth token |
| src/utils/route-handlers-middleware/config/route-handlers-default-middlewares.config.ts | Reordered middlewares so userInfo runs before grpcClusterMethods |
| src/hooks/use-user-info/use-user-info.ts | Client hook for fetching current auth state |
| src/hooks/use-domain-access/use-domain-access.ts | Client hook combining user info and domain metadata to determine access |
| src/views/domains-page/helpers/get-all-domains.ts | Filters domains list by canRead permission |
| src/views/domains-page/domains-page.tsx | Passes auth context to domain loading |
| src/views/redirect-domain/redirect-domain.tsx | Uses auth context for domain access checks |
| src/views/workflow-actions/workflow-actions.tsx | Checks write permission and disables actions accordingly |
| src/views/workflow-actions/workflow-actions-menu/workflow-actions-menu.tsx | Shows "Not authorized" for actions when write access is false |
| src/views/domain-page/domain-page-start-workflow-button/domain-page-start-workflow-button.tsx | Disables start workflow button when write access is denied |
| src/views/domain-workflows/domain-workflows.tsx | Conditionally fetches cluster info based on auth state |
| src/components/app-nav-bar/app-nav-bar.tsx | Adds login/logout UI, token modal, and automatic expiration handling |
| src/components/auth-token-modal/auth-token-modal.tsx | Modal for pasting JWT tokens |
| src/components/snackbar-provider/snackbar-provider.tsx | Changed duration from infinite to medium |
| README.md | Documents RBAC configuration and JWT cookie mechanism |
| src/config/dynamic/dynamic.config.ts | Adds CADENCE_WEB_RBAC_ENABLED config |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export default function AuthTokenModal({ isOpen, onClose, onSubmit }: Props) { | ||
| const [token, setToken] = useState(''); | ||
| const [isSubmitting, setIsSubmitting] = useState(false); | ||
| const [error, setError] = useState<string | null>(null); | ||
|
|
||
| const handleSubmit = async () => { | ||
| if (!token.trim()) { | ||
| setError('Please paste a JWT token first'); | ||
| return; | ||
| } | ||
|
|
||
| setIsSubmitting(true); | ||
| setError(null); | ||
| try { | ||
| await onSubmit(token.trim()); | ||
| setToken(''); | ||
| } catch (e) { | ||
| setError( | ||
| e instanceof Error ? e.message : 'Failed to save authentication token' | ||
| ); | ||
| } finally { | ||
| setIsSubmitting(false); | ||
| } | ||
| }; |
There was a problem hiding this comment.
The modal's internal state (token, error) is not reset when the modal is closed or when it's reopened. If a user encounters an error, closes the modal, and reopens it, the previous error message will still be displayed. Consider adding a useEffect to reset the state when isOpen changes to false, or call an onClose callback that resets the state.
There was a problem hiding this comment.
added reset effect to prevent stale state on reopen.
|
@ribaraka Sorry for the delay in getting back to you! I had some time off that kept me from looking into this sooner. I’ve shared some feedback on the approach above, but please note that this feature will require a design document and discussion for broader review. Let me know today if you would like to continue that process; otherwise, I can pick it up and kick off the design documentation." The design document should cover the following:
|
|
Thank you for your review, @Assem-Uber! |
|
@Assem-Uber, I have added the design doc, please reveiew |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 40 out of 40 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
src/views/domains-page/helpers/get-all-domains.ts:69
- The
failedClusterslogic is incorrect. At line 68, it tries to find ANY rejected result in theresultsarray, not the specific rejection for the current cluster. This means all clusters infailedClusterswill have the samerejection(the first one found byfind()), rather than their own specific rejection. The logic should match each cluster config with its corresponding result by index or cluster name. For example:rejection: results[CLUSTERS_CONFIGS.indexOf(config)](if results are in the same order) or find by matching some cluster identifier.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Drop the useMemo for a trivial boolean derivation. - Remove redundant Boolean(authInfo) - Simplified the isClusterAdvancedVisibilityEnabled block - Add the lifecicle hook for auth (to unload the app-nav-bar) - Domain group parsing is now strict to plain delimited strings (no JSON). - Added a return type to getDomainAccessForUser
- Added isAuthenticated to BaseAuthContext, made PublicAuthContext an alias, renamed UserAuthContext to PrivatAuthContext for clearer intent. - Simplified getPublicAuthContext to a rest-spread (token strip). - Derived CadenceJwtClaims from Zod schema - Removed redundant optional chaining, typeof guards. - error on providing invalid/expired token
- Added tests for authentication route handlers. - admins skip the domain description fetch - stable key based on authentication state only
- removing the domain hook - removed the explicit unauthenticated fallback - aligning auth-token route - fix centralize workflow action auth in dynamic config
…r non-Node runtimes The DOMAIN_ACCESS resolver imports grpc-client which depends on Node-only modules (fs, path). Static imports in instrumentation.ts caused webpack to trace the entire gRPC dependency tree at compile time, failing for non-Node compilation targets.
Signed-off-by: Stanislav Bychkov <stanislb@netapp.com>
Signed-off-by: Stanislav Bychkov <stanislav.bychkov@netapp.com>
Signed-off-by: Stanislav Bychkov <stanislav.bychkov@netapp.com>
Signed-off-by: Stanislav Bychkov <stanislav.bychkov@netapp.com>
Signed-off-by: Stanislav Bychkov <stanislav.bychkov@netapp.com>
Signed-off-by: Stanislav Bychkov <stanislav.bychkov@netapp.com>
Signed-off-by: Stanislav Bychkov <stanislav.bychkov@netapp.com>
Signed-off-by: Stanislav Bychkov <stanislav.bychkov@netapp.com>
Signed-off-by: Stanislav Bychkov <stanislav.bychkov@netapp.com>
| return <DomainWorkflowsAdvancedGate {...props} />; | ||
| } | ||
|
|
||
| function DomainWorkflowsAdvancedGate(props: DomainPageTabContentProps) { |
There was a problem hiding this comment.
TODO: move this to separate file, we follow single component per file pattern.
| } from 'baseui/modal'; | ||
| import { Textarea } from 'baseui/textarea'; | ||
|
|
||
| type Props = { |
There was a problem hiding this comment.
TODO: move this type in a types file
| export const LOGIN_ITEM = 'login'; | ||
| export const LOGOUT_ITEM = 'logout'; | ||
|
|
||
| export const ERROR_SNACKBAR_OVERRIDES = { |
There was a problem hiding this comment.
TODO: overrides should be in styles file
| } = useAuthLifecycle(); | ||
|
|
||
| const logoutInFlightRef = useRef(false); | ||
| const logoutReasonRef = useRef<'manual' | 'expired' | null>(null); |
There was a problem hiding this comment.
nit: logout trigger is better naming
|
|
||
| import useAuthLifecycle from '../use-auth-lifecycle'; | ||
|
|
||
| type AuthResponse = { |
There was a problem hiding this comment.
TODO: this type should be replaced and imported from the endpoint types
| userName?: string; | ||
| }; | ||
|
|
||
| const AUTH_ENABLED: AuthResponse = { |
There was a problem hiding this comment.
TODO: move this to fixtures and reuse it in different tests in other files
|
I am going to handle the TODOs and nits in another PR |
Code Review
|
| Auto-apply | Compact |
|
|
Was this helpful? React with 👍 / 👎 | Gitar
|
Guitar review comments are tracked in #1234 |














Motivation: cadence-workflow/cadence#6706
Plan&Findings: cadence-workflow/cadence#7508
This PR adds JWT-based auth plumbing to Cadence Web and introduces auth-aware workflow action gating.
cadence-authorizationcookie.CADENCE_WEB_AUTH_STRATEGYvalidates auth mode,DOMAIN_ACCESSdefines how domain access is determined,