feat: admin general + verify account

This commit is contained in:
Nicolas Giard
2018-12-24 01:03:10 -05:00
parent 2b98a5f27a
commit fcee4c0945
22 changed files with 494 additions and 80 deletions

View File

@@ -2,6 +2,7 @@
const express = require('express')
const router = express.Router()
const moment = require('moment')
/**
* Login form
@@ -30,6 +31,17 @@ router.get('/register', async (req, res, next) => {
}
})
/**
* Verify
*/
router.get('/verify/:token', async (req, res, next) => {
const usr = await WIKI.models.userKeys.validateToken({ kind: 'verify', token: req.params.token })
await WIKI.models.users.query().patch({ isVerified: true }).where('id', usr.id)
const result = await WIKI.models.users.refreshToken(usr)
res.cookie('jwt', result.token, { expires: moment().add(1, 'years').toDate() })
res.redirect('/')
})
/**
* JWT Public Endpoints
*/

View File

@@ -48,14 +48,14 @@ module.exports = {
}
await this.loadTemplate(opts.template)
return this.transport.sendMail({
from: 'noreply@requarks.io',
from: `"${WIKI.config.mail.senderName}" <${WIKI.config.mail.senderEmail}>`,
to: opts.to,
subject: `${opts.subject} - ${WIKI.config.title}`,
text: opts.text,
html: _.get(this.templates, opts.template)({
logo: '',
siteTitle: WIKI.config.title,
copyright: 'Powered by Wiki.js',
copyright: WIKI.config.company.length > 0 ? WIKI.config.company : 'Powered by Wiki.js',
...opts.data
})
})

View File

@@ -175,7 +175,7 @@ exports.up = knex => {
table.charset('utf8mb4')
table.increments('id').primary()
table.string('kind').notNullable()
table.string('key').notNullable()
table.string('token').notNullable()
table.string('createdAt').notNullable()
table.string('validUntil').notNullable()
})

View File

@@ -61,13 +61,7 @@ module.exports = {
async register(obj, args, context) {
try {
await WIKI.models.users.register(args, context)
const authResult = await WIKI.models.users.login({
username: args.email,
password: args.password,
strategy: 'local'
}, context)
return {
jwt: authResult.jwt,
responseResult: graphHelper.generateSuccess('Registration success')
}
} catch (err) {

View File

@@ -0,0 +1,56 @@
const _ = require('lodash')
const graphHelper = require('../../helpers/graph')
/* global WIKI */
module.exports = {
Query: {
async site() { return {} }
},
Mutation: {
async site() { return {} }
},
SiteQuery: {
async config(obj, args, context, info) {
return {
host: WIKI.config.host,
title: WIKI.config.title,
company: WIKI.config.company,
...WIKI.config.seo,
...WIKI.config.logo,
...WIKI.config.features
}
}
},
SiteMutation: {
async updateConfig(obj, args, context) {
try {
WIKI.config.host = args.host
WIKI.config.title = args.title
WIKI.config.company = args.company
WIKI.config.seo = {
description: args.description,
keywords: args.keywords,
robots: args.robots,
ga: args.ga
}
WIKI.config.logo = {
hasLogo: args.hasLogo,
logoIsSquare: args.logoIsSquare
}
WIKI.config.features = {
featurePageRatings: args.featurePageRatings,
featurePageComments: args.featurePageComments,
featurePersonalWikis: args.featurePersonalWikis
}
await WIKI.configSvc.saveToDb(['host', 'title', 'company', 'seo', 'logo', 'features'])
return {
responseResult: graphHelper.generateSuccess('Site configuration updated successfully')
}
} catch (err) {
return graphHelper.generateError(err)
}
}
}
}

View File

@@ -0,0 +1,59 @@
# ===============================================
# SITE
# ===============================================
extend type Query {
site: SiteQuery
}
extend type Mutation {
site: SiteMutation
}
# -----------------------------------------------
# QUERIES
# -----------------------------------------------
type SiteQuery {
config: SiteConfig @auth(requires: ["manage:system"])
}
# -----------------------------------------------
# MUTATIONS
# -----------------------------------------------
type SiteMutation {
updateConfig(
host: String!
title: String!
description: String!
keywords: String!
robots: [String]!
ga: String!
company: String!
hasLogo: Boolean!
logoIsSquare: Boolean!
featurePageRatings: Boolean!
featurePageComments: Boolean!
featurePersonalWikis: Boolean!
): DefaultResponse @auth(requires: ["manage:system"])
}
# -----------------------------------------------
# TYPES
# -----------------------------------------------
type SiteConfig {
host: String!
title: String!
description: String!
keywords: String!
robots: [String]!
ga: String!
company: String!
hasLogo: Boolean!
logoIsSquare: Boolean!
featurePageRatings: Boolean!
featurePageComments: Boolean!
featurePersonalWikis: Boolean!
}

View File

@@ -41,6 +41,10 @@ module.exports = {
message: 'Invalid TFA Security Code or Login Token.',
code: 1006
}),
AuthValidationTokenInvalid: CustomError('AuthValidationTokenInvalid', {
message: 'Invalid validation token.',
code: 1018
}),
BruteInstanceIsInvalid: CustomError('BruteInstanceIsInvalid', {
message: 'Invalid Brute Force Instance.',
code: 1007

View File

@@ -40,7 +40,7 @@ module.exports = class Authentication extends Model {
...str,
domainWhitelist: _.get(str.domainWhitelist, 'v', []),
autoEnrollGroups: _.get(str.autoEnrollGroups, 'v', [])
})), ['title'])
})), ['key'])
}
static async refreshStrategiesFromDisk() {

View File

@@ -35,7 +35,7 @@ module.exports = class Setting extends Model {
const settings = await WIKI.models.settings.query()
if (settings.length > 0) {
return _.reduce(settings, (res, val, key) => {
_.set(res, val.key, (val.value.v) ? val.value.v : val.value)
_.set(res, val.key, (_.has(val.value, 'v')) ? val.value.v : val.value)
return res
}, {})
} else {

74
server/models/userKeys.js Normal file
View File

@@ -0,0 +1,74 @@
/* global WIKI */
const _ = require('lodash')
const securityHelper = require('../helpers/security')
const Model = require('objection').Model
const moment = require('moment')
const nanoid = require('nanoid')
/**
* Users model
*/
module.exports = class UserKey extends Model {
static get tableName() { return 'userKeys' }
static get jsonSchema () {
return {
type: 'object',
required: ['kind', 'token', 'validUntil'],
properties: {
id: {type: 'integer'},
kind: {type: 'string'},
token: {type: 'string'},
createdAt: {type: 'string'},
validUntil: {type: 'string'}
}
}
}
static get relationMappings() {
return {
user: {
relation: Model.BelongsToOneRelation,
modelClass: require('./users'),
join: {
from: 'userKeys.userId',
to: 'users.id'
}
}
}
}
async $beforeInsert(context) {
await super.$beforeInsert(context)
this.createdAt = moment.utc().toISOString()
}
static async generateToken ({ userId, kind }, context) {
const token = await nanoid()
await WIKI.models.userKeys.query().insert({
kind,
token,
validUntil: moment.utc().add(1, 'days').toISOString(),
userId
})
return token
}
static async validateToken ({ kind, token }, context) {
const res = await WIKI.models.userKeys.query().findOne({ kind, token }).eager('user')
if (res) {
await WIKI.models.userKeys.query().deleteById(res.id)
if (moment.utc().isAfter(moment.utc(res.validUntil))) {
throw new WIKI.Error.AuthValidationTokenInvalid()
}
return res.user
} else {
throw new WIKI.Error.AuthValidationTokenInvalid()
}
return token
}
}

View File

@@ -345,7 +345,7 @@ module.exports = class User extends Model {
const usr = await WIKI.models.users.query().findOne({ email, providerKey: 'local' })
if (!usr) {
// Create the account
await WIKI.models.users.query().insert({
const newUsr = await WIKI.models.users.query().insert({
provider: 'local',
email,
name,
@@ -358,6 +358,12 @@ module.exports = class User extends Model {
isVerified: false
})
// Create verification token
const verificationToken = await WIKI.models.userKeys.generateToken({
kind: 'verify',
userId: newUsr.id
})
// Send verification email
await WIKI.mail.send({
template: 'accountVerify',
@@ -367,10 +373,10 @@ module.exports = class User extends Model {
preheadertext: 'Verify your account in order to gain access to the wiki.',
title: 'Verify your account',
content: 'Click the button below in order to verify your account and gain access to the wiki.',
buttonLink: 'http://www.google.com',
buttonLink: `${WIKI.config.host}/verify/${verificationToken}`,
buttonText: 'Verify'
},
text: `You must open the following link in your browser to verify your account and gain access to the wiki: http://www.google.com`
text: `You must open the following link in your browser to verify your account and gain access to the wiki: ${WIKI.config.host}/verify/${verificationToken}`
})
return true
} else {

View File

@@ -98,18 +98,54 @@ module.exports = () => {
await fs.ensureDir(path.join(dataPath, 'uploads'))
// Set config
_.set(WIKI.config, 'company', '')
_.set(WIKI.config, 'defaultEditor', 'markdown')
_.set(WIKI.config, 'features', {
featurePageRatings: true,
featurePageComments: true,
featurePersonalWikis: true
})
_.set(WIKI.config, 'graphEndpoint', 'https://graph.requarks.io')
_.set(WIKI.config, 'lang.code', 'en')
_.set(WIKI.config, 'lang.autoUpdate', true)
_.set(WIKI.config, 'lang.namespacing', false)
_.set(WIKI.config, 'lang.namespaces', [])
_.set(WIKI.config, 'host', 'http://')
_.set(WIKI.config, 'lang', {
code: 'en',
autoUpdate: true,
namespacing: false,
namespaces: []
})
_.set(WIKI.config, 'logo', {
hasLogo: false,
logoIsSquare: false
})
_.set(WIKI.config, 'mail', {
senderName: '',
senderEmail: '',
host: '',
port: 465,
secure: true,
user: '',
pass: '',
useDKIM: false,
dkimDomainName: '',
dkimKeySelector: '',
dkimPrivateKey: ''
})
_.set(WIKI.config, 'public', false)
_.set(WIKI.config, 'seo', {
description: '',
keywords: '',
robots: ['index', 'follow'],
ga: ''
})
_.set(WIKI.config, 'sessionSecret', (await crypto.randomBytesAsync(32)).toString('hex'))
_.set(WIKI.config, 'telemetry.isEnabled', req.body.telemetry === 'true')
_.set(WIKI.config, 'telemetry.clientId', WIKI.telemetry.cid)
_.set(WIKI.config, 'theming.theme', 'default')
_.set(WIKI.config, 'theming.darkMode', false)
_.set(WIKI.config, 'telemetry', {
isEnabled: req.body.telemetry === 'true',
clientId: WIKI.telemetry.cid
})
_.set(WIKI.config, 'theming', {
theme: 'default',
darkMode: false
})
_.set(WIKI.config, 'title', 'Wiki.js')
// Generate certificates
@@ -128,22 +164,30 @@ module.exports = () => {
}
})
_.set(WIKI.config, 'certs.jwk', pem2jwk(certs.publicKey))
_.set(WIKI.config, 'certs.public', certs.publicKey)
_.set(WIKI.config, 'certs.private', certs.privateKey)
_.set(WIKI.config, 'certs', {
jwk: pem2jwk(certs.publicKey),
public: certs.publicKey,
private: certs.privateKey
})
// Save config to DB
WIKI.logger.info('Persisting config to DB...')
await WIKI.configSvc.saveToDb([
'certs',
'company',
'defaultEditor',
'features',
'graphEndpoint',
'host',
'lang',
'logo',
'mail',
'public',
'seo',
'sessionSecret',
'telemetry',
'theming',
'title',
'certs'
'title'
])
// Create default locale

View File

@@ -233,7 +233,7 @@
<!-- Email Header : BEGIN -->
<tr>
<td style="padding: 20px 0; text-align: center">
<img src="<%= logo %>" width="200" height="50" alt="<%= siteTitle %>" border="0" style="height: auto; background: #dddddd; font-family: sans-serif; font-size: 15px; line-height: 15px; color: #555555;">
<img src="<%= logo %>" height="50" alt="<%= siteTitle %>" border="0" style="width: auto; background: #dddddd; font-family: sans-serif; font-size: 15px; line-height: 15px; color: #555555;">
</td>
</tr>
<!-- Email Header : END -->