refactor: migrate to objection.js + knex
This commit is contained in:
parent
e03e6826a8
commit
c9b643fbf0
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
54
package.json
54
package.json
@ -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%",
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
// 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)
|
||||
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
|
||||
})
|
||||
|
||||
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) => {
|
||||
|
75
server/db/migrations/2.0.0.js
Normal file
75
server/db/migrations/2.0.0.js
Normal file
@ -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')
|
||||
}
|
48
server/db/models/groups.js
Normal file
48
server/db/models/groups.js
Normal file
@ -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()
|
||||
}
|
||||
}
|
34
server/db/models/locales.js
Normal file
34
server/db/models/locales.js
Normal file
@ -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()
|
||||
}
|
||||
}
|
31
server/db/models/settings.js
Normal file
31
server/db/models/settings.js
Normal file
@ -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()
|
||||
}
|
||||
}
|
235
server/db/models/users.js
Normal file
235
server/db/models/users.js
Normal file
@ -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()
|
||||
}
|
||||
}
|
11
server/db/seeds/settings.js
Normal file
11
server/db/seeds/settings.js
Normal file
@ -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
|
||||
}
|
@ -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: {
|
||||
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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user