diff --git a/public/locales/en-US/common.json b/public/locales/en-US/common.json index 74f89d5..e13655f 100644 --- a/public/locales/en-US/common.json +++ b/public/locales/en-US/common.json @@ -96,8 +96,8 @@ }, "values": { "scopes": { - "basic_profile": "Basic Profile", - "full_profile": "Full Profile", + "read_profile": "Read Profile", + "update_profile": "Update Profile", "read_preferences": "Read Preferences", "update_preferences": "Update Preferences", @@ -108,7 +108,16 @@ "delete_alias": "Delete Aliases", "read_report": "Read Reports", - "delete_report": "Delete Reports" + "delete_report": "Delete Reports", + + "read_admin_cron_report": "Read Cron Reports (Admin)", + "read_admin_settings": "Read Settings (Admin)", + "update_admin_settings": "Update Settings (Admin)", + + "read_admin_reserved_alias": "Read Reserved Aliases (Admin)", + "create_admin_reserved_alias": "Create Reserved Aliases (Admin)", + "update_admin_reserved_alias": "Update Reserved Aliases (Admin)", + "delete_admin_reserved_alias": "Delete Reserved Aliases (Admin)" } } } diff --git a/src/constants/values.ts b/src/constants/values.ts index 453e7a5..30bff64 100644 --- a/src/constants/values.ts +++ b/src/constants/values.ts @@ -1,4 +1,4 @@ -import {AliasNote} from "~/server-types" +import {APIKeyScope, AliasNote} from "~/server-types" export const LOCAL_REGEX = /^[a-zA-Z0-9!#$%&‘*+–/=?^_`.{|}~-]{1,64}$/g export const URL_REGEX = @@ -16,9 +16,9 @@ export const DEFAULT_ALIAS_NOTE: AliasNote = { export const ERROR_SNACKBAR_SHOW_DURATION = 5000 export const SUCCESS_SNACKBAR_SHOW_DURATION = 2000 export const AUTHENTICATION_PATHS = ["/auth/login", "/auth/signup", "/auth/complete-account"] -export const API_KEY_SCOPES = [ - "basic_profile", - "full_profile", +export const API_KEY_SCOPES: APIKeyScope[] = [ + "read:profile", + "update:profile", "read:preferences", "update:preferences", @@ -31,3 +31,13 @@ export const API_KEY_SCOPES = [ "read:report", "delete:report", ] +export const ADMIN_API_KEY_SCOPES: APIKeyScope[] = [ + "read:admin_cron_report", + "read:admin_settings", + "update:admin_settings", + + "read:admin_reserved_alias", + "create:admin_reserved_alias", + "update:admin_reserved_alias", + "delete:admin_reserved_alias", +] diff --git a/src/route-widgets/SettingsAPIKeysRoute/CreateNewAPIKeyDialog.tsx b/src/route-widgets/SettingsAPIKeysRoute/CreateNewAPIKeyDialog.tsx index cdabcb5..6e8d262 100644 --- a/src/route-widgets/SettingsAPIKeysRoute/CreateNewAPIKeyDialog.tsx +++ b/src/route-widgets/SettingsAPIKeysRoute/CreateNewAPIKeyDialog.tsx @@ -31,15 +31,17 @@ import {BiText} from "react-icons/bi" import {CgProfile} from "react-icons/cg" import {DatePicker} from "@mui/x-date-pickers" import {useLoaderData} from "react-router-dom" -import {API_KEY_SCOPES} from "~/constants/values" -import {FaMask} from "react-icons/fa" +import {ADMIN_API_KEY_SCOPES, API_KEY_SCOPES} from "~/constants/values" +import {FaMask, FaServer} from "react-icons/fa" import {MdAdd, MdCancel, MdDelete, MdEdit, MdTextSnippet} from "react-icons/md" import {GoSettings} from "react-icons/go" import {TiEye} from "react-icons/ti" import {useMutation} from "@tanstack/react-query" import {AxiosError} from "axios" import {parseFastAPIError} from "~/utils" -import {useErrorSuccessSnacks} from "~/hooks" +import {useErrorSuccessSnacks, useUser} from "~/hooks" +import {HiDocumentReport} from "react-icons/hi" +import {BsStarFill} from "react-icons/bs" import addDays from "date-fns/addDays" import diffInDays from "date-fns/differenceInDays" import set from "date-fns/set" @@ -48,6 +50,9 @@ export interface CreateNewAPIKeyDialogProps { open: boolean onClose: () => void onCreated: (key: APIKey & {key: string}) => void + + prefilledLabel: string + prefilledScopes: APIKeyScope[] } const PRESET_DAYS: number[] = [1, 7, 30, 180, 360] @@ -57,6 +62,9 @@ const API_KEY_SCOPE_ICON_MAP: Record = { alias: , report: , preferences: , + admin_cron_report: , + admin_settings: , + admin_reserved_alias: , } const API_KEY_SCOPE_TYPE_ICON_MAP: Record = { @@ -76,12 +84,15 @@ const normalizeTime = (date: Date) => export default function CreateNewAPIKeyDialog({ open, + prefilledLabel, + prefilledScopes, onClose, onCreated, }: CreateNewAPIKeyDialogProps): ReactElement { const {t} = useTranslation(["settings-api-keys", "common"]) const serverSettings = useLoaderData() as ServerSettings const {showSuccess} = useErrorSuccessSnacks() + const user = useUser() const scheme = yup.object().shape({ label: yup.string().required().label(t("create.form.label.label")), @@ -108,9 +119,9 @@ export default function CreateNewAPIKeyDialog({ const formik = useFormik({ validationSchema: scheme, initialValues: { - label: "", + label: prefilledLabel, expiresAt: addDays(new Date(), 30), - scopes: [], + scopes: [...prefilledScopes], detail: "", }, onSubmit: async (values, {setErrors}) => { @@ -121,6 +132,7 @@ export default function CreateNewAPIKeyDialog({ } }, }) + const availableScopes = [...API_KEY_SCOPES, ...(user.isAdmin ? ADMIN_API_KEY_SCOPES : [])] return ( @@ -185,24 +197,20 @@ export default function CreateNewAPIKeyDialog({ )} > - {API_KEY_SCOPES.map(scope => ( + {availableScopes.map(scope => ( { API_KEY_SCOPE_ICON_MAP[ - scope - .replace(":", "_") - .split("_")[1] + scope.split(":")[1] ] } diff --git a/src/routes/SettingsAPIKeysRoute.tsx b/src/routes/SettingsAPIKeysRoute.tsx index 922f4b2..0f6fae0 100644 --- a/src/routes/SettingsAPIKeysRoute.tsx +++ b/src/routes/SettingsAPIKeysRoute.tsx @@ -1,23 +1,46 @@ -import {ReactElement, useState} from "react" +import {ReactElement, useLayoutEffect, useState} from "react" import {useTranslation} from "react-i18next" import {useQuery} from "@tanstack/react-query" -import {APIKey, PaginationResult} from "~/server-types" +import {APIKey, APIKeyScope, PaginationResult} from "~/server-types" import {AxiosError} from "axios" import {getAPIKeys} from "~/apis" import {QueryResult, SimplePage} from "~/components" import {Button, List} from "@mui/material" import {MdAdd} from "react-icons/md" +import {useQueryParams} from "~/hooks" +import {isArray} from "lodash" +import {API_KEY_SCOPES} from "~/constants/values" +import {useNavigate} from "react-router-dom" import APIKeyListItem from "~/route-widgets/SettingsAPIKeysRoute/APIKeyListItem" import CreateNewAPIKeyDialog from "../route-widgets/SettingsAPIKeysRoute/CreateNewAPIKeyDialog" import EmptyStateScreen from "~/route-widgets/SettingsAPIKeysRoute/EmptyStateScreen" export default function SettingsAPIKeysRoute(): ReactElement { const {t} = useTranslation("settings-api-keys") + const navigate = useNavigate() + + const rawParams = useQueryParams<{action?: any; scopes?: any; label?: any}>() + const params = { + action: rawParams.action === "create-new" ? "create-new" : undefined, + scopes: isArray(rawParams.scopes?.split(",")) + ? rawParams.scopes + .split(",") + .filter((scope: APIKeyScope) => API_KEY_SCOPES.includes(scope)) + : [], + label: rawParams.label, + } + const queryKey = ["get_api_keys"] const query = useQuery, AxiosError>(queryKey, () => getAPIKeys()) const [createdAPIKey, setCreatedAPIKey] = useState<(APIKey & {key: string}) | null>(null) - const [createNew, setCreateNew] = useState(false) + const [createNew, setCreateNew] = useState(params.action === "create-new") + + useLayoutEffect(() => { + if (params.action === "create-new") { + navigate(location.pathname, {replace: true}) + } + }, [params.action, navigate]) return ( <> @@ -60,6 +83,8 @@ export default function SettingsAPIKeysRoute(): ReactElement { setCreateNew(false)} onCreated={key => { query.refetch() diff --git a/src/server-types.ts b/src/server-types.ts index 9f8535e..7f6d24e 100644 --- a/src/server-types.ts +++ b/src/server-types.ts @@ -143,8 +143,8 @@ export interface AliasList { } export type APIKeyScope = - | "full_profile" - | "basic_profile" + | "read:profile" + | "update:profile" | "read:preferences" | "update:preferences" | "read:alias" @@ -153,6 +153,13 @@ export type APIKeyScope = | "delete:alias" | "read:report" | "delete:report" + | "read:admin_cron_report" + | "read:admin_settings" + | "update:admin_settings" + | "read:admin_reserved_alias" + | "create:admin_reserved_alias" + | "update:admin_reserved_alias" + | "delete:admin_reserved_alias" export interface APIKey { id: string