Skip to content

Commit adfaeb7

Browse files
committed
cleanup public websites
1 parent 46b1495 commit adfaeb7

File tree

5 files changed

+120
-23
lines changed

5 files changed

+120
-23
lines changed

apps/api/src/routes/query.ts

Lines changed: 96 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ function getAccessibleWebsites(authCtx: AuthContext) {
6767
? eq(websites.organizationId, authCtx.apiKey.organizationId)
6868
: authCtx.apiKey.userId
6969
? and(
70-
eq(websites.userId, authCtx.apiKey.userId),
71-
isNull(websites.organizationId)
72-
)
70+
eq(websites.userId, authCtx.apiKey.userId),
71+
isNull(websites.organizationId)
72+
)
7373
: eq(websites.id, "");
7474
return db
7575
.select(select)
@@ -92,6 +92,56 @@ function getAccessibleWebsites(authCtx: AuthContext) {
9292
return [];
9393
}
9494

95+
async function verifyWebsiteAccess(
96+
ctx: AuthContext,
97+
websiteId: string
98+
): Promise<boolean> {
99+
if (!ctx.isAuthenticated) {
100+
return false;
101+
}
102+
103+
const website = await db.query.websites.findFirst({
104+
where: eq(websites.id, websiteId),
105+
columns: {
106+
id: true,
107+
isPublic: true,
108+
userId: true,
109+
organizationId: true,
110+
},
111+
});
112+
113+
if (!website) {
114+
return false;
115+
}
116+
117+
if (website.isPublic) {
118+
return true;
119+
}
120+
121+
if (ctx.apiKey) {
122+
if (hasGlobalAccess(ctx.apiKey)) {
123+
if (ctx.apiKey.organizationId) {
124+
return website.organizationId === ctx.apiKey.organizationId;
125+
}
126+
if (ctx.apiKey.userId) {
127+
return (
128+
website.userId === ctx.apiKey.userId && !website.organizationId
129+
);
130+
}
131+
return false;
132+
}
133+
134+
const accessibleIds = getAccessibleWebsiteIds(ctx.apiKey);
135+
return accessibleIds.includes(websiteId);
136+
}
137+
138+
if (ctx.user) {
139+
return website.userId === ctx.user.id && !website.organizationId;
140+
}
141+
142+
return false;
143+
}
144+
95145
const MS_PER_DAY = 86_400_000;
96146

97147
function getTimeUnit(
@@ -116,12 +166,12 @@ function getTimeUnit(
116166
type ParamInput =
117167
| string
118168
| {
119-
name: string;
120-
start_date?: string;
121-
end_date?: string;
122-
granularity?: string;
123-
id?: string;
124-
};
169+
name: string;
170+
start_date?: string;
171+
end_date?: string;
172+
granularity?: string;
173+
id?: string;
174+
};
125175

126176
function parseParam(p: ParamInput) {
127177
if (typeof p === "string") {
@@ -200,10 +250,30 @@ export const query = new Elysia({ prefix: "/v1/query" })
200250
async ({
201251
body,
202252
query: q,
253+
auth: ctx,
203254
}: {
204255
body: CompileRequestType;
205256
query: { website_id?: string; timezone?: string };
257+
auth: AuthContext;
206258
}) => {
259+
if (!ctx.isAuthenticated) {
260+
return AUTH_FAILED;
261+
}
262+
263+
if (q.website_id) {
264+
const hasAccess = await verifyWebsiteAccess(ctx, q.website_id);
265+
if (!hasAccess) {
266+
return new Response(
267+
JSON.stringify({
268+
success: false,
269+
error: "Access denied to this website",
270+
code: "ACCESS_DENIED",
271+
}),
272+
{ status: 403, headers: { "Content-Type": "application/json" } }
273+
);
274+
}
275+
}
276+
207277
try {
208278
const domain = q.website_id
209279
? await getWebsiteDomain(q.website_id)
@@ -227,11 +297,28 @@ export const query = new Elysia({ prefix: "/v1/query" })
227297
({
228298
body,
229299
query: q,
300+
auth: ctx,
230301
}: {
231302
body: DynamicQueryRequestType | DynamicQueryRequestType[];
232303
query: { website_id?: string; timezone?: string };
304+
auth: AuthContext;
233305
}) =>
234306
record("executeQuery", async () => {
307+
if (!ctx.isAuthenticated) {
308+
return AUTH_FAILED;
309+
}
310+
311+
if (q.website_id) {
312+
const hasAccess = await verifyWebsiteAccess(ctx, q.website_id);
313+
if (!hasAccess) {
314+
return {
315+
success: false,
316+
error: "Access denied to this website",
317+
code: "ACCESS_DENIED",
318+
};
319+
}
320+
}
321+
235322
const tz = q.timezone || "UTC";
236323
const isBatch = Array.isArray(body);
237324
setAttributes({

apps/dashboard/components/layout/category-sidebar.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ export function CategorySidebar({
5252
user = null,
5353
}: CategorySidebarProps) {
5454
const pathname = usePathname();
55-
const { websites, isLoading: isLoadingWebsites } = useWebsites();
55+
const isDemo = pathname.startsWith("/demo");
56+
const { websites, isLoading: isLoadingWebsites } = useWebsites({
57+
enabled: !isDemo,
58+
});
5659
const [helpOpen, setHelpOpen] = useState(false);
5760
const { isOn } = useFlags();
5861

apps/dashboard/components/layout/navigation/mobile-category-selector.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,21 @@ import {
2222
getDefaultCategory,
2323
} from "./navigation-config";
2424

25-
type MobileCategorySelectorProps = {
25+
interface MobileCategorySelectorProps {
2626
onCategoryChangeAction?: (categoryId: string) => void;
2727
selectedCategory?: string;
28-
};
28+
}
2929

3030
export function MobileCategorySelector({
3131
onCategoryChangeAction,
3232
selectedCategory,
3333
}: MobileCategorySelectorProps) {
3434
const pathname = usePathname();
35-
const { websites, isLoading: isLoadingWebsites } = useWebsites();
36-
const { isEnabled } = useFlags();
35+
const isDemo = pathname.startsWith("/demo");
36+
const { websites, isLoading: isLoadingWebsites } = useWebsites({
37+
enabled: !isDemo,
38+
});
39+
const { isOn } = useFlags();
3740

3841
const { categories, defaultCategory } = useMemo(() => {
3942
const baseConfig = getContextConfig(pathname);
@@ -56,14 +59,14 @@ export function MobileCategorySelector({
5659
pathname
5760
).filter((category) => {
5861
if (category.flag) {
59-
const flagState = isEnabled(category.flag);
60-
return flagState.isReady && flagState.enabled;
62+
const flagState = isOn(category.flag);
63+
return flagState;
6164
}
6265
return true;
6366
});
6467

6568
return { categories: filteredCategories, defaultCategory: defaultCat };
66-
}, [pathname, websites, isLoadingWebsites, isEnabled]);
69+
}, [pathname, websites, isLoadingWebsites, isOn]);
6770

6871
const activeCategory = selectedCategory || defaultCategory;
6972
const currentCategory = categories.find((cat) => cat.id === activeCategory);

apps/dashboard/components/layout/sidebar.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,17 @@ export function Sidebar({ user = null }: SidebarProps) {
9090
const [selectedCategory, setSelectedCategory] = useState<string | undefined>(
9191
undefined
9292
);
93-
const { websites, isLoading: isLoadingWebsites } = useWebsites();
94-
const accordionStates = useAccordionStates();
95-
const sidebarRef = useRef<HTMLDivElement>(null);
96-
const previousFocusRef = useRef<HTMLElement | null>(null);
9793

9894
const isDemo = pathname.startsWith("/demo");
9995
const isWebsite = pathname.startsWith("/websites/");
96+
const enabled = !isDemo;
97+
98+
const { websites, isLoading: isLoadingWebsites } = useWebsites({
99+
enabled,
100+
});
101+
const accordionStates = useAccordionStates();
102+
const sidebarRef = useRef<HTMLDivElement>(null);
103+
const previousFocusRef = useRef<HTMLElement | null>(null);
100104

101105
const websiteId = useMemo(
102106
() => (isDemo || isWebsite ? pathname.split("/")[2] : null),

apps/dashboard/hooks/use-websites.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,15 @@ const removeWebsiteFromList = (
7979
};
8080
};
8181

82-
export function useWebsites() {
82+
export function useWebsites(options?: { enabled?: boolean }) {
8383
const { activeOrganization, isLoading: isLoadingOrganization } =
8484
useOrganizationsContext();
8585

8686
const query = useQuery({
8787
...orpc.websites.listWithCharts.queryOptions({
8888
input: { organizationId: activeOrganization?.id },
8989
}),
90-
enabled: !isLoadingOrganization,
90+
enabled: options?.enabled !== false && !isLoadingOrganization,
9191
});
9292

9393
return {

0 commit comments

Comments
 (0)