From bcac58b9c386733c2dd82a18d40079f832d6a4d8 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 29 Jun 2023 15:10:45 -0500 Subject: [PATCH] Redirect to login when groups or accounts 403 --- app/soapbox/api/hooks/accounts/useAccount.ts | 13 ++++++++++++- .../api/hooks/accounts/useAccountLookup.ts | 13 ++++++++++++- app/soapbox/api/hooks/groups/useGroup.ts | 13 ++++++++++++- app/soapbox/api/hooks/groups/useGroupLookup.ts | 17 +++++++++++++++-- app/soapbox/entity-store/hooks/useEntity.ts | 12 ++++++++++-- .../entity-store/hooks/useEntityLookup.ts | 8 ++++++-- app/soapbox/features/ui/index.tsx | 2 +- 7 files changed, 68 insertions(+), 10 deletions(-) diff --git a/app/soapbox/api/hooks/accounts/useAccount.ts b/app/soapbox/api/hooks/accounts/useAccount.ts index ac756f6ca..468049441 100644 --- a/app/soapbox/api/hooks/accounts/useAccount.ts +++ b/app/soapbox/api/hooks/accounts/useAccount.ts @@ -1,3 +1,6 @@ +import { useEffect } from 'react'; +import { useHistory } from 'react-router-dom'; + import { Entities } from 'soapbox/entity-store/entities'; import { useEntity } from 'soapbox/entity-store/hooks'; import { useFeatures, useLoggedIn } from 'soapbox/hooks'; @@ -12,11 +15,12 @@ interface UseAccountOpts { function useAccount(accountId?: string, opts: UseAccountOpts = {}) { const api = useApi(); + const history = useHistory(); const features = useFeatures(); const { me } = useLoggedIn(); const { withRelationship } = opts; - const { entity: account, ...result } = useEntity( + const { entity: account, isUnauthorized, ...result } = useEntity( [Entities.ACCOUNTS, accountId!], () => api.get(`/api/v1/accounts/${accountId}`), { schema: accountSchema, enabled: !!accountId }, @@ -30,10 +34,17 @@ function useAccount(accountId?: string, opts: UseAccountOpts = {}) { const isBlocked = account?.relationship?.blocked_by === true; const isUnavailable = (me === account?.id) ? false : (isBlocked && !features.blockersVisible); + useEffect(() => { + if (isUnauthorized) { + history.push('/login'); + } + }, [isUnauthorized]); + return { ...result, isLoading: result.isLoading, isRelationshipLoading, + isUnauthorized, isUnavailable, account: account ? { ...account, relationship } : undefined, }; diff --git a/app/soapbox/api/hooks/accounts/useAccountLookup.ts b/app/soapbox/api/hooks/accounts/useAccountLookup.ts index dc7f2fb29..ffe5885de 100644 --- a/app/soapbox/api/hooks/accounts/useAccountLookup.ts +++ b/app/soapbox/api/hooks/accounts/useAccountLookup.ts @@ -1,3 +1,6 @@ +import { useEffect } from 'react'; +import { useHistory } from 'react-router-dom'; + import { Entities } from 'soapbox/entity-store/entities'; import { useEntityLookup } from 'soapbox/entity-store/hooks'; import { useFeatures, useLoggedIn } from 'soapbox/hooks'; @@ -13,10 +16,11 @@ interface UseAccountLookupOpts { function useAccountLookup(acct: string | undefined, opts: UseAccountLookupOpts = {}) { const api = useApi(); const features = useFeatures(); + const history = useHistory(); const { me } = useLoggedIn(); const { withRelationship } = opts; - const { entity: account, ...result } = useEntityLookup( + const { entity: account, isUnauthorized, ...result } = useEntityLookup( Entities.ACCOUNTS, (account) => account.acct === acct, () => api.get(`/api/v1/accounts/lookup?acct=${acct}`), @@ -31,10 +35,17 @@ function useAccountLookup(acct: string | undefined, opts: UseAccountLookupOpts = const isBlocked = account?.relationship?.blocked_by === true; const isUnavailable = (me === account?.id) ? false : (isBlocked && !features.blockersVisible); + useEffect(() => { + if (isUnauthorized) { + history.push('/login'); + } + }, [isUnauthorized]); + return { ...result, isLoading: result.isLoading, isRelationshipLoading, + isUnauthorized, isUnavailable, account: account ? { ...account, relationship } : undefined, }; diff --git a/app/soapbox/api/hooks/groups/useGroup.ts b/app/soapbox/api/hooks/groups/useGroup.ts index 5eb6147d2..9efafb13c 100644 --- a/app/soapbox/api/hooks/groups/useGroup.ts +++ b/app/soapbox/api/hooks/groups/useGroup.ts @@ -1,3 +1,6 @@ +import { useEffect } from 'react'; +import { useHistory } from 'react-router-dom'; + import { Entities } from 'soapbox/entity-store/entities'; import { useEntity } from 'soapbox/entity-store/hooks'; import { useApi } from 'soapbox/hooks'; @@ -7,8 +10,9 @@ import { useGroupRelationship } from './useGroupRelationship'; function useGroup(groupId: string, refetch = true) { const api = useApi(); + const history = useHistory(); - const { entity: group, ...result } = useEntity( + const { entity: group, isUnauthorized, ...result } = useEntity( [Entities.GROUPS, groupId], () => api.get(`/api/v1/groups/${groupId}`), { @@ -19,8 +23,15 @@ function useGroup(groupId: string, refetch = true) { ); const { groupRelationship: relationship } = useGroupRelationship(groupId); + useEffect(() => { + if (isUnauthorized) { + history.push('/login'); + } + }, [isUnauthorized]); + return { ...result, + isUnauthorized, group: group ? { ...group, relationship: relationship || null } : undefined, }; } diff --git a/app/soapbox/api/hooks/groups/useGroupLookup.ts b/app/soapbox/api/hooks/groups/useGroupLookup.ts index 3e66f72c6..e3a979777 100644 --- a/app/soapbox/api/hooks/groups/useGroupLookup.ts +++ b/app/soapbox/api/hooks/groups/useGroupLookup.ts @@ -1,24 +1,37 @@ +import { useEffect } from 'react'; +import { useHistory } from 'react-router-dom'; + import { Entities } from 'soapbox/entity-store/entities'; import { useEntityLookup } from 'soapbox/entity-store/hooks'; import { useApi } from 'soapbox/hooks/useApi'; +import { useFeatures } from 'soapbox/hooks/useFeatures'; import { groupSchema } from 'soapbox/schemas'; import { useGroupRelationship } from './useGroupRelationship'; function useGroupLookup(slug: string) { const api = useApi(); + const features = useFeatures(); + const history = useHistory(); - const { entity: group, ...result } = useEntityLookup( + const { entity: group, isUnauthorized, ...result } = useEntityLookup( Entities.GROUPS, (group) => group.slug === slug, () => api.get(`/api/v1/groups/lookup?name=${slug}`), - { schema: groupSchema, enabled: !!slug }, + { schema: groupSchema, enabled: features.groups && !!slug }, ); const { groupRelationship: relationship } = useGroupRelationship(group?.id); + useEffect(() => { + if (isUnauthorized) { + history.push('/login'); + } + }, [isUnauthorized]); + return { ...result, + isUnauthorized, entity: group ? { ...group, relationship: relationship || null } : undefined, }; } diff --git a/app/soapbox/entity-store/hooks/useEntity.ts b/app/soapbox/entity-store/hooks/useEntity.ts index 3d57c8ab0..b9b9f001f 100644 --- a/app/soapbox/entity-store/hooks/useEntity.ts +++ b/app/soapbox/entity-store/hooks/useEntity.ts @@ -1,4 +1,5 @@ -import { useEffect } from 'react'; +import { AxiosError } from 'axios'; +import { useEffect, useState } from 'react'; import z from 'zod'; import { useAppDispatch, useAppSelector, useLoading } from 'soapbox/hooks'; @@ -24,6 +25,8 @@ function useEntity( opts: UseEntityOpts = {}, ) { const [isFetching, setPromise] = useLoading(true); + const [error, setError] = useState(); + const dispatch = useAppDispatch(); const [entityType, entityId] = path; @@ -35,6 +38,7 @@ function useEntity( const isEnabled = opts.enabled ?? true; const isLoading = isFetching && !entity; + const isLoaded = !isFetching && !!entity; const fetchEntity = async () => { try { @@ -42,7 +46,7 @@ function useEntity( const entity = schema.parse(response.data); dispatch(importEntities([entity], entityType)); } catch (e) { - // do nothing + setError(e); } }; @@ -58,6 +62,10 @@ function useEntity( fetchEntity, isFetching, isLoading, + isLoaded, + error, + isUnauthorized: error instanceof AxiosError && error.response?.status === 401, + isForbidden: error instanceof AxiosError && error.response?.status === 403, }; } diff --git a/app/soapbox/entity-store/hooks/useEntityLookup.ts b/app/soapbox/entity-store/hooks/useEntityLookup.ts index 73b2ef938..29cf85244 100644 --- a/app/soapbox/entity-store/hooks/useEntityLookup.ts +++ b/app/soapbox/entity-store/hooks/useEntityLookup.ts @@ -1,4 +1,5 @@ -import { useEffect } from 'react'; +import { AxiosError } from 'axios'; +import { useEffect, useState } from 'react'; import { z } from 'zod'; import { useAppDispatch, useAppSelector, useLoading } from 'soapbox/hooks'; @@ -23,6 +24,7 @@ function useEntityLookup( const dispatch = useAppDispatch(); const [isFetching, setPromise] = useLoading(true); + const [error, setError] = useState(); const entity = useAppSelector(state => findEntity(state, entityType, lookupFn)); const isEnabled = opts.enabled ?? true; @@ -34,7 +36,7 @@ function useEntityLookup( const entity = schema.parse(response.data); dispatch(importEntities([entity], entityType)); } catch (e) { - // do nothing + setError(e); } }; @@ -51,6 +53,8 @@ function useEntityLookup( fetchEntity, isFetching, isLoading, + isUnauthorized: error instanceof AxiosError && error.response?.status === 401, + isForbidden: error instanceof AxiosError && error.response?.status === 403, }; } diff --git a/app/soapbox/features/ui/index.tsx b/app/soapbox/features/ui/index.tsx index 847b29d26..6b81e38d5 100644 --- a/app/soapbox/features/ui/index.tsx +++ b/app/soapbox/features/ui/index.tsx @@ -327,7 +327,7 @@ const SwitchingColumnsArea: React.FC = ({ children }) => {features.groupsTags && } {features.groupsTags && } - {features.groups && } + {features.groups && } {features.groups && } {features.groups && } {features.groups && }