Skip to content

Commit 9befd26

Browse files
arthur791004claude
andcommitted
Dashboard: only record sites the user belongs to as recent/omnibar site
Validate site membership before persisting a site as the most recent site or setting it as the omnibar site, so a crafted or inaccessible `origin_site_id` can no longer be recorded. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent bd08180 commit 9befd26

2 files changed

Lines changed: 41 additions & 14 deletions

File tree

client/dashboard/app/interim-omnibar/interim-omnibar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export function InterimOmnibar( {
5353
onToggleNotifications,
5454
}: Props ) {
5555
const user = userProp ?? emptyUser;
56-
const siteId = user.primary_blog ?? null;
56+
const siteId = site?.ID ?? null;
5757
const siteSlug = site?.slug ?? null;
5858
const siteAdminUrl = site?.options?.admin_url ?? null;
5959
const isUnlaunchedSite = !! site && site.launch_status === 'unlaunched' && ! site.is_a4a_dev_site;

client/dashboard/app/omnibar/site.ts

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
omnibarSiteIdQuery,
33
queryClient,
4+
siteByIdQuery,
45
userPreferenceQuery,
56
userPreferenceOptimisticMutation,
67
} from '@automattic/api-queries';
@@ -11,14 +12,22 @@ import { useEffect } from 'react';
1112
import { AUTH_QUERY_KEY } from '../auth';
1213
import type { Site, User } from '@automattic/api-core';
1314

15+
function isMemberOfSite( site: Site ) {
16+
// If the user is a member of the site, the capabilities property will exist
17+
return !! site.capabilities;
18+
}
19+
1420
/**
1521
* Initializes the current site for the omnibar, which is extracted from the URL,
1622
* or the most recent sites, or the user's primary blog, in that priority.
1723
*/
1824
export function useInitializeOmnibarSite() {
1925
const user = queryClient.getQueryData< User >( AUTH_QUERY_KEY );
2026

21-
const { data: recentSiteIds } = useQuery( userPreferenceQuery( 'recentSites' ), queryClient );
27+
const { data: recentSiteIds, isLoading: isLoadingRecentSiteIds } = useQuery(
28+
userPreferenceQuery( 'recentSites' ),
29+
queryClient
30+
);
2231
const { mutate: updateRecentSites } = useMutation(
2332
userPreferenceOptimisticMutation( 'recentSites' ),
2433
queryClient
@@ -39,24 +48,36 @@ export function useInitializeOmnibarSite() {
3948
( location.search as Record< string, string | undefined > ).origin_site_id
4049
);
4150
const originSiteId = originSiteIdParam > 0 ? originSiteIdParam : undefined;
51+
const recentSiteId =
52+
queryClient.getQueryData< number | null >( omnibarSiteIdQuery().queryKey ) ||
53+
recentSiteIds?.[ 0 ];
4254

43-
const selectedSiteId = routeSite?.ID || originSiteId;
44-
const fallbackSiteId = recentSiteIds?.[ 0 ] || user?.primary_blog;
55+
const { data: originSite, isLoading: isLoadingOriginSite } = useQuery( {
56+
...siteByIdQuery( originSiteId ?? 0 ),
57+
enabled: !! originSiteId,
58+
} );
59+
60+
const { data: recentSite, isLoading: isLoadingRecentSite } = useQuery( {
61+
...siteByIdQuery( recentSiteId ?? 0 ),
62+
enabled: !! recentSiteId,
63+
} );
4564

4665
useEffect( () => {
47-
// Wait until the route and recent sites are fully loaded, to avoid flicker.
48-
if ( ! isRouteLoaded || ! recentSiteIds ) {
66+
// Wait until the required data are fully loaded, to avoid flicker.
67+
if ( ! isRouteLoaded || isLoadingRecentSiteIds || isLoadingOriginSite || isLoadingRecentSite ) {
4968
return;
5069
}
5170

5271
// `omnibarSiteIdQuery` is used as cross-tree shared state — its placeholder
5372
// queryFn resolves to `null`. If it's still in flight when we write here,
5473
// the resolution will overwrite our value and the omnibar loses the site.
5574
queryClient.cancelQueries( { queryKey: omnibarSiteIdQuery().queryKey } );
56-
queryClient.setQueryData(
57-
omnibarSiteIdQuery().queryKey,
58-
( currentSiteId ) => selectedSiteId || currentSiteId || fallbackSiteId
75+
76+
const omnibarSite = [ routeSite, originSite, recentSite ].find(
77+
( site ) => site && isMemberOfSite( site )
5978
);
79+
const omnibarSiteId = omnibarSite?.ID ?? user?.primary_blog;
80+
queryClient.setQueryData( omnibarSiteIdQuery().queryKey, () => omnibarSiteId );
6081

6182
// Remove the `origin_site_id` query param from the URL.
6283
if ( originSiteId ) {
@@ -67,16 +88,22 @@ export function useInitializeOmnibarSite() {
6788
);
6889
}
6990

70-
// Push the selected site id as the most recent site.
71-
if ( selectedSiteId && selectedSiteId !== recentSiteIds[ 0 ] ) {
72-
updateRecentSites( [ ...new Set( [ selectedSiteId, ...recentSiteIds ] ) ].slice( 0, 5 ) );
91+
if ( omnibarSiteId && omnibarSiteId !== recentSiteIds?.[ 0 ] ) {
92+
updateRecentSites(
93+
[ ...new Set( [ omnibarSiteId, ...( recentSiteIds || [] ) ] ) ].slice( 0, 5 )
94+
);
7395
}
7496
}, [
7597
isRouteLoaded,
98+
routeSite,
7699
originSiteId,
77-
selectedSiteId,
78-
fallbackSiteId,
100+
originSite,
101+
isLoadingOriginSite,
102+
recentSite,
103+
isLoadingRecentSite,
79104
recentSiteIds,
105+
isLoadingRecentSiteIds,
106+
user,
80107
updateRecentSites,
81108
] );
82109
}

0 commit comments

Comments
 (0)