diff --git a/frontend/web/src/components/setting/WorkspaceGeneralSettingSection.tsx b/frontend/web/src/components/setting/WorkspaceGeneralSettingSection.tsx
index bb49f07..af67c0b 100644
--- a/frontend/web/src/components/setting/WorkspaceGeneralSettingSection.tsx
+++ b/frontend/web/src/components/setting/WorkspaceGeneralSettingSection.tsx
@@ -1,4 +1,4 @@
-import { Button, Divider, Option, Select, Textarea } from "@mui/joy";
+import { Button, Option, Select, Textarea } from "@mui/joy";
import { head, isEqual } from "lodash-es";
import { useRef, useState } from "react";
import toast from "react-hot-toast";
@@ -10,7 +10,6 @@ import { Visibility } from "@/types/proto/api/v1/common";
import { WorkspaceSetting } from "@/types/proto/api/v1/workspace_service";
import FeatureBadge from "../FeatureBadge";
import Icon from "../Icon";
-import SSOSection from "./SSOSection";
const getDefaultVisibility = (visibility?: Visibility) => {
if (!visibility || [Visibility.VISIBILITY_UNSPECIFIED, Visibility.UNRECOGNIZED].includes(visibility)) {
@@ -157,10 +156,6 @@ const WorkspaceGeneralSettingSection = () => {
{t("common.save")}
-
-
-
-
);
diff --git a/frontend/web/src/components/setting/WorkspaceSecuritySection.tsx b/frontend/web/src/components/setting/WorkspaceSecuritySection.tsx
new file mode 100644
index 0000000..b8095e4
--- /dev/null
+++ b/frontend/web/src/components/setting/WorkspaceSecuritySection.tsx
@@ -0,0 +1,67 @@
+import { Switch } from "@mui/joy";
+import toast from "react-hot-toast";
+import { workspaceServiceClient } from "@/grpcweb";
+import { useWorkspaceStore } from "@/stores";
+import { WorkspaceSetting } from "@/types/proto/api/v1/workspace_service";
+import SSOSection from "./SSOSection";
+
+const WorkspaceSecuritySection = () => {
+ const workspaceStore = useWorkspaceStore();
+
+ const toggleDisallowUserRegistration = async (on: boolean) => {
+ if (on) {
+ const confirmed = window.confirm("Are you sure to disallow user registration? This will prevent new users from signing up.");
+ if (!confirmed) {
+ return;
+ }
+ }
+
+ await updateWorkspaceSetting(
+ WorkspaceSetting.fromPartial({
+ disallowUserRegistration: on,
+ }),
+ ["disallow_user_registration"],
+ );
+ };
+
+ const updateWorkspaceSetting = async (workspaceSetting: WorkspaceSetting, updateMask: string[]) => {
+ if (updateMask.length === 0) {
+ toast.error("No changes made");
+ return;
+ }
+
+ console.log("1", {
+ setting: workspaceSetting,
+ updateMask: updateMask,
+ });
+ try {
+ await workspaceServiceClient.updateWorkspaceSetting({
+ setting: workspaceSetting,
+ updateMask: updateMask,
+ });
+ await workspaceStore.fetchWorkspaceSetting();
+ toast.success("Workspace setting saved successfully");
+ } catch (error: any) {
+ toast.error(error.details);
+ }
+ };
+
+ return (
+
+
Security
+
+
+
+ toggleDisallowUserRegistration(event.target.checked)}
+ endDecorator={Disallow user registration}
+ />
+
+
+
+ );
+};
+
+export default WorkspaceSecuritySection;
diff --git a/frontend/web/src/pages/WorkspaceSetting.tsx b/frontend/web/src/pages/WorkspaceSetting.tsx
index 27254f2..70cdd8b 100644
--- a/frontend/web/src/pages/WorkspaceSetting.tsx
+++ b/frontend/web/src/pages/WorkspaceSetting.tsx
@@ -4,6 +4,7 @@ import { Link } from "react-router-dom";
import Icon from "@/components/Icon";
import WorkspaceGeneralSettingSection from "@/components/setting/WorkspaceGeneralSettingSection";
import WorkspaceMembersSection from "@/components/setting/WorkspaceMembersSection";
+import WorkspaceSecuritySection from "@/components/setting/WorkspaceSecuritySection";
import { useUserStore, useWorkspaceStore } from "@/stores";
import { stringifyPlanType } from "@/stores/subscription";
import { Role } from "@/types/proto/api/v1/user_service";
@@ -44,6 +45,8 @@ const WorkspaceSetting = () => {
+
+
);
};
diff --git a/frontend/web/src/stores/workspace.ts b/frontend/web/src/stores/workspace.ts
index b1f278e..3dc1ce8 100644
--- a/frontend/web/src/stores/workspace.ts
+++ b/frontend/web/src/stores/workspace.ts
@@ -7,6 +7,7 @@ export enum FeatureType {
SSO = "ysh.slash.sso",
AdvancedAnalytics = "ysh.slash.advanced-analytics",
UnlimitedAccounts = "ysh.slash.unlimited-accounts",
+ UnlimitedShortcuts = "ysh.slash.unlimited-shortcuts",
UnlimitedCollections = "ysh.slash.unlimited-collections",
CustomeBranding = "ysh.slash.custom-branding",
}
diff --git a/server/route/api/v1/workspace_service.go b/server/route/api/v1/workspace_service.go
index 2b61a2c..1e8e3bf 100644
--- a/server/route/api/v1/workspace_service.go
+++ b/server/route/api/v1/workspace_service.go
@@ -61,6 +61,9 @@ func (s *APIV1Service) GetWorkspaceSetting(ctx context.Context, _ *v1pb.GetWorks
generalSetting := v.GetGeneral()
workspaceSetting.Branding = generalSetting.GetBranding()
workspaceSetting.CustomStyle = generalSetting.GetCustomStyle()
+ } else if v.Key == storepb.WorkspaceSettingKey_WORKSPACE_SETTING_SECURITY {
+ securitySetting := v.GetSecurity()
+ workspaceSetting.DisallowUserRegistration = securitySetting.GetDisallowUserRegistration()
} else if v.Key == storepb.WorkspaceSettingKey_WORKSPACE_SETTING_SHORTCUT_RELATED {
shortcutRelatedSetting := v.GetShortcutRelated()
workspaceSetting.DefaultVisibility = v1pb.Visibility(shortcutRelatedSetting.GetDefaultVisibility())
@@ -153,6 +156,20 @@ func (s *APIV1Service) UpdateWorkspaceSetting(ctx context.Context, request *v1pb
}); err != nil {
return nil, status.Errorf(codes.Internal, "failed to update workspace setting: %v", err)
}
+ } else if path == "disallow_user_registration" {
+ securitySetting, err := s.Store.GetWorkspaceSecuritySetting(ctx)
+ if err != nil {
+ return nil, status.Errorf(codes.Internal, "failed to get workspace setting: %v", err)
+ }
+ securitySetting.DisallowUserRegistration = request.Setting.DisallowUserRegistration
+ if _, err := s.Store.UpsertWorkspaceSetting(ctx, &storepb.WorkspaceSetting{
+ Key: storepb.WorkspaceSettingKey_WORKSPACE_SETTING_SECURITY,
+ Value: &storepb.WorkspaceSetting_Security{
+ Security: securitySetting,
+ },
+ }); err != nil {
+ return nil, status.Errorf(codes.Internal, "failed to update workspace setting: %v", err)
+ }
} else {
return nil, status.Errorf(codes.InvalidArgument, "invalid path: %s", path)
}