From 8b57541d3a3f6c2d06e6c595235eaa1991f41a83 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 18 Oct 2024 00:28:13 +0400 Subject: [PATCH 1/9] feat: multiple auth methods for identities --- backend/package.json | 2 +- ...14084900_identity-multiple-auth-methods.ts | 55 ++ backend/src/db/schemas/identities.ts | 2 +- .../src/db/schemas/identity-access-tokens.ts | 3 +- backend/src/db/schemas/models.ts | 2 +- backend/src/db/seeds/5-machine-identity.ts | 2 +- backend/src/server/routes/index.ts | 7 - .../src/server/routes/v1/identity-router.ts | 12 +- .../server/routes/v2/identity-org-router.ts | 6 +- .../identity-access-token-dal.ts | 86 +-- .../identity-access-token-service.ts | 18 +- .../identity-aws-auth-service.ts | 40 +- .../identity-azure-auth-service.ts | 32 +- .../identity-gcp-auth-service.ts | 30 +- .../identity-kubernetes-auth-service.ts | 31 +- .../identity-oidc-auth-service.ts | 29 +- .../identity-token-auth-service.ts | 46 +- .../identity-ua/identity-ua-service.ts | 59 +- .../src/services/identity/identity-org-dal.ts | 199 +++++- frontend/src/components/v2/Badge/Badge.tsx | 3 +- frontend/src/components/v2/Select/Select.tsx | 3 +- .../src/hooks/api/identities/mutations.tsx | 1 + frontend/src/hooks/api/identities/types.ts | 2 +- .../views/Org/IdentityPage/IdentityPage.tsx | 21 +- .../IdentityAuthenticationSection.tsx | 85 ++- .../components/IdentityClientSecretModal.tsx | 2 + .../IdentityAuthMethodModal.tsx | 351 +++++----- .../IdentitySection/IdentityAwsAuthForm.tsx | 395 +++++------ .../IdentitySection/IdentityAzureAuthForm.tsx | 408 +++++------ .../IdentitySection/IdentityGcpAuthForm.tsx | 418 +++++------ .../IdentityKubernetesAuthForm.tsx | 518 +++++++------- .../IdentitySection/IdentityModal.tsx | 6 - .../IdentitySection/IdentityOidcAuthForm.tsx | 654 ++++++++---------- .../IdentitySection/IdentityTokenAuthForm.tsx | 317 ++++----- .../IdentityUniversalAuthForm.tsx | 431 ++++++------ 35 files changed, 2145 insertions(+), 2131 deletions(-) create mode 100644 backend/src/db/migrations/20241014084900_identity-multiple-auth-methods.ts diff --git a/backend/package.json b/backend/package.json index 97e9512425..c572791f90 100644 --- a/backend/package.json +++ b/backend/package.json @@ -44,7 +44,7 @@ "test:e2e-watch": "vitest -c vitest.e2e.config.ts --bail=1", "test:e2e-coverage": "vitest run --coverage -c vitest.e2e.config.ts", "generate:component": "tsx ./scripts/create-backend-file.ts", - "generate:schema": "tsx ./scripts/generate-schema-types.ts", + "generate:schema": "tsx ./scripts/generate-schema-types.ts && eslint --fix --ext ts ./src/db/schemas", "auditlog-migration:latest": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:latest", "auditlog-migration:up": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:up", "auditlog-migration:down": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:down", diff --git a/backend/src/db/migrations/20241014084900_identity-multiple-auth-methods.ts b/backend/src/db/migrations/20241014084900_identity-multiple-auth-methods.ts new file mode 100644 index 0000000000..a16b5984a6 --- /dev/null +++ b/backend/src/db/migrations/20241014084900_identity-multiple-auth-methods.ts @@ -0,0 +1,55 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + const hasAuthMethodColumnAccessToken = await knex.schema.hasColumn(TableName.IdentityAccessToken, "authMethod"); + + if (!hasAuthMethodColumnAccessToken) { + await knex.schema.alterTable(TableName.IdentityAccessToken, (t) => { + t.string("authMethod").nullable(); + }); + + // Backfilling: Update the authMethod column in the IdentityAccessToken table to match the authMethod of the Identity + await knex(TableName.IdentityAccessToken).update({ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore because generate schema happens after this + authMethod: knex(TableName.Identity) + .select("authMethod") + .whereRaw(`${TableName.IdentityAccessToken}."identityId" = ${TableName.Identity}.id`) + .whereNotNull("authMethod") + .first() + }); + + // ! We delete all access tokens where the identity has no auth method set! + // ! Which means un-configured identities that for some reason have access tokens, will have their access tokens deleted. + await knex(TableName.IdentityAccessToken) + .whereNotExists((queryBuilder) => { + void queryBuilder + .select("id") + .from(TableName.Identity) + .whereRaw(`${TableName.IdentityAccessToken}."identityId" = ${TableName.Identity}.id`) + .whereNotNull("authMethod"); + }) + .delete(); + + // Finally we set the authMethod to notNullable after populating the column. + // This will fail if the data is not populated correctly, so it's safe. + await knex.schema.alterTable(TableName.IdentityAccessToken, (t) => { + t.string("authMethod").notNullable().alter(); + }); + } + + // ! We aren't dropping the authMethod column from the Identity itself, because we wan't to be able to easily rollback for the time being. +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export async function down(knex: Knex): Promise { + const hasAuthMethodColumnAccessToken = await knex.schema.hasColumn(TableName.IdentityAccessToken, "authMethod"); + + if (hasAuthMethodColumnAccessToken) { + await knex.schema.alterTable(TableName.IdentityAccessToken, (t) => { + t.dropColumn("authMethod"); + }); + } +} diff --git a/backend/src/db/schemas/identities.ts b/backend/src/db/schemas/identities.ts index adf3a6ef27..0cd6699aac 100644 --- a/backend/src/db/schemas/identities.ts +++ b/backend/src/db/schemas/identities.ts @@ -10,7 +10,7 @@ import { TImmutableDBKeys } from "./models"; export const IdentitiesSchema = z.object({ id: z.string().uuid(), name: z.string(), - authMethod: z.string().nullable().optional(), + // authMethod: z.string().nullable().optional(), createdAt: z.date(), updatedAt: z.date() }); diff --git a/backend/src/db/schemas/identity-access-tokens.ts b/backend/src/db/schemas/identity-access-tokens.ts index 45445c8114..bbff1b88cf 100644 --- a/backend/src/db/schemas/identity-access-tokens.ts +++ b/backend/src/db/schemas/identity-access-tokens.ts @@ -20,7 +20,8 @@ export const IdentityAccessTokensSchema = z.object({ identityId: z.string().uuid(), createdAt: z.date(), updatedAt: z.date(), - name: z.string().nullable().optional() + name: z.string().nullable().optional(), + authMethod: z.string() }); export type TIdentityAccessTokens = z.infer; diff --git a/backend/src/db/schemas/models.ts b/backend/src/db/schemas/models.ts index 7b48bb6fcd..8fb916358c 100644 --- a/backend/src/db/schemas/models.ts +++ b/backend/src/db/schemas/models.ts @@ -189,7 +189,7 @@ export enum ProjectUpgradeStatus { export enum IdentityAuthMethod { TOKEN_AUTH = "token-auth", - Univeral = "universal-auth", + UNIVERSAL_AUTH = "universal-auth", KUBERNETES_AUTH = "kubernetes-auth", GCP_AUTH = "gcp-auth", AWS_AUTH = "aws-auth", diff --git a/backend/src/db/seeds/5-machine-identity.ts b/backend/src/db/seeds/5-machine-identity.ts index 5447391001..3798d4bf32 100644 --- a/backend/src/db/seeds/5-machine-identity.ts +++ b/backend/src/db/seeds/5-machine-identity.ts @@ -16,7 +16,7 @@ export async function seed(knex: Knex): Promise { // @ts-ignore id: seedData1.machineIdentity.id, name: seedData1.machineIdentity.name, - authMethod: IdentityAuthMethod.Univeral + authMethod: IdentityAuthMethod.UNIVERSAL_AUTH } ]); const identityUa = await knex(TableName.IdentityUniversalAuth) diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 326b283f50..85ef7b12c3 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -1079,7 +1079,6 @@ export const registerRoutes = async ( }); const identityTokenAuthService = identityTokenAuthServiceFactory({ identityTokenAuthDAL, - identityDAL, identityOrgMembershipDAL, identityAccessTokenDAL, permissionService, @@ -1088,7 +1087,6 @@ export const registerRoutes = async ( const identityUaService = identityUaServiceFactory({ identityOrgMembershipDAL, permissionService, - identityDAL, identityAccessTokenDAL, identityUaClientSecretDAL, identityUaDAL, @@ -1098,7 +1096,6 @@ export const registerRoutes = async ( identityKubernetesAuthDAL, identityOrgMembershipDAL, identityAccessTokenDAL, - identityDAL, orgBotDAL, permissionService, licenseService @@ -1107,7 +1104,6 @@ export const registerRoutes = async ( identityGcpAuthDAL, identityOrgMembershipDAL, identityAccessTokenDAL, - identityDAL, permissionService, licenseService }); @@ -1116,7 +1112,6 @@ export const registerRoutes = async ( identityAccessTokenDAL, identityAwsAuthDAL, identityOrgMembershipDAL, - identityDAL, licenseService, permissionService }); @@ -1125,7 +1120,6 @@ export const registerRoutes = async ( identityAzureAuthDAL, identityOrgMembershipDAL, identityAccessTokenDAL, - identityDAL, permissionService, licenseService }); @@ -1134,7 +1128,6 @@ export const registerRoutes = async ( identityOidcAuthDAL, identityOrgMembershipDAL, identityAccessTokenDAL, - identityDAL, permissionService, licenseService, orgBotDAL diff --git a/backend/src/server/routes/v1/identity-router.ts b/backend/src/server/routes/v1/identity-router.ts index 163b560e2c..97ff784939 100644 --- a/backend/src/server/routes/v1/identity-router.ts +++ b/backend/src/server/routes/v1/identity-router.ts @@ -1,6 +1,12 @@ import { z } from "zod"; -import { IdentitiesSchema, IdentityOrgMembershipsSchema, OrgMembershipRole, OrgRolesSchema } from "@app/db/schemas"; +import { + IdentitiesSchema, + IdentityAuthMethod, + IdentityOrgMembershipsSchema, + OrgMembershipRole, + OrgRolesSchema +} from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { IDENTITIES } from "@app/lib/api-docs"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; @@ -216,7 +222,9 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { permissions: true, description: true }).optional(), - identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true }) + identity: IdentitiesSchema.pick({ name: true, id: true }).extend({ + authMethods: z.array(z.nativeEnum(IdentityAuthMethod)) + }) }) }) } diff --git a/backend/src/server/routes/v2/identity-org-router.ts b/backend/src/server/routes/v2/identity-org-router.ts index edc2701715..36856266a2 100644 --- a/backend/src/server/routes/v2/identity-org-router.ts +++ b/backend/src/server/routes/v2/identity-org-router.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import { IdentitiesSchema, IdentityOrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas"; +import { IdentitiesSchema, IdentityAuthMethod, IdentityOrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas"; import { ORGANIZATIONS } from "@app/lib/api-docs"; import { OrderByDirection } from "@app/lib/types"; import { readLimit } from "@app/server/config/rateLimiter"; @@ -58,7 +58,9 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => { permissions: true, description: true }).optional(), - identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true }) + identity: IdentitiesSchema.pick({ name: true, id: true }).extend({ + authMethods: z.array(z.nativeEnum(IdentityAuthMethod)) + }) }) ).array(), totalCount: z.number() diff --git a/backend/src/services/identity-access-token/identity-access-token-dal.ts b/backend/src/services/identity-access-token/identity-access-token-dal.ts index 01a78fa817..bf547f0d8a 100644 --- a/backend/src/services/identity-access-token/identity-access-token-dal.ts +++ b/backend/src/services/identity-access-token/identity-access-token-dal.ts @@ -1,7 +1,7 @@ import { Knex } from "knex"; import { TDbClient } from "@app/db"; -import { IdentityAuthMethod, TableName, TIdentityAccessTokens } from "@app/db/schemas"; +import { TableName, TIdentityAccessTokens } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; import { ormify, selectAllTableCols } from "@app/lib/knex"; import { logger } from "@app/lib/logger"; @@ -17,54 +17,27 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => { const doc = await (tx || db.replicaNode())(TableName.IdentityAccessToken) .where(filter) .join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityAccessToken}.identityId`) - .leftJoin(TableName.IdentityUaClientSecret, (qb) => { - qb.on(`${TableName.Identity}.authMethod`, db.raw("?", [IdentityAuthMethod.Univeral])).andOn( - `${TableName.IdentityAccessToken}.identityUAClientSecretId`, - `${TableName.IdentityUaClientSecret}.id` - ); - }) - .leftJoin(TableName.IdentityUniversalAuth, (qb) => { - qb.on(`${TableName.Identity}.authMethod`, db.raw("?", [IdentityAuthMethod.Univeral])).andOn( - `${TableName.IdentityUaClientSecret}.identityUAId`, - `${TableName.IdentityUniversalAuth}.id` - ); - }) - .leftJoin(TableName.IdentityGcpAuth, (qb) => { - qb.on(`${TableName.Identity}.authMethod`, db.raw("?", [IdentityAuthMethod.GCP_AUTH])).andOn( - `${TableName.Identity}.id`, - `${TableName.IdentityGcpAuth}.identityId` - ); - }) - .leftJoin(TableName.IdentityAwsAuth, (qb) => { - qb.on(`${TableName.Identity}.authMethod`, db.raw("?", [IdentityAuthMethod.AWS_AUTH])).andOn( - `${TableName.Identity}.id`, - `${TableName.IdentityAwsAuth}.identityId` - ); - }) - .leftJoin(TableName.IdentityAzureAuth, (qb) => { - qb.on(`${TableName.Identity}.authMethod`, db.raw("?", [IdentityAuthMethod.AZURE_AUTH])).andOn( - `${TableName.Identity}.id`, - `${TableName.IdentityAzureAuth}.identityId` - ); - }) - .leftJoin(TableName.IdentityKubernetesAuth, (qb) => { - qb.on(`${TableName.Identity}.authMethod`, db.raw("?", [IdentityAuthMethod.KUBERNETES_AUTH])).andOn( - `${TableName.Identity}.id`, - `${TableName.IdentityKubernetesAuth}.identityId` - ); - }) - .leftJoin(TableName.IdentityOidcAuth, (qb) => { - qb.on(`${TableName.Identity}.authMethod`, db.raw("?", [IdentityAuthMethod.OIDC_AUTH])).andOn( - `${TableName.Identity}.id`, - `${TableName.IdentityOidcAuth}.identityId` - ); - }) - .leftJoin(TableName.IdentityTokenAuth, (qb) => { - qb.on(`${TableName.Identity}.authMethod`, db.raw("?", [IdentityAuthMethod.TOKEN_AUTH])).andOn( - `${TableName.Identity}.id`, - `${TableName.IdentityTokenAuth}.identityId` - ); - }) + .leftJoin( + TableName.IdentityUaClientSecret, + `${TableName.IdentityAccessToken}.identityUAClientSecretId`, + `${TableName.IdentityUaClientSecret}.id` + ) + .leftJoin( + TableName.IdentityUniversalAuth, + `${TableName.IdentityUaClientSecret}.identityUAId`, + `${TableName.IdentityUniversalAuth}.id` + ) + .leftJoin(TableName.IdentityGcpAuth, `${TableName.Identity}.id`, `${TableName.IdentityGcpAuth}.identityId`) + .leftJoin(TableName.IdentityAwsAuth, `${TableName.Identity}.id`, `${TableName.IdentityAwsAuth}.identityId`) + .leftJoin(TableName.IdentityAzureAuth, `${TableName.Identity}.id`, `${TableName.IdentityAzureAuth}.identityId`) + .leftJoin( + TableName.IdentityKubernetesAuth, + `${TableName.Identity}.id`, + `${TableName.IdentityKubernetesAuth}.identityId` + ) + .leftJoin(TableName.IdentityOidcAuth, `${TableName.Identity}.id`, `${TableName.IdentityOidcAuth}.identityId`) + .leftJoin(TableName.IdentityTokenAuth, `${TableName.Identity}.id`, `${TableName.IdentityTokenAuth}.identityId`) + .select(selectAllTableCols(TableName.IdentityAccessToken)) .select( db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityUniversalAuth).as("accessTokenTrustedIpsUa"), @@ -82,14 +55,13 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => { return { ...doc, - accessTokenTrustedIps: - doc.accessTokenTrustedIpsUa || - doc.accessTokenTrustedIpsGcp || - doc.accessTokenTrustedIpsAws || - doc.accessTokenTrustedIpsAzure || - doc.accessTokenTrustedIpsK8s || - doc.accessTokenTrustedIpsOidc || - doc.accessTokenTrustedIpsToken + trustedIpsUniversalAuth: doc.accessTokenTrustedIpsUa, + trustedIpsGcpAuth: doc.accessTokenTrustedIpsGcp, + trustedIpsAwsAuth: doc.accessTokenTrustedIpsAws, + trustedIpsAzureAuth: doc.accessTokenTrustedIpsAzure, + trustedIpsKubernetesAuth: doc.accessTokenTrustedIpsK8s, + trustedIpsOidcAuth: doc.accessTokenTrustedIpsOidc, + trustedIpsAccessTokenAuth: doc.accessTokenTrustedIpsToken }; } catch (error) { throw new DatabaseError({ error, name: "IdAccessTokenFindOne" }); diff --git a/backend/src/services/identity-access-token/identity-access-token-service.ts b/backend/src/services/identity-access-token/identity-access-token-service.ts index c625c14079..a59d1e9594 100644 --- a/backend/src/services/identity-access-token/identity-access-token-service.ts +++ b/backend/src/services/identity-access-token/identity-access-token-service.ts @@ -1,6 +1,6 @@ import jwt, { JwtPayload } from "jsonwebtoken"; -import { TableName, TIdentityAccessTokens } from "@app/db/schemas"; +import { IdentityAuthMethod, TableName, TIdentityAccessTokens } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { checkIPAgainstBlocklist, TIp } from "@app/lib/ip"; @@ -164,10 +164,22 @@ export const identityAccessTokenServiceFactory = ({ message: "Failed to authorize revoked access token, access token is revoked" }); - if (ipAddress && identityAccessToken) { + const trustedIpsMap: Record = { + [IdentityAuthMethod.UNIVERSAL_AUTH]: identityAccessToken.trustedIpsUniversalAuth, + [IdentityAuthMethod.GCP_AUTH]: identityAccessToken.trustedIpsGcpAuth, + [IdentityAuthMethod.AWS_AUTH]: identityAccessToken.trustedIpsAwsAuth, + [IdentityAuthMethod.AZURE_AUTH]: identityAccessToken.trustedIpsAzureAuth, + [IdentityAuthMethod.KUBERNETES_AUTH]: identityAccessToken.trustedIpsKubernetesAuth, + [IdentityAuthMethod.OIDC_AUTH]: identityAccessToken.trustedIpsOidcAuth, + [IdentityAuthMethod.TOKEN_AUTH]: identityAccessToken.trustedIpsAccessTokenAuth + }; + + const trustedIps = trustedIpsMap[identityAccessToken.authMethod as IdentityAuthMethod]; + + if (ipAddress) { checkIPAgainstBlocklist({ ipAddress, - trustedIps: identityAccessToken?.accessTokenTrustedIps as TIp[] + trustedIps: trustedIps as TIp[] }); } diff --git a/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts b/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts index 6ef37eba3f..207927d138 100644 --- a/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts +++ b/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts @@ -13,7 +13,7 @@ import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedErro import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { ActorType, AuthTokenType } from "../auth/auth-type"; -import { TIdentityDALFactory } from "../identity/identity-dal"; +// import { TIdentityDALFactory } from "../identity/identity-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; @@ -33,7 +33,7 @@ type TIdentityAwsAuthServiceFactoryDep = { identityAccessTokenDAL: Pick; identityAwsAuthDAL: Pick; identityOrgMembershipDAL: Pick; - identityDAL: Pick; + // identityDAL: Pick; licenseService: Pick; permissionService: Pick; }; @@ -44,7 +44,7 @@ export const identityAwsAuthServiceFactory = ({ identityAccessTokenDAL, identityAwsAuthDAL, identityOrgMembershipDAL, - identityDAL, + // identityDAL, licenseService, permissionService }: TIdentityAwsAuthServiceFactoryDep) => { @@ -113,7 +113,8 @@ export const identityAwsAuthServiceFactory = ({ accessTokenTTL: identityAwsAuth.accessTokenTTL, accessTokenMaxTTL: identityAwsAuth.accessTokenMaxTTL, accessTokenNumUses: 0, - accessTokenNumUsesLimit: identityAwsAuth.accessTokenNumUsesLimit + accessTokenNumUsesLimit: identityAwsAuth.accessTokenNumUsesLimit, + authMethod: IdentityAuthMethod.AWS_AUTH }, tx ); @@ -155,10 +156,11 @@ export const identityAwsAuthServiceFactory = ({ }: TAttachAwsAuthDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity.authMethod) + if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AWS_AUTH)) { throw new BadRequestError({ message: "Failed to add AWS Auth to already configured identity" }); + } if (accessTokenMaxTTL > 0 && accessTokenTTL > accessTokenMaxTTL) { throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); @@ -206,13 +208,6 @@ export const identityAwsAuthServiceFactory = ({ }, tx ); - await identityDAL.updateById( - identityMembershipOrg.identityId, - { - authMethod: IdentityAuthMethod.AWS_AUTH - }, - tx - ); return doc; }); return { ...identityAwsAuth, orgId: identityMembershipOrg.orgId }; @@ -234,10 +229,11 @@ export const identityAwsAuthServiceFactory = ({ }: TUpdateAwsAuthDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AWS_AUTH) - throw new BadRequestError({ - message: "Failed to update AWS Auth" + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AWS_AUTH)) { + throw new NotFoundError({ + message: "he identity does not have AWS Auth attached" }); + } const identityAwsAuth = await identityAwsAuthDAL.findOne({ identityId }); @@ -293,10 +289,11 @@ export const identityAwsAuthServiceFactory = ({ const getAwsAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetAwsAuthDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AWS_AUTH) - throw new BadRequestError({ + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AWS_AUTH)) { + throw new NotFoundError({ message: "The identity does not have AWS Auth attached" }); + } const awsIdentityAuth = await identityAwsAuthDAL.findOne({ identityId }); @@ -320,10 +317,11 @@ export const identityAwsAuthServiceFactory = ({ }: TRevokeAwsAuthDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AWS_AUTH) - throw new BadRequestError({ - message: "The identity does not have aws auth" + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AWS_AUTH)) { + throw new NotFoundError({ + message: "The identity does not have AWS Auth attached" }); + } const { permission } = await permissionService.getOrgPermission( actor, actorId, @@ -348,7 +346,7 @@ export const identityAwsAuthServiceFactory = ({ const revokedIdentityAwsAuth = await identityAwsAuthDAL.transaction(async (tx) => { const deletedAwsAuth = await identityAwsAuthDAL.delete({ identityId }, tx); - await identityDAL.updateById(identityId, { authMethod: null }, tx); + // await identityDAL.updateById(identityId, { authMethod: null }, tx); return { ...deletedAwsAuth?.[0], orgId: identityMembershipOrg.orgId }; }); return revokedIdentityAwsAuth; diff --git a/backend/src/services/identity-azure-auth/identity-azure-auth-service.ts b/backend/src/services/identity-azure-auth/identity-azure-auth-service.ts index cbda504db7..ac31383985 100644 --- a/backend/src/services/identity-azure-auth/identity-azure-auth-service.ts +++ b/backend/src/services/identity-azure-auth/identity-azure-auth-service.ts @@ -11,7 +11,7 @@ import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedErro import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { ActorType, AuthTokenType } from "../auth/auth-type"; -import { TIdentityDALFactory } from "../identity/identity-dal"; +// import { TIdentityDALFactory } from "../identity/identity-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; @@ -32,7 +32,7 @@ type TIdentityAzureAuthServiceFactoryDep = { >; identityOrgMembershipDAL: Pick; identityAccessTokenDAL: Pick; - identityDAL: Pick; + // identityDAL: Pick; permissionService: Pick; licenseService: Pick; }; @@ -43,7 +43,7 @@ export const identityAzureAuthServiceFactory = ({ identityAzureAuthDAL, identityOrgMembershipDAL, identityAccessTokenDAL, - identityDAL, + // identityDAL, permissionService, licenseService }: TIdentityAzureAuthServiceFactoryDep) => { @@ -84,7 +84,8 @@ export const identityAzureAuthServiceFactory = ({ accessTokenTTL: identityAzureAuth.accessTokenTTL, accessTokenMaxTTL: identityAzureAuth.accessTokenMaxTTL, accessTokenNumUses: 0, - accessTokenNumUsesLimit: identityAzureAuth.accessTokenNumUsesLimit + accessTokenNumUsesLimit: identityAzureAuth.accessTokenNumUsesLimit, + authMethod: IdentityAuthMethod.AZURE_AUTH }, tx ); @@ -126,11 +127,11 @@ export const identityAzureAuthServiceFactory = ({ }: TAttachAzureAuthDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity.authMethod) + if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AZURE_AUTH)) { throw new BadRequestError({ message: "Failed to add Azure Auth to already configured identity" }); - + } if (accessTokenMaxTTL > 0 && accessTokenTTL > accessTokenMaxTTL) { throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); } @@ -176,13 +177,7 @@ export const identityAzureAuthServiceFactory = ({ }, tx ); - await identityDAL.updateById( - identityMembershipOrg.identityId, - { - authMethod: IdentityAuthMethod.AZURE_AUTH - }, - tx - ); + return doc; }); return { ...identityAzureAuth, orgId: identityMembershipOrg.orgId }; @@ -204,10 +199,11 @@ export const identityAzureAuthServiceFactory = ({ }: TUpdateAzureAuthDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AZURE_AUTH) + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AZURE_AUTH)) { throw new BadRequestError({ message: "Failed to update Azure Auth" }); + } const identityGcpAuth = await identityAzureAuthDAL.findOne({ identityId }); @@ -266,10 +262,11 @@ export const identityAzureAuthServiceFactory = ({ const getAzureAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetAzureAuthDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AZURE_AUTH) + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AZURE_AUTH)) { throw new BadRequestError({ message: "The identity does not have Azure Auth attached" }); + } const identityAzureAuth = await identityAzureAuthDAL.findOne({ identityId }); @@ -294,10 +291,11 @@ export const identityAzureAuthServiceFactory = ({ }: TRevokeAzureAuthDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AZURE_AUTH) + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AZURE_AUTH)) { throw new BadRequestError({ message: "The identity does not have azure auth" }); + } const { permission } = await permissionService.getOrgPermission( actor, actorId, @@ -321,7 +319,7 @@ export const identityAzureAuthServiceFactory = ({ const revokedIdentityAzureAuth = await identityAzureAuthDAL.transaction(async (tx) => { const deletedAzureAuth = await identityAzureAuthDAL.delete({ identityId }, tx); - await identityDAL.updateById(identityId, { authMethod: null }, tx); + // await identityDAL.updateById(identityId, { authMethod: null }, tx); return { ...deletedAzureAuth?.[0], orgId: identityMembershipOrg.orgId }; }); return revokedIdentityAzureAuth; diff --git a/backend/src/services/identity-gcp-auth/identity-gcp-auth-service.ts b/backend/src/services/identity-gcp-auth/identity-gcp-auth-service.ts index 14e4dcd3fc..605db3016b 100644 --- a/backend/src/services/identity-gcp-auth/identity-gcp-auth-service.ts +++ b/backend/src/services/identity-gcp-auth/identity-gcp-auth-service.ts @@ -11,7 +11,7 @@ import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedErro import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { ActorType, AuthTokenType } from "../auth/auth-type"; -import { TIdentityDALFactory } from "../identity/identity-dal"; +// import { TIdentityDALFactory } from "../identity/identity-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; @@ -30,7 +30,7 @@ type TIdentityGcpAuthServiceFactoryDep = { identityGcpAuthDAL: Pick; identityOrgMembershipDAL: Pick; identityAccessTokenDAL: Pick; - identityDAL: Pick; + // identityDAL: Pick; permissionService: Pick; licenseService: Pick; }; @@ -41,7 +41,7 @@ export const identityGcpAuthServiceFactory = ({ identityGcpAuthDAL, identityOrgMembershipDAL, identityAccessTokenDAL, - identityDAL, + // identityDAL, permissionService, licenseService }: TIdentityGcpAuthServiceFactoryDep) => { @@ -125,7 +125,8 @@ export const identityGcpAuthServiceFactory = ({ accessTokenTTL: identityGcpAuth.accessTokenTTL, accessTokenMaxTTL: identityGcpAuth.accessTokenMaxTTL, accessTokenNumUses: 0, - accessTokenNumUsesLimit: identityGcpAuth.accessTokenNumUsesLimit + accessTokenNumUsesLimit: identityGcpAuth.accessTokenNumUsesLimit, + authMethod: IdentityAuthMethod.GCP_AUTH }, tx ); @@ -168,10 +169,11 @@ export const identityGcpAuthServiceFactory = ({ }: TAttachGcpAuthDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity.authMethod) + if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.GCP_AUTH)) { throw new BadRequestError({ message: "Failed to add GCP Auth to already configured identity" }); + } if (accessTokenMaxTTL > 0 && accessTokenTTL > accessTokenMaxTTL) { throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); @@ -219,13 +221,6 @@ export const identityGcpAuthServiceFactory = ({ }, tx ); - await identityDAL.updateById( - identityMembershipOrg.identityId, - { - authMethod: IdentityAuthMethod.GCP_AUTH - }, - tx - ); return doc; }); return { ...identityGcpAuth, orgId: identityMembershipOrg.orgId }; @@ -248,10 +243,11 @@ export const identityGcpAuthServiceFactory = ({ }: TUpdateGcpAuthDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.GCP_AUTH) + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.GCP_AUTH)) { throw new BadRequestError({ message: "Failed to update GCP Auth" }); + } const identityGcpAuth = await identityGcpAuthDAL.findOne({ identityId }); @@ -311,10 +307,11 @@ export const identityGcpAuthServiceFactory = ({ const getGcpAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetGcpAuthDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.GCP_AUTH) + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.GCP_AUTH)) { throw new BadRequestError({ message: "The identity does not have GCP Auth attached" }); + } const identityGcpAuth = await identityGcpAuthDAL.findOne({ identityId }); @@ -339,10 +336,11 @@ export const identityGcpAuthServiceFactory = ({ }: TRevokeGcpAuthDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.GCP_AUTH) + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.GCP_AUTH)) { throw new BadRequestError({ message: "The identity does not have gcp auth" }); + } const { permission } = await permissionService.getOrgPermission( actor, actorId, @@ -366,7 +364,7 @@ export const identityGcpAuthServiceFactory = ({ const revokedIdentityGcpAuth = await identityGcpAuthDAL.transaction(async (tx) => { const deletedGcpAuth = await identityGcpAuthDAL.delete({ identityId }, tx); - await identityDAL.updateById(identityId, { authMethod: null }, tx); + // await identityDAL.updateById(identityId, { authMethod: null }, tx); return { ...deletedGcpAuth?.[0], orgId: identityMembershipOrg.orgId }; }); return revokedIdentityGcpAuth; diff --git a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts index 83a02d079c..c7dd1d73aa 100644 --- a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts +++ b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts @@ -22,7 +22,7 @@ import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; import { ActorType, AuthTokenType } from "../auth/auth-type"; -import { TIdentityDALFactory } from "../identity/identity-dal"; +// import { TIdentityDALFactory } from "../identity/identity-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; @@ -44,7 +44,7 @@ type TIdentityKubernetesAuthServiceFactoryDep = { >; identityAccessTokenDAL: Pick; identityOrgMembershipDAL: Pick; - identityDAL: Pick; + // identityDAL: Pick; orgBotDAL: Pick; permissionService: Pick; licenseService: Pick; @@ -56,7 +56,7 @@ export const identityKubernetesAuthServiceFactory = ({ identityKubernetesAuthDAL, identityOrgMembershipDAL, identityAccessTokenDAL, - identityDAL, + // identityDAL, orgBotDAL, permissionService, licenseService @@ -206,7 +206,8 @@ export const identityKubernetesAuthServiceFactory = ({ accessTokenTTL: identityKubernetesAuth.accessTokenTTL, accessTokenMaxTTL: identityKubernetesAuth.accessTokenMaxTTL, accessTokenNumUses: 0, - accessTokenNumUsesLimit: identityKubernetesAuth.accessTokenNumUsesLimit + accessTokenNumUsesLimit: identityKubernetesAuth.accessTokenNumUsesLimit, + authMethod: IdentityAuthMethod.KUBERNETES_AUTH }, tx ); @@ -251,10 +252,11 @@ export const identityKubernetesAuthServiceFactory = ({ }: TAttachKubernetesAuthDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity.authMethod) + if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.KUBERNETES_AUTH)) { throw new BadRequestError({ message: "Failed to add Kubernetes Auth to already configured identity" }); + } if (accessTokenMaxTTL > 0 && accessTokenTTL > accessTokenMaxTTL) { throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); @@ -363,13 +365,6 @@ export const identityKubernetesAuthServiceFactory = ({ }, tx ); - await identityDAL.updateById( - identityMembershipOrg.identityId, - { - authMethod: IdentityAuthMethod.KUBERNETES_AUTH - }, - tx - ); return doc; }); @@ -395,10 +390,11 @@ export const identityKubernetesAuthServiceFactory = ({ }: TUpdateKubernetesAuthDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.KUBERNETES_AUTH) + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.KUBERNETES_AUTH)) { throw new BadRequestError({ message: "Failed to update Kubernetes Auth" }); + } const identityKubernetesAuth = await identityKubernetesAuthDAL.findOne({ identityId }); @@ -519,11 +515,11 @@ export const identityKubernetesAuthServiceFactory = ({ }: TGetKubernetesAuthDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.KUBERNETES_AUTH) + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.KUBERNETES_AUTH)) { throw new BadRequestError({ message: "The identity does not have Kubernetes Auth attached" }); - + } const identityKubernetesAuth = await identityKubernetesAuthDAL.findOne({ identityId }); const { permission } = await permissionService.getOrgPermission( @@ -580,10 +576,11 @@ export const identityKubernetesAuthServiceFactory = ({ }: TRevokeKubernetesAuthDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.KUBERNETES_AUTH) + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.KUBERNETES_AUTH)) { throw new BadRequestError({ message: "The identity does not have kubernetes auth" }); + } const { permission } = await permissionService.getOrgPermission( actor, actorId, @@ -607,7 +604,7 @@ export const identityKubernetesAuthServiceFactory = ({ const revokedIdentityKubernetesAuth = await identityKubernetesAuthDAL.transaction(async (tx) => { const deletedKubernetesAuth = await identityKubernetesAuthDAL.delete({ identityId }, tx); - await identityDAL.updateById(identityId, { authMethod: null }, tx); + // await identityDAL.updateById(identityId, { authMethod: null }, tx); return { ...deletedKubernetesAuth?.[0], orgId: identityMembershipOrg.orgId }; }); return revokedIdentityKubernetesAuth; diff --git a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts index 7c481f7880..c5d8af1e4a 100644 --- a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts +++ b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts @@ -22,7 +22,7 @@ import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedErro import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { ActorType, AuthTokenType } from "../auth/auth-type"; -import { TIdentityDALFactory } from "../identity/identity-dal"; +// import { TIdentityDALFactory } from "../identity/identity-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; @@ -41,7 +41,7 @@ type TIdentityOidcAuthServiceFactoryDep = { identityOidcAuthDAL: TIdentityOidcAuthDALFactory; identityOrgMembershipDAL: Pick; identityAccessTokenDAL: Pick; - identityDAL: Pick; + // identityDAL: Pick; permissionService: Pick; licenseService: Pick; orgBotDAL: Pick; @@ -52,7 +52,7 @@ export type TIdentityOidcAuthServiceFactory = ReturnType { const identityOidcAuth = await identityOidcAuthDAL.findOne({ identityId }); if (!identityOidcAuth) { - throw new NotFoundError({ message: "GCP auth method not found for identity, did you configure GCP auth?" }); + throw new NotFoundError({ message: "OIDC auth method not found for identity, did you configure OIDC auth?" }); } const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ @@ -176,7 +176,8 @@ export const identityOidcAuthServiceFactory = ({ accessTokenTTL: identityOidcAuth.accessTokenTTL, accessTokenMaxTTL: identityOidcAuth.accessTokenMaxTTL, accessTokenNumUses: 0, - accessTokenNumUsesLimit: identityOidcAuth.accessTokenNumUsesLimit + accessTokenNumUsesLimit: identityOidcAuth.accessTokenNumUsesLimit, + authMethod: IdentityAuthMethod.OIDC_AUTH }, tx ); @@ -223,10 +224,11 @@ export const identityOidcAuthServiceFactory = ({ if (!identityMembershipOrg) { throw new NotFoundError({ message: "Failed to find identity" }); } - if (identityMembershipOrg.identity.authMethod) + if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OIDC_AUTH)) { throw new BadRequestError({ message: "Failed to add OIDC Auth to already configured identity" }); + } if (accessTokenMaxTTL > 0 && accessTokenTTL > accessTokenMaxTTL) { throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); @@ -329,13 +331,6 @@ export const identityOidcAuthServiceFactory = ({ }, tx ); - await identityDAL.updateById( - identityMembershipOrg.identityId, - { - authMethod: IdentityAuthMethod.OIDC_AUTH - }, - tx - ); return doc; }); return { ...identityOidcAuth, orgId: identityMembershipOrg.orgId, caCert }; @@ -363,7 +358,7 @@ export const identityOidcAuthServiceFactory = ({ throw new NotFoundError({ message: "Failed to find identity" }); } - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.OIDC_AUTH) { + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OIDC_AUTH)) { throw new BadRequestError({ message: "Failed to update OIDC Auth" }); @@ -463,7 +458,7 @@ export const identityOidcAuthServiceFactory = ({ throw new NotFoundError({ message: "Failed to find identity" }); } - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.OIDC_AUTH) { + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OIDC_AUTH)) { throw new BadRequestError({ message: "The identity does not have OIDC Auth attached" }); @@ -508,7 +503,7 @@ export const identityOidcAuthServiceFactory = ({ throw new NotFoundError({ message: "Failed to find identity" }); } - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.OIDC_AUTH) { + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OIDC_AUTH)) { throw new BadRequestError({ message: "The identity does not have OIDC auth" }); @@ -540,7 +535,7 @@ export const identityOidcAuthServiceFactory = ({ const revokedIdentityOidcAuth = await identityOidcAuthDAL.transaction(async (tx) => { const deletedOidcAuth = await identityOidcAuthDAL.delete({ identityId }, tx); - await identityDAL.updateById(identityId, { authMethod: null }, tx); + // await identityDAL.updateById(identityId, { authMethod: null }, tx); return { ...deletedOidcAuth?.[0], orgId: identityMembershipOrg.orgId }; }); diff --git a/backend/src/services/identity-token-auth/identity-token-auth-service.ts b/backend/src/services/identity-token-auth/identity-token-auth-service.ts index b6eacd0d78..9f5c2db36b 100644 --- a/backend/src/services/identity-token-auth/identity-token-auth-service.ts +++ b/backend/src/services/identity-token-auth/identity-token-auth-service.ts @@ -11,7 +11,7 @@ import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/ import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { ActorType, AuthTokenType } from "../auth/auth-type"; -import { TIdentityDALFactory } from "../identity/identity-dal"; +// import { TIdentityDALFactory } from "../identity/identity-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; @@ -32,11 +32,11 @@ type TIdentityTokenAuthServiceFactoryDep = { TIdentityTokenAuthDALFactory, "transaction" | "create" | "findOne" | "updateById" | "delete" >; - identityDAL: Pick; + // identityDAL: Pick; identityOrgMembershipDAL: Pick; identityAccessTokenDAL: Pick< TIdentityAccessTokenDALFactory, - "create" | "find" | "update" | "findById" | "findOne" | "updateById" + "create" | "find" | "update" | "findById" | "findOne" | "updateById" | "delete" >; permissionService: Pick; licenseService: Pick; @@ -46,7 +46,7 @@ export type TIdentityTokenAuthServiceFactory = ReturnType { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity.authMethod) + if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) { throw new BadRequestError({ message: "Failed to add Token Auth to already configured identity" }); + } if (accessTokenMaxTTL > 0 && accessTokenTTL > accessTokenMaxTTL) { throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); @@ -112,13 +113,6 @@ export const identityTokenAuthServiceFactory = ({ }, tx ); - await identityDAL.updateById( - identityMembershipOrg.identityId, - { - authMethod: IdentityAuthMethod.TOKEN_AUTH - }, - tx - ); return doc; }); return { ...identityTokenAuth, orgId: identityMembershipOrg.orgId }; @@ -137,10 +131,11 @@ export const identityTokenAuthServiceFactory = ({ }: TUpdateTokenAuthDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.TOKEN_AUTH) + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) { throw new BadRequestError({ message: "Failed to update Token Auth" }); + } const identityTokenAuth = await identityTokenAuthDAL.findOne({ identityId }); @@ -197,10 +192,11 @@ export const identityTokenAuthServiceFactory = ({ const getTokenAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetTokenAuthDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.TOKEN_AUTH) + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) { throw new BadRequestError({ message: "The identity does not have Token Auth attached" }); + } const identityTokenAuth = await identityTokenAuthDAL.findOne({ identityId }); @@ -225,10 +221,11 @@ export const identityTokenAuthServiceFactory = ({ }: TRevokeTokenAuthDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.TOKEN_AUTH) + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) { throw new BadRequestError({ message: "The identity does not have Token Auth" }); + } const { permission } = await permissionService.getOrgPermission( actor, actorId, @@ -254,7 +251,12 @@ export const identityTokenAuthServiceFactory = ({ const revokedIdentityTokenAuth = await identityTokenAuthDAL.transaction(async (tx) => { const deletedTokenAuth = await identityTokenAuthDAL.delete({ identityId }, tx); - await identityDAL.updateById(identityId, { authMethod: null }, tx); + await identityAccessTokenDAL.delete({ + identityId, + authMethod: IdentityAuthMethod.TOKEN_AUTH + }); + + // await identityDAL.updateById(identityId, { authMethod: null }, tx); return { ...deletedTokenAuth?.[0], orgId: identityMembershipOrg.orgId }; }); return revokedIdentityTokenAuth; @@ -270,10 +272,11 @@ export const identityTokenAuthServiceFactory = ({ }: TCreateTokenAuthTokenDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.TOKEN_AUTH) + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) { throw new BadRequestError({ message: "The identity does not have Token Auth" }); + } const { permission } = await permissionService.getOrgPermission( actor, actorId, @@ -307,7 +310,8 @@ export const identityTokenAuthServiceFactory = ({ accessTokenMaxTTL: identityTokenAuth.accessTokenMaxTTL, accessTokenNumUses: 0, accessTokenNumUsesLimit: identityTokenAuth.accessTokenNumUsesLimit, - name + name, + authMethod: IdentityAuthMethod.TOKEN_AUTH }, tx ); @@ -344,10 +348,11 @@ export const identityTokenAuthServiceFactory = ({ }: TGetTokenAuthTokensDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.TOKEN_AUTH) + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) { throw new BadRequestError({ message: "The identity does not have Token Auth" }); + } const { permission } = await permissionService.getOrgPermission( actor, actorId, @@ -379,10 +384,11 @@ export const identityTokenAuthServiceFactory = ({ if (!foundToken) throw new NotFoundError({ message: "Failed to find token" }); const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: foundToken.identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.TOKEN_AUTH) + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) { throw new BadRequestError({ message: "The identity does not have Token Auth" }); + } const { permission } = await permissionService.getOrgPermission( actor, actorId, diff --git a/backend/src/services/identity-ua/identity-ua-service.ts b/backend/src/services/identity-ua/identity-ua-service.ts index 757afa4d28..509af8462d 100644 --- a/backend/src/services/identity-ua/identity-ua-service.ts +++ b/backend/src/services/identity-ua/identity-ua-service.ts @@ -14,7 +14,7 @@ import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedErro import { checkIPAgainstBlocklist, extractIPDetails, isValidIpOrCidr, TIp } from "@app/lib/ip"; import { ActorType, AuthTokenType } from "../auth/auth-type"; -import { TIdentityDALFactory } from "../identity/identity-dal"; +// import { TIdentityDALFactory } from "../identity/identity-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; @@ -36,7 +36,7 @@ type TIdentityUaServiceFactoryDep = { identityUaClientSecretDAL: TIdentityUaClientSecretDALFactory; identityAccessTokenDAL: TIdentityAccessTokenDALFactory; identityOrgMembershipDAL: TIdentityOrgDALFactory; - identityDAL: Pick; + // identityDAL: Pick; permissionService: Pick; licenseService: Pick; }; @@ -48,7 +48,7 @@ export const identityUaServiceFactory = ({ identityUaClientSecretDAL, identityAccessTokenDAL, identityOrgMembershipDAL, - identityDAL, + // identityDAL, permissionService, licenseService }: TIdentityUaServiceFactoryDep) => { @@ -115,7 +115,8 @@ export const identityUaServiceFactory = ({ accessTokenTTL: identityUa.accessTokenTTL, accessTokenMaxTTL: identityUa.accessTokenMaxTTL, accessTokenNumUses: 0, - accessTokenNumUsesLimit: identityUa.accessTokenNumUsesLimit + accessTokenNumUsesLimit: identityUa.accessTokenNumUsesLimit, + authMethod: IdentityAuthMethod.UNIVERSAL_AUTH }, tx ); @@ -156,10 +157,11 @@ export const identityUaServiceFactory = ({ }: TAttachUaDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity.authMethod) + if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.UNIVERSAL_AUTH)) { throw new BadRequestError({ message: "Failed to add universal auth to already configured identity" }); + } if (accessTokenMaxTTL > 0 && accessTokenTTL > accessTokenMaxTTL) { throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); @@ -221,13 +223,6 @@ export const identityUaServiceFactory = ({ }, tx ); - await identityDAL.updateById( - identityMembershipOrg.identityId, - { - authMethod: IdentityAuthMethod.Univeral - }, - tx - ); return doc; }); return { ...identityUa, orgId: identityMembershipOrg.orgId }; @@ -247,10 +242,12 @@ export const identityUaServiceFactory = ({ }: TUpdateUaDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral) + + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.UNIVERSAL_AUTH)) { throw new BadRequestError({ - message: "Failed to updated universal auth" + message: "Failed to update universal auth" }); + } const uaIdentityAuth = await identityUaDAL.findOne({ identityId }); @@ -321,10 +318,11 @@ export const identityUaServiceFactory = ({ const getIdentityUniversalAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetUaDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral) + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.UNIVERSAL_AUTH)) { throw new BadRequestError({ message: "The identity does not have universal auth" }); + } const uaIdentityAuth = await identityUaDAL.findOne({ identityId }); @@ -348,10 +346,11 @@ export const identityUaServiceFactory = ({ }: TRevokeUaDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral) + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.UNIVERSAL_AUTH)) { throw new BadRequestError({ message: "The identity does not have universal auth" }); + } const { permission } = await permissionService.getOrgPermission( actor, actorId, @@ -375,7 +374,7 @@ export const identityUaServiceFactory = ({ const revokedIdentityUniversalAuth = await identityUaDAL.transaction(async (tx) => { const deletedUniversalAuth = await identityUaDAL.delete({ identityId }, tx); - await identityDAL.updateById(identityId, { authMethod: null }, tx); + // await identityDAL.updateById(identityId, { authMethod: null }, tx); return { ...deletedUniversalAuth?.[0], orgId: identityMembershipOrg.orgId }; }); return revokedIdentityUniversalAuth; @@ -393,10 +392,13 @@ export const identityUaServiceFactory = ({ }: TCreateUaClientSecretDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral) + + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.UNIVERSAL_AUTH)) { throw new BadRequestError({ message: "The identity does not have universal auth" }); + } + const { permission } = await permissionService.getOrgPermission( actor, actorId, @@ -422,12 +424,11 @@ export const identityUaServiceFactory = ({ const appCfg = getConfig(); const clientSecret = crypto.randomBytes(32).toString("hex"); const clientSecretHash = await bcrypt.hash(clientSecret, appCfg.SALT_ROUNDS); - const identityUniversalAuth = await identityUaDAL.findOne({ - identityId - }); + + const identityUaAuth = await identityUaDAL.findOne({ identityId: identityMembershipOrg.identityId }); const identityUaClientSecret = await identityUaClientSecretDAL.create({ - identityUAId: identityUniversalAuth.id, + identityUAId: identityUaAuth.id, description, clientSecretPrefix: clientSecret.slice(0, 4), clientSecretHash, @@ -439,7 +440,6 @@ export const identityUaServiceFactory = ({ return { clientSecret, clientSecretData: identityUaClientSecret, - uaAuth: identityUniversalAuth, orgId: identityMembershipOrg.orgId }; }; @@ -453,10 +453,12 @@ export const identityUaServiceFactory = ({ }: TGetUaClientSecretsDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral) + + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.UNIVERSAL_AUTH)) { throw new BadRequestError({ message: "The identity does not have universal auth" }); + } const { permission } = await permissionService.getOrgPermission( actor, actorId, @@ -500,10 +502,12 @@ export const identityUaServiceFactory = ({ }: TGetUniversalAuthClientSecretByIdDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral) + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.UNIVERSAL_AUTH)) { throw new BadRequestError({ message: "The identity does not have universal auth" }); + } + const { permission } = await permissionService.getOrgPermission( actor, actorId, @@ -539,10 +543,13 @@ export const identityUaServiceFactory = ({ }: TRevokeUaClientSecretDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" }); - if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral) + + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.UNIVERSAL_AUTH)) { throw new BadRequestError({ message: "The identity does not have universal auth" }); + } + const { permission } = await permissionService.getOrgPermission( actor, actorId, diff --git a/backend/src/services/identity/identity-org-dal.ts b/backend/src/services/identity/identity-org-dal.ts index 857f245623..92d5dfab43 100644 --- a/backend/src/services/identity/identity-org-dal.ts +++ b/backend/src/services/identity/identity-org-dal.ts @@ -1,12 +1,52 @@ import { Knex } from "knex"; import { TDbClient } from "@app/db"; -import { TableName, TIdentityOrgMemberships, TOrgRoles } from "@app/db/schemas"; +import { + IdentityAuthMethod, + TableName, + TIdentityAwsAuths, + TIdentityAzureAuths, + TIdentityGcpAuths, + TIdentityKubernetesAuths, + TIdentityOidcAuths, + TIdentityOrgMemberships, + TIdentityTokenAuths, + TIdentityUniversalAuths, + TOrgRoles +} from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex"; import { OrderByDirection } from "@app/lib/types"; import { OrgIdentityOrderBy, TListOrgIdentitiesByOrgIdDTO } from "@app/services/identity/identity-types"; +const buildAuthMethods = ({ + uaId, + gcpId, + awsId, + kubernetesId, + oidcId, + azureId, + tokenId +}: { + uaId?: string; + gcpId?: string; + awsId?: string; + kubernetesId?: string; + oidcId?: string; + azureId?: string; + tokenId?: string; +}) => { + return [ + ...(uaId ? [IdentityAuthMethod.UNIVERSAL_AUTH] : []), + ...(gcpId ? [IdentityAuthMethod.GCP_AUTH] : []), + ...(awsId ? [IdentityAuthMethod.AWS_AUTH] : []), + ...(kubernetesId ? [IdentityAuthMethod.KUBERNETES_AUTH] : []), + ...(oidcId ? [IdentityAuthMethod.OIDC_AUTH] : []), + ...(azureId ? [IdentityAuthMethod.AZURE_AUTH] : []), + ...(tokenId ? [IdentityAuthMethod.TOKEN_AUTH] : []) + ].filter((authMethod) => authMethod); +}; + export type TIdentityOrgDALFactory = ReturnType; export const identityOrgDALFactory = (db: TDbClient) => { @@ -15,14 +55,73 @@ export const identityOrgDALFactory = (db: TDbClient) => { const findOne = async (filter: Partial, tx?: Knex) => { try { const [data] = await (tx || db.replicaNode())(TableName.IdentityOrgMembership) - .where(filter) + .where((queryBuilder) => { + Object.entries(filter).forEach(([key, value]) => { + void queryBuilder.where(`${TableName.IdentityOrgMembership}.${key}`, value); + }); + }) .join(TableName.Identity, `${TableName.IdentityOrgMembership}.identityId`, `${TableName.Identity}.id`) - .select(selectAllTableCols(TableName.IdentityOrgMembership)) - .select(db.ref("name").withSchema(TableName.Identity)) - .select(db.ref("authMethod").withSchema(TableName.Identity)); + + .leftJoin( + TableName.IdentityUniversalAuth, + `${TableName.IdentityOrgMembership}.identityId`, + `${TableName.IdentityUniversalAuth}.identityId` + ) + .leftJoin( + TableName.IdentityGcpAuth, + `${TableName.IdentityOrgMembership}.identityId`, + `${TableName.IdentityGcpAuth}.identityId` + ) + .leftJoin( + TableName.IdentityAwsAuth, + `${TableName.IdentityOrgMembership}.identityId`, + `${TableName.IdentityAwsAuth}.identityId` + ) + .leftJoin( + TableName.IdentityKubernetesAuth, + `${TableName.IdentityOrgMembership}.identityId`, + `${TableName.IdentityKubernetesAuth}.identityId` + ) + .leftJoin( + TableName.IdentityOidcAuth, + `${TableName.IdentityOrgMembership}.identityId`, + `${TableName.IdentityOidcAuth}.identityId` + ) + .leftJoin( + TableName.IdentityAzureAuth, + `${TableName.IdentityOrgMembership}.identityId`, + `${TableName.IdentityAzureAuth}.identityId` + ) + .leftJoin( + TableName.IdentityTokenAuth, + `${TableName.IdentityOrgMembership}.identityId`, + `${TableName.IdentityTokenAuth}.identityId` + ) + + .select( + selectAllTableCols(TableName.IdentityOrgMembership), + + db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth), + db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth), + db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth), + db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth), + db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth), + db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth), + db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth), + + db.ref("name").withSchema(TableName.Identity) + ); + if (data) { - const { name, authMethod } = data; - return { ...data, identity: { id: data.identityId, name, authMethod } }; + const { name } = data; + return { + ...data, + identity: { + id: data.identityId, + name, + authMethods: buildAuthMethods(data) + } + }; } } catch (error) { throw new DatabaseError({ error, name: "FindOne" }); @@ -42,7 +141,7 @@ export const identityOrgDALFactory = (db: TDbClient) => { tx?: Knex ) => { try { - const paginatedIdentity = (tx || db.replicaNode())(TableName.Identity) + const paginatedIdentitySubquery = (tx || db.replicaNode())(TableName.Identity) .join( TableName.IdentityOrgMembership, `${TableName.IdentityOrgMembership}.identityId`, @@ -51,30 +150,70 @@ export const identityOrgDALFactory = (db: TDbClient) => { .orderBy(`${TableName.Identity}.${orderBy}`, orderDirection) .select( selectAllTableCols(TableName.IdentityOrgMembership), - db.ref("name").withSchema(TableName.Identity).as("identityName"), - db.ref("authMethod").withSchema(TableName.Identity).as("identityAuthMethod") + db.ref("name").withSchema(TableName.Identity).as("identityName") + // db.ref("authMethod").withSchema(TableName.Identity).as("identityAuthMethod") ) .where(filter) .as("paginatedIdentity"); if (search?.length) { - void paginatedIdentity.whereILike(`${TableName.Identity}.name`, `%${search}%`); + void paginatedIdentitySubquery.whereILike(`${TableName.Identity}.name`, `%${search}%`); } if (limit) { - void paginatedIdentity.offset(offset).limit(limit); + void paginatedIdentitySubquery.offset(offset).limit(limit); } + const paginatedIdentity = paginatedIdentitySubquery.as("paginatedIdentity"); + // akhilmhdh: refer this for pagination with multiple left queries type TSubquery = Awaited; const query = (tx || db.replicaNode()) .from(paginatedIdentity) .leftJoin(TableName.OrgRoles, `paginatedIdentity.roleId`, `${TableName.OrgRoles}.id`) + .leftJoin(TableName.IdentityMetadata, (queryBuilder) => { void queryBuilder .on(`paginatedIdentity.identityId`, `${TableName.IdentityMetadata}.identityId`) .andOn(`paginatedIdentity.orgId`, `${TableName.IdentityMetadata}.orgId`); }) + + .leftJoin( + TableName.IdentityUniversalAuth, + "paginatedIdentity.identityId", + `${TableName.IdentityUniversalAuth}.identityId` + ) + .leftJoin( + TableName.IdentityGcpAuth, + "paginatedIdentity.identityId", + `${TableName.IdentityGcpAuth}.identityId` + ) + .leftJoin( + TableName.IdentityAwsAuth, + "paginatedIdentity.identityId", + `${TableName.IdentityAwsAuth}.identityId` + ) + .leftJoin( + TableName.IdentityKubernetesAuth, + "paginatedIdentity.identityId", + `${TableName.IdentityKubernetesAuth}.identityId` + ) + .leftJoin( + TableName.IdentityOidcAuth, + "paginatedIdentity.identityId", + `${TableName.IdentityOidcAuth}.identityId` + ) + .leftJoin( + TableName.IdentityAzureAuth, + "paginatedIdentity.identityId", + `${TableName.IdentityAzureAuth}.identityId` + ) + .leftJoin( + TableName.IdentityTokenAuth, + "paginatedIdentity.identityId", + `${TableName.IdentityTokenAuth}.identityId` + ) + .select( db.ref("id").withSchema("paginatedIdentity"), db.ref("role").withSchema("paginatedIdentity"), @@ -82,9 +221,16 @@ export const identityOrgDALFactory = (db: TDbClient) => { db.ref("orgId").withSchema("paginatedIdentity"), db.ref("createdAt").withSchema("paginatedIdentity"), db.ref("updatedAt").withSchema("paginatedIdentity"), - db.ref("identityId").withSchema("paginatedIdentity"), + db.ref("identityId").withSchema("paginatedIdentity").as("identityNewId"), db.ref("identityName").withSchema("paginatedIdentity"), - db.ref("identityAuthMethod").withSchema("paginatedIdentity") + + db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth), + db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth), + db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth), + db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth), + db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth), + db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth), + db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth) ) // cr stands for custom role .select(db.ref("id").as("crId").withSchema(TableName.OrgRoles)) @@ -112,20 +258,27 @@ export const identityOrgDALFactory = (db: TDbClient) => { crSlug, crPermission, crName, - identityId, + identityNewId, identityName, - identityAuthMethod, role, roleId, id, orgId, + uaId, + awsId, + gcpId, + kubernetesId, + oidcId, + azureId, + tokenId, createdAt, updatedAt }) => ({ role, roleId, - identityId, + identityId: identityNewId, id, + orgId, createdAt, updatedAt, @@ -139,9 +292,17 @@ export const identityOrgDALFactory = (db: TDbClient) => { } : undefined, identity: { - id: identityId, + id: identityNewId, name: identityName, - authMethod: identityAuthMethod as string + authMethods: buildAuthMethods({ + uaId, + awsId, + gcpId, + kubernetesId, + oidcId, + azureId, + tokenId + }) } }), childrenMapper: [ diff --git a/frontend/src/components/v2/Badge/Badge.tsx b/frontend/src/components/v2/Badge/Badge.tsx index 321c032966..4afaa2c249 100644 --- a/frontend/src/components/v2/Badge/Badge.tsx +++ b/frontend/src/components/v2/Badge/Badge.tsx @@ -15,7 +15,8 @@ const badgeVariants = cva( variant: { primary: "bg-yellow/20 text-yellow", danger: "bg-red/20 text-red", - success: "bg-green/20 text-green" + success: "bg-green/20 text-green", + info: "bg-blue-500/20 text-blue-500" } } } diff --git a/frontend/src/components/v2/Select/Select.tsx b/frontend/src/components/v2/Select/Select.tsx index dcc7da62c2..a5773640f5 100644 --- a/frontend/src/components/v2/Select/Select.tsx +++ b/frontend/src/components/v2/Select/Select.tsx @@ -125,8 +125,7 @@ export const SelectItem = forwardRef( cursor-pointer select-none items-center overflow-hidden text-ellipsis whitespace-nowrap rounded-md py-2 pl-10 pr-4 text-sm outline-none transition-all hover:bg-mineshaft-500 data-[highlighted]:bg-mineshaft-700/80`, isSelected && "bg-primary", - isDisabled && - "cursor-not-allowed text-gray-600 hover:bg-transparent hover:text-mineshaft-600", + isDisabled && "cursor-not-allowed text-gray-600 opacity-80 hover:!bg-transparent", className )} ref={forwardedRef} diff --git a/frontend/src/hooks/api/identities/mutations.tsx b/frontend/src/hooks/api/identities/mutations.tsx index 21c4c560e1..f7a4b7cf53 100644 --- a/frontend/src/hooks/api/identities/mutations.tsx +++ b/frontend/src/hooks/api/identities/mutations.tsx @@ -137,6 +137,7 @@ export const useUpdateIdentityUniversalAuth = () => { return useMutation({ mutationFn: async ({ identityId, + clientSecretTrustedIps, accessTokenTTL, accessTokenMaxTTL, diff --git a/frontend/src/hooks/api/identities/types.ts b/frontend/src/hooks/api/identities/types.ts index cdad71e9f4..4de1462bfe 100644 --- a/frontend/src/hooks/api/identities/types.ts +++ b/frontend/src/hooks/api/identities/types.ts @@ -12,7 +12,7 @@ export type IdentityTrustedIp = { export type Identity = { id: string; name: string; - authMethod?: IdentityAuthMethod; + authMethods: IdentityAuthMethod[]; createdAt: string; updatedAt: string; }; diff --git a/frontend/src/views/Org/IdentityPage/IdentityPage.tsx b/frontend/src/views/Org/IdentityPage/IdentityPage.tsx index 3d863f2d3d..fa61727b54 100644 --- a/frontend/src/views/Org/IdentityPage/IdentityPage.tsx +++ b/frontend/src/views/Org/IdentityPage/IdentityPage.tsx @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ +import { useState } from "react"; import { useRouter } from "next/router"; import { faChevronLeft, faEllipsis } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -19,12 +20,15 @@ import { import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { withPermission } from "@app/hoc"; import { + IdentityAuthMethod, useDeleteIdentity, useGetIdentityById, useRevokeIdentityTokenAuthToken, - useRevokeIdentityUniversalAuthClientSecret} from "@app/hooks/api"; + useRevokeIdentityUniversalAuthClientSecret +} from "@app/hooks/api"; +import { Identity } from "@app/hooks/api/identities/types"; import { usePopUp } from "@app/hooks/usePopUp"; -import { TabSections } from"@app/views/Org/Types"; +import { TabSections } from "@app/views/Org/Types"; import { IdentityAuthMethodModal } from "../MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal"; import { IdentityModal } from "../MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal"; @@ -49,6 +53,10 @@ export const IdentityPage = withPermission( const { mutateAsync: revokeToken } = useRevokeIdentityTokenAuthToken(); const { mutateAsync: revokeClientSecret } = useRevokeIdentityUniversalAuthClientSecret(); + const [selectedAuthMethod, setSelectedAuthMethod] = useState< + Identity["authMethods"][number] | null + >(null); + const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "identity", "deleteIdentity", @@ -124,7 +132,7 @@ export const IdentityPage = withPermission( const onDeleteClientSecretSubmit = async ({ clientSecretId }: { clientSecretId: string }) => { try { - if (!data?.identity.id) return; + if (!data?.identity.id || selectedAuthMethod !== IdentityAuthMethod.UNIVERSAL_AUTH) return; await revokeClientSecret({ identityId: data?.identity.id, @@ -208,12 +216,13 @@ export const IdentityPage = withPermission( handlePopUpOpen("identityAuthMethod", { identityId, name: data.identity.name, - authMethod: data.identity.authMethod + authMethod: selectedAuthMethod, + allAuthMethods: data.identity.authMethods }); }} disabled={!isAllowed} > - {`${data.identity.authMethod ? "Edit" : "Configure"} Auth Method`} + {`${data.identity.authMethods?.[0] ? "Edit" : "Configure"} Auth Method`} )} @@ -247,6 +256,8 @@ export const IdentityPage = withPermission(
diff --git a/frontend/src/views/Org/IdentityPage/components/IdentityAuthenticationSection/IdentityAuthenticationSection.tsx b/frontend/src/views/Org/IdentityPage/components/IdentityAuthenticationSection/IdentityAuthenticationSection.tsx index be7c744ced..ec3455efd5 100644 --- a/frontend/src/views/Org/IdentityPage/components/IdentityAuthenticationSection/IdentityAuthenticationSection.tsx +++ b/frontend/src/views/Org/IdentityPage/components/IdentityAuthenticationSection/IdentityAuthenticationSection.tsx @@ -1,15 +1,13 @@ -import { faPencil } from "@fortawesome/free-solid-svg-icons"; +import { useEffect } from "react"; +import { faPencil, faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { OrgPermissionCan } from "@app/components/permissions"; -import { - IconButton, - // Button, - Tooltip -} from "@app/components/v2"; +import { IconButton, Select, SelectItem, Tooltip } from "@app/components/v2"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; import { useGetIdentityById } from "@app/hooks/api"; import { IdentityAuthMethod, identityAuthToNameMap } from "@app/hooks/api/identities"; +import { Identity } from "@app/hooks/api/identities/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; import { IdentityClientSecrets } from "./IdentityClientSecrets"; @@ -17,6 +15,8 @@ import { IdentityTokens } from "./IdentityTokens"; type Props = { identityId: string; + setSelectedAuthMethod: (authMethod: Identity["authMethods"][number] | null) => void; + selectedAuthMethod: Identity["authMethods"][number] | null; handlePopUpOpen: ( popUpName: keyof UsePopUpState< [ @@ -33,16 +33,34 @@ type Props = { ) => void; }; -export const IdentityAuthenticationSection = ({ identityId, handlePopUpOpen }: Props) => { +export const IdentityAuthenticationSection = ({ + identityId, + setSelectedAuthMethod, + selectedAuthMethod, + handlePopUpOpen +}: Props) => { const { data } = useGetIdentityById(identityId); + + useEffect(() => { + if (!data?.identity) return; + + if (data.identity.authMethods?.length) { + setSelectedAuthMethod(data.identity.authMethods[0]); + } + + // eslint-disable-next-line consistent-return + return () => setSelectedAuthMethod(null); + }, [data?.identity]); + return data ? (

Authentication

+ {(isAllowed) => { return ( - + - + ); }}
+
+ {data.identity.authMethods.length > 0 && ( + <> +
+ +
+
+ + { + handlePopUpOpen("identityAuthMethod", { + identityId, + name: data.identity.name, + authMethod: selectedAuthMethod, + allAuthMethods: data.identity.authMethods + }); + }} + ariaLabel="copy icon" + variant="plain" + className="group relative" + > + + + {" "} +
+ + )} +

Auth Method

- {data.identity.authMethod - ? identityAuthToNameMap[data.identity.authMethod] - : "Not configured"} + {selectedAuthMethod ? identityAuthToNameMap[selectedAuthMethod] : "Not configured"}

- {data.identity.authMethod === IdentityAuthMethod.UNIVERSAL_AUTH && ( + {selectedAuthMethod === IdentityAuthMethod.UNIVERSAL_AUTH && ( )} - {data.identity.authMethod === IdentityAuthMethod.TOKEN_AUTH && ( + {selectedAuthMethod === IdentityAuthMethod.TOKEN_AUTH && ( )}
diff --git a/frontend/src/views/Org/IdentityPage/components/IdentityClientSecretModal.tsx b/frontend/src/views/Org/IdentityPage/components/IdentityClientSecretModal.tsx index 3f4de4fc19..228a774705 100644 --- a/frontend/src/views/Org/IdentityPage/components/IdentityClientSecretModal.tsx +++ b/frontend/src/views/Org/IdentityPage/components/IdentityClientSecretModal.tsx @@ -62,6 +62,8 @@ export const IdentityClientSecretModal = ({ popUp, handlePopUpToggle }: Props) = identityId: string; }; + console.log(popUpData); + const onFormSubmit = async ({ description, ttl, numUsesLimit }: FormData) => { try { const { clientSecret } = await createClientSecret({ diff --git a/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal.tsx b/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal.tsx index c924b4b66d..80104e2de2 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal.tsx @@ -5,12 +5,14 @@ import * as yup from "yup"; import { createNotification } from "@app/components/notifications"; import { + Badge, DeleteActionModal, FormControl, Modal, ModalContent, Select, SelectItem, + Tooltip, UpgradePlanModal } from "@app/components/v2"; import { useOrganization } from "@app/context"; @@ -43,6 +45,16 @@ type Props = { ) => void; }; +type TRevokeOptions = { + identityId: string; + organizationId: string; +}; + +type TRevokeMethods = { + revokeMethod: (revokeOptions: TRevokeOptions) => Promise; + render: () => JSX.Element; +}; + const identityAuthMethods = [ { label: "Token Auth", value: IdentityAuthMethod.TOKEN_AUTH }, { label: "Universal Auth", value: IdentityAuthMethod.UNIVERSAL_AUTH }, @@ -86,187 +98,123 @@ export const IdentityAuthMethodModal = ({ popUp, handlePopUpOpen, handlePopUpTog }); useEffect(() => { - // reset form on open - if (popUp.identityAuthMethod.isOpen) + if (popUp.identityAuthMethod.isOpen) { reset({ authMethod: popUp?.identityAuthMethod?.data?.authMethod }); + } }, [popUp.identityAuthMethod.isOpen]); + const watchedAuthMethod = watch("authMethod"); + const identityAuthMethodData = { identityId: popUp?.identityAuthMethod.data?.identityId, name: popUp?.identityAuthMethod?.data?.name, - authMethod: watch("authMethod") + authMethod: watch("authMethod"), + configuredAuthMethods: popUp?.identityAuthMethod?.data?.allAuthMethods } as { identityId: string; name: string; authMethod?: IdentityAuthMethod; + configuredAuthMethods?: IdentityAuthMethod[]; }; + const isSelectedAuthAlreadyConfigured = + identityAuthMethodData?.configuredAuthMethods?.includes(watchedAuthMethod); + useEffect(() => { - if (identityAuthMethodData?.authMethod) { - setValue("authMethod", identityAuthMethodData.authMethod); - return; - } + if (popUp?.identityAuthMethod?.data?.authMethod) { + setValue("authMethod", popUp?.identityAuthMethod?.data?.authMethod); + } else { + const firstAuthMethodNotConfiguredAuthMethod = identityAuthMethods.find( + ({ value }) => !identityAuthMethodData?.configuredAuthMethods?.includes(value) + ); - setValue("authMethod", IdentityAuthMethod.UNIVERSAL_AUTH); - }, [identityAuthMethodData?.authMethod]); - - const onRevokeAuthMethodSubmit = async (authMethod: IdentityAuthMethod) => { - if (!orgId || !authMethod) return; - try { - switch (authMethod) { - case IdentityAuthMethod.UNIVERSAL_AUTH: { - await revokeUniversalAuth({ - identityId: identityAuthMethodData.identityId, - organizationId: orgId - }); - break; - } - case IdentityAuthMethod.TOKEN_AUTH: { - await revokeTokenAuth({ - identityId: identityAuthMethodData.identityId, - organizationId: orgId - }); - break; - } - case IdentityAuthMethod.KUBERNETES_AUTH: { - await revokeKubernetesAuth({ - identityId: identityAuthMethodData.identityId, - organizationId: orgId - }); - break; - } - case IdentityAuthMethod.GCP_AUTH: { - await revokeGcpAuth({ - identityId: identityAuthMethodData.identityId, - organizationId: orgId - }); - break; - } - case IdentityAuthMethod.AWS_AUTH: { - await revokeAwsAuth({ - identityId: identityAuthMethodData.identityId, - organizationId: orgId - }); - break; - } - case IdentityAuthMethod.AZURE_AUTH: { - await revokeAzureAuth({ - identityId: identityAuthMethodData.identityId, - organizationId: orgId - }); - break; - } - case IdentityAuthMethod.OIDC_AUTH: { - await revokeOidcAuth({ - identityId: identityAuthMethodData.identityId, - organizationId: orgId - }); - break; - } - default: - break; + if (firstAuthMethodNotConfiguredAuthMethod) { + setValue("authMethod", firstAuthMethodNotConfiguredAuthMethod.value); } - - createNotification({ - text: `Successfully removed ${identityAuthToNameMap[authMethod]} on ${identityAuthMethodData.name}`, - type: "success" - }); - - handlePopUpToggle("revokeAuthMethod", false); - handlePopUpToggle("identityAuthMethod", false); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to remove ${identityAuthToNameMap[authMethod]} on ${identityAuthMethodData.name}`, - type: "error" - }); } - }; - const renderIdentityAuthForm = () => { - switch (identityAuthMethodData.authMethod) { - case IdentityAuthMethod.AWS_AUTH: { - return ( - - ); - } - case IdentityAuthMethod.KUBERNETES_AUTH: { - return ( - - ); - } - case IdentityAuthMethod.GCP_AUTH: { - return ( - - ); - } - case IdentityAuthMethod.AZURE_AUTH: { - return ( - - ); - } - case IdentityAuthMethod.UNIVERSAL_AUTH: { - return ( - - ); - } - case IdentityAuthMethod.OIDC_AUTH: { - return ( - - ); - } - case IdentityAuthMethod.TOKEN_AUTH: { - return ( - - ); - } - default: { - return
; - } + }, [popUp.identityAuthMethod.isOpen]); + + const methodMap: Record = { + [IdentityAuthMethod.UNIVERSAL_AUTH]: { + revokeMethod: revokeUniversalAuth, + render: () => ( + + ) + }, + + [IdentityAuthMethod.OIDC_AUTH]: { + revokeMethod: revokeOidcAuth, + render: () => ( + + ) + }, + + [IdentityAuthMethod.TOKEN_AUTH]: { + revokeMethod: revokeTokenAuth, + render: () => ( + + ) + }, + + [IdentityAuthMethod.AZURE_AUTH]: { + revokeMethod: revokeAzureAuth, + render: () => ( + + ) + }, + + [IdentityAuthMethod.GCP_AUTH]: { + revokeMethod: revokeGcpAuth, + render: () => ( + + ) + }, + + [IdentityAuthMethod.KUBERNETES_AUTH]: { + revokeMethod: revokeKubernetesAuth, + render: () => ( + + ) + }, + + [IdentityAuthMethod.AWS_AUTH]: { + revokeMethod: revokeAwsAuth, + render: () => ( + + ) } }; + const selectedMethodItem = methodMap[identityAuthMethodData.authMethod!]; + return ( ( )} /> - {renderIdentityAuthForm()} + {selectedMethodItem?.render ? selectedMethodItem.render() :
} handlePopUpToggle("upgradePlan", isOpen)} @@ -320,7 +291,37 @@ export const IdentityAuthMethodModal = ({ popUp, handlePopUpOpen, handlePopUpTog onChange={(isOpen) => handlePopUpToggle("revokeAuthMethod", isOpen)} deleteKey="confirm" buttonText="Remove" - onDeleteApproved={() => onRevokeAuthMethodSubmit(identityAuthMethodData.authMethod!)} + onDeleteApproved={async () => { + if (!identityAuthMethodData.authMethod || !orgId) { + return; + } + + const selectedRevoke = methodMap[identityAuthMethodData.authMethod]; + + if (!selectedRevoke) { + return; + } + + try { + await selectedRevoke.revokeMethod({ + identityId: identityAuthMethodData.identityId, + organizationId: orgId + }); + + createNotification({ + text: "Successfully removed auth method", + type: "success" + }); + + handlePopUpToggle("revokeAuthMethod", false); + handlePopUpToggle("identityAuthMethod", false); + } catch (err) { + createNotification({ + text: "Failed to remove auth method", + type: "error" + }); + } + }} /> diff --git a/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm.tsx b/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm.tsx index 781a781257..a254397f41 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm.tsx @@ -6,7 +6,7 @@ import { yupResolver } from "@hookform/resolvers/yup"; import * as yup from "yup"; import { createNotification } from "@app/components/notifications"; -import { Button, DeleteActionModal, FormControl, IconButton, Input } from "@app/components/v2"; +import { Button, FormControl, IconButton, Input } from "@app/components/v2"; import { useOrganization, useSubscription } from "@app/context"; import { useAddIdentityAwsAuth, @@ -15,7 +15,7 @@ import { } from "@app/hooks/api"; import { IdentityAuthMethod } from "@app/hooks/api/identities"; import { IdentityTrustedIp } from "@app/hooks/api/identities/types"; -import { usePopUp, UsePopUpState } from "@app/hooks/usePopUp"; +import { UsePopUpState } from "@app/hooks/usePopUp"; const schema = yup .object({ @@ -62,18 +62,15 @@ type Props = { identityAuthMethodData: { identityId: string; name: string; + configuredAuthMethods?: IdentityAuthMethod[]; authMethod?: IdentityAuthMethod; }; - initialAuthMethod: IdentityAuthMethod; - revokeAuth: (authMethod: IdentityAuthMethod) => Promise; }; export const IdentityAwsAuthForm = ({ handlePopUpOpen, handlePopUpToggle, - identityAuthMethodData, - initialAuthMethod, - revokeAuth + identityAuthMethodData }: Props) => { const { currentOrg } = useOrganization(); const orgId = currentOrg?.id || ""; @@ -82,13 +79,13 @@ export const IdentityAwsAuthForm = ({ const { mutateAsync: addMutateAsync } = useAddIdentityAwsAuth(); const { mutateAsync: updateMutateAsync } = useUpdateIdentityAwsAuth(); - const isCurrentAuthMethod = identityAuthMethodData?.authMethod === initialAuthMethod; + const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes( + identityAuthMethodData.authMethod! || "" + ); const { data } = useGetIdentityAwsAuth(identityAuthMethodData?.identityId ?? "", { - enabled: isCurrentAuthMethod + enabled: isUpdate }); - const internalPopUpState = usePopUp(["overwriteAuthMethod"] as const); - const { control, handleSubmit, @@ -184,230 +181,204 @@ export const IdentityAwsAuthForm = ({ handlePopUpToggle("identityAuthMethod", false); createNotification({ - text: `Successfully ${isCurrentAuthMethod ? "updated" : "configured"} auth method`, + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, type: "success" }); reset(); } catch (err) { createNotification({ - text: `Failed to ${identityAuthMethodData?.authMethod ? "update" : "configure"} identity`, + text: `Failed to ${isUpdate ? "update" : "configure"} identity`, type: "error" }); } }; return ( - <> -
- ( - - - - )} - /> - ( - - - - )} - /> - ( - - - - )} - /> - ( - - - - )} - /> - ( - - - - )} - /> - ( - - - - )} - /> - {accessTokenTrustedIpsFields.map(({ id }, index) => ( -
- { - return ( - - { - if (subscription?.ipAllowlisting) { - field.onChange(e); - return; - } - - handlePopUpOpen("upgradePlan"); - }} - placeholder="123.456.789.0" - /> - - ); - }} + + ( + + - { - if (subscription?.ipAllowlisting) { - removeAccessTokenTrustedIp(index); - return; - } + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + {accessTokenTrustedIpsFields.map(({ id }, index) => ( +
+ { + return ( + + { + if (subscription?.ipAllowlisting) { + field.onChange(e); + return; + } - handlePopUpOpen("upgradePlan"); - }} - size="lg" - colorSchema="danger" - variant="plain" - ariaLabel="update" - className="p-3" - > - - -
- ))} -
- + +
-
-
- {initialAuthMethod && identityAuthMethodData?.authMethod !== initialAuthMethod ? ( - - ) : ( - - )} - -
- {isCurrentAuthMethod && ( - - )} + ))} +
+ +
+
+
+ + +
- - internalPopUpState.handlePopUpToggle("overwriteAuthMethod", isOpen)} - deleteKey="confirm" - buttonText="Overwrite" - onDeleteApproved={async () => { - await revokeAuth(initialAuthMethod); - handleSubmit(onFormSubmit)(); - }} - /> - + {isUpdate && ( + + )} +
+ ); }; diff --git a/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm.tsx b/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm.tsx index a2a6387fb4..0acc7ec5a6 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm.tsx @@ -6,7 +6,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; -import { Button, DeleteActionModal, FormControl, IconButton, Input } from "@app/components/v2"; +import { Button, FormControl, IconButton, Input } from "@app/components/v2"; import { useOrganization, useSubscription } from "@app/context"; import { useAddIdentityAzureAuth, @@ -15,7 +15,7 @@ import { } from "@app/hooks/api"; import { IdentityAuthMethod } from "@app/hooks/api/identities"; import { IdentityTrustedIp } from "@app/hooks/api/identities/types"; -import { usePopUp, UsePopUpState } from "@app/hooks/usePopUp"; +import { UsePopUpState } from "@app/hooks/usePopUp"; const schema = z .object({ @@ -50,18 +50,15 @@ type Props = { identityAuthMethodData: { identityId: string; name: string; + configuredAuthMethods?: IdentityAuthMethod[]; authMethod?: IdentityAuthMethod; }; - initialAuthMethod: IdentityAuthMethod; - revokeAuth: (authMethod: IdentityAuthMethod) => Promise; }; export const IdentityAzureAuthForm = ({ handlePopUpOpen, handlePopUpToggle, - identityAuthMethodData, - initialAuthMethod, - revokeAuth + identityAuthMethodData }: Props) => { const { currentOrg } = useOrganization(); const orgId = currentOrg?.id || ""; @@ -70,18 +67,18 @@ export const IdentityAzureAuthForm = ({ const { mutateAsync: addMutateAsync } = useAddIdentityAzureAuth(); const { mutateAsync: updateMutateAsync } = useUpdateIdentityAzureAuth(); - const isCurrentAuthMethod = identityAuthMethodData?.authMethod === initialAuthMethod; + const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes( + identityAuthMethodData.authMethod! || "" + ); const { data } = useGetIdentityAzureAuth(identityAuthMethodData?.identityId ?? "", { - enabled: isCurrentAuthMethod + enabled: isUpdate }); - const internalPopUpState = usePopUp(["overwriteAuthMethod"] as const); - const { control, handleSubmit, reset, - trigger, + formState: { isSubmitting } } = useForm({ resolver: zodResolver(schema), @@ -173,239 +170,204 @@ export const IdentityAzureAuthForm = ({ handlePopUpToggle("identityAuthMethod", false); createNotification({ - text: `Successfully ${isCurrentAuthMethod ? "updated" : "configured"} auth method`, + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, type: "success" }); reset(); } catch (err) { createNotification({ - text: `Failed to ${identityAuthMethodData?.authMethod ? "update" : "configure"} identity`, + text: `Failed to ${isUpdate ? "update" : "configure"} identity`, type: "error" }); } }; return ( - <> -
- ( - - - - )} - /> - ( - - - - )} - /> - ( - - - - )} - /> - ( - - - - )} - /> - ( - - - - )} - /> - ( - - - - )} - /> - {accessTokenTrustedIpsFields.map(({ id }, index) => ( -
- { - return ( - - { - if (subscription?.ipAllowlisting) { - field.onChange(e); - return; - } - - handlePopUpOpen("upgradePlan"); - }} - placeholder="123.456.789.0" - /> - - ); - }} - /> - { - if (subscription?.ipAllowlisting) { - removeAccessTokenTrustedIp(index); - return; - } + + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + {accessTokenTrustedIpsFields.map(({ id }, index) => ( +
+ { + return ( + + { + if (subscription?.ipAllowlisting) { + field.onChange(e); + return; + } - handlePopUpOpen("upgradePlan"); - }} - size="lg" - colorSchema="danger" - variant="plain" - ariaLabel="update" - className="p-3" - > - - -
- ))} -
- + +
-
-
- {initialAuthMethod && identityAuthMethodData?.authMethod !== initialAuthMethod ? ( - - ) : ( - - )} - -
- {isCurrentAuthMethod && ( - - )} + ))} +
+ +
+
+
+ + +
- - internalPopUpState.handlePopUpToggle("overwriteAuthMethod", isOpen)} - deleteKey="confirm" - buttonText="Overwrite" - onDeleteApproved={async () => { - const result = await trigger(); - if (result) { - await revokeAuth(initialAuthMethod); - handleSubmit(onFormSubmit)(); - } else { - createNotification({ - text: "Please fill in all required fields", - type: "error" - }); - internalPopUpState.handlePopUpToggle("overwriteAuthMethod", false); - } - }} - /> - + {isUpdate && ( + + )} +
+ ); }; diff --git a/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm.tsx b/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm.tsx index 81dd51d22f..fe0c60f277 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm.tsx @@ -6,15 +6,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; -import { - Button, - DeleteActionModal, - FormControl, - IconButton, - Input, - Select, - SelectItem -} from "@app/components/v2"; +import { Button, FormControl, IconButton, Input, Select, SelectItem } from "@app/components/v2"; import { useOrganization, useSubscription } from "@app/context"; import { useAddIdentityGcpAuth, @@ -23,7 +15,7 @@ import { } from "@app/hooks/api"; import { IdentityAuthMethod } from "@app/hooks/api/identities"; import { IdentityTrustedIp } from "@app/hooks/api/identities/types"; -import { usePopUp, UsePopUpState } from "@app/hooks/usePopUp"; +import { UsePopUpState } from "@app/hooks/usePopUp"; const schema = z .object({ @@ -59,18 +51,15 @@ type Props = { identityAuthMethodData: { identityId: string; name: string; + configuredAuthMethods?: IdentityAuthMethod[]; authMethod?: IdentityAuthMethod; }; - initialAuthMethod: IdentityAuthMethod; - revokeAuth: (authMethod: IdentityAuthMethod) => Promise; }; export const IdentityGcpAuthForm = ({ handlePopUpOpen, handlePopUpToggle, - identityAuthMethodData, - revokeAuth, - initialAuthMethod + identityAuthMethodData }: Props) => { const { currentOrg } = useOrganization(); const orgId = currentOrg?.id || ""; @@ -79,11 +68,12 @@ export const IdentityGcpAuthForm = ({ const { mutateAsync: addMutateAsync } = useAddIdentityGcpAuth(); const { mutateAsync: updateMutateAsync } = useUpdateIdentityGcpAuth(); - const isCurrentAuthMethod = identityAuthMethodData?.authMethod === initialAuthMethod; + const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes( + identityAuthMethodData.authMethod! || "" + ); const { data } = useGetIdentityGcpAuth(identityAuthMethodData?.identityId ?? "", { - enabled: isCurrentAuthMethod + enabled: isUpdate }); - const internalPopUpState = usePopUp(["overwriteAuthMethod"] as const); const { control, @@ -189,258 +179,228 @@ export const IdentityGcpAuthForm = ({ handlePopUpToggle("identityAuthMethod", false); createNotification({ - text: `Successfully ${isCurrentAuthMethod ? "updated" : "configured"} auth method`, + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, type: "success" }); reset(); } catch (err) { createNotification({ - text: `Failed to ${identityAuthMethodData?.authMethod ? "update" : "configure"} identity`, + text: `Failed to ${isUpdate ? "update" : "configure"} identity`, type: "error" }); } }; return ( - <> -
- ( - - - - )} - /> - ( - + ( + + - - )} - /> - {watchedType === "gce" && ( - ( - - - - )} - /> + + GCP ID Token Auth (Recommended) + + + GCP IAM Auth + + + )} - {watchedType === "gce" && ( - ( - - - - )} - /> + /> + ( + + + )} + /> + {watchedType === "gce" && ( ( - - - - )} - /> - ( - + )} /> + )} + {watchedType === "gce" && ( ( - - + + )} /> - {accessTokenTrustedIpsFields.map(({ id }, index) => ( -
- { - return ( - - { - if (subscription?.ipAllowlisting) { - field.onChange(e); - return; - } - - handlePopUpOpen("upgradePlan"); - }} - placeholder="123.456.789.0" - /> - - ); - }} - /> - { - if (subscription?.ipAllowlisting) { - removeAccessTokenTrustedIp(index); - return; - } + )} + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + {accessTokenTrustedIpsFields.map(({ id }, index) => ( +
+ { + return ( + + { + if (subscription?.ipAllowlisting) { + field.onChange(e); + return; + } - handlePopUpOpen("upgradePlan"); - }} - size="lg" - colorSchema="danger" - variant="plain" - ariaLabel="update" - className="p-3" - > - - -
- ))} -
- + +
-
-
- {initialAuthMethod && identityAuthMethodData?.authMethod !== initialAuthMethod ? ( - - ) : ( - - )} - -
- {isCurrentAuthMethod && ( - - )} + ))} +
+ +
+
+
+ + +
- - internalPopUpState.handlePopUpToggle("overwriteAuthMethod", isOpen)} - deleteKey="confirm" - onDeleteApproved={async () => { - await revokeAuth(initialAuthMethod); - handleSubmit(onFormSubmit)(); - }} - /> - + {isUpdate && ( + + )} +
+ ); }; diff --git a/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx b/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx index fc272a63f2..af5559f74f 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx @@ -6,14 +6,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; -import { - Button, - DeleteActionModal, - FormControl, - IconButton, - Input, - TextArea -} from "@app/components/v2"; +import { Button, FormControl, IconButton, Input, TextArea } from "@app/components/v2"; import { useOrganization, useSubscription } from "@app/context"; import { useAddIdentityKubernetesAuth, @@ -22,7 +15,7 @@ import { } from "@app/hooks/api"; import { IdentityAuthMethod } from "@app/hooks/api/identities"; import { IdentityTrustedIp } from "@app/hooks/api/identities/types"; -import { usePopUp, UsePopUpState } from "@app/hooks/usePopUp"; +import { UsePopUpState } from "@app/hooks/usePopUp"; const schema = z .object({ @@ -60,18 +53,15 @@ type Props = { identityAuthMethodData: { identityId: string; name: string; + configuredAuthMethods?: IdentityAuthMethod[]; authMethod?: IdentityAuthMethod; }; - initialAuthMethod: IdentityAuthMethod; - revokeAuth: (authMethod: IdentityAuthMethod) => Promise; }; export const IdentityKubernetesAuthForm = ({ handlePopUpOpen, handlePopUpToggle, - identityAuthMethodData, - initialAuthMethod, - revokeAuth + identityAuthMethodData }: Props) => { const { currentOrg } = useOrganization(); const orgId = currentOrg?.id || ""; @@ -80,17 +70,18 @@ export const IdentityKubernetesAuthForm = ({ const { mutateAsync: addMutateAsync } = useAddIdentityKubernetesAuth(); const { mutateAsync: updateMutateAsync } = useUpdateIdentityKubernetesAuth(); - const isCurrentAuthMethod = identityAuthMethodData?.authMethod === initialAuthMethod; + const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes( + identityAuthMethodData.authMethod! || "" + ); const { data } = useGetIdentityKubernetesAuth(identityAuthMethodData?.identityId ?? "", { - enabled: isCurrentAuthMethod + enabled: isUpdate }); - const internalPopUpState = usePopUp(["overwriteAuthMethod"] as const); const { control, handleSubmit, reset, - trigger, + formState: { isSubmitting } } = useForm({ resolver: zodResolver(schema), @@ -200,291 +191,256 @@ export const IdentityKubernetesAuthForm = ({ handlePopUpToggle("identityAuthMethod", false); createNotification({ - text: `Successfully ${isCurrentAuthMethod ? "updated" : "configured"} auth method`, + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, type: "success" }); reset(); } catch (err) { createNotification({ - text: `Failed to ${identityAuthMethodData?.authMethod ? "update" : "configure"} identity`, + text: `Failed to ${isUpdate ? "update" : "configure"} identity`, type: "error" }); } }; return ( - <> -
- ( - - - - )} - /> - ( - - - - )} - /> - ( - - - - )} - /> - ( - - - - )} - /> - ( - - - - )} - /> - ( - -