mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat: changed from token to headers for audit log streams api
This commit is contained in:
@@ -8,11 +8,11 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
await knex.schema.createTable(TableName.AuditLogStream, (t) => {
|
await knex.schema.createTable(TableName.AuditLogStream, (t) => {
|
||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
t.string("url").notNullable();
|
t.string("url").notNullable();
|
||||||
t.text("encryptedTokenCiphertext");
|
t.text("encryptedHeadersCiphertext");
|
||||||
t.text("encryptedTokenIV");
|
t.text("encryptedHeadersIV");
|
||||||
t.text("encryptedTokenTag");
|
t.text("encryptedHeadersTag");
|
||||||
t.string("encryptedTokenAlgorithm");
|
t.string("encryptedHeadersAlgorithm");
|
||||||
t.string("encryptedTokenKeyEncoding");
|
t.string("encryptedHeadersKeyEncoding");
|
||||||
t.uuid("orgId").notNullable();
|
t.uuid("orgId").notNullable();
|
||||||
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
t.timestamps(true, true, true);
|
t.timestamps(true, true, true);
|
||||||
@@ -10,11 +10,11 @@ import { TImmutableDBKeys } from "./models";
|
|||||||
export const AuditLogStreamsSchema = z.object({
|
export const AuditLogStreamsSchema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
url: z.string(),
|
url: z.string(),
|
||||||
encryptedTokenCiphertext: z.string().nullable().optional(),
|
encryptedHeadersCiphertext: z.string().nullable().optional(),
|
||||||
encryptedTokenIV: z.string().nullable().optional(),
|
encryptedHeadersIV: z.string().nullable().optional(),
|
||||||
encryptedTokenTag: z.string().nullable().optional(),
|
encryptedHeadersTag: z.string().nullable().optional(),
|
||||||
encryptedTokenAlgorithm: z.string().nullable().optional(),
|
encryptedHeadersAlgorithm: z.string().nullable().optional(),
|
||||||
encryptedTokenKeyEncoding: z.string().nullable().optional(),
|
encryptedHeadersKeyEncoding: z.string().nullable().optional(),
|
||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date()
|
||||||
|
|||||||
@@ -22,7 +22,14 @@ export const registerAuditLogStreamRouter = async (server: FastifyZodProvider) =
|
|||||||
],
|
],
|
||||||
body: z.object({
|
body: z.object({
|
||||||
url: z.string().min(1).describe(AUDIT_LOG_STREAMS.CREATE.url),
|
url: z.string().min(1).describe(AUDIT_LOG_STREAMS.CREATE.url),
|
||||||
token: z.string().optional().describe(AUDIT_LOG_STREAMS.CREATE.token)
|
headers: z
|
||||||
|
.object({
|
||||||
|
key: z.string().min(1).trim().describe(AUDIT_LOG_STREAMS.CREATE.headers.key),
|
||||||
|
value: z.string().min(1).trim().describe(AUDIT_LOG_STREAMS.CREATE.headers.value)
|
||||||
|
})
|
||||||
|
.describe(AUDIT_LOG_STREAMS.CREATE.headers.desc)
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -38,7 +45,7 @@ export const registerAuditLogStreamRouter = async (server: FastifyZodProvider) =
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
url: req.body.url,
|
url: req.body.url,
|
||||||
token: req.body.token
|
headers: req.body.headers
|
||||||
});
|
});
|
||||||
|
|
||||||
return { auditLogStream };
|
return { auditLogStream };
|
||||||
@@ -63,7 +70,14 @@ export const registerAuditLogStreamRouter = async (server: FastifyZodProvider) =
|
|||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
url: z.string().optional().describe(AUDIT_LOG_STREAMS.UPDATE.url),
|
url: z.string().optional().describe(AUDIT_LOG_STREAMS.UPDATE.url),
|
||||||
token: z.string().optional().describe(AUDIT_LOG_STREAMS.UPDATE.token)
|
headers: z
|
||||||
|
.object({
|
||||||
|
key: z.string().min(1).trim().describe(AUDIT_LOG_STREAMS.UPDATE.headers.key),
|
||||||
|
value: z.string().min(1).trim().describe(AUDIT_LOG_STREAMS.UPDATE.headers.value)
|
||||||
|
})
|
||||||
|
.describe(AUDIT_LOG_STREAMS.UPDATE.headers.desc)
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -80,7 +94,7 @@ export const registerAuditLogStreamRouter = async (server: FastifyZodProvider) =
|
|||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
id: req.params.id,
|
id: req.params.id,
|
||||||
url: req.body.url,
|
url: req.body.url,
|
||||||
token: req.body.token
|
headers: req.body.headers
|
||||||
});
|
});
|
||||||
|
|
||||||
return { auditLogStream };
|
return { auditLogStream };
|
||||||
@@ -141,7 +155,15 @@ export const registerAuditLogStreamRouter = async (server: FastifyZodProvider) =
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
auditLogStream: SanitizedAuditLogStreamSchema.extend({ token: z.string().optional() })
|
auditLogStream: SanitizedAuditLogStreamSchema.extend({
|
||||||
|
headers: z
|
||||||
|
.object({
|
||||||
|
key: z.string(),
|
||||||
|
value: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-p
|
|||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { TAuditLogStreamDALFactory } from "./audit-log-stream-dal";
|
import { TAuditLogStreamDALFactory } from "./audit-log-stream-dal";
|
||||||
import {
|
import {
|
||||||
|
LogStreamHeaders,
|
||||||
TCreateAuditLogStreamDTO,
|
TCreateAuditLogStreamDTO,
|
||||||
TDeleteAuditLogStreamDTO,
|
TDeleteAuditLogStreamDTO,
|
||||||
TGetDetailsAuditLogStreamDTO,
|
TGetDetailsAuditLogStreamDTO,
|
||||||
@@ -33,7 +34,14 @@ export const auditLogStreamServiceFactory = ({
|
|||||||
permissionService,
|
permissionService,
|
||||||
licenseService
|
licenseService
|
||||||
}: TAuditLogStreamServiceFactoryDep) => {
|
}: TAuditLogStreamServiceFactoryDep) => {
|
||||||
const create = async ({ url, actor, token, actorId, actorOrgId, actorAuthMethod }: TCreateAuditLogStreamDTO) => {
|
const create = async ({
|
||||||
|
url,
|
||||||
|
actor,
|
||||||
|
headers = [],
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TCreateAuditLogStreamDTO) => {
|
||||||
if (!actorOrgId) throw new BadRequestError({ message: "Missing org id from token" });
|
if (!actorOrgId) throw new BadRequestError({ message: "Missing org id from token" });
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
@@ -62,30 +70,37 @@ export const auditLogStreamServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// testing connection first
|
// testing connection first
|
||||||
const headers: RawAxiosRequestHeaders = { "Content-Type": "application/json" };
|
const streamHeaders: RawAxiosRequestHeaders = { "Content-Type": "application/json" };
|
||||||
if (token) headers.Authorization = `Bearer ${token}`;
|
if (headers.length)
|
||||||
await request.post(
|
headers.forEach(({ key, value }) => {
|
||||||
url,
|
streamHeaders[key] = value;
|
||||||
{ ping: "ok" },
|
});
|
||||||
{
|
await request
|
||||||
headers,
|
.post(
|
||||||
// request timeout
|
url,
|
||||||
timeout: AUDIT_LOG_STREAM_TIMEOUT,
|
{ ping: "ok" },
|
||||||
// connection timeout
|
{
|
||||||
signal: AbortSignal.timeout(AUDIT_LOG_STREAM_TIMEOUT)
|
headers: streamHeaders,
|
||||||
}
|
// request timeout
|
||||||
);
|
timeout: AUDIT_LOG_STREAM_TIMEOUT,
|
||||||
const encryptedToken = token ? infisicalSymmetricEncypt(token) : undefined;
|
// connection timeout
|
||||||
|
signal: AbortSignal.timeout(AUDIT_LOG_STREAM_TIMEOUT)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch((err) => {
|
||||||
|
throw new Error(`Failed to connect with the source ${(err as Error)?.message}`);
|
||||||
|
});
|
||||||
|
const encryptedHeaders = headers ? infisicalSymmetricEncypt(JSON.stringify(headers)) : undefined;
|
||||||
const logStream = await auditLogStreamDAL.create({
|
const logStream = await auditLogStreamDAL.create({
|
||||||
orgId: actorOrgId,
|
orgId: actorOrgId,
|
||||||
url,
|
url,
|
||||||
...(encryptedToken
|
...(encryptedHeaders
|
||||||
? {
|
? {
|
||||||
encryptedTokenCiphertext: encryptedToken.ciphertext,
|
encryptedHeadersCiphertext: encryptedHeaders.ciphertext,
|
||||||
encryptedTokenIV: encryptedToken.iv,
|
encryptedHeadersIV: encryptedHeaders.iv,
|
||||||
encryptedTokenTag: encryptedToken.tag,
|
encryptedHeadersTag: encryptedHeaders.tag,
|
||||||
encryptedTokenAlgorithm: encryptedToken.algorithm,
|
encryptedHeadersAlgorithm: encryptedHeaders.algorithm,
|
||||||
encryptedTokenKeyEncoding: encryptedToken.encoding
|
encryptedHeadersKeyEncoding: encryptedHeaders.encoding
|
||||||
}
|
}
|
||||||
: {})
|
: {})
|
||||||
});
|
});
|
||||||
@@ -96,7 +111,7 @@ export const auditLogStreamServiceFactory = ({
|
|||||||
id,
|
id,
|
||||||
url,
|
url,
|
||||||
actor,
|
actor,
|
||||||
token,
|
headers = [],
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod
|
actorAuthMethod
|
||||||
@@ -119,13 +134,17 @@ export const auditLogStreamServiceFactory = ({
|
|||||||
if (url) validateLocalIps(url);
|
if (url) validateLocalIps(url);
|
||||||
|
|
||||||
// testing connection first
|
// testing connection first
|
||||||
const headers: RawAxiosRequestHeaders = { "Content-Type": "application/json" };
|
const streamHeaders: RawAxiosRequestHeaders = { "Content-Type": "application/json" };
|
||||||
if (token) headers.Authorization = `Bearer ${token}`;
|
if (headers.length)
|
||||||
|
headers.forEach(({ key, value }) => {
|
||||||
|
streamHeaders[key] = value;
|
||||||
|
});
|
||||||
|
|
||||||
await request.post(
|
await request.post(
|
||||||
url || logStream.url,
|
url || logStream.url,
|
||||||
{ ping: "ok" },
|
{ ping: "ok" },
|
||||||
{
|
{
|
||||||
headers,
|
headers: streamHeaders,
|
||||||
// request timeout
|
// request timeout
|
||||||
timeout: AUDIT_LOG_STREAM_TIMEOUT,
|
timeout: AUDIT_LOG_STREAM_TIMEOUT,
|
||||||
// connection timeout
|
// connection timeout
|
||||||
@@ -133,16 +152,16 @@ export const auditLogStreamServiceFactory = ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const encryptedToken = token ? infisicalSymmetricEncypt(token) : undefined;
|
const encryptedHeaders = headers ? infisicalSymmetricEncypt(JSON.stringify(headers)) : undefined;
|
||||||
const updatedLogStream = await auditLogStreamDAL.updateById(id, {
|
const updatedLogStream = await auditLogStreamDAL.updateById(id, {
|
||||||
url,
|
url,
|
||||||
...(encryptedToken
|
...(encryptedHeaders
|
||||||
? {
|
? {
|
||||||
encryptedTokenCiphertext: encryptedToken.ciphertext,
|
encryptedHeadersCiphertext: encryptedHeaders.ciphertext,
|
||||||
encryptedTokenIV: encryptedToken.iv,
|
encryptedHeadersIV: encryptedHeaders.iv,
|
||||||
encryptedTokenTag: encryptedToken.tag,
|
encryptedHeadersTag: encryptedHeaders.tag,
|
||||||
encryptedTokenAlgorithm: encryptedToken.algorithm,
|
encryptedHeadersAlgorithm: encryptedHeaders.algorithm,
|
||||||
encryptedTokenKeyEncoding: encryptedToken.encoding
|
encryptedHeadersKeyEncoding: encryptedHeaders.encoding
|
||||||
}
|
}
|
||||||
: {})
|
: {})
|
||||||
});
|
});
|
||||||
@@ -171,17 +190,19 @@ export const auditLogStreamServiceFactory = ({
|
|||||||
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.Settings);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
|
||||||
|
|
||||||
const token =
|
const headers =
|
||||||
logStream?.encryptedTokenCiphertext && logStream?.encryptedTokenIV && logStream?.encryptedTokenTag
|
logStream?.encryptedHeadersCiphertext && logStream?.encryptedHeadersIV && logStream?.encryptedHeadersTag
|
||||||
? infisicalSymmetricDecrypt({
|
? (JSON.parse(
|
||||||
tag: logStream.encryptedTokenTag,
|
infisicalSymmetricDecrypt({
|
||||||
iv: logStream.encryptedTokenIV,
|
tag: logStream.encryptedHeadersTag,
|
||||||
ciphertext: logStream.encryptedTokenCiphertext,
|
iv: logStream.encryptedHeadersIV,
|
||||||
keyEncoding: logStream.encryptedTokenKeyEncoding as SecretKeyEncoding
|
ciphertext: logStream.encryptedHeadersCiphertext,
|
||||||
})
|
keyEncoding: logStream.encryptedHeadersKeyEncoding as SecretKeyEncoding
|
||||||
|
})
|
||||||
|
) as LogStreamHeaders[])
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return { ...logStream, token };
|
return { ...logStream, headers };
|
||||||
};
|
};
|
||||||
|
|
||||||
const list = async ({ actor, actorId, actorOrgId, actorAuthMethod }: TListAuditLogStreamDTO) => {
|
const list = async ({ actor, actorId, actorOrgId, actorAuthMethod }: TListAuditLogStreamDTO) => {
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
import { TOrgPermission } from "@app/lib/types";
|
import { TOrgPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
export type LogStreamHeaders = {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type TCreateAuditLogStreamDTO = Omit<TOrgPermission, "orgId"> & {
|
export type TCreateAuditLogStreamDTO = Omit<TOrgPermission, "orgId"> & {
|
||||||
url: string;
|
url: string;
|
||||||
token?: string;
|
headers?: LogStreamHeaders[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TUpdateAuditLogStreamDTO = Omit<TOrgPermission, "orgId"> & {
|
export type TUpdateAuditLogStreamDTO = Omit<TOrgPermission, "orgId"> & {
|
||||||
id: string;
|
id: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
token?: string;
|
headers?: LogStreamHeaders[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TDeleteAuditLogStreamDTO = Omit<TOrgPermission, "orgId"> & {
|
export type TDeleteAuditLogStreamDTO = Omit<TOrgPermission, "orgId"> & {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
|||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
|
||||||
import { TAuditLogStreamDALFactory } from "../audit-log-stream/audit-log-stream-dal";
|
import { TAuditLogStreamDALFactory } from "../audit-log-stream/audit-log-stream-dal";
|
||||||
|
import { LogStreamHeaders } from "../audit-log-stream/audit-log-stream-types";
|
||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { TAuditLogDALFactory } from "./audit-log-dal";
|
import { TAuditLogDALFactory } from "./audit-log-dal";
|
||||||
import { TCreateAuditLogDTO } from "./audit-log-types";
|
import { TCreateAuditLogDTO } from "./audit-log-types";
|
||||||
@@ -74,20 +75,31 @@ export const auditLogQueueServiceFactory = ({
|
|||||||
const logStreams = orgId ? await auditLogStreamDAL.find({ orgId }) : [];
|
const logStreams = orgId ? await auditLogStreamDAL.find({ orgId }) : [];
|
||||||
await Promise.allSettled(
|
await Promise.allSettled(
|
||||||
logStreams.map(
|
logStreams.map(
|
||||||
async ({ url, encryptedTokenTag, encryptedTokenIV, encryptedTokenKeyEncoding, encryptedTokenCiphertext }) => {
|
async ({
|
||||||
const token =
|
url,
|
||||||
encryptedTokenIV && encryptedTokenCiphertext && encryptedTokenTag
|
encryptedHeadersTag,
|
||||||
? infisicalSymmetricDecrypt({
|
encryptedHeadersIV,
|
||||||
keyEncoding: encryptedTokenKeyEncoding as SecretKeyEncoding,
|
encryptedHeadersKeyEncoding,
|
||||||
iv: encryptedTokenIV,
|
encryptedHeadersCiphertext
|
||||||
tag: encryptedTokenTag,
|
}) => {
|
||||||
ciphertext: encryptedTokenCiphertext
|
const streamHeaders =
|
||||||
})
|
encryptedHeadersIV && encryptedHeadersCiphertext && encryptedHeadersTag
|
||||||
: undefined;
|
? (JSON.parse(
|
||||||
|
infisicalSymmetricDecrypt({
|
||||||
|
keyEncoding: encryptedHeadersKeyEncoding as SecretKeyEncoding,
|
||||||
|
iv: encryptedHeadersIV,
|
||||||
|
tag: encryptedHeadersTag,
|
||||||
|
ciphertext: encryptedHeadersCiphertext
|
||||||
|
})
|
||||||
|
) as LogStreamHeaders[])
|
||||||
|
: [];
|
||||||
|
|
||||||
const headers: RawAxiosRequestHeaders = { "Content-Type": "application/json" };
|
const headers: RawAxiosRequestHeaders = { "Content-Type": "application/json" };
|
||||||
|
|
||||||
if (token) headers.Authorization = `Bearer ${token}`;
|
if (headers.length)
|
||||||
|
streamHeaders.forEach(({ key, value }) => {
|
||||||
|
headers[key] = value;
|
||||||
|
});
|
||||||
|
|
||||||
return request.post(url, auditLog, {
|
return request.post(url, auditLog, {
|
||||||
headers,
|
headers,
|
||||||
|
|||||||
@@ -618,12 +618,20 @@ export const INTEGRATION = {
|
|||||||
export const AUDIT_LOG_STREAMS = {
|
export const AUDIT_LOG_STREAMS = {
|
||||||
CREATE: {
|
CREATE: {
|
||||||
url: "The HTTP URL to push logs to.",
|
url: "The HTTP URL to push logs to.",
|
||||||
token: "Authentication token for the external provider used for identification."
|
headers: {
|
||||||
|
desc: "The HTTP headers attached for the external prrovider requests.",
|
||||||
|
key: "The HTTP header key name.",
|
||||||
|
value: "The HTTP header value."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
id: "The ID of the audit log stream to update.",
|
id: "The ID of the audit log stream to update.",
|
||||||
url: "The HTTP URL to push logs to.",
|
url: "The HTTP URL to push logs to.",
|
||||||
token: "Authentication token for the external provider used for identification."
|
headers: {
|
||||||
|
desc: "The HTTP headers attached for the external prrovider requests.",
|
||||||
|
key: "The HTTP header key name.",
|
||||||
|
value: "The HTTP header value."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
id: "The ID of the audit log stream to delete."
|
id: "The ID of the audit log stream to delete."
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
|
export type LogStreamHeaders = {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type TAuditLogStream = {
|
export type TAuditLogStream = {
|
||||||
id: string;
|
id: string;
|
||||||
url: string;
|
url: string;
|
||||||
token: string;
|
headers?: LogStreamHeaders[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCreateAuditLogStreamDTO = {
|
export type TCreateAuditLogStreamDTO = {
|
||||||
url: string;
|
url: string;
|
||||||
token?: string;
|
headers?: LogStreamHeaders[];
|
||||||
orgId: string;
|
orgId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TUpdateAuditLogStreamDTO = {
|
export type TUpdateAuditLogStreamDTO = {
|
||||||
id: string;
|
id: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
token?: string;
|
headers?: LogStreamHeaders[];
|
||||||
orgId: string;
|
orgId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||||
|
import { faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Button, FormControl, Input, Spinner } from "@app/components/v2";
|
import { Button, FormControl, FormLabel, IconButton, Input, Spinner } from "@app/components/v2";
|
||||||
import { useOrganization } from "@app/context";
|
import { useOrganization } from "@app/context";
|
||||||
import {
|
import {
|
||||||
useCreateAuditLogStream,
|
useCreateAuditLogStream,
|
||||||
@@ -17,7 +19,13 @@ type Props = {
|
|||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
url: z.string().url().min(1),
|
url: z.string().url().min(1),
|
||||||
token: z.string().optional()
|
headers: z
|
||||||
|
.object({
|
||||||
|
key: z.string(),
|
||||||
|
value: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
});
|
});
|
||||||
type TForm = z.infer<typeof formSchema>;
|
type TForm = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
@@ -33,18 +41,28 @@ export const AuditLogStreamForm = ({ id = "", onClose }: Props) => {
|
|||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
control,
|
control,
|
||||||
|
setValue,
|
||||||
|
getValues,
|
||||||
formState: { isSubmitting }
|
formState: { isSubmitting }
|
||||||
} = useForm<TForm>({
|
} = useForm<TForm>({
|
||||||
values: auditLogStream?.data
|
values: auditLogStream?.data,
|
||||||
|
defaultValues: {
|
||||||
|
headers: [{ key: "", value: "" }]
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleAuditLogStreamEdit = async ({ token, url }: TForm) => {
|
const headerFields = useFieldArray({
|
||||||
|
control,
|
||||||
|
name: "headers"
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleAuditLogStreamEdit = async ({ headers, url }: TForm) => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
try {
|
try {
|
||||||
await updateAuditLogStream.mutateAsync({
|
await updateAuditLogStream.mutateAsync({
|
||||||
id,
|
id,
|
||||||
orgId,
|
orgId,
|
||||||
token,
|
headers,
|
||||||
url
|
url
|
||||||
});
|
});
|
||||||
createNotification({
|
createNotification({
|
||||||
@@ -61,16 +79,18 @@ export const AuditLogStreamForm = ({ id = "", onClose }: Props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFormSubmit = async ({ token, url }: TForm) => {
|
const handleFormSubmit = async ({ headers = [], url }: TForm) => {
|
||||||
if (isSubmitting) return;
|
if (isSubmitting) return;
|
||||||
|
const sanitizedHeaders = headers.filter(({ key, value }) => Boolean(key) && Boolean(value));
|
||||||
|
const streamHeaders = sanitizedHeaders.length ? sanitizedHeaders : undefined;
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
await handleAuditLogStreamEdit({ token, url });
|
await handleAuditLogStreamEdit({ headers: streamHeaders, url });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await createAuditLogStream.mutateAsync({
|
await createAuditLogStream.mutateAsync({
|
||||||
orgId,
|
orgId,
|
||||||
token,
|
headers: streamHeaders,
|
||||||
url
|
url
|
||||||
});
|
});
|
||||||
createNotification({
|
createNotification({
|
||||||
@@ -96,32 +116,82 @@ export const AuditLogStreamForm = ({ id = "", onClose }: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
<form onSubmit={handleSubmit(handleFormSubmit)} autoComplete="off">
|
||||||
<div>
|
<div>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="url"
|
name="url"
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl label="Endpoint URL" isError={Boolean(error?.message)} errorText={error?.message}>
|
<FormControl
|
||||||
|
label="Endpoint URL"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Controller
|
<FormLabel label="Headers" isOptional />
|
||||||
control={control}
|
{headerFields.fields.map(({ id: headerFieldId }, i) => (
|
||||||
name="token"
|
<div key={headerFieldId} className="flex space-x-2">
|
||||||
render={({ field, fieldState: { error } }) => (
|
<Controller
|
||||||
<FormControl
|
control={control}
|
||||||
label="Token"
|
name={`headers.${i}.key`}
|
||||||
isOptional
|
render={({ field, fieldState: { error } }) => (
|
||||||
isError={Boolean(error?.message)}
|
<FormControl
|
||||||
errorText={error?.message}
|
isError={Boolean(error?.message)}
|
||||||
helperText="The bearer token used to authenticate with the logging provider endpoint"
|
errorText={error?.message}
|
||||||
|
className="w-1/3"
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="Authorization" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={`headers.${i}.value`}
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
className="flex-grow"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
type="password"
|
||||||
|
placeholder="Bearer <token>"
|
||||||
|
autoComplete="new-password"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="delete key"
|
||||||
|
className="h-9"
|
||||||
|
variant="outline_bg"
|
||||||
|
onClick={() => {
|
||||||
|
const header = getValues("headers");
|
||||||
|
if (header && header?.length > 1) {
|
||||||
|
headerFields.remove(i);
|
||||||
|
} else {
|
||||||
|
setValue("headers", [{ key: "", value: "" }]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Input {...field} type="password" />
|
<FontAwesomeIcon icon={faTrash} />
|
||||||
</FormControl>
|
</IconButton>
|
||||||
)}
|
</div>
|
||||||
/>
|
))}
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
size="xs"
|
||||||
|
variant="outline_bg"
|
||||||
|
onClick={() => headerFields.append({ value: "", key: "" })}
|
||||||
|
>
|
||||||
|
Add Key
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-8 flex items-center">
|
<div className="mt-8 flex items-center">
|
||||||
<Button className="mr-4" type="submit" isLoading={isSubmitting}>
|
<Button className="mr-4" type="submit" isLoading={isSubmitting}>
|
||||||
|
|||||||
Reference in New Issue
Block a user