mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-05 04:29:09 +00:00
Compare commits
19 Commits
daniel/bet
...
daniel/int
Author | SHA1 | Date | |
---|---|---|---|
09887a7405 | |||
38ee3a005e | |||
74653e7ed1 | |||
8a0b1bb427 | |||
1f6faadf81 | |||
8f3b7e1698 | |||
24c460c695 | |||
8acceab1e7 | |||
d60aba9339 | |||
3a228f7521 | |||
3f7ac0f142 | |||
63cf535ebb | |||
69a2a46c47 | |||
d081077273 | |||
75034f9350 | |||
eacd7b0c6a | |||
5bad77083c | |||
1025759efb | |||
5e5ab29ab9 |
@ -72,3 +72,6 @@ PLAIN_API_KEY=
|
|||||||
PLAIN_WISH_LABEL_IDS=
|
PLAIN_WISH_LABEL_IDS=
|
||||||
|
|
||||||
SSL_CLIENT_CERTIFICATE_HEADER_KEY=
|
SSL_CLIENT_CERTIFICATE_HEADER_KEY=
|
||||||
|
|
||||||
|
WORKFLOW_SLACK_CLIENT_ID=
|
||||||
|
WORKFLOW_SLACK_CLIENT_SECRET=
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -63,7 +63,6 @@ yarn-error.log*
|
|||||||
|
|
||||||
# Editor specific
|
# Editor specific
|
||||||
.vscode/*
|
.vscode/*
|
||||||
.idea/*
|
|
||||||
|
|
||||||
frontend-build
|
frontend-build
|
||||||
|
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
import { CertKeyUsage } from "@app/services/certificate/certificate-types";
|
|
||||||
|
|
||||||
import { TableName } from "../schemas";
|
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
|
||||||
// Certificate template
|
|
||||||
const hasKeyUsagesCol = await knex.schema.hasColumn(TableName.CertificateTemplate, "keyUsages");
|
|
||||||
const hasExtendedKeyUsagesCol = await knex.schema.hasColumn(TableName.CertificateTemplate, "extendedKeyUsages");
|
|
||||||
|
|
||||||
await knex.schema.alterTable(TableName.CertificateTemplate, (tb) => {
|
|
||||||
if (!hasKeyUsagesCol) {
|
|
||||||
tb.specificType("keyUsages", "text[]");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasExtendedKeyUsagesCol) {
|
|
||||||
tb.specificType("extendedKeyUsages", "text[]");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!hasKeyUsagesCol) {
|
|
||||||
await knex(TableName.CertificateTemplate).update({
|
|
||||||
keyUsages: [CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasExtendedKeyUsagesCol) {
|
|
||||||
await knex(TableName.CertificateTemplate).update({
|
|
||||||
extendedKeyUsages: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Certificate
|
|
||||||
const doesCertTableHaveKeyUsages = await knex.schema.hasColumn(TableName.Certificate, "keyUsages");
|
|
||||||
const doesCertTableHaveExtendedKeyUsages = await knex.schema.hasColumn(TableName.Certificate, "extendedKeyUsages");
|
|
||||||
await knex.schema.alterTable(TableName.Certificate, (tb) => {
|
|
||||||
if (!doesCertTableHaveKeyUsages) {
|
|
||||||
tb.specificType("keyUsages", "text[]");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!doesCertTableHaveExtendedKeyUsages) {
|
|
||||||
tb.specificType("extendedKeyUsages", "text[]");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!doesCertTableHaveKeyUsages) {
|
|
||||||
await knex(TableName.Certificate).update({
|
|
||||||
keyUsages: [CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!doesCertTableHaveExtendedKeyUsages) {
|
|
||||||
await knex(TableName.Certificate).update({
|
|
||||||
extendedKeyUsages: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
|
||||||
// Certificate Template
|
|
||||||
const hasKeyUsagesCol = await knex.schema.hasColumn(TableName.CertificateTemplate, "keyUsages");
|
|
||||||
const hasExtendedKeyUsagesCol = await knex.schema.hasColumn(TableName.CertificateTemplate, "extendedKeyUsages");
|
|
||||||
|
|
||||||
await knex.schema.alterTable(TableName.CertificateTemplate, (t) => {
|
|
||||||
if (hasKeyUsagesCol) {
|
|
||||||
t.dropColumn("keyUsages");
|
|
||||||
}
|
|
||||||
if (hasExtendedKeyUsagesCol) {
|
|
||||||
t.dropColumn("extendedKeyUsages");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Certificate
|
|
||||||
const doesCertTableHaveKeyUsages = await knex.schema.hasColumn(TableName.Certificate, "keyUsages");
|
|
||||||
const doesCertTableHaveExtendedKeyUsages = await knex.schema.hasColumn(TableName.Certificate, "extendedKeyUsages");
|
|
||||||
await knex.schema.alterTable(TableName.Certificate, (t) => {
|
|
||||||
if (doesCertTableHaveKeyUsages) {
|
|
||||||
t.dropColumn("keyUsages");
|
|
||||||
}
|
|
||||||
if (doesCertTableHaveExtendedKeyUsages) {
|
|
||||||
t.dropColumn("extendedKeyUsages");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
@ -16,9 +16,7 @@ export const CertificateTemplatesSchema = z.object({
|
|||||||
subjectAlternativeName: z.string(),
|
subjectAlternativeName: z.string(),
|
||||||
ttl: z.string(),
|
ttl: z.string(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date()
|
||||||
keyUsages: z.string().array().nullable().optional(),
|
|
||||||
extendedKeyUsages: z.string().array().nullable().optional()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCertificateTemplates = z.infer<typeof CertificateTemplatesSchema>;
|
export type TCertificateTemplates = z.infer<typeof CertificateTemplatesSchema>;
|
||||||
|
@ -22,9 +22,7 @@ export const CertificatesSchema = z.object({
|
|||||||
revocationReason: z.number().nullable().optional(),
|
revocationReason: z.number().nullable().optional(),
|
||||||
altNames: z.string().default("").nullable().optional(),
|
altNames: z.string().default("").nullable().optional(),
|
||||||
caCertId: z.string().uuid(),
|
caCertId: z.string().uuid(),
|
||||||
certificateTemplateId: z.string().uuid().nullable().optional(),
|
certificateTemplateId: z.string().uuid().nullable().optional()
|
||||||
keyUsages: z.string().array().nullable().optional(),
|
|
||||||
extendedKeyUsages: z.string().array().nullable().optional()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCertificates = z.infer<typeof CertificatesSchema>;
|
export type TCertificates = z.infer<typeof CertificatesSchema>;
|
||||||
|
@ -11,30 +11,6 @@ export const registerCaCrlRouter = async (server: FastifyZodProvider) => {
|
|||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
schema: {
|
|
||||||
description: "Get CRL in DER format (deprecated)",
|
|
||||||
params: z.object({
|
|
||||||
crlId: z.string().trim().describe(CA_CRLS.GET.crlId)
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.instanceof(Buffer)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handler: async (req, res) => {
|
|
||||||
const { crl } = await server.services.certificateAuthorityCrl.getCrlById(req.params.crlId);
|
|
||||||
|
|
||||||
res.header("Content-Type", "application/pkix-crl");
|
|
||||||
|
|
||||||
return Buffer.from(crl);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "GET",
|
|
||||||
url: "/:crlId/der",
|
|
||||||
config: {
|
|
||||||
rateLimit: readLimit
|
|
||||||
},
|
|
||||||
schema: {
|
schema: {
|
||||||
description: "Get CRL in DER format",
|
description: "Get CRL in DER format",
|
||||||
params: z.object({
|
params: z.object({
|
||||||
|
@ -101,7 +101,6 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
message: "Slug must be a valid"
|
message: "Slug must be a valid"
|
||||||
}),
|
}),
|
||||||
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
||||||
description: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.description),
|
|
||||||
permissions: ProjectPermissionSchema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
permissions: ProjectPermissionSchema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
|
@ -146,12 +146,16 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
projectId: req.params.workspaceId,
|
actor: req.permission.type,
|
||||||
...req.query,
|
|
||||||
endDate: req.query.endDate,
|
filter: {
|
||||||
startDate: req.query.startDate || getLastMidnightDateISO(),
|
...req.query,
|
||||||
auditLogActor: req.query.actor,
|
projectId: req.params.workspaceId,
|
||||||
actor: req.permission.type
|
endDate: req.query.endDate,
|
||||||
|
startDate: req.query.startDate || getLastMidnightDateISO(),
|
||||||
|
auditLogActorId: req.query.actor,
|
||||||
|
eventType: req.query.eventType ? [req.query.eventType] : undefined
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return { auditLogs };
|
return { auditLogs };
|
||||||
}
|
}
|
||||||
|
@ -100,20 +100,9 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
async (req, profile, cb) => {
|
async (req, profile, cb) => {
|
||||||
try {
|
try {
|
||||||
if (!profile) throw new BadRequestError({ message: "Missing profile" });
|
if (!profile) throw new BadRequestError({ message: "Missing profile" });
|
||||||
const email =
|
const email = profile?.email ?? (profile?.emailAddress as string); // emailRippling is added because in Rippling the field `email` reserved
|
||||||
profile?.email ??
|
|
||||||
// entra sends data in this format
|
|
||||||
(profile["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/email"] as string) ??
|
|
||||||
(profile?.emailAddress as string); // emailRippling is added because in Rippling the field `email` reserved\
|
|
||||||
|
|
||||||
const firstName = (profile.firstName ??
|
if (!email || !profile.firstName) {
|
||||||
// entra sends data in this format
|
|
||||||
profile["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/firstName"]) as string;
|
|
||||||
|
|
||||||
const lastName =
|
|
||||||
profile.lastName ?? profile["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/lastName"];
|
|
||||||
|
|
||||||
if (!email || !firstName) {
|
|
||||||
logger.info(
|
logger.info(
|
||||||
{
|
{
|
||||||
err: new Error("Invalid saml request. Missing email or first name"),
|
err: new Error("Invalid saml request. Missing email or first name"),
|
||||||
@ -121,13 +110,14 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
`email: ${email} firstName: ${profile.firstName as string}`
|
`email: ${email} firstName: ${profile.firstName as string}`
|
||||||
);
|
);
|
||||||
|
throw new BadRequestError({ message: "Invalid request. Missing email or first name" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { isUserCompleted, providerAuthToken } = await server.services.saml.samlLogin({
|
const { isUserCompleted, providerAuthToken } = await server.services.saml.samlLogin({
|
||||||
externalId: profile.nameID,
|
externalId: profile.nameID,
|
||||||
email,
|
email,
|
||||||
firstName,
|
firstName: profile.firstName as string,
|
||||||
lastName: lastName as string,
|
lastName: profile.lastName as string,
|
||||||
relayState: (req.body as { RelayState?: string }).RelayState,
|
relayState: (req.body as { RelayState?: string }).RelayState,
|
||||||
authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider as string,
|
authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider as string,
|
||||||
orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId as string
|
orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId as string
|
||||||
|
@ -6,6 +6,9 @@ import { DatabaseError } from "@app/lib/errors";
|
|||||||
import { ormify, selectAllTableCols, stripUndefinedInWhere } from "@app/lib/knex";
|
import { ormify, selectAllTableCols, stripUndefinedInWhere } from "@app/lib/knex";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { QueueName } from "@app/queue";
|
import { QueueName } from "@app/queue";
|
||||||
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
import { EventType } from "./audit-log-types";
|
||||||
|
|
||||||
export type TAuditLogDALFactory = ReturnType<typeof auditLogDALFactory>;
|
export type TAuditLogDALFactory = ReturnType<typeof auditLogDALFactory>;
|
||||||
|
|
||||||
@ -25,7 +28,24 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
const auditLogOrm = ormify(db, TableName.AuditLog);
|
const auditLogOrm = ormify(db, TableName.AuditLog);
|
||||||
|
|
||||||
const find = async (
|
const find = async (
|
||||||
{ orgId, projectId, userAgentType, startDate, endDate, limit = 20, offset = 0, actor, eventType }: TFindQuery,
|
{
|
||||||
|
orgId,
|
||||||
|
projectId,
|
||||||
|
userAgentType,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
limit = 20,
|
||||||
|
offset = 0,
|
||||||
|
actorId,
|
||||||
|
actorType,
|
||||||
|
eventType,
|
||||||
|
eventMetadata
|
||||||
|
}: Omit<TFindQuery, "actor" | "eventType"> & {
|
||||||
|
actorId?: string;
|
||||||
|
actorType?: ActorType;
|
||||||
|
eventType?: EventType[];
|
||||||
|
eventMetadata?: Record<string, string>;
|
||||||
|
},
|
||||||
tx?: Knex
|
tx?: Knex
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
@ -34,7 +54,6 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
stripUndefinedInWhere({
|
stripUndefinedInWhere({
|
||||||
projectId,
|
projectId,
|
||||||
[`${TableName.AuditLog}.orgId`]: orgId,
|
[`${TableName.AuditLog}.orgId`]: orgId,
|
||||||
eventType,
|
|
||||||
userAgentType
|
userAgentType
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -52,8 +71,22 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
.offset(offset)
|
.offset(offset)
|
||||||
.orderBy(`${TableName.AuditLog}.createdAt`, "desc");
|
.orderBy(`${TableName.AuditLog}.createdAt`, "desc");
|
||||||
|
|
||||||
if (actor) {
|
if (actorId) {
|
||||||
void sqlQuery.whereRaw(`"actorMetadata"->>'userId' = ?`, [actor]);
|
void sqlQuery.whereRaw(`"actorMetadata"->>'userId' = ?`, [actorId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventMetadata && Object.keys(eventMetadata).length) {
|
||||||
|
Object.entries(eventMetadata).forEach(([key, value]) => {
|
||||||
|
void sqlQuery.whereRaw(`"eventMetadata"->>'${key}' = ?`, [value]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actorType) {
|
||||||
|
void sqlQuery.where("actor", actorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventType?.length) {
|
||||||
|
void sqlQuery.whereIn("eventType", eventType);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startDate) {
|
if (startDate) {
|
||||||
|
@ -23,25 +23,12 @@ export const auditLogServiceFactory = ({
|
|||||||
auditLogQueue,
|
auditLogQueue,
|
||||||
permissionService
|
permissionService
|
||||||
}: TAuditLogServiceFactoryDep) => {
|
}: TAuditLogServiceFactoryDep) => {
|
||||||
const listAuditLogs = async ({
|
const listAuditLogs = async ({ actorAuthMethod, actorId, actorOrgId, actor, filter }: TListProjectAuditLogDTO) => {
|
||||||
userAgentType,
|
if (filter.projectId) {
|
||||||
eventType,
|
|
||||||
offset,
|
|
||||||
limit,
|
|
||||||
endDate,
|
|
||||||
startDate,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorOrgId,
|
|
||||||
actorAuthMethod,
|
|
||||||
projectId,
|
|
||||||
auditLogActor
|
|
||||||
}: TListProjectAuditLogDTO) => {
|
|
||||||
if (projectId) {
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
filter.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
@ -65,14 +52,16 @@ export const auditLogServiceFactory = ({
|
|||||||
// If project ID is not provided, then we need to return all the audit logs for the organization itself.
|
// If project ID is not provided, then we need to return all the audit logs for the organization itself.
|
||||||
|
|
||||||
const auditLogs = await auditLogDAL.find({
|
const auditLogs = await auditLogDAL.find({
|
||||||
startDate,
|
startDate: filter.startDate,
|
||||||
endDate,
|
endDate: filter.endDate,
|
||||||
limit,
|
limit: filter.limit,
|
||||||
offset,
|
offset: filter.offset,
|
||||||
eventType,
|
eventType: filter.eventType,
|
||||||
userAgentType,
|
userAgentType: filter.userAgentType,
|
||||||
actor: auditLogActor,
|
actorId: filter.auditLogActorId,
|
||||||
...(projectId ? { projectId } : { orgId: actorOrgId })
|
actorType: filter.actorType,
|
||||||
|
eventMetadata: filter.eventMetadata,
|
||||||
|
...(filter.projectId ? { projectId: filter.projectId } : { orgId: actorOrgId })
|
||||||
});
|
});
|
||||||
|
|
||||||
return auditLogs.map(({ eventType: logEventType, actor: eActor, actorMetadata, eventMetadata, ...el }) => ({
|
return auditLogs.map(({ eventType: logEventType, actor: eActor, actorMetadata, eventMetadata, ...el }) => ({
|
||||||
|
@ -5,19 +5,23 @@ import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
|||||||
import { PkiItemType } from "@app/services/pki-collection/pki-collection-types";
|
import { PkiItemType } from "@app/services/pki-collection/pki-collection-types";
|
||||||
|
|
||||||
export type TListProjectAuditLogDTO = {
|
export type TListProjectAuditLogDTO = {
|
||||||
auditLogActor?: string;
|
filter: {
|
||||||
projectId?: string;
|
userAgentType?: UserAgentType;
|
||||||
eventType?: string;
|
eventType?: EventType[];
|
||||||
startDate?: string;
|
offset?: number;
|
||||||
endDate?: string;
|
limit: number;
|
||||||
userAgentType?: string;
|
endDate?: string;
|
||||||
limit?: number;
|
startDate?: string;
|
||||||
offset?: number;
|
projectId?: string;
|
||||||
|
auditLogActorId?: string;
|
||||||
|
actorType?: ActorType;
|
||||||
|
eventMetadata?: Record<string, string>;
|
||||||
|
};
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TCreateAuditLogDTO = {
|
export type TCreateAuditLogDTO = {
|
||||||
event: Event;
|
event: Event;
|
||||||
actor: UserActor | IdentityActor | ServiceActor | ScimClientActor;
|
actor: UserActor | IdentityActor | ServiceActor | ScimClientActor | PlatformActor;
|
||||||
orgId?: string;
|
orgId?: string;
|
||||||
projectId?: string;
|
projectId?: string;
|
||||||
} & BaseAuthData;
|
} & BaseAuthData;
|
||||||
@ -177,7 +181,8 @@ export enum EventType {
|
|||||||
UPDATE_SLACK_INTEGRATION = "update-slack-integration",
|
UPDATE_SLACK_INTEGRATION = "update-slack-integration",
|
||||||
DELETE_SLACK_INTEGRATION = "delete-slack-integration",
|
DELETE_SLACK_INTEGRATION = "delete-slack-integration",
|
||||||
GET_PROJECT_SLACK_CONFIG = "get-project-slack-config",
|
GET_PROJECT_SLACK_CONFIG = "get-project-slack-config",
|
||||||
UPDATE_PROJECT_SLACK_CONFIG = "update-project-slack-config"
|
UPDATE_PROJECT_SLACK_CONFIG = "update-project-slack-config",
|
||||||
|
INTEGRATION_SYNCED = "integration-synced"
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserActorMetadata {
|
interface UserActorMetadata {
|
||||||
@ -198,6 +203,8 @@ interface IdentityActorMetadata {
|
|||||||
|
|
||||||
interface ScimClientActorMetadata {}
|
interface ScimClientActorMetadata {}
|
||||||
|
|
||||||
|
interface PlatformActorMetadata {}
|
||||||
|
|
||||||
export interface UserActor {
|
export interface UserActor {
|
||||||
type: ActorType.USER;
|
type: ActorType.USER;
|
||||||
metadata: UserActorMetadata;
|
metadata: UserActorMetadata;
|
||||||
@ -208,6 +215,11 @@ export interface ServiceActor {
|
|||||||
metadata: ServiceActorMetadata;
|
metadata: ServiceActorMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PlatformActor {
|
||||||
|
type: ActorType.PLATFORM;
|
||||||
|
metadata: PlatformActorMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IdentityActor {
|
export interface IdentityActor {
|
||||||
type: ActorType.IDENTITY;
|
type: ActorType.IDENTITY;
|
||||||
metadata: IdentityActorMetadata;
|
metadata: IdentityActorMetadata;
|
||||||
@ -218,7 +230,7 @@ export interface ScimClientActor {
|
|||||||
metadata: ScimClientActorMetadata;
|
metadata: ScimClientActorMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Actor = UserActor | ServiceActor | IdentityActor | ScimClientActor;
|
export type Actor = UserActor | ServiceActor | IdentityActor | ScimClientActor | PlatformActor;
|
||||||
|
|
||||||
interface GetSecretsEvent {
|
interface GetSecretsEvent {
|
||||||
type: EventType.GET_SECRETS;
|
type: EventType.GET_SECRETS;
|
||||||
@ -1518,6 +1530,16 @@ interface GetProjectSlackConfig {
|
|||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
interface IntegrationSyncedEvent {
|
||||||
|
type: EventType.INTEGRATION_SYNCED;
|
||||||
|
metadata: {
|
||||||
|
integrationId: string;
|
||||||
|
lastSyncJobId: string;
|
||||||
|
lastUsed: Date;
|
||||||
|
syncMessage: string;
|
||||||
|
isSynced: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type Event =
|
export type Event =
|
||||||
| GetSecretsEvent
|
| GetSecretsEvent
|
||||||
@ -1657,4 +1679,5 @@ export type Event =
|
|||||||
| DeleteSlackIntegration
|
| DeleteSlackIntegration
|
||||||
| GetSlackIntegration
|
| GetSlackIntegration
|
||||||
| UpdateProjectSlackConfig
|
| UpdateProjectSlackConfig
|
||||||
| GetProjectSlackConfig;
|
| GetProjectSlackConfig
|
||||||
|
| IntegrationSyncedEvent;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability";
|
import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability";
|
||||||
|
|
||||||
import { conditionsMatcher } from "@app/lib/casl";
|
import { conditionsMatcher } from "@app/lib/casl";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
|
||||||
|
|
||||||
export enum ProjectPermissionActions {
|
export enum ProjectPermissionActions {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
@ -76,125 +75,117 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback]
|
| [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback]
|
||||||
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Kms];
|
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Kms];
|
||||||
|
|
||||||
export const fullProjectPermissionSet: [ProjectPermissionActions, ProjectPermissionSub][] = [
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Secrets],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Secrets],
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.SecretApproval],
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.SecretApproval],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.SecretRotation],
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.SecretRotation],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Member],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Member],
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Member],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Member],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Groups],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Groups],
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Groups],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Groups],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Role],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Role],
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Role],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Role],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Integrations],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Integrations],
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks],
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Identity],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Identity],
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Identity],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Identity],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens],
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Settings],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Settings],
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Settings],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Settings],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Environments],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Environments],
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Environments],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Environments],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Tags],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Tags],
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Tags],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Tags],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.AuditLogs],
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.AuditLogs],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.AuditLogs],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList],
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.IpAllowList],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.IpAllowList],
|
|
||||||
|
|
||||||
// double check if all CRUD are needed for CA and Certificates
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.CertificateAuthorities],
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.CertificateAuthorities],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.CertificateAuthorities],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Certificates],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Certificates],
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Certificates],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.CertificateTemplates],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.CertificateTemplates],
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.CertificateTemplates],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.CertificateTemplates],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.PkiAlerts],
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.PkiAlerts],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.PkiAlerts],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections],
|
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.PkiCollections],
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.PkiCollections],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.PkiCollections],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Project],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Project],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Kms]
|
|
||||||
];
|
|
||||||
|
|
||||||
const buildAdminPermissionRules = () => {
|
const buildAdminPermissionRules = () => {
|
||||||
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
// Admins get full access to everything
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
||||||
fullProjectPermissionSet.forEach((permission) => {
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Secrets);
|
||||||
const [action, subject] = permission;
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets);
|
||||||
can(action, subject);
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets);
|
||||||
});
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretApproval);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRotation);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.SecretRotation);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Groups);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Groups);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Groups);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Groups);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Role);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Role);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Role);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Identity);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Identity);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Settings);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Settings);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Environments);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Environments);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Environments);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Tags);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Tags);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Tags);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.AuditLogs);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.AuditLogs);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.AuditLogs);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.IpAllowList);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.IpAllowList);
|
||||||
|
|
||||||
|
// double check if all CRUD are needed for CA and Certificates
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.CertificateAuthorities);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.CertificateAuthorities);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.CertificateAuthorities);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Certificates);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Certificates);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateTemplates);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.CertificateTemplates);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.CertificateTemplates);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.CertificateTemplates);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.PkiAlerts);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.PkiAlerts);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.PkiAlerts);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.PkiCollections);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.PkiCollections);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.PkiCollections);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Project);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Project);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
@ -381,31 +372,4 @@ export const isAtLeastAsPrivilegedWorkspace = (
|
|||||||
return set1.size >= set2.size;
|
return set1.size >= set2.size;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
* Case: The user requests to create a role with permissions that are not valid and not supposed to be used ever.
|
|
||||||
* If we don't check for this, we can run into issues where functions like the `isAtLeastAsPrivileged` will not work as expected, because we compare the size of each permission set.
|
|
||||||
* If the permission set contains invalid permissions, the size will be different, and result in incorrect results.
|
|
||||||
*/
|
|
||||||
export const validateProjectPermissions = (permissions: unknown) => {
|
|
||||||
const parsedPermissions =
|
|
||||||
typeof permissions === "string" ? (JSON.parse(permissions) as string[]) : (permissions as string[]);
|
|
||||||
|
|
||||||
const flattenedPermissions = [...parsedPermissions];
|
|
||||||
|
|
||||||
for (const perm of flattenedPermissions) {
|
|
||||||
const [action, subject] = perm;
|
|
||||||
|
|
||||||
if (
|
|
||||||
!fullProjectPermissionSet.find(
|
|
||||||
(currentPermission) => currentPermission[0] === action && currentPermission[1] === subject
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Permission action ${action} on subject ${subject} is not valid`,
|
|
||||||
name: "Create Role"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
|
@ -363,12 +363,7 @@ export const ORGANIZATIONS = {
|
|||||||
membershipId: "The ID of the membership to delete."
|
membershipId: "The ID of the membership to delete."
|
||||||
},
|
},
|
||||||
LIST_IDENTITY_MEMBERSHIPS: {
|
LIST_IDENTITY_MEMBERSHIPS: {
|
||||||
orgId: "The ID of the organization to get identity memberships from.",
|
orgId: "The ID of the organization to get identity memberships from."
|
||||||
offset: "The offset to start from. If you enter 10, it will start from the 10th identity membership.",
|
|
||||||
limit: "The number of identity memberships to return.",
|
|
||||||
orderBy: "The column to order identity memberships by.",
|
|
||||||
orderDirection: "The direction identity memberships will be sorted in.",
|
|
||||||
search: "The text string that identity membership names will be filtered by."
|
|
||||||
},
|
},
|
||||||
GET_PROJECTS: {
|
GET_PROJECTS: {
|
||||||
organizationId: "The ID of the organization to get projects from."
|
organizationId: "The ID of the organization to get projects from."
|
||||||
@ -477,12 +472,7 @@ export const PROJECT_USERS = {
|
|||||||
|
|
||||||
export const PROJECT_IDENTITIES = {
|
export const PROJECT_IDENTITIES = {
|
||||||
LIST_IDENTITY_MEMBERSHIPS: {
|
LIST_IDENTITY_MEMBERSHIPS: {
|
||||||
projectId: "The ID of the project to get identity memberships from.",
|
projectId: "The ID of the project to get identity memberships from."
|
||||||
offset: "The offset to start from. If you enter 10, it will start from the 10th identity membership.",
|
|
||||||
limit: "The number of identity memberships to return.",
|
|
||||||
orderBy: "The column to order identity memberships by.",
|
|
||||||
orderDirection: "The direction identity memberships will be sorted in.",
|
|
||||||
search: "The text string that identity membership names will be filtered by."
|
|
||||||
},
|
},
|
||||||
GET_IDENTITY_MEMBERSHIP_BY_ID: {
|
GET_IDENTITY_MEMBERSHIP_BY_ID: {
|
||||||
identityId: "The ID of the identity to get the membership for.",
|
identityId: "The ID of the identity to get the membership for.",
|
||||||
@ -1083,10 +1073,6 @@ export const CERTIFICATE_AUTHORITIES = {
|
|||||||
certificateChain: "The certificate chain of the CA",
|
certificateChain: "The certificate chain of the CA",
|
||||||
serialNumber: "The serial number of the CA certificate"
|
serialNumber: "The serial number of the CA certificate"
|
||||||
},
|
},
|
||||||
GET_CERT_BY_ID: {
|
|
||||||
caId: "The ID of the CA to get the CA certificate from",
|
|
||||||
caCertId: "The ID of the CA certificate to get"
|
|
||||||
},
|
|
||||||
GET_CA_CERTS: {
|
GET_CA_CERTS: {
|
||||||
caId: "The ID of the CA to get the CA certificates for",
|
caId: "The ID of the CA to get the CA certificates for",
|
||||||
certificate: "The certificate body of the CA certificate",
|
certificate: "The certificate body of the CA certificate",
|
||||||
@ -1126,15 +1112,11 @@ export const CERTIFICATE_AUTHORITIES = {
|
|||||||
issuingCaCertificate: "The certificate of the issuing CA",
|
issuingCaCertificate: "The certificate of the issuing CA",
|
||||||
certificateChain: "The certificate chain of the issued certificate",
|
certificateChain: "The certificate chain of the issued certificate",
|
||||||
privateKey: "The private key of the issued certificate",
|
privateKey: "The private key of the issued certificate",
|
||||||
serialNumber: "The serial number of the issued certificate",
|
serialNumber: "The serial number of the issued certificate"
|
||||||
keyUsages: "The key usage extension of the certificate",
|
|
||||||
extendedKeyUsages: "The extended key usage extension of the certificate"
|
|
||||||
},
|
},
|
||||||
SIGN_CERT: {
|
SIGN_CERT: {
|
||||||
caId: "The ID of the CA to issue the certificate from",
|
caId: "The ID of the CA to issue the certificate from",
|
||||||
pkiCollectionId: "The ID of the PKI collection to add the certificate to",
|
pkiCollectionId: "The ID of the PKI collection to add the certificate to",
|
||||||
keyUsages: "The key usage extension of the certificate",
|
|
||||||
extendedKeyUsages: "The extended key usage extension of the certificate",
|
|
||||||
csr: "The pem-encoded CSR to sign with the CA to be used for certificate issuance",
|
csr: "The pem-encoded CSR to sign with the CA to be used for certificate issuance",
|
||||||
friendlyName: "A friendly name for the certificate",
|
friendlyName: "A friendly name for the certificate",
|
||||||
commonName: "The common name (CN) for the certificate",
|
commonName: "The common name (CN) for the certificate",
|
||||||
@ -1184,10 +1166,7 @@ export const CERTIFICATE_TEMPLATES = {
|
|||||||
name: "The name of the template",
|
name: "The name of the template",
|
||||||
commonName: "The regular expression string to use for validating common names",
|
commonName: "The regular expression string to use for validating common names",
|
||||||
subjectAlternativeName: "The regular expression string to use for validating subject alternative names",
|
subjectAlternativeName: "The regular expression string to use for validating subject alternative names",
|
||||||
ttl: "The max TTL for the template",
|
ttl: "The max TTL for the template"
|
||||||
keyUsages: "The key usage constraint or default value for when template is used during certificate issuance",
|
|
||||||
extendedKeyUsages:
|
|
||||||
"The extended key usage constraint or default value for when template is used during certificate issuance"
|
|
||||||
},
|
},
|
||||||
GET: {
|
GET: {
|
||||||
certificateTemplateId: "The ID of the certificate template to get"
|
certificateTemplateId: "The ID of the certificate template to get"
|
||||||
@ -1199,11 +1178,7 @@ export const CERTIFICATE_TEMPLATES = {
|
|||||||
name: "The updated name of the template",
|
name: "The updated name of the template",
|
||||||
commonName: "The updated regular expression string for validating common names",
|
commonName: "The updated regular expression string for validating common names",
|
||||||
subjectAlternativeName: "The updated regular expression string for validating subject alternative names",
|
subjectAlternativeName: "The updated regular expression string for validating subject alternative names",
|
||||||
ttl: "The updated max TTL for the template",
|
ttl: "The updated max TTL for the template"
|
||||||
keyUsages:
|
|
||||||
"The updated key usage constraint or default value for when template is used during certificate issuance",
|
|
||||||
extendedKeyUsages:
|
|
||||||
"The updated extended key usage constraint or default value for when template is used during certificate issuance"
|
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
certificateTemplateId: "The ID of the certificate template to delete"
|
certificateTemplateId: "The ID of the certificate template to delete"
|
||||||
|
@ -147,8 +147,8 @@ const envSchema = z
|
|||||||
PLAIN_WISH_LABEL_IDS: zpStr(z.string().optional()),
|
PLAIN_WISH_LABEL_IDS: zpStr(z.string().optional()),
|
||||||
DISABLE_AUDIT_LOG_GENERATION: zodStrBool.default("false"),
|
DISABLE_AUDIT_LOG_GENERATION: zodStrBool.default("false"),
|
||||||
SSL_CLIENT_CERTIFICATE_HEADER_KEY: zpStr(z.string().optional()).default("x-ssl-client-cert"),
|
SSL_CLIENT_CERTIFICATE_HEADER_KEY: zpStr(z.string().optional()).default("x-ssl-client-cert"),
|
||||||
WORKFLOW_SLACK_CLIENT_ID: zpStr(z.string().optional()),
|
WORKFLOW_SLACK_CLIENT_ID: zpStr(z.string()).optional(),
|
||||||
WORKFLOW_SLACK_CLIENT_SECRET: zpStr(z.string().optional())
|
WORKFLOW_SLACK_CLIENT_SECRET: zpStr(z.string()).optional()
|
||||||
})
|
})
|
||||||
.transform((data) => ({
|
.transform((data) => ({
|
||||||
...data,
|
...data,
|
||||||
|
@ -52,8 +52,3 @@ export enum SecretSharingAccessType {
|
|||||||
Anyone = "anyone",
|
Anyone = "anyone",
|
||||||
Organization = "organization"
|
Organization = "organization"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum OrderByDirection {
|
|
||||||
ASC = "asc",
|
|
||||||
DESC = "desc"
|
|
||||||
}
|
|
||||||
|
@ -91,6 +91,8 @@ export type TQueueJobTypes = {
|
|||||||
[QueueName.IntegrationSync]: {
|
[QueueName.IntegrationSync]: {
|
||||||
name: QueueJobs.IntegrationSync;
|
name: QueueJobs.IntegrationSync;
|
||||||
payload: {
|
payload: {
|
||||||
|
isManual?: boolean;
|
||||||
|
actorId?: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import fastifyPlugin from "fastify-plugin";
|
import fastifyPlugin from "fastify-plugin";
|
||||||
import { JsonWebTokenError } from "jsonwebtoken";
|
|
||||||
import { ZodError } from "zod";
|
import { ZodError } from "zod";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -12,12 +11,6 @@ import {
|
|||||||
UnauthorizedError
|
UnauthorizedError
|
||||||
} from "@app/lib/errors";
|
} from "@app/lib/errors";
|
||||||
|
|
||||||
enum JWTErrors {
|
|
||||||
JwtExpired = "jwt expired",
|
|
||||||
JwtMalformed = "jwt malformed",
|
|
||||||
InvalidAlgorithm = "invalid algorithm"
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => {
|
export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => {
|
||||||
server.setErrorHandler((error, req, res) => {
|
server.setErrorHandler((error, req, res) => {
|
||||||
req.log.error(error);
|
req.log.error(error);
|
||||||
@ -43,27 +36,6 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
|
|||||||
status: error.status,
|
status: error.status,
|
||||||
detail: error.detail
|
detail: error.detail
|
||||||
});
|
});
|
||||||
// Handle JWT errors and make them more human-readable for the end-user.
|
|
||||||
} else if (error instanceof JsonWebTokenError) {
|
|
||||||
const message = (() => {
|
|
||||||
if (error.message === JWTErrors.JwtExpired) {
|
|
||||||
return "Your token has expired. Please re-authenticate.";
|
|
||||||
}
|
|
||||||
if (error.message === JWTErrors.JwtMalformed) {
|
|
||||||
return "The provided access token is malformed. Please use a valid token or generate a new one and try again.";
|
|
||||||
}
|
|
||||||
if (error.message === JWTErrors.InvalidAlgorithm) {
|
|
||||||
return "The access token is signed with an invalid algorithm. Please provide a valid token and try again.";
|
|
||||||
}
|
|
||||||
|
|
||||||
return error.message;
|
|
||||||
})();
|
|
||||||
|
|
||||||
void res.status(401).send({
|
|
||||||
statusCode: 401,
|
|
||||||
error: "TokenError",
|
|
||||||
message
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
void res.send(error);
|
void res.send(error);
|
||||||
}
|
}
|
||||||
|
@ -493,6 +493,7 @@ export const registerRoutes = async (
|
|||||||
orgRoleDAL,
|
orgRoleDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
|
userGroupMembershipDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
incidentContactDAL,
|
incidentContactDAL,
|
||||||
tokenService,
|
tokenService,
|
||||||
@ -810,6 +811,8 @@ export const registerRoutes = async (
|
|||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
webhookDAL,
|
webhookDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
|
auditLogService,
|
||||||
|
userDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
smtpService,
|
smtpService,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@ -8,7 +7,7 @@ import { CERTIFICATE_AUTHORITIES } from "@app/lib/api-docs";
|
|||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
|
||||||
import { CaRenewalType, CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-types";
|
import { CaRenewalType, CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-types";
|
||||||
import {
|
import {
|
||||||
validateAltNamesField,
|
validateAltNamesField,
|
||||||
@ -140,33 +139,6 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// this endpoint will be used to serve the CA certificate when a client makes a request
|
|
||||||
// against the Authority Information Access CA Issuer URL
|
|
||||||
server.route({
|
|
||||||
method: "GET",
|
|
||||||
url: "/:caId/certificates/:caCertId/der",
|
|
||||||
config: {
|
|
||||||
rateLimit: readLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
description: "Get DER-encoded certificate of CA",
|
|
||||||
params: z.object({
|
|
||||||
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET_CERT_BY_ID.caId),
|
|
||||||
caCertId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET_CERT_BY_ID.caCertId)
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.instanceof(Buffer)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handler: async (req, res) => {
|
|
||||||
const caCert = await server.services.certificateAuthority.getCaCertById(req.params);
|
|
||||||
|
|
||||||
res.header("Content-Type", "application/pkix-cert");
|
|
||||||
|
|
||||||
return Buffer.from(caCert.rawData);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
url: "/:caId",
|
url: "/:caId",
|
||||||
@ -601,9 +573,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
|
|||||||
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.ttl),
|
.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.ttl),
|
||||||
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notBefore),
|
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notBefore),
|
||||||
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notAfter),
|
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notAfter)
|
||||||
keyUsages: z.nativeEnum(CertKeyUsage).array().optional(),
|
|
||||||
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional()
|
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
@ -683,9 +653,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
|
|||||||
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.ttl),
|
.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.ttl),
|
||||||
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notBefore),
|
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notBefore),
|
||||||
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notAfter),
|
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notAfter)
|
||||||
keyUsages: z.nativeEnum(CertKeyUsage).array().optional(),
|
|
||||||
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional()
|
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
|
@ -7,7 +7,7 @@ import { CERTIFICATE_AUTHORITIES, CERTIFICATES } from "@app/lib/api-docs";
|
|||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { CertExtendedKeyUsage, CertKeyUsage, CrlReason } from "@app/services/certificate/certificate-types";
|
import { CrlReason } from "@app/services/certificate/certificate-types";
|
||||||
import {
|
import {
|
||||||
validateAltNamesField,
|
validateAltNamesField,
|
||||||
validateCaDateField
|
validateCaDateField
|
||||||
@ -86,17 +86,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
|||||||
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.ttl),
|
.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.ttl),
|
||||||
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notBefore),
|
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notBefore),
|
||||||
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notAfter),
|
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notAfter)
|
||||||
keyUsages: z
|
|
||||||
.nativeEnum(CertKeyUsage)
|
|
||||||
.array()
|
|
||||||
.optional()
|
|
||||||
.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.keyUsages),
|
|
||||||
extendedKeyUsages: z
|
|
||||||
.nativeEnum(CertExtendedKeyUsage)
|
|
||||||
.array()
|
|
||||||
.optional()
|
|
||||||
.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.extendedKeyUsages)
|
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
@ -187,17 +177,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
|||||||
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.ttl),
|
.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.ttl),
|
||||||
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notBefore),
|
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notBefore),
|
||||||
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notAfter),
|
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notAfter)
|
||||||
keyUsages: z
|
|
||||||
.nativeEnum(CertKeyUsage)
|
|
||||||
.array()
|
|
||||||
.optional()
|
|
||||||
.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.keyUsages),
|
|
||||||
extendedKeyUsages: z
|
|
||||||
.nativeEnum(CertExtendedKeyUsage)
|
|
||||||
.array()
|
|
||||||
.optional()
|
|
||||||
.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.extendedKeyUsages)
|
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
|
@ -7,7 +7,6 @@ import { CERTIFICATE_TEMPLATES } from "@app/lib/api-docs";
|
|||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
|
||||||
import { sanitizedCertificateTemplate } from "@app/services/certificate-template/certificate-template-schema";
|
import { sanitizedCertificateTemplate } from "@app/services/certificate-template/certificate-template-schema";
|
||||||
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
|
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
|
||||||
|
|
||||||
@ -75,19 +74,7 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
|
|||||||
ttl: z
|
ttl: z
|
||||||
.string()
|
.string()
|
||||||
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
.describe(CERTIFICATE_TEMPLATES.CREATE.ttl),
|
.describe(CERTIFICATE_TEMPLATES.CREATE.ttl)
|
||||||
keyUsages: z
|
|
||||||
.nativeEnum(CertKeyUsage)
|
|
||||||
.array()
|
|
||||||
.optional()
|
|
||||||
.default([CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT])
|
|
||||||
.describe(CERTIFICATE_TEMPLATES.CREATE.keyUsages),
|
|
||||||
extendedKeyUsages: z
|
|
||||||
.nativeEnum(CertExtendedKeyUsage)
|
|
||||||
.array()
|
|
||||||
.optional()
|
|
||||||
.default([])
|
|
||||||
.describe(CERTIFICATE_TEMPLATES.CREATE.extendedKeyUsages)
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: sanitizedCertificateTemplate
|
200: sanitizedCertificateTemplate
|
||||||
@ -143,13 +130,7 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
|
|||||||
.string()
|
.string()
|
||||||
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
.optional()
|
.optional()
|
||||||
.describe(CERTIFICATE_TEMPLATES.UPDATE.ttl),
|
.describe(CERTIFICATE_TEMPLATES.UPDATE.ttl)
|
||||||
keyUsages: z.nativeEnum(CertKeyUsage).array().optional().describe(CERTIFICATE_TEMPLATES.UPDATE.keyUsages),
|
|
||||||
extendedKeyUsages: z
|
|
||||||
.nativeEnum(CertExtendedKeyUsage)
|
|
||||||
.array()
|
|
||||||
.optional()
|
|
||||||
.describe(CERTIFICATE_TEMPLATES.UPDATE.extendedKeyUsages)
|
|
||||||
}),
|
}),
|
||||||
params: z.object({
|
params: z.object({
|
||||||
certificateTemplateId: z.string().describe(CERTIFICATE_TEMPLATES.UPDATE.certificateTemplateId)
|
certificateTemplateId: z.string().describe(CERTIFICATE_TEMPLATES.UPDATE.certificateTemplateId)
|
||||||
|
@ -246,13 +246,12 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
|||||||
description: true
|
description: true
|
||||||
}).optional(),
|
}).optional(),
|
||||||
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
|
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
|
||||||
}).array(),
|
}).array()
|
||||||
totalCount: z.number()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { identityMemberships, totalCount } = await server.services.identity.listOrgIdentities({
|
const identities = await server.services.identity.listOrgIdentities({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
@ -260,7 +259,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
|||||||
orgId: req.query.orgId
|
orgId: req.query.orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
return { identities: identityMemberships, totalCount };
|
return { identities };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import { IntegrationsSchema } from "@app/db/schemas";
|
|||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { INTEGRATION } from "@app/lib/api-docs";
|
import { INTEGRATION } from "@app/lib/api-docs";
|
||||||
import { removeTrailingSlash, shake } from "@app/lib/fn";
|
import { removeTrailingSlash, shake } from "@app/lib/fn";
|
||||||
import { writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@ -154,6 +154,48 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:integrationId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Get an integration by integration id",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
integrationId: z.string().trim().describe(INTEGRATION.UPDATE.integrationId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
integration: IntegrationsSchema.extend({
|
||||||
|
environment: z.object({
|
||||||
|
slug: z.string().trim(),
|
||||||
|
name: z.string().trim(),
|
||||||
|
id: z.string().trim()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const integration = await server.services.integration.getIntegration({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.params.integrationId
|
||||||
|
});
|
||||||
|
|
||||||
|
return { integration };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/:integrationId",
|
url: "/:integrationId",
|
||||||
|
@ -14,7 +14,7 @@ import { AUDIT_LOGS, ORGANIZATIONS } from "@app/lib/api-docs";
|
|||||||
import { getLastMidnightDateISO } from "@app/lib/fn";
|
import { getLastMidnightDateISO } from "@app/lib/fn";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@ -74,8 +74,35 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
schema: {
|
schema: {
|
||||||
description: "Get all audit logs for an organization",
|
description: "Get all audit logs for an organization",
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
eventType: z.nativeEnum(EventType).optional().describe(AUDIT_LOGS.EXPORT.eventType),
|
projectId: z.string().optional(),
|
||||||
|
actorType: z.nativeEnum(ActorType).optional(),
|
||||||
|
// eventType is split with , for multiple values, we need to transform it to array
|
||||||
|
eventType: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((val) => (val ? val.split(",") : undefined)),
|
||||||
userAgentType: z.nativeEnum(UserAgentType).optional().describe(AUDIT_LOGS.EXPORT.userAgentType),
|
userAgentType: z.nativeEnum(UserAgentType).optional().describe(AUDIT_LOGS.EXPORT.userAgentType),
|
||||||
|
eventMetadata: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((val) => {
|
||||||
|
if (!val) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pairs = val.split(",");
|
||||||
|
|
||||||
|
return pairs.reduce(
|
||||||
|
(acc, pair) => {
|
||||||
|
const [key, value] = pair.split("=");
|
||||||
|
if (key && value) {
|
||||||
|
acc[key] = value;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, string>
|
||||||
|
);
|
||||||
|
}),
|
||||||
startDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.startDate),
|
startDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.startDate),
|
||||||
endDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.endDate),
|
endDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.endDate),
|
||||||
offset: z.coerce.number().default(0).describe(AUDIT_LOGS.EXPORT.offset),
|
offset: z.coerce.number().default(0).describe(AUDIT_LOGS.EXPORT.offset),
|
||||||
@ -114,13 +141,19 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const auditLogs = await server.services.auditLog.listAuditLogs({
|
const auditLogs = await server.services.auditLog.listAuditLogs({
|
||||||
|
filter: {
|
||||||
|
...req.query,
|
||||||
|
endDate: req.query.endDate,
|
||||||
|
projectId: req.query.projectId,
|
||||||
|
startDate: req.query.startDate || getLastMidnightDateISO(),
|
||||||
|
auditLogActorId: req.query.actor,
|
||||||
|
actorType: req.query.actorType,
|
||||||
|
eventType: req.query.eventType as EventType[] | undefined
|
||||||
|
},
|
||||||
|
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
...req.query,
|
|
||||||
endDate: req.query.endDate,
|
|
||||||
startDate: req.query.startDate || getLastMidnightDateISO(),
|
|
||||||
auditLogActor: req.query.actor,
|
|
||||||
actor: req.permission.type
|
actor: req.permission.type
|
||||||
});
|
});
|
||||||
return { auditLogs };
|
return { auditLogs };
|
||||||
|
@ -2,11 +2,9 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { IdentitiesSchema, IdentityOrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
|
import { IdentitiesSchema, IdentityOrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
|
||||||
import { ORGANIZATIONS } from "@app/lib/api-docs";
|
import { ORGANIZATIONS } from "@app/lib/api-docs";
|
||||||
import { OrderByDirection } from "@app/lib/types";
|
|
||||||
import { readLimit } from "@app/server/config/rateLimiter";
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { OrgIdentityOrderBy } from "@app/services/identity/identity-types";
|
|
||||||
|
|
||||||
export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => {
|
export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@ -26,27 +24,6 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
orgId: z.string().trim().describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orgId)
|
orgId: z.string().trim().describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orgId)
|
||||||
}),
|
}),
|
||||||
querystring: z.object({
|
|
||||||
offset: z.coerce.number().min(0).default(0).describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.offset).optional(),
|
|
||||||
limit: z.coerce
|
|
||||||
.number()
|
|
||||||
.min(1)
|
|
||||||
.max(20000) // TODO: temp limit until combobox added to add identity to project modal, reduce once added
|
|
||||||
.default(100)
|
|
||||||
.describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.limit)
|
|
||||||
.optional(),
|
|
||||||
orderBy: z
|
|
||||||
.nativeEnum(OrgIdentityOrderBy)
|
|
||||||
.default(OrgIdentityOrderBy.Name)
|
|
||||||
.describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orderBy)
|
|
||||||
.optional(),
|
|
||||||
orderDirection: z
|
|
||||||
.nativeEnum(OrderByDirection)
|
|
||||||
.default(OrderByDirection.ASC)
|
|
||||||
.describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orderDirection)
|
|
||||||
.optional(),
|
|
||||||
search: z.string().trim().describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.search).optional()
|
|
||||||
}),
|
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityMemberships: IdentityOrgMembershipsSchema.merge(
|
identityMemberships: IdentityOrgMembershipsSchema.merge(
|
||||||
@ -60,26 +37,20 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
}).optional(),
|
}).optional(),
|
||||||
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
|
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
|
||||||
})
|
})
|
||||||
).array(),
|
).array()
|
||||||
totalCount: z.number()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { identityMemberships, totalCount } = await server.services.identity.listOrgIdentities({
|
const identityMemberships = await server.services.identity.listOrgIdentities({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.params.orgId,
|
orgId: req.params.orgId
|
||||||
limit: req.query.limit,
|
|
||||||
offset: req.query.offset,
|
|
||||||
orderBy: req.query.orderBy,
|
|
||||||
orderDirection: req.query.orderDirection,
|
|
||||||
search: req.query.search
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return { identityMemberships, totalCount };
|
return { identityMemberships };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -7,13 +7,11 @@ import {
|
|||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
ProjectUserMembershipRolesSchema
|
ProjectUserMembershipRolesSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { ORGANIZATIONS, PROJECT_IDENTITIES } from "@app/lib/api-docs";
|
import { PROJECT_IDENTITIES } from "@app/lib/api-docs";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { OrderByDirection } from "@app/lib/types";
|
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { ProjectIdentityOrderBy } from "@app/services/identity-project/identity-project-types";
|
|
||||||
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
|
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
|
||||||
|
|
||||||
import { SanitizedProjectSchema } from "../sanitizedSchemas";
|
import { SanitizedProjectSchema } from "../sanitizedSchemas";
|
||||||
@ -216,32 +214,6 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
projectId: z.string().trim().describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.projectId)
|
projectId: z.string().trim().describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.projectId)
|
||||||
}),
|
}),
|
||||||
querystring: z.object({
|
|
||||||
offset: z.coerce
|
|
||||||
.number()
|
|
||||||
.min(0)
|
|
||||||
.default(0)
|
|
||||||
.describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.offset)
|
|
||||||
.optional(),
|
|
||||||
limit: z.coerce
|
|
||||||
.number()
|
|
||||||
.min(1)
|
|
||||||
.max(20000) // TODO: temp limit until combobox added to add identity to project modal, reduce once added
|
|
||||||
.default(100)
|
|
||||||
.describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.limit)
|
|
||||||
.optional(),
|
|
||||||
orderBy: z
|
|
||||||
.nativeEnum(ProjectIdentityOrderBy)
|
|
||||||
.default(ProjectIdentityOrderBy.Name)
|
|
||||||
.describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orderBy)
|
|
||||||
.optional(),
|
|
||||||
orderDirection: z
|
|
||||||
.nativeEnum(OrderByDirection)
|
|
||||||
.default(OrderByDirection.ASC)
|
|
||||||
.describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orderDirection)
|
|
||||||
.optional(),
|
|
||||||
search: z.string().trim().describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.search).optional()
|
|
||||||
}),
|
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityMemberships: z
|
identityMemberships: z
|
||||||
@ -267,25 +239,19 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
|||||||
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true }),
|
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true }),
|
||||||
project: SanitizedProjectSchema.pick({ name: true, id: true })
|
project: SanitizedProjectSchema.pick({ name: true, id: true })
|
||||||
})
|
})
|
||||||
.array(),
|
.array()
|
||||||
totalCount: z.number()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { identityMemberships, totalCount } = await server.services.identityProject.listProjectIdentities({
|
const identityMemberships = await server.services.identityProject.listProjectIdentities({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
projectId: req.params.projectId,
|
projectId: req.params.projectId
|
||||||
limit: req.query.limit,
|
|
||||||
offset: req.query.offset,
|
|
||||||
orderBy: req.query.orderBy,
|
|
||||||
orderDirection: req.query.orderDirection,
|
|
||||||
search: req.query.search
|
|
||||||
});
|
});
|
||||||
return { identityMemberships, totalCount };
|
return { identityMemberships };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ export enum AuthMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum ActorType { // would extend to AWS, Azure, ...
|
export enum ActorType { // would extend to AWS, Azure, ...
|
||||||
|
PLATFORM = "platform", // Useful for when we want to perform logging on automated actions such as integration syncs.
|
||||||
USER = "user", // userIdentity
|
USER = "user", // userIdentity
|
||||||
SERVICE = "service",
|
SERVICE = "service",
|
||||||
IDENTITY = "identity",
|
IDENTITY = "identity",
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
|
|
||||||
/* eslint-disable no-bitwise */
|
/* eslint-disable no-bitwise */
|
||||||
export const createSerialNumber = () => {
|
export const createSerialNumber = () => {
|
||||||
const randomBytes = crypto.randomBytes(20);
|
const randomBytes = crypto.randomBytes(32);
|
||||||
randomBytes[0] &= 0x7f; // ensure the first bit is 0
|
randomBytes[0] &= 0x7f; // ensure the first bit is 0
|
||||||
return randomBytes.toString("hex");
|
return randomBytes.toString("hex");
|
||||||
};
|
};
|
||||||
|
@ -19,13 +19,7 @@ import { TProjectDALFactory } from "@app/services/project/project-dal";
|
|||||||
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
||||||
|
|
||||||
import { TCertificateAuthorityCrlDALFactory } from "../../ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
import { TCertificateAuthorityCrlDALFactory } from "../../ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||||
import {
|
import { CertKeyAlgorithm, CertStatus } from "../certificate/certificate-types";
|
||||||
CertExtendedKeyUsage,
|
|
||||||
CertExtendedKeyUsageOIDToName,
|
|
||||||
CertKeyAlgorithm,
|
|
||||||
CertKeyUsage,
|
|
||||||
CertStatus
|
|
||||||
} from "../certificate/certificate-types";
|
|
||||||
import { TCertificateTemplateDALFactory } from "../certificate-template/certificate-template-dal";
|
import { TCertificateTemplateDALFactory } from "../certificate-template/certificate-template-dal";
|
||||||
import { validateCertificateDetailsAgainstTemplate } from "../certificate-template/certificate-template-fns";
|
import { validateCertificateDetailsAgainstTemplate } from "../certificate-template/certificate-template-fns";
|
||||||
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
|
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
|
||||||
@ -768,39 +762,6 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Return CA certificate object by ID
|
|
||||||
*/
|
|
||||||
const getCaCertById = async ({ caId, caCertId }: { caId: string; caCertId: string }) => {
|
|
||||||
const caCert = await certificateAuthorityCertDAL.findOne({
|
|
||||||
caId,
|
|
||||||
id: caCertId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!caCert) {
|
|
||||||
throw new NotFoundError({ message: "CA certificate not found" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const ca = await certificateAuthorityDAL.findById(caId);
|
|
||||||
const keyId = await getProjectKmsCertificateKeyId({
|
|
||||||
projectId: ca.projectId,
|
|
||||||
projectDAL,
|
|
||||||
kmsService
|
|
||||||
});
|
|
||||||
|
|
||||||
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
|
||||||
kmsId: keyId
|
|
||||||
});
|
|
||||||
|
|
||||||
const decryptedCaCert = await kmsDecryptor({
|
|
||||||
cipherTextBlob: caCert.encryptedCertificate
|
|
||||||
});
|
|
||||||
|
|
||||||
const caCertObj = new x509.X509Certificate(decryptedCaCert);
|
|
||||||
|
|
||||||
return caCertObj;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Issue certificate to be imported back in for intermediate CA
|
* Issue certificate to be imported back in for intermediate CA
|
||||||
*/
|
*/
|
||||||
@ -815,7 +776,6 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
notAfter,
|
notAfter,
|
||||||
maxPathLength
|
maxPathLength
|
||||||
}: TSignIntermediateDTO) => {
|
}: TSignIntermediateDTO) => {
|
||||||
const appCfg = getConfig();
|
|
||||||
const ca = await certificateAuthorityDAL.findById(caId);
|
const ca = await certificateAuthorityDAL.findById(caId);
|
||||||
if (!ca) throw new BadRequestError({ message: "CA not found" });
|
if (!ca) throw new BadRequestError({ message: "CA not found" });
|
||||||
|
|
||||||
@ -890,7 +850,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
throw new BadRequestError({ message: "notAfter date is after CA certificate's notAfter date" });
|
throw new BadRequestError({ message: "notAfter date is after CA certificate's notAfter date" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { caPrivateKey, caSecret } = await getCaCredentials({
|
const { caPrivateKey } = await getCaCredentials({
|
||||||
caId: ca.id,
|
caId: ca.id,
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateAuthoritySecretDAL,
|
certificateAuthoritySecretDAL,
|
||||||
@ -899,11 +859,6 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const serialNumber = createSerialNumber();
|
const serialNumber = createSerialNumber();
|
||||||
|
|
||||||
const caCrl = await certificateAuthorityCrlDAL.findOne({ caSecretId: caSecret.id });
|
|
||||||
const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}/der`;
|
|
||||||
|
|
||||||
const caIssuerUrl = `${appCfg.SITE_URL}/api/v1/pki/ca/${ca.id}/certificates/${caCert.id}/der`;
|
|
||||||
const intermediateCert = await x509.X509CertificateGenerator.create({
|
const intermediateCert = await x509.X509CertificateGenerator.create({
|
||||||
serialNumber,
|
serialNumber,
|
||||||
subject: csrObj.subject,
|
subject: csrObj.subject,
|
||||||
@ -923,11 +878,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
),
|
),
|
||||||
new x509.BasicConstraintsExtension(true, maxPathLength === -1 ? undefined : maxPathLength, true),
|
new x509.BasicConstraintsExtension(true, maxPathLength === -1 ? undefined : maxPathLength, true),
|
||||||
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
||||||
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey),
|
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
|
||||||
new x509.CRLDistributionPointsExtension([distributionPointUrl]),
|
|
||||||
new x509.AuthorityInfoAccessExtension({
|
|
||||||
caIssuers: new x509.GeneralName("url", caIssuerUrl)
|
|
||||||
})
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1101,9 +1052,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
keyUsages,
|
|
||||||
extendedKeyUsages
|
|
||||||
}: TIssueCertFromCaDTO) => {
|
}: TIssueCertFromCaDTO) => {
|
||||||
let ca: TCertificateAuthorities | undefined;
|
let ca: TCertificateAuthorities | undefined;
|
||||||
let certificateTemplate: TCertificateTemplates | undefined;
|
let certificateTemplate: TCertificateTemplates | undefined;
|
||||||
@ -1219,70 +1168,16 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
const caCrl = await certificateAuthorityCrlDAL.findOne({ caSecretId: caSecret.id });
|
const caCrl = await certificateAuthorityCrlDAL.findOne({ caSecretId: caSecret.id });
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}/der`;
|
const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}`;
|
||||||
const caIssuerUrl = `${appCfg.SITE_URL}/api/v1/pki/ca/${ca.id}/certificates/${caCert.id}/der`;
|
|
||||||
|
|
||||||
const extensions: x509.Extension[] = [
|
const extensions: x509.Extension[] = [
|
||||||
|
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment, true),
|
||||||
new x509.BasicConstraintsExtension(false),
|
new x509.BasicConstraintsExtension(false),
|
||||||
new x509.CRLDistributionPointsExtension([distributionPointUrl]),
|
new x509.CRLDistributionPointsExtension([distributionPointUrl]),
|
||||||
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
||||||
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey),
|
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
|
||||||
new x509.AuthorityInfoAccessExtension({
|
|
||||||
caIssuers: new x509.GeneralName("url", caIssuerUrl)
|
|
||||||
}),
|
|
||||||
new x509.CertificatePolicyExtension(["2.5.29.32.0"]) // anyPolicy
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// handle key usages
|
|
||||||
let selectedKeyUsages: CertKeyUsage[] = keyUsages ?? [];
|
|
||||||
if (keyUsages === undefined && !certificateTemplate) {
|
|
||||||
selectedKeyUsages = [CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyUsages === undefined && certificateTemplate) {
|
|
||||||
selectedKeyUsages = (certificateTemplate.keyUsages ?? []) as CertKeyUsage[];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyUsages?.length && certificateTemplate) {
|
|
||||||
const validKeyUsages = certificateTemplate.keyUsages || [];
|
|
||||||
if (keyUsages.some((keyUsage) => !validKeyUsages.includes(keyUsage))) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Invalid key usage value based on template policy"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
selectedKeyUsages = keyUsages;
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyUsagesBitValue = selectedKeyUsages.reduce((accum, keyUsage) => accum | x509.KeyUsageFlags[keyUsage], 0);
|
|
||||||
if (keyUsagesBitValue) {
|
|
||||||
extensions.push(new x509.KeyUsagesExtension(keyUsagesBitValue, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle extended key usages
|
|
||||||
let selectedExtendedKeyUsages: CertExtendedKeyUsage[] = extendedKeyUsages ?? [];
|
|
||||||
if (extendedKeyUsages === undefined && certificateTemplate) {
|
|
||||||
selectedExtendedKeyUsages = (certificateTemplate.extendedKeyUsages ?? []) as CertExtendedKeyUsage[];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extendedKeyUsages?.length && certificateTemplate) {
|
|
||||||
const validExtendedKeyUsages = certificateTemplate.extendedKeyUsages || [];
|
|
||||||
if (extendedKeyUsages.some((eku) => !validExtendedKeyUsages.includes(eku))) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Invalid extended key usage value based on template policy"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
selectedExtendedKeyUsages = extendedKeyUsages;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedExtendedKeyUsages.length) {
|
|
||||||
extensions.push(
|
|
||||||
new x509.ExtendedKeyUsageExtension(
|
|
||||||
selectedExtendedKeyUsages.map((eku) => x509.ExtendedKeyUsage[eku]),
|
|
||||||
true
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let altNamesArray: {
|
let altNamesArray: {
|
||||||
type: "email" | "dns";
|
type: "email" | "dns";
|
||||||
value: string;
|
value: string;
|
||||||
@ -1364,9 +1259,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
altNames,
|
altNames,
|
||||||
serialNumber,
|
serialNumber,
|
||||||
notBefore: notBeforeDate,
|
notBefore: notBeforeDate,
|
||||||
notAfter: notAfterDate,
|
notAfter: notAfterDate
|
||||||
keyUsages: selectedKeyUsages,
|
|
||||||
extendedKeyUsages: selectedExtendedKeyUsages
|
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@ -1415,7 +1308,6 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
* Note: CSR is generated externally and submitted to Infisical.
|
* Note: CSR is generated externally and submitted to Infisical.
|
||||||
*/
|
*/
|
||||||
const signCertFromCa = async (dto: TSignCertFromCaDTO) => {
|
const signCertFromCa = async (dto: TSignCertFromCaDTO) => {
|
||||||
const appCfg = getConfig();
|
|
||||||
let ca: TCertificateAuthorities | undefined;
|
let ca: TCertificateAuthorities | undefined;
|
||||||
let certificateTemplate: TCertificateTemplates | undefined;
|
let certificateTemplate: TCertificateTemplates | undefined;
|
||||||
|
|
||||||
@ -1429,9 +1321,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
altNames,
|
altNames,
|
||||||
ttl,
|
ttl,
|
||||||
notBefore,
|
notBefore,
|
||||||
notAfter,
|
notAfter
|
||||||
keyUsages,
|
|
||||||
extendedKeyUsages
|
|
||||||
} = dto;
|
} = dto;
|
||||||
|
|
||||||
let collectionId = pkiCollectionId;
|
let collectionId = pkiCollectionId;
|
||||||
@ -1542,7 +1432,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
message: "A common name (CN) is required in the CSR or as a parameter to this endpoint"
|
message: "A common name (CN) is required in the CSR or as a parameter to this endpoint"
|
||||||
});
|
});
|
||||||
|
|
||||||
const { caPrivateKey, caSecret } = await getCaCredentials({
|
const { caPrivateKey } = await getCaCredentials({
|
||||||
caId: ca.id,
|
caId: ca.id,
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateAuthoritySecretDAL,
|
certificateAuthoritySecretDAL,
|
||||||
@ -1550,115 +1440,13 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
kmsService
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
const caCrl = await certificateAuthorityCrlDAL.findOne({ caSecretId: caSecret.id });
|
|
||||||
const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}/der`;
|
|
||||||
|
|
||||||
const caIssuerUrl = `${appCfg.SITE_URL}/api/v1/pki/ca/${ca.id}/certificates/${caCert.id}/der`;
|
|
||||||
const extensions: x509.Extension[] = [
|
const extensions: x509.Extension[] = [
|
||||||
|
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment, true),
|
||||||
new x509.BasicConstraintsExtension(false),
|
new x509.BasicConstraintsExtension(false),
|
||||||
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
||||||
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey),
|
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
|
||||||
new x509.CRLDistributionPointsExtension([distributionPointUrl]),
|
|
||||||
new x509.AuthorityInfoAccessExtension({
|
|
||||||
caIssuers: new x509.GeneralName("url", caIssuerUrl)
|
|
||||||
}),
|
|
||||||
new x509.CertificatePolicyExtension(["2.5.29.32.0"]) // anyPolicy
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// handle key usages
|
|
||||||
const csrKeyUsageExtension = csrObj.getExtension("2.5.29.15") as x509.KeyUsagesExtension;
|
|
||||||
let csrKeyUsages: CertKeyUsage[] = [];
|
|
||||||
if (csrKeyUsageExtension) {
|
|
||||||
csrKeyUsages = Object.values(CertKeyUsage).filter(
|
|
||||||
(keyUsage) => (x509.KeyUsageFlags[keyUsage] & csrKeyUsageExtension.usages) !== 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let selectedKeyUsages: CertKeyUsage[] = keyUsages ?? [];
|
|
||||||
if (keyUsages === undefined && !certificateTemplate) {
|
|
||||||
if (csrKeyUsageExtension) {
|
|
||||||
selectedKeyUsages = csrKeyUsages;
|
|
||||||
} else {
|
|
||||||
selectedKeyUsages = [CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyUsages === undefined && certificateTemplate) {
|
|
||||||
if (csrKeyUsageExtension) {
|
|
||||||
const validKeyUsages = certificateTemplate.keyUsages || [];
|
|
||||||
if (csrKeyUsages.some((keyUsage) => !validKeyUsages.includes(keyUsage))) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Invalid key usage value based on template policy"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
selectedKeyUsages = csrKeyUsages;
|
|
||||||
} else {
|
|
||||||
selectedKeyUsages = (certificateTemplate.keyUsages ?? []) as CertKeyUsage[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyUsages?.length && certificateTemplate) {
|
|
||||||
const validKeyUsages = certificateTemplate.keyUsages || [];
|
|
||||||
if (keyUsages.some((keyUsage) => !validKeyUsages.includes(keyUsage))) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Invalid key usage value based on template policy"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
selectedKeyUsages = keyUsages;
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyUsagesBitValue = selectedKeyUsages.reduce((accum, keyUsage) => accum | x509.KeyUsageFlags[keyUsage], 0);
|
|
||||||
if (keyUsagesBitValue) {
|
|
||||||
extensions.push(new x509.KeyUsagesExtension(keyUsagesBitValue, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle extended key usages
|
|
||||||
const csrExtendedKeyUsageExtension = csrObj.getExtension("2.5.29.37") as x509.ExtendedKeyUsageExtension;
|
|
||||||
let csrExtendedKeyUsages: CertExtendedKeyUsage[] = [];
|
|
||||||
if (csrExtendedKeyUsageExtension) {
|
|
||||||
csrExtendedKeyUsages = csrExtendedKeyUsageExtension.usages.map(
|
|
||||||
(ekuOid) => CertExtendedKeyUsageOIDToName[ekuOid as string]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let selectedExtendedKeyUsages: CertExtendedKeyUsage[] = extendedKeyUsages ?? [];
|
|
||||||
if (extendedKeyUsages === undefined && !certificateTemplate && csrExtendedKeyUsageExtension) {
|
|
||||||
selectedExtendedKeyUsages = csrExtendedKeyUsages;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extendedKeyUsages === undefined && certificateTemplate) {
|
|
||||||
if (csrExtendedKeyUsageExtension) {
|
|
||||||
const validExtendedKeyUsages = certificateTemplate.extendedKeyUsages || [];
|
|
||||||
if (csrExtendedKeyUsages.some((eku) => !validExtendedKeyUsages.includes(eku))) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Invalid extended key usage value based on template policy"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
selectedExtendedKeyUsages = csrExtendedKeyUsages;
|
|
||||||
} else {
|
|
||||||
selectedExtendedKeyUsages = (certificateTemplate.extendedKeyUsages ?? []) as CertExtendedKeyUsage[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extendedKeyUsages?.length && certificateTemplate) {
|
|
||||||
const validExtendedKeyUsages = certificateTemplate.extendedKeyUsages || [];
|
|
||||||
if (extendedKeyUsages.some((keyUsage) => !validExtendedKeyUsages.includes(keyUsage))) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Invalid extended key usage value based on template policy"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
selectedExtendedKeyUsages = extendedKeyUsages;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedExtendedKeyUsages.length) {
|
|
||||||
extensions.push(
|
|
||||||
new x509.ExtendedKeyUsageExtension(
|
|
||||||
selectedExtendedKeyUsages.map((eku) => x509.ExtendedKeyUsage[eku]),
|
|
||||||
true
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let altNamesFromCsr: string = "";
|
let altNamesFromCsr: string = "";
|
||||||
let altNamesArray: {
|
let altNamesArray: {
|
||||||
type: "email" | "dns";
|
type: "email" | "dns";
|
||||||
@ -1754,9 +1542,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
altNames: altNamesFromCsr || altNames,
|
altNames: altNamesFromCsr || altNames,
|
||||||
serialNumber,
|
serialNumber,
|
||||||
notBefore: notBeforeDate,
|
notBefore: notBeforeDate,
|
||||||
notAfter: notAfterDate,
|
notAfter: notAfterDate
|
||||||
keyUsages: selectedKeyUsages,
|
|
||||||
extendedKeyUsages: selectedExtendedKeyUsages
|
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@ -1842,7 +1628,6 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
renewCaCert,
|
renewCaCert,
|
||||||
getCaCerts,
|
getCaCerts,
|
||||||
getCaCert,
|
getCaCert,
|
||||||
getCaCertById,
|
|
||||||
signIntermediate,
|
signIntermediate,
|
||||||
importCertToCa,
|
importCertToCa,
|
||||||
issueCertFromCa,
|
issueCertFromCa,
|
||||||
|
@ -4,7 +4,7 @@ import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
|||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
|
||||||
import { TCertificateAuthorityCrlDALFactory } from "../../ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
import { TCertificateAuthorityCrlDALFactory } from "../../ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||||
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "../certificate/certificate-types";
|
import { CertKeyAlgorithm } from "../certificate/certificate-types";
|
||||||
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
|
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
|
||||||
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
|
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
|
||||||
import { TCertificateAuthoritySecretDALFactory } from "./certificate-authority-secret-dal";
|
import { TCertificateAuthoritySecretDALFactory } from "./certificate-authority-secret-dal";
|
||||||
@ -97,8 +97,6 @@ export type TIssueCertFromCaDTO = {
|
|||||||
ttl: string;
|
ttl: string;
|
||||||
notBefore?: string;
|
notBefore?: string;
|
||||||
notAfter?: string;
|
notAfter?: string;
|
||||||
keyUsages?: CertKeyUsage[];
|
|
||||||
extendedKeyUsages?: CertExtendedKeyUsage[];
|
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TSignCertFromCaDTO =
|
export type TSignCertFromCaDTO =
|
||||||
@ -114,8 +112,6 @@ export type TSignCertFromCaDTO =
|
|||||||
ttl?: string;
|
ttl?: string;
|
||||||
notBefore?: string;
|
notBefore?: string;
|
||||||
notAfter?: string;
|
notAfter?: string;
|
||||||
keyUsages?: CertKeyUsage[];
|
|
||||||
extendedKeyUsages?: CertExtendedKeyUsage[];
|
|
||||||
}
|
}
|
||||||
| ({
|
| ({
|
||||||
isInternal: false;
|
isInternal: false;
|
||||||
@ -129,8 +125,6 @@ export type TSignCertFromCaDTO =
|
|||||||
ttl: string;
|
ttl: string;
|
||||||
notBefore?: string;
|
notBefore?: string;
|
||||||
notAfter?: string;
|
notAfter?: string;
|
||||||
keyUsages?: CertKeyUsage[];
|
|
||||||
extendedKeyUsages?: CertExtendedKeyUsage[];
|
|
||||||
} & Omit<TProjectPermission, "projectId">);
|
} & Omit<TProjectPermission, "projectId">);
|
||||||
|
|
||||||
export type TGetCaCertificateTemplatesDTO = {
|
export type TGetCaCertificateTemplatesDTO = {
|
||||||
|
@ -9,9 +9,7 @@ export const sanitizedCertificateTemplate = CertificateTemplatesSchema.pick({
|
|||||||
commonName: true,
|
commonName: true,
|
||||||
subjectAlternativeName: true,
|
subjectAlternativeName: true,
|
||||||
pkiCollectionId: true,
|
pkiCollectionId: true,
|
||||||
ttl: true,
|
ttl: true
|
||||||
keyUsages: true,
|
|
||||||
extendedKeyUsages: true
|
|
||||||
}).merge(
|
}).merge(
|
||||||
z.object({
|
z.object({
|
||||||
projectId: z.string(),
|
projectId: z.string(),
|
||||||
|
@ -57,9 +57,7 @@ export const certificateTemplateServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
keyUsages,
|
|
||||||
extendedKeyUsages
|
|
||||||
}: TCreateCertTemplateDTO) => {
|
}: TCreateCertTemplateDTO) => {
|
||||||
const ca = await certificateAuthorityDAL.findById(caId);
|
const ca = await certificateAuthorityDAL.findById(caId);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
@ -88,9 +86,7 @@ export const certificateTemplateServiceFactory = ({
|
|||||||
name,
|
name,
|
||||||
commonName,
|
commonName,
|
||||||
subjectAlternativeName,
|
subjectAlternativeName,
|
||||||
ttl,
|
ttl
|
||||||
keyUsages,
|
|
||||||
extendedKeyUsages
|
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@ -117,9 +113,7 @@ export const certificateTemplateServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
keyUsages,
|
|
||||||
extendedKeyUsages
|
|
||||||
}: TUpdateCertTemplateDTO) => {
|
}: TUpdateCertTemplateDTO) => {
|
||||||
const certTemplate = await certificateTemplateDAL.getById(id);
|
const certTemplate = await certificateTemplateDAL.getById(id);
|
||||||
if (!certTemplate) {
|
if (!certTemplate) {
|
||||||
@ -159,9 +153,7 @@ export const certificateTemplateServiceFactory = ({
|
|||||||
commonName,
|
commonName,
|
||||||
subjectAlternativeName,
|
subjectAlternativeName,
|
||||||
name,
|
name,
|
||||||
ttl,
|
ttl
|
||||||
keyUsages,
|
|
||||||
extendedKeyUsages
|
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
|
||||||
|
|
||||||
export type TCreateCertTemplateDTO = {
|
export type TCreateCertTemplateDTO = {
|
||||||
caId: string;
|
caId: string;
|
||||||
@ -8,8 +7,6 @@ export type TCreateCertTemplateDTO = {
|
|||||||
commonName: string;
|
commonName: string;
|
||||||
subjectAlternativeName: string;
|
subjectAlternativeName: string;
|
||||||
ttl: string;
|
ttl: string;
|
||||||
keyUsages: CertKeyUsage[];
|
|
||||||
extendedKeyUsages: CertExtendedKeyUsage[];
|
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TUpdateCertTemplateDTO = {
|
export type TUpdateCertTemplateDTO = {
|
||||||
@ -20,8 +17,6 @@ export type TUpdateCertTemplateDTO = {
|
|||||||
commonName?: string;
|
commonName?: string;
|
||||||
subjectAlternativeName?: string;
|
subjectAlternativeName?: string;
|
||||||
ttl?: string;
|
ttl?: string;
|
||||||
keyUsages?: CertKeyUsage[];
|
|
||||||
extendedKeyUsages?: CertExtendedKeyUsage[];
|
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TGetCertTemplateDTO = {
|
export type TGetCertTemplateDTO = {
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import * as x509 from "@peculiar/x509";
|
|
||||||
|
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
export enum CertStatus {
|
export enum CertStatus {
|
||||||
@ -14,36 +12,6 @@ export enum CertKeyAlgorithm {
|
|||||||
ECDSA_P384 = "EC_secp384r1"
|
ECDSA_P384 = "EC_secp384r1"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CertKeyUsage {
|
|
||||||
DIGITAL_SIGNATURE = "digitalSignature",
|
|
||||||
KEY_ENCIPHERMENT = "keyEncipherment",
|
|
||||||
NON_REPUDIATION = "nonRepudiation",
|
|
||||||
DATA_ENCIPHERMENT = "dataEncipherment",
|
|
||||||
KEY_AGREEMENT = "keyAgreement",
|
|
||||||
KEY_CERT_SIGN = "keyCertSign",
|
|
||||||
CRL_SIGN = "cRLSign",
|
|
||||||
ENCIPHER_ONLY = "encipherOnly",
|
|
||||||
DECIPHER_ONLY = "decipherOnly"
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum CertExtendedKeyUsage {
|
|
||||||
CLIENT_AUTH = "clientAuth",
|
|
||||||
SERVER_AUTH = "serverAuth",
|
|
||||||
CODE_SIGNING = "codeSigning",
|
|
||||||
EMAIL_PROTECTION = "emailProtection",
|
|
||||||
TIMESTAMPING = "timeStamping",
|
|
||||||
OCSP_SIGNING = "ocspSigning"
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CertExtendedKeyUsageOIDToName: Record<string, CertExtendedKeyUsage> = {
|
|
||||||
[x509.ExtendedKeyUsage.clientAuth]: CertExtendedKeyUsage.CLIENT_AUTH,
|
|
||||||
[x509.ExtendedKeyUsage.serverAuth]: CertExtendedKeyUsage.SERVER_AUTH,
|
|
||||||
[x509.ExtendedKeyUsage.codeSigning]: CertExtendedKeyUsage.CODE_SIGNING,
|
|
||||||
[x509.ExtendedKeyUsage.emailProtection]: CertExtendedKeyUsage.EMAIL_PROTECTION,
|
|
||||||
[x509.ExtendedKeyUsage.ocspSigning]: CertExtendedKeyUsage.OCSP_SIGNING,
|
|
||||||
[x509.ExtendedKeyUsage.timeStamping]: CertExtendedKeyUsage.TIMESTAMPING
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum CrlReason {
|
export enum CrlReason {
|
||||||
UNSPECIFIED = "UNSPECIFIED",
|
UNSPECIFIED = "UNSPECIFIED",
|
||||||
KEY_COMPROMISE = "KEY_COMPROMISE",
|
KEY_COMPROMISE = "KEY_COMPROMISE",
|
||||||
|
@ -152,7 +152,7 @@ export const groupProjectDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.ProjectRoles}.id`
|
`${TableName.ProjectRoles}.id`
|
||||||
)
|
)
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.UserGroupMembership),
|
db.ref("id").withSchema(TableName.GroupProjectMembership),
|
||||||
db.ref("isGhost").withSchema(TableName.Users),
|
db.ref("isGhost").withSchema(TableName.Users),
|
||||||
db.ref("username").withSchema(TableName.Users),
|
db.ref("username").withSchema(TableName.Users),
|
||||||
db.ref("email").withSchema(TableName.Users),
|
db.ref("email").withSchema(TableName.Users),
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName, TIdentities } from "@app/db/schemas";
|
import { TableName } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
import { ormify, sqlNestRelationships } from "@app/lib/knex";
|
||||||
import { OrderByDirection } from "@app/lib/types";
|
|
||||||
import { ProjectIdentityOrderBy, TListProjectIdentityDTO } from "@app/services/identity-project/identity-project-types";
|
|
||||||
|
|
||||||
export type TIdentityProjectDALFactory = ReturnType<typeof identityProjectDALFactory>;
|
export type TIdentityProjectDALFactory = ReturnType<typeof identityProjectDALFactory>;
|
||||||
|
|
||||||
@ -109,45 +107,12 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const findByProjectId = async (
|
const findByProjectId = async (projectId: string, filter: { identityId?: string } = {}, tx?: Knex) => {
|
||||||
projectId: string,
|
|
||||||
filter: { identityId?: string } & Pick<
|
|
||||||
TListProjectIdentityDTO,
|
|
||||||
"limit" | "offset" | "search" | "orderBy" | "orderDirection"
|
|
||||||
> = {},
|
|
||||||
tx?: Knex
|
|
||||||
) => {
|
|
||||||
try {
|
try {
|
||||||
// TODO: scott - optimize, there's redundancy here with project membership and the below query
|
const docs = await (tx || db.replicaNode())(TableName.IdentityProjectMembership)
|
||||||
const fetchIdentitySubquery = (tx || db.replicaNode())(TableName.Identity)
|
|
||||||
.where((qb) => {
|
|
||||||
if (filter.search) {
|
|
||||||
void qb.whereILike(`${TableName.Identity}.name`, `%${filter.search}%`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.join(
|
|
||||||
TableName.IdentityProjectMembership,
|
|
||||||
`${TableName.IdentityProjectMembership}.identityId`,
|
|
||||||
`${TableName.Identity}.id`
|
|
||||||
)
|
|
||||||
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
|
||||||
.orderBy(
|
|
||||||
`${TableName.Identity}.${filter.orderBy ?? ProjectIdentityOrderBy.Name}`,
|
|
||||||
filter.orderDirection ?? OrderByDirection.ASC
|
|
||||||
)
|
|
||||||
.select(selectAllTableCols(TableName.Identity))
|
|
||||||
.as(TableName.Identity); // required for subqueries
|
|
||||||
|
|
||||||
if (filter.limit) {
|
|
||||||
void fetchIdentitySubquery.offset(filter.offset ?? 0).limit(filter.limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = (tx || db.replicaNode())(TableName.IdentityProjectMembership)
|
|
||||||
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
||||||
.join(TableName.Project, `${TableName.IdentityProjectMembership}.projectId`, `${TableName.Project}.id`)
|
.join(TableName.Project, `${TableName.IdentityProjectMembership}.projectId`, `${TableName.Project}.id`)
|
||||||
.join<TIdentities, TIdentities>(fetchIdentitySubquery, (bd) => {
|
.join(TableName.Identity, `${TableName.IdentityProjectMembership}.identityId`, `${TableName.Identity}.id`)
|
||||||
bd.on(`${TableName.IdentityProjectMembership}.identityId`, `${TableName.Identity}.id`);
|
|
||||||
})
|
|
||||||
.where((qb) => {
|
.where((qb) => {
|
||||||
if (filter.identityId) {
|
if (filter.identityId) {
|
||||||
void qb.where("identityId", filter.identityId);
|
void qb.where("identityId", filter.identityId);
|
||||||
@ -189,19 +154,6 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("name").as("projectName").withSchema(TableName.Project)
|
db.ref("name").as("projectName").withSchema(TableName.Project)
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: scott - joins seem to reorder identities so need to order again, for the sake of urgency will optimize at a later point
|
|
||||||
if (filter.orderBy) {
|
|
||||||
switch (filter.orderBy) {
|
|
||||||
case "name":
|
|
||||||
void query.orderBy(`${TableName.Identity}.${filter.orderBy}`, filter.orderDirection);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const docs = await query;
|
|
||||||
|
|
||||||
const members = sqlNestRelationships({
|
const members = sqlNestRelationships({
|
||||||
data: docs,
|
data: docs,
|
||||||
parentMapper: ({ identityId, identityName, identityAuthMethod, id, createdAt, updatedAt, projectName }) => ({
|
parentMapper: ({ identityId, identityName, identityAuthMethod, id, createdAt, updatedAt, projectName }) => ({
|
||||||
@ -256,37 +208,9 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCountByProjectId = async (
|
|
||||||
projectId: string,
|
|
||||||
filter: { identityId?: string } & Pick<TListProjectIdentityDTO, "search"> = {},
|
|
||||||
tx?: Knex
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const identities = await (tx || db.replicaNode())(TableName.IdentityProjectMembership)
|
|
||||||
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
|
||||||
.join(TableName.Project, `${TableName.IdentityProjectMembership}.projectId`, `${TableName.Project}.id`)
|
|
||||||
.join(TableName.Identity, `${TableName.IdentityProjectMembership}.identityId`, `${TableName.Identity}.id`)
|
|
||||||
.where((qb) => {
|
|
||||||
if (filter.identityId) {
|
|
||||||
void qb.where("identityId", filter.identityId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter.search) {
|
|
||||||
void qb.whereILike(`${TableName.Identity}.name`, `%${filter.search}%`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.count();
|
|
||||||
|
|
||||||
return Number(identities[0].count);
|
|
||||||
} catch (error) {
|
|
||||||
throw new DatabaseError({ error, name: "GetCountByProjectId" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...identityProjectOrm,
|
...identityProjectOrm,
|
||||||
findByIdentityId,
|
findByIdentityId,
|
||||||
findByProjectId,
|
findByProjectId
|
||||||
getCountByProjectId
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -268,12 +268,7 @@ export const identityProjectServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
limit,
|
|
||||||
offset,
|
|
||||||
orderBy,
|
|
||||||
orderDirection,
|
|
||||||
search
|
|
||||||
}: TListProjectIdentityDTO) => {
|
}: TListProjectIdentityDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@ -284,17 +279,8 @@ export const identityProjectServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
|
||||||
|
|
||||||
const identityMemberships = await identityProjectDAL.findByProjectId(projectId, {
|
const identityMemberships = await identityProjectDAL.findByProjectId(projectId);
|
||||||
limit,
|
return identityMemberships;
|
||||||
offset,
|
|
||||||
orderBy,
|
|
||||||
orderDirection,
|
|
||||||
search
|
|
||||||
});
|
|
||||||
|
|
||||||
const totalCount = await identityProjectDAL.getCountByProjectId(projectId, { search });
|
|
||||||
|
|
||||||
return { identityMemberships, totalCount };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProjectIdentityByIdentityId = async ({
|
const getProjectIdentityByIdentityId = async ({
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { OrderByDirection, TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
import { ProjectUserMembershipTemporaryMode } from "../project-membership/project-membership-types";
|
import { ProjectUserMembershipTemporaryMode } from "../project-membership/project-membership-types";
|
||||||
|
|
||||||
@ -40,18 +40,8 @@ export type TDeleteProjectIdentityDTO = {
|
|||||||
identityId: string;
|
identityId: string;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TListProjectIdentityDTO = {
|
export type TListProjectIdentityDTO = TProjectPermission;
|
||||||
limit?: number;
|
|
||||||
offset?: number;
|
|
||||||
orderBy?: ProjectIdentityOrderBy;
|
|
||||||
orderDirection?: OrderByDirection;
|
|
||||||
search?: string;
|
|
||||||
} & TProjectPermission;
|
|
||||||
|
|
||||||
export type TGetProjectIdentityByIdentityIdDTO = {
|
export type TGetProjectIdentityByIdentityIdDTO = {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export enum ProjectIdentityOrderBy {
|
|
||||||
Name = "name"
|
|
||||||
}
|
|
||||||
|
@ -4,8 +4,6 @@ import { TDbClient } from "@app/db";
|
|||||||
import { TableName, TIdentityOrgMemberships } from "@app/db/schemas";
|
import { TableName, TIdentityOrgMemberships } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
import { OrderByDirection } from "@app/lib/types";
|
|
||||||
import { TListOrgIdentitiesByOrgIdDTO } from "@app/services/identity/identity-types";
|
|
||||||
|
|
||||||
export type TIdentityOrgDALFactory = ReturnType<typeof identityOrgDALFactory>;
|
export type TIdentityOrgDALFactory = ReturnType<typeof identityOrgDALFactory>;
|
||||||
|
|
||||||
@ -29,20 +27,9 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const find = async (
|
const find = async (filter: Partial<TIdentityOrgMemberships>, tx?: Knex) => {
|
||||||
{
|
|
||||||
limit,
|
|
||||||
offset = 0,
|
|
||||||
orderBy,
|
|
||||||
orderDirection = OrderByDirection.ASC,
|
|
||||||
search,
|
|
||||||
...filter
|
|
||||||
}: Partial<TIdentityOrgMemberships> &
|
|
||||||
Pick<TListOrgIdentitiesByOrgIdDTO, "offset" | "limit" | "orderBy" | "orderDirection" | "search">,
|
|
||||||
tx?: Knex
|
|
||||||
) => {
|
|
||||||
try {
|
try {
|
||||||
const query = (tx || db.replicaNode())(TableName.IdentityOrgMembership)
|
const docs = await (tx || db.replicaNode())(TableName.IdentityOrgMembership)
|
||||||
.where(filter)
|
.where(filter)
|
||||||
.join(TableName.Identity, `${TableName.IdentityOrgMembership}.identityId`, `${TableName.Identity}.id`)
|
.join(TableName.Identity, `${TableName.IdentityOrgMembership}.identityId`, `${TableName.Identity}.id`)
|
||||||
.leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`)
|
.leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`)
|
||||||
@ -57,30 +44,6 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
.select(db.ref("id").as("identityId").withSchema(TableName.Identity))
|
.select(db.ref("id").as("identityId").withSchema(TableName.Identity))
|
||||||
.select(db.ref("name").as("identityName").withSchema(TableName.Identity))
|
.select(db.ref("name").as("identityName").withSchema(TableName.Identity))
|
||||||
.select(db.ref("authMethod").as("identityAuthMethod").withSchema(TableName.Identity));
|
.select(db.ref("authMethod").as("identityAuthMethod").withSchema(TableName.Identity));
|
||||||
|
|
||||||
if (limit) {
|
|
||||||
void query.offset(offset).limit(limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (orderBy) {
|
|
||||||
switch (orderBy) {
|
|
||||||
case "name":
|
|
||||||
void query.orderBy(`${TableName.Identity}.${orderBy}`, orderDirection);
|
|
||||||
break;
|
|
||||||
case "role":
|
|
||||||
void query.orderBy(`${TableName.IdentityOrgMembership}.${orderBy}`, orderDirection);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (search?.length) {
|
|
||||||
void query.whereILike(`${TableName.Identity}.name`, `%${search}%`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const docs = await query;
|
|
||||||
|
|
||||||
return docs.map(
|
return docs.map(
|
||||||
({
|
({
|
||||||
crId,
|
crId,
|
||||||
@ -116,27 +79,5 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const countAllOrgIdentities = async (
|
return { ...identityOrgOrm, find, findOne };
|
||||||
{ search, ...filter }: Partial<TIdentityOrgMemberships> & Pick<TListOrgIdentitiesByOrgIdDTO, "search">,
|
|
||||||
tx?: Knex
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const query = (tx || db.replicaNode())(TableName.IdentityOrgMembership)
|
|
||||||
.where(filter)
|
|
||||||
.join(TableName.Identity, `${TableName.IdentityOrgMembership}.identityId`, `${TableName.Identity}.id`)
|
|
||||||
.count();
|
|
||||||
|
|
||||||
if (search?.length) {
|
|
||||||
void query.whereILike(`${TableName.Identity}.name`, `%${search}%`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const identities = await query;
|
|
||||||
|
|
||||||
return Number(identities[0].count);
|
|
||||||
} catch (error) {
|
|
||||||
throw new DatabaseError({ error, name: "countAllOrgIdentities" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return { ...identityOrgOrm, find, findOne, countAllOrgIdentities };
|
|
||||||
};
|
};
|
||||||
|
@ -6,6 +6,7 @@ import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/pe
|
|||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { TOrgPermission } from "@app/lib/types";
|
||||||
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
||||||
|
|
||||||
import { ActorType } from "../auth/auth-type";
|
import { ActorType } from "../auth/auth-type";
|
||||||
@ -15,7 +16,6 @@ import {
|
|||||||
TCreateIdentityDTO,
|
TCreateIdentityDTO,
|
||||||
TDeleteIdentityDTO,
|
TDeleteIdentityDTO,
|
||||||
TGetIdentityByIdDTO,
|
TGetIdentityByIdDTO,
|
||||||
TListOrgIdentitiesByOrgIdDTO,
|
|
||||||
TListProjectIdentitiesByIdentityIdDTO,
|
TListProjectIdentitiesByIdentityIdDTO,
|
||||||
TUpdateIdentityDTO
|
TUpdateIdentityDTO
|
||||||
} from "./identity-types";
|
} from "./identity-types";
|
||||||
@ -58,8 +58,7 @@ export const identityServiceFactory = ({
|
|||||||
if (!hasRequiredPriviledges) throw new BadRequestError({ message: "Failed to create a more privileged identity" });
|
if (!hasRequiredPriviledges) throw new BadRequestError({ message: "Failed to create a more privileged identity" });
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(orgId);
|
const plan = await licenseService.getPlan(orgId);
|
||||||
|
if (plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
||||||
if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
|
||||||
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Failed to create identity due to identity limit reached. Upgrade plan to create more identities."
|
message: "Failed to create identity due to identity limit reached. Upgrade plan to create more identities."
|
||||||
@ -196,36 +195,14 @@ export const identityServiceFactory = ({
|
|||||||
return { ...deletedIdentity, orgId: identityOrgMembership.orgId };
|
return { ...deletedIdentity, orgId: identityOrgMembership.orgId };
|
||||||
};
|
};
|
||||||
|
|
||||||
const listOrgIdentities = async ({
|
const listOrgIdentities = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgPermission) => {
|
||||||
orgId,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
limit,
|
|
||||||
offset,
|
|
||||||
orderBy,
|
|
||||||
orderDirection,
|
|
||||||
search
|
|
||||||
}: TListOrgIdentitiesByOrgIdDTO) => {
|
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
const identityMemberships = await identityOrgMembershipDAL.find({
|
const identityMemberships = await identityOrgMembershipDAL.find({
|
||||||
[`${TableName.IdentityOrgMembership}.orgId` as "orgId"]: orgId,
|
[`${TableName.IdentityOrgMembership}.orgId` as "orgId"]: orgId
|
||||||
limit,
|
|
||||||
offset,
|
|
||||||
orderBy,
|
|
||||||
orderDirection,
|
|
||||||
search
|
|
||||||
});
|
});
|
||||||
|
return identityMemberships;
|
||||||
const totalCount = await identityOrgMembershipDAL.countAllOrgIdentities({
|
|
||||||
[`${TableName.IdentityOrgMembership}.orgId` as "orgId"]: orgId,
|
|
||||||
search
|
|
||||||
});
|
|
||||||
|
|
||||||
return { identityMemberships, totalCount };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const listProjectIdentitiesByIdentityId = async ({
|
const listProjectIdentitiesByIdentityId = async ({
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { IPType } from "@app/lib/ip";
|
import { IPType } from "@app/lib/ip";
|
||||||
import { OrderByDirection, TOrgPermission } from "@app/lib/types";
|
import { TOrgPermission } from "@app/lib/types";
|
||||||
|
|
||||||
export type TCreateIdentityDTO = {
|
export type TCreateIdentityDTO = {
|
||||||
role: string;
|
role: string;
|
||||||
@ -29,16 +29,3 @@ export interface TIdentityTrustedIp {
|
|||||||
export type TListProjectIdentitiesByIdentityIdDTO = {
|
export type TListProjectIdentitiesByIdentityIdDTO = {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
} & Omit<TOrgPermission, "orgId">;
|
} & Omit<TOrgPermission, "orgId">;
|
||||||
|
|
||||||
export type TListOrgIdentitiesByOrgIdDTO = {
|
|
||||||
limit?: number;
|
|
||||||
offset?: number;
|
|
||||||
orderBy?: OrgIdentityOrderBy;
|
|
||||||
orderDirection?: OrderByDirection;
|
|
||||||
search?: string;
|
|
||||||
} & TOrgPermission;
|
|
||||||
|
|
||||||
export enum OrgIdentityOrderBy {
|
|
||||||
Name = "name",
|
|
||||||
Role = "role"
|
|
||||||
}
|
|
||||||
|
@ -242,12 +242,37 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
const octokit = new Octokit({
|
||||||
const repos = (await new Octokit({
|
|
||||||
auth: accessToken
|
auth: accessToken
|
||||||
}).paginate("GET /user/repos{?visibility,affiliation,type,sort,direction,per_page,page,since,before}", {
|
});
|
||||||
per_page: 100
|
|
||||||
})) as GitHubApp[];
|
const getAllRepos = async () => {
|
||||||
|
let repos: GitHubApp[] = [];
|
||||||
|
let page = 1;
|
||||||
|
const perPage = 100;
|
||||||
|
let hasMore = true;
|
||||||
|
|
||||||
|
while (hasMore) {
|
||||||
|
const response = await octokit.request(
|
||||||
|
"GET /user/repos{?visibility,affiliation,type,sort,direction,per_page,page,since,before}",
|
||||||
|
{
|
||||||
|
per_page: perPage,
|
||||||
|
page
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if ((response.data as GitHubApp[]).length > 0) {
|
||||||
|
repos = repos.concat(response.data as GitHubApp[]);
|
||||||
|
page += 1;
|
||||||
|
} else {
|
||||||
|
hasMore = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return repos;
|
||||||
|
};
|
||||||
|
|
||||||
|
const repos = await getAllRepos();
|
||||||
|
|
||||||
const apps = repos
|
const apps = repos
|
||||||
.filter((a: GitHubApp) => a.permissions.admin === true)
|
.filter((a: GitHubApp) => a.permissions.admin === true)
|
||||||
|
@ -207,12 +207,6 @@ const syncSecretsGCPSecretManager = async ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!secrets[key].value) {
|
|
||||||
logger.warn(
|
|
||||||
`syncSecretsGcpsecretManager: create secret value in gcp where [key=${key}] and integration appId [appId=${integration.appId}]`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await request.post(
|
await request.post(
|
||||||
`${IntegrationUrls.GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets/${key}:addVersion`,
|
`${IntegrationUrls.GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets/${key}:addVersion`,
|
||||||
{
|
{
|
||||||
@ -243,12 +237,6 @@ const syncSecretsGCPSecretManager = async ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else if (secrets[key].value !== res[key]) {
|
} else if (secrets[key].value !== res[key]) {
|
||||||
if (!secrets[key].value) {
|
|
||||||
logger.warn(
|
|
||||||
`syncSecretsGcpsecretManager: update secret value in gcp where [key=${key}] and integration appId [appId=${integration.appId}]`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await request.post(
|
await request.post(
|
||||||
`${IntegrationUrls.GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets/${key}:addVersion`,
|
`${IntegrationUrls.GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets/${key}:addVersion`,
|
||||||
{
|
{
|
||||||
|
@ -2,7 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability";
|
|||||||
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
import { TIntegrationAuthDALFactory } from "../integration-auth/integration-auth-dal";
|
import { TIntegrationAuthDALFactory } from "../integration-auth/integration-auth-dal";
|
||||||
@ -19,6 +19,7 @@ import { TIntegrationDALFactory } from "./integration-dal";
|
|||||||
import {
|
import {
|
||||||
TCreateIntegrationDTO,
|
TCreateIntegrationDTO,
|
||||||
TDeleteIntegrationDTO,
|
TDeleteIntegrationDTO,
|
||||||
|
TGetIntegrationDTO,
|
||||||
TSyncIntegrationDTO,
|
TSyncIntegrationDTO,
|
||||||
TUpdateIntegrationDTO
|
TUpdateIntegrationDTO
|
||||||
} from "./integration-types";
|
} from "./integration-types";
|
||||||
@ -180,6 +181,27 @@ export const integrationServiceFactory = ({
|
|||||||
return updatedIntegration;
|
return updatedIntegration;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getIntegration = async ({ id, actor, actorAuthMethod, actorId, actorOrgId }: TGetIntegrationDTO) => {
|
||||||
|
const integration = await integrationDAL.findById(id);
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
integration?.projectId || "",
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||||
|
|
||||||
|
if (!integration) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Integration not found"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...integration, envId: integration.environment.id };
|
||||||
|
};
|
||||||
|
|
||||||
const deleteIntegration = async ({
|
const deleteIntegration = async ({
|
||||||
actorId,
|
actorId,
|
||||||
id,
|
id,
|
||||||
@ -276,6 +298,8 @@ export const integrationServiceFactory = ({
|
|||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||||
|
|
||||||
await secretQueueService.syncIntegrations({
|
await secretQueueService.syncIntegrations({
|
||||||
|
isManual: true,
|
||||||
|
actorId,
|
||||||
environment: integration.environment.slug,
|
environment: integration.environment.slug,
|
||||||
secretPath: integration.secretPath,
|
secretPath: integration.secretPath,
|
||||||
projectId: integration.projectId
|
projectId: integration.projectId
|
||||||
@ -289,6 +313,7 @@ export const integrationServiceFactory = ({
|
|||||||
updateIntegration,
|
updateIntegration,
|
||||||
deleteIntegration,
|
deleteIntegration,
|
||||||
listIntegrationByProject,
|
listIntegrationByProject,
|
||||||
|
getIntegration,
|
||||||
syncIntegration
|
syncIntegration
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -39,6 +39,10 @@ export type TCreateIntegrationDTO = {
|
|||||||
};
|
};
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TGetIntegrationDTO = {
|
||||||
|
id: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TUpdateIntegrationDTO = {
|
export type TUpdateIntegrationDTO = {
|
||||||
id: string;
|
id: string;
|
||||||
app?: string;
|
app?: string;
|
||||||
|
@ -17,6 +17,7 @@ import {
|
|||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { TProjects } from "@app/db/schemas/projects";
|
import { TProjects } from "@app/db/schemas/projects";
|
||||||
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
|
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
@ -89,6 +90,7 @@ type TOrgServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
||||||
projectRoleDAL: Pick<TProjectRoleDALFactory, "find">;
|
projectRoleDAL: Pick<TProjectRoleDALFactory, "find">;
|
||||||
|
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "findUserGroupMembershipsInProject">;
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany">;
|
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany">;
|
||||||
};
|
};
|
||||||
@ -114,6 +116,7 @@ export const orgServiceFactory = ({
|
|||||||
licenseService,
|
licenseService,
|
||||||
projectRoleDAL,
|
projectRoleDAL,
|
||||||
samlConfigDAL,
|
samlConfigDAL,
|
||||||
|
userGroupMembershipDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
projectUserMembershipRoleDAL
|
projectUserMembershipRoleDAL
|
||||||
}: TOrgServiceFactoryDep) => {
|
}: TOrgServiceFactoryDep) => {
|
||||||
@ -455,6 +458,7 @@ export const orgServiceFactory = ({
|
|||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
||||||
|
|
||||||
const org = await orgDAL.findOrgById(orgId);
|
const org = await orgDAL.findOrgById(orgId);
|
||||||
|
|
||||||
@ -472,20 +476,19 @@ export const orgServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
const plan = await licenseService.getPlan(orgId);
|
const plan = await licenseService.getPlan(orgId);
|
||||||
if (plan?.slug !== "enterprise" && plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
|
if (plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
|
||||||
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
|
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Failed to invite member due to member limit reached. Upgrade plan to invite more members."
|
message: "Failed to invite member due to member limit reached. Upgrade plan to invite more members."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
if (plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
||||||
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Failed to invite member due to member limit reached. Upgrade plan to invite more members."
|
message: "Failed to invite member due to member limit reached. Upgrade plan to invite more members."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCustomOrgRole = !Object.values(OrgMembershipRole).includes(organizationRoleSlug as OrgMembershipRole);
|
const isCustomOrgRole = !Object.values(OrgMembershipRole).includes(organizationRoleSlug as OrgMembershipRole);
|
||||||
if (isCustomOrgRole) {
|
if (isCustomOrgRole) {
|
||||||
if (!plan?.rbac)
|
if (!plan?.rbac)
|
||||||
@ -580,8 +583,6 @@ export const orgServiceFactory = ({
|
|||||||
|
|
||||||
// if there exist no org membership we set is as given by the request
|
// if there exist no org membership we set is as given by the request
|
||||||
if (!inviteeMembership) {
|
if (!inviteeMembership) {
|
||||||
// as its used by project invite also
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
|
||||||
let roleId;
|
let roleId;
|
||||||
const orgRole = isCustomOrgRole ? OrgMembershipRole.Custom : organizationRoleSlug;
|
const orgRole = isCustomOrgRole ? OrgMembershipRole.Custom : organizationRoleSlug;
|
||||||
if (isCustomOrgRole) {
|
if (isCustomOrgRole) {
|
||||||
@ -615,6 +616,7 @@ export const orgServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const userIds = users.map(({ id }) => id);
|
const userIds = users.map(({ id }) => id);
|
||||||
|
const usernames = users.map((el) => el.username);
|
||||||
const userEncryptionKeys = await userDAL.findUserEncKeyByUserIdsBatch({ userIds }, tx);
|
const userEncryptionKeys = await userDAL.findUserEncKeyByUserIdsBatch({ userIds }, tx);
|
||||||
// we don't need to spam with email. Thus org invitation doesn't need project invitation again
|
// we don't need to spam with email. Thus org invitation doesn't need project invitation again
|
||||||
const userIdsWithOrgInvitation = new Set(mailsForOrgInvitation.map((el) => el.userId));
|
const userIdsWithOrgInvitation = new Set(mailsForOrgInvitation.map((el) => el.userId));
|
||||||
@ -641,10 +643,12 @@ export const orgServiceFactory = ({
|
|||||||
{ tx }
|
{ tx }
|
||||||
);
|
);
|
||||||
const existingMembersGroupByUserId = groupBy(existingMembers, (i) => i.userId);
|
const existingMembersGroupByUserId = groupBy(existingMembers, (i) => i.userId);
|
||||||
const userWithEncryptionKeyInvitedToProject = userEncryptionKeys.filter(
|
const userIdsToExcludeAsPartOfGroup = new Set(
|
||||||
(user) => !existingMembersGroupByUserId?.[user.userId]
|
await userGroupMembershipDAL.findUserGroupMembershipsInProject(usernames, projectId, tx)
|
||||||
|
);
|
||||||
|
const userWithEncryptionKeyInvitedToProject = userEncryptionKeys.filter(
|
||||||
|
(user) => !existingMembersGroupByUserId?.[user.userId] && !userIdsToExcludeAsPartOfGroup.has(user.userId)
|
||||||
);
|
);
|
||||||
|
|
||||||
// eslint-disable-next-line no-continue
|
// eslint-disable-next-line no-continue
|
||||||
if (!userWithEncryptionKeyInvitedToProject.length) continue;
|
if (!userWithEncryptionKeyInvitedToProject.length) continue;
|
||||||
|
|
||||||
|
@ -26,10 +26,7 @@ export const getBotKeyFnFactory = (
|
|||||||
) => {
|
) => {
|
||||||
const getBotKeyFn = async (projectId: string) => {
|
const getBotKeyFn = async (projectId: string) => {
|
||||||
const project = await projectDAL.findById(projectId);
|
const project = await projectDAL.findById(projectId);
|
||||||
if (!project)
|
if (!project) throw new BadRequestError({ message: "Project not found during bot lookup." });
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Project not found during bot lookup. Are you sure you are using the correct project ID?"
|
|
||||||
});
|
|
||||||
|
|
||||||
if (project.version === 3) {
|
if (project.version === 3) {
|
||||||
return { project, shouldUseSecretV2Bridge: true };
|
return { project, shouldUseSecretV2Bridge: true };
|
||||||
|
@ -90,20 +90,15 @@ export const projectMembershipServiceFactory = ({
|
|||||||
// projectMembers[0].project
|
// projectMembers[0].project
|
||||||
if (includeGroupMembers) {
|
if (includeGroupMembers) {
|
||||||
const groupMembers = await groupProjectDAL.findAllProjectGroupMembers(projectId);
|
const groupMembers = await groupProjectDAL.findAllProjectGroupMembers(projectId);
|
||||||
|
|
||||||
const allMembers = [
|
const allMembers = [
|
||||||
...projectMembers.map((m) => ({ ...m, isGroupMember: false })),
|
...projectMembers.map((m) => ({ ...m, isGroupMember: false })),
|
||||||
...groupMembers.map((m) => ({ ...m, isGroupMember: true }))
|
...groupMembers.map((m) => ({ ...m, isGroupMember: true }))
|
||||||
];
|
];
|
||||||
|
|
||||||
// Ensure the userId is unique
|
// Ensure the userId is unique
|
||||||
const uniqueMembers: typeof allMembers = [];
|
const membersIds = new Set(allMembers.map((entity) => entity.user.id));
|
||||||
const addedUserIds = new Set<string>();
|
const uniqueMembers = allMembers.filter((entity) => membersIds.has(entity.user.id));
|
||||||
allMembers.forEach((member) => {
|
|
||||||
if (!addedUserIds.has(member.user.id)) {
|
|
||||||
uniqueMembers.push(member);
|
|
||||||
addedUserIds.add(member.user.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return uniqueMembers;
|
return uniqueMembers;
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
|
|||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionSet,
|
ProjectPermissionSet,
|
||||||
ProjectPermissionSub,
|
ProjectPermissionSub
|
||||||
validateProjectPermissions
|
|
||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
@ -57,9 +56,6 @@ export const projectRoleServiceFactory = ({
|
|||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Role);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Role);
|
||||||
const existingRole = await projectRoleDAL.findOne({ slug: data.slug, projectId });
|
const existingRole = await projectRoleDAL.findOne({ slug: data.slug, projectId });
|
||||||
if (existingRole) throw new BadRequestError({ name: "Create Role", message: "Duplicate role" });
|
if (existingRole) throw new BadRequestError({ name: "Create Role", message: "Duplicate role" });
|
||||||
|
|
||||||
validateProjectPermissions(data.permissions);
|
|
||||||
|
|
||||||
const role = await projectRoleDAL.create({
|
const role = await projectRoleDAL.create({
|
||||||
...data,
|
...data,
|
||||||
projectId
|
projectId
|
||||||
@ -124,11 +120,6 @@ export const projectRoleServiceFactory = ({
|
|||||||
if (existingRole && existingRole.id !== roleId)
|
if (existingRole && existingRole.id !== roleId)
|
||||||
throw new BadRequestError({ name: "Update Role", message: "Duplicate role" });
|
throw new BadRequestError({ name: "Update Role", message: "Duplicate role" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.permissions) {
|
|
||||||
validateProjectPermissions(data.permissions);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [updatedRole] = await projectRoleDAL.update(
|
const [updatedRole] = await projectRoleDAL.update(
|
||||||
{ id: roleId, projectId },
|
{ id: roleId, projectId },
|
||||||
{
|
{
|
||||||
|
@ -512,11 +512,7 @@ export const secretImportServiceFactory = ({
|
|||||||
return importedSecrets;
|
return importedSecrets;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!botKey)
|
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Project bot not found. Please upgrade your project.",
|
|
||||||
name: "bot_not_found_error"
|
|
||||||
});
|
|
||||||
|
|
||||||
const importedSecrets = await fnSecretsFromImports({ allowedImports, folderDAL, secretDAL, secretImportDAL });
|
const importedSecrets = await fnSecretsFromImports({ allowedImports, folderDAL, secretDAL, secretImportDAL });
|
||||||
return importedSecrets.map((el) => ({
|
return importedSecrets.map((el) => ({
|
||||||
|
@ -832,11 +832,7 @@ export const createManySecretsRawFnFactory = ({
|
|||||||
secretDAL
|
secretDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!botKey)
|
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Project bot not found. Please upgrade your project.",
|
|
||||||
name: "bot_not_found_error"
|
|
||||||
});
|
|
||||||
const inputSecrets = secrets.map((secret) => {
|
const inputSecrets = secrets.map((secret) => {
|
||||||
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretName, botKey);
|
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretName, botKey);
|
||||||
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretValue || "", botKey);
|
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretValue || "", botKey);
|
||||||
@ -997,11 +993,7 @@ export const updateManySecretsRawFnFactory = ({
|
|||||||
return updatedSecrets;
|
return updatedSecrets;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!botKey)
|
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Project bot not found. Please upgrade your project.",
|
|
||||||
name: "bot_not_found_error"
|
|
||||||
});
|
|
||||||
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
|
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
|
||||||
if (!blindIndexCfg) throw new BadRequestError({ message: "Blind index not found", name: "Update secret" });
|
if (!blindIndexCfg) throw new BadRequestError({ message: "Blind index not found", name: "Update secret" });
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
|
|
||||||
import { ProjectUpgradeStatus, ProjectVersion, TSecretSnapshotSecretsV2, TSecretVersionsV2 } from "@app/db/schemas";
|
import { ProjectUpgradeStatus, ProjectVersion, TSecretSnapshotSecretsV2, TSecretVersionsV2 } from "@app/db/schemas";
|
||||||
|
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
||||||
|
import { Actor, EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
|
import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
|
||||||
import { TSecretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal";
|
import { TSecretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal";
|
||||||
import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal";
|
import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal";
|
||||||
@ -21,6 +23,7 @@ import { TSecretVersionTagDALFactory } from "@app/services/secret/secret-version
|
|||||||
import { TSecretBlindIndexDALFactory } from "@app/services/secret-blind-index/secret-blind-index-dal";
|
import { TSecretBlindIndexDALFactory } from "@app/services/secret-blind-index/secret-blind-index-dal";
|
||||||
import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
|
import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
|
||||||
|
|
||||||
|
import { ActorType } from "../auth/auth-type";
|
||||||
import { TIntegrationDALFactory } from "../integration/integration-dal";
|
import { TIntegrationDALFactory } from "../integration/integration-dal";
|
||||||
import { TIntegrationAuthDALFactory } from "../integration-auth/integration-auth-dal";
|
import { TIntegrationAuthDALFactory } from "../integration-auth/integration-auth-dal";
|
||||||
import { TIntegrationAuthServiceFactory } from "../integration-auth/integration-auth-service";
|
import { TIntegrationAuthServiceFactory } from "../integration-auth/integration-auth-service";
|
||||||
@ -40,6 +43,7 @@ import { expandSecretReferencesFactory, getAllNestedSecretReferences } from "../
|
|||||||
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
|
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
|
||||||
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
|
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
|
||||||
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||||
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
import { TWebhookDALFactory } from "../webhook/webhook-dal";
|
import { TWebhookDALFactory } from "../webhook/webhook-dal";
|
||||||
import { fnTriggerWebhook } from "../webhook/webhook-fns";
|
import { fnTriggerWebhook } from "../webhook/webhook-fns";
|
||||||
import { TSecretDALFactory } from "./secret-dal";
|
import { TSecretDALFactory } from "./secret-dal";
|
||||||
@ -71,6 +75,7 @@ type TSecretQueueFactoryDep = {
|
|||||||
secretVersionDAL: TSecretVersionDALFactory;
|
secretVersionDAL: TSecretVersionDALFactory;
|
||||||
secretBlindIndexDAL: TSecretBlindIndexDALFactory;
|
secretBlindIndexDAL: TSecretBlindIndexDALFactory;
|
||||||
secretTagDAL: TSecretTagDALFactory;
|
secretTagDAL: TSecretTagDALFactory;
|
||||||
|
userDAL: Pick<TUserDALFactory, "findById">;
|
||||||
secretVersionTagDAL: TSecretVersionTagDALFactory;
|
secretVersionTagDAL: TSecretVersionTagDALFactory;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
secretV2BridgeDAL: TSecretV2BridgeDALFactory;
|
secretV2BridgeDAL: TSecretV2BridgeDALFactory;
|
||||||
@ -81,6 +86,7 @@ type TSecretQueueFactoryDep = {
|
|||||||
snapshotDAL: Pick<TSnapshotDALFactory, "findNSecretV1SnapshotByFolderId" | "deleteSnapshotsAboveLimit">;
|
snapshotDAL: Pick<TSnapshotDALFactory, "findNSecretV1SnapshotByFolderId" | "deleteSnapshotsAboveLimit">;
|
||||||
snapshotSecretV2BridgeDAL: Pick<TSnapshotSecretV2DALFactory, "insertMany" | "batchInsert">;
|
snapshotSecretV2BridgeDAL: Pick<TSnapshotSecretV2DALFactory, "insertMany" | "batchInsert">;
|
||||||
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "setItemWithExpiry" | "getItem">;
|
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "setItemWithExpiry" | "getItem">;
|
||||||
|
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TGetSecrets = {
|
export type TGetSecrets = {
|
||||||
@ -106,6 +112,7 @@ export const secretQueueFactory = ({
|
|||||||
secretDAL,
|
secretDAL,
|
||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
|
userDAL,
|
||||||
webhookDAL,
|
webhookDAL,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
@ -125,7 +132,8 @@ export const secretQueueFactory = ({
|
|||||||
snapshotDAL,
|
snapshotDAL,
|
||||||
snapshotSecretV2BridgeDAL,
|
snapshotSecretV2BridgeDAL,
|
||||||
secretApprovalRequestDAL,
|
secretApprovalRequestDAL,
|
||||||
keyStore
|
keyStore,
|
||||||
|
auditLogService
|
||||||
}: TSecretQueueFactoryDep) => {
|
}: TSecretQueueFactoryDep) => {
|
||||||
const removeSecretReminder = async (dto: TRemoveSecretReminderDTO) => {
|
const removeSecretReminder = async (dto: TRemoveSecretReminderDTO) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
@ -430,7 +438,9 @@ export const secretQueueFactory = ({
|
|||||||
return content;
|
return content;
|
||||||
};
|
};
|
||||||
|
|
||||||
const syncIntegrations = async (dto: TGetSecrets & { deDupeQueue?: Record<string, boolean> }) => {
|
const syncIntegrations = async (
|
||||||
|
dto: TGetSecrets & { isManual?: boolean; actorId?: string; deDupeQueue?: Record<string, boolean> }
|
||||||
|
) => {
|
||||||
await queueService.queue(QueueName.IntegrationSync, QueueJobs.IntegrationSync, dto, {
|
await queueService.queue(QueueName.IntegrationSync, QueueJobs.IntegrationSync, dto, {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
delay: 1000,
|
delay: 1000,
|
||||||
@ -528,7 +538,7 @@ export const secretQueueFactory = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
await syncIntegrations({ secretPath, projectId, environment, deDupeQueue });
|
await syncIntegrations({ secretPath, projectId, environment, deDupeQueue, isManual: false });
|
||||||
if (!excludeReplication) {
|
if (!excludeReplication) {
|
||||||
await replicateSecrets({
|
await replicateSecrets({
|
||||||
_deDupeReplicationQueue: deDupeReplicationQueue,
|
_deDupeReplicationQueue: deDupeReplicationQueue,
|
||||||
@ -544,7 +554,7 @@ export const secretQueueFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
queueService.start(QueueName.IntegrationSync, async (job) => {
|
queueService.start(QueueName.IntegrationSync, async (job) => {
|
||||||
const { environment, projectId, secretPath, depth = 1, deDupeQueue = {} } = job.data;
|
const { environment, actorId, isManual, projectId, secretPath, depth = 1, deDupeQueue = {} } = job.data;
|
||||||
if (depth > MAX_SYNC_SECRET_DEPTH) return;
|
if (depth > MAX_SYNC_SECRET_DEPTH) return;
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||||
@ -693,6 +703,30 @@ export const secretQueueFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const generateActor = async (): Promise<Actor> => {
|
||||||
|
if (isManual && actorId) {
|
||||||
|
const user = await userDAL.findById(actorId);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new Error("User not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: ActorType.USER,
|
||||||
|
metadata: {
|
||||||
|
email: user.email,
|
||||||
|
username: user.username,
|
||||||
|
userId: user.id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: ActorType.PLATFORM,
|
||||||
|
metadata: {}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// akhilmhdh: this try catch is for lock release
|
// akhilmhdh: this try catch is for lock release
|
||||||
try {
|
try {
|
||||||
const secrets = shouldUseSecretV2Bridge
|
const secrets = shouldUseSecretV2Bridge
|
||||||
@ -778,6 +812,21 @@ export const secretQueueFactory = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await auditLogService.createAuditLog({
|
||||||
|
projectId,
|
||||||
|
actor: await generateActor(),
|
||||||
|
event: {
|
||||||
|
type: EventType.INTEGRATION_SYNCED,
|
||||||
|
metadata: {
|
||||||
|
integrationId: integration.id,
|
||||||
|
isSynced: response?.isSynced ?? true,
|
||||||
|
lastSyncJobId: job?.id ?? "",
|
||||||
|
lastUsed: new Date(),
|
||||||
|
syncMessage: response?.syncMessage ?? ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
await integrationDAL.updateById(integration.id, {
|
await integrationDAL.updateById(integration.id, {
|
||||||
lastSyncJobId: job.id,
|
lastSyncJobId: job.id,
|
||||||
lastUsed: new Date(),
|
lastUsed: new Date(),
|
||||||
@ -794,9 +843,23 @@ export const secretQueueFactory = ({
|
|||||||
(err instanceof AxiosError ? JSON.stringify(err?.response?.data) : (err as Error)?.message) ||
|
(err instanceof AxiosError ? JSON.stringify(err?.response?.data) : (err as Error)?.message) ||
|
||||||
"Unknown error occurred.";
|
"Unknown error occurred.";
|
||||||
|
|
||||||
|
await auditLogService.createAuditLog({
|
||||||
|
projectId,
|
||||||
|
actor: await generateActor(),
|
||||||
|
event: {
|
||||||
|
type: EventType.INTEGRATION_SYNCED,
|
||||||
|
metadata: {
|
||||||
|
integrationId: integration.id,
|
||||||
|
isSynced: false,
|
||||||
|
lastSyncJobId: job?.id ?? "",
|
||||||
|
lastUsed: new Date(),
|
||||||
|
syncMessage: message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
await integrationDAL.updateById(integration.id, {
|
await integrationDAL.updateById(integration.id, {
|
||||||
lastSyncJobId: job.id,
|
lastSyncJobId: job.id,
|
||||||
lastUsed: new Date(),
|
|
||||||
syncMessage: message,
|
syncMessage: message,
|
||||||
isSynced: false
|
isSynced: false
|
||||||
});
|
});
|
||||||
|
@ -985,11 +985,7 @@ export const secretServiceFactory = ({
|
|||||||
return { secrets, imports };
|
return { secrets, imports };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!botKey)
|
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Project bot not found. Please upgrade your project.",
|
|
||||||
name: "bot_not_found_error"
|
|
||||||
});
|
|
||||||
|
|
||||||
const { secrets, imports } = await getSecrets({
|
const { secrets, imports } = await getSecrets({
|
||||||
actorId,
|
actorId,
|
||||||
@ -1150,10 +1146,7 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!botKey)
|
if (!botKey)
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({ message: "Please upgrade your project first", name: "bot_not_found_error" });
|
||||||
message: "Project bot not found. Please upgrade your project.",
|
|
||||||
name: "bot_not_found_error"
|
|
||||||
});
|
|
||||||
const decryptedSecret = decryptSecretRaw(encryptedSecret, botKey);
|
const decryptedSecret = decryptSecretRaw(encryptedSecret, botKey);
|
||||||
|
|
||||||
if (expandSecretReferences) {
|
if (expandSecretReferences) {
|
||||||
@ -1245,11 +1238,7 @@ export const secretServiceFactory = ({
|
|||||||
return { secret, type: SecretProtectionType.Direct as const };
|
return { secret, type: SecretProtectionType.Direct as const };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!botKey)
|
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Project bot not found. Please upgrade your project.",
|
|
||||||
name: "bot_not_found_error"
|
|
||||||
});
|
|
||||||
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secretName, botKey);
|
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secretName, botKey);
|
||||||
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey);
|
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey);
|
||||||
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey);
|
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey);
|
||||||
@ -1387,11 +1376,7 @@ export const secretServiceFactory = ({
|
|||||||
return { type: SecretProtectionType.Direct as const, secret };
|
return { type: SecretProtectionType.Direct as const, secret };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!botKey)
|
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Project bot not found. Please upgrade your project.",
|
|
||||||
name: "bot_not_found_error"
|
|
||||||
});
|
|
||||||
|
|
||||||
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey);
|
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey);
|
||||||
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey);
|
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey);
|
||||||
@ -1513,11 +1498,7 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
return { type: SecretProtectionType.Direct as const, secret };
|
return { type: SecretProtectionType.Direct as const, secret };
|
||||||
}
|
}
|
||||||
if (!botKey)
|
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Project bot not found. Please upgrade your project.",
|
|
||||||
name: "bot_not_found_error"
|
|
||||||
});
|
|
||||||
if (policy) {
|
if (policy) {
|
||||||
const approval = await secretApprovalRequestService.generateSecretApprovalRequest({
|
const approval = await secretApprovalRequestService.generateSecretApprovalRequest({
|
||||||
policy,
|
policy,
|
||||||
@ -1617,11 +1598,7 @@ export const secretServiceFactory = ({
|
|||||||
return { secrets, type: SecretProtectionType.Direct as const };
|
return { secrets, type: SecretProtectionType.Direct as const };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!botKey)
|
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Project bot not found. Please upgrade your project.",
|
|
||||||
name: "bot_not_found_error"
|
|
||||||
});
|
|
||||||
const sanitizedSecrets = inputSecrets.map(
|
const sanitizedSecrets = inputSecrets.map(
|
||||||
({ secretComment, secretKey, metadata, tagIds, secretValue, skipMultilineEncoding }) => {
|
({ secretComment, secretKey, metadata, tagIds, secretValue, skipMultilineEncoding }) => {
|
||||||
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secretKey, botKey);
|
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secretKey, botKey);
|
||||||
@ -1743,11 +1720,7 @@ export const secretServiceFactory = ({
|
|||||||
return { type: SecretProtectionType.Direct as const, secrets };
|
return { type: SecretProtectionType.Direct as const, secrets };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!botKey)
|
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Project bot not found. Please upgrade your project.",
|
|
||||||
name: "bot_not_found_error"
|
|
||||||
});
|
|
||||||
const sanitizedSecrets = inputSecrets.map(
|
const sanitizedSecrets = inputSecrets.map(
|
||||||
({
|
({
|
||||||
secretComment,
|
secretComment,
|
||||||
@ -1875,11 +1848,7 @@ export const secretServiceFactory = ({
|
|||||||
return { type: SecretProtectionType.Direct as const, secrets };
|
return { type: SecretProtectionType.Direct as const, secrets };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!botKey)
|
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Project bot not found. Please upgrade your project.",
|
|
||||||
name: "bot_not_found_error"
|
|
||||||
});
|
|
||||||
|
|
||||||
if (policy) {
|
if (policy) {
|
||||||
const approval = await secretApprovalRequestService.generateSecretApprovalRequest({
|
const approval = await secretApprovalRequestService.generateSecretApprovalRequest({
|
||||||
@ -2213,10 +2182,7 @@ export const secretServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!botKey)
|
if (!botKey)
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({ message: "Please upgrade your project first", name: "bot_not_found_error" });
|
||||||
message: "Project bot not found. Please upgrade your project.",
|
|
||||||
name: "bot_not_found_error"
|
|
||||||
});
|
|
||||||
|
|
||||||
await secretDAL.transaction(async (tx) => {
|
await secretDAL.transaction(async (tx) => {
|
||||||
const secrets = await secretDAL.findAllProjectSecretValues(projectId, tx);
|
const secrets = await secretDAL.findAllProjectSecretValues(projectId, tx);
|
||||||
@ -2299,10 +2265,7 @@ export const secretServiceFactory = ({
|
|||||||
|
|
||||||
const { botKey } = await projectBotService.getBotKey(project.id);
|
const { botKey } = await projectBotService.getBotKey(project.id);
|
||||||
if (!botKey) {
|
if (!botKey) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
|
||||||
message: "Project bot not found. Please upgrade your project.",
|
|
||||||
name: "bot_not_found_error"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const sourceFolder = await folderDAL.findBySecretPath(project.id, sourceEnvironment, sourceSecretPath);
|
const sourceFolder = await folderDAL.findBySecretPath(project.id, sourceEnvironment, sourceSecretPath);
|
||||||
|
@ -4,7 +4,6 @@ Copyright (c) 2023 Infisical Inc.
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -44,26 +43,14 @@ func init() {
|
|||||||
rootCmd.PersistentFlags().Bool("silent", false, "Disable output of tip/info messages. Useful when running in scripts or CI/CD pipelines.")
|
rootCmd.PersistentFlags().Bool("silent", false, "Disable output of tip/info messages. Useful when running in scripts or CI/CD pipelines.")
|
||||||
rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
|
rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
|
||||||
silent, err := cmd.Flags().GetBool("silent")
|
silent, err := cmd.Flags().GetBool("silent")
|
||||||
|
config.INFISICAL_URL = util.AppendAPIEndpoint(config.INFISICAL_URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err)
|
util.HandleError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
config.INFISICAL_URL = util.AppendAPIEndpoint(config.INFISICAL_URL)
|
|
||||||
|
|
||||||
if !util.IsRunningInDocker() && !silent {
|
if !util.IsRunningInDocker() && !silent {
|
||||||
util.CheckForUpdate()
|
util.CheckForUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
loggedInDetails, err := util.GetCurrentLoggedInUserDetails()
|
|
||||||
|
|
||||||
if !silent && err == nil && loggedInDetails.IsUserLoggedIn && !loggedInDetails.LoginExpired {
|
|
||||||
token, err := util.GetInfisicalToken(cmd)
|
|
||||||
|
|
||||||
if err == nil && token != nil {
|
|
||||||
util.PrintWarning(fmt.Sprintf("Your logged-in session is being overwritten by the token provided from the %s.", token.Source))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if config.INFISICAL_URL is set to the default value, check if INFISICAL_URL is set in the environment
|
// if config.INFISICAL_URL is set to the default value, check if INFISICAL_URL is set in the environment
|
||||||
|
@ -160,19 +160,19 @@ var secretsSetCmd = &cobra.Command{
|
|||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
if token == nil {
|
if (token == nil) {
|
||||||
util.RequireLocalWorkspaceFile()
|
util.RequireLocalWorkspaceFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
environmentName, _ := cmd.Flags().GetString("env")
|
environmentName, _ := cmd.Flags().GetString("env")
|
||||||
if !cmd.Flags().Changed("env") {
|
if !cmd.Flags().Changed("env") {
|
||||||
environmentFromWorkspace := util.GetEnvFromWorkspaceFile()
|
environmentFromWorkspace := util.GetEnvFromWorkspaceFile()
|
||||||
if environmentFromWorkspace != "" {
|
if environmentFromWorkspace != "" {
|
||||||
environmentName = environmentFromWorkspace
|
environmentName = environmentFromWorkspace
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
projectId, err := cmd.Flags().GetString("projectId")
|
projectId, err := cmd.Flags().GetString("projectId")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
}
|
}
|
||||||
|
@ -63,9 +63,8 @@ type DynamicSecretLease struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TokenDetails struct {
|
type TokenDetails struct {
|
||||||
Type string
|
Type string
|
||||||
Token string
|
Token string
|
||||||
Source string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SingleFolder struct {
|
type SingleFolder struct {
|
||||||
|
@ -87,15 +87,11 @@ func GetInfisicalToken(cmd *cobra.Command) (token *models.TokenDetails, err erro
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var source = "--token flag"
|
|
||||||
|
|
||||||
if infisicalToken == "" { // If no flag is passed, we first check for the universal auth access token env variable.
|
if infisicalToken == "" { // If no flag is passed, we first check for the universal auth access token env variable.
|
||||||
infisicalToken = os.Getenv(INFISICAL_UNIVERSAL_AUTH_ACCESS_TOKEN_NAME)
|
infisicalToken = os.Getenv(INFISICAL_UNIVERSAL_AUTH_ACCESS_TOKEN_NAME)
|
||||||
source = fmt.Sprintf("%s environment variable", INFISICAL_UNIVERSAL_AUTH_ACCESS_TOKEN_NAME)
|
|
||||||
|
|
||||||
if infisicalToken == "" { // If it's still empty after the first env check, we check for the service token env variable.
|
if infisicalToken == "" { // If it's still empty after the first env check, we check for the service token env variable.
|
||||||
infisicalToken = os.Getenv(INFISICAL_TOKEN_NAME)
|
infisicalToken = os.Getenv(INFISICAL_TOKEN_NAME)
|
||||||
source = fmt.Sprintf("%s environment variable", INFISICAL_TOKEN_NAME)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,16 +101,14 @@ func GetInfisicalToken(cmd *cobra.Command) (token *models.TokenDetails, err erro
|
|||||||
|
|
||||||
if strings.HasPrefix(infisicalToken, "st.") {
|
if strings.HasPrefix(infisicalToken, "st.") {
|
||||||
return &models.TokenDetails{
|
return &models.TokenDetails{
|
||||||
Type: SERVICE_TOKEN_IDENTIFIER,
|
Type: SERVICE_TOKEN_IDENTIFIER,
|
||||||
Token: infisicalToken,
|
Token: infisicalToken,
|
||||||
Source: source,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &models.TokenDetails{
|
return &models.TokenDetails{
|
||||||
Type: UNIVERSAL_AUTH_TOKEN_IDENTIFIER,
|
Type: UNIVERSAL_AUTH_TOKEN_IDENTIFIER,
|
||||||
Token: infisicalToken,
|
Token: infisicalToken,
|
||||||
Source: source,
|
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,3 @@
|
|||||||
title: "Create"
|
title: "Create"
|
||||||
openapi: "POST /api/v1/workspace/{projectSlug}/roles"
|
openapi: "POST /api/v1/workspace/{projectSlug}/roles"
|
||||||
---
|
---
|
||||||
|
|
||||||
<Note>
|
|
||||||
You can read more about the permissions field in the [permissions documentation](/internals/permissions).
|
|
||||||
</Note>
|
|
@ -60,8 +60,6 @@ In the following steps, we explore how to issue a X.509 certificate under a CA.
|
|||||||
- Common Name (CN): A regular expression used to validate the common name in certificate requests.
|
- Common Name (CN): A regular expression used to validate the common name in certificate requests.
|
||||||
- Alternative Names (SANs): A regular expression used to validate subject alternative names in certificate requests.
|
- Alternative Names (SANs): A regular expression used to validate subject alternative names in certificate requests.
|
||||||
- TTL: The maximum Time-to-Live (TTL) for certificates issued using this template.
|
- TTL: The maximum Time-to-Live (TTL) for certificates issued using this template.
|
||||||
- Key Usage: The key usage constraint or default value for certificates issued using this template.
|
|
||||||
- Extended Key Usage: The extended key usage constraint or default value for certificates issued using this template.
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Creating a certificate">
|
<Step title="Creating a certificate">
|
||||||
To create a certificate, head to your Project > Internal PKI > Certificates and press **Issue** under the Certificates section.
|
To create a certificate, head to your Project > Internal PKI > Certificates and press **Issue** under the Certificates section.
|
||||||
@ -78,16 +76,13 @@ In the following steps, we explore how to issue a X.509 certificate under a CA.
|
|||||||
- Common Name (CN): The (common) name for the certificate like `service.acme.com`.
|
- Common Name (CN): The (common) name for the certificate like `service.acme.com`.
|
||||||
- Alternative Names (SANs): A comma-delimited list of Subject Alternative Names (SANs) for the certificate; these can be host names or email addresses like `app1.acme.com, app2.acme.com`.
|
- Alternative Names (SANs): A comma-delimited list of Subject Alternative Names (SANs) for the certificate; these can be host names or email addresses like `app1.acme.com, app2.acme.com`.
|
||||||
- TTL: The lifetime of the certificate in seconds.
|
- TTL: The lifetime of the certificate in seconds.
|
||||||
- Key Usage: The key usage extension of the certificate.
|
|
||||||
- Extended Key Usage: The extended key usage extension of the certificate.
|
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
Note that Infisical PKI supports issuing certificates without certificate templates as well. If this is desired, then you can set the **Certificate Template** field to **None**
|
Note that Infisical PKI supports issuing certificates without certificate templates as well. If this is desired, then you can set the **Certificate Template** field to **None**
|
||||||
and specify the **Issuing CA** and optional **Certificate Collection** fields; the rest of the fields for the issued certificate remain the same.
|
and specify the **Issuing CA** and optional **Certificate Collection** fields; the rest of the fields for the issued certificate remain the same.
|
||||||
|
|
||||||
That said, we recommend using certificate templates to enforce policies and attach expiration monitoring on issued certificates.
|
That said, we recommend using certificate templates to enforce policies and attach expiration monitoring on issued certificates.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Copying the certificate details">
|
<Step title="Copying the certificate details">
|
||||||
Once you have created the certificate from step 1, you'll be presented with the certificate details including the **Certificate Body**, **Certificate Chain**, and **Private Key**.
|
Once you have created the certificate from step 1, you'll be presented with the certificate details including the **Certificate Body**, **Certificate Chain**, and **Private Key**.
|
||||||
@ -110,7 +105,7 @@ In the following steps, we explore how to issue a X.509 certificate under a CA.
|
|||||||
With certificate templates, you can specify, for example, that issued certificates must have a common name (CN) adhering to a specific format like .*.acme.com or perhaps that the max TTL cannot be more than 1 year.
|
With certificate templates, you can specify, for example, that issued certificates must have a common name (CN) adhering to a specific format like .*.acme.com or perhaps that the max TTL cannot be more than 1 year.
|
||||||
|
|
||||||
To create a certificate template, make an API request to the [Create Certificate Template](/api-reference/endpoints/certificate-templates/create) API endpoint, specifying the issuing CA.
|
To create a certificate template, make an API request to the [Create Certificate Template](/api-reference/endpoints/certificate-templates/create) API endpoint, specifying the issuing CA.
|
||||||
|
|
||||||
### Sample request
|
### Sample request
|
||||||
|
|
||||||
```bash Request
|
```bash Request
|
||||||
@ -137,7 +132,6 @@ In the following steps, we explore how to issue a X.509 certificate under a CA.
|
|||||||
ttl: "...",
|
ttl: "...",
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Creating a certificate">
|
<Step title="Creating a certificate">
|
||||||
To create a certificate under the certificate template, make an API request to the [Issue Certificate](/api-reference/endpoints/certificates/issue-cert) API endpoint,
|
To create a certificate under the certificate template, make an API request to the [Issue Certificate](/api-reference/endpoints/certificates/issue-cert) API endpoint,
|
||||||
@ -170,7 +164,7 @@ In the following steps, we explore how to issue a X.509 certificate under a CA.
|
|||||||
<Note>
|
<Note>
|
||||||
Note that Infisical PKI supports issuing certificates without certificate templates as well. If this is desired, then you can set the **Certificate Template** field to **None**
|
Note that Infisical PKI supports issuing certificates without certificate templates as well. If this is desired, then you can set the **Certificate Template** field to **None**
|
||||||
and specify the **Issuing CA** and optional **Certificate Collection** fields; the rest of the fields for the issued certificate remain the same.
|
and specify the **Issuing CA** and optional **Certificate Collection** fields; the rest of the fields for the issued certificate remain the same.
|
||||||
|
|
||||||
That said, we recommend using certificate templates to enforce policies and attach expiration monitoring on issued certificates.
|
That said, we recommend using certificate templates to enforce policies and attach expiration monitoring on issued certificates.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
@ -203,7 +197,6 @@ In the following steps, we explore how to issue a X.509 certificate under a CA.
|
|||||||
serialNumber: "..."
|
serialNumber: "..."
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 399 KiB After Width: | Height: | Size: 379 KiB |
Binary file not shown.
Before Width: | Height: | Size: 518 KiB After Width: | Height: | Size: 517 KiB |
@ -1,83 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Permissions"
|
|
||||||
description: "Infisical's permissions system provides granular access control."
|
|
||||||
---
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The Infisical permissions system is based on a role-based access control (RBAC) model. The system allows you to define roles and assign them to users and machines. Each role has a set of permissions that define what actions a user can perform.
|
|
||||||
|
|
||||||
Permissions are built on a subject-action-object model. The subject is the resource permission is being applied to, the action is what the permission allows.
|
|
||||||
An example of a subject/action combination would be `secrets/read`. This permission allows the subject to read secrets.
|
|
||||||
|
|
||||||
Currently Infisical supports 4 actions:
|
|
||||||
1. `read`, allows the subject to read the object.
|
|
||||||
2. `create`, allows the subject to create the object.
|
|
||||||
3. `edit`, allows the subject to edit the object.
|
|
||||||
4. `delete`, allows the subject to delete the object.
|
|
||||||
|
|
||||||
Most subjects support all 4 actions, but some subjects only support a subset of actions. Please view the table below for a list of subjects and the actions they support.
|
|
||||||
|
|
||||||
|
|
||||||
## Subjects and Actions
|
|
||||||
<Tabs>
|
|
||||||
<Tab title="Project Permissions">
|
|
||||||
|
|
||||||
<Note>
|
|
||||||
Not all actions are applicable to all subjects. As an example, the `secrets-rollback` subject only supports `read`, and `create` as actions. While `secrets` support `read`, `create`, `edit`, `delete`.
|
|
||||||
</Note>
|
|
||||||
|
|
||||||
| Subject | Actions |
|
|
||||||
|-----------------------------|---------|
|
|
||||||
| `secrets` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `secret-approval` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `secret-rotation` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `secret-rollback` | `read`, `create` |
|
|
||||||
| `member` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `groups` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `role` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `integrations` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `webhooks` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `identity` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `service-tokens` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `settings` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `environments` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `tags` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `audit-logs` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `ip-allowlist` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `certificate-authorities` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `certificates` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `certificate-templates` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `pki-alerts` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `pki-collections` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `workspace` | `edit`, `delete` |
|
|
||||||
| `kms` | `edit` |
|
|
||||||
|
|
||||||
These details are especially useful if you're using the API to [create new project roles](../api-reference/endpoints/project-roles/create).
|
|
||||||
The rules outlined on this page, also apply when using our Terraform Provider to manage your Infisical project roles, or any other of our clients that manage project roles.
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
|
|
||||||
<Tab title="Organization Permissions">
|
|
||||||
|
|
||||||
<Note>
|
|
||||||
Not all actions are applicable to all subjects. As an example, the `workspace` subject only supports `read`, and `create` as actions. While `member` support `read`, `create`, `edit`, `delete`.
|
|
||||||
</Note>
|
|
||||||
|
|
||||||
| Subject | Actions |
|
|
||||||
|-----------------------------|------------------------------------|
|
|
||||||
| `workspace` | `read`, `create` |
|
|
||||||
| `role` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `member` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `secret-scanning` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `settings` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `incident-account` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `sso` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `scim` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `ldap` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `groups` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `billing` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `identity` | `read`, `create`, `edit`, `delete` |
|
|
||||||
| `kms` | `read` |
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
@ -696,12 +696,7 @@
|
|||||||
{
|
{
|
||||||
"group": "Audit Logs",
|
"group": "Audit Logs",
|
||||||
"pages": ["api-reference/endpoints/audit-logs/export-audit-log"]
|
"pages": ["api-reference/endpoints/audit-logs/export-audit-log"]
|
||||||
}
|
},
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"group": "Infisical PKI",
|
|
||||||
"pages": [
|
|
||||||
{
|
{
|
||||||
"group": "Certificate Authorities",
|
"group": "Certificate Authorities",
|
||||||
"pages": [
|
"pages": [
|
||||||
@ -769,7 +764,6 @@
|
|||||||
"group": "Internals",
|
"group": "Internals",
|
||||||
"pages": [
|
"pages": [
|
||||||
"internals/overview",
|
"internals/overview",
|
||||||
"internals/permissions",
|
|
||||||
"internals/components",
|
"internals/components",
|
||||||
"internals/flows",
|
"internals/flows",
|
||||||
"internals/security",
|
"internals/security",
|
||||||
|
@ -1,83 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { faCaretDown, faCheck } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { Combobox, Transition } from "@headlessui/react";
|
|
||||||
import { ByComparator } from "@headlessui/react/dist/types";
|
|
||||||
import { twMerge } from "tailwind-merge";
|
|
||||||
|
|
||||||
type ComboBoxProps<T extends object> = {
|
|
||||||
value?: T;
|
|
||||||
className?: string;
|
|
||||||
items: {
|
|
||||||
value: T;
|
|
||||||
key: string;
|
|
||||||
label: string;
|
|
||||||
}[];
|
|
||||||
by: ByComparator<T>;
|
|
||||||
defaultValue?: T;
|
|
||||||
displayValue: (value: T) => string;
|
|
||||||
onSelectChange: (value: T) => void;
|
|
||||||
onFilter: (value: { value: T }, filterQuery: string) => boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO(akhilmhdh): This is a very temporary one due to limitation of present situation
|
|
||||||
// don't mind the api for now will be switched to aria later
|
|
||||||
export const ComboBox = <T extends object>({
|
|
||||||
onSelectChange,
|
|
||||||
onFilter,
|
|
||||||
displayValue,
|
|
||||||
by,
|
|
||||||
items,
|
|
||||||
...props
|
|
||||||
}: ComboBoxProps<T>) => {
|
|
||||||
const [query, setQuery] = useState("");
|
|
||||||
|
|
||||||
const filteredResult =
|
|
||||||
query === "" ? items.slice(0, 20) : items.filter((el) => onFilter(el, query)).slice(0, 20);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Combobox by={by} {...props} onChange={onSelectChange}>
|
|
||||||
<div className="relative">
|
|
||||||
<Combobox.Input
|
|
||||||
onChange={(event) => setQuery(event.target.value)}
|
|
||||||
displayValue={displayValue}
|
|
||||||
className=" inline-flex w-full items-center justify-between rounded-md bg-mineshaft-900 px-3 py-2 font-inter text-sm font-normal text-bunker-200 outline-none focus:bg-mineshaft-700/80 data-[placeholder]:text-mineshaft-200"
|
|
||||||
/>
|
|
||||||
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center pr-2">
|
|
||||||
<FontAwesomeIcon icon={faCaretDown} size="sm" aria-hidden="true" />
|
|
||||||
</Combobox.Button>
|
|
||||||
<Transition
|
|
||||||
leave="transition ease-in duration-100"
|
|
||||||
leaveFrom="opacity-100"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
afterLeave={() => setQuery("")}
|
|
||||||
>
|
|
||||||
<Combobox.Options className="absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md bg-mineshaft-900 py-1 text-base shadow-lg ring-1 ring-black/5 focus:outline-none sm:text-sm">
|
|
||||||
{filteredResult.map(({ value, key, label }) => (
|
|
||||||
<Combobox.Option
|
|
||||||
key={key}
|
|
||||||
value={value}
|
|
||||||
className={({ active }) =>
|
|
||||||
`relative cursor-pointer select-none py-2 pl-10 pr-4 transition-all hover:bg-mineshaft-500 ${
|
|
||||||
active ? "text-primary" : "text-white"
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{({ selected }) => (
|
|
||||||
<>
|
|
||||||
{label}
|
|
||||||
{selected ? (
|
|
||||||
<div className={twMerge("absolute top-2 left-3 text-primary")}>
|
|
||||||
<FontAwesomeIcon icon={faCheck} />
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Combobox.Option>
|
|
||||||
))}
|
|
||||||
</Combobox.Options>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
</Combobox>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1 +0,0 @@
|
|||||||
export { ComboBox } from "./ComboBox";
|
|
@ -11,7 +11,6 @@ type Props = {
|
|||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
isReadOnly?: boolean;
|
isReadOnly?: boolean;
|
||||||
autoCapitalization?: boolean;
|
autoCapitalization?: boolean;
|
||||||
containerClassName?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const inputVariants = cva(
|
const inputVariants = cva(
|
||||||
@ -72,7 +71,6 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|||||||
(
|
(
|
||||||
{
|
{
|
||||||
className,
|
className,
|
||||||
containerClassName,
|
|
||||||
isRounded = true,
|
isRounded = true,
|
||||||
isFullWidth = true,
|
isFullWidth = true,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
@ -96,15 +94,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={inputParentContainerVariants({ isRounded, isError, isFullWidth, variant })}>
|
||||||
className={inputParentContainerVariants({
|
|
||||||
isRounded,
|
|
||||||
isError,
|
|
||||||
isFullWidth,
|
|
||||||
variant,
|
|
||||||
className: containerClassName
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{leftIcon && <span className="absolute left-0 ml-3 text-sm">{leftIcon}</span>}
|
{leftIcon && <span className="absolute left-0 ml-3 text-sm">{leftIcon}</span>}
|
||||||
<input
|
<input
|
||||||
{...props}
|
{...props}
|
||||||
|
@ -11,24 +11,13 @@ export type ModalContentProps = DialogPrimitive.DialogContentProps & {
|
|||||||
title?: ReactNode;
|
title?: ReactNode;
|
||||||
subTitle?: ReactNode;
|
subTitle?: ReactNode;
|
||||||
footerContent?: ReactNode;
|
footerContent?: ReactNode;
|
||||||
bodyClassName?: string;
|
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
overlayClassName?: string;
|
overlayClassName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ModalContent = forwardRef<HTMLDivElement, ModalContentProps>(
|
export const ModalContent = forwardRef<HTMLDivElement, ModalContentProps>(
|
||||||
(
|
(
|
||||||
{
|
{ children, title, subTitle, className, overlayClassName, footerContent, onClose, ...props },
|
||||||
children,
|
|
||||||
title,
|
|
||||||
subTitle,
|
|
||||||
className,
|
|
||||||
overlayClassName,
|
|
||||||
footerContent,
|
|
||||||
bodyClassName,
|
|
||||||
onClose,
|
|
||||||
...props
|
|
||||||
},
|
|
||||||
forwardedRef
|
forwardedRef
|
||||||
) => (
|
) => (
|
||||||
<DialogPrimitive.Portal>
|
<DialogPrimitive.Portal>
|
||||||
@ -46,10 +35,7 @@ export const ModalContent = forwardRef<HTMLDivElement, ModalContentProps>(
|
|||||||
style={{ maxHeight: "90%" }}
|
style={{ maxHeight: "90%" }}
|
||||||
>
|
>
|
||||||
{title && <CardTitle subTitle={subTitle}>{title}</CardTitle>}
|
{title && <CardTitle subTitle={subTitle}>{title}</CardTitle>}
|
||||||
<CardBody
|
<CardBody className="overflow-y-auto overflow-x-hidden" style={{ maxHeight: "90%" }}>
|
||||||
className={twMerge("overflow-y-auto overflow-x-hidden", bodyClassName)}
|
|
||||||
style={{ maxHeight: "90%" }}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</CardBody>
|
</CardBody>
|
||||||
{footerContent && <CardFooter>{footerContent}</CardFooter>}
|
{footerContent && <CardFooter>{footerContent}</CardFooter>}
|
||||||
|
@ -40,8 +40,6 @@ export const Pagination = ({
|
|||||||
const upperLimit = Math.ceil(count / perPage);
|
const upperLimit = Math.ceil(count / perPage);
|
||||||
const nextPageNumber = Math.min(upperLimit, page + 1);
|
const nextPageNumber = Math.min(upperLimit, page + 1);
|
||||||
const canGoNext = page + 1 <= upperLimit;
|
const canGoNext = page + 1 <= upperLimit;
|
||||||
const canGoFirst = page > 1;
|
|
||||||
const canGoLast = page < upperLimit;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -52,7 +50,7 @@ export const Pagination = ({
|
|||||||
>
|
>
|
||||||
<div className="mr-6 flex items-center space-x-2">
|
<div className="mr-6 flex items-center space-x-2">
|
||||||
<div className="text-xs">
|
<div className="text-xs">
|
||||||
{(page - 1) * perPage + 1} - {Math.min((page - 1) * perPage + perPage, count)} of {count}
|
{(page - 1) * perPage} - {Math.min((page - 1) * perPage + perPage, count)} of {count}
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
@ -75,16 +73,6 @@ export const Pagination = ({
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<IconButton
|
|
||||||
variant="plain"
|
|
||||||
ariaLabel="pagination-first"
|
|
||||||
className="relative"
|
|
||||||
onClick={() => onChangePage(1)}
|
|
||||||
isDisabled={!canGoFirst}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon className="absolute left-2.5 top-1 text-xs" icon={faChevronLeft} />
|
|
||||||
<FontAwesomeIcon className="text-xs" icon={faChevronLeft} />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
variant="plain"
|
variant="plain"
|
||||||
ariaLabel="pagination-prev"
|
ariaLabel="pagination-prev"
|
||||||
@ -101,16 +89,6 @@ export const Pagination = ({
|
|||||||
>
|
>
|
||||||
<FontAwesomeIcon className="text-xs" icon={faChevronRight} />
|
<FontAwesomeIcon className="text-xs" icon={faChevronRight} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
|
||||||
variant="plain"
|
|
||||||
ariaLabel="pagination-last"
|
|
||||||
className="relative"
|
|
||||||
onClick={() => onChangePage(upperLimit)}
|
|
||||||
isDisabled={!canGoLast}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon className="absolute left-2.5 top-1 text-xs" icon={faChevronRight} />
|
|
||||||
<FontAwesomeIcon className="text-xs" icon={faChevronRight} />
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -50,8 +50,8 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
|
|||||||
<SelectPrimitive.Trigger
|
<SelectPrimitive.Trigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
`inline-flex items-center justify-between rounded-md border border-mineshaft-600
|
`inline-flex items-center justify-between rounded-md
|
||||||
bg-mineshaft-900 px-3 py-2 font-inter text-sm font-normal text-bunker-200 outline-none focus:bg-mineshaft-700/80 data-[placeholder]:text-mineshaft-400`,
|
bg-mineshaft-900 px-3 py-2 font-inter text-sm font-normal text-bunker-200 outline-none focus:bg-mineshaft-700/80 data-[placeholder]:text-mineshaft-200`,
|
||||||
className,
|
className,
|
||||||
isDisabled && "cursor-not-allowed opacity-50"
|
isDisabled && "cursor-not-allowed opacity-50"
|
||||||
)}
|
)}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { createContext, ReactNode, useContext, useEffect, useMemo } from "react";
|
import { createContext, ReactNode, useContext, useMemo } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
|
||||||
import { useGetUserWorkspaces } from "@app/hooks/api";
|
import { useGetUserWorkspaces } from "@app/hooks/api";
|
||||||
import { Workspace } from "@app/hooks/api/workspace/types";
|
import { Workspace } from "@app/hooks/api/workspace/types";
|
||||||
|
|
||||||
@ -32,34 +31,6 @@ export const WorkspaceProvider = ({ children }: Props): JSX.Element => {
|
|||||||
};
|
};
|
||||||
}, [ws, workspaceId, isLoading]);
|
}, [ws, workspaceId, isLoading]);
|
||||||
|
|
||||||
const shouldTriggerNoProjectAccess =
|
|
||||||
!value.isLoading &&
|
|
||||||
!value.currentWorkspace &&
|
|
||||||
router.pathname.startsWith("/project") &&
|
|
||||||
workspaceId;
|
|
||||||
|
|
||||||
// handle redirects for project-specific routes
|
|
||||||
useEffect(() => {
|
|
||||||
if (shouldTriggerNoProjectAccess) {
|
|
||||||
createNotification({
|
|
||||||
text: "You are not a member of this project.",
|
|
||||||
type: "info"
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
router.push("/");
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
}, [shouldTriggerNoProjectAccess, router]);
|
|
||||||
|
|
||||||
if (shouldTriggerNoProjectAccess) {
|
|
||||||
return (
|
|
||||||
<div className="flex h-screen w-screen items-center justify-center bg-bunker-800 text-primary-50">
|
|
||||||
You do not have sufficient access to this project.
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <WorkspaceContext.Provider value={value}>{children}</WorkspaceContext.Provider>;
|
return <WorkspaceContext.Provider value={value}>{children}</WorkspaceContext.Provider>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -79,7 +79,8 @@ export const eventToNameMap: { [K in EventType]: string } = {
|
|||||||
[EventType.UPDATE_CERTIFICATE_TEMPLATE_EST_CONFIG]:
|
[EventType.UPDATE_CERTIFICATE_TEMPLATE_EST_CONFIG]:
|
||||||
"Update certificate template EST configuration",
|
"Update certificate template EST configuration",
|
||||||
[EventType.UPDATE_PROJECT_SLACK_CONFIG]: "Update project slack configuration",
|
[EventType.UPDATE_PROJECT_SLACK_CONFIG]: "Update project slack configuration",
|
||||||
[EventType.GET_PROJECT_SLACK_CONFIG]: "Get project slack configuration"
|
[EventType.GET_PROJECT_SLACK_CONFIG]: "Get project slack configuration",
|
||||||
|
[EventType.INTEGRATION_SYNCED]: "Integration sync"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const userAgentTTypeoNameMap: { [K in UserAgentType]: string } = {
|
export const userAgentTTypeoNameMap: { [K in UserAgentType]: string } = {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
export enum ActorType {
|
export enum ActorType {
|
||||||
|
PLATFORM = "platform",
|
||||||
USER = "user",
|
USER = "user",
|
||||||
SERVICE = "service",
|
SERVICE = "service",
|
||||||
IDENTITY = "identity"
|
IDENTITY = "identity"
|
||||||
@ -91,5 +92,6 @@ export enum EventType {
|
|||||||
UPDATE_CERTIFICATE_TEMPLATE_EST_CONFIG = "update-certificate-template-est-config",
|
UPDATE_CERTIFICATE_TEMPLATE_EST_CONFIG = "update-certificate-template-est-config",
|
||||||
GET_CERTIFICATE_TEMPLATE_EST_CONFIG = "get-certificate-template-est-config",
|
GET_CERTIFICATE_TEMPLATE_EST_CONFIG = "get-certificate-template-est-config",
|
||||||
UPDATE_PROJECT_SLACK_CONFIG = "update-project-slack-config",
|
UPDATE_PROJECT_SLACK_CONFIG = "update-project-slack-config",
|
||||||
GET_PROJECT_SLACK_CONFIG = "get-project-slack-config"
|
GET_PROJECT_SLACK_CONFIG = "get-project-slack-config",
|
||||||
|
INTEGRATION_SYNCED = "integration-synced"
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,58 @@
|
|||||||
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
|
import { useInfiniteQuery, UseInfiniteQueryOptions, useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
import { Actor, AuditLog, AuditLogFilters } from "./types";
|
import { Actor, AuditLog, TGetAuditLogsFilter } from "./types";
|
||||||
|
|
||||||
export const auditLogKeys = {
|
export const auditLogKeys = {
|
||||||
getAuditLogs: (workspaceId: string | null, filters: AuditLogFilters) =>
|
getAuditLogs: (workspaceId: string | null, filters: TGetAuditLogsFilter) =>
|
||||||
[{ workspaceId, filters }, "audit-logs"] as const,
|
[{ workspaceId, filters }, "audit-logs"] as const,
|
||||||
getAuditLogActorFilterOpts: (workspaceId: string) =>
|
getAuditLogActorFilterOpts: (workspaceId: string) =>
|
||||||
[{ workspaceId }, "audit-log-actor-filters"] as const
|
[{ workspaceId }, "audit-log-actor-filters"] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetAuditLogs = (filters: AuditLogFilters, workspaceId: string | null) => {
|
export const useGetAuditLogs = (
|
||||||
|
filters: TGetAuditLogsFilter,
|
||||||
|
projectId: string | null,
|
||||||
|
options: Omit<
|
||||||
|
UseInfiniteQueryOptions<
|
||||||
|
AuditLog[],
|
||||||
|
unknown,
|
||||||
|
AuditLog[],
|
||||||
|
AuditLog[],
|
||||||
|
ReturnType<typeof auditLogKeys.getAuditLogs>
|
||||||
|
>,
|
||||||
|
"queryFn" | "queryKey" | "getNextPageParam"
|
||||||
|
> = {}
|
||||||
|
) => {
|
||||||
return useInfiniteQuery({
|
return useInfiniteQuery({
|
||||||
queryKey: auditLogKeys.getAuditLogs(workspaceId, filters),
|
queryKey: auditLogKeys.getAuditLogs(projectId, filters),
|
||||||
queryFn: async ({ pageParam }) => {
|
queryFn: async ({ pageParam }) => {
|
||||||
const auditLogEndpoint = workspaceId
|
const { data } = await apiRequest.get<{ auditLogs: AuditLog[] }>(
|
||||||
? `/api/v1/workspace/${workspaceId}/audit-logs`
|
"/api/v1/organization/audit-logs",
|
||||||
: "/api/v1/organization/audit-logs";
|
{
|
||||||
const { data } = await apiRequest.get<{ auditLogs: AuditLog[] }>(auditLogEndpoint, {
|
params: {
|
||||||
params: {
|
...filters,
|
||||||
...filters,
|
offset: pageParam,
|
||||||
offset: pageParam,
|
startDate: filters?.startDate?.toISOString(),
|
||||||
startDate: filters?.startDate?.toISOString(),
|
endDate: filters?.endDate?.toISOString(),
|
||||||
endDate: filters?.endDate?.toISOString()
|
...(filters.eventMetadata && Object.keys(filters.eventMetadata).length
|
||||||
|
? {
|
||||||
|
eventMetadata: Object.entries(filters.eventMetadata)
|
||||||
|
.map(([key, value]) => `${key}=${value}`)
|
||||||
|
.join(",")
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
...(filters.eventType?.length ? { eventType: filters.eventType.join(",") } : {}),
|
||||||
|
...(projectId ? { projectId } : {})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
return data.auditLogs;
|
return data.auditLogs;
|
||||||
},
|
},
|
||||||
getNextPageParam: (lastPage, pages) =>
|
getNextPageParam: (lastPage, pages) =>
|
||||||
lastPage.length !== 0 ? pages.length * filters.limit : undefined
|
lastPage.length !== 0 ? pages.length * filters.limit : undefined,
|
||||||
|
...options
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,6 +3,17 @@ import { IdentityTrustedIp } from "../identities/types";
|
|||||||
import { PkiItemType } from "../pkiCollections/constants";
|
import { PkiItemType } from "../pkiCollections/constants";
|
||||||
import { ActorType, EventType, UserAgentType } from "./enums";
|
import { ActorType, EventType, UserAgentType } from "./enums";
|
||||||
|
|
||||||
|
export type TGetAuditLogsFilter = {
|
||||||
|
eventType?: EventType[];
|
||||||
|
userAgentType?: UserAgentType;
|
||||||
|
eventMetadata?: Record<string, string>;
|
||||||
|
actorType?: ActorType;
|
||||||
|
actorId?: string; // user ID format
|
||||||
|
startDate?: Date;
|
||||||
|
endDate?: Date;
|
||||||
|
limit: number;
|
||||||
|
};
|
||||||
|
|
||||||
interface UserActorMetadata {
|
interface UserActorMetadata {
|
||||||
userId: string;
|
userId: string;
|
||||||
email: string;
|
email: string;
|
||||||
@ -33,7 +44,13 @@ export interface IdentityActor {
|
|||||||
metadata: IdentityActorMetadata;
|
metadata: IdentityActorMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Actor = UserActor | ServiceActor | IdentityActor;
|
export interface PlatformActorMetadata {}
|
||||||
|
export interface PlatformActor {
|
||||||
|
type: ActorType.PLATFORM;
|
||||||
|
metadata: PlatformActorMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Actor = UserActor | ServiceActor | IdentityActor | PlatformActor;
|
||||||
|
|
||||||
interface GetSecretsEvent {
|
interface GetSecretsEvent {
|
||||||
type: EventType.GET_SECRETS;
|
type: EventType.GET_SECRETS;
|
||||||
@ -761,6 +778,22 @@ interface GetProjectSlackConfig {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum IntegrationSyncedEventTrigger {
|
||||||
|
MANUAL = "manual",
|
||||||
|
AUTO = "auto"
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IntegrationSyncedEvent {
|
||||||
|
type: EventType.INTEGRATION_SYNCED;
|
||||||
|
metadata: {
|
||||||
|
integrationId: string;
|
||||||
|
lastSyncJobId: string;
|
||||||
|
lastUsed: Date;
|
||||||
|
syncMessage: string;
|
||||||
|
isSynced: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type Event =
|
export type Event =
|
||||||
| GetSecretsEvent
|
| GetSecretsEvent
|
||||||
| GetSecretEvent
|
| GetSecretEvent
|
||||||
@ -838,7 +871,8 @@ export type Event =
|
|||||||
| CreateCertificateTemplateEstConfig
|
| CreateCertificateTemplateEstConfig
|
||||||
| GetCertificateTemplateEstConfig
|
| GetCertificateTemplateEstConfig
|
||||||
| UpdateProjectSlackConfig
|
| UpdateProjectSlackConfig
|
||||||
| GetProjectSlackConfig;
|
| GetProjectSlackConfig
|
||||||
|
| IntegrationSyncedEvent;
|
||||||
|
|
||||||
export type AuditLog = {
|
export type AuditLog = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -856,12 +890,3 @@ export type AuditLog = {
|
|||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AuditLogFilters = {
|
|
||||||
eventType?: EventType;
|
|
||||||
userAgentType?: UserAgentType;
|
|
||||||
actor?: string;
|
|
||||||
limit: number;
|
|
||||||
startDate?: Date;
|
|
||||||
endDate?: Date;
|
|
||||||
};
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "../certificates/enums";
|
import { CertKeyAlgorithm } from "../certificates/enums";
|
||||||
import { CaRenewalType, CaStatus, CaType } from "./enums";
|
import { CaRenewalType, CaStatus, CaType } from "./enums";
|
||||||
|
|
||||||
export type TCertificateAuthority = {
|
export type TCertificateAuthority = {
|
||||||
@ -91,8 +91,6 @@ 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[];
|
|
||||||
extendedKeyUsages: CertExtendedKeyUsage[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCreateCertificateResponse = {
|
export type TCreateCertificateResponse = {
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import { CertExtendedKeyUsage, CertKeyUsage } from "../certificates/enums";
|
|
||||||
|
|
||||||
export type TCertificateTemplate = {
|
export type TCertificateTemplate = {
|
||||||
id: string;
|
id: string;
|
||||||
caId: string;
|
caId: string;
|
||||||
@ -10,8 +8,6 @@ export type TCertificateTemplate = {
|
|||||||
commonName: string;
|
commonName: string;
|
||||||
subjectAlternativeName: string;
|
subjectAlternativeName: string;
|
||||||
ttl: string;
|
ttl: string;
|
||||||
keyUsages: CertKeyUsage[];
|
|
||||||
extendedKeyUsages: CertExtendedKeyUsage[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCreateCertificateTemplateDTO = {
|
export type TCreateCertificateTemplateDTO = {
|
||||||
@ -22,8 +18,6 @@ export type TCreateCertificateTemplateDTO = {
|
|||||||
subjectAlternativeName: string;
|
subjectAlternativeName: string;
|
||||||
ttl: string;
|
ttl: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
keyUsages: CertKeyUsage[];
|
|
||||||
extendedKeyUsages: CertExtendedKeyUsage[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TUpdateCertificateTemplateDTO = {
|
export type TUpdateCertificateTemplateDTO = {
|
||||||
@ -35,8 +29,6 @@ export type TUpdateCertificateTemplateDTO = {
|
|||||||
subjectAlternativeName?: string;
|
subjectAlternativeName?: string;
|
||||||
ttl?: string;
|
ttl?: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
keyUsages?: CertKeyUsage[];
|
|
||||||
extendedKeyUsages?: CertExtendedKeyUsage[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TDeleteCertificateTemplateDTO = {
|
export type TDeleteCertificateTemplateDTO = {
|
||||||
|
@ -1,10 +1,4 @@
|
|||||||
import {
|
import { CertKeyAlgorithm, CertStatus, CrlReason } from "./enums";
|
||||||
CertExtendedKeyUsage,
|
|
||||||
CertKeyAlgorithm,
|
|
||||||
CertKeyUsage,
|
|
||||||
CertStatus,
|
|
||||||
CrlReason
|
|
||||||
} from "./enums";
|
|
||||||
|
|
||||||
export const certStatusToNameMap: { [K in CertStatus]: string } = {
|
export const certStatusToNameMap: { [K in CertStatus]: string } = {
|
||||||
[CertStatus.ACTIVE]: "Active",
|
[CertStatus.ACTIVE]: "Active",
|
||||||
@ -75,24 +69,3 @@ export const crlReasons = [
|
|||||||
},
|
},
|
||||||
{ label: crlReasonToNameMap[CrlReason.A_A_COMPROMISE], value: CrlReason.A_A_COMPROMISE }
|
{ label: crlReasonToNameMap[CrlReason.A_A_COMPROMISE], value: CrlReason.A_A_COMPROMISE }
|
||||||
];
|
];
|
||||||
|
|
||||||
export const KEY_USAGES_OPTIONS = [
|
|
||||||
{ value: CertKeyUsage.DIGITAL_SIGNATURE, label: "Digital Signature" },
|
|
||||||
{ value: CertKeyUsage.KEY_ENCIPHERMENT, label: "Key Encipherment" },
|
|
||||||
{ value: CertKeyUsage.NON_REPUDIATION, label: "Non Repudiation" },
|
|
||||||
{ value: CertKeyUsage.DATA_ENCIPHERMENT, label: "Data Encipherment" },
|
|
||||||
{ value: CertKeyUsage.KEY_AGREEMENT, label: "Key Agreement" },
|
|
||||||
{ value: CertKeyUsage.KEY_CERT_SIGN, label: "Certificate Sign" },
|
|
||||||
{ value: CertKeyUsage.CRL_SIGN, label: "CRL Sign" },
|
|
||||||
{ value: CertKeyUsage.ENCIPHER_ONLY, label: "Encipher Only" },
|
|
||||||
{ value: CertKeyUsage.DECIPHER_ONLY, label: "Decipher Only" }
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const EXTENDED_KEY_USAGES_OPTIONS = [
|
|
||||||
{ value: CertExtendedKeyUsage.CLIENT_AUTH, label: "Client Auth" },
|
|
||||||
{ value: CertExtendedKeyUsage.SERVER_AUTH, label: "Server Auth" },
|
|
||||||
{ value: CertExtendedKeyUsage.EMAIL_PROTECTION, label: "Email Protection" },
|
|
||||||
{ value: CertExtendedKeyUsage.OCSP_SIGNING, label: "OCSP Signing" },
|
|
||||||
{ value: CertExtendedKeyUsage.CODE_SIGNING, label: "Code Signing" },
|
|
||||||
{ value: CertExtendedKeyUsage.TIMESTAMPING, label: "Timestamping" }
|
|
||||||
] as const;
|
|
||||||
|
@ -22,24 +22,3 @@ export enum CrlReason {
|
|||||||
PRIVILEGE_WITHDRAWN = "PRIVILEGE_WITHDRAWN",
|
PRIVILEGE_WITHDRAWN = "PRIVILEGE_WITHDRAWN",
|
||||||
A_A_COMPROMISE = "A_A_COMPROMISE"
|
A_A_COMPROMISE = "A_A_COMPROMISE"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CertKeyUsage {
|
|
||||||
DIGITAL_SIGNATURE = "digitalSignature",
|
|
||||||
KEY_ENCIPHERMENT = "keyEncipherment",
|
|
||||||
NON_REPUDIATION = "nonRepudiation",
|
|
||||||
DATA_ENCIPHERMENT = "dataEncipherment",
|
|
||||||
KEY_AGREEMENT = "keyAgreement",
|
|
||||||
KEY_CERT_SIGN = "keyCertSign",
|
|
||||||
CRL_SIGN = "cRLSign",
|
|
||||||
ENCIPHER_ONLY = "encipherOnly",
|
|
||||||
DECIPHER_ONLY = "decipherOnly"
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum CertExtendedKeyUsage {
|
|
||||||
CLIENT_AUTH = "clientAuth",
|
|
||||||
SERVER_AUTH = "serverAuth",
|
|
||||||
CODE_SIGNING = "codeSigning",
|
|
||||||
EMAIL_PROTECTION = "emailProtection",
|
|
||||||
TIMESTAMPING = "timeStamping",
|
|
||||||
OCSP_SIGNING = "ocspSigning"
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CertExtendedKeyUsage, CertKeyUsage, CertStatus } from "./enums";
|
import { CertStatus } from "./enums";
|
||||||
|
|
||||||
export type TCertificate = {
|
export type TCertificate = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -11,8 +11,6 @@ export type TCertificate = {
|
|||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
notBefore: string;
|
notBefore: string;
|
||||||
notAfter: string;
|
notAfter: string;
|
||||||
keyUsages: CertKeyUsage[];
|
|
||||||
extendedKeyUsages: CertExtendedKeyUsage[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TDeleteCertDTO = {
|
export type TDeleteCertDTO = {
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
export enum OrderByDirection {
|
|
||||||
ASC = "asc",
|
|
||||||
DESC = "desc"
|
|
||||||
}
|
|
@ -469,8 +469,3 @@ export type RevokeTokenDTO = {
|
|||||||
export type RevokeTokenRes = {
|
export type RevokeTokenRes = {
|
||||||
message: string;
|
message: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TProjectIdentitiesList = {
|
|
||||||
identityMemberships: IdentityMembership[];
|
|
||||||
totalCount: number;
|
|
||||||
};
|
|
||||||
|
@ -1 +1,6 @@
|
|||||||
export { useCreateIntegration, useDeleteIntegration, useGetCloudIntegrations } from "./queries";
|
export {
|
||||||
|
useCreateIntegration,
|
||||||
|
useDeleteIntegration,
|
||||||
|
useGetCloudIntegrations,
|
||||||
|
useGetIntegration
|
||||||
|
} from "./queries";
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient, UseQueryOptions } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
import { workspaceKeys } from "../workspace";
|
import { workspaceKeys } from "../workspace";
|
||||||
import { TCloudIntegration } from "./types";
|
import { TCloudIntegration, TIntegrationWithEnv } from "./types";
|
||||||
|
|
||||||
export const integrationQueryKeys = {
|
export const integrationQueryKeys = {
|
||||||
getIntegrations: () => ["integrations"] as const
|
getIntegrations: () => ["integrations"] as const,
|
||||||
|
getIntegration: (id: string) => ["integration", id] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchIntegrations = async () => {
|
const fetchIntegrations = async () => {
|
||||||
@ -18,6 +19,14 @@ const fetchIntegrations = async () => {
|
|||||||
return data.integrationOptions;
|
return data.integrationOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchIntegration = async (id: string) => {
|
||||||
|
const { data } = await apiRequest.get<{ integration: TIntegrationWithEnv }>(
|
||||||
|
`/api/v1/integration/${id}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return data.integration;
|
||||||
|
};
|
||||||
|
|
||||||
export const useGetCloudIntegrations = () =>
|
export const useGetCloudIntegrations = () =>
|
||||||
useQuery({
|
useQuery({
|
||||||
queryKey: integrationQueryKeys.getIntegrations(),
|
queryKey: integrationQueryKeys.getIntegrations(),
|
||||||
@ -128,6 +137,26 @@ export const useDeleteIntegration = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetIntegration = (
|
||||||
|
integrationId: string,
|
||||||
|
options?: Omit<
|
||||||
|
UseQueryOptions<
|
||||||
|
TIntegrationWithEnv,
|
||||||
|
unknown,
|
||||||
|
TIntegrationWithEnv,
|
||||||
|
ReturnType<typeof integrationQueryKeys.getIntegration>
|
||||||
|
>,
|
||||||
|
"queryFn" | "queryKey"
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
return useQuery({
|
||||||
|
...options,
|
||||||
|
enabled: Boolean(integrationId && options?.enabled === undefined ? true : options?.enabled),
|
||||||
|
queryKey: integrationQueryKeys.getIntegration(integrationId),
|
||||||
|
queryFn: () => fetchIntegration(integrationId)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useSyncIntegration = () => {
|
export const useSyncIntegration = () => {
|
||||||
return useMutation<{}, {}, { id: string; workspaceId: string; lastUsed: string }>({
|
return useMutation<{}, {}, { id: string; workspaceId: string; lastUsed: string }>({
|
||||||
mutationFn: ({ id }) => apiRequest.post(`/api/v1/integration/${id}/sync`),
|
mutationFn: ({ id }) => apiRequest.post(`/api/v1/integration/${id}/sync`),
|
||||||
|
@ -36,14 +36,34 @@ export type TIntegration = {
|
|||||||
metadata?: {
|
metadata?: {
|
||||||
githubVisibility?: string;
|
githubVisibility?: string;
|
||||||
githubVisibilityRepoIds?: string[];
|
githubVisibilityRepoIds?: string[];
|
||||||
|
shouldAutoRedeploy?: boolean;
|
||||||
|
secretAWSTag?: {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
kmsKeyId?: string;
|
||||||
secretSuffix?: string;
|
secretSuffix?: string;
|
||||||
|
secretPrefix?: string;
|
||||||
syncBehavior?: IntegrationSyncBehavior;
|
syncBehavior?: IntegrationSyncBehavior;
|
||||||
mappingBehavior?: IntegrationMappingBehavior;
|
mappingBehavior?: IntegrationMappingBehavior;
|
||||||
scope: string;
|
scope: string;
|
||||||
org: string;
|
org: string;
|
||||||
project: string;
|
project: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
|
|
||||||
|
shouldDisableDelete?: boolean;
|
||||||
|
shouldMaskSecrets?: boolean;
|
||||||
|
shouldProtectSecrets?: boolean;
|
||||||
|
shouldEnableDelete?: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIntegrationWithEnv = TIntegration & {
|
||||||
|
environment: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,22 +1,19 @@
|
|||||||
import { useMutation, useQuery, useQueryClient, UseQueryOptions } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
|
||||||
|
|
||||||
import { TGroupOrgMembership } from "../groups/types";
|
import { TGroupOrgMembership } from "../groups/types";
|
||||||
|
import { IdentityMembershipOrg } from "../identities/types";
|
||||||
import {
|
import {
|
||||||
BillingDetails,
|
BillingDetails,
|
||||||
Invoice,
|
Invoice,
|
||||||
License,
|
License,
|
||||||
Organization,
|
Organization,
|
||||||
OrgIdentityOrderBy,
|
|
||||||
OrgPlanTable,
|
OrgPlanTable,
|
||||||
PlanBillingInfo,
|
PlanBillingInfo,
|
||||||
PmtMethod,
|
PmtMethod,
|
||||||
ProductsTable,
|
ProductsTable,
|
||||||
TaxID,
|
TaxID,
|
||||||
TListOrgIdentitiesDTO,
|
|
||||||
TOrgIdentitiesList,
|
|
||||||
UpdateOrgDTO
|
UpdateOrgDTO
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
@ -33,12 +30,6 @@ export const organizationKeys = {
|
|||||||
getOrgLicenses: (orgId: string) => [{ orgId }, "organization-licenses"] as const,
|
getOrgLicenses: (orgId: string) => [{ orgId }, "organization-licenses"] as const,
|
||||||
getOrgIdentityMemberships: (orgId: string) =>
|
getOrgIdentityMemberships: (orgId: string) =>
|
||||||
[{ orgId }, "organization-identity-memberships"] as const,
|
[{ orgId }, "organization-identity-memberships"] as const,
|
||||||
// allows invalidation using above key without knowing params
|
|
||||||
getOrgIdentityMembershipsWithParams: ({
|
|
||||||
organizationId: orgId,
|
|
||||||
...params
|
|
||||||
}: TListOrgIdentitiesDTO) =>
|
|
||||||
[...organizationKeys.getOrgIdentityMemberships(orgId), params] as const,
|
|
||||||
getOrgGroups: (orgId: string) => [{ orgId }, "organization-groups"] as const
|
getOrgGroups: (orgId: string) => [{ orgId }, "organization-groups"] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -369,51 +360,19 @@ export const useGetOrgLicenses = (organizationId: string) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetIdentityMembershipOrgs = (
|
export const useGetIdentityMembershipOrgs = (organizationId: string) => {
|
||||||
{
|
|
||||||
organizationId,
|
|
||||||
offset = 0,
|
|
||||||
limit = 100,
|
|
||||||
orderBy = OrgIdentityOrderBy.Name,
|
|
||||||
orderDirection = OrderByDirection.ASC,
|
|
||||||
search = ""
|
|
||||||
}: TListOrgIdentitiesDTO,
|
|
||||||
options?: Omit<
|
|
||||||
UseQueryOptions<
|
|
||||||
TOrgIdentitiesList,
|
|
||||||
unknown,
|
|
||||||
TOrgIdentitiesList,
|
|
||||||
ReturnType<typeof organizationKeys.getOrgIdentityMembershipsWithParams>
|
|
||||||
>,
|
|
||||||
"queryKey" | "queryFn"
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
offset: String(offset),
|
|
||||||
limit: String(limit),
|
|
||||||
orderBy: String(orderBy),
|
|
||||||
orderDirection: String(orderDirection),
|
|
||||||
search: String(search)
|
|
||||||
});
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: organizationKeys.getOrgIdentityMembershipsWithParams({
|
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId),
|
||||||
organizationId,
|
|
||||||
offset,
|
|
||||||
limit,
|
|
||||||
orderBy,
|
|
||||||
orderDirection,
|
|
||||||
search
|
|
||||||
}),
|
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await apiRequest.get<TOrgIdentitiesList>(
|
const {
|
||||||
`/api/v2/organizations/${organizationId}/identity-memberships`,
|
data: { identityMemberships }
|
||||||
{ params }
|
} = await apiRequest.get<{ identityMemberships: IdentityMembershipOrg[] }>(
|
||||||
|
`/api/v2/organizations/${organizationId}/identity-memberships`
|
||||||
);
|
);
|
||||||
|
|
||||||
return data;
|
return identityMemberships;
|
||||||
},
|
},
|
||||||
enabled: true,
|
enabled: true
|
||||||
...options
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
|
||||||
import { IdentityMembershipOrg } from "@app/hooks/api/identities/types";
|
|
||||||
|
|
||||||
export type Organization = {
|
export type Organization = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -105,22 +102,3 @@ export type ProductsTable = {
|
|||||||
head: ProductsTableHead[];
|
head: ProductsTableHead[];
|
||||||
rows: ProductsTableRow[];
|
rows: ProductsTableRow[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TListOrgIdentitiesDTO = {
|
|
||||||
organizationId: string;
|
|
||||||
offset?: number;
|
|
||||||
limit?: number;
|
|
||||||
orderBy?: OrgIdentityOrderBy;
|
|
||||||
orderDirection?: OrderByDirection;
|
|
||||||
search?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TOrgIdentitiesList = {
|
|
||||||
identityMemberships: IdentityMembershipOrg[];
|
|
||||||
totalCount: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum OrgIdentityOrderBy {
|
|
||||||
Name = "name",
|
|
||||||
Role = "role"
|
|
||||||
}
|
|
||||||
|
@ -163,29 +163,27 @@ export const useGetImportedSecretsAllEnvs = ({
|
|||||||
queryFn: () => fetchImportedSecrets(projectId, env, path).catch(() => []),
|
queryFn: () => fetchImportedSecrets(projectId, env, path).catch(() => []),
|
||||||
enabled: Boolean(projectId) && Boolean(env),
|
enabled: Boolean(projectId) && Boolean(env),
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
select: useCallback(
|
select: (data: TImportedSecrets[]) => {
|
||||||
(data: Awaited<ReturnType<typeof fetchImportedSecrets>>) =>
|
return data.map((el) => ({
|
||||||
data.map((el) => ({
|
environment: el.environment,
|
||||||
environment: el.environment,
|
secretPath: el.secretPath,
|
||||||
secretPath: el.secretPath,
|
environmentInfo: el.environmentInfo,
|
||||||
environmentInfo: el.environmentInfo,
|
folderId: el.folderId,
|
||||||
folderId: el.folderId,
|
secrets: el.secrets.map((encSecret) => {
|
||||||
secrets: el.secrets.map((encSecret) => {
|
return {
|
||||||
return {
|
id: encSecret.id,
|
||||||
id: encSecret.id,
|
env: encSecret.environment,
|
||||||
env: encSecret.environment,
|
key: encSecret.secretKey,
|
||||||
key: encSecret.secretKey,
|
value: encSecret.secretValue,
|
||||||
value: encSecret.secretValue,
|
tags: encSecret.tags,
|
||||||
tags: encSecret.tags,
|
comment: encSecret.secretComment,
|
||||||
comment: encSecret.secretComment,
|
createdAt: encSecret.createdAt,
|
||||||
createdAt: encSecret.createdAt,
|
updatedAt: encSecret.updatedAt,
|
||||||
updatedAt: encSecret.updatedAt,
|
version: encSecret.version
|
||||||
version: encSecret.version
|
};
|
||||||
};
|
})
|
||||||
})
|
}));
|
||||||
})),
|
}
|
||||||
[]
|
|
||||||
)
|
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ export const useGetProjectSecrets = ({
|
|||||||
// wait for all values to be available
|
// wait for all values to be available
|
||||||
enabled: Boolean(workspaceId && environment) && (options?.enabled ?? true),
|
enabled: Boolean(workspaceId && environment) && (options?.enabled ?? true),
|
||||||
queryKey: secretKeys.getProjectSecret({ workspaceId, environment, secretPath }),
|
queryKey: secretKeys.getProjectSecret({ workspaceId, environment, secretPath }),
|
||||||
queryFn: () => fetchProjectSecrets({ workspaceId, environment, secretPath }),
|
queryFn: async () => fetchProjectSecrets({ workspaceId, environment, secretPath }),
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
if (axios.isAxiosError(error)) {
|
if (axios.isAxiosError(error)) {
|
||||||
const serverResponse = error.response?.data as { message: string };
|
const serverResponse = error.response?.data as { message: string };
|
||||||
@ -119,10 +119,7 @@ export const useGetProjectSecrets = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
select: useCallback(
|
select: ({ secrets }) => mergePersonalSecrets(secrets)
|
||||||
(data: Awaited<ReturnType<typeof fetchProjectSecrets>>) => mergePersonalSecrets(data.secrets),
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useGetProjectSecretsAllEnv = ({
|
export const useGetProjectSecretsAllEnv = ({
|
||||||
@ -134,11 +131,7 @@ export const useGetProjectSecretsAllEnv = ({
|
|||||||
|
|
||||||
const secrets = useQueries({
|
const secrets = useQueries({
|
||||||
queries: envs.map((environment) => ({
|
queries: envs.map((environment) => ({
|
||||||
queryKey: secretKeys.getProjectSecret({
|
queryKey: secretKeys.getProjectSecret({ workspaceId, environment, secretPath }),
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
secretPath
|
|
||||||
}),
|
|
||||||
enabled: Boolean(workspaceId && environment),
|
enabled: Boolean(workspaceId && environment),
|
||||||
onError: (error: unknown) => {
|
onError: (error: unknown) => {
|
||||||
if (axios.isAxiosError(error) && !isErrorHandled) {
|
if (axios.isAxiosError(error) && !isErrorHandled) {
|
||||||
@ -154,17 +147,12 @@ export const useGetProjectSecretsAllEnv = ({
|
|||||||
setIsErrorHandled.on();
|
setIsErrorHandled.on();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
queryFn: () => fetchProjectSecrets({ workspaceId, environment, secretPath }),
|
queryFn: async () => fetchProjectSecrets({ workspaceId, environment, secretPath }),
|
||||||
staleTime: 60 * 1000,
|
select: (el: SecretV3RawResponse) =>
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
mergePersonalSecrets(el.secrets).reduce<Record<string, SecretV3RawSanitized>>(
|
||||||
select: useCallback(
|
(prev, curr) => ({ ...prev, [curr.key]: curr }),
|
||||||
(data: Awaited<ReturnType<typeof fetchProjectSecrets>>) =>
|
{}
|
||||||
mergePersonalSecrets(data.secrets).reduce<Record<string, SecretV3RawSanitized>>(
|
)
|
||||||
(prev, curr) => ({ ...prev, [curr.key]: curr }),
|
|
||||||
{}
|
|
||||||
),
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { useMutation, useQuery, useQueryClient, UseQueryOptions } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
|
||||||
|
|
||||||
import { CaStatus } from "../ca/enums";
|
import { CaStatus } from "../ca/enums";
|
||||||
import { TCertificateAuthority } from "../ca/types";
|
import { TCertificateAuthority } from "../ca/types";
|
||||||
@ -9,7 +8,7 @@ import { TCertificate } from "../certificates/types";
|
|||||||
import { TCertificateTemplate } from "../certificateTemplates/types";
|
import { TCertificateTemplate } from "../certificateTemplates/types";
|
||||||
import { TGroupMembership } from "../groups/types";
|
import { TGroupMembership } from "../groups/types";
|
||||||
import { identitiesKeys } from "../identities/queries";
|
import { identitiesKeys } from "../identities/queries";
|
||||||
import { TProjectIdentitiesList } from "../identities/types";
|
import { IdentityMembership } from "../identities/types";
|
||||||
import { IntegrationAuth } from "../integrationAuth/types";
|
import { IntegrationAuth } from "../integrationAuth/types";
|
||||||
import { TIntegration } from "../integrations/types";
|
import { TIntegration } from "../integrations/types";
|
||||||
import { TPkiAlert } from "../pkiAlerts/types";
|
import { TPkiAlert } from "../pkiAlerts/types";
|
||||||
@ -25,10 +24,8 @@ import {
|
|||||||
DeleteEnvironmentDTO,
|
DeleteEnvironmentDTO,
|
||||||
DeleteWorkspaceDTO,
|
DeleteWorkspaceDTO,
|
||||||
NameWorkspaceSecretsDTO,
|
NameWorkspaceSecretsDTO,
|
||||||
ProjectIdentityOrderBy,
|
|
||||||
RenameWorkspaceDTO,
|
RenameWorkspaceDTO,
|
||||||
TGetUpgradeProjectStatusDTO,
|
TGetUpgradeProjectStatusDTO,
|
||||||
TListProjectIdentitiesDTO,
|
|
||||||
ToggleAutoCapitalizationDTO,
|
ToggleAutoCapitalizationDTO,
|
||||||
TUpdateWorkspaceIdentityRoleDTO,
|
TUpdateWorkspaceIdentityRoleDTO,
|
||||||
TUpdateWorkspaceUserRoleDTO,
|
TUpdateWorkspaceUserRoleDTO,
|
||||||
@ -487,51 +484,18 @@ export const useDeleteIdentityFromWorkspace = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetWorkspaceIdentityMemberships = (
|
export const useGetWorkspaceIdentityMemberships = (workspaceId: string) => {
|
||||||
{
|
|
||||||
workspaceId,
|
|
||||||
offset = 0,
|
|
||||||
limit = 100,
|
|
||||||
orderBy = ProjectIdentityOrderBy.Name,
|
|
||||||
orderDirection = OrderByDirection.ASC,
|
|
||||||
search = ""
|
|
||||||
}: TListProjectIdentitiesDTO,
|
|
||||||
options?: Omit<
|
|
||||||
UseQueryOptions<
|
|
||||||
TProjectIdentitiesList,
|
|
||||||
unknown,
|
|
||||||
TProjectIdentitiesList,
|
|
||||||
ReturnType<typeof workspaceKeys.getWorkspaceIdentityMembershipsWithParams>
|
|
||||||
>,
|
|
||||||
"queryKey" | "queryFn"
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: workspaceKeys.getWorkspaceIdentityMembershipsWithParams({
|
queryKey: workspaceKeys.getWorkspaceIdentityMemberships(workspaceId),
|
||||||
workspaceId,
|
|
||||||
offset,
|
|
||||||
limit,
|
|
||||||
orderBy,
|
|
||||||
orderDirection,
|
|
||||||
search
|
|
||||||
}),
|
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const params = new URLSearchParams({
|
const {
|
||||||
offset: String(offset),
|
data: { identityMemberships }
|
||||||
limit: String(limit),
|
} = await apiRequest.get<{ identityMemberships: IdentityMembership[] }>(
|
||||||
orderBy: String(orderBy),
|
`/api/v2/workspace/${workspaceId}/identity-memberships`
|
||||||
orderDirection: String(orderDirection),
|
|
||||||
search: String(search)
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data } = await apiRequest.get<TProjectIdentitiesList>(
|
|
||||||
`/api/v2/workspace/${workspaceId}/identity-memberships`,
|
|
||||||
{ params }
|
|
||||||
);
|
);
|
||||||
return data;
|
return identityMemberships;
|
||||||
},
|
},
|
||||||
enabled: true,
|
enabled: true
|
||||||
...options
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import { TListProjectIdentitiesDTO } from "@app/hooks/api/workspace/types";
|
|
||||||
|
|
||||||
import type { CaStatus } from "../ca";
|
import type { CaStatus } from "../ca";
|
||||||
|
|
||||||
export const workspaceKeys = {
|
export const workspaceKeys = {
|
||||||
@ -17,12 +15,6 @@ export const workspaceKeys = {
|
|||||||
getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }, "workspace-users"] as const,
|
getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }, "workspace-users"] as const,
|
||||||
getWorkspaceIdentityMemberships: (workspaceId: string) =>
|
getWorkspaceIdentityMemberships: (workspaceId: string) =>
|
||||||
[{ workspaceId }, "workspace-identity-memberships"] as const,
|
[{ workspaceId }, "workspace-identity-memberships"] as const,
|
||||||
// allows invalidation using above key without knowing params
|
|
||||||
getWorkspaceIdentityMembershipsWithParams: ({
|
|
||||||
workspaceId,
|
|
||||||
...params
|
|
||||||
}: TListProjectIdentitiesDTO) =>
|
|
||||||
[...workspaceKeys.getWorkspaceIdentityMemberships(workspaceId), params] as const,
|
|
||||||
getWorkspaceGroupMemberships: (workspaceId: string) =>
|
getWorkspaceGroupMemberships: (workspaceId: string) =>
|
||||||
[{ workspaceId }, "workspace-groups"] as const,
|
[{ workspaceId }, "workspace-groups"] as const,
|
||||||
getWorkspaceCas: ({ projectSlug }: { projectSlug: string }) =>
|
getWorkspaceCas: ({ projectSlug }: { projectSlug: string }) =>
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
|
||||||
|
|
||||||
import { TProjectRole } from "../roles/types";
|
import { TProjectRole } from "../roles/types";
|
||||||
|
|
||||||
export enum ProjectVersion {
|
export enum ProjectVersion {
|
||||||
@ -143,16 +141,3 @@ export type TUpdateWorkspaceGroupRoleDTO = {
|
|||||||
}
|
}
|
||||||
)[];
|
)[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TListProjectIdentitiesDTO = {
|
|
||||||
workspaceId: string;
|
|
||||||
offset?: number;
|
|
||||||
limit?: number;
|
|
||||||
orderBy?: ProjectIdentityOrderBy;
|
|
||||||
orderDirection?: OrderByDirection;
|
|
||||||
search?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum ProjectIdentityOrderBy {
|
|
||||||
Name = "name"
|
|
||||||
}
|
|
||||||
|
@ -104,7 +104,6 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
const [tagKey, setTagKey] = useState("");
|
const [tagKey, setTagKey] = useState("");
|
||||||
const [tagValue, setTagValue] = useState("");
|
const [tagValue, setTagValue] = useState("");
|
||||||
const [kmsKeyId, setKmsKeyId] = useState("");
|
const [kmsKeyId, setKmsKeyId] = useState("");
|
||||||
const [secretPrefix, setSecretPrefix] = useState("");
|
|
||||||
|
|
||||||
// const [path, setPath] = useState('');
|
// const [path, setPath] = useState('');
|
||||||
// const [pathErrorText, setPathErrorText] = useState('');
|
// const [pathErrorText, setPathErrorText] = useState('');
|
||||||
@ -166,7 +165,6 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...(secretPrefix && { secretPrefix }),
|
|
||||||
...(kmsKeyId && { kmsKeyId }),
|
...(kmsKeyId && { kmsKeyId }),
|
||||||
mappingBehavior: selectedMappingBehavior
|
mappingBehavior: selectedMappingBehavior
|
||||||
}
|
}
|
||||||
@ -327,7 +325,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
{shouldTag && (
|
{shouldTag && (
|
||||||
<div className="mt-4 flex justify-between">
|
<div className="mt-4">
|
||||||
<FormControl label="Tag Key">
|
<FormControl label="Tag Key">
|
||||||
<Input
|
<Input
|
||||||
placeholder="managed-by"
|
placeholder="managed-by"
|
||||||
@ -344,20 +342,10 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FormControl label="Secret Prefix" className="mt-4">
|
|
||||||
<Input
|
|
||||||
value={secretPrefix}
|
|
||||||
onChange={(e) => setSecretPrefix(e.target.value)}
|
|
||||||
placeholder="INFISICAL_"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormControl label="Encryption Key" className="mt-4">
|
<FormControl label="Encryption Key" className="mt-4">
|
||||||
<Select
|
<Select
|
||||||
value={kmsKeyId}
|
value={kmsKeyId}
|
||||||
onValueChange={(e) => {
|
onValueChange={(e) => {
|
||||||
if (e === "no-keys") return;
|
|
||||||
setKmsKeyId(e);
|
setKmsKeyId(e);
|
||||||
}}
|
}}
|
||||||
className="w-full border border-mineshaft-500"
|
className="w-full border border-mineshaft-500"
|
||||||
@ -375,9 +363,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
) : (
|
) : (
|
||||||
<SelectItem isDisabled value="no-keys" key="no-keys">
|
<div />
|
||||||
No KMS keys available
|
|
||||||
</SelectItem>
|
|
||||||
)}
|
)}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
23
frontend/src/pages/integrations/details/[integrationId].tsx
Normal file
23
frontend/src/pages/integrations/details/[integrationId].tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Head from "next/head";
|
||||||
|
|
||||||
|
import { IntegrationDetailsPage } from "@app/views/IntegrationsPage/IntegrationDetailsPage";
|
||||||
|
|
||||||
|
export default function IntegrationsDetailsPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>Integration Details | Infisical</title>
|
||||||
|
<link rel="icon" href="/infisical.ico" />
|
||||||
|
<meta property="og:image" content="/images/message.png" />
|
||||||
|
<meta property="og:title" content="Manage your .env files in seconds" />
|
||||||
|
<meta name="og:description" content={t("integrations.description") as string} />
|
||||||
|
</Head>
|
||||||
|
<IntegrationDetailsPage />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
IntegrationsDetailsPage.requireAuth = true;
|
@ -0,0 +1,120 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { faChevronLeft, faEllipsis, faRefresh, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { integrationSlugNameMapping } from "public/data/frequentConstants";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
Tooltip
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import {
|
||||||
|
OrgPermissionActions,
|
||||||
|
OrgPermissionSubjects,
|
||||||
|
useOrganization,
|
||||||
|
useUser,
|
||||||
|
useWorkspace
|
||||||
|
} from "@app/context";
|
||||||
|
import { useGetIntegration } from "@app/hooks/api";
|
||||||
|
import { useSyncIntegration } from "@app/hooks/api/integrations/queries";
|
||||||
|
|
||||||
|
import { IntegrationAuditLogsSection } from "./components/IntegrationAuditLogsSection";
|
||||||
|
import { IntegrationConnectionSection } from "./components/IntegrationConnectionSection";
|
||||||
|
import { IntegrationDetailsSection } from "./components/IntegrationDetailsSection";
|
||||||
|
import { IntegrationSettingsSection } from "./components/IntegrationSettingsSection";
|
||||||
|
|
||||||
|
export const IntegrationDetailsPage = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const integrationId = router.query.integrationId as string;
|
||||||
|
|
||||||
|
const { data: integration } = useGetIntegration(integrationId, {
|
||||||
|
refetchInterval: 4000
|
||||||
|
});
|
||||||
|
|
||||||
|
const projectId = useWorkspace().currentWorkspace?.id;
|
||||||
|
const { mutateAsync: syncIntegration } = useSyncIntegration();
|
||||||
|
const { currentOrg } = useOrganization();
|
||||||
|
|
||||||
|
return integration ? (
|
||||||
|
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||||
|
<div className="mx-auto mb-6 w-full max-w-7xl py-6 px-6">
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
type="submit"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||||
|
onClick={() => {
|
||||||
|
router.push(`/integrations/${projectId}`);
|
||||||
|
}}
|
||||||
|
className="mb-4"
|
||||||
|
>
|
||||||
|
Integrations
|
||||||
|
</Button>
|
||||||
|
<div className="mb-4 flex items-center justify-between">
|
||||||
|
<p className="text-3xl font-semibold text-white">
|
||||||
|
{integrationSlugNameMapping[integration.integration]} Integration
|
||||||
|
</p>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||||
|
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||||
|
<Tooltip content="More options">
|
||||||
|
<FontAwesomeIcon size="sm" icon={faEllipsis} />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={async () => {
|
||||||
|
await syncIntegration({
|
||||||
|
id: integration.id,
|
||||||
|
lastUsed: integration.lastUsed!,
|
||||||
|
workspaceId: projectId!
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<FontAwesomeIcon icon={faRefresh} />
|
||||||
|
Manually Sync
|
||||||
|
</div>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<OrgPermissionCan I={OrgPermissionActions.Delete} a={OrgPermissionSubjects.Member}>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className={twMerge(
|
||||||
|
isAllowed
|
||||||
|
? "hover:!bg-red-500 hover:!text-white"
|
||||||
|
: "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
|
)}
|
||||||
|
onClick={() => {}}
|
||||||
|
disabled={!isAllowed}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<FontAwesomeIcon icon={faTrash} />
|
||||||
|
Delete Integration
|
||||||
|
</div>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<div className="mr-4 w-96">
|
||||||
|
<IntegrationDetailsSection integration={integration} />
|
||||||
|
<IntegrationConnectionSection integration={integration} />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<IntegrationSettingsSection integration={integration} />
|
||||||
|
<IntegrationAuditLogsSection orgId={currentOrg?.id || ""} integration={integration} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
};
|
@ -0,0 +1,78 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import { EmptyState } from "@app/components/v2";
|
||||||
|
import { useSubscription } from "@app/context";
|
||||||
|
import { EventType } from "@app/hooks/api/auditLogs/enums";
|
||||||
|
import { TIntegrationWithEnv } from "@app/hooks/api/integrations/types";
|
||||||
|
import { LogsSection } from "@app/views/Project/AuditLogsPage/components";
|
||||||
|
|
||||||
|
// Add more events if needed
|
||||||
|
const INTEGRATION_EVENTS = [EventType.INTEGRATION_SYNCED];
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
integration: TIntegrationWithEnv;
|
||||||
|
orgId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IntegrationAuditLogsSection = ({ integration, orgId }: Props) => {
|
||||||
|
const { subscription, isLoading } = useSubscription();
|
||||||
|
|
||||||
|
const auditLogsRetentionDays = subscription?.auditLogsRetentionDays ?? 30;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-nested-ternary
|
||||||
|
return subscription?.auditLogs ? (
|
||||||
|
<div className="h-full w-full min-w-[51rem] rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="mb-4 flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||||
|
<p className="text-lg font-semibold text-gray-200">Integration Logs</p>
|
||||||
|
<p className="text-xs text-gray-400">
|
||||||
|
Displaying audit logs from the last {auditLogsRetentionDays} days
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<LogsSection
|
||||||
|
refetchInterval={4000}
|
||||||
|
remappedHeaders={{
|
||||||
|
Metadata: "Sync Status"
|
||||||
|
}}
|
||||||
|
showFilters={false}
|
||||||
|
presets={{
|
||||||
|
eventMetadata: { integrationId: integration.id },
|
||||||
|
startDate: new Date(new Date().setDate(new Date().getDate() - auditLogsRetentionDays)),
|
||||||
|
eventType: INTEGRATION_EVENTS
|
||||||
|
}}
|
||||||
|
filterClassName="bg-mineshaft-900 static"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : !isLoading ? (
|
||||||
|
<div className="h-full w-full min-w-[51rem] rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4 opacity-60">
|
||||||
|
<div className="mb-4 flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||||
|
<p className="text-lg font-semibold text-gray-200">Integration Logs</p>
|
||||||
|
</div>
|
||||||
|
<EmptyState
|
||||||
|
className="rounded-lg"
|
||||||
|
title={
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Please{" "}
|
||||||
|
<Link
|
||||||
|
href={
|
||||||
|
subscription && subscription.slug !== null
|
||||||
|
? `/org${orgId}/billing`
|
||||||
|
: "https://infisical.com/scheduledemo"
|
||||||
|
}
|
||||||
|
passHref
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
className="cursor-pointer font-medium text-primary-500 transition-all hover:text-primary-600"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
upgrade your subscription
|
||||||
|
</a>
|
||||||
|
</Link>{" "}
|
||||||
|
to view integration logs
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
};
|
@ -0,0 +1,194 @@
|
|||||||
|
import { integrationSlugNameMapping } from "public/data/frequentConstants";
|
||||||
|
|
||||||
|
import { FormLabel } from "@app/components/v2";
|
||||||
|
import { IntegrationMappingBehavior, TIntegrationWithEnv } from "@app/hooks/api/integrations/types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
integration: TIntegrationWithEnv;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IntegrationConnectionSection = ({ integration }: Props) => {
|
||||||
|
const specifcQoveryDetails = () => {
|
||||||
|
if (integration.integration !== "qovery") return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Organization" />
|
||||||
|
<div className="text-sm text-mineshaft-300">{integration?.owner || "-"}</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Project" />
|
||||||
|
<div className="text-sm text-mineshaft-300">{integration?.targetService || "-"}</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<FormLabel
|
||||||
|
className="text-sm font-semibold text-mineshaft-300"
|
||||||
|
label="Target Environment"
|
||||||
|
/>
|
||||||
|
<div className="text-sm text-mineshaft-300">{integration?.targetEnvironment || "-"}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isNotAwsManagerOneToOneDetails = () => {
|
||||||
|
const isAwsSecretManagerOneToOne =
|
||||||
|
integration.integration === "aws-secret-manager" &&
|
||||||
|
integration.metadata?.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE;
|
||||||
|
|
||||||
|
if (isAwsSecretManagerOneToOne) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formLabel = () => {
|
||||||
|
switch (integration.integration) {
|
||||||
|
case "qovery":
|
||||||
|
return integration.scope;
|
||||||
|
case "circleci":
|
||||||
|
case "terraform-cloud":
|
||||||
|
return "Project";
|
||||||
|
case "aws-secret-manager":
|
||||||
|
return "Secret";
|
||||||
|
case "aws-parameter-store":
|
||||||
|
case "rundeck":
|
||||||
|
return "Path";
|
||||||
|
case "github":
|
||||||
|
if (["github-env", "github-repo"].includes(integration.scope!)) {
|
||||||
|
return "Repository";
|
||||||
|
}
|
||||||
|
return "Organization";
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "App";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const contents = () => {
|
||||||
|
switch (integration.integration) {
|
||||||
|
case "hashicorp-vault":
|
||||||
|
return `${integration.app} - path: ${integration.path}`;
|
||||||
|
case "github":
|
||||||
|
if (integration.scope === "github-org") {
|
||||||
|
return `${integration.owner}`;
|
||||||
|
}
|
||||||
|
return `${integration.owner}/${integration.app}`;
|
||||||
|
|
||||||
|
case "aws-parameter-store":
|
||||||
|
case "rundeck":
|
||||||
|
return `${integration.path}`;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return `${integration.app}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label={formLabel()} />
|
||||||
|
<div className="text-sm text-mineshaft-300">{contents()}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const targetEnvironmentDetails = () => {
|
||||||
|
if (
|
||||||
|
["vercel", "netlify", "railway", "gitlab", "teamcity", "bitbucket"].includes(
|
||||||
|
integration.integration
|
||||||
|
) ||
|
||||||
|
(integration.integration === "github" && integration.scope === "github-env")
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<FormLabel
|
||||||
|
className="text-sm font-semibold text-mineshaft-300"
|
||||||
|
label="Target Environment"
|
||||||
|
/>
|
||||||
|
<div className="text-sm text-mineshaft-300">
|
||||||
|
{integration.targetEnvironment || integration.targetEnvironmentId}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generalIntegrationSpecificDetails = () => {
|
||||||
|
if (integration.integration === "checkly" && integration.targetService) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Group" />
|
||||||
|
<div className="text-sm text-mineshaft-300">{integration.targetService}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (integration.integration === "circleci" && integration.owner) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Organization" />
|
||||||
|
<div className="text-sm text-mineshaft-300">{integration.owner}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (integration.integration === "terraform-cloud" && integration.targetService) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Category" />
|
||||||
|
<div className="text-sm text-mineshaft-300">{integration.targetService}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (integration.integration === "checkly" || integration.integration === "github") {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Secret Suffix" />
|
||||||
|
<div className="text-sm text-mineshaft-300">
|
||||||
|
{integration?.metadata?.secretSuffix || "-"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||||
|
<h3 className="text-lg font-semibold text-mineshaft-100">Connection</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4">
|
||||||
|
<FormLabel className="my-2" label="Source" />
|
||||||
|
|
||||||
|
<div className="space-y-2 rounded-lg border border-mineshaft-700 bg-mineshaft-800 p-2">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Environment" />
|
||||||
|
<div className="text-sm text-mineshaft-300">{integration.environment.name}</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Secret Path" />
|
||||||
|
<div className="text-sm text-mineshaft-300">{integration.secretPath}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormLabel className="my-2" label="Destination" />
|
||||||
|
<div className="space-y-2 rounded-lg border border-mineshaft-700 bg-mineshaft-800 p-2">
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Platform" />
|
||||||
|
<div className="text-sm text-mineshaft-300">
|
||||||
|
{integrationSlugNameMapping[integration.integration]}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{specifcQoveryDetails()}
|
||||||
|
{isNotAwsManagerOneToOneDetails()}
|
||||||
|
{targetEnvironmentDetails()}
|
||||||
|
{generalIntegrationSpecificDetails()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,69 @@
|
|||||||
|
import { faCalendarCheck, faCheckCircle, faCircleXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { integrationSlugNameMapping } from "public/data/frequentConstants";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { TIntegrationWithEnv } from "@app/hooks/api/integrations/types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
integration: TIntegrationWithEnv;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IntegrationDetailsSection = ({ integration }: Props) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||||
|
<h3 className="text-lg font-semibold text-mineshaft-100">Integration Details</h3>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-semibold text-mineshaft-300">Name</p>
|
||||||
|
<p className="text-sm text-mineshaft-300">
|
||||||
|
{integrationSlugNameMapping[integration.integration]}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-semibold text-mineshaft-300">Sync Status</p>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<p
|
||||||
|
className={twMerge(
|
||||||
|
"mr-2 text-sm font-medium",
|
||||||
|
integration.isSynced ? "text-green-500" : "text-red-500"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{integration.isSynced ? "Synced" : "Not Synced"}
|
||||||
|
</p>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
size="sm"
|
||||||
|
className={twMerge(integration.isSynced ? "text-green-500" : "text-red-500")}
|
||||||
|
icon={integration.isSynced ? faCheckCircle : faCircleXmark}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{integration.lastUsed && (
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-semibold text-mineshaft-300">Latest Successful Sync</p>
|
||||||
|
<div className="flex items-center gap-2 text-sm text-mineshaft-300">
|
||||||
|
{format(new Date(integration.lastUsed), "yyyy-MM-dd, hh:mm aaa")}
|
||||||
|
<FontAwesomeIcon icon={faCalendarCheck} className="pt-0.5 pr-2 text-sm" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{!integration.isSynced && integration.syncMessage && (
|
||||||
|
<>
|
||||||
|
<p className="text-sm font-semibold text-mineshaft-300">Latest Sync Error</p>
|
||||||
|
<p className="text-sm text-mineshaft-300">{integration.syncMessage}</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,89 @@
|
|||||||
|
import { TIntegrationWithEnv } from "@app/hooks/api/integrations/types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
integration: TIntegrationWithEnv;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Metadata = NonNullable<TIntegrationWithEnv["metadata"]>;
|
||||||
|
type MetadataKey = keyof Metadata;
|
||||||
|
type MetadataValue<K extends MetadataKey> = Metadata[K];
|
||||||
|
|
||||||
|
const metadataMappings: Record<keyof NonNullable<TIntegrationWithEnv["metadata"]>, string> = {
|
||||||
|
githubVisibility: "Github Visibility",
|
||||||
|
githubVisibilityRepoIds: "Github Visibility Repo Ids",
|
||||||
|
shouldAutoRedeploy: "Auto Redeploy Target Application When Secrets Change",
|
||||||
|
secretAWSTag: "Tags For Secrets Stored In AWS",
|
||||||
|
kmsKeyId: "AWS KMS Key ID",
|
||||||
|
secretSuffix: "Secret Suffix",
|
||||||
|
secretPrefix: "Secret Prefix",
|
||||||
|
syncBehavior: "Secrets Sync behavior",
|
||||||
|
mappingBehavior: "Secrets Mapping Behavior",
|
||||||
|
scope: "Scope",
|
||||||
|
org: "Organization",
|
||||||
|
project: "Project",
|
||||||
|
environment: "Environment",
|
||||||
|
shouldDisableDelete: "AWS Secret Deletion Disabled",
|
||||||
|
shouldMaskSecrets: "GitLab Secrets Masking Enabled",
|
||||||
|
shouldProtectSecrets: "GitLab Secret Protection Enabled",
|
||||||
|
shouldEnableDelete: "GitHub Secret Deletion Enabled"
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const IntegrationSettingsSection = ({ integration }: Props) => {
|
||||||
|
const renderValue = <K extends MetadataKey>(key: K, value: MetadataValue<K>) => {
|
||||||
|
if (!value) return null;
|
||||||
|
|
||||||
|
// If it's a boolean, we render a generic "Yes" or "No" response.
|
||||||
|
if (typeof value === "boolean") {
|
||||||
|
return value ? "Yes" : "No";
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the value is an object or array, or array of objects, we need to handle some special cases.
|
||||||
|
if (typeof value === "object") {
|
||||||
|
if (key === "secretAWSTag") {
|
||||||
|
return (value as MetadataValue<"secretAWSTag">)!.map(({ key: tagKey, value: tagValue }) => (
|
||||||
|
<p key={tagKey} className="text-sm text-gray-200">
|
||||||
|
{tagKey}={tagValue}
|
||||||
|
</p>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === "githubVisibilityRepoIds") {
|
||||||
|
return value.join(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === "string") {
|
||||||
|
return value.length ? value : "N/A";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === "number") {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!integration.metadata || Object.keys(integration.metadata).length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-nested-ternary
|
||||||
|
return (
|
||||||
|
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="mb-4 flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||||
|
<p className="text-lg font-semibold text-gray-200">Integration Settings</p>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
{integration.metadata &&
|
||||||
|
Object.entries(integration.metadata).map(([key, value]) => (
|
||||||
|
<div key={key} className="flex flex-col">
|
||||||
|
<p className="text-sm text-gray-400">
|
||||||
|
{metadataMappings[key as keyof typeof metadataMappings]}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-200">{renderValue(key as MetadataKey, value)}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user