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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Added support to set "Require approval for new members" via config with (`REQUIRE_APPROVAL_NEW_MEMBERS`). [`#858`](https://github.com/sourcebot-dev/sourcebot/pull/858)

## [4.10.27] - 2026-02-05

### Fixed
Expand Down
12 changes: 12 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,15 @@ To build a specific package:
```bash
yarn workspace @sourcebot/<package-name> build
```

## Tailwind CSS

Use Tailwind color classes directly instead of CSS variable syntax:

```tsx
// Correct
className="border-border bg-card text-foreground text-muted-foreground bg-muted bg-secondary"

// Incorrect
className="border-[var(--border)] bg-[var(--card)] text-[var(--foreground)]"
```
2 changes: 1 addition & 1 deletion docs/docs/configuration/auth/access-settings.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ By default, Sourcebot requires new members to be approved by the owner of the de
to configure this behavior.

### Configuration
Member approval can be configured by the owner of the deployment by navigating to **Settings -> Members**:
Member approval can be configured by the owner of the deployment by navigating to **Settings -> Access**, or by setting the `REQUIRE_APPROVAL_NEW_MEMBERS` environment variable. When the environment variable is set, the UI toggle is disabled and the setting is controlled by the environment variable.

![Member Approval Toggle](/images/member_approval_toggle.png)

Expand Down
1 change: 1 addition & 0 deletions docs/docs/configuration/environment-variables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The following environment variables allow you to configure your Sourcebot deploy
| `DATABASE_URL` | `postgresql://postgres@ localhost:5432/sourcebot` | <p>Connection string of your Postgres database. By default, a Postgres database is automatically provisioned at startup within the container.</p><p>If you'd like to use a non-default schema, you can provide it as a parameter in the database url.</p><p>You can also use `DATABASE_HOST`, `DATABASE_USERNAME`, `DATABASE_PASSWORD`, `DATABASE_NAME`, and `DATABASE_ARGS` to construct the database url.</p> |
| `EMAIL_FROM_ADDRESS` | `-` | <p>The email address that transactional emails will be sent from. See [this doc](/docs/configuration/transactional-emails) for more info.</p> |
| `FORCE_ENABLE_ANONYMOUS_ACCESS` | `false` | <p>When enabled, [anonymous access](/docs/configuration/auth/access-settings#anonymous-access) to the organization will always be enabled</p>
| `REQUIRE_APPROVAL_NEW_MEMBERS` | - | <p>When set, controls whether new users require approval before accessing your deployment. If not set, the setting can be configured via the UI. See [member approval](/docs/configuration/auth/access-settings#member-approval) for more info.</p>
| `REDIS_DATA_DIR` | `$DATA_CACHE_DIR/redis` | <p>The data directory for the default Redis instance.</p> |
| `REDIS_URL` | `redis://localhost:6379` | <p>Connection string of your Redis instance. By default, a Redis database is automatically provisioned at startup within the container.</p> |
| `REDIS_REMOVE_ON_COMPLETE` | `0` | <p>Controls how many completed jobs are allowed to remain in Redis queues</p> |
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/env.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export const env = createEnv({

// Auth
FORCE_ENABLE_ANONYMOUS_ACCESS: booleanSchema.default('false'),
REQUIRE_APPROVAL_NEW_MEMBERS: booleanSchema.optional(),
AUTH_SECRET: z.string(),
AUTH_URL: z.string().url(),
AUTH_CREDENTIALS_LOGIN_ENABLED: booleanSchema.default('true'),
Expand Down
35 changes: 30 additions & 5 deletions packages/web/src/app/components/memberApprovalRequiredToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import { useToast } from "@/components/hooks/use-toast"
interface MemberApprovalRequiredToggleProps {
memberApprovalRequired: boolean
onToggleChange?: (checked: boolean) => void
isControlledByEnvVar: boolean
}

export function MemberApprovalRequiredToggle({ memberApprovalRequired, onToggleChange }: MemberApprovalRequiredToggleProps) {
export function MemberApprovalRequiredToggle({ memberApprovalRequired, onToggleChange, isControlledByEnvVar }: MemberApprovalRequiredToggleProps) {
const [enabled, setEnabled] = useState(memberApprovalRequired)
const [isLoading, setIsLoading] = useState(false)
const { toast } = useToast()
Expand Down Expand Up @@ -45,24 +46,48 @@ export function MemberApprovalRequiredToggle({ memberApprovalRequired, onToggleC
}
}

const isDisabled = isLoading || isControlledByEnvVar;

return (
<div className="p-4 rounded-lg border border-[var(--border)] bg-[var(--card)]">
<div className={`p-4 rounded-lg border border-border bg-card ${isControlledByEnvVar ? 'opacity-60' : ''}`}>
<div className="flex items-start justify-between gap-4">
<div className="flex-1 min-w-0">
<h3 className="font-medium text-[var(--foreground)] mb-2">
<h3 className="font-medium text-foreground mb-2">
Require approval for new members
</h3>
<div className="max-w-2xl">
<p className="text-sm text-[var(--muted-foreground)] leading-relaxed">
<p className="text-sm text-muted-foreground leading-relaxed">
When enabled, new users will need approval from an organization owner before they can access your deployment.
</p>
{isControlledByEnvVar && (
<div className="mt-3 p-3 rounded-md bg-muted border border-border">
<p className="text-sm text-foreground leading-relaxed flex items-center gap-2">
<svg
className="w-4 h-4 flex-shrink-0 text-muted-foreground"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>
This setting is controlled by the <code className="bg-secondary px-1 py-0.5 rounded text-xs font-mono">REQUIRE_APPROVAL_NEW_MEMBERS</code> environment variable.
</span>
</p>
</div>
)}
</div>
</div>
<div className="flex-shrink-0">
<Switch
checked={enabled}
onCheckedChange={handleToggle}
disabled={isLoading}
disabled={isDisabled}
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export async function OrganizationAccessSettings() {
const hasAnonymousAccessEntitlement = hasEntitlement("anonymous-access");

const forceEnableAnonymousAccess = env.FORCE_ENABLE_ANONYMOUS_ACCESS === 'true';
const memberApprovalEnvVarSet = env.REQUIRE_APPROVAL_NEW_MEMBERS !== undefined;

return (
<div className="space-y-6">
Expand All @@ -34,6 +35,7 @@ export async function OrganizationAccessSettings() {
memberApprovalRequired={org.memberApprovalRequired}
inviteLinkEnabled={org.inviteLinkEnabled}
inviteLink={inviteLink}
memberApprovalEnvVarSet={memberApprovalEnvVarSet}
/>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ interface OrganizationAccessSettingsWrapperProps {
memberApprovalRequired: boolean
inviteLinkEnabled: boolean
inviteLink: string | null
memberApprovalEnvVarSet: boolean
}

export function OrganizationAccessSettingsWrapper({
memberApprovalRequired,
inviteLinkEnabled,
inviteLink
inviteLink,
memberApprovalEnvVarSet
}: OrganizationAccessSettingsWrapperProps) {
const [showInviteLink, setShowInviteLink] = useState(memberApprovalRequired)

Expand All @@ -27,6 +29,7 @@ export function OrganizationAccessSettingsWrapper({
<MemberApprovalRequiredToggle
memberApprovalRequired={memberApprovalRequired}
onToggleChange={handleMemberApprovalToggle}
isControlledByEnvVar={memberApprovalEnvVarSet}
/>
</div>

Expand Down
13 changes: 13 additions & 0 deletions packages/web/src/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,19 @@ const initSingleTenancy = async () => {
}
}
}

// Sync member approval setting from env var (only if explicitly set)
if (env.REQUIRE_APPROVAL_NEW_MEMBERS !== undefined) {
const requireApprovalNewMembers = env.REQUIRE_APPROVAL_NEW_MEMBERS === 'true';
const org = await getOrgFromDomain(SINGLE_TENANT_ORG_DOMAIN);
if (org && org.memberApprovalRequired !== requireApprovalNewMembers) {
await prisma.org.update({
where: { id: org.id },
data: { memberApprovalRequired: requireApprovalNewMembers },
});
logger.info(`Member approval requirement set to ${requireApprovalNewMembers} via REQUIRE_APPROVAL_NEW_MEMBERS environment variable`);
}
}
}

const initMultiTenancy = async () => {
Expand Down
Loading