refactor: migrate to objection.js + knex

This commit is contained in:
NGPixel committed 2018-05-19 16:40:07 -04:00
1 parent e03e6826a8
commit c9b643fbf0
46 files changed
+614 -502

No files matched your search

+3 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
}
}
+1 -5
View File
@@ -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
View 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
View 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
View 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
View 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
View 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
View 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: {} }
])
}
+2 -2
View File
@@ -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')
+15 -35
View File
@@ -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')
}
}
}
+1 -6
View File
@@ -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])
+6 -3
View File
@@ -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,
+12 -29
View File
@@ -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
+2 -1
View File
@@ -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,
+2 -2
View File
@@ -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 ]')
-210
View File
@@ -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.
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+4 -6
View File
@@ -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)
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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)
BIN
View File
Binary file not shown.