diff --git a/src/index.ts b/src/index.ts index 9f59d07d..87c926e3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,6 +22,7 @@ export * from './lib/utils' export * from './lib/section-categories' export { CreditVocabTerm } from './lib/credit-roles' export * from './lib/citeproc' +export * from './lib/access-control' export * from './schema' export { migrateFor } from './schema/migration/migrate' export * from './transformer' diff --git a/src/lib/access-control.ts b/src/lib/access-control.ts new file mode 100644 index 00000000..3ba36f97 --- /dev/null +++ b/src/lib/access-control.ts @@ -0,0 +1,98 @@ +/*! + * © 2026 Atypon Systems LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NodeType } from 'prosemirror-model' + +import { CommentNode, ManuscriptActions, ManuscriptNode } from '../schema' + +/** + * Attribute-Based Access Control (ABAC) policies for manuscript nodes. + * Used by manuscript-api to evaluate incoming ProseMirror steps against these policies. + */ + +export type AccessContext = { + userId: string + actions: Record +} + +/** Nodes with restricted access */ +type ProtectedResources = { + comment: CommentNode +} + +/** A rule that evaluates whether a subject can perform an operation on a specific node */ +type NodeRule = ( + node: T, + context: AccessContext +) => boolean + +/** Policy for a single node type */ +type NodePolicy = { + /** can subject add this node */ + insert?: NodeRule + /** can subject delete this node */ + delete?: NodeRule + /** can subject modify attributes: + * - As an object per-attribute rules + * - As a function one rule applied to all attribute changes*/ + attrs?: Partial>> | NodeRule +} + +type NodesPolicy = { + [K in keyof ProtectedResources]?: NodePolicy +} + +/** + * Access policy to manuscript content as node-based. + * @example add access policy to citation node based on ManuscriptActions which is resolved at manuscript-api by user role + * citation: { + * insert: (_, context) => context.actions.editCitationsAndRefs, + * delete: (_, context) => context.actions.editCitationsAndRefs, + * attrs: (_, context) => context.actions.editCitationsAndRefs, + * } + */ +const nodesPolicy: NodesPolicy = { + comment: { + insert: (_, context) => context.actions.createComment, + delete: (node, context) => { + const isOwn = node.attrs.userID === context.userId + return isOwn + ? context.actions.handleOwnComments + : context.actions.handleOthersComments + }, + attrs: { + contents: (node, context) => { + const isOwn = node.attrs.userID === context.userId + return isOwn + ? context.actions.handleOwnComments + : context.actions.handleOthersComments + }, + resolved: (node, context) => { + const isOwn = node.attrs.userID === context.userId + return isOwn + ? context.actions.resolveOwnComment + : context.actions.resolveOthersComment + }, + }, + }, +} + +export function getNodeAccessPolicy(nodeType: NodeType) { + if (nodeType.name in nodesPolicy) { + return nodesPolicy[ + nodeType.name as keyof NodesPolicy + ] as NodePolicy + } +} diff --git a/src/schema/types.ts b/src/schema/types.ts index ce075c16..cf2959fc 100644 --- a/src/schema/types.ts +++ b/src/schema/types.ts @@ -238,3 +238,7 @@ export function isNodeOfType( ): node is T { return node.type === type } + +export type ExposedSlice = T & { + insertAt: (pos: number, fragment: F) => T +}