diff --git a/CHANGELOG.md b/CHANGELOG.md index e839d3377..70a26fbb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/CLAUDE.md b/CLAUDE.md index f95cf5baa..2ff8c9615 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,3 +17,15 @@ To build a specific package: ```bash yarn workspace @sourcebot/ 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)]" +``` diff --git a/docs/docs/configuration/auth/access-settings.mdx b/docs/docs/configuration/auth/access-settings.mdx index 5bc638e77..6c466fdbe 100644 --- a/docs/docs/configuration/auth/access-settings.mdx +++ b/docs/docs/configuration/auth/access-settings.mdx @@ -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) diff --git a/docs/docs/configuration/environment-variables.mdx b/docs/docs/configuration/environment-variables.mdx index 859fd1c39..580de3593 100644 --- a/docs/docs/configuration/environment-variables.mdx +++ b/docs/docs/configuration/environment-variables.mdx @@ -21,6 +21,7 @@ The following environment variables allow you to configure your Sourcebot deploy | `DATABASE_URL` | `postgresql://postgres@ localhost:5432/sourcebot` |

Connection string of your Postgres database. By default, a Postgres database is automatically provisioned at startup within the container.

If you'd like to use a non-default schema, you can provide it as a parameter in the database url.

You can also use `DATABASE_HOST`, `DATABASE_USERNAME`, `DATABASE_PASSWORD`, `DATABASE_NAME`, and `DATABASE_ARGS` to construct the database url.

| | `EMAIL_FROM_ADDRESS` | `-` |

The email address that transactional emails will be sent from. See [this doc](/docs/configuration/transactional-emails) for more info.

| | `FORCE_ENABLE_ANONYMOUS_ACCESS` | `false` |

When enabled, [anonymous access](/docs/configuration/auth/access-settings#anonymous-access) to the organization will always be enabled

+| `REQUIRE_APPROVAL_NEW_MEMBERS` | - |

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.

| `REDIS_DATA_DIR` | `$DATA_CACHE_DIR/redis` |

The data directory for the default Redis instance.

| | `REDIS_URL` | `redis://localhost:6379` |

Connection string of your Redis instance. By default, a Redis database is automatically provisioned at startup within the container.

| | `REDIS_REMOVE_ON_COMPLETE` | `0` |

Controls how many completed jobs are allowed to remain in Redis queues

| diff --git a/packages/shared/src/env.server.ts b/packages/shared/src/env.server.ts index 6b4ed8be1..0e5e357b3 100644 --- a/packages/shared/src/env.server.ts +++ b/packages/shared/src/env.server.ts @@ -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'), diff --git a/packages/web/src/app/components/memberApprovalRequiredToggle.tsx b/packages/web/src/app/components/memberApprovalRequiredToggle.tsx index 9d3cf1bcb..671992328 100644 --- a/packages/web/src/app/components/memberApprovalRequiredToggle.tsx +++ b/packages/web/src/app/components/memberApprovalRequiredToggle.tsx @@ -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() @@ -45,24 +46,48 @@ export function MemberApprovalRequiredToggle({ memberApprovalRequired, onToggleC } } + const isDisabled = isLoading || isControlledByEnvVar; + return ( -
+
-

+

Require approval for new members

-

+

When enabled, new users will need approval from an organization owner before they can access your deployment.

+ {isControlledByEnvVar && ( +
+

+ + + + + This setting is controlled by the REQUIRE_APPROVAL_NEW_MEMBERS environment variable. + +

+
+ )}
diff --git a/packages/web/src/app/components/organizationAccessSettings.tsx b/packages/web/src/app/components/organizationAccessSettings.tsx index 94a8c0408..adb039d60 100644 --- a/packages/web/src/app/components/organizationAccessSettings.tsx +++ b/packages/web/src/app/components/organizationAccessSettings.tsx @@ -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 (
@@ -34,6 +35,7 @@ export async function OrganizationAccessSettings() { memberApprovalRequired={org.memberApprovalRequired} inviteLinkEnabled={org.inviteLinkEnabled} inviteLink={inviteLink} + memberApprovalEnvVarSet={memberApprovalEnvVarSet} />
) diff --git a/packages/web/src/app/components/organizationAccessSettingsWrapper.tsx b/packages/web/src/app/components/organizationAccessSettingsWrapper.tsx index 19fa0a095..bc91963bf 100644 --- a/packages/web/src/app/components/organizationAccessSettingsWrapper.tsx +++ b/packages/web/src/app/components/organizationAccessSettingsWrapper.tsx @@ -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) @@ -27,6 +29,7 @@ export function OrganizationAccessSettingsWrapper({
diff --git a/packages/web/src/initialize.ts b/packages/web/src/initialize.ts index 97efc9eab..06647e57d 100644 --- a/packages/web/src/initialize.ts +++ b/packages/web/src/initialize.ts @@ -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 () => {