diff --git a/CHANGELOG.md b/CHANGELOG.md
index f1248604..79daca9c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Fixed typos in UI, docs, code [#369](https://github.com/sourcebot-dev/sourcebot/pull/369)
+- Add anonymous access option to core and deprecate the `enablePublicAccess` config setting. [#385](https://github.com/sourcebot-dev/sourcebot/pull/385)
## [4.5.1] - 2025-07-14
diff --git a/demo-site-config.json b/demo-site-config.json
index 42b8b346..c689a60d 100644
--- a/demo-site-config.json
+++ b/demo-site-config.json
@@ -238,7 +238,6 @@
}
},
"settings": {
- "reindexIntervalMs": 86400000, // 24 hours
- "enablePublicAccess": true
+ "reindexIntervalMs": 86400000 // 24 hours
}
}
diff --git a/docs/docs.json b/docs/docs.json
index 3af230d7..e0834f28 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -73,7 +73,7 @@
"pages": [
"docs/configuration/auth/overview",
"docs/configuration/auth/providers",
- "docs/configuration/auth/inviting-members",
+ "docs/configuration/auth/access-settings",
"docs/configuration/auth/roles-and-permissions",
"docs/configuration/auth/faq"
]
diff --git a/docs/docs/configuration/auth/access-settings.mdx b/docs/docs/configuration/auth/access-settings.mdx
new file mode 100644
index 00000000..5bc638e7
--- /dev/null
+++ b/docs/docs/configuration/auth/access-settings.mdx
@@ -0,0 +1,40 @@
+---
+title: Access Settings
+sidebarTitle: Access settings
+---
+
+There are various settings to control how users access your Sourcebot deployment.
+
+# Anonymous access
+
+Anonymous access cannot be enabled if you have an enterprise license. If you have any questions about this restriction [reach out to us](https://www.sourcebot.dev/contact).
+
+By default, your Sourcebot deployment is gated with a login page. If you'd like users to access the deployment anonymously, you can enable anonymous access.
+
+This can be enabled by navigating to **Settings -> Access** or by setting the `FORCE_ENABLE_ANONYMOUS_ACCESS` environment variable.
+
+When accessing Sourcebot anonymously, a user's permissions are limited to that of the [Guest](/docs/configuration/auth/roles-and-permissions) role.
+
+# Member Approval
+
+By default, Sourcebot requires new members to be approved by the owner of the deployment. This section explains how approvals work and how
+to configure this behavior.
+
+### Configuration
+Member approval can be configured by the owner of the deployment by navigating to **Settings -> Members**:
+
+
+
+### Managing Requests
+
+If member approval is enabled, new members will be asked to submit a join request after signing up. They will not have access to the Sourcebot deployment
+until this request is approved by the owner.
+
+The owner can see and manage all pending join requests by navigating to **Settings -> Members**.
+
+## Invite link
+
+If member approval is required, an owner of the deployment can enable an invite link. When enabled, users
+can use this invite link to register and be automatically added to the organization without approval:
+
+
\ No newline at end of file
diff --git a/docs/docs/configuration/auth/inviting-members.mdx b/docs/docs/configuration/auth/inviting-members.mdx
deleted file mode 100644
index d67e497b..00000000
--- a/docs/docs/configuration/auth/inviting-members.mdx
+++ /dev/null
@@ -1,30 +0,0 @@
----
-title: Inviting Members
-sidebarTitle: Inviting members
----
-
-There are various ways to configure how members can join a Sourcebot deployment.
-
-## Member Approval
-
-**By default, Sourcebot requires new members to be approved by the owner of the deployment**. This section explains how approvals work and how
-to configure this behavior.
-
-### Configuration
-Member approval can be configured by the owner of the deployment by navigating to **Settings -> Members**:
-
-
-
-### Managing Requests
-
-If member approval is enabled, new members will be asked to submit a join request after signing up. They will not have access to the Sourcebot deployment
-until this request is approved by the owner.
-
-The owner can see and manage all pending join requests by navigating to **Settings -> Members**.
-
-## Invite link
-
-If member approval is required, an owner of the deployment can enable an invite link. When enabled, users
-can use this invite link to register and be automatically added to the organization without approval:
-
-
\ No newline at end of file
diff --git a/docs/docs/configuration/auth/roles-and-permissions.mdx b/docs/docs/configuration/auth/roles-and-permissions.mdx
index b639b521..e766b4ee 100644
--- a/docs/docs/configuration/auth/roles-and-permissions.mdx
+++ b/docs/docs/configuration/auth/roles-and-permissions.mdx
@@ -10,4 +10,5 @@ Each member has a role which defines their permissions within an organization:
| Role | Permission |
| :--- | :--------- |
| `Owner` | Each organization has a single `Owner`. This user has full access rights, including: connection management, organization management, and inviting new members. |
-| `Member` | Read-only access to the organization. A `Member` can search across the repos indexed by an organization's connections, but may not manage the organization or its connections. |
\ No newline at end of file
+| `Member` | Read-only access to the organization. A `Member` can search across the repos indexed by an organization's connections, as well as view the organizations configuration and member list. However, they cannot modify this configuration or invite new members. |
+| `Guest` | When accessing Sourcebot [anonymously](/docs/configuration/auth/access-settings#anonymous-access), a user has the `Guest` role. `Guest`'s can search across repos indexed by an organization's connections, but cannot view any information regarding the organizations configuration or members. |
\ No newline at end of file
diff --git a/docs/docs/configuration/environment-variables.mdx b/docs/docs/configuration/environment-variables.mdx
index 9378b023..e8fe8cf0 100644
--- a/docs/docs/configuration/environment-variables.mdx
+++ b/docs/docs/configuration/environment-variables.mdx
@@ -21,6 +21,7 @@ The following environment variables allow you to configure your Sourcebot deploy
| `DATABASE_DATA_DIR` | `$DATA_CACHE_DIR/db` |
The data directory for the default Postgres database.
Connection string of your Postgres database. By default, a Postgres database is automatically provisioned at startup within the container.
If you'd like to use a non-default schema, you can provide it as a parameter in the database url
|
| `EMAIL_FROM_ADDRESS` | `-` |
The email address that transactional emails will be sent from. See [this doc](/docs/configuration/transactional-emails) for more info.
|
+| `FORCE_ENABLE_ANONYMOUS_ACCESS` | `false` |
When enabled, [anonymous access](/docs/configuration/auth/access-settings#anonymous-access) to the organization will always be enabled
| `REDIS_DATA_DIR` | `$DATA_CACHE_DIR/redis` |
The data directory for the default Redis instance.
|
| `REDIS_URL` | `redis://localhost:6379` |
Connection string of your Redis instance. By default, a Redis database is automatically provisioned at startup within the container.
|
| `REDIS_REMOVE_ON_COMPLETE` | `0` |
Controls how many completed jobs are allowed to remain in Redis queues
|
diff --git a/docs/docs/deployment-guide.mdx b/docs/docs/deployment-guide.mdx
index 75b09dc4..7e785486 100644
--- a/docs/docs/deployment-guide.mdx
+++ b/docs/docs/deployment-guide.mdx
@@ -7,23 +7,6 @@ import SupportedPlatforms from '/snippets/platform-support.mdx'
The following guide will walk you through the steps to deploy Sourcebot on your own infrastructure. Sourcebot is distributed as a [single docker container](/docs/overview#architecture) that can be deployed to a k8s cluster, a VM, or any platform that supports docker.
-## Walkthrough video
----
-
-Watch this quick walkthrough video to learn how to deploy Sourcebot using Docker.
-
-
-
-## Step-by-step guide
----
-
Hit an issue? Please let us know on [GitHub discussions](https://github.com/sourcebot-dev/sourcebot/discussions/categories/support) or by [emailing us](mailto:team@sourcebot.dev).
diff --git a/docs/snippets/schemas/v3/index.schema.mdx b/docs/snippets/schemas/v3/index.schema.mdx
index 79bcda80..0b7907ed 100644
--- a/docs/snippets/schemas/v3/index.schema.mdx
+++ b/docs/snippets/schemas/v3/index.schema.mdx
@@ -66,7 +66,8 @@
},
"enablePublicAccess": {
"type": "boolean",
- "description": "[Sourcebot EE] When enabled, allows unauthenticated users to access Sourcebot. Requires an enterprise license with an unlimited number of seats.",
+ "deprecated": true,
+ "description": "This setting is deprecated. Please use the `FORCE_ENABLE_ANONYMOUS_ACCESS` environment variable instead.",
"default": false
}
},
@@ -180,7 +181,8 @@
},
"enablePublicAccess": {
"type": "boolean",
- "description": "[Sourcebot EE] When enabled, allows unauthenticated users to access Sourcebot. Requires an enterprise license with an unlimited number of seats.",
+ "deprecated": true,
+ "description": "This setting is deprecated. Please use the `FORCE_ENABLE_ANONYMOUS_ACCESS` environment variable instead.",
"default": false
}
},
diff --git a/packages/backend/src/constants.ts b/packages/backend/src/constants.ts
index 19bbc978..c0d77f05 100644
--- a/packages/backend/src/constants.ts
+++ b/packages/backend/src/constants.ts
@@ -15,5 +15,5 @@ export const DEFAULT_SETTINGS: Settings = {
maxRepoGarbageCollectionJobConcurrency: 8,
repoGarbageCollectionGracePeriodMs: 10 * 1000, // 10 seconds
repoIndexTimeoutMs: 1000 * 60 * 60 * 2, // 2 hours
- enablePublicAccess: false,
+ enablePublicAccess: false // deprected, use FORCE_ENABLE_ANONYMOUS_ACCESS instead
}
diff --git a/packages/schemas/src/v2/index.schema.ts b/packages/schemas/src/v2/index.schema.ts
index de564ccd..a37f2f3c 100644
--- a/packages/schemas/src/v2/index.schema.ts
+++ b/packages/schemas/src/v2/index.schema.ts
@@ -2258,4 +2258,4 @@ const schema = {
},
"additionalProperties": false
} as const;
-export { schema as indexSchema };
+export { schema as indexSchema };
\ No newline at end of file
diff --git a/packages/schemas/src/v3/index.schema.ts b/packages/schemas/src/v3/index.schema.ts
index 35e7a4fe..1eafaffe 100644
--- a/packages/schemas/src/v3/index.schema.ts
+++ b/packages/schemas/src/v3/index.schema.ts
@@ -65,7 +65,8 @@ const schema = {
},
"enablePublicAccess": {
"type": "boolean",
- "description": "[Sourcebot EE] When enabled, allows unauthenticated users to access Sourcebot. Requires an enterprise license with an unlimited number of seats.",
+ "deprecated": true,
+ "description": "This setting is deprecated. Please use the `FORCE_ENABLE_ANONYMOUS_ACCESS` environment variable instead.",
"default": false
}
},
@@ -179,7 +180,8 @@ const schema = {
},
"enablePublicAccess": {
"type": "boolean",
- "description": "[Sourcebot EE] When enabled, allows unauthenticated users to access Sourcebot. Requires an enterprise license with an unlimited number of seats.",
+ "deprecated": true,
+ "description": "This setting is deprecated. Please use the `FORCE_ENABLE_ANONYMOUS_ACCESS` environment variable instead.",
"default": false
}
},
diff --git a/packages/schemas/src/v3/index.type.ts b/packages/schemas/src/v3/index.type.ts
index d239245f..1390bf4e 100644
--- a/packages/schemas/src/v3/index.type.ts
+++ b/packages/schemas/src/v3/index.type.ts
@@ -80,7 +80,8 @@ export interface Settings {
*/
repoIndexTimeoutMs?: number;
/**
- * [Sourcebot EE] When enabled, allows unauthenticated users to access Sourcebot. Requires an enterprise license with an unlimited number of seats.
+ * @deprecated
+ * This setting is deprecated. Please use the `FORCE_ENABLE_ANONYMOUS_ACCESS` environment variable instead.
*/
enablePublicAccess?: boolean;
}
diff --git a/packages/shared/src/entitlements.ts b/packages/shared/src/entitlements.ts
index 0381e451..965989c1 100644
--- a/packages/shared/src/entitlements.ts
+++ b/packages/shared/src/entitlements.ts
@@ -33,7 +33,7 @@ export type Plan = keyof typeof planLabels;
const entitlements = [
"search-contexts",
"billing",
- "public-access",
+ "anonymous-access",
"multi-tenancy",
"sso",
"code-nav",
@@ -43,12 +43,12 @@ const entitlements = [
export type Entitlement = (typeof entitlements)[number];
const entitlementsByPlan: Record = {
- oss: [],
+ oss: ["anonymous-access"],
"cloud:team": ["billing", "multi-tenancy", "sso", "code-nav"],
"self-hosted:enterprise": ["search-contexts", "sso", "code-nav", "audit", "analytics"],
- "self-hosted:enterprise-unlimited": ["search-contexts", "public-access", "sso", "code-nav", "audit", "analytics"],
+ "self-hosted:enterprise-unlimited": ["search-contexts", "anonymous-access", "sso", "code-nav", "audit", "analytics"],
// Special entitlement for https://demo.sourcebot.dev
- "cloud:demo": ["public-access", "code-nav", "search-contexts"],
+ "cloud:demo": ["anonymous-access", "code-nav", "search-contexts"],
} as const;
diff --git a/packages/web/src/actions.ts b/packages/web/src/actions.ts
index 73dbdcf3..ed73950a 100644
--- a/packages/web/src/actions.ts
+++ b/packages/web/src/actions.ts
@@ -32,12 +32,13 @@ import { decrementOrgSeatCount, getSubscriptionForOrg } from "./ee/features/bill
import { bitbucketSchema } from "@sourcebot/schemas/v3/bitbucket.schema";
import { genericGitHostSchema } from "@sourcebot/schemas/v3/genericGitHost.schema";
import { getPlan, hasEntitlement } from "@sourcebot/shared";
-import { getPublicAccessStatus } from "./ee/features/publicAccess/publicAccess";
import JoinRequestSubmittedEmail from "./emails/joinRequestSubmittedEmail";
import JoinRequestApprovedEmail from "./emails/joinRequestApprovedEmail";
import { createLogger } from "@sourcebot/logger";
import { getAuditService } from "@/ee/features/audit/factory";
import { addUserToOrganization, orgHasAvailability } from "@/lib/authUtils";
+import { getOrgMetadata } from "@/lib/utils";
+import { getOrgFromDomain } from "./data/org";
const ajv = new Ajv({
validateFormats: false,
@@ -62,13 +63,13 @@ export const sew = async (fn: () => Promise): Promise =>
}
}
-export const withAuth = async (fn: (userId: string, apiKeyHash: string | undefined) => Promise, allowSingleTenantUnauthedAccess: boolean = false, apiKey: ApiKeyPayload | undefined = undefined) => {
+export const withAuth = async (fn: (userId: string, apiKeyHash: string | undefined) => Promise, allowAnonymousAccess: boolean = false, apiKey: ApiKeyPayload | undefined = undefined) => {
const session = await auth();
if (!session) {
// First we check if public access is enabled and supported. If not, then we check if an api key was provided. If not,
// then this is an invalid unauthed request and we return a 401.
- const publicAccessEnabled = await getPublicAccessStatus(SINGLE_TENANT_ORG_DOMAIN);
+ const anonymousAccessEnabled = await getAnonymousAccessStatus(SINGLE_TENANT_ORG_DOMAIN);
if (apiKey) {
const apiKeyOrError = await verifyApiKey(apiKey);
if (isServiceError(apiKeyOrError)) {
@@ -98,18 +99,17 @@ export const withAuth = async (fn: (userId: string, apiKeyHash: string | unde
return fn(user.id, apiKeyOrError.apiKey.hash);
} else if (
- env.SOURCEBOT_TENANCY_MODE === 'single' &&
- allowSingleTenantUnauthedAccess &&
- !isServiceError(publicAccessEnabled) &&
- publicAccessEnabled
+ allowAnonymousAccess &&
+ !isServiceError(anonymousAccessEnabled) &&
+ anonymousAccessEnabled
) {
- if (!hasEntitlement("public-access")) {
+ if (!hasEntitlement("anonymous-access")) {
const plan = getPlan();
- logger.error(`Public access isn't supported in your current plan: ${plan}. If you have a valid enterprise license key, pass it via SOURCEBOT_EE_LICENSE_KEY. For support, contact ${SOURCEBOT_SUPPORT_EMAIL}.`);
+ logger.error(`Anonymous access isn't supported in your current plan: ${plan}. For support, contact ${SOURCEBOT_SUPPORT_EMAIL}.`);
return notAuthenticated();
}
- // To support unauthed access a guest user is created in initialize.ts, which we return here
+ // To support anonymous access a guest user is created in initialize.ts, which we return here
return fn(SOURCEBOT_GUEST_USER_ID, undefined);
}
return notAuthenticated();
@@ -672,7 +672,7 @@ export const getRepos = async (domain: string, filter: { status?: RepoIndexingSt
indexedAt: repo.indexedAt ?? undefined,
repoIndexingStatus: repo.repoIndexingStatus,
}));
- }, /* minRequiredRole = */ OrgRole.GUEST), /* allowSingleTenantUnauthedAccess = */ true
+ }, /* minRequiredRole = */ OrgRole.GUEST), /* allowAnonymousAccess = */ true
));
export const getRepoInfoByName = async (repoName: string, domain: string) => sew(() =>
@@ -734,7 +734,7 @@ export const getRepoInfoByName = async (repoName: string, domain: string) => sew
indexedAt: repo.indexedAt ?? undefined,
repoIndexingStatus: repo.repoIndexingStatus,
}
- }, /* minRequiredRole = */ OrgRole.GUEST), /* allowSingleTenantUnauthedAccess = */ true
+ }, /* minRequiredRole = */ OrgRole.GUEST), /* allowAnonymousAccess = */ true
));
export const createConnection = async (name: string, type: CodeHostType, connectionConfig: string, domain: string): Promise<{ id: number } | ServiceError> => sew(() =>
@@ -933,7 +933,7 @@ export const getCurrentUserRole = async (domain: string): Promise
withOrgMembership(userId, domain, async ({ userRole }) => {
return userRole;
- }, /* minRequiredRole = */ OrgRole.GUEST), /* allowSingleTenantUnauthedAccess = */ true
+ }, /* minRequiredRole = */ OrgRole.GUEST), /* allowAnonymousAccess = */ true
));
export const createInvites = async (emails: string[], domain: string): Promise<{ success: boolean } | ServiceError> => sew(() =>
@@ -1863,7 +1863,7 @@ export const getSearchContexts = async (domain: string) => sew(() =>
name: context.name,
description: context.description ?? undefined,
}));
- }, /* minRequiredRole = */ OrgRole.GUEST), /* allowSingleTenantUnauthedAccess = */ true
+ }, /* minRequiredRole = */ OrgRole.GUEST), /* allowAnonymousAccess = */ true
));
export const getRepoImage = async (repoId: number, domain: string): Promise => sew(async () => {
@@ -1934,7 +1934,68 @@ export const getRepoImage = async (repoId: number, domain: string): Promise => sew(async () => {
+ const org = await getOrgFromDomain(domain);
+ if (!org) {
+ return {
+ statusCode: StatusCodes.NOT_FOUND,
+ errorCode: ErrorCode.NOT_FOUND,
+ message: "Organization not found",
+ } satisfies ServiceError;
+ }
+
+ // If no metadata is set we don't try to parse it since it'll result in a parse error
+ if (org.metadata === null) {
+ return false;
+ }
+
+ const orgMetadata = getOrgMetadata(org);
+ if (!orgMetadata) {
+ return {
+ statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
+ errorCode: ErrorCode.INVALID_ORG_METADATA,
+ message: "Invalid organization metadata",
+ } satisfies ServiceError;
+ }
+
+ return !!orgMetadata.anonymousAccessEnabled;
+});
+
+export const setAnonymousAccessStatus = async (domain: string, enabled: boolean): Promise => sew(async () => {
+ return await withAuth(async (userId) => {
+ return await withOrgMembership(userId, domain, async ({ org }) => {
+ const hasAnonymousAccessEntitlement = hasEntitlement("anonymous-access");
+ if (!hasAnonymousAccessEntitlement) {
+ const plan = getPlan();
+ console.error(`Anonymous access isn't supported in your current plan: ${plan}. For support, contact ${SOURCEBOT_SUPPORT_EMAIL}.`);
+ return {
+ statusCode: StatusCodes.FORBIDDEN,
+ errorCode: ErrorCode.INSUFFICIENT_PERMISSIONS,
+ message: "Anonymous access is not supported in your current plan",
+ } satisfies ServiceError;
+ }
+
+ const currentMetadata = getOrgMetadata(org);
+ const mergedMetadata = {
+ ...(currentMetadata ?? {}),
+ anonymousAccessEnabled: enabled,
+ };
+
+ await prisma.org.update({
+ where: {
+ id: org.id,
+ },
+ data: {
+ metadata: mergedMetadata,
+ },
+ });
+
+ return true;
+ }, /* minRequiredRole = */ OrgRole.OWNER);
+ });
});
////// Helpers ///////
diff --git a/packages/web/src/app/[domain]/components/submitJoinRequest.tsx b/packages/web/src/app/[domain]/components/submitJoinRequest.tsx
index b79fbfa5..7160a65c 100644
--- a/packages/web/src/app/[domain]/components/submitJoinRequest.tsx
+++ b/packages/web/src/app/[domain]/components/submitJoinRequest.tsx
@@ -27,7 +27,7 @@ export const SubmitJoinRequest = async ({ domain }: SubmitJoinRequestProps) => {
/>
-
+
diff --git a/packages/web/src/app/[domain]/layout.tsx b/packages/web/src/app/[domain]/layout.tsx
index f24accd3..6a3e34da 100644
--- a/packages/web/src/app/[domain]/layout.tsx
+++ b/packages/web/src/app/[domain]/layout.tsx
@@ -16,10 +16,9 @@ import { getSubscriptionInfo } from "@/ee/features/billing/actions";
import { PendingApprovalCard } from "./components/pendingApproval";
import { SubmitJoinRequest } from "./components/submitJoinRequest";
import { hasEntitlement } from "@sourcebot/shared";
-import { getPublicAccessStatus } from "@/ee/features/publicAccess/publicAccess";
import { env } from "@/env.mjs";
import { GcpIapAuth } from "./components/gcpIapAuth";
-import { getMemberApprovalRequired } from "@/actions";
+import { getAnonymousAccessStatus, getMemberApprovalRequired } from "@/actions";
import { JoinOrganizationCard } from "@/app/components/joinOrganizationCard";
import { LogoutEscapeHatch } from "@/app/components/logoutEscapeHatch";
@@ -39,7 +38,7 @@ export default async function Layout({
}
const session = await auth();
- const publicAccessEnabled = hasEntitlement("public-access") && await getPublicAccessStatus(domain);
+ const anonymousAccessEnabled = hasEntitlement("anonymous-access") && await getAnonymousAccessStatus(domain);
// If the user is authenticated, we must check if they're a member of the org
if (session) {
@@ -84,8 +83,8 @@ export default async function Layout({
}
}
} else {
- // If the user isn't authenticated and public access isn't enabled, we need to redirect them to the login page.
- if (!publicAccessEnabled) {
+ // If the user isn't authenticated and anonymous access isn't enabled, we need to redirect them to the login page.
+ if (!anonymousAccessEnabled) {
const ssoEntitlement = await hasEntitlement("sso");
if (ssoEntitlement && env.AUTH_EE_GCP_IAP_ENABLED && env.AUTH_EE_GCP_IAP_AUDIENCE) {
return ;
diff --git a/packages/web/src/app/[domain]/repos/page.tsx b/packages/web/src/app/[domain]/repos/page.tsx
index ece5f038..f0ffa1e8 100644
--- a/packages/web/src/app/[domain]/repos/page.tsx
+++ b/packages/web/src/app/[domain]/repos/page.tsx
@@ -7,7 +7,7 @@ export default async function ReposPage({ params: { domain } }: { params: { doma
const org = await getOrgFromDomain(domain);
if (!org) {
return
-}
+ }
return (
diff --git a/packages/web/src/app/[domain]/settings/access/page.tsx b/packages/web/src/app/[domain]/settings/access/page.tsx
new file mode 100644
index 00000000..cbce7b50
--- /dev/null
+++ b/packages/web/src/app/[domain]/settings/access/page.tsx
@@ -0,0 +1,35 @@
+import { getOrgFromDomain } from "@/data/org";
+import { OrganizationAccessSettings } from "@/app/components/organizationAccessSettings";
+
+interface AccessPageProps {
+ params: {
+ domain: string;
+ }
+}
+
+export default async function AccessPage({ params: { domain } }: AccessPageProps) {
+ const org = await getOrgFromDomain(domain);
+ if (!org) {
+ throw new Error("Organization not found");
+ }
+
+ return (
+
+
+
Access Control
+
Configure how users can access your Sourcebot deployment.{" "}
+
+ Learn more
+
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/packages/web/src/app/[domain]/settings/layout.tsx b/packages/web/src/app/[domain]/settings/layout.tsx
index 21fc834f..014bb467 100644
--- a/packages/web/src/app/[domain]/settings/layout.tsx
+++ b/packages/web/src/app/[domain]/settings/layout.tsx
@@ -64,6 +64,12 @@ export default async function SettingsLayout({
href: `/${domain}/settings/billing`,
}
] : []),
+ ...(userRoleInOrg === OrgRole.OWNER ? [
+ {
+ title: "Access",
+ href: `/${domain}/settings/access`,
+ }
+ ] : []),
{
title: (
diff --git a/packages/web/src/app/[domain]/settings/members/page.tsx b/packages/web/src/app/[domain]/settings/members/page.tsx
index aa984a93..2c23f3f7 100644
--- a/packages/web/src/app/[domain]/settings/members/page.tsx
+++ b/packages/web/src/app/[domain]/settings/members/page.tsx
@@ -12,9 +12,6 @@ import { ServiceErrorException } from "@/lib/serviceError";
import { getSeats, SOURCEBOT_UNLIMITED_SEATS } from "@sourcebot/shared";
import { RequestsList } from "./components/requestsList";
import { OrgRole } from "@prisma/client";
-import { MemberApprovalRequiredToggle } from "@/app/onboard/components/memberApprovalRequiredToggle";
-import { headers } from "next/headers";
-import { getBaseUrl, createInviteLink } from "@/lib/utils";
interface MembersSettingsPageProps {
params: {
@@ -62,11 +59,6 @@ export default async function MembersSettingsPage({ params: { domain }, searchPa
const usedSeats = members.length
const seatsAvailable = seats === SOURCEBOT_UNLIMITED_SEATS || usedSeats < seats;
- // Get the current URL to construct the full invite link
- const headersList = headers();
- const baseUrl = getBaseUrl(headersList);
- const inviteLink = createInviteLink(baseUrl, org.inviteLinkId);
-
return (