Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

service scoping on the client #9185

Merged
merged 4 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import { useTranslation } from 'react-i18next'

import ConfirmDialog from '@etherealengine/client-core/src/common/components/ConfirmDialog'
import multiLogger from '@etherealengine/engine/src/common/functions/logger'
import { getMutableState, useHookstate } from '@etherealengine/hyperflux'
import Box from '@etherealengine/ui/src/primitives/mui/Box'
import Icon from '@etherealengine/ui/src/primitives/mui/Icon'
import IconButton from '@etherealengine/ui/src/primitives/mui/IconButton'
Expand All @@ -38,8 +37,7 @@ import { useFind } from '@etherealengine/engine/src/common/functions/FeathersHoo
import { ProjectType, projectPath } from '@etherealengine/engine/src/schemas/projects/project.schema'
import { NotificationService } from '../../../common/services/NotificationService'
import { ProjectService } from '../../../common/services/ProjectService'
import { AuthState } from '../../../user/services/AuthService'
import { userIsAdmin } from '../../../user/userHasAccess'
import { useUserHasAccessHook } from '../../../user/userHasAccess'
import TableComponent from '../../common/Table'
import { projectsColumns } from '../../common/variables/projects'
import styles from '../../styles/admin.module.scss'
Expand Down Expand Up @@ -89,9 +87,6 @@ const ProjectTable = ({ className }: Props) => {

const projectsData = projects.data as ProjectType[]

const authState = useHookstate(getMutableState(AuthState))
const user = authState.user

const projectRef = useRef(project)

const setProject = (project: ProjectType | undefined) => {
Expand Down Expand Up @@ -222,7 +217,7 @@ const ProjectTable = ({ className }: Props) => {
})
}

const isAdmin = user.scopes?.value?.find((scope) => scope.type === 'admin:admin')
const hasProjectWritePermission = useUserHasAccessHook('projects:write')

const createData = (el: ProjectType, name: string) => {
const commitSHA = el.commitSHA
Expand Down Expand Up @@ -270,7 +265,7 @@ const ProjectTable = ({ className }: Props) => {
),
update: (
<>
{isAdmin && name !== 'default-project' && (
{hasProjectWritePermission && name !== 'default-project' && (
<IconButton
className={styles.iconButton}
name="update"
Expand All @@ -279,7 +274,7 @@ const ProjectTable = ({ className }: Props) => {
icon={<Icon type="Refresh" />}
/>
)}
{isAdmin && name === 'default-project' && (
{hasProjectWritePermission && name === 'default-project' && (
<Tooltip title={t('admin:components.project.defaultProjectUpdateTooltip')} arrow>
<IconButton className={styles.iconButton} name="update" disabled={true} icon={<Icon type="Refresh" />} />
</Tooltip>
Expand All @@ -288,11 +283,11 @@ const ProjectTable = ({ className }: Props) => {
),
push: (
<>
{isAdmin && (
{hasProjectWritePermission && (
<IconButton
className={styles.iconButton}
name="update"
disabled={(!el.hasWriteAccess && !userIsAdmin()) || !el.repositoryPath}
disabled={!el.hasWriteAccess || !el.repositoryPath}
onClick={() => openPushConfirmation(el)}
icon={<Icon type="Upload" />}
/>
Expand All @@ -301,18 +296,20 @@ const ProjectTable = ({ className }: Props) => {
),
link: (
<>
<IconButton
className={styles.iconButton}
name="update"
disabled={name === 'default-project'}
onClick={() => handleOpenProjectDrawer(el, true)}
icon={<Icon type={!el.repositoryPath ? 'LinkOff' : 'Link'} />}
/>
{hasProjectWritePermission && (
<IconButton
className={styles.iconButton}
name="update"
disabled={name === 'default-project'}
onClick={() => handleOpenProjectDrawer(el, true)}
icon={<Icon type={!el.repositoryPath ? 'LinkOff' : 'Link'} />}
/>
)}
</>
),
projectPermissions: (
<>
{isAdmin && (
{hasProjectWritePermission && (
<IconButton
className={styles.iconButton}
name="editProjectPermissions"
Expand All @@ -324,7 +321,7 @@ const ProjectTable = ({ className }: Props) => {
),
invalidate: (
<>
{isAdmin && (
{hasProjectWritePermission && (
<IconButton
className={styles.iconButton}
name="invalidate"
Expand All @@ -336,7 +333,7 @@ const ProjectTable = ({ className }: Props) => {
),
view: (
<>
{isAdmin && (
{hasProjectWritePermission && (
<IconButton
className={styles.iconButton}
name="view"
Expand All @@ -348,7 +345,7 @@ const ProjectTable = ({ className }: Props) => {
),
action: (
<>
{isAdmin && (
{hasProjectWritePermission && (
<IconButton
className={styles.iconButton}
name="remove"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { ProjectType } from '@etherealengine/engine/src/schemas/projects/project
import { NotificationService } from '../../../common/services/NotificationService'
import { ProjectService } from '../../../common/services/ProjectService'
import { AuthState } from '../../../user/services/AuthService'
import { userHasAccess } from '../../../user/userHasAccess'
import DrawerView from '../../common/DrawerView'
import styles from '../../styles/admin.module.scss'

Expand All @@ -61,7 +62,7 @@ const UserPermissionDrawer = ({ open, project, onClose }: Props) => {
const selfUser = useHookstate(getMutableState(AuthState)).user
const selfUserPermission =
project?.projectPermissions?.find((permission) => permission.userId === selfUser.id.value)?.type === 'owner' ||
selfUser.scopes?.value?.find((scope) => scope.type === 'admin:admin')
userHasAccess('admin:admin')
? 'owner'
: 'user'

Expand Down
36 changes: 20 additions & 16 deletions packages/client-core/src/admin/components/Setting/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import ListItemAvatar from '@etherealengine/ui/src/primitives/mui/ListItemAvatar
import ListItemText from '@etherealengine/ui/src/primitives/mui/ListItemText'
import Typography from '@etherealengine/ui/src/primitives/mui/Typography'

import { userHasAccess } from '../../../user/userHasAccess'
import styles from '../../styles/settings.module.scss'
import Authentication from './Authentication'
import Aws from './Aws'
Expand Down Expand Up @@ -78,7 +79,8 @@ const settingItems = [
name: 'client',
title: 'Client',
icon: <Icon type="ViewCompact" sx={{ color: 'orange' }} />,
content: <Client />
content: <Client />,
scope: 'settings_client:read'
},
{
name: 'clientTheme',
Expand Down Expand Up @@ -144,21 +146,23 @@ interface SidebarProps {
const Sidebar = ({ selected, onChange }: SidebarProps) => {
return (
<List>
{settingItems.map((item) => (
<Fragment key={item.name}>
<ListItem
button
onClick={() => onChange(item.name)}
className={selected === item.name ? `${styles.focused}` : `${styles.notFocused}`}
>
<ListItemAvatar>
<Avatar style={{ background: '#5e5a4d' }}>{item.icon}</Avatar>
</ListItemAvatar>
<ListItemText primary={item.title} />
</ListItem>
<Divider variant="inset" component="li" sx={{ marginLeft: '0px' }} />
</Fragment>
))}
{settingItems
.filter((item) => (item.scope ? userHasAccess(item.scope) : true))
.map((item) => (
<Fragment key={item.name}>
<ListItem
button
onClick={() => onChange(item.name)}
className={selected === item.name ? `${styles.focused}` : `${styles.notFocused}`}
>
<ListItemAvatar>
<Avatar style={{ background: '#5e5a4d' }}>{item.icon}</Avatar>
</ListItemAvatar>
<ListItemText primary={item.title} />
</ListItem>
<Divider variant="inset" component="li" sx={{ marginLeft: '0px' }} />
</Fragment>
))}
</List>
)
}
Expand Down
2 changes: 0 additions & 2 deletions packages/client-core/src/components/MediaIconsBox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,11 @@ import { useTranslation } from 'react-i18next'
import { VrIcon } from '../../common/components/Icons/VrIcon'
import { RecordingUIState } from '../../systems/ui/RecordingsWidgetUI'
import { MediaStreamService, MediaStreamState } from '../../transports/MediaStreams'
import { useUserHasAccessHook } from '../../user/userHasAccess'
import { useShelfStyles } from '../Shelves/useShelfStyles'
import styles from './index.module.scss'

export const MediaIconsBox = () => {
const { t } = useTranslation()
const recordScopes = useUserHasAccessHook('record')
const playbackState = useHookstate(getMutableState(PlaybackState))
const recordingState = useHookstate(getMutableState(RecordingState))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { initialAuthState, initialOAuthConnectedState } from '../../../../common
import { NotificationService } from '../../../../common/services/NotificationService'
import { useUserAvatarThumbnail } from '../../../functions/useUserAvatarThumbnail'
import { AuthService, AuthState } from '../../../services/AuthService'
import { useUserHasAccessHook } from '../../../userHasAccess'
import { UserMenus } from '../../../UserUISystem'
import styles from '../index.module.scss'
import { PopupMenuServices } from '../PopupMenuService'
Expand Down Expand Up @@ -90,8 +91,7 @@ const ProfileMenu = ({ hideLogin, onClose, isPopover }: Props): JSX.Element => {
const apiKey = selfUser.apiKey?.token?.value
const isGuest = selfUser.isGuest.value

const hasAdminAccess =
selfUser?.id?.value?.length > 0 && selfUser?.scopes?.value?.find((scope) => scope.type === 'admin:admin')
const hasAdminAccess = useUserHasAccessHook('admin:admin')
const avatarThumbnail = useUserAvatarThumbnail(userId)

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,7 @@ const SettingMenu = ({ isPopover }: Props): JSX.Element => {
const [clientSetting] = clientSettingState?.client?.value || []
const userSettings = selfUser.userSetting.value

const hasAdminAccess =
selfUser?.id?.value?.length > 0 && selfUser?.scopes?.value?.find((scope) => scope.type === 'admin:admin')
const hasAdminAccess = userHasAccess('admin:admin')
const hasEditorAccess = userHasAccess('editor:write')
const themeSettings = { ...defaultThemeSettings, ...clientSetting.themeSettings }
const themeModes = {
Expand Down
12 changes: 2 additions & 10 deletions packages/client-core/src/user/userHasAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,11 @@ import { AuthState } from './services/AuthService'
export const useUserHasAccessHook = (scope: string) => {
const authState = useHookstate(getMutableState(AuthState))
const hasScope = authState.value.user?.scopes?.find((r) => r.type === scope)
const isAdmin = authState.value.user?.scopes?.find((r) => r.type === 'admin:admin')
return Boolean(hasScope || isAdmin)
return Boolean(hasScope)
}

export const userHasAccess = (scope: string) => {
const authState = getState(AuthState)
const hasScope = authState.user?.scopes?.find((r) => r.type === scope)
const isAdmin = authState.user?.scopes?.find((r) => r.type === 'admin:admin')
return Boolean(hasScope || isAdmin)
}

export const userIsAdmin = () => {
const authState = getState(AuthState)
const isAdmin = authState.user?.scopes?.find((r) => r.type === 'admin:admin')
return Boolean(isAdmin)
return Boolean(hasScope)
}
16 changes: 8 additions & 8 deletions packages/editor/src/components/projects/ProjectsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ import {
Paper
} from '@mui/material'

import { userIsAdmin } from '@etherealengine/client-core/src/user/userHasAccess'
import { userHasAccess } from '@etherealengine/client-core/src/user/userHasAccess'
import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine'
import { projectPath, ProjectType } from '@etherealengine/engine/src/schemas/projects/project.schema'
import { getProjects } from '../../functions/projectFunctions'
Expand Down Expand Up @@ -182,8 +182,8 @@ const ProjectsPage = () => {
const projectDrawerOpen = useHookstate(false)
const changeDestination = useHookstate(false)

const isAdmin = userIsAdmin()
const hasProjectWriteAccess = activeProject.value?.hasWriteAccess || isAdmin
const hasWriteAccess =
activeProject.value?.hasWriteAccess || (userHasAccess('admin:admin') && userHasAccess('projects:write'))

const authState = useHookstate(getMutableState(AuthState))
const projectState = useHookstate(getMutableState(ProjectState))
Expand Down Expand Up @@ -580,7 +580,7 @@ const ProjectsPage = () => {
{activeProject.value &&
isInstalled(activeProject.value) &&
hasRepo(activeProject.value) &&
hasProjectWriteAccess && (
hasWriteAccess && (
<MenuItem classes={{ root: styles.filterMenuItem }} onClick={() => handleOpenProjectDrawer(false)}>
<Download />
{t(`editor.projects.updateFromGithub`)}
Expand All @@ -589,7 +589,7 @@ const ProjectsPage = () => {
{activeProject.value &&
isInstalled(activeProject.value) &&
!hasRepo(activeProject.value) &&
hasProjectWriteAccess && (
hasWriteAccess && (
<MenuItem classes={{ root: styles.filterMenuItem }} onClick={() => handleOpenProjectDrawer(true)}>
<Link />
{t(`editor.projects.link`)}
Expand All @@ -598,13 +598,13 @@ const ProjectsPage = () => {
{activeProject.value &&
isInstalled(activeProject.value) &&
hasRepo(activeProject.value) &&
hasProjectWriteAccess && (
hasWriteAccess && (
<MenuItem classes={{ root: styles.filterMenuItem }} onClick={() => handleOpenProjectDrawer(true)}>
<LinkOff />
{t(`editor.projects.unlink`)}
</MenuItem>
)}
{hasProjectWriteAccess && hasRepo(activeProject.value) && (
{hasWriteAccess && hasRepo(activeProject.value) && (
<MenuItem
classes={{ root: styles.filterMenuItem }}
onClick={() => activeProject?.value?.id && pushProject(activeProject.value.id)}
Expand All @@ -613,7 +613,7 @@ const ProjectsPage = () => {
{t(`editor.projects.pushToGithub`)}
</MenuItem>
)}
{isInstalled(activeProject.value) && hasProjectWriteAccess && (
{isInstalled(activeProject.value) && hasWriteAccess && (
<MenuItem classes={{ root: styles.filterMenuItem }} onClick={openDeleteConfirm}>
{updatingProject.value ? <CircularProgress size={15} className={styles.progressbar} /> : <Delete />}
{t(`editor.projects.uninstall`)}
Expand Down