Skip to content

V4 #311

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open

V4 #311

wants to merge 28 commits into from

Conversation

msukkari
Copy link
Contributor

@msukkari msukkari commented May 22, 2025

Auth Changes

This version makes significant changes to the authentication behavior in Sourcebot. For a detailed description of the new auth providers, visit the docs

  • Auth is now required for every Sourcebot deployment
  • The first account to login to a Sourcebot deployment is the owner. All new registrations after this first account is made must be approved by the owner.
  • The concept of Sourcebot API keys has been added to support authed API calls (ex. by an MCP client or agent)
  • Credential and email code auth providers are included in OSS, while OAuth providers are in EE
  • Enterprise licenses with an unlimited number of seats can enable public access, allowing for unauthorized access to their deployment

Enterprise License Changes

Enterprise licenses now include the number of seats supported by that license. This seat count is enforced when new members attempt to join the deployment.

Summary by CodeRabbit

  • New Features

    • Introduced public access mode (Enterprise Edition) allowing unauthenticated users to access Sourcebot, configurable per organization.
    • Added API key management: users can create, view, and delete API keys for programmatic access.
    • Implemented guest user role and account request workflow for organization membership, including approval and rejection flows.
    • Expanded SSO and JIT provisioning support for enterprise authentication providers (GitHub, GitLab, Google, Okta, Keycloak).
    • Added license management and seat usage visibility in organization settings.
    • New UI components: API Keys management page, pending approval card, requests list, and enterprise features card.
  • Improvements

    • Navigation and settings menus now reflect actual authentication state and entitlements.
    • Enhanced invite and request handling with seat availability checks and notifications.
    • Documentation reorganized: clearer environment variable reference, authentication, and configuration guides.
    • Added support for public access and multi-tenancy entitlements in configuration and initialization.
  • Bug Fixes

    • Consistent error messages and improved error handling for membership, API key, and entitlement scenarios.
  • Style

    • Updated UI elements for better accessibility and clarity (checkboxes, switches, sidebar navigation, loading states).
  • Documentation

    • Expanded and restructured docs for authentication, environment variables, public access, and enterprise features.
    • Added new email templates for join request notifications and approvals.
  • Chores

    • Updated dependencies and centralized static asset references for email components.
    • Refactored internal constants and type definitions for clarity and maintainability.

@msukkari msukkari requested a review from brendan-kellam May 22, 2025 23:51
Copy link

coderabbitai bot commented May 22, 2025

Walkthrough

This update introduces public access and API key support to Sourcebot, refactors authentication and membership logic, and enhances organization and license management. It adds new database models, environment variables, and entitlement checks, restructures documentation, and updates UI components to support pending approvals, guest users, API key management, and enterprise features.

Changes

Files / Paths Change Summary
.env.development, packages/web/src/env.mjs Renamed OAuth environment variables, added new enterprise auth variables, and updated review agent API key support.
docs/... Major documentation restructuring: added/removed/renamed self-hosting configuration docs, added environment variable and authentication guides, updated navigation, and extended schema docs for public access.
packages/crypto/src/index.ts Added generateApiKey and getApiKeyPrefix functions for API key creation and parsing.
packages/db/prisma/..., packages/db/prisma/schema.prisma Added AccountRequest, ApiKey models, metadata field to Org, pendingApproval to User, and GUEST to OrgRole enum; updated migrations accordingly.
packages/web/src/actions.ts Refactored from session-based to user ID-based authentication, added API key/account request/guest user/public access support, and restructured org membership logic.
packages/web/src/ee/features/publicAccess/publicAccess.tsx New module for managing public access status and guest user creation with entitlement checks.
packages/web/src/ee/sso/sso.tsx New SSO provider and JIT provisioning logic for enterprise auth.
packages/web/src/features/entitlements/... Overhauled entitlements: added new plans and entitlements, improved license key parsing, added seat/unlimited seat logic, and centralized entitlement context.
packages/web/src/features/search/... Updated search, repo listing, and file source APIs to support API key authentication and guest role access.
packages/web/src/app/[domain]/... UI updates: added pending approval card, API key management page, enterprise features card, and requests list; refactored navigation, settings, and repo components for new access and seat logic.
packages/web/src/app/layout.tsx, packages/web/src/initialize.ts Layout and initialization logic now enforce entitlements for public/multi-tenancy, handle guest users, and sync declarative config accordingly.
packages/web/src/auth.ts Refactored provider setup, SSO, email login enablement, and user-org assignment with robust error handling and JIT provisioning.
packages/web/src/components/ui/checkbox.tsx, packages/web/src/components/ui/switch.tsx Added new reusable Checkbox and Switch UI primitives.
packages/web/src/emails/... Centralized email asset URLs, added join request notification/approval emails, and refactored email components for consistency.
packages/web/src/lib/... Added new error codes, constants, and posthog events for API key and request workflows; updated service error handling.
packages/web/package.json Added @radix-ui/react-checkbox and @radix-ui/react-switch dependencies.
schemas/v3/index.json, packages/schemas/src/v3/... Added enablePublicAccess to settings schema and types.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant WebApp
    participant Auth
    participant DB
    participant Entitlements

    User->>WebApp: Request access (with/without API key)
    WebApp->>Auth: Authenticate (userId or API key)
    Auth->>DB: Validate user/API key, check pendingApproval
    Auth->>Entitlements: Check public-access entitlement
    alt API key valid or public access enabled
        Auth-->>WebApp: Grant guest/member access
    else Pending approval
        Auth-->>WebApp: Show PendingApprovalCard
    else Not authorized
        Auth-->>WebApp: Deny access
    end
Loading
sequenceDiagram
    participant Owner
    participant WebApp
    participant DB
    participant Email

    Owner->>WebApp: Approve account request
    WebApp->>DB: Update User.pendingApproval = false, add membership
    WebApp->>Email: Send JoinRequestApprovedEmail to user
    WebApp-->>Owner: Confirmation
Loading
sequenceDiagram
    participant User
    participant WebApp
    participant DB

    User->>WebApp: Create API key
    WebApp->>DB: generateApiKey(), store prefix, associate with user/org
    WebApp-->>User: Show API key (once)
    User->>WebApp: Use API key for API requests
    WebApp->>DB: Validate API key prefix, check user/org
    WebApp-->>User: Grant access if valid
Loading

Possibly related PRs

  • sourcebot-dev/sourcebot#198: Refactored withOrgMembership for improved role-based access and membership handling, directly related to similar changes in this update.

Poem

In the warren of code, a new path appears,
With guest keys and public doors, no more locked ears.
API carrots for every bunny’s quest,
Pending approval? Just hop and rest.
Entitlements checked, the garden’s grown wide—
Sourcebot’s burrow welcomes all inside!
🐇✨🔑

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


Note

⚡️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 30th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

🔭 Outside diff range comments (5)
docs/docs/more/mcp-server.mdx (1)

6-8: 💡 Verification agent

🧩 Analysis chain

Update authentication documentation note

The current note states that the MCP server feature is only available when self-hosting with authentication disabled. However, according to the PR objectives, authentication is now mandatory for all Sourcebot deployments.


🏁 Script executed:

#!/bin/bash
# Check if there are any mentions of authentication being mandatory in the codebase
rg -i "authentication.*(mandatory|required)" --type mdx --type ts --type tsx

Length of output: 106


🏁 Script executed:

#!/bin/bash
# Retry searching for “authentication” being marked mandatory or required in MDX docs
rg -i "authentication.*(mandatory|required)" -g '*.mdx'

Length of output: 516


Update authentication documentation note
The MCP server doc still says authentication must be disabled, but the PR makes authentication mandatory. Change “disabled” to “enabled”:

In docs/docs/more/mcp-server.mdx (lines 6–8):

-<Note>
-This feature is only available when [self-hosting](/self-hosting) with [authentication](/self-hosting/more/authentication) disabled.
-</Note>
+<Note>
+This feature is only available when [self-hosting](/self-hosting) with [authentication](/self-hosting/more/authentication) enabled.
+</Note>
packages/web/src/emails/inviteUserEmail.tsx (1)

52-57: 🛠️ Refactor suggestion

width="auto" is not a valid HTML attribute value
Most email clients follow HTML specs where the width attribute expects a number (pixels). Passing "auto" will be ignored or stripped by some providers and may break responsive centering.

-<Img
-    src={SOURCEBOT_LOGO_LIGHT_LARGE_URL}
-    width="auto"
-    height="60"
+<Img
+    src={SOURCEBOT_LOGO_LIGHT_LARGE_URL}
+    height="60"
+    style={{ width: 'auto' }}
packages/web/src/features/search/searchApi.ts (1)

198-215: 🛠️ Refactor suggestion

Two separate findMany queries can be merged
Running two round-trips to the DB for essentially the same table can be expensive under heavy load.

-const repos = new Map<string | number, Repo>();
-
-(await prisma.repo.findMany({
-    where: {
-        id: { in: numericIds },
-        orgId: org.id,
-    }
-})).forEach(r => repos.set(r.id, r));
-
-(await prisma.repo.findMany({
-    where: {
-        name: { in: stringIds },
-        orgId: org.id,
-    }
-})).forEach(r => repos.set(r.name, r));
+const repos = new Map<string | number, Repo>();
+await prisma.repo.findMany({
+    where: {
+        orgId: org.id,
+        OR: [
+            { id: { in: numericIds } },
+            { name: { in: stringIds } }
+        ]
+    }
+}).then(found => {
+    found.forEach(r => repos.set(typeof r.id === 'number' ? r.id : r.name, r));
+});
packages/web/src/app/[domain]/settings/members/page.tsx (1)

41-55: 🛠️ Refactor suggestion

Run the three org queries in parallel to cut TTFB
getOrgMembers, getOrgInvites, and getOrgAccountRequests are independent network / DB calls but are awaited sequentially, adding unnecessary latency to a server component. Wrapping them in Promise.all keeps the code readable while improving performance:

-const members = await getOrgMembers(domain);
-
-const invites = await getOrgInvites(domain);
-
-const requests = await getOrgAccountRequests(domain);
+const [members, invites, requests] = await Promise.all([
+  getOrgMembers(domain),
+  getOrgInvites(domain),
+  getOrgAccountRequests(domain),
+]);

Make sure to preserve the isServiceError checks for each result afterwards.

packages/web/src/auth.ts (1)

103-108: ⚠️ Potential issue

Await onCreateUser to guarantee side-effects before sign-in completes
onCreateUser performs DB writes that affect the newly created user’s membership. Not awaiting it means the promise can still be in flight when NextAuth finishes the sign-in flow, leading to race conditions (e.g. missing role in session).

- onCreateUser({ user: authJsUser });
+ await onCreateUser({ user: authJsUser });
🧹 Nitpick comments (55)
docs/docs/more/mcp-server.mdx (1)

179-179: Documentation updated with new API key environment variable

The SOURCEBOT_API_KEY environment variable has been correctly added to the documentation. This aligns with the PR objective of introducing API keys for authenticated API calls.

Consider adding a brief note explaining that this API key is required for authentication when making MCP server requests, especially since authentication is now mandatory according to the PR objectives.

packages/web/src/app/login/page.tsx (2)

15-15: Consider refining logging in production

Direct console.log calls may clutter server logs. Consider using a structured logger or guarding debug statements behind NODE_ENV checks.


18-18: Consider refining logging for session detection

The debug log on session detection is useful during development. You may want to replace this with a proper logging utility or remove it in production to avoid exposing sensitive flow details.

packages/web/src/components/ui/data-table.tsx (1)

74-74: Consider implementing TODO or creating a tracked issue

The TODO comment correctly identifies the need to consolidate repository button logic, but it might be better to either implement this change now or create a tracked issue rather than leaving a TODO in the code.

packages/web/src/app/[domain]/repos/addRepoButton.tsx (1)

20-24: Expose useSession loading state to avoid UI “flash”

useSession() is asynchronous; during the “loading” state the hook initially returns { data: null, status: "loading" }.
Because the button is rendered only when session?.user is truthy, users will briefly see the header without the ➕ icon and then the icon appears once the session resolves, causing a visual flicker.

Consider explicitly gating on the status instead of the raw data:

-const { data: session } = useSession();
+const { data: session, status } = useSession();

and inside the JSX:

