Commit 3abd2f91 authored by Nicolas Giard's avatar Nicolas Giard

feat: auth jwt, permissions, login ui (wip)

parent 563d1a4f
...@@ -9,7 +9,7 @@ stop: ## Stop Wiki.js ...@@ -9,7 +9,7 @@ stop: ## Stop Wiki.js
restart: ## Restart Wiki.js restart: ## Restart Wiki.js
node wiki restart node wiki restart
dev: ## Start Wiki.js in development mode dev-up: ## Start Wiki.js in development mode
node wiki dev node wiki dev
build: ## Build Wiki.js client assets build: ## Build Wiki.js client assets
......
...@@ -22,6 +22,7 @@ import VueMoment from 'vue-moment' ...@@ -22,6 +22,7 @@ import VueMoment from 'vue-moment'
import VueTour from 'vue-tour' import VueTour from 'vue-tour'
import VueTreeNavigation from 'vue-tree-navigation' import VueTreeNavigation from 'vue-tree-navigation'
import store from './store' import store from './store'
import Cookies from 'js-cookie'
// ==================================== // ====================================
// Load Modules // Load Modules
...@@ -74,7 +75,10 @@ const graphQLLink = createPersistedQueryLink().concat( ...@@ -74,7 +75,10 @@ const graphQLLink = createPersistedQueryLink().concat(
options.body = JSON.stringify(body) options.body = JSON.stringify(body)
// Inject authentication token // Inject authentication token
options.headers.Authorization = `Bearer TODO` const jwtToken = Cookies.get('jwt')
if (jwtToken) {
options.headers.Authorization = `Bearer ${jwtToken}`
}
return fetch(uri, options) return fetch(uri, options)
} }
......
<template lang="pug"> <template lang="pug">
v-footer.justify-center(:color='color', inset) v-footer.justify-center(:color='bgColor', inset)
.caption.grey--text.text--darken-1 .caption.grey--text.text--darken-1
span(v-if='company && company.length > 0') {{ $t('common:footer.copyright', { company: company, year: currentYear, interpolation: { escapeValue: false } }) }} |&nbsp; span(v-if='company && company.length > 0') {{ $t('common:footer.copyright', { company: company, year: currentYear, interpolation: { escapeValue: false } }) }} |&nbsp;
span {{ $t('common:footer.poweredBy') }} #[a(href='https://wiki.js.org', ref='nofollow') Wiki.js] span {{ $t('common:footer.poweredBy') }} #[a(href='https://wiki.js.org', ref='nofollow') Wiki.js]
v-snackbar( v-snackbar(
:color='notification.style' :color='notification.style'
bottom, bottom
right, right
multi-line, multi-line
v-model='notificationState' v-model='notificationState'
) )
.text-xs-left .text-xs-left
...@@ -21,9 +21,13 @@ import { get, sync } from 'vuex-pathify' ...@@ -21,9 +21,13 @@ import { get, sync } from 'vuex-pathify'
export default { export default {
props: { props: {
altbg: { color: {
type: Boolean, type: String,
default: false default: 'grey lighten-3'
},
darkColor: {
type: String,
default: 'grey darken-3'
} }
}, },
data() { data() {
...@@ -36,13 +40,11 @@ export default { ...@@ -36,13 +40,11 @@ export default {
notification: get('notification'), notification: get('notification'),
darkMode: get('site/dark'), darkMode: get('site/dark'),
notificationState: sync('notification@isActive'), notificationState: sync('notification@isActive'),
color() { bgColor() {
if (this.altbg) { if (!this.darkMode) {
return 'altbg' return this.color
} else if (!this.darkMode) {
return 'grey lighten-3'
} else { } else {
return '' return this.darkColor
} }
} }
} }
......
...@@ -103,7 +103,7 @@ ...@@ -103,7 +103,7 @@
v-list-tile(href='/p') v-list-tile(href='/p')
v-list-tile-action: v-icon(color='red') person v-list-tile-action: v-icon(color='red') person
v-list-tile-title Profile v-list-tile-title Profile
v-list-tile(href='/logout') v-list-tile(@click='logout')
v-list-tile-action: v-icon(color='red') exit_to_app v-list-tile-action: v-icon(color='red') exit_to_app
v-list-tile-title Logout v-list-tile-title Logout
</template> </template>
...@@ -111,6 +111,7 @@ ...@@ -111,6 +111,7 @@
<script> <script>
import { get } from 'vuex-pathify' import { get } from 'vuex-pathify'
import _ from 'lodash' import _ from 'lodash'
import Cookies from 'js-cookie'
export default { export default {
props: { props: {
...@@ -169,6 +170,10 @@ export default { ...@@ -169,6 +170,10 @@ export default {
}, },
pageDelete () { pageDelete () {
},
logout () {
Cookies.remove('jwt')
window.location.assign('/')
} }
} }
} }
......
query { query {
authentication { authentication {
strategies(orderBy: "title ASC") { strategies {
isEnabled isEnabled
key key
title title
......
...@@ -7,6 +7,7 @@ mutation($username: String!, $password: String!, $strategy: String!) { ...@@ -7,6 +7,7 @@ mutation($username: String!, $password: String!, $strategy: String!) {
slug slug
message message
} }
jwt
tfaRequired tfaRequired
tfaLoginToken tfaLoginToken
} }
......
query { query {
authentication { authentication {
strategies( strategies(
filter: "isEnabled eq true", isEnabled: true
orderBy: "title ASC"
) { ) {
key key
title title
useForm useForm
icon icon
color
selfRegistration
} }
} }
} }
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
@import "base/icons"; @import "base/icons";
@import "../libs/animate/animate"; @import "../libs/animate/animate";
@import '~vue2-animate/src/sass/vue2-animate';
@import 'components/markdown-content'; @import 'components/markdown-content';
@import 'components/v-btn'; @import 'components/v-btn';
...@@ -11,6 +12,8 @@ ...@@ -11,6 +12,8 @@
@import 'components/v-dialog'; @import 'components/v-dialog';
@import 'components/vue-tree-navigation'; @import 'components/vue-tree-navigation';
@import 'layout/md2';
// @import '../libs/twemoji/twemoji-awesome'; // @import '../libs/twemoji/twemoji-awesome';
@import '../libs/prism/prism.css'; @import '../libs/prism/prism.css';
@import '~vue-tour/dist/vue-tour.css'; @import '~vue-tour/dist/vue-tour.css';
......
...@@ -18,3 +18,10 @@ html { ...@@ -18,3 +18,10 @@ html {
height: 100vh; height: 100vh;
} }
} }
@for $i from 1 through 25 {
.radius-#{$i} {
border-radius: #{$i}px;
}
}
...@@ -335,3 +335,30 @@ $material-colors: ( ...@@ -335,3 +335,30 @@ $material-colors: (
@function mc($color-name, $color-variant: '500') { @function mc($color-name, $color-variant: '500') {
@return material-color($color-name, $color-variant); @return material-color($color-name, $color-variant);
} }
/**
* Material Elevation
*/
@mixin md-elevation-0 {
box-shadow: none !important;
}
@mixin md-elevation-1 {
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2) !important;
}
@mixin md-elevation-2 {
box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.3) !important;
}
@mixin md-elevation-3 {
box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12), 0 3px 5px -1px rgba(0, 0, 0, 0.3) !important;
}
@mixin md-elevation-4 {
box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.3) !important;
}
@mixin md-elevation-5 {
box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14), 0 6px 30px 5px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 0, 0, 0.3) !important;
}
.md2 {
&.v-text-field .v-input__slot {
border-radius: 28px;
}
}
<svg width="2230" height="2500" viewBox="0 0 256 287" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet"><path d="M203.24 231.531l-28.73-88.434 75.208-54.64h-92.966L128.019.025l-.009-.024h92.98l28.74 88.446.002-.002.024-.013c16.69 51.31-.5 109.67-46.516 143.098zm-150.45 0l-.023.017 75.228 54.655 75.245-54.67-75.221-54.656-75.228 54.654zM6.295 88.434c-17.57 54.088 2.825 111.4 46.481 143.108l.007-.028 28.735-88.429-75.192-54.63h92.944L128.004.024 128.01 0H35.025L6.294 88.434z" fill="#EB5424"/></svg> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
\ No newline at end of file <!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 2230 2500" 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;">
<path d="M1770.4,2016.82l-250.262,-770.331l655.122,-475.959l-809.808,0l-250.287,-770.313l-0.079,-0.209l809.93,0l250.349,770.435l0.017,-0.017l0.209,-0.113c145.384,446.951 -4.355,955.313 -405.191,1246.5l0,0.009Zm-1310.54,0l-0.201,0.148l655.296,476.088l655.445,-476.219l-655.235,-476.098l-655.297,476.081l-0.008,0Zm-405.009,-1246.49c-153.049,471.15 24.608,970.383 404.887,1246.59l0.061,-0.244l250.305,-770.287l-654.983,-475.871l809.617,0l250.296,-770.305l0.052,-0.209l-809.974,0l-250.27,770.331l0.009,0Z" style="fill:#fff;fill-rule:nonzero;"/>
</svg>
<?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 60 74" 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;">
<path d="M21.292,54.324c0.332,0.67 0.402,0.971 0.786,1.564c2.846,4.39 6.612,6.586 11.297,6.586c3.749,0 6.942,-1.476 9.58,-4.426c2.637,-2.95 4.13,-6.473 4.477,-10.568l11.87,1.301c-0.764,7.115 -3.514,12.972 -8.252,17.571c-4.737,4.599 -10.959,6.898 -18.664,6.898c-6.49,0 -11.757,-1.631 -15.8,-4.894c-2.868,-2.313 -5.268,-5.211 -7.201,-8.694c3.018,-1.229 7.139,-2.71 11.907,-5.338Zm-14.486,-0.403c-1.897,-5.277 -2.845,-11.051 -2.845,-17.322c0,-10.829 2.568,-19.636 7.705,-26.421c5.137,-6.785 12.199,-10.178 21.189,-10.178c7.045,0 12.911,2.169 17.596,6.508c4.686,4.338 7.636,10.551 8.851,18.638l-10.262,3.552c-2.187,-10.725 -8.619,-18.39 -16.081,-18.39c-4.79,0 -8.512,2.23 -11.167,6.69c-2.655,4.46 -3.983,10.924 -3.983,19.393c0,3.518 0.114,6.087 0.59,8.9c-5.15,2.938 -8.825,6.276 -11.593,8.63Z" style="fill:#fff;fill-rule:nonzero;"/>
<path d="M0,61.855c0,-0.579 14.243,-12.318 30.088,-21.125c13.64,-7.581 29.214,-12.496 29.214,-11.997c0,0.474 -11.713,8.753 -24.578,15.996c-16.362,9.21 -34.724,17.729 -34.724,17.126Z" style="fill:#fff;"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M8 9v-3c0-2.206 1.794-4 4-4s4 1.794 4 4v3h2v-3c0-3.313-2.687-6-6-6s-6 2.687-6 6v3h2zm.746 2h2.831l-8.577 8.787v-2.9l5.746-5.887zm12.254 1.562v-1.562h-1.37l-12.69 13h2.894l11.166-11.438zm-6.844-1.562l-11.156 11.431v1.569h1.361l12.689-13h-2.894zm6.844 7.13v-2.927l-8.586 8.797h2.858l5.728-5.87zm-3.149 5.87h3.149v-3.226l-3.149 3.226zm-11.685-13h-3.166v3.244l3.166-3.244z"/></svg>
\ No newline at end of file
<?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 3840 3840" 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;">
<path d="M1920,0c1059.68,0 1920,860.323 1920,1920c0,1059.68 -860.323,1920 -1920,1920c-1059.68,0 -1920,-860.323 -1920,-1920c0,-1059.68 860.323,-1920 1920,-1920Zm0,960c529.838,0 960,430.162 960,960c0,529.838 -430.162,960 -960,960c-529.838,0 -960,-430.162 -960,-960c0,-529.838 430.162,-960 960,-960Z" style="fill:#fff;"/>
</svg>
<?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 71 74" 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;">
<path id="path0" d="M17.853,0.124c-0.103,0.067 -1.003,0.38 -1.999,0.696c-10.672,3.376 -16.206,11.122 -15.837,22.164c0.321,9.583 4.693,16.927 11.683,19.623c2.1,0.809 0.956,0.764 19.409,0.766c14.486,0.002 16.916,0.028 17.532,0.188c2.81,0.732 4.406,2.841 4.707,6.22c0.441,4.964 -1.581,8.399 -5.726,9.727l-1.171,0.375l-17.33,0.124c-9.532,0.067 -19.177,0.136 -21.435,0.152l-4.105,0.028l-0.239,0.295c-0.228,0.282 -0.238,0.579 -0.238,6.532l0,6.236l26.374,0c17.135,0 26.375,-0.043 26.375,-0.123c0,-0.067 0.24,-0.242 0.531,-0.39c8.882,-4.471 13.65,-12.414 13.648,-22.737c-0.001,-9.581 -4.31,-16.351 -12.241,-19.233l-1.25,-0.453l-17.688,-0.063c-16.589,-0.06 -17.722,-0.077 -18.246,-0.286c-2.673,-1.067 -4.032,-3.356 -4.21,-7.09c-0.223,-4.676 1.706,-7.536 6.183,-9.166l1.086,-0.396l21,-0.032l21,-0.031l0.219,-0.264c0.329,-0.397 0.336,-11.475 0.007,-12.392l-0.213,-0.594l-23.819,0.002c-15.145,0.001 -23.888,0.046 -24.007,0.123" style="fill:#fff;"/>
</svg>
<!-- background by SVGBackgrounds.com --> <!-- background by SVGBackgrounds.com -->
<svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%' viewBox='0 0 1600 800'> <svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%' viewBox='0 0 1600 800'><rect fill='#000000' width='1600' height='800'/><g ><path fill='#0d0d0d' d='M486 705.8c-109.3-21.8-223.4-32.2-335.3-19.4C99.5 692.1 49 703 0 719.8V800h843.8c-115.9-33.2-230.8-68.1-347.6-92.2C492.8 707.1 489.4 706.5 486 705.8z'/><path fill='#171717' d='M1600 0H0v719.8c49-16.8 99.5-27.8 150.7-33.5c111.9-12.7 226-2.4 335.3 19.4c3.4 0.7 6.8 1.4 10.2 2c116.8 24 231.7 59 347.6 92.2H1600V0z'/><path fill='#1e1e1e' d='M478.4 581c3.2 0.8 6.4 1.7 9.5 2.5c196.2 52.5 388.7 133.5 593.5 176.6c174.2 36.6 349.5 29.2 518.6-10.2V0H0v574.9c52.3-17.6 106.5-27.7 161.1-30.9C268.4 537.4 375.7 554.2 478.4 581z'/><path fill='#262626' d='M0 0v429.4c55.6-18.4 113.5-27.3 171.4-27.7c102.8-0.8 203.2 22.7 299.3 54.5c3 1 5.9 2 8.9 3c183.6 62 365.7 146.1 562.4 192.1c186.7 43.7 376.3 34.4 557.9-12.6V0H0z'/><path fill='#2e2e2e' d='M181.8 259.4c98.2 6 191.9 35.2 281.3 72.1c2.8 1.1 5.5 2.3 8.3 3.4c171 71.6 342.7 158.5 531.3 207.7c198.8 51.8 403.4 40.8 597.3-14.8V0H0v283.2C59 263.6 120.6 255.7 181.8 259.4z'/><path fill='#292929' d='M1600 0H0v136.3c62.3-20.9 127.7-27.5 192.2-19.2c93.6 12.1 180.5 47.7 263.3 89.6c2.6 1.3 5.1 2.6 7.7 3.9c158.4 81.1 319.7 170.9 500.3 223.2c210.5 61 430.8 49 636.6-16.6V0z'/><path fill='#252525' d='M454.9 86.3C600.7 177 751.6 269.3 924.1 325c208.6 67.4 431.3 60.8 637.9-5.3c12.8-4.1 25.4-8.4 38.1-12.9V0H288.1c56 21.3 108.7 50.6 159.7 82C450.2 83.4 452.5 84.9 454.9 86.3z'/><path fill='#202020' d='M1600 0H498c118.1 85.8 243.5 164.5 386.8 216.2c191.8 69.2 400 74.7 595 21.1c40.8-11.2 81.1-25.2 120.3-41.7V0z'/><path fill='#1b1b1b' d='M1397.5 154.8c47.2-10.6 93.6-25.3 138.6-43.8c21.7-8.9 43-18.8 63.9-29.5V0H643.4c62.9 41.7 129.7 78.2 202.1 107.4C1020.4 178.1 1214.2 196.1 1397.5 154.8z'/><path fill='#171717' d='M1315.3 72.4c75.3-12.6 148.9-37.1 216.8-72.4h-723C966.8 71 1144.7 101 1315.3 72.4z'/></g></svg>
<g>
<path fill='#1356b1' d='M486 705.8c-109.3-21.8-223.4-32.2-335.3-19.4C99.5 692.1 49 703 0 719.8V800h843.8c-115.9-33.2-230.8-68.1-347.6-92.2C492.8 707.1 489.4 706.5 486 705.8z'/>
<path fill='#1866c1' d='M1600 0H0v719.8c49-16.8 99.5-27.8 150.7-33.5c111.9-12.7 226-2.4 335.3 19.4c3.4 0.7 6.8 1.4 10.2 2c116.8 24 231.7 59 347.6 92.2H1600V0z'/>
<path fill='#1c75d2' d='M478.4 581c3.2 0.8 6.4 1.7 9.5 2.5c196.2 52.5 388.7 133.5 593.5 176.6c174.2 36.6 349.5 29.2 518.6-10.2V0H0v574.9c52.3-17.6 106.5-27.7 161.1-30.9C268.4 537.4 375.7 554.2 478.4 581z'/>
<path fill='#1f86e2' d='M0 0v429.4c55.6-18.4 113.5-27.3 171.4-27.7c102.8-0.8 203.2 22.7 299.3 54.5c3 1 5.9 2 8.9 3c183.6 62 365.7 146.1 562.4 192.1c186.7 43.7 376.3 34.4 557.9-12.6V0H0z'/>
<path fill='#2196f3' d='M181.8 259.4c98.2 6 191.9 35.2 281.3 72.1c2.8 1.1 5.5 2.3 8.3 3.4c171 71.6 342.7 158.5 531.3 207.7c198.8 51.8 403.4 40.8 597.3-14.8V0H0v283.2C59 263.6 120.6 255.7 181.8 259.4z'/>
<path fill='#55a4f5' d='M1600 0H0v136.3c62.3-20.9 127.7-27.5 192.2-19.2c93.6 12.1 180.5 47.7 263.3 89.6c2.6 1.3 5.1 2.6 7.7 3.9c158.4 81.1 319.7 170.9 500.3 223.2c210.5 61 430.8 49 636.6-16.6V0z'/>
<path fill='#74b2f7' d='M454.9 86.3C600.7 177 751.6 269.3 924.1 325c208.6 67.4 431.3 60.8 637.9-5.3c12.8-4.1 25.4-8.4 38.1-12.9V0H288.1c56 21.3 108.7 50.6 159.7 82C450.2 83.4 452.5 84.9 454.9 86.3z'/>
<path fill='#8ec0f8' d='M1600 0H498c118.1 85.8 243.5 164.5 386.8 216.2c191.8 69.2 400 74.7 595 21.1c40.8-11.2 81.1-25.2 120.3-41.7V0z'/>
<path fill='#a5cffa' d='M1397.5 154.8c47.2-10.6 93.6-25.3 138.6-43.8c21.7-8.9 43-18.8 63.9-29.5V0H643.4c62.9 41.7 129.7 78.2 202.1 107.4C1020.4 178.1 1214.2 196.1 1397.5 154.8z'/>
<path fill='#bbdefb' d='M1315.3 72.4c75.3-12.6 148.9-37.1 216.8-72.4h-723C966.8 71 1144.7 101 1315.3 72.4z'/>
</g>
</svg>
...@@ -22,7 +22,7 @@ html ...@@ -22,7 +22,7 @@ html
var siteConfig = !{JSON.stringify({ title: config.title, theme: config.theming.theme, darkMode: config.theming.darkMode, lang: config.lang.code })} var siteConfig = !{JSON.stringify({ title: config.title, theme: config.theming.theme, darkMode: config.theming.darkMode, lang: config.lang.code })}
//- CSS //- CSS
link(type='text/css', rel='stylesheet', href='https://fonts.googleapis.com/icon?family=Roboto:400,500,700|Source+Code+Pro:400,700|Material+Icons') link(type='text/css', rel='stylesheet', href='https://fonts.googleapis.com/icon?family=Roboto:400,500,700|Varela+Round|Source+Code+Pro:400,700|Material+Icons')
link(type='text/css', rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/material-design-iconic-font/2.2.0/css/material-design-iconic-font.min.css') link(type='text/css', rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/material-design-iconic-font/2.2.0/css/material-design-iconic-font.min.css')
<% for (var index in htmlWebpackPlugin.files.css) { %> <% for (var index in htmlWebpackPlugin.files.css) { %>
<% if (htmlWebpackPlugin.files.cssIntegrity) { %> <% if (htmlWebpackPlugin.files.cssIntegrity) { %>
......
...@@ -125,6 +125,7 @@ ...@@ -125,6 +125,7 @@
"passport-facebook": "2.1.1", "passport-facebook": "2.1.1",
"passport-github2": "0.1.11", "passport-github2": "0.1.11",
"passport-google-oauth20": "1.0.0", "passport-google-oauth20": "1.0.0",
"passport-jwt": "4.0.0",
"passport-ldapauth": "2.1.0", "passport-ldapauth": "2.1.0",
"passport-local": "1.0.0", "passport-local": "1.0.0",
"passport-oauth2": "1.4.0", "passport-oauth2": "1.4.0",
...@@ -264,6 +265,7 @@ ...@@ -264,6 +265,7 @@
"vue-template-compiler": "2.5.17", "vue-template-compiler": "2.5.17",
"vue-tour": "1.0.1", "vue-tour": "1.0.1",
"vue-tree-navigation": "3.0.1", "vue-tree-navigation": "3.0.1",
"vue2-animate": "2.1.0",
"vuedraggable": "2.16.0", "vuedraggable": "2.16.0",
"vuetify": "1.2.5", "vuetify": "1.2.5",
"vuex": "3.0.1", "vuex": "3.0.1",
......
...@@ -5,6 +5,7 @@ const express = require('express') ...@@ -5,6 +5,7 @@ const express = require('express')
const router = express.Router() const router = express.Router()
const ExpressBrute = require('express-brute') const ExpressBrute = require('express-brute')
const ExpressBruteRedisStore = require('express-brute-redis') const ExpressBruteRedisStore = require('express-brute-redis')
const jwt = require('jsonwebtoken')
const moment = require('moment') const moment = require('moment')
const _ = require('lodash') const _ = require('lodash')
...@@ -40,7 +41,7 @@ router.get('/login', function (req, res, next) { ...@@ -40,7 +41,7 @@ router.get('/login', function (req, res, next) {
router.post('/login', bruteforce.prevent, function (req, res, next) { router.post('/login', bruteforce.prevent, function (req, res, next) {
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
// [1] LOCAL AUTHENTICATION // [1] LOCAL AUTHENTICATION
WIKI.auth.passport.authenticate('local', function (err, user, info) { WIKI.auth.passport.authenticate('local', { session: false }, function (err, user, info) {
if (err) { return reject(err) } if (err) { return reject(err) }
if (!user) { return reject(new Error('INVALID_LOGIN')) } if (!user) { return reject(new Error('INVALID_LOGIN')) }
resolve(user) resolve(user)
...@@ -49,7 +50,7 @@ router.post('/login', bruteforce.prevent, function (req, res, next) { ...@@ -49,7 +50,7 @@ router.post('/login', bruteforce.prevent, function (req, res, next) {
if (_.has(WIKI.config.auth.strategy, 'ldap')) { if (_.has(WIKI.config.auth.strategy, 'ldap')) {
// [2] LDAP AUTHENTICATION // [2] LDAP AUTHENTICATION
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
WIKI.auth.passport.authenticate('ldapauth', function (err, user, info) { WIKI.auth.passport.authenticate('ldapauth', { session: false }, function (err, user, info) {
if (err) { return reject(err) } if (err) { return reject(err) }
if (info && info.message) { return reject(new Error(info.message)) } if (info && info.message) { return reject(new Error(info.message)) }
if (!user) { return reject(new Error('INVALID_LOGIN')) } if (!user) { return reject(new Error('INVALID_LOGIN')) }
...@@ -61,12 +62,12 @@ router.post('/login', bruteforce.prevent, function (req, res, next) { ...@@ -61,12 +62,12 @@ router.post('/login', bruteforce.prevent, function (req, res, next) {
} }
}).then((user) => { }).then((user) => {
// LOGIN SUCCESS // LOGIN SUCCESS
return req.logIn(user, function (err) { return req.logIn(user, { session: false }, function (err) {
if (err) { return next(err) } if (err) { return next(err) }
req.brute.reset(function () { req.brute.reset(function () {
return res.redirect('/') return res.redirect('/')
}) })
}) || true })
}).catch(err => { }).catch(err => {
// LOGIN FAIL // LOGIN FAIL
if (err.message === 'INVALID_LOGIN') { if (err.message === 'INVALID_LOGIN') {
......
const passport = require('passport') const passport = require('passport')
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 NodeCache = require('node-cache')
const userCache = new NodeCache({ const securityHelper = require('../helpers/security')
stdTTL: 10,
checkperiod: 600,
deleteOnExpire: true
})
/* global WIKI */ /* global WIKI */
...@@ -24,22 +20,16 @@ module.exports = { ...@@ -24,22 +20,16 @@ module.exports = {
}) })
passport.deserializeUser(function (id, done) { passport.deserializeUser(function (id, done) {
const usr = userCache.get(id) WIKI.models.users.query().findById(id).then((user) => {
if (usr) { if (user) {
done(null, usr) done(null, user)
} else { } else {
WIKI.models.users.query().findById(id).then((user) => { done(new Error(WIKI.lang.t('auth:errors:usernotfound')), null)
if (user) { }
userCache.set(id, user) return true
done(null, user) }).catch((err) => {
} else { done(err, null)
done(new Error(WIKI.lang.t('auth:errors:usernotfound')), null) })
}
return true
}).catch((err) => {
done(err, null)
})
}
}) })
return this return this
...@@ -52,6 +42,16 @@ module.exports = { ...@@ -52,6 +42,16 @@ module.exports = {
_.pull(currentStrategies, 'session') _.pull(currentStrategies, 'session')
_.forEach(currentStrategies, stg => { passport.unuse(stg) }) _.forEach(currentStrategies, stg => { passport.unuse(stg) })
// Load JWT
passport.use('jwt', new passportJWT.Strategy({
jwtFromRequest: securityHelper.extractJWT,
secretOrKey: WIKI.config.sessionSecret,
audience: 'urn:wiki.js', // TODO: use value from admin
issuer: 'urn:wiki.js'
}, (jwtPayload, cb) => {
cb(null, jwtPayload)
}))
// Load enabled strategies // Load enabled strategies
const enabledStrategies = await WIKI.models.authentication.getStrategies() const enabledStrategies = await WIKI.models.authentication.getStrategies()
for (let idx in enabledStrategies) { for (let idx in enabledStrategies) {
......
...@@ -55,6 +55,8 @@ exports.up = knex => { ...@@ -55,6 +55,8 @@ exports.up = knex => {
table.charset('utf8mb4') table.charset('utf8mb4')
table.increments('id').primary() table.increments('id').primary()
table.string('name').notNullable() table.string('name').notNullable()
table.json('permissions').notNullable()
table.boolean('isSystem').notNullable().defaultTo(false)
table.string('createdAt').notNullable() table.string('createdAt').notNullable()
table.string('updatedAt').notNullable() table.string('updatedAt').notNullable()
}) })
...@@ -118,6 +120,17 @@ exports.up = knex => { ...@@ -118,6 +120,17 @@ exports.up = knex => {
table.string('createdAt').notNullable() table.string('createdAt').notNullable()
table.string('updatedAt').notNullable() table.string('updatedAt').notNullable()
}) })
// PAGE TREE ---------------------------
.createTable('pageTree', table => {
table.charset('utf8mb4')
table.increments('id').primary()
table.string('path').notNullable()
table.integer('depth').unsigned().notNullable()
table.string('title').notNullable()
table.boolean('isPrivate').notNullable().defaultTo(false)
table.boolean('isFolder').notNullable().defaultTo(false)
table.string('privateNS')
})
// RENDERERS --------------------------- // RENDERERS ---------------------------
.createTable('renderers', table => { .createTable('renderers', table => {
table.charset('utf8mb4') table.charset('utf8mb4')
...@@ -166,7 +179,6 @@ exports.up = knex => { ...@@ -166,7 +179,6 @@ exports.up = knex => {
table.string('password') table.string('password')
table.boolean('tfaIsActive').notNullable().defaultTo(false) table.boolean('tfaIsActive').notNullable().defaultTo(false)
table.string('tfaSecret') table.string('tfaSecret')
table.enum('role', ['admin', 'guest', 'user']).notNullable().defaultTo('guest')
table.string('jobTitle').defaultTo('') table.string('jobTitle').defaultTo('')
table.string('location').defaultTo('') table.string('location').defaultTo('')
table.string('pictureUrl') table.string('pictureUrl')
...@@ -221,6 +233,11 @@ exports.up = knex => { ...@@ -221,6 +233,11 @@ exports.up = knex => {
table.integer('authorId').unsigned().references('id').inTable('users') table.integer('authorId').unsigned().references('id').inTable('users')
table.integer('creatorId').unsigned().references('id').inTable('users') table.integer('creatorId').unsigned().references('id').inTable('users')
}) })
.table('pageTree', table => {
table.integer('parent').unsigned().references('id').inTable('pageTree')
table.integer('pageId').unsigned().references('id').inTable('pages')
table.string('localeCode', 2).references('code').inTable('locales')
})
.table('users', table => { .table('users', table => {
table.string('providerKey').references('key').inTable('authentication').notNullable().defaultTo('local') table.string('providerKey').references('key').inTable('authentication').notNullable().defaultTo('local')
table.string('localeCode', 2).references('code').inTable('locales').notNullable().defaultTo('en') table.string('localeCode', 2).references('code').inTable('locales').notNullable().defaultTo('en')
......
const { SchemaDirectiveVisitor } = require('graphql-tools')
const { defaultFieldResolver } = require('graphql')
class AuthDirective extends SchemaDirectiveVisitor {
visitObject(type) {
this.ensureFieldsWrapped(type)
type._requiredAuthScopes = this.args.requires
}
// Visitor methods for nested types like fields and arguments
// also receive a details object that provides information about
// the parent and grandparent types.
visitFieldDefinition(field, details) {
this.ensureFieldsWrapped(details.objectType)
field._requiredAuthScopes = this.args.requires
}
visitArgumentDefinition(argument, details) {
this.ensureFieldsWrapped(details.objectType)
argument._requiredAuthScopes = this.args.requires
}
ensureFieldsWrapped(objectType) {
// Mark the GraphQLObjectType object to avoid re-wrapping:
if (objectType._authFieldsWrapped) return
objectType._authFieldsWrapped = true
const fields = objectType.getFields()
Object.keys(fields).forEach(fieldName => {
const field = fields[fieldName]
const { resolve = defaultFieldResolver } = field
field.resolve = async function (...args) {
// Get the required scopes from the field first, falling back
// to the objectType if no scopes is required by the field:
const requiredScopes = field._requiredAuthScopes || objectType._requiredAuthScopes
if (!requiredScopes) {
return resolve.apply(this, args)
}
const context = args[2]
console.info(context.req.user)
// const user = await getUser(context.headers.authToken)
// if (!user.hasRole(requiredScopes)) {
// throw new Error('not authorized')
// }
return resolve.apply(this, args)
}
})
}
}
module.exports = AuthDirective
...@@ -31,6 +31,10 @@ resolversObj.forEach(resolver => { ...@@ -31,6 +31,10 @@ resolversObj.forEach(resolver => {
_.merge(resolvers, resolver) _.merge(resolvers, resolver)
}) })
// Directives
let schemaDirectives = autoload(path.join(WIKI.SERVERPATH, 'graph/directives'))
// Live Trail Logger (admin) // Live Trail Logger (admin)
let LiveTrailLogger = winston.transports.LiveTrailLogger = function (options) { let LiveTrailLogger = winston.transports.LiveTrailLogger = function (options) {
...@@ -55,5 +59,6 @@ WIKI.logger.info(`GraphQL Schema: [ OK ]`) ...@@ -55,5 +59,6 @@ WIKI.logger.info(`GraphQL Schema: [ OK ]`)
module.exports = { module.exports = {
typeDefs, typeDefs,
resolvers resolvers,
schemaDirectives
} }
...@@ -3,8 +3,6 @@ const fs = require('fs-extra') ...@@ -3,8 +3,6 @@ const fs = require('fs-extra')
const path = require('path') const path = require('path')
const graphHelper = require('../../helpers/graph') const graphHelper = require('../../helpers/graph')
// const getFieldNames = require('graphql-list-fields')
/* global WIKI */ /* global WIKI */
module.exports = { module.exports = {
...@@ -16,7 +14,7 @@ module.exports = { ...@@ -16,7 +14,7 @@ module.exports = {
}, },
AuthenticationQuery: { AuthenticationQuery: {
async strategies(obj, args, context, info) { async strategies(obj, args, context, info) {
let strategies = await WIKI.models.authentication.getStrategies() let strategies = await WIKI.models.authentication.getStrategies(args.isEnabled)
strategies = strategies.map(stg => { strategies = strategies.map(stg => {
const strategyInfo = _.find(WIKI.data.authentication, ['key', stg.key]) || {} const strategyInfo = _.find(WIKI.data.authentication, ['key', stg.key]) || {}
return { return {
...@@ -34,8 +32,6 @@ module.exports = { ...@@ -34,8 +32,6 @@ module.exports = {
}, []), 'key') }, []), 'key')
} }
}) })
if (args.filter) { strategies = graphHelper.filter(strategies, args.filter) }
if (args.orderBy) { strategies = graphHelper.orderBy(strategies, args.orderBy) }
return strategies return strategies
} }
}, },
......
...@@ -16,8 +16,7 @@ extend type Mutation { ...@@ -16,8 +16,7 @@ extend type Mutation {
type AuthenticationQuery { type AuthenticationQuery {
strategies( strategies(
filter: String isEnabled: Boolean
orderBy: String
): [AuthenticationStrategy] ): [AuthenticationStrategy]
} }
...@@ -54,16 +53,18 @@ type AuthenticationStrategy { ...@@ -54,16 +53,18 @@ type AuthenticationStrategy {
description: String description: String
useForm: Boolean! useForm: Boolean!
logo: String logo: String
color: String
website: String website: String
icon: String icon: String
config: [KeyValuePair] config: [KeyValuePair] @auth(requires: ["manage:system"])
selfRegistration: Boolean! selfRegistration: Boolean!
domainWhitelist: [String]! domainWhitelist: [String]! @auth(requires: ["manage:system"])
autoEnrollGroups: [Int]! autoEnrollGroups: [Int]! @auth(requires: ["manage:system"])
} }
type AuthenticationLoginResponse { type AuthenticationLoginResponse {
responseResult: ResponseStatus responseResult: ResponseStatus
jwt: String
tfaRequired: Boolean tfaRequired: Boolean
tfaLoginToken: String tfaLoginToken: String
} }
......
...@@ -13,6 +13,10 @@ enum RightRole { ...@@ -13,6 +13,10 @@ enum RightRole {
manage manage
} }
# DIRECTIVES
directive @auth(requires: [String]) on QUERY | FIELD_DEFINITION | ARGUMENT_DEFINITION
# TYPES # TYPES
type KeyValuePair { type KeyValuePair {
......
const Promise = require('bluebird') const Promise = require('bluebird')
const crypto = require('crypto') const crypto = require('crypto')
const passportJWT = require('passport-jwt')
module.exports = { module.exports = {
sanitizeCommitUser (user) { sanitizeCommitUser (user) {
...@@ -21,5 +22,18 @@ module.exports = { ...@@ -21,5 +22,18 @@ module.exports = {
}).then(buf => { }).then(buf => {
return buf.toString('hex') return buf.toString('hex')
}) })
},
async extractJWT (req) {
return passportJWT.ExtractJwt.fromExtractors([
passportJWT.ExtractJwt.fromAuthHeaderAsBearerToken(),
(req) => {
let token = null
if (req && req.cookies) {
token = req.cookies['jwt']
}
return token
}
])(req)
} }
} }
...@@ -7,8 +7,6 @@ const express = require('express') ...@@ -7,8 +7,6 @@ const express = require('express')
const favicon = require('serve-favicon') const favicon = require('serve-favicon')
const http = require('http') const http = require('http')
const path = require('path') const path = require('path')
const session = require('express-session')
const SessionRedisStore = require('connect-redis')(session)
const { ApolloServer } = require('apollo-server-express') const { ApolloServer } = require('apollo-server-express')
// const oauth2orize = require('oauth2orize') // const oauth2orize = require('oauth2orize')
...@@ -66,20 +64,9 @@ module.exports = async () => { ...@@ -66,20 +64,9 @@ module.exports = async () => {
// Passport Authentication // Passport Authentication
// ---------------------------------------- // ----------------------------------------
let sessionStore = new SessionRedisStore({
client: WIKI.redis
})
app.use(cookieParser()) app.use(cookieParser())
app.use(session({
name: 'wikijs.sid',
store: sessionStore,
secret: WIKI.config.sessionSecret,
resave: false,
saveUninitialized: false
}))
app.use(WIKI.auth.passport.initialize()) app.use(WIKI.auth.passport.initialize())
app.use(WIKI.auth.passport.session()) app.use(mw.auth.jwt)
// ---------------------------------------- // ----------------------------------------
// SEO // SEO
...@@ -145,7 +132,7 @@ module.exports = async () => { ...@@ -145,7 +132,7 @@ module.exports = async () => {
app.use('/', ctrl.auth) app.use('/', ctrl.auth)
app.use('/', mw.auth, ctrl.common) app.use('/', mw.auth.checkPath, ctrl.common)
// ---------------------------------------- // ----------------------------------------
// Error handling // Error handling
......
const jwt = require('jsonwebtoken')
const moment = require('moment')
const securityHelper = require('../helpers/security')
/* global WIKI */ /* global WIKI */
/** /**
* Authentication middleware * Authentication middleware
*/ */
module.exports = (req, res, next) => { module.exports = {
// Is user authenticated ? jwt(req, res, next) {
WIKI.auth.passport.authenticate('jwt', {session: false}, async (err, user, info) => {
if (err) { return next() }
console.info(err, user, info)
// Expired but still valid within 7 days, just renew
if (info instanceof jwt.TokenExpiredError && moment().subtract(7, 'days').isBefore(info.expiredAt)) {
const jwtPayload = jwt.decode(securityHelper.extractJWT(req))
console.info(jwtPayload)
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.headers('new-jwt', newToken.token)
} else {
res.cookie('jwt', newToken.token, { expires: moment().add(7, 'days').toDate() })
}
} catch (err) {
return next()
}
}
if (!req.isAuthenticated()) { // JWT is NOT valid
if (WIKI.config.public !== true) { if (!user) { return next() }
return res.redirect('/login')
// 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 { } else {
// req.user = rights.guest res.locals.isGuest = false
res.locals.isGuest = true
} }
} else {
res.locals.isGuest = false
}
// Check permissions // Check permissions
// res.locals.rights = rights.check(req) // res.locals.rights = rights.check(req)
// if (!res.locals.rights.read) { // if (!res.locals.rights.read) {
// return res.render('error-forbidden') // return res.render('error-forbidden')
// } // }
// Expose user data // Expose user data
res.locals.user = req.user res.locals.user = req.user
return next() return next()
}
} }
...@@ -30,13 +30,13 @@ module.exports = class Authentication extends Model { ...@@ -30,13 +30,13 @@ module.exports = class Authentication extends Model {
} }
} }
static async getStrategies() { static async getStrategies(isEnabled) {
const strategies = await WIKI.models.authentication.query() const strategies = await WIKI.models.authentication.query().where(_.isBoolean(isEnabled) ? { isEnabled } : {})
return strategies.map(str => ({ return _.sortBy(strategies.map(str => ({
...str, ...str,
domainWhitelist: _.get(str.domainWhitelist, 'v', []), domainWhitelist: _.get(str.domainWhitelist, 'v', []),
autoEnrollGroups: _.get(str.autoEnrollGroups, 'v', []) autoEnrollGroups: _.get(str.autoEnrollGroups, 'v', [])
})) })), ['title'])
} }
static async refreshStrategiesFromDisk() { static async refreshStrategiesFromDisk() {
......
...@@ -4,6 +4,7 @@ const bcrypt = require('bcryptjs-then') ...@@ -4,6 +4,7 @@ const bcrypt = require('bcryptjs-then')
const _ = require('lodash') const _ = require('lodash')
const tfa = require('node-2fa') const tfa = require('node-2fa')
const securityHelper = require('../helpers/security') const securityHelper = require('../helpers/security')
const jwt = require('jsonwebtoken')
const Model = require('objection').Model const Model = require('objection').Model
const bcryptRegexp = /^\$2[ayb]\$[0-9]{2}\$[A-Za-z0-9./]{53}$/ const bcryptRegexp = /^\$2[ayb]\$[0-9]{2}\$[A-Za-z0-9./]{53}$/
...@@ -199,7 +200,7 @@ module.exports = class User extends Model { ...@@ -199,7 +200,7 @@ module.exports = class User extends Model {
// Authenticate // Authenticate
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
WIKI.auth.passport.authenticate(opts.strategy, async (err, user, info) => { WIKI.auth.passport.authenticate(opts.strategy, { session: false }, async (err, user, info) => {
if (err) { return reject(err) } if (err) { return reject(err) }
if (!user) { return reject(new WIKI.Error.AuthLoginFailed()) } if (!user) { return reject(new WIKI.Error.AuthLoginFailed()) }
...@@ -218,9 +219,11 @@ module.exports = class User extends Model { ...@@ -218,9 +219,11 @@ module.exports = class User extends Model {
} }
} else { } else {
// No 2FA, log in user // No 2FA, log in user
return context.req.logIn(user, err => { return context.req.logIn(user, { session: false }, async err => {
if (err) { return reject(err) } if (err) { return reject(err) }
const jwtToken = await WIKI.models.users.refreshToken(user)
resolve({ resolve({
jwt: jwtToken.token,
tfaRequired: false tfaRequired: false
}) })
}) })
...@@ -232,6 +235,33 @@ module.exports = class User extends Model { ...@@ -232,6 +235,33 @@ module.exports = class User extends Model {
} }
} }
static async refreshToken(user) {
if (_.isSafeInteger(user)) {
user = await WIKI.models.users.query().findById(user)
if (!user) {
WIKI.logger.warn(`Failed to refresh token for user ${user}: Not found.`)
throw new WIKI.Error.AuthGenericError()
}
}
return {
token: jwt.sign({
id: user.id,
email: user.email,
name: user.name,
pictureUrl: user.pictureUrl,
timezone: user.timezone,
localeCode: user.localeCode,
defaultEditor: user.defaultEditor,
permissions: []
}, WIKI.config.sessionSecret, {
expiresIn: '10s',
audience: 'urn:wiki.js', // TODO: use value from admin
issuer: 'urn:wiki.js'
}),
user
}
}
static async loginTFA(opts, context) { static async loginTFA(opts, context) {
if (opts.securityCode.length === 6 && opts.loginToken.length === 64) { if (opts.securityCode.length === 6 && opts.loginToken.length === 64) {
let result = await WIKI.redis.get(`tfa:${opts.loginToken}`) let result = await WIKI.redis.get(`tfa:${opts.loginToken}`)
......
...@@ -3,6 +3,7 @@ title: Auth0 ...@@ -3,6 +3,7 @@ title: Auth0
description: Auth0 provides universal identity platform for web, mobile, IoT, and internal applications. description: Auth0 provides universal identity platform for web, mobile, IoT, and internal applications.
author: requarks.io author: requarks.io
logo: https://static.requarks.io/logo/auth0.svg logo: https://static.requarks.io/logo/auth0.svg
color: deep-orange
website: https://auth0.com/ website: https://auth0.com/
useForm: false useForm: false
props: props:
......
...@@ -3,6 +3,7 @@ title: Azure Active Directory ...@@ -3,6 +3,7 @@ title: Azure Active Directory
description: Azure Active Directory (Azure AD) is Microsoft’s multi-tenant, cloud-based directory, and identity management service that combines core directory services, application access management, and identity protection into a single solution. description: Azure Active Directory (Azure AD) is Microsoft’s multi-tenant, cloud-based directory, and identity management service that combines core directory services, application access management, and identity protection into a single solution.
author: requarks.io author: requarks.io
logo: https://static.requarks.io/logo/azure.svg logo: https://static.requarks.io/logo/azure.svg
color: blue darken-3
website: https://azure.microsoft.com/services/active-directory/ website: https://azure.microsoft.com/services/active-directory/
useForm: false useForm: false
props: props:
......
...@@ -3,6 +3,7 @@ title: CAS ...@@ -3,6 +3,7 @@ title: CAS
description: The Central Authentication Service (CAS) is a single sign-on protocol for the web. description: The Central Authentication Service (CAS) is a single sign-on protocol for the web.
author: requarks.io author: requarks.io
logo: https://static.requarks.io/logo/cas.svg logo: https://static.requarks.io/logo/cas.svg
color: green darken-2
website: https://apereo.github.io/cas/ website: https://apereo.github.io/cas/
useForm: false useForm: false
props: props:
......
...@@ -3,6 +3,7 @@ title: Discord ...@@ -3,6 +3,7 @@ title: Discord
description: Discord is a proprietary freeware VoIP application designed for gaming communities, that specializes in text, video and audio communication between users in a chat channel. description: Discord is a proprietary freeware VoIP application designed for gaming communities, that specializes in text, video and audio communication between users in a chat channel.
author: requarks.io author: requarks.io
logo: https://static.requarks.io/logo/discord.svg logo: https://static.requarks.io/logo/discord.svg
color: indigo lighten-2
website: https://discordapp.com/ website: https://discordapp.com/
useForm: false useForm: false
props: props:
......
...@@ -3,6 +3,7 @@ title: Dropbox ...@@ -3,6 +3,7 @@ title: Dropbox
description: Dropbox is a file hosting service that offers cloud storage, file synchronization, personal cloud, and client software. description: Dropbox is a file hosting service that offers cloud storage, file synchronization, personal cloud, and client software.
author: requarks.io author: requarks.io
logo: https://static.requarks.io/logo/dropbox.svg logo: https://static.requarks.io/logo/dropbox.svg
color: blue darken-2
website: https://dropbox.com website: https://dropbox.com
useForm: false useForm: false
props: props:
......
...@@ -3,6 +3,7 @@ title: Facebook ...@@ -3,6 +3,7 @@ title: Facebook
description: Facebook is an online social media and social networking service company. description: Facebook is an online social media and social networking service company.
author: requarks.io author: requarks.io
logo: https://static.requarks.io/logo/facebook.svg logo: https://static.requarks.io/logo/facebook.svg
color: indigo
website: https://facebook.com/ website: https://facebook.com/
useForm: false useForm: false
props: props:
......
...@@ -3,6 +3,7 @@ title: GitHub ...@@ -3,6 +3,7 @@ title: GitHub
description: GitHub Inc. is a web-based hosting service for version control using Git. description: GitHub Inc. is a web-based hosting service for version control using Git.
author: requarks.io author: requarks.io
logo: https://static.requarks.io/logo/github.svg logo: https://static.requarks.io/logo/github.svg
color: grey darken-3
website: https://github.com website: https://github.com
useForm: false useForm: false
props: props:
......
...@@ -3,6 +3,7 @@ title: Google ...@@ -3,6 +3,7 @@ title: Google
description: Google specializes in Internet-related services and products, which include online advertising technologies, search engine, cloud computing, software, and hardware. description: Google specializes in Internet-related services and products, which include online advertising technologies, search engine, cloud computing, software, and hardware.
author: requarks.io author: requarks.io
logo: https://static.requarks.io/logo/google.svg logo: https://static.requarks.io/logo/google.svg
color: red darken-1
website: https://console.developers.google.com/ website: https://console.developers.google.com/
useForm: false useForm: false
props: props:
......
...@@ -3,6 +3,7 @@ title: LDAP / Active Directory ...@@ -3,6 +3,7 @@ title: LDAP / Active Directory
description: Active Directory is a directory service that Microsoft developed for the Windows domain networks. description: Active Directory is a directory service that Microsoft developed for the Windows domain networks.
author: requarks.io author: requarks.io
logo: https://static.requarks.io/logo/active-directory.svg logo: https://static.requarks.io/logo/active-directory.svg
color: blue darken-3
website: https://www.microsoft.com/windowsserver website: https://www.microsoft.com/windowsserver
useForm: true useForm: true
props: props:
......
...@@ -3,6 +3,7 @@ title: Local ...@@ -3,6 +3,7 @@ title: Local
description: Built-in authentication for Wiki.js description: Built-in authentication for Wiki.js
author: requarks.io author: requarks.io
logo: https://static.requarks.io/logo/wikijs.svg logo: https://static.requarks.io/logo/wikijs.svg
color: yellow darken-3
website: https://wiki.js.org website: https://wiki.js.org
useForm: true useForm: true
props: {} props: {}
...@@ -3,6 +3,7 @@ title: Microsoft ...@@ -3,6 +3,7 @@ title: Microsoft
description: Microsoft is a software company, best known for it's Windows, Office, Azure, Xbox and Surface products. description: Microsoft is a software company, best known for it's Windows, Office, Azure, Xbox and Surface products.
author: requarks.io author: requarks.io
logo: https://static.requarks.io/logo/microsoft.svg logo: https://static.requarks.io/logo/microsoft.svg
color: blue
website: https://apps.dev.microsoft.com/ website: https://apps.dev.microsoft.com/
useForm: false useForm: false
props: props:
......
...@@ -3,6 +3,7 @@ title: Generic OAuth2 ...@@ -3,6 +3,7 @@ title: Generic OAuth2
description: OAuth 2 is an authorization framework that enables applications to obtain limited access to user accounts on an HTTP service. description: OAuth 2 is an authorization framework that enables applications to obtain limited access to user accounts on an HTTP service.
author: requarks.io author: requarks.io
logo: https://static.requarks.io/logo/oauth2.svg logo: https://static.requarks.io/logo/oauth2.svg
color: grey darken-4
website: https://oauth.net/2/ website: https://oauth.net/2/
useForm: false useForm: false
props: props:
......
...@@ -3,6 +3,7 @@ title: Generic OpenID Connect ...@@ -3,6 +3,7 @@ title: Generic OpenID Connect
description: OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 protocol. description: OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 protocol.
author: requarks.io author: requarks.io
logo: https://static.requarks.io/logo/oidc.svg logo: https://static.requarks.io/logo/oidc.svg
color: blue-grey darken-2
website: http://openid.net/connect/ website: http://openid.net/connect/
useForm: false useForm: false
props: props:
......
...@@ -3,6 +3,7 @@ title: Okta ...@@ -3,6 +3,7 @@ title: Okta
description: Okta provide secure identity management and single sign-on to any application. description: Okta provide secure identity management and single sign-on to any application.
author: requarks.io author: requarks.io
logo: https://static.requarks.io/logo/okta.svg logo: https://static.requarks.io/logo/okta.svg
color: blue darken-1
website: https://www.okta.com/ website: https://www.okta.com/
useForm: false useForm: false
props: props:
......
...@@ -3,6 +3,7 @@ title: SAML 2.0 ...@@ -3,6 +3,7 @@ title: SAML 2.0
description: Security Assertion Markup Language 2.0 (SAML 2.0) is a version of the SAML standard for exchanging authentication and authorization data between security domains. description: Security Assertion Markup Language 2.0 (SAML 2.0) is a version of the SAML standard for exchanging authentication and authorization data between security domains.
author: requarks.io author: requarks.io
logo: https://static.requarks.io/logo/saml.svg logo: https://static.requarks.io/logo/saml.svg
color: red darken-3
website: https://wiki.oasis-open.org/security/FrontPage website: https://wiki.oasis-open.org/security/FrontPage
useForm: false useForm: false
props: props:
......
...@@ -3,6 +3,7 @@ title: Slack ...@@ -3,6 +3,7 @@ title: Slack
description: Slack is a cloud-based set of proprietary team collaboration tools and services. description: Slack is a cloud-based set of proprietary team collaboration tools and services.
author: requarks.io author: requarks.io
logo: https://static.requarks.io/logo/slack.svg logo: https://static.requarks.io/logo/slack.svg
color: green
website: https://api.slack.com/docs/oauth website: https://api.slack.com/docs/oauth
useForm: false useForm: false
props: props:
......
...@@ -3,6 +3,7 @@ title: Twitch ...@@ -3,6 +3,7 @@ title: Twitch
description: Twitch is a live streaming video platform. description: Twitch is a live streaming video platform.
author: requarks.io author: requarks.io
logo: https://static.requarks.io/logo/twitch.svg logo: https://static.requarks.io/logo/twitch.svg
color: indigo darken-2
website: https://dev.twitch.tv/docs/authentication/ website: https://dev.twitch.tv/docs/authentication/
useForm: false useForm: false
props: props:
......
const path = require('path') const path = require('path')
const os = require('os')
/* global WIKI */ /* global WIKI */
...@@ -133,6 +132,20 @@ module.exports = () => { ...@@ -133,6 +132,20 @@ module.exports = () => {
nativeName: 'English' nativeName: 'English'
}) })
// Create default locale
WIKI.logger.info('Creating default groups...')
const adminGroup = await WIKI.models.groups.query().insert({
name: 'Administrators',
permissions: JSON.stringify(['manage:system']),
isSystem: true
})
const guestGroup = await WIKI.models.groups.query().insert({
name: 'Guests',
permissions: JSON.stringify(['read:page:/']),
isSystem: true
})
// Load authentication strategies + enable local // Load authentication strategies + enable local
await WIKI.models.authentication.refreshStrategiesFromDisk() await WIKI.models.authentication.refreshStrategiesFromDisk()
await WIKI.models.authentication.query().patch({ isEnabled: true }).where('key', 'local') await WIKI.models.authentication.query().patch({ isEnabled: true }).where('key', 'local')
...@@ -160,35 +173,33 @@ module.exports = () => { ...@@ -160,35 +173,33 @@ module.exports = () => {
providerKey: 'local', providerKey: 'local',
email: req.body.adminEmail email: req.body.adminEmail
}) })
await WIKI.models.users.query().insert({ const adminUser = await WIKI.models.users.query().insert({
email: req.body.adminEmail, email: req.body.adminEmail,
provider: 'local', provider: 'local',
password: req.body.adminPassword, password: req.body.adminPassword,
name: 'Administrator', name: 'Administrator',
role: 'admin',
locale: 'en', locale: 'en',
defaultEditor: 'markdown', defaultEditor: 'markdown',
tfaIsActive: false tfaIsActive: false
}) })
await adminUser.$relatedQuery('groups').relate(adminGroup.id)
// Create Guest account // Create Guest account
WIKI.logger.info('Creating guest account...') WIKI.logger.info('Creating guest account...')
const guestUsr = await WIKI.models.users.query().findOne({ await WIKI.models.users.query().delete().where({
providerKey: 'local', providerKey: 'local',
email: 'guest@example.com' email: 'guest@example.com'
}) })
if (!guestUsr) { const guestUser = await WIKI.models.users.query().insert({
await WIKI.models.users.query().insert({ provider: 'local',
provider: 'local', email: 'guest@example.com',
email: 'guest@example.com', name: 'Guest',
name: 'Guest', password: '',
password: '', locale: 'en',
role: 'guest', defaultEditor: 'markdown',
locale: 'en', tfaIsActive: false
defaultEditor: 'markdown', })
tfaIsActive: false await guestUser.$relatedQuery('groups').relate(guestGroup.id)
})
}
WIKI.logger.info('Setup is complete!') WIKI.logger.info('Setup is complete!')
res.json({ res.json({
......
...@@ -22,7 +22,7 @@ html ...@@ -22,7 +22,7 @@ html
var siteConfig = !{JSON.stringify({ title: config.title, theme: config.theming.theme, darkMode: config.theming.darkMode, lang: config.lang.code })} var siteConfig = !{JSON.stringify({ title: config.title, theme: config.theming.theme, darkMode: config.theming.darkMode, lang: config.lang.code })}
//- CSS //- CSS
link(type='text/css', rel='stylesheet', href='https://fonts.googleapis.com/icon?family=Roboto:400,500,700|Source+Code+Pro:400,700|Material+Icons') link(type='text/css', rel='stylesheet', href='https://fonts.googleapis.com/icon?family=Roboto:400,500,700|Varela+Round|Source+Code+Pro:400,700|Material+Icons')
link(type='text/css', rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/material-design-iconic-font/2.2.0/css/material-design-iconic-font.min.css') link(type='text/css', rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/material-design-iconic-font/2.2.0/css/material-design-iconic-font.min.css')
......
This diff was suppressed by a .gitattributes entry.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment