feat: enforce per-user module access in Makelore

This commit is contained in:
2026-08-17 08:51:15 +08:00
parent f7171a471a
commit d16922f18c
12 changed files with 457 additions and 35 deletions

View File

@@ -31,7 +31,11 @@ import { useProviderStore } from './stores/providers';
import { useAuthStore } from './stores/auth';
import { useOpencodeStore } from './stores/opencode';
import { useProjectConfigStore } from './stores/project-config';
import { AI_MODULE_SELECTION_PATH } from './lib/ai-modules';
import {
AI_MODULE_SELECTION_PATH,
getGuardedAiModuleForPath,
isAiModuleAllowed,
} from './lib/ai-modules';
import { useUserSyncStore } from './stores/user-sync';
import { flushPendingAgentSessionSync } from '@/lib/agent-session-sync';
import { subscribeHostEvent } from '@/lib/host-events';
@@ -118,25 +122,8 @@ function getReturnPath(location: ReturnType<typeof useLocation>): string {
return `${location.pathname}${location.search}`;
}
const PROGRAMMING_ROUTE_PREFIXES = [
'/project-config',
'/makelore-home',
'/kangaroo',
'/subagents',
'/chat',
'/deliverables',
'/workbench',
'/opencode-chat',
'/projects',
'/sessions',
'/models',
'/settings',
] as const;
function isProgrammingRoute(pathname: string): boolean {
return PROGRAMMING_ROUTE_PREFIXES.some(
(route) => pathname === route || pathname.startsWith(`${route}/`),
);
return getGuardedAiModuleForPath(pathname) === 'programming';
}
function ProtectedLayout({
@@ -152,6 +139,8 @@ function ProtectedLayout({
}) {
const location = useLocation();
const authenticated = useAuthStore((state) => state.isAuthenticated());
const moduleAccess = useAuthStore((state) => state.moduleAccess);
const requestedModule = getGuardedAiModuleForPath(location.pathname);
const allowsAnonymousImageWorkspace = imageWorkspaceLocalDevelopment
&& (location.pathname === '/image-canvas'
|| location.pathname.startsWith('/image-canvas/')
@@ -176,6 +165,10 @@ function ProtectedLayout({
);
}
if (authenticated && requestedModule && !isAiModuleAllowed(requestedModule, moduleAccess)) {
return <Navigate to={AI_MODULE_SELECTION_PATH} replace />;
}
return <MainLayout />;
}
@@ -264,6 +257,9 @@ function App() {
const authInitialized = useAuthStore((state) => state.initialized);
const authenticated = useAuthStore((state) => state.isAuthenticated());
const authAccessToken = useAuthStore((state) => state.accessToken);
const programmingModuleAllowed = useAuthStore(
(state) => state.moduleAccess.programming,
);
const bootstrapUserSync = useUserSyncStore((state) => state.bootstrap);
const setupReady = setupComplete || skipSetupForE2E || rendererOnlyPreview;
const authRequired = !skipSetupForE2E && !rendererOnlyPreview;
@@ -325,9 +321,10 @@ function App() {
useEffect(() => {
if (rendererOnlyPreview) return;
if (!setupReady) return;
if (!programmingModuleAllowed) return;
if (!isProgrammingRoute(location.pathname)) return;
initProviders();
}, [initProviders, location.pathname, rendererOnlyPreview, setupReady]);
}, [initProviders, location.pathname, programmingModuleAllowed, rendererOnlyPreview, setupReady]);
useEffect(() => {
if (rendererOnlyPreview) return;