Przeglądaj źródła

feat: add permission check to resolvers (wip)

NGPixel 9 miesięcy temu
rodzic
commit
1c65d23408

+ 0 - 2
server/controllers/auth.mjs

@@ -1,5 +1,3 @@
-/* global WIKI */
-
 import express from 'express'
 import express from 'express'
 import ExpressBrute from 'express-brute'
 import ExpressBrute from 'express-brute'
 import BruteKnex from '../helpers/brute-knex.mjs'
 import BruteKnex from '../helpers/brute-knex.mjs'

+ 1 - 0
server/db/migrations/3.0.0.mjs

@@ -475,6 +475,7 @@ export async function up (knex) {
           private: certs.privateKey
           private: certs.privateKey
         },
         },
         secret,
         secret,
+        rootAdminGroupId: groupAdminId,
         rootAdminUserId: userAdminId,
         rootAdminUserId: userAdminId,
         guestUserId: userGuestId
         guestUserId: userGuestId
       }
       }

+ 8 - 0
server/graph/resolvers/analytics.mjs

@@ -4,6 +4,10 @@ import { generateError, generateSuccess } from '../../helpers/graph.mjs'
 export default {
 export default {
   Query: {
   Query: {
     async analyticsProviders(obj, args, context, info) {
     async analyticsProviders(obj, args, context, info) {
+      if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
+        throw new Error('ERR_FORBIDDEN')
+      }
+
       let providers = await WIKI.db.analytics.getProviders(args.isEnabled)
       let providers = await WIKI.db.analytics.getProviders(args.isEnabled)
       providers = providers.map(stg => {
       providers = providers.map(stg => {
         const providerInfo = find(WIKI.data.analytics, ['key', stg.key]) || {}
         const providerInfo = find(WIKI.data.analytics, ['key', stg.key]) || {}
@@ -28,6 +32,10 @@ export default {
   Mutation: {
   Mutation: {
     async updateAnalyticsProviders(obj, args, context) {
     async updateAnalyticsProviders(obj, args, context) {
       try {
       try {
+        if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
+          throw new Error('ERR_FORBIDDEN')
+        }
+
         for (let str of args.providers) {
         for (let str of args.providers) {
           await WIKI.db.analytics.query().patch({
           await WIKI.db.analytics.query().patch({
             isEnabled: str.isEnabled,
             isEnabled: str.isEnabled,

+ 6 - 0
server/graph/resolvers/asset.mjs

@@ -10,6 +10,7 @@ import { pipeline } from 'node:stream/promises'
 export default {
 export default {
   Query: {
   Query: {
     async assetById(obj, args, context) {
     async assetById(obj, args, context) {
+      // FIXME: Perm
       const asset = await WIKI.db.assets.query().findById(args.id)
       const asset = await WIKI.db.assets.query().findById(args.id)
       if (asset) {
       if (asset) {
         return asset
         return asset
@@ -145,6 +146,7 @@ export default {
      */
      */
     async uploadAssets(obj, args, context) {
     async uploadAssets(obj, args, context) {
       try {
       try {
+        // FIXME: Perm
         // -> Get Folder
         // -> Get Folder
         let folder = {}
         let folder = {}
         if (args.folderId || args.folderPath) {
         if (args.folderId || args.folderPath) {
@@ -380,6 +382,10 @@ export default {
      */
      */
     async flushTempUploads(obj, args, context) {
     async flushTempUploads(obj, args, context) {
       try {
       try {
+        if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
+          throw new Error('ERR_FORBIDDEN')
+        }
+
         await WIKI.db.assets.flushTempUploads()
         await WIKI.db.assets.flushTempUploads()
         return {
         return {
           operation: generateSuccess('Temporary Uploads have been flushed successfully.')
           operation: generateSuccess('Temporary Uploads have been flushed successfully.')

+ 9 - 1
server/graph/resolvers/authentication.mjs

@@ -46,7 +46,11 @@ export default {
     /**
     /**
      * Fetch authentication strategies
      * Fetch authentication strategies
      */
      */
-    async authStrategies () {
+    async authStrategies (obj, args, context) {
+      if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
+        throw new Error('ERR_FORBIDDEN')
+      }
+
       return WIKI.data.authentication.map(stg => ({
       return WIKI.data.authentication.map(stg => ({
         ...stg,
         ...stg,
         isAvailable: stg.isAvailable === true
         isAvailable: stg.isAvailable === true
@@ -56,6 +60,10 @@ export default {
      * Fetch active authentication strategies
      * Fetch active authentication strategies
      */
      */
     async authActiveStrategies (obj, args, context) {
     async authActiveStrategies (obj, args, context) {
+      if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
+        throw new Error('ERR_FORBIDDEN')
+      }
+
       const strategies = await WIKI.db.authentication.getStrategies({ enabledOnly: args.enabledOnly })
       const strategies = await WIKI.db.authentication.getStrategies({ enabledOnly: args.enabledOnly })
       return strategies.map(a => {
       return strategies.map(a => {
         const str = _.find(WIKI.data.authentication, ['key', a.module]) || {}
         const str = _.find(WIKI.data.authentication, ['key', a.module]) || {}

+ 8 - 0
server/graph/resolvers/comment.mjs

@@ -7,6 +7,10 @@ export default {
      * Fetch list of Comments Providers
      * Fetch list of Comments Providers
      */
      */
     async commentsProviders(obj, args, context, info) {
     async commentsProviders(obj, args, context, info) {
+      if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
+        throw new Error('ERR_FORBIDDEN')
+      }
+
       const providers = await WIKI.db.commentProviders.getProviders()
       const providers = await WIKI.db.commentProviders.getProviders()
       return providers.map(provider => {
       return providers.map(provider => {
         const providerInfo = _.find(WIKI.data.commentProviders, ['key', provider.key]) || {}
         const providerInfo = _.find(WIKI.data.commentProviders, ['key', provider.key]) || {}
@@ -137,6 +141,10 @@ export default {
      */
      */
     async updateCommentsProviders(obj, args, context) {
     async updateCommentsProviders(obj, args, context) {
       try {
       try {
+        if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
+          throw new Error('ERR_FORBIDDEN')
+        }
+
         for (let provider of args.providers) {
         for (let provider of args.providers) {
           await WIKI.db.commentProviders.query().patch({
           await WIKI.db.commentProviders.query().patch({
             isEnabled: provider.isEnabled,
             isEnabled: provider.isEnabled,

+ 34 - 8
server/graph/resolvers/group.mjs

@@ -1,4 +1,4 @@
-import { generateError, generateSuccess } from '../../helpers/graph.mjs'
+import { generateSuccess } from '../../helpers/graph.mjs'
 import safeRegex from 'safe-regex'
 import safeRegex from 'safe-regex'
 import _ from 'lodash-es'
 import _ from 'lodash-es'
 import { v4 as uuid } from 'uuid'
 import { v4 as uuid } from 'uuid'
@@ -8,7 +8,10 @@ export default {
     /**
     /**
      * FETCH ALL GROUPS
      * FETCH ALL GROUPS
      */
      */
-    async groups () {
+    async groups (obj, args, context) {
+      if (!WIKI.auth.checkAccess(context.req.user, ['manage:groups', 'manage:users', 'manage:system'])) {
+        throw new Error('ERR_FORBIDDEN')
+      }
       return WIKI.db.groups.query().select(
       return WIKI.db.groups.query().select(
         'groups.*',
         'groups.*',
         WIKI.db.groups.relatedQuery('users').count().as('userCount')
         WIKI.db.groups.relatedQuery('users').count().as('userCount')
@@ -17,7 +20,10 @@ export default {
     /**
     /**
      * FETCH A SINGLE GROUP
      * FETCH A SINGLE GROUP
      */
      */
-    async groupById(obj, args) {
+    async groupById(obj, args, context) {
+      if (!WIKI.auth.checkAccess(context.req.user, ['manage:groups', 'manage:users', 'manage:system'])) {
+        throw new Error('ERR_FORBIDDEN')
+      }
       return WIKI.db.groups.query().findById(args.id)
       return WIKI.db.groups.query().findById(args.id)
     }
     }
   },
   },
@@ -26,8 +32,12 @@ export default {
      * ASSIGN USER TO GROUP
      * ASSIGN USER TO GROUP
      */
      */
     async assignUserToGroup (obj, args, { req }) {
     async assignUserToGroup (obj, args, { req }) {
+      if (!WIKI.auth.checkAccess(req.user, ['manage:groups', 'manage:users', 'manage:system'])) {
+        throw new Error('ERR_FORBIDDEN')
+      }
+
       // Check for guest user
       // Check for guest user
-      if (args.userId === 2) {
+      if (args.userId === WIKI.config.auth.guestUserId) {
         throw new Error('Cannot assign the Guest user to a group.')
         throw new Error('Cannot assign the Guest user to a group.')
       }
       }
 
 
@@ -78,6 +88,10 @@ export default {
      * CREATE NEW GROUP
      * CREATE NEW GROUP
      */
      */
     async createGroup (obj, args, { req }) {
     async createGroup (obj, args, { req }) {
+      if (!WIKI.auth.checkAccess(req.user, ['manage:groups', 'manage:system'])) {
+        throw new Error('ERR_FORBIDDEN')
+      }
+
       const group = await WIKI.db.groups.query().insertAndFetch({
       const group = await WIKI.db.groups.query().insertAndFetch({
         name: args.name,
         name: args.name,
         permissions: JSON.stringify(WIKI.data.groups.defaultPermissions),
         permissions: JSON.stringify(WIKI.data.groups.defaultPermissions),
@@ -97,8 +111,12 @@ export default {
     /**
     /**
      * DELETE GROUP
      * DELETE GROUP
      */
      */
-    async deleteGroup (obj, args) {
-      if (args.id === 1 || args.id === 2) {
+    async deleteGroup (obj, args, { req }) {
+      if (!WIKI.auth.checkAccess(req.user, ['manage:groups', 'manage:system'])) {
+        throw new Error('ERR_FORBIDDEN')
+      }
+
+      if (args.id === WIKI.data.systemIds.guestsGroupId || args.id === WIKI.data.systemIds.usersGroupId || args.id === WIKI.config.auth.rootAdminGroupId) {
         throw new Error('Cannot delete this group.')
         throw new Error('Cannot delete this group.')
       }
       }
 
 
@@ -117,11 +135,15 @@ export default {
     /**
     /**
      * UNASSIGN USER FROM GROUP
      * UNASSIGN USER FROM GROUP
      */
      */
-    async unassignUserFromGroup (obj, args) {
+    async unassignUserFromGroup (obj, args, { req }) {
+      if (!WIKI.auth.checkAccess(req.user, ['manage:groups', 'manage:users', 'manage:system'])) {
+        throw new Error('ERR_FORBIDDEN')
+      }
+
       if (args.userId === 2) {
       if (args.userId === 2) {
         throw new Error('Cannot unassign Guest user')
         throw new Error('Cannot unassign Guest user')
       }
       }
-      if (args.userId === 1 && args.groupId === 1) {
+      if (args.userId === WIKI.config.auth.guestUserId && args.groupId === WIKI.data.systemIds.guestsGroupId) {
         throw new Error('Cannot unassign Administrator user from Administrators group.')
         throw new Error('Cannot unassign Administrator user from Administrators group.')
       }
       }
       const grp = await WIKI.db.groups.query().findById(args.groupId)
       const grp = await WIKI.db.groups.query().findById(args.groupId)
@@ -145,6 +167,10 @@ export default {
      * UPDATE GROUP
      * UPDATE GROUP
      */
      */
     async updateGroup (obj, args, { req }) {
     async updateGroup (obj, args, { req }) {
+      if (!WIKI.auth.checkAccess(req.user, ['manage:groups', 'manage:system'])) {
+        throw new Error('ERR_FORBIDDEN')
+      }
+
       // Check for unsafe regex page rules
       // Check for unsafe regex page rules
       if (_.some(args.pageRules, pr => {
       if (_.some(args.pageRules, pr => {
         return pr.match === 'REGEX' && !safeRegex(pr.path)
         return pr.match === 'REGEX' && !safeRegex(pr.path)

+ 13 - 13
server/graph/resolvers/user.mjs

@@ -11,7 +11,7 @@ export default {
      * FETCH ALL USERS
      * FETCH ALL USERS
      */
      */
     async users (obj, args, context, info) {
     async users (obj, args, context, info) {
-      if (!WIKI.auth.checkAccess(context.req.user, ['read:users', 'write:users', 'manage:users'])) {
+      if (!WIKI.auth.checkAccess(context.req.user, ['read:users', 'write:users', 'manage:users', 'manage:groups'])) {
         throw new Error('ERR_FORBIDDEN')
         throw new Error('ERR_FORBIDDEN')
       }
       }
 
 
@@ -51,7 +51,7 @@ export default {
      */
      */
     async userById (obj, args, context, info) {
     async userById (obj, args, context, info) {
       if (!context.req.isAuthenticated || context.req.user.id !== args.id) {
       if (!context.req.isAuthenticated || context.req.user.id !== args.id) {
-        if (!WIKI.auth.checkAccess(context.req.user, ['read:users', 'write:users', 'manage:users'])) {
+        if (!WIKI.auth.checkAccess(context.req.user, ['read:users', 'write:users', 'manage:users', 'manage:groups'])) {
           throw new Error('ERR_FORBIDDEN')
           throw new Error('ERR_FORBIDDEN')
         }
         }
       }
       }
@@ -88,7 +88,7 @@ export default {
     },
     },
 
 
     async userDefaults (obj, args, context) {
     async userDefaults (obj, args, context) {
-      if (!WIKI.auth.checkAccess(context.req.user, ['read:users', 'write:users', 'manage:users'])) {
+      if (!WIKI.auth.checkAccess(context.req.user, ['read:users', 'write:users', 'manage:users', 'manage:groups'])) {
         throw new Error('ERR_FORBIDDEN')
         throw new Error('ERR_FORBIDDEN')
       }
       }
 
 
@@ -134,7 +134,7 @@ export default {
   Mutation: {
   Mutation: {
     async createUser (obj, args, context) {
     async createUser (obj, args, context) {
       try {
       try {
-        if (!WIKI.auth.checkAccess(context.req.user, ['write:users', 'manage:users'])) {
+        if (!WIKI.auth.checkAccess(context.req.user, ['write:users', 'manage:users', 'manage:groups'])) {
           throw new Error('ERR_FORBIDDEN')
           throw new Error('ERR_FORBIDDEN')
         }
         }
 
 
@@ -149,7 +149,7 @@ export default {
     },
     },
     async deleteUser (obj, args, context) {
     async deleteUser (obj, args, context) {
       try {
       try {
-        if (!WIKI.auth.checkAccess(context.req.user, ['manage:users'])) {
+        if (!WIKI.auth.checkAccess(context.req.user, ['manage:users', 'manage:groups'])) {
           throw new Error('ERR_FORBIDDEN')
           throw new Error('ERR_FORBIDDEN')
         }
         }
 
 
@@ -174,7 +174,7 @@ export default {
     },
     },
     async updateUser (obj, args, context) {
     async updateUser (obj, args, context) {
       try {
       try {
-        if (!WIKI.auth.checkAccess(context.req.user, ['manage:users'])) {
+        if (!WIKI.auth.checkAccess(context.req.user, ['manage:users', 'manage:groups'])) {
           throw new Error('ERR_FORBIDDEN')
           throw new Error('ERR_FORBIDDEN')
         }
         }
 
 
@@ -189,7 +189,7 @@ export default {
     },
     },
     async verifyUser (obj, args, context) {
     async verifyUser (obj, args, context) {
       try {
       try {
-        if (!WIKI.auth.checkAccess(context.req.user, ['manage:users'])) {
+        if (!WIKI.auth.checkAccess(context.req.user, ['manage:users', 'manage:groups'])) {
           throw new Error('ERR_FORBIDDEN')
           throw new Error('ERR_FORBIDDEN')
         }
         }
 
 
@@ -204,7 +204,7 @@ export default {
     },
     },
     async activateUser (obj, args, context) {
     async activateUser (obj, args, context) {
       try {
       try {
-        if (!WIKI.auth.checkAccess(context.req.user, ['manage:users'])) {
+        if (!WIKI.auth.checkAccess(context.req.user, ['manage:users', 'manage:groups'])) {
           throw new Error('ERR_FORBIDDEN')
           throw new Error('ERR_FORBIDDEN')
         }
         }
 
 
@@ -219,7 +219,7 @@ export default {
     },
     },
     async deactivateUser (obj, args, context) {
     async deactivateUser (obj, args, context) {
       try {
       try {
-        if (!WIKI.auth.checkAccess(context.req.user, ['manage:users'])) {
+        if (!WIKI.auth.checkAccess(context.req.user, ['manage:users', 'manage:groups'])) {
           throw new Error('ERR_FORBIDDEN')
           throw new Error('ERR_FORBIDDEN')
         }
         }
 
 
@@ -240,7 +240,7 @@ export default {
     },
     },
     async enableUserTFA (obj, args, context) {
     async enableUserTFA (obj, args, context) {
       try {
       try {
-        if (!WIKI.auth.checkAccess(context.req.user, ['manage:users'])) {
+        if (!WIKI.auth.checkAccess(context.req.user, ['manage:users', 'manage:groups'])) {
           throw new Error('ERR_FORBIDDEN')
           throw new Error('ERR_FORBIDDEN')
         }
         }
 
 
@@ -255,7 +255,7 @@ export default {
     },
     },
     async disableUserTFA (obj, args, context) {
     async disableUserTFA (obj, args, context) {
       try {
       try {
-        if (!WIKI.auth.checkAccess(context.req.user, ['manage:users'])) {
+        if (!WIKI.auth.checkAccess(context.req.user, ['manage:users', 'manage:groups'])) {
           throw new Error('ERR_FORBIDDEN')
           throw new Error('ERR_FORBIDDEN')
         }
         }
 
 
@@ -270,7 +270,7 @@ export default {
     },
     },
     async changeUserPassword (obj, args, context) {
     async changeUserPassword (obj, args, context) {
       try {
       try {
-        if (!WIKI.auth.checkAccess(context.req.user, ['manage:users'])) {
+        if (!WIKI.auth.checkAccess(context.req.user, ['manage:users', 'manage:groups'])) {
           throw new Error('ERR_FORBIDDEN')
           throw new Error('ERR_FORBIDDEN')
         }
         }
 
 
@@ -430,7 +430,7 @@ export default {
      */
      */
     async updateUserDefaults (obj, args, context) {
     async updateUserDefaults (obj, args, context) {
       try {
       try {
-        if (!WIKI.auth.checkAccess(context.req.user, ['manage:users'])) {
+        if (!WIKI.auth.checkAccess(context.req.user, ['manage:users', 'manage:groups'])) {
           throw new Error('ERR_FORBIDDEN')
           throw new Error('ERR_FORBIDDEN')
         }
         }
 
 

+ 4 - 3
ux/src/layouts/AdminLayout.vue

@@ -188,7 +188,7 @@ q-layout.admin(view='hHh Lpr lff')
             q-item-section {{ t('admin.metrics.title') }}
             q-item-section {{ t('admin.metrics.title') }}
             q-item-section(side)
             q-item-section(side)
               status-light(:color='adminStore.info.isMetricsEnabled ? `positive` : `negative`')
               status-light(:color='adminStore.info.isMetricsEnabled ? `positive` : `negative`')
-          q-item(to='/_admin/rendering', v-ripple, active-class='bg-primary text-white')
+          q-item(to='/_admin/rendering', v-ripple, active-class='bg-primary text-white', v-if='flagsStore.experimental')
             q-item-section(avatar)
             q-item-section(avatar)
               q-icon(name='img:/_assets/icons/fluent-rich-text-converter.svg')
               q-icon(name='img:/_assets/icons/fluent-rich-text-converter.svg')
             q-item-section {{ t('admin.rendering.title') }}
             q-item-section {{ t('admin.rendering.title') }}
@@ -416,14 +416,15 @@ onMounted(async () => {
     }
     }
 
 
     > .q-layout-container {
     > .q-layout-container {
+      border-radius: 6px;
+      box-shadow: 0 0 0 1px rgba(0,0,0,.5);
+
       @at-root .body--light & {
       @at-root .body--light & {
         background-image: linear-gradient(to bottom, $dark-5 10px, $grey-3 11px, $grey-4);
         background-image: linear-gradient(to bottom, $dark-5 10px, $grey-3 11px, $grey-4);
       }
       }
       @at-root .body--dark & {
       @at-root .body--dark & {
         background-image: linear-gradient(to bottom, $dark-4 10px, $dark-4 11px, $dark-3);
         background-image: linear-gradient(to bottom, $dark-4 10px, $dark-4 11px, $dark-3);
       }
       }
-      border-radius: 6px;
-      box-shadow: 0 0 0 1px rgba(0,0,0,.5);
     }
     }
   }
   }
 }
 }

+ 5 - 4
ux/src/layouts/MainLayout.vue

@@ -2,7 +2,7 @@
 q-layout(view='hHh Lpr lff')
 q-layout(view='hHh Lpr lff')
   header-nav
   header-nav
   q-drawer.bg-sidebar(
   q-drawer.bg-sidebar(
-    :modelValue='isSidebarShown'
+    :model-value='isSidebarShown'
     :show-if-above='siteStore.theme.sidebarPosition !== `off`'
     :show-if-above='siteStore.theme.sidebarPosition !== `off`'
     :width='isSidebarMini ? 56 : 255'
     :width='isSidebarMini ? 56 : 255'
     :side='siteStore.theme.sidebarPosition === `right` ? `right` : `left`'
     :side='siteStore.theme.sidebarPosition === `right` ? `right` : `left`'
@@ -63,7 +63,7 @@ q-layout(view='hHh Lpr lff')
           :aria-label='commonStore.locale'
           :aria-label='commonStore.locale'
           size='sm'
           size='sm'
           )
           )
-          locale-selector-menu(:offset="[-5, 5]")
+          locale-selector-menu(:offset='[-5, 5]')
         q-separator(vertical)
         q-separator(vertical)
         q-btn.q-px-sm.col(
         q-btn.q-px-sm.col(
           flat
           flat
@@ -232,14 +232,15 @@ body.body--dark {
     }
     }
 
 
     > .q-layout-container {
     > .q-layout-container {
+      border-radius: 6px;
+      box-shadow: 0 0 30px 0 rgba(0,0,0,.3);
+
       @at-root .body--light & {
       @at-root .body--light & {
         background-image: linear-gradient(to bottom, $dark-5 10px, $grey-3 11px, $grey-4);
         background-image: linear-gradient(to bottom, $dark-5 10px, $grey-3 11px, $grey-4);
       }
       }
       @at-root .body--dark & {
       @at-root .body--dark & {
         background-image: linear-gradient(to bottom, $dark-4 10px, $dark-4 11px, $dark-3);
         background-image: linear-gradient(to bottom, $dark-4 10px, $dark-4 11px, $dark-3);
       }
       }
-      border-radius: 6px;
-      box-shadow: 0 0 30px 0 rgba(0,0,0,.3);
     }
     }
   }
   }
 }
 }