Fix merge conflicts

This commit is contained in:
Tuan Dang
2023-11-23 15:37:14 +07:00
28 changed files with 1607 additions and 937 deletions
+181 -62
View File
@@ -306,12 +306,77 @@
}, },
"/api/v1/workspace/{workspaceId}/audit-logs": { "/api/v1/workspace/{workspaceId}/audit-logs": {
"get": { "get": {
"description": "", "summary": "Return audit logs",
"description": "Return audit logs",
"parameters": [ "parameters": [
{ {
"name": "workspaceId", "name": "workspaceId",
"in": "path", "in": "path",
"required": true, "required": true,
"schema": {
"type": "string"
},
"description": "ID of the workspace where to get folders from"
},
{
"name": "offset",
"description": "Number of logs to skip before starting to return logs for pagination",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"description": "Maximum number of logs to return for pagination",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "startDate",
"description": "Filter logs from this date in ISO-8601 format",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "endDate",
"description": "Filter logs till this date in ISO-8601 format",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "eventType",
"description": "Filter by type of event such as get-secrets, get-secret, create-secret, update-secret, delete-secret, etc.",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "userAgentType",
"description": "Filter by type of user agent such as web, cli, k8-operator, or other",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "actor",
"description": "Filter by actor such as user or service",
"required": false,
"in": "query",
"schema": { "schema": {
"type": "string" "type": "string"
} }
@@ -319,9 +384,30 @@
], ],
"responses": { "responses": {
"200": { "200": {
"description": "OK" "description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"auditLogs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/AuditLog"
},
"description": "List of audit log"
}
}
}
}
}
} }
} },
"security": [
{
"apiKeyAuth": []
}
]
} }
}, },
"/api/v1/workspace/{workspaceId}/audit-logs/filters/actors": { "/api/v1/workspace/{workspaceId}/audit-logs/filters/actors": {
@@ -1132,6 +1218,43 @@
} }
} }
}, },
"/api/v1/admin/config": {
"get": {
"description": "",
"responses": {
"200": {
"description": "OK"
}
}
},
"patch": {
"description": "",
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v1/admin/signup": {
"post": {
"description": "",
"parameters": [
{
"name": "user-agent",
"in": "header",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v1/bot/{workspaceId}": { "/api/v1/bot/{workspaceId}": {
"get": { "get": {
"description": "", "description": "",
@@ -4560,65 +4683,6 @@
} }
} }
} }
},
"get": {
"summary": "Get all accessible environments of a workspace",
"description": "Fetch all environments that the user has access to in a specified workspace",
"parameters": [
{
"name": "workspaceId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "ID of the workspace"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"accessibleEnvironments": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"example": "Development"
},
"slug": {
"type": "string",
"example": "development"
},
"isWriteDenied": {
"type": "boolean",
"example": false
},
"isReadDenied": {
"type": "boolean",
"example": false
}
}
}
}
},
"description": "List of environments the user has access to in the specified workspace"
}
}
}
}
},
"security": [
{
"apiKeyAuth": []
}
]
} }
}, },
"/api/v2/workspace/{workspaceId}/tags": { "/api/v2/workspace/{workspaceId}/tags": {
@@ -6864,6 +6928,61 @@
"example": "2023-01-13T14:16:12.210Z" "example": "2023-01-13T14:16:12.210Z"
} }
} }
},
"AuditLog": {
"type": "object",
"properties": {
"actor": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": ""
},
"metadata": {
"type": "object",
"properties": {}
}
}
},
"organization": {
"type": "string",
"example": ""
},
"workspace": {
"type": "string",
"example": ""
},
"ipAddress": {
"type": "string",
"example": ""
},
"event": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": ""
},
"metadata": {
"type": "object",
"properties": {}
}
}
},
"userAgent": {
"type": "string",
"example": ""
},
"userAgentType": {
"type": "string",
"example": ""
},
"expiresAt": {
"type": "string",
"example": ""
}
}
} }
}, },
"securitySchemes": { "securitySchemes": {
@@ -578,6 +578,82 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
* @param res * @param res
*/ */
export const getWorkspaceAuditLogs = async (req: Request, res: Response) => { export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Return audit logs'
#swagger.description = 'Return audit logs'
#swagger.security = [{
"apiKeyAuth": []
}]
#swagger.parameters['workspaceId'] = {
"description": "ID of the workspace where to get folders from",
"required": true,
"type": "string",
"in": "path"
}
#swagger.parameters['offset'] = {
"description": "Number of logs to skip before starting to return logs for pagination",
"required": false,
"type": "string"
}
#swagger.parameters['limit'] = {
"description": "Maximum number of logs to return for pagination",
"required": false,
"type": "string"
}
#swagger.parameters['startDate'] = {
"description": "Filter logs from this date in ISO-8601 format",
"required": false,
"type": "string"
}
#swagger.parameters['endDate'] = {
"description": "Filter logs till this date in ISO-8601 format",
"required": false,
"type": "string"
}
#swagger.parameters['eventType'] = {
"description": "Filter by type of event such as get-secrets, get-secret, create-secret, update-secret, delete-secret, etc.",
"required": false,
"type": "string",
}
#swagger.parameters['userAgentType'] = {
"description": "Filter by type of user agent such as web, cli, k8-operator, or other",
"required": false,
"type": "string",
}
#swagger.parameters['actor'] = {
"description": "Filter by actor such as user or service",
"required": false,
"type": "string"
}
#swagger.responses[200] = {
content: {
"application/json": {
schema: {
"type": "object",
"properties": {
"auditLogs": {
"type": "array",
"items": {
$ref: "#/components/schemas/AuditLog",
},
"description": "List of audit log"
},
}
}
}
}
}
*/
const { const {
query: { limit, offset, endDate, eventType, startDate, userAgentType, actor }, query: { limit, offset, endDate, eventType, startDate, userAgentType, actor },
params: { workspaceId } params: { workspaceId }
@@ -592,7 +668,7 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.AuditLogs ProjectPermissionSub.AuditLogs
); );
const query = { const query = {
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
...(eventType ...(eventType
@@ -626,14 +702,9 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
} }
: {}) : {})
}; };
const auditLogs = await AuditLog.find(query).sort({ createdAt: -1 }).skip(offset).limit(limit); const auditLogs = await AuditLog.find(query).sort({ createdAt: -1 }).skip(offset).limit(limit);
const totalCount = await AuditLog.countDocuments(query);
return res.status(200).send({ return res.status(200).send({
auditLogs, auditLogs
totalCount
}); });
}; };
@@ -685,7 +756,7 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res
name: serviceTokenData.name name: serviceTokenData.name
} }
})); }));
const serviceV3Actors: ServiceActorV3[] = ( const serviceV3Actors: ServiceActorV3[] = (
await ServiceTokenDataV3.find({ await ServiceTokenDataV3.find({
workspace: new Types.ObjectId(workspaceId) workspace: new Types.ObjectId(workspaceId)
@@ -697,12 +768,8 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res
name: serviceTokenData.name name: serviceTokenData.name
} }
})); }));
const actors = [ const actors = [...userActors, ...serviceActors, ...serviceV3Actors];
...userActors,
...serviceActors,
...serviceV3Actors
];
return res.status(200).send({ return res.status(200).send({
actors actors
+60 -66
View File
@@ -1,76 +1,70 @@
import { Schema, Types, model } from "mongoose"; import { Schema, Types, model } from "mongoose";
import { import { ActorType, EventType, UserAgentType } from "./enums";
ActorType, import { Actor, Event } from "./types";
EventType,
UserAgentType
} from "./enums";
import {
Actor,
Event
} from "./types";
export interface IAuditLog { export interface IAuditLog {
actor: Actor; actor: Actor;
organization: Types.ObjectId; organization: Types.ObjectId;
workspace: Types.ObjectId; workspace: Types.ObjectId;
ipAddress: string; ipAddress: string;
event: Event; event: Event;
userAgent: string; userAgent: string;
userAgentType: UserAgentType; userAgentType: UserAgentType;
expiresAt: Date; expiresAt: Date;
} }
const auditLogSchema = new Schema<IAuditLog>( const auditLogSchema = new Schema<IAuditLog>(
{ {
actor: { actor: {
type: { type: {
type: String, type: String,
enum: ActorType, enum: ActorType,
required: true required: true
}, },
metadata: { metadata: {
type: Schema.Types.Mixed type: Schema.Types.Mixed
} }
},
organization: {
type: Schema.Types.ObjectId,
required: false
},
workspace: {
type: Schema.Types.ObjectId,
required: false
},
ipAddress: {
type: String,
required: true
},
event: {
type: {
type: String,
enum: EventType,
required: true
},
metadata: {
type: Schema.Types.Mixed
}
},
userAgent: {
type: String,
required: true
},
userAgentType: {
type: String,
enum: UserAgentType,
required: true
},
expiresAt: {
type: Date,
expires: 0
}
}, },
{ organization: {
timestamps: true type: Schema.Types.ObjectId,
required: false
},
workspace: {
type: Schema.Types.ObjectId,
required: false,
index: true
},
ipAddress: {
type: String,
required: true
},
event: {
type: {
type: String,
enum: EventType,
required: true
},
metadata: {
type: Schema.Types.Mixed
}
},
userAgent: {
type: String,
required: true
},
userAgentType: {
type: String,
enum: UserAgentType,
required: true
},
expiresAt: {
type: Date,
expires: 0
} }
},
{
timestamps: true
}
); );
export const AuditLog = model<IAuditLog>("AuditLog", auditLogSchema); export const AuditLog = model<IAuditLog>("AuditLog", auditLogSchema);
+42
View File
@@ -10,6 +10,8 @@ import {
INTEGRATION_CIRCLECI_API_URL, INTEGRATION_CIRCLECI_API_URL,
INTEGRATION_CLOUDFLARE_PAGES, INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_CLOUDFLARE_PAGES_API_URL, INTEGRATION_CLOUDFLARE_PAGES_API_URL,
INTEGRATION_CLOUDFLARE_WORKERS,
INTEGRATION_CLOUDFLARE_WORKERS_API_URL,
INTEGRATION_CLOUD_66, INTEGRATION_CLOUD_66,
INTEGRATION_CLOUD_66_API_URL, INTEGRATION_CLOUD_66_API_URL,
INTEGRATION_CODEFRESH, INTEGRATION_CODEFRESH,
@@ -186,6 +188,12 @@ const getApps = async ({
accountId: accessId accountId: accessId
}); });
break; break;
case INTEGRATION_CLOUDFLARE_WORKERS:
apps = await getAppsCloudflareWorkers({
accessToken,
accountId: accessId
});
break;
case INTEGRATION_NORTHFLANK: case INTEGRATION_NORTHFLANK:
apps = await getAppsNorthflank({ apps = await getAppsNorthflank({
accessToken accessToken
@@ -968,6 +976,40 @@ const getAppsCloudflarePages = async ({
return apps; return apps;
}; };
/**
* Return list of projects for the Cloudflare Workers integration
* @param {Object} obj
* @param {String} obj.accessToken - api key for the Cloudflare API
* @returns {Object[]} apps - Cloudflare Workers projects
* @returns {String} apps.id - Id of Cloudflare Workers project
* @returns {String} apps.name - Id of Cloudflare Workers project (Cloudflare workers API does not return the name)
*/
const getAppsCloudflareWorkers = async ({
accessToken,
accountId
}: {
accessToken: string;
accountId?: string;
}) => {
const { data } = await standardRequest.get(
`${INTEGRATION_CLOUDFLARE_WORKERS_API_URL}/client/v4/accounts/${accountId}/workers/services`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
}
);
const apps = data.result.map((a: any) => {
return {
name: a.id,
appId: a.id
};
});
return apps;
};
/** /**
* Return list of repositories for the BitBucket integration based on provided BitBucket workspace * Return list of repositories for the BitBucket integration based on provided BitBucket workspace
* @param {Object} obj * @param {Object} obj
+102
View File
@@ -18,6 +18,8 @@ import {
INTEGRATION_CIRCLECI_API_URL, INTEGRATION_CIRCLECI_API_URL,
INTEGRATION_CLOUDFLARE_PAGES, INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_CLOUDFLARE_PAGES_API_URL, INTEGRATION_CLOUDFLARE_PAGES_API_URL,
INTEGRATION_CLOUDFLARE_WORKERS,
INTEGRATION_CLOUDFLARE_WORKERS_API_URL,
INTEGRATION_CLOUD_66, INTEGRATION_CLOUD_66,
INTEGRATION_CLOUD_66_API_URL, INTEGRATION_CLOUD_66_API_URL,
INTEGRATION_CODEFRESH, INTEGRATION_CODEFRESH,
@@ -262,6 +264,14 @@ const syncSecrets = async ({
accessToken accessToken
}); });
break; break;
case INTEGRATION_CLOUDFLARE_WORKERS:
await syncSecretsCloudflareWorkers({
integration,
secrets,
accessId,
accessToken
});
break;
case INTEGRATION_CODEFRESH: case INTEGRATION_CODEFRESH:
await syncSecretsCodefresh({ await syncSecretsCodefresh({
integration, integration,
@@ -2746,6 +2756,98 @@ const syncSecretsCloudflarePages = async ({
); );
}; };
/**
* Sync/push [secrets] to Cloudflare Workers project with name [integration.app]
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
* @param {String} obj.accessToken - API token for Cloudflare workers
*/
const syncSecretsCloudflareWorkers = async ({
integration,
secrets,
accessId,
accessToken
}: {
integration: IIntegration;
secrets: Record<string, { value: string; comment?: string }>;
accessId: string | null;
accessToken: string;
}) => {
// get secrets from cloudflare workers
const getSecretsRes = (
await standardRequest.get(
`${INTEGRATION_CLOUDFLARE_WORKERS_API_URL}/client/v4/accounts/${accessId}/workers/scripts/${integration.app}/secrets`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
}
)
).data.result;
const secretsObj: any = getSecretKeyValuePair(secrets);
for (const [key, val] of Object.entries(secretsObj)) {
secretsObj[key] = { type: "secret_text", value: val };
}
// get deleted secrets list
const deletedSecretKeys: string[] = [];
if (getSecretsRes) {
getSecretsRes.forEach((secretRes: any) => {
if (!(Object.keys(secrets).includes(secretRes.name))) {
deletedSecretKeys.push(secretRes.name);
}
})
}
deletedSecretKeys.forEach(async (secretKey) => {
await standardRequest.delete(
`${INTEGRATION_CLOUDFLARE_WORKERS_API_URL}/client/v4/accounts/${accessId}/workers/scripts/${integration.app}/secrets/${secretKey}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
}
);
});
interface ConvertedSecret {
name: string;
text: string;
type: string;
}
interface SecretsObj {
[key: string]: {
type: string;
value: string;
};
}
const data: ConvertedSecret[] = Object.entries(secretsObj as SecretsObj).map(([name, secret]) => ({
name,
text: secret.value,
type: "secret_text"
}));
data.forEach(async (secret) => {
await standardRequest.put(
`${INTEGRATION_CLOUDFLARE_WORKERS_API_URL}/client/v4/accounts/${accessId}/workers/scripts/${integration.app}/secrets`,
secret,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
}
);
})
};
/** /**
* Sync/push [secrets] to BitBucket repo with name [integration.app] * Sync/push [secrets] to BitBucket repo with name [integration.app]
* @param {Object} obj * @param {Object} obj
@@ -6,6 +6,7 @@ import {
INTEGRATION_CHECKLY, INTEGRATION_CHECKLY,
INTEGRATION_CIRCLECI, INTEGRATION_CIRCLECI,
INTEGRATION_CLOUDFLARE_PAGES, INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_CLOUDFLARE_WORKERS,
INTEGRATION_CLOUD_66, INTEGRATION_CLOUD_66,
INTEGRATION_CODEFRESH, INTEGRATION_CODEFRESH,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM, INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
@@ -71,6 +72,7 @@ export interface IIntegration {
| "teamcity" | "teamcity"
| "hashicorp-vault" | "hashicorp-vault"
| "cloudflare-pages" | "cloudflare-pages"
| "cloudflare-workers"
| "bitbucket" | "bitbucket"
| "codefresh" | "codefresh"
| "digital-ocean-app-platform" | "digital-ocean-app-platform"
@@ -179,6 +181,7 @@ const integrationSchema = new Schema<IIntegration>(
INTEGRATION_TEAMCITY, INTEGRATION_TEAMCITY,
INTEGRATION_HASHICORP_VAULT, INTEGRATION_HASHICORP_VAULT,
INTEGRATION_CLOUDFLARE_PAGES, INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_CLOUDFLARE_WORKERS,
INTEGRATION_CODEFRESH, INTEGRATION_CODEFRESH,
INTEGRATION_WINDMILL, INTEGRATION_WINDMILL,
INTEGRATION_BITBUCKET, INTEGRATION_BITBUCKET,
@@ -8,6 +8,7 @@ import {
INTEGRATION_BITBUCKET, INTEGRATION_BITBUCKET,
INTEGRATION_CIRCLECI, INTEGRATION_CIRCLECI,
INTEGRATION_CLOUDFLARE_PAGES, INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_CLOUDFLARE_WORKERS,
INTEGRATION_CLOUD_66, INTEGRATION_CLOUD_66,
INTEGRATION_CODEFRESH, INTEGRATION_CODEFRESH,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM, INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
@@ -55,6 +56,7 @@ export interface IIntegrationAuth extends Document {
| "checkly" | "checkly"
| "qovery" | "qovery"
| "cloudflare-pages" | "cloudflare-pages"
| "cloudflare-workers"
| "codefresh" | "codefresh"
| "digital-ocean-app-platform" | "digital-ocean-app-platform"
| "bitbucket" | "bitbucket"
@@ -113,6 +115,7 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
INTEGRATION_TERRAFORM_CLOUD, INTEGRATION_TERRAFORM_CLOUD,
INTEGRATION_HASHICORP_VAULT, INTEGRATION_HASHICORP_VAULT,
INTEGRATION_CLOUDFLARE_PAGES, INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_CLOUDFLARE_WORKERS,
INTEGRATION_CODEFRESH, INTEGRATION_CODEFRESH,
INTEGRATION_WINDMILL, INTEGRATION_WINDMILL,
INTEGRATION_BITBUCKET, INTEGRATION_BITBUCKET,
+4 -4
View File
@@ -59,7 +59,7 @@ export const validateClientForWorkspace = async ({
environment, environment,
requiredPermissions requiredPermissions
}); });
return { membership, workspace}; return { membership, workspace };
case ActorType.SERVICE_V3: case ActorType.SERVICE_V3:
throw UnauthorizedRequestError({ throw UnauthorizedRequestError({
message: "Failed service token authorization for organization" message: "Failed service token authorization for organization"
@@ -122,8 +122,8 @@ export const GetWorkspaceAuditLogsV1 = z.object({
userAgentType: z.nativeEnum(UserAgentType).nullable().optional(), userAgentType: z.nativeEnum(UserAgentType).nullable().optional(),
startDate: z.string().datetime().nullable().optional(), startDate: z.string().datetime().nullable().optional(),
endDate: z.string().datetime().nullable().optional(), endDate: z.string().datetime().nullable().optional(),
offset: z.coerce.number(), offset: z.coerce.number().default(0),
limit: z.coerce.number(), limit: z.coerce.number().default(20),
actor: z.string().nullish().optional() actor: z.string().nullish().optional()
}) })
}); });
@@ -327,4 +327,4 @@ export const NameWorkspaceSecretsV3 = z.object({
}) })
.array() .array()
}) })
}); });
+12
View File
@@ -32,6 +32,7 @@ export const INTEGRATION_QOVERY = "qovery";
export const INTEGRATION_TERRAFORM_CLOUD = "terraform-cloud"; export const INTEGRATION_TERRAFORM_CLOUD = "terraform-cloud";
export const INTEGRATION_HASHICORP_VAULT = "hashicorp-vault"; export const INTEGRATION_HASHICORP_VAULT = "hashicorp-vault";
export const INTEGRATION_CLOUDFLARE_PAGES = "cloudflare-pages"; export const INTEGRATION_CLOUDFLARE_PAGES = "cloudflare-pages";
export const INTEGRATION_CLOUDFLARE_WORKERS = "cloudflare-workers";
export const INTEGRATION_BITBUCKET = "bitbucket"; export const INTEGRATION_BITBUCKET = "bitbucket";
export const INTEGRATION_CODEFRESH = "codefresh"; export const INTEGRATION_CODEFRESH = "codefresh";
export const INTEGRATION_WINDMILL = "windmill"; export const INTEGRATION_WINDMILL = "windmill";
@@ -59,6 +60,7 @@ export const INTEGRATION_SET = new Set([
INTEGRATION_TERRAFORM_CLOUD, INTEGRATION_TERRAFORM_CLOUD,
INTEGRATION_HASHICORP_VAULT, INTEGRATION_HASHICORP_VAULT,
INTEGRATION_CLOUDFLARE_PAGES, INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_CLOUDFLARE_WORKERS,
INTEGRATION_CODEFRESH, INTEGRATION_CODEFRESH,
INTEGRATION_WINDMILL, INTEGRATION_WINDMILL,
INTEGRATION_BITBUCKET, INTEGRATION_BITBUCKET,
@@ -101,6 +103,7 @@ export const INTEGRATION_CHECKLY_API_URL = "https://api.checklyhq.com";
export const INTEGRATION_QOVERY_API_URL = "https://api.qovery.com"; export const INTEGRATION_QOVERY_API_URL = "https://api.qovery.com";
export const INTEGRATION_TERRAFORM_CLOUD_API_URL = "https://app.terraform.io"; export const INTEGRATION_TERRAFORM_CLOUD_API_URL = "https://app.terraform.io";
export const INTEGRATION_CLOUDFLARE_PAGES_API_URL = "https://api.cloudflare.com"; export const INTEGRATION_CLOUDFLARE_PAGES_API_URL = "https://api.cloudflare.com";
export const INTEGRATION_CLOUDFLARE_WORKERS_API_URL = "https://api.cloudflare.com";
export const INTEGRATION_BITBUCKET_API_URL = "https://api.bitbucket.org"; export const INTEGRATION_BITBUCKET_API_URL = "https://api.bitbucket.org";
export const INTEGRATION_CODEFRESH_API_URL = "https://g.codefresh.io/api"; export const INTEGRATION_CODEFRESH_API_URL = "https://g.codefresh.io/api";
export const INTEGRATION_WINDMILL_API_URL = "https://app.windmill.dev/api"; export const INTEGRATION_WINDMILL_API_URL = "https://app.windmill.dev/api";
@@ -316,6 +319,15 @@ export const getIntegrationOptions = async () => {
clientId: "", clientId: "",
docsLink: "" docsLink: ""
}, },
{
name: "Cloudflare Workers",
slug: "cloudflare-workers",
image: "Cloudflare.png",
isAvailable: true,
type: "pat",
clientId: "",
docsLink: ""
},
{ {
name: "BitBucket", name: "BitBucket",
slug: "bitbucket", slug: "bitbucket",
+57 -41
View File
@@ -12,32 +12,32 @@ const generateOpenAPISpec = async () => {
const doc = { const doc = {
info: { info: {
title: "Infisical API", title: "Infisical API",
description: "List of all available APIs that can be consumed", description: "List of all available APIs that can be consumed"
}, },
host: ["https://infisical.com"], host: ["https://infisical.com"],
servers: [ servers: [
{ {
url: "https://app.infisical.com", url: "https://app.infisical.com",
description: "Production server", description: "Production server"
}, },
{ {
url: "http://localhost:8080", url: "http://localhost:8080",
description: "Local server", description: "Local server"
}, }
], ],
securityDefinitions: { securityDefinitions: {
bearerAuth: { bearerAuth: {
type: "http", type: "http",
scheme: "bearer", scheme: "bearer",
bearerFormat: "JWT", bearerFormat: "JWT",
description: "A service token in Infisical", description: "A service token in Infisical"
}, },
apiKeyAuth: { apiKeyAuth: {
type: "apiKey", type: "apiKey",
in: "header", in: "header",
name: "X-API-Key", name: "X-API-Key",
description: "An API Key in Infisical", description: "An API Key in Infisical"
}, }
}, },
definitions: { definitions: {
CurrentUser: { CurrentUser: {
@@ -50,7 +50,7 @@ const generateOpenAPISpec = async () => {
iv: "iv_of_enc_nacl_private_key", iv: "iv_of_enc_nacl_private_key",
tag: "tag_of_enc_nacl_private_key", tag: "tag_of_enc_nacl_private_key",
updatedAt: "2023-01-13T14:16:12.210Z", updatedAt: "2023-01-13T14:16:12.210Z",
createdAt: "2023-01-13T14:16:12.210Z", createdAt: "2023-01-13T14:16:12.210Z"
}, },
Membership: { Membership: {
user: { user: {
@@ -60,10 +60,10 @@ const generateOpenAPISpec = async () => {
lastName: "Doe", lastName: "Doe",
publicKey: "johns_nacl_public_key", publicKey: "johns_nacl_public_key",
updatedAt: "2023-01-13T14:16:12.210Z", updatedAt: "2023-01-13T14:16:12.210Z",
createdAt: "2023-01-13T14:16:12.210Z", createdAt: "2023-01-13T14:16:12.210Z"
}, },
workspace: "", workspace: "",
role: "admin", role: "admin"
}, },
MembershipOrg: { MembershipOrg: {
user: { user: {
@@ -73,33 +73,35 @@ const generateOpenAPISpec = async () => {
lastName: "Doe", lastName: "Doe",
publicKey: "johns_nacl_public_key", publicKey: "johns_nacl_public_key",
updatedAt: "2023-01-13T14:16:12.210Z", updatedAt: "2023-01-13T14:16:12.210Z",
createdAt: "2023-01-13T14:16:12.210Z", createdAt: "2023-01-13T14:16:12.210Z"
}, },
organization: "", organization: "",
role: "owner", role: "owner",
status: "accepted", status: "accepted"
}, },
Organization: { Organization: {
_id: "", _id: "",
name: "Acme Corp.", name: "Acme Corp.",
customerId: "", customerId: ""
}, },
Project: { Project: {
name: "My Project", name: "My Project",
organization: "", organization: "",
environments: [{ environments: [
name: "development", {
slug: "dev", name: "development",
}], slug: "dev"
}
]
}, },
ProjectKey: { ProjectKey: {
encryptedkey: "", encryptedkey: "",
nonce: "", nonce: "",
sender: { sender: {
publicKey: "senders_nacl_public_key", publicKey: "senders_nacl_public_key"
}, },
receiver: "", receiver: "",
workspace: "", workspace: ""
}, },
CreateSecret: { CreateSecret: {
type: "shared", type: "shared",
@@ -111,7 +113,7 @@ const generateOpenAPISpec = async () => {
secretValueTag: "", secretValueTag: "",
secretCommentCiphertext: "", secretCommentCiphertext: "",
secretCommentIV: "", secretCommentIV: "",
secretCommentTag: "", secretCommentTag: ""
}, },
UpdateSecret: { UpdateSecret: {
id: "", id: "",
@@ -123,12 +125,12 @@ const generateOpenAPISpec = async () => {
secretValueTag: "", secretValueTag: "",
secretCommentCiphertext: "", secretCommentCiphertext: "",
secretCommentIV: "", secretCommentIV: "",
secretCommentTag: "", secretCommentTag: ""
}, },
Secret: { Secret: {
_id: "", _id: "",
version: 1, version: 1,
workspace : "", workspace: "",
type: "shared", type: "shared",
user: null, user: null,
secretKeyCiphertext: "", secretKeyCiphertext: "",
@@ -141,7 +143,7 @@ const generateOpenAPISpec = async () => {
secretCommentIV: "", secretCommentIV: "",
secretCommentTag: "", secretCommentTag: "",
updatedAt: "2023-01-13T14:16:12.210Z", updatedAt: "2023-01-13T14:16:12.210Z",
createdAt: "2023-01-13T14:16:12.210Z", createdAt: "2023-01-13T14:16:12.210Z"
}, },
RawSecret: { RawSecret: {
_id: "abc123", _id: "abc123",
@@ -167,12 +169,10 @@ const generateOpenAPISpec = async () => {
_id: "", _id: "",
email: "[email protected]", email: "[email protected]",
firstName: "John", firstName: "John",
lastName: "Doe", lastName: "Doe"
}, },
workspace: "", workspace: "",
actionNames: [ actionNames: ["addSecrets"],
"addSecrets",
],
actions: [ actions: [
{ {
name: "addSecrets", name: "addSecrets",
@@ -181,24 +181,24 @@ const generateOpenAPISpec = async () => {
payload: [ payload: [
{ {
oldSecretVersion: "", oldSecretVersion: "",
newSecretVersion: "", newSecretVersion: ""
}, }
], ]
}, }
], ],
channel: "cli", channel: "cli",
ipAddress: "192.168.0.1", ipAddress: "192.168.0.1",
updatedAt: "2023-01-13T14:16:12.210Z", updatedAt: "2023-01-13T14:16:12.210Z",
createdAt: "2023-01-13T14:16:12.210Z", createdAt: "2023-01-13T14:16:12.210Z"
}, },
SecretSnapshot: { SecretSnapshot: {
workspace: "", workspace: "",
version: 1, version: 1,
secretVersions: [ secretVersions: [
{ {
_id: "", _id: ""
}, }
], ]
}, },
SecretVersion: { SecretVersion: {
_id: "", _id: "",
@@ -214,7 +214,7 @@ const generateOpenAPISpec = async () => {
secretKeyTag: "", secretKeyTag: "",
secretValueCiphertext: "", secretValueCiphertext: "",
secretValueIV: "", secretValueIV: "",
secretValueTag: "", secretValueTag: ""
}, },
ServiceTokenData: { ServiceTokenData: {
_id: "", _id: "",
@@ -224,16 +224,32 @@ const generateOpenAPISpec = async () => {
user: { user: {
_id: "", _id: "",
firstName: "", firstName: "",
lastName: "", lastName: ""
}, },
expiresAt: "2023-01-13T14:16:12.210Z", expiresAt: "2023-01-13T14:16:12.210Z",
encryptedKey: "", encryptedKey: "",
iv: "", iv: "",
tag: "", tag: "",
updatedAt: "2023-01-13T14:16:12.210Z", updatedAt: "2023-01-13T14:16:12.210Z",
createdAt: "2023-01-13T14:16:12.210Z", createdAt: "2023-01-13T14:16:12.210Z"
}, },
}, AuditLog: {
actor: {
type: "",
metadata: {}
},
organization: "",
workspace: "",
ipAddress: "",
event: {
type: "",
metadata: {}
},
userAgent: "",
userAgentType: "",
expiresAt: ""
}
}
}; };
const outputJSONFile = "../spec.json"; const outputJSONFile = "../spec.json";
@@ -243,6 +259,6 @@ const generateOpenAPISpec = async () => {
const spec = await swaggerAutogen(outputJSONFile, endpointsFiles, doc); const spec = await swaggerAutogen(outputJSONFile, endpointsFiles, doc);
await fs.writeFile(outputYAMLFile, yaml.dump(spec.data)); await fs.writeFile(outputYAMLFile, yaml.dump(spec.data));
} };
generateOpenAPISpec(); generateOpenAPISpec();
@@ -0,0 +1,4 @@
---
title: "Export"
openapi: "GET /api/v1/workspace/{workspaceId}/audit-logs"
---
@@ -1,4 +1,4 @@
--- ---
title: "Create" title: "Create"
openapi: "POST /api/v2/workspace/{workspaceId}/environments" openapi: "POST /api/v1/workspace/{workspaceId}/environments"
--- ---
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

@@ -0,0 +1,43 @@
---
title: "Cloudflare Workers"
description: "How to sync secrets from Infisical to Cloudflare Workers"
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
## Navigate to your project's integrations tab
![integrations](../../images/integrations.png)
## Authorize Infisical for Cloudflare Workers
Obtain a Cloudflare [API token](https://dash.cloudflare.com/profile/api-tokens) and [Account ID](https://developers.cloudflare.com/fundamentals/get-started/basic-tasks/find-account-and-zone-ids/):
1. Create a new [API token](https://dash.cloudflare.com/profile/api-tokens) in My Profile > API Tokens
![integrations cloudflare credentials 1](../../images/integrations/cloudflare/integrations-cloudflare-credentials-1.png)
![integrations cloudflare credentials 2](../../images/integrations/cloudflare/integrations-cloudflare-credentials-2.png)
![integrations cloudflare credentials 3](../../images/integrations/cloudflare/integrations-cloudflare-credentials-3.png)
2. Copy your [Account ID](https://developers.cloudflare.com/fundamentals/get-started/basic-tasks/find-account-and-zone-ids/) from Account > Workers & Pages > Overview
![integrations cloudflare credentials 4](../../images/integrations/cloudflare/integrations-cloudflare-credentials-4.png)
Press on the Cloudflare Workers tile and input your Cloudflare API token and account ID to grant Infisical access to your Cloudflare Workers.
![integrations cloudflare authorization](../../images/integrations/cloudflare/integration-cloudflare-workers-connect.png)
<Info>
If this is your project's first cloud integration, then you'll have to grant
Infisical access to your project's environment variables. Although this step
breaks E2EE, it's necessary for Infisical to sync the environment variables to
the cloud platform.
</Info>
## Start integration
Select which Infisical environment secrets you want to sync to Cloudflare Workers and press create integration to start syncing secrets.
![integrations cloudflare](../../images/integrations/cloudflare/integration-cloudflare-workers-create.png)
+1
View File
@@ -26,6 +26,7 @@ Missing an integration? [Throw in a request](https://github.com/Infisical/infisi
| [Supabase](/integrations/cloud/supabase) | Cloud | Available | | [Supabase](/integrations/cloud/supabase) | Cloud | Available |
| [Northflank](/integrations/cloud/northflank) | Cloud | Available | | [Northflank](/integrations/cloud/northflank) | Cloud | Available |
| [Cloudflare Pages](/integrations/cloud/cloudflare-pages) | Cloud | Available | | [Cloudflare Pages](/integrations/cloud/cloudflare-pages) | Cloud | Available |
| [Cloudflare Workers](/integrations/cloud/cloudflare-workers) | Cloud | Available |
| [Checkly](/integrations/cloud/checkly) | Cloud | Available | | [Checkly](/integrations/cloud/checkly) | Cloud | Available |
| [Qovery](/integrations/cloud/qovery) | Cloud | Available | | [Qovery](/integrations/cloud/qovery) | Cloud | Available |
| [HashiCorp Vault](/integrations/cloud/hashicorp-vault) | Cloud | Available | | [HashiCorp Vault](/integrations/cloud/hashicorp-vault) | Cloud | Available |
+5
View File
@@ -242,6 +242,7 @@
"integrations/cloud/hasura-cloud", "integrations/cloud/hasura-cloud",
"integrations/cloud/terraform-cloud", "integrations/cloud/terraform-cloud",
"integrations/cloud/cloudflare-pages", "integrations/cloud/cloudflare-pages",
"integrations/cloud/cloudflare-workers",
"integrations/cloud/qovery", "integrations/cloud/qovery",
"integrations/cloud/hashicorp-vault", "integrations/cloud/hashicorp-vault",
"integrations/cloud/azure-key-vault", "integrations/cloud/azure-key-vault",
@@ -380,6 +381,10 @@
{ {
"group": "Service Tokens", "group": "Service Tokens",
"pages": ["api-reference/endpoints/service-tokens/get"] "pages": ["api-reference/endpoints/service-tokens/get"]
},
{
"group": "Audit Logs",
"pages": ["api-reference/endpoints/audit-logs/export-audit-log"]
} }
] ]
}, },
+120 -43
View File
@@ -192,16 +192,74 @@ paths:
description: Version of secret snapshot to roll back to description: Version of secret snapshot to roll back to
/api/v1/workspace/{workspaceId}/audit-logs: /api/v1/workspace/{workspaceId}/audit-logs:
get: get:
description: '' summary: Return audit logs
description: Return audit logs
parameters: parameters:
- name: workspaceId - name: workspaceId
in: path in: path
required: true required: true
schema: schema:
type: string type: string
description: ID of the workspace where to get folders from
- name: offset
description: Number of logs to skip before starting to return logs for pagination
required: false
in: query
schema:
type: string
- name: limit
description: Maximum number of logs to return for pagination
required: false
in: query
schema:
type: string
- name: startDate
description: Filter logs from this date in ISO-8601 format
required: false
in: query
schema:
type: string
- name: endDate
description: Filter logs till this date in ISO-8601 format
required: false
in: query
schema:
type: string
- name: eventType
description: >-
Filter by type of event such as get-secrets, get-secret,
create-secret, update-secret, delete-secret, etc.
required: false
in: query
schema:
type: string
- name: userAgentType
description: Filter by type of user agent such as web, cli, k8-operator, or other
required: false
in: query
schema:
type: string
- name: actor
description: Filter by actor such as user or service
required: false
in: query
schema:
type: string
responses: responses:
'200': '200':
description: OK description: OK
content:
application/json:
schema:
type: object
properties:
auditLogs:
type: array
items:
$ref: '#/components/schemas/AuditLog'
description: List of audit log
security:
- apiKeyAuth: []
/api/v1/workspace/{workspaceId}/audit-logs/filters/actors: /api/v1/workspace/{workspaceId}/audit-logs/filters/actors:
get: get:
description: '' description: ''
@@ -691,6 +749,28 @@ paths:
responses: responses:
'200': '200':
description: OK description: OK
/api/v1/admin/config:
get:
description: ''
responses:
'200':
description: OK
patch:
description: ''
responses:
'200':
description: OK
/api/v1/admin/signup:
post:
description: ''
parameters:
- name: user-agent
in: header
schema:
type: string
responses:
'200':
description: OK
/api/v1/bot/{workspaceId}: /api/v1/bot/{workspaceId}:
get: get:
description: '' description: ''
@@ -2834,48 +2914,6 @@ paths:
example: dev example: dev
required: required:
- environmentSlug - environmentSlug
get:
summary: Get all accessible environments of a workspace
description: >-
Fetch all environments that the user has access to in a specified
workspace
parameters:
- name: workspaceId
in: path
required: true
schema:
type: string
description: ID of the workspace
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
accessibleEnvironments:
type: array
items:
type: object
properties:
name:
type: string
example: Development
slug:
type: string
example: development
isWriteDenied:
type: boolean
example: false
isReadDenied:
type: boolean
example: false
description: >-
List of environments the user has access to in the specified
workspace
security:
- apiKeyAuth: []
/api/v2/workspace/{workspaceId}/tags: /api/v2/workspace/{workspaceId}/tags:
get: get:
description: '' description: ''
@@ -4342,6 +4380,45 @@ components:
createdAt: createdAt:
type: string type: string
example: '2023-01-13T14:16:12.210Z' example: '2023-01-13T14:16:12.210Z'
AuditLog:
type: object
properties:
actor:
type: object
properties:
type:
type: string
example: ''
metadata:
type: object
properties: {}
organization:
type: string
example: ''
workspace:
type: string
example: ''
ipAddress:
type: string
example: ''
event:
type: object
properties:
type:
type: string
example: ''
metadata:
type: object
properties: {}
userAgent:
type: string
example: ''
userAgentType:
type: string
example: ''
expiresAt:
type: string
example: ''
securitySchemes: securitySchemes:
bearerAuth: bearerAuth:
type: http type: http
@@ -24,6 +24,7 @@ const integrationSlugNameMapping: Mapping = {
teamcity: "TeamCity", teamcity: "TeamCity",
"hashicorp-vault": "Vault", "hashicorp-vault": "Vault",
"cloudflare-pages": "Cloudflare Pages", "cloudflare-pages": "Cloudflare Pages",
"cloudflare-workers": "Cloudflare Workers",
codefresh: "Codefresh", codefresh: "Codefresh",
"digital-ocean-app-platform": "Digital Ocean App Platform", "digital-ocean-app-platform": "Digital Ocean App Platform",
bitbucket: "BitBucket", bitbucket: "BitBucket",
+36 -49
View File
@@ -1,59 +1,46 @@
import { useQuery } from "@tanstack/react-query"; import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request"; import { apiRequest } from "@app/config/request";
import { import { Actor, AuditLog, AuditLogFilters } from "./types";
Actor,
AuditLog,
AuditLogFilters
} from "./types";
export const workspaceKeys = { export const workspaceKeys = {
getAuditLogs: (workspaceId: string, filters: AuditLogFilters) => [{ workspaceId, filters }, "audit-logs"] as const, getAuditLogs: (workspaceId: string, filters: AuditLogFilters) =>
getAuditLogActorFilterOpts: (workspaceId: string) => [{ workspaceId }, "audit-log-actor-filters"] as const [{ workspaceId, filters }, "audit-logs"] as const,
} getAuditLogActorFilterOpts: (workspaceId: string) =>
[{ workspaceId }, "audit-log-actor-filters"] as const
};
export const useGetAuditLogs = (workspaceId: string, filters: AuditLogFilters) => { export const useGetAuditLogs = (workspaceId: string, filters: AuditLogFilters) => {
return useQuery({ return useInfiniteQuery({
queryKey: workspaceKeys.getAuditLogs(workspaceId, filters), queryKey: workspaceKeys.getAuditLogs(workspaceId, filters),
queryFn: async () => { queryFn: async ({ pageParam }) => {
const { data } = await apiRequest.get<{ auditLogs: AuditLog[] }>(
const params = new URLSearchParams(); `/api/v1/workspace/${workspaceId}/audit-logs`,
if (filters.eventType) { {
params.append("eventType", filters.eventType); params: {
} ...filters,
offset: pageParam,
if (filters.userAgentType) { startDate: filters?.startDate?.toISOString(),
params.append("userAgentType", filters.userAgentType); endDate: filters?.endDate?.toISOString()
} }
if (filters.actor) {
params.append("actor", filters.actor);
}
if (filters.startDate) {
params.append("startDate", filters.startDate.toISOString());
}
if (filters.endDate) {
params.append("endDate", filters.endDate.toISOString());
}
params.append("offset", String(filters.offset));
params.append("limit", String(filters.limit));
const { data } = await apiRequest.get<{ auditLogs: AuditLog[], totalCount: number }>(`/api/v1/workspace/${workspaceId}/audit-logs`, { params });
return data;
} }
}); );
} return data.auditLogs;
},
getNextPageParam: (lastPage, pages) =>
lastPage.length !== 0 ? pages.length * filters.limit : undefined
});
};
export const useGetAuditLogActorFilterOpts = (workspaceId: string) => { export const useGetAuditLogActorFilterOpts = (workspaceId: string) => {
return useQuery({ return useQuery({
queryKey: workspaceKeys.getAuditLogActorFilterOpts(workspaceId), queryKey: workspaceKeys.getAuditLogActorFilterOpts(workspaceId),
queryFn: async () => { queryFn: async () => {
const { data } = await apiRequest.get<{ actors: Actor[] }>(`/api/v1/workspace/${workspaceId}/audit-logs/filters/actors`); const { data } = await apiRequest.get<{ actors: Actor[] }>(
return data.actors; `/api/v1/workspace/${workspaceId}/audit-logs/filters/actors`
} );
}); return data.actors;
} }
});
};
+294 -303
View File
@@ -1,8 +1,4 @@
import { import { ActorType, EventType, UserAgentType } from "./enums";
ActorType,
EventType,
UserAgentType
} from "./enums";
interface UserActorMetadata { interface UserActorMetadata {
userId: string; userId: string;
@@ -25,174 +21,171 @@ export interface ServiceActor {
} }
export interface ServiceActorV3 { export interface ServiceActorV3 {
type: ActorType.SERVICE_V3; type: ActorType.SERVICE_V3;
metadata: ServiceActorMetadata; metadata: ServiceActorMetadata;
} }
export type Actor = export type Actor = UserActor | ServiceActor | ServiceActorV3;
| UserActor
| ServiceActor
| ServiceActorV3;
interface GetSecretsEvent { interface GetSecretsEvent {
type: EventType.GET_SECRETS; type: EventType.GET_SECRETS;
metadata: { metadata: {
environment: string; environment: string;
secretPath: string; secretPath: string;
numberOfSecrets: number; numberOfSecrets: number;
}; };
} }
interface GetSecretEvent { interface GetSecretEvent {
type: EventType.GET_SECRET; type: EventType.GET_SECRET;
metadata: { metadata: {
environment: string; environment: string;
secretPath: string; secretPath: string;
secretId: string; secretId: string;
secretKey: string; secretKey: string;
secretVersion: number; secretVersion: number;
}; };
} }
interface CreateSecretEvent { interface CreateSecretEvent {
type: EventType.CREATE_SECRET; type: EventType.CREATE_SECRET;
metadata: { metadata: {
environment: string; environment: string;
secretPath: string; secretPath: string;
secretId: string; secretId: string;
secretKey: string; secretKey: string;
secretVersion: number; secretVersion: number;
} };
} }
interface UpdateSecretEvent { interface UpdateSecretEvent {
type: EventType.UPDATE_SECRET; type: EventType.UPDATE_SECRET;
metadata: { metadata: {
environment: string; environment: string;
secretPath: string; secretPath: string;
secretId: string; secretId: string;
secretKey: string; secretKey: string;
secretVersion: number; secretVersion: number;
} };
} }
interface DeleteSecretEvent { interface DeleteSecretEvent {
type: EventType.DELETE_SECRET; type: EventType.DELETE_SECRET;
metadata: { metadata: {
environment: string; environment: string;
secretPath: string; secretPath: string;
secretId: string; secretId: string;
secretKey: string; secretKey: string;
secretVersion: number; secretVersion: number;
} };
} }
interface GetWorkspaceKeyEvent { interface GetWorkspaceKeyEvent {
type: EventType.GET_WORKSPACE_KEY, type: EventType.GET_WORKSPACE_KEY;
metadata: { metadata: {
keyId: string; keyId: string;
} };
} }
interface AuthorizeIntegrationEvent { interface AuthorizeIntegrationEvent {
type: EventType.AUTHORIZE_INTEGRATION; type: EventType.AUTHORIZE_INTEGRATION;
metadata: { metadata: {
integration: string; integration: string;
} };
} }
interface UnauthorizeIntegrationEvent { interface UnauthorizeIntegrationEvent {
type: EventType.UNAUTHORIZE_INTEGRATION; type: EventType.UNAUTHORIZE_INTEGRATION;
metadata: { metadata: {
integration: string; integration: string;
} };
} }
interface CreateIntegrationEvent { interface CreateIntegrationEvent {
type: EventType.CREATE_INTEGRATION; type: EventType.CREATE_INTEGRATION;
metadata: { metadata: {
integrationId: string; integrationId: string;
integration: string; integration: string;
environment: string; environment: string;
secretPath: string; secretPath: string;
url?: string; url?: string;
app?: string; app?: string;
appId?: string; appId?: string;
targetEnvironment?: string; targetEnvironment?: string;
targetEnvironmentId?: string; targetEnvironmentId?: string;
targetService?: string; targetService?: string;
targetServiceId?: string; targetServiceId?: string;
path?: string; path?: string;
region?: string; region?: string;
} };
} }
interface DeleteIntegrationEvent { interface DeleteIntegrationEvent {
type: EventType.DELETE_INTEGRATION; type: EventType.DELETE_INTEGRATION;
metadata: { metadata: {
integrationId: string; integrationId: string;
integration: string; integration: string;
environment: string; environment: string;
secretPath: string; secretPath: string;
url?: string; url?: string;
app?: string; app?: string;
appId?: string; appId?: string;
targetEnvironment?: string; targetEnvironment?: string;
targetEnvironmentId?: string; targetEnvironmentId?: string;
targetService?: string; targetService?: string;
targetServiceId?: string; targetServiceId?: string;
path?: string; path?: string;
region?: string; region?: string;
} };
} }
interface AddTrustedIPEvent { interface AddTrustedIPEvent {
type: EventType.ADD_TRUSTED_IP; type: EventType.ADD_TRUSTED_IP;
metadata: { metadata: {
trustedIpId: string; trustedIpId: string;
ipAddress: string; ipAddress: string;
prefix?: number; prefix?: number;
} };
} }
interface UpdateTrustedIPEvent { interface UpdateTrustedIPEvent {
type: EventType.UPDATE_TRUSTED_IP; type: EventType.UPDATE_TRUSTED_IP;
metadata: { metadata: {
trustedIpId: string; trustedIpId: string;
ipAddress: string; ipAddress: string;
prefix?: number; prefix?: number;
} };
} }
interface DeleteTrustedIPEvent { interface DeleteTrustedIPEvent {
type: EventType.DELETE_TRUSTED_IP; type: EventType.DELETE_TRUSTED_IP;
metadata: { metadata: {
trustedIpId: string; trustedIpId: string;
ipAddress: string; ipAddress: string;
prefix?: number; prefix?: number;
} };
} }
interface CreateServiceTokenEvent { interface CreateServiceTokenEvent {
type: EventType.CREATE_SERVICE_TOKEN; type: EventType.CREATE_SERVICE_TOKEN;
metadata: { metadata: {
name: string; name: string;
scopes: Array<{ scopes: Array<{
environment: string; environment: string;
secretPath: string; secretPath: string;
}>; }>;
} };
} }
interface DeleteServiceTokenEvent { interface DeleteServiceTokenEvent {
type: EventType.DELETE_SERVICE_TOKEN; type: EventType.DELETE_SERVICE_TOKEN;
metadata: { metadata: {
name: string; name: string;
scopes: Array<{ scopes: Array<{
environment: string; environment: string;
secretPath: string; secretPath: string;
}>; }>;
} };
} }
interface CreateServiceTokenV3Event { interface CreateServiceTokenV3Event {
@@ -226,227 +219,226 @@ interface DeleteServiceTokenV3Event {
} }
interface CreateEnvironmentEvent { interface CreateEnvironmentEvent {
type: EventType.CREATE_ENVIRONMENT; type: EventType.CREATE_ENVIRONMENT;
metadata: { metadata: {
name: string; name: string;
slug: string; slug: string;
} };
} }
interface UpdateEnvironmentEvent { interface UpdateEnvironmentEvent {
type: EventType.UPDATE_ENVIRONMENT; type: EventType.UPDATE_ENVIRONMENT;
metadata: { metadata: {
oldName: string; oldName: string;
newName: string; newName: string;
oldSlug: string; oldSlug: string;
newSlug: string; newSlug: string;
} };
} }
interface DeleteEnvironmentEvent { interface DeleteEnvironmentEvent {
type: EventType.DELETE_ENVIRONMENT; type: EventType.DELETE_ENVIRONMENT;
metadata: { metadata: {
name: string; name: string;
slug: string; slug: string;
} };
} }
interface AddWorkspaceMemberEvent { interface AddWorkspaceMemberEvent {
type: EventType.ADD_WORKSPACE_MEMBER; type: EventType.ADD_WORKSPACE_MEMBER;
metadata: { metadata: {
userId: string; userId: string;
email: string; email: string;
} };
} }
interface RemoveWorkspaceMemberEvent { interface RemoveWorkspaceMemberEvent {
type: EventType.REMOVE_WORKSPACE_MEMBER; type: EventType.REMOVE_WORKSPACE_MEMBER;
metadata: { metadata: {
userId: string; userId: string;
email: string; email: string;
} };
} }
interface CreateFolderEvent { interface CreateFolderEvent {
type: EventType.CREATE_FOLDER; type: EventType.CREATE_FOLDER;
metadata: { metadata: {
environment: string; environment: string;
folderId: string; folderId: string;
folderName: string; folderName: string;
folderPath: string; folderPath: string;
} };
} }
interface UpdateFolderEvent { interface UpdateFolderEvent {
type: EventType.UPDATE_FOLDER; type: EventType.UPDATE_FOLDER;
metadata: { metadata: {
environment: string; environment: string;
folderId: string; folderId: string;
oldFolderName: string; oldFolderName: string;
newFolderName: string; newFolderName: string;
folderPath: string; folderPath: string;
} };
} }
interface DeleteFolderEvent { interface DeleteFolderEvent {
type: EventType.DELETE_FOLDER; type: EventType.DELETE_FOLDER;
metadata: { metadata: {
environment: string; environment: string;
folderId: string; folderId: string;
folderName: string; folderName: string;
folderPath: string; folderPath: string;
} };
} }
interface CreateWebhookEvent { interface CreateWebhookEvent {
type: EventType.CREATE_WEBHOOK, type: EventType.CREATE_WEBHOOK;
metadata: { metadata: {
webhookId: string; webhookId: string;
environment: string; environment: string;
secretPath: string; secretPath: string;
webhookUrl: string; webhookUrl: string;
isDisabled: boolean; isDisabled: boolean;
} };
} }
interface UpdateWebhookStatusEvent { interface UpdateWebhookStatusEvent {
type: EventType.UPDATE_WEBHOOK_STATUS, type: EventType.UPDATE_WEBHOOK_STATUS;
metadata: { metadata: {
webhookId: string; webhookId: string;
environment: string; environment: string;
secretPath: string; secretPath: string;
webhookUrl: string; webhookUrl: string;
isDisabled: boolean; isDisabled: boolean;
} };
} }
interface DeleteWebhookEvent { interface DeleteWebhookEvent {
type: EventType.DELETE_WEBHOOK, type: EventType.DELETE_WEBHOOK;
metadata: { metadata: {
webhookId: string; webhookId: string;
environment: string; environment: string;
secretPath: string; secretPath: string;
webhookUrl: string; webhookUrl: string;
isDisabled: boolean; isDisabled: boolean;
} };
} }
interface GetSecretImportsEvent { interface GetSecretImportsEvent {
type: EventType.GET_SECRET_IMPORTS, type: EventType.GET_SECRET_IMPORTS;
metadata: { metadata: {
environment: string; environment: string;
secretImportId: string; secretImportId: string;
folderId: string; folderId: string;
numberOfImports: number; numberOfImports: number;
} };
} }
interface CreateSecretImportEvent { interface CreateSecretImportEvent {
type: EventType.CREATE_SECRET_IMPORT, type: EventType.CREATE_SECRET_IMPORT;
metadata: { metadata: {
secretImportId: string; secretImportId: string;
folderId: string; folderId: string;
importFromEnvironment: string; importFromEnvironment: string;
importFromSecretPath: string; importFromSecretPath: string;
importToEnvironment: string; importToEnvironment: string;
importToSecretPath: string; importToSecretPath: string;
} };
} }
interface UpdateSecretImportEvent { interface UpdateSecretImportEvent {
type: EventType.UPDATE_SECRET_IMPORT, type: EventType.UPDATE_SECRET_IMPORT;
metadata: { metadata: {
secretImportId: string; secretImportId: string;
folderId: string; folderId: string;
importToEnvironment: string; importToEnvironment: string;
importToSecretPath: string; importToSecretPath: string;
orderBefore: { orderBefore: {
environment: string; environment: string;
secretPath: string; secretPath: string;
}[], }[];
orderAfter: { orderAfter: {
environment: string; environment: string;
secretPath: string; secretPath: string;
}[] }[];
} };
} }
interface DeleteSecretImportEvent { interface DeleteSecretImportEvent {
type: EventType.DELETE_SECRET_IMPORT, type: EventType.DELETE_SECRET_IMPORT;
metadata: { metadata: {
secretImportId: string; secretImportId: string;
folderId: string; folderId: string;
importFromEnvironment: string; importFromEnvironment: string;
importFromSecretPath: string; importFromSecretPath: string;
importToEnvironment: string; importToEnvironment: string;
importToSecretPath: string; importToSecretPath: string;
} };
} }
interface UpdateUserRole { interface UpdateUserRole {
type: EventType.UPDATE_USER_WORKSPACE_ROLE, type: EventType.UPDATE_USER_WORKSPACE_ROLE;
metadata: { metadata: {
userId: string; userId: string;
email: string; email: string;
oldRole: string; oldRole: string;
newRole: string; newRole: string;
} };
} }
interface UpdateUserDeniedPermissions { interface UpdateUserDeniedPermissions {
type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS, type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS;
metadata: { metadata: {
userId: string; userId: string;
email: string; email: string;
deniedPermissions: { deniedPermissions: {
environmentSlug: string; environmentSlug: string;
ability: string; ability: string;
}[] }[];
} };
} }
export type Event =
export type Event = | GetSecretsEvent
| GetSecretsEvent | GetSecretEvent
| GetSecretEvent | CreateSecretEvent
| CreateSecretEvent | UpdateSecretEvent
| UpdateSecretEvent | DeleteSecretEvent
| DeleteSecretEvent | GetWorkspaceKeyEvent
| GetWorkspaceKeyEvent | AuthorizeIntegrationEvent
| AuthorizeIntegrationEvent | UnauthorizeIntegrationEvent
| UnauthorizeIntegrationEvent | CreateIntegrationEvent
| CreateIntegrationEvent | DeleteIntegrationEvent
| DeleteIntegrationEvent | AddTrustedIPEvent
| AddTrustedIPEvent | UpdateTrustedIPEvent
| UpdateTrustedIPEvent | DeleteTrustedIPEvent
| DeleteTrustedIPEvent | CreateServiceTokenEvent
| CreateServiceTokenEvent | DeleteServiceTokenEvent
| DeleteServiceTokenEvent | CreateServiceTokenV3Event
| CreateServiceTokenV3Event | UpdateServiceTokenV3Event
| UpdateServiceTokenV3Event | DeleteServiceTokenV3Event
| DeleteServiceTokenV3Event | CreateEnvironmentEvent
| CreateEnvironmentEvent | UpdateEnvironmentEvent
| UpdateEnvironmentEvent | DeleteEnvironmentEvent
| DeleteEnvironmentEvent | AddWorkspaceMemberEvent
| AddWorkspaceMemberEvent | RemoveWorkspaceMemberEvent
| RemoveWorkspaceMemberEvent | CreateFolderEvent
| CreateFolderEvent | UpdateFolderEvent
| UpdateFolderEvent | DeleteFolderEvent
| DeleteFolderEvent | CreateWebhookEvent
| CreateWebhookEvent | UpdateWebhookStatusEvent
| UpdateWebhookStatusEvent | DeleteWebhookEvent
| DeleteWebhookEvent | GetSecretImportsEvent
| GetSecretImportsEvent | CreateSecretImportEvent
| CreateSecretImportEvent | UpdateSecretImportEvent
| UpdateSecretImportEvent | DeleteSecretImportEvent
| DeleteSecretImportEvent | UpdateUserRole
| UpdateUserRole | UpdateUserDeniedPermissions;
| UpdateUserDeniedPermissions;
export type AuditLog = { export type AuditLog = {
_id: string; _id: string;
actor: Actor; actor: Actor;
organization: string; organization: string;
workspace: string; workspace: string;
ipAddress: string; ipAddress: string;
event: Event; event: Event;
@@ -454,14 +446,13 @@ export type AuditLog = {
userAgentType: UserAgentType; userAgentType: UserAgentType;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
} };
export type AuditLogFilters = { export type AuditLogFilters = {
eventType?: EventType; eventType?: EventType;
userAgentType?: UserAgentType; userAgentType?: UserAgentType;
actor?: string; actor?: string;
offset: number; limit: number;
limit: number; startDate?: Date;
startDate?: Date; endDate?: Date;
endDate?: Date; };
}
@@ -0,0 +1,92 @@
import { useState } from "react";
import { useRouter } from "next/router";
import {
useSaveIntegrationAccessToken
} from "@app/hooks/api";
import { Button,Card, CardTitle, FormControl, Input } from "../../../components/v2";
export default function CloudflareWorkersIntegrationPage() {
const router = useRouter();
const { mutateAsync } = useSaveIntegrationAccessToken();
const [accessKey, setAccessKey] = useState("");
const [accessKeyErrorText, setAccessKeyErrorText] = useState("");
const [accountId, setAccountId] = useState("");
const [accountIdErrorText, setAccountIdErrorText] = useState("");
const [isLoading, setIsLoading] = useState(false);
const handleButtonClick = async () => {
try {
setAccessKeyErrorText("");
setAccountIdErrorText("");
if (accessKey.length === 0 || accountId.length === 0) {
if (accessKey.length === 0) setAccessKeyErrorText("API token cannot be blank!");
if (accountId.length === 0) setAccountIdErrorText("Account ID cannot be blank!");
return;
}
setIsLoading(true);
const integrationAuth = await mutateAsync({
workspaceId: localStorage.getItem("projectData.id"),
integration: "cloudflare-workers",
accessId: accountId,
accessToken: accessKey
});
setAccessKey("");
setAccountId("");
setIsLoading(false);
router.push(`/integrations/cloudflare-workers/create?integrationAuthId=${integrationAuth._id}`);
} catch (err) {
console.error(err);
}
}
return (
<div className="flex h-full w-full items-center justify-center">
<Card className="max-w-lg rounded-md border border-mineshaft-600 mb-12">
<CardTitle className="text-left px-6" subTitle="After adding your API-key, you will be prompted to set up an integration for a particular Infisical project and environment.">Cloudflare Workers Integration</CardTitle>
<FormControl
label="Cloudflare Workers API token"
errorText={accessKeyErrorText}
isError={accessKeyErrorText !== "" ?? false}
className="mx-6"
>
<Input
placeholder=""
value={accessKey}
onChange={(e) => setAccessKey(e.target.value)}
/>
</FormControl>
<FormControl
label="Cloudflare Workers Account ID"
errorText={accountIdErrorText}
isError={accountIdErrorText !== "" ?? false}
className="mx-6"
>
<Input
placeholder=""
value={accountId}
onChange={(e) => setAccountId(e.target.value)}
/>
</FormControl>
<Button
onClick={handleButtonClick}
color="mineshaft"
variant="outline_bg"
className="mb-6 mt-2 ml-auto mr-6 w-min"
isFullWidth={false}
isLoading={isLoading}
>
Connect to Cloudflare Workers
</Button>
</Card>
</div>
);
}
CloudflareWorkersIntegrationPage.requireAuth = true;
@@ -0,0 +1,133 @@
import { useEffect,useState } from "react";
import { useRouter } from "next/router";
import queryString from "query-string";
import {
useCreateIntegration
, useGetWorkspaceById } from "@app/hooks/api";
import { Button, Card, CardTitle, FormControl, Select, SelectItem } from "../../../components/v2";
import { useGetIntegrationAuthApps, useGetIntegrationAuthById } from "../../../hooks/api/integrationAuth";
export default function CloudflareWorkersIntegrationPage() {
const router = useRouter();
const { mutateAsync } = useCreateIntegration();
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
integrationAuthId: (integrationAuthId as string) ?? ""
});
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
const [targetApp, setTargetApp] = useState("");
const [targetAppId, setTargetAppId] = useState("");
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (workspace) {
setSelectedSourceEnvironment(workspace.environments[0].slug);
}
}, [workspace]);
useEffect(() => {
if (integrationAuthApps) {
if (integrationAuthApps.length > 0) {
setTargetApp(integrationAuthApps[0].name);
setTargetAppId(String(integrationAuthApps[0].appId));
} else {
setTargetApp("none");
}
}
}, [integrationAuthApps]);
const handleButtonClick = async () => {
try {
if (!integrationAuth?._id) return;
setIsLoading(true);
await mutateAsync({
integrationAuthId: integrationAuth?._id,
isActive: true,
app: targetApp,
appId: targetAppId,
sourceEnvironment: selectedSourceEnvironment,
secretPath: "/",
});
setIsLoading(false);
router.push(`/integrations/${localStorage.getItem("projectData.id")}`);
} catch(err) {
console.error(err);
}
}
return integrationAuth &&
workspace &&
selectedSourceEnvironment &&
integrationAuthApps &&
targetApp ? (
<div className="flex h-full w-full items-center justify-center bg-gradient-to-tr from-mineshaft-900 to-bunker-900">
<Card className="max-w-lg rounded-md p-0 border border-mineshaft-600">
<CardTitle className="text-left px-6" subTitle="Choose which environment in Infisical you want to sync with your Cloudflare Workers project.">Cloudflare Workers Integration</CardTitle>
<FormControl label="Infisical Project Environment" className="mt-2 px-6">
<Select
value={selectedSourceEnvironment}
onValueChange={(val) => setSelectedSourceEnvironment(val)}
className="w-full border border-mineshaft-500"
>
{workspace?.environments.map((sourceEnvironment) => (
<SelectItem
value={sourceEnvironment.slug}
key={`source-environment-${sourceEnvironment.slug}`}
>
{sourceEnvironment.name}
</SelectItem>
))}
</Select>
</FormControl>
<FormControl label="Cloudflare Workers Project" className="mt-4 px-6">
<Select
value={targetApp}
onValueChange={(val) => setTargetApp(val)}
className="w-full border border-mineshaft-500"
isDisabled={integrationAuthApps.length === 0}
>
{integrationAuthApps.length > 0 ? (
integrationAuthApps.map((integrationAuthApp) => (
<SelectItem
value={integrationAuthApp.name}
key={`target-app-${integrationAuthApp.name}`}
>
{integrationAuthApp.name}
</SelectItem>
))
) : (
<SelectItem value="none" key="target-app-none">
No apps found
</SelectItem>
)}
</Select>
</FormControl>
<Button
onClick={handleButtonClick}
color="mineshaft"
variant="outline_bg"
className="mt-2 mb-6 ml-auto mr-6"
isFullWidth={false}
isLoading={isLoading}
>
Create Integration
</Button>
</Card>
</div>
) : (
<div />
);
}
CloudflareWorkersIntegrationPage.requireAuth = true;
@@ -101,6 +101,9 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) =>
case "cloudflare-pages": case "cloudflare-pages":
link = `${window.location.origin}/integrations/cloudflare-pages/authorize`; link = `${window.location.origin}/integrations/cloudflare-pages/authorize`;
break; break;
case "cloudflare-workers":
link = `${window.location.origin}/integrations/cloudflare-workers/authorize`;
break;
case "bitbucket": case "bitbucket":
link = `https://bitbucket.org/site/oauth2/authorize?client_id=${integrationOption.clientId}&response_type=code&redirect_uri=${window.location.origin}/integrations/bitbucket/oauth2/callback&state=${state}`; link = `https://bitbucket.org/site/oauth2/authorize?client_id=${integrationOption.clientId}&response_type=code&redirect_uri=${window.location.origin}/integrations/bitbucket/oauth2/callback&state=${state}`;
break; break;
@@ -8,7 +8,7 @@ export const AuditLogsPage = withProjectPermission(
return ( return (
<div className="flex justify-center bg-bunker-800 text-white w-full h-full"> <div className="flex justify-center bg-bunker-800 text-white w-full h-full">
<div className="max-w-7xl px-6 w-full"> <div className="max-w-7xl px-6 w-full">
<div className="my-6"> <div className="py-6 sticky top-0 z-10 bg-bunker-800">
<p className="text-3xl font-semibold text-gray-200">Audit Logs</p> <p className="text-3xl font-semibold text-gray-200">Audit Logs</p>
<div /> <div />
</div> </div>
@@ -3,13 +3,7 @@ import { Control, Controller, UseFormReset } from "react-hook-form";
import { faFilterCircleXmark } from "@fortawesome/free-solid-svg-icons"; import { faFilterCircleXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { import { Button, DatePicker, FormControl, Select, SelectItem } from "@app/components/v2";
Button,
DatePicker,
FormControl,
Select,
SelectItem
} from "@app/components/v2";
import { useWorkspace } from "@app/context"; import { useWorkspace } from "@app/context";
import { useGetAuditLogActorFilterOpts } from "@app/hooks/api"; import { useGetAuditLogActorFilterOpts } from "@app/hooks/api";
import { eventToNameMap, userAgentTTypeoNameMap } from "@app/hooks/api/auditLogs/constants"; import { eventToNameMap, userAgentTTypeoNameMap } from "@app/hooks/api/auditLogs/constants";
@@ -19,201 +13,207 @@ import { Actor } from "@app/hooks/api/auditLogs/types";
import { AuditLogFilterFormData } from "./types"; import { AuditLogFilterFormData } from "./types";
const eventTypes = Object.entries(eventToNameMap).map(([value, label]) => ({ label, value })); const eventTypes = Object.entries(eventToNameMap).map(([value, label]) => ({ label, value }));
const userAgentTypes = Object.entries(userAgentTTypeoNameMap).map(([value, label]) => ({ label, value })); const userAgentTypes = Object.entries(userAgentTTypeoNameMap).map(([value, label]) => ({
label,
value
}));
type Props = { type Props = {
control: Control<AuditLogFilterFormData>; control: Control<AuditLogFilterFormData>;
reset: UseFormReset<AuditLogFilterFormData>; reset: UseFormReset<AuditLogFilterFormData>;
} };
export const LogsFilter = ({ export const LogsFilter = ({ control, reset }: Props) => {
control, const [isStartDatePickerOpen, setIsStartDatePickerOpen] = useState(false);
reset const [isEndDatePickerOpen, setIsEndDatePickerOpen] = useState(false);
}: Props) => {
const [isStartDatePickerOpen, setIsStartDatePickerOpen] = useState(false);
const [isEndDatePickerOpen, setIsEndDatePickerOpen] = useState(false);
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { data, isLoading } = useGetAuditLogActorFilterOpts(currentWorkspace?._id ?? ""); const { data, isLoading } = useGetAuditLogActorFilterOpts(currentWorkspace?._id ?? "");
const renderActorSelectItem = (actor: Actor) => {
switch (actor.type) {
case ActorType.USER:
return (
<SelectItem value={`${actor.type}-${actor.metadata.userId}`} key={`user-actor-filter-${actor.metadata.userId}`}>
{actor.metadata.email}
</SelectItem>
);
case ActorType.SERVICE:
return (
<SelectItem value={`${actor.type}-${actor.metadata.serviceId}`} key={`service-actor-filter-${actor.metadata.serviceId}`}>
{actor.metadata.name}
</SelectItem>
);
case ActorType.SERVICE_V3:
return (
<SelectItem value={`${actor.type}-${actor.metadata.serviceId}`} key={`service-actor-v3-filter-${actor.metadata.serviceId}`}>
{actor.metadata.name}
</SelectItem>
);
default:
return (
<SelectItem value="actor-none" key="actor-none">
N/A
</SelectItem>
);
}
const renderActorSelectItem = (actor: Actor) => {
switch (actor.type) {
case ActorType.USER:
return (
<SelectItem
value={`${actor.type}-${actor.metadata.userId}`}
key={`user-actor-filter-${actor.metadata.userId}`}
>
{actor.metadata.email}
</SelectItem>
);
case ActorType.SERVICE:
return (
<SelectItem
value={`${actor.type}-${actor.metadata.serviceId}`}
key={`service-actor-filter-${actor.metadata.serviceId}`}
>
{actor.metadata.name}
</SelectItem>
);
case ActorType.SERVICE_V3:
return (
<SelectItem
value={`${actor.type}-${actor.metadata.serviceId}`}
key={`service-actor-v3-filter-${actor.metadata.serviceId}`}
>
{actor.metadata.name}
</SelectItem>
);
default:
return (
<SelectItem value="actor-none" key="actor-none">
N/A
</SelectItem>
);
} }
};
return ( return (
<div className="flex justify-between items-center"> <div className="flex justify-between items-center sticky top-20 z-10 bg-bunker-800">
<div className="flex items-center"> <div className="flex items-center">
<Controller <Controller
control={control} control={control}
name="eventType" name="eventType"
render={({ field: { onChange, ...field }, fieldState: { error } }) => ( render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl <FormControl
label="Event" label="Event"
errorText={error?.message} errorText={error?.message}
isError={Boolean(error)} isError={Boolean(error)}
className="w-40 mr-4" className="w-40 mr-4"
> >
<Select <Select
{...(field.value ? { value: field.value } : { placeholder: "Select" })} {...(field.value ? { value: field.value } : { placeholder: "Select" })}
{...field} {...field}
onValueChange={(e) => onChange(e)} onValueChange={(e) => onChange(e)}
className="w-full bg-mineshaft-700 border border-mineshaft-500 text-mineshaft-100" className="w-full bg-mineshaft-700 border border-mineshaft-500 text-mineshaft-100"
> >
{eventTypes.map(({ label, value }) => ( {eventTypes.map(({ label, value }) => (
<SelectItem value={String(value || "")} key={label}> <SelectItem value={String(value || "")} key={label}>
{label} {label}
</SelectItem> </SelectItem>
))} ))}
</Select> </Select>
</FormControl> </FormControl>
)} )}
/> />
{!isLoading && data && data.length > 0 && ( {!isLoading && data && data.length > 0 && (
<Controller <Controller
control={control} control={control}
name="actor" name="actor"
render={({ field: { onChange, ...field }, fieldState: { error } }) => ( render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl <FormControl
label="Actor" label="Actor"
errorText={error?.message} errorText={error?.message}
isError={Boolean(error)} isError={Boolean(error)}
className="w-40 mr-4" className="w-40 mr-4"
> >
<Select <Select
{...(field.value ? { value: field.value } : { placeholder: "Select" })} {...(field.value ? { value: field.value } : { placeholder: "Select" })}
{...field} {...field}
onValueChange={(e) => onChange(e)} onValueChange={(e) => onChange(e)}
className="w-full bg-mineshaft-700 border border-mineshaft-500 text-mineshaft-100" className="w-full bg-mineshaft-700 border border-mineshaft-500 text-mineshaft-100"
>
{data.map((actor) => renderActorSelectItem(actor))}
</Select>
</FormControl>
)}
/>
)}
<Controller
control={control}
name="userAgentType"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="Source"
errorText={error?.message}
isError={Boolean(error)}
className="w-40 mr-4"
>
<Select
{...(field.value ? { value: field.value } : { placeholder: "Select" })}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full bg-mineshaft-700 border border-mineshaft-500 text-mineshaft-100"
>
{userAgentTypes.map(({ label, value }) => (
<SelectItem value={String(value || "")} key={label}>
{label}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<Controller
name="startDate"
control={control}
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
return (
<FormControl
label="Start date"
errorText={error?.message}
isError={Boolean(error)}
className="mr-4"
>
<DatePicker
value={field.value || undefined}
onChange={date => {
onChange(date);
setIsStartDatePickerOpen(false);
}}
popUpProps={{
open: isStartDatePickerOpen,
onOpenChange: setIsStartDatePickerOpen
}}
popUpContentProps={{}}
/>
</FormControl>
);
}}
/>
<Controller
name="endDate"
control={control}
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
return (
<FormControl
label="End date"
errorText={error?.message}
isError={Boolean(error)}
>
<DatePicker
value={field.value || undefined}
onChange={date => {
onChange(date);
setIsEndDatePickerOpen(false);
}}
popUpProps={{
open: isEndDatePickerOpen,
onOpenChange: setIsEndDatePickerOpen
}}
popUpContentProps={{}}
/>
</FormControl>
);
}}
/>
</div>
<div>
<Button
isLoading={false}
colorSchema="primary"
variant="outline_bg"
type="submit"
leftIcon={<FontAwesomeIcon icon={faFilterCircleXmark} className="mr-2" />}
onClick={() => reset({
eventType: undefined,
actor: undefined,
userAgentType: undefined,
startDate: undefined,
endDate: undefined
})}
> >
Clear filters {data.map((actor) => renderActorSelectItem(actor))}
</Button> </Select>
</div> </FormControl>
</div> )}
); />
} )}
<Controller
control={control}
name="userAgentType"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="Source"
errorText={error?.message}
isError={Boolean(error)}
className="w-40 mr-4"
>
<Select
{...(field.value ? { value: field.value } : { placeholder: "Select" })}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full bg-mineshaft-700 border border-mineshaft-500 text-mineshaft-100"
>
{userAgentTypes.map(({ label, value }) => (
<SelectItem value={String(value || "")} key={label}>
{label}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<Controller
name="startDate"
control={control}
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
return (
<FormControl
label="Start date"
errorText={error?.message}
isError={Boolean(error)}
className="mr-4"
>
<DatePicker
value={field.value || undefined}
onChange={(date) => {
onChange(date);
setIsStartDatePickerOpen(false);
}}
popUpProps={{
open: isStartDatePickerOpen,
onOpenChange: setIsStartDatePickerOpen
}}
popUpContentProps={{}}
/>
</FormControl>
);
}}
/>
<Controller
name="endDate"
control={control}
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
return (
<FormControl label="End date" errorText={error?.message} isError={Boolean(error)}>
<DatePicker
value={field.value || undefined}
onChange={(date) => {
onChange(date);
setIsEndDatePickerOpen(false);
}}
popUpProps={{
open: isEndDatePickerOpen,
onOpenChange: setIsEndDatePickerOpen
}}
popUpContentProps={{}}
/>
</FormControl>
);
}}
/>
</div>
<div>
<Button
isLoading={false}
colorSchema="primary"
variant="outline_bg"
type="submit"
leftIcon={<FontAwesomeIcon icon={faFilterCircleXmark} className="mr-2" />}
onClick={() =>
reset({
eventType: undefined,
actor: undefined,
userAgentType: undefined,
startDate: undefined,
endDate: undefined
})
}
>
Clear filters
</Button>
</div>
</div>
);
};
@@ -13,78 +13,54 @@ import { LogsTable } from "./LogsTable";
import { AuditLogFilterFormData, auditLogFilterFormSchema } from "./types"; import { AuditLogFilterFormData, auditLogFilterFormSchema } from "./types";
export const LogsSection = () => { export const LogsSection = () => {
const { subscription } = useSubscription(); const { subscription } = useSubscription();
const router = useRouter(); const router = useRouter();
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
"upgradePlan"
] as const);
const {
control,
reset,
watch,
setValue,
} = useForm<AuditLogFilterFormData>({
resolver: yupResolver(auditLogFilterFormSchema),
defaultValues: {
page: 1,
perPage: 10
}
});
useEffect(() => {
if (subscription && !subscription.auditLogs) {
handlePopUpOpen("upgradePlan");
}
}, [subscription]);
const eventType = watch("eventType") as EventType | undefined; const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
const userAgentType = watch("userAgentType") as UserAgentType | undefined;
const actor = watch("actor");
const startDate = watch("startDate"); const { control, reset, watch } = useForm<AuditLogFilterFormData>({
const endDate = watch("endDate"); resolver: yupResolver(auditLogFilterFormSchema),
defaultValues: {
page: 1,
perPage: 10
}
});
const page = watch("page") as number; useEffect(() => {
const perPage = watch("perPage") as number; if (subscription && !subscription.auditLogs) {
handlePopUpOpen("upgradePlan");
return ( }
<div }, [subscription]);
// className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
>
{/* <div className="flex items-center mb-8">
<h2 className="text-xl font-semibold flex-1 text-white">
Audit Logs
</h2>
</div> */}
<LogsFilter
control={control}
reset={reset}
/>
<LogsTable
eventType={eventType}
userAgentType={userAgentType}
actor={actor}
startDate={startDate}
endDate={endDate}
page={page}
perPage={perPage}
setValue={setValue}
/>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => {
if (!isOpen) {
router.back();
return;
}
handlePopUpToggle("upgradePlan", isOpen) const eventType = watch("eventType") as EventType | undefined;
}} const userAgentType = watch("userAgentType") as UserAgentType | undefined;
text="You can use audit logs if you switch to a paid Infisical plan." const actor = watch("actor");
/>
</div> const startDate = watch("startDate");
); const endDate = watch("endDate");
}
return (
<div>
<LogsFilter control={control} reset={reset} />
<LogsTable
eventType={eventType}
userAgentType={userAgentType}
actor={actor}
startDate={startDate}
endDate={endDate}
/>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => {
if (!isOpen) {
router.back();
return;
}
handlePopUpToggle("upgradePlan", isOpen);
}}
text="You can use audit logs if you switch to a paid Infisical plan."
/>
</div>
);
};
@@ -1,96 +1,95 @@
import { Fragment } from "react";
import { faFile } from "@fortawesome/free-solid-svg-icons"; import { faFile } from "@fortawesome/free-solid-svg-icons";
import { import {
EmptyState, Button,
Pagination, EmptyState,
Table, Table,
TableContainer, TableContainer,
TableSkeleton, TableSkeleton,
TBody, TBody,
Td, Td,
Th, Th,
THead, THead,
Tr} from "@app/components/v2"; Tr
} from "@app/components/v2";
import { useWorkspace } from "@app/context"; import { useWorkspace } from "@app/context";
import { useGetAuditLogs } from "@app/hooks/api"; import { useGetAuditLogs } from "@app/hooks/api";
import { EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums"; import { EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums";
import { LogsTableRow } from "./LogsTableRow"; import { LogsTableRow } from "./LogsTableRow";
import { SetValueType } from "./types";
type Props = { type Props = {
eventType?: EventType; eventType?: EventType;
userAgentType?: UserAgentType; userAgentType?: UserAgentType;
actor?: string; actor?: string;
startDate?: Date; startDate?: Date;
endDate?: Date; endDate?: Date;
page: number; };
perPage: number;
setValue: SetValueType;
}
export const LogsTable = ({ const AUDIT_LOG_LIMIT = 15;
eventType,
userAgentType, export const LogsTable = ({ eventType, userAgentType, actor, startDate, endDate }: Props) => {
actor, const { currentWorkspace } = useWorkspace();
startDate, const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = useGetAuditLogs(
endDate, currentWorkspace?._id ?? "",
page, {
perPage, eventType,
setValue userAgentType,
}: Props) => { actor,
const { currentWorkspace } = useWorkspace(); startDate,
const { data, isLoading } = useGetAuditLogs(currentWorkspace?._id ?? "", { endDate,
eventType, limit: AUDIT_LOG_LIMIT
userAgentType, }
actor, );
startDate,
endDate, const isEmpty = !isLoading && !data?.pages?.[0].length;
offset: (page - 1) * perPage,
limit: perPage return (
}); <div>
<TableContainer>
return ( <Table>
<TableContainer> <THead>
<Table> <Tr>
<THead> <Th>Timestamp</Th>
<Tr> <Th>Event</Th>
<Th>Timestamp</Th> <Th>Actor</Th>
<Th>Event</Th> <Th>Source</Th>
<Th>Actor</Th> <Th>Metadata</Th>
<Th>Source</Th> </Tr>
<Th>Metadata</Th> </THead>
</Tr> <TBody>
</THead> {!isLoading &&
<TBody> data?.pages?.map((group, i) => (
{!isLoading && data?.auditLogs && data?.auditLogs.map((auditLog) => ( <Fragment key={`auditlog-item-${i + 1}`}>
<LogsTableRow {group.map((auditLog) => (
auditLog={auditLog} <LogsTableRow auditLog={auditLog} key={`audit-log-${auditLog._id}`} />
key={`audit-log-${auditLog._id}`} ))}
/> </Fragment>
))} ))}
{isLoading && <TableSkeleton innerKey="logs-table" columns={5} key="logs" />} {isLoading && <TableSkeleton innerKey="logs-table" columns={5} key="logs" />}
{!isLoading && data?.auditLogs && data?.auditLogs.length === 0 && ( {isEmpty && (
<Tr> <Tr>
<Td colSpan={5}> <Td colSpan={5}>
<EmptyState <EmptyState title="No audit logs on file" icon={faFile} />
title="No audit logs on file" </Td>
icon={faFile} </Tr>
/>
</Td>
</Tr>
)}
</TBody>
</Table>
{!isLoading && data?.totalCount !== undefined && (
<Pagination
count={data?.totalCount}
page={page}
perPage={perPage}
onChangePage={(newPage) => setValue("page", newPage)}
onChangePerPage={(newPerPage) => setValue("perPage", newPerPage)}
/>
)} )}
</TableContainer> </TBody>
); </Table>
} </TableContainer>
{!isEmpty && (
<Button
className="mt-4 mb-20 py-3 px-4 text-sm"
isFullWidth
variant="star"
isLoading={isFetchingNextPage}
isDisabled={isFetchingNextPage || !hasNextPage}
onClick={() => fetchNextPage()}
>
{hasNextPage ? "Load More" : "End of logs"}
</Button>
)}
</div>
);
};