Skip to content

Commit 854b785

Browse files
committed
enforce connection management perms to owner (#253)
* enforce conneciton management perms to owner * fix formatting * more formatting * naming nits * fix var name error * change empty repo set copy if auth is disabled
1 parent 61ea4ae commit 854b785

File tree

12 files changed

+175
-66
lines changed

12 files changed

+175
-66
lines changed

packages/web/src/actions.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ export const createConnection = async (name: string, type: string, connectionCon
479479
return {
480480
id: connection.id,
481481
}
482-
})
482+
}, OrgRole.OWNER)
483483
));
484484

485485
export const updateConnectionDisplayName = async (connectionId: number, name: string, domain: string): Promise<{ success: boolean } | ServiceError> => sew(() =>
@@ -520,7 +520,7 @@ export const updateConnectionDisplayName = async (connectionId: number, name: st
520520
return {
521521
success: true,
522522
}
523-
})
523+
}, OrgRole.OWNER)
524524
));
525525

526526
export const updateConnectionConfigAndScheduleSync = async (connectionId: number, config: string, domain: string): Promise<{ success: boolean } | ServiceError> => sew(() =>
@@ -560,7 +560,7 @@ export const updateConnectionConfigAndScheduleSync = async (connectionId: number
560560
return {
561561
success: true,
562562
}
563-
})
563+
}, OrgRole.OWNER)
564564
));
565565

566566
export const flagConnectionForSync = async (connectionId: number, domain: string): Promise<{ success: boolean } | ServiceError> => sew(() =>
@@ -623,7 +623,7 @@ export const deleteConnection = async (connectionId: number, domain: string): Pr
623623
return {
624624
success: true,
625625
}
626-
})
626+
}, OrgRole.OWNER)
627627
));
628628

629629
export const getCurrentUserRole = async (domain: string): Promise<OrgRole | ServiceError> => sew(() =>
@@ -1377,6 +1377,26 @@ export const getSubscriptionData = async (domain: string) => sew(() =>
13771377
})
13781378
));
13791379

