fix: add permissions to resolvers

pull/6813/head
NGPixel 1 year ago
parent 88197c174c
commit 2a3e1400a7
No known key found for this signature in database
GPG Key ID: B755FB6870B30F63

@ -105,6 +105,8 @@ export default {
* @param {Express Next Callback} next * @param {Express Next Callback} next
*/ */
authenticate (req, res, next) { authenticate (req, res, next) {
req.isAuthenticated = false
WIKI.auth.passport.authenticate('jwt', { session: false }, async (err, user, info) => { WIKI.auth.passport.authenticate('jwt', { session: false }, async (err, user, info) => {
if (err) { return next() } if (err) { return next() }
let mustRevalidate = false let mustRevalidate = false
@ -170,6 +172,7 @@ export default {
WIKI.auth.guest.cacheExpiration = DateTime.utc().plus({ minutes: 1 }) WIKI.auth.guest.cacheExpiration = DateTime.utc().plus({ minutes: 1 })
} }
req.user = WIKI.auth.guest req.user = WIKI.auth.guest
req.isAuthenticated = false
return next() return next()
} }
@ -203,6 +206,7 @@ export default {
// JWT is valid // JWT is valid
req.logIn(user, { session: false }, (errc) => { req.logIn(user, { session: false }, (errc) => {
if (errc) { return next(errc) } if (errc) { return next(errc) }
req.isAuthenticated = true
next() next()
}) })
})(req, res, next) })(req, res, next)
@ -223,7 +227,7 @@ export default {
return true return true
} }
// Check Global Permissions // Check Permissions
if (_.intersection(userPermissions, permissions).length < 1) { if (_.intersection(userPermissions, permissions).length < 1) {
return false return false
} }

