mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-17 12:56:29 +00:00
Merge branch 'main' into feature/slack-secret-sync-error-notification
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import { ProjectType } from "@app/db/schemas";
|
||||
import { TAppConnections } from "@app/db/schemas/app-connections";
|
||||
import {
|
||||
ChefConnectionMethod,
|
||||
getChefConnectionListItem,
|
||||
validateChefConnectionCredentials
|
||||
} from "@app/ee/services/app-connections/chef";
|
||||
import {
|
||||
getOCIConnectionListItem,
|
||||
OCIConnectionMethod,
|
||||
@@ -68,7 +73,6 @@ import {
|
||||
} from "./bitbucket";
|
||||
import { CamundaConnectionMethod, getCamundaConnectionListItem, validateCamundaConnectionCredentials } from "./camunda";
|
||||
import { ChecklyConnectionMethod, getChecklyConnectionListItem, validateChecklyConnectionCredentials } from "./checkly";
|
||||
import { ChefConnectionMethod, getChefConnectionListItem, validateChefConnectionCredentials } from "./chef";
|
||||
import { CloudflareConnectionMethod } from "./cloudflare/cloudflare-connection-enum";
|
||||
import {
|
||||
getCloudflareConnectionListItem,
|
||||
|
||||
@@ -86,6 +86,6 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
|
||||
[AppConnection.Netlify]: AppConnectionPlanType.Regular,
|
||||
[AppConnection.Okta]: AppConnectionPlanType.Regular,
|
||||
[AppConnection.Redis]: AppConnectionPlanType.Regular,
|
||||
[AppConnection.Chef]: AppConnectionPlanType.Regular,
|
||||
[AppConnection.Chef]: AppConnectionPlanType.Enterprise,
|
||||
[AppConnection.Northflank]: AppConnectionPlanType.Regular
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType, OrganizationActionScope, TAppConnections } from "@app/db/schemas";
|
||||
import { ValidateChefConnectionCredentialsSchema } from "@app/ee/services/app-connections/chef";
|
||||
import { chefConnectionService } from "@app/ee/services/app-connections/chef/chef-connection-service";
|
||||
import { ValidateOCIConnectionCredentialsSchema } from "@app/ee/services/app-connections/oci";
|
||||
import { ociConnectionService } from "@app/ee/services/app-connections/oci/oci-connection-service";
|
||||
import { ValidateOracleDBConnectionCredentialsSchema } from "@app/ee/services/app-connections/oracledb";
|
||||
@@ -67,8 +69,6 @@ import { ValidateCamundaConnectionCredentialsSchema } from "./camunda";
|
||||
import { camundaConnectionService } from "./camunda/camunda-connection-service";
|
||||
import { ValidateChecklyConnectionCredentialsSchema } from "./checkly";
|
||||
import { checklyConnectionService } from "./checkly/checkly-connection-service";
|
||||
import { ValidateChefConnectionCredentialsSchema } from "./chef";
|
||||
import { chefConnectionService } from "./chef/chef-connection-service";
|
||||
import { ValidateCloudflareConnectionCredentialsSchema } from "./cloudflare/cloudflare-connection-schema";
|
||||
import { cloudflareConnectionService } from "./cloudflare/cloudflare-connection-service";
|
||||
import { ValidateDatabricksConnectionCredentialsSchema } from "./databricks";
|
||||
@@ -885,6 +885,6 @@ export const appConnectionServiceFactory = ({
|
||||
northflank: northflankConnectionService(connectAppConnectionById),
|
||||
okta: oktaConnectionService(connectAppConnectionById),
|
||||
laravelForge: laravelForgeConnectionService(connectAppConnectionById),
|
||||
chef: chefConnectionService(connectAppConnectionById)
|
||||
chef: chefConnectionService(connectAppConnectionById, licenseService)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import {
|
||||
TChefConnection,
|
||||
TChefConnectionConfig,
|
||||
TChefConnectionInput,
|
||||
TValidateChefConnectionCredentialsSchema
|
||||
} from "@app/ee/services/app-connections/chef";
|
||||
import {
|
||||
TOCIConnection,
|
||||
TOCIConnectionConfig,
|
||||
@@ -82,12 +88,6 @@ import {
|
||||
TChecklyConnectionInput,
|
||||
TValidateChecklyConnectionCredentialsSchema
|
||||
} from "./checkly";
|
||||
import {
|
||||
TChefConnection,
|
||||
TChefConnectionConfig,
|
||||
TChefConnectionInput,
|
||||
TValidateChefConnectionCredentialsSchema
|
||||
} from "./chef";
|
||||
import {
|
||||
TCloudflareConnection,
|
||||
TCloudflareConnectionConfig,
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export enum ChefConnectionMethod {
|
||||
UserKey = "user-key"
|
||||
}
|
||||
@@ -1,288 +0,0 @@
|
||||
import { AxiosError } from "axios";
|
||||
import crypto from "crypto";
|
||||
|
||||
import { request } from "@app/lib/config/request";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||
|
||||
import { TChefDataBagItemContent } from "../../secret-sync/chef/chef-sync-types";
|
||||
import { AppConnection } from "../app-connection-enums";
|
||||
import { ChefConnectionMethod } from "./chef-connection-enums";
|
||||
import {
|
||||
TChefConnection,
|
||||
TChefConnectionConfig,
|
||||
TChefDataBag,
|
||||
TChefDataBagItem,
|
||||
TGetChefDataBagItem,
|
||||
TUpdateChefDataBagItem
|
||||
} from "./chef-connection-types";
|
||||
|
||||
export const getChefServerUrl = async (serverUrl?: string) => {
|
||||
const chefServerUrl = serverUrl ? removeTrailingSlash(serverUrl) : IntegrationUrls.CHEF_API_URL;
|
||||
|
||||
await blockLocalAndPrivateIpAddresses(chefServerUrl);
|
||||
|
||||
return chefServerUrl;
|
||||
};
|
||||
|
||||
// Helper to ensure private key is in proper PEM format
|
||||
const formatPrivateKey = (key: string): string => {
|
||||
let formattedKey = key.trim();
|
||||
|
||||
// Ensure proper line breaks in PEM format (handle escaped newlines)
|
||||
formattedKey = formattedKey.replace(/\\n/g, "\n");
|
||||
|
||||
// Remove any extra whitespace between lines
|
||||
formattedKey = formattedKey.replace(/\n\s+/g, "\n");
|
||||
|
||||
// If key doesn't have headers, add PKCS#1 RSA headers
|
||||
if (!formattedKey.includes("BEGIN")) {
|
||||
formattedKey = `-----BEGIN RSA PRIVATE KEY-----\n${formattedKey}\n-----END RSA PRIVATE KEY-----`;
|
||||
}
|
||||
|
||||
// Ensure the key has proper line breaks after headers and before footers
|
||||
formattedKey = formattedKey.replace(/(-----BEGIN[^-]+-----)\s*/g, "$1\n").replace(/\s*(-----END[^-]+-----)/g, "\n$1");
|
||||
|
||||
// Remove any duplicate newlines
|
||||
formattedKey = formattedKey.replace(/\n{3,}/g, "\n\n");
|
||||
|
||||
return formattedKey;
|
||||
};
|
||||
|
||||
const getChefAuthHeaders = (
|
||||
method: string,
|
||||
path: string,
|
||||
body: string,
|
||||
userId: string,
|
||||
privateKey: string,
|
||||
apiVersion: "1.0" | "1.3" = "1.3"
|
||||
) => {
|
||||
const timestamp = new Date().toISOString().replace(/\.\d{3}Z$/, "Z"); // Remove milliseconds from timestamp
|
||||
|
||||
// Calculate content hash based on version
|
||||
let contentHash: string;
|
||||
if (apiVersion === "1.3") {
|
||||
contentHash = crypto.createHash("sha256").update(body).digest("base64");
|
||||
} else {
|
||||
contentHash = crypto.createHash("sha1").update(body).digest("base64");
|
||||
}
|
||||
|
||||
// Build canonical request based on version
|
||||
let canonicalRequest: string;
|
||||
if (apiVersion === "1.3") {
|
||||
canonicalRequest = [
|
||||
`Method:${method}`,
|
||||
`Path:${path}`,
|
||||
`X-Ops-Content-Hash:${contentHash}`,
|
||||
"X-Ops-Sign:version=1.3",
|
||||
`X-Ops-Timestamp:${timestamp}`,
|
||||
`X-Ops-UserId:${userId}`,
|
||||
"X-Ops-Server-API-Version:1"
|
||||
].join("\n");
|
||||
} else {
|
||||
const hashedPath = crypto.createHash("sha1").update(path).digest("base64");
|
||||
canonicalRequest = [
|
||||
`Method:${method}`,
|
||||
`Hashed Path:${hashedPath}`,
|
||||
`X-Ops-Content-Hash:${contentHash}`,
|
||||
`X-Ops-Timestamp:${timestamp}`,
|
||||
`X-Ops-UserId:${userId}`
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
// Format the private key properly
|
||||
const formattedKey = formatPrivateKey(privateKey);
|
||||
|
||||
// Sign the canonical request
|
||||
const sign = crypto.createSign(apiVersion === "1.3" ? "RSA-SHA256" : "RSA-SHA1");
|
||||
sign.update(canonicalRequest);
|
||||
const signature = sign.sign(formattedKey, "base64");
|
||||
|
||||
// Split signature into 60-character chunks
|
||||
const authHeaders: Record<string, string> = {};
|
||||
const signatureLines = signature.match(/.{1,60}/g) || [];
|
||||
signatureLines.forEach((line, index) => {
|
||||
authHeaders[`X-Ops-Authorization-${index + 1}`] = line;
|
||||
});
|
||||
|
||||
return {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"X-Chef-Version": "14.0.0",
|
||||
"X-Ops-Timestamp": timestamp,
|
||||
"X-Ops-UserId": userId,
|
||||
"X-Ops-Sign": apiVersion === "1.3" ? "version=1.3" : "algorithm=sha1;version=1.0",
|
||||
"X-Ops-Content-Hash": contentHash,
|
||||
...(apiVersion === "1.3" && { "X-Ops-Server-API-Version": "1" }),
|
||||
...authHeaders
|
||||
};
|
||||
};
|
||||
|
||||
export const getChefConnectionListItem = () => {
|
||||
return {
|
||||
name: "Chef" as const,
|
||||
app: AppConnection.Chef as const,
|
||||
methods: Object.values(ChefConnectionMethod) as [ChefConnectionMethod.UserKey]
|
||||
};
|
||||
};
|
||||
|
||||
export const validateChefConnectionCredentials = async (config: TChefConnectionConfig) => {
|
||||
const { credentials: inputCredentials } = config;
|
||||
|
||||
try {
|
||||
const path = `/organizations/${inputCredentials.orgName}/users/${inputCredentials.userName}`;
|
||||
|
||||
const hostServerUrl = await getChefServerUrl(inputCredentials.serverUrl);
|
||||
|
||||
const headers = getChefAuthHeaders("GET", path, "", inputCredentials.userName, inputCredentials.privateKey);
|
||||
|
||||
await request.get(`${hostServerUrl}${path}`, {
|
||||
headers
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) {
|
||||
throw new BadRequestError({
|
||||
message: `Failed to validate Chef credentials: ${error.message || "Unknown error"}`
|
||||
});
|
||||
}
|
||||
throw new BadRequestError({
|
||||
message: "Unable to validate Chef connection: verify credentials"
|
||||
});
|
||||
}
|
||||
|
||||
return inputCredentials;
|
||||
};
|
||||
|
||||
export const listChefDataBags = async (appConnection: TChefConnection): Promise<TChefDataBag[]> => {
|
||||
const {
|
||||
credentials: { serverUrl, userName, privateKey, orgName }
|
||||
} = appConnection;
|
||||
|
||||
try {
|
||||
const path = `/organizations/${orgName}/data`;
|
||||
const body = "";
|
||||
|
||||
const hostServerUrl = await getChefServerUrl(serverUrl);
|
||||
|
||||
const headers = getChefAuthHeaders("GET", path, body, userName, privateKey);
|
||||
|
||||
const res = await request.get<Record<string, string>>(`${hostServerUrl}${path}`, {
|
||||
headers
|
||||
});
|
||||
|
||||
return Object.keys(res.data).map((name) => ({
|
||||
name
|
||||
}));
|
||||
} catch (error) {
|
||||
if (error instanceof AxiosError) {
|
||||
throw new BadRequestError({
|
||||
message: `Failed to list Chef data bags: ${error.message || "Unknown error"}`
|
||||
});
|
||||
}
|
||||
throw new BadRequestError({
|
||||
message: "Unable to list Chef data bags"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const listChefDataBagItems = async (
|
||||
appConnection: TChefConnection,
|
||||
dataBagName: string
|
||||
): Promise<TChefDataBagItem[]> => {
|
||||
const {
|
||||
credentials: { serverUrl, userName, privateKey, orgName }
|
||||
} = appConnection;
|
||||
|
||||
try {
|
||||
const path = `/organizations/${orgName}/data/${dataBagName}`;
|
||||
const body = "";
|
||||
|
||||
const hostServerUrl = await getChefServerUrl(serverUrl);
|
||||
|
||||
const headers = getChefAuthHeaders("GET", path, body, userName, privateKey);
|
||||
|
||||
const res = await request.get<Record<string, string>>(`${hostServerUrl}${path}`, {
|
||||
headers
|
||||
});
|
||||
|
||||
return Object.keys(res.data).map((name) => ({
|
||||
name
|
||||
}));
|
||||
} catch (error) {
|
||||
if (error instanceof AxiosError) {
|
||||
throw new BadRequestError({
|
||||
message: `Failed to list Chef data bag items: ${error.message || "Unknown error"}`
|
||||
});
|
||||
}
|
||||
throw new BadRequestError({
|
||||
message: "Unable to list Chef data bag items"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const getChefDataBagItem = async ({
|
||||
serverUrl,
|
||||
userName,
|
||||
privateKey,
|
||||
orgName,
|
||||
dataBagName,
|
||||
dataBagItemName
|
||||
}: TGetChefDataBagItem): Promise<TChefDataBagItemContent> => {
|
||||
try {
|
||||
const path = `/organizations/${orgName}/data/${dataBagName}/${dataBagItemName}`;
|
||||
const body = "";
|
||||
|
||||
const hostServerUrl = await getChefServerUrl(serverUrl);
|
||||
|
||||
const headers = getChefAuthHeaders("GET", path, body, userName, privateKey);
|
||||
|
||||
const res = await request.get<TChefDataBagItemContent>(`${hostServerUrl}${path}`, {
|
||||
headers
|
||||
});
|
||||
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
if (error instanceof AxiosError) {
|
||||
throw new BadRequestError({
|
||||
message: `Failed to get Chef data bag item: ${error.message || "Unknown error"}`
|
||||
});
|
||||
}
|
||||
throw new BadRequestError({
|
||||
message: "Unable to get Chef data bag item"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const updateChefDataBagItem = async ({
|
||||
serverUrl,
|
||||
userName,
|
||||
privateKey,
|
||||
orgName,
|
||||
dataBagName,
|
||||
dataBagItemName,
|
||||
data
|
||||
}: TUpdateChefDataBagItem): Promise<void> => {
|
||||
try {
|
||||
const path = `/organizations/${orgName}/data/${dataBagName}/${dataBagItemName}`;
|
||||
const body = JSON.stringify(data);
|
||||
|
||||
const hostServerUrl = await getChefServerUrl(serverUrl);
|
||||
|
||||
const headers = getChefAuthHeaders("PUT", path, body, userName, privateKey);
|
||||
|
||||
await request.put(`${hostServerUrl}${path}`, data, {
|
||||
headers
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof AxiosError) {
|
||||
throw new BadRequestError({
|
||||
message: `Failed to update Chef data bag item: ${error.message || "Unknown error"}`
|
||||
});
|
||||
}
|
||||
throw new BadRequestError({
|
||||
message: "Unable to update Chef data bag item"
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,77 +0,0 @@
|
||||
import z from "zod";
|
||||
|
||||
import { AppConnections } from "@app/lib/api-docs";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import {
|
||||
BaseAppConnectionSchema,
|
||||
GenericCreateAppConnectionFieldsSchema,
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { ChefConnectionMethod } from "./chef-connection-enums";
|
||||
|
||||
export const ChefConnectionUserKeyCredentialsSchema = z.object({
|
||||
serverUrl: z
|
||||
.string()
|
||||
.trim()
|
||||
.url("Valid Chef Server URL required")
|
||||
.optional()
|
||||
.describe(AppConnections.CREDENTIALS.CHEF.serverUrl),
|
||||
orgName: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Organization name required")
|
||||
.max(256, "Organization name cannot exceed 256 characters")
|
||||
.describe(AppConnections.CREDENTIALS.CHEF.orgName),
|
||||
userName: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "User name required")
|
||||
.max(256, "User name cannot exceed 256 characters")
|
||||
.describe(AppConnections.CREDENTIALS.CHEF.userName),
|
||||
privateKey: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Private key required")
|
||||
.max(16384, "Private key cannot exceed 16384 characters")
|
||||
.describe(AppConnections.CREDENTIALS.CHEF.privateKey)
|
||||
});
|
||||
|
||||
const BaseChefConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.Chef) });
|
||||
|
||||
export const ChefConnectionSchema = BaseChefConnectionSchema.extend({
|
||||
method: z.literal(ChefConnectionMethod.UserKey),
|
||||
credentials: ChefConnectionUserKeyCredentialsSchema
|
||||
});
|
||||
|
||||
export const SanitizedChefConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseChefConnectionSchema.extend({
|
||||
method: z.literal(ChefConnectionMethod.UserKey),
|
||||
credentials: ChefConnectionUserKeyCredentialsSchema.pick({ serverUrl: true, orgName: true, userName: true })
|
||||
})
|
||||
]);
|
||||
|
||||
export const ValidateChefConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
z.object({
|
||||
method: z.literal(ChefConnectionMethod.UserKey).describe(AppConnections.CREATE(AppConnection.Chef).method),
|
||||
credentials: ChefConnectionUserKeyCredentialsSchema.describe(AppConnections.CREATE(AppConnection.Chef).credentials)
|
||||
})
|
||||
]);
|
||||
|
||||
export const CreateChefConnectionSchema = ValidateChefConnectionCredentialsSchema.and(
|
||||
GenericCreateAppConnectionFieldsSchema(AppConnection.Chef)
|
||||
);
|
||||
|
||||
export const UpdateChefConnectionSchema = z
|
||||
.object({
|
||||
credentials: ChefConnectionUserKeyCredentialsSchema.optional().describe(
|
||||
AppConnections.UPDATE(AppConnection.Chef).credentials
|
||||
)
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Chef));
|
||||
|
||||
export const ChefConnectionListItemSchema = z.object({
|
||||
name: z.literal("Chef"),
|
||||
app: z.literal(AppConnection.Chef),
|
||||
methods: z.nativeEnum(ChefConnectionMethod).array()
|
||||
});
|
||||
@@ -1,39 +0,0 @@
|
||||
import { ForbiddenRequestError } from "@app/lib/errors";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
|
||||
import { AppConnection } from "../app-connection-enums";
|
||||
import { listChefDataBagItems, listChefDataBags } from "./chef-connection-fns";
|
||||
import { TChefConnection } from "./chef-connection-types";
|
||||
|
||||
type TGetAppConnectionFunc = (
|
||||
app: AppConnection,
|
||||
connectionId: string,
|
||||
actor: OrgServiceActor
|
||||
) => Promise<TChefConnection>;
|
||||
|
||||
export const chefConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
|
||||
const listDataBags = async (appConnectionId: string, actor: OrgServiceActor) => {
|
||||
const appConnection = await getAppConnection(AppConnection.Chef, appConnectionId, actor);
|
||||
|
||||
if (!appConnection) {
|
||||
throw new ForbiddenRequestError({ message: "App connection not found" });
|
||||
}
|
||||
|
||||
return listChefDataBags(appConnection);
|
||||
};
|
||||
|
||||
const listDataBagItems = async (appConnectionId: string, dataBagName: string, actor: OrgServiceActor) => {
|
||||
const appConnection = await getAppConnection(AppConnection.Chef, appConnectionId, actor);
|
||||
|
||||
if (!appConnection) {
|
||||
throw new ForbiddenRequestError({ message: "App connection not found" });
|
||||
}
|
||||
|
||||
return listChefDataBagItems(appConnection, dataBagName);
|
||||
};
|
||||
|
||||
return {
|
||||
listDataBags,
|
||||
listDataBagItems
|
||||
};
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
import z from "zod";
|
||||
|
||||
import { DiscriminativePick } from "@app/lib/types";
|
||||
import { TChefDataBagItemContent } from "@app/services/secret-sync/chef";
|
||||
|
||||
import { AppConnection } from "../app-connection-enums";
|
||||
import {
|
||||
ChefConnectionSchema,
|
||||
CreateChefConnectionSchema,
|
||||
ValidateChefConnectionCredentialsSchema
|
||||
} from "./chef-connection-schemas";
|
||||
|
||||
export type TChefConnection = z.infer<typeof ChefConnectionSchema>;
|
||||
|
||||
export type TChefConnectionInput = z.infer<typeof CreateChefConnectionSchema> & {
|
||||
app: AppConnection.Chef;
|
||||
};
|
||||
|
||||
export type TValidateChefConnectionCredentialsSchema = typeof ValidateChefConnectionCredentialsSchema;
|
||||
|
||||
export type TChefConnectionConfig = DiscriminativePick<TChefConnectionInput, "method" | "app" | "credentials"> & {
|
||||
orgName: string;
|
||||
};
|
||||
|
||||
export type TChefDataBag = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type TChefDataBagItem = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type TGetChefDataBagItem = {
|
||||
serverUrl?: string;
|
||||
userName: string;
|
||||
privateKey: string;
|
||||
orgName: string;
|
||||
dataBagName: string;
|
||||
dataBagItemName: string;
|
||||
};
|
||||
|
||||
export type TUpdateChefDataBagItem = {
|
||||
serverUrl?: string;
|
||||
userName: string;
|
||||
privateKey: string;
|
||||
orgName: string;
|
||||
dataBagName: string;
|
||||
dataBagItemName: string;
|
||||
data: TChefDataBagItemContent;
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
export * from "./chef-connection-enums";
|
||||
export * from "./chef-connection-fns";
|
||||
export * from "./chef-connection-schemas";
|
||||
export * from "./chef-connection-types";
|
||||
@@ -112,7 +112,20 @@ export const identityOidcAuthServiceFactory = ({
|
||||
});
|
||||
|
||||
const { kid } = decodedToken.header as { kid: string };
|
||||
const oidcSigningKey = await client.getSigningKey(kid);
|
||||
|
||||
let oidcSigningKey;
|
||||
try {
|
||||
oidcSigningKey = await client.getSigningKey(kid);
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === "SigningKeyNotFoundError") {
|
||||
throw new UnauthorizedError({
|
||||
message: `Access denied: Unable to verify JWT signature. The signing key '${kid}' was not found in the OIDC provider's JWKS endpoint. This may indicate an invalid token or misconfigured OIDC provider.`
|
||||
});
|
||||
}
|
||||
throw new UnauthorizedError({
|
||||
message: `Access denied: Failed to retrieve signing key from OIDC provider: ${error instanceof Error ? error.message : String(error)}`
|
||||
});
|
||||
}
|
||||
|
||||
let tokenData: Record<string, string>;
|
||||
try {
|
||||
|
||||
@@ -244,8 +244,8 @@ export const identityTokenAuthServiceFactory = ({
|
||||
}
|
||||
|
||||
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) {
|
||||
throw new BadRequestError({
|
||||
message: "The identity does not have Token Auth attached"
|
||||
throw new NotFoundError({
|
||||
message: "Token Auth configuration not found for identity"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -419,13 +419,14 @@ export const secretFolderDALFactory = (db: TDbClient) => {
|
||||
.select(
|
||||
selectAllTableCols(TableName.SecretFolder),
|
||||
db.raw(
|
||||
`DENSE_RANK() OVER (ORDER BY ${TableName.SecretFolder}."name" ${
|
||||
orderDirection ?? OrderByDirection.ASC
|
||||
}) as rank`
|
||||
`DENSE_RANK() OVER (ORDER BY ${TableName.SecretFolder}."name" COLLATE "en-x-icu" ${orderDirection === OrderByDirection.ASC ? "ASC" : "DESC"}) as rank`
|
||||
),
|
||||
db.ref("slug").withSchema(TableName.Environment).as("environment")
|
||||
)
|
||||
.orderBy(`${TableName.SecretFolder}.${orderBy}`, orderDirection);
|
||||
.orderByRaw(
|
||||
`${TableName.SecretFolder}.?? COLLATE "en-x-icu" ${orderDirection === OrderByDirection.ASC ? "ASC" : "DESC"}`,
|
||||
[orderBy]
|
||||
);
|
||||
|
||||
if (limit) {
|
||||
const rankOffset = offset + 1; // ranks start from 1
|
||||
@@ -434,7 +435,10 @@ export const secretFolderDALFactory = (db: TDbClient) => {
|
||||
.select("*")
|
||||
.from<Awaited<typeof query>[number]>("w")
|
||||
.where("w.rank", ">=", rankOffset)
|
||||
.andWhere("w.rank", "<", rankOffset + limit);
|
||||
.andWhere("w.rank", "<", rankOffset + limit)
|
||||
.orderByRaw(`"w".?? COLLATE "en-x-icu" ${orderDirection === OrderByDirection.ASC ? "ASC" : "DESC"}`, [
|
||||
orderBy
|
||||
]);
|
||||
}
|
||||
|
||||
const folders = await query;
|
||||
@@ -445,7 +449,10 @@ export const secretFolderDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const findByEnvsDeep = async ({ parentIds }: TFindFoldersDeepByParentIdsDTO, tx?: Knex) => {
|
||||
const findByEnvsDeep = async (
|
||||
{ parentIds, orderBy = SecretsOrderBy.Name, orderDirection = OrderByDirection.ASC }: TFindFoldersDeepByParentIdsDTO,
|
||||
tx?: Knex
|
||||
) => {
|
||||
try {
|
||||
const folders = await (tx || db.replicaNode())
|
||||
.withRecursive("parents", (qb) =>
|
||||
@@ -480,7 +487,9 @@ export const secretFolderDALFactory = (db: TDbClient) => {
|
||||
.select<(TSecretFolders & { path: string; depth: number; environment: string })[]>("*")
|
||||
.from("parents")
|
||||
.orderBy("depth")
|
||||
.orderBy(`name`);
|
||||
.orderByRaw(`"parents".?? COLLATE "en-x-icu" ${orderDirection === OrderByDirection.ASC ? "ASC" : "DESC"}`, [
|
||||
orderBy
|
||||
]);
|
||||
|
||||
return folders;
|
||||
} catch (error) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { PgSqlLock } from "@app/keystore/keystore";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { OrderByDirection, OrgServiceActor } from "@app/lib/types";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
||||
import { buildFolderPath } from "@app/services/secret-folder/secret-folder-fns";
|
||||
|
||||
import {
|
||||
@@ -781,7 +782,11 @@ export const secretFolderServiceFactory = ({
|
||||
if (!parentFolder) return [];
|
||||
|
||||
if (recursive) {
|
||||
const recursiveFolders = await folderDAL.findByEnvsDeep({ parentIds: [parentFolder.id] });
|
||||
const recursiveFolders = await folderDAL.findByEnvsDeep({
|
||||
parentIds: [parentFolder.id],
|
||||
orderBy: orderBy || SecretsOrderBy.Name,
|
||||
orderDirection: orderDirection || OrderByDirection.ASC
|
||||
});
|
||||
// remove the parent folder
|
||||
return recursiveFolders
|
||||
.filter((folder) => {
|
||||
@@ -800,19 +805,15 @@ export const secretFolderServiceFactory = ({
|
||||
}));
|
||||
}
|
||||
|
||||
const folders = await folderDAL.find(
|
||||
{
|
||||
envId: env.id,
|
||||
parentId: parentFolder.id,
|
||||
isReserved: false,
|
||||
$search: search ? { name: `%${search}%` } : undefined
|
||||
},
|
||||
{
|
||||
sort: orderBy ? [[orderBy, orderDirection ?? OrderByDirection.ASC]] : undefined,
|
||||
limit,
|
||||
offset
|
||||
}
|
||||
);
|
||||
const folders = await folderDAL.findByMultiEnv({
|
||||
environmentIds: [env.id],
|
||||
parentIds: [parentFolder.id],
|
||||
search,
|
||||
orderBy: orderBy || SecretsOrderBy.Name,
|
||||
orderDirection: orderDirection || OrderByDirection.ASC,
|
||||
limit,
|
||||
offset
|
||||
});
|
||||
if (lastSecretModified) {
|
||||
return folders.filter((el) =>
|
||||
el.lastSecretModified ? el.lastSecretModified >= new Date(lastSecretModified) : false
|
||||
|
||||
@@ -64,6 +64,8 @@ export type TGetFoldersDeepByEnvsDTO = {
|
||||
|
||||
export type TFindFoldersDeepByParentIdsDTO = {
|
||||
parentIds: string[];
|
||||
orderBy?: SecretsOrderBy;
|
||||
orderDirection?: OrderByDirection;
|
||||
};
|
||||
|
||||
export type TCreateManyFoldersDTO = {
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||
import { TSecretSyncListItem } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
export const CHEF_SYNC_LIST_OPTION: TSecretSyncListItem = {
|
||||
name: "Chef",
|
||||
destination: SecretSync.Chef,
|
||||
connection: AppConnection.Chef,
|
||||
canImportSecrets: true
|
||||
};
|
||||
@@ -1,151 +0,0 @@
|
||||
import { getChefDataBagItem, updateChefDataBagItem } from "@app/services/app-connection/chef";
|
||||
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
|
||||
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import {
|
||||
ChefSecret,
|
||||
TChefDataBagItemContent,
|
||||
TChefSecret,
|
||||
TChefSecrets,
|
||||
TChefSyncWithCredentials,
|
||||
TGetChefSecrets
|
||||
} from "./chef-sync-types";
|
||||
|
||||
const getChefSecretsRaw = async ({
|
||||
serverUrl,
|
||||
userName,
|
||||
privateKey,
|
||||
orgName,
|
||||
dataBagName,
|
||||
dataBagItemName
|
||||
}: TGetChefSecrets): Promise<TChefDataBagItemContent> => {
|
||||
const dataBagItem = await getChefDataBagItem({
|
||||
serverUrl,
|
||||
userName,
|
||||
privateKey,
|
||||
orgName,
|
||||
dataBagName,
|
||||
dataBagItemName
|
||||
});
|
||||
|
||||
// Ensure the data bag item has an id field
|
||||
if (!dataBagItem.id) {
|
||||
dataBagItem.id = dataBagItemName;
|
||||
}
|
||||
|
||||
return dataBagItem;
|
||||
};
|
||||
|
||||
const getChefSecrets = async (secretSync: TChefSyncWithCredentials): Promise<TChefSecrets> => {
|
||||
const {
|
||||
connection,
|
||||
destinationConfig: { dataBagName, dataBagItemName }
|
||||
} = secretSync;
|
||||
|
||||
const { serverUrl, userName, privateKey, orgName } = connection.credentials;
|
||||
|
||||
const dataBagItem = await getChefSecretsRaw({
|
||||
serverUrl,
|
||||
orgName,
|
||||
userName,
|
||||
privateKey,
|
||||
dataBagName,
|
||||
dataBagItemName
|
||||
});
|
||||
|
||||
const { id, ...existingSecrets } = dataBagItem;
|
||||
|
||||
// Convert data bag item to key-value pairs
|
||||
const secrets: ChefSecret[] = [];
|
||||
Object.entries(existingSecrets).forEach(([key, value]) => {
|
||||
if (key !== "id" && value !== null && value !== undefined) {
|
||||
secrets.push({ key, value: String(value) });
|
||||
}
|
||||
});
|
||||
|
||||
return { id, secrets };
|
||||
};
|
||||
|
||||
const updateChefSecrets = async (
|
||||
secretSync: TChefSyncWithCredentials,
|
||||
id: string,
|
||||
secrets: Record<string, TChefSecret>
|
||||
) => {
|
||||
const {
|
||||
connection,
|
||||
destinationConfig: { dataBagName, dataBagItemName }
|
||||
} = secretSync;
|
||||
|
||||
const { serverUrl, userName, privateKey, orgName } = connection.credentials;
|
||||
|
||||
// Chef data bag items must have an 'id' field
|
||||
const dataBagItemContent: TChefDataBagItemContent = {
|
||||
id,
|
||||
...secrets
|
||||
};
|
||||
|
||||
await updateChefDataBagItem({
|
||||
serverUrl,
|
||||
orgName,
|
||||
userName,
|
||||
privateKey,
|
||||
dataBagName,
|
||||
dataBagItemName,
|
||||
data: dataBagItemContent
|
||||
});
|
||||
};
|
||||
|
||||
export const ChefSyncFns = {
|
||||
async syncSecrets(secretSync: TChefSyncWithCredentials, secretMap: TSecretMap) {
|
||||
const {
|
||||
environment,
|
||||
syncOptions: { disableSecretDeletion, keySchema }
|
||||
} = secretSync;
|
||||
|
||||
const { id, secrets } = await getChefSecrets(secretSync);
|
||||
|
||||
// Create a map of the existing secrets
|
||||
const updatedSecretsMap = new Map(secrets.map((secret) => [secret.key, secret.value]));
|
||||
|
||||
// Add/update new secrets
|
||||
for (const [key, { value }] of Object.entries(secretMap)) {
|
||||
updatedSecretsMap.set(key, value);
|
||||
}
|
||||
|
||||
// Delete secrets if not disabled
|
||||
if (!disableSecretDeletion) {
|
||||
secrets.forEach((secret) => {
|
||||
if (!matchesSchema(secret.key, environment?.slug || "", keySchema)) return;
|
||||
|
||||
if (!secretMap[secret.key]) {
|
||||
updatedSecretsMap.delete(secret.key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Convert map to object for Chef API
|
||||
const updatedSecrets = Object.fromEntries(updatedSecretsMap.entries());
|
||||
|
||||
await updateChefSecrets(secretSync, id, updatedSecrets);
|
||||
},
|
||||
|
||||
async getSecrets(secretSync: TChefSyncWithCredentials): Promise<TSecretMap> {
|
||||
const { secrets } = await getChefSecrets(secretSync);
|
||||
|
||||
return Object.fromEntries(secrets.map((secret) => [secret.key, { value: secret.value }]));
|
||||
},
|
||||
|
||||
async removeSecrets(secretSync: TChefSyncWithCredentials, secretMap: TSecretMap) {
|
||||
const { id, secrets: existingSecrets } = await getChefSecrets(secretSync);
|
||||
|
||||
const newSecrets = existingSecrets.filter((secret) => !Object.hasOwn(secretMap, secret.key));
|
||||
|
||||
if (newSecrets.length === existingSecrets.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedSecrets = Object.fromEntries(newSecrets.map((secret) => [secret.key, secret.value]));
|
||||
|
||||
await updateChefSecrets(secretSync, id, updatedSecrets);
|
||||
}
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { SecretSyncs } from "@app/lib/api-docs";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||
import {
|
||||
BaseSecretSyncSchema,
|
||||
GenericCreateSecretSyncFieldsSchema,
|
||||
GenericUpdateSecretSyncFieldsSchema
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
const ChefSyncDestinationConfigSchema = z.object({
|
||||
dataBagName: z
|
||||
.string()
|
||||
.min(1, "Data Bag Name is required")
|
||||
.max(256, "Data Bag Name cannot exceed 256 characters")
|
||||
.describe(SecretSyncs.DESTINATION_CONFIG.CHEF.dataBagName),
|
||||
dataBagItemName: z
|
||||
.string()
|
||||
.min(1, "Data Bag Item Name is required")
|
||||
.max(256, "Data Bag Item Name cannot exceed 256 characters")
|
||||
.describe(SecretSyncs.DESTINATION_CONFIG.CHEF.dataBagItemName)
|
||||
});
|
||||
|
||||
const ChefSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
|
||||
|
||||
export const ChefSyncSchema = BaseSecretSyncSchema(SecretSync.Chef, ChefSyncOptionsConfig).extend({
|
||||
destination: z.literal(SecretSync.Chef),
|
||||
destinationConfig: ChefSyncDestinationConfigSchema
|
||||
});
|
||||
|
||||
export const CreateChefSyncSchema = GenericCreateSecretSyncFieldsSchema(SecretSync.Chef, ChefSyncOptionsConfig).extend({
|
||||
destinationConfig: ChefSyncDestinationConfigSchema
|
||||
});
|
||||
|
||||
export const UpdateChefSyncSchema = GenericUpdateSecretSyncFieldsSchema(SecretSync.Chef, ChefSyncOptionsConfig).extend({
|
||||
destinationConfig: ChefSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const ChefSyncListItemSchema = z.object({
|
||||
name: z.literal("Chef"),
|
||||
connection: z.literal(AppConnection.Chef),
|
||||
destination: z.literal(SecretSync.Chef),
|
||||
canImportSecrets: z.literal(true)
|
||||
});
|
||||
@@ -1,41 +0,0 @@
|
||||
import z from "zod";
|
||||
|
||||
import { TChefConnection } from "@app/services/app-connection/chef";
|
||||
|
||||
import { ChefSyncListItemSchema, ChefSyncSchema, CreateChefSyncSchema } from "./chef-sync-schemas";
|
||||
|
||||
export type TChefSyncListItem = z.infer<typeof ChefSyncListItemSchema>;
|
||||
|
||||
export type TChefSync = z.infer<typeof ChefSyncSchema>;
|
||||
|
||||
export type TChefSyncInput = z.infer<typeof CreateChefSyncSchema>;
|
||||
|
||||
export type TChefSyncWithCredentials = TChefSync & {
|
||||
connection: TChefConnection;
|
||||
};
|
||||
|
||||
export type TGetChefSecrets = {
|
||||
serverUrl?: string;
|
||||
userName: string;
|
||||
privateKey: string;
|
||||
orgName: string;
|
||||
dataBagName: string;
|
||||
dataBagItemName: string;
|
||||
};
|
||||
|
||||
export type TChefSecret = string | number | boolean | null;
|
||||
|
||||
export type TChefDataBagItemContent = {
|
||||
id: string;
|
||||
[key: string]: TChefSecret;
|
||||
};
|
||||
|
||||
export type TChefSecrets = {
|
||||
id: string;
|
||||
secrets: ChefSecret[];
|
||||
};
|
||||
|
||||
export type ChefSecret = {
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
export * from "./chef-sync-constants";
|
||||
export * from "./chef-sync-fns";
|
||||
export * from "./chef-sync-schemas";
|
||||
export * from "./chef-sync-types";
|
||||
@@ -4,6 +4,7 @@ import handlebars from "handlebars";
|
||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||
import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { CHEF_SYNC_LIST_OPTION, ChefSyncFns } from "@app/ee/services/secret-sync/chef";
|
||||
import { OCI_VAULT_SYNC_LIST_OPTION, OCIVaultSyncFns } from "@app/ee/services/secret-sync/oci-vault";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import {
|
||||
@@ -34,7 +35,6 @@ import { BITBUCKET_SYNC_LIST_OPTION, BitbucketSyncFns } from "./bitbucket";
|
||||
import { CAMUNDA_SYNC_LIST_OPTION, camundaSyncFactory } from "./camunda";
|
||||
import { CHECKLY_SYNC_LIST_OPTION } from "./checkly/checkly-sync-constants";
|
||||
import { ChecklySyncFns } from "./checkly/checkly-sync-fns";
|
||||
import { CHEF_SYNC_LIST_OPTION, ChefSyncFns } from "./chef";
|
||||
import { CLOUDFLARE_PAGES_SYNC_LIST_OPTION } from "./cloudflare-pages/cloudflare-pages-constants";
|
||||
import { CloudflarePagesSyncFns } from "./cloudflare-pages/cloudflare-pages-fns";
|
||||
import { CLOUDFLARE_WORKERS_SYNC_LIST_OPTION, CloudflareWorkersSyncFns } from "./cloudflare-workers";
|
||||
|
||||
@@ -107,7 +107,7 @@ export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
|
||||
[SecretSync.Northflank]: SecretSyncPlanType.Regular,
|
||||
[SecretSync.Bitbucket]: SecretSyncPlanType.Regular,
|
||||
[SecretSync.LaravelForge]: SecretSyncPlanType.Regular,
|
||||
[SecretSync.Chef]: SecretSyncPlanType.Regular
|
||||
[SecretSync.Chef]: SecretSyncPlanType.Enterprise
|
||||
};
|
||||
|
||||
export const SECRET_SYNC_SKIP_FIELDS_MAP: Record<SecretSync, string[]> = {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { Job } from "bullmq";
|
||||
|
||||
import { AuditLogInfo } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import {
|
||||
TChefSync,
|
||||
TChefSyncInput,
|
||||
TChefSyncListItem,
|
||||
TChefSyncWithCredentials
|
||||
} from "@app/ee/services/secret-sync/chef";
|
||||
import {
|
||||
TOCIVaultSync,
|
||||
TOCIVaultSyncInput,
|
||||
@@ -21,7 +27,6 @@ import {
|
||||
TCamundaSyncListItem,
|
||||
TCamundaSyncWithCredentials
|
||||
} from "@app/services/secret-sync/camunda";
|
||||
import { TChefSync, TChefSyncInput, TChefSyncListItem, TChefSyncWithCredentials } from "@app/services/secret-sync/chef";
|
||||
import {
|
||||
TDatabricksSync,
|
||||
TDatabricksSyncInput,
|
||||
|
||||
@@ -793,6 +793,7 @@ export const reshapeBridgeSecret = (
|
||||
userActorId?: string | null;
|
||||
identityActorId?: string | null;
|
||||
membershipId?: string | null;
|
||||
groupId?: string | null;
|
||||
actorType?: string | null;
|
||||
tags?: {
|
||||
id: string;
|
||||
@@ -823,7 +824,8 @@ export const reshapeBridgeSecret = (
|
||||
actorType: secret.actorType,
|
||||
actorId: secret.userActorId || secret.identityActorId,
|
||||
name: secret.identityActorName || secret.userActorName,
|
||||
membershipId: secret.membershipId
|
||||
membershipId: secret.membershipId,
|
||||
groupId: secret.groupId
|
||||
}
|
||||
: undefined,
|
||||
tags: secret.tags,
|
||||
|
||||
@@ -182,7 +182,6 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
||||
|
||||
const findVersionsBySecretIdWithActors = async ({
|
||||
secretId,
|
||||
projectId,
|
||||
secretVersions,
|
||||
findOpt = {},
|
||||
tx
|
||||
@@ -196,13 +195,22 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
||||
try {
|
||||
const { offset, limit, sort = [["createdAt", "desc"]] } = findOpt;
|
||||
const query = (tx || db.replicaNode())(TableName.SecretVersionV2)
|
||||
.leftJoin(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.SecretVersionV2}.folderId`)
|
||||
.leftJoin(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretFolder}.envId`)
|
||||
.leftJoin(TableName.Users, `${TableName.Users}.id`, `${TableName.SecretVersionV2}.userActorId`)
|
||||
.leftJoin(TableName.Identity, `${TableName.Identity}.id`, `${TableName.SecretVersionV2}.identityActorId`)
|
||||
.leftJoin(TableName.UserGroupMembership, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
|
||||
.leftJoin(TableName.Membership, (qb) => {
|
||||
void qb
|
||||
.on(`${TableName.Membership}.actorUserId`, `${TableName.SecretVersionV2}.userActorId`)
|
||||
.andOn(`${TableName.Membership}.scope`, db.raw("?", [AccessScope.Project]));
|
||||
.on(`${TableName.Membership}.scope`, db.raw("?", [AccessScope.Project]))
|
||||
.andOn(`${TableName.Membership}.scopeProjectId`, `${TableName.Environment}.projectId`)
|
||||
.andOn((sqb) => {
|
||||
void sqb
|
||||
.on(`${TableName.Membership}.actorUserId`, `${TableName.SecretVersionV2}.userActorId`)
|
||||
.orOn(`${TableName.Membership}.actorIdentityId`, `${TableName.SecretVersionV2}.identityActorId`)
|
||||
.orOn(`${TableName.Membership}.actorGroupId`, `${TableName.UserGroupMembership}.groupId`);
|
||||
});
|
||||
})
|
||||
.leftJoin(TableName.Identity, `${TableName.Identity}.id`, `${TableName.SecretVersionV2}.identityActorId`)
|
||||
.leftJoin(TableName.SecretV2, `${TableName.SecretVersionV2}.secretId`, `${TableName.SecretV2}.id`)
|
||||
.leftJoin(
|
||||
TableName.SecretVersionV2Tag,
|
||||
@@ -216,12 +224,6 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
||||
)
|
||||
.where((qb) => {
|
||||
void qb.where(`${TableName.SecretVersionV2}.secretId`, secretId);
|
||||
void qb.where(`${TableName.Membership}.scopeProjectId`, projectId);
|
||||
if (secretVersions?.length) void qb.whereIn(`${TableName.SecretVersionV2}.version`, secretVersions);
|
||||
})
|
||||
.orWhere((qb) => {
|
||||
void qb.where(`${TableName.SecretVersionV2}.secretId`, secretId);
|
||||
void qb.whereNull(`${TableName.Membership}.scopeProjectId`);
|
||||
if (secretVersions?.length) void qb.whereIn(`${TableName.SecretVersionV2}.version`, secretVersions);
|
||||
})
|
||||
.select(
|
||||
@@ -229,6 +231,7 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
||||
db.ref("username").withSchema(TableName.Users).as("userActorName"),
|
||||
db.ref("name").withSchema(TableName.Identity).as("identityActorName"),
|
||||
db.ref("id").withSchema(TableName.Membership).as("membershipId"),
|
||||
db.ref("actorGroupId").withSchema(TableName.Membership).as("groupId"),
|
||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||
@@ -256,7 +259,8 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
||||
...SecretVersionsV2Schema.parse(el),
|
||||
userActorName: el.userActorName,
|
||||
identityActorName: el.identityActorName,
|
||||
membershipId: el.membershipId
|
||||
membershipId: el.membershipId,
|
||||
groupId: el.groupId
|
||||
}),
|
||||
childrenMapper: [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user