UNPKG

@speckle/shared

Version:

Shared code between various Speckle JS packages

229 lines 9.69 kB
import { err, ok } from 'true-myth/result'; import { hasAnyWorkspaceRole, hasMinimumWorkspaceRole } from '../checks/workspaceRole.js'; import { PersonalProjectsLimitedError, ProjectNotFoundError, WorkspaceLimitsReachedError, WorkspaceNoAccessError, WorkspaceNoEditorSeatError, WorkspaceNotEnoughPermissionsError, WorkspacePlanNoFeatureAccessError, WorkspaceReadOnlyError, WorkspacesNotEnabledError, WorkspaceSsoSessionNoAccessError } from '../domain/authErrors.js'; import { Roles } from '../../core/constants.js'; import { isWorkspacePlanStatusReadOnly } from '../../workspaces/helpers/plans.js'; import { hasEditorSeat } from '../checks/workspaceSeat.js'; import { ensureMinimumServerRoleFragment } from './server.js'; import { workspacePlanHasAccessToFeature } from '../../workspaces/helpers/features.js'; /** * Ensure user has a workspace role, and a valid SSO session (if SSO is configured) */ export const ensureWorkspaceRoleAndSessionFragment = (loaders) => async ({ userId, workspaceId, role }) => { const testedRole = role ?? Roles.Workspace.Guest; const testingForMinimumRole = testedRole === Roles.Workspace.Guest; // Get workspace, so we can resolve its slug for error scenarios const workspace = await loaders.getWorkspace({ workspaceId }); // hides the fact, that the workspace does not exist if (!workspace) return err(new WorkspaceNoAccessError()); const hasMinimumRole = await hasMinimumWorkspaceRole(loaders)({ userId, workspaceId, role: testedRole }); if (!hasMinimumRole) return err(testingForMinimumRole ? new WorkspaceNoAccessError() : new WorkspaceNotEnoughPermissionsError()); const hasMinimumMemberRole = await hasMinimumWorkspaceRole(loaders)({ userId, workspaceId, role: 'workspace:member' }); // only members and above need to use sso if (!hasMinimumMemberRole) return ok(); const workspaceSsoProvider = await loaders.getWorkspaceSsoProvider({ workspaceId }); if (!workspaceSsoProvider) return ok(); const workspaceSsoSession = await loaders.getWorkspaceSsoSession({ userId, workspaceId }); if (!workspaceSsoSession) return err(new WorkspaceSsoSessionNoAccessError({ payload: { workspaceSlug: workspace.slug } })); const isExpiredSession = new Date().getTime() > workspaceSsoSession.validUntil.getTime(); if (isExpiredSession) return err(new WorkspaceSsoSessionNoAccessError({ payload: { workspaceSlug: workspace.slug } })); return ok(); }; /** * Ensure the workspaces module is enabled */ export const ensureWorkspacesEnabledFragment = (loaders) => async () => { const env = await loaders.getEnv(); if (!env.FF_WORKSPACES_MODULE_ENABLED) return err(new WorkspacesNotEnabledError()); return ok(); }; /** * Ensure workspace is not read-only */ export const ensureWorkspaceNotReadOnlyFragment = (loaders) => async ({ workspaceId }) => { const workspacePlan = await loaders.getWorkspacePlan({ workspaceId }); if (!workspacePlan) return err(new WorkspaceNoAccessError()); if (isWorkspacePlanStatusReadOnly(workspacePlan.status)) return err(new WorkspaceReadOnlyError()); return ok(); }; /** * Ensure workspace can accept new project (not read-only, limits not reached). * If userId is specified, will also check for user role & seat */ export const ensureWorkspaceProjectCanBeCreatedFragment = (loaders) => async ({ workspaceId, userId }) => { // First check user even has access if (userId) { // Is Member+ const isNotGuest = await hasMinimumWorkspaceRole(loaders)({ userId, workspaceId, role: Roles.Workspace.Member }); if (!isNotGuest) { return err(new WorkspaceNotEnoughPermissionsError('Guests cannot create projects in the workspace')); } } const ensuredNotReadOnly = await ensureWorkspaceNotReadOnlyFragment(loaders)({ workspaceId }); if (ensuredNotReadOnly.isErr) return err(ensuredNotReadOnly.error); const workspacePlan = await loaders.getWorkspacePlan({ workspaceId }); if (!workspacePlan) return err(new WorkspaceNoAccessError()); // Now check editor seat if (userId) { const isEditor = await hasEditorSeat(loaders)({ userId, workspaceId }); if (!isEditor) return err(new WorkspaceNoEditorSeatError()); } const workspaceLimits = await loaders.getWorkspaceLimits({ workspaceId }); if (!workspaceLimits) return err(new WorkspaceNoAccessError()); // no limits imposed if (workspaceLimits.projectCount === null) return ok(); const currentProjectCount = await loaders.getWorkspaceProjectCount({ workspaceId }); // this will not happen in practice if (currentProjectCount === null) return err(new WorkspaceNoAccessError()); return currentProjectCount < workspaceLimits.projectCount ? ok() : err(new WorkspaceLimitsReachedError({ message: 'You have reached the maximum number of projects for your plan. Upgrade to increase it.', payload: { limit: 'projectCount' } })); }; /** * Ensure model can be created (workspace not read-only, limits not reached). * If userId is specified, will also check for appropriate user role & seat */ export const ensureModelCanBeCreatedFragment = (loaders) => async ({ projectId, userId, addedModelCount, workspaceId }) => { addedModelCount = addedModelCount ?? 1; const { FF_WORKSPACES_MODULE_ENABLED, FF_PERSONAL_PROJECTS_LIMITS_ENABLED } = await loaders.getEnv(); const project = await loaders.getProject({ projectId }); if (!project) return err(new ProjectNotFoundError()); // Project may not be attached to a workspace yet, then we use the specified workspaceId workspaceId = workspaceId || project.workspaceId || undefined; // If workspace if (workspaceId && FF_WORKSPACES_MODULE_ENABLED) { if (userId) { // Has workspace role const isInWorkspace = await hasAnyWorkspaceRole(loaders)({ userId, workspaceId }); if (!isInWorkspace) { return err(new WorkspaceNoAccessError()); } } const ensuredNotReadOnly = await ensureWorkspaceNotReadOnlyFragment(loaders)({ workspaceId }); if (ensuredNotReadOnly.isErr) return err(ensuredNotReadOnly.error); const workspacePlan = await loaders.getWorkspacePlan({ workspaceId }); if (!workspacePlan) return err(new WorkspaceNoAccessError()); const workspaceLimits = await loaders.getWorkspaceLimits({ workspaceId }); if (!workspaceLimits) return err(new WorkspaceNoAccessError()); if (workspaceLimits.modelCount === null) return ok(); const currentModelCount = await loaders.getWorkspaceModelCount({ workspaceId }); if (currentModelCount === null) return err(new WorkspaceNoAccessError()); return currentModelCount + addedModelCount <= workspaceLimits.modelCount ? ok() : err(new WorkspaceLimitsReachedError({ message: 'You have reached the maximum number of models for your plan. Upgrade to increase it.', payload: { limit: 'modelCount' } })); } else { // If not - check personal project limits if (FF_PERSONAL_PROJECTS_LIMITS_ENABLED) { return err(new PersonalProjectsLimitedError('No new models can be added to personal projects')); } return ok(); } }; export const ensureUserIsWorkspaceAdminFragment = (loaders) => async ({ userId, workspaceId }) => { const ensuredWorkspacesEnabled = await ensureWorkspacesEnabledFragment(loaders)({}); if (ensuredWorkspacesEnabled.isErr) return err(ensuredWorkspacesEnabled.error); const ensuredServerRole = await ensureMinimumServerRoleFragment(loaders)({ userId, role: Roles.Server.User }); if (ensuredServerRole.isErr) return err(ensuredServerRole.error); const ensuredWorkspaceAccess = await ensureWorkspaceRoleAndSessionFragment(loaders)({ userId: userId, workspaceId, role: Roles.Workspace.Admin }); if (ensuredWorkspaceAccess.isErr) return err(ensuredWorkspaceAccess.error); return ok(); }; /** * Check if workspace has access to a specific feature */ export const ensureCanUseWorkspacePlanFeatureFragment = (loaders) => async ({ workspaceId, feature }) => { const ensuredWorkspacesEnabled = await ensureWorkspacesEnabledFragment(loaders)({}); if (ensuredWorkspacesEnabled.isErr) return err(ensuredWorkspacesEnabled.error); const ensuredNotReadOnly = await ensureWorkspaceNotReadOnlyFragment(loaders)({ workspaceId }); if (ensuredNotReadOnly.isErr) return err(ensuredNotReadOnly.error); const workspacePlan = await loaders.getWorkspacePlan({ workspaceId }); if (!workspacePlan) return err(new WorkspaceNoAccessError()); const featureFlags = await loaders.getEnv(); const canUseFeature = workspacePlanHasAccessToFeature({ plan: workspacePlan.name, feature, featureFlags }); return canUseFeature ? ok() : err(new WorkspacePlanNoFeatureAccessError()); }; //# sourceMappingURL=workspaces.js.map