diff --git a/backend/src/controllers/v1/secretScanningController.ts b/backend/src/controllers/v1/secretScanningController.ts index e84e5994c..3acd7e293 100644 --- a/backend/src/controllers/v1/secretScanningController.ts +++ b/backend/src/controllers/v1/secretScanningController.ts @@ -1,11 +1,11 @@ import { Request, Response } from "express"; -import GitAppInstallationSession from "../../models/gitAppInstallationSession"; +import GitAppInstallationSession from "../../ee/models/gitAppInstallationSession"; import crypto from "crypto"; import { Types } from "mongoose"; import { UnauthorizedRequestError } from "../../utils/errors"; -import GitAppOrganizationInstallation from "../../models/gitAppOrganizationInstallation"; +import GitAppOrganizationInstallation from "../../ee/models/gitAppOrganizationInstallation"; import { MembershipOrg } from "../../models"; -import GitRisks, { STATUS_RESOLVED_FALSE_POSITIVE, STATUS_RESOLVED_NOT_REVOKED, STATUS_RESOLVED_REVOKED } from "../../models/gitRisks"; +import GitRisks, { STATUS_RESOLVED_FALSE_POSITIVE, STATUS_RESOLVED_NOT_REVOKED, STATUS_RESOLVED_REVOKED } from "../../ee/models/gitRisks"; export const createInstallationSession = async (req: Request, res: Response) => { const sessionId = crypto.randomBytes(16).toString("hex"); diff --git a/backend/src/models/gitAppInstallationSession.ts b/backend/src/ee/models/gitAppInstallationSession.ts similarity index 100% rename from backend/src/models/gitAppInstallationSession.ts rename to backend/src/ee/models/gitAppInstallationSession.ts diff --git a/backend/src/models/gitAppOrganizationInstallation.ts b/backend/src/ee/models/gitAppOrganizationInstallation.ts similarity index 100% rename from backend/src/models/gitAppOrganizationInstallation.ts rename to backend/src/ee/models/gitAppOrganizationInstallation.ts diff --git a/backend/src/models/gitRisks.ts b/backend/src/ee/models/gitRisks.ts similarity index 100% rename from backend/src/models/gitRisks.ts rename to backend/src/ee/models/gitRisks.ts diff --git a/backend/src/ee/models/index.ts b/backend/src/ee/models/index.ts index 420e51d3a..6e9ec34a9 100644 --- a/backend/src/ee/models/index.ts +++ b/backend/src/ee/models/index.ts @@ -6,3 +6,6 @@ export * from "./action"; export * from "./ssoConfig"; export * from "./trustedIp"; export * from "./auditLog"; +export * from "./gitRisks"; +export * from "./gitAppOrganizationInstallation"; +export * from "./gitAppInstallationSession"; diff --git a/backend/src/ee/routes/v1/index.ts b/backend/src/ee/routes/v1/index.ts index cf92bfc6c..be4847cef 100644 --- a/backend/src/ee/routes/v1/index.ts +++ b/backend/src/ee/routes/v1/index.ts @@ -6,6 +6,7 @@ import users from "./users"; import workspace from "./workspace"; import action from "./action"; import cloudProducts from "./cloudProducts"; +import secretScanning from "./secretScanning"; export { secret, @@ -16,4 +17,5 @@ export { workspace, action, cloudProducts, + secretScanning } \ No newline at end of file diff --git a/backend/src/routes/v1/secretScanning.ts b/backend/src/ee/routes/v1/secretScanning.ts similarity index 91% rename from backend/src/routes/v1/secretScanning.ts rename to backend/src/ee/routes/v1/secretScanning.ts index 135556b1e..1cc3adc46 100644 --- a/backend/src/routes/v1/secretScanning.ts +++ b/backend/src/ee/routes/v1/secretScanning.ts @@ -4,10 +4,10 @@ import { requireAuth, requireOrganizationAuth, validateRequest, -} from "../../middleware"; +} from "../../../middleware"; import { body, param } from "express-validator"; -import { createInstallationSession, getCurrentOrganizationInstallationStatus, getRisksForOrganization, linkInstallationToOrganization, updateRisksStatus } from "../../controllers/v1/secretScanningController"; -import { ACCEPTED, ADMIN, MEMBER, OWNER, AuthMode } from "../../variables"; +import { createInstallationSession, getCurrentOrganizationInstallationStatus, getRisksForOrganization, linkInstallationToOrganization, updateRisksStatus } from "../../../controllers/v1/secretScanningController"; +import { ACCEPTED, ADMIN, AuthMode, MEMBER, OWNER } from "../../../variables"; router.post( "/create-installation-session/organization/:organizationId", diff --git a/backend/src/services/GithubSecretScanningService.ts b/backend/src/ee/services/GithubSecretScanning/GithubSecretScanningService.ts similarity index 94% rename from backend/src/services/GithubSecretScanningService.ts rename to backend/src/ee/services/GithubSecretScanning/GithubSecretScanningService.ts index 295be13b9..a8e374ba5 100644 --- a/backend/src/services/GithubSecretScanningService.ts +++ b/backend/src/ee/services/GithubSecretScanning/GithubSecretScanningService.ts @@ -3,13 +3,13 @@ import { exec } from "child_process"; import { mkdir, readFile, rm, writeFile } from "fs"; import { tmpdir } from "os"; import { join } from "path" -import GitRisks from "../models/gitRisks"; -import GitAppOrganizationInstallation from "../models/gitAppOrganizationInstallation"; -import MembershipOrg from "../models/membershipOrg"; -import { ADMIN, OWNER } from "../variables"; -import User from "../models/user"; -import { sendMail } from "../helpers"; -import TelemetryService from "./TelemetryService"; +import GitRisks from "../../models/gitRisks"; +import GitAppOrganizationInstallation from "../../models/gitAppOrganizationInstallation"; +import MembershipOrg from "../../../models/membershipOrg"; +import { ADMIN, OWNER } from "../../../variables"; +import User from "../../../models/user"; +import { sendMail } from "../../../helpers"; +import TelemetryService from "../../../services/TelemetryService"; type SecretMatch = { Description: string; diff --git a/backend/src/ee/services/index.ts b/backend/src/ee/services/index.ts index ba25df7fa..20571d5bb 100644 --- a/backend/src/ee/services/index.ts +++ b/backend/src/ee/services/index.ts @@ -2,10 +2,12 @@ import EELicenseService from "./EELicenseService"; import EESecretService from "./EESecretService"; import EELogService from "./EELogService"; import EEAuditLogService from "./EEAuditLogService"; +import GithubSecretScanningService from "./GithubSecretScanning/GithubSecretScanningService" export { EELicenseService, EESecretService, EELogService, - EEAuditLogService + EEAuditLogService, + GithubSecretScanningService } \ No newline at end of file diff --git a/backend/src/index.ts b/backend/src/index.ts index 098e7fc27..3c6e82180 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -5,8 +5,8 @@ import express from "express"; require("express-async-errors"); import helmet from "helmet"; import cors from "cors"; -import { DatabaseService, GithubSecretScanningService } from "./services"; -import { EELicenseService } from "./ee/services"; +import { DatabaseService } from "./services"; +import { EELicenseService, GithubSecretScanningService} from "./ee/services"; import { setUpHealthEndpoint } from "./services/health"; import cookieParser from "cookie-parser"; import swaggerUi = require("swagger-ui-express"); @@ -24,6 +24,7 @@ import { secretSnapshot as eeSecretSnapshotRouter, users as eeUsersRouter, workspace as eeWorkspaceRouter, + secretScanning as v1SecretScanningRouter, } from "./ee/routes/v1"; import { auth as v1AuthRouter, @@ -38,7 +39,6 @@ import { password as v1PasswordRouter, secretImport as v1SecretImportRouter, secret as v1SecretRouter, - secretScanning as v1SecretScanningRouter, secretsFolder as v1SecretsFolder, serviceToken as v1ServiceTokenRouter, signup as v1SignupRouter, diff --git a/backend/src/models/index.ts b/backend/src/models/index.ts index d94d2b917..b852b3245 100644 --- a/backend/src/models/index.ts +++ b/backend/src/models/index.ts @@ -24,7 +24,6 @@ import ServiceTokenData, { IServiceTokenData } from "./serviceTokenData"; import APIKeyData, { IAPIKeyData } from "./apiKeyData"; import LoginSRPDetail, { ILoginSRPDetail } from "./loginSRPDetail"; import TokenVersion, { ITokenVersion } from "./tokenVersion"; -import GitRisks, { STATUS_RESOLVED_FALSE_POSITIVE } from "./gitRisks"; export { AuthProvider, @@ -79,7 +78,5 @@ export { LoginSRPDetail, ILoginSRPDetail, TokenVersion, - ITokenVersion, - GitRisks, - STATUS_RESOLVED_FALSE_POSITIVE + ITokenVersion }; diff --git a/backend/src/routes/v1/index.ts b/backend/src/routes/v1/index.ts index ce5b5dd58..08298a1c6 100644 --- a/backend/src/routes/v1/index.ts +++ b/backend/src/routes/v1/index.ts @@ -15,7 +15,6 @@ import password from "./password"; import integration from "./integration"; import integrationAuth from "./integrationAuth"; import secretsFolder from "./secretsFolder"; -import secretScanning from "./secretScanning"; import webhooks from "./webhook"; import secretImport from "./secretImport"; @@ -37,7 +36,6 @@ export { integration, integrationAuth, secretsFolder, - secretScanning, webhooks, secretImport }; diff --git a/backend/src/routes/v3/secrets.ts b/backend/src/routes/v3/secrets.ts index 11500cb97..224123445 100644 --- a/backend/src/routes/v3/secrets.ts +++ b/backend/src/routes/v3/secrets.ts @@ -5,8 +5,8 @@ import { body, param, query } from "express-validator"; import { secretsController } from "../../controllers/v3"; import { ADMIN, - MEMBER, AuthMode, + MEMBER, PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS, SECRET_PERSONAL, @@ -52,7 +52,7 @@ router.get( requiredPermissions: [PERMISSION_READ_SECRETS], requireBlindIndicesEnabled: true, requireE2EEOff: true, - checkIPAllowlist: true + checkIPAllowlist: false }), secretsController.getSecretByNameRaw ); @@ -80,7 +80,7 @@ router.post( requiredPermissions: [PERMISSION_WRITE_SECRETS], requireBlindIndicesEnabled: true, requireE2EEOff: true, - checkIPAllowlist: true + checkIPAllowlist: false }), secretsController.createSecretRaw ); @@ -108,7 +108,7 @@ router.patch( requiredPermissions: [PERMISSION_WRITE_SECRETS], requireBlindIndicesEnabled: true, requireE2EEOff: true, - checkIPAllowlist: true + checkIPAllowlist: false }), secretsController.updateSecretByNameRaw ); @@ -135,7 +135,7 @@ router.delete( requiredPermissions: [PERMISSION_WRITE_SECRETS], requireBlindIndicesEnabled: true, requireE2EEOff: true, - checkIPAllowlist: true + checkIPAllowlist: false }), secretsController.deleteSecretByNameRaw ); @@ -160,7 +160,7 @@ router.get( requiredPermissions: [PERMISSION_READ_SECRETS], requireBlindIndicesEnabled: true, requireE2EEOff: false, - checkIPAllowlist: true + checkIPAllowlist: false }), secretsController.getSecrets ); @@ -195,7 +195,7 @@ router.post( requiredPermissions: [PERMISSION_WRITE_SECRETS], requireBlindIndicesEnabled: true, requireE2EEOff: false, - checkIPAllowlist: true + checkIPAllowlist: false }), secretsController.createSecret ); @@ -221,7 +221,7 @@ router.get( locationEnvironment: "query", requiredPermissions: [PERMISSION_READ_SECRETS], requireBlindIndicesEnabled: true, - checkIPAllowlist: true + checkIPAllowlist: false }), secretsController.getSecretByName ); @@ -251,7 +251,7 @@ router.patch( requiredPermissions: [PERMISSION_WRITE_SECRETS], requireBlindIndicesEnabled: true, requireE2EEOff: false, - checkIPAllowlist: true + checkIPAllowlist: false }), secretsController.updateSecretByName ); @@ -278,7 +278,7 @@ router.delete( requiredPermissions: [PERMISSION_WRITE_SECRETS], requireBlindIndicesEnabled: true, requireE2EEOff: false, - checkIPAllowlist: true + checkIPAllowlist: false }), secretsController.deleteSecretByName ); diff --git a/backend/src/services/index.ts b/backend/src/services/index.ts index 5b6d42f26..781fb435c 100644 --- a/backend/src/services/index.ts +++ b/backend/src/services/index.ts @@ -7,7 +7,6 @@ import EventService from "./EventService"; import IntegrationService from "./IntegrationService"; import TokenService from "./TokenService"; import SecretService from "./SecretService"; -import GithubSecretScanningService from "./GithubSecretScanningService" export { TelemetryService, @@ -18,5 +17,4 @@ export { IntegrationService, TokenService, SecretService, - GithubSecretScanningService } diff --git a/cli/packages/cmd/run.go b/cli/packages/cmd/run.go index 906723df8..ef83bc10c 100644 --- a/cli/packages/cmd/run.go +++ b/cli/packages/cmd/run.go @@ -144,12 +144,14 @@ var runCmd = &cobra.Command{ err = executeMultipleCommandWithEnvs(command, len(secretsByKey), env) if err != nil { fmt.Println(err) + os.Exit(1) } } else { err = executeSingleCommandWithEnvs(args, len(secretsByKey), env) if err != nil { fmt.Println(err) + os.Exit(1) } } }, diff --git a/docs/cli/overview.mdx b/docs/cli/overview.mdx index 434a39c51..ab913ec1a 100644 --- a/docs/cli/overview.mdx +++ b/docs/cli/overview.mdx @@ -111,3 +111,8 @@ You can use it across various environments, whether it's local development, CI/C + +## Quick Usage Guide + + Now that you have the CLI installed on your system, follow this guide to make the best use of it + \ No newline at end of file diff --git a/docs/cli/usage.mdx b/docs/cli/usage.mdx index 36f6a4165..2ea7b6425 100644 --- a/docs/cli/usage.mdx +++ b/docs/cli/usage.mdx @@ -7,7 +7,7 @@ The CLI is designed for a variety of applications, ranging from local secret man The distinguishing factor, however, is the authentication method used. - + To use the Infisical CLI in your local development environment, simply run the command below and follow the interactive guide. ```bash @@ -32,7 +32,7 @@ The distinguishing factor, however, is the authentication method used. - + To use Infisical for non local development scenarios, please create a [service token](../documentation/platform/token). The service token will allow you to authenticate and interact with Infisical. Once you have created a service token with the required permissions, you'll need to feed the token to the CLI. @@ -61,16 +61,16 @@ The distinguishing factor, however, is the authentication method used. ```bash - infisical run -- [your application start command] + infisical run --env=dev --path=/apps/firefly -- [your application start command] # example with node (nodemon) - infisical run --env=dev --path=/apps/firefly -- nodemon index.js + infisical run --env=staging --path=/apps/spotify -- nodemon index.js # example with flask - infisical run -- flask run + infisical run --env=prod --path=/apps/backend -- flask run # example with spring boot - maven - infisical run -- ./mvnw spring-boot:run --quiet + infisical run --env=dev --path=/apps/ -- ./mvnw spring-boot:run --quiet ``` @@ -86,7 +86,7 @@ The distinguishing factor, however, is the authentication method used. To make the secrets available from Infisical to `yd`, you can run the following command: ```bash - infisical run --command="source custom.sh && yd" + infisical run --env=prod --path=/apps/reddit --command="source custom.sh && yd" ``` diff --git a/frontend/src/components/signup/InitialSignupStep.tsx b/frontend/src/components/signup/InitialSignupStep.tsx index 01ede6411..0a1248b2d 100644 --- a/frontend/src/components/signup/InitialSignupStep.tsx +++ b/frontend/src/components/signup/InitialSignupStep.tsx @@ -3,6 +3,7 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { faGithub,faGoogle } from "@fortawesome/free-brands-svg-icons"; import { faEnvelope } from "@fortawesome/free-regular-svg-icons"; +import { faLock } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Button } from "../v2"; @@ -26,7 +27,7 @@ export default function InitialSignupStep({ window.close(); }} leftIcon={} - className="h-14 w-full mx-0" + className="h-12 w-full mx-0" > {t("signup.continue-with-google")} @@ -40,7 +41,7 @@ export default function InitialSignupStep({ window.close(); }} leftIcon={} - className="h-14 w-full mx-0" + className="h-12 w-full mx-0" > Continue with GitHub @@ -53,7 +54,7 @@ export default function InitialSignupStep({ setIsSignupWithEmail(true); }} leftIcon={} - className="h-14 w-full mx-0" + className="h-12 w-full mx-0" > Continue with Email @@ -63,10 +64,10 @@ export default function InitialSignupStep({ colorSchema="primary" variant="outline_bg" onClick={() => router.push("/saml-sso")} - isFullWidth - className="h-14 w-full mx-0" + leftIcon={} + className="h-12 w-full mx-0" > - Continue with SAML SSO + Continue with SSO
diff --git a/frontend/src/components/v2/Button/Button.tsx b/frontend/src/components/v2/Button/Button.tsx index b69545766..7c2a5fd6d 100644 --- a/frontend/src/components/v2/Button/Button.tsx +++ b/frontend/src/components/v2/Button/Button.tsx @@ -51,10 +51,10 @@ const buttonVariants = cva( false: "" }, size: { - xs: ["text-xs", "py-1", "px-1"], - sm: ["text-sm", "py-2", "px-2"], - md: ["text-md", "py-2", "px-4"], - lg: ["text-lg", "py-2", "px-8"] + xs: ["text-xs", "py-1", "px-2"], + sm: ["text-sm", "py-2", "px-4"], + md: ["text-md", "py-2", "px-5"], + lg: ["text-lg", "py-2", "px-6"] } }, compoundVariants: [ @@ -186,16 +186,17 @@ export const Button = forwardRef( className="absolute rounded-xl opacity-80" /> )} -
- {leftIcon} -
+ {leftIcon && ( +
+ {leftIcon} +
+ )} ( > {children} -
- {rightIcon} -
+ {rightIcon && ( +
+ {rightIcon} +
+ )} ); } diff --git a/frontend/src/components/v2/Input/Input.tsx b/frontend/src/components/v2/Input/Input.tsx index 17573e527..7ec540232 100644 --- a/frontend/src/components/v2/Input/Input.tsx +++ b/frontend/src/components/v2/Input/Input.tsx @@ -87,7 +87,6 @@ export const Input = forwardRef( ref ): JSX.Element => { const handleInput = (event: ChangeEvent) => { - console.log(123, props, autoCapitalization) if (autoCapitalization) { // eslint-disable-next-line no-param-reassign event.target.value = event.target.value.toUpperCase(); diff --git a/frontend/src/hooks/api/secrets/queries.tsx b/frontend/src/hooks/api/secrets/queries.tsx index 34ecd7ec4..6f65a8edd 100644 --- a/frontend/src/hooks/api/secrets/queries.tsx +++ b/frontend/src/hooks/api/secrets/queries.tsx @@ -54,13 +54,14 @@ export const useGetProjectSecrets = ({ env, decryptFileKey, isPaused, - folderId + folderId, + secretPath }: GetProjectSecretsDTO) => useQuery({ // wait for all values to be available enabled: Boolean(decryptFileKey && workspaceId && env) && !isPaused, - queryKey: secretKeys.getProjectSecret(workspaceId, env, folderId), - queryFn: () => fetchProjectEncryptedSecrets(workspaceId, env, folderId), + queryKey: secretKeys.getProjectSecret(workspaceId, env, folderId || secretPath), + queryFn: () => fetchProjectEncryptedSecrets(workspaceId, env, folderId, secretPath), select: useCallback( (data: EncryptedSecret[]) => { const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; diff --git a/frontend/src/hooks/index.ts b/frontend/src/hooks/index.ts index a3ded034c..98fbe2413 100644 --- a/frontend/src/hooks/index.ts +++ b/frontend/src/hooks/index.ts @@ -1,3 +1,4 @@ +export { useDebounce } from "./useDebounce"; export { useLeaveConfirm } from "./useLeaveConfirm"; export { usePersistentState } from "./usePersistentState"; export { usePopUp } from "./usePopUp"; diff --git a/frontend/src/hooks/useDebounce.tsx b/frontend/src/hooks/useDebounce.tsx new file mode 100644 index 000000000..318763210 --- /dev/null +++ b/frontend/src/hooks/useDebounce.tsx @@ -0,0 +1,26 @@ +import { useEffect, useState } from "react"; + +// Ref: https://usehooks.com/useDebounce/ +export const useDebounce = (value: T, delay = 500): T => { + // State and setters for debounced value + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect( + () => { + // Update debounced value after delay + const handler = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + // Cancel the timeout if value changes (also on delay change or unmount) + // This is how we prevent debounced value from updating if value is changed ... + // .. within the delay period. Timeout gets cleared and restarted. + return () => { + clearTimeout(handler); + }; + }, + [value, delay] // Only re-call effect if value or delay changes + ); + + return debouncedValue; +}; diff --git a/frontend/src/views/DashboardPage/DashboardPage.tsx b/frontend/src/views/DashboardPage/DashboardPage.tsx index 5763128e4..32133445b 100644 --- a/frontend/src/views/DashboardPage/DashboardPage.tsx +++ b/frontend/src/views/DashboardPage/DashboardPage.tsx @@ -740,7 +740,7 @@ export const DashboardPage = () => { return (
-
+ {/* breadcrumb row */}
{
{!isEmptyPage && ( @@ -935,7 +935,7 @@ export const DashboardPage = () => { collisionDetection={closestCenter} modifiers={[restrictToVerticalAxis]} > - + @@ -1017,9 +1017,12 @@ export const DashboardPage = () => { {/* secrets table and drawers, modals */} diff --git a/frontend/src/views/DashboardPage/DashboardPage.utils.ts b/frontend/src/views/DashboardPage/DashboardPage.utils.ts index a46e97a84..375a05489 100644 --- a/frontend/src/views/DashboardPage/DashboardPage.utils.ts +++ b/frontend/src/views/DashboardPage/DashboardPage.utils.ts @@ -75,6 +75,13 @@ export type FormData = yup.InferType; export type TSecretDetailsOpen = { index: number; id: string }; export type TSecOverwriteOpt = { secrets: Record }; +// to convert multi line into single line ones by quoting them and changing to string \n +const formatMultiValueEnv = (val?: string) => { + if (!val) return ""; + if (!val.match("\n")) return val; + return `"${val.replace(/\n/g, "\\n")}"`; +}; + export const downloadSecret = ( secrets: FormData["secrets"] = [], importedSecrets: { key: string; value?: string; comment?: string }[] = [], @@ -86,9 +93,11 @@ export const downloadSecret = ( }); const finalSecret = [...importedSecrets]; secrets.forEach(({ key, value, valueOverride, overrideAction, comment }) => { + const finalVal = + overrideAction && overrideAction !== SecretActionType.Deleted ? valueOverride : value; const newValue = { key, - value: overrideAction && overrideAction !== SecretActionType.Deleted ? valueOverride : value, + value: formatMultiValueEnv(finalVal), comment }; // can also be zero thus failing diff --git a/frontend/src/views/DashboardPage/components/SecretDropzone/SecretDropzone.tsx b/frontend/src/views/DashboardPage/components/SecretDropzone/SecretDropzone.tsx index 7b8671583..315eaf63a 100644 --- a/frontend/src/views/DashboardPage/components/SecretDropzone/SecretDropzone.tsx +++ b/frontend/src/views/DashboardPage/components/SecretDropzone/SecretDropzone.tsx @@ -1,26 +1,128 @@ -import { ChangeEvent, DragEvent } from "react"; +import { ChangeEvent, DragEvent, useEffect, useState } from "react"; +import { Controller, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; -import { faUpload } from "@fortawesome/free-solid-svg-icons"; +import { faSquareCheck } from "@fortawesome/free-regular-svg-icons"; +import { + faClone, + faKey, + faSearch, + faSquareXmark, + faUpload +} from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { yupResolver } from "@hookform/resolvers/yup"; import { twMerge } from "tailwind-merge"; +import * as yup from "yup"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; -// TODO:(akhilmhdh) convert all the util functions like this into a lib folder grouped by functionalityj +// TODO:(akhilmhdh) convert all the util functions like this into a lib folder grouped by functionality import { parseDotEnv } from "@app/components/utilities/parseDotEnv"; -import { Button } from "@app/components/v2"; -import { useToggle } from "@app/hooks/useToggle"; +import { + Button, + Checkbox, + EmptyState, + FormControl, + IconButton, + Input, + Modal, + ModalContent, + ModalTrigger, + Select, + SelectItem, + Skeleton, + Tooltip +} from "@app/components/v2"; +import { useDebounce, usePopUp, useToggle } from "@app/hooks"; +import { useGetProjectSecrets } from "@app/hooks/api"; +import { UserWsKeyPair } from "@app/hooks/api/types"; + +const formSchema = yup.object({ + environment: yup.string().required().label("Environment").trim(), + secretPath: yup + .string() + .required() + .label("Secret Path") + .trim() + .transform((val) => + typeof val === "string" && val.at(-1) === "/" && val.length > 1 ? val.slice(0, -1) : val + ), + secrets: yup.lazy((val) => { + const valSchema: Record = {}; + Object.keys(val).forEach((key) => { + valSchema[key] = yup.string().trim(); + }); + return yup.object(valSchema); + }) +}); + +type TFormSchema = yup.InferType; + +const parseJson = (src: ArrayBuffer) => { + const file = src.toString(); + const formatedData: Record = JSON.parse(file); + const env: Record = {}; + Object.keys(formatedData).forEach((key) => { + if (typeof formatedData[key] === "string") { + env[key] = { value: formatedData[key], comments: [] }; + } + }); + return env; +}; type Props = { isSmaller: boolean; onParsedEnv: (env: Record) => void; onAddNewSecret?: () => void; + environments?: { name: string; slug: string }[]; + workspaceId: string; + decryptFileKey: UserWsKeyPair; }; -export const SecretDropzone = ({ isSmaller, onParsedEnv, onAddNewSecret }: Props): JSX.Element => { +export const SecretDropzone = ({ + isSmaller, + onParsedEnv, + onAddNewSecret, + environments = [], + workspaceId, + decryptFileKey +}: Props): JSX.Element => { const { t } = useTranslation(); const [isDragActive, setDragActive] = useToggle(); const [isLoading, setIsLoading] = useToggle(); const { createNotification } = useNotificationContext(); + const { popUp, handlePopUpClose, handlePopUpToggle } = usePopUp(["importSecEnv"] as const); + const [searchFilter, setSearchFilter] = useState(""); + const [shouldIncludeValues, setShouldIncludeValues] = useState(true); + + const { + handleSubmit, + control, + watch, + register, + reset, + setValue, + formState: { isDirty } + } = useForm({ + resolver: yupResolver(formSchema), + defaultValues: { secretPath: "/", environment: environments?.[0]?.slug } + }); + + const secretPath = watch("secretPath"); + const selectedEnvSlug = watch("environment"); + const debouncedSecretPath = useDebounce(secretPath); + + const { data: secrets, isLoading: isSecretsLoading } = useGetProjectSecrets({ + workspaceId, + env: selectedEnvSlug, + secretPath: debouncedSecretPath, + isPaused: !(Boolean(workspaceId) && Boolean(selectedEnvSlug) && Boolean(debouncedSecretPath)), + decryptFileKey + }); + + useEffect(() => { + setValue("secrets", {}); + setSearchFilter(""); + }, [debouncedSecretPath]); const handleDrag = (e: DragEvent) => { e.preventDefault(); @@ -32,7 +134,7 @@ export const SecretDropzone = ({ isSmaller, onParsedEnv, onAddNewSecret }: Props } }; - const parseFile = (file?: File) => { + const parseFile = (file?: File, isJson?: boolean) => { const reader = new FileReader(); if (!file) { createNotification({ @@ -47,7 +149,9 @@ export const SecretDropzone = ({ isSmaller, onParsedEnv, onAddNewSecret }: Props reader.onload = (event) => { if (!event?.target?.result) return; // parse function's argument looks like to be ArrayBuffer - const env = parseDotEnv(event.target.result as ArrayBuffer); + const env = isJson + ? parseJson(event.target.result as ArrayBuffer) + : parseDotEnv(event.target.result as ArrayBuffer); setIsLoading.off(); onParsedEnv(env); }; @@ -74,7 +178,32 @@ export const SecretDropzone = ({ isSmaller, onParsedEnv, onAddNewSecret }: Props const handleFileUpload = (e: ChangeEvent) => { e.preventDefault(); - parseFile(e.target?.files?.[0]); + parseFile(e.target?.files?.[0], e.target?.files?.[0]?.type === "application/json"); + }; + + const handleFormSubmit = (data: TFormSchema) => { + const secretsToBePulled: Record = {}; + Object.keys(data.secrets || {}).forEach((key) => { + if (data.secrets[key]) { + secretsToBePulled[key] = { + value: (shouldIncludeValues && data.secrets[key]) || "", + comments: [""] + }; + } + }); + onParsedEnv(secretsToBePulled); + handlePopUpClose("importSecEnv"); + reset(); + }; + + const handleSecSelectAll = () => { + if (secrets?.secrets) { + setValue( + "secrets", + secrets?.secrets?.reduce((prev, curr) => ({ ...prev, [curr.key]: curr.value }), {}), + { shouldDirty: true } + ); + } }; return ( @@ -84,9 +213,9 @@ export const SecretDropzone = ({ isSmaller, onParsedEnv, onAddNewSecret }: Props onDragOver={handleDrag} onDrop={handleDrop} className={twMerge( - "relative mx-0.5 mb-4 mt-4 flex w-full max-w-[calc(100vw-292px)] cursor-pointer items-center justify-center space-x-2 rounded-md bg-mineshaft-900 py-8 px-2 text-mineshaft-200 opacity-60 outline-dashed outline-2 outline-chicago-600 duration-200 hover:opacity-100", + "relative mx-0.5 mb-4 mt-4 flex cursor-pointer items-center justify-center rounded-md bg-mineshaft-900 py-4 text-sm px-2 text-mineshaft-200 opacity-60 outline-dashed outline-2 outline-chicago-600 duration-200 hover:opacity-100", isDragActive && "opacity-100", - !isSmaller && "max-w-3xl flex-col space-y-4 py-20", + !isSmaller && "w-full max-w-3xl flex-col space-y-4 py-20", isLoading && "bg-bunker-800" )} > @@ -95,35 +224,184 @@ export const SecretDropzone = ({ isSmaller, onParsedEnv, onAddNewSecret }: Props loading animation ) : ( - <> -
- -
-
-

{t(isSmaller ? "common.drop-zone-keys" : "common.drop-zone")}

-
- - {!isSmaller && ( - <> -
-
-

OR

-
-
-
+ +
+
+ +
+
+

{t(isSmaller ? "common.drop-zone-keys" : "common.drop-zone")}

+
+ +
+
+

OR

+
+
+
+ { + handlePopUpToggle("importSecEnv", isOpen); + reset(); + setSearchFilter(""); + }} + > + + + + + +
+ ( + + + + )} + /> + + + +
+
+
+
Secrets
+
+ } + onChange={(evt) => setSearchFilter(evt.target.value)} + /> + + + + + + + reset()} + > + + + +
+
+ {!isSecretsLoading && !secrets?.secrets?.length && ( + + )} +
+ {isSecretsLoading && + Array.apply(0, Array(2)).map((_x, i) => ( + + ))} + + {secrets?.secrets + ?.filter(({ key }) => + key.toLowerCase().includes(searchFilter.toLowerCase()) + ) + ?.map(({ _id, key, value: secVal }) => ( + ( + onChange(isChecked ? secVal : "")} + > + {key} + + )} + /> + ))} +
+
+ + setShouldIncludeValues(isChecked as boolean) + } + > + Include secret values + +
+
+ + +
+
+ +
+
+ {!isSmaller && ( -
- - )}{" "} - + )} +
+
+ )}
); diff --git a/frontend/src/views/DashboardPage/components/SecretInputRow/SecretInputRow.tsx b/frontend/src/views/DashboardPage/components/SecretInputRow/SecretInputRow.tsx index a2f95e4a4..aba283b27 100644 --- a/frontend/src/views/DashboardPage/components/SecretInputRow/SecretInputRow.tsx +++ b/frontend/src/views/DashboardPage/components/SecretInputRow/SecretInputRow.tsx @@ -281,7 +281,6 @@ export const SecretInputRow = memo( value={editorRef} isVisible={!isSecretValueHidden} onChange={(val, html) => { - console.log(val); onChange(val); setEditorRef(html); }} diff --git a/frontend/src/views/Login/components/InitialStep/InitialStep.tsx b/frontend/src/views/Login/components/InitialStep/InitialStep.tsx index c814cd510..2bee45669 100644 --- a/frontend/src/views/Login/components/InitialStep/InitialStep.tsx +++ b/frontend/src/views/Login/components/InitialStep/InitialStep.tsx @@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next"; import Link from "next/link"; import { useRouter } from "next/router"; import { faGithub,faGoogle } from "@fortawesome/free-brands-svg-icons"; +import { faLock } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import axios from "axios" @@ -116,47 +117,6 @@ export const InitialStep = ({ return (

Login to Infisical

-
-
-
-
- setEmail(e.target.value)} - type="email" - placeholder="Enter your email..." - isRequired - autoComplete="username" - className="h-12" - /> -
-
- setPassword(e.target.value)} - type="password" - placeholder="Enter your password..." - isRequired - autoComplete="current-password" - id="current-password" - className="h-12 select:-webkit-autofill:focus" - /> -
-
- -
- {!isLoading && loginError && } -
-
-
@@ -185,7 +145,7 @@ export const InitialStep = ({ window.close(); }} leftIcon={} - className="h-12 w-full mx-0" + className="h-11 w-full mx-0" > Continue with GitHub @@ -197,12 +157,52 @@ export const InitialStep = ({ onClick={() => { setStep(2); }} - isFullWidth - className="h-12 w-full mx-0" + leftIcon={} + className="h-11 w-full mx-0" > - Continue with SAML SSO + Continue with SSO
+
+
+ or +
+
+
+ setEmail(e.target.value)} + type="email" + placeholder="Enter your email..." + isRequired + autoComplete="username" + className="h-11" + /> +
+
+ setPassword(e.target.value)} + type="password" + placeholder="Enter your password..." + isRequired + autoComplete="current-password" + id="current-password" + className="h-11 select:-webkit-autofill:focus" + /> +
+
+ +
+ {!isLoading && loginError && } { !serverDetails?.inviteOnlySignup ?
diff --git a/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx b/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx index fed039467..032552958 100644 --- a/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx +++ b/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx @@ -3,7 +3,7 @@ import crypto from "crypto"; import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { faXmark } from "@fortawesome/free-solid-svg-icons"; +import { faInfoCircle, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import jsrp from "jsrp"; import nacl from "tweetnacl"; @@ -198,7 +198,7 @@ export const UserInfoSSOStep = ({ localStorage.setItem("orgData.id", orgId); localStorage.setItem("projectData.id", project._id); - setStep(1); + setStep(1); } catch (error) { setIsLoading(false); console.error(error); @@ -256,7 +256,7 @@ export const UserInfoSSOStep = ({ )}
{ setPassword(pass); checkPassword({ @@ -272,6 +272,7 @@ export const UserInfoSSOStep = ({ autoComplete="new-password" id="new-password" /> +
Infisical Password is used as part of the encryption mechanism so that even the authentication provider is not able to access your secrets.
{Object.keys(errors).length > 0 && (
{t("section.password.validate-base")}
@@ -307,7 +308,7 @@ export const UserInfoSSOStep = ({ onClick={signupErrorCheck} size="sm" isFullWidth - className='h-14' + className='h-12' colorSchema="primary" variant="outline_bg" isLoading={isLoading}