diff --git a/backend/src/data-source.ts b/backend/src/data-source.ts index d95042389..d3cc40a1e 100644 --- a/backend/src/data-source.ts +++ b/backend/src/data-source.ts @@ -7,12 +7,9 @@ import { OpenApiSpec, Alert, Session, - Connections, ApiEndpointTest, ApiKey, - BlockFields, InstanceSettings, - AuthenticationConfig, AggregateTraceDataHourly, Attack, Webhook, @@ -51,6 +48,7 @@ import { dropArrayFields1677803493465 } from "migrations/1677803493465-drop-arra import { removeTraceHash1678477672617 } from "migrations/1678477672617-remove-trace-hash" import { addResourcePermsEndpoint1679174209000 } from "migrations/1679174209000-add-resource-perms-endpoint" import { apiEndpointTokenColumns1679515538397 } from "migrations/1679515538397-api-endpoint-token-columns" +import { dropBlockFieldAuthenticationTables1679774815747 } from "migrations/1679774815747-drop-block-field-authentication-tables" export const AppDataSource: DataSource = new DataSource({ type: "postgres", @@ -62,12 +60,9 @@ export const AppDataSource: DataSource = new DataSource({ OpenApiSpec, Alert, Session, - Connections, ApiEndpointTest, ApiKey, - BlockFields, InstanceSettings, - AuthenticationConfig, AggregateTraceDataHourly, Attack, MetloConfig, @@ -107,6 +102,7 @@ export const AppDataSource: DataSource = new DataSource({ removeTraceHash1678477672617, addResourcePermsEndpoint1679174209000, apiEndpointTokenColumns1679515538397, + dropBlockFieldAuthenticationTables1679774815747, ], migrationsRun: runMigration, logging: false, diff --git a/backend/src/migrations/1679774815747-drop-block-field-authentication-tables.ts b/backend/src/migrations/1679774815747-drop-block-field-authentication-tables.ts new file mode 100644 index 000000000..33cd4a984 --- /dev/null +++ b/backend/src/migrations/1679774815747-drop-block-field-authentication-tables.ts @@ -0,0 +1,23 @@ +import { MigrationInterface, QueryRunner } from "typeorm" + +export class dropBlockFieldAuthenticationTables1679774815747 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE IF EXISTS "authentication_config"`) + await queryRunner.query(`DROP TABLE IF EXISTS "block_fields"`) + await queryRunner.query(`DROP TABLE IF EXISTS "connections"`) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE IF NOT EXISTS "authentication_config" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "host" character varying NOT NULL, "authType" "public"."auth_type_enum" NOT NULL, "headerKey" character varying, "jwtUserPath" character varying, "cookieName" character varying, CONSTRAINT "PK_9b463ac4dad1b0cf46a6fad5575" PRIMARY KEY ("uuid"))`, + ) + await queryRunner.query( + `CREATE TABLE IF NOT EXISTS "block_fields" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "path" character varying NOT NULL, "pathRegex" character varying NOT NULL, "method" "public"."disable_rest_method_enum" NOT NULL, "host" character varying NOT NULL, "numberParams" integer NOT NULL DEFAULT '0', "disabledPaths" jsonb, CONSTRAINT "PK_af4435534e7494a34ce5330fba7" PRIMARY KEY ("uuid"))`, + ) + await queryRunner.query( + `CREATE TABLE IF NOT EXISTS "connections" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "connectionType" "public"."connection_type_enum" NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "name" character varying NOT NULL, "aws" jsonb, "aws_meta" jsonb, "gcp" jsonb, "gcp_meta" jsonb, CONSTRAINT "PK_fd0e765308bb2b80af73ba85de6" PRIMARY KEY ("uuid"))`, + ) + } +} diff --git a/backend/src/models/authentication-config.ts b/backend/src/models/authentication-config.ts deleted file mode 100644 index 76bb25eab..000000000 --- a/backend/src/models/authentication-config.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { BaseEntity, Column, Entity, PrimaryColumn } from "typeorm" -import { AuthType } from "@common/enums" -import MetloBaseEntity from "./metlo-base-entity" - -@Entity() -export class AuthenticationConfig extends MetloBaseEntity { - @PrimaryColumn() - host: string - - @Column({ type: "enum", enum: AuthType }) - authType: AuthType - - @Column({ nullable: true }) - headerKey: string - - @Column({ nullable: true }) - jwtUserPath: string - - @Column({ nullable: true }) - cookieName: string -} diff --git a/backend/src/models/block-fields.ts b/backend/src/models/block-fields.ts deleted file mode 100644 index 64b4b61e7..000000000 --- a/backend/src/models/block-fields.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { - Column, - Entity, - PrimaryGeneratedColumn, -} from "typeorm" -import { DisableRestMethod } from "@common/enums" -import MetloBaseEntity from "./metlo-base-entity" -import { DisabledPathSection } from "@common/types" - -@Entity() -export class BlockFields extends MetloBaseEntity { - @PrimaryGeneratedColumn("uuid") - uuid: string - - @Column() - path: string - - @Column() - pathRegex: string - - @Column({ - type: "enum", - enum: DisableRestMethod, - enumName: "disable_rest_method_enum", - }) - method: DisableRestMethod - - @Column() - host: string - - @Column({ type: "integer", nullable: false, default: 0 }) - numberParams: number - - @Column({ type: "jsonb", nullable: true, default: null }) - disabledPaths: DisabledPathSection -} diff --git a/backend/src/models/connections.ts b/backend/src/models/connections.ts deleted file mode 100644 index 950e528f6..000000000 --- a/backend/src/models/connections.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { - Entity, - Column, - PrimaryGeneratedColumn, - CreateDateColumn, - UpdateDateColumn, - BeforeInsert, -} from "typeorm" -import { ConnectionType } from "@common/enums" -import { encrypt, generate_iv } from "utils/encryption" -import MetloBaseEntity from "./metlo-base-entity" - -@Entity() -export class Connections extends MetloBaseEntity { - @PrimaryGeneratedColumn("uuid") - uuid: string - - @Column({ - type: "enum", - enum: ConnectionType, - enumName: "connection_type_enum", - }) - connectionType: ConnectionType - - @CreateDateColumn({ type: "timestamptz" }) - createdAt: Date - - @UpdateDateColumn({ type: "timestamptz" }) - updatedAt: Date - - @Column({ nullable: false }) - name: string - - @Column({ nullable: true, type: "jsonb" }) - aws?: any - - @Column({ nullable: true, type: "jsonb" }) - aws_meta?: any - - @Column({ nullable: true, type: "jsonb" }) - gcp?: any - - @Column({ nullable: true, type: "jsonb" }) - gcp_meta?: any - - @BeforeInsert() - beforeInsert() { - let key = process.env.ENCRYPTION_KEY - let encryptionKey = Buffer.from(key, "base64") - if (this.connectionType == ConnectionType.AWS && this.aws) { - // Encrypt Keypairs, i.e. ssh keys - let keypair_iv = generate_iv() - let { encrypted: encrypted_k, tag: tag_k } = encrypt( - this.aws.keypair, - encryptionKey, - keypair_iv, - ) - this.aws.keypair = encrypted_k - - // Encrypt AWS secret access key - let sa_key_iv = generate_iv() - let { encrypted: encrypted_sa, tag: tag_sa } = encrypt( - this.aws.secret_access_key, - encryptionKey, - sa_key_iv, - ) - this.aws.secret_access_key = encrypted_sa - - // Encrypt AWS access id - let access_id_iv = generate_iv() - let { encrypted: encrypted_access_id, tag: tag_access_id } = encrypt( - this.aws.access_id, - encryptionKey, - access_id_iv, - ) - this.aws.access_id = encrypted_access_id - if (!this.aws_meta) { - this.aws_meta = { - keypair_tag: tag_k.toString("base64"), - keypair_iv: keypair_iv.toString("base64"), - access_id_iv: access_id_iv.toString("base64"), - access_id_tag: tag_access_id.toString("base64"), - secret_access_key_tag: tag_sa.toString("base64"), - secret_access_key_iv: sa_key_iv.toString("base64"), - } - } - } else if (this.connectionType == ConnectionType.GCP && this.gcp) { - // Encrypt GCP Key File - let key_file_iv = generate_iv() - let { encrypted: encrypted_k, tag: tag_k } = encrypt( - this.gcp.key_file, - encryptionKey, - key_file_iv, - ) - this.gcp.key_file = encrypted_k - if (!this.gcp_meta) { - this.gcp_meta = { - key_file_tag: tag_k.toString("base64"), - key_file_iv: key_file_iv.toString("base64"), - } - } - } - } -} diff --git a/backend/src/models/index.ts b/backend/src/models/index.ts index b98e71ba9..994be11b4 100644 --- a/backend/src/models/index.ts +++ b/backend/src/models/index.ts @@ -4,12 +4,9 @@ import { ApiTrace } from "models/api-trace" import { OpenApiSpec } from "models/openapi-spec" import { Alert } from "models/alert" import { Session } from "models/sessions" -import { Connections } from "./connections" import { ApiEndpointTest } from "./api-endpoint-test" import { ApiKey } from "./keys" -import { BlockFields } from "./block-fields" import { InstanceSettings } from "./instance-settings" -import { AuthenticationConfig } from "./authentication-config" import { AggregateTraceDataHourly } from "./aggregate-trace-data-hourly" import { Attack } from "./attack" import { Webhook } from "./webhook" @@ -23,12 +20,9 @@ export type DatabaseModel = | OpenApiSpec | Alert | Session - | Connections | ApiEndpointTest | ApiKey - | BlockFields | InstanceSettings - | AuthenticationConfig | AggregateTraceDataHourly | Attack | Webhook @@ -42,12 +36,9 @@ export { OpenApiSpec, Alert, Session, - Connections, ApiEndpointTest, ApiKey, - BlockFields, InstanceSettings, - AuthenticationConfig, AggregateTraceDataHourly, Attack, Webhook, diff --git a/backend/src/services/authentication-config/index.ts b/backend/src/services/authentication-config/index.ts index c19ac224b..b0cdc8469 100644 --- a/backend/src/services/authentication-config/index.ts +++ b/backend/src/services/authentication-config/index.ts @@ -1,126 +1,119 @@ import { AuthType } from "@common/enums" import { QueuedApiTrace, SessionMeta } from "@common/types" -import { AuthenticationConfig } from "models" import { encryptEcb } from "utils/encryption" -import { AuthenticationConfig as CachedAuthConfig } from "@common/types" -import { AUTH_CONFIG_LIST_KEY } from "~/constants" +import { AuthenticationConfig } from "@common/types" import { RedisClient } from "utils/redis" import { MetloContext } from "types" -import { getRepository } from "services/database/utils" +import { getAuthenticationConfigForHost } from "services/metlo-config" -export class AuthenticationConfigService { - static async setSessionMetadata(ctx: MetloContext, apiTrace: QueuedApiTrace) { - const redisKey = `auth_config_${apiTrace.host}` - let cachedAuthConfig: CachedAuthConfig = await RedisClient.getFromRedis( - ctx, - redisKey, - ) - if (!cachedAuthConfig) { - const authConfigRepo = getRepository(ctx, AuthenticationConfig) - const authConfig = await authConfigRepo.findOneBy({ - host: apiTrace.host, - }) +export const setSessionMetadata = async ( + ctx: MetloContext, + apiTrace: QueuedApiTrace, +) => { + const redisKey = `auth_config_${apiTrace.host}` + let cachedAuthConfig: AuthenticationConfig = await RedisClient.getFromRedis( + ctx, + redisKey, + ) + if (!cachedAuthConfig) { + const authConfig = await getAuthenticationConfigForHost(ctx, apiTrace.host) - if (authConfig) { - cachedAuthConfig = { - host: authConfig.host, - authType: authConfig.authType, - headerKey: authConfig.headerKey, - jwtUserPath: authConfig.jwtUserPath, - cookieName: authConfig.cookieName, - } - } else { - cachedAuthConfig = {} as CachedAuthConfig + if (authConfig) { + cachedAuthConfig = { + host: authConfig.host, + authType: authConfig.authType, + headerKey: authConfig.headerKey, + jwtUserPath: authConfig.jwtUserPath, + cookieName: authConfig.cookieName, } - RedisClient.addToRedis(ctx, redisKey, cachedAuthConfig, 600) - RedisClient.addValueToSet(ctx, AUTH_CONFIG_LIST_KEY, [ - `auth_config_${apiTrace.host}`, - ]) + } else { + cachedAuthConfig = {} as AuthenticationConfig } + await RedisClient.addToRedis(ctx, redisKey, cachedAuthConfig, 60) + } - if (Object.keys(cachedAuthConfig).length === 0) { - return - } + if (Object.keys(cachedAuthConfig).length === 0) { + return + } - const key = process.env.ENCRYPTION_KEY + const key = process.env.ENCRYPTION_KEY - const requestHeaders = apiTrace.requestHeaders - const successfulAuth = - apiTrace.responseStatus !== 401 && apiTrace.responseStatus !== 403 - let sessionMeta: SessionMeta = { - authenticationProvided: false, - authType: cachedAuthConfig.authType, - authenticationSuccessful: successfulAuth, - } as SessionMeta - requestHeaders.forEach(header => { - switch (cachedAuthConfig.authType) { - case AuthType.BASIC: - const authHeaderBasic = header.name.toLowerCase() - const authHeaderValue = header.value.toLowerCase().includes("basic") - if (authHeaderBasic === "authorization" && authHeaderValue) { - const encodedValue = header.value.split("Basic")[1].trim() - const decodedUser = Buffer.from(encodedValue, "base64") - ?.toString() - ?.split(":")[0] - sessionMeta["authenticationProvided"] = true - sessionMeta["user"] = decodedUser - if (key) { - const encrypted = encryptEcb(encodedValue, key) - sessionMeta["uniqueSessionKey"] = encrypted - } + const requestHeaders = apiTrace.requestHeaders + const successfulAuth = + apiTrace.responseStatus !== 401 && apiTrace.responseStatus !== 403 + let sessionMeta: SessionMeta = { + authenticationProvided: false, + authType: cachedAuthConfig.authType, + authenticationSuccessful: successfulAuth, + } as SessionMeta + requestHeaders.forEach(header => { + switch (cachedAuthConfig.authType) { + case AuthType.BASIC: + const authHeaderBasic = header.name.toLowerCase() + const authHeaderValue = header.value.toLowerCase().includes("basic") + if (authHeaderBasic === "authorization" && authHeaderValue) { + const encodedValue = header.value.split("Basic")[1].trim() + const decodedUser = Buffer.from(encodedValue, "base64") + ?.toString() + ?.split(":")[0] + sessionMeta["authenticationProvided"] = true + sessionMeta["user"] = decodedUser + if (key) { + const encrypted = encryptEcb(encodedValue, key) + sessionMeta["uniqueSessionKey"] = encrypted } - break - case AuthType.HEADER: - const authHeader = cachedAuthConfig.headerKey ?? "" - if (header.name.toLowerCase() === authHeader.toLowerCase()) { - const headerValue = header.value - sessionMeta["authenticationProvided"] = true - if (key) { - const encrypted = encryptEcb(headerValue, key) - sessionMeta["uniqueSessionKey"] = encrypted - } + } + break + case AuthType.HEADER: + const authHeader = cachedAuthConfig.headerKey ?? "" + if (header.name.toLowerCase() === authHeader.toLowerCase()) { + const headerValue = header.value + sessionMeta["authenticationProvided"] = true + if (key) { + const encrypted = encryptEcb(headerValue, key) + sessionMeta["uniqueSessionKey"] = encrypted } - break - case AuthType.SESSION_COOKIE: - const cookieName = cachedAuthConfig?.cookieName ?? "" - if (header.name.toLowerCase() === cookieName.toLowerCase()) { - const cookieValue = header.value - sessionMeta["authenticationProvided"] = true - if (key) { - const encrypted = encryptEcb(cookieValue, key) - sessionMeta["uniqueSessionKey"] = encrypted - } + } + break + case AuthType.SESSION_COOKIE: + const cookieName = cachedAuthConfig?.cookieName ?? "" + if (header.name.toLowerCase() === cookieName.toLowerCase()) { + const cookieValue = header.value + sessionMeta["authenticationProvided"] = true + if (key) { + const encrypted = encryptEcb(cookieValue, key) + sessionMeta["uniqueSessionKey"] = encrypted } - break - case AuthType.JWT: - const jwtHeader = cachedAuthConfig.headerKey ?? "" - if (header.name.toLowerCase() === jwtHeader.toLowerCase()) { - sessionMeta["authenticationProvided"] = true - const decodedPayload = JSON.parse( - Buffer.from( - header.value?.split(".")?.[1] ?? "", - "base64", - )?.toString() || "{}", - ) - if (cachedAuthConfig.jwtUserPath) { - const jwtUser = cachedAuthConfig.jwtUserPath - .split(".") - .reduce((o, k) => { - return o && o[k] - }, decodedPayload) - if (jwtUser && typeof jwtUser === "string") { - sessionMeta["user"] = jwtUser - } - } - if (key) { - const encrypted = encryptEcb(header.value, key) - sessionMeta["uniqueSessionKey"] = encrypted + } + break + case AuthType.JWT: + const jwtHeader = cachedAuthConfig.headerKey ?? "" + if (header.name.toLowerCase() === jwtHeader.toLowerCase()) { + sessionMeta["authenticationProvided"] = true + const decodedPayload = JSON.parse( + Buffer.from( + header.value?.split(".")?.[1] ?? "", + "base64", + )?.toString() || "{}", + ) + if (cachedAuthConfig.jwtUserPath) { + const jwtUser = cachedAuthConfig.jwtUserPath + .split(".") + .reduce((o, k) => { + return o && o[k] + }, decodedPayload) + if (jwtUser && typeof jwtUser === "string") { + sessionMeta["user"] = jwtUser } } - break - default: - } - }) - apiTrace.sessionMeta = sessionMeta - } + if (key) { + const encrypted = encryptEcb(header.value, key) + sessionMeta["uniqueSessionKey"] = encrypted + } + } + break + default: + } + }) + apiTrace.sessionMeta = sessionMeta } diff --git a/backend/src/services/block-fields/index.ts b/backend/src/services/block-fields/index.ts index cd069d3bc..1cba1f490 100644 --- a/backend/src/services/block-fields/index.ts +++ b/backend/src/services/block-fields/index.ts @@ -1,114 +1,211 @@ -import { DataType, DisableRestMethod } from "@common/enums" -import { getDataType, isParameter, parsedJson, parsedJsonNonNull } from "utils" -import { BlockFieldEntry, PairObject, QueuedApiTrace } from "@common/types" -import { getPathTokens } from "@common/utils" -import { BLOCK_FIELDS_ALL_REGEX, BLOCK_FIELDS_LIST_KEY } from "~/constants" +import { DataType } from "@common/enums" +import { + getDataType, + getPathRegex, + getValidPath, + parsedJson, + parsedJsonNonNull, +} from "utils" +import { PairObject, QueuedApiTrace } from "@common/types" import { isSensitiveDataKey } from "./utils" -import { BlockFields } from "models" -import { getRepoQB } from "services/database/utils" import { MetloContext } from "types" -import { RedisClient } from "utils/redis" +import { + getHostMapCached, + getMetloConfigProcessedCached, +} from "services/metlo-config" +import { DisablePaths } from "services/metlo-config/types" -export class BlockFieldsService { - static getNumberParams( - pathRegex: string, - method: DisableRestMethod, - path: string, - ) { - let numParams = 0 - if (pathRegex === BLOCK_FIELDS_ALL_REGEX) { - numParams += 1000 - } else if (method === DisableRestMethod.ALL) { - numParams += 500 +interface DisablePathsObjSet { + reqQuery: Set + reqHeaders: Set + reqBody: Set + resHeaders: Set + resBody: Set +} + +interface DisablePathsObj { + reqQuery: string[] + reqHeaders: string[] + reqBody: string[] + resHeaders: string[] + resBody: string[] +} + +const updateDisabledPaths = ( + disablePathsObj: DisablePathsObjSet, + disabledPaths: string[], +) => { + disabledPaths.forEach(path => { + if (path.includes("req.query")) disablePathsObj.reqQuery.add(path) + else if (path.includes("req.headers")) disablePathsObj.reqHeaders.add(path) + else if (path.includes("req.body")) disablePathsObj.reqBody.add(path) + else if (path.includes("res.headers")) disablePathsObj.resHeaders.add(path) + else if (path.includes("res.body")) disablePathsObj.resBody.add(path) + }) +} + +export const getDisabledPaths = async ( + ctx: MetloContext, + apiTrace: QueuedApiTrace, +): Promise => { + const metloConfig = await getMetloConfigProcessedCached(ctx) + const blockFields = metloConfig?.blockFields + if (!blockFields) { + return { + reqQuery: [], + reqHeaders: [], + reqBody: [], + resHeaders: [], + resBody: [], } - if (path) { - const pathTokens = getPathTokens(path) - for (let i = 0; i < pathTokens.length; i++) { - const token = pathTokens[i] - if (isParameter(token)) { - numParams += 1 - } - } - return numParams + } + const hostMap = await getHostMapCached(ctx) + let currHost = apiTrace.host + for (const e of hostMap) { + const match = apiTrace.host.match(e.pattern) + if (match && match[0].length === apiTrace.host.length) { + currHost = e.host + break } - return 0 } - - static async getBlockFieldsEntry( - ctx: MetloContext, - apiTrace: QueuedApiTrace, - ): Promise { - const redisKey = `block_fields_${apiTrace.host}` - let cachedBlockFields: any = await RedisClient.getFromRedis(ctx, redisKey) - if (!cachedBlockFields) { - const blockFieldEntries = await getRepoQB(ctx, BlockFields) - .andWhere("host = :host", { host: apiTrace.host }) - .getMany() - - cachedBlockFields = blockFieldEntries - RedisClient.addToRedis( - ctx, - redisKey, - JSON.stringify(blockFieldEntries), - 600, - ) - RedisClient.addValueToSet(ctx, BLOCK_FIELDS_LIST_KEY, [ - `block_fields_${apiTrace.host}`, - ]) - } else { - cachedBlockFields = JSON.parse(cachedBlockFields) + const disablePathsObj = { + reqQuery: new Set(), + reqHeaders: new Set(), + reqBody: new Set(), + resHeaders: new Set(), + resBody: new Set(), + } + const hostEntry = blockFields[currHost] + if (hostEntry) { + if (hostEntry["ALL"]) { + const disabledPaths = + (hostEntry["ALL"] as DisablePaths).disable_paths ?? [] + updateDisabledPaths(disablePathsObj, disabledPaths) } - let res = null - if (cachedBlockFields?.length > 0) { - for (const item of cachedBlockFields) { - const regex = new RegExp(item.pathRegex) - if ( - (item.method === DisableRestMethod[apiTrace.method] || - item.method === DisableRestMethod.ALL) && - regex.test(apiTrace.path) && - (res === null || item.numberParams < res?.numberParams) - ) { - res = item + for (const endpoint in hostEntry) { + if (endpoint && endpoint !== "ALL") { + const validPath = getValidPath(endpoint) + if (!validPath.isValid) { + continue + } + const validPathString = validPath.path + const pathRegex = getPathRegex(validPathString) + const regex = new RegExp(pathRegex) + if (!regex.test(apiTrace.path)) { + continue + } + if (hostEntry[endpoint]["ALL"]) { + const disabledPaths = + hostEntry[endpoint]["ALL"]["disable_paths"] ?? [] + updateDisabledPaths(disablePathsObj, disabledPaths) + } + if (hostEntry[endpoint][apiTrace.method]) { + const disablePaths = + hostEntry[endpoint][apiTrace.method]["disable_paths"] ?? [] + updateDisabledPaths(disablePathsObj, disablePaths) } } } - return res } + return { + reqQuery: [...disablePathsObj.reqQuery], + reqHeaders: [...disablePathsObj.reqHeaders], + reqBody: [...disablePathsObj.reqBody], + resHeaders: [...disablePathsObj.resHeaders], + resBody: [...disablePathsObj.resBody], + } +} + +const isContained = (arr: string[], str: string): boolean => { + const strLower = str.toLowerCase() + for (const e of arr) { + const entryLower = e.toLowerCase().trim() + if (entryLower === strLower) { + return true + } + } + return false +} - static isContained(arr: string[], str: string): boolean { - const strLower = str.toLowerCase() - for (const e of arr) { - const entryLower = e.toLowerCase().trim() - if (entryLower === strLower) { - return true +const recursiveParseBody = ( + dataPath: string, + dataSection: string, + jsonBody: any, + disabledPaths: string[], + redacted: boolean, +): any => { + const dataType = getDataType(jsonBody) + const path = dataPath ? `${dataSection}.${dataPath}` : dataSection + if (dataType === DataType.OBJECT) { + for (const key in jsonBody) { + const contained = isContained(disabledPaths, `${path}.${key}`) + if (redacted || contained || isSensitiveDataKey(key)) { + jsonBody[key] = recursiveParseBody( + `${dataPath}.${key}`, + dataSection, + jsonBody[key], + disabledPaths, + true, + ) + } else { + jsonBody[key] = recursiveParseBody( + `${dataPath}.${key}`, + dataSection, + jsonBody[key], + disabledPaths, + false, + ) } } - return false + } else if (dataType === DataType.ARRAY) { + ;(jsonBody as any[]).forEach((item, idx) => { + jsonBody[idx] = recursiveParseBody( + dataPath, + dataSection, + item, + disabledPaths, + redacted, + ) + }) + } else { + if (redacted) { + return "[REDACTED]" + } + return jsonBody } + return jsonBody +} - static recursiveParseBody( - dataPath: string, - dataSection: string, - jsonBody: any, - disabledPaths: string[], - redacted: boolean, - ): any { +const redactBlockedFieldsBodyData = ( + body: string, + dataSection: string, + disabledPaths: string[], +) => { + if (!body) { + return + } + let redacted = false + if (isContained(disabledPaths, dataSection)) { + redacted = true + } + + let jsonBody = parsedJson(body) + if (jsonBody) { const dataType = getDataType(jsonBody) - const path = dataPath ? `${dataSection}.${dataPath}` : dataSection if (dataType === DataType.OBJECT) { - for (const key in jsonBody) { - const contained = this.isContained(disabledPaths, `${path}.${key}`) + for (let key in jsonBody) { + const contained = isContained(disabledPaths, `${dataSection}.${key}`) if (redacted || contained || isSensitiveDataKey(key)) { - jsonBody[key] = this.recursiveParseBody( - `${dataPath}.${key}`, + jsonBody[key] = recursiveParseBody( + key, dataSection, jsonBody[key], disabledPaths, true, ) } else { - jsonBody[key] = this.recursiveParseBody( - `${dataPath}.${key}`, + jsonBody[key] = recursiveParseBody( + key, dataSection, jsonBody[key], disabledPaths, @@ -118,156 +215,93 @@ export class BlockFieldsService { } } else if (dataType === DataType.ARRAY) { ;(jsonBody as any[]).forEach((item, idx) => { - jsonBody[idx] = this.recursiveParseBody( - dataPath, + jsonBody[idx] = recursiveParseBody( + "", dataSection, item, disabledPaths, redacted, ) }) - } else { - if (redacted) { - return "[REDACTED]" - } - return jsonBody } - return jsonBody - } - - static redactBlockedFieldsBodyData( - body: string, - dataSection: string, - disabledPaths: string[], - ) { - if (!body) { - return - } - let redacted = false - if (this.isContained(disabledPaths, dataSection)) { - redacted = true + } else { + if (redacted) { + body = "[REDACTED]" } + } + return jsonBody ?? body +} - let jsonBody = parsedJson(body) - if (jsonBody) { - const dataType = getDataType(jsonBody) - if (dataType === DataType.OBJECT) { - for (let key in jsonBody) { - const contained = this.isContained( - disabledPaths, - `${dataSection}.${key}`, - ) - if (redacted || contained || isSensitiveDataKey(key)) { - jsonBody[key] = this.recursiveParseBody( - key, - dataSection, - jsonBody[key], - disabledPaths, - true, - ) - } else { - jsonBody[key] = this.recursiveParseBody( - key, - dataSection, - jsonBody[key], - disabledPaths, - false, - ) - } - } - } else if (dataType === DataType.ARRAY) { - ;(jsonBody as any[]).forEach((item, idx) => { - jsonBody[idx] = this.recursiveParseBody( - "", - dataSection, - item, - disabledPaths, - redacted, - ) - }) - } - } else { - if (redacted) { - body = "[REDACTED]" - } - } - return jsonBody ?? body +const redactBlockedFieldsPairObject = ( + data: PairObject[], + dataSection: string, + disabledPaths: string[], +): PairObject[] => { + if (!data) { + return data + } + let redacted = false + if (isContained(disabledPaths, dataSection)) { + redacted = true } + return data.map(item => { + const contained = isContained(disabledPaths, `${dataSection}.${item.name}`) - static redactBlockedFieldsPairObject( - data: PairObject[], - dataSection: string, - disabledPaths: string[], - ): PairObject[] { - if (!data) { - return data - } - let redacted = false - if (this.isContained(disabledPaths, dataSection)) { - redacted = true - } - return data.map(item => { - const contained = this.isContained( + return { + name: item.name, + value: recursiveParseBody( + item.name, + dataSection, + parsedJsonNonNull(item.value, true), disabledPaths, - `${dataSection}.${item.name}`, - ) + redacted || contained || isSensitiveDataKey(item.name), + ), + } + }) +} - return { - name: item.name, - value: this.recursiveParseBody( - item.name, - dataSection, - parsedJsonNonNull(item.value, true), - disabledPaths, - redacted || contained || isSensitiveDataKey(item.name), - ), - } - }) +export const redactBlockedFields = async ( + ctx: MetloContext, + apiTrace: QueuedApiTrace, +) => { + const res = await getDisabledPaths(ctx, apiTrace) + const disabledPaths = res ?? { + reqQuery: [], + reqHeaders: [], + reqBody: [], + resHeaders: [], + resBody: [], } - static async redactBlockedFields( - ctx: MetloContext, - apiTrace: QueuedApiTrace, - ) { - const blockFieldEntry = await this.getBlockFieldsEntry(ctx, apiTrace) - const disabledPaths = blockFieldEntry?.disabledPaths ?? { - reqQuery: [], - reqHeaders: [], - reqBody: [], - resHeaders: [], - resBody: [], - } + const validRequestParams = redactBlockedFieldsPairObject( + apiTrace.requestParameters, + "req.query", + disabledPaths.reqQuery, + ) + const validRequestHeaders = redactBlockedFieldsPairObject( + apiTrace.requestHeaders, + "req.headers", + disabledPaths.reqHeaders, + ) + const validRequestBody = redactBlockedFieldsBodyData( + apiTrace.requestBody, + "req.body", + disabledPaths.reqBody, + ) + const validResponseHeaders = redactBlockedFieldsPairObject( + apiTrace.responseHeaders, + "res.headers", + disabledPaths.resHeaders, + ) + const validResponseBody = redactBlockedFieldsBodyData( + apiTrace.responseBody, + "res.body", + disabledPaths.resBody, + ) - const validRequestParams = this.redactBlockedFieldsPairObject( - apiTrace.requestParameters, - "req.query", - disabledPaths.reqQuery, - ) - const validRequestHeaders = this.redactBlockedFieldsPairObject( - apiTrace.requestHeaders, - "req.headers", - disabledPaths.reqHeaders, - ) - const validRequestBody = this.redactBlockedFieldsBodyData( - apiTrace.requestBody, - "req.body", - disabledPaths.reqBody, - ) - const validResponseHeaders = this.redactBlockedFieldsPairObject( - apiTrace.responseHeaders, - "res.headers", - disabledPaths.resHeaders, - ) - const validResponseBody = this.redactBlockedFieldsBodyData( - apiTrace.responseBody, - "res.body", - disabledPaths.resBody, - ) - - apiTrace.requestParameters = validRequestParams - apiTrace.requestHeaders = validRequestHeaders - apiTrace.requestBody = validRequestBody - apiTrace.responseHeaders = validResponseHeaders - apiTrace.responseBody = validResponseBody - } + apiTrace.requestParameters = validRequestParams + apiTrace.requestHeaders = validRequestHeaders + apiTrace.requestBody = validRequestBody + apiTrace.responseHeaders = validResponseHeaders + apiTrace.responseBody = validResponseBody } diff --git a/backend/src/services/log-request/index.ts b/backend/src/services/log-request/index.ts index f35aedf87..501e10f24 100644 --- a/backend/src/services/log-request/index.ts +++ b/backend/src/services/log-request/index.ts @@ -1,8 +1,8 @@ import mlog from "logger" import { Meta, QueuedApiTrace, SessionMeta, TraceParams } from "@common/types" import Error500InternalServer from "errors/error-500-internal-server" -import { BlockFieldsService } from "services/block-fields" -import { AuthenticationConfigService } from "services/authentication-config" +import { redactBlockedFields } from "services/block-fields" +import { setSessionMetadata } from "services/authentication-config" import { RedisClient } from "utils/redis" import { TRACES_QUEUE } from "~/constants" import { MetloContext } from "types" @@ -68,8 +68,8 @@ export class LogRequestService { sessionMeta: {} as SessionMeta, } - await AuthenticationConfigService.setSessionMetadata(ctx, apiTraceObj) - await BlockFieldsService.redactBlockedFields(ctx, apiTraceObj) + await setSessionMetadata(ctx, apiTraceObj) + await redactBlockedFields(ctx, apiTraceObj) mlog.debug("Pushed trace to redis queue") await unsafeRedisClient.rpush( diff --git a/backend/src/services/log-request/v2/index.ts b/backend/src/services/log-request/v2/index.ts index ff6f2ac21..c87a3741e 100644 --- a/backend/src/services/log-request/v2/index.ts +++ b/backend/src/services/log-request/v2/index.ts @@ -8,8 +8,8 @@ import { TraceParams, } from "@common/types" import Error500InternalServer from "errors/error-500-internal-server" -import { BlockFieldsService } from "services/block-fields" -import { AuthenticationConfigService } from "services/authentication-config" +import { redactBlockedFields } from "services/block-fields" +import { setSessionMetadata } from "services/authentication-config" import { RedisClient } from "utils/redis" import { TRACES_QUEUE } from "~/constants" import { MetloContext } from "types" @@ -94,9 +94,9 @@ export const logRequest = async ( } if (!traceParams?.sessionMeta) { - await AuthenticationConfigService.setSessionMetadata(ctx, apiTraceObj) + await setSessionMetadata(ctx, apiTraceObj) } - await BlockFieldsService.redactBlockedFields(ctx, apiTraceObj) + await redactBlockedFields(ctx, apiTraceObj) mlog.debug("Pushed trace to redis queue") await unsafeRedisClient.rpush( diff --git a/backend/src/services/metlo-config/index.ts b/backend/src/services/metlo-config/index.ts index d363838ad..37c081c79 100644 --- a/backend/src/services/metlo-config/index.ts +++ b/backend/src/services/metlo-config/index.ts @@ -10,8 +10,11 @@ import { createQB, getQB, insertValueBuilder } from "services/database/utils" import jsyaml from "js-yaml" import { decrypt, encrypt, generate_iv } from "utils/encryption" import { HostMappingCompiled, MetloConfigType } from "./types" -import { validateMetloConfig } from "./validate" -import { populateAuthentication, populateBlockFields } from "./populate-tables" +import { + validateMetloConfig, + validateAuthentication, + validateBlockFields, +} from "./validate" import { NodeCache } from "utils/node-cache" export const getMetloConfig = async ( @@ -107,6 +110,24 @@ export const updateMetloConfig = async ( await populateMetloConfig(ctx, updateMetloConfigParams.configString) } +export const getAuthenticationConfigForHost = async ( + ctx: MetloContext, + host: string, +): Promise => { + const authenticationConfig = await getAuthenticationConfig(ctx) + const hostMap = await getHostMapCached(ctx) + let currHost = host + for (const e of hostMap) { + const match = host.match(e.pattern) + if (match && match[0].length === host.length) { + currHost = e.host + break + } + } + const authConfig = authenticationConfig.find(e => e.host === currHost) + return authConfig ?? null +} + const populateEnvironment = (metloConfig: object) => { const parsedConfigString = metloConfig if ("globalTestEnv" in parsedConfigString) { @@ -142,8 +163,8 @@ export const populateMetloConfig = async ( await queryRunner.connect() const metloConfig = validateMetloConfig(configString) await queryRunner.startTransaction() - await populateAuthentication(ctx, metloConfig, queryRunner) - await populateBlockFields(ctx, metloConfig, queryRunner) + await validateAuthentication(ctx, metloConfig) + await validateBlockFields(ctx, metloConfig) const metloConfigEntry = await getQB(ctx, queryRunner) .select(["uuid"]) .from(MetloConfig, "config") diff --git a/backend/src/services/metlo-config/populate-tables.ts b/backend/src/services/metlo-config/populate-tables.ts deleted file mode 100644 index fb9eed8f9..000000000 --- a/backend/src/services/metlo-config/populate-tables.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { AuthType, DisableRestMethod } from "@common/enums" -import Error400BadRequest from "errors/error-400-bad-request" -import Error422UnprocessableEntity from "errors/error-422-unprocessable-entity" -import Error500InternalServer from "errors/error-500-internal-server" -import mlog from "logger" -import { AuthenticationConfig, BlockFields } from "models" -import { BlockFieldsService } from "services/block-fields" -import { getQB, insertValuesBuilder } from "services/database/utils" -import { QueryRunner } from "typeorm" -import { MetloContext } from "types" -import { getPathRegex, getValidPath } from "utils" -import { RedisClient } from "utils/redis" -import { - AUTH_CONFIG_LIST_KEY, - BLOCK_FIELDS_ALL_REGEX, - BLOCK_FIELDS_LIST_KEY, -} from "~/constants" - -const addToBlockFields = ( - blockFieldsEntries: BlockFields[], - host: string, - method: DisableRestMethod, - path: string, - pathRegex: string, - disabledPaths: string[], -) => { - const reqQueryPaths = new Set() - const reqHeadersPaths = new Set() - const reqBodyPaths = new Set() - const resHeadersPaths = new Set() - const resBodyPaths = new Set() - disabledPaths.forEach(path => { - if (path.includes("req.query")) reqQueryPaths.add(path) - else if (path.includes("req.headers")) reqHeadersPaths.add(path) - else if (path.includes("req.body")) reqBodyPaths.add(path) - else if (path.includes("res.headers")) resHeadersPaths.add(path) - else if (path.includes("res.body")) resBodyPaths.add(path) - }) - const disabledPathsObj = { - reqQuery: [...reqQueryPaths], - reqHeaders: [...reqHeadersPaths], - reqBody: [...reqBodyPaths], - resHeaders: [...resHeadersPaths], - resBody: [...resBodyPaths], - } - const blockFieldEntry = BlockFields.create() - blockFieldEntry.host = host - blockFieldEntry.method = method - blockFieldEntry.path = path - blockFieldEntry.pathRegex = pathRegex - blockFieldEntry.disabledPaths = disabledPathsObj - blockFieldEntry.numberParams = BlockFieldsService.getNumberParams( - pathRegex, - method, - path, - ) - blockFieldsEntries.push(blockFieldEntry) -} - -export const populateBlockFields = async ( - ctx: MetloContext, - metloConfig: object, - queryRunner: QueryRunner, -) => { - const blockFieldsDoc = metloConfig?.["blockFields"] - const blockFieldsEntries: BlockFields[] = [] - const currBlockFieldsEntries = await RedisClient.getValuesFromSet( - ctx, - BLOCK_FIELDS_LIST_KEY, - ) - if (blockFieldsDoc) { - for (const host in blockFieldsDoc) { - const hostObj = blockFieldsDoc[host] - let allDisablePaths = [] - if (hostObj) { - if (hostObj["ALL"]) { - allDisablePaths = hostObj["ALL"]["disable_paths"] ?? [] - const pathRegex = BLOCK_FIELDS_ALL_REGEX - const path = "/" - addToBlockFields( - blockFieldsEntries, - host, - DisableRestMethod.ALL, - path, - pathRegex, - allDisablePaths, - ) - } - for (const endpoint in hostObj) { - if (endpoint && endpoint !== "ALL") { - const validPath = getValidPath(endpoint) - if (!validPath.isValid) { - throw new Error400BadRequest(`${endpoint}: ${validPath.errMsg}`) - } - const validPathString = validPath.path - const pathRegex = getPathRegex(validPathString) - let endpointDisablePaths = allDisablePaths - if (hostObj[endpoint]["ALL"]) { - endpointDisablePaths = endpointDisablePaths?.concat( - hostObj[endpoint]["ALL"]["disable_paths"] ?? [], - ) - addToBlockFields( - blockFieldsEntries, - host, - DisableRestMethod.ALL, - validPathString, - pathRegex, - endpointDisablePaths, - ) - } - for (const method in hostObj[endpoint]) { - if (method && method !== "ALL") { - const blockFieldMethod = DisableRestMethod[method] - const disabledPaths = endpointDisablePaths?.concat( - hostObj[endpoint][method]?.["disable_paths"] ?? [], - ) - addToBlockFields( - blockFieldsEntries, - host, - blockFieldMethod, - validPathString, - pathRegex, - disabledPaths, - ) - } - } - } - } - } - } - } - await getQB(ctx, queryRunner).delete().from(BlockFields).execute() - await insertValuesBuilder( - ctx, - queryRunner, - BlockFields, - blockFieldsEntries, - ).execute() - if (currBlockFieldsEntries) { - await RedisClient.deleteFromRedis(ctx, [ - ...currBlockFieldsEntries, - BLOCK_FIELDS_LIST_KEY, - ]) - } -} - -export const populateAuthentication = async ( - ctx: MetloContext, - metloConfig: object, - queryRunner: QueryRunner, -) => { - const key = process.env.ENCRYPTION_KEY - if (!key) { - mlog.error(`No ENCRYPTION_KEY found. Cannot set authentication config.`) - throw new Error500InternalServer( - "No ENCRYPTION_KEY found. Cannot set authentication config.", - ) - } - const authConfigDoc = metloConfig?.["authentication"] - const authConfigEntries: AuthenticationConfig[] = [] - const currAuthConfigEntries = await RedisClient.getValuesFromSet( - ctx, - AUTH_CONFIG_LIST_KEY, - ) - if (authConfigDoc) { - const hosts = new Set() - authConfigDoc.forEach(item => { - if (hosts.has(item.host)) { - throw new Error422UnprocessableEntity( - `Host ${item.host} is included more than once in the authentication config.`, - ) - } - hosts.add(item.host) - const newConfig = new AuthenticationConfig() - newConfig.host = item.host - newConfig.authType = item.authType as AuthType - if (item.headerKey) newConfig.headerKey = item.headerKey - if (item.jwtUserPath) newConfig.jwtUserPath = item.jwtUserPath - if (item.cookieName) newConfig.cookieName = item.cookieName - authConfigEntries.push(newConfig) - }) - } - const deleteQb = getQB(ctx, queryRunner).delete().from(AuthenticationConfig) - const addQb = insertValuesBuilder( - ctx, - queryRunner, - AuthenticationConfig, - authConfigEntries, - ) - await deleteQb.execute() - await addQb.execute() - if (currAuthConfigEntries) { - await RedisClient.deleteFromRedis(ctx, [ - ...currAuthConfigEntries, - AUTH_CONFIG_LIST_KEY, - ]) - } -} diff --git a/backend/src/services/metlo-config/types.ts b/backend/src/services/metlo-config/types.ts index c7aff3f98..bc46252d9 100644 --- a/backend/src/services/metlo-config/types.ts +++ b/backend/src/services/metlo-config/types.ts @@ -1,3 +1,4 @@ +import { DisableRestMethod } from "@common/enums" import { AuthenticationConfig } from "@common/types" export interface HostMapping { @@ -10,10 +11,20 @@ export interface HostMappingCompiled { pattern: RegExp } +export interface DisablePaths { + disable_paths: string[] +} + +export type BlockFieldConfig = Record< + string, + DisablePaths | Record +> + export interface MetloConfigType { globalFullTraceCapture?: boolean minAnalyzeTraces?: number hostMap?: HostMapping[] authentication?: AuthenticationConfig[] customWords?: string[] + blockFields?: Record } diff --git a/backend/src/services/metlo-config/validate.ts b/backend/src/services/metlo-config/validate.ts index 36c69b48f..efb23b98a 100644 --- a/backend/src/services/metlo-config/validate.ts +++ b/backend/src/services/metlo-config/validate.ts @@ -4,6 +4,13 @@ import formatsPlugin from "ajv-formats" import SourceMap from "js-yaml-source-map" import Error400BadRequest from "errors/error-400-bad-request" import { METLO_CONFIG_SCHEMA } from "./constants" +import Error422UnprocessableEntity from "errors/error-422-unprocessable-entity" +import Error500InternalServer from "errors/error-500-internal-server" +import mlog from "logger" +import { MetloContext } from "types" +import { getValidPath } from "utils" +import { RedisClient } from "utils/redis" +import { AUTH_CONFIG_LIST_KEY, BLOCK_FIELDS_LIST_KEY } from "~/constants" export const validateMetloConfig = (configString: string) => { configString = configString.trim() @@ -49,3 +56,70 @@ export const validateMetloConfig = (configString: string) => { } return metloConfig } + +export const validateBlockFields = async ( + ctx: MetloContext, + metloConfig: object, +) => { + const blockFieldsDoc = metloConfig?.["blockFields"] + if (blockFieldsDoc) { + for (const host in blockFieldsDoc) { + const hostObj = blockFieldsDoc[host] + if (hostObj) { + for (const endpoint in hostObj) { + if (endpoint && endpoint !== "ALL") { + const validPath = getValidPath(endpoint) + if (!validPath.isValid) { + throw new Error400BadRequest(`${endpoint}: ${validPath.errMsg}`) + } + } + } + } + } + } + const currBlockFieldsEntries = await RedisClient.getValuesFromSet( + ctx, + BLOCK_FIELDS_LIST_KEY, + ) + if (currBlockFieldsEntries) { + await RedisClient.deleteFromRedis(ctx, [ + ...currBlockFieldsEntries, + BLOCK_FIELDS_LIST_KEY, + ]) + } +} + +export const validateAuthentication = async ( + ctx: MetloContext, + metloConfig: object, +) => { + const key = process.env.ENCRYPTION_KEY + if (!key) { + mlog.error(`No ENCRYPTION_KEY found. Cannot set authentication config.`) + throw new Error500InternalServer( + "No ENCRYPTION_KEY found. Cannot set authentication config.", + ) + } + const authConfigDoc = metloConfig?.["authentication"] + if (authConfigDoc) { + const hosts = new Set() + authConfigDoc.forEach(item => { + if (hosts.has(item.host)) { + throw new Error422UnprocessableEntity( + `Host ${item.host} is included more than once in the authentication config.`, + ) + } + hosts.add(item.host) + }) + } + const currAuthConfigEntries = await RedisClient.getValuesFromSet( + ctx, + AUTH_CONFIG_LIST_KEY, + ) + if (currAuthConfigEntries) { + await RedisClient.deleteFromRedis(ctx, [ + ...currAuthConfigEntries, + AUTH_CONFIG_LIST_KEY, + ]) + } +} diff --git a/backend/src/services/spec/index.ts b/backend/src/services/spec/index.ts index d7161e98c..1eb1bc4d4 100644 --- a/backend/src/services/spec/index.ts +++ b/backend/src/services/spec/index.ts @@ -59,7 +59,7 @@ import { MetloContext } from "types" import { getEntityManager, getQB, getRepository } from "services/database/utils" import Error400BadRequest from "errors/error-400-bad-request" import { createSpecDiffAlerts } from "services/alert/openapi-spec" -import { BlockFieldsService } from "services/block-fields" +import { getDisabledPaths } from "services/block-fields" import { updateDataFields } from "analyze-traces" interface EndpointsMap { @@ -576,10 +576,7 @@ export class SpecService { // Validate response info let respErrorItems = {} if (pathString) { - const blockField = await BlockFieldsService.getBlockFieldsEntry( - ctx, - trace, - ) + const disabledPaths = await getDisabledPaths(ctx, trace) const responses = getSpecResponses(parsedSpec, endpoint, pathString) const responseValidator = new OpenAPIResponseValidator({ components: specObject["components"], @@ -604,7 +601,7 @@ export class SpecService { respErrorItems = generateAlertMessageFromRespErrors( responseErrors as AjvError[], responses?.path, - blockField?.disabledPaths?.resBody ?? [], + disabledPaths?.resBody ?? [], ) } diff --git a/backend/src/services/testing/utils.ts b/backend/src/services/testing/utils.ts index 848d5d634..a4b3f1a5f 100644 --- a/backend/src/services/testing/utils.ts +++ b/backend/src/services/testing/utils.ts @@ -1,9 +1,10 @@ import validator from "validator" import { MetloContext } from "types" import { getRepository } from "services/database/utils" -import { ApiEndpoint, AuthenticationConfig } from "models" +import { ApiEndpoint } from "models" import { GenTestEndpoint } from "@metlo/testing" import { RestMethod } from "@common/enums" +import { getAuthenticationConfigForHost } from "services/metlo-config" export const getGenTestEndpoint = async ( ctx: MetloContext, @@ -54,10 +55,8 @@ export const getGenTestEndpoint = async ( entity: e.entity, })), } - const authConfigRepo = getRepository(ctx, AuthenticationConfig) - const authConfig = await authConfigRepo.findOneBy({ - host: endpointObj.host, - }) + + const authConfig = await getAuthenticationConfigForHost(ctx, endpointObj.host) if (authConfig) { genTestEndpoint.authConfig = { authType: authConfig.authType,