Files
speckle-server/packages/shared/src/authz/policies/canQueryProject.ts
T
Kristaps Fabians Geikins a6a4ceee86 feat: true-myth result structures & other auth policy improvements (#4262)
* fixing up typing

* better dynamic loader mechanism

* buildReqLoaders cleanup

* added caching to loaders

* ensuring all loaders are async

* fe2 plugins error handling fix

* feat(shared): true-myth result structures & other auth policy improvements

* moving workspaceCore loaders to correct place
2025-03-25 17:49:02 +01:00

146 lines
4.5 KiB
TypeScript

import {
requireAnyWorkspaceRole,
requireMinimumWorkspaceRole
} from '../checks/workspaceRole.js'
import {
requireExactProjectVisibilityFactory,
requireMinimumProjectRoleFactory
} from '../checks/projects.js'
import { AuthPolicyFactory, ProjectContext, UserContext } from '../domain/policies.js'
import { requireExactServerRole } from '../checks/serverRole.js'
import { requireValidWorkspaceSsoSession } from '../checks/workspaceSso.js'
import { Roles } from '../../core/constants.js'
import {
ProjectNoAccessError,
ProjectNotFoundError,
WorkspaceNoAccessError,
WorkspaceSsoSessionInvalidError
} from '../domain/authErrors.js'
import { err, isOk, ok } from 'true-myth/result'
import { AuthCheckContextLoaderKeys } from '../domain/loaders.js'
import { LogicError } from '../domain/errors.js'
export const canQueryProjectPolicyFactory: AuthPolicyFactory<
| typeof AuthCheckContextLoaderKeys.getEnv
| typeof AuthCheckContextLoaderKeys.getProject
| typeof AuthCheckContextLoaderKeys.getProjectRole
| typeof AuthCheckContextLoaderKeys.getServerRole
| typeof AuthCheckContextLoaderKeys.getWorkspaceRole
| typeof AuthCheckContextLoaderKeys.getWorkspaceSsoProvider
| typeof AuthCheckContextLoaderKeys.getWorkspaceSsoSession,
UserContext & ProjectContext,
| typeof ProjectNotFoundError
| typeof ProjectNoAccessError
| typeof WorkspaceNoAccessError
| typeof WorkspaceSsoSessionInvalidError
> =
(loaders) =>
async ({ userId, projectId }) => {
const env = await loaders.getEnv()
if (!isOk(env)) {
throw new LogicError('Failed to load environment variables')
}
const { FF_ADMIN_OVERRIDE_ENABLED, FF_WORKSPACES_MODULE_ENABLED } = env.value
const project = await loaders.getProject({ projectId })
if (!isOk(project)) {
return err(project.error)
}
// All users may read public projects
const isPublicResult = await requireExactProjectVisibilityFactory({ loaders })({
projectId,
projectVisibility: 'public'
})
if (isPublicResult) {
return ok(true)
}
// All users may read link-shareable projects
const isLinkShareableResult = await requireExactProjectVisibilityFactory({
loaders
})({
projectId,
projectVisibility: 'linkShareable'
})
if (isLinkShareableResult) {
return ok(true)
}
// From this point on, you cannot pass as an unknown user
if (!userId) {
return err(ProjectNoAccessError)
}
// When G O D M O D E is enabled
if (FF_ADMIN_OVERRIDE_ENABLED) {
// Server admins may read all project data
const isServerAdminResult = await requireExactServerRole({ loaders })({
userId,
role: Roles.Server.Admin
})
if (isServerAdminResult) {
return ok(true)
}
}
const { workspaceId } = project.value
// When a project belongs to a workspace
if (FF_WORKSPACES_MODULE_ENABLED && !!workspaceId) {
// User must have a workspace role to read project data
const hasWorkspaceRoleResult = await requireAnyWorkspaceRole({ loaders })({
userId,
workspaceId
})
if (!hasWorkspaceRoleResult) {
// Should we hide the fact, the project is in a workspace?
return err(WorkspaceNoAccessError)
}
const hasMinimumMemberRole = await requireMinimumWorkspaceRole({
loaders
})({
userId,
workspaceId,
role: 'workspace:member'
})
if (hasMinimumMemberRole) {
const workspaceSsoProvider = await loaders.getWorkspaceSsoProvider({
workspaceId
})
if (workspaceSsoProvider.isOk) {
// Member and admin user must have a valid SSO session to read project data
const hasValidSsoSessionResult = await requireValidWorkspaceSsoSession({
loaders
})({
userId,
workspaceId
})
if (!hasValidSsoSessionResult) {
return err(WorkspaceSsoSessionInvalidError)
}
}
// Workspace members get to go through without an explicit project role
return ok(true)
} else {
// just fall through to the generic project role check for workspace:guest-s
}
}
// User must have at least stream reviewer role to read project data
const hasMinimumProjectRoleResult = await requireMinimumProjectRoleFactory({
loaders
})({
userId,
projectId,
role: 'stream:reviewer'
})
if (hasMinimumProjectRoleResult) {
return ok(true)
}
return err(ProjectNoAccessError)
}