diff --git a/backend/src/controllers/v2/apiKeyDataController.ts b/backend/src/controllers/v2/apiKeyDataController.ts deleted file mode 100644 index ee19b0e4c..000000000 --- a/backend/src/controllers/v2/apiKeyDataController.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Request, Response } from "express"; -import crypto from "crypto"; -import bcrypt from "bcrypt"; -import { APIKeyData } from "../../models"; -import { getSaltRounds } from "../../config"; - -/** - * Return API key data for user with id [req.user_id] - * @param req - * @param res - * @returns - */ -export const getAPIKeyData = async (req: Request, res: Response) => { - const apiKeyData = await APIKeyData.find({ - user: req.user._id, - }); - - return res.status(200).send({ - apiKeyData, - }); -}; - -/** - * Create new API key data for user with id [req.user._id] - * @param req - * @param res - */ -export const createAPIKeyData = async (req: Request, res: Response) => { - const { name, expiresIn } = req.body; - - const secret = crypto.randomBytes(16).toString("hex"); - const secretHash = await bcrypt.hash(secret, await getSaltRounds()); - - const expiresAt = new Date(); - expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn); - - let apiKeyData = await new APIKeyData({ - name, - lastUsed: new Date(), - expiresAt, - user: req.user._id, - secretHash, - }).save(); - - // return api key data without sensitive data - // FIX: fix this any - apiKeyData = (await APIKeyData.findById(apiKeyData._id)) as any; - - if (!apiKeyData) throw new Error("Failed to find API key data"); - - const apiKey = `ak.${apiKeyData._id.toString()}.${secret}`; - - return res.status(200).send({ - apiKey, - apiKeyData, - }); -}; - -/** - * Delete API key data with id [apiKeyDataId]. - * @param req - * @param res - * @returns - */ -export const deleteAPIKeyData = async (req: Request, res: Response) => { - const { apiKeyDataId } = req.params; - const apiKeyData = await APIKeyData.findByIdAndDelete(apiKeyDataId); - - return res.status(200).send({ - apiKeyData, - }); -}; diff --git a/backend/src/controllers/v2/index.ts b/backend/src/controllers/v2/index.ts index 5496097db..a5955fef4 100644 --- a/backend/src/controllers/v2/index.ts +++ b/backend/src/controllers/v2/index.ts @@ -4,7 +4,6 @@ import * as usersController from "./usersController"; import * as organizationsController from "./organizationsController"; import * as workspaceController from "./workspaceController"; import * as serviceTokenDataController from "./serviceTokenDataController"; -import * as apiKeyDataController from "./apiKeyDataController"; import * as secretController from "./secretController"; import * as secretsController from "./secretsController"; import * as serviceAccountsController from "./serviceAccountsController"; @@ -18,7 +17,6 @@ export { organizationsController, workspaceController, serviceTokenDataController, - apiKeyDataController, secretController, secretsController, serviceAccountsController, diff --git a/backend/src/controllers/v2/tagController.ts b/backend/src/controllers/v2/tagController.ts index eea5ce275..0d945c3e5 100644 --- a/backend/src/controllers/v2/tagController.ts +++ b/backend/src/controllers/v2/tagController.ts @@ -15,7 +15,7 @@ export const createWorkspaceTag = async (req: Request, res: Response) => { user: new Types.ObjectId(req.user._id), }; - const createdTag = await new Tag(tagToCreate); + const createdTag = await new Tag(tagToCreate).save(); res.json(createdTag); }; @@ -48,7 +48,11 @@ export const deleteWorkspaceTag = async (req: Request, res: Response) => { export const getWorkspaceTags = async (req: Request, res: Response) => { const { workspaceId } = req.params; - const workspaceTags = await Tag.find({ workspace: workspaceId }); + + const workspaceTags = await Tag.find({ + workspace: new Types.ObjectId(workspaceId) + }); + return res.json({ workspaceTags }); diff --git a/backend/src/controllers/v2/usersController.ts b/backend/src/controllers/v2/usersController.ts index 2b4784b29..19d88d56d 100644 --- a/backend/src/controllers/v2/usersController.ts +++ b/backend/src/controllers/v2/usersController.ts @@ -1,8 +1,14 @@ import { Request, Response } from "express"; +import { Types } from "mongoose"; +import crypto from "crypto"; +import bcrypt from "bcrypt"; import { MembershipOrg, User, + APIKeyData, + TokenVersion } from "../../models"; +import { getSaltRounds } from "../../config"; /** * Return the current user. @@ -117,3 +123,106 @@ export const getMyOrganizations = async (req: Request, res: Response) => { organizations, }); } + +/** + * Return API keys belonging to current user. + * @param req + * @param res + * @returns + */ +export const getMyAPIKeys = async (req: Request, res: Response) => { + const apiKeyData = await APIKeyData.find({ + user: req.user._id, + }); + + return res.status(200).send(apiKeyData); +} + +/** + * Create new API key for current user. + * @param req + * @param res + * @returns + */ +export const createAPIKey = async (req: Request, res: Response) => { + const { name, expiresIn } = req.body; + + const secret = crypto.randomBytes(16).toString("hex"); + const secretHash = await bcrypt.hash(secret, await getSaltRounds()); + + const expiresAt = new Date(); + expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn); + + let apiKeyData = await new APIKeyData({ + name, + lastUsed: new Date(), + expiresAt, + user: req.user._id, + secretHash, + }).save(); + + // return api key data without sensitive data + apiKeyData = (await APIKeyData.findById(apiKeyData._id)) as any; + + if (!apiKeyData) throw new Error("Failed to find API key data"); + + const apiKey = `ak.${apiKeyData._id.toString()}.${secret}`; + + return res.status(200).send({ + apiKey, + apiKeyData, + }); +} + +/** + * Delete API key with id [apiKeyDataId] belonging to current user + * @param req + * @param res + */ +export const deleteAPIKey = async (req: Request, res: Response) => { + const { apiKeyDataId } = req.params; + + const apiKeyData = await APIKeyData.findOneAndDelete({ + _id: new Types.ObjectId(apiKeyDataId), + user: req.user._id + }); + + return res.status(200).send({ + apiKeyData + }); +} + +/** + * Return active sessions (TokenVersion) belonging to user + * @param req + * @param res + * @returns + */ +export const getMySessions = async (req: Request, res: Response) => { + const tokenVersions = await TokenVersion.find({ + user: req.user._id + }); + + return res.status(200).send(tokenVersions); +} + +/** + * Revoke all active sessions belong to user + * @param req + * @param res + * @returns + */ +export const deleteMySessions = async (req: Request, res: Response) => { + await TokenVersion.updateMany({ + user: req.user._id, + }, { + $inc: { + refreshVersion: 1, + accessVersion: 1, + }, + }); + + return res.status(200).send({ + message: "Successfully revoked all sessions" + }); +} \ No newline at end of file diff --git a/backend/src/ee/controllers/v1/secretController.ts b/backend/src/ee/controllers/v1/secretController.ts index 9ba3c77e3..2dcc3c2c7 100644 --- a/backend/src/ee/controllers/v1/secretController.ts +++ b/backend/src/ee/controllers/v1/secretController.ts @@ -54,23 +54,20 @@ export const getSecretVersions = async (req: Request, res: Response) => { } } */ - const { secretId, workspaceId, environment, folderId } = req.params; + const { secretId } = req.params; const offset: number = parseInt(req.query.offset as string); const limit: number = parseInt(req.query.limit as string); const secretVersions = await SecretVersion.find({ - secret: secretId, - workspace: workspaceId, - environment, - folder: folderId, + secret: secretId }) .sort({ createdAt: -1 }) .skip(offset) .limit(limit); return res.status(200).send({ - secretVersions, + secretVersions }); }; @@ -135,7 +132,7 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => { // validate secret version const oldSecretVersion = await SecretVersion.findOne({ secret: secretId, - version, + version }).select("+secretBlindIndex"); if (!oldSecretVersion) throw new Error("Failed to find secret version"); @@ -154,7 +151,7 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => { secretValueTag, algorithm, folder, - keyEncoding, + keyEncoding } = oldSecretVersion; // update secret @@ -162,7 +159,7 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => { secretId, { $inc: { - version: 1, + version: 1 }, workspace, type, @@ -177,10 +174,10 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => { secretValueTag, folderId: folder, algorithm, - keyEncoding, + keyEncoding }, { - new: true, + new: true } ); @@ -204,17 +201,17 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => { secretValueTag, folder, algorithm, - keyEncoding, + keyEncoding }).save(); // take secret snapshot await EESecretService.takeSecretSnapshot({ workspaceId: secret.workspace, environment, - folderId: folder, + folderId: folder }); return res.status(200).send({ - secret, + secret }); }; diff --git a/backend/src/index.ts b/backend/src/index.ts index 826d5ca9c..2dfa4c5dd 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -51,7 +51,6 @@ import { secrets as v2SecretsRouter, serviceTokenData as v2ServiceTokenDataRouter, serviceAccounts as v2ServiceAccountsRouter, - apiKeyData as v2APIKeyDataRouter, environment as v2EnvironmentRouter, tags as v2TagsRouter, } from "./routes/v2"; @@ -138,7 +137,6 @@ const main = async () => { app.use("/api/v2/secrets", v2SecretsRouter); // note: in the process of moving to v3/secrets app.use("/api/v2/service-token", v2ServiceTokenDataRouter); app.use("/api/v2/service-accounts", v2ServiceAccountsRouter); // new - app.use("/api/v2/api-key", v2APIKeyDataRouter); // v3 routes (experimental) app.use("/api/v3/auth", v3AuthRouter); diff --git a/backend/src/routes/v2/apiKeyData.ts b/backend/src/routes/v2/apiKeyData.ts deleted file mode 100644 index eae8d7ddd..000000000 --- a/backend/src/routes/v2/apiKeyData.ts +++ /dev/null @@ -1,42 +0,0 @@ -import express from "express"; -const router = express.Router(); -import { body, param } from "express-validator"; -import { - requireAuth, - validateRequest, -} from "../../middleware"; -import { apiKeyDataController } from "../../controllers/v2"; -import { - AUTH_MODE_JWT, -} from "../../variables"; - -router.get( - "/", - requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], - }), - apiKeyDataController.getAPIKeyData -); - -router.post( - "/", - requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], - }), - body("name").exists().trim(), - body("expiresIn"), // measured in ms - validateRequest, - apiKeyDataController.createAPIKeyData -); - -router.delete( - "/:apiKeyDataId", - requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], - }), - param("apiKeyDataId").exists().trim(), - validateRequest, - apiKeyDataController.deleteAPIKeyData -); - -export default router; \ No newline at end of file diff --git a/backend/src/routes/v2/index.ts b/backend/src/routes/v2/index.ts index fc7353729..807760268 100644 --- a/backend/src/routes/v2/index.ts +++ b/backend/src/routes/v2/index.ts @@ -7,7 +7,6 @@ import secret from "./secret"; // deprecated import secrets from "./secrets"; import serviceTokenData from "./serviceTokenData"; import serviceAccounts from "./serviceAccounts"; -import apiKeyData from "./apiKeyData"; import environment from "./environment" import tags from "./tags" @@ -21,7 +20,6 @@ export { secrets, serviceTokenData, serviceAccounts, - apiKeyData, environment, tags, } \ No newline at end of file diff --git a/backend/src/routes/v2/users.ts b/backend/src/routes/v2/users.ts index 970824eaa..f5a1401dc 100644 --- a/backend/src/routes/v2/users.ts +++ b/backend/src/routes/v2/users.ts @@ -4,7 +4,7 @@ import { requireAuth, validateRequest, } from "../../middleware"; -import { body } from "express-validator"; +import { body, param } from "express-validator"; import { usersController } from "../../controllers/v2"; import { AUTH_MODE_API_KEY, @@ -37,4 +37,49 @@ router.get( usersController.getMyOrganizations ); +router.get( + "/me/api-keys", + requireAuth({ + acceptedAuthModes: [AUTH_MODE_JWT], + }), + usersController.getMyAPIKeys +); + +router.post( + "/me/api-keys", + requireAuth({ + acceptedAuthModes: [AUTH_MODE_JWT], + }), + body("name").exists().isString().trim(), + body("expiresIn").isNumeric(), + validateRequest, + usersController.createAPIKey +); + +router.delete( + "/me/api-keys/:apiKeyDataId", + requireAuth({ + acceptedAuthModes: [AUTH_MODE_JWT], + }), + param("apiKeyDataId").exists().trim(), + validateRequest, + usersController.deleteAPIKey +); + +router.get( // new + "/me/sessions", + requireAuth({ + acceptedAuthModes: [AUTH_MODE_JWT], + }), + usersController.getMySessions +); + +router.delete( // new + "/me/sessions", + requireAuth({ + acceptedAuthModes: [AUTH_MODE_JWT], + }), + usersController.deleteMySessions +); + export default router; \ No newline at end of file diff --git a/docs/integrations/platforms/kubernetes.mdx b/docs/integrations/platforms/kubernetes.mdx index 58a836305..d797293a0 100644 --- a/docs/integrations/platforms/kubernetes.mdx +++ b/docs/integrations/platforms/kubernetes.mdx @@ -50,6 +50,7 @@ metadata: spec: # The host that should be used to pull secrets from. If left empty, the value specified in Global configuration will be used hostAPI: https://app.infisical.com/api + resyncInterval: 60 # <-- the time in seconds between secret re-sync. Faster re-syncs will require higher rate limits authentication: serviceToken: serviceTokenSecretReference: @@ -79,6 +80,11 @@ spec: + +This property defines the time in seconds between each secret re-sync from Infisical. Shorter time between re-syncs will require higher rate limits only available on paid plans. +Default re-sync interval is every 1 minute. + + The `authentication` property tells the operator where it should look to find credentials needed to fetch secrets from Infisical. diff --git a/frontend/src/components/basic/dialog/AddApiKeyDialog.tsx b/frontend/src/components/basic/dialog/AddApiKeyDialog.tsx deleted file mode 100644 index eeb0ca6b1..000000000 --- a/frontend/src/components/basic/dialog/AddApiKeyDialog.tsx +++ /dev/null @@ -1,208 +0,0 @@ -import { Fragment, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Dialog, Transition } from "@headlessui/react"; - -import addAPIKey from "@app/pages/api/apiKey/addAPIKey"; - -import Button from "../buttons/Button"; -import InputField from "../InputField"; -import ListBox from "../Listbox"; - -const expiryMapping = { - "1 day": 86400, - "7 days": 604800, - "1 month": 2592000, - "6 months": 15552000, - "12 months": 31104000 -}; - -type Props = { - isOpen: boolean; - closeModal: () => void; - // TODO: These never and any will be filled by single folder that contains types and hooks about the API - apiKeys: any[]; - setApiKeys: (arg: any[]) => void; -}; - -// TODO: convert to TS -const AddApiKeyDialog = ({ isOpen, closeModal, apiKeys, setApiKeys }: Props) => { - const [apiKey, setApiKey] = useState(""); - const [apiKeyName, setApiKeyName] = useState(""); - const [apiKeyExpiresIn, setApiKeyExpiresIn] = useState("1 day"); - const [apiKeyCopied, setApiKeyCopied] = useState(false); - const { t } = useTranslation(); - - const generateAPIKey = async () => { - const newApiKey = await addAPIKey({ - name: apiKeyName, - expiresIn: expiryMapping[apiKeyExpiresIn as keyof typeof expiryMapping] - }); - - setApiKeys([...apiKeys, newApiKey.apiKeyData]); - setApiKey(newApiKey.apiKey); - }; - - function copyToClipboard() { - // Get the text field - const copyText = document.getElementById("apiKey") as HTMLInputElement; - - // Select the text field - copyText.select(); - copyText.setSelectionRange(0, 99999); // For mobile devices - - // Copy the text inside the text field - navigator.clipboard.writeText(copyText.value); - - setApiKeyCopied(true); - setTimeout(() => setApiKeyCopied(false), 2000); - // Alert the copied text - // alert("Copied the text: " + copyText.value); - } - - const closeAddApiKeyModal = () => { - closeModal(); - setApiKeyName(""); - setApiKey(""); - }; - - return ( -
- - - -
- - -
-
- - {apiKey === "" ? ( - - - {t("section.api-key.add-dialog.title")} - -
-
-

- {t("section.api-key.add-dialog.description")} -

-
-
-
- -
-
- -
-
-
-
-
-
- ) : ( - - - {t("section.api-key.add-dialog.copy-service-token")} - -
-
-

- {t("section.api-key.add-dialog.copy-service-token-description")} -

-
-
-
-
- -
- {apiKey} -
-
- - - {t("common.click-to-copy")} - -
-
-
-
-
-
- )} -
-
-
-
-
-
- ); -}; - -export default AddApiKeyDialog; diff --git a/frontend/src/components/basic/table/ApiKeyTable.tsx b/frontend/src/components/basic/table/ApiKeyTable.tsx deleted file mode 100644 index 1522df00d..000000000 --- a/frontend/src/components/basic/table/ApiKeyTable.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { faX } from "@fortawesome/free-solid-svg-icons"; - -import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; - -import deleteAPIKey from "../../../pages/api/apiKey/deleteAPIKey"; -import guidGenerator from "../../utilities/randomId"; -import Button from "../buttons/Button"; - -interface TokenProps { - _id: string; - name: string; - expiresAt: string; -} - -interface ServiceTokensProps { - data: TokenProps[]; - setApiKeys: (value: TokenProps[]) => void; -} - -/** - * This is the component that we utilize for the api key table - * @param {object} obj - * @param {any[]} obj.data - current state of the api key table - * @param {function} obj.setApiKeys - updating the state of the api key table - * @returns - */ -const ApiKeyTable = ({ data, setApiKeys }: ServiceTokensProps) => { - const { createNotification } = useNotificationContext(); - return ( -
-
- - - - - - - - - {data?.length > 0 ? ( - data?.map((row) => ( - - - - - - )) - ) : ( - - - - )} - -
API KEY NAMEVALID UNTIL -
- {row.name} - - {new Date(row.expiresAt).toUTCString()} - -
-
-
- No API keys yet -
-
- ); -}; - -export default ApiKeyTable; diff --git a/frontend/src/components/basic/table/ProjectUsersTable.tsx b/frontend/src/components/basic/table/ProjectUsersTable.tsx index a94d2b313..022eda020 100644 --- a/frontend/src/components/basic/table/ProjectUsersTable.tsx +++ b/frontend/src/components/basic/table/ProjectUsersTable.tsx @@ -1,12 +1,11 @@ import { useEffect, useState } from "react"; import { useRouter } from "next/router"; import { faEye, faEyeSlash, faPenToSquare, faPlus, faX } from "@fortawesome/free-solid-svg-icons"; -import { plans } from "public/data/frequentConstants"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { Select, SelectItem } from "@app/components/v2"; +import { useSubscription } from "@app/context"; import updateUserProjectPermission from "@app/ee/api/memberships/UpdateUserProjectPermission"; -import getOrganizationSubscriptions from "@app/pages/api/organization/GetOrgSubscription"; import changeUserRoleInWorkspace from "@app/pages/api/workspace/changeUserRoleInWorkspace"; import deleteUserFromWorkspace from "@app/pages/api/workspace/deleteUserFromWorkspace"; import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey"; @@ -40,13 +39,12 @@ type EnvironmentProps = { * @returns */ const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoading }: Props) => { + const { subscription } = useSubscription(); const [roleSelected, setRoleSelected] = useState( Array(userData?.length).fill(userData.map((user) => user.role)) ); - const host = window.location.origin; const router = useRouter(); const [myRole, setMyRole] = useState("member"); - const [currentPlan, setCurrentPlan] = useState(""); const [workspaceEnvs, setWorkspaceEnvs] = useState([]); const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false); const { createNotification } = useNotificationContext(); @@ -128,7 +126,7 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoa denials = []; } - if (currentPlan !== plans.professional && host === "https://app.infisical.com" && workspaceId !== "63ea8121b6e2b0543ba79616") { + if (subscription?.rbac === false) { setIsUpgradeModalOpen(true); } else { const allDenials = userData[index].deniedPermissions @@ -167,14 +165,6 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoa (async () => { const result = await getProjectInfo({ projectId: workspaceId }); setWorkspaceEnvs(result.environments); - - const orgId = localStorage.getItem("orgData.id") as string; - const subscriptions = await getOrganizationSubscriptions({ - orgId - }); - if (subscriptions) { - setCurrentPlan(subscriptions.data[0].plan.product); - } })(); }, [userData, myUser]); @@ -208,11 +198,13 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoa return (
- + {subscription && ( + + )} diff --git a/frontend/src/components/billing/Plan.tsx b/frontend/src/components/billing/Plan.tsx deleted file mode 100644 index 5b0f2256e..000000000 --- a/frontend/src/components/billing/Plan.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React from "react"; - -import StripeRedirect from "@app/pages/api/organization/StripeRedirect"; - -import { tempLocalStorage } from "../utilities/checks/tempLocalStorage"; - -interface Props { - plan: { - name: string; - price: string; - priceExplanation?: string; - text: string; - subtext?: string; - buttonTextMain: string; - buttonTextSecondary: string; - current: boolean; - }; -} - -export default function Plan({ plan }: Props) { - return ( -
-
-
-

{plan.name}

-
-
-

{plan.price}

-

{plan.priceExplanation}

-
-

{plan.text}

-

{plan.subtext}

-
-
- {plan.current === false ? ( - <> - {plan.buttonTextMain === "Schedule a Demo" ? ( - -
- {plan.buttonTextMain} -
-
- ) : ( -
- -
- )} - -
- {plan.buttonTextSecondary} -
-
- - ) : ( -
-

CURRENT PLAN

-
- )} -
-
- ); -} diff --git a/frontend/src/components/navigation/NavHeader.tsx b/frontend/src/components/navigation/NavHeader.tsx index ee7274fdc..4bbdce1bc 100644 --- a/frontend/src/components/navigation/NavHeader.tsx +++ b/frontend/src/components/navigation/NavHeader.tsx @@ -57,7 +57,7 @@ export default function NavHeader({ ); return ( -
+
{currentOrg?.name?.charAt(0)}
diff --git a/frontend/src/components/utilities/attemptChangePassword.ts b/frontend/src/components/utilities/attemptChangePassword.ts new file mode 100644 index 000000000..5b42e0190 --- /dev/null +++ b/frontend/src/components/utilities/attemptChangePassword.ts @@ -0,0 +1,108 @@ +/* eslint-disable new-cap */ +import crypto from "crypto"; + +import jsrp from "jsrp"; + +import changePassword2 from "@app/pages/api/auth/ChangePassword2"; +import SRP1 from "@app/pages/api/auth/SRP1"; + +import Aes256Gcm from "./cryptography/aes-256-gcm"; +import { deriveArgonKey } from "./cryptography/crypto"; +import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage"; + +const clientOldPassword = new jsrp.client(); +const clientNewPassword = new jsrp.client(); + +type Params = { + email: string; + currentPassword: string; + newPassword: string; +} + +const attemptChangePassword = ({ email, currentPassword, newPassword }: Params): Promise => { + return new Promise((resolve, reject) => { + clientOldPassword.init({ username: email, password: currentPassword }, async () => { + let serverPublicKey; let salt; + + try { + const clientPublicKey = clientOldPassword.getPublicKey(); + + const res = await SRP1({ clientPublicKey }); + + serverPublicKey = res.serverPublicKey; + salt = res.salt; + + clientOldPassword.setSalt(salt); + clientOldPassword.setServerPublicKey(serverPublicKey); + + const clientProof = clientOldPassword.getProof(); + + clientNewPassword.init({ username: email, password: newPassword }, async () => { + clientNewPassword.createVerifier(async (err, result) => { + try { + const derivedKey = await deriveArgonKey({ + password: newPassword, + salt: result.salt, + mem: 65536, + time: 3, + parallelism: 1, + hashLen: 32 + }); + + if (!derivedKey) throw new Error("Failed to derive key from password"); + + const key = crypto.randomBytes(32); + + const { + ciphertext: encryptedPrivateKey, + iv: encryptedPrivateKeyIV, + tag: encryptedPrivateKeyTag + } = Aes256Gcm.encrypt({ + text: localStorage.getItem("PRIVATE_KEY") as string, + secret: key + }); + + const { + ciphertext: protectedKey, + iv: protectedKeyIV, + tag: protectedKeyTag + } = Aes256Gcm.encrypt({ + text: key.toString("hex"), + secret: Buffer.from(derivedKey.hash) + }); + + await changePassword2({ + clientProof, + protectedKey, + protectedKeyIV, + protectedKeyTag, + encryptedPrivateKey, + encryptedPrivateKeyIV, + encryptedPrivateKeyTag, + salt: result.salt, + verifier: result.verifier + }); + + saveTokenToLocalStorage({ + encryptedPrivateKey, + iv: encryptedPrivateKeyIV, + tag: encryptedPrivateKeyTag + }); + + resolve(); + } catch (err2) { + console.error(err2); + reject(err2); + } + + }); + }); + } catch (err) { + console.error(err); + reject(err); + } + }); + }); +} + +export default attemptChangePassword; \ No newline at end of file diff --git a/frontend/src/hooks/api/auth/index.tsx b/frontend/src/hooks/api/auth/index.tsx index 2e66fe36f..a4f967888 100644 --- a/frontend/src/hooks/api/auth/index.tsx +++ b/frontend/src/hooks/api/auth/index.tsx @@ -1,7 +1,6 @@ export { useGetAuthToken, useGetCommonPasswords, - useRevokeAllSessions, - useSendMfaToken, + useSendMfaToken, useVerifyMfaToken -} from "./queries"; +} from "./queries" diff --git a/frontend/src/hooks/api/auth/queries.tsx b/frontend/src/hooks/api/auth/queries.tsx index d98b5e721..57878d2b3 100644 --- a/frontend/src/hooks/api/auth/queries.tsx +++ b/frontend/src/hooks/api/auth/queries.tsx @@ -51,15 +51,6 @@ export const useGetAuthToken = () => retry: 0 }); -export const useRevokeAllSessions = () => { - return useMutation({ - mutationFn: async () => { - const { data } = await apiRequest.delete("/api/v1/auth/sessions"); - return data; - } - }); -} - const fetchCommonPasswords = async () => { const { data } = await apiRequest.get("/api/v1/auth/common-passwords"); return data || []; diff --git a/frontend/src/hooks/api/secrets/queries.tsx b/frontend/src/hooks/api/secrets/queries.tsx index cc1d40aba..397f68aa7 100644 --- a/frontend/src/hooks/api/secrets/queries.tsx +++ b/frontend/src/hooks/api/secrets/queries.tsx @@ -81,76 +81,79 @@ export const useGetProjectSecrets = ({ enabled: Boolean(decryptFileKey && workspaceId && env) && !isPaused, queryKey: secretKeys.getProjectSecret(workspaceId, env, folderId), queryFn: () => fetchProjectEncryptedSecrets(workspaceId, env, folderId), - select: useCallback((data: EncryptedSecret[]) => { - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; - const latestKey = decryptFileKey; - const key = decryptAssymmetric({ - ciphertext: latestKey.encryptedKey, - nonce: latestKey.nonce, - publicKey: latestKey.sender.publicKey, - privateKey: PRIVATE_KEY - }); - - const sharedSecrets: DecryptedSecret[] = []; - const personalSecrets: Record = {}; - // this used for add-only mode in dashboard - // type won't be there thus only one key is shown - const duplicateSecretKey: Record = {}; - data.forEach((encSecret: EncryptedSecret) => { - const secretKey = decryptSymmetric({ - ciphertext: encSecret.secretKeyCiphertext, - iv: encSecret.secretKeyIV, - tag: encSecret.secretKeyTag, - key + select: useCallback( + (data: EncryptedSecret[]) => { + const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; + const latestKey = decryptFileKey; + const key = decryptAssymmetric({ + ciphertext: latestKey.encryptedKey, + nonce: latestKey.nonce, + publicKey: latestKey.sender.publicKey, + privateKey: PRIVATE_KEY }); - const secretValue = decryptSymmetric({ - ciphertext: encSecret.secretValueCiphertext, - iv: encSecret.secretValueIV, - tag: encSecret.secretValueTag, - key - }); + const sharedSecrets: DecryptedSecret[] = []; + const personalSecrets: Record = {}; + // this used for add-only mode in dashboard + // type won't be there thus only one key is shown + const duplicateSecretKey: Record = {}; + data.forEach((encSecret: EncryptedSecret) => { + const secretKey = decryptSymmetric({ + ciphertext: encSecret.secretKeyCiphertext, + iv: encSecret.secretKeyIV, + tag: encSecret.secretKeyTag, + key + }); - const secretComment = decryptSymmetric({ - ciphertext: encSecret.secretCommentCiphertext, - iv: encSecret.secretCommentIV, - tag: encSecret.secretCommentTag, - key - }); + const secretValue = decryptSymmetric({ + ciphertext: encSecret.secretValueCiphertext, + iv: encSecret.secretValueIV, + tag: encSecret.secretValueTag, + key + }); - const decryptedSecret = { - _id: encSecret._id, - env: encSecret.environment, - key: secretKey, - value: secretValue, - tags: encSecret.tags, - comment: secretComment, - createdAt: encSecret.createdAt, - updatedAt: encSecret.updatedAt - }; + const secretComment = decryptSymmetric({ + ciphertext: encSecret.secretCommentCiphertext, + iv: encSecret.secretCommentIV, + tag: encSecret.secretCommentTag, + key + }); - if (encSecret.type === "personal") { - personalSecrets[`${decryptedSecret.key}-${decryptedSecret.env}`] = { - id: encSecret._id, - value: secretValue + const decryptedSecret = { + _id: encSecret._id, + env: encSecret.environment, + key: secretKey, + value: secretValue, + tags: encSecret.tags, + comment: secretComment, + createdAt: encSecret.createdAt, + updatedAt: encSecret.updatedAt }; - } else { - if (!duplicateSecretKey?.[`${decryptedSecret.key}-${decryptedSecret.env}`]) { - sharedSecrets.push(decryptedSecret); + + if (encSecret.type === "personal") { + personalSecrets[`${decryptedSecret.key}-${decryptedSecret.env}`] = { + id: encSecret._id, + value: secretValue + }; + } else { + if (!duplicateSecretKey?.[`${decryptedSecret.key}-${decryptedSecret.env}`]) { + sharedSecrets.push(decryptedSecret); + } + duplicateSecretKey[`${decryptedSecret.key}-${decryptedSecret.env}`] = true; } - duplicateSecretKey[`${decryptedSecret.key}-${decryptedSecret.env}`] = true; - } - }); - sharedSecrets.forEach((val) => { - const dupKey = `${val.key}-${val.env}`; - if (personalSecrets?.[dupKey]) { - val.idOverride = personalSecrets[dupKey].id; - val.valueOverride = personalSecrets[dupKey].value; - val.overrideAction = "modified"; - } - }); - return { secrets: sharedSecrets }; - }, [decryptFileKey]) + }); + sharedSecrets.forEach((val) => { + const dupKey = `${val.key}-${val.env}`; + if (personalSecrets?.[dupKey]) { + val.idOverride = personalSecrets[dupKey].id; + val.valueOverride = personalSecrets[dupKey].value; + val.overrideAction = "modified"; + } + }); + return { secrets: sharedSecrets }; + }, + [decryptFileKey] + ) }); export const useGetProjectSecretsByKey = ({ @@ -167,82 +170,85 @@ export const useGetProjectSecretsByKey = ({ // right now secretpath is passed as folderid as only this is used in overview queryKey: secretKeys.getProjectSecret(workspaceId, env, secretPath), queryFn: () => fetchProjectEncryptedSecrets(workspaceId, env, folderId, secretPath), - select: useCallback((data: EncryptedSecret[]) => { - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; - const latestKey = decryptFileKey; - const key = decryptAssymmetric({ - ciphertext: latestKey.encryptedKey, - nonce: latestKey.nonce, - publicKey: latestKey.sender.publicKey, - privateKey: PRIVATE_KEY - }); - - const sharedSecrets: Record = {}; - const personalSecrets: Record = {}; - // this used for add-only mode in dashboard - // type won't be there thus only one key is shown - const duplicateSecretKey: Record = {}; - const uniqSecKeys: Record = {}; - data.forEach((encSecret: EncryptedSecret) => { - const secretKey = decryptSymmetric({ - ciphertext: encSecret.secretKeyCiphertext, - iv: encSecret.secretKeyIV, - tag: encSecret.secretKeyTag, - key - }); - if (!uniqSecKeys?.[secretKey]) uniqSecKeys[secretKey] = true; - - const secretValue = decryptSymmetric({ - ciphertext: encSecret.secretValueCiphertext, - iv: encSecret.secretValueIV, - tag: encSecret.secretValueTag, - key + select: useCallback( + (data: EncryptedSecret[]) => { + const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; + const latestKey = decryptFileKey; + const key = decryptAssymmetric({ + ciphertext: latestKey.encryptedKey, + nonce: latestKey.nonce, + publicKey: latestKey.sender.publicKey, + privateKey: PRIVATE_KEY }); - const secretComment = decryptSymmetric({ - ciphertext: encSecret.secretCommentCiphertext, - iv: encSecret.secretCommentIV, - tag: encSecret.secretCommentTag, - key - }); + const sharedSecrets: Record = {}; + const personalSecrets: Record = {}; + // this used for add-only mode in dashboard + // type won't be there thus only one key is shown + const duplicateSecretKey: Record = {}; + const uniqSecKeys: Record = {}; + data.forEach((encSecret: EncryptedSecret) => { + const secretKey = decryptSymmetric({ + ciphertext: encSecret.secretKeyCiphertext, + iv: encSecret.secretKeyIV, + tag: encSecret.secretKeyTag, + key + }); + if (!uniqSecKeys?.[secretKey]) uniqSecKeys[secretKey] = true; - const decryptedSecret = { - _id: encSecret._id, - env: encSecret.environment, - key: secretKey, - value: secretValue, - tags: encSecret.tags, - comment: secretComment, - createdAt: encSecret.createdAt, - updatedAt: encSecret.updatedAt - }; + const secretValue = decryptSymmetric({ + ciphertext: encSecret.secretValueCiphertext, + iv: encSecret.secretValueIV, + tag: encSecret.secretValueTag, + key + }); - if (encSecret.type === "personal") { - personalSecrets[`${decryptedSecret.key}-${decryptedSecret.env}`] = { - id: encSecret._id, - value: secretValue + const secretComment = decryptSymmetric({ + ciphertext: encSecret.secretCommentCiphertext, + iv: encSecret.secretCommentIV, + tag: encSecret.secretCommentTag, + key + }); + + const decryptedSecret = { + _id: encSecret._id, + env: encSecret.environment, + key: secretKey, + value: secretValue, + tags: encSecret.tags, + comment: secretComment, + createdAt: encSecret.createdAt, + updatedAt: encSecret.updatedAt }; - } else { - if (!duplicateSecretKey?.[`${decryptedSecret.key}-${decryptedSecret.env}`]) { - if (!sharedSecrets?.[secretKey]) sharedSecrets[secretKey] = []; - sharedSecrets[secretKey].push(decryptedSecret); - } - duplicateSecretKey[`${decryptedSecret.key}-${decryptedSecret.env}`] = true; - } - }); - Object.keys(sharedSecrets).forEach((secName) => { - sharedSecrets[secName].forEach((val) => { - const dupKey = `${val.key}-${val.env}`; - if (personalSecrets?.[dupKey]) { - val.idOverride = personalSecrets[dupKey].id; - val.valueOverride = personalSecrets[dupKey].value; - val.overrideAction = "modified"; + + if (encSecret.type === "personal") { + personalSecrets[`${decryptedSecret.key}-${decryptedSecret.env}`] = { + id: encSecret._id, + value: secretValue + }; + } else { + if (!duplicateSecretKey?.[`${decryptedSecret.key}-${decryptedSecret.env}`]) { + if (!sharedSecrets?.[secretKey]) sharedSecrets[secretKey] = []; + sharedSecrets[secretKey].push(decryptedSecret); + } + duplicateSecretKey[`${decryptedSecret.key}-${decryptedSecret.env}`] = true; } }); - }); + Object.keys(sharedSecrets).forEach((secName) => { + sharedSecrets[secName].forEach((val) => { + const dupKey = `${val.key}-${val.env}`; + if (personalSecrets?.[dupKey]) { + val.idOverride = personalSecrets[dupKey].id; + val.valueOverride = personalSecrets[dupKey].value; + val.overrideAction = "modified"; + } + }); + }); - return { secrets: sharedSecrets, uniqueSecCount: Object.keys(uniqSecKeys).length }; - }, [decryptFileKey]) + return { secrets: sharedSecrets, uniqueSecCount: Object.keys(uniqSecKeys).length }; + }, + [decryptFileKey] + ) }); const fetchEncryptedSecretVersion = async (secretId: string, offset: number, limit: number) => { @@ -263,29 +269,32 @@ export const useGetSecretVersion = (dto: GetSecretVersionsDTO) => enabled: Boolean(dto.secretId && dto.decryptFileKey), queryKey: secretKeys.getSecretVersion(dto.secretId), queryFn: () => fetchEncryptedSecretVersion(dto.secretId, dto.offset, dto.limit), - select: useCallback((data: EncryptedSecretVersion[]) => { - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; - const latestKey = dto.decryptFileKey; - const key = decryptAssymmetric({ - ciphertext: latestKey.encryptedKey, - nonce: latestKey.nonce, - publicKey: latestKey.sender.publicKey, - privateKey: PRIVATE_KEY - }); + select: useCallback( + (data: EncryptedSecretVersion[]) => { + const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; + const latestKey = dto.decryptFileKey; + const key = decryptAssymmetric({ + ciphertext: latestKey.encryptedKey, + nonce: latestKey.nonce, + publicKey: latestKey.sender.publicKey, + privateKey: PRIVATE_KEY + }); - return data - .map((el) => ({ - createdAt: el.createdAt, - id: el._id, - value: decryptSymmetric({ - ciphertext: el.secretValueCiphertext, - iv: el.secretValueIV, - tag: el.secretValueTag, - key - }) - })) - .sort((a, b) => b.createdAt.localeCompare(a.createdAt)); - }, []) + return data + .map((el) => ({ + createdAt: el.createdAt, + id: el._id, + value: decryptSymmetric({ + ciphertext: el.secretValueCiphertext, + iv: el.secretValueIV, + tag: el.secretValueTag, + key + }) + })) + .sort((a, b) => b.createdAt.localeCompare(a.createdAt)); + }, + [dto.decryptFileKey] + ) }); export const useBatchSecretsOp = () => { diff --git a/frontend/src/hooks/api/users/index.tsx b/frontend/src/hooks/api/users/index.tsx index a997a3fad..e1e530443 100644 --- a/frontend/src/hooks/api/users/index.tsx +++ b/frontend/src/hooks/api/users/index.tsx @@ -2,11 +2,15 @@ export { fetchOrgUsers, useAddUserToOrg, useAddUserToWs, + useCreateAPIKey, + useDeleteAPIKey, useDeleteOrgMembership, + useGetMyAPIKeys, + useGetMySessions, useGetOrgUsers, useGetUser, useGetUserAction, useLogoutUser, useRegisterUserAction, - useUpdateOrgUserRole -} from "./queries"; + useRevokeMySessions, + useUpdateOrgUserRole} from "./queries"; diff --git a/frontend/src/hooks/api/users/queries.tsx b/frontend/src/hooks/api/users/queries.tsx index 9baf7b784..c1bcf43a2 100644 --- a/frontend/src/hooks/api/users/queries.tsx +++ b/frontend/src/hooks/api/users/queries.tsx @@ -12,16 +12,20 @@ import { AddUserToOrgDTO, AddUserToWsDTO, AddUserToWsRes, + APIKeyData, + CreateAPIKeyRes, DeletOrgMembershipDTO, OrgUser, + TokenVersion, UpdateOrgUserRoleDTO, - User -} from "./types"; + User} from "./types"; const userKeys = { getUser: ["user"] as const, userAction: ["user-action"] as const, - getOrgUsers: (orgId: string) => [{ orgId }, "user"] + getOrgUsers: (orgId: string) => [{ orgId }, "user"], + myAPIKeys: ["api-keys"] as const, + mySessions: ["sessions"] as const }; export const fetchUserDetails = async () => { @@ -167,3 +171,90 @@ export const useLogoutUser = () => localStorage.setItem("PRIVATE_KEY", ""); } }); + +export const useGetMyAPIKeys = () => { + return useQuery({ + queryKey: userKeys.myAPIKeys, + queryFn: async () => { + const { data } = await apiRequest.get( + "/api/v2/users/me/api-keys" + ); + return data; + }, + enabled: true + }); +} + +export const useCreateAPIKey = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ + name, + expiresIn + }: { + name: string; + expiresIn: number; + }) => { + const { data } = await apiRequest.post( + "/api/v2/users/me/api-keys", + { + name, + expiresIn + } + ); + + return data; + }, + onSuccess() { + queryClient.invalidateQueries(userKeys.myAPIKeys); + } + }); +} + +export const useDeleteAPIKey = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async (apiKeyDataId: string) => { + const { data } = await apiRequest.delete( + `/api/v2/users/me/api-keys/${apiKeyDataId}` + ); + + return data; + }, + onSuccess() { + queryClient.invalidateQueries(userKeys.myAPIKeys); + } + }); +} + +export const useGetMySessions = () => { + return useQuery({ + queryKey: userKeys.mySessions, + queryFn: async () => { + const { data } = await apiRequest.get( + "/api/v2/users/me/sessions" + ); + + return data; + }, + enabled: true + }); +} + +export const useRevokeMySessions = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async () => { + console.log("useRevokeAllSessions 1"); + const { data } = await apiRequest.delete( + "/api/v2/users/me/sessions" + ); + + console.log("useRevokeAllSessions 2: ", data); + return data; + }, + onSuccess() { + queryClient.invalidateQueries(userKeys.mySessions); + } + }); +} \ No newline at end of file diff --git a/frontend/src/hooks/api/users/types.ts b/frontend/src/hooks/api/users/types.ts index f7cb4dd22..e272e7bfd 100644 --- a/frontend/src/hooks/api/users/types.ts +++ b/frontend/src/hooks/api/users/types.ts @@ -61,3 +61,27 @@ export type AddUserToOrgDTO = { inviteeEmail: string; organizationId: string; }; + +export type CreateAPIKeyRes = { + apiKey: string; + apiKeyData: APIKeyData; +} + +export type APIKeyData = { + _id: string; + name: string; + user: string; + lastUsed: string; + createdAt: string; + expiresAt: string; +} + +export type TokenVersion = { + _id: string; + user: string; + userAgent: string; + ip: string; + lastUsed: string; + createdAt: string; + updatedAt: string; +} \ No newline at end of file diff --git a/frontend/src/hooks/index.ts b/frontend/src/hooks/index.ts index fc7acc575..a3ded034c 100644 --- a/frontend/src/hooks/index.ts +++ b/frontend/src/hooks/index.ts @@ -1,4 +1,5 @@ export { useLeaveConfirm } from "./useLeaveConfirm"; export { usePersistentState } from "./usePersistentState"; export { usePopUp } from "./usePopUp"; +export { useSyntaxHighlight } from "./useSyntaxHighlight"; export { useToggle } from "./useToggle"; diff --git a/frontend/src/hooks/useSyntaxHighlight.tsx b/frontend/src/hooks/useSyntaxHighlight.tsx new file mode 100644 index 000000000..325917c83 --- /dev/null +++ b/frontend/src/hooks/useSyntaxHighlight.tsx @@ -0,0 +1,45 @@ +import { useCallback } from "react"; +import { faCircle } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +const REGEX = /([$]{.*?})/g; + +export const useSyntaxHighlight = () => { + const syntaxHighlight = useCallback((text: string, isHidden?: boolean) => { + if (isHidden) { + return text + .split("") + .slice(0, 200) + .map((el, i) => + el === "\n" ? ( + el + ) : ( + + ) + ); + } + + // append a space on last new line this to show new line in ui for code component + const val = text.at(-1) === "\n" ? text.concat(" ") : text; + if (val?.length === 0) return EMPTY; + return val?.split(REGEX).map((word, i) => + word.match(REGEX) !== null ? ( + + ${ + {word.slice(2, word.length - 1)} + } + + ) : ( + + {word} + + ) + ); + }, []); + + return syntaxHighlight; +}; diff --git a/frontend/src/pages/activity/[id].tsx b/frontend/src/pages/activity/[id].tsx index 1f7b7b75a..91f918540 100644 --- a/frontend/src/pages/activity/[id].tsx +++ b/frontend/src/pages/activity/[id].tsx @@ -158,7 +158,9 @@ export default function Activity() { - +
+ +
{currentSidebarAction && ( )} diff --git a/frontend/src/pages/api/apiKey/addAPIKey.ts b/frontend/src/pages/api/apiKey/addAPIKey.ts deleted file mode 100644 index b4f339a16..000000000 --- a/frontend/src/pages/api/apiKey/addAPIKey.ts +++ /dev/null @@ -1,33 +0,0 @@ -import SecurityClient from "@app/components/utilities/SecurityClient"; - -interface Props { - name: string; - expiresIn: number; -} - -/** - * This route adds an API key for the user - * @param {object} obj - * @param {string} obj.name - name of the API key - * @param {string} obj.expiresIn - how soon the API key expires in ms - * @returns - */ -const addAPIKey = ({ name, expiresIn }: Props) => - SecurityClient.fetchCall("/api/v2/api-key/", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - name, - expiresIn - }) - }).then(async (res) => { - if (res && res.status === 200) { - return res.json(); - } - console.log("Failed to add API key"); - return undefined; - }); - -export default addAPIKey; diff --git a/frontend/src/pages/api/apiKey/deleteAPIKey.ts b/frontend/src/pages/api/apiKey/deleteAPIKey.ts deleted file mode 100644 index 8fcc9fcd8..000000000 --- a/frontend/src/pages/api/apiKey/deleteAPIKey.ts +++ /dev/null @@ -1,27 +0,0 @@ -import SecurityClient from "@app/components/utilities/SecurityClient"; - -interface Props { - apiKeyId: string; -} - -/** - * This route revokes the API key with id [apiKeyId] - * @param {object} obj - * @param {string} obj.apiKeyId - id of the API key to delete - * @returns - */ -const deleteAPIKey = ({ apiKeyId }: Props) => - SecurityClient.fetchCall(`/api/v2/api-key/${apiKeyId}`, { - method: "DELETE", - headers: { - "Content-Type": "application/json" - } - }).then(async (res) => { - if (res && res.status === 200) { - return res.json(); - } - console.log("Failed to delete API key"); - return undefined; - }); - -export default deleteAPIKey; diff --git a/frontend/src/pages/api/apiKey/getAPIKeys.ts b/frontend/src/pages/api/apiKey/getAPIKeys.ts deleted file mode 100644 index 0c858879c..000000000 --- a/frontend/src/pages/api/apiKey/getAPIKeys.ts +++ /dev/null @@ -1,22 +0,0 @@ -import SecurityClient from "@app/components/utilities/SecurityClient"; - -/** - * This route gets API keys for the user - * @param {*} param0 - * @returns - */ -const getAPIKeys = () => - SecurityClient.fetchCall("/api/v2/api-key", { - method: "GET", - headers: { - "Content-Type": "application/json" - } - }).then(async (res) => { - if (res && res.status === 200) { - return (await res.json()).apiKeyData; - } - console.log("Failed to get API keys"); - return undefined; - }); - -export default getAPIKeys; diff --git a/frontend/src/pages/settings/personal/[id].tsx b/frontend/src/pages/settings/personal/[id].tsx index f05918059..62c279200 100644 --- a/frontend/src/pages/settings/personal/[id].tsx +++ b/frontend/src/pages/settings/personal/[id].tsx @@ -1,322 +1,18 @@ -import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import Head from "next/head"; -import { useRouter } from "next/router"; -import { faBan,faCheck, faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import Button from "@app/components/basic/buttons/Button"; -import InputField from "@app/components/basic/InputField"; -import ListBox from "@app/components/basic/Listbox"; -import ApiKeyTable from "@app/components/basic/table/ApiKeyTable"; -import NavHeader from "@app/components/navigation/NavHeader"; -import checkPassword from "@app/components/utilities/checks/checkPassword"; -import changePassword from "@app/components/utilities/cryptography/changePassword"; -import issueBackupKey from "@app/components/utilities/cryptography/issueBackupKey"; -import { - useGetCommonPasswords, - useRevokeAllSessions} from "@app/hooks/api"; -import { SecuritySection } from "@app/views/Settings/PersonalSettingsPage/SecuritySection/SecuritySection"; - -import AddApiKeyDialog from "../../../components/basic/dialog/AddApiKeyDialog"; -import getAPIKeys from "../../api/apiKey/getAPIKeys"; -import getUser from "../../api/user/getUser"; - -type Errors = { - length?: string, - upperCase?: string, - lowerCase?: string, - number?: string, - specialChar?: string, - repeatedChar?: string, -}; +import { PersonalSettingsPage } from "@app/views/Settings/PersonalSettingsPage"; export default function PersonalSettings() { - const { data: commonPasswords } = useGetCommonPasswords(); - const [personalEmail, setPersonalEmail] = useState(""); - const [personalName, setPersonalName] = useState(""); - const [currentPasswordError, setCurrentPasswordError] = useState(false); - const [currentPassword, setCurrentPassword] = useState(""); - const [newPassword, setNewPassword] = useState(""); - const [backupPassword, setBackupPassword] = useState(""); - const [passwordChanged, setPasswordChanged] = useState(false); - const [backupKeyIssued, setBackupKeyIssued] = useState(false); - const [backupKeyError, setBackupKeyError] = useState(false); - const [isAddApiKeyDialogOpen, setIsAddApiKeyDialogOpen] = useState(false); - const [apiKeys, setApiKeys] = useState([]); - const [errors, setErrors] = useState({}); - - const revokeAllSessions = useRevokeAllSessions(); - - const { t, i18n } = useTranslation(); - const router = useRouter(); - const lang = router.locale ?? "en"; - - const setLanguage = async (to: string) => { - router.push(router.asPath, router.asPath, { locale: to }); - localStorage.setItem("lang", to); - i18n.changeLanguage(to); - }; - - useEffect(() => { - const load = async () => { - try { - const user = await getUser(); - setApiKeys(await getAPIKeys()); - setPersonalEmail(user.email); - setPersonalName(`${user.firstName} ${user.lastName}`); - } catch (err) { - console.error(err); - } - }; - - load(); - }, []); - - const closeAddApiKeyModal = () => { - setIsAddApiKeyDialogOpen(false); - }; + const { t } = useTranslation(); return ( -
+
{t("common.head-title", { title: t("settings.personal.title") })} - -
-
- -
-
-

{t("settings.personal.title")}

-

- {t("settings.personal.description")} -

-
-
-
-
-

- {t("settings.personal.change-language")} -

-
- -
-
- -
-
-
-

- {t("settings.personal.api-keys.title")} -

-

- {t("settings.personal.api-keys.description")} -

-
-
-
-
- -
- -
-
-
-

- {t("section.password.change")} -

-
-
-
- { - setCurrentPassword(password); - }} - type="password" - value={currentPassword} - isRequired - error={currentPasswordError} - errorText={t("section.password.current-wrong") as string} - autoComplete="current-password" - id="current-password" - /> -
- { - setNewPassword(password); - checkPassword({ - password, - commonPasswords, - setErrors - }); - }} - type="password" - value={newPassword} - isRequired - error={Object.keys(errors).length > 0} - autoComplete="new-password" - id="new-password" - /> -
- {Object.keys(errors).length > 0 && ( -
-
{t("section.password.validate-base")}
- {Object.keys(errors).map((key) => { - if (errors[key as keyof Errors]) { - return ( -
-
- -
-

- {errors[key as keyof Errors]} -

-
- ); - } - - return null; - })} -
- )} -
-
-
-
-
-

- Sessions -

-
-
-
-

- Logging into Infisical via browser or CLI creates a session. Revoking all sessions logs your account out all active sessions across all browsers and CLIs. -

-
- -
-
-
-

- {t("settings.personal.emergency.name")} -

-

- {t("settings.personal.emergency.text1")} -

-

- {t("settings.personal.emergency.text2")} -

-
-
-
- -
-
-
-
-
-
-
+
); } diff --git a/frontend/src/pages/users/[id].tsx b/frontend/src/pages/users/[id].tsx index 75e051d6f..5070030b0 100644 --- a/frontend/src/pages/users/[id].tsx +++ b/frontend/src/pages/users/[id].tsx @@ -154,7 +154,9 @@ export default function Users() { {t("common.head-title", { title: t("settings.members.title") })} - +
+ +

{t("settings.members.title")}

diff --git a/frontend/src/views/DashboardPage/DashboardEnvOverview.tsx b/frontend/src/views/DashboardPage/DashboardEnvOverview.tsx index 60b85139b..9d131dafe 100644 --- a/frontend/src/views/DashboardPage/DashboardEnvOverview.tsx +++ b/frontend/src/views/DashboardPage/DashboardEnvOverview.tsx @@ -143,7 +143,7 @@ export const DashboardEnvOverview = () => { return (
-
+
diff --git a/frontend/src/views/DashboardPage/DashboardPage.tsx b/frontend/src/views/DashboardPage/DashboardPage.tsx index 539d9fea7..2a4f79c4b 100644 --- a/frontend/src/views/DashboardPage/DashboardPage.tsx +++ b/frontend/src/views/DashboardPage/DashboardPage.tsx @@ -545,7 +545,7 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
{/* breadcrumb row */} -
+
{ - const syntaxHighlight = useCallback((val: string) => { - if (val === undefined) - return ( - - - - ); - if (val?.length === 0) - return EMPTY; - return val?.split(REGEX).map((word, index) => - word.match(REGEX) !== null ? ( - - {word.slice(0, 2)} - {word.slice(2, word.length - 1)} - {word.slice(word.length - 1, word.length) === "}" ? ( - - {word.slice(word.length - 1, word.length)} - - ) : ( - - {word.slice(word.length - 1, word.length)} - - )} - - ) : ( - - {word} - - ) - ); - }, []); + const ref = useRef(null); + const [isFocused, setIsFocused] = useToggle(); + const syntaxHighlight = useSyntaxHighlight(); + + const value = isOverridden ? secret.valueOverride : secret?.value; + const multilineExpandUnit = ((value?.match(/\n/g)?.length || 0) + 1) * SEC_VAL_LINE_HEIGHT; + const maxMultilineHeight = Math.min(multilineExpandUnit, 21 * MAX_MULTI_LINE); return (
-
- +