From b94b1d480f9e2134461620e9ad94925f429cbcdb Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Fri, 28 Nov 2025 01:30:40 +0530 Subject: [PATCH] feat: adds scope org id column and fixes inject identity middleware --- ...-scope-org-id-to-identity-access-tokens.ts | 16 ++++++ .../src/db/schemas/identity-access-tokens.ts | 3 +- .../server/plugins/auth/inject-identity.ts | 20 +------ .../identity-access-token-dal.ts | 3 +- .../identity-access-token-service.ts | 56 +++++++------------ .../identity-access-token-types.ts | 1 + .../IdentitySection/IdentityTable.tsx | 2 +- 7 files changed, 43 insertions(+), 58 deletions(-) create mode 100644 backend/src/db/migrations/20251127192155_adds-scope-org-id-to-identity-access-tokens.ts diff --git a/backend/src/db/migrations/20251127192155_adds-scope-org-id-to-identity-access-tokens.ts b/backend/src/db/migrations/20251127192155_adds-scope-org-id-to-identity-access-tokens.ts new file mode 100644 index 000000000..cc55c1fb6 --- /dev/null +++ b/backend/src/db/migrations/20251127192155_adds-scope-org-id-to-identity-access-tokens.ts @@ -0,0 +1,16 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + await knex.schema.alterTable(TableName.IdentityAccessToken, (t) => { + t.uuid("scopeOrgId").notNullable(); + t.foreign("scopeOrgId").references("id").inTable(TableName.Organization).onDelete("CASCADE"); + }); +} + +export async function down(knex: Knex): Promise { + await knex.schema.alterTable(TableName.IdentityAccessToken, (t) => { + t.dropColumn("scopeOrgId"); + }); +} diff --git a/backend/src/db/schemas/identity-access-tokens.ts b/backend/src/db/schemas/identity-access-tokens.ts index 8f2b8b73b..dfa962439 100644 --- a/backend/src/db/schemas/identity-access-tokens.ts +++ b/backend/src/db/schemas/identity-access-tokens.ts @@ -22,7 +22,8 @@ export const IdentityAccessTokensSchema = z.object({ updatedAt: z.date(), name: z.string().nullable().optional(), authMethod: z.string(), - accessTokenPeriod: z.coerce.number().default(0) + accessTokenPeriod: z.coerce.number().default(0), + scopeOrgId: z.string().uuid().nullable().optional() }); export type TIdentityAccessTokens = z.infer; diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index e6ba2eec7..a25430d99 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -8,7 +8,6 @@ import { TScimTokenJwtPayload } from "@app/ee/services/scim/scim-types"; import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto"; import { BadRequestError } from "@app/lib/errors"; -import { slugSchema } from "@app/server/lib/schemas"; import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type"; import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types"; import { getServerCfg } from "@app/services/super-admin/super-admin-service"; @@ -152,15 +151,10 @@ export const injectIdentity = fp( if (!authMode) return; - const subOrganizationSelector = req.headers?.["x-infisical-org"] as string | undefined; - if (subOrganizationSelector) { - await slugSchema().parseAsync(subOrganizationSelector); - } - switch (authMode) { case AuthMode.JWT: { const { user, tokenVersionId, orgId, orgName, rootOrgId, parentOrgId } = - await server.services.authToken.fnValidateJwtIdentity(token, subOrganizationSelector); + await server.services.authToken.fnValidateJwtIdentity(token); requestContext.set("orgId", orgId); requestContext.set("orgName", orgName); requestContext.set("userAuthInfo", { userId: user.id, email: user.email || "" }); @@ -180,11 +174,7 @@ export const injectIdentity = fp( break; } case AuthMode.IDENTITY_ACCESS_TOKEN: { - const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken( - token, - req.realIp, - subOrganizationSelector - ); + const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken(token, req.realIp); const serverCfg = await getServerCfg(); requestContext.set("orgId", identity.orgId); requestContext.set("orgName", identity.orgName); @@ -223,9 +213,6 @@ export const injectIdentity = fp( const serviceToken = await server.services.serviceToken.fnValidateServiceToken(token); requestContext.set("orgId", serviceToken.orgId); - if (subOrganizationSelector) - throw new BadRequestError({ message: `Service token doesn't support sub organization selector` }); - req.auth = { orgId: serviceToken.orgId, rootOrgId: serviceToken.rootOrgId, @@ -248,9 +235,6 @@ export const injectIdentity = fp( const { orgId, scimTokenId } = await server.services.scim.fnValidateScimToken(token); requestContext.set("orgId", orgId); - if (subOrganizationSelector) - throw new BadRequestError({ message: `SCIM token doesn't support sub organization selector` }); - req.auth = { authMode: AuthMode.SCIM_TOKEN, actor, 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 74b624a7e..7c98ac9f9 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 @@ -18,7 +18,8 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => { .where(filter) .join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityAccessToken}.identityId`) .select(selectAllTableCols(TableName.IdentityAccessToken)) - .select(db.ref("orgId").withSchema(TableName.Identity).as("identityScopeOrgId")) + .select(db.ref("orgId").withSchema(TableName.Identity).as("identityOrgId")) + .select(db.ref("scopeOrgId").withSchema(TableName.IdentityAccessToken).as("scopeOrgId")) .first(); return doc; 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 244e98908..cf39b38c9 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 @@ -184,11 +184,7 @@ export const identityAccessTokenServiceFactory = ({ return { revokedToken }; }; - const fnValidateIdentityAccessToken = async ( - token: TIdentityAccessTokenJwtPayload, - ipAddress?: string, - subOrganizationSelector?: string - ) => { + const fnValidateIdentityAccessToken = async (token: TIdentityAccessTokenJwtPayload, ipAddress?: string) => { const identityAccessToken = await identityAccessTokenDAL.findOne({ [`${TableName.IdentityAccessToken}.id` as "id"]: token.identityAccessTokenId, isAccessTokenRevoked: false @@ -209,46 +205,32 @@ export const identityAccessTokenServiceFactory = ({ trustedIps: trustedIps as TIp[] }); } - let orgId = ""; - let orgName = ""; - let parentOrgId = ""; - const identityOrgDetails = await orgDAL.findOne({ id: identityAccessToken.identityScopeOrgId }); - const rootOrgId = identityOrgDetails.rootOrgId || identityOrgDetails.id; - if (subOrganizationSelector) { - const subOrganization = await orgDAL.findOne({ rootOrgId, slug: subOrganizationSelector }); - if (!subOrganization) - throw new BadRequestError({ message: `Sub organization ${subOrganizationSelector} not found` }); + const scopeOrgId = identityAccessToken.scopeOrgId || identityAccessToken.identityOrgId; - const identityOrgMembership = await membershipIdentityDAL.findOne({ - scope: AccessScope.Organization, - actorIdentityId: identityAccessToken.identityId, - scopeOrgId: subOrganization.id - }); + const identityOrgDetails = await orgDAL.findOne({ id: scopeOrgId }); - if (!identityOrgMembership) { - throw new BadRequestError({ message: "Identity does not belong to this organization" }); - } - orgId = subOrganization.id; - orgName = subOrganization.name; + const isSubOrg = !!(identityOrgDetails.rootOrgId || identityOrgDetails.parentOrgId); - parentOrgId = subOrganization.parentOrgId as string; - } else { - const identityOrgMembership = await membershipIdentityDAL.findOne({ - scope: AccessScope.Organization, - actorIdentityId: identityAccessToken.identityId, - scopeOrgId: identityOrgDetails.id - }); + const rootOrgId = isSubOrg + ? identityOrgDetails.rootOrgId || identityOrgDetails.parentOrgId || identityOrgDetails.id + : identityOrgDetails.id; - if (!identityOrgMembership) { - throw new BadRequestError({ message: "Identity does not belong to this organization" }); - } + // Verify identity membership in the organization + const identityOrgMembership = await membershipIdentityDAL.findOne({ + scope: AccessScope.Organization, + actorIdentityId: identityAccessToken.identityId, + scopeOrgId: identityOrgDetails.id + }); - orgId = identityOrgDetails.id; - orgName = identityOrgDetails.name; - parentOrgId = rootOrgId; + if (!identityOrgMembership) { + throw new BadRequestError({ message: "Identity does not belong to this organization" }); } + const orgId = identityOrgDetails.id; + const orgName = identityOrgDetails.name; + const parentOrgId = identityOrgDetails.parentOrgId || rootOrgId; + let { accessTokenNumUses } = identityAccessToken; const tokenStatusInCache = await accessTokenQueue.getIdentityTokenDetailsInCache(identityAccessToken.id); if (tokenStatusInCache) { diff --git a/backend/src/services/identity-access-token/identity-access-token-types.ts b/backend/src/services/identity-access-token/identity-access-token-types.ts index e7b73a8c1..b7505810d 100644 --- a/backend/src/services/identity-access-token/identity-access-token-types.ts +++ b/backend/src/services/identity-access-token/identity-access-token-types.ts @@ -7,6 +7,7 @@ export type TIdentityAccessTokenJwtPayload = { clientSecretId: string; identityAccessTokenId: string; authTokenType: string; + subOrganizationId?: string; identityAuth: { oidc?: { claims: Record; diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx index b94846f07..a3c8abaa6 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx @@ -304,7 +304,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => { to: "/organizations/$orgId/identities/$identityId", params: { identityId: id, - orgId + orgId: currentOrg.id } }) }