@ -17,6 +17,10 @@ export default {
* List of API Keys * List of API Keys
*/ */
async apiKeys (obj, args, context) { async apiKeys (obj, args, context) {
if (!WIKI.auth.checkAccess(context.req.user, ['read:api', 'manage:api'])) {
throw new Error('ERR_FORBIDDEN')
}
const keys = await WIKI.db.apiKeys.query().orderBy(['isRevoked', 'name']) const keys = await WIKI.db.apiKeys.query().orderBy(['isRevoked', 'name'])
return keys.map(k => ({ return keys.map(k => ({
id: k.id, id: k.id,
@ -31,7 +35,11 @@ export default {
/** /**
* Current API State * Current API State
*/ */
apiState () { apiState (obj, args, context) {
if (!WIKI.auth.checkAccess(context.req.user, ['read:api', 'manage:api', 'read:dashboard'])) {
throw new Error('ERR_FORBIDDEN')
}
return WIKI.config.api.isEnabled return WIKI.config.api.isEnabled
}, },
/** /**
@ -82,6 +90,10 @@ export default {
*/ */
async createApiKey (obj, args, context) { async createApiKey (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:api'])) {
throw new Error('ERR_FORBIDDEN')
}
const key = await WIKI.db.apiKeys.createNewKey(args) const key = await WIKI.db.apiKeys.createNewKey(args)
await WIKI.auth.reloadApiKeys() await WIKI.auth.reloadApiKeys()
WIKI.events.outbound.emit('reloadApiKeys') WIKI.events.outbound.emit('reloadApiKeys')
@ -136,7 +148,7 @@ export default {
try { try {
const userId = context.req.user?.id const userId = context.req.user?.id
if (!userId) { if (!userId) {
throw new Error('ERR_USER_NOT_AUTHENTICATED') throw new Error('ERR_NOT_AUTHENTICATED')
} }
const usr = await WIKI.db.users.query().findById(userId) const usr = await WIKI.db.users.query().findById(userId)
@ -182,7 +194,7 @@ export default {
try { try {
const userId = context.req.user?.id const userId = context.req.user?.id
if (!userId) { if (!userId) {
throw new Error('ERR_USER_NOT_AUTHENTICATED') throw new Error('ERR_NOT_AUTHENTICATED')
} }
const usr = await WIKI.db.users.query().findById(userId) const usr = await WIKI.db.users.query().findById(userId)
@ -224,7 +236,7 @@ export default {
try { try {
const userId = context.req.user?.id const userId = context.req.user?.id
if (!userId) { if (!userId) {
throw new Error('ERR_USER_NOT_AUTHENTICATED') throw new Error('ERR_NOT_AUTHENTICATED')
} }
const usr = await WIKI.db.users.query().findById(userId) const usr = await WIKI.db.users.query().findById(userId)
@ -283,7 +295,7 @@ export default {
try { try {
const userId = context.req.user?.id const userId = context.req.user?.id
if (!userId) { if (!userId) {
throw new Error('ERR_USER_NOT_AUTHENTICATED') throw new Error('ERR_NOT_AUTHENTICATED')
} }
const usr = await WIKI.db.users.query().findById(userId) const usr = await WIKI.db.users.query().findById(userId)
@ -346,7 +358,7 @@ export default {
try { try {
const userId = context.req.user?.id const userId = context.req.user?.id
if (!userId) { if (!userId) {
throw new Error('ERR_USER_NOT_AUTHENTICATED') throw new Error('ERR_NOT_AUTHENTICATED')
} }
const usr = await WIKI.db.users.query().findById(userId) const usr = await WIKI.db.users.query().findById(userId)
@ -584,7 +596,7 @@ export default {
*/ */
async revokeApiKey (obj, args, context) { async revokeApiKey (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) { if (!WIKI.auth.checkAccess(context.req.user, ['manage:api'])) {
throw new Error('ERR_FORBIDDEN') throw new Error('ERR_FORBIDDEN')
} }

@ -11,6 +11,9 @@ export default {
Mutation: { Mutation: {
async setBlocksState(obj, args, context) { async setBlocksState(obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:blocks'])) {
throw new Error('ERR_FORBIDDEN')
}
// TODO: update blocks state // TODO: update blocks state
return { return {
operation: generateSuccess('Blocks state updated successfully') operation: generateSuccess('Blocks state updated successfully')

@ -3,10 +3,18 @@ import _ from 'lodash-es'
export default { export default {
Query: { Query: {
async hooks () { async hooks (obj, args, context) {
if (!WIKI.auth.checkAccess(context.req.user, ['read:webhooks', 'write:webhooks', 'manage:webhooks'])) {
throw new Error('ERR_FORBIDDEN')
}
return WIKI.db.hooks.query().orderBy('name') return WIKI.db.hooks.query().orderBy('name')
}, },
async hookById (obj, args) { async hookById (obj, args, context) {
if (!WIKI.auth.checkAccess(context.req.user, ['read:webhooks', 'write:webhooks', 'manage:webhooks'])) {
throw new Error('ERR_FORBIDDEN')
}
return WIKI.db.hooks.query().findById(args.id) return WIKI.db.hooks.query().findById(args.id)
} }
}, },
@ -14,8 +22,12 @@ export default {
/** /**
* CREATE HOOK * CREATE HOOK
*/ */
async createHook (obj, args) { async createHook (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['write:webhooks', 'manage:webhooks'])) {
throw new Error('ERR_FORBIDDEN')
}
// -> Validate inputs // -> Validate inputs
if (!args.name || args.name.length < 1) { if (!args.name || args.name.length < 1) {
throw new WIKI.Error.Custom('HookCreateInvalidName', 'Invalid Hook Name') throw new WIKI.Error.Custom('HookCreateInvalidName', 'Invalid Hook Name')
@ -41,8 +53,12 @@ export default {
/** /**
* UPDATE HOOK * UPDATE HOOK
*/ */
async updateHook (obj, args) { async updateHook (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:webhooks'])) {
throw new Error('ERR_FORBIDDEN')
}
// -> Load hook // -> Load hook
const hook = await WIKI.db.hooks.query().findById(args.id) const hook = await WIKI.db.hooks.query().findById(args.id)
if (!hook) { if (!hook) {
@ -72,8 +88,12 @@ export default {
/** /**
* DELETE HOOK * DELETE HOOK
*/ */
async deleteHook (obj, args) { async deleteHook (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:webhooks'])) {
throw new Error('ERR_FORBIDDEN')
}
await WIKI.db.hooks.deleteHook(args.id) await WIKI.db.hooks.deleteHook(args.id)
WIKI.logger.debug(`Hook ${args.id} deleted successfully.`) WIKI.logger.debug(`Hook ${args.id} deleted successfully.`)
return { return {

@ -1,6 +1,3 @@
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
import _ from 'lodash-es'
export default { export default {
Query: { Query: {
async locales(obj, args, context, info) { async locales(obj, args, context, info) {
@ -9,45 +6,5 @@ export default {
localeStrings (obj, args, context, info) { localeStrings (obj, args, context, info) {
return WIKI.db.locales.getStrings(args.locale) return WIKI.db.locales.getStrings(args.locale)
} }
},
Mutation: {
async downloadLocale(obj, args, context) {
try {
const job = await WIKI.scheduler.registerJob({
name: 'fetch-graph-locale',
immediate: true
}, args.locale)
await job.finished
return {
responseResult: generateSuccess('Locale downloaded successfully')
}
} catch (err) {
return generateError(err)
}
},
async updateLocale(obj, args, context) {
try {
WIKI.config.lang.code = args.locale
WIKI.config.lang.autoUpdate = args.autoUpdate
WIKI.config.lang.namespacing = args.namespacing
WIKI.config.lang.namespaces = _.union(args.namespaces, [args.locale])
const newLocale = await WIKI.db.locales.query().select('isRTL').where('code', args.locale).first()
WIKI.config.lang.rtl = newLocale.isRTL
await WIKI.configSvc.saveToDb(['lang'])
await WIKI.lang.setCurrentLocale(args.locale)
await WIKI.lang.refreshNamespaces()
await WIKI.cache.del('nav:locales')
return {
responseResult: generateSuccess('Locale config updated')
}
} catch (err) {
return generateError(err)
}
}
} }
} }

@ -3,7 +3,11 @@ import { generateError, generateSuccess } from '../../helpers/graph.mjs'
export default { export default {
Query: { Query: {
async mailConfig(obj, args, context, info) { async mailConfig(obj, args, context) {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
throw new Error('ERR_FORBIDDEN')
}
return { return {
...WIKI.config.mail, ...WIKI.config.mail,
pass: WIKI.config.mail.pass.length > 0 ? '********' : '' pass: WIKI.config.mail.pass.length > 0 ? '********' : ''
@ -13,6 +17,10 @@ export default {
Mutation: { Mutation: {
async sendMailTest(obj, args, context) { async sendMailTest(obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
throw new Error('ERR_FORBIDDEN')
}
if (_.isEmpty(args.recipientEmail) || args.recipientEmail.length < 6) { if (_.isEmpty(args.recipientEmail) || args.recipientEmail.length < 6) {
throw new WIKI.Error.MailInvalidRecipient() throw new WIKI.Error.MailInvalidRecipient()
} }
@ -36,6 +44,10 @@ export default {
}, },
async updateMailConfig(obj, args, context) { async updateMailConfig(obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
throw new Error('ERR_FORBIDDEN')
}
WIKI.config.mail = { WIKI.config.mail = {
senderName: args.senderName, senderName: args.senderName,
senderEmail: args.senderEmail, senderEmail: args.senderEmail,

@ -1,6 +1,6 @@
import _ from 'lodash-es' import _ from 'lodash-es'
import { generateError, generateSuccess } from '../../helpers/graph.mjs' import { generateError, generateSuccess } from '../../helpers/graph.mjs'
import { parsePath }from '../../helpers/page.mjs' import { parsePath } from '../../helpers/page.mjs'
import tsquery from 'pg-tsquery' import tsquery from 'pg-tsquery'
const tsq = tsquery() const tsq = tsquery()
@ -247,12 +247,19 @@ export default {
siteId: args.siteId siteId: args.siteId
}) })
if (page) { if (page) {
return { if (WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
...page, path: page.path,
...page.config, locale: page.locale
scriptCss: page.scripts?.css, })) {
scriptJsLoad: page.scripts?.jsLoad, return {
scriptJsUnload: page.scripts?.jsUnload ...page,
...page.config,
scriptCss: page.scripts?.css,
scriptJsLoad: page.scripts?.jsLoad,
scriptJsUnload: page.scripts?.jsUnload
}
} else {
throw new Error('ERR_FORBIDDEN')
} }
} else { } else {
throw new Error('ERR_PAGE_NOT_FOUND') throw new Error('ERR_PAGE_NOT_FOUND')
@ -265,17 +272,17 @@ export default {
async pathFromAlias (obj, args, context, info) { async pathFromAlias (obj, args, context, info) {
const alias = args.alias?.trim() const alias = args.alias?.trim()
if (!alias) { if (!alias) {
throw new Error('ERR_ALIAS_MISSING') throw new Error('ERR_PAGE_ALIAS_MISSING')
} }
if (!WIKI.sites[args.siteId]) { if (!WIKI.sites[args.siteId]) {
throw new Error('ERR_INVALID_SITE_ID') throw new Error('ERR_INVALID_SITE')
} }
const page = await WIKI.db.pages.query().findOne({ const page = await WIKI.db.pages.query().findOne({
alias: args.alias, alias: args.alias,
siteId: args.siteId siteId: args.siteId
}).select('id', 'path', 'locale') }).select('id', 'path', 'locale')
if (!page) { if (!page) {
throw new Error('ERR_ALIAS_NOT_FOUND') throw new Error('ERR_PAGE_ALIAS_NOT_FOUND')
} }
return { return {
id: page.id, id: page.id,
@ -287,7 +294,7 @@ export default {
* FETCH TAGS * FETCH TAGS
*/ */
async tags (obj, args, context, info) { async tags (obj, args, context, info) {
if (!args.siteId) { throw new Error('Missing Site ID')} if (!args.siteId) { throw new Error('Missing Site ID') }
const tags = await WIKI.db.knex('tags').where('siteId', args.siteId).orderBy('tag') const tags = await WIKI.db.knex('tags').where('siteId', args.siteId).orderBy('tag')
// TODO: check permissions // TODO: check permissions
return tags return tags
@ -670,19 +677,29 @@ export default {
} }
}, },
Page: { Page: {
icon (obj) { icon (page) {
return obj.icon || 'las la-file-alt' return page.icon || 'las la-file-alt'
},
password (page) {
return page.password ? '********' : ''
}, },
password (obj) { content (page, args, context) {
return obj.password ? '********' : '' if (!WIKI.auth.checkAccess(context.req.user, ['read:source', 'write:pages', 'manage:pages'], {
path: page.path,
locale: page.locale
})) {
throw new Error('ERR_FORBIDDEN')
}
return page.content
}, },
// async tags (obj) { // async tags (page) {
// return WIKI.db.pages.relatedQuery('tags').for(obj.id) // return WIKI.db.pages.relatedQuery('tags').for(page.id)
// }, // },
tocDepth (obj) { tocDepth (page) {
return { return {
min: obj.extra?.tocDepth?.min ?? 1, min: page.extra?.tocDepth?.min ?? 1,
max: obj.extra?.tocDepth?.max ?? 2 max: page.extra?.tocDepth?.max ?? 2
} }
} }
// comments(pg) { // comments(pg) {

@ -49,8 +49,12 @@ export default {
/** /**
* CREATE SITE * CREATE SITE
*/ */
async createSite (obj, args) { async createSite (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['write:sites', 'manage:sites'])) {
throw new Error('ERR_FORBIDDEN')
}
// -> Validate inputs // -> Validate inputs
if (!args.hostname || args.hostname.length < 1 || !/^(\\*)|([a-z0-9\-.:]+)$/.test(args.hostname)) { if (!args.hostname || args.hostname.length < 1 || !/^(\\*)|([a-z0-9\-.:]+)$/.test(args.hostname)) {
throw new WIKI.Error.Custom('SiteCreateInvalidHostname', 'Invalid Site Hostname') throw new WIKI.Error.Custom('SiteCreateInvalidHostname', 'Invalid Site Hostname')
@ -83,8 +87,12 @@ export default {
/** /**
* UPDATE SITE * UPDATE SITE
*/ */
async updateSite (obj, args) { async updateSite (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:sites'])) {
throw new Error('ERR_FORBIDDEN')
}
// -> Load site // -> Load site
const site = await WIKI.db.sites.query().findById(args.id) const site = await WIKI.db.sites.query().findById(args.id)
if (!site) { if (!site) {
@ -127,8 +135,12 @@ export default {
/** /**
* DELETE SITE * DELETE SITE
*/ */
async deleteSite (obj, args) { async deleteSite (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:sites'])) {
throw new Error('ERR_FORBIDDEN')
}
// -> Ensure site isn't last one // -> Ensure site isn't last one
const sitesCount = await WIKI.db.sites.query().count('id').first() const sitesCount = await WIKI.db.sites.query().count('id').first()
if (sitesCount?.count && _.toNumber(sitesCount?.count) <= 1) { if (sitesCount?.count && _.toNumber(sitesCount?.count) <= 1) {
@ -149,6 +161,10 @@ export default {
*/ */
async uploadSiteLogo (obj, args, context) { async uploadSiteLogo (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:sites'])) {
throw new Error('ERR_FORBIDDEN')
}
const { filename, mimetype, createReadStream } = await args.image const { filename, mimetype, createReadStream } = await args.image
WIKI.logger.info(`Processing site logo ${filename} of type ${mimetype}...`) WIKI.logger.info(`Processing site logo ${filename} of type ${mimetype}...`)
if (!WIKI.extensions.ext.sharp.isInstalled) { if (!WIKI.extensions.ext.sharp.isInstalled) {
@ -208,6 +224,10 @@ export default {
*/ */
async uploadSiteFavicon (obj, args, context) { async uploadSiteFavicon (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:sites'])) {
throw new Error('ERR_FORBIDDEN')
}
const { filename, mimetype, createReadStream } = await args.image const { filename, mimetype, createReadStream } = await args.image
WIKI.logger.info(`Processing site favicon ${filename} of type ${mimetype}...`) WIKI.logger.info(`Processing site favicon ${filename} of type ${mimetype}...`)
if (!WIKI.extensions.ext.sharp.isInstalled) { if (!WIKI.extensions.ext.sharp.isInstalled) {
@ -268,6 +288,10 @@ export default {
*/ */
async uploadSiteLoginBg (obj, args, context) { async uploadSiteLoginBg (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:sites'])) {
throw new Error('ERR_FORBIDDEN')
}
const { filename, mimetype, createReadStream } = await args.image const { filename, mimetype, createReadStream } = await args.image
WIKI.logger.info(`Processing site login bg ${filename} of type ${mimetype}...`) WIKI.logger.info(`Processing site login bg ${filename} of type ${mimetype}...`)
if (!WIKI.extensions.ext.sharp.isInstalled) { if (!WIKI.extensions.ext.sharp.isInstalled) {

@ -12,18 +12,44 @@ const getos = util.promisify(getosSync)
export default { export default {
Query: { Query: {
/**
* System Flags
*/
systemFlags () { systemFlags () {
return WIKI.config.flags return WIKI.config.flags
}, },
async systemInfo () { return {} }, /**
async systemExtensions () { * System Info
*/
async systemInfo (obj, args, context) {
if (!WIKI.auth.checkAccess(context.req.user, ['read:dashboard', 'manage:sites'])) {
throw new Error('ERR_FORBIDDEN')
}
return {}
},
/**
* System Extensions
*/
async systemExtensions (obj, args, context) {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
throw new Error('ERR_FORBIDDEN')
}
const exts = Object.values(WIKI.extensions.ext).map(ext => _.pick(ext, ['key', 'title', 'description', 'isInstalled', 'isInstallable'])) const exts = Object.values(WIKI.extensions.ext).map(ext => _.pick(ext, ['key', 'title', 'description', 'isInstalled', 'isInstallable']))
for (const ext of exts) { for (const ext of exts) {
ext.isCompatible = await WIKI.extensions.ext[ext.key].isCompatible() ext.isCompatible = await WIKI.extensions.ext[ext.key].isCompatible()
} }
return exts return exts
}, },
async systemInstances () { /**
* List System Instances
*/
async systemInstances (obj, args, context) {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
throw new Error('ERR_FORBIDDEN')
}
const instRaw = await WIKI.db.knex('pg_stat_activity') const instRaw = await WIKI.db.knex('pg_stat_activity')
.select([ .select([
'usename', 'usename',
@ -56,10 +82,24 @@ export default {
} }
return _.values(insts) return _.values(insts)
}, },
systemSecurity () { /**
* System Security Settings
*/
systemSecurity (obj, args, context) {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
throw new Error('ERR_FORBIDDEN')
}
return WIKI.config.security return WIKI.config.security
}, },
async systemJobs (obj, args) { /**
* List System Jobs
*/
async systemJobs (obj, args, context) {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
throw new Error('ERR_FORBIDDEN')
}
const results = args.states?.length > 0 ? const results = args.states?.length > 0 ?
await WIKI.db.knex('jobHistory').whereIn('state', args.states.map(s => s.toLowerCase())).orderBy('startedAt', 'desc') : await WIKI.db.knex('jobHistory').whereIn('state', args.states.map(s => s.toLowerCase())).orderBy('startedAt', 'desc') :
await WIKI.db.knex('jobHistory').orderBy('startedAt', 'desc') await WIKI.db.knex('jobHistory').orderBy('startedAt', 'desc')
@ -68,16 +108,37 @@ export default {
state: r.state.toUpperCase() state: r.state.toUpperCase()
})) }))
}, },
async systemJobsScheduled (obj, args) { /**
* List Scheduled Jobs
*/
async systemJobsScheduled (obj, args, context) {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
throw new Error('ERR_FORBIDDEN')
}
return WIKI.db.knex('jobSchedule').orderBy('task') return WIKI.db.knex('jobSchedule').orderBy('task')
}, },
async systemJobsUpcoming (obj, args) { /**
* List Upcoming Jobs
*/
async systemJobsUpcoming (obj, args, context) {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
throw new Error('ERR_FORBIDDEN')
}
return WIKI.db.knex('jobs').orderBy([ return WIKI.db.knex('jobs').orderBy([
{ column: 'waitUntil', order: 'asc', nulls: 'first' }, { column: 'waitUntil', order: 'asc', nulls: 'first' },
{ column: 'createdAt', order: 'asc' } { column: 'createdAt', order: 'asc' }
]) ])
}, },
systemSearch () { /**
* Search Settings
*/
systemSearch (obj, args, context) {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
throw new Error('ERR_FORBIDDEN')
}
return { return {
...WIKI.config.search, ...WIKI.config.search,
dictOverrides: JSON.stringify(WIKI.config.search.dictOverrides, null, 2) dictOverrides: JSON.stringify(WIKI.config.search.dictOverrides, null, 2)
@ -86,8 +147,13 @@ export default {
}, },
Mutation: { Mutation: {
async cancelJob (obj, args, context) { async cancelJob (obj, args, context) {
WIKI.logger.info(`Admin requested cancelling job ${args.id}...`)
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
throw new Error('ERR_FORBIDDEN')
}
WIKI.logger.info(`Admin requested cancelling job ${args.id}...`)
const result = await WIKI.db.knex('jobs') const result = await WIKI.db.knex('jobs')
.where('id', args.id) .where('id', args.id)
.del() .del()
@ -100,12 +166,18 @@ export default {
operation: generateSuccess('Cancelled job successfully.') operation: generateSuccess('Cancelled job successfully.')
} }
} catch (err) { } catch (err) {
WIKI.logger.warn(err) if (err.message !== 'ERR_FORBIDDEN') {
WIKI.logger.warn(err)
}
return generateError(err) return generateError(err)
} }
}, },
async checkForUpdates (obj, args, context) { async checkForUpdates (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['read:dashboard', 'manage:system'])) {
throw new Error('ERR_FORBIDDEN')
}
const renderJob = await WIKI.scheduler.addJob({ const renderJob = await WIKI.scheduler.addJob({
task: 'checkVersion', task: 'checkVersion',
maxRetries: 0, maxRetries: 0,
@ -119,15 +191,28 @@ export default {
latestDate: WIKI.config.update.versionDate latestDate: WIKI.config.update.versionDate
} }
} catch (err) { } catch (err) {
WIKI.logger.warn(err) if (err.message !== 'ERR_FORBIDDEN') {
WIKI.logger.warn(err)
}
return generateError(err) return generateError(err)
} }
}, },
async disconnectWS (obj, args, context) { async disconnectWS (obj, args, context) {
WIKI.servers.ws.disconnectSockets(true) try {
WIKI.logger.info('All active websocket connections have been terminated.') if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
return { throw new Error('ERR_FORBIDDEN')
operation: generateSuccess('All websocket connections closed successfully.') }
WIKI.servers.ws.disconnectSockets(true)
WIKI.logger.info('All active websocket connections have been terminated.')
return {
operation: generateSuccess('All websocket connections closed successfully.')
}
} catch (err) {
if (err.message !== 'ERR_FORBIDDEN') {
WIKI.logger.warn(err)
}
return generateError(err)
} }
}, },
async installExtension (obj, args, context) { async installExtension (obj, args, context) {
@ -143,6 +228,10 @@ export default {
}, },
async rebuildSearchIndex (obj, args, context) { async rebuildSearchIndex (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
throw new Error('ERR_FORBIDDEN')
}
await WIKI.scheduler.addJob({ await WIKI.scheduler.addJob({
task: 'rebuildSearchIndex', task: 'rebuildSearchIndex',
maxRetries: 0 maxRetries: 0
@ -155,8 +244,13 @@ export default {
} }
}, },
async retryJob (obj, args, context) { async retryJob (obj, args, context) {
WIKI.logger.info(`Admin requested rescheduling of job ${args.id}...`)
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
throw new Error('ERR_FORBIDDEN')
}
WIKI.logger.info(`Admin requested rescheduling of job ${args.id}...`)
const job = await WIKI.db.knex('jobHistory') const job = await WIKI.db.knex('jobHistory')
.where('id', args.id) .where('id', args.id)
.first() .first()
@ -188,34 +282,61 @@ export default {
} }
}, },
async updateSystemFlags (obj, args, context) { async updateSystemFlags (obj, args, context) {
WIKI.config.flags = { try {
...WIKI.config.flags, if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
...args.flags throw new Error('ERR_FORBIDDEN')
} }
await WIKI.configSvc.applyFlags()
await WIKI.configSvc.saveToDb(['flags']) WIKI.config.flags = {
return { ...WIKI.config.flags,
operation: generateSuccess('System Flags applied successfully') ...args.flags
}
await WIKI.configSvc.applyFlags()
await WIKI.configSvc.saveToDb(['flags'])
return {
operation: generateSuccess('System Flags applied successfully')
}
} catch (err) {
WIKI.logger.warn(err)
return generateError(err)
} }
}, },
async updateSystemSearch (obj, args, context) { async updateSystemSearch (obj, args, context) {
WIKI.config.search = { try {
...WIKI.config.search, if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
termHighlighting: args.termHighlighting ?? WIKI.config.search.termHighlighting, throw new Error('ERR_FORBIDDEN')
dictOverrides: args.dictOverrides ? JSON.parse(args.dictOverrides) : WIKI.config.search.dictOverrides }
}
// TODO: broadcast config update WIKI.config.search = {
await WIKI.configSvc.saveToDb(['search']) ...WIKI.config.search,
return { termHighlighting: args.termHighlighting ?? WIKI.config.search.termHighlighting,
operation: generateSuccess('System Search configuration applied successfully') dictOverrides: args.dictOverrides ? JSON.parse(args.dictOverrides) : WIKI.config.search.dictOverrides
}
// TODO: broadcast config update
await WIKI.configSvc.saveToDb(['search'])
return {
operation: generateSuccess('System Search configuration applied successfully')
}
} catch (err) {
WIKI.logger.warn(err)
return generateError(err)
} }
}, },
async updateSystemSecurity (obj, args, context) { async updateSystemSecurity (obj, args, context) {
WIKI.config.security = _.defaultsDeep(_.omit(args, ['__typename']), WIKI.config.security) try {
// TODO: broadcast config update if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
await WIKI.configSvc.saveToDb(['security']) throw new Error('ERR_FORBIDDEN')
return { }
operation: generateSuccess('System Security configuration applied successfully')
WIKI.config.security = _.defaultsDeep(_.omit(args, ['__typename']), WIKI.config.security)
// TODO: broadcast config update
await WIKI.configSvc.saveToDb(['security'])
return {
operation: generateSuccess('System Security configuration applied successfully')
}
} catch (err) {
WIKI.logger.warn(err)
return generateError(err)
} }
} }
}, },
@ -310,12 +431,16 @@ export default {
const total = await WIKI.db.pages.query().count('* as total').first() const total = await WIKI.db.pages.query().count('* as total').first()
return _.toSafeInteger(total.total) return _.toSafeInteger(total.total)
}, },
async tagsTotal () {
const total = await WIKI.db.tags.query().count('* as total').first()
return _.toSafeInteger(total.total)
},
async usersTotal () { async usersTotal () {
const total = await WIKI.db.users.query().count('* as total').first() const total = await WIKI.db.users.query().count('* as total').first()
return _.toSafeInteger(total.total) return _.toSafeInteger(total.total)
}, },
async tagsTotal () { async loginsPastDay () {
const total = await WIKI.db.tags.query().count('* as total').first() const total = await WIKI.db.users.query().count('* as total').whereRaw('"lastLoginAt" >= NOW() - INTERVAL \'1 DAY\'').first()
return _.toSafeInteger(total.total) return _.toSafeInteger(total.total)
} }
} }

@ -133,7 +133,7 @@ export default {
.first() .first()
if (!folder) { if (!folder) {
throw new Error('ERR_FOLDER_NOT_EXIST') throw new Error('ERR_INVALID_FOLDER')
} }
return { return {
@ -158,7 +158,7 @@ export default {
.first() .first()
if (!folder) { if (!folder) {
throw new Error('ERR_FOLDER_NOT_EXIST') throw new Error('ERR_INVALID_FOLDER')
} }
return { return {

@ -3,6 +3,7 @@ import _, { isNil } from 'lodash-es'
import path from 'node:path' import path from 'node:path'
import fs from 'fs-extra' import fs from 'fs-extra'
import { DateTime } from 'luxon' import { DateTime } from 'luxon'
import bcrypt from 'bcryptjs'
export default { export default {
Query: { Query: {
@ -10,6 +11,10 @@ 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'])) {
throw new Error('ERR_FORBIDDEN')
}
// -> Sanitize limit // -> Sanitize limit
let limit = args.pageSize ?? 20 let limit = args.pageSize ?? 20
if (limit < 1 || limit > 1000) { if (limit < 1 || limit > 1000) {
@ -39,6 +44,12 @@ export default {
* FETCH A SINGLE USER * FETCH A SINGLE USER
*/ */
async userById (obj, args, context, info) { 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'])) {
throw new Error('ERR_FORBIDDEN')
}
}
const usr = await WIKI.db.users.query().findById(args.id) const usr = await WIKI.db.users.query().findById(args.id)
if (!usr) { if (!usr) {
@ -69,31 +80,20 @@ export default {
return usr return usr
}, },
// async profile (obj, args, context, info) {
// if (!context.req.user || context.req.user.id < 1 || context.req.user.id === 2) {
// throw new WIKI.Error.AuthRequired()
// }
// const usr = await WIKI.db.users.query().findById(context.req.user.id)
// if (!usr.isActive) {
// throw new WIKI.Error.AuthAccountBanned()
// }
// const providerInfo = _.get(WIKI.auth.strategies, usr.providerKey, {})
// usr.providerName = providerInfo.displayName || 'Unknown'
// usr.lastLoginAt = usr.lastLoginAt || usr.updatedAt
// usr.password = ''
// usr.providerId = ''
// usr.tfaSecret = ''
// return usr
// },
async userDefaults (obj, args, context) { async userDefaults (obj, args, context) {
if (!WIKI.auth.checkAccess(context.req.user, ['read:users', 'write:users', 'manage:users'])) {
throw new Error('ERR_FORBIDDEN')
}
return WIKI.config.userDefaults return WIKI.config.userDefaults
}, },
async lastLogins (obj, args, context, info) { async lastLogins (obj, args, context, info) {
if (!WIKI.auth.checkAccess(context.req.user, ['read:dashboard', 'read:users', 'write:users', 'manage:users'])) {
throw new Error('ERR_FORBIDDEN')
}
return WIKI.db.users.query() return WIKI.db.users.query()
.select('id', 'name', 'lastLoginAt') .select('id', 'name', 'lastLoginAt')
.whereNotNull('lastLoginAt') .whereNotNull('lastLoginAt')
@ -114,8 +114,12 @@ export default {
} }
}, },
Mutation: { Mutation: {
async createUser (obj, args) { async createUser (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['write:users', 'manage:users'])) {
throw new Error('ERR_FORBIDDEN')
}
await WIKI.db.users.createNewUser({ ...args, isVerified: true }) await WIKI.db.users.createNewUser({ ...args, isVerified: true })
return { return {
@ -125,8 +129,12 @@ export default {
return generateError(err) return generateError(err)
} }
}, },
async deleteUser (obj, args) { async deleteUser (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:users'])) {
throw new Error('ERR_FORBIDDEN')
}
if (args.id <= 2) { if (args.id <= 2) {
throw new WIKI.Error.UserDeleteProtected() throw new WIKI.Error.UserDeleteProtected()
} }
@ -146,8 +154,12 @@ export default {
} }
} }
}, },
async updateUser (obj, args) { async updateUser (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:users'])) {
throw new Error('ERR_FORBIDDEN')
}
await WIKI.db.users.updateUser(args.id, args.patch) await WIKI.db.users.updateUser(args.id, args.patch)
return { return {
@ -157,8 +169,12 @@ export default {
return generateError(err) return generateError(err)
} }
}, },
async verifyUser (obj, args) { async verifyUser (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:users'])) {
throw new Error('ERR_FORBIDDEN')
}
await WIKI.db.users.query().patch({ isVerified: true }).findById(args.id) await WIKI.db.users.query().patch({ isVerified: true }).findById(args.id)
return { return {
@ -168,8 +184,12 @@ export default {
return generateError(err) return generateError(err)
} }
}, },
async activateUser (obj, args) { async activateUser (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:users'])) {
throw new Error('ERR_FORBIDDEN')
}
await WIKI.db.users.query().patch({ isActive: true }).findById(args.id) await WIKI.db.users.query().patch({ isActive: true }).findById(args.id)
return { return {
@ -179,8 +199,12 @@ export default {
return generateError(err) return generateError(err)
} }
}, },
async deactivateUser (obj, args) { async deactivateUser (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:users'])) {
throw new Error('ERR_FORBIDDEN')
}
if (args.id <= 2) { if (args.id <= 2) {
throw new Error('Cannot deactivate system accounts.') throw new Error('Cannot deactivate system accounts.')
} }
@ -196,8 +220,12 @@ export default {
return generateError(err) return generateError(err)
} }
}, },
async enableUserTFA (obj, args) { async enableUserTFA (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:users'])) {
throw new Error('ERR_FORBIDDEN')
}
await WIKI.db.users.query().patch({ tfaIsActive: true, tfaSecret: null }).findById(args.id) await WIKI.db.users.query().patch({ tfaIsActive: true, tfaSecret: null }).findById(args.id)
return { return {
@ -207,8 +235,12 @@ export default {
return generateError(err) return generateError(err)
} }
}, },
async disableUserTFA (obj, args) { async disableUserTFA (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:users'])) {
throw new Error('ERR_FORBIDDEN')
}
await WIKI.db.users.query().patch({ tfaIsActive: false, tfaSecret: null }).findById(args.id) await WIKI.db.users.query().patch({ tfaIsActive: false, tfaSecret: null }).findById(args.id)
return { return {
@ -220,13 +252,17 @@ export default {
}, },
async changeUserPassword (obj, args, context) { async changeUserPassword (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:users'])) {
throw new Error('ERR_FORBIDDEN')
}
if (args.newPassword?.length < 8) { if (args.newPassword?.length < 8) {
throw new Error('ERR_PASSWORD_TOO_SHORT') throw new Error('ERR_PASSWORD_TOO_SHORT')
} }
const usr = await WIKI.db.users.query().findById(args.id) const usr = await WIKI.db.users.query().findById(args.id)
if (!usr) { if (!usr) {
throw new Error('ERR_USER_NOT_FOUND') throw new Error('ERR_INVALID_USER')
} }
const localAuth = await WIKI.db.authentication.getStrategy('local') const localAuth = await WIKI.db.authentication.getStrategy('local')
@ -249,22 +285,22 @@ export default {
async updateProfile (obj, args, context) { async updateProfile (obj, args, context) {
try { try {
if (!context.req.user || context.req.user.id === WIKI.auth.guest.id) { if (!context.req.user || context.req.user.id === WIKI.auth.guest.id) {
throw new WIKI.Error.AuthRequired() throw new Error('ERR_NOT_AUTHENTICATED')
} }
const usr = await WIKI.db.users.query().findById(context.req.user.id) const usr = await WIKI.db.users.query().findById(context.req.user.id)
if (!usr.isActive) { if (!usr.isActive) {
throw new WIKI.Error.AuthAccountBanned() throw new Error('ERR_INACTIVE_USER')
} }
if (!usr.isVerified) { if (!usr.isVerified) {
throw new WIKI.Error.AuthAccountNotVerified() throw new Error('ERR_USER_NOT_VERIFIED')
} }
if (args.dateFormat && !['', 'DD/MM/YYYY', 'DD.MM.YYYY', 'MM/DD/YYYY', 'YYYY-MM-DD', 'YYYY/MM/DD'].includes(args.dateFormat)) { if (args.dateFormat && !['', 'DD/MM/YYYY', 'DD.MM.YYYY', 'MM/DD/YYYY', 'YYYY-MM-DD', 'YYYY/MM/DD'].includes(args.dateFormat)) {
throw new WIKI.Error.InputInvalid() throw new Error('ERR_INVALID_INPUT')
} }
if (args.appearance && !['site', 'light', 'dark'].includes(args.appearance)) { if (args.appearance && !['site', 'light', 'dark'].includes(args.appearance)) {
throw new WIKI.Error.InputInvalid() throw new Error('ERR_INVALID_INPUT')
} }
await WIKI.db.users.query().findById(usr.id).patch({ await WIKI.db.users.query().findById(usr.id).patch({
@ -292,47 +328,19 @@ export default {
return generateError(err) return generateError(err)
} }
}, },
// async changePassword (obj, args, context) {
// try {
// if (!context.req.user || context.req.user.id < 1 || context.req.user.id === 2) {
// throw new WIKI.Error.AuthRequired()
// }
// const usr = await WIKI.db.users.query().findById(context.req.user.id)
// if (!usr.isActive) {
// throw new WIKI.Error.AuthAccountBanned()
// }
// if (!usr.isVerified) {
// throw new WIKI.Error.AuthAccountNotVerified()
// }
// if (usr.providerKey !== 'local') {
// throw new WIKI.Error.AuthProviderInvalid()
// }
// try {
// await usr.verifyPassword(args.current)
// } catch (err) {
// throw new WIKI.Error.AuthPasswordInvalid()
// }
// await WIKI.db.users.updateUser({
// id: usr.id,
// newPassword: args.new
// })
// const newToken = await WIKI.db.users.refreshToken(usr)
// return {
// responseResult: generateSuccess('Password changed successfully'),
// jwt: newToken.token
// }
// } catch (err) {
// return generateError(err)
// }
// },
/** /**
* UPLOAD USER AVATAR * UPLOAD USER AVATAR
*/ */
async uploadUserAvatar (obj, args) { async uploadUserAvatar (obj, args, context) {
try { try {
if (!context.req.user || context.req.user.id === WIKI.auth.guest.id) {
throw new Error('ERR_NOT_AUTHENTICATED')
}
const usr = await WIKI.db.users.query().findById(context.req.user.id)
if (!usr) {
throw new Error('ERR_INVALID_USER')
}
const { filename, mimetype, createReadStream } = await args.image const { filename, mimetype, createReadStream } = await args.image
const lowercaseFilename = filename.toLowerCase() const lowercaseFilename = filename.toLowerCase()
WIKI.logger.debug(`Processing user ${args.id} avatar ${lowercaseFilename} of type ${mimetype}...`) WIKI.logger.debug(`Processing user ${args.id} avatar ${lowercaseFilename} of type ${mimetype}...`)
@ -358,7 +366,7 @@ export default {
height: 180 height: 180
}) })
// -> Set avatar flag for this user in the DB // -> Set avatar flag for this user in the DB
await WIKI.db.users.query().findById(args.id).patch({ hasAvatar: true }) usr.$query().patch({ hasAvatar: true })
// -> Save image data to DB // -> Save image data to DB
const imgBuffer = await fs.readFile(destPath) const imgBuffer = await fs.readFile(destPath)
await WIKI.db.knex('userAvatars').insert({ await WIKI.db.knex('userAvatars').insert({
@ -377,10 +385,18 @@ export default {
/** /**
* CLEAR USER AVATAR * CLEAR USER AVATAR
*/ */
async clearUserAvatar (obj, args) { async clearUserAvatar (obj, args, context) {
try { try {
if (!context.req.user || context.req.user.id === WIKI.auth.guest.id) {
throw new Error('ERR_NOT_AUTHENTICATED')
}
const usr = await WIKI.db.users.query().findById(context.req.user.id)
if (!usr) {
throw new Error('ERR_INVALID_USER')
}
WIKI.logger.debug(`Clearing user ${args.id} avatar...`) WIKI.logger.debug(`Clearing user ${args.id} avatar...`)
await WIKI.db.users.query().findById(args.id).patch({ hasAvatar: false }) usr.$query.patch({ hasAvatar: false })
await WIKI.db.knex('userAvatars').where({ id: args.id }).del() await WIKI.db.knex('userAvatars').where({ id: args.id }).del()
WIKI.logger.debug(`Cleared user ${args.id} avatar successfully.`) WIKI.logger.debug(`Cleared user ${args.id} avatar successfully.`)
return { return {
@ -395,19 +411,27 @@ export default {
* UPDATE USER DEFAULTS * UPDATE USER DEFAULTS
*/ */
async updateUserDefaults (obj, args, context) { async updateUserDefaults (obj, args, context) {
WIKI.config.userDefaults = { try {
timezone: args.timezone, if (!WIKI.auth.checkAccess(context.req.user, ['manage:users'])) {
dateFormat: args.dateFormat, throw new Error('ERR_FORBIDDEN')
timeFormat: args.timeFormat }
}
await WIKI.configSvc.saveToDb(['userDefaults']) WIKI.config.userDefaults = {
return { timezone: args.timezone,
operation: generateSuccess('User defaults saved successfully') dateFormat: args.dateFormat,
timeFormat: args.timeFormat
}
await WIKI.configSvc.saveToDb(['userDefaults'])
return {
operation: generateSuccess('User defaults saved successfully')
}
} catch (err) {
return generateError(err)
} }
} }
}, },
User: { User: {
async auth (usr) { async auth (usr, args, context) {
const authStrategies = await WIKI.db.authentication.getStrategies({ enabledOnly: true }) const authStrategies = await WIKI.db.authentication.getStrategies({ enabledOnly: true })
return _.transform(usr.auth, (result, value, key) => { return _.transform(usr.auth, (result, value, key) => {
const authStrategy = _.find(authStrategies, ['id', key]) const authStrategy = _.find(authStrategies, ['id', key])
@ -432,14 +456,4 @@ export default {
return usr.$relatedQuery('groups') return usr.$relatedQuery('groups')
} }
} }
// UserProfile: {
// async groups (usr) {
// const usrGroups = await usr.$relatedQuery('groups')
// return usrGroups.map(g => g.name)
// },
// async pagesTotal (usr) {
// const result = await WIKI.db.pages.query().count('* as total').where('creatorId', usr.id).first()
// return _.toSafeInteger(result.total)
// }
// }
} }

@ -7,19 +7,6 @@ extend type Query {
localeStrings(locale: String!): JSON localeStrings(locale: String!): JSON
} }
extend type Mutation {
downloadLocale(
locale: String!
): DefaultResponse
updateLocale(
locale: String!
autoUpdate: Boolean!
namespacing: Boolean!
namespaces: [String]!
): DefaultResponse
}
# ----------------------------------------------- # -----------------------------------------------
# TYPES # TYPES
# ----------------------------------------------- # -----------------------------------------------

@ -86,6 +86,7 @@ type SystemInfo {
isSchedulerHealthy: Boolean isSchedulerHealthy: Boolean
latestVersion: String latestVersion: String
latestVersionReleaseDate: Date latestVersionReleaseDate: Date
loginsPastDay: Int
nodeVersion: String nodeVersion: String
operatingSystem: String operatingSystem: String
pagesTotal: Int pagesTotal: Int

@ -227,7 +227,7 @@ export class Page extends Model {
static async createPage(opts) { static async createPage(opts) {
// -> Validate site // -> Validate site
if (!WIKI.sites[opts.siteId]) { if (!WIKI.sites[opts.siteId]) {
throw new Error('ERR_INVALID_SITE_ID') throw new Error('ERR_INVALID_SITE')
} }
// -> Remove trailing slash // -> Remove trailing slash

@ -77,7 +77,7 @@ export class Tree extends Model {
if (id) { if (id) {
const parent = await WIKI.db.knex('tree').where('id', id).first() const parent = await WIKI.db.knex('tree').where('id', id).first()
if (!parent) { if (!parent) {
throw new Error('ERR_NONEXISTING_FOLDER_ID') throw new Error('ERR_INVALID_FOLDER')
} }
return parent return parent
} else { } else {
@ -105,7 +105,7 @@ export class Tree extends Model {
siteId siteId
}) })
} else { } else {
throw new Error('ERR_NONEXISTING_FOLDER_PATH') throw new Error('ERR_INVALID_FOLDER')
} }
} }
} }
@ -150,7 +150,7 @@ export class Tree extends Model {
siteId, siteId,
tags, tags,
meta, meta,
navigationId: siteId, navigationId: siteId
}).returning('*') }).returning('*')
return pageEntry[0] return pageEntry[0]
@ -215,12 +215,12 @@ export class Tree extends Model {
static async createFolder ({ parentId, parentPath, pathName, title, locale, siteId }) { static async createFolder ({ parentId, parentPath, pathName, title, locale, siteId }) {
// Validate path name // Validate path name
if (!rePathName.test(pathName)) { if (!rePathName.test(pathName)) {
throw new Error('ERR_INVALID_PATH_NAME') throw new Error('ERR_INVALID_PATH')
} }
// Validate title // Validate title
if (!reTitle.test(title)) { if (!reTitle.test(title)) {
throw new Error('ERR_INVALID_TITLE') throw new Error('ERR_FOLDER_TITLE_INVALID')
} }
parentPath = encodeTreePath(parentPath) parentPath = encodeTreePath(parentPath)
@ -236,7 +236,7 @@ export class Tree extends Model {
if (parentId) { if (parentId) {
parent = await WIKI.db.knex('tree').where('id', parentId).first() parent = await WIKI.db.knex('tree').where('id', parentId).first()
if (!parent) { if (!parent) {
throw new Error('ERR_NONEXISTING_PARENT_ID') throw new Error('ERR_FOLDER_PARENT_INVALID')
} }
parentPath = parent.folderPath ? `${decodeFolderPath(parent.folderPath)}.${parent.fileName}` : parent.fileName parentPath = parent.folderPath ? `${decodeFolderPath(parent.folderPath)}.${parent.fileName}` : parent.fileName
} else if (parentPath) { } else if (parentPath) {
@ -254,7 +254,7 @@ export class Tree extends Model {
type: 'folder' type: 'folder'
}).first() }).first()
if (existingFolder) { if (existingFolder) {
throw new Error('ERR_FOLDER_ALREADY_EXISTS') throw new Error('ERR_FOLDER_DUPLICATE')
} }
// Ensure all ancestors exist // Ensure all ancestors exist
@ -338,17 +338,17 @@ export class Tree extends Model {
// Get folder // Get folder
const folder = await WIKI.db.knex('tree').where('id', folderId).first() const folder = await WIKI.db.knex('tree').where('id', folderId).first()
if (!folder) { if (!folder) {
throw new Error('ERR_NONEXISTING_FOLDER_ID') throw new Error('ERR_INVALID_FOLDER')
} }
// Validate path name // Validate path name
if (!rePathName.test(pathName)) { if (!rePathName.test(pathName)) {
throw new Error('ERR_INVALID_PATH_NAME') throw new Error('ERR_INVALID_PATH')
} }
// Validate title // Validate title
if (!reTitle.test(title)) { if (!reTitle.test(title)) {
throw new Error('ERR_INVALID_TITLE') throw new Error('ERR_FOLDER_TITLE_INVALID')
} }
WIKI.logger.debug(`Renaming folder ${folder.id} path to ${pathName}...`) WIKI.logger.debug(`Renaming folder ${folder.id} path to ${pathName}...`)
@ -364,7 +364,7 @@ export class Tree extends Model {
type: 'folder' type: 'folder'
}).first() }).first()
if (existingFolder) { if (existingFolder) {
throw new Error('ERR_FOLDER_ALREADY_EXISTS') throw new Error('ERR_FOLDER_DUPLICATE')
} }
// Build new paths // Build new paths
@ -406,7 +406,7 @@ export class Tree extends Model {
// Get folder // Get folder
const folder = await WIKI.db.knex('tree').where('id', folderId).first() const folder = await WIKI.db.knex('tree').where('id', folderId).first()
if (!folder) { if (!folder) {
throw new Error('ERR_NONEXISTING_FOLDER_ID') throw new Error('ERR_INVALID_FOLDER')
} }
const folderPath = folder.folderPath ? `${folder.folderPath}.${folder.fileName}` : folder.fileName const folderPath = folder.folderPath ? `${folder.folderPath}.${folder.fileName}` : folder.fileName
WIKI.logger.debug(`Deleting folder ${folder.id} at path ${folderPath}...`) WIKI.logger.debug(`Deleting folder ${folder.id} at path ${folderPath}...`)

@ -451,7 +451,7 @@ export class User extends Model {
}) })
if (strategyId !== expectedStrategyId) { if (strategyId !== expectedStrategyId) {
throw new Error('ERR_UNEXPECTED_STRATEGY_ID') throw new Error('ERR_INVALID_STRATEGY')
} }
if (user) { if (user) {
@ -461,11 +461,11 @@ export class User extends Model {
} }
return WIKI.db.users.afterLoginChecks(user, strategyId, context, { siteId, skipTFA: true }) return WIKI.db.users.afterLoginChecks(user, strategyId, context, { siteId, skipTFA: true })
} else { } else {
throw new Error('ERR_INCORRECT_TFA_TOKEN') throw new Error('ERR_TFA_INCORRECT_TOKEN')
} }
} }
} }
throw new Error('ERR_INVALID_TFA_REQUEST') throw new Error('ERR_TFA_INVALID_REQUEST')
} }
/** /**
@ -481,7 +481,7 @@ export class User extends Model {
}) })
if (strategyId !== expectedStrategyId) { if (strategyId !== expectedStrategyId) {
throw new Error('ERR_UNEXPECTED_STRATEGY_ID') throw new Error('ERR_INVALID_STRATEGY')
} }
if (user) { if (user) {
@ -503,12 +503,12 @@ export class User extends Model {
static async changePassword ({ strategyId, siteId, currentPassword, newPassword }, context) { static async changePassword ({ strategyId, siteId, currentPassword, newPassword }, context) {
const userId = context.req.user?.id const userId = context.req.user?.id
if (!userId) { if (!userId) {
throw new Error('ERR_USER_NOT_AUTHENTICATED') throw new Error('ERR_NOT_AUTHENTICATED')
} }
const user = await WIKI.db.users.query().findById(userId) const user = await WIKI.db.users.query().findById(userId)
if (!user) { if (!user) {
throw new Error('ERR_USER_NOT_FOUND') throw new Error('ERR_INVALID_USER')
} }
if (!newPassword || newPassword.length < 8) { if (!newPassword || newPassword.length < 8) {
@ -516,7 +516,7 @@ export class User extends Model {
} }
if (!user.auth[strategyId]?.password) { if (!user.auth[strategyId]?.password) {
throw new Error('ERR_UNEXPECTED_STRATEGY_ID') throw new Error('ERR_INVALID_STRATEGY')
} }
if (await bcrypt.compare(currentPassword, user.auth[strategyId].password) !== true) { if (await bcrypt.compare(currentPassword, user.auth[strategyId].password) !== true) {
@ -627,7 +627,7 @@ export class User extends Model {
// Check if email already exists // Check if email already exists
const usr = await WIKI.db.users.query().findOne({ email }) const usr = await WIKI.db.users.query().findOne({ email })
if (usr) { if (usr) {
throw new Error('ERR_ACCOUNT_ALREADY_EXIST') throw new Error('ERR_DUPLICATE_ACCOUNT_EMAIL')
} }
WIKI.logger.debug(`Creating new user account for ${email}...`) WIKI.logger.debug(`Creating new user account for ${email}...`)

@ -21,9 +21,9 @@ export default {
if (user) { if (user) {
const authStrategyData = user.auth[strategyId] const authStrategyData = user.auth[strategyId]
if (!authStrategyData) { if (!authStrategyData) {
throw new Error('ERR_INVALID_STRATEGY_ID') throw new Error('ERR_INVALID_STRATEGY')
} else if (await bcrypt.compare(uPassword, authStrategyData.password) !== true) { } else if (await bcrypt.compare(uPassword, authStrategyData.password) !== true) {
throw new Error('ERR_AUTH_FAILED') throw new Error('ERR_LOGIN_FAILED')
} else if (!user.isActive) { } else if (!user.isActive) {
throw new Error('ERR_INACTIVE_USER') throw new Error('ERR_INACTIVE_USER')
} else if (authStrategyData.restrictLogin) { } else if (authStrategyData.restrictLogin) {
@ -34,7 +34,7 @@ export default {
done(null, user) done(null, user)
} }
} else { } else {
throw new Error('ERR_AUTH_FAILED') throw new Error('ERR_LOGIN_FAILED')
} }
} catch (err) { } catch (err) {
done(err, null) done(err, null)

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="NyTLzsOvu2hiH2q16GFlAa" x1="-5.326" x2="17.563" y1="96.186" y2="50.024" gradientTransform="translate(20.942 -50.558)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0077d2"/><stop offset="1" stop-color="#0b59a2"/></linearGradient><path fill="url(#NyTLzsOvu2hiH2q16GFlAa)" d="M25.524,9.068L9.455,25.137c-1.435,1.435-1.435,3.761,0,5.196l11.212,11.212 c1.435,1.435,3.761,1.435,5.196,0l16.07-16.07C42.616,24.792,43,23.865,43,22.898V11.646C43,9.641,41.359,8,39.354,8H28.103 C27.135,8,26.208,8.384,25.524,9.068z"/><linearGradient id="NyTLzsOvu2hiH2q16GFlAb" x1="10.819" x2="34.706" y1="5.309" y2="29.196" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#32bdef"/><stop offset="1" stop-color="#1ea2e4"/></linearGradient><path fill="url(#NyTLzsOvu2hiH2q16GFlAb)" d="M21.837,6.049L6.057,21.83c-1.409,1.409-1.409,3.694,0,5.103l11.011,11.011 c1.409,1.409,3.694,1.409,5.103,0l15.781-15.781C38.623,21.491,39,20.58,39,19.63V8.581C39,6.611,37.389,5,35.419,5H24.37 C23.42,5,22.509,5.377,21.837,6.049z M33.629,12.351c-0.985,0-1.79-0.806-1.79-1.79s0.806-1.79,1.79-1.79s1.79,0.806,1.79,1.79 S34.614,12.351,33.629,12.351"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@ -125,6 +125,10 @@ q-layout.admin(view='hHh Lpr lff')
q-item-section(side) q-item-section(side)
//- TODO: Reflect site storage status //- TODO: Reflect site storage status
status-light(:color='true ? `positive` : `warning`', :pulse='false') status-light(:color='true ? `positive` : `warning`', :pulse='false')
q-item(:to='`/_admin/` + adminStore.currentSiteId + `/tags`', v-ripple, active-class='bg-primary text-white', disabled, v-if='flagsStore.experimental && (userStore.can(`manage:sites`))')
q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-tag.svg')
q-item-section {{ t('admin.tags.title') }}
q-item(:to='`/_admin/` + adminStore.currentSiteId + `/theme`', v-ripple, active-class='bg-primary text-white', v-if='userStore.can(`manage:sites`) || userStore.can(`manage:theme`)') q-item(:to='`/_admin/` + adminStore.currentSiteId + `/theme`', v-ripple, active-class='bg-primary text-white', v-if='userStore.can(`manage:sites`) || userStore.can(`manage:theme`)')
q-item-section(avatar) q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-paint-roller.svg') q-icon(name='img:/_assets/icons/fluent-paint-roller.svg')

@ -39,7 +39,7 @@ q-page.admin-dashboard
img(src='/_assets/icons/fluent-people.svg') img(src='/_assets/icons/fluent-people.svg')
div div
strong {{ t('admin.groups.title') }} strong {{ t('admin.groups.title') }}
small.text-positive {{adminStore.info.groupsTotal}} span {{adminStore.info.groupsTotal}}
q-separator q-separator
q-card-actions(align='right') q-card-actions(align='right')
q-btn( q-btn(
@ -85,6 +85,23 @@ q-page.admin-dashboard
:disable='!userStore.can(`manage:users`)' :disable='!userStore.can(`manage:users`)'
to='/_admin/users' to='/_admin/users'
) )
.col-12.col-sm-6.col-lg-3
q-card
q-card-section.admin-dashboard-card
img(src='/_assets/icons/fluent-tag.svg')
div
strong {{ t('admin.tags.title') }}
span {{adminStore.info.tagsTotal}}
q-separator
q-card-actions(align='right')
q-btn(
flat
color='primary'
icon='las la-tags'
:label='t(`common.actions.manage`)'
:disable='!userStore.can(`manage:sites`)'
:to='`/_admin/` + adminStore.currentSiteId + `/tags`'
)
.col-12.col-sm-6.col-lg-3 .col-12.col-sm-6.col-lg-3
q-card q-card
q-card-section.admin-dashboard-card q-card-section.admin-dashboard-card
@ -102,6 +119,10 @@ q-page.admin-dashboard
:disable='!userStore.can(`manage:sites`)' :disable='!userStore.can(`manage:sites`)'
:to='`/_admin/` + adminStore.currentSiteId + `/analytics`' :to='`/_admin/` + adminStore.currentSiteId + `/analytics`'
) )
.col-12.col-lg-9
q-card
q-card-section ---
.col-12 .col-12
q-banner.bg-positive.text-white( q-banner.bg-positive.text-white(
:class='adminStore.isVersionLatest ? `bg-positive` : `bg-warning`' :class='adminStore.isVersionLatest ? `bg-positive` : `bg-warning`'
@ -123,9 +144,6 @@ q-page.admin-dashboard
:label='t(`admin.system.title`)' :label='t(`admin.system.title`)'
to='/_admin/system' to='/_admin/system'
) )
.col-12
q-card
q-card-section ---
//- v-container(fluid, grid-list-lg) //- v-container(fluid, grid-list-lg)
//- v-layout(row, wrap) //- v-layout(row, wrap)

@ -13,6 +13,7 @@ export const useAdminStore = defineStore('admin', {
latestVersion: 'n/a', latestVersion: 'n/a',
groupsTotal: 0, groupsTotal: 0,
pagesTotal: 0, pagesTotal: 0,
tagsTotal: 0,
usersTotal: 0, usersTotal: 0,
loginsPastDay: 0, loginsPastDay: 0,
isApiEnabled: false, isApiEnabled: false,
@ -57,7 +58,9 @@ export const useAdminStore = defineStore('admin', {
apiState apiState
systemInfo { systemInfo {
groupsTotal groupsTotal
tagsTotal
usersTotal usersTotal
loginsPastDay
currentVersion currentVersion
latestVersion latestVersion
isMailConfigured isMailConfigured
@ -68,7 +71,9 @@ export const useAdminStore = defineStore('admin', {
fetchPolicy: 'network-only' fetchPolicy: 'network-only'
}) })
this.info.groupsTotal = clone(resp?.data?.systemInfo?.groupsTotal ?? 0) this.info.groupsTotal = clone(resp?.data?.systemInfo?.groupsTotal ?? 0)
this.info.tagsTotal = clone(resp?.data?.systemInfo?.tagsTotal ?? 0)
this.info.usersTotal = clone(resp?.data?.systemInfo?.usersTotal ?? 0) this.info.usersTotal = clone(resp?.data?.systemInfo?.usersTotal ?? 0)
this.info.loginsPastDay = clone(resp?.data?.systemInfo?.loginsPastDay ?? 0)
this.info.currentVersion = clone(resp?.data?.systemInfo?.currentVersion ?? 'n/a') this.info.currentVersion = clone(resp?.data?.systemInfo?.currentVersion ?? 'n/a')
this.info.latestVersion = clone(resp?.data?.systemInfo?.latestVersion ?? 'n/a') this.info.latestVersion = clone(resp?.data?.systemInfo?.latestVersion ?? 'n/a')
this.info.isApiEnabled = clone(resp?.data?.apiState ?? false) this.info.isApiEnabled = clone(resp?.data?.apiState ?? false)

Loading…
Cancel
Save