Skip to content

Commit 11efaa6

Browse files
Add SOURCEBOT_AUTH_ENABLED env-var
1 parent aacbb4d commit 11efaa6

File tree

19 files changed

+290
-255
lines changed

19 files changed

+290
-255
lines changed

packages/web/src/actions.ts

Lines changed: 63 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { render } from "@react-email/components";
2323
import InviteUserEmail from "./emails/inviteUserEmail";
2424
import { createTransport } from "nodemailer";
2525
import { orgDomainSchema, orgNameSchema, repositoryQuerySchema } from "./lib/schemas";
26-
import { RepositoryQuery, TenancyMode } from "./lib/types";
26+
import { TenancyMode } from "./lib/types";
2727
import { MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME, SINGLE_TENANT_USER_EMAIL, SINGLE_TENANT_USER_ID } from "./lib/constants";
2828
import { stripeClient } from "./lib/stripe";
2929
import { IS_BILLING_ENABLED } from "./lib/stripe";
@@ -32,19 +32,27 @@ const ajv = new Ajv({
3232
validateFormats: false,
3333
});
3434

35-
export const withAuth = async <T>(fn: (session: Session) => Promise<T>) => {
36-
if (env.SOURCEBOT_TENANCY_MODE === 'single') {
37-
return fn({
38-
user: {
39-
id: SINGLE_TENANT_USER_ID,
40-
email: SINGLE_TENANT_USER_EMAIL,
41-
},
42-
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30).toISOString(),
43-
});
44-
}
45-
35+
export const withAuth = async <T>(fn: (session: Session) => Promise<T>, allowSingleTenantUnauthedAccess: boolean = false) => {
4636
const session = await auth();
4737
if (!session) {
38+
if (
39+
env.SOURCEBOT_TENANCY_MODE === 'single' &&
40+
env.SOURCEBOT_AUTH_ENABLED === 'false' &&
41+
allowSingleTenantUnauthedAccess === true
42+
) {
43+
// To allow for unauthed acccess in single-tenant mode, we can
44+
// create a fake session with the default user. This user has membership
45+
// in the default org.
46+
// @see: initialize.ts
47+
return fn({
48+
user: {
49+
id: SINGLE_TENANT_USER_ID,
50+
email: SINGLE_TENANT_USER_EMAIL,
51+
},
52+
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30).toISOString(),
53+
});
54+
}
55+
4856
return notAuthenticated();
4957
}
5058
return fn(session);
@@ -135,28 +143,27 @@ export const createOrg = (name: string, domain: string): Promise<{ id: number }
135143
}));
136144

137145
export const updateOrgName = async (name: string, domain: string) =>
138-
withTenancyModeEnforcement('multi', () =>
139-
withAuth((session) =>
140-
withOrgMembership(session, domain, async ({ orgId }) => {
141-
const { success } = orgNameSchema.safeParse(name);
142-
if (!success) {
143-
return {
144-
statusCode: StatusCodes.BAD_REQUEST,
145-
errorCode: ErrorCode.INVALID_REQUEST_BODY,
146-
message: "Invalid organization url",
147-
} satisfies ServiceError;
148-
}
146+
withAuth((session) =>
147+
withOrgMembership(session, domain, async ({ orgId }) => {
148+
const { success } = orgNameSchema.safeParse(name);
149+
if (!success) {
150+
return {
151+
statusCode: StatusCodes.BAD_REQUEST,
152+
errorCode: ErrorCode.INVALID_REQUEST_BODY,
153+
message: "Invalid organization url",
154+
} satisfies ServiceError;
155+
}
149156

150-
await prisma.org.update({
151-
where: { id: orgId },
152-
data: { name },
153-
});
157+
await prisma.org.update({
158+
where: { id: orgId },
159+
data: { name },
160+
});
154161

155-
return {
156-
success: true,
157-
}
158-
}, /* minRequiredRole = */ OrgRole.OWNER)
159-
));
162+
return {
163+
success: true,
164+
}
165+
}, /* minRequiredRole = */ OrgRole.OWNER)
166+
);
160167

