Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wiki-js
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
1
Issues
1
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
wiki-js
Commits
2a3e1400
Unverified
Commit
2a3e1400
authored
Oct 16, 2023
by
NGPixel
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: add permissions to resolvers
parent
88197c17
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
354 additions
and
163 deletions
+354
-163
auth.mjs
server/core/auth.mjs
+5
-1
authentication.mjs
server/graph/resolvers/authentication.mjs
+19
-7
block.mjs
server/graph/resolvers/block.mjs
+3
-0
hooks.mjs
server/graph/resolvers/hooks.mjs
+25
-5
localization.mjs
server/graph/resolvers/localization.mjs
+0
-43
mail.mjs
server/graph/resolvers/mail.mjs
+13
-1
page.mjs
server/graph/resolvers/page.mjs
+37
-20
site.mjs
server/graph/resolvers/site.mjs
+27
-3
system.mjs
server/graph/resolvers/system.mjs
+165
-40
tree.mjs
server/graph/resolvers/tree.mjs
+2
-2
user.mjs
server/graph/resolvers/user.mjs
+0
-0
localization.graphql
server/graph/schemas/localization.graphql
+0
-13
system.graphql
server/graph/schemas/system.graphql
+1
-0
pages.mjs
server/models/pages.mjs
+1
-1
tree.mjs
server/models/tree.mjs
+12
-12
users.mjs
server/models/users.mjs
+8
-8
authentication.mjs
server/modules/authentication/local/authentication.mjs
+3
-3
fluent-tag.svg
ux/public/_assets/icons/fluent-tag.svg
+2
-0
AdminLayout.vue
ux/src/layouts/AdminLayout.vue
+4
-0
AdminDashboard.vue
ux/src/pages/AdminDashboard.vue
+22
-4
admin.js
ux/src/stores/admin.js
+5
-0
No files found.
server/core/auth.mjs
View file @
2a3e1400
...
...
@@ -105,6 +105,8 @@ export default {
* @param {Express Next Callback} next
*/
authenticate
(
req
,
res
,
next
)
{
req
.
isAuthenticated
=
false
WIKI
.
auth
.
passport
.
authenticate
(
'jwt'
,
{
session
:
false
},
async
(
err
,
user
,
info
)
=>
{
if
(
err
)
{
return
next
()
}
let
mustRevalidate
=
false
...
...
@@ -170,6 +172,7 @@ export default {
WIKI
.
auth
.
guest
.
cacheExpiration
=
DateTime
.
utc
().
plus
({
minutes
:
1
})
}
req
.
user
=
WIKI
.
auth
.
guest
req
.
isAuthenticated
=
false
return
next
()
}
...
...
@@ -203,6 +206,7 @@ export default {
// JWT is valid
req
.
logIn
(
user
,
{
session
:
false
},
(
errc
)
=>
{
if
(
errc
)
{
return
next
(
errc
)
}
req
.
isAuthenticated
=
true
next
()
})
})(
req
,
res
,
next
)
...
...
@@ -223,7 +227,7 @@ export default {
return
true
}
// Check
Global
Permissions
// Check Permissions
if
(
_
.
intersection
(
userPermissions
,
permissions
).
length
<
1
)
{
return
false
}
...
...
server/graph/resolvers/authentication.mjs
View file @
2a3e1400
...
...
@@ -17,6 +17,10 @@ export default {
* List of API Keys
*/
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'
])
return
keys
.
map
(
k
=>
({
id
:
k
.
id
,
...
...
@@ -31,7 +35,11 @@ export default {
/**
* 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
},
/**
...
...
@@ -82,6 +90,10 @@ export default {
*/
async
createApiKey
(
obj
,
args
,
context
)
{
try
{
if
(
!
WIKI
.
auth
.
checkAccess
(
context
.
req
.
user
,
[
'manage:api'
]))
{
throw
new
Error
(
'ERR_FORBIDDEN'
)
}
const
key
=
await
WIKI
.
db
.
apiKeys
.
createNewKey
(
args
)
await
WIKI
.
auth
.
reloadApiKeys
()
WIKI
.
events
.
outbound
.
emit
(
'reloadApiKeys'
)
...
...
@@ -136,7 +148,7 @@ export default {
try
{
const
userId
=
context
.
req
.
user
?.
id
if
(
!
userId
)
{
throw
new
Error
(
'ERR_
USER_
NOT_AUTHENTICATED'
)
throw
new
Error
(
'ERR_NOT_AUTHENTICATED'
)
}
const
usr
=
await
WIKI
.
db
.
users
.
query
().
findById
(
userId
)
...
...
@@ -182,7 +194,7 @@ export default {
try
{
const
userId
=
context
.
req
.
user
?.
id
if
(
!
userId
)
{
throw
new
Error
(
'ERR_
USER_
NOT_AUTHENTICATED'
)
throw
new
Error
(
'ERR_NOT_AUTHENTICATED'
)
}
const
usr
=
await
WIKI
.
db
.
users
.
query
().
findById
(
userId
)
...
...
@@ -224,7 +236,7 @@ export default {
try
{
const
userId
=
context
.
req
.
user
?.
id
if
(
!
userId
)
{
throw
new
Error
(
'ERR_
USER_
NOT_AUTHENTICATED'
)
throw
new
Error
(
'ERR_NOT_AUTHENTICATED'
)
}
const
usr
=
await
WIKI
.
db
.
users
.
query
().
findById
(
userId
)
...
...
@@ -283,7 +295,7 @@ export default {
try
{
const
userId
=
context
.
req
.
user
?.
id
if
(
!
userId
)
{
throw
new
Error
(
'ERR_
USER_
NOT_AUTHENTICATED'
)
throw
new
Error
(
'ERR_NOT_AUTHENTICATED'
)
}
const
usr
=
await
WIKI
.
db
.
users
.
query
().
findById
(
userId
)
...
...
@@ -346,7 +358,7 @@ export default {
try
{
const
userId
=
context
.
req
.
user
?.
id
if
(
!
userId
)
{
throw
new
Error
(
'ERR_
USER_
NOT_AUTHENTICATED'
)
throw
new
Error
(
'ERR_NOT_AUTHENTICATED'
)
}
const
usr
=
await
WIKI
.
db
.
users
.
query
().
findById
(
userId
)
...
...
@@ -584,7 +596,7 @@ export default {
*/
async
revokeApiKey
(
obj
,
args
,
context
)
{
try
{
if
(
!
WIKI
.
auth
.
checkAccess
(
context
.
req
.
user
,
[
'manage:
system
'
]))
{
if
(
!
WIKI
.
auth
.
checkAccess
(
context
.
req
.
user
,
[
'manage:
api
'
]))
{
throw
new
Error
(
'ERR_FORBIDDEN'
)
}
...
...
server/graph/resolvers/block.mjs
View file @
2a3e1400
...
...
@@ -11,6 +11,9 @@ export default {
Mutation
:
{
async
setBlocksState
(
obj
,
args
,
context
)
{
try
{
if
(
!
WIKI
.
auth
.
checkAccess
(
context
.
req
.
user
,
[
'manage:blocks'
]))
{
throw
new
Error
(
'ERR_FORBIDDEN'
)
}
// TODO: update blocks state
return
{
operation
:
generateSuccess
(
'Blocks state updated successfully'
)
...
...
server/graph/resolvers/hooks.mjs
View file @
2a3e1400
...
...
@@ -3,10 +3,18 @@ import _ from 'lodash-es'
export
default
{
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'
)
},
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
)
}
},
...
...
@@ -14,8 +22,12 @@ export default {
/**
* CREATE HOOK
*/
async
createHook
(
obj
,
args
)
{
async
createHook
(
obj
,
args
,
context
)
{
try
{
if
(
!
WIKI
.
auth
.
checkAccess
(
context
.
req
.
user
,
[
'write:webhooks'
,
'manage:webhooks'
]))
{
throw
new
Error
(
'ERR_FORBIDDEN'
)
}
// -> Validate inputs
if
(
!
args
.
name
||
args
.
name
.
length
<
1
)
{
throw
new
WIKI
.
Error
.
Custom
(
'HookCreateInvalidName'
,
'Invalid Hook Name'
)
...
...
@@ -41,8 +53,12 @@ export default {
/**
* UPDATE HOOK
*/
async
updateHook
(
obj
,
args
)
{
async
updateHook
(
obj
,
args
,
context
)
{
try
{
if
(
!
WIKI
.
auth
.
checkAccess
(
context
.
req
.
user
,
[
'manage:webhooks'
]))
{
throw
new
Error
(
'ERR_FORBIDDEN'
)
}
// -> Load hook
const
hook
=
await
WIKI
.
db
.
hooks
.
query
().
findById
(
args
.
id
)
if
(
!
hook
)
{
...
...
@@ -72,8 +88,12 @@ export default {
/**
* DELETE HOOK
*/
async
deleteHook
(
obj
,
args
)
{
async
deleteHook
(
obj
,
args
,
context
)
{
try
{
if
(
!
WIKI
.
auth
.
checkAccess
(
context
.
req
.
user
,
[
'manage:webhooks'
]))
{
throw
new
Error
(
'ERR_FORBIDDEN'
)
}
await
WIKI
.
db
.
hooks
.
deleteHook
(
args
.
id
)
WIKI
.
logger
.
debug
(
`Hook
${
args
.
id
}
deleted successfully.`
)
return
{
...
...
server/graph/resolvers/localization.mjs
View file @
2a3e1400
import
{
generateError
,
generateSuccess
}
from
'../../helpers/graph.mjs'
import
_
from
'lodash-es'
export
default
{
Query
:
{
async
locales
(
obj
,
args
,
context
,
info
)
{
...
...
@@ -9,45 +6,5 @@ export default {
localeStrings
(
obj
,
args
,
context
,
info
)
{
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
)
}
}
}
}
server/graph/resolvers/mail.mjs
View file @
2a3e1400
...
...
@@ -3,7 +3,11 @@ import { generateError, generateSuccess } from '../../helpers/graph.mjs'
export
default
{
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
{
...
WIKI
.
config
.
mail
,
pass
:
WIKI
.
config
.
mail
.
pass
.
length
>
0
?
'********'
:
''
...
...
@@ -13,6 +17,10 @@ export default {
Mutation
:
{
async
sendMailTest
(
obj
,
args
,
context
)
{
try
{
if
(
!
WIKI
.
auth
.
checkAccess
(
context
.
req
.
user
,
[
'manage:system'
]))
{
throw
new
Error
(
'ERR_FORBIDDEN'
)
}
if
(
_
.
isEmpty
(
args
.
recipientEmail
)
||
args
.
recipientEmail
.
length
<
6
)
{
throw
new
WIKI
.
Error
.
MailInvalidRecipient
()
}
...
...
@@ -36,6 +44,10 @@ export default {
},
async
updateMailConfig
(
obj
,
args
,
context
)
{
try
{
if
(
!
WIKI
.
auth
.
checkAccess
(
context
.
req
.
user
,
[
'manage:system'
]))
{
throw
new
Error
(
'ERR_FORBIDDEN'
)
}
WIKI
.
config
.
mail
=
{
senderName
:
args
.
senderName
,
senderEmail
:
args
.
senderEmail
,
...
...
server/graph/resolvers/page.mjs
View file @
2a3e1400
import
_
from
'lodash-es'
import
{
generateError
,
generateSuccess
}
from
'../../helpers/graph.mjs'
import
{
parsePath
}
from
'../../helpers/page.mjs'
import
{
parsePath
}
from
'../../helpers/page.mjs'
import
tsquery
from
'pg-tsquery'
const
tsq
=
tsquery
()
...
...
@@ -247,12 +247,19 @@ export default {
siteId
:
args
.
siteId
})
if
(
page
)
{
return
{
...
page
,
...
page
.
config
,
scriptCss
:
page
.
scripts
?.
css
,
scriptJsLoad
:
page
.
scripts
?.
jsLoad
,
scriptJsUnload
:
page
.
scripts
?.
jsUnload
if
(
WIKI
.
auth
.
checkAccess
(
context
.
req
.
user
,
[
'read:pages'
],
{
path
:
page
.
path
,
locale
:
page
.
locale
}))
{
return
{
...
page
,
...
page
.
config
,
scriptCss
:
page
.
scripts
?.
css
,
scriptJsLoad
:
page
.
scripts
?.
jsLoad
,
scriptJsUnload
:
page
.
scripts
?.
jsUnload
}
}
else
{
throw
new
Error
(
'ERR_FORBIDDEN'
)
}
}
else
{
throw
new
Error
(
'ERR_PAGE_NOT_FOUND'
)
...
...
@@ -265,17 +272,17 @@ export default {
async
pathFromAlias
(
obj
,
args
,
context
,
info
)
{
const
alias
=
args
.
alias
?.
trim
()
if
(
!
alias
)
{
throw
new
Error
(
'ERR_ALIAS_MISSING'
)
throw
new
Error
(
'ERR_
PAGE_
ALIAS_MISSING'
)
}
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
({
alias
:
args
.
alias
,
siteId
:
args
.
siteId
}).
select
(
'id'
,
'path'
,
'locale'
)
if
(
!
page
)
{
throw
new
Error
(
'ERR_ALIAS_NOT_FOUND'
)
throw
new
Error
(
'ERR_
PAGE_
ALIAS_NOT_FOUND'
)
}
return
{
id
:
page
.
id
,
...
...
@@ -287,7 +294,7 @@ export default {
* FETCH TAGS
*/
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'
)
// TODO: check permissions
return
tags
...
...
@@ -670,19 +677,29 @@ export default {
}
},
Page
:
{
icon
(
obj
)
{
return
obj
.
icon
||
'las la-file-alt'
icon
(
page
)
{
return
page
.
icon
||
'las la-file-alt'
},
password
(
page
)
{
return
page
.
password
?
'********'
:
''
},
password
(
obj
)
{
return
obj
.
password
?
'********'
:
''
content
(
page
,
args
,
context
)
{
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
) {
// return WIKI.db.pages.relatedQuery('tags').for(
obj
.id)
// async tags (
page
) {
// return WIKI.db.pages.relatedQuery('tags').for(
page
.id)
// },
tocDepth
(
obj
)
{
tocDepth
(
page
)
{
return
{
min
:
obj
.
extra
?.
tocDepth
?.
min
??
1
,
max
:
obj
.
extra
?.
tocDepth
?.
max
??
2
min
:
page
.
extra
?.
tocDepth
?.
min
??
1
,
max
:
page
.
extra
?.
tocDepth
?.
max
??
2
}
}
// comments(pg) {
...
...
server/graph/resolvers/site.mjs
View file @
2a3e1400
...
...
@@ -49,8 +49,12 @@ export default {
/**
* CREATE SITE
*/
async
createSite
(
obj
,
args
)
{
async
createSite
(
obj
,
args
,
context
)
{
try
{
if
(
!
WIKI
.
auth
.
checkAccess
(
context
.
req
.
user
,
[
'write:sites'
,
'manage:sites'
]))
{
throw
new
Error
(
'ERR_FORBIDDEN'
)
}
// -> Validate inputs
if
(
!
args
.
hostname
||
args
.
hostname
.
length
<
1
||
!
/^
(\\
*
)
|
([
a-z0-9
\-
.:
]
+
)
$/
.
test
(
args
.
hostname
))
{
throw
new
WIKI
.
Error
.
Custom
(
'SiteCreateInvalidHostname'
,
'Invalid Site Hostname'
)
...
...
@@ -83,8 +87,12 @@ export default {
/**
* UPDATE SITE
*/
async
updateSite
(
obj
,
args
)
{
async
updateSite
(
obj
,
args
,
context
)
{
try
{
if
(
!
WIKI
.
auth
.
checkAccess
(
context
.
req
.
user
,
[
'manage:sites'
]))
{
throw
new
Error
(
'ERR_FORBIDDEN'
)
}
// -> Load site
const
site
=
await
WIKI
.
db
.
sites
.
query
().
findById
(
args
.
id
)
if
(
!
site
)
{
...
...
@@ -127,8 +135,12 @@ export default {
/**
* DELETE SITE
*/
async
deleteSite
(
obj
,
args
)
{
async
deleteSite
(
obj
,
args
,
context
)
{
try
{
if
(
!
WIKI
.
auth
.
checkAccess
(
context
.
req
.
user
,
[
'manage:sites'
]))
{
throw
new
Error
(
'ERR_FORBIDDEN'
)
}
// -> Ensure site isn't last one
const
sitesCount
=
await
WIKI
.
db
.
sites
.
query
().
count
(
'id'
).
first
()
if
(
sitesCount
?.
count
&&
_
.
toNumber
(
sitesCount
?.
count
)
<=
1
)
{
...
...
@@ -149,6 +161,10 @@ export default {
*/
async
uploadSiteLogo
(
obj
,
args
,
context
)
{
try
{
if
(
!
WIKI
.
auth
.
checkAccess
(
context
.
req
.
user
,
[
'manage:sites'
]))
{
throw
new
Error
(
'ERR_FORBIDDEN'
)
}
const
{
filename
,
mimetype
,
createReadStream
}
=
await
args
.
image
WIKI
.
logger
.
info
(
`Processing site logo
${
filename
}
of type
${
mimetype
}
...`
)
if
(
!
WIKI
.
extensions
.
ext
.
sharp
.
isInstalled
)
{
...
...
@@ -208,6 +224,10 @@ export default {
*/
async
uploadSiteFavicon
(
obj
,
args
,
context
)
{
try
{
if
(
!
WIKI
.
auth
.
checkAccess
(
context
.
req
.
user
,
[
'manage:sites'
]))
{
throw
new
Error
(
'ERR_FORBIDDEN'
)
}
const
{
filename
,
mimetype
,
createReadStream
}
=
await
args
.
image
WIKI
.
logger
.
info
(
`Processing site favicon
${
filename
}
of type
${
mimetype
}
...`
)
if
(
!
WIKI
.
extensions
.
ext
.
sharp
.
isInstalled
)
{
...
...
@@ -268,6 +288,10 @@ export default {
*/
async
uploadSiteLoginBg
(
obj
,
args
,
context
)
{
try
{
if
(
!
WIKI
.
auth
.
checkAccess
(
context
.
req
.
user
,
[
'manage:sites'
]))
{
throw
new
Error
(
'ERR_FORBIDDEN'
)
}
const
{
filename
,
mimetype
,
createReadStream
}
=
await
args
.
image
WIKI
.
logger
.
info
(
`Processing site login bg
${
filename
}
of type
${
mimetype
}
...`
)
if
(
!
WIKI
.
extensions
.
ext
.
sharp
.
isInstalled
)
{
...
...
server/graph/resolvers/system.mjs
View file @
2a3e1400
This diff is collapsed.
Click to expand it.
server/graph/resolvers/tree.mjs
View file @
2a3e1400
...
...
@@ -133,7 +133,7 @@ export default {
.
first
()
if
(
!
folder
)
{
throw
new
Error
(
'ERR_
FOLDER_NOT_EXIST
'
)
throw
new
Error
(
'ERR_
INVALID_FOLDER
'
)
}
return
{
...
...
@@ -158,7 +158,7 @@ export default {
.
first
()
if
(
!
folder
)
{
throw
new
Error
(
'ERR_
FOLDER_NOT_EXIST
'
)
throw
new
Error
(
'ERR_
INVALID_FOLDER
'
)
}
return
{
...
...
server/graph/resolvers/user.mjs
View file @
2a3e1400
This diff is collapsed.
Click to expand it.
server/graph/schemas/localization.graphql
View file @
2a3e1400
...
...
@@ -7,19 +7,6 @@ extend type Query {
localeStrings
(
locale
:
String
!):
JSON
}
extend
type
Mutation
{
downloadLocale
(
locale
:
String
!
):
DefaultResponse
updateLocale
(
locale
:
String
!
autoUpdate
:
Boolean
!
namespacing
:
Boolean
!
namespaces
:
[
String
]!
):
DefaultResponse
}
# -----------------------------------------------
# TYPES
# -----------------------------------------------
...
...
server/graph/schemas/system.graphql
View file @
2a3e1400
...
...
@@ -86,6 +86,7 @@ type SystemInfo {
isSchedulerHealthy
:
Boolean
latestVersion
:
String
latestVersionReleaseDate
:
Date
loginsPastDay
:
Int
nodeVersion
:
String
operatingSystem
:
String
pagesTotal
:
Int
...
...
server/models/pages.mjs
View file @
2a3e1400
...
...
@@ -227,7 +227,7 @@ export class Page extends Model {
static
async
createPage
(
opts
)
{
// -> Validate site
if
(
!
WIKI
.
sites
[
opts
.
siteId
])
{
throw
new
Error
(
'ERR_INVALID_SITE
_ID
'
)
throw
new
Error
(
'ERR_INVALID_SITE'
)
}
// -> Remove trailing slash
...
...
server/models/tree.mjs
View file @
2a3e1400
...
...
@@ -77,7 +77,7 @@ export class Tree extends Model {
if
(
id
)
{
const
parent
=
await
WIKI
.
db
.
knex
(
'tree'
).
where
(
'id'
,
id
).
first
()
if
(
!
parent
)
{
throw
new
Error
(
'ERR_
NONEXISTING_FOLDER_ID
'
)
throw
new
Error
(
'ERR_
INVALID_FOLDER
'
)
}
return
parent
}
else
{
...
...
@@ -105,7 +105,7 @@ export class Tree extends Model {
siteId
})
}
else
{
throw
new
Error
(
'ERR_
NONEXISTING_FOLDER_PATH
'
)
throw
new
Error
(
'ERR_
INVALID_FOLDER
'
)
}
}
}
...
...
@@ -150,7 +150,7 @@ export class Tree extends Model {
siteId
,
tags
,
meta
,
navigationId
:
siteId
,
navigationId
:
siteId
}).
returning
(
'*'
)
return
pageEntry
[
0
]
...
...
@@ -215,12 +215,12 @@ export class Tree extends Model {
static
async
createFolder
({
parentId
,
parentPath
,
pathName
,
title
,
locale
,
siteId
})
{
// Validate path name
if
(
!
rePathName
.
test
(
pathName
))
{
throw
new
Error
(
'ERR_INVALID_PATH
_NAME
'
)
throw
new
Error
(
'ERR_INVALID_PATH'
)
}
// Validate title
if
(
!
reTitle
.
test
(
title
))
{
throw
new
Error
(
'ERR_
INVALID_TITLE
'
)
throw
new
Error
(
'ERR_
FOLDER_TITLE_INVALID
'
)
}
parentPath
=
encodeTreePath
(
parentPath
)
...
...
@@ -236,7 +236,7 @@ export class Tree extends Model {
if
(
parentId
)
{
parent
=
await
WIKI
.
db
.
knex
(
'tree'
).
where
(
'id'
,
parentId
).
first
()
if
(
!
parent
)
{
throw
new
Error
(
'ERR_
NONEXISTING_PARENT_
ID'
)
throw
new
Error
(
'ERR_
FOLDER_PARENT_INVAL
ID'
)
}
parentPath
=
parent
.
folderPath
?
`
${
decodeFolderPath
(
parent
.
folderPath
)}
.
${
parent
.
fileName
}
`
:
parent
.
fileName
}
else
if
(
parentPath
)
{
...
...
@@ -254,7 +254,7 @@ export class Tree extends Model {
type
:
'folder'
}).
first
()
if
(
existingFolder
)
{
throw
new
Error
(
'ERR_FOLDER_
ALREADY_EXISTS
'
)
throw
new
Error
(
'ERR_FOLDER_
DUPLICATE
'
)
}
// Ensure all ancestors exist
...
...
@@ -338,17 +338,17 @@ export class Tree extends Model {
// Get folder
const
folder
=
await
WIKI
.
db
.
knex
(
'tree'
).
where
(
'id'
,
folderId
).
first
()
if
(
!
folder
)
{
throw
new
Error
(
'ERR_
NONEXISTING_FOLDER_ID
'
)
throw
new
Error
(
'ERR_
INVALID_FOLDER
'
)
}
// Validate path name
if
(
!
rePathName
.
test
(
pathName
))
{
throw
new
Error
(
'ERR_INVALID_PATH
_NAME
'
)
throw
new
Error
(
'ERR_INVALID_PATH'
)
}
// Validate 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
}
...`
)
...
...
@@ -364,7 +364,7 @@ export class Tree extends Model {
type
:
'folder'
}).
first
()
if
(
existingFolder
)
{
throw
new
Error
(
'ERR_FOLDER_
ALREADY_EXISTS
'
)
throw
new
Error
(
'ERR_FOLDER_
DUPLICATE
'
)
}
// Build new paths
...
...
@@ -406,7 +406,7 @@ export class Tree extends Model {
// Get folder
const
folder
=
await
WIKI
.
db
.
knex
(
'tree'
).
where
(
'id'
,
folderId
).
first
()
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
WIKI
.
logger
.
debug
(
`Deleting folder
${
folder
.
id
}
at path
${
folderPath
}
...`
)
...
...
server/models/users.mjs
View file @
2a3e1400
...
...
@@ -451,7 +451,7 @@ export class User extends Model {
})
if
(
strategyId
!==
expectedStrategyId
)
{
throw
new
Error
(
'ERR_
UNEXPECTED_STRATEGY_ID
'
)
throw
new
Error
(
'ERR_
INVALID_STRATEGY
'
)
}
if
(
user
)
{
...
...
@@ -461,11 +461,11 @@ export class User extends Model {
}
return
WIKI
.
db
.
users
.
afterLoginChecks
(
user
,
strategyId
,
context
,
{
siteId
,
skipTFA
:
true
})
}
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
)
{
throw
new
Error
(
'ERR_
UNEXPECTED_STRATEGY_ID
'
)
throw
new
Error
(
'ERR_
INVALID_STRATEGY
'
)
}
if
(
user
)
{
...
...
@@ -503,12 +503,12 @@ export class User extends Model {
static
async
changePassword
({
strategyId
,
siteId
,
currentPassword
,
newPassword
},
context
)
{
const
userId
=
context
.
req
.
user
?.
id
if
(
!
userId
)
{
throw
new
Error
(
'ERR_
USER_
NOT_AUTHENTICATED'
)
throw
new
Error
(
'ERR_NOT_AUTHENTICATED'
)
}
const
user
=
await
WIKI
.
db
.
users
.
query
().
findById
(
userId
)
if
(
!
user
)
{
throw
new
Error
(
'ERR_
USER_NOT_FOUND
'
)
throw
new
Error
(
'ERR_
INVALID_USER
'
)
}
if
(
!
newPassword
||
newPassword
.
length
<
8
)
{
...
...
@@ -516,7 +516,7 @@ export class User extends Model {
}
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
)
{
...
...
@@ -627,7 +627,7 @@ export class User extends Model {
// Check if email already exists
const
usr
=
await
WIKI
.
db
.
users
.
query
().
findOne
({
email
})
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
}
...`
)
...
...
server/modules/authentication/local/authentication.mjs
View file @
2a3e1400
...
...
@@ -21,9 +21,9 @@ export default {
if
(
user
)
{
const
authStrategyData
=
user
.
auth
[
strategyId
]
if
(
!
authStrategyData
)
{
throw
new
Error
(
'ERR_INVALID_STRATEGY
_ID
'
)
throw
new
Error
(
'ERR_INVALID_STRATEGY'
)
}
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
)
{
throw
new
Error
(
'ERR_INACTIVE_USER'
)
}
else
if
(
authStrategyData
.
restrictLogin
)
{
...
...
@@ -34,7 +34,7 @@ export default {
done
(
null
,
user
)
}
}
else
{
throw
new
Error
(
'ERR_
AUTH
_FAILED'
)
throw
new
Error
(
'ERR_
LOGIN
_FAILED'
)
}
}
catch
(
err
)
{
done
(
err
,
null
)
...
...
ux/public/_assets/icons/fluent-tag.svg
0 → 100644
View file @
2a3e1400
<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
ux/src/layouts/AdminLayout.vue
View file @
2a3e1400
...
...
@@ -125,6 +125,10 @@ q-layout.admin(view='hHh Lpr lff')
q-item-section(side)
//- TODO: Reflect site storage status
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-section(avatar)
q-icon(name='img:/_assets/icons/fluent-paint-roller.svg')
...
...
ux/src/pages/AdminDashboard.vue
View file @
2a3e1400
...
...
@@ -39,7 +39,7 @@ q-page.admin-dashboard
img(src='/_assets/icons/fluent-people.svg')
div
strong
{{
t
(
'admin.groups.title'
)
}}
s
mall.text-positive
{{
adminStore
.
info
.
groupsTotal
}}
s
pan
{{
adminStore
.
info
.
groupsTotal
}}
q-separator
q-card-actions(align='right')
q-btn(
...
...
@@ -88,6 +88,23 @@ q-page.admin-dashboard
.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
q-card
q-card-section.admin-dashboard-card
img(src='/_assets/icons/fluent-female-working-with-a-laptop.svg')
div
strong Logins
...
...
@@ -102,6 +119,10 @@ q-page.admin-dashboard
:disable='!userStore.can(`manage:sites`)'
:to='`/_admin/` + adminStore.currentSiteId + `/analytics`'
)
.col-12.col-lg-9
q-card
q-card-section ---
.col-12
q-banner.bg-positive.text-white(
:class='adminStore.isVersionLatest ? `bg-positive` : `bg-warning`'
...
...
@@ -123,9 +144,6 @@ q-page.admin-dashboard
:label='t(`admin.system.title`)'
to='/_admin/system'
)
.col-12
q-card
q-card-section ---
//- v-container(fluid, grid-list-lg)
//- v-layout(row, wrap)
...
...
ux/src/stores/admin.js
View file @
2a3e1400
...
...
@@ -13,6 +13,7 @@ export const useAdminStore = defineStore('admin', {
latestVersion
:
'n/a'
,
groupsTotal
:
0
,
pagesTotal
:
0
,
tagsTotal
:
0
,
usersTotal
:
0
,
loginsPastDay
:
0
,
isApiEnabled
:
false
,
...
...
@@ -57,7 +58,9 @@ export const useAdminStore = defineStore('admin', {
apiState
systemInfo {
groupsTotal
tagsTotal
usersTotal
loginsPastDay
currentVersion
latestVersion
isMailConfigured
...
...
@@ -68,7 +71,9 @@ export const useAdminStore = defineStore('admin', {
fetchPolicy
:
'network-only'
})
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
.
loginsPastDay
=
clone
(
resp
?.
data
?.
systemInfo
?.
loginsPastDay
??
0
)
this
.
info
.
currentVersion
=
clone
(
resp
?.
data
?.
systemInfo
?.
currentVersion
??
'n/a'
)
this
.
info
.
latestVersion
=
clone
(
resp
?.
data
?.
systemInfo
?.
latestVersion
??
'n/a'
)
this
.
info
.
isApiEnabled
=
clone
(
resp
?.
data
?.
apiState
??
false
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment