refactor: migrate to objection.js + knex
This commit is contained in:
1 parent
e03e6826a8
commit
c9b643fbf0
46 files changed
+614
-502
No files matched your search
@@ -220,10 +220,11 @@
|
||||
)
|
||||
v-flex(xs6)
|
||||
v-text-field(
|
||||
ref='adminPasswordConfirm',
|
||||
v-model='conf.adminPasswordConfirm',
|
||||
label='Confirm Password',
|
||||
hint='Verify your password again.',
|
||||
v-validate='{ required: true, confirmed: `$adminPassword` }',
|
||||
v-validate='{ required: true, min: 8 }',
|
||||
data-vv-name='adminPasswordConfirm',
|
||||
data-vv-as='Confirm Password',
|
||||
data-vv-scope='admin',
|
||||
@@ -308,10 +309,6 @@ export default {
|
||||
wikiVersion: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
langs: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -394,7 +391,7 @@ export default {
|
||||
async proceedToUpgrade () {
|
||||
if (this.state < 5) {
|
||||
const validationSuccess = await this.$validator.validateAll('admin')
|
||||
if (!validationSuccess) {
|
||||
if (!validationSuccess || this.conf.adminPassword !== this.conf.adminPasswordConfirm) {
|
||||
this.state = 4
|
||||
return
|
||||
}
|
||||
|
||||
+3
-2
@@ -23,12 +23,13 @@ paths:
|
||||
# ---------------------------------------------------------------------
|
||||
# Supported Database Engines:
|
||||
# - postgres = PostgreSQL 9.5 or later
|
||||
# - mysql = MySQL 5.7.8 or later
|
||||
# - mysql = MySQL 8.0 / MariaDB 10.2.7 or later
|
||||
# - mssql = MS SQL Server 2012 or later
|
||||
# - sqlite = SQLite 3.9 or later
|
||||
|
||||
db:
|
||||
type: postgres
|
||||
# PostgreSQL and MySQL only:
|
||||
# PostgreSQL / MySQL / MariaDB / MS SQL Server only:
|
||||
host: localhost
|
||||
port: 5432
|
||||
user: wikijs
|
||||
|
||||
+27
-27
@@ -9,7 +9,6 @@
|
||||
"restart": "node wiki restart",
|
||||
"dev": "node wiki dev",
|
||||
"build": "webpack --profile --config dev/webpack/webpack.prod.js",
|
||||
"build:locales": "node dev/tasks/localization",
|
||||
"watch": "webpack --config dev/webpack/webpack.dev.js",
|
||||
"test": "eslint --format codeframe --ext .js,.vue . && pug-lint server/views && jest"
|
||||
},
|
||||
@@ -43,9 +42,9 @@
|
||||
"axios": "0.18.0",
|
||||
"bcryptjs-then": "1.0.1",
|
||||
"bluebird": "3.5.1",
|
||||
"body-parser": "1.18.2",
|
||||
"body-parser": "1.18.3",
|
||||
"bugsnag": "2.3.1",
|
||||
"bull": "3.4.1",
|
||||
"bull": "3.4.2",
|
||||
"cheerio": "1.0.0-rc.2",
|
||||
"child-process-promise": "2.2.1",
|
||||
"chokidar": "2.0.3",
|
||||
@@ -61,15 +60,15 @@
|
||||
"express-brute": "1.0.1",
|
||||
"express-brute-redis": "0.0.1",
|
||||
"express-session": "1.15.6",
|
||||
"file-type": "7.7.1",
|
||||
"file-type": "8.0.0",
|
||||
"filesize.js": "1.0.2",
|
||||
"follow-redirects": "1.4.1",
|
||||
"follow-redirects": "1.5.0",
|
||||
"fs-extra": "6.0.1",
|
||||
"getos": "3.1.0",
|
||||
"graphql": "0.13.2",
|
||||
"graphql-list-fields": "2.0.2",
|
||||
"graphql-tools": "3.0.1",
|
||||
"i18next": "11.3.1",
|
||||
"i18next": "11.3.2",
|
||||
"i18next-express-middleware": "1.1.1",
|
||||
"i18next-localstorage-cache": "1.1.1",
|
||||
"i18next-node-fs-backend": "1.0.0",
|
||||
@@ -78,6 +77,7 @@
|
||||
"js-yaml": "3.11.0",
|
||||
"jsonwebtoken": "8.2.1",
|
||||
"klaw": "2.1.1",
|
||||
"knex": "0.14.6",
|
||||
"lodash": "4.17.10",
|
||||
"markdown-it": "8.4.1",
|
||||
"markdown-it-abbr": "1.0.4",
|
||||
@@ -96,12 +96,14 @@
|
||||
"mathjax-node": "2.1.0",
|
||||
"mime-types": "2.1.18",
|
||||
"moment": "2.22.1",
|
||||
"moment-timezone": "0.5.16",
|
||||
"mongodb": "3.0.7",
|
||||
"moment-timezone": "0.5.17",
|
||||
"mongodb": "3.1.0-beta4",
|
||||
"mssql": "4.1.0",
|
||||
"multer": "1.3.0",
|
||||
"mysql2": "1.5.3",
|
||||
"node-2fa": "1.1.2",
|
||||
"oauth2orize": "1.11.0",
|
||||
"objection": "1.1.8",
|
||||
"ora": "2.1.0",
|
||||
"passport": "0.4.0",
|
||||
"passport-auth0": "0.6.1",
|
||||
@@ -117,20 +119,18 @@
|
||||
"passport-slack": "0.0.7",
|
||||
"passport-twitch": "1.0.3",
|
||||
"passport-windowslive": "1.0.2",
|
||||
"pg": "6.4.2",
|
||||
"pg": "7.4.3",
|
||||
"pg-hstore": "2.3.2",
|
||||
"pg-promise": "7.5.3",
|
||||
"pm2": "2.10.3",
|
||||
"pm2": "2.10.4",
|
||||
"pug": "2.0.3",
|
||||
"qr-image": "3.2.0",
|
||||
"raven": "2.6.1",
|
||||
"raven": "2.6.2",
|
||||
"read-chunk": "2.1.0",
|
||||
"remove-markdown": "0.2.2",
|
||||
"request": "2.85.0",
|
||||
"request": "2.86.0",
|
||||
"request-promise": "4.2.2",
|
||||
"scim-query-filter-parser": "1.1.0",
|
||||
"semver": "5.5.0",
|
||||
"sequelize": "4.37.7",
|
||||
"serve-favicon": "2.5.0",
|
||||
"sqlite3": "4.0.0",
|
||||
"uuid": "3.2.1",
|
||||
@@ -145,7 +145,7 @@
|
||||
"apollo-client-preset": "1.0.8",
|
||||
"apollo-fetch": "0.7.0",
|
||||
"apollo-link-batch-http": "1.2.2",
|
||||
"autoprefixer": "8.4.1",
|
||||
"autoprefixer": "8.5.0",
|
||||
"babel-cli": "6.26.0",
|
||||
"babel-core": "6.26.3",
|
||||
"babel-eslint": "8.2.3",
|
||||
@@ -169,7 +169,7 @@
|
||||
"eslint": "4.19.1",
|
||||
"eslint-config-requarks": "1.0.7",
|
||||
"eslint-config-standard": "11.0.0",
|
||||
"eslint-plugin-import": "2.11.0",
|
||||
"eslint-plugin-import": "2.12.0",
|
||||
"eslint-plugin-node": "6.0.1",
|
||||
"eslint-plugin-promise": "3.7.0",
|
||||
"eslint-plugin-standard": "3.1.0",
|
||||
@@ -183,8 +183,8 @@
|
||||
"html-webpack-pug-plugin": "0.3.0",
|
||||
"i18next-xhr-backend": "1.5.1",
|
||||
"ignore-loader": "0.1.2",
|
||||
"jest": "22.4.3",
|
||||
"jest-junit": "3.7.0",
|
||||
"jest": "22.4.4",
|
||||
"jest-junit": "4.0.0",
|
||||
"js-cookie": "2.2.0",
|
||||
"lodash-webpack-plugin": "0.11.5",
|
||||
"mini-css-extract-plugin": "0.4.0",
|
||||
@@ -196,7 +196,7 @@
|
||||
"postcss-flexibility": "2.0.0",
|
||||
"postcss-import": "11.1.0",
|
||||
"postcss-loader": "2.1.5",
|
||||
"postcss-selector-parser": "4.0.0",
|
||||
"postcss-selector-parser": "5.0.0-rc.3",
|
||||
"pug-lint": "2.5.0",
|
||||
"pug-loader": "2.4.0",
|
||||
"pug-plain-loader": "1.0.0",
|
||||
@@ -218,23 +218,23 @@
|
||||
"vue-clipboards": "1.2.4",
|
||||
"vue-codemirror": "4.0.5",
|
||||
"vue-hot-reload-api": "2.3.0",
|
||||
"vue-loader": "15.0.10",
|
||||
"vue-loader": "15.1.0",
|
||||
"vue-material-design-icons": "1.4.0",
|
||||
"vue-moment": "3.2.0",
|
||||
"vue-moment": "4.0.0-0",
|
||||
"vue-router": "3.0.1",
|
||||
"vue-simple-breakpoints": "1.0.3",
|
||||
"vue-template-compiler": "2.5.16",
|
||||
"vuetify": "1.0.17",
|
||||
"vuetify": "1.0.18",
|
||||
"vuex": "3.0.1",
|
||||
"vuex-persistedstate": "2.5.2",
|
||||
"webpack": "4.8.2",
|
||||
"webpack-bundle-analyzer": "2.11.2",
|
||||
"vuex-persistedstate": "2.5.4",
|
||||
"webpack": "4.8.3",
|
||||
"webpack-bundle-analyzer": "2.12.0",
|
||||
"webpack-cli": "2.1.3",
|
||||
"webpack-dev-middleware": "3.1.3",
|
||||
"webpack-hot-middleware": "2.22.1",
|
||||
"webpack-hot-middleware": "2.22.2",
|
||||
"webpack-merge": "4.1.2",
|
||||
"whatwg-fetch": "2.0.4",
|
||||
"write-file-webpack-plugin": "4.2.0"
|
||||
"write-file-webpack-plugin": "4.3.2"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
|
||||
+1
-52
@@ -18,7 +18,7 @@ module.exports = {
|
||||
})
|
||||
|
||||
passport.deserializeUser(function (id, done) {
|
||||
WIKI.db.User.findById(id).then((user) => {
|
||||
WIKI.db.users.query().findById(id).then((user) => {
|
||||
if (user) {
|
||||
done(null, user)
|
||||
} else {
|
||||
@@ -58,57 +58,6 @@ module.exports = {
|
||||
WIKI.logger.info(`Authentication Provider ${strategy.title}: [ OK ]`)
|
||||
})
|
||||
|
||||
// Create Guest account for first-time
|
||||
|
||||
WIKI.db.User.findOne({
|
||||
where: {
|
||||
provider: 'local',
|
||||
email: 'guest@example.com'
|
||||
}
|
||||
}).then((c) => {
|
||||
if (c < 1) {
|
||||
return WIKI.db.User.create({
|
||||
provider: 'local',
|
||||
email: 'guest@example.com',
|
||||
name: 'Guest',
|
||||
password: '',
|
||||
role: 'guest'
|
||||
}).then(() => {
|
||||
WIKI.logger.info('[AUTH] Guest account created successfully!')
|
||||
return true
|
||||
}).catch((err) => {
|
||||
WIKI.logger.error('[AUTH] An error occured while creating guest account:')
|
||||
WIKI.logger.error(err)
|
||||
return err
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// .then(() => {
|
||||
// if (process.env.WIKI_JS_HEROKU) {
|
||||
// return WIKI.db.User.findOne({ provider: 'local', email: process.env.WIKI_ADMIN_EMAIL }).then((c) => {
|
||||
// if (c < 1) {
|
||||
// // Create root admin account (HEROKU ONLY)
|
||||
|
||||
// return WIKI.db.User.create({
|
||||
// provider: 'local',
|
||||
// email: process.env.WIKI_ADMIN_EMAIL,
|
||||
// name: 'Administrator',
|
||||
// password: '$2a$04$MAHRw785Xe/Jd5kcKzr3D.VRZDeomFZu2lius4gGpZZ9cJw7B7Mna', // admin123 (default)
|
||||
// role: 'admin'
|
||||
// }).then(() => {
|
||||
// WIKI.logger.info('[AUTH] Root admin account created successfully!')
|
||||
// return true
|
||||
// }).catch((err) => {
|
||||
// WIKI.logger.error('[AUTH] An error occured while creating root admin account:')
|
||||
// WIKI.logger.error(err)
|
||||
// return err
|
||||
// })
|
||||
// } else { return true }
|
||||
// })
|
||||
// } else { return true }
|
||||
// })
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
||||
+10
-13
@@ -59,17 +59,10 @@ module.exports = {
|
||||
subsets = WIKI.data.configNamespaces
|
||||
}
|
||||
|
||||
let results = await WIKI.db.Setting.findAll({
|
||||
attributes: ['key', 'config'],
|
||||
where: {
|
||||
key: {
|
||||
$in: subsets
|
||||
}
|
||||
}
|
||||
})
|
||||
let results = await WIKI.db.settings.query().select(['key', 'value']).whereIn('key', subsets)
|
||||
if (_.isArray(results) && results.length === subsets.length) {
|
||||
results.forEach(result => {
|
||||
WIKI.config[result.key] = result.config
|
||||
WIKI.config[result.key] = result.value
|
||||
})
|
||||
return true
|
||||
} else {
|
||||
@@ -88,14 +81,18 @@ module.exports = {
|
||||
subsets = WIKI.data.configNamespaces
|
||||
}
|
||||
|
||||
let trx = await WIKI.db.Objection.transaction.start(WIKI.db.knex)
|
||||
|
||||
try {
|
||||
for (let set of subsets) {
|
||||
await WIKI.db.Setting.upsert({
|
||||
key: set,
|
||||
config: _.get(WIKI.config, set, {})
|
||||
})
|
||||
console.info(set)
|
||||
await WIKI.db.settings.query(trx).patch({
|
||||
value: _.get(WIKI.config, set, {})
|
||||
}).where('key', set)
|
||||
}
|
||||
await trx.commit()
|
||||
} catch (err) {
|
||||
await trx.rollback(err)
|
||||
WIKI.logger.error(`Failed to save configuration to DB: ${err.message}`)
|
||||
return false
|
||||
}
|
||||
|
||||
+49
-85
@@ -1,55 +1,18 @@
|
||||
const _ = require('lodash')
|
||||
const fs = require('fs')
|
||||
const autoload = require('auto-load')
|
||||
const path = require('path')
|
||||
const Promise = require('bluebird')
|
||||
const Sequelize = require('sequelize')
|
||||
const Knex = require('knex')
|
||||
const Objection = require('objection')
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
const operatorsAliases = {
|
||||
$eq: Sequelize.Op.eq,
|
||||
$ne: Sequelize.Op.ne,
|
||||
$gte: Sequelize.Op.gte,
|
||||
$gt: Sequelize.Op.gt,
|
||||
$lte: Sequelize.Op.lte,
|
||||
$lt: Sequelize.Op.lt,
|
||||
$not: Sequelize.Op.not,
|
||||
$in: Sequelize.Op.in,
|
||||
$notIn: Sequelize.Op.notIn,
|
||||
$is: Sequelize.Op.is,
|
||||
$like: Sequelize.Op.like,
|
||||
$notLike: Sequelize.Op.notLike,
|
||||
$iLike: Sequelize.Op.iLike,
|
||||
$notILike: Sequelize.Op.notILike,
|
||||
$regexp: Sequelize.Op.regexp,
|
||||
$notRegexp: Sequelize.Op.notRegexp,
|
||||
$iRegexp: Sequelize.Op.iRegexp,
|
||||
$notIRegexp: Sequelize.Op.notIRegexp,
|
||||
$between: Sequelize.Op.between,
|
||||
$notBetween: Sequelize.Op.notBetween,
|
||||
$overlap: Sequelize.Op.overlap,
|
||||
$contains: Sequelize.Op.contains,
|
||||
$contained: Sequelize.Op.contained,
|
||||
$adjacent: Sequelize.Op.adjacent,
|
||||
$strictLeft: Sequelize.Op.strictLeft,
|
||||
$strictRight: Sequelize.Op.strictRight,
|
||||
$noExtendRight: Sequelize.Op.noExtendRight,
|
||||
$noExtendLeft: Sequelize.Op.noExtendLeft,
|
||||
$and: Sequelize.Op.and,
|
||||
$or: Sequelize.Op.or,
|
||||
$any: Sequelize.Op.any,
|
||||
$all: Sequelize.Op.all,
|
||||
$values: Sequelize.Op.values,
|
||||
$col: Sequelize.Op.col
|
||||
}
|
||||
|
||||
/**
|
||||
* PostgreSQL DB module
|
||||
*/
|
||||
module.exports = {
|
||||
Sequelize,
|
||||
Op: Sequelize.Op,
|
||||
|
||||
Objection,
|
||||
knex: null,
|
||||
/**
|
||||
* Initialize DB
|
||||
*
|
||||
@@ -57,65 +20,63 @@ module.exports = {
|
||||
*/
|
||||
init() {
|
||||
let self = this
|
||||
let dbModelsPath = path.join(WIKI.SERVERPATH, 'models')
|
||||
|
||||
// Define Sequelize instance
|
||||
|
||||
this.inst = new this.Sequelize(WIKI.config.db.db, WIKI.config.db.user, WIKI.config.db.pass, {
|
||||
let dbClient = null
|
||||
const dbConfig = (!_.isEmpty(process.env.WIKI_DB_CONNSTR)) ? process.env.WIKI_DB_CONNSTR : {
|
||||
host: WIKI.config.db.host,
|
||||
user: WIKI.config.db.user,
|
||||
password: WIKI.config.db.pass,
|
||||
database: WIKI.config.db.db,
|
||||
port: WIKI.config.db.port,
|
||||
dialect: WIKI.config.db.type,
|
||||
storage: WIKI.config.db.storage,
|
||||
pool: {
|
||||
max: 10,
|
||||
min: 0,
|
||||
idle: 10000
|
||||
},
|
||||
logging: log => { WIKI.logger.log('debug', log) },
|
||||
operatorsAliases
|
||||
filename: WIKI.config.db.storage
|
||||
}
|
||||
|
||||
switch (WIKI.config.db.type) {
|
||||
case 'postgres':
|
||||
dbClient = 'pg'
|
||||
break
|
||||
case 'mysql':
|
||||
dbClient = 'mysql2'
|
||||
break
|
||||
case 'mssql':
|
||||
dbClient = 'mssql'
|
||||
break
|
||||
case 'sqlite':
|
||||
dbClient = 'sqlite3'
|
||||
break
|
||||
default:
|
||||
WIKI.logger.error('Invalid DB Type')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
this.knex = Knex({
|
||||
client: dbClient,
|
||||
useNullAsDefault: true,
|
||||
connection: dbConfig,
|
||||
debug: WIKI.IS_DEBUG
|
||||
})
|
||||
|
||||
// Attempt to connect and authenticate to DB
|
||||
|
||||
this.inst.authenticate().then(() => {
|
||||
WIKI.logger.info(`Database (${WIKI.config.db.type}) connection: [ OK ]`)
|
||||
}).catch(err => {
|
||||
WIKI.logger.error(`Failed to connect to ${WIKI.config.db.type} instance.`)
|
||||
WIKI.logger.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
Objection.Model.knex(this.knex)
|
||||
|
||||
// Load DB Models
|
||||
|
||||
fs
|
||||
.readdirSync(dbModelsPath)
|
||||
.filter(file => {
|
||||
return (file.indexOf('.') !== 0 && file.indexOf('_') !== 0)
|
||||
})
|
||||
.forEach(file => {
|
||||
let modelName = _.upperFirst(_.camelCase(_.split(file, '.')[0]))
|
||||
self[modelName] = self.inst.import(path.join(dbModelsPath, file))
|
||||
})
|
||||
|
||||
// Associate DB Models
|
||||
|
||||
require(path.join(dbModelsPath, '_relations.js'))(self)
|
||||
const models = autoload(path.join(WIKI.SERVERPATH, 'db/models'))
|
||||
|
||||
// Set init tasks
|
||||
|
||||
let initTasks = {
|
||||
// -> Sync DB Schemas
|
||||
syncSchemas() {
|
||||
return self.inst.sync({
|
||||
force: false,
|
||||
logging: log => { WIKI.logger.log('debug', log) }
|
||||
// -> Migrate DB Schemas
|
||||
async syncSchemas() {
|
||||
return self.knex.migrate.latest({
|
||||
directory: path.join(WIKI.SERVERPATH, 'db/migrations'),
|
||||
tableName: 'migrations'
|
||||
})
|
||||
},
|
||||
// -> Set Connection App Name
|
||||
setAppName() {
|
||||
async setAppName() {
|
||||
switch (WIKI.config.db.type) {
|
||||
case 'postgres':
|
||||
return self.inst.query(`set application_name = 'WIKI.js'`, { raw: true })
|
||||
return self.knex.raw(`set application_name = 'Wiki.js'`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,6 +92,9 @@ module.exports = {
|
||||
|
||||
this.onReady = Promise.each(initTasksQueue, t => t()).return(true)
|
||||
|
||||
return this
|
||||
return {
|
||||
...this,
|
||||
...models
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,11 +52,7 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
async loadLocale(locale, opts = { silent: false }) {
|
||||
const res = await WIKI.db.Locale.findOne({
|
||||
where: {
|
||||
code: locale
|
||||
}
|
||||
})
|
||||
const res = await WIKI.db.locales.query().findOne('code', locale)
|
||||
if (res) {
|
||||
if (_.isPlainObject(res.strings)) {
|
||||
_.forOwn(res.strings, (data, ns) => {
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
exports.up = knex => {
|
||||
return knex.schema
|
||||
// -------------------------------------
|
||||
// GROUPS
|
||||
// -------------------------------------
|
||||
.createTable('groups', table => {
|
||||
table.increments('id').primary()
|
||||
|
||||
table.string('name').notNullable()
|
||||
table.string('createdAt').notNullable()
|
||||
table.string('updatedAt').notNullable()
|
||||
})
|
||||
// -------------------------------------
|
||||
// LOCALES
|
||||
// -------------------------------------
|
||||
.createTable('locales', table => {
|
||||
table.increments('id').primary()
|
||||
|
||||
table.string('code', 2).notNullable().unique()
|
||||
table.json('strings')
|
||||
table.boolean('isRTL').notNullable().defaultTo(false)
|
||||
table.string('name').notNullable()
|
||||
table.string('nativeName').notNullable()
|
||||
table.string('createdAt').notNullable()
|
||||
table.string('updatedAt').notNullable()
|
||||
})
|
||||
// -------------------------------------
|
||||
// SETTINGS
|
||||
// -------------------------------------
|
||||
.createTable('settings', table => {
|
||||
table.increments('id').primary()
|
||||
|
||||
table.string('key').notNullable().unique()
|
||||
table.json('value')
|
||||
table.string('createdAt').notNullable()
|
||||
table.string('updatedAt').notNullable()
|
||||
})
|
||||
// -------------------------------------
|
||||
// USERS
|
||||
// -------------------------------------
|
||||
.createTable('users', table => {
|
||||
table.increments('id').primary()
|
||||
|
||||
table.string('email').notNullable()
|
||||
table.string('name').notNullable()
|
||||
table.string('provider').notNullable().defaultTo('local')
|
||||
table.string('providerId')
|
||||
table.string('password')
|
||||
table.boolean('tfaIsActive').notNullable().defaultTo(false)
|
||||
table.string('tfaSecret')
|
||||
table.enum('role', ['admin', 'guest', 'user']).notNullable().defaultTo('guest')
|
||||
table.string('createdAt').notNullable()
|
||||
table.string('updatedAt').notNullable()
|
||||
|
||||
table.unique(['provider', 'email'])
|
||||
})
|
||||
// -------------------------------------
|
||||
// USER GROUPS
|
||||
// -------------------------------------
|
||||
.createTable('userGroups', table => {
|
||||
table.increments('id').primary()
|
||||
|
||||
table.integer('userId').unsigned().references('id').inTable('users')
|
||||
table.integer('groupId').unsigned().references('id').inTable('groups')
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = knex => {
|
||||
return knex.schema
|
||||
.dropTableIfExists('userGroups')
|
||||
.dropTableIfExists('groups')
|
||||
.dropTableIfExists('locales')
|
||||
.dropTableIfExists('settings')
|
||||
.dropTableIfExists('users')
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
const Model = require('objection').Model
|
||||
|
||||
/**
|
||||
* Settings model
|
||||
*/
|
||||
module.exports = class Group extends Model {
|
||||
static get tableName() { return 'groups' }
|
||||
|
||||
static get jsonSchema () {
|
||||
return {
|
||||
type: 'object',
|
||||
required: ['name'],
|
||||
|
||||
properties: {
|
||||
id: {type: 'integer'},
|
||||
name: {type: 'string'},
|
||||
createdAt: {type: 'string'},
|
||||
updatedAt: {type: 'string'}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static get relationMappings() {
|
||||
const User = require('./users')
|
||||
return {
|
||||
users: {
|
||||
relation: Model.ManyToManyRelation,
|
||||
modelClass: User,
|
||||
join: {
|
||||
from: 'groups.id',
|
||||
through: {
|
||||
from: 'userGroups.groupId',
|
||||
to: 'userGroups.userId'
|
||||
},
|
||||
to: 'users.id'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$beforeUpdate() {
|
||||
this.updatedAt = new Date().toISOString()
|
||||
}
|
||||
$beforeInsert() {
|
||||
this.createdAt = new Date().toISOString()
|
||||
this.updatedAt = new Date().toISOString()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
const Model = require('objection').Model
|
||||
|
||||
/**
|
||||
* Locales model
|
||||
*/
|
||||
module.exports = class User extends Model {
|
||||
static get tableName() { return 'locales' }
|
||||
|
||||
static get jsonSchema () {
|
||||
return {
|
||||
type: 'object',
|
||||
required: ['code', 'name'],
|
||||
|
||||
properties: {
|
||||
id: {type: 'integer'},
|
||||
code: {type: 'string'},
|
||||
strings: {type: 'object'},
|
||||
isRTL: {type: 'boolean', default: false},
|
||||
name: {type: 'string'},
|
||||
nativeName: {type: 'string'},
|
||||
createdAt: {type: 'string'},
|
||||
updatedAt: {type: 'string'}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$beforeUpdate() {
|
||||
this.updatedAt = new Date().toISOString()
|
||||
}
|
||||
$beforeInsert() {
|
||||
this.createdAt = new Date().toISOString()
|
||||
this.updatedAt = new Date().toISOString()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
const Model = require('objection').Model
|
||||
|
||||
/**
|
||||
* Settings model
|
||||
*/
|
||||
module.exports = class User extends Model {
|
||||
static get tableName() { return 'settings' }
|
||||
|
||||
static get jsonSchema () {
|
||||
return {
|
||||
type: 'object',
|
||||
required: ['key', 'value'],
|
||||
|
||||
properties: {
|
||||
id: {type: 'integer'},
|
||||
key: {type: 'string'},
|
||||
value: {type: 'object'},
|
||||
createdAt: {type: 'string'},
|
||||
updatedAt: {type: 'string'}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$beforeUpdate() {
|
||||
this.updatedAt = new Date().toISOString()
|
||||
}
|
||||
$beforeInsert() {
|
||||
this.createdAt = new Date().toISOString()
|
||||
this.updatedAt = new Date().toISOString()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
/* global WIKI */
|
||||
|
||||
const bcrypt = require('bcryptjs-then')
|
||||
const _ = require('lodash')
|
||||
const tfa = require('node-2fa')
|
||||
const securityHelper = require('../../helpers/security')
|
||||
const Model = require('objection').Model
|
||||
|
||||
const bcryptRegexp = /^\$2[ayb]\$[0-9]{2}\$[A-Za-z0-9./]{53}$/
|
||||
|
||||
/**
|
||||
* Users model
|
||||
*/
|
||||
module.exports = class User extends Model {
|
||||
static get tableName() { return 'users' }
|
||||
|
||||
static get jsonSchema () {
|
||||
return {
|
||||
type: 'object',
|
||||
required: ['email', 'name', 'provider'],
|
||||
|
||||
properties: {
|
||||
id: {type: 'integer'},
|
||||
email: {type: 'string', format: 'email'},
|
||||
name: {type: 'string', minLength: 1, maxLength: 255},
|
||||
provider: {type: 'string', minLength: 1, maxLength: 255},
|
||||
providerId: {type: 'number'},
|
||||
password: {type: 'string'},
|
||||
role: {type: 'string', enum: ['admin', 'guest', 'user']},
|
||||
tfaIsActive: {type: 'boolean', default: false},
|
||||
tfaSecret: {type: 'string'},
|
||||
createdAt: {type: 'string'},
|
||||
updatedAt: {type: 'string'}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static get relationMappings() {
|
||||
const Group = require('./groups')
|
||||
return {
|
||||
groups: {
|
||||
relation: Model.ManyToManyRelation,
|
||||
modelClass: Group,
|
||||
join: {
|
||||
from: 'users.id',
|
||||
through: {
|
||||
from: 'userGroups.userId',
|
||||
to: 'userGroups.groupId'
|
||||
},
|
||||
to: 'groups.id'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async $beforeUpdate(opt, context) {
|
||||
await super.$beforeUpdate(opt, context)
|
||||
|
||||
this.updatedAt = new Date().toISOString()
|
||||
|
||||
if (!(opt.patch && this.password === undefined)) {
|
||||
await this.generateHash()
|
||||
}
|
||||
}
|
||||
async $beforeInsert(context) {
|
||||
await super.$beforeInsert(context)
|
||||
|
||||
this.createdAt = new Date().toISOString()
|
||||
this.updatedAt = new Date().toISOString()
|
||||
|
||||
await this.generateHash()
|
||||
}
|
||||
|
||||
async generateHash() {
|
||||
if (this.password) {
|
||||
if (bcryptRegexp.test(this.password)) { return }
|
||||
this.password = await bcrypt.hash(this.password, 12)
|
||||
}
|
||||
}
|
||||
|
||||
async verifyPassword(pwd) {
|
||||
if (await bcrypt.compare(this.password, pwd) === true) {
|
||||
return true
|
||||
} else {
|
||||
throw new WIKI.Error.AuthLoginFailed()
|
||||
}
|
||||
}
|
||||
|
||||
async enableTFA() {
|
||||
let tfaInfo = tfa.generateSecret({
|
||||
name: WIKI.config.site.title
|
||||
})
|
||||
return this.$query.patch({
|
||||
tfaIsActive: true,
|
||||
tfaSecret: tfaInfo.secret
|
||||
})
|
||||
}
|
||||
|
||||
async disableTFA() {
|
||||
return this.$query.patch({
|
||||
tfaIsActive: false,
|
||||
tfaSecret: ''
|
||||
})
|
||||
}
|
||||
|
||||
async verifyTFA(code) {
|
||||
let result = tfa.verifyToken(this.tfaSecret, code)
|
||||
return (result && _.has(result, 'delta') && result.delta === 0)
|
||||
}
|
||||
|
||||
static async processProfile(profile) {
|
||||
let primaryEmail = ''
|
||||
if (_.isArray(profile.emails)) {
|
||||
let e = _.find(profile.emails, ['primary', true])
|
||||
primaryEmail = (e) ? e.value : _.first(profile.emails).value
|
||||
} else if (_.isString(profile.email) && profile.email.length > 5) {
|
||||
primaryEmail = profile.email
|
||||
} else if (_.isString(profile.mail) && profile.mail.length > 5) {
|
||||
primaryEmail = profile.mail
|
||||
} else if (profile.user && profile.user.email && profile.user.email.length > 5) {
|
||||
primaryEmail = profile.user.email
|
||||
} else {
|
||||
return Promise.reject(new Error(WIKI.lang.t('auth:errors.invaliduseremail')))
|
||||
}
|
||||
|
||||
profile.provider = _.lowerCase(profile.provider)
|
||||
primaryEmail = _.toLower(primaryEmail)
|
||||
|
||||
let user = await WIKI.db.users.query().findOne({
|
||||
email: primaryEmail,
|
||||
provider: profile.provider
|
||||
})
|
||||
if (user) {
|
||||
user.$query().patchAdnFetch({
|
||||
email: primaryEmail,
|
||||
provider: profile.provider,
|
||||
providerId: profile.id,
|
||||
name: profile.displayName || _.split(primaryEmail, '@')[0]
|
||||
})
|
||||
} else {
|
||||
user = await WIKI.db.users.query().insertAndFetch({
|
||||
email: primaryEmail,
|
||||
provider: profile.provider,
|
||||
providerId: profile.id,
|
||||
name: profile.displayName || _.split(primaryEmail, '@')[0]
|
||||
})
|
||||
}
|
||||
|
||||
// Handle unregistered accounts
|
||||
// if (!user && profile.provider !== 'local' && (WIKI.config.auth.defaultReadAccess || profile.provider === 'ldap' || profile.provider === 'azure')) {
|
||||
// let nUsr = {
|
||||
// email: primaryEmail,
|
||||
// provider: profile.provider,
|
||||
// providerId: profile.id,
|
||||
// password: '',
|
||||
// name: profile.displayName || profile.name || profile.cn,
|
||||
// rights: [{
|
||||
// role: 'read',
|
||||
// path: '/',
|
||||
// exact: false,
|
||||
// deny: false
|
||||
// }]
|
||||
// }
|
||||
// return WIKI.db.users.query().insert(nUsr)
|
||||
// }
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
static async login (opts, context) {
|
||||
if (_.has(WIKI.config.auth.strategies, opts.provider)) {
|
||||
_.set(context.req, 'body.email', opts.username)
|
||||
_.set(context.req, 'body.password', opts.password)
|
||||
|
||||
// Authenticate
|
||||
return new Promise((resolve, reject) => {
|
||||
WIKI.auth.passport.authenticate(opts.provider, async (err, user, info) => {
|
||||
if (err) { return reject(err) }
|
||||
if (!user) { return reject(new WIKI.Error.AuthLoginFailed()) }
|
||||
|
||||
// Is 2FA required?
|
||||
if (user.tfaIsActive) {
|
||||
try {
|
||||
let loginToken = await securityHelper.generateToken(32)
|
||||
await WIKI.redis.set(`tfa:${loginToken}`, user.id, 'EX', 600)
|
||||
return resolve({
|
||||
tfaRequired: true,
|
||||
tfaLoginToken: loginToken
|
||||
})
|
||||
} catch (err) {
|
||||
WIKI.logger.warn(err)
|
||||
return reject(new WIKI.Error.AuthGenericError())
|
||||
}
|
||||
} else {
|
||||
// No 2FA, log in user
|
||||
return context.req.logIn(user, err => {
|
||||
if (err) { return reject(err) }
|
||||
resolve({
|
||||
tfaRequired: false
|
||||
})
|
||||
})
|
||||
}
|
||||
})(context.req, context.res, () => {})
|
||||
})
|
||||
} else {
|
||||
throw new WIKI.Error.AuthProviderInvalid()
|
||||
}
|
||||
}
|
||||
|
||||
static async loginTFA(opts, context) {
|
||||
if (opts.securityCode.length === 6 && opts.loginToken.length === 64) {
|
||||
let result = await WIKI.redis.get(`tfa:${opts.loginToken}`)
|
||||
if (result) {
|
||||
let userId = _.toSafeInteger(result)
|
||||
if (userId && userId > 0) {
|
||||
let user = await WIKI.db.users.query().findById(userId)
|
||||
if (user && user.verifyTFA(opts.securityCode)) {
|
||||
return Promise.fromCallback(clb => {
|
||||
context.req.logIn(user, clb)
|
||||
}).return({
|
||||
succeeded: true,
|
||||
message: 'Login Successful'
|
||||
}).catch(err => {
|
||||
WIKI.logger.warn(err)
|
||||
throw new WIKI.Error.AuthGenericError()
|
||||
})
|
||||
} else {
|
||||
throw new WIKI.Error.AuthTFAFailed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new WIKI.Error.AuthTFAInvalid()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
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: {} }
|
||||
])
|
||||
}
|
||||
@@ -32,7 +32,7 @@ module.exports = {
|
||||
AuthenticationMutation: {
|
||||
async login(obj, args, context) {
|
||||
try {
|
||||
let authResult = await WIKI.db.User.login(args, context)
|
||||
let authResult = await WIKI.db.users.login(args, context)
|
||||
return {
|
||||
...authResult,
|
||||
responseResult: graphHelper.generateSuccess('Login success')
|
||||
@@ -43,7 +43,7 @@ module.exports = {
|
||||
},
|
||||
async loginTFA(obj, args, context) {
|
||||
try {
|
||||
let authResult = await WIKI.db.User.loginTFA(args, context)
|
||||
let authResult = await WIKI.db.users.loginTFA(args, context)
|
||||
return {
|
||||
...authResult,
|
||||
responseResult: graphHelper.generateSuccess('TFA success')
|
||||
|
||||
@@ -13,43 +13,32 @@ module.exports = {
|
||||
},
|
||||
GroupQuery: {
|
||||
async list(obj, args, context, info) {
|
||||
return WIKI.db.Group.findAll({
|
||||
attributes: {
|
||||
include: [[WIKI.db.inst.fn('COUNT', WIKI.db.inst.col('users.id')), 'userCount']]
|
||||
},
|
||||
include: [{
|
||||
model: WIKI.db.User,
|
||||
attributes: [],
|
||||
through: {
|
||||
attributes: []
|
||||
}
|
||||
}],
|
||||
raw: true,
|
||||
// TODO: Figure out how to exclude these extra fields...
|
||||
group: ['group.id', 'users->userGroups.createdAt', 'users->userGroups.updatedAt', 'users->userGroups.version', 'users->userGroups.userId', 'users->userGroups.groupId']
|
||||
})
|
||||
return WIKI.db.groups.query().select(
|
||||
'groups.*',
|
||||
WIKI.db.groups.relatedQuery('users').count().as('userCount')
|
||||
)
|
||||
},
|
||||
async single(obj, args, context, info) {
|
||||
return WIKI.db.Group.findById(args.id)
|
||||
return WIKI.db.groups.query().findById(args.id)
|
||||
}
|
||||
},
|
||||
GroupMutation: {
|
||||
async assignUser(obj, args) {
|
||||
const grp = await WIKI.db.Group.findById(args.groupId)
|
||||
const grp = await WIKI.db.groups.query().findById(args.groupId)
|
||||
if (!grp) {
|
||||
throw new gql.GraphQLError('Invalid Group ID')
|
||||
}
|
||||
const usr = await WIKI.db.User.findById(args.userId)
|
||||
const usr = await WIKI.db.users.query().findById(args.userId)
|
||||
if (!usr) {
|
||||
throw new gql.GraphQLError('Invalid User ID')
|
||||
}
|
||||
await grp.addUser(usr)
|
||||
await grp.$relatedQuery('users').relate(usr.id)
|
||||
return {
|
||||
responseResult: graphHelper.generateSuccess('User has been assigned to group.')
|
||||
}
|
||||
},
|
||||
async create(obj, args) {
|
||||
const group = await WIKI.db.Group.create({
|
||||
const group = await WIKI.db.groups.query().insertAndFetch({
|
||||
name: args.name
|
||||
})
|
||||
return {
|
||||
@@ -58,36 +47,27 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
async delete(obj, args) {
|
||||
await WIKI.db.Group.destroy({
|
||||
where: {
|
||||
id: args.id
|
||||
},
|
||||
limit: 1
|
||||
})
|
||||
await WIKI.db.groups.query().deleteById(args.id)
|
||||
return {
|
||||
responseResult: graphHelper.generateSuccess('Group has been deleted.')
|
||||
}
|
||||
},
|
||||
async unassignUser(obj, args) {
|
||||
const grp = await WIKI.db.Group.findById(args.groupId)
|
||||
const grp = await WIKI.db.groups.query().findById(args.groupId)
|
||||
if (!grp) {
|
||||
throw new gql.GraphQLError('Invalid Group ID')
|
||||
}
|
||||
const usr = await WIKI.db.User.findById(args.userId)
|
||||
const usr = await WIKI.db.users.query().findById(args.userId)
|
||||
if (!usr) {
|
||||
throw new gql.GraphQLError('Invalid User ID')
|
||||
}
|
||||
await grp.removeUser(usr)
|
||||
await grp.$relatedQuery('users').unrelate().where('userId', usr.id)
|
||||
return {
|
||||
responseResult: graphHelper.generateSuccess('User has been unassigned from group.')
|
||||
}
|
||||
},
|
||||
async update(obj, args) {
|
||||
await WIKI.db.Group.update({
|
||||
name: args.name
|
||||
}, {
|
||||
where: { id: args.id }
|
||||
})
|
||||
await WIKI.db.groups.query().patch({ name: args.name }).where('id', args.id)
|
||||
return {
|
||||
responseResult: graphHelper.generateSuccess('Group has been updated.')
|
||||
}
|
||||
@@ -95,7 +75,7 @@ module.exports = {
|
||||
},
|
||||
Group: {
|
||||
users(grp) {
|
||||
return grp.getUsers()
|
||||
return grp.$relatedQuery('users')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,7 @@ module.exports = {
|
||||
LocalizationQuery: {
|
||||
async locales(obj, args, context, info) {
|
||||
let remoteLocales = await WIKI.redis.get('locales')
|
||||
let localLocales = await WIKI.db.Locale.findAll({
|
||||
attributes: {
|
||||
exclude: ['strings']
|
||||
},
|
||||
raw: true
|
||||
})
|
||||
let localLocales = await WIKI.db.locales.query().select('id', 'code', 'isRTL', 'name', 'nativeName', 'createdAt', 'updatedAt')
|
||||
remoteLocales = (remoteLocales) ? JSON.parse(remoteLocales) : localLocales
|
||||
return _.map(remoteLocales, rl => {
|
||||
let isInstalled = _.some(localLocales, ['code', rl.code])
|
||||
|
||||
@@ -10,7 +10,8 @@ const path = require('path')
|
||||
const dbTypes = {
|
||||
mysql: 'MySQL / MariaDB',
|
||||
postgres: 'PostgreSQL',
|
||||
sqlite: 'SQLite'
|
||||
sqlite: 'SQLite',
|
||||
mssql: 'MS SQL Server'
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@@ -28,12 +29,14 @@ module.exports = {
|
||||
osLabel = `${os.type()} - ${osInfo.dist} (${osInfo.codename || os.platform()}) ${osInfo.release || os.release()} ${os.arch()}`
|
||||
}
|
||||
|
||||
console.info(WIKI.db.knex.client)
|
||||
|
||||
return {
|
||||
configFile: path.join(process.cwd(), 'config.yml'),
|
||||
currentVersion: WIKI.version,
|
||||
dbType: _.get(dbTypes, WIKI.config.db.type, 'Unknown DB'),
|
||||
dbVersion: WIKI.db.inst.options.databaseVersion,
|
||||
dbHost: WIKI.db.inst.options.host,
|
||||
dbVersion: _.get(WIKI.db, 'knex.client.version', 'Unknown version'),
|
||||
dbHost: WIKI.config.db.host,
|
||||
latestVersion: WIKI.version, // TODO
|
||||
latestVersionReleaseDate: new Date(), // TODO
|
||||
operatingSystem: osLabel,
|
||||
|
||||
@@ -10,52 +10,35 @@ module.exports = {
|
||||
},
|
||||
UserQuery: {
|
||||
async list(obj, args, context, info) {
|
||||
return WIKI.db.User.findAll({
|
||||
attributes: {
|
||||
exclude: ['password', 'tfaSecret']
|
||||
},
|
||||
raw: true
|
||||
})
|
||||
return WIKI.db.users.query()
|
||||
.select('id', 'email', 'name', 'provider', 'role', 'createdAt', 'updatedAt')
|
||||
},
|
||||
async search(obj, args, context, info) {
|
||||
return WIKI.db.User.findAll({
|
||||
where: {
|
||||
$or: [
|
||||
{ email: { $like: `%${args.query}%` } },
|
||||
{ name: { $like: `%${args.query}%` } }
|
||||
]
|
||||
},
|
||||
limit: 10,
|
||||
attributes: ['id', 'email', 'name', 'provider', 'role', 'createdAt', 'updatedAt'],
|
||||
raw: true
|
||||
})
|
||||
return WIKI.db.users.query()
|
||||
.where('email', 'like', `%${args.query}%`)
|
||||
.orWhere('name', 'like', `%${args.query}%`)
|
||||
.limit(10)
|
||||
.select('id', 'email', 'name', 'provider', 'role', 'createdAt', 'updatedAt')
|
||||
},
|
||||
async single(obj, args, context, info) {
|
||||
return WIKI.db.User.findById(args.id)
|
||||
return WIKI.db.users.query().findById(args.id)
|
||||
}
|
||||
},
|
||||
UserMutation: {
|
||||
create(obj, args) {
|
||||
return WIKI.db.User.create(args)
|
||||
return WIKI.db.users.query().insertAndFetch(args)
|
||||
},
|
||||
delete(obj, args) {
|
||||
return WIKI.db.User.destroy({
|
||||
where: {
|
||||
id: args.id
|
||||
},
|
||||
limit: 1
|
||||
})
|
||||
return WIKI.db.users.query().deleteById(args.id)
|
||||
},
|
||||
update(obj, args) {
|
||||
return WIKI.db.User.update({
|
||||
return WIKI.db.users.query().patch({
|
||||
email: args.email,
|
||||
name: args.name,
|
||||
provider: args.provider,
|
||||
providerId: args.providerId,
|
||||
role: args.role
|
||||
}, {
|
||||
where: { id: args.id }
|
||||
})
|
||||
}).where('id', args.id)
|
||||
},
|
||||
resetPassword(obj, args) {
|
||||
return false
|
||||
|
||||
@@ -38,7 +38,8 @@ module.exports = async (job) => {
|
||||
const locales = await WIKI.redis.get('locales')
|
||||
if (locales) {
|
||||
const currentLocale = _.find(JSON.parse(locales), ['code', job.data.locale]) || {}
|
||||
await WIKI.db.Locale.upsert({
|
||||
await WIKI.db.locales.query().delete().where('code', job.data.locale)
|
||||
await WIKI.db.locales.query().insert({
|
||||
code: job.data.locale,
|
||||
strings: lcObj,
|
||||
isRTL: currentLocale.isRTL,
|
||||
|
||||
@@ -60,13 +60,13 @@ module.exports = async (job) => {
|
||||
_.set(lcObj, row.key.replace(':', '.'), row.value)
|
||||
})
|
||||
|
||||
await WIKI.db.Locale.upsert({
|
||||
await WIKI.db.locales.query().update({
|
||||
code: WIKI.config.site.lang,
|
||||
strings: lcObj,
|
||||
isRTL: currentLocale.isRTL,
|
||||
name: currentLocale.name,
|
||||
nativeName: currentLocale.nativeName
|
||||
})
|
||||
}).where('code', WIKI.config.site.lang)
|
||||
}
|
||||
|
||||
WIKI.logger.info('Syncing locales with Graph endpoint: [ COMPLETED ]')
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
/* global WIKI */
|
||||
|
||||
const Promise = require('bluebird')
|
||||
const bcrypt = require('bcryptjs-then')
|
||||
const _ = require('lodash')
|
||||
const tfa = require('node-2fa')
|
||||
const securityHelper = require('../helpers/security')
|
||||
|
||||
/**
|
||||
* Users schema
|
||||
*/
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
let userSchema = sequelize.define('user', {
|
||||
email: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
isEmail: true
|
||||
}
|
||||
},
|
||||
provider: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
providerId: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
password: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
role: {
|
||||
type: DataTypes.ENUM('admin', 'user', 'guest'),
|
||||
allowNull: false
|
||||
},
|
||||
tfaIsActive: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false
|
||||
},
|
||||
tfaSecret: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
timestamps: true,
|
||||
version: true,
|
||||
indexes: [
|
||||
{
|
||||
unique: true,
|
||||
fields: ['provider', 'email']
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
userSchema.prototype.validatePassword = async function (rawPwd) {
|
||||
if (await bcrypt.compare(rawPwd, this.password) === true) {
|
||||
return true
|
||||
} else {
|
||||
throw new WIKI.Error.AuthLoginFailed()
|
||||
}
|
||||
}
|
||||
|
||||
userSchema.prototype.enableTFA = async function () {
|
||||
let tfaInfo = tfa.generateSecret({
|
||||
name: WIKI.config.site.title
|
||||
})
|
||||
this.tfaIsActive = true
|
||||
this.tfaSecret = tfaInfo.secret
|
||||
return this.save()
|
||||
}
|
||||
|
||||
userSchema.prototype.disableTFA = async function () {
|
||||
this.tfaIsActive = false
|
||||
this.tfaSecret = ''
|
||||
return this.save()
|
||||
}
|
||||
|
||||
userSchema.prototype.verifyTFA = function (code) {
|
||||
let result = tfa.verifyToken(this.tfaSecret, code)
|
||||
return (result && _.has(result, 'delta') && result.delta === 0)
|
||||
}
|
||||
|
||||
userSchema.login = async (opts, context) => {
|
||||
if (_.has(WIKI.config.auth.strategies, opts.provider)) {
|
||||
_.set(context.req, 'body.email', opts.username)
|
||||
_.set(context.req, 'body.password', opts.password)
|
||||
|
||||
// Authenticate
|
||||
return new Promise((resolve, reject) => {
|
||||
WIKI.auth.passport.authenticate(opts.provider, async (err, user, info) => {
|
||||
if (err) { return reject(err) }
|
||||
if (!user) { return reject(new WIKI.Error.AuthLoginFailed()) }
|
||||
|
||||
// Is 2FA required?
|
||||
if (user.tfaIsActive) {
|
||||
try {
|
||||
let loginToken = await securityHelper.generateToken(32)
|
||||
await WIKI.redis.set(`tfa:${loginToken}`, user.id, 'EX', 600)
|
||||
return resolve({
|
||||
tfaRequired: true,
|
||||
tfaLoginToken: loginToken
|
||||
})
|
||||
} catch (err) {
|
||||
WIKI.logger.warn(err)
|
||||
return reject(new WIKI.Error.AuthGenericError())
|
||||
}
|
||||
} else {
|
||||
// No 2FA, log in user
|
||||
return context.req.logIn(user, err => {
|
||||
if (err) { return reject(err) }
|
||||
resolve({
|
||||
tfaRequired: false
|
||||
})
|
||||
})
|
||||
}
|
||||
})(context.req, context.res, () => {})
|
||||
})
|
||||
} else {
|
||||
throw new WIKI.Error.AuthProviderInvalid()
|
||||
}
|
||||
}
|
||||
|
||||
userSchema.loginTFA = async (opts, context) => {
|
||||
if (opts.securityCode.length === 6 && opts.loginToken.length === 64) {
|
||||
let result = await WIKI.redis.get(`tfa:${opts.loginToken}`)
|
||||
if (result) {
|
||||
let userId = _.toSafeInteger(result)
|
||||
if (userId && userId > 0) {
|
||||
let user = await WIKI.db.User.findById(userId)
|
||||
if (user && user.verifyTFA(opts.securityCode)) {
|
||||
return Promise.fromCallback(clb => {
|
||||
context.req.logIn(user, clb)
|
||||
}).return({
|
||||
succeeded: true,
|
||||
message: 'Login Successful'
|
||||
}).catch(err => {
|
||||
WIKI.logger.warn(err)
|
||||
throw new WIKI.Error.AuthGenericError()
|
||||
})
|
||||
} else {
|
||||
throw new WIKI.Error.AuthTFAFailed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new WIKI.Error.AuthTFAInvalid()
|
||||
}
|
||||
|
||||
userSchema.processProfile = (profile) => {
|
||||
let primaryEmail = ''
|
||||
if (_.isArray(profile.emails)) {
|
||||
let e = _.find(profile.emails, ['primary', true])
|
||||
primaryEmail = (e) ? e.value : _.first(profile.emails).value
|
||||
} else if (_.isString(profile.email) && profile.email.length > 5) {
|
||||
primaryEmail = profile.email
|
||||
} else if (_.isString(profile.mail) && profile.mail.length > 5) {
|
||||
primaryEmail = profile.mail
|
||||
} else if (profile.user && profile.user.email && profile.user.email.length > 5) {
|
||||
primaryEmail = profile.user.email
|
||||
} else {
|
||||
return Promise.reject(new Error(WIKI.lang.t('auth:errors.invaliduseremail')))
|
||||
}
|
||||
|
||||
profile.provider = _.lowerCase(profile.provider)
|
||||
primaryEmail = _.toLower(primaryEmail)
|
||||
|
||||
return WIKI.db.User.findOneAndUpdate({
|
||||
email: primaryEmail,
|
||||
provider: profile.provider
|
||||
}, {
|
||||
email: primaryEmail,
|
||||
provider: profile.provider,
|
||||
providerId: profile.id,
|
||||
name: profile.displayName || _.split(primaryEmail, '@')[0]
|
||||
}, {
|
||||
new: true
|
||||
}).then((user) => {
|
||||
// Handle unregistered accounts
|
||||
if (!user && profile.provider !== 'local' && (WIKI.config.auth.defaultReadAccess || profile.provider === 'ldap' || profile.provider === 'azure')) {
|
||||
let nUsr = {
|
||||
email: primaryEmail,
|
||||
provider: profile.provider,
|
||||
providerId: profile.id,
|
||||
password: '',
|
||||
name: profile.displayName || profile.name || profile.cn,
|
||||
rights: [{
|
||||
role: 'read',
|
||||
path: '/',
|
||||
exact: false,
|
||||
deny: false
|
||||
}]
|
||||
}
|
||||
return WIKI.db.User.create(nUsr)
|
||||
}
|
||||
return user || Promise.reject(new Error(WIKI.lang.t('auth:errors:notyetauthorized')))
|
||||
})
|
||||
}
|
||||
|
||||
userSchema.hashPassword = (rawPwd) => {
|
||||
return bcrypt.hash(rawPwd)
|
||||
}
|
||||
|
||||
return userSchema
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -19,7 +19,7 @@ module.exports = {
|
||||
clientSecret: conf.clientSecret,
|
||||
callbackURL: conf.callbackURL
|
||||
}, function (accessToken, refreshToken, profile, cb) {
|
||||
WIKI.db.User.processProfile(profile).then((user) => {
|
||||
WIKI.db.users.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
|
||||
@@ -24,7 +24,7 @@ module.exports = {
|
||||
let waadProfile = jwt.decode(params.id_token)
|
||||
waadProfile.id = waadProfile.oid
|
||||
waadProfile.provider = 'azure'
|
||||
WIKI.db.User.processProfile(waadProfile).then((user) => {
|
||||
WIKI.db.users.processProfile(waadProfile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
|
||||
@@ -19,7 +19,7 @@ module.exports = {
|
||||
callbackURL: conf.callbackURL,
|
||||
scope: 'identify email'
|
||||
}, function (accessToken, refreshToken, profile, cb) {
|
||||
WIKI.db.User.processProfile(profile).then((user) => {
|
||||
WIKI.db.users.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
|
||||
@@ -19,7 +19,7 @@ module.exports = {
|
||||
clientSecret: conf.clientSecret,
|
||||
callbackURL: conf.callbackURL
|
||||
}, (accessToken, refreshToken, profile, cb) => {
|
||||
WIKI.db.User.processProfile(profile).then((user) => {
|
||||
WIKI.db.users.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
|
||||
@@ -19,7 +19,7 @@ module.exports = {
|
||||
callbackURL: conf.callbackURL,
|
||||
profileFields: ['id', 'displayName', 'email']
|
||||
}, function (accessToken, refreshToken, profile, cb) {
|
||||
WIKI.db.User.processProfile(profile).then((user) => {
|
||||
WIKI.db.users.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
|
||||
@@ -19,7 +19,7 @@ module.exports = {
|
||||
callbackURL: conf.callbackURL,
|
||||
scope: ['user:email']
|
||||
}, (accessToken, refreshToken, profile, cb) => {
|
||||
WIKI.db.User.processProfile(profile).then((user) => {
|
||||
WIKI.db.users.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
|
||||
@@ -18,7 +18,7 @@ module.exports = {
|
||||
clientSecret: conf.clientSecret,
|
||||
callbackURL: conf.callbackURL
|
||||
}, (accessToken, refreshToken, profile, cb) => {
|
||||
WIKI.db.User.processProfile(profile).then((user) => {
|
||||
WIKI.db.users.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
|
||||
@@ -33,7 +33,7 @@ module.exports = {
|
||||
}, (profile, cb) => {
|
||||
profile.provider = 'ldap'
|
||||
profile.id = profile.dn
|
||||
WIKI.db.User.processProfile(profile).then((user) => {
|
||||
WIKI.db.users.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
|
||||
@@ -17,14 +17,12 @@ module.exports = {
|
||||
usernameField: 'email',
|
||||
passwordField: 'password'
|
||||
}, (uEmail, uPassword, done) => {
|
||||
WIKI.db.User.findOne({
|
||||
where: {
|
||||
email: uEmail,
|
||||
provider: 'local'
|
||||
}
|
||||
WIKI.db.users.query().findOne({
|
||||
email: uEmail,
|
||||
provider: 'local'
|
||||
}).then((user) => {
|
||||
if (user) {
|
||||
return user.validatePassword(uPassword).then(() => {
|
||||
return user.verifyPassword(uPassword).then(() => {
|
||||
return done(null, user) || true
|
||||
}).catch((err) => {
|
||||
return done(err, null)
|
||||
|
||||
@@ -18,7 +18,7 @@ module.exports = {
|
||||
clientSecret: conf.clientSecret,
|
||||
callbackURL: conf.callbackURL
|
||||
}, function (accessToken, refreshToken, profile, cb) {
|
||||
WIKI.db.User.processProfile(profile).then((user) => {
|
||||
WIKI.db.users.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
|
||||
@@ -20,7 +20,7 @@ module.exports = {
|
||||
clientSecret: conf.clientSecret,
|
||||
callbackURL: conf.callbackURL
|
||||
}, (accessToken, refreshToken, profile, cb) => {
|
||||
WIKI.db.User.processProfile(profile).then((user) => {
|
||||
WIKI.db.users.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
|
||||
@@ -18,7 +18,7 @@ module.exports = {
|
||||
clientSecret: conf.clientSecret,
|
||||
callbackURL: conf.callbackURL
|
||||
}, (accessToken, refreshToken, profile, cb) => {
|
||||
WIKI.db.User.processProfile(profile).then((user) => {
|
||||
WIKI.db.users.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
|
||||
@@ -19,7 +19,7 @@ module.exports = {
|
||||
callbackURL: conf.callbackURL,
|
||||
scope: 'user_read'
|
||||
}, function (accessToken, refreshToken, profile, cb) {
|
||||
WIKI.db.User.processProfile(profile).then((user) => {
|
||||
WIKI.db.users.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
|
||||
+30
-6
@@ -5,7 +5,7 @@ const path = require('path')
|
||||
module.exports = () => {
|
||||
WIKI.config.site = {
|
||||
path: '',
|
||||
title: 'WIKI.js'
|
||||
title: 'Wiki.js'
|
||||
}
|
||||
|
||||
WIKI.system = require('./core/system')
|
||||
@@ -298,22 +298,46 @@ module.exports = () => {
|
||||
|
||||
// Save config to DB
|
||||
WIKI.logger.info('Persisting config to DB...')
|
||||
await WIKI.configSvc.saveToDb()
|
||||
await WIKI.db.settings.query().insert([
|
||||
{ key: 'auth', value: WIKI.config.auth },
|
||||
{ key: 'features', value: WIKI.config.features },
|
||||
{ key: 'logging', value: WIKI.config.logging },
|
||||
{ key: 'site', value: WIKI.config.site },
|
||||
{ key: 'theme', value: WIKI.config.theme },
|
||||
{ key: 'uploads', value: WIKI.config.uploads }
|
||||
])
|
||||
|
||||
// Create root administrator
|
||||
WIKI.logger.info('Creating root administrator...')
|
||||
await WIKI.db.User.upsert({
|
||||
await WIKI.db.users.query().insert({
|
||||
email: req.body.adminEmail,
|
||||
provider: 'local',
|
||||
password: await WIKI.db.User.hashPassword(req.body.adminPassword),
|
||||
password: req.body.adminPassword,
|
||||
name: 'Administrator',
|
||||
role: 'admin',
|
||||
tfaIsActive: false
|
||||
})
|
||||
|
||||
// Create Guest account
|
||||
WIKI.logger.info('Creating root administrator...')
|
||||
const guestUsr = await WIKI.db.users.query().findOne({
|
||||
provider: 'local',
|
||||
email: 'guest@example.com'
|
||||
})
|
||||
if (!guestUsr) {
|
||||
await WIKI.db.users.query().insert({
|
||||
provider: 'local',
|
||||
email: 'guest@example.com',
|
||||
name: 'Guest',
|
||||
password: '',
|
||||
role: 'guest',
|
||||
tfaIsActive: false
|
||||
})
|
||||
}
|
||||
|
||||
// Create default locale
|
||||
WIKI.logger.info('Installing default locale...')
|
||||
await WIKI.db.Locale.upsert({
|
||||
await WIKI.db.locales.query().insert({
|
||||
code: 'en',
|
||||
strings: require('./locales/default.json'),
|
||||
isRTL: false,
|
||||
@@ -330,7 +354,7 @@ module.exports = () => {
|
||||
|
||||
WIKI.logger.info('Stopping Setup...')
|
||||
WIKI.server.destroy(() => {
|
||||
WIKI.logger.info('Setup stopped. Starting WIKI.js...')
|
||||
WIKI.logger.info('Setup stopped. Starting Wiki.js...')
|
||||
_.delay(() => {
|
||||
WIKI.kernel.bootMaster()
|
||||
}, 1000)
|
||||
|
||||
Reference in new issue
Block a user