1380+
export const getOrgMembership = async (domain: string) => sew(() =>
1381+
withAuth(async (session) =>
1382+
withOrgMembership(session, domain, async ({ orgId }) => {
1383+
const membership = await prisma.userToOrg.findUnique({
1384+
where: {
1385+
orgId_userId: {
1386+
orgId,
1387+
userId: session.user.id,
1388+
}
1389+
}
1390+
});
1391+
1392+
if (!membership) {
1393+
return notFound();
1394+
}
1395+
1396+
return membership;
1397+
})
1398+
));
1399+
13801400
export const getOrgMembers = async (domain: string) => sew(() =>
13811401
withAuth(async (session) =>
13821402
withOrgMembership(session, domain, async ({ orgId }) => {

packages/web/src/app/[domain]/components/repositorySnapshot.tsx

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
import { RepoIndexingStatus } from "@sourcebot/db";
1717
import { SymbolIcon } from "@radix-ui/react-icons";
1818

19-
export function RepositorySnapshot() {
19+
export function RepositorySnapshot({ authEnabled }: { authEnabled: boolean }) {
2020
const domain = useDomain();
2121

2222
const { data: repos, isPending, isError } = useQuery({
@@ -44,7 +44,7 @@ export function RepositorySnapshot() {
4444
)
4545
} else if (numIndexedRepos == 0) {
4646
return (
47-
<EmptyRepoState domain={domain} />
47+
<EmptyRepoState domain={domain} authEnabled={authEnabled} />
4848
)
4949
}
5050

@@ -65,19 +65,31 @@ export function RepositorySnapshot() {
6565
)
6666
}
6767

68-
function EmptyRepoState({ domain }: { domain: string }) {
68+
function EmptyRepoState({ domain, authEnabled }: { domain: string, authEnabled: boolean }) {
6969
return (
7070
<div className="flex flex-col items-center gap-3">
7171
<span className="text-sm">No repositories found</span>
7272

7373
<div className="w-full max-w-lg">
7474
<div className="flex flex-row items-center gap-2 border rounded-md p-4 justify-center">
7575
<span className="text-sm text-muted-foreground">
76-
Create a{" "}
77-
<Link href={`/${domain}/connections`} className="text-blue-500 hover:underline inline-flex items-center gap-1">
78-
connection
79-
</Link>{" "}
80-
to start indexing repositories
76+
{authEnabled ? (
77+
<>
78+
Create a{" "}
79+
<Link href={`/${domain}/connections`} className="text-blue-500 hover:underline inline-flex items-center gap-1">
80+
connection
81+
</Link>{" "}
82+
to start indexing repositories
83+
</>
84+
) : (
85+
<>
86+
Create a {" "}
87+
<Link href={`https://docs.sourcebot.dev/self-hosting`} className="text-blue-500 hover:underline inline-flex items-center gap-1" target="_blank">
88+
configuration file
89+
</Link>{" "}
90+
to start indexing repositories
91+
</>
92+
)}
8193
</span>
8294
</div>
8395
</div>

packages/web/src/app/[domain]/connections/[id]/components/configSetting.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ interface ConfigSettingProps {
3232
connectionId: number;
3333
config: string;
3434
type: string;
35+
disabled?: boolean;
3536
}
3637

3738
export const ConfigSetting = (props: ConfigSettingProps) => {
@@ -83,10 +84,12 @@ function ConfigSettingInternal<T>({
8384
quickActions,
8485
schema,
8586
type,
87+
disabled,
8688
}: ConfigSettingProps & {
8789
quickActions?: QuickAction<T>[],
8890
schema: Schema,
8991
type: CodeHostType,
92+
disabled?: boolean,
9093
}) {
9194
const { toast } = useToast();
9295
const router = useRouter();
@@ -237,7 +240,7 @@ function ConfigSettingInternal<T>({
237240
<Button
238241
size="sm"
239242
type="submit"
240-
disabled={isLoading}
243+
disabled={isLoading || disabled}
241244
>
242245
{isLoading && <Loader2 className="animate-spin mr-2" />}
243246
{isLoading ? 'Syncing...' : 'Save'}

packages/web/src/app/[domain]/connections/[id]/components/deleteConnectionSetting.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ import useCaptureEvent from "@/hooks/useCaptureEvent";
2323

2424
interface DeleteConnectionSettingProps {
2525
connectionId: number;
26+
disabled?: boolean;
2627
}
2728

2829
export const DeleteConnectionSetting = ({
2930
connectionId,
31+
disabled,
3032
}: DeleteConnectionSettingProps) => {
3133
const [isDialogOpen, setIsDialogOpen] = useState(false);
3234
const [isLoading, setIsLoading] = useState(false);
@@ -73,7 +75,7 @@ export const DeleteConnectionSetting = ({
7375
<Button
7476
variant="destructive"
7577
className="mt-4"
76-
disabled={isLoading}
78+
disabled={isLoading || disabled}
7779
>
7880
{isLoading && <Loader2 className="animate-spin mr-2" />}
7981
Delete

packages/web/src/app/[domain]/connections/[id]/components/displayNameSetting.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ const formSchema = z.object({
2121
interface DisplayNameSettingProps {
2222
connectionId: number;
2323
name: string;
24+
disabled?: boolean;
2425
}
2526

2627
export const DisplayNameSetting = ({
2728
connectionId,
2829
name,
30+
disabled,
2931
}: DisplayNameSettingProps) => {
3032
const { toast } = useToast();
3133
const router = useRouter();
@@ -85,7 +87,7 @@ export const DisplayNameSetting = ({
8587
<Button
8688
size="sm"
8789
type="submit"
88-
disabled={isLoading}
90+
disabled={isLoading || disabled}
8991
>
9092
{isLoading && <Loader2 className="animate-spin mr-2" />}
9193
Save

packages/web/src/app/[domain]/connections/[id]/page.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ import { DisplayNameSetting } from "./components/displayNameSetting"
1717
import { RepoList } from "./components/repoList"
1818
import { getConnectionByDomain } from "@/data/connection"
1919
import { Overview } from "./components/overview"
20-
20+
import { getOrgMembership } from "@/actions"
21+
import { isServiceError } from "@/lib/utils"
22+
import { notFound } from "next/navigation"
23+
import { OrgRole } from "@sourcebot/db"
2124
interface ConnectionManagementPageProps {
2225
params: {
2326
domain: string
@@ -34,6 +37,12 @@ export default async function ConnectionManagementPage({ params, searchParams }:
3437
return <NotFound className="flex w-full h-full items-center justify-center" message="Connection not found" />
3538
}
3639

40+
const membership = await getOrgMembership(params.domain);
41+
if (isServiceError(membership)) {
42+
return notFound();
43+
}
44+
45+
const isOwner = membership.role === OrgRole.OWNER;
3746
const currentTab = searchParams.tab || "overview";
3847

3948
return (
@@ -81,13 +90,14 @@ export default async function ConnectionManagementPage({ params, searchParams }:
8190
value="settings"
8291
className="flex flex-col gap-6"
8392
>
84-
<DisplayNameSetting connectionId={connection.id} name={connection.name} />
93+
<DisplayNameSetting connectionId={connection.id} name={connection.name} disabled={!isOwner} />
8594
<ConfigSetting
8695
connectionId={connection.id}
8796
type={connection.connectionType}
8897
config={JSON.stringify(connection.config, null, 2)}
98+
disabled={!isOwner}
8999
/>
90-
<DeleteConnectionSetting connectionId={connection.id} />
100+
<DeleteConnectionSetting connectionId={connection.id} disabled={!isOwner} />
91101
</TabsContent>
92102
</Tabs>
93103
)

packages/web/src/app/[domain]/connections/components/connectionList/connectionListItem.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ interface ConnectionListItemProps {
3232
editedAt: Date;
3333
syncedAt?: Date;
3434
failedRepos?: { repoId: number, repoName: string }[];
35+
disabled: boolean;
3536
}
3637

3738
export const ConnectionListItem = ({
@@ -43,6 +44,7 @@ export const ConnectionListItem = ({
4344
editedAt,
4445
syncedAt,
4546
failedRepos,
47+
disabled,
4648
}: ConnectionListItemProps) => {
4749
const statusDisplayName = useMemo(() => {
4850
switch (status) {
@@ -111,7 +113,7 @@ export const ConnectionListItem = ({
111113
)
112114
}
113115
</p>
114-
<ConnectionListItemManageButton id={id} />
116+
<ConnectionListItemManageButton id={id} disabled={disabled} />
115117
</div>
116118
</div>
117119
)

packages/web/src/app/[domain]/connections/components/connectionList/connectionListItemManageButton.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import { useDomain } from "@/hooks/useDomain";
77

88
interface ConnectionListItemManageButtonProps {
99
id: string;
10+
disabled: boolean;
1011
}
1112

1213
export const ConnectionListItemManageButton = ({
13-
id
14+
id,
15+
disabled,
1416
}: ConnectionListItemManageButtonProps) => {
1517
const captureEvent = useCaptureEvent()
1618
const router = useRouter();
@@ -21,9 +23,12 @@ export const ConnectionListItemManageButton = ({
2123
variant="outline"
2224
size={"sm"}
2325
className="ml-4"
26+
disabled={disabled}
2427
onClick={() => {
25-
captureEvent('wa_connection_list_item_manage_pressed', {})
26-
router.push(`/${domain}/connections/${id}`)
28+
if (!disabled) {
29+
captureEvent('wa_connection_list_item_manage_pressed', {})
30+
router.push(`/${domain}/connections/${id}`)
31+
}
2732
}}
2833
>
2934
Manage

packages/web/src/app/[domain]/connections/components/connectionList/index.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ import { Search } from "lucide-react";
1212
import { Input } from "@/components/ui/input";
1313
import { useMemo, useState } from "react";
1414
import { MultiSelect } from "@/components/ui/multi-select";
15+
import { OrgRole } from "@sourcebot/db";
16+
1517
interface ConnectionListProps {
1618
className?: string;
19+
role: OrgRole;
1720
}
1821

1922
const convertSyncStatus = (status: ConnectionSyncStatus) => {
@@ -35,6 +38,7 @@ const convertSyncStatus = (status: ConnectionSyncStatus) => {
3538

3639
export const ConnectionList = ({
3740
className,
41+
role,
3842
}: ConnectionListProps) => {
3943
const domain = useDomain();
4044
const [searchQuery, setSearchQuery] = useState("");
@@ -127,6 +131,7 @@ export const ConnectionList = ({
127131
repoId: repo.id,
128132
repoName: repo.name,
129133
}))}
134+
disabled={role !== OrgRole.OWNER}
130135
/>
131136
))
132137
) : (

0 commit comments

Comments
 (0)