feat: complete project identities

This commit is contained in:
Scott Wilson
2025-11-13 12:52:38 +05:30
committed by =
parent 1fe635678e
commit b32544e76c
111 changed files with 4820 additions and 2113 deletions
@@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import { ForbiddenError, subject } from "@casl/ability";
import { requestContext } from "@fastify/request-context";
import { AccessScope, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
import { AccessScope, ActionProjectType, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
@@ -9,6 +9,7 @@ import {
validatePrivilegeChangeOperation
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto";
import {
@@ -44,7 +45,7 @@ type TIdentityGcpAuthServiceFactoryDep = {
identityGcpAuthDAL: Pick<TIdentityGcpAuthDALFactory, "findOne" | "transaction" | "create" | "updateById" | "delete">;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
orgDAL: Pick<TOrgDALFactory, "findById">;
};
@@ -139,7 +140,18 @@ export const identityGcpAuthServiceFactory = ({
const identityAccessToken = await identityGcpAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.update(
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
identity.projectId
? {
scope: AccessScope.Project,
scopeOrgId: identity.orgId,
scopeProjectId: identity.projectId,
actorIdentityId: identity.id
}
: {
scope: AccessScope.Organization,
scopeOrgId: identity.orgId,
actorIdentityId: identity.id
},
{
lastLoginAuthMethod: IdentityAuthMethod.GCP_AUTH,
lastLoginTime: new Date()
@@ -246,16 +258,34 @@ export const identityGcpAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
}
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
if (identityMembershipOrg.identity.projectId) {
const { permission } = await permissionService.getProjectPermission({
actionProjectType: ActionProjectType.Any,
actor,
actorId,
projectId: identityMembershipOrg.identity.projectId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Create,
subject(ProjectPermissionSub.Identity, { identityId })
);
} else {
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionIdentityActions.Create,
OrgPermissionSubjects.Identity
);
}
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
if (
@@ -336,16 +366,31 @@ export const identityGcpAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
}
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
if (identityMembershipOrg.identity.projectId) {
const { permission } = await permissionService.getProjectPermission({
actionProjectType: ActionProjectType.Any,
actor,
actorId,
projectId: identityMembershipOrg.identity.projectId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId })
);
} else {
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
}
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
if (
@@ -404,16 +449,31 @@ export const identityGcpAuthServiceFactory = ({
const identityGcpAuth = await identityGcpAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
if (identityMembershipOrg.identity.projectId) {
const { permission } = await permissionService.getProjectPermission({
actionProjectType: ActionProjectType.Any,
actor,
actorId,
projectId: identityMembershipOrg.identity.projectId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Read,
subject(ProjectPermissionSub.Identity, { identityId })
);
} else {
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
}
return { ...identityGcpAuth, orgId: identityMembershipOrg.scopeOrgId };
};
@@ -441,43 +501,58 @@ export const identityGcpAuthServiceFactory = ({
message: "The identity does not have gcp auth"
});
}
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor: ActorType.IDENTITY,
actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to revoke gcp auth of identity with more privileged role",
shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
if (identityMembershipOrg.identity.projectId) {
const { permission } = await permissionService.getProjectPermission({
actionProjectType: ActionProjectType.Any,
actor,
actorId,
projectId: identityMembershipOrg.identity.projectId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.RevokeAuth,
subject(ProjectPermissionSub.Identity, { identityId })
);
} else {
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor: ActorType.IDENTITY,
actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to revoke gcp auth of identity with more privileged role",
shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
}
const revokedIdentityGcpAuth = await identityGcpAuthDAL.transaction(async (tx) => {
const deletedGcpAuth = await identityGcpAuthDAL.delete({ identityId }, tx);
await identityAccessTokenDAL.delete({ identityId, authMethod: IdentityAuthMethod.GCP_AUTH }, tx);