Feat: Scoped JWT to organization

This commit is contained in:
Daniel Hougaard
2024-03-17 18:48:10 +01:00
parent 560274bde8
commit edbf459d04
@@ -11,13 +11,14 @@ import {
} from "@app/db/schemas"; } from "@app/db/schemas";
import { conditionsMatcher } from "@app/lib/casl"; import { conditionsMatcher } from "@app/lib/casl";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type"; import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal"; import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
import { TProjectRoleDALFactory } from "@app/services/project-role/project-role-dal"; import { TProjectRoleDALFactory } from "@app/services/project-role/project-role-dal";
import { TServiceTokenDALFactory } from "@app/services/service-token/service-token-dal"; import { TServiceTokenDALFactory } from "@app/services/service-token/service-token-dal";
import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission"; import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission";
import { TPermissionDALFactory } from "./permission-dal"; import { TPermissionDALFactory } from "./permission-dal";
import { validateOrgSAML } from "./permission-fns";
import { TBuildProjectPermissionDTO } from "./permission-types"; import { TBuildProjectPermissionDTO } from "./permission-types";
import { import {
buildServiceTokenProjectPermission, buildServiceTokenProjectPermission,
@@ -99,15 +100,24 @@ export const permissionServiceFactory = ({
/* /*
* Get user permission in an organization * Get user permission in an organization
* */ * */
const getUserOrgPermission = async (userId: string, orgId: string, userOrgId?: string) => { const getUserOrgPermission = async (
userId: string,
orgId: string,
authMethod: ActorAuthMethod,
userOrgId?: string
) => {
const membership = await permissionDAL.getOrgPermission(userId, orgId); const membership = await permissionDAL.getOrgPermission(userId, orgId);
if (!membership) throw new UnauthorizedError({ name: "User not in org" }); if (!membership) throw new UnauthorizedError({ name: "User not in org" });
if (membership.role === OrgMembershipRole.Custom && !membership.permissions) { if (membership.role === OrgMembershipRole.Custom && !membership.permissions) {
throw new BadRequestError({ name: "Custom permission not found" }); throw new BadRequestError({ name: "Custom permission not found" });
} }
if (membership.orgAuthEnforced && membership.orgId !== userOrgId) {
throw new BadRequestError({ name: "Cannot access org-scoped resource" }); if (membership.orgId !== userOrgId) {
throw new UnauthorizedError({ name: "You are not a member of this organization" });
} }
validateOrgSAML(authMethod, membership.orgAuthEnforced);
return { permission: buildOrgPermission(membership.role, membership.permissions), membership }; return { permission: buildOrgPermission(membership.role, membership.permissions), membership };
}; };
@@ -120,10 +130,16 @@ export const permissionServiceFactory = ({
return { permission: buildOrgPermission(membership.role, membership.permissions), membership }; return { permission: buildOrgPermission(membership.role, membership.permissions), membership };
}; };
const getOrgPermission = async (type: ActorType, id: string, orgId: string, actorOrgId?: string) => { const getOrgPermission = async (
type: ActorType,
id: string,
orgId: string,
authMethod: ActorAuthMethod,
actorOrgId?: string
) => {
switch (type) { switch (type) {
case ActorType.USER: case ActorType.USER:
return getUserOrgPermission(id, orgId, actorOrgId); return getUserOrgPermission(id, orgId, authMethod, actorOrgId);
case ActorType.IDENTITY: case ActorType.IDENTITY:
return getIdentityOrgPermission(id, orgId); return getIdentityOrgPermission(id, orgId);
default: default:
@@ -153,28 +169,32 @@ export const permissionServiceFactory = ({
const getUserProjectPermission = async ( const getUserProjectPermission = async (
userId: string, userId: string,
projectId: string, projectId: string,
authMethod: ActorAuthMethod,
userOrgId?: string userOrgId?: string
): Promise<TProjectPermissionRT<ActorType.USER>> => { ): Promise<TProjectPermissionRT<ActorType.USER>> => {
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId); const membership = await permissionDAL.getProjectPermission(userId, projectId);
if (!userProjectPermission) throw new UnauthorizedError({ name: "User not in project" }); if (!membership) throw new UnauthorizedError({ name: "User not in project" });
if ( if (membership.roles.some(({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions)) {
userProjectPermission.roles.some(({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions)
) {
throw new BadRequestError({ name: "Custom permission not found" }); throw new BadRequestError({ name: "Custom permission not found" });
} }
if (userProjectPermission.orgAuthEnforced && userProjectPermission.orgId !== userOrgId) { if (membership.role === ProjectMembershipRole.Custom && !membership.permissions) {
throw new BadRequestError({ name: "Cannot access org-scoped resource" }); throw new BadRequestError({ name: "Custom permission not found" });
} }
if (membership.orgId !== userOrgId) {
throw new UnauthorizedError({ name: "You are not a member of this organization" });
}
validateOrgSAML(authMethod, membership.orgAuthEnforced);
return { return {
permission: buildProjectPermission(userProjectPermission.roles), permission: buildProjectPermission(membership.roles),
membership: userProjectPermission, membership,
hasRole: (role: string) => hasRole: (role: string) =>
userProjectPermission.roles.findIndex( membership.roles.findIndex(({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug) !== -1
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
) !== -1
}; };
}; };
@@ -238,11 +258,12 @@ export const permissionServiceFactory = ({
type: T, type: T,
id: string, id: string,
projectId: string, projectId: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId?: string actorOrgId?: string
): Promise<TProjectPermissionRT<T>> => { ): Promise<TProjectPermissionRT<T>> => {
switch (type) { switch (type) {
case ActorType.USER: case ActorType.USER:
return getUserProjectPermission(id, projectId, actorOrgId) as Promise<TProjectPermissionRT<T>>; return getUserProjectPermission(id, projectId, actorAuthMethod, actorOrgId) as Promise<TProjectPermissionRT<T>>;
case ActorType.SERVICE: case ActorType.SERVICE:
return getServiceTokenProjectPermission(id, projectId) as Promise<TProjectPermissionRT<T>>; return getServiceTokenProjectPermission(id, projectId) as Promise<TProjectPermissionRT<T>>;
case ActorType.IDENTITY: case ActorType.IDENTITY: