From dace44f1d90b19c64592bfeda4e8a8fab6cb7467 Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Sat, 11 Mar 2023 12:07:12 +0100
Subject: [PATCH] feat(api-key): Add create api key functionality
---
package.json | 1 +
public/locales/en-US/common.json | 20 ++
public/locales/en-US/settings-api-keys.json | 27 +-
src/App.tsx | 11 +-
src/apis/create-api-key.ts | 25 +-
src/constants/values.ts | 15 +
.../SettingsAPIKeysRoute/APIKeyListItem.tsx | 49 ++-
.../CreateNewAPIKeyDialog.tsx | 311 ++++++++++++++++++
src/routes/SettingsAPIKeysRoute.tsx | 84 +++--
src/server-types.ts | 15 +-
yarn.lock | 97 ++++++
11 files changed, 602 insertions(+), 53 deletions(-)
create mode 100644 src/route-widgets/SettingsAPIKeysRoute/CreateNewAPIKeyDialog.tsx
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..3fb6c83 100644
--- a/public/locales/en-US/common.json
+++ b/public/locales/en-US/common.json
@@ -59,6 +59,9 @@
},
"report": {
"deleted": "Report has been deleted!"
+ },
+ "apiKey": {
+ "keyCopied": "API key has been copied to your clipboard!"
}
},
"general": {
@@ -89,5 +92,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
index fe1a5d7..e2e5623 100644
--- a/public/locales/en-US/settings-api-keys.json
+++ b/public/locales/en-US/settings-api-keys.json
@@ -1,7 +1,32 @@
{
"title": "Manage your API Keys",
"create": {
- "label": "Create a new API Key"
+ "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",
diff --git a/src/App.tsx b/src/App.tsx
index c58c86c..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"
@@ -108,6 +110,7 @@ const router = createBrowserRouter([
},
{
path: "/settings/api-keys",
+ loader: getServerSettings,
element: ,
},
{
@@ -167,9 +170,11 @@ export default function App(): ReactElement {
-
-
-
+
+
+
+
+
diff --git a/src/apis/create-api-key.ts b/src/apis/create-api-key.ts
index e44558f..ac7582c 100644
--- a/src/apis/create-api-key.ts
+++ b/src/apis/create-api-key.ts
@@ -1,5 +1,6 @@
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 {
@@ -13,12 +14,22 @@ 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,
- })
+}: 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)
+ return parseAPIKey(data) as APIKey & {key: string}
}
diff --git a/src/constants/values.ts b/src/constants/values.ts
index 430804c..453e7a5 100644
--- a/src/constants/values.ts
+++ b/src/constants/values.ts
@@ -16,3 +16,18 @@ 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",
+
+ "read:preferences",
+ "update:preferences",
+
+ "read:alias",
+ "create:alias",
+ "update:alias",
+ "delete:alias",
+
+ "read:report",
+ "delete:report",
+]
diff --git a/src/route-widgets/SettingsAPIKeysRoute/APIKeyListItem.tsx b/src/route-widgets/SettingsAPIKeysRoute/APIKeyListItem.tsx
index 93e9783..c410a51 100644
--- a/src/route-widgets/SettingsAPIKeysRoute/APIKeyListItem.tsx
+++ b/src/route-widgets/SettingsAPIKeysRoute/APIKeyListItem.tsx
@@ -1,24 +1,53 @@
import {ReactElement} from "react"
import {APIKey} from "~/server-types"
-import {IconButton, ListItem, ListItemSecondaryAction, ListItemText} from "@mui/material"
+import {Alert, IconButton, ListItem, ListItemSecondaryAction, ListItemText} from "@mui/material"
import {useTranslation} from "react-i18next"
-import {MdDelete} from "react-icons/md"
+import {MdContentCopy, MdDelete} from "react-icons/md"
+import {useCopyToClipboard} from "react-use"
+import {ErrorSnack, SuccessSnack} from "~/components"
export interface APIKeyListItemProps {
apiKey: APIKey
+ privateKey?: string
}
-export default function APIKeyListItem({apiKey}: APIKeyListItemProps): ReactElement {
- const {t} = useTranslation("settings-api-keys")
+export default function APIKeyListItem({apiKey, privateKey}: APIKeyListItemProps): ReactElement {
+ const {t} = useTranslation(["settings-api-keys", "common"])
+
+ const [{value, error}, copy] = useCopyToClipboard()
+
+ if (privateKey) {
+ return (
+ <>
+
+
+ {privateKey}
+
+ copy(privateKey)}>
+
+
+
+
+
+
+
+ >
+ )
+ }
return (
-
-
-
-
-
-
+ <>
+
+
+
+
+
+
+ >
)
}
diff --git a/src/route-widgets/SettingsAPIKeysRoute/CreateNewAPIKeyDialog.tsx b/src/route-widgets/SettingsAPIKeysRoute/CreateNewAPIKeyDialog.tsx
new file mode 100644
index 0000000..cdabcb5
--- /dev/null
+++ b/src/route-widgets/SettingsAPIKeysRoute/CreateNewAPIKeyDialog.tsx
@@ -0,0 +1,311 @@
+import * as yup from "yup"
+import {ReactElement} from "react"
+import {
+ Alert,
+ Badge,
+ Box,
+ Button,
+ Chip,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogContentText,
+ DialogTitle,
+ FormControl,
+ FormHelperText,
+ Grid,
+ InputAdornment,
+ InputLabel,
+ ListItem,
+ ListItemIcon,
+ ListItemText,
+ MenuItem,
+ Select,
+ TextField,
+} from "@mui/material"
+import {useTranslation} from "react-i18next"
+import {useFormik} from "formik"
+import {CreateAPIKeyData, createAPIKey} from "~/apis"
+import {APIKey, APIKeyScope, ServerSettings} from "~/server-types"
+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 {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 addDays from "date-fns/addDays"
+import diffInDays from "date-fns/differenceInDays"
+import set from "date-fns/set"
+
+export interface CreateNewAPIKeyDialogProps {
+ open: boolean
+ onClose: () => void
+ onCreated: (key: APIKey & {key: string}) => void
+}
+
+const PRESET_DAYS: number[] = [1, 7, 30, 180, 360]
+
+const API_KEY_SCOPE_ICON_MAP: Record = {
+ profile: ,
+ alias: ,
+ report: ,
+ preferences: ,
+}
+
+const API_KEY_SCOPE_TYPE_ICON_MAP: Record = {
+ read: ,
+ create: ,
+ update: ,
+ delete: ,
+}
+
+const normalizeTime = (date: Date) =>
+ set(date, {
+ hours: 0,
+ minutes: 0,
+ seconds: 0,
+ milliseconds: 0,
+ })
+
+export default function CreateNewAPIKeyDialog({
+ open,
+ onClose,
+ onCreated,
+}: CreateNewAPIKeyDialogProps): ReactElement {
+ const {t} = useTranslation(["settings-api-keys", "common"])
+ const serverSettings = useLoaderData() as ServerSettings
+ const {showSuccess} = useErrorSuccessSnacks()
+
+ const scheme = yup.object().shape({
+ label: yup.string().required().label(t("create.form.label.label")),
+ expiresAt: yup.date().required().label(t("create.form.expiresAt.label")),
+ scopes: yup
+ .array()
+ .of(yup.string())
+ .required()
+ .label(t("create.form.scopes.label")),
+ })
+
+ const {mutateAsync} = useMutation(
+ data => createAPIKey(data),
+ {
+ onSuccess: async key => {
+ onClose()
+
+ showSuccess(t("create.success"))
+
+ onCreated(key)
+ },
+ },
+ )
+ const formik = useFormik({
+ validationSchema: scheme,
+ initialValues: {
+ label: "",
+ expiresAt: addDays(new Date(), 30),
+ scopes: [],
+ detail: "",
+ },
+ onSubmit: async (values, {setErrors}) => {
+ try {
+ await mutateAsync(values)
+ } catch (error) {
+ setErrors(parseFastAPIError(error as AxiosError))
+ }
+ },
+ })
+
+ return (
+
+ )
+}
diff --git a/src/routes/SettingsAPIKeysRoute.tsx b/src/routes/SettingsAPIKeysRoute.tsx
index 82d9ab8..684e1f5 100644
--- a/src/routes/SettingsAPIKeysRoute.tsx
+++ b/src/routes/SettingsAPIKeysRoute.tsx
@@ -1,4 +1,4 @@
-import {ReactElement} from "react"
+import {ReactElement, useState} from "react"
import {useTranslation} from "react-i18next"
import {useQuery} from "@tanstack/react-query"
import {APIKey, PaginationResult} from "~/server-types"
@@ -6,43 +6,65 @@ import {AxiosError} from "axios"
import {getAPIKeys} from "~/apis"
import {QueryResult, SimplePage} from "~/components"
import {Button, List} from "@mui/material"
-import {Link} from "react-router-dom"
+import {MdAdd} from "react-icons/md"
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 query = useQuery, AxiosError>(["get_api_keys"], () =>
- getAPIKeys(),
- )
+ 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)
return (
-
- {t("create.label")}
-
- }
- >
- , AxiosError> query={query}>
- {({items: apiKeys}) =>
- apiKeys.length > 0 ? (
-
- {apiKeys.map(apiKey => (
-
- ))}
-
- ) : (
-
- )
+ <>
+ setCreateNew(true)}
+ startIcon={}
+ >
+ {t("create.label")}
+
}
-
-
+ >
+ , AxiosError> query={query}>
+ {({items: apiKeys}) =>
+ apiKeys.length > 0 ? (
+
+ {apiKeys.map(apiKey => (
+
+ ))}
+
+ ) : (
+
+ )
+ }
+
+
+ setCreateNew(false)}
+ onCreated={key => {
+ query.refetch()
+ setCreatedAPIKey(key)
+ }}
+ />
+ >
)
}
diff --git a/src/server-types.ts b/src/server-types.ts
index 6046098..a46d565 100644
--- a/src/server-types.ts
+++ b/src/server-types.ts
@@ -84,6 +84,7 @@ export interface ServerSettings {
instanceSalt: string
publicKey: string
allowAliasDeletion: boolean
+ apiKeyMaxDays: number
}
export interface Alias {
@@ -141,11 +142,23 @@ export interface AliasList {
type: AliasType
}
+export type APIKeyScope =
+ | "profile_basic"
+ | "full_profile"
+ | "read:preferences"
+ | "update:preferences"
+ | "read:alias"
+ | "create:alias"
+ | "update:alias"
+ | "delete:alias"
+ | "read:report"
+ | "delete:report"
+
export interface APIKey {
id: string
label: string
expiresAt: Date
- scopes: string[]
+ scopes: APIKeyScope[]
}
export interface Report {
diff --git a/yarn.lock b/yarn.lock
index 4d7337a..9763a1a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -464,6 +464,13 @@
dependencies:
regenerator-runtime "^0.13.10"
+"@babel/runtime@^7.20.13", "@babel/runtime@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673"
+ integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==
+ dependencies:
+ regenerator-runtime "^0.13.11"
+
"@babel/template@^7.18.10":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71"
@@ -562,6 +569,60 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
+"@date-io/core@^2.16.0":
+ version "2.16.0"
+ resolved "https://registry.npmjs.org/@date-io/core/-/core-2.16.0.tgz#7871bfc1d9bca9aa35ad444a239505589d0f22f6"
+ integrity sha512-DYmSzkr+jToahwWrsiRA2/pzMEtz9Bq1euJwoOuYwuwIYXnZFtHajY2E6a1VNVDc9jP8YUXK1BvnZH9mmT19Zg==
+
+"@date-io/date-fns-jalali@^2.16.0":
+ version "2.16.0"
+ resolved "https://registry.npmjs.org/@date-io/date-fns-jalali/-/date-fns-jalali-2.16.0.tgz#0e2916c287e00d708a6f9f6d6e6c44ec377994c5"
+ integrity sha512-MNVvGYwRiBydbvY7gvZM14W2kosIG29G1Ekw5qmYWOXkIIFngh6ZvV7/uVGDCW+gqlIeSz/XitZXA9n8RO0tJw==
+ dependencies:
+ "@date-io/core" "^2.16.0"
+
+"@date-io/date-fns@^2.16.0":
+ version "2.16.0"
+ resolved "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-2.16.0.tgz#bd5e09b6ecb47ee55e593fc3a87e7b2caaa3da40"
+ integrity sha512-bfm5FJjucqlrnQcXDVU5RD+nlGmL3iWgkHTq3uAZWVIuBu6dDmGa3m8a6zo2VQQpu8ambq9H22UyUpn7590joA==
+ dependencies:
+ "@date-io/core" "^2.16.0"
+
+"@date-io/dayjs@^2.16.0":
+ version "2.16.0"
+ resolved "https://registry.npmjs.org/@date-io/dayjs/-/dayjs-2.16.0.tgz#0d2c254ad8db1306fdc4b8eda197cb53c9af89dc"
+ integrity sha512-y5qKyX2j/HG3zMvIxTobYZRGnd1FUW2olZLS0vTj7bEkBQkjd2RO7/FEwDY03Z1geVGlXKnzIATEVBVaGzV4Iw==
+ dependencies:
+ "@date-io/core" "^2.16.0"
+
+"@date-io/hijri@^2.16.1":
+ version "2.16.1"
+ resolved "https://registry.npmjs.org/@date-io/hijri/-/hijri-2.16.1.tgz#a5e7e5b875e0ac8719c235eaf51d4188f21ea193"
+ integrity sha512-6BxY0mtnqj5cBiXluRs3uWN0mSJwGw0AB2ZxqtEHvBFoiSYEojW51AETnfPIWpdvDsBn+WAC7QrfBvQZnoyIkQ==
+ dependencies:
+ "@date-io/moment" "^2.16.1"
+
+"@date-io/jalaali@^2.16.1":
+ version "2.16.1"
+ resolved "https://registry.npmjs.org/@date-io/jalaali/-/jalaali-2.16.1.tgz#c53323fc429f8fe6ab205d36c003d6de071f17e8"
+ integrity sha512-GLw87G/WJ1DNrQHW8p/LqkqAqTUSqBSRin0H1pRPwCccB5Fh7GT64sadjzEvjW56lPJ0aq2vp5yI2eIjZajfrw==
+ dependencies:
+ "@date-io/moment" "^2.16.1"
+
+"@date-io/luxon@^2.16.1":
+ version "2.16.1"
+ resolved "https://registry.npmjs.org/@date-io/luxon/-/luxon-2.16.1.tgz#b08786614cb58831c729a15807753011e4acb966"
+ integrity sha512-aeYp5K9PSHV28946pC+9UKUi/xMMYoaGelrpDibZSgHu2VWHXrr7zWLEr+pMPThSs5vt8Ei365PO+84pCm37WQ==
+ dependencies:
+ "@date-io/core" "^2.16.0"
+
+"@date-io/moment@^2.16.1":
+ version "2.16.1"
+ resolved "https://registry.npmjs.org/@date-io/moment/-/moment-2.16.1.tgz#ec6e0daa486871e0e6412036c6f806842a0eeed4"
+ integrity sha512-JkxldQxUqZBfZtsaCcCMkm/dmytdyq5pS1RxshCQ4fHhsvP5A7gSqPD22QbVXMcJydi3d3v1Y8BQdUKEuGACZQ==
+ dependencies:
+ "@date-io/core" "^2.16.0"
+
"@emotion/babel-plugin@^11.10.0":
version "11.10.2"
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.2.tgz#879db80ba622b3f6076917a1e6f648b1c7d008c7"
@@ -1077,6 +1138,37 @@
prop-types "^15.8.1"
react-is "^18.2.0"
+"@mui/utils@^5.11.7":
+ version "5.11.12"
+ resolved "https://registry.npmjs.org/@mui/utils/-/utils-5.11.12.tgz#627f491c0e7267398590af5e6cb14b306170d914"
+ integrity sha512-5vH9B/v8pzkpEPO2HvGM54ToXV6cFdAn8UrvdN8TMEEwpn/ycW0jLiyBcgUlPsQ+xha7hqXCPQYHaYFDIcwaiw==
+ dependencies:
+ "@babel/runtime" "^7.21.0"
+ "@types/prop-types" "^15.7.5"
+ "@types/react-is" "^16.7.1 || ^17.0.0"
+ prop-types "^15.8.1"
+ react-is "^18.2.0"
+
+"@mui/x-date-pickers@^6.0.1":
+ version "6.0.1"
+ resolved "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.0.1.tgz#855f71b0661602c105dfd75bd0d38cd8c40d59f5"
+ integrity sha512-eZ3uTWp2uA08h4IULoqI7rkP16ltzZH0kSdZdvLCvaa4ix1ZP5zGcvt8CdtxM+NFWjRS7fGWPOgOoScGw9lKLQ==
+ dependencies:
+ "@babel/runtime" "^7.20.13"
+ "@date-io/core" "^2.16.0"
+ "@date-io/date-fns" "^2.16.0"
+ "@date-io/date-fns-jalali" "^2.16.0"
+ "@date-io/dayjs" "^2.16.0"
+ "@date-io/hijri" "^2.16.1"
+ "@date-io/jalaali" "^2.16.1"
+ "@date-io/luxon" "^2.16.1"
+ "@date-io/moment" "^2.16.1"
+ "@mui/utils" "^5.11.7"
+ "@types/react-transition-group" "^4.4.5"
+ clsx "^1.2.1"
+ prop-types "^15.8.1"
+ react-transition-group "^4.4.5"
+
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@@ -4774,6 +4866,11 @@ regenerator-runtime@^0.13.10, regenerator-runtime@^0.13.4:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz#ed07b19616bcbec5da6274ebc75ae95634bfc2ee"
integrity sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==
+regenerator-runtime@^0.13.11:
+ version "0.13.11"
+ resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
+ integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
+
regexp.prototype.flags@^1.3.0, regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac"