Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Redesigned the app layout with a new collapsible sidebar navigation, replacing the previous top navigation bar. [#1097](https://github.com/sourcebot-dev/sourcebot/pull/1097)
- Expired offline license keys no longer crash the process. An expired key now degrades to the unlicensed state. [#1109](https://github.com/sourcebot-dev/sourcebot/pull/1109)
- [**Breaking Change**] Changed the default role assignment to `Owner` for organizations on the free tier. [#1234](https://github.com/sourcebot-dev/sourcebot/pull/1234)
- Improved the `setup-sourcebot` wizard: prompts for a setup directory, clarifies that secrets are stored locally in `.env`, switches multi-select to Tab, hides "No results" until a real search runs, and detects/cleans up conflicting Docker deployments and volumes before starting. [#1232](https://github.com/sourcebot-dev/sourcebot/pull/1232)

## [4.17.2] - 2026-05-16
Expand Down
25 changes: 16 additions & 9 deletions docs/docs/configuration/auth/roles-and-permissions.mdx
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
---
title: Roles and Permissions
sidebarTitle: Roles and permissions
title: Members and roles
sidebarTitle: Members and roles
---

Each member has a role which defines their permissions within an organization:
Sourcebot provides different role types to help you control access and permissions across your organization.

| Role | Permission |
| :--- | :--------- |
| `Owner` | An organization can have one or more `Owner`s. Owners have 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, 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. |
| `Owner` | Owners have full administrative control over all organization-level settings, including user management, access control, billing, and audit logs, in addition to all permissions a member can perform. |
| `Member` | Members have access and to use all standard features, such as code search, ask, mcp, etc., as well as their account settings. Members **cannot access** organization-level administration pages. |
| `Guest` | Guests are users that access Sourcebot without a account when [anonymous access](/docs/configuration/auth/access-settings#anonymous-access) is enabled. Guests can use some features, such as code search and browsing files, with additional limitations like not having chat history. Guests **cannot access** organization-level administration pages. |

## Managing owners
Note that when [permission syncing](/docs/features/permission-syncing) is enabled, users will only be able to view repositories they have access to. This applies to all roles, **including Owners**.

<Note>
On the free plan, all signed-in users are given the `Owner` role.
</Note>


## Managing member roles

import LicenseKeyRequired from '/snippets/license-key-required.mdx'

<LicenseKeyRequired feature="Multiple owners" />
<LicenseKeyRequired feature="Role management" />

organizations support multiple owners, allowing you to share administrative responsibilities across your team. Owners can promote members to owner and demote other owners back to member from **Settings -> Members**.
Owners can change the role of any user in the organization from **Settings → Members**. This lets you control who has administrative access by promoting trusted members to `Owner`, and scoping access back down to `Member` when administrative responsibilities change.

<Frame>
<img src="/images/managing_owners.png" alt="Members settings page showing team members and their roles" />
Expand Down
2 changes: 1 addition & 1 deletion docs/snippets/license-key-required.mdx
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@

<Note>
{feature} is only available with an active Enterprise license. Please add your [license key](/docs/license-key) to activate it.
{feature} is only available in a paid plan. Please add your [license key](/docs/license-key) to activate it.
</Note>
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@ export interface MembersListProps {
hasOrgManagement: boolean,
}

const ROLES_AND_PERMISSIONS_DOCS_LINK = "https://docs.sourcebot.dev/docs/configuration/auth/roles-and-permissions#managing-owners"
const ROLES_AND_PERMISSIONS_DOCS_LINK = "https://docs.sourcebot.dev/docs/configuration/auth/roles-and-permissions"

export const MembersList = ({ members, currentUserId, currentUserRole, orgName, hasOrgManagement }: MembersListProps) => {
const [searchQuery, setSearchQuery] = useState("")
Expand DownExpand Up@@ -256,7 +256,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName,
side="left"
sideOffset={12}
>
Upgrade your plan to promote members to owner. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link>
Upgrade your plan to manage roles. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link>
</TooltipContent>
)}
</Tooltip>
Expand All@@ -280,7 +280,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName,
{(ownerCount <= 1 || !hasOrgManagement) && (
<TooltipContent side="left" sideOffset={12}>
{!hasOrgManagement
? <>Upgrade your plan to demote owners. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link></>
? <>Upgrade your plan to manage roles. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link></>
: "Cannot demote the last owner. Promote another member to owner first."
}
</TooltipContent>
Expand Down
19 changes: 13 additions & 6 deletions packages/web/src/lib/authUtils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import { createAudit } from "@/ee/features/audit/audit";
import { StatusCodes } from "http-status-codes";
import { ErrorCode } from "./errorCodes";
import { syncWithLighthouse } from "@/ee/features/lighthouse/servicePing";
import { hasEntitlement } from "./entitlements";

const logger = createLogger('web-auth-utils');

Expand DownExpand Up@@ -108,10 +109,12 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
});
}

// Subsequent users auto-join as MEMBER only when the org is in open
// self-serve mode. If memberApprovalRequired is true, the user is left
// without a membership and must submit an AccountRequest for an owner to
// approve via addUserToOrganization.
// Subsequent users auto-join only when the org is in open self-serve
// mode. Their role depends on the `org-management` entitlement: on paid
// plans they join as MEMBER, on free they join as OWNER (no role
// distinction exists without the entitlement). If memberApprovalRequired
// is true, the user is left without a membership and must submit an
// AccountRequest for an owner to approve via addUserToOrganization.
else if (!defaultOrg.memberApprovalRequired) {
// Don't exceed the licensed seat count. The user row still exists;
// they just aren't attached to the org until a seat frees up.
Expand All@@ -121,11 +124,13 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
return;
}

const hasOrgManagement = await hasEntitlement("org-management");

await __unsafePrisma.userToOrg.create({
data: {
userId: user.id,
orgId: SINGLE_TENANT_ORG_ID,
role: OrgRole.MEMBER,
role: hasOrgManagement ? OrgRole.MEMBER : OrgRole.OWNER,
}
});

Expand DownExpand Up@@ -212,6 +217,8 @@ export const addUserToOrganization = async (userId: string, orgId: number): Prom
} satisfies ServiceError;
}

const hasOrgManagement = await hasEntitlement('org-management');

await __unsafePrisma.$transaction(async (tx) => {
// Upsert rather than create: the user may already be a member from the
// self-serve auto-join in onCreateUser, in which case this call is
Expand All@@ -226,7 +233,7 @@ export const addUserToOrganization = async (userId: string, orgId: number): Prom
create: {
userId: user.id,
orgId: org.id,
role: OrgRole.MEMBER,
role: hasOrgManagement ? OrgRole.MEMBER : OrgRole.OWNER,
},
update: {},
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Redesigned the app layout with a new collapsible sidebar navigation, replacing the previous top navigation bar. [#1097](https://github.com/sourcebot-dev/sourcebot/pull/1097)
- Expired offline license keys no longer crash the process. An expired key now degrades to the unlicensed state. [#1109](https://github.com/sourcebot-dev/sourcebot/pull/1109)
- [**Breaking Change**] Changed the default role assignment to `Owner` for organizations on the free tier. [#1234](https://github.com/sourcebot-dev/sourcebot/pull/1234)
- Improved the `setup-sourcebot` wizard: prompts for a setup directory, clarifies that secrets are stored locally in `.env`, switches multi-select to Tab, hides "No results" until a real search runs, and detects/cleans up conflicting Docker deployments and volumes before starting. [#1232](https://github.com/sourcebot-dev/sourcebot/pull/1232)

## [4.17.2] - 2026-05-16
Expand Down
25 changes: 16 additions & 9 deletions docs/docs/configuration/auth/roles-and-permissions.mdx
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
---
title: Roles and Permissions
sidebarTitle: Roles and permissions
title: Members and roles
sidebarTitle: Members and roles
---

Each member has a role which defines their permissions within an organization:
Sourcebot provides different role types to help you control access and permissions across your organization.

| Role | Permission |
| :--- | :--------- |
| `Owner` | An organization can have one or more `Owner`s. Owners have 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, 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. |
| `Owner` | Owners have full administrative control over all organization-level settings, including user management, access control, billing, and audit logs, in addition to all permissions a member can perform. |
| `Member` | Members have access and to use all standard features, such as code search, ask, mcp, etc., as well as their account settings. Members **cannot access** organization-level administration pages. |
| `Guest` | Guests are users that access Sourcebot without a account when [anonymous access](/docs/configuration/auth/access-settings#anonymous-access) is enabled. Guests can use some features, such as code search and browsing files, with additional limitations like not having chat history. Guests **cannot access** organization-level administration pages. |

## Managing owners
Note that when [permission syncing](/docs/features/permission-syncing) is enabled, users will only be able to view repositories they have access to. This applies to all roles, **including Owners**.

<Note>
On the free plan, all signed-in users are given the `Owner` role.
</Note>


## Managing member roles

import LicenseKeyRequired from '/snippets/license-key-required.mdx'

<LicenseKeyRequired feature="Multiple owners" />
<LicenseKeyRequired feature="Role management" />

organizations support multiple owners, allowing you to share administrative responsibilities across your team. Owners can promote members to owner and demote other owners back to member from **Settings -> Members**.
Owners can change the role of any user in the organization from **Settings → Members**. This lets you control who has administrative access by promoting trusted members to `Owner`, and scoping access back down to `Member` when administrative responsibilities change.

<Frame>
<img src="/images/managing_owners.png" alt="Members settings page showing team members and their roles" />
Expand Down
2 changes: 1 addition & 1 deletion docs/snippets/license-key-required.mdx
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@

<Note>
{feature} is only available with an active Enterprise license. Please add your [license key](/docs/license-key) to activate it.
{feature} is only available in a paid plan. Please add your [license key](/docs/license-key) to activate it.
</Note>
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@ export interface MembersListProps {
hasOrgManagement: boolean,
}

const ROLES_AND_PERMISSIONS_DOCS_LINK = "https://docs.sourcebot.dev/docs/configuration/auth/roles-and-permissions#managing-owners"
const ROLES_AND_PERMISSIONS_DOCS_LINK = "https://docs.sourcebot.dev/docs/configuration/auth/roles-and-permissions"

export const MembersList = ({ members, currentUserId, currentUserRole, orgName, hasOrgManagement }: MembersListProps) => {
const [searchQuery, setSearchQuery] = useState("")
Expand DownExpand Up@@ -256,7 +256,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName,
side="left"
sideOffset={12}
>
Upgrade your plan to promote members to owner. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link>
Upgrade your plan to manage roles. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link>
</TooltipContent>
)}
</Tooltip>
Expand All@@ -280,7 +280,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName,
{(ownerCount <= 1 || !hasOrgManagement) && (
<TooltipContent side="left" sideOffset={12}>
{!hasOrgManagement
? <>Upgrade your plan to demote owners. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link></>
? <>Upgrade your plan to manage roles. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link></>
: "Cannot demote the last owner. Promote another member to owner first."
}
</TooltipContent>
Expand Down
19 changes: 13 additions & 6 deletions packages/web/src/lib/authUtils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import { createAudit } from "@/ee/features/audit/audit";
import { StatusCodes } from "http-status-codes";
import { ErrorCode } from "./errorCodes";
import { syncWithLighthouse } from "@/ee/features/lighthouse/servicePing";
import { hasEntitlement } from "./entitlements";

const logger = createLogger('web-auth-utils');

Expand DownExpand Up@@ -108,10 +109,12 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
});
}

// Subsequent users auto-join as MEMBER only when the org is in open
// self-serve mode. If memberApprovalRequired is true, the user is left
// without a membership and must submit an AccountRequest for an owner to
// approve via addUserToOrganization.
// Subsequent users auto-join only when the org is in open self-serve
// mode. Their role depends on the `org-management` entitlement: on paid
// plans they join as MEMBER, on free they join as OWNER (no role
// distinction exists without the entitlement). If memberApprovalRequired
// is true, the user is left without a membership and must submit an
// AccountRequest for an owner to approve via addUserToOrganization.
else if (!defaultOrg.memberApprovalRequired) {
// Don't exceed the licensed seat count. The user row still exists;
// they just aren't attached to the org until a seat frees up.
Expand All@@ -121,11 +124,13 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
return;
}

const hasOrgManagement = await hasEntitlement("org-management");

await __unsafePrisma.userToOrg.create({
data: {
userId: user.id,
orgId: SINGLE_TENANT_ORG_ID,
role: OrgRole.MEMBER,
role: hasOrgManagement ? OrgRole.MEMBER : OrgRole.OWNER,
}
});

Expand DownExpand Up@@ -212,6 +217,8 @@ export const addUserToOrganization = async (userId: string, orgId: number): Prom
} satisfies ServiceError;
}

const hasOrgManagement = await hasEntitlement('org-management');

await __unsafePrisma.$transaction(async (tx) => {
// Upsert rather than create: the user may already be a member from the
// self-serve auto-join in onCreateUser, in which case this call is
Expand All@@ -226,7 +233,7 @@ export const addUserToOrganization = async (userId: string, orgId: number): Prom
create: {
userId: user.id,
orgId: org.id,
role: OrgRole.MEMBER,
role: hasOrgManagement ? OrgRole.MEMBER : OrgRole.OWNER,
},
update: {},
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Redesigned the app layout with a new collapsible sidebar navigation, replacing the previous top navigation bar. [#1097](https://github.com/sourcebot-dev/sourcebot/pull/1097)
- Expired offline license keys no longer crash the process. An expired key now degrades to the unlicensed state. [#1109](https://github.com/sourcebot-dev/sourcebot/pull/1109)
- [**Breaking Change**] Changed the default role assignment to `Owner` for organizations on the free tier. [#1234](https://github.com/sourcebot-dev/sourcebot/pull/1234)
- Improved the `setup-sourcebot` wizard: prompts for a setup directory, clarifies that secrets are stored locally in `.env`, switches multi-select to Tab, hides "No results" until a real search runs, and detects/cleans up conflicting Docker deployments and volumes before starting. [#1232](https://github.com/sourcebot-dev/sourcebot/pull/1232)

## [4.17.2] - 2026-05-16
Expand Down
25 changes: 16 additions & 9 deletions docs/docs/configuration/auth/roles-and-permissions.mdx
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
---
title: Roles and Permissions
sidebarTitle: Roles and permissions
title: Members and roles
sidebarTitle: Members and roles
---

Each member has a role which defines their permissions within an organization:
Sourcebot provides different role types to help you control access and permissions across your organization.

| Role | Permission |
| :--- | :--------- |
| `Owner` | An organization can have one or more `Owner`s. Owners have 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, 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. |
| `Owner` | Owners have full administrative control over all organization-level settings, including user management, access control, billing, and audit logs, in addition to all permissions a member can perform. |
| `Member` | Members have access and to use all standard features, such as code search, ask, mcp, etc., as well as their account settings. Members **cannot access** organization-level administration pages. |
| `Guest` | Guests are users that access Sourcebot without a account when [anonymous access](/docs/configuration/auth/access-settings#anonymous-access) is enabled. Guests can use some features, such as code search and browsing files, with additional limitations like not having chat history. Guests **cannot access** organization-level administration pages. |

## Managing owners
Note that when [permission syncing](/docs/features/permission-syncing) is enabled, users will only be able to view repositories they have access to. This applies to all roles, **including Owners**.

<Note>
On the free plan, all signed-in users are given the `Owner` role.
</Note>


## Managing member roles

import LicenseKeyRequired from '/snippets/license-key-required.mdx'

<LicenseKeyRequired feature="Multiple owners" />
<LicenseKeyRequired feature="Role management" />

organizations support multiple owners, allowing you to share administrative responsibilities across your team. Owners can promote members to owner and demote other owners back to member from **Settings -> Members**.
Owners can change the role of any user in the organization from **Settings → Members**. This lets you control who has administrative access by promoting trusted members to `Owner`, and scoping access back down to `Member` when administrative responsibilities change.

<Frame>
<img src="/images/managing_owners.png" alt="Members settings page showing team members and their roles" />
Expand Down
2 changes: 1 addition & 1 deletion docs/snippets/license-key-required.mdx
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@

<Note>
{feature} is only available with an active Enterprise license. Please add your [license key](/docs/license-key) to activate it.
{feature} is only available in a paid plan. Please add your [license key](/docs/license-key) to activate it.
</Note>
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@ export interface MembersListProps {
hasOrgManagement: boolean,
}

const ROLES_AND_PERMISSIONS_DOCS_LINK = "https://docs.sourcebot.dev/docs/configuration/auth/roles-and-permissions#managing-owners"
const ROLES_AND_PERMISSIONS_DOCS_LINK = "https://docs.sourcebot.dev/docs/configuration/auth/roles-and-permissions"

export const MembersList = ({ members, currentUserId, currentUserRole, orgName, hasOrgManagement }: MembersListProps) => {
const [searchQuery, setSearchQuery] = useState("")
Expand DownExpand Up@@ -256,7 +256,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName,
side="left"
sideOffset={12}
>
Upgrade your plan to promote members to owner. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link>
Upgrade your plan to manage roles. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link>
</TooltipContent>
)}
</Tooltip>
Expand All@@ -280,7 +280,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName,
{(ownerCount <= 1 || !hasOrgManagement) && (
<TooltipContent side="left" sideOffset={12}>
{!hasOrgManagement
? <>Upgrade your plan to demote owners. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link></>
? <>Upgrade your plan to manage roles. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link></>
: "Cannot demote the last owner. Promote another member to owner first."
}
</TooltipContent>
Expand Down
19 changes: 13 additions & 6 deletions packages/web/src/lib/authUtils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import { createAudit } from "@/ee/features/audit/audit";
import { StatusCodes } from "http-status-codes";
import { ErrorCode } from "./errorCodes";
import { syncWithLighthouse } from "@/ee/features/lighthouse/servicePing";
import { hasEntitlement } from "./entitlements";

const logger = createLogger('web-auth-utils');

Expand DownExpand Up@@ -108,10 +109,12 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
});
}

// Subsequent users auto-join as MEMBER only when the org is in open
// self-serve mode. If memberApprovalRequired is true, the user is left
// without a membership and must submit an AccountRequest for an owner to
// approve via addUserToOrganization.
// Subsequent users auto-join only when the org is in open self-serve
// mode. Their role depends on the `org-management` entitlement: on paid
// plans they join as MEMBER, on free they join as OWNER (no role
// distinction exists without the entitlement). If memberApprovalRequired
// is true, the user is left without a membership and must submit an
// AccountRequest for an owner to approve via addUserToOrganization.
else if (!defaultOrg.memberApprovalRequired) {
// Don't exceed the licensed seat count. The user row still exists;
// they just aren't attached to the org until a seat frees up.
Expand All@@ -121,11 +124,13 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
return;
}

const hasOrgManagement = await hasEntitlement("org-management");

await __unsafePrisma.userToOrg.create({
data: {
userId: user.id,
orgId: SINGLE_TENANT_ORG_ID,
role: OrgRole.MEMBER,
role: hasOrgManagement ? OrgRole.MEMBER : OrgRole.OWNER,
}
});

Expand DownExpand Up@@ -212,6 +217,8 @@ export const addUserToOrganization = async (userId: string, orgId: number): Prom
} satisfies ServiceError;
}

const hasOrgManagement = await hasEntitlement('org-management');

await __unsafePrisma.$transaction(async (tx) => {
// Upsert rather than create: the user may already be a member from the
// self-serve auto-join in onCreateUser, in which case this call is
Expand All@@ -226,7 +233,7 @@ export const addUserToOrganization = async (userId: string, orgId: number): Prom
create: {
userId: user.id,
orgId: org.id,
role: OrgRole.MEMBER,
role: hasOrgManagement ? OrgRole.MEMBER : OrgRole.OWNER,
},
update: {},
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Redesigned the app layout with a new collapsible sidebar navigation, replacing the previous top navigation bar. [#1097](https://github.com/sourcebot-dev/sourcebot/pull/1097)
- Expired offline license keys no longer crash the process. An expired key now degrades to the unlicensed state. [#1109](https://github.com/sourcebot-dev/sourcebot/pull/1109)
- [**Breaking Change**] Changed the default role assignment to `Owner` for organizations on the free tier. [#1234](https://github.com/sourcebot-dev/sourcebot/pull/1234)
- Improved the `setup-sourcebot` wizard: prompts for a setup directory, clarifies that secrets are stored locally in `.env`, switches multi-select to Tab, hides "No results" until a real search runs, and detects/cleans up conflicting Docker deployments and volumes before starting. [#1232](https://github.com/sourcebot-dev/sourcebot/pull/1232)

## [4.17.2] - 2026-05-16
Expand Down
25 changes: 16 additions & 9 deletions docs/docs/configuration/auth/roles-and-permissions.mdx
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
---
title: Roles and Permissions
sidebarTitle: Roles and permissions
title: Members and roles
sidebarTitle: Members and roles
---

Each member has a role which defines their permissions within an organization:
Sourcebot provides different role types to help you control access and permissions across your organization.

| Role | Permission |
| :--- | :--------- |
| `Owner` | An organization can have one or more `Owner`s. Owners have 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, 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. |
| `Owner` | Owners have full administrative control over all organization-level settings, including user management, access control, billing, and audit logs, in addition to all permissions a member can perform. |
| `Member` | Members have access and to use all standard features, such as code search, ask, mcp, etc., as well as their account settings. Members **cannot access** organization-level administration pages. |
| `Guest` | Guests are users that access Sourcebot without a account when [anonymous access](/docs/configuration/auth/access-settings#anonymous-access) is enabled. Guests can use some features, such as code search and browsing files, with additional limitations like not having chat history. Guests **cannot access** organization-level administration pages. |

## Managing owners
Note that when [permission syncing](/docs/features/permission-syncing) is enabled, users will only be able to view repositories they have access to. This applies to all roles, **including Owners**.

<Note>
On the free plan, all signed-in users are given the `Owner` role.
</Note>


## Managing member roles

import LicenseKeyRequired from '/snippets/license-key-required.mdx'

<LicenseKeyRequired feature="Multiple owners" />
<LicenseKeyRequired feature="Role management" />

organizations support multiple owners, allowing you to share administrative responsibilities across your team. Owners can promote members to owner and demote other owners back to member from **Settings -> Members**.
Owners can change the role of any user in the organization from **Settings → Members**. This lets you control who has administrative access by promoting trusted members to `Owner`, and scoping access back down to `Member` when administrative responsibilities change.

<Frame>
<img src="/images/managing_owners.png" alt="Members settings page showing team members and their roles" />
Expand Down
2 changes: 1 addition & 1 deletion docs/snippets/license-key-required.mdx
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@

<Note>
{feature} is only available with an active Enterprise license. Please add your [license key](/docs/license-key) to activate it.
{feature} is only available in a paid plan. Please add your [license key](/docs/license-key) to activate it.
</Note>
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@ export interface MembersListProps {
hasOrgManagement: boolean,
}

const ROLES_AND_PERMISSIONS_DOCS_LINK = "https://docs.sourcebot.dev/docs/configuration/auth/roles-and-permissions#managing-owners"
const ROLES_AND_PERMISSIONS_DOCS_LINK = "https://docs.sourcebot.dev/docs/configuration/auth/roles-and-permissions"

export const MembersList = ({ members, currentUserId, currentUserRole, orgName, hasOrgManagement }: MembersListProps) => {
const [searchQuery, setSearchQuery] = useState("")
Expand DownExpand Up@@ -256,7 +256,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName,
side="left"
sideOffset={12}
>
Upgrade your plan to promote members to owner. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link>
Upgrade your plan to manage roles. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link>
</TooltipContent>
)}
</Tooltip>
Expand All@@ -280,7 +280,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName,
{(ownerCount <= 1 || !hasOrgManagement) && (
<TooltipContent side="left" sideOffset={12}>
{!hasOrgManagement
? <>Upgrade your plan to demote owners. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link></>
? <>Upgrade your plan to manage roles. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link></>
: "Cannot demote the last owner. Promote another member to owner first."
}
</TooltipContent>
Expand Down
19 changes: 13 additions & 6 deletions packages/web/src/lib/authUtils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import { createAudit } from "@/ee/features/audit/audit";
import { StatusCodes } from "http-status-codes";
import { ErrorCode } from "./errorCodes";
import { syncWithLighthouse } from "@/ee/features/lighthouse/servicePing";
import { hasEntitlement } from "./entitlements";

const logger = createLogger('web-auth-utils');

Expand DownExpand Up@@ -108,10 +109,12 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
});
}

// Subsequent users auto-join as MEMBER only when the org is in open
// self-serve mode. If memberApprovalRequired is true, the user is left
// without a membership and must submit an AccountRequest for an owner to
// approve via addUserToOrganization.
// Subsequent users auto-join only when the org is in open self-serve
// mode. Their role depends on the `org-management` entitlement: on paid
// plans they join as MEMBER, on free they join as OWNER (no role
// distinction exists without the entitlement). If memberApprovalRequired
// is true, the user is left without a membership and must submit an
// AccountRequest for an owner to approve via addUserToOrganization.
else if (!defaultOrg.memberApprovalRequired) {
// Don't exceed the licensed seat count. The user row still exists;
// they just aren't attached to the org until a seat frees up.
Expand All@@ -121,11 +124,13 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
return;
}

const hasOrgManagement = await hasEntitlement("org-management");

await __unsafePrisma.userToOrg.create({
data: {
userId: user.id,
orgId: SINGLE_TENANT_ORG_ID,
role: OrgRole.MEMBER,
role: hasOrgManagement ? OrgRole.MEMBER : OrgRole.OWNER,
}
});

Expand DownExpand Up@@ -212,6 +217,8 @@ export const addUserToOrganization = async (userId: string, orgId: number): Prom
} satisfies ServiceError;
}

const hasOrgManagement = await hasEntitlement('org-management');

await __unsafePrisma.$transaction(async (tx) => {
// Upsert rather than create: the user may already be a member from the
// self-serve auto-join in onCreateUser, in which case this call is
Expand All@@ -226,7 +233,7 @@ export const addUserToOrganization = async (userId: string, orgId: number): Prom
create: {
userId: user.id,
orgId: org.id,
role: OrgRole.MEMBER,
role: hasOrgManagement ? OrgRole.MEMBER : OrgRole.OWNER,
},
update: {},
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Redesigned the app layout with a new collapsible sidebar navigation, replacing the previous top navigation bar. [#1097](https://github.com/sourcebot-dev/sourcebot/pull/1097)
- Expired offline license keys no longer crash the process. An expired key now degrades to the unlicensed state. [#1109](https://github.com/sourcebot-dev/sourcebot/pull/1109)
- [**Breaking Change**] Changed the default role assignment to `Owner` for organizations on the free tier. [#1234](https://github.com/sourcebot-dev/sourcebot/pull/1234)
- Improved the `setup-sourcebot` wizard: prompts for a setup directory, clarifies that secrets are stored locally in `.env`, switches multi-select to Tab, hides "No results" until a real search runs, and detects/cleans up conflicting Docker deployments and volumes before starting. [#1232](https://github.com/sourcebot-dev/sourcebot/pull/1232)

## [4.17.2] - 2026-05-16
Expand Down
25 changes: 16 additions & 9 deletions docs/docs/configuration/auth/roles-and-permissions.mdx
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
---
title: Roles and Permissions
sidebarTitle: Roles and permissions
title: Members and roles
sidebarTitle: Members and roles
---

Each member has a role which defines their permissions within an organization:
Sourcebot provides different role types to help you control access and permissions across your organization.

| Role | Permission |
| :--- | :--------- |
| `Owner` | An organization can have one or more `Owner`s. Owners have 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, 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. |
| `Owner` | Owners have full administrative control over all organization-level settings, including user management, access control, billing, and audit logs, in addition to all permissions a member can perform. |
| `Member` | Members have access and to use all standard features, such as code search, ask, mcp, etc., as well as their account settings. Members **cannot access** organization-level administration pages. |
| `Guest` | Guests are users that access Sourcebot without a account when [anonymous access](/docs/configuration/auth/access-settings#anonymous-access) is enabled. Guests can use some features, such as code search and browsing files, with additional limitations like not having chat history. Guests **cannot access** organization-level administration pages. |

## Managing owners
Note that when [permission syncing](/docs/features/permission-syncing) is enabled, users will only be able to view repositories they have access to. This applies to all roles, **including Owners**.

<Note>
On the free plan, all signed-in users are given the `Owner` role.
</Note>


## Managing member roles

import LicenseKeyRequired from '/snippets/license-key-required.mdx'

<LicenseKeyRequired feature="Multiple owners" />
<LicenseKeyRequired feature="Role management" />

organizations support multiple owners, allowing you to share administrative responsibilities across your team. Owners can promote members to owner and demote other owners back to member from **Settings -> Members**.
Owners can change the role of any user in the organization from **Settings → Members**. This lets you control who has administrative access by promoting trusted members to `Owner`, and scoping access back down to `Member` when administrative responsibilities change.

<Frame>
<img src="/images/managing_owners.png" alt="Members settings page showing team members and their roles" />
Expand Down
2 changes: 1 addition & 1 deletion docs/snippets/license-key-required.mdx
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@

<Note>
{feature} is only available with an active Enterprise license. Please add your [license key](/docs/license-key) to activate it.
{feature} is only available in a paid plan. Please add your [license key](/docs/license-key) to activate it.
</Note>
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@ export interface MembersListProps {
hasOrgManagement: boolean,
}

const ROLES_AND_PERMISSIONS_DOCS_LINK = "https://docs.sourcebot.dev/docs/configuration/auth/roles-and-permissions#managing-owners"
const ROLES_AND_PERMISSIONS_DOCS_LINK = "https://docs.sourcebot.dev/docs/configuration/auth/roles-and-permissions"

export const MembersList = ({ members, currentUserId, currentUserRole, orgName, hasOrgManagement }: MembersListProps) => {
const [searchQuery, setSearchQuery] = useState("")
Expand DownExpand Up@@ -256,7 +256,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName,
side="left"
sideOffset={12}
>
Upgrade your plan to promote members to owner. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link>
Upgrade your plan to manage roles. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link>
</TooltipContent>
)}
</Tooltip>
Expand All@@ -280,7 +280,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName,
{(ownerCount <= 1 || !hasOrgManagement) && (
<TooltipContent side="left" sideOffset={12}>
{!hasOrgManagement
? <>Upgrade your plan to demote owners. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link></>
? <>Upgrade your plan to manage roles. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link></>
: "Cannot demote the last owner. Promote another member to owner first."
}
</TooltipContent>
Expand Down
19 changes: 13 additions & 6 deletions packages/web/src/lib/authUtils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import { createAudit } from "@/ee/features/audit/audit";
import { StatusCodes } from "http-status-codes";
import { ErrorCode } from "./errorCodes";
import { syncWithLighthouse } from "@/ee/features/lighthouse/servicePing";
import { hasEntitlement } from "./entitlements";

const logger = createLogger('web-auth-utils');

Expand DownExpand Up@@ -108,10 +109,12 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
});
}

// Subsequent users auto-join as MEMBER only when the org is in open
// self-serve mode. If memberApprovalRequired is true, the user is left
// without a membership and must submit an AccountRequest for an owner to
// approve via addUserToOrganization.
// Subsequent users auto-join only when the org is in open self-serve
// mode. Their role depends on the `org-management` entitlement: on paid
// plans they join as MEMBER, on free they join as OWNER (no role
// distinction exists without the entitlement). If memberApprovalRequired
// is true, the user is left without a membership and must submit an
// AccountRequest for an owner to approve via addUserToOrganization.
else if (!defaultOrg.memberApprovalRequired) {
// Don't exceed the licensed seat count. The user row still exists;
// they just aren't attached to the org until a seat frees up.
Expand All@@ -121,11 +124,13 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
return;
}

const hasOrgManagement = await hasEntitlement("org-management");

await __unsafePrisma.userToOrg.create({
data: {
userId: user.id,
orgId: SINGLE_TENANT_ORG_ID,
role: OrgRole.MEMBER,
role: hasOrgManagement ? OrgRole.MEMBER : OrgRole.OWNER,
}
});

Expand DownExpand Up@@ -212,6 +217,8 @@ export const addUserToOrganization = async (userId: string, orgId: number): Prom
} satisfies ServiceError;
}

const hasOrgManagement = await hasEntitlement('org-management');

await __unsafePrisma.$transaction(async (tx) => {
// Upsert rather than create: the user may already be a member from the
// self-serve auto-join in onCreateUser, in which case this call is
Expand All@@ -226,7 +233,7 @@ export const addUserToOrganization = async (userId: string, orgId: number): Prom
create: {
userId: user.id,
orgId: org.id,
role: OrgRole.MEMBER,
role: hasOrgManagement ? OrgRole.MEMBER : OrgRole.OWNER,
},
update: {},
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Redesigned the app layout with a new collapsible sidebar navigation, replacing the previous top navigation bar. [#1097](https://github.com/sourcebot-dev/sourcebot/pull/1097)
- Expired offline license keys no longer crash the process. An expired key now degrades to the unlicensed state. [#1109](https://github.com/sourcebot-dev/sourcebot/pull/1109)
- [**Breaking Change**] Changed the default role assignment to `Owner` for organizations on the free tier. [#1234](https://github.com/sourcebot-dev/sourcebot/pull/1234)
- Improved the `setup-sourcebot` wizard: prompts for a setup directory, clarifies that secrets are stored locally in `.env`, switches multi-select to Tab, hides "No results" until a real search runs, and detects/cleans up conflicting Docker deployments and volumes before starting. [#1232](https://github.com/sourcebot-dev/sourcebot/pull/1232)

## [4.17.2] - 2026-05-16
Expand Down
25 changes: 16 additions & 9 deletions docs/docs/configuration/auth/roles-and-permissions.mdx
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
---
title: Roles and Permissions
sidebarTitle: Roles and permissions
title: Members and roles
sidebarTitle: Members and roles
---

Each member has a role which defines their permissions within an organization:
Sourcebot provides different role types to help you control access and permissions across your organization.

| Role | Permission |
| :--- | :--------- |
| `Owner` | An organization can have one or more `Owner`s. Owners have 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, 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. |
| `Owner` | Owners have full administrative control over all organization-level settings, including user management, access control, billing, and audit logs, in addition to all permissions a member can perform. |
| `Member` | Members have access and to use all standard features, such as code search, ask, mcp, etc., as well as their account settings. Members **cannot access** organization-level administration pages. |
| `Guest` | Guests are users that access Sourcebot without a account when [anonymous access](/docs/configuration/auth/access-settings#anonymous-access) is enabled. Guests can use some features, such as code search and browsing files, with additional limitations like not having chat history. Guests **cannot access** organization-level administration pages. |

## Managing owners
Note that when [permission syncing](/docs/features/permission-syncing) is enabled, users will only be able to view repositories they have access to. This applies to all roles, **including Owners**.

<Note>
On the free plan, all signed-in users are given the `Owner` role.
</Note>


## Managing member roles

import LicenseKeyRequired from '/snippets/license-key-required.mdx'

<LicenseKeyRequired feature="Multiple owners" />
<LicenseKeyRequired feature="Role management" />

organizations support multiple owners, allowing you to share administrative responsibilities across your team. Owners can promote members to owner and demote other owners back to member from **Settings -> Members**.
Owners can change the role of any user in the organization from **Settings → Members**. This lets you control who has administrative access by promoting trusted members to `Owner`, and scoping access back down to `Member` when administrative responsibilities change.

<Frame>
<img src="/images/managing_owners.png" alt="Members settings page showing team members and their roles" />
Expand Down
2 changes: 1 addition & 1 deletion docs/snippets/license-key-required.mdx
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@

<Note>
{feature} is only available with an active Enterprise license. Please add your [license key](/docs/license-key) to activate it.
{feature} is only available in a paid plan. Please add your [license key](/docs/license-key) to activate it.
</Note>
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@ export interface MembersListProps {
hasOrgManagement: boolean,
}

const ROLES_AND_PERMISSIONS_DOCS_LINK = "https://docs.sourcebot.dev/docs/configuration/auth/roles-and-permissions#managing-owners"
const ROLES_AND_PERMISSIONS_DOCS_LINK = "https://docs.sourcebot.dev/docs/configuration/auth/roles-and-permissions"

export const MembersList = ({ members, currentUserId, currentUserRole, orgName, hasOrgManagement }: MembersListProps) => {
const [searchQuery, setSearchQuery] = useState("")
Expand DownExpand Up@@ -256,7 +256,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName,
side="left"
sideOffset={12}
>
Upgrade your plan to promote members to owner. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link>
Upgrade your plan to manage roles. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link>
</TooltipContent>
)}
</Tooltip>
Expand All@@ -280,7 +280,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName,
{(ownerCount <= 1 || !hasOrgManagement) && (
<TooltipContent side="left" sideOffset={12}>
{!hasOrgManagement
? <>Upgrade your plan to demote owners. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link></>
? <>Upgrade your plan to manage roles. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link></>
: "Cannot demote the last owner. Promote another member to owner first."
}
</TooltipContent>
Expand Down
19 changes: 13 additions & 6 deletions packages/web/src/lib/authUtils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import { createAudit } from "@/ee/features/audit/audit";
import { StatusCodes } from "http-status-codes";
import { ErrorCode } from "./errorCodes";
import { syncWithLighthouse } from "@/ee/features/lighthouse/servicePing";
import { hasEntitlement } from "./entitlements";

const logger = createLogger('web-auth-utils');

Expand DownExpand Up@@ -108,10 +109,12 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
});
}

// Subsequent users auto-join as MEMBER only when the org is in open
// self-serve mode. If memberApprovalRequired is true, the user is left
// without a membership and must submit an AccountRequest for an owner to
// approve via addUserToOrganization.
// Subsequent users auto-join only when the org is in open self-serve
// mode. Their role depends on the `org-management` entitlement: on paid
// plans they join as MEMBER, on free they join as OWNER (no role
// distinction exists without the entitlement). If memberApprovalRequired
// is true, the user is left without a membership and must submit an
// AccountRequest for an owner to approve via addUserToOrganization.
else if (!defaultOrg.memberApprovalRequired) {
// Don't exceed the licensed seat count. The user row still exists;
// they just aren't attached to the org until a seat frees up.
Expand All@@ -121,11 +124,13 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
return;
}

const hasOrgManagement = await hasEntitlement("org-management");

await __unsafePrisma.userToOrg.create({
data: {
userId: user.id,
orgId: SINGLE_TENANT_ORG_ID,
role: OrgRole.MEMBER,
role: hasOrgManagement ? OrgRole.MEMBER : OrgRole.OWNER,
}
});

Expand DownExpand Up@@ -212,6 +217,8 @@ export const addUserToOrganization = async (userId: string, orgId: number): Prom
} satisfies ServiceError;
}

const hasOrgManagement = await hasEntitlement('org-management');

await __unsafePrisma.$transaction(async (tx) => {
// Upsert rather than create: the user may already be a member from the
// self-serve auto-join in onCreateUser, in which case this call is
Expand All@@ -226,7 +233,7 @@ export const addUserToOrganization = async (userId: string, orgId: number): Prom
create: {
userId: user.id,
orgId: org.id,
role: OrgRole.MEMBER,
role: hasOrgManagement ? OrgRole.MEMBER : OrgRole.OWNER,
},
update: {},
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Redesigned the app layout with a new collapsible sidebar navigation, replacing the previous top navigation bar. [#1097](https://github.com/sourcebot-dev/sourcebot/pull/1097)
- Expired offline license keys no longer crash the process. An expired key now degrades to the unlicensed state. [#1109](https://github.com/sourcebot-dev/sourcebot/pull/1109)
- [**Breaking Change**] Changed the default role assignment to `Owner` for organizations on the free tier. [#1234](https://github.com/sourcebot-dev/sourcebot/pull/1234)
- Improved the `setup-sourcebot` wizard: prompts for a setup directory, clarifies that secrets are stored locally in `.env`, switches multi-select to Tab, hides "No results" until a real search runs, and detects/cleans up conflicting Docker deployments and volumes before starting. [#1232](https://github.com/sourcebot-dev/sourcebot/pull/1232)

## [4.17.2] - 2026-05-16
Expand Down
25 changes: 16 additions & 9 deletions docs/docs/configuration/auth/roles-and-permissions.mdx
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
---
title: Roles and Permissions
sidebarTitle: Roles and permissions
title: Members and roles
sidebarTitle: Members and roles
---

Each member has a role which defines their permissions within an organization:
Sourcebot provides different role types to help you control access and permissions across your organization.

| Role | Permission |
| :--- | :--------- |
| `Owner` | An organization can have one or more `Owner`s. Owners have 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, 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. |
| `Owner` | Owners have full administrative control over all organization-level settings, including user management, access control, billing, and audit logs, in addition to all permissions a member can perform. |
| `Member` | Members have access and to use all standard features, such as code search, ask, mcp, etc., as well as their account settings. Members **cannot access** organization-level administration pages. |
| `Guest` | Guests are users that access Sourcebot without a account when [anonymous access](/docs/configuration/auth/access-settings#anonymous-access) is enabled. Guests can use some features, such as code search and browsing files, with additional limitations like not having chat history. Guests **cannot access** organization-level administration pages. |

## Managing owners
Note that when [permission syncing](/docs/features/permission-syncing) is enabled, users will only be able to view repositories they have access to. This applies to all roles, **including Owners**.

<Note>
On the free plan, all signed-in users are given the `Owner` role.
</Note>


## Managing member roles

import LicenseKeyRequired from '/snippets/license-key-required.mdx'

<LicenseKeyRequired feature="Multiple owners" />
<LicenseKeyRequired feature="Role management" />

organizations support multiple owners, allowing you to share administrative responsibilities across your team. Owners can promote members to owner and demote other owners back to member from **Settings -> Members**.
Owners can change the role of any user in the organization from **Settings → Members**. This lets you control who has administrative access by promoting trusted members to `Owner`, and scoping access back down to `Member` when administrative responsibilities change.

<Frame>
<img src="/images/managing_owners.png" alt="Members settings page showing team members and their roles" />
Expand Down
2 changes: 1 addition & 1 deletion docs/snippets/license-key-required.mdx
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@

<Note>
{feature} is only available with an active Enterprise license. Please add your [license key](/docs/license-key) to activate it.
{feature} is only available in a paid plan. Please add your [license key](/docs/license-key) to activate it.
</Note>
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@ export interface MembersListProps {
hasOrgManagement: boolean,
}

const ROLES_AND_PERMISSIONS_DOCS_LINK = "https://docs.sourcebot.dev/docs/configuration/auth/roles-and-permissions#managing-owners"
const ROLES_AND_PERMISSIONS_DOCS_LINK = "https://docs.sourcebot.dev/docs/configuration/auth/roles-and-permissions"

export const MembersList = ({ members, currentUserId, currentUserRole, orgName, hasOrgManagement }: MembersListProps) => {
const [searchQuery, setSearchQuery] = useState("")
Expand DownExpand Up@@ -256,7 +256,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName,
side="left"
sideOffset={12}
>
Upgrade your plan to promote members to owner. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link>
Upgrade your plan to manage roles. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link>
</TooltipContent>
)}
</Tooltip>
Expand All@@ -280,7 +280,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName,
{(ownerCount <= 1 || !hasOrgManagement) && (
<TooltipContent side="left" sideOffset={12}>
{!hasOrgManagement
? <>Upgrade your plan to demote owners. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link></>
? <>Upgrade your plan to manage roles. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link></>
: "Cannot demote the last owner. Promote another member to owner first."
}
</TooltipContent>
Expand Down
19 changes: 13 additions & 6 deletions packages/web/src/lib/authUtils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import { createAudit } from "@/ee/features/audit/audit";
import { StatusCodes } from "http-status-codes";
import { ErrorCode } from "./errorCodes";
import { syncWithLighthouse } from "@/ee/features/lighthouse/servicePing";
import { hasEntitlement } from "./entitlements";

const logger = createLogger('web-auth-utils');

Expand DownExpand Up@@ -108,10 +109,12 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
});
}

// Subsequent users auto-join as MEMBER only when the org is in open
// self-serve mode. If memberApprovalRequired is true, the user is left
// without a membership and must submit an AccountRequest for an owner to
// approve via addUserToOrganization.
// Subsequent users auto-join only when the org is in open self-serve
// mode. Their role depends on the `org-management` entitlement: on paid
// plans they join as MEMBER, on free they join as OWNER (no role
// distinction exists without the entitlement). If memberApprovalRequired
// is true, the user is left without a membership and must submit an
// AccountRequest for an owner to approve via addUserToOrganization.
else if (!defaultOrg.memberApprovalRequired) {
// Don't exceed the licensed seat count. The user row still exists;
// they just aren't attached to the org until a seat frees up.
Expand All@@ -121,11 +124,13 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
return;
}

const hasOrgManagement = await hasEntitlement("org-management");

await __unsafePrisma.userToOrg.create({
data: {
userId: user.id,
orgId: SINGLE_TENANT_ORG_ID,
role: OrgRole.MEMBER,
role: hasOrgManagement ? OrgRole.MEMBER : OrgRole.OWNER,
}
});

Expand DownExpand Up@@ -212,6 +217,8 @@ export const addUserToOrganization = async (userId: string, orgId: number): Prom
} satisfies ServiceError;
}

const hasOrgManagement = await hasEntitlement('org-management');

await __unsafePrisma.$transaction(async (tx) => {
// Upsert rather than create: the user may already be a member from the
// self-serve auto-join in onCreateUser, in which case this call is
Expand All@@ -226,7 +233,7 @@ export const addUserToOrganization = async (userId: string, orgId: number): Prom
create: {
userId: user.id,
orgId: org.id,
role: OrgRole.MEMBER,
role: hasOrgManagement ? OrgRole.MEMBER : OrgRole.OWNER,
},
update: {},
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Redesigned the app layout with a new collapsible sidebar navigation, replacing the previous top navigation bar. [#1097](https://github.com/sourcebot-dev/sourcebot/pull/1097)
- Expired offline license keys no longer crash the process. An expired key now degrades to the unlicensed state. [#1109](https://github.com/sourcebot-dev/sourcebot/pull/1109)
- [**Breaking Change**] Changed the default role assignment to `Owner` for organizations on the free tier. [#1234](https://github.com/sourcebot-dev/sourcebot/pull/1234)
- Improved the `setup-sourcebot` wizard: prompts for a setup directory, clarifies that secrets are stored locally in `.env`, switches multi-select to Tab, hides "No results" until a real search runs, and detects/cleans up conflicting Docker deployments and volumes before starting. [#1232](https://github.com/sourcebot-dev/sourcebot/pull/1232)

## [4.17.2] - 2026-05-16
Expand Down
25 changes: 16 additions & 9 deletions docs/docs/configuration/auth/roles-and-permissions.mdx
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
---
title: Roles and Permissions
sidebarTitle: Roles and permissions
title: Members and roles
sidebarTitle: Members and roles
---

Each member has a role which defines their permissions within an organization:
Sourcebot provides different role types to help you control access and permissions across your organization.

| Role | Permission |
| :--- | :--------- |
| `Owner` | An organization can have one or more `Owner`s. Owners have 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, 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. |
| `Owner` | Owners have full administrative control over all organization-level settings, including user management, access control, billing, and audit logs, in addition to all permissions a member can perform. |
| `Member` | Members have access and to use all standard features, such as code search, ask, mcp, etc., as well as their account settings. Members **cannot access** organization-level administration pages. |
| `Guest` | Guests are users that access Sourcebot without a account when [anonymous access](/docs/configuration/auth/access-settings#anonymous-access) is enabled. Guests can use some features, such as code search and browsing files, with additional limitations like not having chat history. Guests **cannot access** organization-level administration pages. |

## Managing owners
Note that when [permission syncing](/docs/features/permission-syncing) is enabled, users will only be able to view repositories they have access to. This applies to all roles, **including Owners**.

<Note>
On the free plan, all signed-in users are given the `Owner` role.
</Note>


## Managing member roles

import LicenseKeyRequired from '/snippets/license-key-required.mdx'

<LicenseKeyRequired feature="Multiple owners" />
<LicenseKeyRequired feature="Role management" />

organizations support multiple owners, allowing you to share administrative responsibilities across your team. Owners can promote members to owner and demote other owners back to member from **Settings -> Members**.
Owners can change the role of any user in the organization from **Settings → Members**. This lets you control who has administrative access by promoting trusted members to `Owner`, and scoping access back down to `Member` when administrative responsibilities change.

<Frame>
<img src="/images/managing_owners.png" alt="Members settings page showing team members and their roles" />
Expand Down
2 changes: 1 addition & 1 deletion docs/snippets/license-key-required.mdx
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@

<Note>
{feature} is only available with an active Enterprise license. Please add your [license key](/docs/license-key) to activate it.
{feature} is only available in a paid plan. Please add your [license key](/docs/license-key) to activate it.
</Note>
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@ export interface MembersListProps {
hasOrgManagement: boolean,
}

const ROLES_AND_PERMISSIONS_DOCS_LINK = "https://docs.sourcebot.dev/docs/configuration/auth/roles-and-permissions#managing-owners"
const ROLES_AND_PERMISSIONS_DOCS_LINK = "https://docs.sourcebot.dev/docs/configuration/auth/roles-and-permissions"

export const MembersList = ({ members, currentUserId, currentUserRole, orgName, hasOrgManagement }: MembersListProps) => {
const [searchQuery, setSearchQuery] = useState("")
Expand DownExpand Up@@ -256,7 +256,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName,
side="left"
sideOffset={12}
>
Upgrade your plan to promote members to owner. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link>
Upgrade your plan to manage roles. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link>
</TooltipContent>
)}
</Tooltip>
Expand All@@ -280,7 +280,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName,
{(ownerCount <= 1 || !hasOrgManagement) && (
<TooltipContent side="left" sideOffset={12}>
{!hasOrgManagement
? <>Upgrade your plan to demote owners. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link></>
? <>Upgrade your plan to manage roles. <Link href={ROLES_AND_PERMISSIONS_DOCS_LINK} className="text-link hover:underline">Learn more</Link></>
: "Cannot demote the last owner. Promote another member to owner first."
}
</TooltipContent>
Expand Down
19 changes: 13 additions & 6 deletions packages/web/src/lib/authUtils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import { createAudit } from "@/ee/features/audit/audit";
import { StatusCodes } from "http-status-codes";
import { ErrorCode } from "./errorCodes";
import { syncWithLighthouse } from "@/ee/features/lighthouse/servicePing";
import { hasEntitlement } from "./entitlements";

const logger = createLogger('web-auth-utils');

Expand DownExpand Up@@ -108,10 +109,12 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
});
}

// Subsequent users auto-join as MEMBER only when the org is in open
// self-serve mode. If memberApprovalRequired is true, the user is left
// without a membership and must submit an AccountRequest for an owner to
// approve via addUserToOrganization.
// Subsequent users auto-join only when the org is in open self-serve
// mode. Their role depends on the `org-management` entitlement: on paid
// plans they join as MEMBER, on free they join as OWNER (no role
// distinction exists without the entitlement). If memberApprovalRequired
// is true, the user is left without a membership and must submit an
// AccountRequest for an owner to approve via addUserToOrganization.
else if (!defaultOrg.memberApprovalRequired) {
// Don't exceed the licensed seat count. The user row still exists;
// they just aren't attached to the org until a seat frees up.
Expand All@@ -121,11 +124,13 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
return;
}

const hasOrgManagement = await hasEntitlement("org-management");

await __unsafePrisma.userToOrg.create({
data: {
userId: user.id,
orgId: SINGLE_TENANT_ORG_ID,
role: OrgRole.MEMBER,
role: hasOrgManagement ? OrgRole.MEMBER : OrgRole.OWNER,
}
});

Expand DownExpand Up@@ -212,6 +217,8 @@ export const addUserToOrganization = async (userId: string, orgId: number): Prom
} satisfies ServiceError;
}

const hasOrgManagement = await hasEntitlement('org-management');

await __unsafePrisma.$transaction(async (tx) => {
// Upsert rather than create: the user may already be a member from the
// self-serve auto-join in onCreateUser, in which case this call is
Expand All@@ -226,7 +233,7 @@ export const addUserToOrganization = async (userId: string, orgId: number): Prom
create: {
userId: user.id,
orgId: org.id,
role: OrgRole.MEMBER,
role: hasOrgManagement ? OrgRole.MEMBER : OrgRole.OWNER,
},
update: {},
});
Expand Down
Loading