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
75eb2774
Commit
75eb2774
authored
Jan 06, 2019
by
Nicolas Giard
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: guest + user permissions
parent
aa57ea92
Hide whitespace changes
Inline
Side-by-side
Showing
33 changed files
with
463 additions
and
191 deletions
+463
-191
admin-api.vue
client/components/admin/admin-api.vue
+5
-5
admin-auth.vue
client/components/admin/admin-auth.vue
+5
-0
admin-dev.vue
client/components/admin/admin-dev.vue
+36
-5
admin-general.vue
client/components/admin/admin-general.vue
+7
-1
admin-groups-edit-permissions.vue
client/components/admin/admin-groups-edit-permissions.vue
+4
-4
admin-locale.vue
client/components/admin/admin-locale.vue
+2
-0
admin-logging.vue
client/components/admin/admin-logging.vue
+3
-2
admin-search.vue
client/components/admin/admin-search.vue
+1
-1
admin-storage.vue
client/components/admin/admin-storage.vue
+1
-1
editor.vue
client/components/editor.vue
+7
-3
editor-markdown.vue
client/components/editor/editor-markdown.vue
+1
-1
auth-mutation-save-strategies.gql
client/graph/admin/auth/auth-mutation-save-strategies.gql
+2
-2
app.scss
client/scss/app.scss
+1
-0
_unauthorized.scss
client/scss/pages/_unauthorized.scss
+81
-0
icon-delete-shield.svg
client/static/svg/icon-delete-shield.svg
+14
-0
icon-safety-float.svg
client/static/svg/icon-safety-float.svg
+33
-0
motif-diagonals.svg
client/static/svg/motif-diagonals.svg
+3
-0
page.vue
client/themes/default/components/page.vue
+2
-4
Dockerfile
dev/examples/Dockerfile
+0
-12
docker-compose.yml
dev/examples/docker-compose.yml
+50
-16
data.yml
server/app/data.yml
+0
-9
common.js
server/controllers/common.js
+41
-0
auth.js
server/core/auth.js
+84
-4
config.js
server/core/config.js
+0
-2
2.0.0-beta.1.js
server/db/migrations/2.0.0-beta.1.js
+25
-22
settings.js
server/db/seeds/settings.js
+0
-11
authentication.js
server/graph/resolvers/authentication.js
+7
-0
authentication.graphql
server/graph/schemas/authentication.graphql
+8
-1
master.js
server/master.js
+2
-3
auth.js
server/middlewares/auth.js
+0
-72
users.js
server/models/users.js
+13
-2
setup.js
server/setup.js
+12
-8
unauthorized.pug
server/views/unauthorized.pug
+13
-0
No files found.
client/components/admin/admin-api.vue
View file @
75eb2774
...
...
@@ -6,14 +6,14 @@
img(src='/svg/icon-rest-api.svg', alt='API', style='width: 80px;')
.admin-header-title
.headline.blue--text.text--darken-2 API
.subheading.grey--text Manage keys to access the API
.subheading.grey--text Manage keys to access the API
#[v-chip(label, color='primary', small).white--text coming soon]
v-spacer
v-btn(outline, color='grey', large, @click='refresh')
v-btn(outline, color='grey', large, @click='refresh'
, disabled
)
v-icon refresh
v-btn(color='green', d
ark
, depressed, large, @click='globalSwitch')
v-btn(color='green', d
isabled
, depressed, large, @click='globalSwitch')
v-icon(left) power_settings_new
| Enable API
v-btn(color='primary', depressed, large, @click='newKey')
v-btn(color='primary', depressed, large, @click='newKey'
, disabled
)
v-icon(left) add
| New API Key
v-card.mt-3
...
...
@@ -58,7 +58,7 @@
td
{{
props
.
item
.
updatedOn
}}
td: v-btn(icon): v-icon.grey--text.text--darken-1 more_horiz
template(slot='no-data')
v-alert.mt-3(icon='
warning', :value='true', outline
) No API have been generated yet.
v-alert.mt-3(icon='
info', :value='true', outline, color='info'
) No API have been generated yet.
.text-xs-center.py-2
v-pagination(v-model='pagination.page', :length='pages')
</
template
>
...
...
client/components/admin/admin-auth.vue
View file @
75eb2774
...
...
@@ -207,6 +207,11 @@ export default {
await
this
.
$apollo
.
mutate
({
mutation
:
strategiesSaveMutation
,
variables
:
{
config
:
{
audience
:
this
.
jwtAudience
,
tokenExpiration
:
this
.
jwtExpiration
,
tokenRenewal
:
this
.
jwtRenewablePeriod
},
strategies
:
this
.
strategies
.
map
(
str
=>
_
.
pick
(
str
,
[
'isEnabled'
,
'key'
,
...
...
client/components/admin/admin-dev.vue
View file @
75eb2774
...
...
@@ -7,8 +7,18 @@
.admin-header-title
.headline.primary--text Developer Tools
.subheading.grey--text ¯\_(ツ)_/¯
v-spacer
v-card.radius-7
v-card-text
.caption Enables extra dev options and removes many safeguards.
.caption.red--text Do not enable unless you know what you're doing!
v-switch.mt-1(
color='primary'
hide-details
label='Dev Mode'
)
v-card.mt-3
v-card.mt-3
.white.grey--text.text--darken-3
v-tabs(
v-model='selectedTab'
color='grey darken-2'
...
...
@@ -92,9 +102,8 @@ export default {
},
500
)
return
resp
},
query
:
''
,
response
:
null
,
variables
:
null
,
variables
:
'{}'
,
operationName
:
null
,
websocketConnectionParams
:
null
}),
...
...
@@ -103,6 +112,7 @@ export default {
graphiQLInstance
.
queryEditorComponent
.
editor
.
refresh
()
graphiQLInstance
.
variableEditorComponent
.
editor
.
refresh
()
graphiQLInstance
.
state
.
variableEditorOpen
=
true
graphiQLInstance
.
state
.
docExplorerOpen
=
true
},
renderVoyager
()
{
ReactDOM
.
render
(
...
...
@@ -120,7 +130,7 @@ export default {
<
style
lang=
'scss'
>
#graphiql
{
height
:
calc
(
100vh
-
2
3
0px
);
height
:
calc
(
100vh
-
2
7
0px
);
.topBar
{
background-color
:
mc
(
'grey'
,
'200'
);
...
...
@@ -136,10 +146,14 @@ export default {
background-color
:
initial
;
box-shadow
:
initial
;
}
.doc-explorer-title-bar
,
.history-title-bar
{
height
:
auto
;
}
}
#voyager
{
height
:
calc
(
100vh
-
2
5
0px
);
height
:
calc
(
100vh
-
2
7
0px
);
.title-area
{
display
:
none
;
...
...
@@ -147,5 +161,22 @@ export default {
.type-doc
{
margin-top
:
5px
;
}
.doc-navigation
{
>
span
{
overflow-y
:
hidden
;
display
:
block
;
}
min-height
:
40px
;
}
.contents
{
padding-bottom
:
0
;
color
:
#666
;
}
.type-info-popover
{
display
:
none
;
}
}
</
style
>
client/components/admin/admin-general.vue
View file @
75eb2774
...
...
@@ -38,6 +38,8 @@
:counter='50'
v-model='config.title'
prepend-icon='public'
hint='Displayed in the top bar and appended to all pages meta title.'
persistent-hint
)
v-divider
v-subheader SEO
...
...
@@ -48,6 +50,8 @@
:counter='255'
v-model='config.description'
prepend-icon='explore'
hint='Default description when none is provided for a page.'
persistent-hint
)
v-select.mt-2(
outline
...
...
@@ -57,7 +61,7 @@
v-model='config.robots'
prepend-icon='explore'
:return-object='false'
hint='Default: Index, Follow'
hint='Default: Index, Follow
. Can also be set on a per-page basis.
'
persistent-hint
)
v-divider
...
...
@@ -69,6 +73,8 @@
:items='analyticsServices'
v-model='config.analyticsService'
prepend-icon='timeline'
persistent-hint
hint='Automatically add tracking code for services like Google Analytics.'
)
v-text-field.mt-2(
v-if='config.analyticsService !== ``'
...
...
client/components/admin/admin-groups-edit-permissions.vue
View file @
75eb2774
...
...
@@ -60,14 +60,14 @@ export default {
},
{
permission
:
'write:pages'
,
hint
:
'Can
view and
create new pages, as specified in the Page Rules'
,
hint
:
'Can create new pages, as specified in the Page Rules'
,
warning
:
false
,
restrictedForSystem
:
false
,
disabled
:
false
},
{
permission
:
'manage:pages'
,
hint
:
'Can
view, create,
edit and move existing pages as specified in the Page Rules'
,
hint
:
'Can edit and move existing pages as specified in the Page Rules'
,
warning
:
false
,
restrictedForSystem
:
false
,
disabled
:
false
...
...
@@ -95,7 +95,7 @@ export default {
},
{
permission
:
'manage:assets'
,
hint
:
'Can edit and delete assets (such as images and files), as specified in the Page Rules'
,
hint
:
'Can edit and delete
existing
assets (such as images and files), as specified in the Page Rules'
,
warning
:
false
,
restrictedForSystem
:
false
,
disabled
:
false
...
...
@@ -116,7 +116,7 @@ export default {
},
{
permission
:
'manage:comments'
,
hint
:
'Can edit and delete comments, as specified in the Page Rules'
,
hint
:
'Can edit and delete
existing
comments, as specified in the Page Rules'
,
warning
:
false
,
restrictedForSystem
:
false
,
disabled
:
false
...
...
client/components/admin/admin-locale.vue
View file @
75eb2774
...
...
@@ -52,6 +52,8 @@
v-toolbar(color='primary', dark, dense, flat)
v-toolbar-title
.subheading
{{
$t
(
'admin:locale.namespacing'
)
}}
v-spacer
v-chip(label, color='white', small).primary--text coming soon
v-card-text
v-switch(
v-model='namespacing'
...
...
client/components/admin/admin-logging.vue
View file @
75eb2774
...
...
@@ -6,11 +6,11 @@
img(src='/svg/icon-registry-editor.svg', alt='Logging', style='width: 80px;')
.admin-header-title
.headline.primary--text Logging
.subheading.grey--text Configure the system logger(s)
.subheading.grey--text Configure the system logger(s)
#[v-chip(label, color='primary', small).white--text coming soon]
v-spacer
v-btn(outline, color='grey', @click='refresh', large)
v-icon refresh
v-btn(color='black', d
ark
, depressed, @click='toggleConsole', large)
v-btn(color='black', d
isabled
, depressed, @click='toggleConsole', large)
ConsoleLineIcon.mr-3
span Live Trail
v-btn(color='success', @click='save', depressed, large)
...
...
@@ -34,6 +34,7 @@
:label='logger.title'
color='primary'
hide-details
disabled
)
v-tab-item(v-for='(logger, n) in activeLoggers', :key='logger.key', :transition='false', :reverse-transition='false')
...
...
client/components/admin/admin-search.vue
View file @
75eb2774
...
...
@@ -6,7 +6,7 @@
img(src='/svg/icon-search.svg', alt='Search Engine', style='width: 80px;')
.admin-header-title
.headline.primary--text Search Engine
.subheading.grey--text Configure the search capabilities of your wiki
.subheading.grey--text Configure the search capabilities of your wiki
#[v-chip(label, color='primary', small).white--text coming soon]
v-spacer
v-btn(outline, color='grey', @click='refresh', large)
v-icon refresh
...
...
client/components/admin/admin-storage.vue
View file @
75eb2774
...
...
@@ -6,7 +6,7 @@
img(src='/svg/icon-cloud-storage.svg', alt='Storage', style='width: 80px;')
.admin-header-title
.headline.primary--text Storage
.subheading.grey--text Set backup and sync targets for your content
.subheading.grey--text Set backup and sync targets for your content
#[v-chip(label, color='primary', small).white--text coming soon]
v-spacer
v-btn(outline, color='grey', @click='refresh', large)
v-icon refresh
...
...
client/components/editor.vue
View file @
75eb2774
...
...
@@ -14,12 +14,12 @@
outline
color='blue'
@click.native.stop='openPropsModal'
:class='{ "is-icon": $vuetify.breakpoint.mdAndDown, "mx-0":
mode === `create`, "ml-0": mode !== `create`
}'
:class='{ "is-icon": $vuetify.breakpoint.mdAndDown, "mx-0":
!welcomeMode, "ml-0": !welcomeMode
}'
)
v-icon(color='blue', :left='$vuetify.breakpoint.lgAndUp') sort_by_alpha
span.white--text(v-if='$vuetify.breakpoint.lgAndUp')
{{
$t
(
'editor:page'
)
}}
v-btn(
v-if='
path !== `home`
'
v-if='
!welcomeMode
'
outline
color='red'
:class='{ "is-icon": $vuetify.breakpoint.mdAndDown }'
...
...
@@ -62,6 +62,7 @@ import editorStore from '@/store/editor'
WIKI
.
$store
.
registerModule
(
'editor'
,
editorStore
)
export
default
{
i18nOptions
:
{
namespaces
:
'editor'
},
components
:
{
AtomSpinner
,
editorCode
:
()
=>
import
(
/* webpackChunkName: "editor-code", webpackMode: "lazy" */
'./editor/editor-code.vue'
),
...
...
@@ -127,7 +128,8 @@ export default {
darkMode
:
get
(
'site/dark'
),
mode
:
get
(
'editor/mode'
),
notification
:
get
(
'notification'
),
notificationState
:
sync
(
'notification@isActive'
)
notificationState
:
sync
(
'notification@isActive'
),
welcomeMode
()
{
return
this
.
mode
===
`create`
&&
this
.
path
===
`home`
}
},
watch
:
{
currentEditor
(
newValue
,
oldValue
)
{
...
...
@@ -242,6 +244,8 @@ export default {
throw
new
Error
(
_
.
get
(
resp
,
'responseResult.message'
))
}
}
this
.
initContentParsed
=
this
.
$store
.
get
(
'editor/content'
)
}
catch
(
err
)
{
this
.
$store
.
commit
(
'showNotification'
,
{
message
:
err
.
message
,
...
...
client/components/editor/editor-markdown.vue
View file @
75eb2774
...
...
@@ -234,7 +234,7 @@ export default {
if
(
!
token
.
type
)
{
return
}
console
.
info
(
token
)
//
console.info(token)
},
/**
* Update scroll sync
...
...
client/graph/admin/auth/auth-mutation-save-strategies.gql
View file @
75eb2774
mutation
(
$strategies
:
[
AuthenticationStrategyInput
])
{
mutation
(
$strategies
:
[
AuthenticationStrategyInput
]
!,
$config
:
AuthenticationConfigInput
)
{
authentication
{
updateStrategies
(
strategies
:
$strategies
)
{
updateStrategies
(
strategies
:
$strategies
,
config
:
$config
)
{
responseResult
{
succeeded
errorCode
...
...
client/scss/app.scss
View file @
75eb2774
...
...
@@ -23,6 +23,7 @@
// @import 'node_modules/diff2html/dist/diff2html.min';
@import
'pages/new'
;
@import
'pages/unauthorized'
;
@import
'pages/welcome'
;
@import
'pages/error'
;
...
...
client/scss/pages/_unauthorized.scss
0 → 100644
View file @
75eb2774
.unauthorized
{
background
:
linear-gradient
(
to
bottom
,
darken
(
mc
(
'blue'
,
'900'
)
,
10%
)
0%
,
mc
(
'red'
,
'500'
)
100%
);
height
:
100%
;
display
:
flex
;
flex-direction
:
column
;
justify-content
:
center
;
align-items
:
center
;
color
:
mc
(
'grey'
,
'50'
);
&
:
:
before
{
content
:
''
;
display
:block
;
width
:
100%
;
height
:
100%
;
position
:
absolute
;
top
:
0
;
left
:
0
;
background-image
:
url('../static/svg/motif-diagonals.svg')
;
background-position
:
center
center
;
background-repeat
:
repeat
;
background-size
:
50px
;
z-index
:
0
;
opacity
:
.75
;
animation
:
onboardingBgReveal
50s
linear
infinite
;
@include
keyframes
(
onboardingBgReveal
)
{
0
%
{
background-position-y
:
0
;
}
100
%
{
background-position-y
:
-2000px
;
}
}
}
&
:
:
after
{
content
:
''
;
position
:
absolute
;
background-color
:
transparent
;
background-image
:
url('../static/svg/motif-overlay.svg')
;
background-attachment
:
fixed
;
background-size
:
cover
;
opacity
:
.5
;
top
:
0
;
left
:
0
;
width
:
100vw
;
height
:
100vh
;
}
&
-content
{
display
:
flex
;
flex-direction
:
column
;
justify-content
:
center
;
align-items
:
center
;
z-index
:
2
;
}
img
{
height
:
250px
;
margin-bottom
:
3rem
;
z-index
:
2
;
animation-duration
:
2s
;
@include
until
(
$tablet
)
{
height
:
200px
;
}
}
h1
{
font-size
:
1
.5rem
;
margin-bottom
:
1rem
;
z-index
:
2
;
}
h2
{
margin-bottom
:
3rem
;
z-index
:
2
;
}
.v-btn
{
z-index
:
2
;
}
}
client/static/svg/icon-delete-shield.svg
0 → 100644
View file @
75eb2774
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg
width=
"100%"
height=
"100%"
viewBox=
"0 0 128 128"
version=
"1.1"
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
xml:space=
"preserve"
xmlns:serif=
"http://www.serif.com/"
style=
"fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"
>
<g>
<g>
<path
d=
"M64,116c-0.5,0 -1,-0.1 -1.5,-0.4c-26,-15.4 -41.5,-42.6 -41.5,-72.7l0,-15.1c0,-1.3 0.8,-2.4 2,-2.8l40,-14.8c0.7,-0.2 1.4,-0.2 2.1,0l39.9,14.8c1.2,0.4 2,1.6 2,2.8l0,15.1c0,7.8 -1.1,15.6 -3.2,23.1c-0.4,1.6 -2.1,2.5 -3.7,2.1c-1.6,-0.4 -2.5,-2.1 -2.1,-3.7c2,-6.9 2.9,-14.1 2.9,-21.4l0,-13l-36.9,-13.8l-37,13.7l0,13c0,28 14.4,53.2 38.5,67.5c1.4,0.8 1.9,2.7 1.1,4.1c-0.6,1 -1.6,1.5 -2.6,1.5Z"
style=
"fill:#fff;fill-rule:nonzero;"
/>
<path
d=
"M64,116c-1,0 -2,-0.5 -2.6,-1.5c-0.8,-1.4 -0.4,-3.3 1.1,-4.1c3.2,-1.9 6.3,-4 9.2,-6.3c1.3,-1 3.2,-0.8 4.2,0.5c1,1.3 0.8,3.2 -0.5,4.2c-3.1,2.5 -6.4,4.8 -9.9,6.8c-0.5,0.3 -1,0.4 -1.5,0.4Z"
style=
"fill:#fff;fill-rule:nonzero;"
/>
<path
d=
"M64,101.6c-0.6,0 -1.3,-0.2 -1.8,-0.6c-15.9,-11.7 -26,-29 -28.6,-48.5c-0.2,-1.6 0.9,-3.1 2.6,-3.4c1.6,-0.2 3.1,0.9 3.4,2.6c2.3,17.1 10.9,32.3 24.4,43c15.9,-12.6 25,-31.4 25,-51.9l0,-4.6l-25,-9.2l-27,10c-1.6,0.6 -3.3,-0.2 -3.9,-1.8c-0.6,-1.6 0.2,-3.3 1.8,-3.9l28.1,-10.3c0.7,-0.2 1.4,-0.2 2.1,0l28,10.4c1.2,0.4 2,1.6 2,2.8l0,6.7c0,23.2 -10.6,44.4 -29.2,58.1c-0.6,0.4 -1.3,0.6 -1.9,0.6Z"
style=
"fill:#d0d4d8;fill-rule:nonzero;"
/>
</g>
<path
d=
"M64,25.8l0,72.8c17.7,-13.1 28,-33.4 28,-55.7l0,-6.7l-28,-10.4Z"
style=
"fill:#d0d4d8;fill-rule:nonzero;"
/>
<path
d=
"M89.857,82.858c7.805,-7.805 20.478,-7.805 28.284,0c7.805,7.805 7.805,20.479 0,28.284c-7.806,7.805 -20.479,7.805 -28.284,0c-7.806,-7.805 -7.806,-20.479 0,-28.284Z"
style=
"fill:#ff5576;"
/>
<path
d=
"M110.9,100l-14,0c-1.7,0 -3,-1.3 -3,-3c0,-1.7 1.3,-3 3,-3l14,0c1.7,0 3,1.3 3,3c0,1.7 -1.3,3 -3,3Z"
style=
"fill:#fff;fill-rule:nonzero;"
/>
</g>
</svg>
client/static/svg/icon-safety-float.svg
0 → 100644
View file @
75eb2774
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
version=
"1.1"
id=
"Layer_1"
x=
"0px"
y=
"0px"
viewBox=
"0 0 128 128"
style=
"enable-background:new 0 0 128 128;"
xml:space=
"preserve"
width=
"128px"
height=
"128px"
>
<path
style=
"fill:#FFFFFF;"
d=
"M64,14c-27.6,0-50,22.4-50,50s22.4,50,50,50s50-22.4,50-50S91.6,14,64,14z M64,89 c-13.8,0-25-11.2-25-25s11.2-25,25-25s25,11.2,25,25S77.8,89,64,89z"
/>
<g>
<path
style=
"fill:#FF5576;"
d=
"M14,64c0,6.5,1.2,12.7,3.5,18.4l22.7-10.6C39.4,69.3,39,66.7,39,64c0-9,4.7-16.8,11.8-21.2L40.2,20 C24.6,28.5,14,45,14,64z"
/>
</g>
<g>
<path
style=
"fill:#FF5576;"
d=
"M64,14c-6.5,0-12.7,1.2-18.4,3.5l10.6,22.7c2.4-0.8,5.1-1.2,7.8-1.2c9,0,16.8,4.7,21.2,11.8 L108,40.2C99.5,24.6,83,14,64,14z"
/>
</g>
<path
style=
"fill:#C3DBEA;"
d=
"M55,37.5L52.4,32c-1.9,0.7-3.7,1.6-5.4,2.6l2.5,5.5C51.2,39,53,38.2,55,37.5z"
/>
<g>
<path
style=
"fill:#FF5576;"
d=
"M64,89c-9,0-16.8-4.7-21.2-11.8L20,87.8c8.5,15.6,25,26.2,44,26.2c6.5,0,12.7-1.2,18.4-3.5 L71.8,87.8C69.3,88.6,66.7,89,64,89z"
/>
</g>
<g>
<path
style=
"fill:#FF5576;"
d=
"M114,64c0-6.5-1.2-12.7-3.5-18.4L87.8,56.2c0.8,2.4,1.2,5.1,1.2,7.8c0,9-4.7,16.8-11.8,21.2 L87.8,108C103.4,99.5,114,83,114,64z"
/>
</g>
<path
style=
"fill:#C3DBEA;"
d=
"M81.1,107.8c1.9-0.7,3.7-1.6,5.4-2.5L84,99.8c-1.7,1-3.6,1.8-5.4,2.5L81.1,107.8z"
/>
<path
style=
"fill:#FFFFFF;"
d=
"M21.5,55.7c-0.3,0-0.5,0-0.8-0.1c-1.6-0.4-2.6-2.1-2.1-3.7c2.5-9.5,7.9-17.9,15.6-24.2 c1.3-1.1,3.2-0.9,4.2,0.4c1.1,1.3,0.9,3.2-0.4,4.2c-6.7,5.5-11.4,12.8-13.6,21.1C24,54.8,22.8,55.7,21.5,55.7z"
/>
<path
style=
"fill:#FFFFFF;"
d=
"M20,67c-0.8,0-1.6-0.3-2.1-0.9c-0.1-0.1-0.3-0.3-0.4-0.4c-0.1-0.2-0.2-0.3-0.3-0.5 c-0.1-0.2-0.1-0.4-0.2-0.6c0-0.2-0.1-0.4-0.1-0.6c0-0.8,0.3-1.6,0.9-2.1c1.1-1.1,3.1-1.1,4.2,0c0.6,0.6,0.9,1.3,0.9,2.1 c0,0.8-0.3,1.6-0.9,2.1C21.6,66.7,20.8,67,20,67z"
/>
<path
style=
"fill:#DB3E64;"
d=
"M33,67c-1.7,0-3-1.3-3-3c0-18.7,15.3-34,34-34c1.7,0,3,1.3,3,3s-1.3,3-3,3c-2.7,0-5.3,2.6-7.8,4.2 c0,0-1.4-1.4-2.9-0.7c-0.9,0.4-1.9,2.9-2.5,3.2l-2.9,1.5C39.8,49.1,36,53.9,36,64C36,65.7,34.7,67,33,67z"
/>
<path
style=
"fill:#FF5576;"
d=
"M56.2,40.2L52.4,32c-1.9,0.7-3.7,1.5-5.4,2.6l3.8,8.2C52.5,41.7,54.3,40.9,56.2,40.2z"
/>
<path
style=
"fill:#DB3E64;"
d=
"M64,111c-1.7,0-3-1.3-3-3s1.3-3,3-3c22.6,0,41-18.4,41-41c0-1.7,1.3-3,3-3s3,1.3,3,3 c0,16.2-8.2,30.5-20.7,39c-1.2,0.8-1.2,4.3-2.4,5c-0.7,0.4-2.7-1.1-3.5-0.8c-1.1,0.5-0.9,2.9-2,3.3c-1.3,0.5-3.8-1.8-5.2-1.4 C73,110.3,68.6,111,64,111z"
/>
<path
style=
"fill:#FF5576;"
d=
"M82.4,110.5c1.9-0.7,3.7-1.6,5.4-2.5L84,99.8c-1.7,1-3.6,1.8-5.4,2.5L82.4,110.5z"
/>
<path
style=
"fill:#444B54;"
d=
"M111,64c0,16.8-8.9,31.6-22.2,39.9c-1.3,0.8-1.8,2.4-1.1,3.8l0,0c0.8,1.6,2.8,2.2,4.3,1.3 c15-9.4,25-26,25-45c0-5.7-0.9-11.3-2.6-16.4c-0.6-1.7-2.5-2.6-4.1-1.8l0,0c-1.4,0.6-2,2.2-1.6,3.7C110.2,54,111,58.9,111,64z"
/>
<path
style=
"fill:#444B54;"
d=
"M64,111c-16.8,0-31.6-8.9-39.9-22.2c-0.8-1.3-2.4-1.8-3.8-1.1l0,0c-1.6,0.8-2.2,2.8-1.3,4.3 c9.4,15,26,25,45,25c5.7,0,11.3-0.9,16.4-2.6c1.7-0.6,2.6-2.5,1.8-4.1l0,0c-0.6-1.4-2.2-2-3.7-1.6C74,110.2,69.1,111,64,111z"
/>
<path
style=
"fill:#444B54;"
d=
"M42,64c0-6.9,3.2-13,8.1-17.1c1.1-0.9,1.5-2.4,0.9-3.6l0,0c-0.8-1.8-3.1-2.3-4.6-1.1 C40.1,47.3,36,55.2,36,64c0,1.9,0.2,3.7,0.5,5.5c0.4,1.9,2.5,2.9,4.2,2.1l0,0c1.2-0.6,1.9-1.9,1.7-3.2C42.1,66.9,42,65.5,42,64z"
/>
<path
style=
"fill:#444B54;"
d=
"M64,42c6.9,0,13,3.2,17.1,8.1c0.9,1.1,2.4,1.5,3.6,0.9l0,0c1.8-0.8,2.3-3.1,1.1-4.6 C80.7,40.1,72.8,36,64,36c-1.9,0-3.7,0.2-5.5,0.5c-1.9,0.4-2.9,2.5-2.1,4.2l0,0c0.6,1.2,1.9,1.9,3.2,1.7C61.1,42.1,62.5,42,64,42z"
/>
<path
style=
"fill:#444B54;"
d=
"M64,86c-6.9,0-13-3.2-17.1-8.1c-0.9-1.1-2.4-1.5-3.6-0.9l0,0c-1.8,0.8-2.3,3.1-1.1,4.6 C47.3,87.9,55.2,92,64,92c1.9,0,3.7-0.2,5.5-0.5c1.9-0.4,2.9-2.5,2.1-4.2l0,0c-0.6-1.2-1.9-1.9-3.2-1.7C66.9,85.9,65.5,86,64,86z"
/>
<path
style=
"fill:#444B54;"
d=
"M86,64c0,6.9-3.2,13-8.1,17.1c-1.1,0.9-1.5,2.4-0.9,3.6l0,0c0.8,1.8,3.1,2.3,4.6,1.1 C87.9,80.7,92,72.8,92,64c0-1.9-0.2-3.7-0.5-5.5c-0.4-1.9-2.5-2.9-4.2-2.1l0,0c-1.2,0.6-1.9,1.9-1.7,3.2C85.9,61.1,86,62.5,86,64z"
/>
<path
style=
"fill:#444B54;"
d=
"M17,64c0-16.8,8.9-31.6,22.2-39.9c1.3-0.8,1.8-2.4,1.1-3.8l0,0c-0.8-1.6-2.8-2.2-4.3-1.3 c-15,9.4-25,26-25,45c0,5.7,0.9,11.3,2.6,16.4c0.6,1.7,2.5,2.6,4.1,1.8l0,0c1.4-0.6,2-2.2,1.6-3.7C17.8,74,17,69.1,17,64z"
/>
<path
style=
"fill:#444B54;"
d=
"M64,17c16.8,0,31.6,8.9,39.9,22.2c0.8,1.3,2.4,1.8,3.8,1.1l0,0c1.6-0.8,2.2-2.8,1.3-4.3 c-9.4-15-26-25-45-25c-5.7,0-11.3,0.9-16.4,2.6c-1.7,0.6-2.6,2.5-1.8,4.1l0,0c0.6,1.4,2.2,2,3.7,1.6C54,17.8,58.9,17,64,17z"
/>
</svg>
client/static/svg/motif-diagonals.svg
0 → 100644
View file @
75eb2774
<svg
width=
"100%"
height=
"100%"
viewBox=
"0 0 40 40"
xmlns=
"http://www.w3.org/2000/svg"
>
<path
d=
"M40,0l-40,40l0,-20l20,-20l20,0Zm0,40l0,-20l-20,20l20,0Z"
fill=
'#FFFFFF'
fill-opacity=
'0.03'
fill-rule=
'evenodd'
/>
</svg>
client/themes/default/components/page.vue
View file @
75eb2774
...
...
@@ -189,10 +189,8 @@ export default {
},
breadcrumbs
:
[
{
path
:
'/'
,
name
:
'Home'
},
{
path
:
'/universe'
,
name
:
'Universe'
},
{
path
:
'/universe/galaxy'
,
name
:
'Galaxy'
},
{
path
:
'/universe/galaxy/solar-system'
,
name
:
'Solar System'
},
{
path
:
'/universe/galaxy/solar-system/planet-earth'
,
name
:
'Planet Earth'
}
{
path
:
'/'
+
this
.
path
,
name
:
'Breadcrumb'
},
{
path
:
'/'
+
this
.
path
,
name
:
'Coming soon'
}
],
scrollStyle
:
{
vuescroll
:
{},
...
...
dev/examples/Dockerfile
deleted
100644 → 0
View file @
aa57ea92
FROM
requarks/wiki:latest
# Replace with your email address:
ENV
WIKI_ADMIN_EMAIL admin@example.com
WORKDIR
/var/wiki
# Replace your-config.yml with the path to your config file:
ADD
your-config.yml config.yml
EXPOSE
3000
ENTRYPOINT
[ "node", "server" ]
dev/examples/docker-compose.yml
View file @
75eb2774
version
:
'
3'
# -- DEV DOCKER-COMPOSE --
# -- DO NOT USE IN PRODUCTION! --
version
:
"
3"
services
:
wikidb
:
image
:
mongo
expose
:
-
'
27017'
command
:
'
--smallfiles
--logpath=/dev/null'
volumes
:
-
./data/mongo:/data/db
wikijs
:
image
:
'
requarks/wiki:latest'
links
:
-
wikidb
ports
:
-
'
80:3000'
redis
:
image
:
redis:4-alpine
logging
:
driver
:
"
none"
networks
:
-
wikinet
db
:
image
:
postgres:9-alpine
environment
:
WIKI_ADMIN_EMAIL
:
admin@example.com
POSTGRES_DB
:
wiki
POSTGRES_PASSWORD
:
wikijsrocks
POSTGRES_USER
:
wikijs
logging
:
driver
:
"
none"
volumes
:
-
./config.yml:/var/wiki/config.yml
-
db-data:/var/lib/postgresql/data
networks
:
-
wikinet
wiki
:
image
:
requarks/wiki:beta
depends_on
:
-
db
-
redis
environment
:
PORT
:
3000
# DO NOT CHANGE! Use ports below to specify listening port.
DB_TYPE
:
postgres
DB_HOST
:
db
DB_PORT
:
5432
DB_USER
:
wikijs
DB_PASS
:
wikijsrocks
DB_NAME
:
wiki
REDIS_HOST
:
redis
REDIS_PORT
:
6379
REDIS_DB
:
0
REDIS_PASS
:
'
'
networks
:
-
wikinet
ports
:
-
"
3000:3000"
# <-- replace with "80:3000" to listen on port 80 instead
networks
:
wikinet
:
volumes
:
db-data
:
server/app/data.yml
View file @
75eb2774
...
...
@@ -22,14 +22,12 @@ defaults:
db
:
0
password
:
null
# DB defaults
defaultEditor
:
'
markdown'
graphEndpoint
:
'
https://graph.requarks.io'
lang
:
code
:
en
autoUpdate
:
true
namespaces
:
[]
namespacing
:
false
public
:
false
telemetry
:
clientId
:
'
'
isEnabled
:
false
...
...
@@ -47,13 +45,6 @@ defaults:
maxAge
:
600
methods
:
'
GET,POST'
origin
:
true
configNamespaces
:
-
auth
-
features
-
logging
-
site
-
theme
-
uploads
localeNamespaces
:
-
admin
-
auth
...
...
server/controllers/common.js
View file @
75eb2774
...
...
@@ -6,6 +6,18 @@ const _ = require('lodash')
/* global WIKI */
/**
* Robots.txt
*/
router
.
get
(
'/robots.txt'
,
(
req
,
res
,
next
)
=>
{
res
.
type
(
'text/plain'
)
if
(
_
.
includes
(
WIKI
.
config
.
seo
.
robots
,
'noindex'
))
{
res
.
send
(
"User-agent: *
\
nDisallow: /"
)
}
else
{
res
.
status
(
200
).
end
()
}
})
/**
* Create/Edit document
*/
router
.
get
([
'/e'
,
'/e/*'
],
async
(
req
,
res
,
next
)
=>
{
...
...
@@ -17,12 +29,20 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
isPrivate
:
false
})
if
(
page
)
{
if
(
!
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'manage:pages'
],
pageArgs
))
{
return
res
.
render
(
'unauthorized'
,
{
action
:
'edit'
})
}
_
.
set
(
res
.
locals
,
'pageMeta.title'
,
`Edit
${
page
.
title
}
`
)
_
.
set
(
res
.
locals
,
'pageMeta.description'
,
page
.
description
)
page
.
mode
=
'update'
page
.
isPublished
=
(
page
.
isPublished
===
true
||
page
.
isPublished
===
1
)
?
'true'
:
'false'
page
.
content
=
Buffer
.
from
(
page
.
content
).
toString
(
'base64'
)
}
else
{
if
(
!
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'write:pages'
],
pageArgs
))
{
return
res
.
render
(
'unauthorized'
,
{
action
:
'create'
})
}
_
.
set
(
res
.
locals
,
'pageMeta.title'
,
`New Page`
)
page
=
{
path
:
pageArgs
.
path
,
...
...
@@ -56,6 +76,11 @@ router.get(['/p', '/p/*'], (req, res, next) => {
*/
router
.
get
([
'/h'
,
'/h/*'
],
async
(
req
,
res
,
next
)
=>
{
const
pageArgs
=
pageHelper
.
parsePath
(
req
.
path
)
if
(
!
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'read:pages'
],
pageArgs
))
{
return
res
.
render
(
'unauthorized'
,
{
action
:
'history'
})
}
const
page
=
await
WIKI
.
models
.
pages
.
getPageFromDb
({
path
:
pageArgs
.
path
,
locale
:
pageArgs
.
locale
,
...
...
@@ -76,6 +101,11 @@ router.get(['/h', '/h/*'], async (req, res, next) => {
*/
router
.
get
([
'/s'
,
'/s/*'
],
async
(
req
,
res
,
next
)
=>
{
const
pageArgs
=
pageHelper
.
parsePath
(
req
.
path
)
if
(
!
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'read:pages'
],
pageArgs
))
{
return
res
.
render
(
'unauthorized'
,
{
action
:
'source'
})
}
const
page
=
await
WIKI
.
models
.
pages
.
getPageFromDb
({
path
:
pageArgs
.
path
,
locale
:
pageArgs
.
locale
,
...
...
@@ -96,6 +126,15 @@ router.get(['/s', '/s/*'], async (req, res, next) => {
*/
router
.
get
(
'/*'
,
async
(
req
,
res
,
next
)
=>
{
const
pageArgs
=
pageHelper
.
parsePath
(
req
.
path
)
if
(
!
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'read:pages'
],
pageArgs
))
{
if
(
pageArgs
.
path
===
'home'
)
{
return
res
.
redirect
(
'/login'
)
}
else
{
return
res
.
render
(
'unauthorized'
,
{
action
:
'view'
})
}
}
const
page
=
await
WIKI
.
models
.
pages
.
getPage
({
path
:
pageArgs
.
path
,
locale
:
pageArgs
.
locale
,
...
...
@@ -108,8 +147,10 @@ router.get('/*', async (req, res, next) => {
const
sidebar
=
await
WIKI
.
models
.
navigation
.
getTree
({
cache
:
true
})
res
.
render
(
'page'
,
{
page
,
sidebar
})
}
else
if
(
pageArgs
.
path
===
'home'
)
{
_
.
set
(
res
.
locals
,
'pageMeta.title'
,
'Welcome'
)
res
.
render
(
'welcome'
)
}
else
{
_
.
set
(
res
.
locals
,
'pageMeta.title'
,
'Page Not Found'
)
res
.
status
(
404
).
render
(
'new'
,
{
pagePath
:
req
.
path
})
}
})
...
...
server/core/auth.js
View file @
75eb2774
...
...
@@ -3,6 +3,8 @@ const passportJWT = require('passport-jwt')
const
fs
=
require
(
'fs-extra'
)
const
_
=
require
(
'lodash'
)
const
path
=
require
(
'path'
)
const
jwt
=
require
(
'jsonwebtoken'
)
const
moment
=
require
(
'moment'
)
const
securityHelper
=
require
(
'../helpers/security'
)
...
...
@@ -10,11 +12,16 @@ const securityHelper = require('../helpers/security')
module
.
exports
=
{
strategies
:
{},
guest
:
{
cacheExpiration
:
moment
.
utc
().
subtract
(
1
,
'd'
)
},
/**
* Initialize the authentication module
*/
init
()
{
this
.
passport
=
passport
// Serialization user methods
passport
.
serializeUser
(
function
(
user
,
done
)
{
done
(
null
,
user
.
id
)
})
...
...
@@ -34,6 +41,10 @@ module.exports = {
return
this
},
/**
* Load authentication strategies
*/
async
activateStrategies
()
{
try
{
// Unload any active strategies
...
...
@@ -46,7 +57,7 @@ module.exports = {
passport
.
use
(
'jwt'
,
new
passportJWT
.
Strategy
({
jwtFromRequest
:
securityHelper
.
extractJWT
,
secretOrKey
:
WIKI
.
config
.
certs
.
public
,
audience
:
'urn:wiki.js'
,
// TODO: use value from admin
audience
:
WIKI
.
config
.
auth
.
audience
,
issuer
:
'urn:wiki.js'
},
(
jwtPayload
,
cb
)
=>
{
cb
(
null
,
jwtPayload
)
...
...
@@ -60,7 +71,7 @@ module.exports = {
const
strategy
=
require
(
`../modules/authentication/
${
stg
.
key
}
/authentication.js`
)
stg
.
config
.
callbackURL
=
`
${
WIKI
.
config
.
host
}
/login/
${
stg
.
key
}
/callback`
// TODO: config.host
stg
.
config
.
callbackURL
=
`
${
WIKI
.
config
.
host
}
/login/
${
stg
.
key
}
/callback`
strategy
.
init
(
passport
,
stg
.
config
)
fs
.
readFile
(
path
.
join
(
WIKI
.
ROOTPATH
,
`assets/svg/auth-icon-
${
strategy
.
key
}
.svg`
),
'utf8'
).
then
(
iconData
=>
{
...
...
@@ -79,5 +90,74 @@ module.exports = {
WIKI
.
logger
.
error
(
`Authentication Strategy: [ FAILED ]`
)
WIKI
.
logger
.
error
(
err
)
}
},
/**
* Authenticate current request
*
* @param {Express Request} req
* @param {Express Response} res
* @param {Express Next Callback} next
*/
authenticate
(
req
,
res
,
next
)
{
WIKI
.
auth
.
passport
.
authenticate
(
'jwt'
,
{
session
:
false
},
async
(
err
,
user
,
info
)
=>
{
if
(
err
)
{
return
next
()
}
// Expired but still valid within N days, just renew
if
(
info
instanceof
Error
&&
info
.
name
===
'TokenExpiredError'
&&
moment
().
subtract
(
14
,
'days'
).
isBefore
(
info
.
expiredAt
))
{
const
jwtPayload
=
jwt
.
decode
(
securityHelper
.
extractJWT
(
req
))
try
{
const
newToken
=
await
WIKI
.
models
.
users
.
refreshToken
(
jwtPayload
.
id
)
user
=
newToken
.
user
// Try headers, otherwise cookies for response
if
(
req
.
get
(
'content-type'
)
===
'application/json'
)
{
res
.
set
(
'new-jwt'
,
newToken
.
token
)
}
else
{
res
.
cookie
(
'jwt'
,
newToken
.
token
,
{
expires
:
moment
().
add
(
365
,
'days'
).
toDate
()
})
}
}
catch
(
err
)
{
return
next
()
}
}
// JWT is NOT valid, set as guest
if
(
!
user
)
{
if
(
WIKI
.
auth
.
guest
.
cacheExpiration
)
{
WIKI
.
auth
.
guest
=
await
WIKI
.
models
.
users
.
getGuestUser
()
WIKI
.
auth
.
guest
.
cacheExpiration
=
moment
.
utc
().
add
(
1
,
'm'
)
}
req
.
user
=
WIKI
.
auth
.
guest
return
next
()
}
// JWT is valid
req
.
logIn
(
user
,
{
session
:
false
},
(
err
)
=>
{
if
(
err
)
{
return
next
(
err
)
}
next
()
})
})(
req
,
res
,
next
)
},
/**
* Check if user has access to resource
*
* @param {User} user
* @param {Array<String>} permissions
* @param {String|Boolean} path
*/
checkAccess
(
user
,
permissions
=
[],
path
=
false
)
{
// System Admin
if
(
_
.
includes
(
user
.
permissions
,
'manage:system'
))
{
return
true
}
// Check Global Permissions
if
(
_
.
intersection
(
user
.
permissions
,
permissions
).
length
<
1
)
{
return
false
}
// Check Page Rules
return
false
}
}
server/core/config.js
View file @
75eb2774
...
...
@@ -52,8 +52,6 @@ module.exports = {
appconfig
.
port
=
process
.
env
.
PORT
||
80
}
appconfig
.
public
=
(
appconfig
.
public
===
true
||
_
.
toLower
(
appconfig
.
public
)
===
'true'
)
WIKI
.
config
=
appconfig
WIKI
.
data
=
appdata
WIKI
.
version
=
require
(
path
.
join
(
WIKI
.
ROOTPATH
,
'package.json'
)).
version
...
...
server/db/migrations/2.0.0.js
→
server/db/migrations/2.0.0
-beta.1
.js
View file @
75eb2774
exports
.
up
=
knex
=>
{
const
dbCompat
=
{
charset
:
(
WIKI
.
config
.
db
.
type
===
`mysql`
||
WIKI
.
config
.
db
.
type
===
`mariadb`
)
}
return
knex
.
schema
// =====================================
// MODEL TABLES
// =====================================
// ASSETS ------------------------------
.
createTable
(
'assets'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
increments
(
'id'
).
primary
()
table
.
string
(
'filename'
).
notNullable
()
table
.
string
(
'basename'
).
notNullable
()
...
...
@@ -19,7 +22,7 @@ exports.up = knex => {
})
// ASSET FOLDERS -----------------------
.
createTable
(
'assetFolders'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
increments
(
'id'
).
primary
()
table
.
string
(
'name'
).
notNullable
()
table
.
string
(
'slug'
).
notNullable
()
...
...
@@ -27,7 +30,7 @@ exports.up = knex => {
})
// AUTHENTICATION ----------------------
.
createTable
(
'authentication'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
string
(
'key'
).
notNullable
().
primary
()
table
.
boolean
(
'isEnabled'
).
notNullable
().
defaultTo
(
false
)
table
.
json
(
'config'
).
notNullable
()
...
...
@@ -37,7 +40,7 @@ exports.up = knex => {
})
// COMMENTS ----------------------------
.
createTable
(
'comments'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
increments
(
'id'
).
primary
()
table
.
text
(
'content'
).
notNullable
()
table
.
string
(
'createdAt'
).
notNullable
()
...
...
@@ -45,14 +48,14 @@ exports.up = knex => {
})
// EDITORS -----------------------------
.
createTable
(
'editors'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
string
(
'key'
).
notNullable
().
primary
()
table
.
boolean
(
'isEnabled'
).
notNullable
().
defaultTo
(
false
)
table
.
json
(
'config'
).
notNullable
()
})
// GROUPS ------------------------------
.
createTable
(
'groups'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
increments
(
'id'
).
primary
()
table
.
string
(
'name'
).
notNullable
()
table
.
json
(
'permissions'
).
notNullable
()
...
...
@@ -63,7 +66,7 @@ exports.up = knex => {
})
// LOCALES -----------------------------
.
createTable
(
'locales'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
string
(
'code'
,
2
).
notNullable
().
primary
()
table
.
json
(
'strings'
)
table
.
boolean
(
'isRTL'
).
notNullable
().
defaultTo
(
false
)
...
...
@@ -74,7 +77,7 @@ exports.up = knex => {
})
// LOGGING ----------------------------
.
createTable
(
'loggers'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
string
(
'key'
).
notNullable
().
primary
()
table
.
boolean
(
'isEnabled'
).
notNullable
().
defaultTo
(
false
)
table
.
string
(
'level'
).
notNullable
().
defaultTo
(
'warn'
)
...
...
@@ -82,13 +85,13 @@ exports.up = knex => {
})
// NAVIGATION ----------------------------
.
createTable
(
'navigation'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
string
(
'key'
).
notNullable
().
primary
()
table
.
json
(
'config'
)
})
// PAGE HISTORY ------------------------
.
createTable
(
'pageHistory'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
increments
(
'id'
).
primary
()
table
.
string
(
'path'
).
notNullable
()
table
.
string
(
'hash'
).
notNullable
()
...
...
@@ -104,7 +107,7 @@ exports.up = knex => {
})
// PAGES -------------------------------
.
createTable
(
'pages'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
increments
(
'id'
).
primary
()
table
.
string
(
'path'
).
notNullable
()
table
.
string
(
'hash'
).
notNullable
()
...
...
@@ -124,7 +127,7 @@ exports.up = knex => {
})
// PAGE TREE ---------------------------
.
createTable
(
'pageTree'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
increments
(
'id'
).
primary
()
table
.
string
(
'path'
).
notNullable
()
table
.
integer
(
'depth'
).
unsigned
().
notNullable
()
...
...
@@ -135,28 +138,28 @@ exports.up = knex => {
})
// RENDERERS ---------------------------
.
createTable
(
'renderers'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
string
(
'key'
).
notNullable
().
primary
()
table
.
boolean
(
'isEnabled'
).
notNullable
().
defaultTo
(
false
)
table
.
json
(
'config'
)
})
// SEARCH ------------------------------
.
createTable
(
'searchEngines'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
string
(
'key'
).
notNullable
().
primary
()
table
.
boolean
(
'isEnabled'
).
notNullable
().
defaultTo
(
false
)
table
.
json
(
'config'
)
})
// SETTINGS ----------------------------
.
createTable
(
'settings'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
string
(
'key'
).
notNullable
().
primary
()
table
.
json
(
'value'
)
table
.
string
(
'updatedAt'
).
notNullable
()
})
// STORAGE -----------------------------
.
createTable
(
'storage'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
string
(
'key'
).
notNullable
().
primary
()
table
.
boolean
(
'isEnabled'
).
notNullable
().
defaultTo
(
false
)
table
.
string
(
'mode'
,
[
'sync'
,
'push'
,
'pull'
]).
notNullable
().
defaultTo
(
'push'
)
...
...
@@ -164,7 +167,7 @@ exports.up = knex => {
})
// TAGS --------------------------------
.
createTable
(
'tags'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
increments
(
'id'
).
primary
()
table
.
string
(
'tag'
).
notNullable
().
unique
()
table
.
string
(
'title'
)
...
...
@@ -173,7 +176,7 @@ exports.up = knex => {
})
// USER KEYS ---------------------------
.
createTable
(
'userKeys'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
increments
(
'id'
).
primary
()
table
.
string
(
'kind'
).
notNullable
()
table
.
string
(
'token'
).
notNullable
()
...
...
@@ -182,7 +185,7 @@ exports.up = knex => {
})
// USERS -------------------------------
.
createTable
(
'users'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
increments
(
'id'
).
primary
()
table
.
string
(
'email'
).
notNullable
()
table
.
string
(
'name'
).
notNullable
()
...
...
@@ -205,21 +208,21 @@ exports.up = knex => {
// =====================================
// PAGE HISTORY TAGS ---------------------------
.
createTable
(
'pageHistoryTags'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
increments
(
'id'
).
primary
()
table
.
integer
(
'pageId'
).
unsigned
().
references
(
'id'
).
inTable
(
'pageHistory'
).
onDelete
(
'CASCADE'
)
table
.
integer
(
'tagId'
).
unsigned
().
references
(
'id'
).
inTable
(
'tags'
).
onDelete
(
'CASCADE'
)
})
// PAGE TAGS ---------------------------
.
createTable
(
'pageTags'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
increments
(
'id'
).
primary
()
table
.
integer
(
'pageId'
).
unsigned
().
references
(
'id'
).
inTable
(
'pages'
).
onDelete
(
'CASCADE'
)
table
.
integer
(
'tagId'
).
unsigned
().
references
(
'id'
).
inTable
(
'tags'
).
onDelete
(
'CASCADE'
)
})
// USER GROUPS -------------------------
.
createTable
(
'userGroups'
,
table
=>
{
table
.
charset
(
'utf8mb4'
)
if
(
dbCompat
.
charset
)
{
table
.
charset
(
'utf8mb4'
)
}
table
.
increments
(
'id'
).
primary
()
table
.
integer
(
'userId'
).
unsigned
().
references
(
'id'
).
inTable
(
'users'
).
onDelete
(
'CASCADE'
)
table
.
integer
(
'groupId'
).
unsigned
().
references
(
'id'
).
inTable
(
'groups'
).
onDelete
(
'CASCADE'
)
...
...
server/db/seeds/settings.js
deleted
100644 → 0
View file @
aa57ea92
exports
.
seed
=
(
knex
,
Promise
)
=>
{
return
knex
(
'settings'
)
.
insert
([
{
key
:
'auth'
,
value
:
{}
},
{
key
:
'features'
,
value
:
{}
},
{
key
:
'logging'
,
value
:
{}
},
{
key
:
'site'
,
value
:
{}
},
{
key
:
'theme'
,
value
:
{}
},
{
key
:
'uploads'
,
value
:
{}
}
])
}
server/graph/resolvers/authentication.js
View file @
75eb2774
...
...
@@ -70,6 +70,13 @@ module.exports = {
},
async
updateStrategies
(
obj
,
args
,
context
)
{
try
{
WIKI
.
config
.
auth
=
{
audience
:
_
.
get
(
args
,
'config.audience'
,
WIKI
.
config
.
auth
.
audience
),
tokenExpiration
:
_
.
get
(
args
,
'config.tokenExpiration'
,
WIKI
.
config
.
auth
.
tokenExpiration
),
tokenRenewal
:
_
.
get
(
args
,
'config.tokenRenewal'
,
WIKI
.
config
.
auth
.
tokenRenewal
)
}
await
WIKI
.
configSvc
.
saveToDb
([
'auth'
])
for
(
let
str
of
args
.
strategies
)
{
await
WIKI
.
models
.
authentication
.
query
().
patch
({
isEnabled
:
str
.
isEnabled
,
...
...
server/graph/schemas/authentication.graphql
View file @
75eb2774
...
...
@@ -43,7 +43,8 @@ type AuthenticationMutation {
):
AuthenticationRegisterResponse
updateStrategies
(
strategies
:
[
AuthenticationStrategyInput
]
strategies
:
[
AuthenticationStrategyInput
]!
config
:
AuthenticationConfigInput
):
DefaultResponse
@
auth
(
requires
:
[
"
manage
:
system
"
])
}
...
...
@@ -88,3 +89,9 @@ input AuthenticationStrategyInput {
domainWhitelist
:
[
String
]!
autoEnrollGroups
:
[
Int
]!
}
input
AuthenticationConfigInput
{
audience
:
String
!
tokenExpiration
:
String
!
tokenRenewal
:
String
!
}
server/master.js
View file @
75eb2774
...
...
@@ -67,7 +67,7 @@ module.exports = async () => {
app
.
use
(
cookieParser
())
app
.
use
(
WIKI
.
auth
.
passport
.
initialize
())
app
.
use
(
mw
.
auth
.
jwt
)
app
.
use
(
WIKI
.
auth
.
authenticate
)
// ----------------------------------------
// SEO
...
...
@@ -138,8 +138,7 @@ module.exports = async () => {
// ----------------------------------------
app
.
use
(
'/'
,
ctrl
.
auth
)
app
.
use
(
'/'
,
mw
.
auth
.
checkPath
,
ctrl
.
common
)
app
.
use
(
'/'
,
ctrl
.
common
)
// ----------------------------------------
// Error handling
...
...
server/middlewares/auth.js
deleted
100644 → 0
View file @
aa57ea92
const
jwt
=
require
(
'jsonwebtoken'
)
const
moment
=
require
(
'moment'
)
const
securityHelper
=
require
(
'../helpers/security'
)
/* global WIKI */
/**
* Authentication middleware
*/
module
.
exports
=
{
jwt
(
req
,
res
,
next
)
{
WIKI
.
auth
.
passport
.
authenticate
(
'jwt'
,
{
session
:
false
},
async
(
err
,
user
,
info
)
=>
{
if
(
err
)
{
return
next
()
}
// Expired but still valid within 7 days, just renew
if
(
info
instanceof
Error
&&
info
.
name
===
'TokenExpiredError'
&&
moment
().
subtract
(
14
,
'days'
).
isBefore
(
info
.
expiredAt
))
{
const
jwtPayload
=
jwt
.
decode
(
securityHelper
.
extractJWT
(
req
))
try
{
const
newToken
=
await
WIKI
.
models
.
users
.
refreshToken
(
jwtPayload
.
id
)
user
=
newToken
.
user
// Try headers, otherwise cookies for response
if
(
req
.
get
(
'content-type'
)
===
'application/json'
)
{
res
.
set
(
'new-jwt'
,
newToken
.
token
)
}
else
{
res
.
cookie
(
'jwt'
,
newToken
.
token
,
{
expires
:
moment
().
add
(
365
,
'days'
).
toDate
()
})
}
}
catch
(
err
)
{
return
next
()
}
}
// JWT is NOT valid
if
(
!
user
)
{
return
next
()
}
// JWT is valid
req
.
logIn
(
user
,
{
session
:
false
},
(
err
)
=>
{
if
(
err
)
{
return
next
(
err
)
}
next
()
})
})(
req
,
res
,
next
)
},
checkPath
(
req
,
res
,
next
)
{
// Is user authenticated ?
if
(
!
req
.
isAuthenticated
())
{
if
(
WIKI
.
config
.
public
!==
true
)
{
return
res
.
redirect
(
'/login'
)
}
else
{
// req.user = rights.guest
res
.
locals
.
isGuest
=
true
}
}
else
{
res
.
locals
.
isGuest
=
false
}
// Check permissions
// res.locals.rights = rights.check(req)
// if (!res.locals.rights.read) {
// return res.render('error-forbidden')
// }
// Expose user data
res
.
locals
.
user
=
req
.
user
return
next
()
}
}
server/models/users.js
View file @
75eb2774
...
...
@@ -138,6 +138,11 @@ module.exports = class User extends Model {
return
(
result
&&
_
.
has
(
result
,
'delta'
)
&&
result
.
delta
===
0
)
}
async
getPermissions
()
{
const
permissions
=
await
this
.
$relatedQuery
(
'groups'
).
select
(
'permissions'
).
pluck
(
'permissions'
)
this
.
permissions
=
_
.
uniq
(
_
.
flatten
(
permissions
))
}
static
async
processProfile
(
profile
)
{
let
primaryEmail
=
''
if
(
_
.
isArray
(
profile
.
emails
))
{
...
...
@@ -262,8 +267,8 @@ module.exports = class User extends Model {
passphrase
:
WIKI
.
config
.
sessionSecret
},
{
algorithm
:
'RS256'
,
expiresIn
:
'30m'
,
audience
:
'urn:wiki.js'
,
// TODO: use value from admin
expiresIn
:
WIKI
.
config
.
auth
.
tokenExpiration
,
audience
:
WIKI
.
config
.
auth
.
audience
,
issuer
:
'urn:wiki.js'
}),
user
...
...
@@ -391,4 +396,10 @@ module.exports = class User extends Model {
throw
new
WIKI
.
Error
.
AuthRegistrationDisabled
()
}
}
static
async
getGuestUser
()
{
let
user
=
await
WIKI
.
models
.
users
.
query
().
findById
(
2
)
user
.
getPermissions
()
return
user
}
}
server/setup.js
View file @
75eb2774
...
...
@@ -104,8 +104,12 @@ module.exports = () => {
await
fs
.
ensureDir
(
path
.
join
(
dataPath
,
'uploads'
))
// Set config
_
.
set
(
WIKI
.
config
,
'auth'
,
{
audience
:
'urn:wiki.js'
,
tokenExpiration
:
'30m'
,
tokenRenewal
:
'14d'
})
_
.
set
(
WIKI
.
config
,
'company'
,
''
)
_
.
set
(
WIKI
.
config
,
'defaultEditor'
,
'markdown'
)
_
.
set
(
WIKI
.
config
,
'features'
,
{
featurePageRatings
:
true
,
featurePageComments
:
true
,
...
...
@@ -136,7 +140,6 @@ module.exports = () => {
dkimKeySelector
:
''
,
dkimPrivateKey
:
''
})
_
.
set
(
WIKI
.
config
,
'public'
,
false
)
_
.
set
(
WIKI
.
config
,
'seo'
,
{
description
:
''
,
robots
:
[
'index'
,
'follow'
],
...
...
@@ -145,7 +148,7 @@ module.exports = () => {
})
_
.
set
(
WIKI
.
config
,
'sessionSecret'
,
(
await
crypto
.
randomBytesAsync
(
32
)).
toString
(
'hex'
))
_
.
set
(
WIKI
.
config
,
'telemetry'
,
{
isEnabled
:
req
.
body
.
telemetry
===
'true'
,
isEnabled
:
req
.
body
.
telemetry
===
true
,
clientId
:
WIKI
.
telemetry
.
cid
})
_
.
set
(
WIKI
.
config
,
'theming'
,
{
...
...
@@ -179,16 +182,15 @@ module.exports = () => {
// Save config to DB
WIKI
.
logger
.
info
(
'Persisting config to DB...'
)
await
WIKI
.
configSvc
.
saveToDb
([
'auth'
,
'certs'
,
'company'
,
'defaultEditor'
,
'features'
,
'graphEndpoint'
,
'host'
,
'lang'
,
'logo'
,
'mail'
,
'public'
,
'seo'
,
'sessionSecret'
,
'telemetry'
,
...
...
@@ -389,8 +391,10 @@ module.exports = () => {
WIKI
.
server
.
on
(
'listening'
,
()
=>
{
WIKI
.
logger
.
info
(
'HTTP Server: [ RUNNING ]'
)
WIKI
.
logger
.
info
(
'========================================'
)
WIKI
.
logger
.
info
(
`Browse to http://localhost:
${
WIKI
.
config
.
port
}
/`
)
WIKI
.
logger
.
info
(
'========================================'
)
WIKI
.
logger
.
info
(
'🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻'
)
WIKI
.
logger
.
info
(
''
)
WIKI
.
logger
.
info
(
`Browse to http://localhost:
${
WIKI
.
config
.
port
}
/ to complete setup!`
)
WIKI
.
logger
.
info
(
''
)
WIKI
.
logger
.
info
(
'🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺'
)
})
}
server/views/unauthorized.pug
0 → 100644
View file @
75eb2774
extends master.pug
block body
#root.is-fullscreen
v-app
.unauthorized
.unauthorized-content
img.animated.fadeIn(src='/svg/icon-delete-shield.svg', alt='Unauthorized')
.headline= t('unauthorized.title')
.subheading.mt-3= t('unauthorized.action.' + action)
v-btn.mt-5(color='red lighten-4', href='javascript:window.history.go(-1);', large, outline)
v-icon(left) arrow_back
span= t('unauthorized.goback')
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