must be org member

This commit is contained in:
Nate Kelley 2025-09-20 13:49:20 -06:00
parent f406dd5ca3
commit 3bff20c8e0
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
8 changed files with 71 additions and 41 deletions

View File

@ -40,14 +40,7 @@ export const requireAuth = bearerAuth({
c.set('busterUser', busterUser); c.set('busterUser', busterUser);
// Ensure user is not anonymous return true;
const isAuthenticated = !data.user.is_anonymous;
if (!isAuthenticated) {
console.info('Anonymous user attempted to access protected resource');
}
return isAuthenticated;
} catch (error) { } catch (error) {
console.error('Unexpected error during token validation:', error); console.error('Unexpected error during token validation:', error);
return false; return false;

View File

@ -1,19 +1,23 @@
import type { AssetType } from '@buster/server-shared/assets';
import type { QueryClient } from '@tanstack/react-query'; import type { QueryClient } from '@tanstack/react-query';
import { prefetchGetMetric } from '@/api/buster_rest/metrics'; import { prefetchGetMetric } from '@/api/buster_rest/metrics';
import { useGetMetricParams } from '@/context/Metrics/useGetMetricParams'; import { useGetMetricParams } from '@/context/Metrics/useGetMetricParams';
import { MetricViewChartController } from '@/controllers/MetricController/MetricViewChartController'; import { MetricViewChartController } from '@/controllers/MetricController/MetricViewChartController';
import { AppAssetCheckLayout } from '@/layouts/AppAssetCheckLayout';
export const ssr = true; export const ssr = true;
export const component = () => { export const component = () => {
const { metricId } = useGetMetricParams(); const { metricId } = useGetMetricParams();
return ( return (
<MetricViewChartController <AppAssetCheckLayout assetType={'metric_file'}>
metricId={metricId} <MetricViewChartController
className="h-full w-full" metricId={metricId}
cardClassName="max-h-full!" className="h-full w-full"
readOnly cardClassName="max-h-full!"
/> readOnly
/>
</AppAssetCheckLayout>
); );
}; };
@ -38,3 +42,7 @@ export const loader = async ({
title: metric?.name, title: metric?.name,
}; };
}; };
export const staticData = {
assetType: 'metric_file' as AssetType,
};

View File

@ -48,6 +48,17 @@ const getAssetAccess = (
}; };
} }
// 403 is no access
if (error?.status === 403) {
return {
hasAccess: false,
passwordRequired: false,
isPublic: false,
isDeleted: false,
isFetched,
};
}
return { return {
hasAccess: true, hasAccess: true,
passwordRequired: false, passwordRequired: false,
@ -86,7 +97,7 @@ export const useGetAssetPasswordConfig = (
return chatQueryKeys.chatsGetChat(assetId); return chatQueryKeys.chatsGetChat(assetId);
}, [type, assetId, chosenVersionNumber]); }, [type, assetId, chosenVersionNumber]);
const { error, isFetched } = useQuery({ const { error, isFetched, data } = useQuery({
queryKey: selectedQuery.queryKey, queryKey: selectedQuery.queryKey,
enabled: true, enabled: true,
select: useCallback((v: unknown) => !!v, []), select: useCallback((v: unknown) => !!v, []),

View File

@ -10,21 +10,17 @@ export const Route = createFileRoute('/app/_app/_asset')({
beforeLoad: async ({ matches }) => { beforeLoad: async ({ matches }) => {
const assetType = [...matches].reverse().find(({ staticData }) => staticData?.assetType) const assetType = [...matches].reverse().find(({ staticData }) => staticData?.assetType)
?.staticData?.assetType as AssetType; ?.staticData?.assetType as AssetType;
return { return { assetType };
assetType,
};
}, },
loader: async ({ context }) => { loader: async ({ context }) => {
const { assetType } = context; const { assetType } = context;
return { return { assetType };
assetType,
};
}, },
}); });
const stableCtxSelector = (ctx: RouteContext) => ctx.assetType; const stableCtxSelector = (ctx: RouteContext) => ctx.assetType;
function RouteComponent() { function RouteComponent() {
const assetType = Route.useRouteContext({ select: stableCtxSelector }) || 'metric_file'; const assetType = Route.useLoaderData({ select: stableCtxSelector }) || 'metric_file';
return ( return (
<AppAssetCheckLayout assetType={assetType}> <AppAssetCheckLayout assetType={assetType}>

View File

@ -1,15 +1,41 @@
import { createFileRoute, Outlet } from '@tanstack/react-router'; import type { AssetType } from '@buster/server-shared/assets';
import { createFileRoute, Outlet, type RouteContext } from '@tanstack/react-router';
import { prefetchGetMyUserInfo } from '@/api/buster_rest/users'; import { prefetchGetMyUserInfo } from '@/api/buster_rest/users';
import { signInWithAnonymousUser } from '@/integrations/supabase/signIn'; import { signInWithAnonymousUser } from '@/integrations/supabase/signIn';
import { AppAssetCheckLayout } from '@/layouts/AppAssetCheckLayout';
export const Route = createFileRoute('/embed')({ export const Route = createFileRoute('/embed')({
beforeLoad: async ({ context }) => { beforeLoad: async ({ context, matches }) => {
const user = await prefetchGetMyUserInfo(context.queryClient); const user = await prefetchGetMyUserInfo(context.queryClient);
if (!user) await signInWithAnonymousUser(); //we fallback to an anonymous user if (!user) await signInWithAnonymousUser(); //we fallback to an anonymous user
const assetType = [...matches].reverse().find(({ staticData }) => staticData?.assetType)
?.staticData?.assetType as AssetType;
return {
assetType,
};
},
loader: async ({ context }) => {
const { assetType } = context;
return { assetType };
}, },
component: RouteComponent, component: RouteComponent,
ssr: false,
}); });
const stableCtxSelector = (ctx: RouteContext) => ctx.assetType;
function RouteComponent() { function RouteComponent() {
return <Outlet />; const assetType = Route.useLoaderData({ select: stableCtxSelector });
if (!assetType) {
return (
<div className="flex h-full w-full items-center justify-center">No asset type found</div>
);
}
return (
<AppAssetCheckLayout assetType={assetType}>
<Outlet />
</AppAssetCheckLayout>
);
} }

View File

@ -23,6 +23,9 @@ export const Route = createFileRoute('/embed/dashboard/$dashboardId')({
}; };
}, },
component: RouteComponent, component: RouteComponent,
staticData: {
assetType: 'dashboard_file',
},
ssr: true, ssr: true,
}); });

