feat: adds scope org id column and fixes inject identity middleware

This commit is contained in:
Piyush Gupta
2025-11-28 01:30:40 +05:30
parent 5464729c04
commit b94b1d480f
7 changed files with 43 additions and 58 deletions

View File

@@ -0,0 +1,16 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
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<void> {
await knex.schema.alterTable(TableName.IdentityAccessToken, (t) => {
t.dropColumn("scopeOrgId");
});
}

View File

@@ -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<typeof IdentityAccessTokensSchema>;

View File

@@ -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,

View File

@@ -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;

View File

@@ -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) {

View File

@@ -7,6 +7,7 @@ export type TIdentityAccessTokenJwtPayload = {
clientSecretId: string;
identityAccessTokenId: string;
authTokenType: string;
subOrganizationId?: string;
identityAuth: {
oidc?: {
claims: Record<string, string>;

View File

@@ -304,7 +304,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
to: "/organizations/$orgId/identities/$identityId",
params: {
identityId: id,
orgId
orgId: currentOrg.id
}
})
}