diff --git a/package.json b/package.json
index 45423c9..02a2912 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"@emotion/styled": "^11.10.4",
"@mui/lab": "^5.0.0-alpha.103",
"@mui/material": "^5.10.9",
+ "@mui/x-date-pickers": "^6.0.1",
"@originjs/vite-plugin-commonjs": "^1.0.3",
"@tanstack/react-query": "^4.12.0",
"axios": "^1.1.2",
diff --git a/public/locales/en-US/common.json b/public/locales/en-US/common.json
index ce207da..74f89d5 100644
--- a/public/locales/en-US/common.json
+++ b/public/locales/en-US/common.json
@@ -59,6 +59,10 @@
},
"report": {
"deleted": "Report has been deleted!"
+ },
+ "apiKey": {
+ "keyCopied": "API key has been copied to your clipboard!",
+ "deleted": "API key has been deleted!"
}
},
"general": {
@@ -89,5 +93,22 @@
"signup": "Sign up",
"login": "Log in",
"logout": "Log out"
+ },
+ "values": {
+ "scopes": {
+ "basic_profile": "Basic Profile",
+ "full_profile": "Full Profile",
+
+ "read_preferences": "Read Preferences",
+ "update_preferences": "Update Preferences",
+
+ "read_alias": "Read Aliases",
+ "create_alias": "Create Aliases",
+ "update_alias": "Update Aliases",
+ "delete_alias": "Delete Aliases",
+
+ "read_report": "Read Reports",
+ "delete_report": "Delete Reports"
+ }
}
}
diff --git a/public/locales/en-US/settings-api-keys.json b/public/locales/en-US/settings-api-keys.json
new file mode 100644
index 0000000..e909f35
--- /dev/null
+++ b/public/locales/en-US/settings-api-keys.json
@@ -0,0 +1,42 @@
+{
+ "title": "Manage your API Keys",
+ "create": {
+ "label": "Create a new API Key",
+ "description": "Define a label and the scopes you want to grant to this API Key.",
+ "continueActionLabel": "Create API Key",
+ "success": "Your API Key has been created. Copy it now, you won't be able to see it again.",
+ "form": {
+ "label": {
+ "label": "Label"
+ },
+ "expiresAt": {
+ "label": "Expiration date",
+ "errors": {
+ "tooFarInFuture": "This date is too far in the future.",
+ "inPast": "Expiration date must be in the future."
+ },
+ "values": {
+ "1days": "1 Day",
+ "7days": "1 Week",
+ "30days": "1 Month",
+ "180days": "1/2 Year",
+ "360days": "1 Year"
+ }
+ },
+ "scopes": {
+ "label": "Scopes"
+ }
+ }
+ },
+ "emptyState": {
+ "title": "Welcome to your API Keys",
+ "description": "Create an API Key to get started with the API."
+ },
+ "actions": {
+ "delete": {
+ "label": "Delete API Key",
+ "description": "Are you sure you want to delete this API Key? Your existing integrations using this API Key will stop working. You can create a new API Key to replace it.",
+ "continueActionLabel": "Delete API Key"
+ }
+ }
+}
diff --git a/public/locales/en-US/settings.json b/public/locales/en-US/settings.json
index 7ab1855..756d4aa 100644
--- a/public/locales/en-US/settings.json
+++ b/public/locales/en-US/settings.json
@@ -2,6 +2,7 @@
"title": "Settings",
"actions": {
"enable2fa": "Two-Factor-Authentication",
- "aliasPreferences": "Alias Preferences"
+ "aliasPreferences": "Alias Preferences",
+ "apiKeys": "Manage API Keys"
}
}
diff --git a/public/locales/en-US/signup.json b/public/locales/en-US/signup.json
index dc1b562..e7486e7 100644
--- a/public/locales/en-US/signup.json
+++ b/public/locales/en-US/signup.json
@@ -7,7 +7,7 @@
"mailVerification": {
"title": "You got mail!",
"description": "We sent you an email with a link to confirm your email address. Please check your inbox and click on the link to continue.",
- "maskAsNotSpam": "If you can't find the email, please check your spam folder. If the email lands in your spam folder, please mark it as \"Not Spam\" to receive all emails in the future",
+ "markAsNotSpam": "If you can't find the email, please check your spam folder. If the email lands in your spam folder, please mark it as \"Not Spam\" to receive all emails in the future",
"editEmail": {
"title": "Edit email address?",
"description": "Would you like to return to the previous step and edit your email address?",
diff --git a/src/App.tsx b/src/App.tsx
index c54ecea..48a0768 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -7,6 +7,8 @@ import React, {ReactElement} from "react"
import {queryClient} from "~/constants/react-query"
import {getServerSettings} from "~/apis"
import {darkTheme, lightTheme} from "~/constants/themes"
+import {LocalizationProvider} from "@mui/x-date-pickers"
+import {AdapterDateFns} from "@mui/x-date-pickers/AdapterDateFns"
import AdminRoute from "~/routes/AdminRoute"
import AliasDetailRoute from "~/routes/AliasDetailRoute"
import AliasesRoute from "~/routes/AliasesRoute"
@@ -28,6 +30,7 @@ import ReservedAliasDetailRoute from "~/routes/ReservedAliasDetailRoute"
import ReservedAliasesRoute from "~/routes/ReservedAliasesRoute"
import RootRoute from "~/routes/Root"
import Settings2FARoute from "~/routes/Settings2FARoute"
+import SettingsAPIKeysRoute from "~/routes/SettingsAPIKeysRoute"
import SettingsAliasPreferencesRoute from "~/routes/SettingsAliasPreferencesRoute"
import SettingsRoute from "~/routes/SettingsRoute"
import SignupRoute from "~/routes/SignupRoute"
@@ -105,6 +108,11 @@ const router = createBrowserRouter([
path: "/settings/2fa",
element: ,
},
+ {
+ path: "/settings/api-keys",
+ loader: getServerSettings,
+ element: ,
+ },
{
path: "/reports",
loader: getServerSettings,
@@ -162,9 +170,11 @@ export default function App(): ReactElement {
-
-
-
+
+
+
+
+
diff --git a/src/apis/create-api-key.ts b/src/apis/create-api-key.ts
new file mode 100644
index 0000000..ac7582c
--- /dev/null
+++ b/src/apis/create-api-key.ts
@@ -0,0 +1,35 @@
+import {APIKey} from "~/server-types"
+import {client} from "~/constants/axios-client"
+import formatISO from "date-fns/formatISO"
+import parseAPIKey from "~/apis/helpers/parse-api-key"
+
+export interface CreateAPIKeyData {
+ label: string
+ scopes: APIKey["scopes"]
+
+ expiresAt?: Date
+}
+
+export default async function createAPIKey({
+ label,
+ scopes,
+ expiresAt,
+}: CreateAPIKeyData): Promise {
+ const {data} = await client.post(
+ `${import.meta.env.VITE_SERVER_BASE_URL}/v1/api-key`,
+ {
+ label,
+ scopes,
+ expiresAt: expiresAt
+ ? formatISO(expiresAt, {
+ representation: "date",
+ })
+ : undefined,
+ },
+ {
+ withCredentials: true,
+ },
+ )
+
+ return parseAPIKey(data) as APIKey & {key: string}
+}
diff --git a/src/apis/delete-api-key.ts b/src/apis/delete-api-key.ts
new file mode 100644
index 0000000..9280592
--- /dev/null
+++ b/src/apis/delete-api-key.ts
@@ -0,0 +1,10 @@
+import {client} from "~/constants/axios-client"
+import {SimpleDetailResponse} from "~/server-types"
+
+export default async function deleteAPIKey(id: string): Promise {
+ const {data} = await client.delete(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/api-key/${id}`, {
+ withCredentials: true,
+ })
+
+ return data
+}
diff --git a/src/apis/get-api-keys.ts b/src/apis/get-api-keys.ts
new file mode 100644
index 0000000..f927dd3
--- /dev/null
+++ b/src/apis/get-api-keys.ts
@@ -0,0 +1,22 @@
+import {APIKey, GetPageData, PaginationResult} from "~/server-types"
+import {client} from "~/constants/axios-client"
+import parseAPIKey from "~/apis/helpers/parse-api-key"
+
+export interface GetAPIKeysData extends GetPageData {}
+
+export default async function getAPIKeys({size, page}: GetAPIKeysData = {}): Promise<
+ PaginationResult
+> {
+ const {data} = await client.get(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/api-key/`, {
+ withCredentials: true,
+ params: {
+ size,
+ page,
+ },
+ })
+
+ return {
+ ...data,
+ items: data.items.map(parseAPIKey),
+ }
+}
diff --git a/src/apis/helpers/parse-api-key.ts b/src/apis/helpers/parse-api-key.ts
new file mode 100644
index 0000000..d62c51a
--- /dev/null
+++ b/src/apis/helpers/parse-api-key.ts
@@ -0,0 +1,8 @@
+import {APIKey} from "~/server-types"
+
+export default function parseAPIKey(key: APIKey): APIKey {
+ return {
+ ...key,
+ expiresAt: new Date(key.expiresAt),
+ }
+}
diff --git a/src/apis/index.ts b/src/apis/index.ts
index 7340c69..ce3aceb 100644
--- a/src/apis/index.ts
+++ b/src/apis/index.ts
@@ -68,3 +68,9 @@ export * from "./delete-2fa"
export {default as delete2FA} from "./delete-2fa"
export * from "./verify-otp"
export {default as verifyOTP} from "./verify-otp"
+export * from "./create-api-key"
+export {default as createAPIKey} from "./create-api-key"
+export * from "./get-api-keys"
+export {default as getAPIKeys} from "./get-api-keys"
+export * from "./delete-api-key"
+export {default as deleteAPIKey} from "./delete-api-key"
diff --git a/src/components/widgets/DeleteAPIButton.tsx b/src/components/widgets/DeleteAPIButton.tsx
index 5a8e30b..e6da96c 100644
--- a/src/components/widgets/DeleteAPIButton.tsx
+++ b/src/components/widgets/DeleteAPIButton.tsx
@@ -25,6 +25,8 @@ export interface DeleteAPIButtonProps {
description?: string
successMessage?: string
navigateTo?: string
+ children?: (onDelete: () => void) => ReactElement
+ onDone?: () => void
}
export default function DeleteAPIButton({
@@ -33,7 +35,9 @@ export default function DeleteAPIButton({
label,
continueLabel,
description,
- navigateTo = "/aliases",
+ navigateTo,
+ onDone,
+ children: render,
}: DeleteAPIButtonProps): ReactElement {
const {t} = useTranslation("common")
const {showError, showSuccess} = useErrorSuccessSnacks()
@@ -43,7 +47,12 @@ export default function DeleteAPIButton({
onError: showError,
onSuccess: () => {
showSuccess(successMessage || t("messages.deletedObject"))
- navigate(navigateTo)
+
+ if (navigateTo) {
+ navigate(navigateTo)
+ } else if (onDone) {
+ onDone()
+ }
},
})
@@ -51,15 +60,19 @@ export default function DeleteAPIButton({
return (
<>
- }
- onClick={() => setShowDeleteDialog(true)}
- >
- {label}
-
+ {render ? (
+ render(() => setShowDeleteDialog(true))
+ ) : (
+ }
+ onClick={() => setShowDeleteDialog(true)}
+ >
+ {label}
+
+ )}