View File

@ -30,6 +30,9 @@ export const Route = createFileRoute('/embed/report/$reportId')({
], ],
}; };
}, },
staticData: {
assetType: 'report_file',
},
}); });
function RouteComponent() { function RouteComponent() {

View File

@ -8,7 +8,6 @@ import {
reportFiles, reportFiles,
users, users,
} from '../../schema'; } from '../../schema';
import { AssetPermissionRoleSchema } from '../../schema-types';
import { getAssetPermission } from '../assets'; import { getAssetPermission } from '../assets';
import { getOrganizationMemberCount, getUserOrganizationId } from '../organizations'; import { getOrganizationMemberCount, getUserOrganizationId } from '../organizations';
@ -27,11 +26,8 @@ export async function getReportFileById(input: GetReportInput) {
const userOrg = await getUserOrganizationId(userId); const userOrg = await getUserOrganizationId(userId);
if (!userOrg?.organizationId) { const organizationId = userOrg?.organizationId || '';
throw new Error('User not found in any organization'); const isOrganizationMember = organizationId !== '';
}
const { organizationId } = userOrg;
const reportCollectionsQuery = db const reportCollectionsQuery = db
.select({ .select({
@ -73,13 +69,7 @@ export async function getReportFileById(input: GetReportInput) {
}) })
.from(reportFiles) .from(reportFiles)
.innerJoin(users, eq(reportFiles.createdBy, users.id)) .innerJoin(users, eq(reportFiles.createdBy, users.id))
.where( .where(and(eq(reportFiles.id, reportId), isNull(reportFiles.deletedAt)))
and(
eq(reportFiles.id, reportId),
eq(reportFiles.organizationId, organizationId),
isNull(reportFiles.deletedAt)
)
)
.limit(1); .limit(1);
// Individual permissions query - get users with direct permissions to this report // Individual permissions query - get users with direct permissions to this report
@ -111,9 +101,9 @@ export async function getReportFileById(input: GetReportInput) {
userPermission, userPermission,
] = await Promise.all([ ] = await Promise.all([
reportDataQuery, reportDataQuery,
reportCollectionsQuery, isOrganizationMember ? reportCollectionsQuery : Promise.resolve([]),
individualPermissionsQuery, isOrganizationMember ? individualPermissionsQuery : Promise.resolve([]),
getOrganizationMemberCount(organizationId), isOrganizationMember ? getOrganizationMemberCount(organizationId) : Promise.resolve(0),
getAssetPermission(userId, reportId, 'report_file'), getAssetPermission(userId, reportId, 'report_file'),
]); ]);
const reportData = reportDataResult[0]; const reportData = reportDataResult[0];