Unverified Commit 2a3e1400 authored by NGPixel's avatar NGPixel

fix: add permissions to resolvers

parent 88197c17
...@@ -105,6 +105,8 @@ export default { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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' ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 {
......
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 { ...@@ -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' ...@@ -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 { ...@@ -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 { ...@@ -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,
......
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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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) {
......
...@@ -133,7 +133,7 @@ export default { ...@@ -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 { ...@@ -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 {
......
...@@ -7,19 +7,6 @@ extend type Query { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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)
......
<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>
\ No newline at end of file
...@@ -125,6 +125,10 @@ q-layout.admin(view='hHh Lpr lff') ...@@ -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 ...@@ -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(
...@@ -88,6 +88,23 @@ q-page.admin-dashboard ...@@ -88,6 +88,23 @@ q-page.admin-dashboard
.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
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
q-card
q-card-section.admin-dashboard-card
img(src='/_assets/icons/fluent-female-working-with-a-laptop.svg') img(src='/_assets/icons/fluent-female-working-with-a-laptop.svg')
div div
strong Logins strong Logins
...@@ -102,6 +119,10 @@ q-page.admin-dashboard ...@@ -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 ...@@ -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', { ...@@ -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', { ...@@ -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', { ...@@ -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)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment