Skip to content

Commit 9d8a321

Browse files
authored
(feat): Allow only some roles to configure Mux (#401)
1 parent 4d350c5 commit 9d8a321

File tree

8 files changed

+71
-20
lines changed

8 files changed

+71
-20
lines changed

src/_exports/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const defaultConfig: PluginConfig = {
1313
normalize_audio: false,
1414
defaultSigned: false,
1515
tool: DEFAULT_TOOL_CONFIG,
16+
allowedRolesForConfiguration: [],
1617
}
1718

1819
export const muxInput = definePlugin<Partial<PluginConfig> | void>((userConfig) => {

src/components/Input.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import ErrorBoundaryCard from './ErrorBoundaryCard'
1212
import {InputFallback} from './Input.styled'
1313
import Onboard from './Onboard'
1414
import Uploader from './Uploader'
15+
import {useAccessControl} from '../hooks/useAccessControl'
1516

1617
export interface InputProps extends MuxInputProps {
1718
config: PluginConfig
@@ -22,6 +23,7 @@ const Input = (props: InputProps) => {
2223
const assetDocumentValues = useAssetDocumentValues(props.value?.asset)
2324
const poll = useMuxPolling(props.readOnly ? undefined : assetDocumentValues?.value || undefined)
2425
const [dialogState, setDialogState] = useDialogState()
26+
const {hasConfigAccess} = useAccessControl(props.config)
2527

2628
const error = secretDocumentValues.error || assetDocumentValues.error || poll.error /*||
2729
// @TODO move errored logic to Uploader, where handleRemoveVideo can be called
@@ -44,7 +46,7 @@ const Input = (props: InputProps) => {
4446
) : (
4547
<>
4648
{secretDocumentValues.value.needsSetup && !assetDocumentValues.value ? (
47-
<Onboard setDialogState={setDialogState} />
49+
<Onboard setDialogState={setDialogState} config={props.config} />
4850
) : (
4951
<Uploader
5052
{...props}
@@ -59,7 +61,7 @@ const Input = (props: InputProps) => {
5961
/>
6062
)}
6163

62-
{dialogState === 'secrets' && (
64+
{dialogState === 'secrets' && hasConfigAccess && (
6365
<ConfigureApi
6466
setDialogState={setDialogState}
6567
secrets={secretDocumentValues.value.secrets}

src/components/Onboard.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import {PlugIcon} from '@sanity/icons'
2-
import {Button, Card, Flex, Grid, Heading, Inline} from '@sanity/ui'
2+
import {Button, Card, Flex, Grid, Heading, Inline, Text} from '@sanity/ui'
33
import {useCallback} from 'react'
44

55
import type {SetDialogState} from '../hooks/useDialogState'
66
import MuxLogo from './MuxLogo'
7+
import {PluginConfig} from '../util/types'
8+
import {useAccessControl} from '../hooks/useAccessControl'
79

810
interface OnboardProps {
911
setDialogState: SetDialogState
12+
config: PluginConfig
1013
}
1114

1215
export default function Onboard(props: OnboardProps) {
1316
const {setDialogState} = props
1417
const handleOpen = useCallback(() => setDialogState('secrets'), [setDialogState])
18+
const {hasConfigAccess} = useAccessControl(props.config)
1519

1620
return (
1721
<>
@@ -41,7 +45,16 @@ export default function Onboard(props: OnboardProps) {
4145
</Heading>
4246
</Inline>
4347
<Inline paddingY={1}>
44-
<Button mode="ghost" icon={PlugIcon} text="Configure API" onClick={handleOpen} />
48+
{hasConfigAccess ? (
49+
<Button mode="ghost" icon={PlugIcon} text="Configure API" onClick={handleOpen} />
50+
) : (
51+
<Card padding={[3, 3, 3]} radius={2} shadow={1} tone="critical">
52+
<Text>
53+
You do not have access to configure the Mux API. Please contact your
54+
administrator.
55+
</Text>
56+
</Card>
57+
)}
4558
</Inline>
4659
</Grid>
4760
</Flex>

src/components/PlayerActionsMenu.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ import {styled} from 'styled-components'
2727

2828
import {type DialogState, type SetDialogState} from '../hooks/useDialogState'
2929
import {getPlaybackPolicy} from '../util/getPlaybackPolicy'
30-
import type {MuxInputProps, VideoAssetDocument} from '../util/types'
30+
import type {MuxInputProps, PluginConfig, VideoAssetDocument} from '../util/types'
3131
import {FileInputMenuItem} from './FileInputMenuItem'
32+
import {useAccessControl} from '../hooks/useAccessControl'
3233

3334
const LockCard = styled(Card)`
3435
position: absolute;
@@ -55,12 +56,14 @@ function PlayerActionsMenu(
5556
onSelect: (files: File[]) => void
5657
dialogState: DialogState
5758
setDialogState: SetDialogState
59+
config: PluginConfig
5860
}
5961
) {
6062
const {asset, readOnly, dialogState, setDialogState, onChange, onSelect} = props
6163
const [open, setOpen] = useState(false)
6264
const [menuElement, setMenuRef] = useState<HTMLDivElement | null>(null)
6365
const isSigned = useMemo(() => getPlaybackPolicy(asset) === 'signed', [asset])
66+
const {hasConfigAccess} = useAccessControl(props.config)
6467

6568
const onReset = useCallback(() => onChange(PatchEvent.from(unset([]))), [onChange])
6669

@@ -125,12 +128,16 @@ function PlayerActionsMenu(
125128
/>
126129
)}
127130
<MenuDivider />
128-
<MenuItem
129-
icon={PlugIcon}
130-
text="Configure API"
131-
onClick={() => setDialogState('secrets')}
132-
/>
133-
<MenuDivider />
131+
{hasConfigAccess && (
132+
<>
133+
<MenuItem
134+
icon={PlugIcon}
135+
text="Configure API"
136+
onClick={() => setDialogState('secrets')}
137+
/>
138+
<MenuDivider />
139+
</>
140+
)}
134141
<MenuItem
135142
tone="critical"
136143
icon={ResetIcon}

src/components/UploadPlaceholder.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,22 @@ import {useCallback} from 'react'
55

66
import type {SetDialogState} from '../hooks/useDialogState'
77
import {FileInputButton, type FileInputButtonProps} from './FileInputButton'
8+
import {useAccessControl} from '../hooks/useAccessControl'
9+
import {PluginConfig} from '../util/types'
810

911
interface UploadPlaceholderProps {
1012
setDialogState: SetDialogState
1113
readOnly: boolean
1214
hovering: boolean
1315
needsSetup: boolean
1416
onSelect: FileInputButtonProps['onSelect']
17+
config: PluginConfig
1518
}
1619
export default function UploadPlaceholder(props: UploadPlaceholderProps) {
1720
const {setDialogState, readOnly, onSelect, hovering, needsSetup} = props
1821
const handleBrowse = useCallback(() => setDialogState('select-video'), [setDialogState])
1922
const handleConfigureApi = useCallback(() => setDialogState('secrets'), [setDialogState])
23+
const {hasConfigAccess} = useAccessControl(props.config)
2024

2125
return (
2226
<Card
@@ -58,15 +62,17 @@ export default function UploadPlaceholder(props: UploadPlaceholderProps) {
5862
/>
5963
<Button mode="bleed" icon={SearchIcon} text="Select" onClick={handleBrowse} />
6064

61-
<Button
62-
padding={3}
63-
radius={3}
64-
tone={needsSetup ? 'critical' : undefined}
65-
onClick={handleConfigureApi}
66-
icon={PlugIcon}
67-
mode="bleed"
68-
title="Configure plugin credentials"
69-
/>
65+
{hasConfigAccess && (
66+
<Button
67+
padding={3}
68+
radius={3}
69+
tone={needsSetup ? 'critical' : undefined}
70+
onClick={handleConfigureApi}
71+
icon={PlugIcon}
72+
mode="bleed"
73+
title="Configure plugin credentials"
74+
/>
75+
)}
7076
</Inline>
7177
</Flex>
7278
</Card>

src/components/Uploader.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ export default function Uploader(props: Props) {
364364
onChange={props.onChange}
365365
onSelect={handleUpload}
366366
readOnly={props.readOnly}
367+
config={props.config}
367368
/>
368369
}
369370
/>
@@ -375,6 +376,7 @@ export default function Uploader(props: Props) {
375376
readOnly={!!props.readOnly}
376377
setDialogState={props.setDialogState}
377378
needsSetup={props.needsSetup}
379+
config={props.config}
378380
/>
379381
)}
380382
</UploadCard>

src/hooks/useAccessControl.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import {useCurrentUser} from 'sanity'
2+
import {PluginConfig} from '../util/types'
3+
4+
export const useAccessControl = (config: PluginConfig) => {
5+
const user = useCurrentUser()
6+
7+
const hasConfigAccess =
8+
!config?.allowedRolesForConfiguration ||
9+
user?.roles?.some((role) => config.allowedRolesForConfiguration.includes(role.name))
10+
11+
return {hasConfigAccess}
12+
}

src/util/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@ export interface PluginConfig extends MuxInputConfig {
8888
title?: string
8989
icon?: React.ComponentType
9090
}
91+
92+
/**
93+
* The roles that are allowed to configure the plugin.
94+
*
95+
* If not set, all roles will be allowed to configure the plugin.
96+
* @defaultValue []
97+
*/
98+
allowedRolesForConfiguration: string[]
9199
}
92100

93101
export const SUPPORTED_MUX_LANGUAGES = [

0 commit comments

Comments
 (0)