diff --git a/server/controllers/auth.mjs b/server/controllers/auth.mjs index 8437488801..5e333e0bd7 100644 --- a/server/controllers/auth.mjs +++ b/server/controllers/auth.mjs @@ -1,5 +1,3 @@ -/* global WIKI */ - import express from 'express' import ExpressBrute from 'express-brute' import BruteKnex from '../helpers/brute-knex.mjs' diff --git a/server/db/migrations/3.0.0.mjs b/server/db/migrations/3.0.0.mjs index 795d3c4d38..76ca146b26 100644 --- a/server/db/migrations/3.0.0.mjs +++ b/server/db/migrations/3.0.0.mjs @@ -475,6 +475,7 @@ export async function up (knex) { private: certs.privateKey }, secret, + rootAdminGroupId: groupAdminId, rootAdminUserId: userAdminId, guestUserId: userGuestId } diff --git a/server/graph/resolvers/analytics.mjs b/server/graph/resolvers/analytics.mjs index 911745cb06..497e0045b1 100644 --- a/server/graph/resolvers/analytics.mjs +++ b/server/graph/resolvers/analytics.mjs @@ -4,6 +4,10 @@ import { generateError, generateSuccess } from '../../helpers/graph.mjs' export default { Query: { 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) providers = providers.map(stg => { const providerInfo = find(WIKI.data.analytics, ['key', stg.key]) || {} @@ -28,6 +32,10 @@ export default { Mutation: { async updateAnalyticsProviders(obj, args, context) { try { + if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) { + throw new Error('ERR_FORBIDDEN') + } + for (let str of args.providers) { await WIKI.db.analytics.query().patch({ isEnabled: str.isEnabled, diff --git a/server/graph/resolvers/asset.mjs b/server/graph/resolvers/asset.mjs index 58ac299c16..78e4edc443 100644 --- a/server/graph/resolvers/asset.mjs +++ b/server/graph/resolvers/asset.mjs @@ -10,6 +10,7 @@ import { pipeline } from 'node:stream/promises' export default { Query: { async assetById(obj, args, context) { + // FIXME: Perm const asset = await WIKI.db.assets.query().findById(args.id) if (asset) { return asset @@ -145,6 +146,7 @@ export default { */ async uploadAssets(obj, args, context) { try { + // FIXME: Perm // -> Get Folder let folder = {} if (args.folderId || args.folderPath) { @@ -380,6 +382,10 @@ export default { */ async flushTempUploads(obj, args, context) { try { + if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) { + throw new Error('ERR_FORBIDDEN') + } + await WIKI.db.assets.flushTempUploads() return { operation: generateSuccess('Temporary Uploads have been flushed successfully.') diff --git a/server/graph/resolvers/authentication.mjs b/server/graph/resolvers/authentication.mjs index a92756bdf5..5117456e2a 100644 --- a/server/graph/resolvers/authentication.mjs +++ b/server/graph/resolvers/authentication.mjs @@ -46,7 +46,11 @@ export default { /** * 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 => ({ ...stg, isAvailable: stg.isAvailable === true @@ -56,6 +60,10 @@ export default { * Fetch active authentication strategies */ 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 }) return strategies.map(a => { const str = _.find(WIKI.data.authentication, ['key', a.module]) || {} diff --git a/server/graph/resolvers/comment.mjs b/server/graph/resolvers/comment.mjs index 968ece8f13..d9831c4f8c 100644 --- a/server/graph/resolvers/comment.mjs +++ b/server/graph/resolvers/comment.mjs @@ -7,6 +7,10 @@ export default { * Fetch list of Comments Providers */ 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() return providers.map(provider => { const providerInfo = _.find(WIKI.data.commentProviders, ['key', provider.key]) || {} @@ -137,6 +141,10 @@ export default { */ async updateCommentsProviders(obj, args, context) { try { + if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) { + throw new Error('ERR_FORBIDDEN') + } + for (let provider of args.providers) { await WIKI.db.commentProviders.query().patch({ isEnabled: provider.isEnabled, diff --git a/server/graph/resolvers/group.mjs b/server/graph/resolvers/group.mjs index 1754149f5b..9b97edcd00 100644 --- a/server/graph/resolvers/group.mjs +++ b/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 _ from 'lodash-es' import { v4 as uuid } from 'uuid' @@ -8,7 +8,10 @@ export default { /** * 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( 'groups.*', WIKI.db.groups.relatedQuery('users').count().as('userCount') @@ -17,7 +20,10 @@ export default { /** * 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) } }, @@ -26,8 +32,12 @@ export default { * ASSIGN USER TO GROUP */ 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 - if (args.userId === 2) { + if (args.userId === WIKI.config.auth.guestUserId) { throw new Error('Cannot assign the Guest user to a group.') } @@ -78,6 +88,10 @@ export default { * CREATE NEW GROUP */ 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({ name: args.name, permissions: JSON.stringify(WIKI.data.groups.defaultPermissions), @@ -97,8 +111,12 @@ export default { /** * 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.') } @@ -117,11 +135,15 @@ export default { /** * 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) { 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.') } const grp = await WIKI.db.groups.query().findById(args.groupId) @@ -145,6 +167,10 @@ export default { * UPDATE GROUP */ 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 if (_.some(args.pageRules, pr => { return pr.match === 'REGEX' && !safeRegex(pr.path) diff --git a/server/graph/resolvers/user.mjs b/server/graph/resolvers/user.mjs index dd1d1d91b1..ae814f50ed 100644 --- a/server/graph/resolvers/user.mjs +++ b/server/graph/resolvers/user.mjs @@ -11,7 +11,7 @@ export default { * FETCH ALL USERS */ 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') } @@ -51,7 +51,7 @@ export default { */ async userById (obj, args, context, info) { 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') } } @@ -88,7 +88,7 @@ export default { }, 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') } @@ -134,7 +134,7 @@ export default { Mutation: { async createUser (obj, args, context) { 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') } @@ -149,7 +149,7 @@ export default { }, async deleteUser (obj, args, context) { 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') } @@ -174,7 +174,7 @@ export default { }, async updateUser (obj, args, context) { 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') } @@ -189,7 +189,7 @@ export default { }, async verifyUser (obj, args, context) { 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') } @@ -204,7 +204,7 @@ export default { }, async activateUser (obj, args, context) { 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') } @@ -219,7 +219,7 @@ export default { }, async deactivateUser (obj, args, context) { 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') } @@ -240,7 +240,7 @@ export default { }, async enableUserTFA (obj, args, context) { 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') } @@ -255,7 +255,7 @@ export default { }, async disableUserTFA (obj, args, context) { 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') } @@ -270,7 +270,7 @@ export default { }, async changeUserPassword (obj, args, context) { 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') } @@ -430,7 +430,7 @@ export default { */ async updateUserDefaults (obj, args, context) { 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') } diff --git a/ux/src/layouts/AdminLayout.vue b/ux/src/layouts/AdminLayout.vue index 0743f0d9b5..62cf4a21e1 100644 --- a/ux/src/layouts/AdminLayout.vue +++ b/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(side) 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-icon(name='img:/_assets/icons/fluent-rich-text-converter.svg') q-item-section {{ t('admin.rendering.title') }} @@ -416,14 +416,15 @@ onMounted(async () => { } > .q-layout-container { + border-radius: 6px; + box-shadow: 0 0 0 1px rgba(0,0,0,.5); + @at-root .body--light & { background-image: linear-gradient(to bottom, $dark-5 10px, $grey-3 11px, $grey-4); } @at-root .body--dark & { 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); } } } diff --git a/ux/src/layouts/MainLayout.vue b/ux/src/layouts/MainLayout.vue index 4bcc088beb..ecb97a5bcd 100644 --- a/ux/src/layouts/MainLayout.vue +++ b/ux/src/layouts/MainLayout.vue @@ -2,7 +2,7 @@ q-layout(view='hHh Lpr lff') header-nav q-drawer.bg-sidebar( - :modelValue='isSidebarShown' + :model-value='isSidebarShown' :show-if-above='siteStore.theme.sidebarPosition !== `off`' :width='isSidebarMini ? 56 : 255' :side='siteStore.theme.sidebarPosition === `right` ? `right` : `left`' @@ -63,7 +63,7 @@ q-layout(view='hHh Lpr lff') :aria-label='commonStore.locale' size='sm' ) - locale-selector-menu(:offset="[-5, 5]") + locale-selector-menu(:offset='[-5, 5]') q-separator(vertical) q-btn.q-px-sm.col( flat @@ -232,14 +232,15 @@ body.body--dark { } > .q-layout-container { + border-radius: 6px; + box-shadow: 0 0 30px 0 rgba(0,0,0,.3); + @at-root .body--light & { background-image: linear-gradient(to bottom, $dark-5 10px, $grey-3 11px, $grey-4); } @at-root .body--dark & { 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); } } }