mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
feat: API key restriction
This commit is contained in:
@@ -604,6 +604,10 @@
|
|||||||
"This action cannot be undone. Any applications using this API key will stop working.": "This action cannot be undone. Any applications using this API key will stop working.",
|
"This action cannot be undone. Any applications using this API key will stop working.": "This action cannot be undone. Any applications using this API key will stop working.",
|
||||||
"Update API key": "Update API key",
|
"Update API key": "Update API key",
|
||||||
"Manage API keys for all users in the workspace": "Manage API keys for all users in the workspace",
|
"Manage API keys for all users in the workspace": "Manage API keys for all users in the workspace",
|
||||||
|
"Restrict API key creation to admins": "Restrict API key creation to admins",
|
||||||
|
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Only admins and owners can create new API keys. Existing member keys will continue to work.",
|
||||||
|
"Toggle restrict API keys to admins": "Toggle restrict API keys to admins",
|
||||||
|
"API key creation is restricted to admins by your workspace administrator.": "API key creation is restricted to admins by your workspace administrator.",
|
||||||
"AI settings": "AI settings",
|
"AI settings": "AI settings",
|
||||||
"AI search": "AI search",
|
"AI search": "AI search",
|
||||||
"AI Answer": "AI Answer",
|
"AI Answer": "AI Answer",
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { Text, Switch, Tooltip } from "@mantine/core";
|
||||||
|
import { useAtom } from "jotai";
|
||||||
|
import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { updateWorkspace } from "@/features/workspace/services/workspace-service.ts";
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
|
import useEnterpriseAccess from "@/ee/hooks/use-enterprise-access.tsx";
|
||||||
|
import {
|
||||||
|
ResponsiveSettingsRow,
|
||||||
|
ResponsiveSettingsContent,
|
||||||
|
ResponsiveSettingsControl,
|
||||||
|
} from "@/components/ui/responsive-settings-row";
|
||||||
|
|
||||||
|
export default function RestrictApiToAdmins() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [workspace, setWorkspace] = useAtom(workspaceAtom);
|
||||||
|
const [checked, setChecked] = useState(
|
||||||
|
workspace?.settings?.api?.restrictToAdmins === true,
|
||||||
|
);
|
||||||
|
const hasAccess = useEnterpriseAccess();
|
||||||
|
|
||||||
|
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = event.currentTarget.checked;
|
||||||
|
try {
|
||||||
|
const updatedWorkspace = await updateWorkspace({
|
||||||
|
restrictApiToAdmins: value,
|
||||||
|
});
|
||||||
|
setChecked(value);
|
||||||
|
setWorkspace(updatedWorkspace);
|
||||||
|
} catch (err) {
|
||||||
|
notifications.show({
|
||||||
|
message: err?.response?.data?.message,
|
||||||
|
color: "red",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ResponsiveSettingsRow>
|
||||||
|
<ResponsiveSettingsContent>
|
||||||
|
<Text size="md">
|
||||||
|
{t("Restrict API key creation to admins")}
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
{t(
|
||||||
|
"Only admins and owners can create new API keys. Existing member keys will continue to work.",
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</ResponsiveSettingsContent>
|
||||||
|
|
||||||
|
<ResponsiveSettingsControl>
|
||||||
|
<Tooltip
|
||||||
|
label={t("Requires an enterprise license")}
|
||||||
|
disabled={hasAccess}
|
||||||
|
refProp="rootRef"
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
checked={checked}
|
||||||
|
onChange={handleChange}
|
||||||
|
disabled={!hasAccess}
|
||||||
|
aria-label={t("Toggle restrict API keys to admins")}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</ResponsiveSettingsControl>
|
||||||
|
</ResponsiveSettingsRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Anchor, Alert, Button, Group, Space, Text } from "@mantine/core";
|
import { Anchor, Alert, Button, Group, Space, Text } from "@mantine/core";
|
||||||
|
import { IconInfoCircle } from "@tabler/icons-react";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import SettingsTitle from "@/components/settings/settings-title";
|
import SettingsTitle from "@/components/settings/settings-title";
|
||||||
@@ -15,6 +16,7 @@ import { useGetApiKeysQuery } from "@/ee/api-key/queries/api-key-query.ts";
|
|||||||
import { IApiKey } from "@/ee/api-key";
|
import { IApiKey } from "@/ee/api-key";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts";
|
import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts";
|
||||||
|
import useUserRole from "@/hooks/use-user-role.tsx";
|
||||||
|
|
||||||
export default function UserApiKeys() {
|
export default function UserApiKeys() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -26,7 +28,10 @@ export default function UserApiKeys() {
|
|||||||
const [selectedApiKey, setSelectedApiKey] = useState<IApiKey | null>(null);
|
const [selectedApiKey, setSelectedApiKey] = useState<IApiKey | null>(null);
|
||||||
const { data, isLoading } = useGetApiKeysQuery({ cursor });
|
const { data, isLoading } = useGetApiKeysQuery({ cursor });
|
||||||
const [workspace] = useAtom(workspaceAtom);
|
const [workspace] = useAtom(workspaceAtom);
|
||||||
|
const { isAdmin } = useUserRole();
|
||||||
const mcpEnabled = workspace?.settings?.ai?.mcp === true;
|
const mcpEnabled = workspace?.settings?.ai?.mcp === true;
|
||||||
|
const restrictToAdmins = workspace?.settings?.api?.restrictToAdmins === true;
|
||||||
|
const canCreate = !restrictToAdmins || isAdmin;
|
||||||
|
|
||||||
const handleCreateSuccess = (response: IApiKey) => {
|
const handleCreateSuccess = (response: IApiKey) => {
|
||||||
setCreatedApiKey(response);
|
setCreatedApiKey(response);
|
||||||
@@ -60,8 +65,8 @@ export default function UserApiKeys() {
|
|||||||
{t("for usage details.")}
|
{t("for usage details.")}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{mcpEnabled && (
|
{mcpEnabled && canCreate && (
|
||||||
<Alert variant="light" color="blue" mb="md" p="sm">
|
<Alert variant="light" color="blue" mb="md" p="sm" icon={<IconInfoCircle />}>
|
||||||
<Text size="sm">
|
<Text size="sm">
|
||||||
{t(
|
{t(
|
||||||
"Your workspace has MCP enabled. Use your API key to connect AI assistants.",
|
"Your workspace has MCP enabled. Use your API key to connect AI assistants.",
|
||||||
@@ -83,11 +88,19 @@ export default function UserApiKeys() {
|
|||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Group justify="flex-end" mb="md">
|
{canCreate ? (
|
||||||
<Button onClick={() => setCreateModalOpened(true)}>
|
<Group justify="flex-end" mb="md">
|
||||||
{t("Create API Key")}
|
<Button onClick={() => setCreateModalOpened(true)}>
|
||||||
</Button>
|
{t("Create API Key")}
|
||||||
</Group>
|
</Button>
|
||||||
|
</Group>
|
||||||
|
) : restrictToAdmins ? (
|
||||||
|
<Alert variant="light" color="yellow" mb="md" p="sm" icon={<IconInfoCircle />}>
|
||||||
|
<Text size="sm">
|
||||||
|
{t("API key creation is restricted to admins by your workspace administrator.")}
|
||||||
|
</Text>
|
||||||
|
</Alert>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<ApiKeyTable
|
<ApiKeyTable
|
||||||
apiKeys={data?.items || []}
|
apiKeys={data?.items || []}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Anchor, Button, Group, Space, Text } from "@mantine/core";
|
import { Anchor, Button, Divider, Group, Space, Text } from "@mantine/core";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import SettingsTitle from "@/components/settings/settings-title";
|
import SettingsTitle from "@/components/settings/settings-title";
|
||||||
@@ -14,6 +14,7 @@ import { useCursorPaginate } from "@/hooks/use-cursor-paginate";
|
|||||||
import { useGetApiKeysQuery } from "@/ee/api-key/queries/api-key-query.ts";
|
import { useGetApiKeysQuery } from "@/ee/api-key/queries/api-key-query.ts";
|
||||||
import { IApiKey } from "@/ee/api-key";
|
import { IApiKey } from "@/ee/api-key";
|
||||||
import useUserRole from '@/hooks/use-user-role.tsx';
|
import useUserRole from '@/hooks/use-user-role.tsx';
|
||||||
|
import RestrictApiToAdmins from "@/ee/api-key/components/restrict-api-to-admins";
|
||||||
|
|
||||||
export default function WorkspaceApiKeys() {
|
export default function WorkspaceApiKeys() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -63,6 +64,9 @@ export default function WorkspaceApiKeys() {
|
|||||||
{t("for usage details.")}
|
{t("for usage details.")}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
<RestrictApiToAdmins />
|
||||||
|
<Divider my="lg" />
|
||||||
|
|
||||||
<Group justify="flex-end" mb="md">
|
<Group justify="flex-end" mb="md">
|
||||||
<Button onClick={() => setCreateModalOpened(true)}>
|
<Button onClick={() => setCreateModalOpened(true)}>
|
||||||
{t("Create API Key")}
|
{t("Create API Key")}
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ export interface IWorkspace {
|
|||||||
export interface IWorkspaceSettings {
|
export interface IWorkspaceSettings {
|
||||||
ai?: IWorkspaceAiSettings;
|
ai?: IWorkspaceAiSettings;
|
||||||
sharing?: IWorkspaceSharingSettings;
|
sharing?: IWorkspaceSharingSettings;
|
||||||
|
api?: IWorkspaceApiSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWorkspaceApiSettings {
|
||||||
|
restrictToAdmins?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWorkspaceAiSettings {
|
export interface IWorkspaceAiSettings {
|
||||||
|
|||||||
@@ -327,7 +327,8 @@ export class WorkspaceService {
|
|||||||
if (
|
if (
|
||||||
typeof updateWorkspaceDto.disablePublicSharing !== 'undefined' ||
|
typeof updateWorkspaceDto.disablePublicSharing !== 'undefined' ||
|
||||||
typeof updateWorkspaceDto.trashRetentionDays !== 'undefined' ||
|
typeof updateWorkspaceDto.trashRetentionDays !== 'undefined' ||
|
||||||
typeof updateWorkspaceDto.mcpEnabled !== 'undefined'
|
typeof updateWorkspaceDto.mcpEnabled !== 'undefined' ||
|
||||||
|
typeof updateWorkspaceDto.restrictApiToAdmins !== 'undefined'
|
||||||
) {
|
) {
|
||||||
const ws = await this.db
|
const ws = await this.db
|
||||||
.selectFrom('workspaces')
|
.selectFrom('workspaces')
|
||||||
|
|||||||
+1
-1
Submodule apps/server/src/ee updated: 45fe7b1fda...8b7ae8cf1b
Reference in New Issue
Block a user