Feat: Scoped JWT to organization, add actorAuthMethod to services

This commit is contained in:
Daniel Hougaard
2024-03-17 18:49:29 +01:00
parent 750a43c978
commit fe638ce2c1
8 changed files with 366 additions and 68 deletions
+27 -8
View File
@@ -12,6 +12,7 @@ import {
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { BadRequestError } from "@app/lib/errors";
import { ActorAuthMethod } from "../auth/auth-type";
import { TOrgRoleDALFactory } from "./org-role-dal";
type TOrgRoleServiceFactoryDep = {
@@ -26,9 +27,10 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol
userId: string,
orgId: string,
data: Omit<TOrgRolesInsert, "orgId">,
actorAuthMethod: ActorAuthMethod,
actorOrgId?: string
) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId);
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Role);
const existingRole = await orgRoleDAL.findOne({ slug: data.slug, orgId });
if (existingRole) throw new BadRequestError({ name: "Create Role", message: "Duplicate role" });
@@ -45,9 +47,10 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol
orgId: string,
roleId: string,
data: Omit<TOrgRolesUpdate, "orgId">,
actorAuthMethod: ActorAuthMethod,
actorOrgId?: string
) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId);
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Role);
if (data?.slug) {
const existingRole = await orgRoleDAL.findOne({ slug: data.slug, orgId });
@@ -62,8 +65,14 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol
return updatedRole;
};
const deleteRole = async (userId: string, orgId: string, roleId: string, actorOrgId?: string) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId);
const deleteRole = async (
userId: string,
orgId: string,
roleId: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId?: string
) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Role);
const [deletedRole] = await orgRoleDAL.delete({ id: roleId, orgId });
if (!deletedRole) throw new BadRequestError({ message: "Role not found", name: "Update role" });
@@ -71,8 +80,8 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol
return deletedRole;
};
const listRoles = async (userId: string, orgId: string, actorOrgId?: string) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId);
const listRoles = async (userId: string, orgId: string, actorAuthMethod: ActorAuthMethod, actorOrgId?: string) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
const customRoles = await orgRoleDAL.find({ orgId });
const roles = [
@@ -115,8 +124,18 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol
return roles;
};
const getUserPermission = async (userId: string, orgId: string, actorOrgId?: string) => {
const { permission, membership } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId);
const getUserPermission = async (
userId: string,
orgId: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId?: string
) => {
const { permission, membership } = await permissionService.getUserOrgPermission(
userId,
orgId,
actorAuthMethod,
actorOrgId
);
return { permissions: packRules(permission.rules), membership };
};
+83 -24
View File
@@ -18,7 +18,7 @@ import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { isDisposableEmail } from "@app/lib/validator";
import { ActorType, AuthMethod, AuthTokenType } from "../auth/auth-type";
import { ActorAuthMethod, ActorType, AuthMethod, AuthTokenType } from "../auth/auth-type";
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
import { TokenType } from "../auth-token/auth-token-types";
import { TProjectDALFactory } from "../project/project-dal";
@@ -79,8 +79,13 @@ export const orgServiceFactory = ({
/*
* Get organization details by the organization id
* */
const findOrganizationById = async (userId: string, orgId: string, actorOrgId?: string) => {
await permissionService.getUserOrgPermission(userId, orgId, actorOrgId);
const findOrganizationById = async (
userId: string,
orgId: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId?: string
) => {
await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
const org = await orgDAL.findOrgById(orgId);
if (!org) throw new BadRequestError({ name: "Org not found", message: "Organization not found" });
return org;
@@ -95,16 +100,28 @@ export const orgServiceFactory = ({
/*
* Get all workspace members
* */
const findAllOrgMembers = async (userId: string, orgId: string, actorOrgId?: string) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId);
const findAllOrgMembers = async (
userId: string,
orgId: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId?: string
) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
const members = await orgDAL.findAllOrgMembers(orgId);
return members;
};
const findOrgMembersByUsername = async ({ actor, actorId, orgId, emails }: TFindOrgMembersByEmailDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
const findOrgMembersByUsername = async ({
actor,
actorId,
actorOrgId,
actorAuthMethod,
orgId,
emails
}: TFindOrgMembersByEmailDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
const members = await orgDAL.findOrgMembersByUsername(orgId, emails);
@@ -112,8 +129,8 @@ export const orgServiceFactory = ({
return members;
};
const findAllWorkspaces = async ({ actor, actorId, actorOrgId, orgId }: TFindAllWorkspacesDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const findAllWorkspaces = async ({ actor, actorId, actorOrgId, actorAuthMethod, orgId }: TFindAllWorkspacesDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Workspace);
const organizationWorkspaceIds = new Set((await projectDAL.find({ orgId })).map((workspace) => workspace.id));
@@ -193,10 +210,11 @@ export const orgServiceFactory = ({
actor,
actorId,
actorOrgId,
actorAuthMethod,
orgId,
data: { name, slug, authEnforced, scimEnabled }
}: TUpdateOrgDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const plan = await licenseService.getPlan(orgId);
@@ -309,8 +327,13 @@ export const orgServiceFactory = ({
/*
* Delete organization by id
* */
const deleteOrganizationById = async (userId: string, orgId: string, actorOrgId?: string) => {
const { membership } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId);
const deleteOrganizationById = async (
userId: string,
orgId: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId?: string
) => {
const { membership } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
if ((membership.role as OrgMembershipRole) !== OrgMembershipRole.Admin)
throw new UnauthorizedError({ name: "Delete org by id", message: "Not an admin" });
@@ -324,8 +347,15 @@ export const orgServiceFactory = ({
* Org membership management
* Not another service because it has close ties with how an org works doesn't make sense to seperate them
* */
const updateOrgMembership = async ({ role, orgId, userId, membershipId, actorOrgId }: TUpdateOrgMembershipDTO) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId);
const updateOrgMembership = async ({
role,
orgId,
userId,
membershipId,
actorAuthMethod,
actorOrgId
}: TUpdateOrgMembershipDTO) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Member);
const isCustomRole = !Object.values(OrgMembershipRole).includes(role as OrgMembershipRole);
@@ -355,8 +385,14 @@ export const orgServiceFactory = ({
/*
* Invite user to organization
*/
const inviteUserToOrganization = async ({ orgId, userId, inviteeEmail, actorOrgId }: TInviteUserToOrgDTO) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId);
const inviteUserToOrganization = async ({
orgId,
userId,
inviteeEmail,
actorAuthMethod,
actorOrgId
}: TInviteUserToOrgDTO) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
const org = await orgDAL.findOrgById(orgId);
@@ -515,8 +551,14 @@ export const orgServiceFactory = ({
return { token, user };
};
const deleteOrgMembership = async ({ orgId, userId, membershipId, actorOrgId }: TDeleteOrgMembershipDTO) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId);
const deleteOrgMembership = async ({
orgId,
userId,
membershipId,
actorAuthMethod,
actorOrgId
}: TDeleteOrgMembershipDTO) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member);
const deletedMembership = await orgDAL.transaction(async (tx) => {
@@ -568,15 +610,26 @@ export const orgServiceFactory = ({
/*
* CRUD operations of incident contacts
* */
const findIncidentContacts = async (userId: string, orgId: string, actorOrgId?: string) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId);
const findIncidentContacts = async (
userId: string,
orgId: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId?: string
) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount);
const incidentContacts = await incidentContactDAL.findByOrgId(orgId);
return incidentContacts;
};
const createIncidentContact = async (userId: string, orgId: string, email: string, actorOrgId?: string) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId);
const createIncidentContact = async (
userId: string,
orgId: string,
email: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId?: string
) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.IncidentAccount);
const doesIncidentContactExist = await incidentContactDAL.findOne(orgId, { email });
if (doesIncidentContactExist) {
@@ -590,8 +643,14 @@ export const orgServiceFactory = ({
return incidentContact;
};
const deleteIncidentContact = async (userId: string, orgId: string, id: string, actorOrgId?: string) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId);
const deleteIncidentContact = async (
userId: string,
orgId: string,
id: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId?: string
) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.IncidentAccount);
const incidentContact = await incidentContactDAL.deleteById(id, orgId);
+3 -1
View File
@@ -1,6 +1,6 @@
import { TOrgPermission } from "@app/lib/types";
import { ActorType } from "../auth/auth-type";
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
export type TUpdateOrgMembershipDTO = {
userId: string;
@@ -32,6 +32,8 @@ export type TVerifyUserToOrgDTO = {
export type TFindOrgMembersByEmailDTO = {
actor: ActorType;
actorAuthMethod: ActorAuthMethod;
actorOrgId: string | undefined;
actorId: string;
orgId: string;
emails: string[];