diff --git a/config/permissions.js b/config/permissions.js index c377a7c20e4efc4a88a046ef803febac7bd39d50..f51de9a0dee1dd7c1486a0d4f66a1cd555228351 100644 --- a/config/permissions.js +++ b/config/permissions.js @@ -1,7 +1,7 @@ // eslint-disable-next-line no-unused-vars const { rule, shield, and, or, not, allow, deny } = require('graphql-shield') -const _userIsEditor = async (user, manuscriptId) => { +const userIsEditorQuery = async (user, manuscriptId) => { if (!user) { return false } @@ -25,9 +25,9 @@ const _userIsEditor = async (user, manuscriptId) => { const userIsEditor = rule({ cache: 'contextual', -})(async (parent, args, ctx, info) => _userIsEditor(ctx.user)) +})(async (parent, args, ctx, info) => userIsEditorQuery(ctx.user)) -const _userIsMemberOfTeamWithRole = async (user, manuscriptId, role) => { +const userIsMemberOfTeamWithRoleQuery = async (user, manuscriptId, role) => { if (!user) { return false } @@ -36,6 +36,7 @@ const _userIsMemberOfTeamWithRole = async (user, manuscriptId, role) => { .$relatedQuery('teams') .where({ role }) .andWhere({ manuscriptId }) + const rows = await query.resultSize() return !!rows } @@ -44,27 +45,26 @@ const userIsAdmin = rule({ cache: 'contextual' })( async (parent, args, ctx, info) => ctx.user && ctx.user.admin, ) -const parent_manuscript_is_published = rule({ cache: 'contextual' })( +const parentManuscriptIsPublished = rule({ cache: 'contextual' })( async (parent, args, ctx, info) => { const manuscript = await ctx.models.Manuscript.query().findById( parent.manuscriptId, ) + return !!manuscript.published }, ) -const review_is_by_user = rule({ cache: 'contextual' })( +const reviewIsByUser = rule({ cache: 'contextual' })( async (parent, args, ctx, info) => { const rows = ctx.user && - ctx.user - .$relatedQuery('teams') - .where({ role: 'reviewer' }) - .resultSize() + ctx.user.$relatedQuery('teams').where({ role: 'reviewer' }).resultSize() return !!rows }, ) + const isAuthenticated = rule({ cache: 'contextual' })( async (parent, args, ctx, info) => !!ctx.user, ) @@ -78,35 +78,44 @@ const userIsAllowedToChat = rule({ cache: 'strict' })( if (ctx.user && ctx.user.admin) { return true } + const channel = await ctx.models.Channel.query().findById(args.channelId) + const manuscript = await ctx.models.Manuscript.query().findById( channel.manuscriptId, ) - const isAuthor = await _userIsMemberOfTeamWithRole( + const isAuthor = await userIsMemberOfTeamWithRoleQuery( ctx.user, manuscript.id, 'author', ) - const isReviewer = await _userIsMemberOfTeamWithRole( + + const isReviewer = await userIsMemberOfTeamWithRoleQuery( ctx.user, manuscript.id, 'reviewer', ) - const isEditor = await _userIsEditor(ctx.user, manuscript.id) + + const isEditor = await userIsEditorQuery(ctx.user, manuscript.id) if (channel.type === 'all') { return isAuthor || isReviewer || isEditor - } else if (channel.type === 'editorial') { + } + + if (channel.type === 'editorial') { return isReviewer || isEditor } + + return false }, ) -const user_is_review_author_and_review_is_not_completed = rule({ +const userIsReviewAuthorAndReviewIsNotCompleted = rule({ cache: 'strict', })(async (parent, args, ctx, info) => { let manuscriptId + if (args.id) { ;({ manuscriptId } = await ctx.models.Review.query().findById(args.id)) } else { @@ -114,12 +123,14 @@ const user_is_review_author_and_review_is_not_completed = rule({ } const manuscript = await ctx.models.Manuscript.query().findById(manuscriptId) + const team = await ctx.models.Team.query() .where({ manuscriptId: manuscript.id, role: 'reviewer', }) .first() + if (!team) return false const members = await team @@ -133,22 +144,24 @@ const user_is_review_author_and_review_is_not_completed = rule({ return false }) -const user_is_editor_of_the_manuscript_of_the_review = rule({ +const userIsEditorOfTheManuscriptOfTheReview = rule({ cache: 'strict', })(async (parent, args, ctx, info) => { let manuscriptId + if (args.id) { ;({ manuscriptId } = await ctx.models.Review.query().findById(args.id)) } else { ;({ manuscriptId } = args.input) } - return _userIsEditor(ctx.user, manuscriptId) + return userIsEditorQuery(ctx.user, manuscriptId) }) -const user_is_invited_reviewer = rule({ cache: 'strict' })( +const userIsInvitedReviewer = rule({ cache: 'strict' })( async (parent, args, ctx, info) => { const team = await ctx.models.Team.query().findById(args.teamId) + const member = await team .$relatedQuery('members') .where({ userId: ctx.user.id, status: 'invited' }) @@ -158,7 +171,7 @@ const user_is_invited_reviewer = rule({ cache: 'strict' })( }, ) -const user_is_author = rule({ cache: 'strict' })( +const userIsAuthor = rule({ cache: 'strict' })( async (parent, args, ctx, info) => { const team = await ctx.models.Team.query() .where({ @@ -166,6 +179,7 @@ const user_is_author = rule({ cache: 'strict' })( role: 'author', }) .first() + const author = team .$relatedQuery('members') .where({ userId: ctx.user.id }) @@ -175,10 +189,11 @@ const user_is_author = rule({ cache: 'strict' })( }, ) -const user_is_author_of_files_associated_manuscript = rule({ +const userIsAuthorOfFilesAssociatedManuscript = rule({ cache: 'no_cache', })(async (parent, args, ctx, info) => { let manuscriptId + if (args.meta && args.meta.manuscriptId) { // Meta is supplied for createFile // eslint-disable-next-line prefer-destructuring @@ -202,6 +217,7 @@ const user_is_author_of_files_associated_manuscript = rule({ if (!team) { return false } + const members = await team .$relatedQuery('members') .where('userId', ctx.user.id) @@ -212,8 +228,13 @@ const user_is_author_of_files_associated_manuscript = rule({ return false }) -const user_is_author_of_the_manuscript_of_the_file = rule({ cache: 'strict' })( + +const userIsAuthorOfTheManuscriptOfTheFile = rule({ cache: 'strict' })( async (parent, args, ctx, info) => { + if (!ctx.user) { + return false + } + const manuscript = await ctx.models.File.relatedQuery('manuscript') .for(parent.id) .first() @@ -228,6 +249,7 @@ const user_is_author_of_the_manuscript_of_the_file = rule({ cache: 'strict' })( if (!team) { return false } + const members = await team .$relatedQuery('members') .where('userId', ctx.user.id) @@ -241,11 +263,13 @@ const user_is_author_of_the_manuscript_of_the_file = rule({ cache: 'strict' })( ) // ¯\_(ツ)_/¯ -const user_is_the_reviewer_of_the_manuscript_of_the_file_and_review_not_complete = rule( - { - cache: 'strict', - }, -)(async (parent, args, ctx, info) => { +const userIsTheReviewerOfTheManuscriptOfTheFileAndReviewNotComplete = rule({ + cache: 'strict', +})(async (parent, args, ctx, info) => { + if (!ctx.user) { + return false + } + const manuscript = await ctx.models.File.relatedQuery('manuscript') .for(parent.id) .first() @@ -260,6 +284,7 @@ const user_is_the_reviewer_of_the_manuscript_of_the_file_and_review_not_complete if (!team) { return false } + const members = await team .$relatedQuery('members') .where('userId', ctx.user.id) @@ -286,21 +311,21 @@ const permissions = { Mutation: { upload: isAuthenticated, createManuscript: isAuthenticated, - updateManuscript: user_is_author, - submitManuscript: user_is_author, + updateManuscript: userIsAuthor, + submitManuscript: userIsAuthor, createMessage: userIsAllowedToChat, updateReview: or( - user_is_review_author_and_review_is_not_completed, - user_is_editor_of_the_manuscript_of_the_review, + userIsReviewAuthorAndReviewIsNotCompleted, + userIsEditorOfTheManuscriptOfTheReview, ), - reviewerResponse: user_is_invited_reviewer, + reviewerResponse: userIsInvitedReviewer, completeReview: or( - user_is_review_author_and_review_is_not_completed, - user_is_editor_of_the_manuscript_of_the_review, + userIsReviewAuthorAndReviewIsNotCompleted, + userIsEditorOfTheManuscriptOfTheReview, ), createNewVersion: allow, - createFile: user_is_author_of_files_associated_manuscript, - deleteFile: user_is_author_of_files_associated_manuscript, + createFile: userIsAuthorOfFilesAssociatedManuscript, + deleteFile: userIsAuthorOfFilesAssociatedManuscript, }, Subscription: { messageCreated: userIsAllowedToChat, @@ -314,16 +339,16 @@ const permissions = { Manuscript: allow, ManuscriptVersion: allow, File: or( - parent_manuscript_is_published, + parentManuscriptIsPublished, or( - user_is_author_of_the_manuscript_of_the_file, - user_is_the_reviewer_of_the_manuscript_of_the_file_and_review_not_complete, + userIsAuthorOfTheManuscriptOfTheFile, + userIsTheReviewerOfTheManuscriptOfTheFileAndReviewNotComplete, userIsEditor, userIsAdmin, ), ), UploadResult: allow, - Review: or(parent_manuscript_is_published, review_is_by_user), + Review: or(parentManuscriptIsPublished, reviewIsByUser), ReviewComment: allow, Channel: allow, Message: allow, @@ -337,10 +362,11 @@ const permissions = { const fallbackRule = or(userIsAdmin, userIsEditor) // We only ever need to go two levels down, so no need for recursion -const addOverrideRule = permissions => { +const addOverrideRule = perms => { const adaptedPermissions = {} - Object.keys(permissions).forEach(key1 => { - const value = permissions[key1] + Object.keys(perms).forEach(key1 => { + const value = perms[key1] + if (value.constructor.name !== 'Object') { adaptedPermissions[key1] = or(fallbackRule, value) } else { diff --git a/package.json b/package.json index 82ef0b407e17826ca83c91632cd54212a0d82c5e..430a2897c269d712c351b1cf44105df89c2907ff 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "got": "11.7.0", "graphql": "14.7.0", "graphql-middleware": "4.0.2", - "graphql-shield": "7.3.7", + "graphql-shield": "^7.5.0", "graphql-tools": "4.0.8", "history": "4.10.1", "jimp": "0.16.1", @@ -145,7 +145,7 @@ "@babel/plugin-syntax-import-meta": "7.10.4", "@babel/preset-env": "7.11.5", "@babel/preset-react": "7.10.4", - "@coko/lint": "^1.3.0", + "@coko/lint": "^1.4.0", "babel-core": "7.0.0-bridge.0", "babel-jest": "23.6.0", "babel-loader": "8.1.0", diff --git a/yarn.lock b/yarn.lock index b5b098ae00148b59e4c5d2b3f39ca7ea49d00579..f4b5ad19e883f6349b53e2ca710e365935f4b48b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1365,10 +1365,10 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" -"@coko/lint@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@coko/lint/-/lint-1.3.0.tgz#b51464ad04162a378a681a7ae22514e3ad18c2df" - integrity sha512-jBLcukH05btHV+EBZfwSou5FFAu6UrZKZPjP4wslLU7fGLFpOBl28mlLAKg8PpCcEISe0PK0TWZGtEJNQ1YDqA== +"@coko/lint@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@coko/lint/-/lint-1.4.0.tgz#476986325676607d89a23cca2566da31d1ebd5b3" + integrity sha512-ed9WswpDJoxB8VVja/CgFUGjD0NxoQDvD/BXJ5kM/X/AaFYeanY7nr1bI4nCcas/IHHDb9nnEPRulqfMCuMBLA== dependencies: "@commitlint/cli" "^8.3.5" "@commitlint/config-conventional" "^8.3.4" @@ -2763,10 +2763,10 @@ dependencies: "@types/node" "*" -"@types/yup@0.29.8": - version "0.29.8" - resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.8.tgz#83db15735987db9fe5a38772a0fb9500e3c5bf39" - integrity sha512-MBSp62AjB1KrSOI3gX9GekddXU5YYQAVA93+aSl78biBqoSzxg876aQY2KJK5Gnfbpqq7O2cadVX5kPAtBqIXw== +"@types/yup@0.29.11": + version "0.29.11" + resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.11.tgz#d654a112973f5e004bf8438122bd7e56a8e5cd7e" + integrity sha512-9cwk3c87qQKZrT251EDoibiYRILjCmxBvvcb4meofCmx1vdnNcR9gyildy5vOHASpOKMsn42CugxUvcwK5eu1g== "@types/zen-observable@^0.8.0": version "0.8.0" @@ -8240,11 +8240,6 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" -fn-name@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-3.0.0.tgz#0596707f635929634d791f452309ab41558e3c5c" - integrity sha512-eNMNr5exLoavuAMhIUVsOKF79SWd/zG104ef6sxBTSw+cZc6BXdQXDvYcGvp0VbxVVSp1XDUNoz7mg1xMtSznA== - follow-redirects@1.5.10: version "1.5.10" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" @@ -8868,14 +8863,14 @@ graphql-postgres-subscriptions@^1.0.4: pg "^7.4.1" pg-ipc "^1.0.4" -graphql-shield@7.3.7: - version "7.3.7" - resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-7.3.7.tgz#6bca6ed5e4c45669a0485a6c0a4e1c46b76c0c1c" - integrity sha512-/bf0l5+is8IOfdafjbzws8oIgZJx7jhvegC1Wa2lyTATLFq0kQLvHPMwkVWmqbnW+3m8tgBALlyZABAHSzVl0A== +graphql-shield@^7.5.0: + version "7.5.0" + resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-7.5.0.tgz#aa3af226946946dfadac33eccc6cbe7fec6e9000" + integrity sha512-T1A6OreOe/dHDk/1Qg3AHCrKLmTkDJ3fPFGYpSOmUbYXyDnjubK4J5ab5FjHdKHK5fWQRZNTvA0SrBObYsyfaw== dependencies: - "@types/yup" "0.29.8" + "@types/yup" "0.29.11" object-hash "^2.0.3" - yup "^0.29.3" + yup "^0.31.0" graphql-subscriptions@^0.5.8: version "0.5.8" @@ -14247,10 +14242,10 @@ prop-types@15.7.2, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, object-assign "^4.1.1" react-is "^16.8.1" -property-expr@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.2.tgz#fff2a43919135553a3bc2fdd94bdb841965b2330" - integrity sha512-bc/5ggaYZxNkFKj374aLbEDqVADdYaLcFo8XBkishUWbaAdjlphaBFns9TvRA2pUseVL/wMFmui9X3IdNDU37g== +property-expr@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.4.tgz#37b925478e58965031bb612ec5b3260f8241e910" + integrity sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg== prosemirror-commands@^1.0.1, prosemirror-commands@^1.1.3: version "1.1.4" @@ -16916,11 +16911,6 @@ symbol-tree@^3.2.2: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -synchronous-promise@^2.0.13: - version "2.0.14" - resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.14.tgz#86b3b3284ad8567b69dc0b2d51c6fd446ddc2d91" - integrity sha512-XassJA855Rbhsggr5Yr1sms+iDDzkmwfhqPJzl7Lq544+7fjo9NmCpetrvQc33S/HE1JnoI2zA3CLDxTQTpWHA== - table@^5.2.3: version "5.4.6" resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" @@ -18838,17 +18828,15 @@ yjs@13.3.0: dependencies: lib0 "^0.2.32" -yup@^0.29.3: - version "0.29.3" - resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.3.tgz#69a30fd3f1c19f5d9e31b1cf1c2b851ce8045fea" - integrity sha512-RNUGiZ/sQ37CkhzKFoedkeMfJM0vNQyaz+wRZJzxdKE7VfDeVKH8bb4rr7XhRLbHJz5hSjoDNwMEIaKhuMZ8gQ== +yup@^0.31.0: + version "0.31.1" + resolved "https://registry.yarnpkg.com/yup/-/yup-0.31.1.tgz#0954cb181161f397b804346037a04f8a4b31599e" + integrity sha512-Lf6648jDYOWR75IlWkVfwesPyW6oj+50NpxlKvsQlpPsB8eI+ndI7b4S1VrwbmeV9hIZDu1MzrlIL4W+gK1jPw== dependencies: "@babel/runtime" "^7.10.5" - fn-name "~3.0.0" - lodash "^4.17.15" + lodash "^4.17.20" lodash-es "^4.17.11" - property-expr "^2.0.2" - synchronous-promise "^2.0.13" + property-expr "^2.0.4" toposort "^2.0.2" zen-observable-ts@^0.8.21: