Skip to content

Commit

Permalink
Merge pull request #2726 from Infisical/scott/paste-secrets
Browse files Browse the repository at this point in the history
Feat: Paste Secrets for Upload
  • Loading branch information
scott-ray-wilson authored Nov 13, 2024
2 parents dd1cabf + c8109b4 commit 60fb195
Show file tree
Hide file tree
Showing 13 changed files with 403 additions and 60 deletions.
87 changes: 87 additions & 0 deletions backend/src/server/routes/v1/dashboard-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -840,4 +840,91 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
};
}
});

server.route({
method: "GET",
url: "/secrets-by-keys",
config: {
rateLimit: secretsLimit
},
schema: {
security: [
{
bearerAuth: []
}
],
querystring: z.object({
projectId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
keys: z.string().trim().transform(decodeURIComponent)
}),
response: {
200: z.object({
secrets: secretRawSchema
.extend({
secretPath: z.string().optional(),
tags: SecretTagsSchema.pick({
id: true,
slug: true,
color: true
})
.extend({ name: z.string() })
.array()
.optional()
})
.array()
.optional()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { secretPath, projectId, environment } = req.query;

const keys = req.query.keys?.split(",").filter((key) => Boolean(key.trim())) ?? [];
if (!keys.length) throw new BadRequestError({ message: "One or more keys required" });

const { secrets } = await server.services.secret.getSecretsRaw({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
environment,
actorAuthMethod: req.permission.authMethod,
projectId,
path: secretPath,
keys
});

await server.services.auditLog.createAuditLog({
projectId,
...req.auditLogInfo,
event: {
type: EventType.GET_SECRETS,
metadata: {
environment,
secretPath,
numberOfSecrets: secrets.length
}
}
});

if (getUserAgentType(req.headers["user-agent"]) !== UserAgentType.K8_OPERATOR) {
await server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.SecretPulled,
distinctId: getTelemetryDistinctId(req),
properties: {
numberOfSecrets: secrets.length,
workspaceId: projectId,
environment,
secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
...req.auditLogInfo
}
});
}

return { secrets };
}
});
};
4 changes: 4 additions & 0 deletions backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,10 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
void bd.whereILike(`${TableName.SecretV2}.key`, `%${filters?.search}%`);
}
}

if (filters?.keys) {
void bd.whereIn(`${TableName.SecretV2}.key`, filters.keys);
}
})
.where((bd) => {
void bd.whereNull(`${TableName.SecretV2}.userId`).orWhere({ userId: userId || null });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export type TGetSecretsDTO = {
offset?: number;
limit?: number;
search?: string;
keys?: string[];
} & TProjectPermission;

export type TGetASecretDTO = {
Expand Down Expand Up @@ -294,6 +295,7 @@ export type TFindSecretsByFolderIdsFilter = {
search?: string;
tagSlugs?: string[];
includeTagsInSearch?: boolean;
keys?: string[];
};

export type TGetSecretsRawByFolderMappingsDTO = {
Expand Down
1 change: 1 addition & 0 deletions backend/src/services/secret/secret-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ export type TGetSecretsRawDTO = {
offset?: number;
limit?: number;
search?: string;
keys?: string[];
} & TProjectPermission;

export type TGetASecretRawDTO = {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/dashboard/DropZone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { SecretType } from "@app/hooks/api/types";
import Button from "../basic/buttons/Button";
import Error from "../basic/Error";
import { createNotification } from "../notifications";
import { parseDotEnv } from "../utilities/parseDotEnv";
import { parseDotEnv } from "../utilities/parseSecrets";
import guidGenerator from "../utilities/randomId";

interface DropZoneProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const LINE =
* @param {ArrayBuffer} src - source buffer
* @returns {String} text - text of buffer
*/
export function parseDotEnv(src: ArrayBuffer) {
export function parseDotEnv(src: ArrayBuffer | string) {
const object: {
[key: string]: { value: string; comments: string[] };
} = {};
Expand Down Expand Up @@ -65,3 +65,15 @@ export function parseDotEnv(src: ArrayBuffer) {

return object;
}

export const parseJson = (src: ArrayBuffer | string) => {
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;
};
5 changes: 4 additions & 1 deletion frontend/src/components/v2/FormControl/FormControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export type FormControlProps = {
className?: string;
icon?: ReactNode;
tooltipText?: ReactElement | string;
tooltipClassName?: string;
};

export const FormControl = ({
Expand All @@ -96,7 +97,8 @@ export const FormControl = ({
isError,
icon,
className,
tooltipText
tooltipText,
tooltipClassName
}: FormControlProps): JSX.Element => {
return (
<div className={twMerge("mb-4", className)}>
Expand All @@ -108,6 +110,7 @@ export const FormControl = ({
id={id}
icon={icon}
tooltipText={tooltipText}
tooltipClassName={tooltipClassName}
/>
) : (
label
Expand Down
19 changes: 19 additions & 0 deletions frontend/src/hooks/api/dashboard/queries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import axios from "axios";
import { createNotification } from "@app/components/notifications";
import { apiRequest } from "@app/config/request";
import {
DashboardProjectSecretsByKeys,
DashboardProjectSecretsDetails,
DashboardProjectSecretsDetailsResponse,
DashboardProjectSecretsOverview,
DashboardProjectSecretsOverviewResponse,
DashboardSecretsOrderBy,
TDashboardProjectSecretsQuickSearch,
TDashboardProjectSecretsQuickSearchResponse,
TGetDashboardProjectSecretsByKeys,
TGetDashboardProjectSecretsDetailsDTO,
TGetDashboardProjectSecretsOverviewDTO,
TGetDashboardProjectSecretsQuickSearchDTO
Expand Down Expand Up @@ -101,6 +103,23 @@ export const fetchProjectSecretsDetails = async ({
return data;
};

export const fetchDashboardProjectSecretsByKeys = async ({
keys,
...params
}: TGetDashboardProjectSecretsByKeys) => {
const { data } = await apiRequest.get<DashboardProjectSecretsByKeys>(
"/api/v1/dashboard/secrets-by-keys",
{
params: {
...params,
keys: encodeURIComponent(keys.join(","))
}
}
);

return data;
};

export const useGetProjectSecretsOverview = (
{
projectId,
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/hooks/api/dashboard/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ export type DashboardProjectSecretsDetailsResponse = {
totalCount: number;
};

export type DashboardProjectSecretsByKeys = {
secrets: SecretV3Raw[];
};

export type DashboardProjectSecretsOverview = Omit<
DashboardProjectSecretsOverviewResponse,
"secrets"
Expand Down Expand Up @@ -89,3 +93,10 @@ export type TGetDashboardProjectSecretsQuickSearchDTO = {
search: string;
environments: string[];
};

export type TGetDashboardProjectSecretsByKeys = {
projectId: string;
secretPath: string;
environment: string;
keys: string[];
};
1 change: 0 additions & 1 deletion frontend/src/views/SecretMainPage/SecretMainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,6 @@ const SecretMainPageContent = () => {
</ModalContent>
</Modal>
<SecretDropzone
secrets={secrets}
environment={environment}
workspaceId={workspaceId}
secretPath={secretPath}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Controller, useForm } from "react-hook-form";
import { subject } from "@casl/ability";
import {
faClone,
faFileImport,
faKey,
faSearch,
faSquareCheck,
Expand Down Expand Up @@ -151,6 +152,7 @@ export const CopySecretsFromBoard = ({
>
{(isAllowed) => (
<Button
leftIcon={<FontAwesomeIcon icon={faFileImport} />}
onClick={() => onToggle(true)}
isDisabled={!isAllowed}
variant="star"
Expand Down
Loading

0 comments on commit 60fb195

Please sign in to comment.