mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-17 18:19:50 +00:00
feat: first iteration changes for sub org routers
This commit is contained in:
@@ -3,17 +3,19 @@ import { Knex } from "knex";
|
||||
import { AccessScope, TAuthTokens, TAuthTokenSessions } from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
import { ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||
|
||||
import { AuthModeJwtTokenPayload, AuthModeRefreshJwtTokenPayload, AuthTokenType } from "../auth/auth-type";
|
||||
import { TMembershipUserDALFactory } from "../membership-user/membership-user-dal";
|
||||
import { TUserDALFactory } from "../user/user-dal";
|
||||
import { TTokenDALFactory } from "./auth-token-dal";
|
||||
import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenForUserDTO } from "./auth-token-types";
|
||||
import { TOrgDALFactory } from "../org/org-dal";
|
||||
|
||||
type TAuthTokenServiceFactoryDep = {
|
||||
tokenDAL: TTokenDALFactory;
|
||||
userDAL: Pick<TUserDALFactory, "findById" | "transaction">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findOne">;
|
||||
membershipUserDAL: Pick<TMembershipUserDALFactory, "findOne">;
|
||||
};
|
||||
|
||||
@@ -80,7 +82,7 @@ export const getTokenConfig = (tokenType: TokenType) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL }: TAuthTokenServiceFactoryDep) => {
|
||||
export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL, orgDAL }: TAuthTokenServiceFactoryDep) => {
|
||||
const createTokenForUser = async ({ type, userId, orgId, aliasId, payload }: TCreateTokenForUserDTO) => {
|
||||
const { token, ...tkCfg } = getTokenConfig(type);
|
||||
const appCfg = getConfig();
|
||||
@@ -194,7 +196,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL }: TA
|
||||
};
|
||||
|
||||
// to parse jwt identity in inject identity plugin
|
||||
const fnValidateJwtIdentity = async (token: AuthModeJwtTokenPayload) => {
|
||||
const fnValidateJwtIdentity = async (token: AuthModeJwtTokenPayload, subOrganizationSelector?: string) => {
|
||||
const session = await tokenDAL.findOneTokenSession({
|
||||
id: token.tokenVersionId,
|
||||
userId: token.userId
|
||||
@@ -207,22 +209,53 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL }: TA
|
||||
const user = await userDAL.findById(session.userId);
|
||||
if (!user || !user.isAccepted) throw new NotFoundError({ message: `User with ID '${session.userId}' not found` });
|
||||
|
||||
let orgId = "";
|
||||
let parentOrgId = "";
|
||||
if (token.organizationId) {
|
||||
const orgMembership = await membershipUserDAL.findOne({
|
||||
actorUserId: user.id,
|
||||
scopeOrgId: token.organizationId,
|
||||
scope: AccessScope.Organization
|
||||
});
|
||||
if (subOrganizationSelector) {
|
||||
const subOrganization = await orgDAL.findOne({
|
||||
parentOrgId: token.organizationId,
|
||||
slug: subOrganizationSelector
|
||||
});
|
||||
if (!subOrganization)
|
||||
throw new BadRequestError({ message: `Sub organization ${subOrganizationSelector} not found` });
|
||||
|
||||
if (!orgMembership) {
|
||||
throw new ForbiddenRequestError({ message: "User not member of organization" });
|
||||
}
|
||||
if (!orgMembership.isActive) {
|
||||
throw new ForbiddenRequestError({ message: "User organization membership is inactive" });
|
||||
const orgMembership = await membershipUserDAL.findOne({
|
||||
actorUserId: user.id,
|
||||
scopeOrgId: subOrganization.id,
|
||||
scope: AccessScope.Organization
|
||||
});
|
||||
|
||||
if (!orgMembership) {
|
||||
throw new ForbiddenRequestError({ message: "User not member of organization" });
|
||||
}
|
||||
|
||||
if (!orgMembership.isActive) {
|
||||
throw new ForbiddenRequestError({ message: "User organization membership is inactive" });
|
||||
}
|
||||
orgId = subOrganization.id;
|
||||
parentOrgId = token.organizationId;
|
||||
} else {
|
||||
const orgMembership = await membershipUserDAL.findOne({
|
||||
actorUserId: user.id,
|
||||
scopeOrgId: token.organizationId,
|
||||
scope: AccessScope.Organization
|
||||
});
|
||||
|
||||
if (!orgMembership) {
|
||||
throw new ForbiddenRequestError({ message: "User not member of organization" });
|
||||
}
|
||||
|
||||
if (!orgMembership.isActive) {
|
||||
throw new ForbiddenRequestError({ message: "User organization membership is inactive" });
|
||||
}
|
||||
|
||||
orgId = token.organizationId;
|
||||
parentOrgId = token.organizationId;
|
||||
}
|
||||
}
|
||||
|
||||
return { user, tokenVersionId: token.tokenVersionId, orgId: token.organizationId };
|
||||
return { user, tokenVersionId: token.tokenVersionId, orgId, parentOrgId };
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -19,6 +19,7 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
|
||||
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityAccessToken}.identityId`)
|
||||
.select(selectAllTableCols(TableName.IdentityAccessToken))
|
||||
.select(db.ref("name").withSchema(TableName.Identity))
|
||||
.select(db.ref("orgId").withSchema(TableName.Identity).as("identityScopeOrgId"))
|
||||
.first();
|
||||
|
||||
return doc;
|
||||
|
||||
@@ -10,6 +10,7 @@ import { TIdentityDALFactory } from "../identity/identity-dal";
|
||||
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
|
||||
import { TIdentityAccessTokenDALFactory } from "./identity-access-token-dal";
|
||||
import { TIdentityAccessTokenJwtPayload, TRenewAccessTokenDTO } from "./identity-access-token-types";
|
||||
import { TOrgDALFactory } from "../org/org-dal";
|
||||
|
||||
type TIdentityAccessTokenServiceFactoryDep = {
|
||||
identityAccessTokenDAL: TIdentityAccessTokenDALFactory;
|
||||
@@ -19,6 +20,7 @@ type TIdentityAccessTokenServiceFactoryDep = {
|
||||
"updateIdentityAccessTokenStatus" | "getIdentityTokenDetailsInCache"
|
||||
>;
|
||||
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findOne">;
|
||||
};
|
||||
|
||||
export type TIdentityAccessTokenServiceFactory = ReturnType<typeof identityAccessTokenServiceFactory>;
|
||||
@@ -27,7 +29,8 @@ export const identityAccessTokenServiceFactory = ({
|
||||
identityAccessTokenDAL,
|
||||
accessTokenQueue,
|
||||
identityDAL,
|
||||
membershipIdentityDAL
|
||||
membershipIdentityDAL,
|
||||
orgDAL
|
||||
}: TIdentityAccessTokenServiceFactoryDep) => {
|
||||
const validateAccessTokenExp = async (identityAccessToken: TIdentityAccessTokens) => {
|
||||
const {
|
||||
@@ -181,7 +184,11 @@ export const identityAccessTokenServiceFactory = ({
|
||||
return { revokedToken };
|
||||
};
|
||||
|
||||
const fnValidateIdentityAccessToken = async (token: TIdentityAccessTokenJwtPayload, ipAddress?: string) => {
|
||||
const fnValidateIdentityAccessToken = async (
|
||||
token: TIdentityAccessTokenJwtPayload,
|
||||
subOrganizationSelector?: string,
|
||||
ipAddress?: string
|
||||
) => {
|
||||
const identityAccessToken = await identityAccessTokenDAL.findOne({
|
||||
[`${TableName.IdentityAccessToken}.id` as "id"]: token.identityAccessTokenId,
|
||||
isAccessTokenRevoked: false
|
||||
@@ -202,13 +209,36 @@ export const identityAccessTokenServiceFactory = ({
|
||||
trustedIps: trustedIps as TIp[]
|
||||
});
|
||||
}
|
||||
const identityOrgMembership = await membershipIdentityDAL.findOne({
|
||||
scope: AccessScope.Organization,
|
||||
actorIdentityId: identityAccessToken.identityId
|
||||
});
|
||||
let orgId = "";
|
||||
const parentOrgId = identityAccessToken.identityScopeOrgId;
|
||||
|
||||
if (!identityOrgMembership) {
|
||||
throw new BadRequestError({ message: "Identity does not belong to any organization" });
|
||||
if (subOrganizationSelector) {
|
||||
const subOrganization = await orgDAL.findOne({ parentOrgId, slug: subOrganizationSelector });
|
||||
if (!subOrganizationSelector)
|
||||
throw new BadRequestError({ message: `Sub organization ${subOrganizationSelector} not found` });
|
||||
|
||||
const identityOrgMembership = await membershipIdentityDAL.findOne({
|
||||
scope: AccessScope.Organization,
|
||||
actorIdentityId: identityAccessToken.identityId,
|
||||
scopeOrgId: subOrganization.id
|
||||
});
|
||||
|
||||
if (!identityOrgMembership) {
|
||||
throw new BadRequestError({ message: "Identity does not belong to any organization" });
|
||||
}
|
||||
orgId = subOrganization.id;
|
||||
} else {
|
||||
const identityOrgMembership = await membershipIdentityDAL.findOne({
|
||||
scope: AccessScope.Organization,
|
||||
actorIdentityId: identityAccessToken.identityId,
|
||||
scopeOrgId: parentOrgId
|
||||
});
|
||||
|
||||
if (!identityOrgMembership) {
|
||||
throw new BadRequestError({ message: "Identity does not belong to any organization" });
|
||||
}
|
||||
|
||||
orgId = parentOrgId;
|
||||
}
|
||||
|
||||
let { accessTokenNumUses } = identityAccessToken;
|
||||
@@ -219,7 +249,7 @@ export const identityAccessTokenServiceFactory = ({
|
||||
await validateAccessTokenExp({ ...identityAccessToken, accessTokenNumUses });
|
||||
|
||||
await accessTokenQueue.updateIdentityAccessTokenStatus(identityAccessToken.id, Number(accessTokenNumUses) + 1);
|
||||
return { ...identityAccessToken, orgId: identityOrgMembership.scopeOrgId };
|
||||
return { ...identityAccessToken, orgId, parentOrgId };
|
||||
};
|
||||
|
||||
return { renewAccessToken, revokeAccessToken, fnValidateIdentityAccessToken };
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
import { generateKnexQueryFromScim } from "@app/lib/knex/scim";
|
||||
|
||||
import { OrgAuthMethod } from "./org-types";
|
||||
import { ActorType } from "../auth/auth-type";
|
||||
|
||||
export type TOrgDALFactory = ReturnType<typeof orgDALFactory>;
|
||||
|
||||
@@ -64,6 +65,7 @@ export const orgDALFactory = (db: TDbClient) => {
|
||||
const buildBaseQuery = (orgIdSubquery: Knex.QueryBuilder) => {
|
||||
return db
|
||||
.replicaNode()(TableName.Organization)
|
||||
.whereNull(`${TableName.Organization}.parentOrgId`)
|
||||
.whereIn(`${TableName.Organization}.id`, orgIdSubquery)
|
||||
.leftJoin(TableName.Project, `${TableName.Organization}.id`, `${TableName.Project}.orgId`)
|
||||
.leftJoin(TableName.Membership, `${TableName.Organization}.id`, `${TableName.Membership}.scopeOrgId`)
|
||||
@@ -154,11 +156,47 @@ export const orgDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const listSubOrganizations = async (dto: {
|
||||
actorId: string;
|
||||
actorType: ActorType;
|
||||
orgId: string;
|
||||
isAccessible?: boolean;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}) => {
|
||||
try {
|
||||
// TODO(sub-org:group): check this when implement group support
|
||||
const query = db
|
||||
.replicaNode()(TableName.Organization)
|
||||
.where(`${TableName.Organization}.parentOrgId`, dto.orgId)
|
||||
.select(selectAllTableCols(TableName.Organization));
|
||||
|
||||
if (dto.isAccessible) {
|
||||
void query.leftJoin(`${TableName.Membership}`, (qb) => {
|
||||
void qb.on(`${TableName.Membership}.scope`, AccessScope.Organization);
|
||||
if (dto.actorType === ActorType.IDENTITY) {
|
||||
void qb.andOn(`${TableName.Membership}.actorIdentityId`, dto.actorId);
|
||||
} else {
|
||||
void qb.andOn(`${TableName.Membership}.actorUserId`, dto.actorId);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (dto.limit) void query.limit(dto.limit);
|
||||
if (dto.offset) void query.offset(dto.offset);
|
||||
|
||||
const orgs = await query;
|
||||
return orgs;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "List sub organization" });
|
||||
}
|
||||
};
|
||||
|
||||
const findOrgById = async (orgId: string) => {
|
||||
try {
|
||||
const org = (await db
|
||||
.replicaNode()(TableName.Organization)
|
||||
.where({ [`${TableName.Organization}.id` as "id"]: orgId })
|
||||
.whereNull(`${TableName.Organization}.parentOrgId`)
|
||||
.leftJoin(TableName.SamlConfig, (qb) => {
|
||||
qb.on(`${TableName.SamlConfig}.orgId`, "=", `${TableName.Organization}.id`).andOn(
|
||||
`${TableName.SamlConfig}.isActive`,
|
||||
@@ -195,6 +233,7 @@ export const orgDALFactory = (db: TDbClient) => {
|
||||
try {
|
||||
const org = (await db
|
||||
.replicaNode()(TableName.Organization)
|
||||
.whereNull(`${TableName.Organization}.parentOrgId`)
|
||||
.where({ [`${TableName.Organization}.slug` as "slug"]: orgSlug })
|
||||
.leftJoin(TableName.SamlConfig, (qb) => {
|
||||
qb.on(`${TableName.SamlConfig}.orgId`, "=", `${TableName.Organization}.id`).andOn(
|
||||
@@ -240,6 +279,7 @@ export const orgDALFactory = (db: TDbClient) => {
|
||||
.whereNotNull(`${TableName.Membership}.actorUserId`)
|
||||
.join(TableName.MembershipRole, `${TableName.Membership}.id`, `${TableName.MembershipRole}.membershipId`)
|
||||
.join(TableName.Organization, `${TableName.Membership}.scopeOrgId`, `${TableName.Organization}.id`)
|
||||
.whereNull(`${TableName.Organization}.parentOrgId`)
|
||||
.leftJoin(TableName.SamlConfig, (qb) => {
|
||||
qb.on(`${TableName.SamlConfig}.orgId`, "=", `${TableName.Organization}.id`).andOn(
|
||||
`${TableName.SamlConfig}.isActive`,
|
||||
@@ -337,6 +377,7 @@ export const orgDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
// TODO(sub-org): updated this logic later
|
||||
const countAllOrgMembers = async (orgId: string) => {
|
||||
try {
|
||||
interface CountResult {
|
||||
@@ -610,6 +651,7 @@ export const orgDALFactory = (db: TDbClient) => {
|
||||
})
|
||||
.join(TableName.Users, `${TableName.Users}.id`, `${TableName.Membership}.actorUserId`)
|
||||
.join(TableName.Organization, `${TableName.Organization}.id`, `${TableName.Membership}.scopeOrgId`)
|
||||
.whereNull(`${TableName.Organization}.parentOrgId`)
|
||||
.leftJoin(TableName.UserAliases, function joinUserAlias() {
|
||||
this.on(`${TableName.UserAliases}.userId`, "=", `${TableName.Membership}.actorUserId`)
|
||||
.andOn(`${TableName.UserAliases}.orgId`, "=", `${TableName.Membership}.scopeOrgId`)
|
||||
@@ -648,6 +690,7 @@ export const orgDALFactory = (db: TDbClient) => {
|
||||
.replicaNode()(TableName.Membership)
|
||||
.where({ actorIdentityId: identityId })
|
||||
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
|
||||
.whereNull(`${TableName.Organization}.parentOrgId`)
|
||||
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
|
||||
.join(TableName.MembershipRole, `${TableName.Membership}.id`, `${TableName.MembershipRole}.membershipId`)
|
||||
.join(TableName.Organization, `${TableName.Membership}.scopeOrgId`, `${TableName.Organization}.id`)
|
||||
@@ -667,6 +710,7 @@ export const orgDALFactory = (db: TDbClient) => {
|
||||
findOrgByProjectId,
|
||||
findAllOrgMembers,
|
||||
countAllOrgMembers,
|
||||
listSubOrganizations,
|
||||
findOrgById,
|
||||
findOrgBySlug,
|
||||
findAllOrgsByUserId,
|
||||
|
||||
Reference in New Issue
Block a user