Skip to content

Commit

Permalink
[PAY-3240] Prevent wrong users from seeing /edit pages (#9298)
Browse files Browse the repository at this point in the history
  • Loading branch information
raymondjacobson authored Jul 30, 2024
1 parent 8b92b23 commit d87357e
Show file tree
Hide file tree
Showing 10 changed files with 71 additions and 27 deletions.
1 change: 1 addition & 0 deletions packages/harmony/src/components/tag/Tag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export const Tag = (props: TagProps) => {
</Text>
{Icon ? (
<IconButton
asChild
icon={Icon}
color='staticWhite'
onClick={isDefaultMultiselect ? onClick : undefined}
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/components/data-entry/ContextualMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export const SelectedValue = (props: SelectedValueProps) => {
const { label, icon: Icon, children, ...rest } = props
return (
<label className={styles.selectedValue} {...rest}>
<HiddenInput value={label} />
<HiddenInput value={label} readOnly />
{Icon ? <Icon size='s' color='default' /> : null}
{label ? (
<Text variant='body' size='s'>
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/components/data-entry/DropdownInput.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class DropdownInput extends Component {
}
notFoundContent={''}
getPopupContainer={popupContainer}
onVisibleChange={this.onVisibleChange}
onDropdownVisibleChange={this.onVisibleChange}
{...other}
>
{options}
Expand Down
4 changes: 3 additions & 1 deletion packages/web/src/components/form-fields/TextAreaField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ export const TextAreaField = (props: TextAreaFieldProps) => {

const hasError = Boolean(meta.touched && meta.error)

return <TextAreaV2 value={value} {...field} error={hasError} {...other} />
return (
<TextAreaV2 value={value ?? ''} {...field} error={hasError} {...other} />
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ ImageSelectionButton.propTypes = {
hasImage: PropTypes.bool.isRequired,
// The name of the image (e.g. render the button as Add "Artwork" or Add "Cover Photo")
imageName: PropTypes.string,
showImageName: PropTypes.boolean,
showImageName: PropTypes.bool,
// Whether or not to show the image selection modal. Otherwise, the
// button itself is the dropzone.
includePopup: PropTypes.bool,
Expand All @@ -150,7 +150,7 @@ ImageSelectionButton.propTypes = {
onClick: PropTypes.func,
defaultPopupOpen: PropTypes.bool,
isImageAutogenerated: PropTypes.bool,
onRemoveArtwork: PropTypes.bool,
onRemoveArtwork: PropTypes.func,
...ImageSelectionProps
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { memo, useEffect } from 'react'
import { memo } from 'react'

import { useGatedContentAccess } from '@audius/common/hooks'
import {
Expand Down Expand Up @@ -82,21 +82,12 @@ const PlayingTrackInfo = ({
'usdc_purchase' in track.stream_conditions &&
!hasStreamAccess)

const [artistSpringProps, setArtistSpringProps] = useSpring(() => springProps)
const [trackSpringProps, setTrackSpringProps] = useSpring(() => springProps)
const spring = useSpring(springProps)
const profileImage = useProfilePicture(
artistUserId ?? null,
SquareSizes.SIZE_150_BY_150
)

useEffect(() => {
setArtistSpringProps(springProps)
}, [artistUserId, setArtistSpringProps])

useEffect(() => {
setTrackSpringProps(springProps)
}, [trackTitle, setTrackSpringProps])

const boxShadowStyle =
hasShadow && dominantColor
? {
Expand All @@ -106,10 +97,7 @@ const PlayingTrackInfo = ({

const renderTrackTitle = () => {
return (
<animated.div
style={trackSpringProps}
className={styles.trackTitleContainer}
>
<animated.div style={spring} className={styles.trackTitleContainer}>
<div
className={cn(styles.trackTitle, {
[styles.textShadow]: hasShadow
Expand Down Expand Up @@ -159,10 +147,7 @@ const PlayingTrackInfo = ({
{renderTrackTitle()}
</Draggable>
)}
<animated.div
className={styles.artistNameWrapper}
style={artistSpringProps}
>
<animated.div style={spring} className={styles.artistNameWrapper}>
<div
className={cn(styles.artistName, {
[styles.textShadow]: hasShadow
Expand Down
50 changes: 47 additions & 3 deletions packages/web/src/hooks/useManagedAccountNotAllowedRedirect.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { useContext, useEffect } from 'react'

import { useGetManagedAccounts } from '@audius/common/api'
import { useIsManagedAccount } from '@audius/common/hooks'
import { Status } from '@audius/common/models'
import { accountSelectors } from '@audius/common/store'
import { useSelector } from 'react-redux'

import { ToastContext } from 'components/toast/ToastContext'
import { FEED_PAGE } from 'utils/route'

import { useNavigateToPage } from './useNavigateToPage'

const messages = {
unauthorized: `You can't do that as a managed user`
unauthorized: 'Unauthorized',
unauthorizedAsManaged: `You can't do that as a managed user`
}

/**
Expand All @@ -25,7 +30,7 @@ export const useManagedAccountNotAllowedRedirect = (
useEffect(() => {
if (isManagedAccount) {
navigate(route)
toast(messages.unauthorized)
toast(messages.unauthorizedAsManaged)
}
}, [isManagedAccount, navigate, route, toast])
}
Expand All @@ -48,7 +53,46 @@ export const useManagedAccountNotAllowedCallback = ({
useEffect(() => {
if (isManagedAccount && trigger) {
callback()
toast(messages.unauthorized)
toast(messages.unauthorizedAsManaged)
}
}, [isManagedAccount, callback, toast, trigger])
}

/**
* Hook to prevent a managed account from some action if the
* managed account does not have access to the provided account
* @param handle handle of the user we are doing something on behalf of
* @param route route to redirect the user to
*/
export const useIsUnauthorizedForHandleRedirect = (
handle: string,
route: string = FEED_PAGE
) => {
const { handle: actingHandle, user_id: userId } =
useSelector(accountSelectors.getAccountUser) || {}
const navigate = useNavigateToPage()
const { toast } = useContext(ToastContext)

const { data: managedAccounts = [], status: accountsStatus } =
useGetManagedAccounts({ userId: userId! }, { disabled: !userId })

const isLoading =
!actingHandle ||
!userId ||
accountsStatus === Status.LOADING ||
accountsStatus === Status.IDLE
const isOwner = actingHandle === handle
const isManaged =
!!actingHandle &&
managedAccounts.find(({ user }) => user.handle.toLowerCase() === handle)

useEffect(() => {
if (isLoading) {
return
}
if (!isOwner && !isManaged) {
navigate(route)
toast(messages.unauthorized)
}
}, [isLoading, isOwner, isManaged, navigate, route, toast])
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import Header from 'components/header/desktop/Header'
import LoadingSpinnerFullPage from 'components/loading-spinner-full-page/LoadingSpinnerFullPage'
import Page from 'components/page/Page'
import { useCollectionCoverArt2 } from 'hooks/useCollectionCoverArt'
import { useIsUnauthorizedForHandleRedirect } from 'hooks/useManagedAccountNotAllowedRedirect'
import { useRequiresAccount } from 'hooks/useRequiresAccount'
import { track } from 'services/analytics'

import { updatePlaylistContents } from '../utils'
Expand All @@ -42,6 +44,8 @@ export const EditCollectionPage = () => {
const focus = searchParams.get('focus')
const permalink = `/${handle}/${isAlbum ? 'album' : 'playlist'}/${slug}`
const dispatch = useDispatch()
useRequiresAccount()
useIsUnauthorizedForHandleRedirect(handle)

const { data: currentUserId } = useGetCurrentUserId({})
const { data: collection, status } = useGetPlaylistByPermalink(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import { useTemporaryNavContext } from 'components/nav/mobile/NavContext'
import TextElement, { Type } from 'components/nav/mobile/TextElement'
import TrackList from 'components/track/mobile/TrackList'
import { useCollectionCoverArt2 } from 'hooks/useCollectionCoverArt'
import { useIsUnauthorizedForHandleRedirect } from 'hooks/useManagedAccountNotAllowedRedirect'
import { useRequiresAccount } from 'hooks/useRequiresAccount'
import UploadStub from 'pages/profile-page/components/mobile/UploadStub'
import { track } from 'services/analytics'
import { AppState } from 'store/types'
Expand Down Expand Up @@ -76,6 +78,8 @@ const EditCollectionPage = g(
const permalink = `/${handle}/${isAlbum ? 'album' : 'playlist'}/${slug}`
const dispatch = useDispatch()
const history = useHistory()
useRequiresAccount()
useIsUnauthorizedForHandleRedirect(handle)

const { data: currentUserId } = useGetCurrentUserId({})
const { data: collection } = useGetPlaylistByPermalink(
Expand Down
4 changes: 4 additions & 0 deletions packages/web/src/pages/edit-page/EditTrackPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { TrackEditFormValues } from 'components/edit-track/types'
import Header from 'components/header/desktop/Header'
import LoadingSpinnerFullPage from 'components/loading-spinner-full-page/LoadingSpinnerFullPage'
import Page from 'components/page/Page'
import { useIsUnauthorizedForHandleRedirect } from 'hooks/useManagedAccountNotAllowedRedirect'
import { useRequiresAccount } from 'hooks/useRequiresAccount'
import { useTrackCoverArt2 } from 'hooks/useTrackCoverArt'

const { deleteTrack, editTrack } = cacheTracksActions
Expand All @@ -47,6 +49,8 @@ export const EditTrackPage = (props: EditPageProps) => {
const { scrollToTop } = props
const { handle, slug } = useParams<{ handle: string; slug: string }>()
const dispatch = useDispatch()
useRequiresAccount()
useIsUnauthorizedForHandleRedirect(handle)

const { data: currentUserId } = useGetCurrentUserId({})
const permalink = `/${handle}/${slug}`
Expand Down

0 comments on commit d87357e

Please sign in to comment.