161168
export const updateOrgDomain = async (newDomain: string, existingDomain: string) =>
162169
withTenancyModeEnforcement('multi', () =>
@@ -226,25 +233,23 @@ export const completeOnboarding = async (domain: string): Promise<{ success: boo
226233
);
227234

228235
export const getSecrets = (domain: string): Promise<{ createdAt: Date; key: string; }[] | ServiceError> =>
229-
withTenancyModeEnforcement('multi', () =>
230-
withAuth((session) =>
231-
withOrgMembership(session, domain, async ({ orgId }) => {
232-
const secrets = await prisma.secret.findMany({
233-
where: {
234-
orgId,
235-
},
236-
select: {
237-
key: true,
238-
createdAt: true
239-
}
240-
});
241-
242-
return secrets.map((secret) => ({
243-
key: secret.key,
244-
createdAt: secret.createdAt,
245-
}));
236+
withAuth((session) =>
237+
withOrgMembership(session, domain, async ({ orgId }) => {
238+
const secrets = await prisma.secret.findMany({
239+
where: {
240+
orgId,
241+
},
242+
select: {
243+
key: true,
244+
createdAt: true
245+
}
246+
});
246247

247-
})));
248+
return secrets.map((secret) => ({
249+
key: secret.key,
250+
createdAt: secret.createdAt,
251+
}));
252+
}));
248253

249254
export const createSecret = async (key: string, value: string, domain: string): Promise<{ success: boolean } | ServiceError> =>
250255
withAuth((session) =>
@@ -294,8 +299,7 @@ export const checkIfSecretExists = async (key: string, domain: string): Promise<
294299
});
295300

296301
return !!secret;
297-
})
298-
);
302+
}));
299303

300304
export const deleteSecret = async (key: string, domain: string): Promise<{ success: boolean } | ServiceError> =>
301305
withAuth((session) =>
@@ -379,9 +383,9 @@ export const getConnectionInfo = async (connectionId: number, domain: string) =>
379383
numLinkedRepos: connection.repos.length,
380384
}
381385
})
382-
)
386+
);
383387

384-
export const getRepos = async (domain: string, filter: { status?: RepoIndexingStatus[], connectionId?: number } = {}): Promise<RepositoryQuery[] | ServiceError> =>
388+
export const getRepos = async (domain: string, filter: { status?: RepoIndexingStatus[], connectionId?: number } = {}) =>
385389
withAuth((session) =>
386390
withOrgMembership(session, domain, async ({ orgId }) => {
387391
const repos = await prisma.repo.findMany({
@@ -420,8 +424,8 @@ export const getRepos = async (domain: string, filter: { status?: RepoIndexingSt
420424
indexedAt: repo.indexedAt ?? undefined,
421425
repoIndexingStatus: repo.repoIndexingStatus,
422426
}));
423-
})
424-
);
427+
}
428+
), /* allowSingleTenantUnauthedAccess = */ true);
425429

426430
export const createConnection = async (name: string, type: string, connectionConfig: string, domain: string): Promise<{ id: number } | ServiceError> =>
427431
withAuth((session) =>
@@ -443,7 +447,8 @@ export const createConnection = async (name: string, type: string, connectionCon
443447
return {
444448
id: connection.id,
445449
}
446-
}));
450+
})
451+
);
447452

448453
export const updateConnectionDisplayName = async (connectionId: number, name: string, domain: string): Promise<{ success: boolean } | ServiceError> =>
449454
withAuth((session) =>
@@ -1085,7 +1090,7 @@ export const createStripeCheckoutSession = async (domain: string) =>
10851090
url: stripeSession.url,
10861091
}
10871092
})
1088-
)
1093+
);
10891094

