mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 19:28:51 +00:00
Merge branch 'main' into feature/slack-secret-sync-error-notification
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.PkiCertificateTemplateV2)) {
|
||||||
|
await knex.schema.alterTable(TableName.PkiCertificateTemplateV2, (t) => {
|
||||||
|
t.dropForeign(["projectId"]);
|
||||||
|
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.PkiCertificateTemplateV2)) {
|
||||||
|
await knex.schema.alterTable(TableName.PkiCertificateTemplateV2, (t) => {
|
||||||
|
t.dropForeign(["projectId"]);
|
||||||
|
t.foreign("projectId").references("id").inTable(TableName.Project);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-6
@@ -1,17 +1,16 @@
|
|||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
import { readLimit } from "@app/server/config/rateLimiter";
|
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
|
||||||
import {
|
import {
|
||||||
CreateChefConnectionSchema,
|
CreateChefConnectionSchema,
|
||||||
SanitizedChefConnectionSchema,
|
SanitizedChefConnectionSchema,
|
||||||
UpdateChefConnectionSchema
|
UpdateChefConnectionSchema
|
||||||
} from "@app/services/app-connection/chef";
|
} from "@app/ee/services/app-connections/chef";
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { registerAppConnectionEndpoints } from "@app/server/routes/v1/app-connection-routers/app-connection-endpoints";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
|
||||||
|
|
||||||
export const registerChefConnectionRouter = async (server: FastifyZodProvider) => {
|
export const registerChefConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
registerAppConnectionEndpoints({
|
registerAppConnectionEndpoints({
|
||||||
app: AppConnection.Chef,
|
app: AppConnection.Chef,
|
||||||
@@ -92,7 +92,8 @@ export const registerPamAccountRouter = async (server: FastifyZodProvider) => {
|
|||||||
gatewayClientCertificate: z.string(),
|
gatewayClientCertificate: z.string(),
|
||||||
gatewayClientPrivateKey: z.string(),
|
gatewayClientPrivateKey: z.string(),
|
||||||
gatewayServerCertificateChain: z.string(),
|
gatewayServerCertificateChain: z.string(),
|
||||||
relayHost: z.string()
|
relayHost: z.string(),
|
||||||
|
metadata: z.record(z.string(), z.string()).optional()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -468,7 +468,10 @@ export const registerPITRouter = async (server: FastifyZodProvider) => {
|
|||||||
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
|
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
|
||||||
.optional(),
|
.optional(),
|
||||||
secretComment: z.string().trim().optional().default(""),
|
secretComment: z.string().trim().optional().default(""),
|
||||||
skipMultilineEncoding: z.boolean().optional(),
|
skipMultilineEncoding: z
|
||||||
|
.boolean()
|
||||||
|
.nullish()
|
||||||
|
.transform((val) => (val === null ? false : val)),
|
||||||
metadata: z.record(z.string()).optional(),
|
metadata: z.record(z.string()).optional(),
|
||||||
secretMetadata: ResourceMetadataSchema.optional(),
|
secretMetadata: ResourceMetadataSchema.optional(),
|
||||||
tagIds: z.string().array().optional()
|
tagIds: z.string().array().optional()
|
||||||
|
|||||||
+2
-3
@@ -1,8 +1,7 @@
|
|||||||
import { ChefSyncSchema, CreateChefSyncSchema, UpdateChefSyncSchema } from "@app/services/secret-sync/chef";
|
import { ChefSyncSchema, CreateChefSyncSchema, UpdateChefSyncSchema } from "@app/ee/services/secret-sync/chef";
|
||||||
|
import { registerSyncSecretsEndpoints } from "@app/server/routes/v1/secret-sync-routers/secret-sync-endpoints";
|
||||||
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
|
||||||
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
|
|
||||||
|
|
||||||
export const registerChefSyncRouter = async (server: FastifyZodProvider) =>
|
export const registerChefSyncRouter = async (server: FastifyZodProvider) =>
|
||||||
registerSyncSecretsEndpoints({
|
registerSyncSecretsEndpoints({
|
||||||
destination: SecretSync.Chef,
|
destination: SecretSync.Chef,
|
||||||
+1
-1
@@ -5,10 +5,10 @@ import { request } from "@app/lib/config/request";
|
|||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||||
|
|
||||||
import { TChefDataBagItemContent } from "../../secret-sync/chef/chef-sync-types";
|
import { TChefDataBagItemContent } from "../../secret-sync/chef/chef-sync-types";
|
||||||
import { AppConnection } from "../app-connection-enums";
|
|
||||||
import { ChefConnectionMethod } from "./chef-connection-enums";
|
import { ChefConnectionMethod } from "./chef-connection-enums";
|
||||||
import {
|
import {
|
||||||
TChefConnection,
|
TChefConnection,
|
||||||
+21
-3
@@ -1,7 +1,8 @@
|
|||||||
import { ForbiddenRequestError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
|
||||||
import { OrgServiceActor } from "@app/lib/types";
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
|
||||||
import { AppConnection } from "../app-connection-enums";
|
import { AppConnection } from "../../../../services/app-connection/app-connection-enums";
|
||||||
|
import { TLicenseServiceFactory } from "../../license/license-service";
|
||||||
import { listChefDataBagItems, listChefDataBags } from "./chef-connection-fns";
|
import { listChefDataBagItems, listChefDataBags } from "./chef-connection-fns";
|
||||||
import { TChefConnection } from "./chef-connection-types";
|
import { TChefConnection } from "./chef-connection-types";
|
||||||
|
|
||||||
@@ -11,8 +12,23 @@ type TGetAppConnectionFunc = (
|
|||||||
actor: OrgServiceActor
|
actor: OrgServiceActor
|
||||||
) => Promise<TChefConnection>;
|
) => Promise<TChefConnection>;
|
||||||
|
|
||||||
export const chefConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
|
// Enterprise check
|
||||||
|
export const checkPlan = async (licenseService: Pick<TLicenseServiceFactory, "getPlan">, orgId: string) => {
|
||||||
|
const plan = await licenseService.getPlan(orgId);
|
||||||
|
if (!plan.enterpriseAppConnections)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to use app connection due to plan restriction. Upgrade plan to access enterprise app connections."
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const chefConnectionService = (
|
||||||
|
getAppConnection: TGetAppConnectionFunc,
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">
|
||||||
|
) => {
|
||||||
const listDataBags = async (appConnectionId: string, actor: OrgServiceActor) => {
|
const listDataBags = async (appConnectionId: string, actor: OrgServiceActor) => {
|
||||||
|
await checkPlan(licenseService, actor.orgId);
|
||||||
|
|
||||||
const appConnection = await getAppConnection(AppConnection.Chef, appConnectionId, actor);
|
const appConnection = await getAppConnection(AppConnection.Chef, appConnectionId, actor);
|
||||||
|
|
||||||
if (!appConnection) {
|
if (!appConnection) {
|
||||||
@@ -23,6 +39,8 @@ export const chefConnectionService = (getAppConnection: TGetAppConnectionFunc) =
|
|||||||
};
|
};
|
||||||
|
|
||||||
const listDataBagItems = async (appConnectionId: string, dataBagName: string, actor: OrgServiceActor) => {
|
const listDataBagItems = async (appConnectionId: string, dataBagName: string, actor: OrgServiceActor) => {
|
||||||
|
await checkPlan(licenseService, actor.orgId);
|
||||||
|
|
||||||
const appConnection = await getAppConnection(AppConnection.Chef, appConnectionId, actor);
|
const appConnection = await getAppConnection(AppConnection.Chef, appConnectionId, actor);
|
||||||
|
|
||||||
if (!appConnection) {
|
if (!appConnection) {
|
||||||
+2
-2
@@ -1,9 +1,9 @@
|
|||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
|
import { TChefDataBagItemContent } from "@app/ee/services/secret-sync/chef";
|
||||||
import { DiscriminativePick } from "@app/lib/types";
|
import { DiscriminativePick } from "@app/lib/types";
|
||||||
import { TChefDataBagItemContent } from "@app/services/secret-sync/chef";
|
|
||||||
|
|
||||||
import { AppConnection } from "../app-connection-enums";
|
import { AppConnection } from "../../../../services/app-connection/app-connection-enums";
|
||||||
import {
|
import {
|
||||||
ChefConnectionSchema,
|
ChefConnectionSchema,
|
||||||
CreateChefConnectionSchema,
|
CreateChefConnectionSchema,
|
||||||
@@ -3,7 +3,7 @@ import net from "node:net";
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
|
|
||||||
import { OrganizationActionScope, OrgMembershipRole, TRelays } from "@app/db/schemas";
|
import { OrganizationActionScope, OrgMembershipRole, OrgMembershipStatus, TRelays } from "@app/db/schemas";
|
||||||
import { PgSqlLock } from "@app/keystore/keystore";
|
import { PgSqlLock } from "@app/keystore/keystore";
|
||||||
import { crypto } from "@app/lib/crypto";
|
import { crypto } from "@app/lib/crypto";
|
||||||
import { DatabaseErrorCode } from "@app/lib/error-codes";
|
import { DatabaseErrorCode } from "@app/lib/error-codes";
|
||||||
@@ -909,7 +909,9 @@ export const gatewayV2ServiceFactory = ({
|
|||||||
|
|
||||||
for await (const [orgId, gateways] of Object.entries(gatewaysByOrg)) {
|
for await (const [orgId, gateways] of Object.entries(gatewaysByOrg)) {
|
||||||
try {
|
try {
|
||||||
const admins = await orgDAL.findOrgMembersByRole(orgId, OrgMembershipRole.Admin);
|
const admins = (await orgDAL.findOrgMembersByRole(orgId, OrgMembershipRole.Admin)).filter(
|
||||||
|
(admin) => admin.status !== OrgMembershipStatus.Invited
|
||||||
|
);
|
||||||
if (admins.length === 0) {
|
if (admins.length === 0) {
|
||||||
logger.warn({ orgId }, "Organization has no admins to notify about unhealthy gateway.");
|
logger.warn({ orgId }, "Organization has no admins to notify about unhealthy gateway.");
|
||||||
// eslint-disable-next-line no-continue
|
// eslint-disable-next-line no-continue
|
||||||
|
|||||||
@@ -480,6 +480,36 @@ export const pamAccountServiceFactory = ({
|
|||||||
throw new NotFoundError({ message: `Gateway connection details for gateway '${gatewayId}' not found.` });
|
throw new NotFoundError({ message: `Gateway connection details for gateway '${gatewayId}' not found.` });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let metadata;
|
||||||
|
|
||||||
|
switch (resourceType) {
|
||||||
|
case PamResource.Postgres:
|
||||||
|
case PamResource.MySQL:
|
||||||
|
{
|
||||||
|
const connectionCredentials = await decryptResourceConnectionDetails({
|
||||||
|
encryptedConnectionDetails: resource.encryptedConnectionDetails,
|
||||||
|
kmsService,
|
||||||
|
projectId: account.projectId
|
||||||
|
});
|
||||||
|
|
||||||
|
const credentials = await decryptAccountCredentials({
|
||||||
|
encryptedCredentials: account.encryptedCredentials,
|
||||||
|
kmsService,
|
||||||
|
projectId: account.projectId
|
||||||
|
});
|
||||||
|
|
||||||
|
metadata = {
|
||||||
|
username: credentials.username,
|
||||||
|
database: connectionCredentials.database,
|
||||||
|
accountName: account.name,
|
||||||
|
accountPath
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sessionId: session.id,
|
sessionId: session.id,
|
||||||
resourceType,
|
resourceType,
|
||||||
@@ -491,7 +521,8 @@ export const pamAccountServiceFactory = ({
|
|||||||
gatewayServerCertificateChain: gatewayConnectionDetails.gateway.serverCertificateChain,
|
gatewayServerCertificateChain: gatewayConnectionDetails.gateway.serverCertificateChain,
|
||||||
relayHost: gatewayConnectionDetails.relayHost,
|
relayHost: gatewayConnectionDetails.relayHost,
|
||||||
projectId: account.projectId,
|
projectId: account.projectId,
|
||||||
account
|
account,
|
||||||
|
metadata
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import knex from "knex";
|
import knex from "knex";
|
||||||
import mysql, { Connection } from "mysql2/promise";
|
import mysql, { Connection } from "mysql2/promise";
|
||||||
import * as pg from "pg";
|
|
||||||
import tls, { PeerCertificate } from "tls";
|
import tls, { PeerCertificate } from "tls";
|
||||||
|
|
||||||
import { verifyHostInputValidity } from "@app/ee/services/dynamic-secret/dynamic-secret-fns";
|
import { verifyHostInputValidity } from "@app/ee/services/dynamic-secret/dynamic-secret-fns";
|
||||||
@@ -97,7 +96,7 @@ const makeSqlConnection = (
|
|||||||
try {
|
try {
|
||||||
await client.raw(SIMPLE_QUERY);
|
await client.raw(SIMPLE_QUERY);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof pg.DatabaseError) {
|
if (error instanceof Error) {
|
||||||
// Hacky way to know if we successfully hit the database.
|
// Hacky way to know if we successfully hit the database.
|
||||||
// TODO: potentially two approaches to solve the problem.
|
// TODO: potentially two approaches to solve the problem.
|
||||||
// 1. change the work flow, add account first then resource
|
// 1. change the work flow, add account first then resource
|
||||||
|
|||||||
@@ -201,11 +201,11 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => {
|
|||||||
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||||
if (actorType === ActorType.USER) {
|
if (actorType === ActorType.USER) {
|
||||||
void queryBuilder
|
void queryBuilder
|
||||||
.on(`${TableName.Membership}.actorUserId`, `${TableName.IdentityMetadata}.userId`)
|
.on(`${TableName.IdentityMetadata}.userId`, db.raw("?", [actorId]))
|
||||||
.andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`);
|
.andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`);
|
||||||
} else if (actorType === ActorType.IDENTITY) {
|
} else if (actorType === ActorType.IDENTITY) {
|
||||||
void queryBuilder
|
void queryBuilder
|
||||||
.on(`${TableName.Membership}.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`)
|
.on(`${TableName.IdentityMetadata}.identityId`, db.raw("?", [actorId]))
|
||||||
.andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`);
|
.andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -488,7 +488,7 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => {
|
|||||||
})
|
})
|
||||||
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||||
void queryBuilder
|
void queryBuilder
|
||||||
.on(`${TableName.Membership}.actorUserId`, `${TableName.IdentityMetadata}.userId`)
|
.on(`${TableName.Users}.id`, `${TableName.IdentityMetadata}.userId`)
|
||||||
.andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`);
|
.andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`);
|
||||||
})
|
})
|
||||||
.where(`${TableName.Membership}.scopeOrgId`, orgId)
|
.where(`${TableName.Membership}.scopeOrgId`, orgId)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { isIP } from "node:net";
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
|
|
||||||
import { OrganizationActionScope, OrgMembershipRole, TRelays } from "@app/db/schemas";
|
import { OrganizationActionScope, OrgMembershipRole, OrgMembershipStatus, TRelays } from "@app/db/schemas";
|
||||||
import { PgSqlLock } from "@app/keystore/keystore";
|
import { PgSqlLock } from "@app/keystore/keystore";
|
||||||
import { crypto } from "@app/lib/crypto";
|
import { crypto } from "@app/lib/crypto";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
@@ -996,7 +996,9 @@ export const relayServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (existingRelay && (existingRelay.host !== host || existingRelay.name !== name)) {
|
if (existingRelay && (existingRelay.host !== host || existingRelay.name !== name)) {
|
||||||
return relayDAL.updateById(existingRelay.id, { host, name }, tx);
|
throw new BadRequestError({
|
||||||
|
message: `Machine identity already has an existing relay with the name "${existingRelay.name}" and host "${existingRelay.host}". Delete the existing relay or use a different machine identity.`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!existingRelay) {
|
if (!existingRelay) {
|
||||||
@@ -1248,7 +1250,9 @@ export const relayServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const admins = await orgDAL.findOrgMembersByRole(orgId, OrgMembershipRole.Admin);
|
const admins = (await orgDAL.findOrgMembersByRole(orgId, OrgMembershipRole.Admin)).filter(
|
||||||
|
(admin) => admin.status !== OrgMembershipStatus.Invited
|
||||||
|
);
|
||||||
if (admins.length === 0) {
|
if (admins.length === 0) {
|
||||||
// eslint-disable-next-line no-continue
|
// eslint-disable-next-line no-continue
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -670,6 +670,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
.select(
|
.select(
|
||||||
db.ref("projectId").withSchema(TableName.Environment),
|
db.ref("projectId").withSchema(TableName.Environment),
|
||||||
db.ref("slug").withSchema(TableName.Environment).as("environment"),
|
db.ref("slug").withSchema(TableName.Environment).as("environment"),
|
||||||
|
db.ref("name").withSchema(TableName.Environment).as("environmentName"),
|
||||||
db.ref("id").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerId"),
|
db.ref("id").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerId"),
|
||||||
db.ref("reviewerUserId").withSchema(TableName.SecretApprovalRequestReviewer),
|
db.ref("reviewerUserId").withSchema(TableName.SecretApprovalRequestReviewer),
|
||||||
db.ref("status").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerStatus"),
|
db.ref("status").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerStatus"),
|
||||||
@@ -699,30 +700,30 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
)
|
)
|
||||||
.as("inner");
|
.as("inner");
|
||||||
|
|
||||||
const countQuery = (await (tx || db)
|
|
||||||
.select(db.raw("count(*) OVER() as total_count"))
|
|
||||||
.from(innerQuery.clone().distinctOn(`${TableName.SecretApprovalRequest}.id`))) as Array<{
|
|
||||||
total_count: number;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
const query = (tx || db).select("*").from(innerQuery).orderBy("createdAt", "desc") as typeof innerQuery;
|
const query = (tx || db).select("*").from(innerQuery).orderBy("createdAt", "desc") as typeof innerQuery;
|
||||||
|
|
||||||
if (search) {
|
if (search) {
|
||||||
void query.where((qb) => {
|
void query.where((qb) => {
|
||||||
void qb
|
void qb
|
||||||
.whereRaw(`CONCAT_WS(' ', ??, ??) ilike ?`, [
|
.whereRaw(`CONCAT_WS(' ', ??, ??) ilike ?`, [
|
||||||
db.ref("firstName").withSchema("committerUser"),
|
db.ref("committerUserFirstName"),
|
||||||
db.ref("lastName").withSchema("committerUser"),
|
db.ref("committerUserLastName"),
|
||||||
`%${search}%`
|
`%${search}%`
|
||||||
])
|
])
|
||||||
.orWhereRaw(`?? ilike ?`, [db.ref("username").withSchema("committerUser"), `%${search}%`])
|
.orWhereRaw(`?? ilike ?`, [db.ref("committerUserUsername"), `%${search}%`])
|
||||||
.orWhereRaw(`?? ilike ?`, [db.ref("email").withSchema("committerUser"), `%${search}%`])
|
.orWhereRaw(`?? ilike ?`, [db.ref("committerUserEmail"), `%${search}%`])
|
||||||
.orWhereILike(`${TableName.Environment}.name`, `%${search}%`)
|
.orWhereILike(`environmentName`, `%${search}%`)
|
||||||
.orWhereILike(`${TableName.Environment}.slug`, `%${search}%`)
|
.orWhereILike(`environment`, `%${search}%`)
|
||||||
.orWhereILike(`${TableName.SecretApprovalPolicy}.secretPath`, `%${search}%`);
|
.orWhereILike(`policySecretPath`, `%${search}%`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const countQuery = (await (tx || db)
|
||||||
|
.select(db.raw("count(*) OVER() as total_count"))
|
||||||
|
.from(query.clone().as("outer"))) as Array<{
|
||||||
|
total_count: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
const rankOffset = offset + 1;
|
const rankOffset = offset + 1;
|
||||||
const docs = await (tx || db)
|
const docs = await (tx || db)
|
||||||
.with("w", query)
|
.with("w", query)
|
||||||
|
|||||||
+2
-1
@@ -6,5 +6,6 @@ export const CHEF_SYNC_LIST_OPTION: TSecretSyncListItem = {
|
|||||||
name: "Chef",
|
name: "Chef",
|
||||||
destination: SecretSync.Chef,
|
destination: SecretSync.Chef,
|
||||||
connection: AppConnection.Chef,
|
connection: AppConnection.Chef,
|
||||||
canImportSecrets: true
|
canImportSecrets: true,
|
||||||
|
enterprise: true
|
||||||
};
|
};
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { getChefDataBagItem, updateChefDataBagItem } from "@app/services/app-connection/chef";
|
import { getChefDataBagItem, updateChefDataBagItem } from "@app/ee/services/app-connections/chef";
|
||||||
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
|
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
|
||||||
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
+2
-1
@@ -42,5 +42,6 @@ export const ChefSyncListItemSchema = z.object({
|
|||||||
name: z.literal("Chef"),
|
name: z.literal("Chef"),
|
||||||
connection: z.literal(AppConnection.Chef),
|
connection: z.literal(AppConnection.Chef),
|
||||||
destination: z.literal(SecretSync.Chef),
|
destination: z.literal(SecretSync.Chef),
|
||||||
canImportSecrets: z.literal(true)
|
canImportSecrets: z.literal(true),
|
||||||
|
enterprise: z.boolean()
|
||||||
});
|
});
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
import { TChefConnection } from "@app/services/app-connection/chef";
|
import { TChefConnection } from "@app/ee/services/app-connections/chef";
|
||||||
|
|
||||||
import { ChefSyncListItemSchema, ChefSyncSchema, CreateChefSyncSchema } from "./chef-sync-schemas";
|
import { ChefSyncListItemSchema, ChefSyncSchema, CreateChefSyncSchema } from "./chef-sync-schemas";
|
||||||
|
|
||||||
@@ -141,7 +141,8 @@ export const secretRawSchema = z.object({
|
|||||||
actorId: z.string().nullable().optional(),
|
actorId: z.string().nullable().optional(),
|
||||||
actorType: z.string().nullable().optional(),
|
actorType: z.string().nullable().optional(),
|
||||||
name: z.string().nullable().optional(),
|
name: z.string().nullable().optional(),
|
||||||
membershipId: z.string().nullable().optional()
|
membershipId: z.string().nullable().optional(),
|
||||||
|
groupId: z.string().nullable().optional()
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.nullable(),
|
.nullable(),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectType } from "@app/db/schemas";
|
import { ProjectType } from "@app/db/schemas";
|
||||||
|
import { ChefConnectionListItemSchema, SanitizedChefConnectionSchema } from "@app/ee/services/app-connections/chef";
|
||||||
import { OCIConnectionListItemSchema, SanitizedOCIConnectionSchema } from "@app/ee/services/app-connections/oci";
|
import { OCIConnectionListItemSchema, SanitizedOCIConnectionSchema } from "@app/ee/services/app-connections/oci";
|
||||||
import {
|
import {
|
||||||
OracleDBConnectionListItemSchema,
|
OracleDBConnectionListItemSchema,
|
||||||
@@ -48,7 +49,6 @@ import {
|
|||||||
ChecklyConnectionListItemSchema,
|
ChecklyConnectionListItemSchema,
|
||||||
SanitizedChecklyConnectionSchema
|
SanitizedChecklyConnectionSchema
|
||||||
} from "@app/services/app-connection/checkly";
|
} from "@app/services/app-connection/checkly";
|
||||||
import { ChefConnectionListItemSchema, SanitizedChefConnectionSchema } from "@app/services/app-connection/chef";
|
|
||||||
import {
|
import {
|
||||||
CloudflareConnectionListItemSchema,
|
CloudflareConnectionListItemSchema,
|
||||||
SanitizedCloudflareConnectionSchema
|
SanitizedCloudflareConnectionSchema
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { registerChefConnectionRouter } from "@app/ee/routes/v1/app-connection-routers/chef-connection-router";
|
||||||
import { registerOCIConnectionRouter } from "@app/ee/routes/v1/app-connection-routers/oci-connection-router";
|
import { registerOCIConnectionRouter } from "@app/ee/routes/v1/app-connection-routers/oci-connection-router";
|
||||||
import { registerOracleDBConnectionRouter } from "@app/ee/routes/v1/app-connection-routers/oracledb-connection-router";
|
import { registerOracleDBConnectionRouter } from "@app/ee/routes/v1/app-connection-routers/oracledb-connection-router";
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
@@ -13,7 +14,6 @@ import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connect
|
|||||||
import { registerBitbucketConnectionRouter } from "./bitbucket-connection-router";
|
import { registerBitbucketConnectionRouter } from "./bitbucket-connection-router";
|
||||||
import { registerCamundaConnectionRouter } from "./camunda-connection-router";
|
import { registerCamundaConnectionRouter } from "./camunda-connection-router";
|
||||||
import { registerChecklyConnectionRouter } from "./checkly-connection-router";
|
import { registerChecklyConnectionRouter } from "./checkly-connection-router";
|
||||||
import { registerChefConnectionRouter } from "./chef-connection-router";
|
|
||||||
import { registerCloudflareConnectionRouter } from "./cloudflare-connection-router";
|
import { registerCloudflareConnectionRouter } from "./cloudflare-connection-router";
|
||||||
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
|
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
|
||||||
import { registerDigitalOceanConnectionRouter } from "./digital-ocean-connection-router";
|
import { registerDigitalOceanConnectionRouter } from "./digital-ocean-connection-router";
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
AccessScope,
|
AccessScope,
|
||||||
|
OrgMembershipRole,
|
||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
ProjectMembershipsSchema,
|
ProjectMembershipsSchema,
|
||||||
ProjectUserMembershipRolesSchema,
|
ProjectUserMembershipRolesSchema,
|
||||||
@@ -266,6 +267,19 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
|||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const usernamesAndEmails = [...req.body.emails, ...req.body.usernames];
|
const usernamesAndEmails = [...req.body.emails, ...req.body.usernames];
|
||||||
|
|
||||||
|
await server.services.membershipUser.createMembership({
|
||||||
|
permission: req.permission,
|
||||||
|
scopeData: {
|
||||||
|
scope: AccessScope.Organization,
|
||||||
|
orgId: req.permission.orgId
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
roles: [{ isTemporary: false, role: OrgMembershipRole.NoAccess }],
|
||||||
|
usernames: usernamesAndEmails
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const { memberships } = await server.services.membershipUser.createMembership({
|
const { memberships } = await server.services.membershipUser.createMembership({
|
||||||
permission: req.permission,
|
permission: req.permission,
|
||||||
scopeData: {
|
scopeData: {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { registerChefSyncRouter } from "@app/ee/routes/v1/secret-sync-routers/chef-sync-router";
|
||||||
import { registerOCIVaultSyncRouter } from "@app/ee/routes/v1/secret-sync-routers/oci-vault-sync-router";
|
import { registerOCIVaultSyncRouter } from "@app/ee/routes/v1/secret-sync-routers/oci-vault-sync-router";
|
||||||
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
|
||||||
@@ -10,7 +11,6 @@ import { registerAzureKeyVaultSyncRouter } from "./azure-key-vault-sync-router";
|
|||||||
import { registerBitbucketSyncRouter } from "./bitbucket-sync-router";
|
import { registerBitbucketSyncRouter } from "./bitbucket-sync-router";
|
||||||
import { registerCamundaSyncRouter } from "./camunda-sync-router";
|
import { registerCamundaSyncRouter } from "./camunda-sync-router";
|
||||||
import { registerChecklySyncRouter } from "./checkly-sync-router";
|
import { registerChecklySyncRouter } from "./checkly-sync-router";
|
||||||
import { registerChefSyncRouter } from "./chef-sync-router";
|
|
||||||
import { registerCloudflarePagesSyncRouter } from "./cloudflare-pages-sync-router";
|
import { registerCloudflarePagesSyncRouter } from "./cloudflare-pages-sync-router";
|
||||||
import { registerCloudflareWorkersSyncRouter } from "./cloudflare-workers-sync-router";
|
import { registerCloudflareWorkersSyncRouter } from "./cloudflare-workers-sync-router";
|
||||||
import { registerDatabricksSyncRouter } from "./databricks-sync-router";
|
import { registerDatabricksSyncRouter } from "./databricks-sync-router";
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { ChefSyncListItemSchema, ChefSyncSchema } from "@app/ee/services/secret-sync/chef";
|
||||||
import { OCIVaultSyncListItemSchema, OCIVaultSyncSchema } from "@app/ee/services/secret-sync/oci-vault";
|
import { OCIVaultSyncListItemSchema, OCIVaultSyncSchema } from "@app/ee/services/secret-sync/oci-vault";
|
||||||
import { ApiDocsTags, SecretSyncs } from "@app/lib/api-docs";
|
import { ApiDocsTags, SecretSyncs } from "@app/lib/api-docs";
|
||||||
import { readLimit } from "@app/server/config/rateLimiter";
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
@@ -24,7 +25,6 @@ import { AzureKeyVaultSyncListItemSchema, AzureKeyVaultSyncSchema } from "@app/s
|
|||||||
import { BitbucketSyncListItemSchema, BitbucketSyncSchema } from "@app/services/secret-sync/bitbucket";
|
import { BitbucketSyncListItemSchema, BitbucketSyncSchema } from "@app/services/secret-sync/bitbucket";
|
||||||
import { CamundaSyncListItemSchema, CamundaSyncSchema } from "@app/services/secret-sync/camunda";
|
import { CamundaSyncListItemSchema, CamundaSyncSchema } from "@app/services/secret-sync/camunda";
|
||||||
import { ChecklySyncListItemSchema, ChecklySyncSchema } from "@app/services/secret-sync/checkly/checkly-sync-schemas";
|
import { ChecklySyncListItemSchema, ChecklySyncSchema } from "@app/services/secret-sync/checkly/checkly-sync-schemas";
|
||||||
import { ChefSyncListItemSchema, ChefSyncSchema } from "@app/services/secret-sync/chef";
|
|
||||||
import {
|
import {
|
||||||
CloudflarePagesSyncListItemSchema,
|
CloudflarePagesSyncListItemSchema,
|
||||||
CloudflarePagesSyncSchema
|
CloudflarePagesSyncSchema
|
||||||
|
|||||||
@@ -550,12 +550,14 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
providerAuthToken: req.body.providerAuthToken
|
providerAuthToken: req.body.providerAuthToken
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if ([AuthMethod.GOOGLE, AuthMethod.GITHUB, AuthMethod.GITLAB].includes(data.decodedProviderToken.authMethod)) {
|
||||||
void res.setCookie("jid", data.token.refresh, {
|
void res.setCookie("jid", data.token.refresh, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
path: "/",
|
path: "/",
|
||||||
sameSite: "strict",
|
sameSite: "strict",
|
||||||
secure: appCfg.HTTPS_ENABLED
|
secure: appCfg.HTTPS_ENABLED
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
addAuthOriginDomainCookie(res);
|
addAuthOriginDomainCookie(res);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { ProjectType } from "@app/db/schemas";
|
import { ProjectType } from "@app/db/schemas";
|
||||||
import { TAppConnections } from "@app/db/schemas/app-connections";
|
import { TAppConnections } from "@app/db/schemas/app-connections";
|
||||||
|
import {
|
||||||
|
ChefConnectionMethod,
|
||||||
|
getChefConnectionListItem,
|
||||||
|
validateChefConnectionCredentials
|
||||||
|
} from "@app/ee/services/app-connections/chef";
|
||||||
import {
|
import {
|
||||||
getOCIConnectionListItem,
|
getOCIConnectionListItem,
|
||||||
OCIConnectionMethod,
|
OCIConnectionMethod,
|
||||||
@@ -68,7 +73,6 @@ import {
|
|||||||
} from "./bitbucket";
|
} from "./bitbucket";
|
||||||
import { CamundaConnectionMethod, getCamundaConnectionListItem, validateCamundaConnectionCredentials } from "./camunda";
|
import { CamundaConnectionMethod, getCamundaConnectionListItem, validateCamundaConnectionCredentials } from "./camunda";
|
||||||
import { ChecklyConnectionMethod, getChecklyConnectionListItem, validateChecklyConnectionCredentials } from "./checkly";
|
import { ChecklyConnectionMethod, getChecklyConnectionListItem, validateChecklyConnectionCredentials } from "./checkly";
|
||||||
import { ChefConnectionMethod, getChefConnectionListItem, validateChefConnectionCredentials } from "./chef";
|
|
||||||
import { CloudflareConnectionMethod } from "./cloudflare/cloudflare-connection-enum";
|
import { CloudflareConnectionMethod } from "./cloudflare/cloudflare-connection-enum";
|
||||||
import {
|
import {
|
||||||
getCloudflareConnectionListItem,
|
getCloudflareConnectionListItem,
|
||||||
|
|||||||
@@ -86,6 +86,6 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
|
|||||||
[AppConnection.Netlify]: AppConnectionPlanType.Regular,
|
[AppConnection.Netlify]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.Okta]: AppConnectionPlanType.Regular,
|
[AppConnection.Okta]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.Redis]: AppConnectionPlanType.Regular,
|
[AppConnection.Redis]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.Chef]: AppConnectionPlanType.Regular,
|
[AppConnection.Chef]: AppConnectionPlanType.Enterprise,
|
||||||
[AppConnection.Northflank]: AppConnectionPlanType.Regular
|
[AppConnection.Northflank]: AppConnectionPlanType.Regular
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
|
||||||
import { ActionProjectType, OrganizationActionScope, TAppConnections } from "@app/db/schemas";
|
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 { ValidateOCIConnectionCredentialsSchema } from "@app/ee/services/app-connections/oci";
|
||||||
import { ociConnectionService } from "@app/ee/services/app-connections/oci/oci-connection-service";
|
import { ociConnectionService } from "@app/ee/services/app-connections/oci/oci-connection-service";
|
||||||
import { ValidateOracleDBConnectionCredentialsSchema } from "@app/ee/services/app-connections/oracledb";
|
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 { camundaConnectionService } from "./camunda/camunda-connection-service";
|
||||||
import { ValidateChecklyConnectionCredentialsSchema } from "./checkly";
|
import { ValidateChecklyConnectionCredentialsSchema } from "./checkly";
|
||||||
import { checklyConnectionService } from "./checkly/checkly-connection-service";
|
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 { ValidateCloudflareConnectionCredentialsSchema } from "./cloudflare/cloudflare-connection-schema";
|
||||||
import { cloudflareConnectionService } from "./cloudflare/cloudflare-connection-service";
|
import { cloudflareConnectionService } from "./cloudflare/cloudflare-connection-service";
|
||||||
import { ValidateDatabricksConnectionCredentialsSchema } from "./databricks";
|
import { ValidateDatabricksConnectionCredentialsSchema } from "./databricks";
|
||||||
@@ -885,6 +885,6 @@ export const appConnectionServiceFactory = ({
|
|||||||
northflank: northflankConnectionService(connectAppConnectionById),
|
northflank: northflankConnectionService(connectAppConnectionById),
|
||||||
okta: oktaConnectionService(connectAppConnectionById),
|
okta: oktaConnectionService(connectAppConnectionById),
|
||||||
laravelForge: laravelForgeConnectionService(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 {
|
import {
|
||||||
TOCIConnection,
|
TOCIConnection,
|
||||||
TOCIConnectionConfig,
|
TOCIConnectionConfig,
|
||||||
@@ -82,12 +88,6 @@ import {
|
|||||||
TChecklyConnectionInput,
|
TChecklyConnectionInput,
|
||||||
TValidateChecklyConnectionCredentialsSchema
|
TValidateChecklyConnectionCredentialsSchema
|
||||||
} from "./checkly";
|
} from "./checkly";
|
||||||
import {
|
|
||||||
TChefConnection,
|
|
||||||
TChefConnectionConfig,
|
|
||||||
TChefConnectionInput,
|
|
||||||
TValidateChefConnectionCredentialsSchema
|
|
||||||
} from "./chef";
|
|
||||||
import {
|
import {
|
||||||
TCloudflareConnection,
|
TCloudflareConnection,
|
||||||
TCloudflareConnectionConfig,
|
TCloudflareConnectionConfig,
|
||||||
|
|||||||
@@ -112,7 +112,20 @@ export const identityOidcAuthServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { kid } = decodedToken.header as { kid: string };
|
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>;
|
let tokenData: Record<string, string>;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -244,8 +244,8 @@ export const identityTokenAuthServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) {
|
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "The identity does not have Token Auth attached"
|
message: "Token Auth configuration not found for identity"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -419,13 +419,14 @@ export const secretFolderDALFactory = (db: TDbClient) => {
|
|||||||
.select(
|
.select(
|
||||||
selectAllTableCols(TableName.SecretFolder),
|
selectAllTableCols(TableName.SecretFolder),
|
||||||
db.raw(
|
db.raw(
|
||||||
`DENSE_RANK() OVER (ORDER BY ${TableName.SecretFolder}."name" ${
|
`DENSE_RANK() OVER (ORDER BY ${TableName.SecretFolder}."name" COLLATE "en-x-icu" ${orderDirection === OrderByDirection.ASC ? "ASC" : "DESC"}) as rank`
|
||||||
orderDirection ?? OrderByDirection.ASC
|
|
||||||
}) as rank`
|
|
||||||
),
|
),
|
||||||
db.ref("slug").withSchema(TableName.Environment).as("environment")
|
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) {
|
if (limit) {
|
||||||
const rankOffset = offset + 1; // ranks start from 1
|
const rankOffset = offset + 1; // ranks start from 1
|
||||||
@@ -434,7 +435,10 @@ export const secretFolderDALFactory = (db: TDbClient) => {
|
|||||||
.select("*")
|
.select("*")
|
||||||
.from<Awaited<typeof query>[number]>("w")
|
.from<Awaited<typeof query>[number]>("w")
|
||||||
.where("w.rank", ">=", rankOffset)
|
.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;
|
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 {
|
try {
|
||||||
const folders = await (tx || db.replicaNode())
|
const folders = await (tx || db.replicaNode())
|
||||||
.withRecursive("parents", (qb) =>
|
.withRecursive("parents", (qb) =>
|
||||||
@@ -480,7 +487,9 @@ export const secretFolderDALFactory = (db: TDbClient) => {
|
|||||||
.select<(TSecretFolders & { path: string; depth: number; environment: string })[]>("*")
|
.select<(TSecretFolders & { path: string; depth: number; environment: string })[]>("*")
|
||||||
.from("parents")
|
.from("parents")
|
||||||
.orderBy("depth")
|
.orderBy("depth")
|
||||||
.orderBy(`name`);
|
.orderByRaw(`"parents".?? COLLATE "en-x-icu" ${orderDirection === OrderByDirection.ASC ? "ASC" : "DESC"}`, [
|
||||||
|
orderBy
|
||||||
|
]);
|
||||||
|
|
||||||
return folders;
|
return folders;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { PgSqlLock } from "@app/keystore/keystore";
|
|||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { OrderByDirection, OrgServiceActor } from "@app/lib/types";
|
import { OrderByDirection, OrgServiceActor } from "@app/lib/types";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
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 { buildFolderPath } from "@app/services/secret-folder/secret-folder-fns";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -781,7 +782,11 @@ export const secretFolderServiceFactory = ({
|
|||||||
if (!parentFolder) return [];
|
if (!parentFolder) return [];
|
||||||
|
|
||||||
if (recursive) {
|
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
|
// remove the parent folder
|
||||||
return recursiveFolders
|
return recursiveFolders
|
||||||
.filter((folder) => {
|
.filter((folder) => {
|
||||||
@@ -800,19 +805,15 @@ export const secretFolderServiceFactory = ({
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const folders = await folderDAL.find(
|
const folders = await folderDAL.findByMultiEnv({
|
||||||
{
|
environmentIds: [env.id],
|
||||||
envId: env.id,
|
parentIds: [parentFolder.id],
|
||||||
parentId: parentFolder.id,
|
search,
|
||||||
isReserved: false,
|
orderBy: orderBy || SecretsOrderBy.Name,
|
||||||
$search: search ? { name: `%${search}%` } : undefined
|
orderDirection: orderDirection || OrderByDirection.ASC,
|
||||||
},
|
|
||||||
{
|
|
||||||
sort: orderBy ? [[orderBy, orderDirection ?? OrderByDirection.ASC]] : undefined,
|
|
||||||
limit,
|
limit,
|
||||||
offset
|
offset
|
||||||
}
|
});
|
||||||
);
|
|
||||||
if (lastSecretModified) {
|
if (lastSecretModified) {
|
||||||
return folders.filter((el) =>
|
return folders.filter((el) =>
|
||||||
el.lastSecretModified ? el.lastSecretModified >= new Date(lastSecretModified) : false
|
el.lastSecretModified ? el.lastSecretModified >= new Date(lastSecretModified) : false
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ export type TGetFoldersDeepByEnvsDTO = {
|
|||||||
|
|
||||||
export type TFindFoldersDeepByParentIdsDTO = {
|
export type TFindFoldersDeepByParentIdsDTO = {
|
||||||
parentIds: string[];
|
parentIds: string[];
|
||||||
|
orderBy?: SecretsOrderBy;
|
||||||
|
orderDirection?: OrderByDirection;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCreateManyFoldersDTO = {
|
export type TCreateManyFoldersDTO = {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import handlebars from "handlebars";
|
|||||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||||
import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service";
|
import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-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 { OCI_VAULT_SYNC_LIST_OPTION, OCIVaultSyncFns } from "@app/ee/services/secret-sync/oci-vault";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import {
|
import {
|
||||||
@@ -34,7 +35,6 @@ import { BITBUCKET_SYNC_LIST_OPTION, BitbucketSyncFns } from "./bitbucket";
|
|||||||
import { CAMUNDA_SYNC_LIST_OPTION, camundaSyncFactory } from "./camunda";
|
import { CAMUNDA_SYNC_LIST_OPTION, camundaSyncFactory } from "./camunda";
|
||||||
import { CHECKLY_SYNC_LIST_OPTION } from "./checkly/checkly-sync-constants";
|
import { CHECKLY_SYNC_LIST_OPTION } from "./checkly/checkly-sync-constants";
|
||||||
import { ChecklySyncFns } from "./checkly/checkly-sync-fns";
|
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 { CLOUDFLARE_PAGES_SYNC_LIST_OPTION } from "./cloudflare-pages/cloudflare-pages-constants";
|
||||||
import { CloudflarePagesSyncFns } from "./cloudflare-pages/cloudflare-pages-fns";
|
import { CloudflarePagesSyncFns } from "./cloudflare-pages/cloudflare-pages-fns";
|
||||||
import { CLOUDFLARE_WORKERS_SYNC_LIST_OPTION, CloudflareWorkersSyncFns } from "./cloudflare-workers";
|
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.Northflank]: SecretSyncPlanType.Regular,
|
||||||
[SecretSync.Bitbucket]: SecretSyncPlanType.Regular,
|
[SecretSync.Bitbucket]: SecretSyncPlanType.Regular,
|
||||||
[SecretSync.LaravelForge]: SecretSyncPlanType.Regular,
|
[SecretSync.LaravelForge]: SecretSyncPlanType.Regular,
|
||||||
[SecretSync.Chef]: SecretSyncPlanType.Regular
|
[SecretSync.Chef]: SecretSyncPlanType.Enterprise
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SECRET_SYNC_SKIP_FIELDS_MAP: Record<SecretSync, string[]> = {
|
export const SECRET_SYNC_SKIP_FIELDS_MAP: Record<SecretSync, string[]> = {
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { Job } from "bullmq";
|
import { Job } from "bullmq";
|
||||||
|
|
||||||
import { AuditLogInfo } from "@app/ee/services/audit-log/audit-log-types";
|
import { AuditLogInfo } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import {
|
||||||
|
TChefSync,
|
||||||
|
TChefSyncInput,
|
||||||
|
TChefSyncListItem,
|
||||||
|
TChefSyncWithCredentials
|
||||||
|
} from "@app/ee/services/secret-sync/chef";
|
||||||
import {
|
import {
|
||||||
TOCIVaultSync,
|
TOCIVaultSync,
|
||||||
TOCIVaultSyncInput,
|
TOCIVaultSyncInput,
|
||||||
@@ -21,7 +27,6 @@ import {
|
|||||||
TCamundaSyncListItem,
|
TCamundaSyncListItem,
|
||||||
TCamundaSyncWithCredentials
|
TCamundaSyncWithCredentials
|
||||||
} from "@app/services/secret-sync/camunda";
|
} from "@app/services/secret-sync/camunda";
|
||||||
import { TChefSync, TChefSyncInput, TChefSyncListItem, TChefSyncWithCredentials } from "@app/services/secret-sync/chef";
|
|
||||||
import {
|
import {
|
||||||
TDatabricksSync,
|
TDatabricksSync,
|
||||||
TDatabricksSyncInput,
|
TDatabricksSyncInput,
|
||||||
|
|||||||
@@ -793,6 +793,7 @@ export const reshapeBridgeSecret = (
|
|||||||
userActorId?: string | null;
|
userActorId?: string | null;
|
||||||
identityActorId?: string | null;
|
identityActorId?: string | null;
|
||||||
membershipId?: string | null;
|
membershipId?: string | null;
|
||||||
|
groupId?: string | null;
|
||||||
actorType?: string | null;
|
actorType?: string | null;
|
||||||
tags?: {
|
tags?: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -823,7 +824,8 @@ export const reshapeBridgeSecret = (
|
|||||||
actorType: secret.actorType,
|
actorType: secret.actorType,
|
||||||
actorId: secret.userActorId || secret.identityActorId,
|
actorId: secret.userActorId || secret.identityActorId,
|
||||||
name: secret.identityActorName || secret.userActorName,
|
name: secret.identityActorName || secret.userActorName,
|
||||||
membershipId: secret.membershipId
|
membershipId: secret.membershipId,
|
||||||
|
groupId: secret.groupId
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
tags: secret.tags,
|
tags: secret.tags,
|
||||||
|
|||||||
@@ -182,7 +182,6 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const findVersionsBySecretIdWithActors = async ({
|
const findVersionsBySecretIdWithActors = async ({
|
||||||
secretId,
|
secretId,
|
||||||
projectId,
|
|
||||||
secretVersions,
|
secretVersions,
|
||||||
findOpt = {},
|
findOpt = {},
|
||||||
tx
|
tx
|
||||||
@@ -196,13 +195,22 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
try {
|
try {
|
||||||
const { offset, limit, sort = [["createdAt", "desc"]] } = findOpt;
|
const { offset, limit, sort = [["createdAt", "desc"]] } = findOpt;
|
||||||
const query = (tx || db.replicaNode())(TableName.SecretVersionV2)
|
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.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) => {
|
.leftJoin(TableName.Membership, (qb) => {
|
||||||
void qb
|
void qb
|
||||||
|
.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`)
|
.on(`${TableName.Membership}.actorUserId`, `${TableName.SecretVersionV2}.userActorId`)
|
||||||
.andOn(`${TableName.Membership}.scope`, db.raw("?", [AccessScope.Project]));
|
.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.SecretV2, `${TableName.SecretVersionV2}.secretId`, `${TableName.SecretV2}.id`)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.SecretVersionV2Tag,
|
TableName.SecretVersionV2Tag,
|
||||||
@@ -216,12 +224,6 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
)
|
)
|
||||||
.where((qb) => {
|
.where((qb) => {
|
||||||
void qb.where(`${TableName.SecretVersionV2}.secretId`, secretId);
|
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);
|
if (secretVersions?.length) void qb.whereIn(`${TableName.SecretVersionV2}.version`, secretVersions);
|
||||||
})
|
})
|
||||||
.select(
|
.select(
|
||||||
@@ -229,6 +231,7 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("username").withSchema(TableName.Users).as("userActorName"),
|
db.ref("username").withSchema(TableName.Users).as("userActorName"),
|
||||||
db.ref("name").withSchema(TableName.Identity).as("identityActorName"),
|
db.ref("name").withSchema(TableName.Identity).as("identityActorName"),
|
||||||
db.ref("id").withSchema(TableName.Membership).as("membershipId"),
|
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("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||||
@@ -256,7 +259,8 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
...SecretVersionsV2Schema.parse(el),
|
...SecretVersionsV2Schema.parse(el),
|
||||||
userActorName: el.userActorName,
|
userActorName: el.userActorName,
|
||||||
identityActorName: el.identityActorName,
|
identityActorName: el.identityActorName,
|
||||||
membershipId: el.membershipId
|
membershipId: el.membershipId,
|
||||||
|
groupId: el.groupId
|
||||||
}),
|
}),
|
||||||
childrenMapper: [
|
childrenMapper: [
|
||||||
{
|
{
|
||||||
|
|||||||
+4
-1
@@ -784,7 +784,10 @@
|
|||||||
"groups": [
|
"groups": [
|
||||||
{
|
{
|
||||||
"group": "Infisical PAM",
|
"group": "Infisical PAM",
|
||||||
"pages": ["documentation/platform/pam/overview"]
|
"pages": [
|
||||||
|
"documentation/platform/pam/overview",
|
||||||
|
"documentation/platform/pam/session-recording"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,11 @@ Infisical is designed to provide comprehensive, centralized, and efficient manag
|
|||||||
### 2. Projects
|
### 2. Projects
|
||||||
|
|
||||||
- **Definition and Role**: [Projects](/documentation/platform/project) are the highest-level construct within an [organization](/documentation/platform/organization) in Infisical. They serve as the primary container for all functionalities.
|
- **Definition and Role**: [Projects](/documentation/platform/project) are the highest-level construct within an [organization](/documentation/platform/organization) in Infisical. They serve as the primary container for all functionalities.
|
||||||
- **Correspondence to Code Repositories**: Projects typically align with specific code repositories.
|
- **Common Project Mappings**: Projects typically align with applications, services, or code repositories — each being a valid and common approach depending on your organizational structure.
|
||||||
- **Functional Capabilities**: Each project encompasses features for managing secrets, certificates, and encryption keys, serving as the central hub for these resources.
|
- **Functional Capabilities**: Each project encompasses features for managing secrets, certificates, and encryption keys, serving as the central hub for these resources.
|
||||||
|
|
||||||
|
<Note>Projects are isolated from one another. Secrets, certificates, and other resources cannot be shared or referenced across different projects. Each project maintains its own separate set of resources.</Note>
|
||||||
|
|
||||||
### 3. Environments
|
### 3. Environments
|
||||||
|
|
||||||
- **Purpose**: Environments are designed for organizing and compartmentalizing secrets within projects.
|
- **Purpose**: Environments are designed for organizing and compartmentalizing secrets within projects.
|
||||||
@@ -40,8 +42,9 @@ Infisical is designed to provide comprehensive, centralized, and efficient manag
|
|||||||
|
|
||||||
### 5. Imports
|
### 5. Imports
|
||||||
|
|
||||||
- **Purpose and Benefits**: To promote reusability and avoid redundancy, Infisical supports the use of imports. This allows secrets, folders, or entire environments to be referenced across multiple projects as needed.
|
- **Purpose and Benefits**: To promote reusability and avoid redundancy within a project, Infisical supports the use of imports and references. This allows secrets, folders, or entire environments to be referenced within the same project as needed.
|
||||||
- **Best Practice**: Utilizing [secret imports](/documentation/platform/secret-reference#secret-imports) or [references](/documentation/platform/secret-reference#secret-referencing) ensures consistency and minimizes manual overhead.
|
- **Project Isolation**: Imports and references only work within a single project. Secrets cannot be imported or referenced across different projects, as projects are isolated from one another.
|
||||||
|
- **Best Practice**: Utilizing [secret imports](/documentation/platform/secret-reference#secret-imports) or [references](/documentation/platform/secret-reference#secret-referencing) ensures consistency and minimizes manual overhead when managing secrets within a project.
|
||||||
|
|
||||||
### 6. Approval Workflows
|
### 6. Approval Workflows
|
||||||
|
|
||||||
|
|||||||
@@ -30,13 +30,96 @@ Using a hardware security module comes with the added benefit of having a secure
|
|||||||
Enabling HSM encryption has a set of key benefits:
|
Enabling HSM encryption has a set of key benefits:
|
||||||
1. **Root Key Wrapping**: The root KMS encryption key that is used to secure your Infisical instance will be encrypted using the HSM device rather than the standard software-protected key.
|
1. **Root Key Wrapping**: The root KMS encryption key that is used to secure your Infisical instance will be encrypted using the HSM device rather than the standard software-protected key.
|
||||||
|
|
||||||
|
|
||||||
#### Caveats
|
#### Caveats
|
||||||
- **Performance**: Using an HSM device can have a performance impact on your Infisical instance. This is due to the additional latency introduced by the HSM device. This is however only noticeable when your instance(s) start up or when the encryption strategy is changed.
|
- **Performance**: Using an HSM device can have a performance impact on your Infisical instance. This is due to the additional latency introduced by the HSM device. This is however only noticeable when your instance(s) start up or when the encryption strategy is changed.
|
||||||
- **Key Recovery**: If the HSM device is lost or destroyed, you will no longer be able to decrypt your data stored within Infisical. Most HSM providers offer recovery options, which you should consider when setting up an HSM device.
|
- **Key Recovery**: If the HSM device is lost or destroyed, you will no longer be able to decrypt your data stored within Infisical. Most HSM providers offer recovery options, which you should consider when setting up an HSM device.
|
||||||
|
|
||||||
### Requirements
|
## Requirements
|
||||||
- An Infisical instance with a version number that is equal to or greater than `v0.91.0`.
|
- An HSM device _(PKCS#11 compatible library)_ from a compatible provider such as [Thales Luna HSM](https://cpl.thalesgroup.com/encryption/data-protection-on-demand/services/luna-cloud-hsm), [AWS CloudHSM](https://aws.amazon.com/cloudhsm/), [Fortanix HSM](https://www.fortanix.com/platform/data-security-manager), or others.
|
||||||
- An HSM device from a provider such as [Thales Luna HSM](https://cpl.thalesgroup.com/encryption/data-protection-on-demand/services/luna-cloud-hsm), [AWS CloudHSM](https://aws.amazon.com/cloudhsm/), [Fortanix HSM](https://www.fortanix.com/platform/data-security-manager), or others.
|
Infisical is validated to work with PKCS#11 2.30 and newer. If your HSM device doesn't follow the >=2.30 PKCS#11 standard you may see degraded performance.
|
||||||
|
|
||||||
|
|
||||||
|
## Environment Variable Configuration
|
||||||
|
To configure your Infisical instance to use an HSM, you must set the required environment variables. Below you'll find an example of the required environment variables.
|
||||||
|
For further instructions on how to configure the HSM device for your Infisical instance, please see the [Setup Instructions](#setup-instructions) section.
|
||||||
|
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
HSM_LIB_PATH=/usr/local/lib/cloudhsm/cloudhsm.so
|
||||||
|
HSM_SLOT=1
|
||||||
|
HSM_KEY_LABEL=infisical-key
|
||||||
|
HSM_PIN=your:pin
|
||||||
|
```
|
||||||
|
|
||||||
|
- `HSM_LIB_PATH`: The path to the PKCS#11 library provided by the HSM provider. This usually comes in the form of a `.so` for Linux and MacOS, or a `.dll` file for Windows. For Docker, you need to mount the library path as a volume. Further instructions can be found below. If you are using Docker, make sure to set the HSM_LIB_PATH environment variable to the path where the library is mounted in the container.
|
||||||
|
- `HSM_PIN`: The PKCS#11 PIN to use for authentication with the HSM device.
|
||||||
|
- `HSM_SLOT`: The slot number to use for the HSM device. This is typically between `0` and `5` for most HSM devices.
|
||||||
|
- `HSM_KEY_LABEL`: The label of the key to use for encryption. **Please note that if no key is found with the provided label, the HSM will create a new key with the provided label.**
|
||||||
|
|
||||||
|
You can read more about the [default instance configurations](/self-hosting/configuration/envars) here.
|
||||||
|
|
||||||
|
## PKCS#11 Key Attributes
|
||||||
|
|
||||||
|
If no AES key or HMAC key already exists with the label you defined on the `HSM_KEY_LABEL` environment variable, then Infisical will create one for you automatically using the label specified on `HSM_KEY_LABEL`.
|
||||||
|
Below you'll find a list of the attributes each key will be created with.
|
||||||
|
|
||||||
|
### AES Key
|
||||||
|
|
||||||
|
<Accordion title="Bring your own key minimum requirements (optional)">
|
||||||
|
If you bring your own AES key and don't let Infisical create it for you it must have at least the following attributes:
|
||||||
|
|
||||||
|
* `CKA_CLASS`: `CKO_SECRET_KEY` — Defines the key class _(secret key)_.
|
||||||
|
* `CKA_KEY_TYPE`: `CKO_AES` — Defines the key type _(AES key)_.
|
||||||
|
* `CKA_VALUE_LEN`: `32` — 256-bit key size.
|
||||||
|
* `CKA_ENCRYPT`: `true` — Encryption capabilities enabled.
|
||||||
|
* `CKA_DECRYPT`: `true` — Decryption capabilities enabled.
|
||||||
|
* `CKA_TOKEN`: `true` — The key material will persist in your HSM so it can be reused.
|
||||||
|
|
||||||
|
<Warning>
|
||||||
|
Note that for security reasons it is highly recommended to create an AES key with the full set of key attributes seen below if you're going to bring your own key.
|
||||||
|
</Warning>
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
* `CKA_CLASS`: `CKO_SECRET_KEY` — Defines the key class _(secret key)_.
|
||||||
|
* `CKA_KEY_TYPE`: `CKO_AES` — Defines the key type _(AES key)_.
|
||||||
|
* `CKA_VALUE_LEN`: `32` — 256-bit key size.
|
||||||
|
* `CKA_LABEL`: Your specified label in the `HSM_KEY_LABEL` environment variable.
|
||||||
|
* `CKA_ENCRYPT`: `true` — Encryption capabilities enabled.
|
||||||
|
* `CKA_DECRYPT`: `true` — Decryption capabilities enabled.
|
||||||
|
* `CKA_TOKEN`: `true` — The key material will persist in your HSM so it can be reused.
|
||||||
|
* `CKA_EXTRACTABLE`: `false` — The key material is not extractable from the HSM.
|
||||||
|
* `CKA_SENSITIVE`: `true` — The key material is marked as sensitive.
|
||||||
|
* `CKA_PRIVATE`: `true` — The key material is marked as private to the slot and can't be accessed from other slots.
|
||||||
|
|
||||||
|
### HMAC Key
|
||||||
|
|
||||||
|
<Accordion title="Bring your own key minimum requirements (optional)">
|
||||||
|
If you bring your own HMAC key and don't let Infisical create it for you it must have at least the following attributes:
|
||||||
|
|
||||||
|
* `CKA_CLASS`: `CKO_SECRET_KEY` — Defines the key class _(secret key)_.
|
||||||
|
* `CKA_KEY_TYPE`: `CKO_GENERIC_SECRET` — Defines the key class _(generic secret key)_.
|
||||||
|
* `CKA_VALUE_LEN`: `32` — 256-bit key size
|
||||||
|
* `CKA_SIGN`: `true` — Signing capabilities enabled
|
||||||
|
* `CKA_VERIFY`: `true` — Verifying capabilities enabled.
|
||||||
|
* `CKA_TOKEN`: `true` — The key material will persist in your HSM so it can be reused.
|
||||||
|
|
||||||
|
<Warning>
|
||||||
|
Note that for security reasons it is highly recommended to create an HMAC key with the full set of key attributes seen below if you're going to bring your own key.
|
||||||
|
</Warning>
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
* `CKA_CLASS`: `CKO_SECRET_KEY` — Defines the key class _(secret key)_.
|
||||||
|
* `CKA_KEY_TYPE`: `CKO_GENERIC_SECRET` — Defines the key class _(generic secret key)_.
|
||||||
|
* `CKA_VALUE_LEN`: `32` — 256-bit key size.
|
||||||
|
* `CKA_LABEL`: Your specified label in the `HSM_KEY_LABEL` environment variable, suffixed with `_HMAC`. If you specify `infisical-key-v1`, then the HMAC key label will become `infisical-key-v1_HMAC`.
|
||||||
|
* `CKA_SIGN`: `true` — Signing capabilities enabled
|
||||||
|
* `CKA_VERIFY`: `true` — Verifying capabilities enabled.
|
||||||
|
* `CKA_TOKEN`: `true` — The key material will persist in your HSM so it can be reused.
|
||||||
|
* `CKA_EXTRACTABLE`: `false` — The key material is not extractable from the HSM.
|
||||||
|
* `CKA_SENSITIVE`: `true` — The key material is marked as sensitive.
|
||||||
|
* `CKA_PRIVATE`: `true` — The key material is marked as private to the slot and can't be accessed from other slots.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Setup Instructions
|
## Setup Instructions
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
title: "Session Recording"
|
||||||
|
sidebarTitle: "Session Recording"
|
||||||
|
description: "Learn how Infisical records and stores session activity for auditing and monitoring."
|
||||||
|
---
|
||||||
|
|
||||||
|
Infisical's Privileged Access Management (PAM) provides robust session recording capabilities to help you audit and monitor user activity across your infrastructure.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
When a user initiates a session through the Infisical Gateway, a recording of the session begins. The gateway securely caches all recording data in temporary encrypted files on its local system.
|
||||||
|
|
||||||
|
Once the session concludes, the gateway transmits the complete recording to the Infisical platform for long-term, centralized storage. This asynchronous process ensures that sessions remain operational even if the connection to the Infisical platform is temporarily lost. After the upload is complete, administrators can search and review the session logs in the Infisical UI.
|
||||||
|
|
||||||
|
## What's Captured
|
||||||
|
|
||||||
|
The content captured during a session depends on the type of resource being accessed.
|
||||||
|
|
||||||
|
### Database Sessions
|
||||||
|
|
||||||
|
For database connections, Infisical captures all queries executed and their corresponding responses.
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Support for additional resource types like SSH and RDP is coming soon.
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
## Viewing Recordings
|
||||||
|
|
||||||
|
To review session recordings:
|
||||||
|
|
||||||
|
1. Navigate to the **PAM Sessions** page in your project.
|
||||||
|
2. Click on a session from the list to view its details.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The session details page provides key information, including the complete session logs, connection status, the user who initiated it, and more.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Searching Logs
|
||||||
|
|
||||||
|
You can use the search bar to quickly find relevant information:
|
||||||
|
|
||||||
|
- **On the main Sessions page:** Search across all session logs to locate specific queries or outputs.
|
||||||
|
- **On an individual session page:** Search within that specific session's logs to pinpoint activity.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
<AccordionGroup>
|
||||||
|
<Accordion title="Are session recordings encrypted?">
|
||||||
|
Yes. All session recordings are encrypted at rest by default, ensuring your audit data is always secure.
|
||||||
|
</Accordion>
|
||||||
|
<Accordion title="Why aren't recordings streamed in real-time?">
|
||||||
|
Currently, Infisical uses an asynchronous approach where the gateway records the entire session locally before uploading it. This design makes your PAM sessions more resilient, as they don't depend on a constant, active connection to the Infisical platform. We may introduce live streaming capabilities in a future release.
|
||||||
|
</Accordion>
|
||||||
|
</AccordionGroup>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 415 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 462 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 500 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 570 KiB |
@@ -3,6 +3,14 @@ title: "Chef Connection"
|
|||||||
description: "Learn how to configure a Chef Connection for Infisical."
|
description: "Learn how to configure a Chef Connection for Infisical."
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
Chef App Connection is a paid feature.
|
||||||
|
|
||||||
|
If you're using Infisical Cloud, then it is available under the **Enterprise Tier**. If you're self-hosting Infisical,
|
||||||
|
then you should contact [email protected] to purchase an enterprise license to use it.
|
||||||
|
|
||||||
|
</Info>
|
||||||
|
|
||||||
Infisical supports the use of User Private Key to connect with Chef Server.
|
Infisical supports the use of User Private Key to connect with Chef Server.
|
||||||
|
|
||||||
Please access your **starter kit** to get all the required information to create a Chef Connection.
|
Please access your **starter kit** to get all the required information to create a Chef Connection.
|
||||||
|
|||||||
@@ -51,6 +51,27 @@ $ kubectl logs deployment/infisical-agent-injector
|
|||||||
2025/05/19 14:20:06 Successfully updated webhook configuration with CA bundle
|
2025/05/19 14:20:06 Successfully updated webhook configuration with CA bundle
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Windows support
|
||||||
|
|
||||||
|
The Infisical Agent Injector supports both running on Windows-based pods, and injecting the agent into Windows-based pods.
|
||||||
|
|
||||||
|
To run the agent injector on a Windows pod, it's important that you add the `nodeSelector.kubernetes.io/os` label to the pod's deployment with the value `windows`.
|
||||||
|
This can be done by changing the helm values.yaml by adding the following:
|
||||||
|
|
||||||
|
```yaml values.yaml
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/os: windows
|
||||||
|
```
|
||||||
|
|
||||||
|
By default the agent injector will run on Linux-based pods, unless you specify otherwise like in the example above.
|
||||||
|
No extra configuration is needed to inject into Windows-based pods, as the agent injector will detect and handle the injection automatically.
|
||||||
|
|
||||||
|
The Agent Injector will only run and inject into Windows-based pods that are running on the supported Windows versions:
|
||||||
|
- **Windows Server 2022**
|
||||||
|
|
||||||
|
We're looking to add support for other Windows versions in the future. If you're using a different Windows version, please let us know by opening [an issue](https://github.com/Infisical/infisical-agent-injector/issues/new), and we'll look into adding support for your desired version as soon as possible.
|
||||||
|
|
||||||
|
|
||||||
## Supported annotations
|
## Supported annotations
|
||||||
|
|
||||||
The Infisical Agent Injector supports the following annotations:
|
The Infisical Agent Injector supports the following annotations:
|
||||||
|
|||||||
@@ -3,6 +3,14 @@ title: "Chef Sync"
|
|||||||
description: "Learn how to configure a Chef Sync for Infisical."
|
description: "Learn how to configure a Chef Sync for Infisical."
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
Chef Sync is a paid feature.
|
||||||
|
|
||||||
|
If you're using Infisical Cloud, then it is available under the **Enterprise Tier**. If you're self-hosting Infisical,
|
||||||
|
then you should contact [email protected] to purchase an enterprise license to use it.
|
||||||
|
|
||||||
|
</Info>
|
||||||
|
|
||||||
**Prerequisites:**
|
**Prerequisites:**
|
||||||
|
|
||||||
- Create a [Chef Connection](/integrations/app-connections/chef)
|
- Create a [Chef Connection](/integrations/app-connections/chef)
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ Both approaches provide the same metrics data in OTEL format, so you can choose
|
|||||||
- Access to deploy monitoring services (Prometheus, Grafana, etc.)
|
- Access to deploy monitoring services (Prometheus, Grafana, etc.)
|
||||||
- Basic understanding of Prometheus and Grafana
|
- Basic understanding of Prometheus and Grafana
|
||||||
|
|
||||||
## Environment Variables
|
## Setup
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
Configure the following environment variables in your Infisical backend:
|
Configure the following environment variables in your Infisical backend:
|
||||||
|
|
||||||
@@ -37,36 +39,32 @@ OTEL_TELEMETRY_COLLECTION_ENABLED=true
|
|||||||
|
|
||||||
# Choose export type: "prometheus" or "otlp"
|
# Choose export type: "prometheus" or "otlp"
|
||||||
OTEL_EXPORT_TYPE=prometheus
|
OTEL_EXPORT_TYPE=prometheus
|
||||||
|
|
||||||
# For OTLP push mode, also configure:
|
|
||||||
# OTEL_EXPORT_OTLP_ENDPOINT=http://otel-collector:4318/v1/metrics
|
|
||||||
# OTEL_COLLECTOR_BASIC_AUTH_USERNAME=your_collector_username
|
|
||||||
# OTEL_COLLECTOR_BASIC_AUTH_PASSWORD=your_collector_password
|
|
||||||
# OTEL_OTLP_PUSH_INTERVAL=30000
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note**: The `OTEL_COLLECTOR_BASIC_AUTH_USERNAME` and `OTEL_COLLECTOR_BASIC_AUTH_PASSWORD` values must match the credentials configured in your OpenTelemetry Collector's `basicauth/server` extension. These are not hardcoded values - you configure them in your collector configuration file.
|
<Tabs>
|
||||||
|
<Tab title="Pull-based Monitoring (Prometheus)">
|
||||||
## Option 1: Pull-based Monitoring (Prometheus)
|
|
||||||
|
|
||||||
This approach exposes metrics on port 9464 at the `/metrics` endpoint, allowing Prometheus to scrape the data. The metrics are exposed in Prometheus format but originate from OpenTelemetry instrumentation.
|
This approach exposes metrics on port 9464 at the `/metrics` endpoint, allowing Prometheus to scrape the data. The metrics are exposed in Prometheus format but originate from OpenTelemetry instrumentation.
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
1. **Enable Prometheus export in Infisical**:
|
<Steps>
|
||||||
|
<Step title="Enable Prometheus export in Infisical">
|
||||||
```bash
|
```bash
|
||||||
OTEL_TELEMETRY_COLLECTION_ENABLED=true
|
OTEL_TELEMETRY_COLLECTION_ENABLED=true
|
||||||
OTEL_EXPORT_TYPE=prometheus
|
OTEL_EXPORT_TYPE=prometheus
|
||||||
```
|
```
|
||||||
|
</Step>
|
||||||
|
|
||||||
2. **Expose the metrics port** in your Infisical backend:
|
<Step title="Expose the metrics port">
|
||||||
|
Expose the metrics port in your Infisical backend:
|
||||||
|
|
||||||
- **Docker**: Expose port 9464
|
- **Docker**: Expose port 9464
|
||||||
- **Kubernetes**: Create a service exposing port 9464
|
- **Kubernetes**: Create a service exposing port 9464
|
||||||
- **Other**: Ensure port 9464 is accessible to your monitoring stack
|
- **Other**: Ensure port 9464 is accessible to your monitoring stack
|
||||||
|
</Step>
|
||||||
|
|
||||||
3. **Create Prometheus configuration** (`prometheus.yml`):
|
<Step title="Create Prometheus configuration">
|
||||||
|
Create `prometheus.yml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
global:
|
global:
|
||||||
@@ -81,17 +79,23 @@ This approach exposes metrics on port 9464 at the `/metrics` endpoint, allowing
|
|||||||
metrics_path: "/metrics"
|
metrics_path: "/metrics"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note**: Replace `infisical-backend:9464` with the actual hostname and port where your Infisical backend is running. This could be:
|
<Note>
|
||||||
|
Replace `infisical-backend:9464` with the actual hostname and port where your Infisical backend is running. This could be:
|
||||||
|
|
||||||
- **Docker Compose**: `infisical-backend:9464` (service name)
|
- **Docker Compose**: `infisical-backend:9464` (service name)
|
||||||
- **Kubernetes**: `infisical-backend.default.svc.cluster.local:9464` (service name)
|
- **Kubernetes**: `infisical-backend.default.svc.cluster.local:9464` (service name)
|
||||||
- **Bare Metal**: `192.168.1.100:9464` (actual IP address)
|
- **Bare Metal**: `192.168.1.100:9464` (actual IP address)
|
||||||
- **Cloud**: `your-infisical.example.com:9464` (domain name)
|
- **Cloud**: `your-infisical.example.com:9464` (domain name)
|
||||||
|
</Note>
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
### Deployment Options
|
### Deployment Options
|
||||||
|
|
||||||
#### Docker Compose
|
Once you've configured Infisical to expose metrics, you'll need to deploy Prometheus to scrape and store them. Below are examples for different deployment environments. Choose the option that matches your infrastructure.
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Docker Compose">
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
prometheus:
|
prometheus:
|
||||||
@@ -111,9 +115,8 @@ services:
|
|||||||
- GF_SECURITY_ADMIN_USER=admin
|
- GF_SECURITY_ADMIN_USER=admin
|
||||||
- GF_SECURITY_ADMIN_PASSWORD=admin
|
- GF_SECURITY_ADMIN_PASSWORD=admin
|
||||||
```
|
```
|
||||||
|
</Tab>
|
||||||
#### Kubernetes
|
<Tab title="Kubernetes">
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# prometheus-deployment.yaml
|
# prometheus-deployment.yaml
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
@@ -157,9 +160,8 @@ spec:
|
|||||||
targetPort: 9090
|
targetPort: 9090
|
||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
```
|
```
|
||||||
|
</Tab>
|
||||||
#### Helm
|
<Tab title="Helm">
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
|
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
|
||||||
helm install prometheus prometheus-community/prometheus \
|
helm install prometheus prometheus-community/prometheus \
|
||||||
@@ -167,15 +169,17 @@ helm install prometheus prometheus-community/prometheus \
|
|||||||
--set server.config.scrape_configs[0].job_name=infisical \
|
--set server.config.scrape_configs[0].job_name=infisical \
|
||||||
--set server.config.scrape_configs[0].static_configs[0].targets[0]=infisical-backend:9464
|
--set server.config.scrape_configs[0].static_configs[0].targets[0]=infisical-backend:9464
|
||||||
```
|
```
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
## Option 2: Push-based Monitoring (OTLP)
|
</Tab>
|
||||||
|
<Tab title="Push-based Monitoring (OTLP)">
|
||||||
This approach sends metrics directly to an OpenTelemetry Collector via the OTLP protocol. This gives you the most flexibility as you can configure the collector to export to multiple backends simultaneously.
|
This approach sends metrics directly to an OpenTelemetry Collector via the OTLP protocol. This gives you the most flexibility as you can configure the collector to export to multiple backends simultaneously.
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
1. **Enable OTLP export in Infisical**:
|
<Steps>
|
||||||
|
<Step title="Enable OTLP export in Infisical">
|
||||||
```bash
|
```bash
|
||||||
OTEL_TELEMETRY_COLLECTION_ENABLED=true
|
OTEL_TELEMETRY_COLLECTION_ENABLED=true
|
||||||
OTEL_EXPORT_TYPE=otlp
|
OTEL_EXPORT_TYPE=otlp
|
||||||
@@ -184,8 +188,10 @@ This approach sends metrics directly to an OpenTelemetry Collector via the OTLP
|
|||||||
OTEL_COLLECTOR_BASIC_AUTH_PASSWORD=infisical
|
OTEL_COLLECTOR_BASIC_AUTH_PASSWORD=infisical
|
||||||
OTEL_OTLP_PUSH_INTERVAL=30000
|
OTEL_OTLP_PUSH_INTERVAL=30000
|
||||||
```
|
```
|
||||||
|
</Step>
|
||||||
|
|
||||||
2. **Create OpenTelemetry Collector configuration** (`otel-collector-config.yaml`):
|
<Step title="Create OpenTelemetry Collector configuration">
|
||||||
|
Create `otel-collector-config.yaml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
extensions:
|
extensions:
|
||||||
@@ -236,9 +242,13 @@ This approach sends metrics directly to an OpenTelemetry Collector via the OTLP
|
|||||||
exporters: [prometheus]
|
exporters: [prometheus]
|
||||||
```
|
```
|
||||||
|
|
||||||
**Important**: Replace `your_username:your_password` with your chosen credentials. These must match the values you set in Infisical's `OTEL_COLLECTOR_BASIC_AUTH_USERNAME` and `OTEL_COLLECTOR_BASIC_AUTH_PASSWORD` environment variables.
|
<Warning>
|
||||||
|
Replace `your_username:your_password` with your chosen credentials. These must match the values you set in Infisical's `OTEL_COLLECTOR_BASIC_AUTH_USERNAME` and `OTEL_COLLECTOR_BASIC_AUTH_PASSWORD` environment variables.
|
||||||
|
</Warning>
|
||||||
|
</Step>
|
||||||
|
|
||||||
3. **Create Prometheus configuration** for the collector:
|
<Step title="Create Prometheus configuration">
|
||||||
|
Create Prometheus configuration for the collector:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
global:
|
global:
|
||||||
@@ -253,17 +263,23 @@ This approach sends metrics directly to an OpenTelemetry Collector via the OTLP
|
|||||||
metrics_path: "/metrics"
|
metrics_path: "/metrics"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note**: Replace `otel-collector:8889` with the actual hostname and port where your OpenTelemetry Collector is running. This could be:
|
<Note>
|
||||||
|
Replace `otel-collector:8889` with the actual hostname and port where your OpenTelemetry Collector is running. This could be:
|
||||||
|
|
||||||
- **Docker Compose**: `otel-collector:8889` (service name)
|
- **Docker Compose**: `otel-collector:8889` (service name)
|
||||||
- **Kubernetes**: `otel-collector.default.svc.cluster.local:8889` (service name)
|
- **Kubernetes**: `otel-collector.default.svc.cluster.local:8889` (service name)
|
||||||
- **Bare Metal**: `192.168.1.100:8889` (actual IP address)
|
- **Bare Metal**: `192.168.1.100:8889` (actual IP address)
|
||||||
- **Cloud**: `your-collector.example.com:8889` (domain name)
|
- **Cloud**: `your-collector.example.com:8889` (domain name)
|
||||||
|
</Note>
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
### Deployment Options
|
### Deployment Options
|
||||||
|
|
||||||
#### Docker Compose
|
After configuring Infisical and the OpenTelemetry Collector, you'll need to deploy the collector to receive metrics from Infisical. Below are examples for different deployment environments. Choose the option that matches your infrastructure.
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Docker Compose">
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
otel-collector:
|
otel-collector:
|
||||||
@@ -276,9 +292,8 @@ services:
|
|||||||
command:
|
command:
|
||||||
- "--config=/etc/otelcol-contrib/config.yaml"
|
- "--config=/etc/otelcol-contrib/config.yaml"
|
||||||
```
|
```
|
||||||
|
</Tab>
|
||||||
#### Kubernetes
|
<Tab title="Kubernetes">
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# otel-collector-deployment.yaml
|
# otel-collector-deployment.yaml
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
@@ -309,15 +324,19 @@ spec:
|
|||||||
configMap:
|
configMap:
|
||||||
name: otel-collector-config
|
name: otel-collector-config
|
||||||
```
|
```
|
||||||
|
</Tab>
|
||||||
#### Helm
|
<Tab title="Helm">
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
|
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
|
||||||
helm install otel-collector open-telemetry/opentelemetry-collector \
|
helm install otel-collector open-telemetry/opentelemetry-collector \
|
||||||
--set config.receivers.otlp.protocols.http.endpoint=0.0.0.0:4318 \
|
--set config.receivers.otlp.protocols.http.endpoint=0.0.0.0:4318 \
|
||||||
--set config.exporters.prometheus.endpoint=0.0.0.0:8889
|
--set config.exporters.prometheus.endpoint=0.0.0.0:8889
|
||||||
```
|
```
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
## Available Metrics
|
## Available Metrics
|
||||||
|
|
||||||
@@ -327,13 +346,17 @@ Infisical exposes the following key metrics in OpenTelemetry format:
|
|||||||
|
|
||||||
These metrics track all HTTP API requests to Infisical, including request counts, latency, and errors. Use these to monitor overall API health, identify performance bottlenecks, and track usage patterns across users and machine identities.
|
These metrics track all HTTP API requests to Infisical, including request counts, latency, and errors. Use these to monitor overall API health, identify performance bottlenecks, and track usage patterns across users and machine identities.
|
||||||
|
|
||||||
#### Total API Requests
|
<AccordionGroup>
|
||||||
|
<Accordion title="Total API Requests">
|
||||||
|
**Metric Name**: `infisical.http.server.request.count`
|
||||||
|
|
||||||
- **Metric Name**: `infisical.http.server.request.count`
|
**Type**: Counter
|
||||||
- **Type**: Counter
|
|
||||||
- **Unit**: `{request}`
|
**Unit**: `{request}`
|
||||||
- **Description**: Total number of API requests to Infisical (covers both human users and machine identities)
|
|
||||||
- **Attributes**:
|
**Description**: Total number of API requests to Infisical (covers both human users and machine identities)
|
||||||
|
|
||||||
|
**Attributes**:
|
||||||
- `infisical.organization.id` (string): Organization ID
|
- `infisical.organization.id` (string): Organization ID
|
||||||
- `infisical.organization.name` (string): Organization name (e.g., "Platform Engineering Team")
|
- `infisical.organization.name` (string): Organization name (e.g., "Platform Engineering Team")
|
||||||
- `infisical.user.id` (string, optional): User ID if human user
|
- `infisical.user.id` (string, optional): User ID if human user
|
||||||
@@ -348,15 +371,20 @@ These metrics track all HTTP API requests to Infisical, including request counts
|
|||||||
- `infisical.project.name` (string, optional): Project name
|
- `infisical.project.name` (string, optional): Project name
|
||||||
- `user_agent.original` (string, optional): User agent string
|
- `user_agent.original` (string, optional): User agent string
|
||||||
- `client.address` (string, optional): IP address
|
- `client.address` (string, optional): IP address
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
#### Request Duration
|
<Accordion title="Request Duration">
|
||||||
|
**Metric Name**: `infisical.http.server.request.duration`
|
||||||
|
|
||||||
- **Metric Name**: `infisical.http.server.request.duration`
|
**Type**: Histogram
|
||||||
- **Type**: Histogram
|
|
||||||
- **Unit**: `s` (seconds)
|
**Unit**: `s` (seconds)
|
||||||
- **Description**: API request latency
|
|
||||||
- **Buckets**: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
|
**Description**: API request latency
|
||||||
- **Attributes**:
|
|
||||||
|
**Buckets**: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
|
||||||
|
|
||||||
|
**Attributes**:
|
||||||
- `infisical.organization.id` (string): Organization ID
|
- `infisical.organization.id` (string): Organization ID
|
||||||
- `infisical.organization.name` (string): Organization name
|
- `infisical.organization.name` (string): Organization name
|
||||||
- `infisical.user.id` (string, optional): User ID if human user
|
- `infisical.user.id` (string, optional): User ID if human user
|
||||||
@@ -368,14 +396,18 @@ These metrics track all HTTP API requests to Infisical, including request counts
|
|||||||
- `http.response.status_code` (int): HTTP status code
|
- `http.response.status_code` (int): HTTP status code
|
||||||
- `infisical.project.id` (string, optional): Project ID
|
- `infisical.project.id` (string, optional): Project ID
|
||||||
- `infisical.project.name` (string, optional): Project name
|
- `infisical.project.name` (string, optional): Project name
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
#### API Errors by Actor
|
<Accordion title="API Errors by Actor">
|
||||||
|
**Metric Name**: `infisical.http.server.error.count`
|
||||||
|
|
||||||
- **Metric Name**: `infisical.http.server.error.count`
|
**Type**: Counter
|
||||||
- **Type**: Counter
|
|
||||||
- **Unit**: `{error}`
|
**Unit**: `{error}`
|
||||||
- **Description**: API errors grouped by actor (for identifying misconfigured services)
|
|
||||||
- **Attributes**:
|
**Description**: API errors grouped by actor (for identifying misconfigured services)
|
||||||
|
|
||||||
|
**Attributes**:
|
||||||
- `infisical.organization.id` (string): Organization ID
|
- `infisical.organization.id` (string): Organization ID
|
||||||
- `infisical.organization.name` (string): Organization name
|
- `infisical.organization.name` (string): Organization name
|
||||||
- `infisical.user.id` (string, optional): User ID if human
|
- `infisical.user.id` (string, optional): User ID if human
|
||||||
@@ -389,18 +421,24 @@ These metrics track all HTTP API requests to Infisical, including request counts
|
|||||||
- `infisical.project.name` (string, optional): Project name
|
- `infisical.project.name` (string, optional): Project name
|
||||||
- `client.address` (string, optional): IP address
|
- `client.address` (string, optional): IP address
|
||||||
- `user_agent.original` (string, optional): User agent information
|
- `user_agent.original` (string, optional): User agent information
|
||||||
|
</Accordion>
|
||||||
|
</AccordionGroup>
|
||||||
|
|
||||||
### Secret Operations Metrics
|
### Secret Operations Metrics
|
||||||
|
|
||||||
These metrics provide visibility into secret access patterns, helping you understand which secrets are being accessed, by whom, and from where. Essential for security auditing and access pattern analysis.
|
These metrics provide visibility into secret access patterns, helping you understand which secrets are being accessed, by whom, and from where. Essential for security auditing and access pattern analysis.
|
||||||
|
|
||||||
#### Secret Read Operations
|
<AccordionGroup>
|
||||||
|
<Accordion title="Secret Read Operations">
|
||||||
|
**Metric Name**: `infisical.secret.read.count`
|
||||||
|
|
||||||
- **Metric Name**: `infisical.secret.read.count`
|
**Type**: Counter
|
||||||
- **Type**: Counter
|
|
||||||
- **Unit**: `{operation}`
|
**Unit**: `{operation}`
|
||||||
- **Description**: Number of secret read operations
|
|
||||||
- **Attributes**:
|
**Description**: Number of secret read operations
|
||||||
|
|
||||||
|
**Attributes**:
|
||||||
- `infisical.organization.id` (string): Organization ID
|
- `infisical.organization.id` (string): Organization ID
|
||||||
- `infisical.organization.name` (string): Organization name
|
- `infisical.organization.name` (string): Organization name
|
||||||
- `infisical.project.id` (string): Project ID
|
- `infisical.project.id` (string): Project ID
|
||||||
@@ -414,18 +452,24 @@ These metrics provide visibility into secret access patterns, helping you unders
|
|||||||
- `infisical.identity.name` (string, optional): Machine identity name
|
- `infisical.identity.name` (string, optional): Machine identity name
|
||||||
- `user_agent.original` (string, optional): User agent/SDK information
|
- `user_agent.original` (string, optional): User agent/SDK information
|
||||||
- `client.address` (string, optional): IP address
|
- `client.address` (string, optional): IP address
|
||||||
|
</Accordion>
|
||||||
|
</AccordionGroup>
|
||||||
|
|
||||||
### Authentication Metrics
|
### Authentication Metrics
|
||||||
|
|
||||||
These metrics track authentication attempts and outcomes, enabling you to monitor login success rates, detect potential security threats, and identify authentication issues.
|
These metrics track authentication attempts and outcomes, enabling you to monitor login success rates, detect potential security threats, and identify authentication issues.
|
||||||
|
|
||||||
#### Login Attempts
|
<AccordionGroup>
|
||||||
|
<Accordion title="Login Attempts">
|
||||||
|
**Metric Name**: `infisical.auth.attempt.count`
|
||||||
|
|
||||||
- **Metric Name**: `infisical.auth.attempt.count`
|
**Type**: Counter
|
||||||
- **Type**: Counter
|
|
||||||
- **Unit**: `{attempt}`
|
**Unit**: `{attempt}`
|
||||||
- **Description**: Authentication attempts (both successful and failed)
|
|
||||||
- **Attributes**:
|
**Description**: Authentication attempts (both successful and failed)
|
||||||
|
|
||||||
|
**Attributes**:
|
||||||
- `infisical.organization.id` (string): Organization ID
|
- `infisical.organization.id` (string): Organization ID
|
||||||
- `infisical.organization.name` (string): Organization name
|
- `infisical.organization.name` (string): Organization name
|
||||||
- `infisical.user.id` (string, optional): User ID if human (if identifiable)
|
- `infisical.user.id` (string, optional): User ID if human (if identifiable)
|
||||||
@@ -438,55 +482,75 @@ These metrics track authentication attempts and outcomes, enabling you to monito
|
|||||||
- `client.address` (string): IP address
|
- `client.address` (string): IP address
|
||||||
- `user_agent.original` (string, optional): User agent/client information
|
- `user_agent.original` (string, optional): User agent/client information
|
||||||
- `infisical.auth.attempt.username` (string, optional): Attempted username/email (if available)
|
- `infisical.auth.attempt.username` (string, optional): Attempted username/email (if available)
|
||||||
|
</Accordion>
|
||||||
### Legacy Metrics
|
</AccordionGroup>
|
||||||
|
|
||||||
These metrics are from the previous instrumentation and may be deprecated in future versions. Consider migrating to the new Core API Metrics for more comprehensive observability.
|
|
||||||
|
|
||||||
- `API_latency` - API request latency histogram in milliseconds (Labels: `route`, `method`, `statusCode`)
|
|
||||||
- `API_errors` - API error count histogram (Labels: `route`, `method`, `type`, `name`)
|
|
||||||
|
|
||||||
### Integration & Secret Sync Metrics
|
### Integration & Secret Sync Metrics
|
||||||
|
|
||||||
These metrics monitor secret synchronization operations between Infisical and external systems, helping you track sync health, identify integration failures, and troubleshoot connectivity issues.
|
These metrics monitor secret synchronization operations between Infisical and external systems, helping you track sync health, identify integration failures, and troubleshoot connectivity issues.
|
||||||
|
|
||||||
- `integration_secret_sync_errors` - Integration secret sync error count
|
<AccordionGroup>
|
||||||
|
<Accordion title="integration_secret_sync_errors">
|
||||||
|
Integration secret sync error count
|
||||||
|
|
||||||
- **Labels**: `version`, `integration`, `integrationId`, `type`, `status`, `name`, `projectId`
|
- **Labels**: `version`, `integration`, `integrationId`, `type`, `status`, `name`, `projectId`
|
||||||
- **Example**: Monitor integration sync failures across different services
|
- **Example**: Monitor integration sync failures across different services
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
- `secret_sync_sync_secrets_errors` - Secret sync operation error count
|
<Accordion title="secret_sync_sync_secrets_errors">
|
||||||
|
Secret sync operation error count
|
||||||
|
|
||||||
- **Labels**: `version`, `destination`, `syncId`, `projectId`, `type`, `status`, `name`
|
- **Labels**: `version`, `destination`, `syncId`, `projectId`, `type`, `status`, `name`
|
||||||
- **Example**: Track secret sync failures to external systems
|
- **Example**: Track secret sync failures to external systems
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
- `secret_sync_import_secrets_errors` - Secret import operation error count
|
<Accordion title="secret_sync_import_secrets_errors">
|
||||||
|
Secret import operation error count
|
||||||
|
|
||||||
- **Labels**: `version`, `destination`, `syncId`, `projectId`, `type`, `status`, `name`
|
- **Labels**: `version`, `destination`, `syncId`, `projectId`, `type`, `status`, `name`
|
||||||
- **Example**: Monitor secret import failures
|
- **Example**: Monitor secret import failures
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
<Accordion title="secret_sync_remove_secrets_errors">
|
||||||
|
Secret removal operation error count
|
||||||
|
|
||||||
- `secret_sync_remove_secrets_errors` - Secret removal operation error count
|
|
||||||
- **Labels**: `version`, `destination`, `syncId`, `projectId`, `type`, `status`, `name`
|
- **Labels**: `version`, `destination`, `syncId`, `projectId`, `type`, `status`, `name`
|
||||||
- **Example**: Track secret removal operation failures
|
- **Example**: Track secret removal operation failures
|
||||||
|
</Accordion>
|
||||||
|
</AccordionGroup>
|
||||||
|
|
||||||
### System Metrics
|
### System Metrics
|
||||||
|
|
||||||
These low-level HTTP metrics are automatically collected by OpenTelemetry's instrumentation layer, providing baseline performance data for all HTTP traffic.
|
These low-level HTTP metrics are automatically collected by OpenTelemetry's instrumentation layer, providing baseline performance data for all HTTP traffic.
|
||||||
|
|
||||||
- `http_server_duration` - HTTP server request duration metrics (histogram buckets, count, sum)
|
<AccordionGroup>
|
||||||
- `http_client_duration` - HTTP client request duration metrics (histogram buckets, count, sum)
|
<Accordion title="http_server_duration">
|
||||||
|
HTTP server request duration metrics (histogram buckets, count, sum)
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
<Accordion title="http_client_duration">
|
||||||
|
HTTP client request duration metrics (histogram buckets, count, sum)
|
||||||
|
</Accordion>
|
||||||
|
</AccordionGroup>
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### Common Issues
|
<Accordion title="Metrics not appearing">
|
||||||
|
If your metrics are not showing up in Prometheus or your monitoring system, check the following:
|
||||||
|
|
||||||
1. **Metrics not appearing**:
|
- Verify `OTEL_TELEMETRY_COLLECTION_ENABLED=true` is set in your Infisical environment variables
|
||||||
|
- Ensure the correct `OTEL_EXPORT_TYPE` is set (`prometheus` or `otlp`)
|
||||||
|
- Check network connectivity between Infisical and your monitoring services (Prometheus or OTLP collector)
|
||||||
|
- For pull-based monitoring: Verify port 9464 is exposed and accessible
|
||||||
|
- For push-based monitoring: Verify the OTLP endpoint URL is correct and reachable
|
||||||
|
- Check Infisical backend logs for any errors related to metrics export
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
- Check if `OTEL_TELEMETRY_COLLECTION_ENABLED=true`
|
<Accordion title="Authentication errors">
|
||||||
- Verify the correct `OTEL_EXPORT_TYPE` is set
|
If you're experiencing authentication errors with the OpenTelemetry Collector:
|
||||||
- Check network connectivity between services
|
|
||||||
|
|
||||||
2. **Authentication errors**:
|
- Verify basic auth credentials in your OTLP configuration match between Infisical and the collector
|
||||||
|
- Check that `OTEL_COLLECTOR_BASIC_AUTH_USERNAME` and `OTEL_COLLECTOR_BASIC_AUTH_PASSWORD` match the credentials in your `otel-collector-config.yaml`
|
||||||
- Verify basic auth credentials in OTLP configuration
|
- Ensure the htpasswd format in the collector configuration is correct
|
||||||
- Check if credentials match between Infisical and collector
|
- Test the collector endpoint manually using curl with the same credentials to verify they work
|
||||||
|
</Accordion>
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ export const WishForm = () => {
|
|||||||
const [isOpen, setIsOpen] = useToggle(false);
|
const [isOpen, setIsOpen] = useToggle(false);
|
||||||
|
|
||||||
const createWish = async (data: TFormData) => {
|
const createWish = async (data: TFormData) => {
|
||||||
try {
|
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
text: data.text
|
text: data.text
|
||||||
});
|
});
|
||||||
@@ -46,12 +45,6 @@ export const WishForm = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setIsOpen.off();
|
setIsOpen.off();
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
text: "An error occured while sending your wish to the Infisical team.",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ const TotpRegistration = ({ onComplete, shouldCenterQr }: Props) => {
|
|||||||
|
|
||||||
const handleTotpVerify = async (event: React.FormEvent<HTMLFormElement>) => {
|
const handleTotpVerify = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
try {
|
|
||||||
const result = await verifyUserTotp({
|
const result = await verifyUserTotp({
|
||||||
totp
|
totp
|
||||||
});
|
});
|
||||||
@@ -41,12 +40,6 @@ const TotpRegistration = ({ onComplete, shouldCenterQr }: Props) => {
|
|||||||
} else if (onComplete) {
|
} else if (onComplete) {
|
||||||
onComplete();
|
onComplete();
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to verify TOTP code",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRecoveryDownloadComplete = async () => {
|
const handleRecoveryDownloadComplete = async () => {
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ export const CreateOrgModal: FC<CreateOrgModalProps> = ({ isOpen, onClose }) =>
|
|||||||
const { mutateAsync: selectOrg } = useSelectOrganization();
|
const { mutateAsync: selectOrg } = useSelectOrganization();
|
||||||
|
|
||||||
const onFormSubmit = async ({ name }: FormData) => {
|
const onFormSubmit = async ({ name }: FormData) => {
|
||||||
try {
|
|
||||||
const organization = await createOrg({
|
const organization = await createOrg({
|
||||||
name
|
name
|
||||||
});
|
});
|
||||||
@@ -65,13 +64,6 @@ export const CreateOrgModal: FC<CreateOrgModalProps> = ({ isOpen, onClose }) =>
|
|||||||
|
|
||||||
reset();
|
reset();
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to created organization",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export const DeletePkiSyncModal = ({ isOpen, onOpenChange, pkiSync, onComplete }
|
|||||||
const handleDeletePkiSync = async () => {
|
const handleDeletePkiSync = async () => {
|
||||||
const destinationName = PKI_SYNC_MAP[destination].name;
|
const destinationName = PKI_SYNC_MAP[destination].name;
|
||||||
|
|
||||||
try {
|
|
||||||
await deleteSync.mutateAsync({
|
await deleteSync.mutateAsync({
|
||||||
syncId,
|
syncId,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -34,14 +33,6 @@ export const DeletePkiSyncModal = ({ isOpen, onOpenChange, pkiSync, onComplete }
|
|||||||
|
|
||||||
if (onComplete) onComplete();
|
if (onComplete) onComplete();
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
|
|
||||||
createNotification({
|
|
||||||
text: `Failed to delete ${destinationName} PKI Sync`,
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ const Content = ({ pkiSync, onComplete }: ContentProps) => {
|
|||||||
const triggerImportCertificates = useTriggerPkiSyncImportCertificates();
|
const triggerImportCertificates = useTriggerPkiSyncImportCertificates();
|
||||||
|
|
||||||
const handleTriggerImportCertificates = async () => {
|
const handleTriggerImportCertificates = async () => {
|
||||||
try {
|
|
||||||
await triggerImportCertificates.mutateAsync({
|
await triggerImportCertificates.mutateAsync({
|
||||||
syncId,
|
syncId,
|
||||||
destination,
|
destination,
|
||||||
@@ -34,14 +33,6 @@ const Content = ({ pkiSync, onComplete }: ContentProps) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onComplete();
|
onComplete();
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
|
|
||||||
createNotification({
|
|
||||||
text: `Failed to trigger certificate import for ${destinationName} Sync`,
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ const Content = ({ pkiSync, onComplete }: ContentProps) => {
|
|||||||
const triggerRemoveCertificates = useTriggerPkiSyncRemoveCertificates();
|
const triggerRemoveCertificates = useTriggerPkiSyncRemoveCertificates();
|
||||||
|
|
||||||
const handleTriggerRemoveCertificates = async () => {
|
const handleTriggerRemoveCertificates = async () => {
|
||||||
try {
|
|
||||||
await triggerRemoveCertificates.mutateAsync({
|
await triggerRemoveCertificates.mutateAsync({
|
||||||
syncId,
|
syncId,
|
||||||
destination,
|
destination,
|
||||||
@@ -34,14 +33,6 @@ const Content = ({ pkiSync, onComplete }: ContentProps) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onComplete();
|
onComplete();
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
|
|
||||||
createNotification({
|
|
||||||
text: `Failed to trigger certificate removal for ${destinationName} Sync`,
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -86,14 +86,8 @@ export const CreatePkiSyncForm = ({ destination, onComplete, onCancel, initialDa
|
|||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
onComplete(pkiSync);
|
onComplete(pkiSync);
|
||||||
} catch (err: Error | unknown) {
|
} catch {
|
||||||
console.error("PKI sync creation failed:", err);
|
|
||||||
setShowConfirmation(false);
|
setShowConfirmation(false);
|
||||||
createNotification({
|
|
||||||
title: `Failed to create ${destinationName} Certificate Sync`,
|
|
||||||
text: err instanceof Error ? err.message : "An unknown error occurred",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ export const EditPkiSyncForm = ({ pkiSync, fields, onComplete }: Props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = async ({ connection, ...formData }: TUpdatePkiSyncForm) => {
|
const onSubmit = async ({ connection, ...formData }: TUpdatePkiSyncForm) => {
|
||||||
try {
|
|
||||||
const updatedPkiSync = await updatePkiSync.mutateAsync({
|
const updatedPkiSync = await updatePkiSync.mutateAsync({
|
||||||
syncId: pkiSync.id,
|
syncId: pkiSync.id,
|
||||||
...formData,
|
...formData,
|
||||||
@@ -56,14 +55,6 @@ export const EditPkiSyncForm = ({ pkiSync, fields, onComplete }: Props) => {
|
|||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
onComplete(updatedPkiSync);
|
onComplete(updatedPkiSync);
|
||||||
} catch (err: any) {
|
|
||||||
console.error(err);
|
|
||||||
createNotification({
|
|
||||||
title: `Failed to update ${destinationName} PKI Sync`,
|
|
||||||
text: err.message,
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let Component: ReactNode;
|
let Component: ReactNode;
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
|
|||||||
}, [currentProject, showSlugField]);
|
}, [currentProject, showSlugField]);
|
||||||
|
|
||||||
const onFormSubmit = async (data: BaseFormData | FormDataWithSlug) => {
|
const onFormSubmit = async (data: BaseFormData | FormDataWithSlug) => {
|
||||||
try {
|
|
||||||
if (!currentProject?.id) return;
|
if (!currentProject?.id) return;
|
||||||
|
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
@@ -73,13 +72,6 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
|
|||||||
text: "Successfully updated project overview",
|
text: "Successfully updated project overview",
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to update project overview",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -141,7 +141,6 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
|
|||||||
// type check
|
// type check
|
||||||
if (!currentOrg) return;
|
if (!currentOrg) return;
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
try {
|
|
||||||
const {
|
const {
|
||||||
data: { project }
|
data: { project }
|
||||||
} = await createWs.mutateAsync({
|
} = await createWs.mutateAsync({
|
||||||
@@ -160,10 +159,6 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
|
|||||||
to: getProjectHomePage(project.type, project.environments),
|
to: getProjectHomePage(project.type, project.environments),
|
||||||
params: { projectId: project.id }
|
params: { projectId: project.id }
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
createNotification({ text: "Failed to create project", type: "error" });
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
const onSubmit = handleSubmit((data) => {
|
const onSubmit = handleSubmit((data) => {
|
||||||
return onCreateProject(data);
|
return onCreateProject(data);
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ export const DeleteSecretRotationV2Modal = ({
|
|||||||
const handleDeleteSecretRotation = async () => {
|
const handleDeleteSecretRotation = async () => {
|
||||||
const rotationType = SECRET_ROTATION_MAP[type].name;
|
const rotationType = SECRET_ROTATION_MAP[type].name;
|
||||||
|
|
||||||
try {
|
|
||||||
await deleteSecretRotation.mutateAsync({
|
await deleteSecretRotation.mutateAsync({
|
||||||
rotationId,
|
rotationId,
|
||||||
type,
|
type,
|
||||||
@@ -54,12 +53,6 @@ export const DeleteSecretRotationV2Modal = ({
|
|||||||
|
|
||||||
if (onComplete) onComplete();
|
if (onComplete) onComplete();
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
text: `Failed to delete ${rotationType} Rotation`,
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ const Content = ({ secretRotation, onComplete }: ContentProps) => {
|
|||||||
const rotationType = SECRET_ROTATION_MAP[type].name;
|
const rotationType = SECRET_ROTATION_MAP[type].name;
|
||||||
|
|
||||||
const handleRotateSecrets = async () => {
|
const handleRotateSecrets = async () => {
|
||||||
try {
|
|
||||||
await rotateSecrets.mutateAsync({
|
await rotateSecrets.mutateAsync({
|
||||||
rotationId,
|
rotationId,
|
||||||
type,
|
type,
|
||||||
@@ -36,14 +35,6 @@ const Content = ({ secretRotation, onComplete }: ContentProps) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onComplete();
|
onComplete();
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
|
|
||||||
createNotification({
|
|
||||||
text: `Failed to rotate ${rotationType} secrets`,
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -120,7 +120,6 @@ export const SecretRotationV2Form = ({
|
|||||||
environment: environment.slug,
|
environment: environment.slug,
|
||||||
projectId: currentProject.id
|
projectId: currentProject.id
|
||||||
});
|
});
|
||||||
try {
|
|
||||||
const rotation = await mutation;
|
const rotation = await mutation;
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
@@ -128,13 +127,6 @@ export const SecretRotationV2Form = ({
|
|||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
onComplete(rotation);
|
onComplete(rotation);
|
||||||
} catch (err: any) {
|
|
||||||
createNotification({
|
|
||||||
title: `Failed to ${secretRotation ? "update" : "create"} ${rotationType} Rotation`,
|
|
||||||
text: err.message,
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePrev = () => {
|
const handlePrev = () => {
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export const DeleteSecretScanningDataSourceModal = ({
|
|||||||
const handleDeleteDataSource = async () => {
|
const handleDeleteDataSource = async () => {
|
||||||
const dataSourceType = SECRET_SCANNING_DATA_SOURCE_MAP[type].name;
|
const dataSourceType = SECRET_SCANNING_DATA_SOURCE_MAP[type].name;
|
||||||
|
|
||||||
try {
|
|
||||||
await deleteDataSource.mutateAsync({
|
await deleteDataSource.mutateAsync({
|
||||||
dataSourceId,
|
dataSourceId,
|
||||||
type,
|
type,
|
||||||
@@ -42,12 +41,6 @@ export const DeleteSecretScanningDataSourceModal = ({
|
|||||||
|
|
||||||
if (onComplete) onComplete();
|
if (onComplete) onComplete();
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
text: `Failed to delete ${dataSourceType} Data Source`,
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ export const SecretScanningDataSourceForm = ({
|
|||||||
connectionId: connection?.id,
|
connectionId: connection?.id,
|
||||||
projectId: currentProject.id
|
projectId: currentProject.id
|
||||||
});
|
});
|
||||||
try {
|
|
||||||
const source = await mutation;
|
const source = await mutation;
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
@@ -81,13 +80,6 @@ export const SecretScanningDataSourceForm = ({
|
|||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
onComplete(source);
|
onComplete(source);
|
||||||
} catch (err: any) {
|
|
||||||
createNotification({
|
|
||||||
title: `Failed to ${dataSource ? "update" : "create"} ${sourceType} Data Source`,
|
|
||||||
text: err.message,
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePrev = () => {
|
const handlePrev = () => {
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ export const DeleteSecretSyncModal = ({ isOpen, onOpenChange, secretSync, onComp
|
|||||||
const handleDeleteSecretSync = async () => {
|
const handleDeleteSecretSync = async () => {
|
||||||
const destinationName = SECRET_SYNC_MAP[destination].name;
|
const destinationName = SECRET_SYNC_MAP[destination].name;
|
||||||
|
|
||||||
try {
|
|
||||||
await deleteSync.mutateAsync({
|
await deleteSync.mutateAsync({
|
||||||
syncId,
|
syncId,
|
||||||
destination,
|
destination,
|
||||||
@@ -38,14 +37,6 @@ export const DeleteSecretSyncModal = ({ isOpen, onOpenChange, secretSync, onComp
|
|||||||
|
|
||||||
if (onComplete) onComplete();
|
if (onComplete) onComplete();
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
|
|
||||||
createNotification({
|
|
||||||
text: `Failed to remove ${destinationName} Sync`,
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ const Content = ({ secretSync, onComplete }: ContentProps) => {
|
|||||||
const triggerImportSecrets = useTriggerSecretSyncImportSecrets();
|
const triggerImportSecrets = useTriggerSecretSyncImportSecrets();
|
||||||
|
|
||||||
const handleTriggerImportSecrets = async ({ importBehavior }: TFormData) => {
|
const handleTriggerImportSecrets = async ({ importBehavior }: TFormData) => {
|
||||||
try {
|
|
||||||
await triggerImportSecrets.mutateAsync({
|
await triggerImportSecrets.mutateAsync({
|
||||||
syncId,
|
syncId,
|
||||||
destination,
|
destination,
|
||||||
@@ -65,14 +64,6 @@ const Content = ({ secretSync, onComplete }: ContentProps) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onComplete();
|
onComplete();
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
|
|
||||||
createNotification({
|
|
||||||
text: `Failed to trigger secret import for ${destinationName} Sync`,
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ const Content = ({ secretSync, onComplete }: ContentProps) => {
|
|||||||
const triggerSyncImport = useTriggerSecretSyncRemoveSecrets();
|
const triggerSyncImport = useTriggerSecretSyncRemoveSecrets();
|
||||||
|
|
||||||
const handleTriggerRemoveSecrets = async () => {
|
const handleTriggerRemoveSecrets = async () => {
|
||||||
try {
|
|
||||||
await triggerSyncImport.mutateAsync({
|
await triggerSyncImport.mutateAsync({
|
||||||
syncId,
|
syncId,
|
||||||
destination,
|
destination,
|
||||||
@@ -34,14 +33,6 @@ const Content = ({ secretSync, onComplete }: ContentProps) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onComplete();
|
onComplete();
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
|
|
||||||
createNotification({
|
|
||||||
text: `Failed to trigger secret removal for ${destinationName} Sync`,
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -88,14 +88,8 @@ export const CreateSecretSyncForm = ({
|
|||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
onComplete(secretSync);
|
onComplete(secretSync);
|
||||||
} catch (err: any) {
|
} catch {
|
||||||
console.error(err);
|
|
||||||
setShowConfirmation(false);
|
setShowConfirmation(false);
|
||||||
createNotification({
|
|
||||||
title: `Failed to add ${destinationName} Sync`,
|
|
||||||
text: err.message,
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ export const EditSecretSyncForm = ({ secretSync, fields, onComplete }: Props) =>
|
|||||||
|
|
||||||
const performUpdate = useCallback(
|
const performUpdate = useCallback(
|
||||||
async (formData: TSecretSyncForm) => {
|
async (formData: TSecretSyncForm) => {
|
||||||
try {
|
|
||||||
const { environment, connection, ...updateData } = formData;
|
const { environment, connection, ...updateData } = formData;
|
||||||
const updatedSecretSync = await updateSecretSync.mutateAsync({
|
const updatedSecretSync = await updateSecretSync.mutateAsync({
|
||||||
syncId: secretSync.id,
|
syncId: secretSync.id,
|
||||||
@@ -73,14 +72,6 @@ export const EditSecretSyncForm = ({ secretSync, fields, onComplete }: Props) =>
|
|||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
onComplete(updatedSecretSync);
|
onComplete(updatedSecretSync);
|
||||||
} catch (err: any) {
|
|
||||||
console.error(err);
|
|
||||||
createNotification({
|
|
||||||
title: `Failed to update ${destinationName} Sync`,
|
|
||||||
text: err.message,
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[updateSecretSync, secretSync.id, secretSync.projectId, destinationName, onComplete]
|
[updateSecretSync, secretSync.id, secretSync.projectId, destinationName, onComplete]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -130,7 +130,6 @@ export const CreateTagModal = ({ isOpen, onToggle, append, currentSecret }: Prop
|
|||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
const onFormSubmit = async ({ slug, color }: FormData) => {
|
const onFormSubmit = async ({ slug, color }: FormData) => {
|
||||||
try {
|
|
||||||
const data = await createWsTag({
|
const data = await createWsTag({
|
||||||
projectId,
|
projectId,
|
||||||
tagColor: color,
|
tagColor: color,
|
||||||
@@ -143,13 +142,6 @@ export const CreateTagModal = ({ isOpen, onToggle, append, currentSecret }: Prop
|
|||||||
text: "Successfully created a tag",
|
text: "Successfully created a tag",
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to create a tag",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -9,22 +9,10 @@ export const HighlightText = ({
|
|||||||
}) => {
|
}) => {
|
||||||
if (!text) return null;
|
if (!text) return null;
|
||||||
|
|
||||||
const renderTextWithNewlines = (input: string, baseKeyPrefix: string = ""): React.ReactNode[] => {
|
|
||||||
if (!input) return [];
|
|
||||||
const lines = input.split("\n");
|
|
||||||
return lines.flatMap((line, index) => {
|
|
||||||
const nodes: React.ReactNode[] = [line];
|
|
||||||
if (index < lines.length - 1) {
|
|
||||||
nodes.push(<br key={`${baseKeyPrefix}-br-${line}`} />);
|
|
||||||
}
|
|
||||||
return nodes;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const searchTerm = highlight.toLowerCase().trim();
|
const searchTerm = highlight.toLowerCase().trim();
|
||||||
|
|
||||||
if (!searchTerm) {
|
if (!searchTerm) {
|
||||||
return <span>{renderTextWithNewlines(text, "full-text")}</span>;
|
return <span>{text}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parts: React.ReactNode[] = [];
|
const parts: React.ReactNode[] = [];
|
||||||
@@ -36,16 +24,12 @@ export const HighlightText = ({
|
|||||||
text.replace(regex, (match: string, offset: number) => {
|
text.replace(regex, (match: string, offset: number) => {
|
||||||
if (offset > lastIndex) {
|
if (offset > lastIndex) {
|
||||||
const preMatchText = text.substring(lastIndex, offset);
|
const preMatchText = text.substring(lastIndex, offset);
|
||||||
parts.push(
|
parts.push(<span key={`pre-${lastIndex}`}>{preMatchText}</span>);
|
||||||
<span key={`pre-${lastIndex}`}>
|
|
||||||
{renderTextWithNewlines(preMatchText, `pre-${lastIndex}`)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parts.push(
|
parts.push(
|
||||||
<span key={`match-${offset}`} className={highlightClassName || "bg-yellow/30"}>
|
<span key={`match-${offset}`} className={highlightClassName || "bg-yellow/30"}>
|
||||||
{renderTextWithNewlines(match, `match-${offset}`)}
|
{match}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -56,11 +40,7 @@ export const HighlightText = ({
|
|||||||
|
|
||||||
if (lastIndex < text.length) {
|
if (lastIndex < text.length) {
|
||||||
const postMatchText = text.substring(lastIndex);
|
const postMatchText = text.substring(lastIndex);
|
||||||
parts.push(
|
parts.push(<span key={`post-${lastIndex}`}>{postMatchText}</span>);
|
||||||
<span key={`post-${lastIndex}`}>
|
|
||||||
{renderTextWithNewlines(postMatchText, `post-${lastIndex}`)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return parts;
|
return parts;
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ export const APP_CONNECTION_MAP: Record<
|
|||||||
image: "Laravel Forge.png",
|
image: "Laravel Forge.png",
|
||||||
size: 65
|
size: 65
|
||||||
},
|
},
|
||||||
[AppConnection.Chef]: { name: "Chef", image: "Chef.png" }
|
[AppConnection.Chef]: { name: "Chef", image: "Chef.png", enterprise: true }
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => {
|
export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => {
|
||||||
|
|||||||
@@ -159,8 +159,8 @@ export type TCreateCertificateDTO = {
|
|||||||
ttl: string; // string compatible with ms
|
ttl: string; // string compatible with ms
|
||||||
notBefore?: string;
|
notBefore?: string;
|
||||||
notAfter?: string;
|
notAfter?: string;
|
||||||
keyUsages: CertKeyUsage[];
|
keyUsages: string[];
|
||||||
extendedKeyUsages: CertExtendedKeyUsage[];
|
extendedKeyUsages: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCreateCertificateResponse = {
|
export type TCreateCertificateResponse = {
|
||||||
|
|||||||
@@ -90,7 +90,12 @@ export const useCreateCertTemplateV2 = () => {
|
|||||||
return data.certificateTemplate;
|
return data.certificateTemplate;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { projectId }) => {
|
onSuccess: (_, { projectId }) => {
|
||||||
queryClient.invalidateQueries({ queryKey: certTemplateKeys.listTemplates({ projectId }) });
|
queryClient.invalidateQueries({
|
||||||
|
predicate: (query) => {
|
||||||
|
const [firstKey, queryProjectId] = query.queryKey;
|
||||||
|
return firstKey === "list-template" && queryProjectId === projectId;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -107,7 +112,12 @@ export const useUpdateCertTemplateV2 = () => {
|
|||||||
return data.certificateTemplate;
|
return data.certificateTemplate;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { projectId }) => {
|
onSuccess: (_, { projectId }) => {
|
||||||
queryClient.invalidateQueries({ queryKey: certTemplateKeys.listTemplates({ projectId }) });
|
queryClient.invalidateQueries({
|
||||||
|
predicate: (query) => {
|
||||||
|
const [firstKey, queryProjectId] = query.queryKey;
|
||||||
|
return firstKey === "list-template" && queryProjectId === projectId;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -127,7 +137,12 @@ export const useDeleteCertTemplateV2 = () => {
|
|||||||
return data.certificateTemplate;
|
return data.certificateTemplate;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { projectId }) => {
|
onSuccess: (_, { projectId }) => {
|
||||||
queryClient.invalidateQueries({ queryKey: certTemplateKeys.listTemplates({ projectId }) });
|
queryClient.invalidateQueries({
|
||||||
|
predicate: (query) => {
|
||||||
|
const [firstKey, queryProjectId] = query.queryKey;
|
||||||
|
return firstKey === "list-template" && queryProjectId === projectId;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ export type SecretVersions = {
|
|||||||
actorType?: string | null;
|
actorType?: string | null;
|
||||||
name?: string | null;
|
name?: string | null;
|
||||||
membershipId?: string | null;
|
membershipId?: string | null;
|
||||||
|
groupId?: string | null;
|
||||||
} | null;
|
} | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ export const NewSubOrganizationForm = ({ onClose }: ContentProps) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const onSubmit = async ({ name }: FormData) => {
|
const onSubmit = async ({ name }: FormData) => {
|
||||||
try {
|
|
||||||
const { organization } = await createSubOrg.mutateAsync({
|
const { organization } = await createSubOrg.mutateAsync({
|
||||||
name
|
name
|
||||||
});
|
});
|
||||||
@@ -52,12 +51,6 @@ export const NewSubOrganizationForm = ({ onClose }: ContentProps) => {
|
|||||||
search: (prev) => ({ ...prev, subOrganization: organization.name })
|
search: (prev) => ({ ...prev, subOrganization: organization.name })
|
||||||
});
|
});
|
||||||
await router.invalidate({ sync: true }).catch(() => null);
|
await router.invalidate({ sync: true }).catch(() => null);
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to create sub organization",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import { Link, linkOptions } from "@tanstack/react-router";
|
import { Link, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||||
import { createNotification } from "@app/components/notifications";
|
|
||||||
import { OrgPermissionCan } from "@app/components/permissions";
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import { NewProjectModal } from "@app/components/projects";
|
import { NewProjectModal } from "@app/components/projects";
|
||||||
import {
|
import {
|
||||||
@@ -59,31 +58,17 @@ export const ProjectSelect = () => {
|
|||||||
const { mutateAsync: updateUserProjectFavorites } = useUpdateUserProjectFavorites();
|
const { mutateAsync: updateUserProjectFavorites } = useUpdateUserProjectFavorites();
|
||||||
|
|
||||||
const addProjectToFavorites = async (projectId: string) => {
|
const addProjectToFavorites = async (projectId: string) => {
|
||||||
try {
|
|
||||||
await updateUserProjectFavorites({
|
await updateUserProjectFavorites({
|
||||||
orgId: currentOrg!.id,
|
orgId: currentOrg!.id,
|
||||||
projectFavorites: [...(projectFavorites || []), projectId]
|
projectFavorites: [...(projectFavorites || []), projectId]
|
||||||
});
|
});
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to add project to favorites.",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeProjectFromFavorites = async (projectId: string) => {
|
const removeProjectFromFavorites = async (projectId: string) => {
|
||||||
try {
|
|
||||||
await updateUserProjectFavorites({
|
await updateUserProjectFavorites({
|
||||||
orgId: currentOrg!.id,
|
orgId: currentOrg!.id,
|
||||||
projectFavorites: [...(projectFavorites || []).filter((entry) => entry !== projectId)]
|
projectFavorites: [...(projectFavorites || []).filter((entry) => entry !== projectId)]
|
||||||
});
|
});
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to remove project from favorites.",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAddingProjectsAllowed = subscription?.workspaceLimit
|
const isAddingProjectsAllowed = subscription?.workspaceLimit
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ const Content = ({ onClose }: ContentProps) => {
|
|||||||
const users = usersData.filter((user) => !user.superAdmin);
|
const users = usersData.filter((user) => !user.superAdmin);
|
||||||
|
|
||||||
const onSubmit = async ({ user }: FormData) => {
|
const onSubmit = async ({ user }: FormData) => {
|
||||||
try {
|
|
||||||
await grantAdmin.mutateAsync(user.id);
|
await grantAdmin.mutateAsync(user.id);
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
@@ -73,12 +72,6 @@ const Content = ({ onClose }: ContentProps) => {
|
|||||||
text: "Successfully granted server admin status"
|
text: "Successfully granted server admin status"
|
||||||
});
|
});
|
||||||
onClose();
|
onClose();
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to grant server admin status",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -303,18 +303,11 @@ export const ServerAdminsTable = () => {
|
|||||||
const handleRemoveUser = async () => {
|
const handleRemoveUser = async () => {
|
||||||
const { id } = popUp?.removeUser?.data as { id: string; username: string };
|
const { id } = popUp?.removeUser?.data as { id: string; username: string };
|
||||||
|
|
||||||
try {
|
|
||||||
await deleteUser(id);
|
await deleteUser(id);
|
||||||
createNotification({
|
createNotification({
|
||||||
type: "success",
|
type: "success",
|
||||||
text: "Successfully deleted user"
|
text: "Successfully deleted user"
|
||||||
});
|
});
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
type: "error",
|
|
||||||
text: "Error deleting user"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePopUpClose("removeUser");
|
handlePopUpClose("removeUser");
|
||||||
};
|
};
|
||||||
@@ -322,24 +315,16 @@ export const ServerAdminsTable = () => {
|
|||||||
const handleRemoveServerAdminAccess = async () => {
|
const handleRemoveServerAdminAccess = async () => {
|
||||||
const { id } = popUp?.removeServerAdmin?.data as { id: string; username: string };
|
const { id } = popUp?.removeServerAdmin?.data as { id: string; username: string };
|
||||||
|
|
||||||
try {
|
|
||||||
await removeAdminAccess(id);
|
await removeAdminAccess(id);
|
||||||
createNotification({
|
createNotification({
|
||||||
type: "success",
|
type: "success",
|
||||||
text: "Successfully removed server admin access from user"
|
text: "Successfully removed server admin access from user"
|
||||||
});
|
});
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
type: "error",
|
|
||||||
text: "Error removing server admin access from user"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePopUpClose("removeServerAdmin");
|
handlePopUpClose("removeServerAdmin");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveUsers = async () => {
|
const handleRemoveUsers = async () => {
|
||||||
try {
|
|
||||||
await deleteUsers(selectedUsers.map((user) => user.id));
|
await deleteUsers(selectedUsers.map((user) => user.id));
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
@@ -349,12 +334,6 @@ export const ServerAdminsTable = () => {
|
|||||||
|
|
||||||
setSelectedUsers([]);
|
setSelectedUsers([]);
|
||||||
handlePopUpClose("removeUsers");
|
handlePopUpClose("removeUsers");
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to remove users",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ export const AuthenticationPageForm = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const onAuthFormSubmit = async (formData: TAuthForm) => {
|
const onAuthFormSubmit = async (formData: TAuthForm) => {
|
||||||
try {
|
|
||||||
const enabledMethods: LoginMethod[] = [];
|
const enabledMethods: LoginMethod[] = [];
|
||||||
if (formData.isEmailEnabled) {
|
if (formData.isEmailEnabled) {
|
||||||
enabledMethods.push(LoginMethod.EMAIL);
|
enabledMethods.push(LoginMethod.EMAIL);
|
||||||
@@ -100,13 +99,6 @@ export const AuthenticationPageForm = () => {
|
|||||||
text: "Login methods have been successfully updated.",
|
text: "Login methods have been successfully updated.",
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
createNotification({
|
|
||||||
type: "error",
|
|
||||||
text: "Failed to update login methods."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -31,15 +31,10 @@ export const CachingPageForm = () => {
|
|||||||
const handleInvalidateCacheSubmit = async () => {
|
const handleInvalidateCacheSubmit = async () => {
|
||||||
if (!type || isInvalidating) return;
|
if (!type || isInvalidating) return;
|
||||||
|
|
||||||
try {
|
|
||||||
await invalidateCache({ type });
|
await invalidateCache({ type });
|
||||||
createNotification({ text: `Began invalidating ${type} cache`, type: "success" });
|
createNotification({ text: `Began invalidating ${type} cache`, type: "success" });
|
||||||
setShouldPoll(true);
|
setShouldPoll(true);
|
||||||
handlePopUpClose("invalidateCache");
|
handlePopUpClose("invalidateCache");
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
createNotification({ text: `Failed to invalidate ${type} cache`, type: "error" });
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -60,19 +60,12 @@ export const EncryptionPageForm = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
await updateEncryptionStrategy(formData.encryptionStrategy);
|
await updateEncryptionStrategy(formData.encryptionStrategy);
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
type: "success",
|
type: "success",
|
||||||
text: "Encryption strategy updated successfully"
|
text: "Encryption strategy updated successfully"
|
||||||
});
|
});
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
type: "error",
|
|
||||||
text: "Failed to update encryption strategy"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -176,7 +176,6 @@ export const EnvironmentPageForm = () => {
|
|||||||
|
|
||||||
const onSubmit = useCallback(
|
const onSubmit = useCallback(
|
||||||
async (formData: TForm) => {
|
async (formData: TForm) => {
|
||||||
try {
|
|
||||||
const filteredFormData = Object.fromEntries(
|
const filteredFormData = Object.fromEntries(
|
||||||
Object.entries(formData).filter(([, value]) => value !== "")
|
Object.entries(formData).filter(([, value]) => value !== "")
|
||||||
);
|
);
|
||||||
@@ -190,17 +189,6 @@ export const EnvironmentPageForm = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
reset(formData);
|
reset(formData);
|
||||||
} catch (error) {
|
|
||||||
const errorMessage =
|
|
||||||
(error as any)?.response?.data?.message ||
|
|
||||||
(error as any)?.message ||
|
|
||||||
"An unknown error occurred";
|
|
||||||
createNotification({
|
|
||||||
type: "error",
|
|
||||||
title: "Failed to update environment overrides",
|
|
||||||
text: errorMessage
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[reset, updateServerConfig]
|
[reset, updateServerConfig]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ export const GeneralPageForm = () => {
|
|||||||
const organizations = useGetOrganizations();
|
const organizations = useGetOrganizations();
|
||||||
|
|
||||||
const onFormSubmit = async (formData: TDashboardForm) => {
|
const onFormSubmit = async (formData: TDashboardForm) => {
|
||||||
try {
|
|
||||||
const {
|
const {
|
||||||
allowedSignUpDomain,
|
allowedSignUpDomain,
|
||||||
trustSamlEmails,
|
trustSamlEmails,
|
||||||
@@ -92,13 +91,6 @@ export const GeneralPageForm = () => {
|
|||||||
text: "Successfully changed sign up setting.",
|
text: "Successfully changed sign up setting.",
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
createNotification({
|
|
||||||
type: "error",
|
|
||||||
text: "Failed to update sign up setting."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export const UsageReportSection = () => {
|
|||||||
const generateUsageReport = useGenerateUsageReport();
|
const generateUsageReport = useGenerateUsageReport();
|
||||||
|
|
||||||
const handleGenerateReport = async () => {
|
const handleGenerateReport = async () => {
|
||||||
try {
|
|
||||||
const response = await generateUsageReport.mutateAsync();
|
const response = await generateUsageReport.mutateAsync();
|
||||||
const { csvContent, filename } = response;
|
const { csvContent, filename } = response;
|
||||||
|
|
||||||
@@ -20,13 +19,6 @@ export const UsageReportSection = () => {
|
|||||||
text: `Usage report downloaded: "${filename}"`,
|
text: `Usage report downloaded: "${filename}"`,
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to generate usage report:", error);
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to generate usage report. Please try again.",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ const Content = ({ onClose }: ContentProps) => {
|
|||||||
const { users = [] } = data ?? {};
|
const { users = [] } = data ?? {};
|
||||||
|
|
||||||
const onSubmit = async ({ name, invitees }: FormData) => {
|
const onSubmit = async ({ name, invitees }: FormData) => {
|
||||||
try {
|
|
||||||
await createOrg.mutateAsync({
|
await createOrg.mutateAsync({
|
||||||
name,
|
name,
|
||||||
inviteAdminEmails: invitees
|
inviteAdminEmails: invitees
|
||||||
@@ -94,12 +93,6 @@ const Content = ({ onClose }: ContentProps) => {
|
|||||||
text: "Successfully created organization"
|
text: "Successfully created organization"
|
||||||
});
|
});
|
||||||
onClose();
|
onClose();
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to create organization",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const { append } = useFieldArray<FormData>({ control, name: "invitees" });
|
const { append } = useFieldArray<FormData>({ control, name: "invitees" });
|
||||||
|
|||||||
@@ -185,18 +185,11 @@ export const MachineIdentitiesTable = () => {
|
|||||||
const handleRemoveServerAdmin = async () => {
|
const handleRemoveServerAdmin = async () => {
|
||||||
const { id } = popUp?.removeServerAdmin?.data as { id: string; name: string };
|
const { id } = popUp?.removeServerAdmin?.data as { id: string; name: string };
|
||||||
|
|
||||||
try {
|
|
||||||
await deleteIdentitySuperAdminAccess(id);
|
await deleteIdentitySuperAdminAccess(id);
|
||||||
createNotification({
|
createNotification({
|
||||||
type: "success",
|
type: "success",
|
||||||
text: "Successfully removed server admin permissions"
|
text: "Successfully removed server admin permissions"
|
||||||
});
|
});
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
type: "error",
|
|
||||||
text: "Error removing server admin permissions"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePopUpClose("removeServerAdmin");
|
handlePopUpClose("removeServerAdmin");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -179,12 +179,6 @@ const ViewMembersModalContent = ({
|
|||||||
text: "Successfully resent org invitation",
|
text: "Successfully resent org invitation",
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to resend org invitation",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
setResendInviteId(null);
|
setResendInviteId(null);
|
||||||
}
|
}
|
||||||
@@ -479,7 +473,6 @@ const OrganizationsPanelTable = ({
|
|||||||
const { mutateAsync: accessOrganization } = useServerAdminAccessOrg();
|
const { mutateAsync: accessOrganization } = useServerAdminAccessOrg();
|
||||||
|
|
||||||
const handleAccessOrg = async (orgId: string) => {
|
const handleAccessOrg = async (orgId: string) => {
|
||||||
try {
|
|
||||||
await accessOrganization(orgId);
|
await accessOrganization(orgId);
|
||||||
|
|
||||||
navigate({
|
navigate({
|
||||||
@@ -493,12 +486,6 @@ const OrganizationsPanelTable = ({
|
|||||||
text: "Successfully joined organization",
|
text: "Successfully joined organization",
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to join organization",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -364,18 +364,11 @@ export const UserIdentitiesTable = () => {
|
|||||||
const handleRemoveUser = async () => {
|
const handleRemoveUser = async () => {
|
||||||
const { id } = popUp?.removeUser?.data as { id: string; username: string };
|
const { id } = popUp?.removeUser?.data as { id: string; username: string };
|
||||||
|
|
||||||
try {
|
|
||||||
await deleteUser(id);
|
await deleteUser(id);
|
||||||
createNotification({
|
createNotification({
|
||||||
type: "success",
|
type: "success",
|
||||||
text: "Successfully deleted user"
|
text: "Successfully deleted user"
|
||||||
});
|
});
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
type: "error",
|
|
||||||
text: "Error deleting user"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePopUpClose("removeUser");
|
handlePopUpClose("removeUser");
|
||||||
};
|
};
|
||||||
@@ -383,18 +376,11 @@ export const UserIdentitiesTable = () => {
|
|||||||
const handleGrantServerAdminAccess = async () => {
|
const handleGrantServerAdminAccess = async () => {
|
||||||
const { id } = popUp?.upgradeToServerAdmin?.data as { id: string; username: string };
|
const { id } = popUp?.upgradeToServerAdmin?.data as { id: string; username: string };
|
||||||
|
|
||||||
try {
|
|
||||||
await grantAdminAccess(id);
|
await grantAdminAccess(id);
|
||||||
createNotification({
|
createNotification({
|
||||||
type: "success",
|
type: "success",
|
||||||
text: "Successfully granted server admin access to user"
|
text: "Successfully granted server admin access to user"
|
||||||
});
|
});
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
type: "error",
|
|
||||||
text: "Error granting server admin access to user"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePopUpClose("upgradeToServerAdmin");
|
handlePopUpClose("upgradeToServerAdmin");
|
||||||
};
|
};
|
||||||
@@ -402,24 +388,16 @@ export const UserIdentitiesTable = () => {
|
|||||||
const handleRemoveServerAdminAccess = async () => {
|
const handleRemoveServerAdminAccess = async () => {
|
||||||
const { id } = popUp?.removeServerAdmin?.data as { id: string; username: string };
|
const { id } = popUp?.removeServerAdmin?.data as { id: string; username: string };
|
||||||
|
|
||||||
try {
|
|
||||||
await removeAdminAccess(id);
|
await removeAdminAccess(id);
|
||||||
createNotification({
|
createNotification({
|
||||||
type: "success",
|
type: "success",
|
||||||
text: "Successfully removed server admin access from user"
|
text: "Successfully removed server admin access from user"
|
||||||
});
|
});
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
type: "error",
|
|
||||||
text: "Error removing server admin access from user"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePopUpClose("removeServerAdmin");
|
handlePopUpClose("removeServerAdmin");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveUsers = async () => {
|
const handleRemoveUsers = async () => {
|
||||||
try {
|
|
||||||
await deleteUsers(selectedUsers.map((user) => user.id));
|
await deleteUsers(selectedUsers.map((user) => user.id));
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
@@ -429,12 +407,6 @@ export const UserIdentitiesTable = () => {
|
|||||||
|
|
||||||
setSelectedUsers([]);
|
setSelectedUsers([]);
|
||||||
handlePopUpClose("removeUsers");
|
handlePopUpClose("removeUsers");
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to remove users",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { useNavigate } from "@tanstack/react-router";
|
|||||||
import { AnimatePresence, motion } from "framer-motion";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
|
||||||
// TODO(akhilmhdh): rewrite this into module functions in lib
|
// TODO(akhilmhdh): rewrite this into module functions in lib
|
||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||||
import { Button, ContentLoader, FormControl, Input } from "@app/components/v2";
|
import { Button, ContentLoader, FormControl, Input } from "@app/components/v2";
|
||||||
@@ -46,7 +45,6 @@ export const SignUpPage = () => {
|
|||||||
const handleFormSubmit = async ({ email, password, firstName, lastName }: TFormSchema) => {
|
const handleFormSubmit = async ({ email, password, firstName, lastName }: TFormSchema) => {
|
||||||
// avoid multi submission
|
// avoid multi submission
|
||||||
if (isSubmitting) return;
|
if (isSubmitting) return;
|
||||||
try {
|
|
||||||
const res = await createAdminUser({
|
const res = await createAdminUser({
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
@@ -62,13 +60,6 @@ export const SignUpPage = () => {
|
|||||||
// Part of migration to nextjs 14
|
// Part of migration to nextjs 14
|
||||||
localStorage.setItem("orgData.id", res.organization.id);
|
localStorage.setItem("orgData.id", res.organization.id);
|
||||||
navigate({ to: "/admin" });
|
navigate({ to: "/admin" });
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
createNotification({
|
|
||||||
type: "error",
|
|
||||||
text: "Failed to create admin"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (config?.initialized) return <ContentLoader text="Redirecting to admin page..." />;
|
if (config?.initialized) return <ContentLoader text="Redirecting to admin page..." />;
|
||||||
|
|||||||
@@ -75,11 +75,7 @@ export const PasswordSetupPage = () => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.href = "/login";
|
window.location.href = "/login";
|
||||||
}, 3000);
|
}, 3000);
|
||||||
} catch (error) {
|
} catch {
|
||||||
createNotification({
|
|
||||||
type: "error",
|
|
||||||
text: (error as Error).message ?? "Error setting password"
|
|
||||||
});
|
|
||||||
navigate({ to: "/personal-settings" });
|
navigate({ to: "/personal-settings" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-14
@@ -73,7 +73,6 @@ export const EmailConfirmationStep = ({
|
|||||||
const { mutateAsync: verifyEmailVerificationCode } = useVerifyEmailVerificationCode();
|
const { mutateAsync: verifyEmailVerificationCode } = useVerifyEmailVerificationCode();
|
||||||
|
|
||||||
const checkCode = async () => {
|
const checkCode = async () => {
|
||||||
try {
|
|
||||||
await verifyEmailVerificationCode({ username, code });
|
await verifyEmailVerificationCode({ username, code });
|
||||||
setCodeError(false);
|
setCodeError(false);
|
||||||
|
|
||||||
@@ -102,18 +101,11 @@ export const EmailConfirmationStep = ({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to verify code",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setCode("");
|
setCode("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const resendCode = async () => {
|
const resendCode = async () => {
|
||||||
try {
|
|
||||||
const queryParams = new URLSearchParams(window.location.search);
|
const queryParams = new URLSearchParams(window.location.search);
|
||||||
const token = queryParams.get("token");
|
const token = queryParams.get("token");
|
||||||
if (!token) {
|
if (!token) {
|
||||||
@@ -128,12 +120,6 @@ export const EmailConfirmationStep = ({
|
|||||||
text: "Successfully resent code",
|
text: "Successfully resent code",
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to resend code",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -113,7 +113,6 @@ export const PkiAlertModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
alertUnit,
|
alertUnit,
|
||||||
emails
|
emails
|
||||||
}: FormData) => {
|
}: FormData) => {
|
||||||
try {
|
|
||||||
if (!projectId) return;
|
if (!projectId) return;
|
||||||
|
|
||||||
const emailArray = emails
|
const emailArray = emails
|
||||||
@@ -152,13 +151,6 @@ export const PkiAlertModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
text: `Successfully ${alert ? "updated" : "created"} alert`,
|
text: `Successfully ${alert ? "updated" : "created"} alert`,
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
createNotification({
|
|
||||||
text: `Failed to ${alert ? "updated" : "created"} alert`,
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ export const PkiAlertsSection = () => {
|
|||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
const onRemoveAlertSubmit = async (alertId: string) => {
|
const onRemoveAlertSubmit = async (alertId: string) => {
|
||||||
try {
|
|
||||||
if (!projectId) return;
|
if (!projectId) return;
|
||||||
|
|
||||||
await deletePkiAlert({
|
await deletePkiAlert({
|
||||||
@@ -36,13 +35,6 @@ export const PkiAlertsSection = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
handlePopUpClose("deletePkiAlert");
|
handlePopUpClose("deletePkiAlert");
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to delete alert",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user