diff --git a/app/components/Menu.tsx b/app/components/Menu.tsx index 4dc13f7..fbef87a 100644 --- a/app/components/Menu.tsx +++ b/app/components/Menu.tsx @@ -17,6 +17,7 @@ import cn from '~/utils/cn'; interface MenuProps extends MenuTriggerProps { placement?: Placement; isDisabled?: boolean; + disabledKeys?: Key[]; children: [ React.ReactElement | React.ReactElement, React.ReactElement, @@ -26,7 +27,7 @@ interface MenuProps extends MenuTriggerProps { // TODO: onAction is called twice for some reason? // TODO: isDisabled per-prop function Menu(props: MenuProps) { - const { placement = 'bottom', isDisabled } = props; + const { placement = 'bottom', isDisabled, disabledKeys = [] } = props; const state = useMenuTriggerState(props); const ref = useRef(null); const { menuTriggerProps, menuProps } = useMenuTrigger( @@ -51,6 +52,7 @@ function Menu(props: MenuProps) { ...menuProps, autoFocus: state.focusStrategy ?? true, onClose: () => state.close(), + disabledKeys, })} )} @@ -60,6 +62,7 @@ function Menu(props: MenuProps) { interface MenuPanelProps extends AriaMenuProps { onClose?: () => void; + disabledKeys?: Key[]; } function Panel(props: MenuPanelProps) { @@ -74,7 +77,12 @@ function Panel(props: MenuPanelProps) { className="pt-1 pb-1 shadow-xs rounded-md min-w-[200px] focus:outline-none" > {[...state.collection].map((item) => ( - + ))} ); @@ -83,9 +91,10 @@ function Panel(props: MenuPanelProps) { interface MenuSectionProps { section: Node; state: TreeState; + disabledKeys?: Key[]; } -function MenuSection({ section, state }: MenuSectionProps) { +function MenuSection({ section, state, disabledKeys }: MenuSectionProps) { const { itemProps, groupProps } = useMenuSection({ heading: section.rendered, 'aria-label': section['aria-label'], @@ -109,7 +118,12 @@ function MenuSection({ section, state }: MenuSectionProps) {
    • {[...section.childNodes].map((item) => ( - + ))}
  • @@ -120,14 +134,14 @@ function MenuSection({ section, state }: MenuSectionProps) { interface MenuItemProps { item: Node; state: TreeState; + isDisabled?: boolean; } -function MenuItem({ item, state }: MenuItemProps) { +function MenuItem({ item, state, isDisabled }: MenuItemProps) { const ref = useRef(null); const { menuItemProps } = useMenuItem({ key: item.key }, state, ref); const isFocused = state.selectionManager.focusedKey === item.key; - const isDisabled = state.selectionManager.isDisabled(item.key); return (
  • )} - + ( + 'v1/user', + apiKey, + ); + + const user = users.find((user) => user.id === userId); + if (!user) { + throw data400(`No user found with id: ${userId}`); + } + + if (user.provider === 'oidc') { + // OIDC users cannot be renamed via this endpoint, return an error + throw data403('Users managed by OIDC cannot be renamed'); + } + await context.client.post(`v1/user/${userId}/rename/${newName}`, apiKey); } @@ -86,12 +102,11 @@ async function reassignUser( formData: FormData, apiKey: string, context: LoadContext, - session: Session, ) { const userId = formData.get('user_id')?.toString(); const newRole = formData.get('new_role')?.toString(); if (!userId || !newRole) { - return data({ success: false }, 400); + throw data400('Missing `user_id` or `new_role` in the form data.'); } const { users } = await context.client.get<{ users: User[] }>( @@ -101,14 +116,16 @@ async function reassignUser( const user = users.find((user) => user.id === userId); if (!user?.providerId) { - return data({ success: false }, 400); + throw data400('Specified user is not an OIDC user'); } // For some reason, headscale makes providerID a url where the // last component is the subject, so we need to strip that out const subject = user.providerId?.split('/').pop(); if (!subject) { - return data({ success: false }, 400); + throw data400( + 'Malformed `providerId` for the specified user. Cannot find subject.', + ); } const result = await context.sessions.reassignSubject(