10901095
export const getCustomerPortalSessionLink = async (domain: string): Promise<string | ServiceError> =>
10911096
withAuth((session) =>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export const ImportSecretDialog = ({ open, onOpenChange, onSecretCreated, codeHo
6363
const response = await createSecret(data.key, data.value, domain);
6464
if (isServiceError(response)) {
6565
toast({
66-
description: `❌ Failed to create secret`
66+
description: `❌ Failed to create secret. Reason: ${response.message}`
6767
});
6868
captureEvent('wa_secret_combobox_import_secret_fail', {
6969
type: codeHostType,

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

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { ProgressNavIndicator } from "./progressNavIndicator";
1313
import { SourcebotLogo } from "@/app/components/sourcebotLogo";
1414
import { TrialNavIndicator } from "./trialNavIndicator";
1515
import { IS_BILLING_ENABLED } from "@/lib/stripe";
16+
import { env } from "@/env.mjs";
1617
const SOURCEBOT_DISCORD_URL = "https://discord.gg/6Fhp27x7Pb";
1718
const SOURCEBOT_GITHUB_URL = "https://github.com/sourcebot-dev/sourcebot";
1819

@@ -39,10 +40,14 @@ export const NavigationMenu = async ({
3940
/>
4041
</Link>
4142

42-
<OrgSelector
43-
domain={domain}
44-
/>
45-
<Separator orientation="vertical" className="h-6 mx-2" />
43+
{env.SOURCEBOT_TENANCY_MODE === 'multi' && (
44+
<>
45+
<OrgSelector
46+
domain={domain}
47+
/>
48+
<Separator orientation="vertical" className="h-6 mx-2" />
49+
</>
50+
)}
4651

4752
<NavigationMenuBase>
4853
<NavigationMenuList>
@@ -60,20 +65,24 @@ export const NavigationMenu = async ({
6065
</NavigationMenuLink>
6166
</Link>
6267
</NavigationMenuItem>
63-
<NavigationMenuItem>
64-
<Link href={`/${domain}/connections`} legacyBehavior passHref>
65-
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
66-
Connections
67-
</NavigationMenuLink>
68-
</Link>
69-
</NavigationMenuItem>
70-
<NavigationMenuItem>
71-
<Link href={`/${domain}/settings`} legacyBehavior passHref>
72-
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
73-
Settings
74-
</NavigationMenuLink>
75-
</Link>
76-
</NavigationMenuItem>
68+
{env.SOURCEBOT_AUTH_ENABLED === 'true' && (
69+
<NavigationMenuItem>
70+
<Link href={`/${domain}/connections`} legacyBehavior passHref>
71+
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
72+
Connections
73+
</NavigationMenuLink>
74+
</Link>
75+
</NavigationMenuItem>
76+
)}
77+
{env.SOURCEBOT_AUTH_ENABLED === 'true' && (
78+
<NavigationMenuItem>
79+
<Link href={`/${domain}/settings`} legacyBehavior passHref>
80+
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
81+
Settings
82+
</NavigationMenuLink>
83+
</Link>
84+
</NavigationMenuItem>
85+
)}
7786
</NavigationMenuList>
7887
</NavigationMenuBase>
7988
</div>

packages/web/src/app/[domain]/layout.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { SyntaxReferenceGuide } from "./components/syntaxReferenceGuide";
1414
import { SyntaxGuideProvider } from "./components/syntaxGuideProvider";
1515
import { IS_BILLING_ENABLED } from "@/lib/stripe";
1616
import { env } from "@/env.mjs";
17-
17+
import { notFound, redirect } from "next/navigation";
1818
interface LayoutProps {
1919
children: React.ReactNode,
2020
params: { domain: string }
@@ -27,13 +27,13 @@ export default async function Layout({
2727
const org = await getOrgFromDomain(domain);
2828

2929
if (!org) {
30-
return <PageNotFound />
30+
return notFound();
3131
}
3232

33-
if (env.SOURCEBOT_TENANCY_MODE === 'multi') {
33+
if (env.SOURCEBOT_AUTH_ENABLED === 'true') {
3434
const session = await auth();
3535
if (!session) {
36-
return <PageNotFound />
36+
redirect('/login');
3737
}
3838

3939
const membership = await prisma.userToOrg.findUnique({
@@ -46,10 +46,8 @@ export default async function Layout({
4646
});
4747

4848
if (!membership) {
49-
return <PageNotFound />
49+
return notFound();
5050
}
51-
} else {
52-
// no-op
5351
}
5452

5553
if (!org.isOnboarded) {

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

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,48 +43,48 @@ export default async function Home({ params: { domain } }: { params: { domain: s
4343
title="Search in files or paths"
4444
>
4545
<QueryExample>
46-
<Query query="test todo">test todo</Query> <QueryExplanation>(both test and todo)</QueryExplanation>
46+
<Query query="test todo" domain={domain}>test todo</Query> <QueryExplanation>(both test and todo)</QueryExplanation>
4747
</QueryExample>
4848
<QueryExample>
49-
<Query query="test or todo">test <Highlight>or</Highlight> todo</Query> <QueryExplanation>(either test or todo)</QueryExplanation>
49+
<Query query="test or todo" domain={domain}>test <Highlight>or</Highlight> todo</Query> <QueryExplanation>(either test or todo)</QueryExplanation>
5050
</QueryExample>
5151
<QueryExample>
52-
<Query query={`"exit boot"`}>{`"exit boot"`}</Query> <QueryExplanation>(exact match)</QueryExplanation>
52+
<Query query={`"exit boot"`} domain={domain}>{`"exit boot"`}</Query> <QueryExplanation>(exact match)</QueryExplanation>
5353
</QueryExample>
5454
<QueryExample>
55-
<Query query="TODO case:yes">TODO <Highlight>case:</Highlight>yes</Query> <QueryExplanation>(case sensitive)</QueryExplanation>
55+
<Query query="TODO case:yes" domain={domain}>TODO <Highlight>case:</Highlight>yes</Query> <QueryExplanation>(case sensitive)</QueryExplanation>
5656
</QueryExample>
5757
</HowToSection>
5858
<HowToSection
5959
title="Filter results"
6060
>
6161
<QueryExample>
62-
<Query query="file:README setup"><Highlight>file:</Highlight>README setup</Query> <QueryExplanation>(by filename)</QueryExplanation>
62+
<Query query="file:README setup" domain={domain}><Highlight>file:</Highlight>README setup</Query> <QueryExplanation>(by filename)</QueryExplanation>
6363
</QueryExample>
6464
<QueryExample>
65-
<Query query="repo:torvalds/linux test"><Highlight>repo:</Highlight>torvalds/linux test</Query> <QueryExplanation>(by repo)</QueryExplanation>
65+
<Query query="repo:torvalds/linux test" domain={domain}><Highlight>repo:</Highlight>torvalds/linux test</Query> <QueryExplanation>(by repo)</QueryExplanation>
6666
</QueryExample>
6767
<QueryExample>
68-
<Query query="lang:typescript"><Highlight>lang:</Highlight>typescript</Query> <QueryExplanation>(by language)</QueryExplanation>
68+
<Query query="lang:typescript" domain={domain}><Highlight>lang:</Highlight>typescript</Query> <QueryExplanation>(by language)</QueryExplanation>
6969
</QueryExample>
7070
<QueryExample>
71-
<Query query="rev:HEAD"><Highlight>rev:</Highlight>HEAD</Query> <QueryExplanation>(by branch or tag)</QueryExplanation>
71+
<Query query="rev:HEAD" domain={domain}><Highlight>rev:</Highlight>HEAD</Query> <QueryExplanation>(by branch or tag)</QueryExplanation>
7272
</QueryExample>
7373
</HowToSection>
7474
<HowToSection
7575
title="Advanced"
7676
>
7777
<QueryExample>
78-
<Query query="file:\.py$"><Highlight>file:</Highlight>{`\\.py$`}</Query> <QueryExplanation>{`(files that end in ".py")`}</QueryExplanation>
78+
<Query query="file:\.py$" domain={domain}><Highlight>file:</Highlight>{`\\.py$`}</Query> <QueryExplanation>{`(files that end in ".py")`}</QueryExplanation>
7979
</QueryExample>
8080
<QueryExample>
81-
<Query query="sym:main"><Highlight>sym:</Highlight>main</Query> <QueryExplanation>{`(symbols named "main")`}</QueryExplanation>
81+
<Query query="sym:main" domain={domain}><Highlight>sym:</Highlight>main</Query> <QueryExplanation>{`(symbols named "main")`}</QueryExplanation>
8282
</QueryExample>
8383
<QueryExample>
84-
<Query query="todo -lang:c">todo <Highlight>-lang:c</Highlight></Query> <QueryExplanation>(negate filter)</QueryExplanation>
84+
<Query query="todo -lang:c" domain={domain}>todo <Highlight>-lang:c</Highlight></Query> <QueryExplanation>(negate filter)</QueryExplanation>
8585
</QueryExample>
8686
<QueryExample>
87-
<Query query="content:README"><Highlight>content:</Highlight>README</Query> <QueryExplanation>(search content only)</QueryExplanation>
87+
<Query query="content:README" domain={domain}><Highlight>content:</Highlight>README</Query> <QueryExplanation>(search content only)</QueryExplanation>
8888
</QueryExample>
8989
</HowToSection>
9090
</div>
@@ -130,10 +130,10 @@ const QueryExplanation = ({ children }: { children: React.ReactNode }) => {
130130
)
131131
}
132132

133-
const Query = ({ query, children }: { query: string, children: React.ReactNode }) => {
133+
const Query = ({ query, domain, children }: { query: string, domain: string, children: React.ReactNode }) => {
134134
return (
135135
<Link
136-
href={`/search?query=${query}`}
136+
href={`/${domain}/search?query=${query}`}
137137
className="cursor-pointer hover:underline"
138138
>
139139
{children}

packages/web/src/app/[domain]/repos/columns.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,13 @@ const StatusIndicator = ({ status }: { status: RepoIndexingStatus }) => {
9393
)
9494
}
9595

96-
export const columns = (domain: string): ColumnDef<RepositoryColumnInfo>[] => [
96+
export const columns = (domain: string, isAddNewRepoButtonVisible: boolean): ColumnDef<RepositoryColumnInfo>[] => [
9797
{
9898
accessorKey: "name",
9999
header: () => (
100100
<div className="flex items-center w-[400px]">
101101
<span>Repository</span>
102-
<AddRepoButton />
102+
{isAddNewRepoButtonVisible && <AddRepoButton />}
103103
</div>
104104
),
105105
cell: ({ row }) => {

0 commit comments

Comments
 (0)