Skip to content

Commit 7d414d1

Browse files
further refinement
1 parent d4bfeb6 commit 7d414d1

8 files changed

Lines changed: 30 additions & 120 deletions

File tree

packages/web/src/app/(app)/settings/components/settingsCard.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ interface BasicSettingsCardProps {
2929
name: string;
3030
description?: string;
3131
children: ReactNode;
32-
/** Optional content rendered full-width below the name/description/control row (e.g. an info notice or an expandable section). */
3332
footer?: ReactNode;
3433
className?: string;
3534
}

packages/web/src/app/(app)/settings/security/actions.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ import { StatusCodes } from "http-status-codes";
1515
export const setMemberApprovalRequired = async (required: boolean): Promise<{ success: boolean } | ServiceError> => sew(async () =>
1616
withAuth(async ({ org, role, prisma }) =>
1717
withMinimumOrgRole(role, OrgRole.OWNER, async () => {
18+
if (env.REQUIRE_APPROVAL_NEW_MEMBERS !== undefined) {
19+
return {
20+
statusCode: StatusCodes.BAD_REQUEST,
21+
errorCode: ErrorCode.MEMBER_APPROVAL_CONTROLLED_BY_ENV,
22+
message: "Member approval is controlled by the REQUIRE_APPROVAL_NEW_MEMBERS environment variable and cannot be changed from the UI.",
23+
} satisfies ServiceError;
24+
}
25+
1826
await prisma.org.update({
1927
where: { id: org.id },
2028
data: { memberApprovalRequired: required },
@@ -66,13 +74,20 @@ export const setCredentialsLoginEnabled = async (enabled: boolean): Promise<{ su
6674
export const setAnonymousAccessStatus = async (enabled: boolean): Promise<ServiceError | boolean> => sew(async () =>
6775
withAuth(async ({ org, role, prisma }) =>
6876
withMinimumOrgRole(role, OrgRole.OWNER, async () => {
77+
if (env.FORCE_ENABLE_ANONYMOUS_ACCESS !== undefined) {
78+
return {
79+
statusCode: StatusCodes.BAD_REQUEST,
80+
errorCode: ErrorCode.ANONYMOUS_ACCESS_CONTROLLED_BY_ENV,
81+
message: "Anonymous access is controlled by the FORCE_ENABLE_ANONYMOUS_ACCESS environment variable and cannot be changed from the UI.",
82+
} satisfies ServiceError;
83+
}
84+
6985
const anonymousAccessAvailable = await isAnonymousAccessAvailable();
7086
if (!anonymousAccessAvailable) {
71-
console.error(`Anonymous access isn't supported in your current plan. For support, contact ${SOURCEBOT_SUPPORT_EMAIL}.`);
7287
return {
7388
statusCode: StatusCodes.FORBIDDEN,
7489
errorCode: ErrorCode.INSUFFICIENT_PERMISSIONS,
75-
message: "Anonymous access is not supported in your current plan",
90+
message: `Anonymous access is not supported in your current plan. For support, contact ${SOURCEBOT_SUPPORT_EMAIL}.`,
7691
} satisfies ServiceError;
7792
}
7893

packages/web/src/app/(app)/settings/security/components/anonymousAccessEnabledSettingsCard.tsx

Lines changed: 4 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,15 @@
33
import { useState } from "react"
44
import { Switch } from "@/components/ui/switch"
55
import { setAnonymousAccessStatus } from "@/app/(app)/settings/security/actions"
6-
import { cn, isServiceError } from "@/lib/utils"
6+
import { isServiceError } from "@/lib/utils"
77
import { useToast } from "@/components/hooks/use-toast"
88
import { BasicSettingsCard } from "@/app/(app)/settings/components/settingsCard"
9-
import { SettingNotice } from "./settingNotice"
109

1110
interface AnonymousAccessEnabledSettingsCardProps {
12-
anonymousAccessAvailable: boolean;
1311
anonymousAccessEnabled: boolean
14-
isControlledByEnvVar: boolean
1512
}
1613

17-
export function AnonymousAccessEnabledSettingsCard({ anonymousAccessAvailable, anonymousAccessEnabled, isControlledByEnvVar }: AnonymousAccessEnabledSettingsCardProps) {
14+
export function AnonymousAccessEnabledSettingsCard({ anonymousAccessEnabled }: AnonymousAccessEnabledSettingsCardProps) {
1815
const [enabled, setEnabled] = useState(anonymousAccessEnabled)
1916
const [isLoading, setIsLoading] = useState(false)
2017
const { toast } = useToast()
@@ -27,7 +24,7 @@ export function AnonymousAccessEnabledSettingsCard({ anonymousAccessAvailable, a
2724
if (isServiceError(result)) {
2825
toast({
2926
title: "Error",
30-
description: result.message || "Failed to update anonymous access setting",
27+
description: result.message,
3128
variant: "destructive",
3229
})
3330
return
@@ -45,43 +42,16 @@ export function AnonymousAccessEnabledSettingsCard({ anonymousAccessAvailable, a
4542
setIsLoading(false)
4643
}
4744
}
48-
const isDisabled = isLoading || !anonymousAccessAvailable || isControlledByEnvVar;
49-
const showPlanMessage = !anonymousAccessAvailable;
50-
const showForceEnableMessage = !showPlanMessage && isControlledByEnvVar;
5145

5246
return (
5347
<BasicSettingsCard
5448
name="Enable anonymous access"
5549
description="When enabled, users can access your deployment without logging in."
56-
className={cn((!anonymousAccessAvailable || isControlledByEnvVar) && "opacity-60")}
57-
footer={
58-
<>
59-
{showPlanMessage && (
60-
<SettingNotice>
61-
Your current plan doesn&apos;t allow for anonymous access. Please{" "}
62-
<a
63-
href="https://www.sourcebot.dev/contact"
64-
target="_blank"
65-
rel="noopener"
66-
className="font-medium text-primary hover:text-primary/80 underline underline-offset-2 transition-colors"
67-
>
68-
reach out
69-
</a>
70-
{" "}for assistance.
71-
</SettingNotice>
72-
)}
73-
{showForceEnableMessage && (
74-
<SettingNotice>
75-
<code className="bg-secondary px-1 py-0.5 rounded text-xs font-mono">FORCE_ENABLE_ANONYMOUS_ACCESS</code> is set, so this cannot be changed from the UI.
76-
</SettingNotice>
77-
)}
78-
</>
79-
}
8050
>
8151
<Switch
8252
checked={enabled}
8353
onCheckedChange={handleToggle}
84-
disabled={isDisabled}
54+
disabled={isLoading}
8555
/>
8656
</BasicSettingsCard>
8757
)

packages/web/src/app/(app)/settings/security/components/credentialsLoginEnabledSettingsCard.tsx

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,16 @@
33
import { useState } from "react"
44
import { Switch } from "@/components/ui/switch"
55
import { setCredentialsLoginEnabled } from "@/app/(app)/settings/security/actions"
6-
import { cn, isServiceError } from "@/lib/utils"
6+
import { isServiceError } from "@/lib/utils"
77
import { useToast } from "@/components/hooks/use-toast"
88
import { BasicSettingsCard } from "@/app/(app)/settings/components/settingsCard"
9-
import { SettingNotice } from "./settingNotice"
109

1110
interface CredentialsLoginEnabledSettingsCardProps {
1211
isCredentialsLoginEnabled: boolean
13-
isControlledByEnvVar: boolean
14-
hasAlternativeLoginMethod: boolean
1512
}
1613

1714
export function CredentialsLoginEnabledSettingsCard({
1815
isCredentialsLoginEnabled,
19-
isControlledByEnvVar,
20-
hasAlternativeLoginMethod
2116
}: CredentialsLoginEnabledSettingsCardProps) {
2217
const [enabled, setEnabled] = useState(isCredentialsLoginEnabled)
2318
const [isLoading, setIsLoading] = useState(false)
@@ -31,7 +26,7 @@ export function CredentialsLoginEnabledSettingsCard({
3126
if (isServiceError(result)) {
3227
toast({
3328
title: "Error",
34-
description: result.message ?? "Failed to update email login setting",
29+
description: result.message,
3530
variant: "destructive",
3631
})
3732
return
@@ -50,35 +45,15 @@ export function CredentialsLoginEnabledSettingsCard({
5045
}
5146
}
5247

53-
// The toggle can't be turned off when there's no other way to sign in, but it
54-
// should still be possible to turn it back on in that situation.
55-
const lockedOnForLoginMethod = enabled && !hasAlternativeLoginMethod;
56-
const isDisabled = isLoading || isControlledByEnvVar || lockedOnForLoginMethod;
57-
5848
return (
5949
<BasicSettingsCard
6050
name="Email & password login"
6151
description="When enabled, users can sign in with an email and password."
62-
className={cn((isControlledByEnvVar || lockedOnForLoginMethod) && "opacity-60")}
63-
footer={
64-
<>
65-
{isControlledByEnvVar && (
66-
<SettingNotice>
67-
This setting is controlled by the <code className="bg-secondary px-1 py-0.5 rounded text-xs font-mono">AUTH_CREDENTIALS_LOGIN_ENABLED</code> environment variable.
68-
</SettingNotice>
69-
)}
70-
{!isControlledByEnvVar && lockedOnForLoginMethod && (
71-
<SettingNotice>
72-
Email login can&apos;t be disabled because no other login method is configured. Configure an identity provider (SSO) or magic-code email login first.
73-
</SettingNotice>
74-
)}
75-
</>
76-
}
7752
>
7853
<Switch
7954
checked={enabled}
8055
onCheckedChange={handleToggle}
81-
disabled={isDisabled}
56+
disabled={isLoading}
8257
/>
8358
</BasicSettingsCard>
8459
)

packages/web/src/app/(app)/settings/security/components/memberApprovalRequiredSettingsCard.tsx

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,16 @@
33
import { useState } from "react"
44
import { Switch } from "@/components/ui/switch"
55
import { setMemberApprovalRequired } from "@/app/(app)/settings/security/actions"
6-
import { cn, isServiceError } from "@/lib/utils"
6+
import { isServiceError } from "@/lib/utils"
77
import { useToast } from "@/components/hooks/use-toast"
88
import { BasicSettingsCard } from "@/app/(app)/settings/components/settingsCard"
9-
import { SettingNotice } from "./settingNotice"
109

1110
interface MemberApprovalRequiredSettingsCardProps {
1211
memberApprovalRequired: boolean
13-
isControlledByEnvVar: boolean
1412
}
1513

1614
export const MemberApprovalRequiredSettingsCard = ({
1715
memberApprovalRequired,
18-
isControlledByEnvVar
1916
}: MemberApprovalRequiredSettingsCardProps) => {
2017
const [enabled, setEnabled] = useState(memberApprovalRequired)
2118
const [isLoading, setIsLoading] = useState(false)
@@ -29,7 +26,7 @@ export const MemberApprovalRequiredSettingsCard = ({
2926
if (isServiceError(result)) {
3027
toast({
3128
title: "Error",
32-
description: "Failed to update member approval setting",
29+
description: result.message,
3330
variant: "destructive",
3431
})
3532
return
@@ -48,23 +45,15 @@ export const MemberApprovalRequiredSettingsCard = ({
4845
}
4946
}
5047

51-
const isDisabled = isLoading || isControlledByEnvVar;
52-
5348
return (
5449
<BasicSettingsCard
5550
name="Require approval for new members"
5651
description="When enabled, new users will need approval from an organization owner before they can access your deployment."
57-
className={cn(isControlledByEnvVar && "opacity-60")}
58-
footer={isControlledByEnvVar && (
59-
<SettingNotice>
60-
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.
61-
</SettingNotice>
62-
)}
6352
>
6453
<Switch
6554
checked={enabled}
6655
onCheckedChange={handleToggle}
67-
disabled={isDisabled}
56+
disabled={isLoading}
6857
/>
6958
</BasicSettingsCard>
7059
)

packages/web/src/app/(app)/settings/security/components/settingNotice.tsx

Lines changed: 0 additions & 32 deletions
This file was deleted.

packages/web/src/app/(app)/settings/security/page.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import { AnonymousAccessEnabledSettingsCard } from "./components/anonymousAccess
22
import { InviteLinkEnabledSettingsCard } from "./components/inviteLinkEnabledSettingsCard";
33
import { MemberApprovalRequiredSettingsCard } from "./components/memberApprovalRequiredSettingsCard";
44
import { CredentialsLoginEnabledSettingsCard } from "./components/credentialsLoginEnabledSettingsCard";
5-
import { getProviders } from "@/auth";
6-
import { isAnonymousAccessAvailable, isAnonymousAccessEnabled } from "@/lib/entitlements";
5+
import { isAnonymousAccessEnabled } from "@/lib/entitlements";
76
import { createInviteLink } from "@/lib/utils";
87
import { authenticatedPage } from "@/middleware/authenticatedPage";
98
import { OrgRole } from "@sourcebot/db";
@@ -12,9 +11,7 @@ import { SettingsCardGroup } from "../components/settingsCard";
1211

1312
export default authenticatedPage(async ({ org }) => {
1413
const anonymousAccessEnabled = await isAnonymousAccessEnabled();
15-
const anonymousAccessAvailable = await isAnonymousAccessAvailable();
1614
const inviteLink = createInviteLink(env.AUTH_URL, org.inviteLinkId);
17-
const providers = await getProviders();
1815

1916
return (
2017
<div className="flex flex-col gap-6">
@@ -41,12 +38,9 @@ export default authenticatedPage(async ({ org }) => {
4138
/>
4239
<MemberApprovalRequiredSettingsCard
4340
memberApprovalRequired={isMemberApprovalRequired(org)}
44-
isControlledByEnvVar={env.REQUIRE_APPROVAL_NEW_MEMBERS !== undefined}
4541
/>
4642
<AnonymousAccessEnabledSettingsCard
47-
anonymousAccessAvailable={anonymousAccessAvailable}
4843
anonymousAccessEnabled={anonymousAccessEnabled}
49-
isControlledByEnvVar={env.FORCE_ENABLE_ANONYMOUS_ACCESS !== undefined}
5044
/>
5145
</SettingsCardGroup>
5246

@@ -55,8 +49,6 @@ export default authenticatedPage(async ({ org }) => {
5549
<SettingsCardGroup>
5650
<CredentialsLoginEnabledSettingsCard
5751
isCredentialsLoginEnabled={isCredentialsLoginEnabled(org)}
58-
isControlledByEnvVar={env.AUTH_CREDENTIALS_LOGIN_ENABLED !== undefined}
59-
hasAlternativeLoginMethod={providers.some((provider) => provider.type !== "credentials")}
6052
/>
6153
</SettingsCardGroup>
6254
</div>

packages/web/src/lib/errorCodes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,6 @@ export enum ErrorCode {
4040
LIGHTHOUSE_UNREACHABLE = 'LIGHTHOUSE_UNREACHABLE',
4141
EMAIL_LOGIN_CONTROLLED_BY_ENV = 'EMAIL_LOGIN_CONTROLLED_BY_ENV',
4242
EMAIL_LOGIN_CANNOT_BE_DISABLED = 'EMAIL_LOGIN_CANNOT_BE_DISABLED',
43+
MEMBER_APPROVAL_CONTROLLED_BY_ENV = 'MEMBER_APPROVAL_CONTROLLED_BY_ENV',
44+
ANONYMOUS_ACCESS_CONTROLLED_BY_ENV = 'ANONYMOUS_ACCESS_CONTROLLED_BY_ENV',
4345
}

0 commit comments

Comments
 (0)