mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 03:28:43 +00:00
feat: adds scope org id column and fixes inject identity middleware
This commit is contained in:
+16
@@ -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");
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -22,7 +22,8 @@ export const IdentityAccessTokensSchema = z.object({
|
|||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
name: z.string().nullable().optional(),
|
name: z.string().nullable().optional(),
|
||||||
authMethod: z.string(),
|
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>;
|
export type TIdentityAccessTokens = z.infer<typeof IdentityAccessTokensSchema>;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { TScimTokenJwtPayload } from "@app/ee/services/scim/scim-types";
|
|||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { crypto } from "@app/lib/crypto";
|
import { crypto } from "@app/lib/crypto";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
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 { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types";
|
import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types";
|
||||||
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||||
@@ -152,15 +151,10 @@ export const injectIdentity = fp(
|
|||||||
|
|
||||||
if (!authMode) return;
|
if (!authMode) return;
|
||||||
|
|
||||||
const subOrganizationSelector = req.headers?.["x-infisical-org"] as string | undefined;
|
|
||||||
if (subOrganizationSelector) {
|
|
||||||
await slugSchema().parseAsync(subOrganizationSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (authMode) {
|
switch (authMode) {
|
||||||
case AuthMode.JWT: {
|
case AuthMode.JWT: {
|
||||||
const { user, tokenVersionId, orgId, orgName, rootOrgId, parentOrgId } =
|
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("orgId", orgId);
|
||||||
requestContext.set("orgName", orgName);
|
requestContext.set("orgName", orgName);
|
||||||
requestContext.set("userAuthInfo", { userId: user.id, email: user.email || "" });
|
requestContext.set("userAuthInfo", { userId: user.id, email: user.email || "" });
|
||||||
@@ -180,11 +174,7 @@ export const injectIdentity = fp(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AuthMode.IDENTITY_ACCESS_TOKEN: {
|
case AuthMode.IDENTITY_ACCESS_TOKEN: {
|
||||||
const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken(
|
const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken(token, req.realIp);
|
||||||
token,
|
|
||||||
req.realIp,
|
|
||||||
subOrganizationSelector
|
|
||||||
);
|
|
||||||
const serverCfg = await getServerCfg();
|
const serverCfg = await getServerCfg();
|
||||||
requestContext.set("orgId", identity.orgId);
|
requestContext.set("orgId", identity.orgId);
|
||||||
requestContext.set("orgName", identity.orgName);
|
requestContext.set("orgName", identity.orgName);
|
||||||
@@ -223,9 +213,6 @@ export const injectIdentity = fp(
|
|||||||
const serviceToken = await server.services.serviceToken.fnValidateServiceToken(token);
|
const serviceToken = await server.services.serviceToken.fnValidateServiceToken(token);
|
||||||
requestContext.set("orgId", serviceToken.orgId);
|
requestContext.set("orgId", serviceToken.orgId);
|
||||||
|
|
||||||
if (subOrganizationSelector)
|
|
||||||
throw new BadRequestError({ message: `Service token doesn't support sub organization selector` });
|
|
||||||
|
|
||||||
req.auth = {
|
req.auth = {
|
||||||
orgId: serviceToken.orgId,
|
orgId: serviceToken.orgId,
|
||||||
rootOrgId: serviceToken.rootOrgId,
|
rootOrgId: serviceToken.rootOrgId,
|
||||||
@@ -248,9 +235,6 @@ export const injectIdentity = fp(
|
|||||||
const { orgId, scimTokenId } = await server.services.scim.fnValidateScimToken(token);
|
const { orgId, scimTokenId } = await server.services.scim.fnValidateScimToken(token);
|
||||||
requestContext.set("orgId", orgId);
|
requestContext.set("orgId", orgId);
|
||||||
|
|
||||||
if (subOrganizationSelector)
|
|
||||||
throw new BadRequestError({ message: `SCIM token doesn't support sub organization selector` });
|
|
||||||
|
|
||||||
req.auth = {
|
req.auth = {
|
||||||
authMode: AuthMode.SCIM_TOKEN,
|
authMode: AuthMode.SCIM_TOKEN,
|
||||||
actor,
|
actor,
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
|
|||||||
.where(filter)
|
.where(filter)
|
||||||
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityAccessToken}.identityId`)
|
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityAccessToken}.identityId`)
|
||||||
.select(selectAllTableCols(TableName.IdentityAccessToken))
|
.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();
|
.first();
|
||||||
|
|
||||||
return doc;
|
return doc;
|
||||||
|
|||||||
@@ -184,11 +184,7 @@ export const identityAccessTokenServiceFactory = ({
|
|||||||
return { revokedToken };
|
return { revokedToken };
|
||||||
};
|
};
|
||||||
|
|
||||||
const fnValidateIdentityAccessToken = async (
|
const fnValidateIdentityAccessToken = async (token: TIdentityAccessTokenJwtPayload, ipAddress?: string) => {
|
||||||
token: TIdentityAccessTokenJwtPayload,
|
|
||||||
ipAddress?: string,
|
|
||||||
subOrganizationSelector?: string
|
|
||||||
) => {
|
|
||||||
const identityAccessToken = await identityAccessTokenDAL.findOne({
|
const identityAccessToken = await identityAccessTokenDAL.findOne({
|
||||||
[`${TableName.IdentityAccessToken}.id` as "id"]: token.identityAccessTokenId,
|
[`${TableName.IdentityAccessToken}.id` as "id"]: token.identityAccessTokenId,
|
||||||
isAccessTokenRevoked: false
|
isAccessTokenRevoked: false
|
||||||
@@ -209,46 +205,32 @@ export const identityAccessTokenServiceFactory = ({
|
|||||||
trustedIps: trustedIps as TIp[]
|
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 scopeOrgId = identityAccessToken.scopeOrgId || identityAccessToken.identityOrgId;
|
||||||
const subOrganization = await orgDAL.findOne({ rootOrgId, slug: subOrganizationSelector });
|
|
||||||
if (!subOrganization)
|
|
||||||
throw new BadRequestError({ message: `Sub organization ${subOrganizationSelector} not found` });
|
|
||||||
|
|
||||||
const identityOrgMembership = await membershipIdentityDAL.findOne({
|
const identityOrgDetails = await orgDAL.findOne({ id: scopeOrgId });
|
||||||
scope: AccessScope.Organization,
|
|
||||||
actorIdentityId: identityAccessToken.identityId,
|
|
||||||
scopeOrgId: subOrganization.id
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!identityOrgMembership) {
|
const isSubOrg = !!(identityOrgDetails.rootOrgId || identityOrgDetails.parentOrgId);
|
||||||
throw new BadRequestError({ message: "Identity does not belong to this organization" });
|
|
||||||
}
|
|
||||||
orgId = subOrganization.id;
|
|
||||||
orgName = subOrganization.name;
|
|
||||||
|
|
||||||
parentOrgId = subOrganization.parentOrgId as string;
|
const rootOrgId = isSubOrg
|
||||||
} else {
|
? identityOrgDetails.rootOrgId || identityOrgDetails.parentOrgId || identityOrgDetails.id
|
||||||
const identityOrgMembership = await membershipIdentityDAL.findOne({
|
: identityOrgDetails.id;
|
||||||
scope: AccessScope.Organization,
|
|
||||||
actorIdentityId: identityAccessToken.identityId,
|
|
||||||
scopeOrgId: identityOrgDetails.id
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!identityOrgMembership) {
|
// Verify identity membership in the organization
|
||||||
throw new BadRequestError({ message: "Identity does not belong to this organization" });
|
const identityOrgMembership = await membershipIdentityDAL.findOne({
|
||||||
}
|
scope: AccessScope.Organization,
|
||||||
|
actorIdentityId: identityAccessToken.identityId,
|
||||||
|
scopeOrgId: identityOrgDetails.id
|
||||||
|
});
|
||||||
|
|
||||||
orgId = identityOrgDetails.id;
|
if (!identityOrgMembership) {
|
||||||
orgName = identityOrgDetails.name;
|
throw new BadRequestError({ message: "Identity does not belong to this organization" });
|
||||||
parentOrgId = rootOrgId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const orgId = identityOrgDetails.id;
|
||||||
|
const orgName = identityOrgDetails.name;
|
||||||
|
const parentOrgId = identityOrgDetails.parentOrgId || rootOrgId;
|
||||||
|
|
||||||
let { accessTokenNumUses } = identityAccessToken;
|
let { accessTokenNumUses } = identityAccessToken;
|
||||||
const tokenStatusInCache = await accessTokenQueue.getIdentityTokenDetailsInCache(identityAccessToken.id);
|
const tokenStatusInCache = await accessTokenQueue.getIdentityTokenDetailsInCache(identityAccessToken.id);
|
||||||
if (tokenStatusInCache) {
|
if (tokenStatusInCache) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export type TIdentityAccessTokenJwtPayload = {
|
|||||||
clientSecretId: string;
|
clientSecretId: string;
|
||||||
identityAccessTokenId: string;
|
identityAccessTokenId: string;
|
||||||
authTokenType: string;
|
authTokenType: string;
|
||||||
|
subOrganizationId?: string;
|
||||||
identityAuth: {
|
identityAuth: {
|
||||||
oidc?: {
|
oidc?: {
|
||||||
claims: Record<string, string>;
|
claims: Record<string, string>;
|
||||||
|
|||||||
+1
-1
@@ -304,7 +304,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
to: "/organizations/$orgId/identities/$identityId",
|
to: "/organizations/$orgId/identities/$identityId",
|
||||||
params: {
|
params: {
|
||||||
identityId: id,
|
identityId: id,
|
||||||
orgId
|
orgId: currentOrg.id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user