feat: guest + user permissions
This commit is contained in:
parent
aa57ea920e
commit
75eb277401
@ -6,14 +6,14 @@
|
|||||||
img(src='/svg/icon-rest-api.svg', alt='API', style='width: 80px;')
|
img(src='/svg/icon-rest-api.svg', alt='API', style='width: 80px;')
|
||||||
.admin-header-title
|
.admin-header-title
|
||||||
.headline.blue--text.text--darken-2 API
|
.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-spacer
|
||||||
v-btn(outline, color='grey', large, @click='refresh')
|
v-btn(outline, color='grey', large, @click='refresh', disabled)
|
||||||
v-icon refresh
|
v-icon refresh
|
||||||
v-btn(color='green', dark, depressed, large, @click='globalSwitch')
|
v-btn(color='green', disabled, depressed, large, @click='globalSwitch')
|
||||||
v-icon(left) power_settings_new
|
v-icon(left) power_settings_new
|
||||||
| Enable API
|
| Enable API
|
||||||
v-btn(color='primary', depressed, large, @click='newKey')
|
v-btn(color='primary', depressed, large, @click='newKey', disabled)
|
||||||
v-icon(left) add
|
v-icon(left) add
|
||||||
| New API Key
|
| New API Key
|
||||||
v-card.mt-3
|
v-card.mt-3
|
||||||
@ -58,7 +58,7 @@
|
|||||||
td {{ props.item.updatedOn }}
|
td {{ props.item.updatedOn }}
|
||||||
td: v-btn(icon): v-icon.grey--text.text--darken-1 more_horiz
|
td: v-btn(icon): v-icon.grey--text.text--darken-1 more_horiz
|
||||||
template(slot='no-data')
|
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
|
.text-xs-center.py-2
|
||||||
v-pagination(v-model='pagination.page', :length='pages')
|
v-pagination(v-model='pagination.page', :length='pages')
|
||||||
</template>
|
</template>
|
||||||
|
@ -207,6 +207,11 @@ export default {
|
|||||||
await this.$apollo.mutate({
|
await this.$apollo.mutate({
|
||||||
mutation: strategiesSaveMutation,
|
mutation: strategiesSaveMutation,
|
||||||
variables: {
|
variables: {
|
||||||
|
config: {
|
||||||
|
audience: this.jwtAudience,
|
||||||
|
tokenExpiration: this.jwtExpiration,
|
||||||
|
tokenRenewal: this.jwtRenewablePeriod
|
||||||
|
},
|
||||||
strategies: this.strategies.map(str => _.pick(str, [
|
strategies: this.strategies.map(str => _.pick(str, [
|
||||||
'isEnabled',
|
'isEnabled',
|
||||||
'key',
|
'key',
|
||||||
|
@ -7,8 +7,18 @@
|
|||||||
.admin-header-title
|
.admin-header-title
|
||||||
.headline.primary--text Developer Tools
|
.headline.primary--text Developer Tools
|
||||||
.subheading.grey--text ¯\_(ツ)_/¯
|
.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-tabs(
|
||||||
v-model='selectedTab'
|
v-model='selectedTab'
|
||||||
color='grey darken-2'
|
color='grey darken-2'
|
||||||
@ -92,9 +102,8 @@ export default {
|
|||||||
}, 500)
|
}, 500)
|
||||||
return resp
|
return resp
|
||||||
},
|
},
|
||||||
query: '',
|
|
||||||
response: null,
|
response: null,
|
||||||
variables: null,
|
variables: '{}',
|
||||||
operationName: null,
|
operationName: null,
|
||||||
websocketConnectionParams: null
|
websocketConnectionParams: null
|
||||||
}),
|
}),
|
||||||
@ -103,6 +112,7 @@ export default {
|
|||||||
graphiQLInstance.queryEditorComponent.editor.refresh()
|
graphiQLInstance.queryEditorComponent.editor.refresh()
|
||||||
graphiQLInstance.variableEditorComponent.editor.refresh()
|
graphiQLInstance.variableEditorComponent.editor.refresh()
|
||||||
graphiQLInstance.state.variableEditorOpen = true
|
graphiQLInstance.state.variableEditorOpen = true
|
||||||
|
graphiQLInstance.state.docExplorerOpen = true
|
||||||
},
|
},
|
||||||
renderVoyager() {
|
renderVoyager() {
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
@ -120,7 +130,7 @@ export default {
|
|||||||
<style lang='scss'>
|
<style lang='scss'>
|
||||||
|
|
||||||
#graphiql {
|
#graphiql {
|
||||||
height: calc(100vh - 230px);
|
height: calc(100vh - 270px);
|
||||||
|
|
||||||
.topBar {
|
.topBar {
|
||||||
background-color: mc('grey', '200');
|
background-color: mc('grey', '200');
|
||||||
@ -136,10 +146,14 @@ export default {
|
|||||||
background-color: initial;
|
background-color: initial;
|
||||||
box-shadow: initial;
|
box-shadow: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.doc-explorer-title-bar, .history-title-bar {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#voyager {
|
#voyager {
|
||||||
height: calc(100vh - 250px);
|
height: calc(100vh - 270px);
|
||||||
|
|
||||||
.title-area {
|
.title-area {
|
||||||
display: none;
|
display: none;
|
||||||
@ -147,5 +161,22 @@ export default {
|
|||||||
.type-doc {
|
.type-doc {
|
||||||
margin-top: 5px;
|
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>
|
</style>
|
||||||
|
@ -38,6 +38,8 @@
|
|||||||
:counter='50'
|
:counter='50'
|
||||||
v-model='config.title'
|
v-model='config.title'
|
||||||
prepend-icon='public'
|
prepend-icon='public'
|
||||||
|
hint='Displayed in the top bar and appended to all pages meta title.'
|
||||||
|
persistent-hint
|
||||||
)
|
)
|
||||||
v-divider
|
v-divider
|
||||||
v-subheader SEO
|
v-subheader SEO
|
||||||
@ -48,6 +50,8 @@
|
|||||||
:counter='255'
|
:counter='255'
|
||||||
v-model='config.description'
|
v-model='config.description'
|
||||||
prepend-icon='explore'
|
prepend-icon='explore'
|
||||||
|
hint='Default description when none is provided for a page.'
|
||||||
|
persistent-hint
|
||||||
)
|
)
|
||||||
v-select.mt-2(
|
v-select.mt-2(
|
||||||
outline
|
outline
|
||||||
@ -57,7 +61,7 @@
|
|||||||
v-model='config.robots'
|
v-model='config.robots'
|
||||||
prepend-icon='explore'
|
prepend-icon='explore'
|
||||||
:return-object='false'
|
:return-object='false'
|
||||||
hint='Default: Index, Follow'
|
hint='Default: Index, Follow. Can also be set on a per-page basis.'
|
||||||
persistent-hint
|
persistent-hint
|
||||||
)
|
)
|
||||||
v-divider
|
v-divider
|
||||||
@ -69,6 +73,8 @@
|
|||||||
:items='analyticsServices'
|
:items='analyticsServices'
|
||||||
v-model='config.analyticsService'
|
v-model='config.analyticsService'
|
||||||
prepend-icon='timeline'
|
prepend-icon='timeline'
|
||||||
|
persistent-hint
|
||||||
|
hint='Automatically add tracking code for services like Google Analytics.'
|
||||||
)
|
)
|
||||||
v-text-field.mt-2(
|
v-text-field.mt-2(
|
||||||
v-if='config.analyticsService !== ``'
|
v-if='config.analyticsService !== ``'
|
||||||
|
@ -60,14 +60,14 @@ export default {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
permission: 'write:pages',
|
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,
|
warning: false,
|
||||||
restrictedForSystem: false,
|
restrictedForSystem: false,
|
||||||
disabled: false
|
disabled: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
permission: 'manage:pages',
|
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,
|
warning: false,
|
||||||
restrictedForSystem: false,
|
restrictedForSystem: false,
|
||||||
disabled: false
|
disabled: false
|
||||||
@ -95,7 +95,7 @@ export default {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
permission: 'manage:assets',
|
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,
|
warning: false,
|
||||||
restrictedForSystem: false,
|
restrictedForSystem: false,
|
||||||
disabled: false
|
disabled: false
|
||||||
@ -116,7 +116,7 @@ export default {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
permission: 'manage:comments',
|
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,
|
warning: false,
|
||||||
restrictedForSystem: false,
|
restrictedForSystem: false,
|
||||||
disabled: false
|
disabled: false
|
||||||
|
@ -52,6 +52,8 @@
|
|||||||
v-toolbar(color='primary', dark, dense, flat)
|
v-toolbar(color='primary', dark, dense, flat)
|
||||||
v-toolbar-title
|
v-toolbar-title
|
||||||
.subheading {{ $t('admin:locale.namespacing') }}
|
.subheading {{ $t('admin:locale.namespacing') }}
|
||||||
|
v-spacer
|
||||||
|
v-chip(label, color='white', small).primary--text coming soon
|
||||||
v-card-text
|
v-card-text
|
||||||
v-switch(
|
v-switch(
|
||||||
v-model='namespacing'
|
v-model='namespacing'
|
||||||
|
@ -6,11 +6,11 @@
|
|||||||
img(src='/svg/icon-registry-editor.svg', alt='Logging', style='width: 80px;')
|
img(src='/svg/icon-registry-editor.svg', alt='Logging', style='width: 80px;')
|
||||||
.admin-header-title
|
.admin-header-title
|
||||||
.headline.primary--text Logging
|
.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-spacer
|
||||||
v-btn(outline, color='grey', @click='refresh', large)
|
v-btn(outline, color='grey', @click='refresh', large)
|
||||||
v-icon refresh
|
v-icon refresh
|
||||||
v-btn(color='black', dark, depressed, @click='toggleConsole', large)
|
v-btn(color='black', disabled, depressed, @click='toggleConsole', large)
|
||||||
ConsoleLineIcon.mr-3
|
ConsoleLineIcon.mr-3
|
||||||
span Live Trail
|
span Live Trail
|
||||||
v-btn(color='success', @click='save', depressed, large)
|
v-btn(color='success', @click='save', depressed, large)
|
||||||
@ -34,6 +34,7 @@
|
|||||||
:label='logger.title'
|
:label='logger.title'
|
||||||
color='primary'
|
color='primary'
|
||||||
hide-details
|
hide-details
|
||||||
|
disabled
|
||||||
)
|
)
|
||||||
|
|
||||||
v-tab-item(v-for='(logger, n) in activeLoggers', :key='logger.key', :transition='false', :reverse-transition='false')
|
v-tab-item(v-for='(logger, n) in activeLoggers', :key='logger.key', :transition='false', :reverse-transition='false')
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
img(src='/svg/icon-search.svg', alt='Search Engine', style='width: 80px;')
|
img(src='/svg/icon-search.svg', alt='Search Engine', style='width: 80px;')
|
||||||
.admin-header-title
|
.admin-header-title
|
||||||
.headline.primary--text Search Engine
|
.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-spacer
|
||||||
v-btn(outline, color='grey', @click='refresh', large)
|
v-btn(outline, color='grey', @click='refresh', large)
|
||||||
v-icon refresh
|
v-icon refresh
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
img(src='/svg/icon-cloud-storage.svg', alt='Storage', style='width: 80px;')
|
img(src='/svg/icon-cloud-storage.svg', alt='Storage', style='width: 80px;')
|
||||||
.admin-header-title
|
.admin-header-title
|
||||||
.headline.primary--text Storage
|
.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-spacer
|
||||||
v-btn(outline, color='grey', @click='refresh', large)
|
v-btn(outline, color='grey', @click='refresh', large)
|
||||||
v-icon refresh
|
v-icon refresh
|
||||||
|
@ -14,12 +14,12 @@
|
|||||||
outline
|
outline
|
||||||
color='blue'
|
color='blue'
|
||||||
@click.native.stop='openPropsModal'
|
@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
|
v-icon(color='blue', :left='$vuetify.breakpoint.lgAndUp') sort_by_alpha
|
||||||
span.white--text(v-if='$vuetify.breakpoint.lgAndUp') {{ $t('editor:page') }}
|
span.white--text(v-if='$vuetify.breakpoint.lgAndUp') {{ $t('editor:page') }}
|
||||||
v-btn(
|
v-btn(
|
||||||
v-if='path !== `home`'
|
v-if='!welcomeMode'
|
||||||
outline
|
outline
|
||||||
color='red'
|
color='red'
|
||||||
:class='{ "is-icon": $vuetify.breakpoint.mdAndDown }'
|
:class='{ "is-icon": $vuetify.breakpoint.mdAndDown }'
|
||||||
@ -62,6 +62,7 @@ import editorStore from '@/store/editor'
|
|||||||
WIKI.$store.registerModule('editor', editorStore)
|
WIKI.$store.registerModule('editor', editorStore)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
i18nOptions: { namespaces: 'editor' },
|
||||||
components: {
|
components: {
|
||||||
AtomSpinner,
|
AtomSpinner,
|
||||||
editorCode: () => import(/* webpackChunkName: "editor-code", webpackMode: "lazy" */ './editor/editor-code.vue'),
|
editorCode: () => import(/* webpackChunkName: "editor-code", webpackMode: "lazy" */ './editor/editor-code.vue'),
|
||||||
@ -127,7 +128,8 @@ export default {
|
|||||||
darkMode: get('site/dark'),
|
darkMode: get('site/dark'),
|
||||||
mode: get('editor/mode'),
|
mode: get('editor/mode'),
|
||||||
notification: get('notification'),
|
notification: get('notification'),
|
||||||
notificationState: sync('notification@isActive')
|
notificationState: sync('notification@isActive'),
|
||||||
|
welcomeMode() { return this.mode === `create` && this.path === `home` }
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
currentEditor(newValue, oldValue) {
|
currentEditor(newValue, oldValue) {
|
||||||
@ -242,6 +244,8 @@ export default {
|
|||||||
throw new Error(_.get(resp, 'responseResult.message'))
|
throw new Error(_.get(resp, 'responseResult.message'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.initContentParsed = this.$store.get('editor/content')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.$store.commit('showNotification', {
|
this.$store.commit('showNotification', {
|
||||||
message: err.message,
|
message: err.message,
|
||||||
|
@ -234,7 +234,7 @@ export default {
|
|||||||
|
|
||||||
if (!token.type) { return }
|
if (!token.type) { return }
|
||||||
|
|
||||||
console.info(token)
|
// console.info(token)
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Update scroll sync
|
* Update scroll sync
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
mutation($strategies: [AuthenticationStrategyInput]) {
|
mutation($strategies: [AuthenticationStrategyInput]!, $config: AuthenticationConfigInput) {
|
||||||
authentication {
|
authentication {
|
||||||
updateStrategies(strategies: $strategies) {
|
updateStrategies(strategies: $strategies, config: $config) {
|
||||||
responseResult {
|
responseResult {
|
||||||
succeeded
|
succeeded
|
||||||
errorCode
|
errorCode
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
// @import 'node_modules/diff2html/dist/diff2html.min';
|
// @import 'node_modules/diff2html/dist/diff2html.min';
|
||||||
|
|
||||||
@import 'pages/new';
|
@import 'pages/new';
|
||||||
|
@import 'pages/unauthorized';
|
||||||
@import 'pages/welcome';
|
@import 'pages/welcome';
|
||||||
@import 'pages/error';
|
@import 'pages/error';
|
||||||
|
|
||||||
|
81
client/scss/pages/_unauthorized.scss
Normal file
81
client/scss/pages/_unauthorized.scss
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
14
client/static/svg/icon-delete-shield.svg
Normal file
14
client/static/svg/icon-delete-shield.svg
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?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>
|
After Width: | Height: | Size: 2.1 KiB |
33
client/static/svg/icon-safety-float.svg
Normal file
33
client/static/svg/icon-safety-float.svg
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?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>
|
After Width: | Height: | Size: 4.5 KiB |
3
client/static/svg/motif-diagonals.svg
Normal file
3
client/static/svg/motif-diagonals.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<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>
|
After Width: | Height: | Size: 223 B |
@ -189,10 +189,8 @@ export default {
|
|||||||
},
|
},
|
||||||
breadcrumbs: [
|
breadcrumbs: [
|
||||||
{ path: '/', name: 'Home' },
|
{ path: '/', name: 'Home' },
|
||||||
{ path: '/universe', name: 'Universe' },
|
{ path: '/' + this.path, name: 'Breadcrumb' },
|
||||||
{ path: '/universe/galaxy', name: 'Galaxy' },
|
{ path: '/' + this.path, name: 'Coming soon' }
|
||||||
{ path: '/universe/galaxy/solar-system', name: 'Solar System' },
|
|
||||||
{ path: '/universe/galaxy/solar-system/planet-earth', name: 'Planet Earth' }
|
|
||||||
],
|
],
|
||||||
scrollStyle: {
|
scrollStyle: {
|
||||||
vuescroll: {},
|
vuescroll: {},
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
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" ]
|
|
@ -1,19 +1,53 @@
|
|||||||
version: '3'
|
# -- DEV DOCKER-COMPOSE --
|
||||||
|
# -- DO NOT USE IN PRODUCTION! --
|
||||||
|
|
||||||
|
version: "3"
|
||||||
services:
|
services:
|
||||||
wikidb:
|
|
||||||
image: mongo
|
redis:
|
||||||
expose:
|
image: redis:4-alpine
|
||||||
- '27017'
|
logging:
|
||||||
command: '--smallfiles --logpath=/dev/null'
|
driver: "none"
|
||||||
volumes:
|
networks:
|
||||||
- ./data/mongo:/data/db
|
- wikinet
|
||||||
wikijs:
|
|
||||||
image: 'requarks/wiki:latest'
|
db:
|
||||||
links:
|
image: postgres:9-alpine
|
||||||
- wikidb
|
|
||||||
ports:
|
|
||||||
- '80:3000'
|
|
||||||
environment:
|
environment:
|
||||||
WIKI_ADMIN_EMAIL: admin@example.com
|
POSTGRES_DB: wiki
|
||||||
|
POSTGRES_PASSWORD: wikijsrocks
|
||||||
|
POSTGRES_USER: wikijs
|
||||||
|
logging:
|
||||||
|
driver: "none"
|
||||||
volumes:
|
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:
|
||||||
|
@ -22,14 +22,12 @@ defaults:
|
|||||||
db: 0
|
db: 0
|
||||||
password: null
|
password: null
|
||||||
# DB defaults
|
# DB defaults
|
||||||
defaultEditor: 'markdown'
|
|
||||||
graphEndpoint: 'https://graph.requarks.io'
|
graphEndpoint: 'https://graph.requarks.io'
|
||||||
lang:
|
lang:
|
||||||
code: en
|
code: en
|
||||||
autoUpdate: true
|
autoUpdate: true
|
||||||
namespaces: []
|
namespaces: []
|
||||||
namespacing: false
|
namespacing: false
|
||||||
public: false
|
|
||||||
telemetry:
|
telemetry:
|
||||||
clientId: ''
|
clientId: ''
|
||||||
isEnabled: false
|
isEnabled: false
|
||||||
@ -47,13 +45,6 @@ defaults:
|
|||||||
maxAge: 600
|
maxAge: 600
|
||||||
methods: 'GET,POST'
|
methods: 'GET,POST'
|
||||||
origin: true
|
origin: true
|
||||||
configNamespaces:
|
|
||||||
- auth
|
|
||||||
- features
|
|
||||||
- logging
|
|
||||||
- site
|
|
||||||
- theme
|
|
||||||
- uploads
|
|
||||||
localeNamespaces:
|
localeNamespaces:
|
||||||
- admin
|
- admin
|
||||||
- auth
|
- auth
|
||||||
|
@ -5,6 +5,18 @@ const _ = require('lodash')
|
|||||||
|
|
||||||
/* global WIKI */
|
/* 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
|
* Create/Edit document
|
||||||
*/
|
*/
|
||||||
@ -17,12 +29,20 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
|
|||||||
isPrivate: false
|
isPrivate: false
|
||||||
})
|
})
|
||||||
if (page) {
|
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.title', `Edit ${page.title}`)
|
||||||
_.set(res.locals, 'pageMeta.description', page.description)
|
_.set(res.locals, 'pageMeta.description', page.description)
|
||||||
page.mode = 'update'
|
page.mode = 'update'
|
||||||
page.isPublished = (page.isPublished === true || page.isPublished === 1) ? 'true' : 'false'
|
page.isPublished = (page.isPublished === true || page.isPublished === 1) ? 'true' : 'false'
|
||||||
page.content = Buffer.from(page.content).toString('base64')
|
page.content = Buffer.from(page.content).toString('base64')
|
||||||
} else {
|
} else {
|
||||||
|
if (!WIKI.auth.checkAccess(req.user, ['write:pages'], pageArgs)) {
|
||||||
|
return res.render('unauthorized', { action: 'create'})
|
||||||
|
}
|
||||||
|
|
||||||
_.set(res.locals, 'pageMeta.title', `New Page`)
|
_.set(res.locals, 'pageMeta.title', `New Page`)
|
||||||
page = {
|
page = {
|
||||||
path: pageArgs.path,
|
path: pageArgs.path,
|
||||||
@ -56,6 +76,11 @@ router.get(['/p', '/p/*'], (req, res, next) => {
|
|||||||
*/
|
*/
|
||||||
router.get(['/h', '/h/*'], async (req, res, next) => {
|
router.get(['/h', '/h/*'], async (req, res, next) => {
|
||||||
const pageArgs = pageHelper.parsePath(req.path)
|
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({
|
const page = await WIKI.models.pages.getPageFromDb({
|
||||||
path: pageArgs.path,
|
path: pageArgs.path,
|
||||||
locale: pageArgs.locale,
|
locale: pageArgs.locale,
|
||||||
@ -76,6 +101,11 @@ router.get(['/h', '/h/*'], async (req, res, next) => {
|
|||||||
*/
|
*/
|
||||||
router.get(['/s', '/s/*'], async (req, res, next) => {
|
router.get(['/s', '/s/*'], async (req, res, next) => {
|
||||||
const pageArgs = pageHelper.parsePath(req.path)
|
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({
|
const page = await WIKI.models.pages.getPageFromDb({
|
||||||
path: pageArgs.path,
|
path: pageArgs.path,
|
||||||
locale: pageArgs.locale,
|
locale: pageArgs.locale,
|
||||||
@ -96,6 +126,15 @@ router.get(['/s', '/s/*'], async (req, res, next) => {
|
|||||||
*/
|
*/
|
||||||
router.get('/*', async (req, res, next) => {
|
router.get('/*', async (req, res, next) => {
|
||||||
const pageArgs = pageHelper.parsePath(req.path)
|
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({
|
const page = await WIKI.models.pages.getPage({
|
||||||
path: pageArgs.path,
|
path: pageArgs.path,
|
||||||
locale: pageArgs.locale,
|
locale: pageArgs.locale,
|
||||||
@ -108,8 +147,10 @@ router.get('/*', async (req, res, next) => {
|
|||||||
const sidebar = await WIKI.models.navigation.getTree({ cache: true })
|
const sidebar = await WIKI.models.navigation.getTree({ cache: true })
|
||||||
res.render('page', { page, sidebar })
|
res.render('page', { page, sidebar })
|
||||||
} else if (pageArgs.path === 'home') {
|
} else if (pageArgs.path === 'home') {
|
||||||
|
_.set(res.locals, 'pageMeta.title', 'Welcome')
|
||||||
res.render('welcome')
|
res.render('welcome')
|
||||||
} else {
|
} else {
|
||||||
|
_.set(res.locals, 'pageMeta.title', 'Page Not Found')
|
||||||
res.status(404).render('new', { pagePath: req.path })
|
res.status(404).render('new', { pagePath: req.path })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -3,6 +3,8 @@ const passportJWT = require('passport-jwt')
|
|||||||
const fs = require('fs-extra')
|
const fs = require('fs-extra')
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
const jwt = require('jsonwebtoken')
|
||||||
|
const moment = require('moment')
|
||||||
|
|
||||||
const securityHelper = require('../helpers/security')
|
const securityHelper = require('../helpers/security')
|
||||||
|
|
||||||
@ -10,11 +12,16 @@ const securityHelper = require('../helpers/security')
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
strategies: {},
|
strategies: {},
|
||||||
|
guest: {
|
||||||
|
cacheExpiration: moment.utc().subtract(1, 'd')
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the authentication module
|
||||||
|
*/
|
||||||
init() {
|
init() {
|
||||||
this.passport = passport
|
this.passport = passport
|
||||||
|
|
||||||
// Serialization user methods
|
|
||||||
|
|
||||||
passport.serializeUser(function (user, done) {
|
passport.serializeUser(function (user, done) {
|
||||||
done(null, user.id)
|
done(null, user.id)
|
||||||
})
|
})
|
||||||
@ -34,6 +41,10 @@ module.exports = {
|
|||||||
|
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load authentication strategies
|
||||||
|
*/
|
||||||
async activateStrategies() {
|
async activateStrategies() {
|
||||||
try {
|
try {
|
||||||
// Unload any active strategies
|
// Unload any active strategies
|
||||||
@ -46,7 +57,7 @@ module.exports = {
|
|||||||
passport.use('jwt', new passportJWT.Strategy({
|
passport.use('jwt', new passportJWT.Strategy({
|
||||||
jwtFromRequest: securityHelper.extractJWT,
|
jwtFromRequest: securityHelper.extractJWT,
|
||||||
secretOrKey: WIKI.config.certs.public,
|
secretOrKey: WIKI.config.certs.public,
|
||||||
audience: 'urn:wiki.js', // TODO: use value from admin
|
audience: WIKI.config.auth.audience,
|
||||||
issuer: 'urn:wiki.js'
|
issuer: 'urn:wiki.js'
|
||||||
}, (jwtPayload, cb) => {
|
}, (jwtPayload, cb) => {
|
||||||
cb(null, jwtPayload)
|
cb(null, jwtPayload)
|
||||||
@ -60,7 +71,7 @@ module.exports = {
|
|||||||
|
|
||||||
const strategy = require(`../modules/authentication/${stg.key}/authentication.js`)
|
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)
|
strategy.init(passport, stg.config)
|
||||||
|
|
||||||
fs.readFile(path.join(WIKI.ROOTPATH, `assets/svg/auth-icon-${strategy.key}.svg`), 'utf8').then(iconData => {
|
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(`Authentication Strategy: [ FAILED ]`)
|
||||||
WIKI.logger.error(err)
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,8 +52,6 @@ module.exports = {
|
|||||||
appconfig.port = process.env.PORT || 80
|
appconfig.port = process.env.PORT || 80
|
||||||
}
|
}
|
||||||
|
|
||||||
appconfig.public = (appconfig.public === true || _.toLower(appconfig.public) === 'true')
|
|
||||||
|
|
||||||
WIKI.config = appconfig
|
WIKI.config = appconfig
|
||||||
WIKI.data = appdata
|
WIKI.data = appdata
|
||||||
WIKI.version = require(path.join(WIKI.ROOTPATH, 'package.json')).version
|
WIKI.version = require(path.join(WIKI.ROOTPATH, 'package.json')).version
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
exports.up = knex => {
|
exports.up = knex => {
|
||||||
|
const dbCompat = {
|
||||||
|
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
|
||||||
|
}
|
||||||
return knex.schema
|
return knex.schema
|
||||||
// =====================================
|
// =====================================
|
||||||
// MODEL TABLES
|
// MODEL TABLES
|
||||||
// =====================================
|
// =====================================
|
||||||
// ASSETS ------------------------------
|
// ASSETS ------------------------------
|
||||||
.createTable('assets', table => {
|
.createTable('assets', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.string('filename').notNullable()
|
table.string('filename').notNullable()
|
||||||
table.string('basename').notNullable()
|
table.string('basename').notNullable()
|
||||||
@ -19,7 +22,7 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// ASSET FOLDERS -----------------------
|
// ASSET FOLDERS -----------------------
|
||||||
.createTable('assetFolders', table => {
|
.createTable('assetFolders', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.string('name').notNullable()
|
table.string('name').notNullable()
|
||||||
table.string('slug').notNullable()
|
table.string('slug').notNullable()
|
||||||
@ -27,7 +30,7 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// AUTHENTICATION ----------------------
|
// AUTHENTICATION ----------------------
|
||||||
.createTable('authentication', table => {
|
.createTable('authentication', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.string('key').notNullable().primary()
|
table.string('key').notNullable().primary()
|
||||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||||
table.json('config').notNullable()
|
table.json('config').notNullable()
|
||||||
@ -37,7 +40,7 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// COMMENTS ----------------------------
|
// COMMENTS ----------------------------
|
||||||
.createTable('comments', table => {
|
.createTable('comments', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.text('content').notNullable()
|
table.text('content').notNullable()
|
||||||
table.string('createdAt').notNullable()
|
table.string('createdAt').notNullable()
|
||||||
@ -45,14 +48,14 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// EDITORS -----------------------------
|
// EDITORS -----------------------------
|
||||||
.createTable('editors', table => {
|
.createTable('editors', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.string('key').notNullable().primary()
|
table.string('key').notNullable().primary()
|
||||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||||
table.json('config').notNullable()
|
table.json('config').notNullable()
|
||||||
})
|
})
|
||||||
// GROUPS ------------------------------
|
// GROUPS ------------------------------
|
||||||
.createTable('groups', table => {
|
.createTable('groups', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.string('name').notNullable()
|
table.string('name').notNullable()
|
||||||
table.json('permissions').notNullable()
|
table.json('permissions').notNullable()
|
||||||
@ -63,7 +66,7 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// LOCALES -----------------------------
|
// LOCALES -----------------------------
|
||||||
.createTable('locales', table => {
|
.createTable('locales', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.string('code', 2).notNullable().primary()
|
table.string('code', 2).notNullable().primary()
|
||||||
table.json('strings')
|
table.json('strings')
|
||||||
table.boolean('isRTL').notNullable().defaultTo(false)
|
table.boolean('isRTL').notNullable().defaultTo(false)
|
||||||
@ -74,7 +77,7 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// LOGGING ----------------------------
|
// LOGGING ----------------------------
|
||||||
.createTable('loggers', table => {
|
.createTable('loggers', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.string('key').notNullable().primary()
|
table.string('key').notNullable().primary()
|
||||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||||
table.string('level').notNullable().defaultTo('warn')
|
table.string('level').notNullable().defaultTo('warn')
|
||||||
@ -82,13 +85,13 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// NAVIGATION ----------------------------
|
// NAVIGATION ----------------------------
|
||||||
.createTable('navigation', table => {
|
.createTable('navigation', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.string('key').notNullable().primary()
|
table.string('key').notNullable().primary()
|
||||||
table.json('config')
|
table.json('config')
|
||||||
})
|
})
|
||||||
// PAGE HISTORY ------------------------
|
// PAGE HISTORY ------------------------
|
||||||
.createTable('pageHistory', table => {
|
.createTable('pageHistory', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.string('path').notNullable()
|
table.string('path').notNullable()
|
||||||
table.string('hash').notNullable()
|
table.string('hash').notNullable()
|
||||||
@ -104,7 +107,7 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// PAGES -------------------------------
|
// PAGES -------------------------------
|
||||||
.createTable('pages', table => {
|
.createTable('pages', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.string('path').notNullable()
|
table.string('path').notNullable()
|
||||||
table.string('hash').notNullable()
|
table.string('hash').notNullable()
|
||||||
@ -124,7 +127,7 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// PAGE TREE ---------------------------
|
// PAGE TREE ---------------------------
|
||||||
.createTable('pageTree', table => {
|
.createTable('pageTree', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.string('path').notNullable()
|
table.string('path').notNullable()
|
||||||
table.integer('depth').unsigned().notNullable()
|
table.integer('depth').unsigned().notNullable()
|
||||||
@ -135,28 +138,28 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// RENDERERS ---------------------------
|
// RENDERERS ---------------------------
|
||||||
.createTable('renderers', table => {
|
.createTable('renderers', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.string('key').notNullable().primary()
|
table.string('key').notNullable().primary()
|
||||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||||
table.json('config')
|
table.json('config')
|
||||||
})
|
})
|
||||||
// SEARCH ------------------------------
|
// SEARCH ------------------------------
|
||||||
.createTable('searchEngines', table => {
|
.createTable('searchEngines', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.string('key').notNullable().primary()
|
table.string('key').notNullable().primary()
|
||||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||||
table.json('config')
|
table.json('config')
|
||||||
})
|
})
|
||||||
// SETTINGS ----------------------------
|
// SETTINGS ----------------------------
|
||||||
.createTable('settings', table => {
|
.createTable('settings', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.string('key').notNullable().primary()
|
table.string('key').notNullable().primary()
|
||||||
table.json('value')
|
table.json('value')
|
||||||
table.string('updatedAt').notNullable()
|
table.string('updatedAt').notNullable()
|
||||||
})
|
})
|
||||||
// STORAGE -----------------------------
|
// STORAGE -----------------------------
|
||||||
.createTable('storage', table => {
|
.createTable('storage', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.string('key').notNullable().primary()
|
table.string('key').notNullable().primary()
|
||||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||||
table.string('mode', ['sync', 'push', 'pull']).notNullable().defaultTo('push')
|
table.string('mode', ['sync', 'push', 'pull']).notNullable().defaultTo('push')
|
||||||
@ -164,7 +167,7 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// TAGS --------------------------------
|
// TAGS --------------------------------
|
||||||
.createTable('tags', table => {
|
.createTable('tags', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.string('tag').notNullable().unique()
|
table.string('tag').notNullable().unique()
|
||||||
table.string('title')
|
table.string('title')
|
||||||
@ -173,7 +176,7 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// USER KEYS ---------------------------
|
// USER KEYS ---------------------------
|
||||||
.createTable('userKeys', table => {
|
.createTable('userKeys', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.string('kind').notNullable()
|
table.string('kind').notNullable()
|
||||||
table.string('token').notNullable()
|
table.string('token').notNullable()
|
||||||
@ -182,7 +185,7 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// USERS -------------------------------
|
// USERS -------------------------------
|
||||||
.createTable('users', table => {
|
.createTable('users', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.string('email').notNullable()
|
table.string('email').notNullable()
|
||||||
table.string('name').notNullable()
|
table.string('name').notNullable()
|
||||||
@ -205,21 +208,21 @@ exports.up = knex => {
|
|||||||
// =====================================
|
// =====================================
|
||||||
// PAGE HISTORY TAGS ---------------------------
|
// PAGE HISTORY TAGS ---------------------------
|
||||||
.createTable('pageHistoryTags', table => {
|
.createTable('pageHistoryTags', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.integer('pageId').unsigned().references('id').inTable('pageHistory').onDelete('CASCADE')
|
table.integer('pageId').unsigned().references('id').inTable('pageHistory').onDelete('CASCADE')
|
||||||
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
|
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
|
||||||
})
|
})
|
||||||
// PAGE TAGS ---------------------------
|
// PAGE TAGS ---------------------------
|
||||||
.createTable('pageTags', table => {
|
.createTable('pageTags', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE')
|
table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE')
|
||||||
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
|
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
|
||||||
})
|
})
|
||||||
// USER GROUPS -------------------------
|
// USER GROUPS -------------------------
|
||||||
.createTable('userGroups', table => {
|
.createTable('userGroups', table => {
|
||||||
table.charset('utf8mb4')
|
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.integer('userId').unsigned().references('id').inTable('users').onDelete('CASCADE')
|
table.integer('userId').unsigned().references('id').inTable('users').onDelete('CASCADE')
|
||||||
table.integer('groupId').unsigned().references('id').inTable('groups').onDelete('CASCADE')
|
table.integer('groupId').unsigned().references('id').inTable('groups').onDelete('CASCADE')
|
@ -1,11 +0,0 @@
|
|||||||
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: {} }
|
|
||||||
])
|
|
||||||
}
|
|
@ -70,6 +70,13 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
async updateStrategies(obj, args, context) {
|
async updateStrategies(obj, args, context) {
|
||||||
try {
|
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) {
|
for (let str of args.strategies) {
|
||||||
await WIKI.models.authentication.query().patch({
|
await WIKI.models.authentication.query().patch({
|
||||||
isEnabled: str.isEnabled,
|
isEnabled: str.isEnabled,
|
||||||
|
@ -43,7 +43,8 @@ type AuthenticationMutation {
|
|||||||
): AuthenticationRegisterResponse
|
): AuthenticationRegisterResponse
|
||||||
|
|
||||||
updateStrategies(
|
updateStrategies(
|
||||||
strategies: [AuthenticationStrategyInput]
|
strategies: [AuthenticationStrategyInput]!
|
||||||
|
config: AuthenticationConfigInput
|
||||||
): DefaultResponse @auth(requires: ["manage:system"])
|
): DefaultResponse @auth(requires: ["manage:system"])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,3 +89,9 @@ input AuthenticationStrategyInput {
|
|||||||
domainWhitelist: [String]!
|
domainWhitelist: [String]!
|
||||||
autoEnrollGroups: [Int]!
|
autoEnrollGroups: [Int]!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input AuthenticationConfigInput {
|
||||||
|
audience: String!
|
||||||
|
tokenExpiration: String!
|
||||||
|
tokenRenewal: String!
|
||||||
|
}
|
||||||
|
@ -67,7 +67,7 @@ module.exports = async () => {
|
|||||||
|
|
||||||
app.use(cookieParser())
|
app.use(cookieParser())
|
||||||
app.use(WIKI.auth.passport.initialize())
|
app.use(WIKI.auth.passport.initialize())
|
||||||
app.use(mw.auth.jwt)
|
app.use(WIKI.auth.authenticate)
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// SEO
|
// SEO
|
||||||
@ -138,8 +138,7 @@ module.exports = async () => {
|
|||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
app.use('/', ctrl.auth)
|
app.use('/', ctrl.auth)
|
||||||
|
app.use('/', ctrl.common)
|
||||||
app.use('/', mw.auth.checkPath, ctrl.common)
|
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Error handling
|
// Error handling
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -138,6 +138,11 @@ module.exports = class User extends Model {
|
|||||||
return (result && _.has(result, 'delta') && result.delta === 0)
|
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) {
|
static async processProfile(profile) {
|
||||||
let primaryEmail = ''
|
let primaryEmail = ''
|
||||||
if (_.isArray(profile.emails)) {
|
if (_.isArray(profile.emails)) {
|
||||||
@ -262,8 +267,8 @@ module.exports = class User extends Model {
|
|||||||
passphrase: WIKI.config.sessionSecret
|
passphrase: WIKI.config.sessionSecret
|
||||||
}, {
|
}, {
|
||||||
algorithm: 'RS256',
|
algorithm: 'RS256',
|
||||||
expiresIn: '30m',
|
expiresIn: WIKI.config.auth.tokenExpiration,
|
||||||
audience: 'urn:wiki.js', // TODO: use value from admin
|
audience: WIKI.config.auth.audience,
|
||||||
issuer: 'urn:wiki.js'
|
issuer: 'urn:wiki.js'
|
||||||
}),
|
}),
|
||||||
user
|
user
|
||||||
@ -391,4 +396,10 @@ module.exports = class User extends Model {
|
|||||||
throw new WIKI.Error.AuthRegistrationDisabled()
|
throw new WIKI.Error.AuthRegistrationDisabled()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async getGuestUser () {
|
||||||
|
let user = await WIKI.models.users.query().findById(2)
|
||||||
|
user.getPermissions()
|
||||||
|
return user
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,8 +104,12 @@ module.exports = () => {
|
|||||||
await fs.ensureDir(path.join(dataPath, 'uploads'))
|
await fs.ensureDir(path.join(dataPath, 'uploads'))
|
||||||
|
|
||||||
// Set config
|
// Set config
|
||||||
|
_.set(WIKI.config, 'auth', {
|
||||||
|
audience: 'urn:wiki.js',
|
||||||
|
tokenExpiration: '30m',
|
||||||
|
tokenRenewal: '14d'
|
||||||
|
})
|
||||||
_.set(WIKI.config, 'company', '')
|
_.set(WIKI.config, 'company', '')
|
||||||
_.set(WIKI.config, 'defaultEditor', 'markdown')
|
|
||||||
_.set(WIKI.config, 'features', {
|
_.set(WIKI.config, 'features', {
|
||||||
featurePageRatings: true,
|
featurePageRatings: true,
|
||||||
featurePageComments: true,
|
featurePageComments: true,
|
||||||
@ -136,7 +140,6 @@ module.exports = () => {
|
|||||||
dkimKeySelector: '',
|
dkimKeySelector: '',
|
||||||
dkimPrivateKey: ''
|
dkimPrivateKey: ''
|
||||||
})
|
})
|
||||||
_.set(WIKI.config, 'public', false)
|
|
||||||
_.set(WIKI.config, 'seo', {
|
_.set(WIKI.config, 'seo', {
|
||||||
description: '',
|
description: '',
|
||||||
robots: ['index', 'follow'],
|
robots: ['index', 'follow'],
|
||||||
@ -145,7 +148,7 @@ module.exports = () => {
|
|||||||
})
|
})
|
||||||
_.set(WIKI.config, 'sessionSecret', (await crypto.randomBytesAsync(32)).toString('hex'))
|
_.set(WIKI.config, 'sessionSecret', (await crypto.randomBytesAsync(32)).toString('hex'))
|
||||||
_.set(WIKI.config, 'telemetry', {
|
_.set(WIKI.config, 'telemetry', {
|
||||||
isEnabled: req.body.telemetry === 'true',
|
isEnabled: req.body.telemetry === true,
|
||||||
clientId: WIKI.telemetry.cid
|
clientId: WIKI.telemetry.cid
|
||||||
})
|
})
|
||||||
_.set(WIKI.config, 'theming', {
|
_.set(WIKI.config, 'theming', {
|
||||||
@ -179,16 +182,15 @@ module.exports = () => {
|
|||||||
// Save config to DB
|
// Save config to DB
|
||||||
WIKI.logger.info('Persisting config to DB...')
|
WIKI.logger.info('Persisting config to DB...')
|
||||||
await WIKI.configSvc.saveToDb([
|
await WIKI.configSvc.saveToDb([
|
||||||
|
'auth',
|
||||||
'certs',
|
'certs',
|
||||||
'company',
|
'company',
|
||||||
'defaultEditor',
|
|
||||||
'features',
|
'features',
|
||||||
'graphEndpoint',
|
'graphEndpoint',
|
||||||
'host',
|
'host',
|
||||||
'lang',
|
'lang',
|
||||||
'logo',
|
'logo',
|
||||||
'mail',
|
'mail',
|
||||||
'public',
|
|
||||||
'seo',
|
'seo',
|
||||||
'sessionSecret',
|
'sessionSecret',
|
||||||
'telemetry',
|
'telemetry',
|
||||||
@ -389,8 +391,10 @@ module.exports = () => {
|
|||||||
|
|
||||||
WIKI.server.on('listening', () => {
|
WIKI.server.on('listening', () => {
|
||||||
WIKI.logger.info('HTTP Server: [ RUNNING ]')
|
WIKI.logger.info('HTTP Server: [ RUNNING ]')
|
||||||
WIKI.logger.info('========================================')
|
WIKI.logger.info('🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻')
|
||||||
WIKI.logger.info(`Browse to http://localhost:${WIKI.config.port}/`)
|
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('🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
13
server/views/unauthorized.pug
Normal file
13
server/views/unauthorized.pug
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
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')
|
Loading…
x
Reference in New Issue
Block a user