From 4927cc804aeaf6372d11165f15c2c7fb16e8b513 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Thu, 4 Jul 2024 00:53:24 +0800 Subject: [PATCH] feat: added endpoint for oidc auth revocation --- backend/src/lib/api-docs/constants.ts | 6 ++ .../routes/v1/identity-oidc-auth-router.ts | 50 ++++++++++++++++ .../identity-oidc-auth-service.ts | 60 ++++++++++++++++++- .../identity-oidc-auth-types.ts | 4 ++ 4 files changed, 117 insertions(+), 3 deletions(-) diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index de0a1d4c25..9b042c5c9a 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -142,6 +142,12 @@ export const KUBERNETES_AUTH = { } } as const; +export const OIDC_AUTH = { + REVOKE: { + identityId: "The ID of the identity to revoke." + } +} as const; + export const ORGANIZATIONS = { LIST_USER_MEMBERSHIPS: { organizationId: "The ID of the organization to get memberships from." diff --git a/backend/src/server/routes/v1/identity-oidc-auth-router.ts b/backend/src/server/routes/v1/identity-oidc-auth-router.ts index 61a74aa735..7ebcd339fb 100644 --- a/backend/src/server/routes/v1/identity-oidc-auth-router.ts +++ b/backend/src/server/routes/v1/identity-oidc-auth-router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { IdentityOidcAuthsSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { OIDC_AUTH } from "@app/lib/api-docs"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -297,4 +298,53 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider) return { identityOidcAuth }; } }); + + server.route({ + method: "DELETE", + url: "/oidc-auth/identities/:identityId", + config: { + rateLimit: writeLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + description: "Delete OIDC Auth configuration on identity", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + identityId: z.string().describe(OIDC_AUTH.REVOKE.identityId) + }), + response: { + 200: z.object({ + identityOidcAuth: IdentityOidcAuthResponseSchema.omit({ + caCert: true + }) + }) + } + }, + handler: async (req) => { + const identityOidcAuth = await server.services.identityOidcAuth.revokeOidcAuth({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + identityId: req.params.identityId + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: identityOidcAuth.orgId, + event: { + type: EventType.REVOKE_IDENTITY_OIDC_AUTH, + metadata: { + identityId: identityOidcAuth.identityId + } + } + }); + + return { identityOidcAuth }; + } + }); }; 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 fcd0d4ef09..c639289de8 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 @@ -8,6 +8,7 @@ import { IdentityAuthMethod, SecretKeyEncoding, TIdentityOidcAuthsUpdate } from import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; +import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { getConfig } from "@app/lib/config/env"; import { generateAsymmetricKeyPair } from "@app/lib/crypto"; import { @@ -17,17 +18,23 @@ import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; -import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; +import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; -import { AuthTokenType } from "../auth/auth-type"; +import { ActorType, AuthTokenType } from "../auth/auth-type"; 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"; import { TOrgBotDALFactory } from "../org/org-bot-dal"; import { TIdentityOidcAuthDALFactory } from "./identity-oidc-auth-dal"; -import { TAttachOidcAuthDTO, TGetOidcAuthDTO, TLoginOidcAuthDTO, TUpdateOidcAuthDTO } from "./identity-oidc-auth-types"; +import { + TAttachOidcAuthDTO, + TGetOidcAuthDTO, + TLoginOidcAuthDTO, + TRevokeOidcAuthDTO, + TUpdateOidcAuthDTO +} from "./identity-oidc-auth-types"; type TIdentityOidcAuthServiceFactoryDep = { identityOidcAuthDAL: TIdentityOidcAuthDALFactory; @@ -471,10 +478,57 @@ export const identityOidcAuthServiceFactory = ({ return { ...identityOidcAuth, orgId: identityMembershipOrg.orgId, caCert }; }; + const revokeOidcAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TRevokeOidcAuthDTO) => { + const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); + if (!identityMembershipOrg) { + throw new BadRequestError({ message: "Failed to find identity" }); + } + + if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.OIDC_AUTH) { + throw new BadRequestError({ + message: "The identity does not have OIDC auth" + }); + } + + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + identityMembershipOrg.orgId, + actorAuthMethod, + actorOrgId + ); + + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); + + const { permission: rolePermission } = await permissionService.getOrgPermission( + ActorType.IDENTITY, + identityMembershipOrg.identityId, + identityMembershipOrg.orgId, + actorAuthMethod, + actorOrgId + ); + + const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); + if (!hasPriviledge) { + throw new ForbiddenRequestError({ + message: "Failed to revoke OIDC auth of identity with more privileged role" + }); + } + + const revokedIdentityOidcAuth = await identityOidcAuthDAL.transaction(async (tx) => { + const deletedOidcAuth = await identityOidcAuthDAL.delete({ identityId }, tx); + await identityDAL.updateById(identityId, { authMethod: null }, tx); + return { ...deletedOidcAuth?.[0], orgId: identityMembershipOrg.orgId }; + }); + + return revokedIdentityOidcAuth; + }; + return { attachOidcAuth, updateOidcAuth, getOidcAuth, + revokeOidcAuth, login }; }; diff --git a/backend/src/services/identity-oidc-auth/identity-oidc-auth-types.ts b/backend/src/services/identity-oidc-auth/identity-oidc-auth-types.ts index 0a164311c2..761f68aa74 100644 --- a/backend/src/services/identity-oidc-auth/identity-oidc-auth-types.ts +++ b/backend/src/services/identity-oidc-auth/identity-oidc-auth-types.ts @@ -36,3 +36,7 @@ export type TLoginOidcAuthDTO = { identityId: string; jwt: string; }; + +export type TRevokeOidcAuthDTO = { + identityId: string; +} & Omit;