mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-03 19:26:07 +00:00
Fix merge conflicts
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import GitAppInstallationSession from "../../models/gitAppInstallationSession";
|
import GitAppInstallationSession from "../../ee/models/gitAppInstallationSession";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { UnauthorizedRequestError } from "../../utils/errors";
|
import { UnauthorizedRequestError } from "../../utils/errors";
|
||||||
import GitAppOrganizationInstallation from "../../models/gitAppOrganizationInstallation";
|
import GitAppOrganizationInstallation from "../../ee/models/gitAppOrganizationInstallation";
|
||||||
import { MembershipOrg } from "../../models";
|
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) => {
|
export const createInstallationSession = async (req: Request, res: Response) => {
|
||||||
const sessionId = crypto.randomBytes(16).toString("hex");
|
const sessionId = crypto.randomBytes(16).toString("hex");
|
||||||
|
|||||||
@@ -6,3 +6,6 @@ export * from "./action";
|
|||||||
export * from "./ssoConfig";
|
export * from "./ssoConfig";
|
||||||
export * from "./trustedIp";
|
export * from "./trustedIp";
|
||||||
export * from "./auditLog";
|
export * from "./auditLog";
|
||||||
|
export * from "./gitRisks";
|
||||||
|
export * from "./gitAppOrganizationInstallation";
|
||||||
|
export * from "./gitAppInstallationSession";
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import users from "./users";
|
|||||||
import workspace from "./workspace";
|
import workspace from "./workspace";
|
||||||
import action from "./action";
|
import action from "./action";
|
||||||
import cloudProducts from "./cloudProducts";
|
import cloudProducts from "./cloudProducts";
|
||||||
|
import secretScanning from "./secretScanning";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
secret,
|
secret,
|
||||||
@@ -16,4 +17,5 @@ export {
|
|||||||
workspace,
|
workspace,
|
||||||
action,
|
action,
|
||||||
cloudProducts,
|
cloudProducts,
|
||||||
|
secretScanning
|
||||||
}
|
}
|
||||||
+3
-3
@@ -4,10 +4,10 @@ import {
|
|||||||
requireAuth,
|
requireAuth,
|
||||||
requireOrganizationAuth,
|
requireOrganizationAuth,
|
||||||
validateRequest,
|
validateRequest,
|
||||||
} from "../../middleware";
|
} from "../../../middleware";
|
||||||
import { body, param } from "express-validator";
|
import { body, param } from "express-validator";
|
||||||
import { createInstallationSession, getCurrentOrganizationInstallationStatus, getRisksForOrganization, linkInstallationToOrganization, updateRisksStatus } from "../../controllers/v1/secretScanningController";
|
import { createInstallationSession, getCurrentOrganizationInstallationStatus, getRisksForOrganization, linkInstallationToOrganization, updateRisksStatus } from "../../../controllers/v1/secretScanningController";
|
||||||
import { ACCEPTED, ADMIN, MEMBER, OWNER, AuthMode } from "../../variables";
|
import { ACCEPTED, ADMIN, AuthMode, MEMBER, OWNER } from "../../../variables";
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/create-installation-session/organization/:organizationId",
|
"/create-installation-session/organization/:organizationId",
|
||||||
+7
-7
@@ -3,13 +3,13 @@ import { exec } from "child_process";
|
|||||||
import { mkdir, readFile, rm, writeFile } from "fs";
|
import { mkdir, readFile, rm, writeFile } from "fs";
|
||||||
import { tmpdir } from "os";
|
import { tmpdir } from "os";
|
||||||
import { join } from "path"
|
import { join } from "path"
|
||||||
import GitRisks from "../models/gitRisks";
|
import GitRisks from "../../models/gitRisks";
|
||||||
import GitAppOrganizationInstallation from "../models/gitAppOrganizationInstallation";
|
import GitAppOrganizationInstallation from "../../models/gitAppOrganizationInstallation";
|
||||||
import MembershipOrg from "../models/membershipOrg";
|
import MembershipOrg from "../../../models/membershipOrg";
|
||||||
import { ADMIN, OWNER } from "../variables";
|
import { ADMIN, OWNER } from "../../../variables";
|
||||||
import User from "../models/user";
|
import User from "../../../models/user";
|
||||||
import { sendMail } from "../helpers";
|
import { sendMail } from "../../../helpers";
|
||||||
import TelemetryService from "./TelemetryService";
|
import TelemetryService from "../../../services/TelemetryService";
|
||||||
|
|
||||||
type SecretMatch = {
|
type SecretMatch = {
|
||||||
Description: string;
|
Description: string;
|
||||||
@@ -2,10 +2,12 @@ import EELicenseService from "./EELicenseService";
|
|||||||
import EESecretService from "./EESecretService";
|
import EESecretService from "./EESecretService";
|
||||||
import EELogService from "./EELogService";
|
import EELogService from "./EELogService";
|
||||||
import EEAuditLogService from "./EEAuditLogService";
|
import EEAuditLogService from "./EEAuditLogService";
|
||||||
|
import GithubSecretScanningService from "./GithubSecretScanning/GithubSecretScanningService"
|
||||||
|
|
||||||
export {
|
export {
|
||||||
EELicenseService,
|
EELicenseService,
|
||||||
EESecretService,
|
EESecretService,
|
||||||
EELogService,
|
EELogService,
|
||||||
EEAuditLogService
|
EEAuditLogService,
|
||||||
|
GithubSecretScanningService
|
||||||
}
|
}
|
||||||
@@ -5,8 +5,8 @@ import express from "express";
|
|||||||
require("express-async-errors");
|
require("express-async-errors");
|
||||||
import helmet from "helmet";
|
import helmet from "helmet";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import { DatabaseService, GithubSecretScanningService } from "./services";
|
import { DatabaseService } from "./services";
|
||||||
import { EELicenseService } from "./ee/services";
|
import { EELicenseService, GithubSecretScanningService} from "./ee/services";
|
||||||
import { setUpHealthEndpoint } from "./services/health";
|
import { setUpHealthEndpoint } from "./services/health";
|
||||||
import cookieParser from "cookie-parser";
|
import cookieParser from "cookie-parser";
|
||||||
import swaggerUi = require("swagger-ui-express");
|
import swaggerUi = require("swagger-ui-express");
|
||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
secretSnapshot as eeSecretSnapshotRouter,
|
secretSnapshot as eeSecretSnapshotRouter,
|
||||||
users as eeUsersRouter,
|
users as eeUsersRouter,
|
||||||
workspace as eeWorkspaceRouter,
|
workspace as eeWorkspaceRouter,
|
||||||
|
secretScanning as v1SecretScanningRouter,
|
||||||
} from "./ee/routes/v1";
|
} from "./ee/routes/v1";
|
||||||
import {
|
import {
|
||||||
auth as v1AuthRouter,
|
auth as v1AuthRouter,
|
||||||
@@ -38,7 +39,6 @@ import {
|
|||||||
password as v1PasswordRouter,
|
password as v1PasswordRouter,
|
||||||
secretImport as v1SecretImportRouter,
|
secretImport as v1SecretImportRouter,
|
||||||
secret as v1SecretRouter,
|
secret as v1SecretRouter,
|
||||||
secretScanning as v1SecretScanningRouter,
|
|
||||||
secretsFolder as v1SecretsFolder,
|
secretsFolder as v1SecretsFolder,
|
||||||
serviceToken as v1ServiceTokenRouter,
|
serviceToken as v1ServiceTokenRouter,
|
||||||
signup as v1SignupRouter,
|
signup as v1SignupRouter,
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import ServiceTokenData, { IServiceTokenData } from "./serviceTokenData";
|
|||||||
import APIKeyData, { IAPIKeyData } from "./apiKeyData";
|
import APIKeyData, { IAPIKeyData } from "./apiKeyData";
|
||||||
import LoginSRPDetail, { ILoginSRPDetail } from "./loginSRPDetail";
|
import LoginSRPDetail, { ILoginSRPDetail } from "./loginSRPDetail";
|
||||||
import TokenVersion, { ITokenVersion } from "./tokenVersion";
|
import TokenVersion, { ITokenVersion } from "./tokenVersion";
|
||||||
import GitRisks, { STATUS_RESOLVED_FALSE_POSITIVE } from "./gitRisks";
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AuthProvider,
|
AuthProvider,
|
||||||
@@ -79,7 +78,5 @@ export {
|
|||||||
LoginSRPDetail,
|
LoginSRPDetail,
|
||||||
ILoginSRPDetail,
|
ILoginSRPDetail,
|
||||||
TokenVersion,
|
TokenVersion,
|
||||||
ITokenVersion,
|
ITokenVersion
|
||||||
GitRisks,
|
|
||||||
STATUS_RESOLVED_FALSE_POSITIVE
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import password from "./password";
|
|||||||
import integration from "./integration";
|
import integration from "./integration";
|
||||||
import integrationAuth from "./integrationAuth";
|
import integrationAuth from "./integrationAuth";
|
||||||
import secretsFolder from "./secretsFolder";
|
import secretsFolder from "./secretsFolder";
|
||||||
import secretScanning from "./secretScanning";
|
|
||||||
import webhooks from "./webhook";
|
import webhooks from "./webhook";
|
||||||
import secretImport from "./secretImport";
|
import secretImport from "./secretImport";
|
||||||
|
|
||||||
@@ -37,7 +36,6 @@ export {
|
|||||||
integration,
|
integration,
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
secretsFolder,
|
secretsFolder,
|
||||||
secretScanning,
|
|
||||||
webhooks,
|
webhooks,
|
||||||
secretImport
|
secretImport
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { body, param, query } from "express-validator";
|
|||||||
import { secretsController } from "../../controllers/v3";
|
import { secretsController } from "../../controllers/v3";
|
||||||
import {
|
import {
|
||||||
ADMIN,
|
ADMIN,
|
||||||
MEMBER,
|
|
||||||
AuthMode,
|
AuthMode,
|
||||||
|
MEMBER,
|
||||||
PERMISSION_READ_SECRETS,
|
PERMISSION_READ_SECRETS,
|
||||||
PERMISSION_WRITE_SECRETS,
|
PERMISSION_WRITE_SECRETS,
|
||||||
SECRET_PERSONAL,
|
SECRET_PERSONAL,
|
||||||
@@ -52,7 +52,7 @@ router.get(
|
|||||||
requiredPermissions: [PERMISSION_READ_SECRETS],
|
requiredPermissions: [PERMISSION_READ_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: true,
|
requireE2EEOff: true,
|
||||||
checkIPAllowlist: true
|
checkIPAllowlist: false
|
||||||
}),
|
}),
|
||||||
secretsController.getSecretByNameRaw
|
secretsController.getSecretByNameRaw
|
||||||
);
|
);
|
||||||
@@ -80,7 +80,7 @@ router.post(
|
|||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: true,
|
requireE2EEOff: true,
|
||||||
checkIPAllowlist: true
|
checkIPAllowlist: false
|
||||||
}),
|
}),
|
||||||
secretsController.createSecretRaw
|
secretsController.createSecretRaw
|
||||||
);
|
);
|
||||||
@@ -108,7 +108,7 @@ router.patch(
|
|||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: true,
|
requireE2EEOff: true,
|
||||||
checkIPAllowlist: true
|
checkIPAllowlist: false
|
||||||
}),
|
}),
|
||||||
secretsController.updateSecretByNameRaw
|
secretsController.updateSecretByNameRaw
|
||||||
);
|
);
|
||||||
@@ -135,7 +135,7 @@ router.delete(
|
|||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: true,
|
requireE2EEOff: true,
|
||||||
checkIPAllowlist: true
|
checkIPAllowlist: false
|
||||||
}),
|
}),
|
||||||
secretsController.deleteSecretByNameRaw
|
secretsController.deleteSecretByNameRaw
|
||||||
);
|
);
|
||||||
@@ -160,7 +160,7 @@ router.get(
|
|||||||
requiredPermissions: [PERMISSION_READ_SECRETS],
|
requiredPermissions: [PERMISSION_READ_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: false,
|
requireE2EEOff: false,
|
||||||
checkIPAllowlist: true
|
checkIPAllowlist: false
|
||||||
}),
|
}),
|
||||||
secretsController.getSecrets
|
secretsController.getSecrets
|
||||||
);
|
);
|
||||||
@@ -195,7 +195,7 @@ router.post(
|
|||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: false,
|
requireE2EEOff: false,
|
||||||
checkIPAllowlist: true
|
checkIPAllowlist: false
|
||||||
}),
|
}),
|
||||||
secretsController.createSecret
|
secretsController.createSecret
|
||||||
);
|
);
|
||||||
@@ -221,7 +221,7 @@ router.get(
|
|||||||
locationEnvironment: "query",
|
locationEnvironment: "query",
|
||||||
requiredPermissions: [PERMISSION_READ_SECRETS],
|
requiredPermissions: [PERMISSION_READ_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
checkIPAllowlist: true
|
checkIPAllowlist: false
|
||||||
}),
|
}),
|
||||||
secretsController.getSecretByName
|
secretsController.getSecretByName
|
||||||
);
|
);
|
||||||
@@ -251,7 +251,7 @@ router.patch(
|
|||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: false,
|
requireE2EEOff: false,
|
||||||
checkIPAllowlist: true
|
checkIPAllowlist: false
|
||||||
}),
|
}),
|
||||||
secretsController.updateSecretByName
|
secretsController.updateSecretByName
|
||||||
);
|
);
|
||||||
@@ -278,7 +278,7 @@ router.delete(
|
|||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: false,
|
requireE2EEOff: false,
|
||||||
checkIPAllowlist: true
|
checkIPAllowlist: false
|
||||||
}),
|
}),
|
||||||
secretsController.deleteSecretByName
|
secretsController.deleteSecretByName
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import EventService from "./EventService";
|
|||||||
import IntegrationService from "./IntegrationService";
|
import IntegrationService from "./IntegrationService";
|
||||||
import TokenService from "./TokenService";
|
import TokenService from "./TokenService";
|
||||||
import SecretService from "./SecretService";
|
import SecretService from "./SecretService";
|
||||||
import GithubSecretScanningService from "./GithubSecretScanningService"
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
TelemetryService,
|
TelemetryService,
|
||||||
@@ -18,5 +17,4 @@ export {
|
|||||||
IntegrationService,
|
IntegrationService,
|
||||||
TokenService,
|
TokenService,
|
||||||
SecretService,
|
SecretService,
|
||||||
GithubSecretScanningService
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,12 +144,14 @@ var runCmd = &cobra.Command{
|
|||||||
err = executeMultipleCommandWithEnvs(command, len(secretsByKey), env)
|
err = executeMultipleCommandWithEnvs(command, len(secretsByKey), env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
err = executeSingleCommandWithEnvs(args, len(secretsByKey), env)
|
err = executeSingleCommandWithEnvs(args, len(secretsByKey), env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -111,3 +111,8 @@ You can use it across various environments, whether it's local development, CI/C
|
|||||||
</Tip>
|
</Tip>
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
|
## Quick Usage Guide
|
||||||
|
<Card color="#00A300" href="./usage">
|
||||||
|
Now that you have the CLI installed on your system, follow this guide to make the best use of it
|
||||||
|
</Card>
|
||||||
+7
-7
@@ -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.
|
The distinguishing factor, however, is the authentication method used.
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<Tab title="Local development">
|
<Tab title="Local development only">
|
||||||
To use the Infisical CLI in your local development environment, simply run the command below and follow the interactive guide.
|
To use the Infisical CLI in your local development environment, simply run the command below and follow the interactive guide.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -32,7 +32,7 @@ The distinguishing factor, however, is the authentication method used.
|
|||||||
|
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab title="CI/CD, Production usage, etc">
|
<Tab title="Staging, production & all other use case">
|
||||||
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.
|
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.
|
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.
|
|||||||
<Tabs>
|
<Tabs>
|
||||||
<Tab title="Feed secrets to your application">
|
<Tab title="Feed secrets to your application">
|
||||||
```bash
|
```bash
|
||||||
infisical run -- [your application start command]
|
infisical run --env=dev --path=/apps/firefly -- [your application start command]
|
||||||
|
|
||||||
# example with node (nodemon)
|
# 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
|
# example with flask
|
||||||
infisical run -- flask run
|
infisical run --env=prod --path=/apps/backend -- flask run
|
||||||
|
|
||||||
# example with spring boot - maven
|
# example with spring boot - maven
|
||||||
infisical run -- ./mvnw spring-boot:run --quiet
|
infisical run --env=dev --path=/apps/ -- ./mvnw spring-boot:run --quiet
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="Feed secrets via custom aliases (advanced)">
|
<Tab title="Feed secrets via custom aliases (advanced)">
|
||||||
@@ -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:
|
To make the secrets available from Infisical to `yd`, you can run the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
infisical run --command="source custom.sh && yd"
|
infisical run --env=prod --path=/apps/reddit --command="source custom.sh && yd"
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import Link from "next/link";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { faGithub,faGoogle } from "@fortawesome/free-brands-svg-icons";
|
import { faGithub,faGoogle } from "@fortawesome/free-brands-svg-icons";
|
||||||
import { faEnvelope } from "@fortawesome/free-regular-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 { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { Button } from "../v2";
|
import { Button } from "../v2";
|
||||||
@@ -26,7 +27,7 @@ export default function InitialSignupStep({
|
|||||||
window.close();
|
window.close();
|
||||||
}}
|
}}
|
||||||
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-2" />}
|
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-2" />}
|
||||||
className="h-14 w-full mx-0"
|
className="h-12 w-full mx-0"
|
||||||
>
|
>
|
||||||
{t("signup.continue-with-google")}
|
{t("signup.continue-with-google")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -40,7 +41,7 @@ export default function InitialSignupStep({
|
|||||||
window.close();
|
window.close();
|
||||||
}}
|
}}
|
||||||
leftIcon={<FontAwesomeIcon icon={faGithub} className="mr-2" />}
|
leftIcon={<FontAwesomeIcon icon={faGithub} className="mr-2" />}
|
||||||
className="h-14 w-full mx-0"
|
className="h-12 w-full mx-0"
|
||||||
>
|
>
|
||||||
Continue with GitHub
|
Continue with GitHub
|
||||||
</Button>
|
</Button>
|
||||||
@@ -53,7 +54,7 @@ export default function InitialSignupStep({
|
|||||||
setIsSignupWithEmail(true);
|
setIsSignupWithEmail(true);
|
||||||
}}
|
}}
|
||||||
leftIcon={<FontAwesomeIcon icon={faEnvelope} className="mr-2" />}
|
leftIcon={<FontAwesomeIcon icon={faEnvelope} className="mr-2" />}
|
||||||
className="h-14 w-full mx-0"
|
className="h-12 w-full mx-0"
|
||||||
>
|
>
|
||||||
Continue with Email
|
Continue with Email
|
||||||
</Button>
|
</Button>
|
||||||
@@ -63,10 +64,10 @@ export default function InitialSignupStep({
|
|||||||
colorSchema="primary"
|
colorSchema="primary"
|
||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
onClick={() => router.push("/saml-sso")}
|
onClick={() => router.push("/saml-sso")}
|
||||||
isFullWidth
|
leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />}
|
||||||
className="h-14 w-full mx-0"
|
className="h-12 w-full mx-0"
|
||||||
>
|
>
|
||||||
Continue with SAML SSO
|
Continue with SSO
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] px-8 text-center mt-6 text-xs text-bunker-400'>
|
<div className='lg:w-1/6 w-1/4 min-w-[20rem] px-8 text-center mt-6 text-xs text-bunker-400'>
|
||||||
|
|||||||
@@ -51,10 +51,10 @@ const buttonVariants = cva(
|
|||||||
false: ""
|
false: ""
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
xs: ["text-xs", "py-1", "px-1"],
|
xs: ["text-xs", "py-1", "px-2"],
|
||||||
sm: ["text-sm", "py-2", "px-2"],
|
sm: ["text-sm", "py-2", "px-4"],
|
||||||
md: ["text-md", "py-2", "px-4"],
|
md: ["text-md", "py-2", "px-5"],
|
||||||
lg: ["text-lg", "py-2", "px-8"]
|
lg: ["text-lg", "py-2", "px-6"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
compoundVariants: [
|
compoundVariants: [
|
||||||
@@ -186,16 +186,17 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
className="absolute rounded-xl opacity-80"
|
className="absolute rounded-xl opacity-80"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div
|
{leftIcon && (
|
||||||
className={twMerge(
|
<div
|
||||||
"inline-flex shrink-0 cursor-pointer items-center justify-center transition-all",
|
className={twMerge(
|
||||||
loadingToggleClass,
|
"inline-flex shrink-0 cursor-pointer items-center justify-center transition-all",
|
||||||
leftIcon && "ml-2",
|
loadingToggleClass,
|
||||||
size === "xs" ? "mr-1" : "mr-2"
|
size === "xs" ? "mr-1" : "mr-2"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{leftIcon}
|
{leftIcon}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<span
|
<span
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"transition-all",
|
"transition-all",
|
||||||
@@ -205,15 +206,16 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
<div
|
{rightIcon && (
|
||||||
className={twMerge(
|
<div
|
||||||
"inline-flex shrink-0 cursor-pointer items-center justify-center transition-all",
|
className={twMerge(
|
||||||
loadingToggleClass,
|
"inline-flex shrink-0 cursor-pointer items-center justify-center transition-all",
|
||||||
size === "xs" ? "ml-1" : "ml-2"
|
loadingToggleClass
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{rightIcon}
|
{rightIcon}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|||||||
ref
|
ref
|
||||||
): JSX.Element => {
|
): JSX.Element => {
|
||||||
const handleInput = (event: ChangeEvent<HTMLInputElement>) => {
|
const handleInput = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
console.log(123, props, autoCapitalization)
|
|
||||||
if (autoCapitalization) {
|
if (autoCapitalization) {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
event.target.value = event.target.value.toUpperCase();
|
event.target.value = event.target.value.toUpperCase();
|
||||||
|
|||||||
@@ -54,13 +54,14 @@ export const useGetProjectSecrets = ({
|
|||||||
env,
|
env,
|
||||||
decryptFileKey,
|
decryptFileKey,
|
||||||
isPaused,
|
isPaused,
|
||||||
folderId
|
folderId,
|
||||||
|
secretPath
|
||||||
}: GetProjectSecretsDTO) =>
|
}: GetProjectSecretsDTO) =>
|
||||||
useQuery({
|
useQuery({
|
||||||
// wait for all values to be available
|
// wait for all values to be available
|
||||||
enabled: Boolean(decryptFileKey && workspaceId && env) && !isPaused,
|
enabled: Boolean(decryptFileKey && workspaceId && env) && !isPaused,
|
||||||
queryKey: secretKeys.getProjectSecret(workspaceId, env, folderId),
|
queryKey: secretKeys.getProjectSecret(workspaceId, env, folderId || secretPath),
|
||||||
queryFn: () => fetchProjectEncryptedSecrets(workspaceId, env, folderId),
|
queryFn: () => fetchProjectEncryptedSecrets(workspaceId, env, folderId, secretPath),
|
||||||
select: useCallback(
|
select: useCallback(
|
||||||
(data: EncryptedSecret[]) => {
|
(data: EncryptedSecret[]) => {
|
||||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
|
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
export { useDebounce } from "./useDebounce";
|
||||||
export { useLeaveConfirm } from "./useLeaveConfirm";
|
export { useLeaveConfirm } from "./useLeaveConfirm";
|
||||||
export { usePersistentState } from "./usePersistentState";
|
export { usePersistentState } from "./usePersistentState";
|
||||||
export { usePopUp } from "./usePopUp";
|
export { usePopUp } from "./usePopUp";
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
// Ref: https://usehooks.com/useDebounce/
|
||||||
|
export const useDebounce = <T extends unknown>(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;
|
||||||
|
};
|
||||||
@@ -740,7 +740,7 @@ export const DashboardPage = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto h-full px-6 text-mineshaft-50 dark:[color-scheme:dark]">
|
<div className="container mx-auto h-full px-6 text-mineshaft-50 dark:[color-scheme:dark]">
|
||||||
<form autoComplete="off" className="h-full">
|
<form autoComplete="off" className="h-full flex flex-col">
|
||||||
{/* breadcrumb row */}
|
{/* breadcrumb row */}
|
||||||
<div className="relative right-6 -top-2 mb-2 ml-6">
|
<div className="relative right-6 -top-2 mb-2 ml-6">
|
||||||
<NavHeader
|
<NavHeader
|
||||||
@@ -924,8 +924,8 @@ export const DashboardPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
isEmptyPage ? "flex flex-col items-center justify-center" : ""
|
isEmptyPage ? "flex flex-col flex-grow items-center justify-center" : ""
|
||||||
} no-scrollbar::-webkit-scrollbar mt-3 h-3/4 overflow-x-hidden overflow-y-scroll no-scrollbar`}
|
} no-scrollbar::-webkit-scrollbar mt-3 flex flex-col overflow-x-hidden overflow-y-scroll no-scrollbar`}
|
||||||
ref={secretContainer}
|
ref={secretContainer}
|
||||||
>
|
>
|
||||||
{!isEmptyPage && (
|
{!isEmptyPage && (
|
||||||
@@ -935,7 +935,7 @@ export const DashboardPage = () => {
|
|||||||
collisionDetection={closestCenter}
|
collisionDetection={closestCenter}
|
||||||
modifiers={[restrictToVerticalAxis]}
|
modifiers={[restrictToVerticalAxis]}
|
||||||
>
|
>
|
||||||
<TableContainer className="no-scrollbar::-webkit-scrollbar max-h-[calc(100%-120px)] no-scrollbar">
|
<TableContainer className="no-scrollbar::-webkit-scrollbar max-h-[calc(100%-120px)] no-scrollbar flex-grow">
|
||||||
<table className="secret-table relative">
|
<table className="secret-table relative">
|
||||||
<SecretTableHeader sortDir={sortDir} onSort={onSortSecrets} />
|
<SecretTableHeader sortDir={sortDir} onSort={onSortSecrets} />
|
||||||
<tbody className="max-h-96 overflow-y-auto">
|
<tbody className="max-h-96 overflow-y-auto">
|
||||||
@@ -1017,9 +1017,12 @@ export const DashboardPage = () => {
|
|||||||
</FormProvider>
|
</FormProvider>
|
||||||
|
|
||||||
<SecretDropzone
|
<SecretDropzone
|
||||||
|
workspaceId={workspaceId}
|
||||||
isSmaller={!isEmptyPage}
|
isSmaller={!isEmptyPage}
|
||||||
onParsedEnv={handleUploadedEnv}
|
onParsedEnv={handleUploadedEnv}
|
||||||
onAddNewSecret={onAppendSecret}
|
onAddNewSecret={onAppendSecret}
|
||||||
|
environments={userAvailableEnvs}
|
||||||
|
decryptFileKey={latestFileKey!}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* secrets table and drawers, modals */}
|
{/* secrets table and drawers, modals */}
|
||||||
|
|||||||
@@ -75,6 +75,13 @@ export type FormData = yup.InferType<typeof schema>;
|
|||||||
export type TSecretDetailsOpen = { index: number; id: string };
|
export type TSecretDetailsOpen = { index: number; id: string };
|
||||||
export type TSecOverwriteOpt = { secrets: Record<string, { comments: string[]; value: string }> };
|
export type TSecOverwriteOpt = { secrets: Record<string, { comments: string[]; value: string }> };
|
||||||
|
|
||||||
|
// 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 = (
|
export const downloadSecret = (
|
||||||
secrets: FormData["secrets"] = [],
|
secrets: FormData["secrets"] = [],
|
||||||
importedSecrets: { key: string; value?: string; comment?: string }[] = [],
|
importedSecrets: { key: string; value?: string; comment?: string }[] = [],
|
||||||
@@ -86,9 +93,11 @@ export const downloadSecret = (
|
|||||||
});
|
});
|
||||||
const finalSecret = [...importedSecrets];
|
const finalSecret = [...importedSecrets];
|
||||||
secrets.forEach(({ key, value, valueOverride, overrideAction, comment }) => {
|
secrets.forEach(({ key, value, valueOverride, overrideAction, comment }) => {
|
||||||
|
const finalVal =
|
||||||
|
overrideAction && overrideAction !== SecretActionType.Deleted ? valueOverride : value;
|
||||||
const newValue = {
|
const newValue = {
|
||||||
key,
|
key,
|
||||||
value: overrideAction && overrideAction !== SecretActionType.Deleted ? valueOverride : value,
|
value: formatMultiValueEnv(finalVal),
|
||||||
comment
|
comment
|
||||||
};
|
};
|
||||||
// can also be zero thus failing
|
// can also be zero thus failing
|
||||||
|
|||||||
@@ -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 { 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 { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
import * as yup from "yup";
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
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 { parseDotEnv } from "@app/components/utilities/parseDotEnv";
|
||||||
import { Button } from "@app/components/v2";
|
import {
|
||||||
import { useToggle } from "@app/hooks/useToggle";
|
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<string, yup.StringSchema> = {};
|
||||||
|
Object.keys(val).forEach((key) => {
|
||||||
|
valSchema[key] = yup.string().trim();
|
||||||
|
});
|
||||||
|
return yup.object(valSchema);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
type TFormSchema = yup.InferType<typeof formSchema>;
|
||||||
|
|
||||||
|
const parseJson = (src: ArrayBuffer) => {
|
||||||
|
const file = src.toString();
|
||||||
|
const formatedData: Record<string, string> = JSON.parse(file);
|
||||||
|
const env: Record<string, { value: string; comments: string[] }> = {};
|
||||||
|
Object.keys(formatedData).forEach((key) => {
|
||||||
|
if (typeof formatedData[key] === "string") {
|
||||||
|
env[key] = { value: formatedData[key], comments: [] };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return env;
|
||||||
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isSmaller: boolean;
|
isSmaller: boolean;
|
||||||
onParsedEnv: (env: Record<string, { value: string; comments: string[] }>) => void;
|
onParsedEnv: (env: Record<string, { value: string; comments: string[] }>) => void;
|
||||||
onAddNewSecret?: () => 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 { t } = useTranslation();
|
||||||
const [isDragActive, setDragActive] = useToggle();
|
const [isDragActive, setDragActive] = useToggle();
|
||||||
const [isLoading, setIsLoading] = useToggle();
|
const [isLoading, setIsLoading] = useToggle();
|
||||||
const { createNotification } = useNotificationContext();
|
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<TFormSchema>({
|
||||||
|
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) => {
|
const handleDrag = (e: DragEvent) => {
|
||||||
e.preventDefault();
|
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();
|
const reader = new FileReader();
|
||||||
if (!file) {
|
if (!file) {
|
||||||
createNotification({
|
createNotification({
|
||||||
@@ -47,7 +149,9 @@ export const SecretDropzone = ({ isSmaller, onParsedEnv, onAddNewSecret }: Props
|
|||||||
reader.onload = (event) => {
|
reader.onload = (event) => {
|
||||||
if (!event?.target?.result) return;
|
if (!event?.target?.result) return;
|
||||||
// parse function's argument looks like to be ArrayBuffer
|
// 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();
|
setIsLoading.off();
|
||||||
onParsedEnv(env);
|
onParsedEnv(env);
|
||||||
};
|
};
|
||||||
@@ -74,7 +178,32 @@ export const SecretDropzone = ({ isSmaller, onParsedEnv, onAddNewSecret }: Props
|
|||||||
|
|
||||||
const handleFileUpload = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleFileUpload = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
e.preventDefault();
|
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<string, { value: string; comments: string[] }> = {};
|
||||||
|
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 (
|
return (
|
||||||
@@ -84,9 +213,9 @@ export const SecretDropzone = ({ isSmaller, onParsedEnv, onAddNewSecret }: Props
|
|||||||
onDragOver={handleDrag}
|
onDragOver={handleDrag}
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
className={twMerge(
|
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",
|
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"
|
isLoading && "bg-bunker-800"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -95,35 +224,184 @@ export const SecretDropzone = ({ isSmaller, onParsedEnv, onAddNewSecret }: Props
|
|||||||
<img src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" />
|
<img src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
||||||
<div>
|
<div className="flex items-center justify-cente flex-col space-y-2">
|
||||||
<FontAwesomeIcon icon={faUpload} size={isSmaller ? "2x" : "5x"} />
|
<div>
|
||||||
</div>
|
<FontAwesomeIcon icon={faUpload} size={isSmaller ? "2x" : "5x"} />
|
||||||
<div>
|
</div>
|
||||||
<p className="">{t(isSmaller ? "common.drop-zone-keys" : "common.drop-zone")}</p>
|
<div>
|
||||||
</div>
|
<p className="">{t(isSmaller ? "common.drop-zone-keys" : "common.drop-zone")}</p>
|
||||||
<input
|
</div>
|
||||||
id="fileSelect"
|
<input
|
||||||
type="file"
|
id="fileSelect"
|
||||||
className="absolute h-full w-full cursor-pointer opacity-0"
|
type="file"
|
||||||
accept=".txt,.env,.yml,.yaml"
|
className="absolute h-full w-full cursor-pointer opacity-0"
|
||||||
onChange={handleFileUpload}
|
accept=".txt,.env,.yml,.yaml,.json"
|
||||||
/>
|
onChange={handleFileUpload}
|
||||||
{!isSmaller && (
|
/>
|
||||||
<>
|
<div
|
||||||
<div className="flex w-full flex-row items-center justify-center py-4">
|
className={twMerge(
|
||||||
<div className="w-1/5 border-t border-mineshaft-700" />
|
"flex w-full flex-row items-center justify-center py-4",
|
||||||
<p className="mx-4 text-xs text-mineshaft-400">OR</p>
|
isSmaller && "py-1"
|
||||||
<div className="w-1/5 border-t border-mineshaft-700" />
|
)}
|
||||||
</div>
|
>
|
||||||
<div>
|
<div className="w-1/5 border-t border-mineshaft-700" />
|
||||||
|
<p className="mx-4 text-xs text-mineshaft-400">OR</p>
|
||||||
|
<div className="w-1/5 border-t border-mineshaft-700" />
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-center space-x-8">
|
||||||
|
<Modal
|
||||||
|
isOpen={popUp.importSecEnv.isOpen}
|
||||||
|
onOpenChange={(isOpen) => {
|
||||||
|
handlePopUpToggle("importSecEnv", isOpen);
|
||||||
|
reset();
|
||||||
|
setSearchFilter("");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ModalTrigger asChild>
|
||||||
|
<Button variant="star" size={isSmaller ? "xs" : "sm"}>
|
||||||
|
Copy Secrets From An Environment
|
||||||
|
</Button>
|
||||||
|
</ModalTrigger>
|
||||||
|
<ModalContent
|
||||||
|
className="max-w-2xl"
|
||||||
|
title="Copy Secret From An Environment"
|
||||||
|
subTitle="Copy/paste secrets from other environments into this context"
|
||||||
|
>
|
||||||
|
<form>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="environment"
|
||||||
|
render={({ field: { value, onChange } }) => (
|
||||||
|
<FormControl label="Environment" isRequired className="w-1/3">
|
||||||
|
<Select
|
||||||
|
value={value}
|
||||||
|
onValueChange={(val) => onChange(val)}
|
||||||
|
className="w-full border border-mineshaft-500"
|
||||||
|
defaultValue={environments?.[0]?.slug}
|
||||||
|
position="popper"
|
||||||
|
>
|
||||||
|
{environments.map((sourceEnvironment) => (
|
||||||
|
<SelectItem
|
||||||
|
value={sourceEnvironment.slug}
|
||||||
|
key={`source-environment-${sourceEnvironment.slug}`}
|
||||||
|
>
|
||||||
|
{sourceEnvironment.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormControl label="Secret Path" className="flex-grow" isRequired>
|
||||||
|
<Input
|
||||||
|
{...register("secretPath")}
|
||||||
|
placeholder="Provide a path, default is /"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
<div className="border-t border-mineshaft-600 pt-4">
|
||||||
|
<div className="mb-4 flex items-center justify-between">
|
||||||
|
<div>Secrets</div>
|
||||||
|
<div className="w-1/2 flex items-center space-x-2">
|
||||||
|
<Input
|
||||||
|
placeholder="Search for secret"
|
||||||
|
value={searchFilter}
|
||||||
|
size="xs"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faSearch} />}
|
||||||
|
onChange={(evt) => setSearchFilter(evt.target.value)}
|
||||||
|
/>
|
||||||
|
<Tooltip content="Select All">
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="Select all"
|
||||||
|
variant="outline_bg"
|
||||||
|
size="xs"
|
||||||
|
onClick={handleSecSelectAll}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faSquareCheck} size="lg" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="Unselect All">
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="UnSelect all"
|
||||||
|
variant="outline_bg"
|
||||||
|
size="xs"
|
||||||
|
onClick={() => reset()}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faSquareXmark} size="lg" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{!isSecretsLoading && !secrets?.secrets?.length && (
|
||||||
|
<EmptyState title="No secrets found" icon={faKey} />
|
||||||
|
)}
|
||||||
|
<div className="grid grid-cols-2 gap-4 max-h-64 overflow-auto thin-scrollbar ">
|
||||||
|
{isSecretsLoading &&
|
||||||
|
Array.apply(0, Array(2)).map((_x, i) => (
|
||||||
|
<Skeleton
|
||||||
|
key={`secret-pull-loading-${i + 1}`}
|
||||||
|
className="bg-mineshaft-700"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{secrets?.secrets
|
||||||
|
?.filter(({ key }) =>
|
||||||
|
key.toLowerCase().includes(searchFilter.toLowerCase())
|
||||||
|
)
|
||||||
|
?.map(({ _id, key, value: secVal }) => (
|
||||||
|
<Controller
|
||||||
|
key={`pull-secret--${_id}`}
|
||||||
|
control={control}
|
||||||
|
name={`secrets.${key}`}
|
||||||
|
render={({ field: { value, onChange } }) => (
|
||||||
|
<Checkbox
|
||||||
|
id={`pull-secret-${_id}`}
|
||||||
|
isChecked={Boolean(value)}
|
||||||
|
onCheckedChange={(isChecked) => onChange(isChecked ? secVal : "")}
|
||||||
|
>
|
||||||
|
{key}
|
||||||
|
</Checkbox>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 mb-4">
|
||||||
|
<Checkbox
|
||||||
|
id="populate-include-value"
|
||||||
|
isChecked={shouldIncludeValues}
|
||||||
|
onCheckedChange={(isChecked) =>
|
||||||
|
setShouldIncludeValues(isChecked as boolean)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Include secret values
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Button
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faClone} />}
|
||||||
|
type="submit"
|
||||||
|
isDisabled={!isDirty}
|
||||||
|
>
|
||||||
|
Paste Secrets
|
||||||
|
</Button>
|
||||||
|
<Button variant="plain" colorSchema="secondary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
{!isSmaller && (
|
||||||
<Button variant="star" onClick={onAddNewSecret}>
|
<Button variant="star" onClick={onAddNewSecret}>
|
||||||
Add a new secret
|
Add a new secret
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
)}
|
||||||
</>
|
</div>
|
||||||
)}{" "}
|
</div>
|
||||||
</>
|
</form>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -281,7 +281,6 @@ export const SecretInputRow = memo(
|
|||||||
value={editorRef}
|
value={editorRef}
|
||||||
isVisible={!isSecretValueHidden}
|
isVisible={!isSecretValueHidden}
|
||||||
onChange={(val, html) => {
|
onChange={(val, html) => {
|
||||||
console.log(val);
|
|
||||||
onChange(val);
|
onChange(val);
|
||||||
setEditorRef(html);
|
setEditorRef(html);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { faGithub,faGoogle } from "@fortawesome/free-brands-svg-icons";
|
import { faGithub,faGoogle } from "@fortawesome/free-brands-svg-icons";
|
||||||
|
import { faLock } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import axios from "axios"
|
import axios from "axios"
|
||||||
|
|
||||||
@@ -116,47 +117,6 @@ export const InitialStep = ({
|
|||||||
return (
|
return (
|
||||||
<form onSubmit={handleLogin} className='flex flex-col mx-auto w-full justify-center items-center'>
|
<form onSubmit={handleLogin} className='flex flex-col mx-auto w-full justify-center items-center'>
|
||||||
<h1 className='text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8' >Login to Infisical</h1>
|
<h1 className='text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8' >Login to Infisical</h1>
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] flex flex-row items-center my-4 py-2'>
|
|
||||||
<div className='w-full border-t border-mineshaft-500' />
|
|
||||||
</div>
|
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md'>
|
|
||||||
<Input
|
|
||||||
value={email}
|
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
|
||||||
type="email"
|
|
||||||
placeholder="Enter your email..."
|
|
||||||
isRequired
|
|
||||||
autoComplete="username"
|
|
||||||
className="h-12"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md mt-4'>
|
|
||||||
<Input
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
type="password"
|
|
||||||
placeholder="Enter your password..."
|
|
||||||
isRequired
|
|
||||||
autoComplete="current-password"
|
|
||||||
id="current-password"
|
|
||||||
className="h-12 select:-webkit-autofill:focus"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md mt-4'>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
size="sm"
|
|
||||||
isFullWidth
|
|
||||||
className='h-12'
|
|
||||||
colorSchema="primary"
|
|
||||||
variant="solid"
|
|
||||||
isLoading={isLoading}
|
|
||||||
> Continue with Email </Button>
|
|
||||||
</div>
|
|
||||||
{!isLoading && loginError && <Error text={t("login.error-login") ?? ""} />}
|
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] flex flex-row items-center mt-4 py-2'>
|
|
||||||
<div className='w-full border-t border-mineshaft-500' />
|
|
||||||
</div>
|
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md mt-4'>
|
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md mt-4'>
|
||||||
<Button
|
<Button
|
||||||
colorSchema="primary"
|
colorSchema="primary"
|
||||||
@@ -168,7 +128,7 @@ export const InitialStep = ({
|
|||||||
window.close();
|
window.close();
|
||||||
}}
|
}}
|
||||||
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-2" />}
|
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-2" />}
|
||||||
className="h-12 w-full mx-0"
|
className="h-11 w-full mx-0"
|
||||||
>
|
>
|
||||||
{t("login.continue-with-google")}
|
{t("login.continue-with-google")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -185,7 +145,7 @@ export const InitialStep = ({
|
|||||||
window.close();
|
window.close();
|
||||||
}}
|
}}
|
||||||
leftIcon={<FontAwesomeIcon icon={faGithub} className="mr-2" />}
|
leftIcon={<FontAwesomeIcon icon={faGithub} className="mr-2" />}
|
||||||
className="h-12 w-full mx-0"
|
className="h-11 w-full mx-0"
|
||||||
>
|
>
|
||||||
Continue with GitHub
|
Continue with GitHub
|
||||||
</Button>
|
</Button>
|
||||||
@@ -197,12 +157,52 @@ export const InitialStep = ({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setStep(2);
|
setStep(2);
|
||||||
}}
|
}}
|
||||||
isFullWidth
|
leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />}
|
||||||
className="h-12 w-full mx-0"
|
className="h-11 w-full mx-0"
|
||||||
>
|
>
|
||||||
Continue with SAML SSO
|
Continue with SSO
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<div className='lg:w-1/6 w-1/4 min-w-[20rem] flex flex-row items-center my-4 py-2'>
|
||||||
|
<div className='w-full border-t border-mineshaft-400/60' />
|
||||||
|
<span className="mx-2 text-mineshaft-200 text-xs">or</span>
|
||||||
|
<div className='w-full border-t border-mineshaft-400/60' />
|
||||||
|
</div>
|
||||||
|
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md'>
|
||||||
|
<Input
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
type="email"
|
||||||
|
placeholder="Enter your email..."
|
||||||
|
isRequired
|
||||||
|
autoComplete="username"
|
||||||
|
className="h-11"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md mt-4'>
|
||||||
|
<Input
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
type="password"
|
||||||
|
placeholder="Enter your password..."
|
||||||
|
isRequired
|
||||||
|
autoComplete="current-password"
|
||||||
|
id="current-password"
|
||||||
|
className="h-11 select:-webkit-autofill:focus"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md mt-5'>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
size="sm"
|
||||||
|
isFullWidth
|
||||||
|
className='h-11'
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="solid"
|
||||||
|
isLoading={isLoading}
|
||||||
|
> Continue with Email </Button>
|
||||||
|
</div>
|
||||||
|
{!isLoading && loginError && <Error text={t("login.error-login") ?? ""} />}
|
||||||
{
|
{
|
||||||
!serverDetails?.inviteOnlySignup ?
|
!serverDetails?.inviteOnlySignup ?
|
||||||
<div className="mt-6 text-bunker-400 text-sm flex flex-row">
|
<div className="mt-6 text-bunker-400 text-sm flex flex-row">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import crypto from "crypto";
|
|||||||
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
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 { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import jsrp from "jsrp";
|
import jsrp from "jsrp";
|
||||||
import nacl from "tweetnacl";
|
import nacl from "tweetnacl";
|
||||||
@@ -198,7 +198,7 @@ export const UserInfoSSOStep = ({
|
|||||||
localStorage.setItem("orgData.id", orgId);
|
localStorage.setItem("orgData.id", orgId);
|
||||||
localStorage.setItem("projectData.id", project._id);
|
localStorage.setItem("projectData.id", project._id);
|
||||||
|
|
||||||
setStep(1);
|
setStep(1);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -256,7 +256,7 @@ export const UserInfoSSOStep = ({
|
|||||||
)}
|
)}
|
||||||
<div className="mt-2 flex lg:w-1/6 w-1/4 min-w-[20rem] max-h-60 w-full flex-col items-center justify-center rounded-lg py-2">
|
<div className="mt-2 flex lg:w-1/6 w-1/4 min-w-[20rem] max-h-60 w-full flex-col items-center justify-center rounded-lg py-2">
|
||||||
<InputField
|
<InputField
|
||||||
label={t("section.password.password")}
|
label="Infisical Password"
|
||||||
onChangeHandler={(pass: string) => {
|
onChangeHandler={(pass: string) => {
|
||||||
setPassword(pass);
|
setPassword(pass);
|
||||||
checkPassword({
|
checkPassword({
|
||||||
@@ -272,6 +272,7 @@ export const UserInfoSSOStep = ({
|
|||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
id="new-password"
|
id="new-password"
|
||||||
/>
|
/>
|
||||||
|
<div className="mt-2 w-min min-w-[20rem] max-h-60 flex-col items-center justify-center rounded-md px-1.5 bg-mineshaft-500 text-mineshaft-300 text-xs p-1.5"><FontAwesomeIcon icon={faInfoCircle} className="mr-1.5" />Infisical Password is used as part of the encryption mechanism so that even the authentication provider is not able to access your secrets.</div>
|
||||||
{Object.keys(errors).length > 0 && (
|
{Object.keys(errors).length > 0 && (
|
||||||
<div className="mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-2 py-2">
|
<div className="mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-2 py-2">
|
||||||
<div className="mb-2 text-sm text-gray-400">{t("section.password.validate-base")}</div>
|
<div className="mb-2 text-sm text-gray-400">{t("section.password.validate-base")}</div>
|
||||||
@@ -307,7 +308,7 @@ export const UserInfoSSOStep = ({
|
|||||||
onClick={signupErrorCheck}
|
onClick={signupErrorCheck}
|
||||||
size="sm"
|
size="sm"
|
||||||
isFullWidth
|
isFullWidth
|
||||||
className='h-14'
|
className='h-12'
|
||||||
colorSchema="primary"
|
colorSchema="primary"
|
||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
|||||||
Reference in New Issue
Block a user