mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
fix(database): restrict db delete to agent-owning org (system agents admin-only)
- UI: delete button gated by canDeleteDatabases prop threaded from each page (org page: only org-created agents; admin page: system agents) - authz: org path requires agent.organizationId === active org; drop join-based attribution so a system agent merely linked to an org is admin-delete-only
This commit is contained in:
@@ -81,7 +81,7 @@ export default async function RoutePage(
|
|||||||
</PageDescription>
|
</PageDescription>
|
||||||
)}
|
)}
|
||||||
<PageContent className="flex flex-col w-full h-full justify-between gap-6">
|
<PageContent className="flex flex-col w-full h-full justify-between gap-6">
|
||||||
<AgentContentPage agent={agent} edgeKey={edgeKey} />
|
<AgentContentPage agent={agent} edgeKey={edgeKey} canDeleteDatabases={true} />
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export default async function RoutePage(
|
|||||||
</PageDescription>
|
</PageDescription>
|
||||||
)}
|
)}
|
||||||
<PageContent className="flex flex-col w-full h-full justify-between gap-6">
|
<PageContent className="flex flex-col w-full h-full justify-between gap-6">
|
||||||
<AgentContentPage agent={agent} edgeKey={edgeKey}/>
|
<AgentContentPage agent={agent} edgeKey={edgeKey} canDeleteDatabases={!!agent.organizationId}/>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ import {HealthcheckLog} from "@/db/schema/15_healthcheck-log";
|
|||||||
type AgentContentPageProps = {
|
type AgentContentPageProps = {
|
||||||
edgeKey: string;
|
edgeKey: string;
|
||||||
agent: AgentWithDatabases
|
agent: AgentWithDatabases
|
||||||
|
canDeleteDatabases?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AgentContentPage = ({edgeKey, agent: initialAgent}: AgentContentPageProps) => {
|
export const AgentContentPage = ({edgeKey, agent: initialAgent, canDeleteDatabases = false}: AgentContentPageProps) => {
|
||||||
|
|
||||||
const {data} = useQuery({
|
const {data} = useQuery({
|
||||||
queryKey: ["agent-data", initialAgent.id],
|
queryKey: ["agent-data", initialAgent.id],
|
||||||
@@ -121,6 +122,7 @@ export const AgentContentPage = ({edgeKey, agent: initialAgent}: AgentContentPag
|
|||||||
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
||||||
)}
|
)}
|
||||||
cardItem={AgentDatabaseCard}
|
cardItem={AgentDatabaseCard}
|
||||||
|
canDeleteDatabases={canDeleteDatabases}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -4,20 +4,24 @@ import {Database} from "@/db/schema/07_database";
|
|||||||
import { DatabaseCard } from "@/components/common/database-card";
|
import { DatabaseCard } from "@/components/common/database-card";
|
||||||
import { DatabaseDeleteButton } from "@/features/agents/components/database-delete-button";
|
import { DatabaseDeleteButton } from "@/features/agents/components/database-delete-button";
|
||||||
|
|
||||||
export type agentDatabaseCardProps = {
|
export type AgentDatabaseCardProps = {
|
||||||
data: Database;
|
data: Database;
|
||||||
|
canDeleteDatabases?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AgentDatabaseCard = (props: agentDatabaseCardProps) => {
|
export const AgentDatabaseCard = (props: AgentDatabaseCardProps) => {
|
||||||
const {data: database} = props;
|
const { data: database, canDeleteDatabases = false } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DatabaseCard
|
<DatabaseCard
|
||||||
withDetails={false}
|
withDetails={false}
|
||||||
data={database}
|
data={database}
|
||||||
deleteButton={
|
deleteButton={
|
||||||
database.agentId ? (
|
canDeleteDatabases && database.agentId ? (
|
||||||
<DatabaseDeleteButton databaseId={database.id} agentId={database.agentId}/>
|
<DatabaseDeleteButton
|
||||||
|
databaseId={database.id}
|
||||||
|
agentId={database.agentId}
|
||||||
|
/>
|
||||||
) : undefined
|
) : undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -35,11 +35,7 @@ async function assertCanDeleteDatabase(databaseId: string): Promise<void> {
|
|||||||
const database = await db.query.database.findFirst({
|
const database = await db.query.database.findFirst({
|
||||||
where: eq(drizzleDb.schemas.database.id, databaseId),
|
where: eq(drizzleDb.schemas.database.id, databaseId),
|
||||||
with: {
|
with: {
|
||||||
agent: {
|
agent: true,
|
||||||
with: {
|
|
||||||
organizations: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -64,10 +60,11 @@ async function assertCanDeleteDatabase(databaseId: string): Promise<void> {
|
|||||||
const canManage = activeMember
|
const canManage = activeMember
|
||||||
? computeOrganizationPermissions(activeMember).canManageAgents
|
? computeOrganizationPermissions(activeMember).canManageAgents
|
||||||
: false;
|
: false;
|
||||||
|
// Only the organization that CREATED the agent may delete its databases.
|
||||||
|
// A system agent merely attributed to an org via the join table is
|
||||||
|
// handled by the isAdmin branch above (agent.organizationId === null).
|
||||||
const hasAccess =
|
const hasAccess =
|
||||||
!!organization &&
|
!!organization && agent.organizationId === organization.id;
|
||||||
(agent.organizationId === organization.id ||
|
|
||||||
agent.organizations.some((o) => o.organizationId === organization.id));
|
|
||||||
authorized = canManage && hasAccess;
|
authorized = canManage && hasAccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user