-{session?.user && (
+{status === "authenticated" && session?.user && (

This eliminates the flash of missing content and reads clearer.

packages/crypto/src/index.ts (1)

37-44: Consider enhancing API key validation.

The function properly validates API key structure, but the error message could be more specific about the expected format.

-    if (sb !== 'sourcebot' || !prefix || !hmac) {
-        throw new Error('Invalid API key');
+    if (sb !== 'sourcebot' || !prefix || !hmac) {
+        throw new Error('Invalid API key format. Expected format: sourcebot-{prefix}-{hmac}');
packages/web/src/components/ui/checkbox.tsx (1)

1-31: Well-implemented accessible Checkbox component.

This new component follows best practices:

  • Uses Radix UI primitives for accessibility
  • Properly implements React.forwardRef for component composition
  • Includes appropriate styling for different states (focus, disabled, checked)
  • Provides clear visual feedback with the check icon

Consider adding JSDoc comments to enhance developer experience:

+/**
+ * A checkbox component built on Radix UI Checkbox primitive.
+ * Supports all standard checkbox props plus custom styling.
+ */
const Checkbox = React.forwardRef<
  React.ElementRef<typeof CheckboxPrimitive.Root>,
  React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>((...)
packages/web/src/app/[domain]/layout.tsx (1)

56-68: Well-implemented pending approval handling.

This code correctly checks for pending approval status and renders the appropriate UI component. The comment about single-tenant mode is helpful for understanding the limitation.

There's a slight indentation inconsistency in the else block, but it doesn't affect functionality.

Consider adjusting the indentation in the else block to match the surrounding code:

                if (env.SOURCEBOT_TENANCY_MODE === "single" && user?.pendingApproval) {
                    return <PendingApprovalCard domain={domain} />
                } else {
-                    return notFound();
-                }
+                return notFound();
+            }
packages/web/src/app/[domain]/settings/members/components/inviteMemberCard.tsx (1)

85-108: Prevent interaction with disabled card via pointer-events-none

Even with reduced opacity, links, inputs and buttons inside the card remain focusable/hoverable when seatsAvailable is false.
Adding pointer-events-none makes the entire card truly inert and avoids confusing keyboard-navigation behaviour.

-            <Card className={!seatsAvailable ? "opacity-70" : ""}>
+            <Card className={!seatsAvailable ? "opacity-70 pointer-events-none" : ""}>
packages/web/src/app/[domain]/settings/license/page.tsx (1)

70-74: URL-encode query parameters in mailto: link

licenseKey.id could contain characters that break the URL (spaces, +, = …).
Wrapping each query value with encodeURIComponent ensures the link works for all keys.

- <a href={`mailto:support@sourcebot.dev?subject=License Support - ${licenseKey.id}&body=License ID: ${licenseKey.id}`}>
+ <a
+   href={`mailto:support@sourcebot.dev?subject=${encodeURIComponent(
+         `License Support - ${licenseKey.id}`
+     )}&body=${encodeURIComponent(`License ID: ${licenseKey.id}`)}`}>
packages/web/src/features/search/fileSourceApi.ts (1)

11-13: Prefer an optional parameter over an explicit undefined = undefined default

apiKey?: string is the idiomatic way to mark the argument optional and avoids an unnecessary default assignment.

-export const getFileSource = async ({ fileName, repository, branch }: FileSourceRequest, domain: string, apiKey: string | undefined = undefined): Promise<FileSourceResponse | ServiceError> => sew(() =>
+export const getFileSource = async (
+  { fileName, repository, branch }: FileSourceRequest,
+  domain: string,
+  apiKey?: string,
+): Promise<FileSourceResponse | ServiceError> =>
+  sew(() =>

This is a non-functional change but improves readability and IntelliSense.

packages/web/src/app/[domain]/components/pendingApproval.tsx (2)

12-19: Consider adding error handling for auth() failure.

The component correctly retrieves the user session and returns null if no user ID is found. However, there's no explicit error handling if the auth() call fails unexpectedly.

export const PendingApprovalCard = async ({ domain }: PendingApprovalCardProps) => {
-    const session = await auth()
-    const userId = session?.user?.id
+    try {
+        const session = await auth()
+        const userId = session?.user?.id
+        
+        if (!userId) {
+            return null
+        }
+        
+        // Rest of component...
+    } catch (error) {
+        console.error("Failed to authenticate user:", error)
+        return null
+    }

-    if (!userId) {
-        return null
-    }

37-40: Consider adding feedback for failed resubmission attempts.

The ResubmitAccountRequestButton is used to resubmit an account request, but there's no clear feedback mechanism if this action fails. Consider wrapping it with error handling or a notification system.

<div className="flex flex-col items-center space-y-2 mt-4">
-    <ResubmitAccountRequestButton domain={domain} userId={userId} />
+    <ResubmitAccountRequestButton 
+        domain={domain} 
+        userId={userId} 
+        onError={(error) => {
+            // Handle error, e.g., show toast notification
+            console.error("Failed to resubmit request:", error)
+        }}
+    />
</div>
packages/db/prisma/migrations/20250522010540_add_api_key/migration.sql (1)

1-12: Good database schema for API key management.

The migration creates a well-structured ApiKey table with appropriate fields:

  • name: For user-friendly identification
  • prefix: For key identification (likely used for lookups without exposing full keys)
  • createdAt: For auditing and lifecycle management
  • lastUsedAt: For tracking usage and potentially identifying stale keys
  • orgId: For organization association
  • createdById: For creator association

The composite primary key (prefix, createdById) ensures uniqueness per creator and prefix.

Consider adding an index on orgId for improved query performance.

For efficiently querying API keys by organization, consider adding an index on the orgId column.

-- CreateTable
CREATE TABLE "ApiKey" (
    "name" TEXT NOT NULL,
    "prefix" TEXT NOT NULL,
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
    "lastUsedAt" TIMESTAMP(3),
    "orgId" INTEGER NOT NULL,
    "createdById" TEXT NOT NULL,

    CONSTRAINT "ApiKey_pkey" PRIMARY KEY ("prefix","createdById")
);

+-- CreateIndex
+CREATE INDEX "ApiKey_orgId_idx" ON "ApiKey"("orgId");
packages/web/src/emails/inviteUserEmail.tsx (2)

74-77: Missing alt text for host avatar
Screen-reader users won’t get any context. Even a brief description such as the host’s name helps accessibility.

-<Img
-    className="rounded-full"
-    src={host.avatarUrl ? host.avatarUrl : SOURCEBOT_PLACEHOLDER_AVATAR_URL}
-    width="64"
-    height="64"
+<Img
+    className="rounded-full"
+    src={host.avatarUrl ?? SOURCEBOT_PLACEHOLDER_AVATAR_URL}
+    width="64"
+    height="64"
+    alt={host.name ?? host.email}

90-93: Add descriptive alt text for organisation avatar
Same accessibility concern as above.

docs/self-hosting/configuration/authentication.mdx (3)

22-23: Grammar: use the phrasal verb “set up”

-You can setup emails to be sent
+You can set up emails to be sent

The verb form is “set up”; “setup” is the noun.

🧰 Tools
🪛 LanguageTool

[grammar] ~22-~22: The word “setup” is a noun. The verb is spelled with a space.
Context: ... and approved on registration. You can setup emails to be sent when new join request...

(NOUN_VERB_CONFUSION)


25-25: Missing comma after introductory clause

-Under the hood, Sourcebot uses Auth.js which supports
+Under the hood, Sourcebot uses Auth.js, which supports
🧰 Tools
🪛 LanguageTool

[uncategorized] ~25-~25: Possible missing comma found.
Context: ...er. Under the hood, Sourcebot uses Auth.js which supports [many providers](https:/...

(AI_HYDRA_LEO_MISSING_COMMA)


35-35: Hyphenate compound adjective “6-digit”

-Email codes are 6 digit codes
+Email codes are 6-digit codes
🧰 Tools
🪛 LanguageTool

[grammar] ~35-~35: When ‘6-digit’ is used as a modifier, it is usually spelled with a hyphen.
Context: ...`. ### Email codes --- Email codes are 6 digit codes sent to a provided email. Email c...

(WORD_ESSAY_HYPHEN)


[style] ~35-~35: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...6 digit codes sent to a provided email. Email codes are enabled when transactional em...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

packages/web/src/features/search/searchApi.ts (1)

161-165: Magic header string – consider a central constant
If Zoekt’s header name ever changes you’ll need to hunt in multiple files. Declare something like export const TENANT_HEADER = "X-Tenant-ID"; in a shared module and reuse it here and in the Go codegen.

packages/web/src/emails/joinRequestApprovedEmail.tsx (1)

85-94: Verify the placeholder domain value

The preview props use "~" as the orgDomain value. While this might be intentional as a placeholder, consider using a more realistic example like "example" or "demo" for better preview rendering.

-    orgDomain: '~',
+    orgDomain: 'example',
packages/web/src/app/[domain]/settings/apiKeys/columns.tsx (2)

36-36: Consider using React Router instead of page reload

Using window.location.reload() isn't the most React-friendly approach and causes a full page refresh. Consider using a state update or router navigation for a smoother user experience.

-            window.location.reload()
+            // Option 1: Use React Router
+            router.refresh()
+            
+            // Option 2: Use a callback pattern
+            onDeleteSuccess?.()

For option 1, you'd need to import:

import { useRouter } from "next/navigation"

For option 2, you'd need to add the callback to the component props:

function ApiKeyActions({ apiKey, onDeleteSuccess }: { apiKey: ApiKeyColumnInfo, onDeleteSuccess?: () => void })

90-148: Reduce code duplication in date formatting

The date formatting logic is duplicated between the createdAt and lastUsedAt columns. Consider extracting this into a reusable formatter function.

+// Add this helper function
+const formatDate = (dateString: string | null) => {
+    if (!dateString) return null;
+    
+    return new Date(dateString).toLocaleDateString("en-US", {
+        month: "short",
+        day: "numeric",
+        year: "numeric",
+    });
+}

// Then replace the formatting logic in both column cells
-            const date = new Date(row.original.createdAt)
-            return (
-                <div className="py-2 text-muted-foreground">
-                    {date.toLocaleDateString("en-US", {
-                        month: "short",
-                        day: "numeric",
-                        year: "numeric",
-                    })}
-                </div>
-            )
+            return (
+                <div className="py-2 text-muted-foreground">
+                    {formatDate(row.original.createdAt)}
+                </div>
+            )
packages/web/src/app/[domain]/settings/apiKeys/page.tsx (5)

22-22: Consider adding typing for ApiKey interface instead of inline type.

Using an inline type for apiKeys state makes it harder to maintain consistency across components. Create a dedicated interface for API keys that can be reused.

+interface ApiKey {
+  name: string;
+  createdAt: Date;
+  lastUsedAt: Date | null;
+}

-const [apiKeys, setApiKeys] = useState<{ name: string; createdAt: Date; lastUsedAt: Date | null }[]>([]);
+const [apiKeys, setApiKeys] = useState<ApiKey[]>([]);

35-44: Error handling could be more specific.

The error handling doesn't distinguish between different types of service errors, which could help users understand the issue better.

if (isServiceError(keys)) {
-   setError("Failed to load API keys");
+   setError(`Failed to load API keys: ${keys.message}`);
    toast({
        title: "Error",
-       description: "Failed to load API keys",
+       description: `Failed to load API keys: ${keys.message}`,
        variant: "destructive",
    });
    return;
}

103-118: Enhance clipboard API error handling.

The clipboard API may fail for several reasons including permissions. Providing more context in the error message would help users troubleshoot.

navigator.clipboard.writeText(newlyCreatedKey)
    .then(() => {
        setCopySuccess(true);
        setTimeout(() => setCopySuccess(false), 2000);
    })
-   .catch(() => {
+   .catch((error) => {
+       console.error("Clipboard error:", error);
        toast({
            title: "Error",
-           description: "Failed to copy API key to clipboard",
+           description: "Failed to copy API key to clipboard. You may need to grant clipboard permission.",
            variant: "destructive",
        });
    });

198-205: Enhance security warning for API key visibility.

The security warning for one-time visibility of API keys could be more prominent with stronger visual cues.

<div className="space-y-4">
-   <div className="flex items-center gap-2 p-3 border border-yellow-200 dark:border-yellow-800 bg-yellow-50 dark:bg-yellow-900/20 rounded-md text-yellow-700 dark:text-yellow-400">
+   <div className="flex items-center gap-2 p-3 border-2 border-yellow-500 dark:border-yellow-600 bg-yellow-50 dark:bg-yellow-900/40 rounded-md text-yellow-700 dark:text-yellow-400 font-medium">
        <AlertTriangle className="h-5 w-5 flex-shrink-0" />
        <p className="text-sm">
-           This is the only time you&apos;ll see this API key. Make sure to copy it now.
+           <strong>IMPORTANT:</strong> This is the only time you&apos;ll see this API key. It cannot be recovered later. Make sure to copy it now.
        </p>
    </div>

259-264: Consider adding pagination for API key table.

As users accumulate more API keys, the table might become unwieldy. Adding pagination would improve performance and usability.

You could extend the DataTable component to support pagination:

<DataTable
    columns={tableColumns}
    data={tableData}
    searchKey="name"
    searchPlaceholder="Search API keys..."
+   pagination={{
+     pageSize: 10,
+     pageIndex: 0
+   }}
/>
packages/web/src/app/[domain]/connections/components/newConnectionCard.tsx (2)

14-19: Improve documentation for configPathProvided prop.

The configPathProvided prop lacks documentation about its purpose and impact on component behavior.

interface NewConnectionCardProps {
    className?: string
    role: OrgRole
-   configPathProvided: boolean
+   /** 
+    * When true, indicates that connections are managed via configuration file
+    * and the UI should be disabled with appropriate messaging
+    */
+   configPathProvided: boolean
}

79-85: Consider providing more actionable guidance in disabled state.

The current message explains why the UI is disabled but doesn't offer users guidance on what to do next.

{isDisabled && (
    <p className="mt-4 text-xs text-center text-muted-foreground">
        {configPathProvided 
-           ? "Connections are managed through the configuration file."
+           ? "Connections are managed through the configuration file. See the documentation for details on how to update connections."
            : "Only organization owners can manage connections."}
    </p>
)}
packages/web/src/app/[domain]/settings/(general)/components/enterpriseFeaturesCard.tsx (2)

51-66: Clarify that the checkbox is informational only.

The disabled checkbox might confuse users who expect to be able to toggle the setting directly.

<div className="space-y-1">
    <Label htmlFor="public-access-status">Public Access</Label>
    <p className="text-sm text-muted-foreground">
-       When enabled, enables unauthenticated access to your Sourcebot deployment. Requires an enterprise license with an unlimited number of seats.
+       When enabled, enables unauthenticated access to your Sourcebot deployment. Requires an enterprise license with an unlimited number of seats. This setting can only be configured via license management.
    </p>
</div>

37-69: Consider adding more enterprise features.

The card is titled "Enterprise Features" but currently only displays a single feature (public access). The structure suggests it's designed to accommodate more features.

The component is well-structured to display enterprise features. Consider extending it to include other enterprise features in the future, such as:

  1. SSO integration status
  2. Custom branding options
  3. Advanced security features
  4. Audit logging

Each feature could follow the same pattern of checkbox + description, making the component easily extensible.

packages/db/prisma/schema.prisma (3)

166-166: Add documentation for the Org metadata field.

The new metadata JSON field has a comment referencing a schema but provides no information about what it stores.

- metadata    Json?       // For schema see orgMetadataSchema in packages/web/src/types.ts
+ metadata    Json?       // Stores org configuration including public access settings, custom branding, etc.
+                         // For schema definition see orgMetadataSchema in packages/web/src/types.ts

180-184: Add documentation for the new GUEST role.

The new GUEST role is added without any documentation on its purpose or permissions.

enum OrgRole {
  OWNER
  MEMBER
- GUEST
+ GUEST  // Limited access role for users with restricted permissions
}

241-242: Consider adding a field for tracking approval timestamp.

The User model has a pendingApproval flag but no way to track when approval occurred.

model User {
  // ...existing fields
  pendingApproval Boolean @default(true)
+ approvedAt      DateTime?
  accountRequest  AccountRequest?
  // ...
}

This would provide useful auditing information about when users were approved.

packages/web/src/app/[domain]/settings/members/page.tsx (1)

64-78: Seat-usage banner should hide when unlimited seats
Inside the banner condition you already check seats && seats !== SOURCEBOT_UNLIMITED_SEATS, but the outer wrapper will still reserve layout space. Consider short-circuiting entirely to avoid rendering empty elements when seats are unlimited.

packages/web/src/auth.ts (2)

1-10: Duplicate side-effect import of next-auth/jwt
Lines 1 and 9 both import the same module solely for side effects. One import is sufficient:

-import 'next-auth/jwt';-import 'next-auth/jwt';
+import 'next-auth/jwt';

192-204: Unhandled branch for multi-tenant mode leaves new users orphaned
The else block is single-tenant–specific, but the inner comment // TODO(auth): handle multi tenant case suggests multi-tenant sign-ups are not yet covered. Users in multi-tenant deployments will neither be auto-provisioned nor queued for approval, effectively locking them out.

Consider:

  1. Moving this block outside the single-tenant guard, or
  2. Adding an explicit check and throwing a descriptive error until multi-tenant logic is implemented.

Would you like help drafting a safe fallback mechanism?

docs/self-hosting/configuration/environment-variables.mdx (2)

13-15: Minor grammar and spelling fixes (“Check out”, “declarative”)

-... postgres database. Checkout the [auth docs]...
+... Postgres database. Check out the [auth docs]...

-... if no value is provided. Generated using `openssl rand -base64 33` | <p>Used to validate login session cookies</p> |
+... if no value is provided, generated using `openssl rand -base64 33` | <p>Used to validate login session cookies.</p> |

-... The container relative path to the declerative configuration file.
+... The container-relative path to the declarative configuration file.
🧰 Tools
🪛 LanguageTool

[grammar] ~13-~13: This sentence should probably be started with a verb instead of the noun ‘Checkout’. If not, consider inserting a comma for better clarity.
Context: ...d at rest within the postgres database. Checkout the [auth docs](/self-hosting/configura...

(SENT_START_NN_DT)


[grammar] ~14-~14: This sentence should probably be started with a verb instead of the noun ‘Checkout’. If not, consider inserting a comma for better clarity.
Context: ... EMAIL_FROM_ADDRESS must also be set. Checkout the [auth docs](/self-hosting/configura...

(SENT_START_NN_DT)


31-31: Hyphenate compound adjective “real-world”

-The maximum real world duration
+The maximum real-world duration
🧰 Tools
🪛 LanguageTool

[uncategorized] ~31-~31: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...ALL_TIME_MS|10000` |

The maximum real world duration (in milliseconds) per zoekt qu...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

packages/web/src/app/[domain]/settings/members/components/requestsList.tsx (3)

31-96: Add loading states to improve user experience during approvals and rejections.

The component correctly handles approval and rejection flows, but it doesn't indicate to users when an action is in progress. This could lead to multiple clicks if the operation takes time.

Consider adding loading states to the approve and reject operations:

export const RequestsList = ({ requests, currentUserRole }: RequestsListProps) => {
    const [searchQuery, setSearchQuery] = useState("")
    const [dateSort, setDateSort] = useState<"newest" | "oldest">("newest")
    const [isApproveRequestDialogOpen, setIsApproveRequestDialogOpen] = useState(false)
    const [isRejectRequestDialogOpen, setIsRejectRequestDialogOpen] = useState(false)
    const [requestToAction, setRequestToAction] = useState<Request | null>(null)
+   const [loadingRequestId, setLoadingRequestId] = useState<string | null>(null)
    const { toast } = useToast();
    const router = useRouter();
    const domain = useDomain();
    const captureEvent = useCaptureEvent();

    // ...existing code...

    const onApproveRequest = useCallback((requestId: string) => {
+       setLoadingRequestId(requestId);
        approveAccountRequest(requestId, domain)
            .then((response) => {
                if (isServiceError(response)) {
                    toast({
                        description: `❌ Failed to approve request. Reason: ${response.message}`
                    })
                    captureEvent('wa_requests_list_approve_request_fail', {
                        error: response.errorCode,
                    })
                } else {
                    toast({
                        description: `✅ Request approved successfully.`
                    })
                    captureEvent('wa_requests_list_approve_request_success', {})
                    router.refresh();
                }
+               setLoadingRequestId(null);
            });
    }, [domain, toast, router, captureEvent]);

    // Apply the same pattern to onRejectRequest

Then update the buttons to show loading state:

<Button
    variant="outline"
    size="sm"
    className="gap-2"
+   disabled={loadingRequestId === request.id}
    onClick={() => {
        setRequestToAction(request);
        setIsApproveRequestDialogOpen(true);
    }}
>
-   <CheckCircle className="h-4 w-4" />
+   {loadingRequestId === request.id ? (
+       <span className="animate-spin">...</span>
+   ) : (
+       <CheckCircle className="h-4 w-4" />
+   )}
    Approve
</Button>

78-96: Include specific error message in rejection failure toast.

For approval failures, you show the specific error message, but not for rejection failures.

Include the specific error message in the rejection failure toast for consistency:

onRejectRequest = useCallback((requestId: string) => {
    rejectAccountRequest(requestId, domain)
        .then((response) => {
            if (isServiceError(response)) {
                toast({
-                   description: `❌ Failed to reject request.`
+                   description: `❌ Failed to reject request. Reason: ${response.message}`
                })
                captureEvent('wa_requests_list_reject_request_fail', {
                    error: response.errorCode,
                })
            } else {
                // ...

132-175: Ensure proper handling of empty requests array.

The code logic has a condition check that might cause rendering issues.

The condition at line 124 checks requests.length === 0, but at line 132, it maps through filteredRequests. If requests is empty, filteredRequests will also be empty, but the render logic doesn't account for this edge case properly.

To fix this, change the map call to use filteredRequests consistently:

{requests.length === 0 || (filteredRequests.length === 0 && searchQuery.length > 0) ? (
    <div className="flex flex-col items-center justify-center h-96 p-4">
        <p className="font-medium text-sm">No Pending Requests Found</p>
        <p className="text-sm text-muted-foreground mt-2">
            {filteredRequests.length === 0 && searchQuery.length > 0 ? "No pending requests found matching your filters." : "There are currently no pending requests to join your organization."}
        </p>
    </div>
) : (
-   filteredRequests.map((request) => (
+   filteredRequests.map((request) => (
        // Existing code...
    ))
)}
packages/web/src/emails/joinRequestSubmittedEmail.tsx (1)

129-139: Fix placeholder domain in preview props.

The preview props use a tilde character '~' as the orgDomain, which isn't a valid domain value and could cause issues when testing the email component.

Replace the placeholder domain with a more realistic example:

JoinRequestSubmittedEmail.PreviewProps = {
    baseUrl: 'http://localhost:3000',
    requestor: {
        name: 'Alan Turing',
        email: 'alan.turing@example.com',
        avatarUrl: `http://localhost:3000/placeholder_avatar.png`,
    },
    orgName: 'Enigma',
-   orgDomain: '~',
+   orgDomain: 'enigma',
    orgImageUrl: `http://localhost:3000/placeholder_avatar.png`,
} satisfies JoinRequestSubmittedEmailProps;
packages/web/src/env.mjs (2)

27-42: Add descriptive comments to Enterprise Auth environment variables.

The newly added Enterprise Auth environment variables lack descriptive comments explaining their purpose and requirements.

Add comments to describe the Enterprise Auth variables and their usage:

// Enterprise Auth
+// Enable Just-In-Time provisioning for SSO users (automatically adds users to organization)
AUTH_EE_ENABLE_JIT_PROVISIONING: booleanSchema.default('false'),
+// GitHub OAuth configuration
AUTH_EE_GITHUB_CLIENT_ID: z.string().optional(),
AUTH_EE_GITHUB_CLIENT_SECRET: z.string().optional(),
AUTH_EE_GITHUB_BASE_URL: z.string().default("https://github.com"),
+// GitLab OAuth configuration
AUTH_EE_GITLAB_CLIENT_ID: z.string().optional(),
AUTH_EE_GITLAB_CLIENT_SECRET: z.string().optional(),
AUTH_EE_GITLAB_BASE_URL: z.string().default("https://gitlab.com"),
+// Google OAuth configuration
AUTH_EE_GOOGLE_CLIENT_ID: z.string().optional(),
AUTH_EE_GOOGLE_CLIENT_SECRET: z.string().optional(),
+// Okta OAuth configuration
AUTH_EE_OKTA_CLIENT_ID: z.string().optional(),
AUTH_EE_OKTA_CLIENT_SECRET: z.string().optional(),
AUTH_EE_OKTA_ISSUER: z.string().optional(),
+// Keycloak OAuth configuration
AUTH_EE_KEYCLOAK_CLIENT_ID: z.string().optional(),
AUTH_EE_KEYCLOAK_CLIENT_SECRET: z.string().optional(),
AUTH_EE_KEYCLOAK_ISSUER: z.string().optional(),

76-76: Add a comment explaining the REVIEW_AGENT_API_KEY environment variable.

The new REVIEW_AGENT_API_KEY variable lacks description of its purpose and usage.

Add a descriptive comment for this environment variable:

OPENAI_API_KEY: z.string().optional(),
-REVIEW_AGENT_API_KEY: z.string().optional(),
+// API key for review agent to make authenticated requests to Sourcebot API
+REVIEW_AGENT_API_KEY: z.string().optional(),
REVIEW_AGENT_LOGGING_ENABLED: booleanSchema.default('true'),
packages/web/src/ee/features/publicAccess/publicAccess.tsx (2)

24-31: Treat missing metadata as “no metadata” rather than an internal error.

org.metadata can legitimately be null/undefined for newly-created orgs. Returning a 500 Invalid organization metadata in that case is misleading and blocks UI flows.

Recommended approach:

  1. If org.metadata is falsy, treat it as {}.
  2. Only return INVALID_ORG_METADATA when the JSON structure is present but fails validation.

This yields a friendlier 200/false response and avoids penalising new tenants.


71-81: Specify an explicit return type and value for createGuestUser.

createGuestUser currently returns Promise<unknown> – callers can’t rely on a consistent contract (e.g. void | ServiceError).
Either:

  • return true on success, or
  • change to Promise<ServiceError | undefined> and document it.

This prevents accidental misuse such as:

const ok = await createGuestUser(domain);  // always `undefined`

Also applies to: 128-128

packages/web/src/features/entitlements/server.ts (2)

41-59: Variable shadowing hampers readability.

Inside getPlan a new const licenseKey = getLicenseKey() shadows the outer licenseKey string.
Rename one of them to avoid cognitive overhead and accidental misuse:

-        const licenseKey = getLicenseKey();
+        const parsedKey = getLicenseKey();
-        if (!licenseKey) { … }
+        if (!parsedKey) { … }
🧰 Tools
🪛 Biome (1.9.4)

[error] 42-42: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


64-73: Reuse existing parsing logic in getSeats().

getSeats re-decodes the payload instead of delegating to getLicenseKey().
This duplicates work and re-introduces the un-guarded parse risk mentioned earlier.

-    const licenseKey = env.SOURCEBOT_EE_LICENSE_KEY;
-    if (licenseKey && licenseKey.startsWith(eeLicenseKeyPrefix)) {
-        const payload = licenseKey.substring(eeLicenseKeyPrefix.length);
-        const { seats } = decodeLicenseKeyPayload(payload);
-        return seats;
-    }
+    const parsedKey = getLicenseKey();
+    if (parsedKey) {
+        return parsedKey.seats;
+    }
🧰 Tools
🪛 Biome (1.9.4)

[error] 66-66: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

packages/web/src/initialize.ts (2)

174-180: Create guest user only when public access is enabled.

A guest account is added whenever the license permits public access, regardless of whether the org actually turned it on.
This may clutter the user table in deployments that never intend to expose public access.

Guard on publicAccessEnabled (use getPublicAccessStatus) before creating the guest:

if (hasPublicAccessEntitlement) {
    const isEnabled = await getPublicAccessStatus(SINGLE_TENANT_ORG_DOMAIN);
    if (isEnabled === true) {
        
    }
}

198-204: Watch callback should handle async errors.

syncDeclarativeConfig returns a promise, but the FS watch callback ignores rejections, leading to unhandled-rejection warnings.

watch(configPath, () => {
    syncDeclarativeConfig(configPath).catch(console.error);
});
packages/web/src/actions.ts (3)

31-33: Duplicate import from the same module

Lines 31–32 import from "./features/entitlements/server" twice:

import { getPlan, getSeats, SOURCEBOT_UNLIMITED_SEATS } from "./features/entitlements/server";
import { hasEntitlement } from "./features/entitlements/server";

Combine them to avoid churn and keep the import list tidy:

-import { getPlan, getSeats, SOURCEBOT_UNLIMITED_SEATS } from "./features/entitlements/server";
-import { hasEntitlement } from "./features/entitlements/server";
+import { getPlan, getSeats, SOURCEBOT_UNLIMITED_SEATS, hasEntitlement } 
+  from "./features/entitlements/server";

426-433: Incorrect error code on unexpected exception in createApiKey

Inside the catch block every failure is reported as API_KEY_ALREADY_EXISTS, even when the root cause is unrelated (e.g. DB outage). This hides real problems and misleads clients.

-  errorCode: ErrorCode.API_KEY_ALREADY_EXISTS,
+  errorCode: ErrorCode.UNEXPECTED_ERROR,   // or introduce API_KEY_CREATION_FAILED

Also consider logging the original error (console.error(e)) before returning.


163-172: Spelling / naming nit – getAuthorizationPrecendence

precendence is misspelled. Prefer the correct getAuthorizationPrecedence to avoid confusion and improve autocomplete.

-const getAuthorizationPrecendence = (role: OrgRole): number => {
+const getAuthorizationPrecedence = (role: OrgRole): number => {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b71a28b and aa3ce38.

⛔ Files ignored due to path filters (3)
  • docs/images/login.png is excluded by !**/*.png
  • docs/images/login_basic.png is excluded by !**/*.png
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (92)
  • .env.development (1 hunks)
  • docs/docs.json (1 hunks)
  • docs/docs/agents/review-agent.mdx (2 hunks)
  • docs/docs/more/mcp-server.mdx (1 hunks)
  • docs/docs/more/roles-and-permissions.mdx (1 hunks)
  • docs/self-hosting/configuration.mdx (0 hunks)
  • docs/self-hosting/configuration/authentication.mdx (1 hunks)
  • docs/self-hosting/configuration/declarative-config.mdx (0 hunks)
  • docs/self-hosting/configuration/environment-variables.mdx (1 hunks)
  • docs/self-hosting/configuration/transactional-emails.mdx (1 hunks)
  • docs/self-hosting/more/authentication.mdx (0 hunks)
  • docs/self-hosting/overview.mdx (2 hunks)
  • docs/snippets/schemas/v3/index.schema.mdx (2 hunks)
  • packages/backend/src/constants.ts (1 hunks)
  • packages/crypto/src/index.ts (1 hunks)
  • packages/db/prisma/migrations/20250519235121_add_org_metadata_field/migration.sql (1 hunks)
  • packages/db/prisma/migrations/20250520021309_add_pending_approval_user/migration.sql (1 hunks)
  • packages/db/prisma/migrations/20250520022249_add_account_request/migration.sql (1 hunks)
  • packages/db/prisma/migrations/20250520182630_add_guest_role/migration.sql (1 hunks)
  • packages/db/prisma/migrations/20250522010540_add_api_key/migration.sql (1 hunks)
  • packages/db/prisma/schema.prisma (4 hunks)
  • packages/mcp/src/client.ts (3 hunks)
  • packages/mcp/src/env.ts (1 hunks)
  • packages/mcp/src/index.ts (3 hunks)
  • packages/schemas/src/v3/index.schema.ts (2 hunks)
  • packages/schemas/src/v3/index.type.ts (1 hunks)
  • packages/web/package.json (2 hunks)
  • packages/web/src/actions.ts (52 hunks)
  • packages/web/src/app/[domain]/browse/[...path]/page.tsx (0 hunks)
  • packages/web/src/app/[domain]/components/navigationMenu.tsx (4 hunks)
  • packages/web/src/app/[domain]/components/pendingApproval.tsx (1 hunks)
  • packages/web/src/app/[domain]/components/repositorySnapshot.tsx (2 hunks)
  • packages/web/src/app/[domain]/components/resubmitAccountRequestButton.tsx (1 hunks)
  • packages/web/src/app/[domain]/components/settingsDropdown.tsx (4 hunks)
  • packages/web/src/app/[domain]/components/topBar.tsx (0 hunks)
  • packages/web/src/app/[domain]/connections/[id]/components/repoList.tsx (1 hunks)
  • packages/web/src/app/[domain]/connections/[id]/page.tsx (3 hunks)
  • packages/web/src/app/[domain]/connections/components/newConnectionCard.tsx (2 hunks)
  • packages/web/src/app/[domain]/connections/page.tsx (2 hunks)
  • packages/web/src/app/[domain]/layout.tsx (3 hunks)
  • packages/web/src/app/[domain]/page.tsx (0 hunks)
  • packages/web/src/app/[domain]/repos/addRepoButton.tsx (4 hunks)
  • packages/web/src/app/[domain]/repos/columns.tsx (3 hunks)
  • packages/web/src/app/[domain]/repos/page.tsx (2 hunks)
  • packages/web/src/app/[domain]/repos/repositoryTable.tsx (3 hunks)
  • packages/web/src/app/[domain]/settings/(general)/components/enterpriseFeaturesCard.tsx (1 hunks)
  • packages/web/src/app/[domain]/settings/(general)/page.tsx (2 hunks)
  • packages/web/src/app/[domain]/settings/apiKeys/columns.tsx (1 hunks)
  • packages/web/src/app/[domain]/settings/apiKeys/page.tsx (1 hunks)
  • packages/web/src/app/[domain]/settings/components/sidebar-nav.tsx (2 hunks)
  • packages/web/src/app/[domain]/settings/layout.tsx (3 hunks)
  • packages/web/src/app/[domain]/settings/license/page.tsx (1 hunks)
  • packages/web/src/app/[domain]/settings/members/components/inviteMemberCard.tsx (6 hunks)
  • packages/web/src/app/[domain]/settings/members/components/requestsList.tsx (1 hunks)
  • packages/web/src/app/[domain]/settings/members/page.tsx (3 hunks)
  • packages/web/src/app/api/(server)/repos/route.ts (2 hunks)
  • packages/web/src/app/api/(server)/search/route.ts (2 hunks)
  • packages/web/src/app/api/(server)/source/route.ts (2 hunks)
  • packages/web/src/app/layout.tsx (2 hunks)
  • packages/web/src/app/login/components/loginForm.tsx (1 hunks)
  • packages/web/src/app/login/page.tsx (1 hunks)
  • packages/web/src/auth.ts (5 hunks)
  • packages/web/src/components/ui/checkbox.tsx (1 hunks)
  • packages/web/src/components/ui/data-table.tsx (1 hunks)
  • packages/web/src/components/ui/switch.tsx (1 hunks)
  • packages/web/src/components/ui/tab-switcher.tsx (1 hunks)
  • packages/web/src/ee/features/billing/actions.ts (8 hunks)
  • packages/web/src/ee/features/publicAccess/publicAccess.tsx (1 hunks)
  • packages/web/src/ee/sso/sso.tsx (1 hunks)
  • packages/web/src/emails/constants.ts (1 hunks)
  • packages/web/src/emails/inviteUserEmail.tsx (5 hunks)
  • packages/web/src/emails/joinRequestApprovedEmail.tsx (1 hunks)
  • packages/web/src/emails/joinRequestSubmittedEmail.tsx (1 hunks)
  • packages/web/src/emails/magicLinkEmail.tsx (2 hunks)
  • packages/web/src/env.mjs (2 hunks)
  • packages/web/src/features/agents/review-agent/nodes/fetchFileContent.ts (2 hunks)
  • packages/web/src/features/entitlements/constants.ts (1 hunks)
  • packages/web/src/features/entitlements/planProvider.tsx (1 hunks)
  • packages/web/src/features/entitlements/server.ts (1 hunks)
  • packages/web/src/features/entitlements/useHasEntitlement.ts (1 hunks)
  • packages/web/src/features/entitlements/usePlan.ts (0 hunks)
  • packages/web/src/features/search/fileSourceApi.ts (3 hunks)
  • packages/web/src/features/search/listReposApi.ts (3 hunks)
  • packages/web/src/features/search/searchApi.ts (6 hunks)
  • packages/web/src/initialize.ts (6 hunks)
  • packages/web/src/lib/constants.ts (1 hunks)
  • packages/web/src/lib/errorCodes.ts (1 hunks)
  • packages/web/src/lib/posthogEvents.ts (1 hunks)
  • packages/web/src/lib/serviceError.ts (1 hunks)
  • packages/web/src/middleware.ts (1 hunks)
  • packages/web/src/types.ts (1 hunks)
  • schemas/v3/index.json (1 hunks)
💤 Files with no reviewable changes (7)
  • packages/web/src/app/[domain]/browse/[...path]/page.tsx
  • docs/self-hosting/configuration/declarative-config.mdx
  • packages/web/src/features/entitlements/usePlan.ts
  • packages/web/src/app/[domain]/page.tsx
  • docs/self-hosting/configuration.mdx
  • packages/web/src/app/[domain]/components/topBar.tsx
  • docs/self-hosting/more/authentication.mdx
🧰 Additional context used
🧬 Code Graph Analysis (22)
packages/web/src/app/login/components/loginForm.tsx (1)
packages/web/src/app/[domain]/onboard/components/demoCard.tsx (1)
  • DemoCard (10-39)
packages/web/src/app/[domain]/settings/(general)/page.tsx (1)
packages/web/src/app/[domain]/settings/(general)/components/enterpriseFeaturesCard.tsx (1)
  • EnterpriseFeaturesCard (16-71)
packages/web/src/app/[domain]/repos/repositoryTable.tsx (1)
packages/web/src/app/[domain]/repos/columns.tsx (1)
  • columns (96-264)
packages/web/src/app/api/(server)/repos/route.ts (1)
packages/web/src/features/search/listReposApi.ts (1)
  • listRepositories (8-47)
packages/web/src/app/[domain]/repos/addRepoButton.tsx (1)
packages/web/src/auth.ts (1)
  • session (229-238)
packages/web/src/components/ui/switch.tsx (1)
packages/web/src/lib/utils.ts (1)
  • cn (11-13)
packages/web/src/app/[domain]/repos/page.tsx (1)
packages/web/src/app/[domain]/repos/repositoryTable.tsx (1)
  • RepositoryTable (14-91)
packages/web/src/app/[domain]/components/resubmitAccountRequestButton.tsx (3)
packages/web/src/components/hooks/use-toast.ts (2)
  • useToast (194-194)
  • toast (194-194)
packages/web/src/actions.ts (1)
  • createAccountRequest (1403-1507)
packages/web/src/components/ui/button.tsx (1)
  • Button (56-56)
packages/web/src/app/layout.tsx (2)
packages/web/src/features/entitlements/planProvider.tsx (1)
  • PlanProvider (13-21)
packages/web/src/features/entitlements/server.ts (1)
  • getEntitlements (80-107)
packages/web/src/emails/magicLinkEmail.tsx (1)
packages/web/src/emails/constants.ts (1)
  • SOURCEBOT_LOGO_LIGHT_LARGE_URL (1-1)
packages/web/src/components/ui/checkbox.tsx (1)
packages/web/src/lib/utils.ts (1)
  • cn (11-13)
packages/web/src/features/entitlements/useHasEntitlement.ts (2)
packages/web/src/features/entitlements/constants.ts (1)
  • Entitlement (21-21)
packages/web/src/features/entitlements/planProvider.tsx (1)
  • PlanContext (6-6)
packages/web/src/features/entitlements/planProvider.tsx (1)
packages/web/src/features/entitlements/constants.ts (1)
  • Entitlement (21-21)
packages/web/src/features/search/searchApi.ts (2)
packages/mcp/src/client.ts (1)
  • search (6-23)
packages/web/src/actions.ts (3)
  • sew (47-55)
  • withAuth (57-108)
  • withOrgMembership (139-187)
packages/mcp/src/client.ts (1)
packages/mcp/src/env.ts (1)
  • env (8-25)
packages/web/src/features/search/listReposApi.ts (3)
packages/web/src/features/search/types.ts (1)
  • ListRepositoriesResponse (23-23)
packages/mcp/src/types.ts (2)
  • ListRepositoriesResponse (24-24)
  • ServiceError (32-32)
packages/web/src/actions.ts (3)
  • sew (47-55)
  • withAuth (57-108)
  • withOrgMembership (139-187)
packages/web/src/app/[domain]/components/settingsDropdown.tsx (2)
packages/web/src/auth.ts (1)
  • session (229-238)
packages/web/src/components/ui/dropdown-menu.tsx (2)
  • DropdownMenuItem (188-188)
  • DropdownMenuSeparator (192-192)
packages/web/src/emails/inviteUserEmail.tsx (1)
packages/web/src/emails/constants.ts (3)
  • SOURCEBOT_LOGO_LIGHT_LARGE_URL (1-1)
  • SOURCEBOT_PLACEHOLDER_AVATAR_URL (3-3)
  • SOURCEBOT_ARROW_IMAGE_URL (2-2)
packages/web/src/auth.ts (5)
packages/web/src/features/entitlements/server.ts (1)
  • hasEntitlement (75-78)
packages/web/src/ee/sso/sso.tsx (2)
  • getSSOProviders (15-80)
  • handleJITProvisioning (82-144)
packages/web/src/lib/constants.ts (2)
  • SINGLE_TENANT_ORG_ID (29-29)
  • SINGLE_TENANT_ORG_DOMAIN (30-30)
packages/web/src/lib/serviceError.ts (1)
  • ServiceErrorException (16-20)
packages/web/src/actions.ts (1)
  • createAccountRequest (1403-1507)
packages/web/src/ee/sso/sso.tsx (2)
packages/web/src/lib/serviceError.ts (1)
  • notFound (91-97)
packages/web/src/features/entitlements/server.ts (2)
  • getSeats (64-73)
  • SOURCEBOT_UNLIMITED_SEATS (8-8)
packages/web/src/features/entitlements/server.ts (2)
packages/web/src/features/entitlements/constants.ts (4)
  • Plan (10-10)
  • Entitlement (21-21)
  • entitlementsByPlan (27-33)
  • isValidEntitlement (23-25)
packages/web/src/lib/constants.ts (1)
  • SOURCEBOT_SUPPORT_EMAIL (33-33)
packages/web/src/ee/features/publicAccess/publicAccess.tsx (4)
packages/web/src/actions.ts (1)
  • sew (47-55)
packages/web/src/types.ts (1)
  • orgMetadataSchema (3-5)
packages/web/src/features/entitlements/server.ts (2)
  • hasEntitlement (75-78)
  • getPlan (36-62)
packages/web/src/lib/constants.ts (3)
  • SOURCEBOT_SUPPORT_EMAIL (33-33)
  • SOURCEBOT_GUEST_USER_ID (27-27)
  • SOURCEBOT_GUEST_USER_EMAIL (28-28)
🪛 LanguageTool
docs/self-hosting/configuration/authentication.mdx

[grammar] ~22-~22: The word “setup” is a noun. The verb is spelled with a space.
Context: ... and approved on registration. You can setup emails to be sent when new join request...

(NOUN_VERB_CONFUSION)


[uncategorized] ~25-~25: Possible missing comma found.
Context: ...er. Under the hood, Sourcebot uses Auth.js which supports [many providers](https:/...

(AI_HYDRA_LEO_MISSING_COMMA)


[grammar] ~35-~35: When ‘6-digit’ is used as a modifier, it is usually spelled with a hyphen.
Context: ...`. ### Email codes --- Email codes are 6 digit codes sent to a provided email. Email c...

(WORD_ESSAY_HYPHEN)


[style] ~35-~35: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...6 digit codes sent to a provided email. Email codes are enabled when transactional em...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[uncategorized] ~108-~108: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...ry clearing your cookies & performing a full page refresh (Cmd/Ctrl + Shift + R on most...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

docs/self-hosting/configuration/environment-variables.mdx

[grammar] ~13-~13: This sentence should probably be started with a verb instead of the noun ‘Checkout’. If not, consider inserting a comma for better clarity.
Context: ...d at rest within the postgres database. Checkout the [auth docs](/self-hosting/configura...

(SENT_START_NN_DT)


[grammar] ~14-~14: This sentence should probably be started with a verb instead of the noun ‘Checkout’. If not, consider inserting a comma for better clarity.
Context: ... EMAIL_FROM_ADDRESS must also be set. Checkout the [auth docs](/self-hosting/configura...

(SENT_START_NN_DT)


[uncategorized] ~31-~31: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...ALL_TIME_MS|10000` |

The maximum real world duration (in milliseconds) per zoekt qu...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

🪛 Biome (1.9.4)
packages/web/src/lib/posthogEvents.ts

[error] 254-254: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 259-259: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 264-264: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 265-265: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

packages/web/src/features/entitlements/server.ts

[error] 28-28: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 42-42: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 66-66: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (121)
packages/web/src/app/[domain]/connections/[id]/components/repoList.tsx (1)

183-183: Fixed container height with padding for scrollbar
Changing from max-h-96 overflow-scroll to a fixed h-96 ensures consistent layout height, and adding pr-4 neatly offsets the scrollbar so content won’t be obscured. This aligns well with the ScrollArea component’s built-in scroll logic and improves UX.

packages/web/src/components/ui/switch.tsx (1)

1-29: Well-implemented Switch component with proper accessibility and styling

This is a well-structured React component that wraps the Radix UI Switch primitive with comprehensive Tailwind CSS styling. The component correctly:

  • Uses forwardRef to pass refs to the underlying primitive
  • Properly handles class name composition with the cn utility
  • Implements appropriate accessibility features (focus states, disabled states)
  • Has proper transitions for state changes
  • Uses semantic naming conventions
packages/web/src/app/[domain]/connections/page.tsx (2)

8-8: LGTM: Environment configuration import added

The import of the environment configuration is correctly added.


34-34: Correctly passes configuration path status to UI component

This change passes whether the CONFIG_PATH environment variable is defined to the NewConnectionCard component, allowing the UI to adapt appropriately when connections are managed via configuration files rather than the UI.

packages/backend/src/constants.ts (1)

18-18: Public access feature flag correctly added with appropriate default value

The enablePublicAccess setting has been added with a default value of false, which is appropriate for a feature that requires an enterprise license. This aligns with the PR objective of allowing enterprise licenses with unlimited seats to enable public access.

packages/web/package.json (2)

47-47: Approve addition of Checkbox dependency

The new @radix-ui/react-checkbox dependency at version ^1.3.2 aligns with the introduced Checkbox UI component usage.


59-59: Approve addition of Switch dependency

The new @radix-ui/react-switch dependency at version ^1.2.4 supports the introduced Switch component usage.

docs/self-hosting/configuration/transactional-emails.mdx (1)

9-9: Approve new email trigger documentation

The added bullet point for sending emails when organization join requests are created or accepted accurately reflects the new transactional email capability.

docs/docs/more/roles-and-permissions.mdx (1)

7-7: Approve simplified roles description

The updated introductory line clearly and concisely describes that member roles define permissions without unnecessary environment-specific context.

packages/web/src/app/[domain]/settings/(general)/page.tsx (2)

9-9: Approve import of EnterpriseFeaturesCard

Adding the EnterpriseFeaturesCard import aligns with the newly introduced public access feature in the settings page.


51-53: Approve inclusion of EnterpriseFeaturesCard in JSX

Rendering the EnterpriseFeaturesCard with the required domain prop correctly integrates the new enterprise feature panel into the general settings.

packages/db/prisma/migrations/20250520182630_add_guest_role/migration.sql (1)

1-2: Looks good - this migration adds GUEST role support

This migration adds the GUEST role to the OrgRole enum, supporting the enterprise feature that allows unauthorized users to access the deployment when public access is enabled.

packages/db/prisma/migrations/20250520021309_add_pending_approval_user/migration.sql (1)

1-2: Looks good - this migration supports the new approval workflow

This migration adds a pendingApproval flag to the User table with default value true, supporting the requirement that "subsequent registrations require the owner's approval" as mentioned in the PR objectives.

packages/db/prisma/migrations/20250519235121_add_org_metadata_field/migration.sql (1)

1-2: Looks good - this migration supports organization metadata storage

This migration adds a flexible JSONB metadata field to the Org table, which will be used to store public access settings and other organization-specific configuration data.

packages/web/src/lib/serviceError.ts (1)

91-95: Looks good - enhanced error message flexibility

This change improves error handling by allowing custom "not found" error messages, which will provide more context-specific information for authentication and resource access failures.

packages/web/src/app/login/components/loginForm.tsx (1)

61-65: Appropriate environment-based rendering of the demo card

The conditional rendering of the DemoCard component based on the NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT environment variable is appropriate. This ensures the demo card is only shown in cloud environments and not in self-hosted deployments, which aligns with the PR objectives.

packages/web/src/app/[domain]/repos/page.tsx (2)

10-10: Fixed syntax error with closing brace

The redundant closing brace has been correctly removed, fixing the block structure.


19-19: Simplified component usage by removing environment-based prop

The isAddNewRepoButtonVisible prop has been removed, which aligns with the PR's goal of shifting from environment variable checks to entitlement-based authorization. This simplifies the component interface and improves maintainability.

packages/schemas/src/v3/index.type.ts (1)

82-85: Well-documented enterprise feature for public access

The addition of the enablePublicAccess optional boolean property is well-documented and properly implemented. The JSDoc comment clearly explains that this is an Enterprise Edition feature requiring an unlimited seat license, which aligns with the PR objectives for supporting public access in enterprise deployments.

packages/web/src/app/api/(server)/repos/route.ts (2)

12-12: Added API key header extraction

The addition of API key extraction from the request headers is correct and follows the pattern established in other API routes. Using the nullish coalescing operator ensures the API key is properly passed as undefined when not present.


21-21: Updated listRepositories call to support API key authentication

The listRepositories function call now correctly passes the API key as a second parameter, enabling API key-based authentication for repository listing. This aligns with the PR's objective of facilitating authenticated API calls.

packages/mcp/src/env.ts (1)

12-13: API key support added correctly

The addition of an optional SOURCEBOT_API_KEY environment variable aligns well with the PR objectives of supporting authenticated API calls for MCP clients and agents.

docs/docs/agents/review-agent.mdx (2)

56-57: Documentation updated with new API key requirement

Good addition of the REVIEW_AGENT_API_KEY environment variable to the documentation, clearly explaining its purpose for the review agent to access the Sourcebot API.


80-81: Example configuration updated with API key

The example Docker Compose configuration has been properly updated to include the new required API key.

packages/web/src/app/api/(server)/source/route.ts (2)

13-14: API key extraction implemented correctly

The code properly extracts the API key from the request headers with a safe fallback to undefined if not present.


30-31: API key forwarded to getFileSource function

The extracted API key is correctly passed to the getFileSource function, enabling API key-based authentication for file source retrieval.

packages/web/src/app/api/(server)/search/route.ts (2)

13-14: API key extraction implemented correctly

The code properly extracts the API key from the request headers with a safe fallback to undefined if not present.


30-31: API key forwarded to search function

The extracted API key is correctly passed to the search function, enabling API key-based authentication for search operations.

packages/web/src/features/agents/review-agent/nodes/fetchFileContent.ts (2)

6-6: Added necessary import for API key authentication

The import of env from @/env.mjs is required to access the API key for authentication.


19-19: Properly implemented API key authentication for file content fetching

The function now passes the review agent API key to getFileSource as required by the new authentication system. This change aligns with the broader API key authentication implementation across the codebase.

packages/web/src/app/[domain]/settings/components/sidebar-nav.tsx (2)

3-3: Added explicit React import

This import is necessary to use React types like React.ReactNode.


12-12: Enhanced sidebar navigation with React node support

Changing the title type from string to React.ReactNode allows for more flexible content in navigation items, such as badges for pending account requests or icons alongside text.

packages/web/src/components/ui/tab-switcher.tsx (2)

5-5: Added ReactNode import for enhanced tab labels

This import is necessary to use the ReactNode type in the component props.


8-8: Enhanced tab labels with React node support

Changing the label type from string to ReactNode allows for more flexible content in tab labels, such as badges or icons. This is consistent with the similar enhancement made to the SidebarNav component.

schemas/v3/index.json (1)

65-69: Added enterprise feature for public access

The new enablePublicAccess property is correctly implemented as a boolean setting with appropriate documentation indicating it's an enterprise feature requiring a license with unlimited seats. The default value of false is a good security practice.

packages/web/src/lib/errorCodes.ts (1)

27-31: Good addition of error codes for the new features

These new error codes align perfectly with the PR objectives, providing specific error handling for:

  • Organization seat limits (ORG_SEAT_COUNT_REACHED)
  • Connection configuration validation (CONNECTION_CONFIG_PATH_SET)
  • Organization metadata validation for public access settings (INVALID_ORG_METADATA)
  • API key management operations (API_KEY_ALREADY_EXISTS, API_KEY_NOT_FOUND)

The naming follows the existing convention and these additions will support the mandatory authentication, API key functionality, and seat limit enforcement described in the PR.

packages/web/src/app/layout.tsx (2)

11-11: Appropriate import for the entitlements system

Adding getEntitlements import supports the shift from plan-based to entitlements-based feature control.


32-32: Good transition from plan to entitlements-based approach

Switching from plan={getPlan()} to entitlements={getEntitlements()} provides more granular control over features based on license entitlements rather than a single plan string. This change supports the new enterprise licensing model with seat counts and public access capabilities.

packages/web/src/middleware.ts (1)

13-13: Simplified conditional logic for authentication paths

Removing the dependency on SOURCEBOT_AUTH_ENABLED environment variable aligns with making authentication mandatory for all Sourcebot deployments as specified in the PR objectives. This change streamlines the middleware logic while supporting the new entitlement-driven access control model.

packages/web/src/lib/constants.ts (1)

27-28: Improved naming for guest user constants

Renaming from SINGLE_TENANT_USER_* to SOURCEBOT_GUEST_USER_* better reflects the purpose of these constants in supporting guest users for the public access feature in enterprise licenses. The email change from default@sourcebot.dev to guest@sourcebot.dev also more clearly indicates the guest role of this user.

.env.development (1)

21-24: LGTM! OAuth provider variables updated for enterprise edition

The prefix change from AUTH_ to AUTH_EE_ for OAuth provider variables correctly aligns with the PR objective of reserving OAuth providers for the enterprise edition.

packages/web/src/types.ts (1)

1-7: Well-structured schema for organization metadata

The implementation of orgMetadataSchema using Zod provides proper runtime validation for the new publicAccessEnabled flag mentioned in the PR objectives. Making the field optional is a good approach to ensure backward compatibility with existing data.

docs/docs.json (1)

84-89: Documentation structure properly updated for new auth features

The documentation reorganization creates a more logical structure with dedicated pages for authentication, environment variables, transactional emails, and declarative configuration. This aligns well with the authentication system changes described in the PR objectives.

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

25-25: LGTM! Environment configuration import added

The import of the environment configuration is necessary for the new disabled state logic.


49-49: Connection settings now respect declarative configuration

The isDisabled flag now considers both user role and presence of a configuration file, which prevents UI-based modifications when connections are managed through declarative config files.


97-97: Settings components properly use the enhanced disabled condition

All settings components now consistently use the new isDisabled flag instead of just checking owner status, ensuring UI elements are properly disabled when configuration is managed via config files.

Also applies to: 102-103, 104-104

packages/web/src/features/entitlements/useHasEntitlement.ts (1)

3-5: Clean refactoring of entitlement retrieval logic

The hook has been simplified by directly using React's Context API instead of depending on an intermediary hook. This is a good change that reduces indirection while maintaining the same functionality.

Also applies to: 8-8

packages/web/src/emails/constants.ts (1)

1-3:

❓ Verification inconclusive

Good centralization of email asset URLs

Centralizing these URLs as constants is a good practice that ensures consistency across email templates and simplifies future updates.

Consider whether these external URLs from Framer's CDN should be configuration-based rather than hardcoded, to handle different environments or potential CDN changes:


🏁 Script executed:

#!/bin/bash
# Check if these image URLs are accessible
curl -s -I "https://framerusercontent.com/images/hFtwmNtuQSpgYuypFCICKr384.png" | head -n 1
curl -s -I "https://framerusercontent.com/images/hEk6w8i85onEQ3mrn1jhHzq8c.png" | head -n 1
curl -s -I "https://framerusercontent.com/images/YW1HBfmcq7oLix1MFvLJObMv4Q.png" | head -n 1

# Look for any environment configuration files that might be better places for these URLs
fd -t f -e ts -e js "config|env" --exec grep -l "URL"

Length of output: 399


Good centralization and URLs are reachable – consider externalizing them
All three Framer CDN URLs return HTTP 200. You currently centralize them in packages/web/src/emails/constants.ts, which is great for consistency. However, since you already have an environment config in packages/backend/src/env.ts (and none in the web package), you may want to externalize these into a web-side config or environment variables to handle:

  • different deployment environments (staging, production)
  • future CDN changes without a code rebuild

Locations to review:

  • packages/web/src/emails/constants.ts

Consider a pattern like:

// packages/web/src/config/emailImages.ts
export const SOURCEBOT_LOGO_LIGHT_LARGE_URL =
  process.env.REACT_APP_SOURCEBOT_LOGO_LIGHT_LARGE_URL ??
  "https://framerusercontent.com/images/hFtwmNtuQSpgYuypFCICKr384.png";
// …and so on

And load these via your build tool or .env files for each environment.

packages/mcp/src/client.ts (3)

30-31: API key authentication added consistently

The API key header is consistently implemented across all API endpoints.


47-48: API key authentication added consistently

The API key header is consistently implemented across all API endpoints.


12-13:

❓ Verification inconclusive

Added API key authentication support

The implementation correctly adds the API key header conditionally when the environment variable is set, supporting the new authentication system.

What is the significance of setting X-Org-Domain to '~'? This appears to be a placeholder but might affect how the API identifies which organization to work with.


🏁 Script executed:

#!/bin/bash
# Search for usages of X-Org-Domain header to understand its purpose
rg -A 5 -B 5 "X-Org-Domain" --glob "*.{ts,js}"

Length of output: 9640


Verify placeholder X-Org-Domain usage
The MCP client is currently hard-coding the X-Org-Domain header to "~", which looks like a placeholder rather than a real organization identifier. Since the server routes reject missing domains and use this header to scope requests, please confirm the intended behavior here:

  • Is ~ a deliberate default (e.g. “all organizations” or a sandbox domain)?
  • Should this be configurable (via an environment variable or a function parameter) instead of hard-coded?
  • If you intend to keep ~, please document its semantics in the client’s README or inline comments.
packages/web/src/app/[domain]/repos/columns.tsx (3)

96-96: Simplified component API by removing conditional visibility parameter

Good refactoring to remove the isAddNewRepoButtonVisible parameter and unconditionally render the <AddRepoButton />. This properly encapsulates the visibility logic within the AddRepoButton component itself.

Also applies to: 102-102


185-185: Added horizontal padding reset for consistent button styling

Added px-0 class to reset horizontal padding for a cleaner button appearance.


230-230: Added horizontal padding reset for consistent button styling

Added px-0 class to reset horizontal padding for a cleaner button appearance.

packages/web/src/app/[domain]/repos/addRepoButton.tsx (1)

25-61: Change looks good – logic now relies solely on authentication state
Nothing functionally incorrect is spotted; the component is slimmer and removes the redundant prop.

packages/web/src/app/[domain]/repos/repositoryTable.tsx (1)

49-76: Column factory update aligns with new signature

The removal of the extra parameter and dependency cleanup are correct.
Be aware that columns(domain) is invoked twice (once during loading for skeletons, once later).
If the factory becomes expensive in the future, consider memoising it in a separate hook – not required now.

packages/web/src/app/[domain]/settings/layout.tsx (3)

41-45: Verify membership lookup field

me.memberships.find((membership) => membership.id === org.id)?.role

In many schemas the Membership object’s id is its own primary key, not the organisation id (which would be orgId).
Double-check the type: if the field is really orgId, the lookup will silently fail and treat the user as non-member, triggering an error.

Would you run a quick check on the Membership type and update the property if necessary?


64-73: SidebarNav title prop may need to accept ReactNode

title is now a JSX element containing a badge.
If SidebarNav’s type still expects string, this will break type-checking and could cause rendering issues (e.g., missing key warnings when mapping).
Either update SidebarNavItem to title: React.ReactNode or move the badge logic into SidebarNav itself.

Please confirm the prop type in components/sidebar-nav.tsx; adjust as needed.


80-87: Navigation additions look correct

Adding “API Keys” and “License” to the sidebar is straightforward and matches the new pages introduced in the PR.

packages/crypto/src/index.ts (1)

27-35: API Key generation implementation looks good.

The generateApiKey function uses secure cryptographic practices:

  • Verifies encryption key presence
  • Generates random prefix for additional entropy
  • Uses HMAC-SHA256 for secure, deterministic key generation
  • Follows a clear format structure
packages/web/src/features/entitlements/planProvider.tsx (1)

4-17: Good transition from plan to entitlements approach.

The changes correctly refactor the code from using a single plan string to handling multiple entitlements. This aligns with the PR objective of enhancing license management.

packages/mcp/src/index.ts (2)

24-24: Clear authentication error guidance.

Good addition of instructions to help users understand what to do if they encounter authentication errors.


155-155: Consistent authentication instructions.

The instructions for handling authentication errors are consistently applied across tools, which is good for user experience.

docs/snippets/schemas/v3/index.schema.mdx (2)

67-70: Schema addition for Enterprise public access feature

The new enablePublicAccess property is well-defined with clear type, description, and default value. The description properly indicates this is an Enterprise Edition feature and explains its requirements.


181-184: Consistent schema definition in both locations

Good job maintaining consistency by adding identical property definitions in both the Settings definition and the top-level settings property. This ensures the schema validation works correctly regardless of which schema section is referenced.

packages/web/src/features/search/listReposApi.ts (4)

1-1: Added required OrgRole import

The import of OrgRole from @sourcebot/db is correctly added to support the new role-based access control in the API.


8-10: Updated API signature to support authentication enhancements

The listRepositories function now correctly supports API key authentication and properly destructures the full organization object rather than just the ID.


19-19: Updated tenant ID header construction

Using org.id.toString() ensures proper string conversion of the organization ID for the header value.


46-46: Added role-based access control and API key support

The function now enforces a minimum role requirement of OrgRole.GUEST, enables public access for single-tenant deployments, and passes through the API key correctly.

This approach aligns with the PR objectives to support authenticated API calls and allow public access for enterprise licenses.

docs/self-hosting/overview.mdx (2)

48-48: Added important note about AUTH_URL configuration

Good addition of guidance for users deploying behind a domain, with a helpful link to the detailed environment variables documentation.


76-83: Added clear documentation about authentication and owner account creation

This new step clearly explains the authentication workflow and initial owner account setup, which is crucial for users to understand the new mandatory authentication system.

The documentation aligns perfectly with the PR objectives about making authentication mandatory and establishing the first user as the owner.

packages/web/src/emails/magicLinkEmail.tsx (2)

12-12: Imported logo URL constant

Using a centralized constant improves maintainability by removing duplication and making updates easier.


29-29: Replaced dynamic URL construction with constant

Using the imported constant simplifies the code and ensures consistency across email templates.

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

17-17: Good addition of the auth import.

The new authentication system requires this import to access the user session.


30-31: Clean implementation of authentication status check.

This properly retrieves the current user session and creates a boolean flag for authenticated status, which is more robust than using environment flags.


34-34: Layout improvement.

Changing from w-screen to w-full prevents potential horizontal overflow issues while maintaining full-width layout.


72-98: Proper conditional rendering based on authentication.

This implementation correctly shows navigation items only to authenticated users, with an additional environment check for the Agents item. This aligns with the new authentication requirements where these sections should only be accessible to authenticated users.


136-136: Good removal of the prop.

Removing the displaySettingsOption prop is appropriate since the SettingsDropdown component now handles its own visibility based on session state.

packages/web/src/app/[domain]/components/settingsDropdown.tsx (4)

6-6: Good addition of the LogIn icon.

This icon is appropriately used for the new sign-in option in the dropdown menu.


84-119: Well-implemented authentication-based UI.

The dropdown now correctly handles both authenticated and unauthenticated states:

  • For authenticated users: shows user avatar, email, and logout option
  • For unauthenticated users: shows a sign-in option with the new LogIn icon

This aligns with the new authentication system requirements.


120-120: Cleaner separator placement.

Moving the separator outside the conditional blocks simplifies the code structure.


161-168: Appropriate conditional rendering of Settings link.

The Settings menu item is now only shown to authenticated users, which is consistent with the navigation menu changes and the overall authentication strategy.

packages/web/src/app/[domain]/layout.tsx (3)

16-19: Good additions for new authentication features.

These imports support the new public access and pending approval features.


36-37: Properly implemented public access check.

This code checks for both the entitlement and the actual status of public access for the domain, enforcing authentication only when public access is disabled. This aligns with the PR objective of allowing enterprise licenses with unlimited seats to enable public access.


50-52: Good query enhancement.

Including the user information in the membership query provides the necessary data for handling pending approval states.

packages/schemas/src/v3/index.schema.ts (2)

66-69: Well-defined public access property.

The enablePublicAccess property is correctly defined with:

  • Boolean type
  • Clear description indicating it's an enterprise feature
  • Appropriate documentation of the unlimited seats requirement
  • Default value of false (disabled by default)

This aligns with the PR objective of adding public access as an enterprise feature.


179-184: Consistent schema extension.

The property is correctly added to both the Settings definition and the top-level settings object, maintaining schema consistency.

packages/web/src/features/search/fileSourceApi.ts (1)

45-46: Double-check withAuth parameter order to avoid silent auth bypass

withAuth(cb, allowSingleTenantUnauthedAccess, apiKey) appears to receive true for allowSingleTenantUnauthedAccess.
If the function previously expected (cb, apiKey?), this change could inadvertently grant unauthenticated access.
Please verify the helper’s signature and update call sites to match.

packages/web/src/app/[domain]/components/pendingApproval.tsx (3)

1-7: Imports and component dependencies look good.

The component imports all necessary UI components, icons, and utilities for the pending approval card functionality.


8-10: Props interface is properly defined.

The PendingApprovalCardProps interface correctly defines the required domain prop which will be used to identify the organization.


20-59: UI implementation is well structured and follows good practices.

The component has a well-organized layout with clear visual hierarchy:

  1. The LogoutEscapeHatch in the top-right corner provides an exit route
  2. The Sourcebot logo establishes branding
  3. Clear card title and description explain the pending approval state
  4. The ResubmitAccountRequestButton provides user action
  5. Support information with a link to GitHub discussions offers help

The component properly integrates with the new account request workflow and aligns with the PR objective of requiring owner approval for new registrations.

packages/web/src/ee/features/billing/actions.ts (8)

16-19: Good refactoring to use userId and withOrgMembership.

The createOnboardingSubscription function has been properly refactored to use userId instead of session-based authentication, and now relies on the organization object provided by withOrgMembership.


57-57: Improved efficiency by using org.id directly.

The code now uses org.id directly from the withOrgMembership context instead of querying the database again, which reduces redundant database operations.


111-114: Consistent refactoring pattern applied.

The createStripeCheckoutSession function follows the same refactoring pattern as other functions, properly using userId and withOrgMembership.


124-124: Consistent use of org.id in database queries.

The refactoring consistently uses org.id in database queries, which aligns with the broader authentication refactoring pattern.


167-170: Consistent refactoring in getCustomerPortalSessionLink.

The getCustomerPortalSessionLink function has been properly refactored to use userId and withOrgMembership, maintaining consistency across all billing actions.


188-191: Consistent refactoring in getSubscriptionBillingEmail.

The getSubscriptionBillingEmail function has been properly refactored to use userId and withOrgMembership, maintaining consistency across all billing actions.


207-210: Consistent refactoring in changeSubscriptionBillingEmail.

The changeSubscriptionBillingEmail function has been properly refactored to use userId and withOrgMembership, maintaining consistency across all billing actions.


228-231: Consistent refactoring in getSubscriptionInfo.

The getSubscriptionInfo function has been properly refactored to use userId and withOrgMembership, maintaining consistency across all billing actions.

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

20-22: Simplified props interface by removing authEnabled.

The RepositorySnapshotProps interface has been simplified by removing the authEnabled property, which aligns with the PR objective of making authentication mandatory.


24-26: Component parameter simplified to match updated interface.

The RepositorySnapshot function parameters have been updated to match the simplified interface, removing the authEnabled parameter.


60-66: Simplified conditional rendering.

The conditional rendering has been simplified by removing the check for authentication status, which aligns with the PR objective of making authentication mandatory.


84-99: EmptyRepoState component has been simplified.

The EmptyRepoState component has been simplified to always show a static message with a link to the connections overview documentation, removing conditional rendering based on authentication status. This aligns with the PR objective of making authentication mandatory.

packages/db/prisma/migrations/20250522010540_add_api_key/migration.sql (1)

13-17: Appropriate foreign key constraints.

The foreign key constraints ensure referential integrity:

  1. The orgId reference to Org ensures the API key is associated with a valid organization
  2. The createdById reference to User ensures the API key is associated with a valid user

The CASCADE delete behavior ensures that API keys are automatically deleted when the associated organization or user is deleted, preventing orphaned records.

packages/web/src/emails/inviteUserEmail.tsx (1)

18-18: Nice consolidation of asset URLs
Importing from a shared constants.ts file removes the need to pass baseUrl through every email component and reduces the risk of typos. 👍

packages/web/src/features/search/searchApi.ts (1)

127-131:

✅ Verification successful

Potential oversight: callers must forward apiKey
The function signature now includes apiKey, but existing call-sites may not yet supply it, resulting in silent unauthenticated requests. Please grep the repo for search( and verify each invocation.


🏁 Script executed:

#!/bin/bash
# Find occurrences of the updated search() call that don't pass a third argument
rg -n "search\\s*\\(" --type ts,tsx | grep -v "apiKey"

Length of output: 83


I wasn’t able to detect any call-sites because the --type ts,tsx filter isn’t configured in ripgrep. Let’s re-run the search against all .ts/.tsx files:


🏁 Script executed:

#!/bin/bash
# List all invocations of `search(` in TS/TSX, excluding its own definition
rg -n "\bsearch\s*\(" -g '*.ts' -g '*.tsx' | grep -v 'searchApi\.ts'

Length of output: 1258


Let’s narrow down to only those files that actually import our search from searchApi.ts and list their invocations:


🏁 Script executed:

#!/bin/bash
# For each TS/TSX file importing search from searchApi.ts, show all search(...) calls (excluding its own definition)
files=$(rg -l "from ['\"].*searchApi['\"]" -g '*.ts' -g '*.tsx')
for f in $files; do
  echo "$f"
  grep -n "\bsearch\s*(" "$f" | grep -v "searchApi\.ts"
done

Length of output: 748


🏁 Script executed:

#!/bin/bash
# Show imports and context around the search call in fileSourceApi.ts
sed -n '1,80p' packages/web/src/features/search/fileSourceApi.ts

Length of output: 2044


All search call-sites correctly forward apiKey
I’ve verified the two modules importing from searchApi.ts

  • packages/web/src/features/search/fileSourceApi.ts
  • packages/web/src/app/api/(server)/search/route.ts
    —and confirmed both invocations include the apiKey parameter. No callers are missing the third argument, so no changes are required.
packages/web/src/features/entitlements/constants.ts (4)

7-8: Plan types expanded for enterprise tiers - LGTM!

The addition of "enterprise-unlimited" and "enterprise-custom" plan types aligns with the PR objectives for supporting different enterprise license types, including unlimited seats.


16-19: Entitlements match features mentioned in PR objectives - LGTM!

The new entitlements ("billing", "public-access", "multi-tenancy", "sso") properly support the features mentioned in the PR objectives, particularly "public-access" which enables unauthorized users to access deployments with unlimited enterprise licenses.


23-25: Good addition of type guard for entitlement validation

Adding isValidEntitlement improves type safety when working with entitlements from external sources.


29-32: Entitlement assignments properly configured

The entitlement assignments correctly implement the PR objectives:

  • "public-access" is assigned to the enterprise-unlimited plan
  • SSO is available across enterprise and cloud team plans
  • Custom enterprise plans start with empty entitlements (likely for customization)
packages/db/prisma/migrations/20250520022249_add_account_request/migration.sql (3)

1-9: AccountRequest table schema looks good

The table structure properly captures all necessary data for account requests, with appropriate primary key and timestamp fields.


11-15: Well-designed unique constraints

The unique indexes effectively prevent duplicate requests:

  1. One index ensures a user can only have one active request (by requestedById)
  2. Another ensures a user cannot request to join the same organization multiple times

17-21: Appropriate foreign key relationships

The cascade behavior on the foreign keys is a good choice - if a user or organization is deleted, related account requests will be automatically removed, preventing orphaned records.

packages/web/src/emails/joinRequestApprovedEmail.tsx (3)

1-17: Email component imports look good

The imports correctly include all necessary React Email components and local dependencies.


18-27: Well-defined component interface

The props interface is comprehensive and properly typed to support all necessary personalization for the email.


29-83: Email template follows best practices

The email implementation includes all necessary elements for a good user experience:

  • Preview text for email clients
  • Proper branding with logo
  • Clear heading and personalized greeting
  • Concise message explaining the approval
  • Call-to-action button with fallback text URL
  • Consistent styling using Tailwind
packages/web/src/app/[domain]/settings/apiKeys/columns.tsx (3)

21-25: API key type definition looks good

The ApiKeyColumnInfo type properly includes all necessary fields with appropriate types.


75-88: Name column implementation looks good

The name column with the key icon provides good visual context for the API key entries.


149-152: Actions column implementation looks good

The actions column properly renders the ApiKeyActions component with the row data.

packages/web/src/app/[domain]/connections/components/newConnectionCard.tsx (1)

16-87: Code logic is well-structured for handling disabled states.

The component properly handles disabling based on both role restrictions and configuration path presence, with appropriate visual cues and explanations.

packages/web/src/actions.ts (1)

129-136: Seat-availability race condition

orgHasAvailability() checks seat usage and then the caller later performs membership-creating mutations in a separate transaction. Two concurrent requests could pass the check and exceed the seat cap.

Mitigation options:

  1. Perform the availability check inside the same transaction that inserts the new membership.
  2. Add a unique constraint & counter column updated atomically, or
  3. Use SELECT … FOR UPDATE/Prisma’s transaction with serializable isolation.

Flagging for your consideration and possible follow-up.

Comment on lines +46 to +51
const requests = await getOrgAccountRequests(domain);
if (isServiceError(requests)) {
throw new ServiceErrorException(requests);
}
const numRequests = requests.length;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid unnecessary DB call for non-owners

getOrgAccountRequests is executed for every member even though only owners can act on the result.
Save latency and CPU by fetching the requests only when userRoleInOrg === OrgRole.OWNER.

-let numRequests = 0;
-const requests = await getOrgAccountRequests(domain);
-if (isServiceError(requests)) {
-    throw new ServiceErrorException(requests);
-}
-numRequests = requests.length;
+let numRequests = 0;
+if (userRoleInOrg === OrgRole.OWNER) {
+    const requests = await getOrgAccountRequests(domain);
+    if (isServiceError(requests)) {
+        throw new ServiceErrorException(requests);
+    }
+    numRequests = requests.length;
+}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/web/src/app/[domain]/settings/layout.tsx around lines 46 to 51, the
code calls getOrgAccountRequests for every user regardless of their role, which
is inefficient. Modify the code to check if userRoleInOrg equals OrgRole.OWNER
before calling getOrgAccountRequests. Only fetch the requests and perform the
service error check when the user is an owner, otherwise skip this DB call to
save latency and CPU.

@@ -178,7 +179,7 @@ server.tool(

server.tool(
"get_file_source",
"Fetches the source code for a given file.",
"Fetches the source code for a given file. If you receive an error that indicates that you're not authenticated, please inform the user to set the SOURCEBOT_API_KEY environment variable.",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify if API key environment variable is properly documented.

Instructions for API key authentication are clear, but users might need more guidance on how to obtain or create an API key.

Let's verify if documentation for the API key usage exists:


🏁 Script executed:

#!/bin/bash
# Search for documentation related to API keys
echo "Searching for documentation about SOURCEBOT_API_KEY..."
rg -i "sourcebot_api_key" --type md
rg -i "api.?key" --type md

Length of output: 183


Add missing API key documentation

I ran a search across our Markdown files and didn’t find any references to SOURCEBOT_API_KEY. We need to provide users with clear, step-by-step instructions on how to obtain or generate their API key and where to configure it.

Please update the project documentation (e.g., README.md or a dedicated docs/API.md) to include:

  • A link or directions to the Sourcebot dashboard/portal where users can sign up and create an API key
  • Any required permissions or scopes for that key
  • An example of exporting the environment variable in Unix (export SOURCEBOT_API_KEY=…) and Windows (set SOURCEBOT_API_KEY=…) shells
  • A quick troubleshooting note if users still see authentication errors after setting the variable

This will ensure users aren’t left guessing where or how to get their key.

🤖 Prompt for AI Agents
In packages/mcp/src/index.ts at line 182, the comment mentions setting the
SOURCEBOT_API_KEY environment variable but lacks guidance on obtaining the API
key. To fix this, update the project documentation (such as README.md or
docs/API.md) to include clear instructions on how users can sign up or access
the Sourcebot dashboard to create their API key, specify any required
permissions or scopes, provide example commands for setting the environment
variable in Unix and Windows shells, and add a troubleshooting note for
authentication errors after setting the variable.

Comment on lines +47 to +55
return (
<form action={handleSubmit}>
<input type="hidden" name="domain" value={domain} />
<Button
type="submit"
className="flex items-center gap-2"
variant="outline"
disabled={isSubmitting}
>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

form action={handleSubmit} won’t fire in the browser – use onSubmit instead

action expects either a URL or a React server action.
Passing a client callback results in no-op navigation in most browsers and skips your logic. Swap to onSubmit (or convert the component into a proper server action).

-    return (
-        <form action={handleSubmit}>
+    return (
+        <form onSubmit={(e) => { e.preventDefault(); handleSubmit(); }}>

This also makes the hidden <input> unnecessary unless the server action consumes formData.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return (
<form action={handleSubmit}>
<input type="hidden" name="domain" value={domain} />
<Button
type="submit"
className="flex items-center gap-2"
variant="outline"
disabled={isSubmitting}
>
return (
<form onSubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
<input type="hidden" name="domain" value={domain} />
<Button
type="submit"
className="flex items-center gap-2"
variant="outline"
disabled={isSubmitting}
>
🤖 Prompt for AI Agents
In packages/web/src/app/[domain]/components/resubmitAccountRequestButton.tsx
around lines 47 to 55, the form uses the attribute action={handleSubmit}, which
does not trigger the client-side submit handler as expected. Replace the action
attribute with onSubmit={handleSubmit} to correctly handle the form submission
in the browser. Additionally, review if the hidden input for "domain" is needed
based on how the form data is processed, and remove it if the server action does
not consume formData.

Comment on lines +19 to +45
const handleSubmit = async () => {
setIsSubmitting(true)
const result = await createAccountRequest(userId, domain)
if (!isServiceError(result)) {
if (result.existingRequest) {
toast({
title: "Request Already Submitted",
description: "Your request to join the organization has already been submitted. Please wait for it to be approved.",
variant: "default",
})
} else {
toast({
title: "Request Resubmitted",
description: "Your request to join the organization has been resubmitted.",
variant: "default",
})
}
} else {
toast({
title: "Failed to Resubmit",
description: `There was an error resubmitting your request. Reason: ${result.message}`,
variant: "destructive",
})
}

setIsSubmitting(false)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Wrap the server call in try / catch / finally to avoid a permanent loading state on hard errors

If createAccountRequest rejects (network issue, unexpected exception, etc.) the execution jumps past the setIsSubmitting(false) line and the button remains disabled forever.
Guard against this by surrounding the await with try/catch and moving the final state-reset to finally.

-    const handleSubmit = async () => {
-        setIsSubmitting(true)
-        const result = await createAccountRequest(userId, domain)
-        if (!isServiceError(result)) {
+    const handleSubmit = async () => {
+        setIsSubmitting(true)
+        try {
+            const result = await createAccountRequest(userId, domain)
+            if (!isServiceError(result)) {
@@
-        }
-
-        setIsSubmitting(false)
+        } catch (err) {
+            toast({
+                title: "Failed to Resubmit",
+                description: "An unexpected error occurred. Please try again later.",
+                variant: "destructive",
+            })
+        } finally {
+            setIsSubmitting(false)
+        }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleSubmit = async () => {
setIsSubmitting(true)
const result = await createAccountRequest(userId, domain)
if (!isServiceError(result)) {
if (result.existingRequest) {
toast({
title: "Request Already Submitted",
description: "Your request to join the organization has already been submitted. Please wait for it to be approved.",
variant: "default",
})
} else {
toast({
title: "Request Resubmitted",
description: "Your request to join the organization has been resubmitted.",
variant: "default",
})
}
} else {
toast({
title: "Failed to Resubmit",
description: `There was an error resubmitting your request. Reason: ${result.message}`,
variant: "destructive",
})
}
setIsSubmitting(false)
}
const handleSubmit = async () => {
setIsSubmitting(true)
try {
const result = await createAccountRequest(userId, domain)
if (!isServiceError(result)) {
if (result.existingRequest) {
toast({
title: "Request Already Submitted",
description: "Your request to join the organization has already been submitted. Please wait for it to be approved.",
variant: "default",
})
} else {
toast({
title: "Request Resubmitted",
description: "Your request to join the organization has been resubmitted.",
variant: "default",
})
}
} else {
toast({
title: "Failed to Resubmit",
description: `There was an error resubmitting your request. Reason: ${result.message}`,
variant: "destructive",
})
}
} catch (err) {
toast({
title: "Failed to Resubmit",
description: "An unexpected error occurred. Please try again later.",
variant: "destructive",
})
} finally {
setIsSubmitting(false)
}
}
🤖 Prompt for AI Agents
In packages/web/src/app/[domain]/components/resubmitAccountRequestButton.tsx
between lines 19 and 45, the async call to createAccountRequest is not wrapped
in try/catch/finally, so if it throws an error, setIsSubmitting(false) is never
called and the button stays disabled. Fix this by wrapping the await
createAccountRequest call and subsequent logic in a try block, handle errors in
a catch block (e.g., show a toast for unexpected errors), and move
setIsSubmitting(false) into a finally block to ensure it always runs.

Comment on lines +57 to +66
await prisma.org.update({
where: {
id: org.id,
},
data: {
metadata: {
publicAccessEnabled: enabled,
},
},
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Do not overwrite existing org metadata – merge instead.

prisma.org.update writes a brand-new object to the metadata column, discarding every other metadata field that might already be stored.
If other features have also started persisting data to the same JSONB column, those keys will silently vanish.

-        data: {
-            metadata: {
-                publicAccessEnabled: enabled,
-            },
-        },
+        data: {
+            // Preserve other metadata keys
+            metadata: {
+                ...((org.metadata ?? {}) as Prisma.JsonObject),
+                publicAccessEnabled: enabled,
+            },
+        },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await prisma.org.update({
where: {
id: org.id,
},
data: {
metadata: {
publicAccessEnabled: enabled,
},
},
});
await prisma.org.update({
where: {
id: org.id,
},
data: {
// Preserve other metadata keys
metadata: {
...((org.metadata ?? {}) as Prisma.JsonObject),
publicAccessEnabled: enabled,
},
},
});
🤖 Prompt for AI Agents
In packages/web/src/ee/features/publicAccess/publicAccess.tsx around lines 57 to
66, the prisma.org.update call overwrites the entire metadata object, which can
remove existing metadata keys. To fix this, first fetch the current metadata for
the org, merge the new publicAccessEnabled field into it without removing other
keys, and then update the org with the merged metadata object to preserve all
existing metadata fields.

Comment on lines +20 to +34
const decodeLicenseKeyPayload = (payload: string): LicenseKeyPayload => {
const decodedPayload = base64Decode(payload);
const payloadJson = JSON.parse(decodedPayload);
return eeLicenseKeyPayloadSchema.parse(payloadJson);
}

export const getLicenseKey = (): LicenseKeyPayload | null => {
const licenseKey = env.SOURCEBOT_EE_LICENSE_KEY;
if (licenseKey && licenseKey.startsWith(eeLicenseKeyPrefix)) {
const payload = licenseKey.substring(eeLicenseKeyPrefix.length);
const decodedPayload = decodeLicenseKeyPayload(payload);
return decodedPayload;
}
return null;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard against malformed / tampered license keys.

decodeLicenseKeyPayload throws if the payload is not valid base-64 or fails Zod validation, which will bring down the whole process whenever getLicenseKey() is called (e.g. every request).

Wrap the decode/parse in a try/catch and return null on failure:

-        const decodedPayload = decodeLicenseKeyPayload(payload);
-        return decodedPayload;
+        try {
+            return decodeLicenseKeyPayload(payload);
+        } catch (err) {
+            console.error("Failed to decode Sourcebot EE license key:", err);
+            return null;
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const decodeLicenseKeyPayload = (payload: string): LicenseKeyPayload => {
const decodedPayload = base64Decode(payload);
const payloadJson = JSON.parse(decodedPayload);
return eeLicenseKeyPayloadSchema.parse(payloadJson);
}
export const getLicenseKey = (): LicenseKeyPayload | null => {
const licenseKey = env.SOURCEBOT_EE_LICENSE_KEY;
if (licenseKey && licenseKey.startsWith(eeLicenseKeyPrefix)) {
const payload = licenseKey.substring(eeLicenseKeyPrefix.length);
const decodedPayload = decodeLicenseKeyPayload(payload);
return decodedPayload;
}
return null;
}
export const getLicenseKey = (): LicenseKeyPayload | null => {
const licenseKey = env.SOURCEBOT_EE_LICENSE_KEY;
if (licenseKey && licenseKey.startsWith(eeLicenseKeyPrefix)) {
const payload = licenseKey.substring(eeLicenseKeyPrefix.length);
try {
return decodeLicenseKeyPayload(payload);
} catch (err) {
console.error("Failed to decode Sourcebot EE license key:", err);
return null;
}
}
return null;
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 28-28: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🤖 Prompt for AI Agents
In packages/web/src/features/entitlements/server.ts around lines 20 to 34, the
decodeLicenseKeyPayload function can throw errors if the payload is malformed or
fails validation, causing the entire process to crash when getLicenseKey is
called. To fix this, wrap the call to decodeLicenseKeyPayload inside
getLicenseKey in a try/catch block and return null if any error occurs during
decoding or parsing, ensuring the process remains stable on invalid license
keys.

Comment on lines +142 to +155
const hasPublicAccessEntitlement = hasEntitlement("public-access");
const enablePublicAccess = config.settings?.enablePublicAccess;
if (enablePublicAccess !== undefined && !hasPublicAccessEntitlement) {
console.error(`Public access flag is set in the config file but your license doesn't have public access entitlement. Please contact ${SOURCEBOT_SUPPORT_EMAIL} to request a license upgrade.`);
process.exit(1);
}

if (hasPublicAccessEntitlement) {
console.log(`Setting public access status to ${!!enablePublicAccess} for org ${SINGLE_TENANT_ORG_DOMAIN}`);
const res = await setPublicAccessStatus(SINGLE_TENANT_ORG_DOMAIN, !!enablePublicAccess);
if (isServiceError(res)) {
throw new ServiceErrorException(res);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid unintentionally disabling public access on every config sync.

When enablePublicAccess is undefined you coerce it to false and call setPublicAccessStatus, which may turn off a previously enabled flag each time the watcher fires.

-    if (hasPublicAccessEntitlement) {
+    if (hasPublicAccessEntitlement && enablePublicAccess !== undefined) {

Consider leaving the setting untouched if the field is absent.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const hasPublicAccessEntitlement = hasEntitlement("public-access");
const enablePublicAccess = config.settings?.enablePublicAccess;
if (enablePublicAccess !== undefined && !hasPublicAccessEntitlement) {
console.error(`Public access flag is set in the config file but your license doesn't have public access entitlement. Please contact ${SOURCEBOT_SUPPORT_EMAIL} to request a license upgrade.`);
process.exit(1);
}
if (hasPublicAccessEntitlement) {
console.log(`Setting public access status to ${!!enablePublicAccess} for org ${SINGLE_TENANT_ORG_DOMAIN}`);
const res = await setPublicAccessStatus(SINGLE_TENANT_ORG_DOMAIN, !!enablePublicAccess);
if (isServiceError(res)) {
throw new ServiceErrorException(res);
}
}
const hasPublicAccessEntitlement = hasEntitlement("public-access");
const enablePublicAccess = config.settings?.enablePublicAccess;
if (enablePublicAccess !== undefined && !hasPublicAccessEntitlement) {
console.error(`Public access flag is set in the config file but your license doesn't have public access entitlement. Please contact ${SOURCEBOT_SUPPORT_EMAIL} to request a license upgrade.`);
process.exit(1);
}
if (hasPublicAccessEntitlement && enablePublicAccess !== undefined) {
console.log(`Setting public access status to ${!!enablePublicAccess} for org ${SINGLE_TENANT_ORG_DOMAIN}`);
const res = await setPublicAccessStatus(SINGLE_TENANT_ORG_DOMAIN, !!enablePublicAccess);
if (isServiceError(res)) {
throw new ServiceErrorException(res);
}
}
🤖 Prompt for AI Agents
In packages/web/src/initialize.ts around lines 142 to 155, the code coerces
enablePublicAccess to false when it is undefined, causing setPublicAccessStatus
to disable public access unintentionally on each config sync. To fix this,
modify the logic to only call setPublicAccessStatus if enablePublicAccess is
explicitly defined, thereby leaving the public access setting unchanged when the
field is absent.

Comment on lines +1258 to +1267
// We need to set pendingApproval to be true here though so that if the user tries to sign into the deployment again it will send another request. Without
// this, the user will never be able to request to join the org again.
await tx.user.update({
where: {
id: memberId,
},
data: {
pendingApproval: true,
}
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Clarify pendingApproval semantics when removing members

The TODO notes that setting pendingApproval = true on a removed member blocks approval workflows in multi-tenant mode. This warrants a concrete issue:

• In single-tenant deployments it enables re-requesting access – good.
• In multi-tenant mode it creates orphaned users who can never be approved.

Consider:

pendingApproval: env.SOURCEBOT_TENANCY_MODE === 'single'

or a dedicated “removedByOwner” flag so approval logic can differentiate.

🤖 Prompt for AI Agents
In packages/web/src/actions.ts around lines 1258 to 1267, the current code sets
pendingApproval to true unconditionally when removing a member, which causes
issues in multi-tenant mode by blocking approval workflows and creating orphaned
users. To fix this, modify the assignment of pendingApproval to be conditional
based on the tenancy mode, for example setting it to true only if
env.SOURCEBOT_TENANCY_MODE is 'single'. Alternatively, introduce a new flag like
"removedByOwner" to distinguish removed members and adjust approval logic
accordingly.

Comment on lines +64 to +90
if (apiKey) {
const prefix = getApiKeyPrefix(apiKey);

const apiKeyDb = await prisma.apiKey.findFirst({
where: {
prefix,
},
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30).toISOString(),
});
}

if (!apiKeyDb) {
console.error(`No API key found for prefix: ${prefix}`);
return notAuthenticated();
}

const user = await prisma.user.findUnique({
where: {
id: apiKeyDb.createdById,
},
});

if (!user) {
console.error(`No user found for API key: ${apiKey}`);
return notAuthenticated();
}

return fn(user.id);
} else if (
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🔐 API-Key authentication validates only the prefix – allows trivial impersonation

withAuth() looks up the API-key record solely by prefix and, if it finds one, authenticates the caller without checking the secret part of the key.
Anyone who learns/guesses a short prefix (often ≤8 chars) can forge "<prefix>-anything" and gain full access as that user.

Suggested remediation (outline):

-const prefix = getApiKeyPrefix(apiKey);
-const apiKeyDb = await prisma.apiKey.findFirst({ where: { prefix } });
+const { prefix, secret } = splitApiKey(apiKey);        // helper to split key
+const apiKeyDb = await prisma.apiKey.findFirst({ where: { prefix } });
+
+if (!apiKeyDb) { … }
+
+// Store and compare a constant–time hash of the secret when you create the key
+import { timingSafeEqual, createHash } from 'crypto';
+const expected = Buffer.from(apiKeyDb.secretHash, 'hex');
+const received = createHash('sha256').update(secret).digest();
+if (expected.length !== received.length ||
+    !timingSafeEqual(expected, received)) {
+   console.error(`Invalid secret for prefix ${prefix}`);
+   return notAuthenticated();
+}

This requires:

  1. prisma.apiKey to persist secretHash (e.g. SHA-256).
  2. createApiKey() to store that hash when the key is generated.
  3. A tiny helper (splitApiKey) that rejects malformed keys.

Without a full-key check, the new API-key mechanism is fundamentally insecure.

Committable suggestion skipped: line range outside the PR's diff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant