users.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773
  1. /* global WIKI */
  2. const bcrypt = require('bcryptjs-then')
  3. const _ = require('lodash')
  4. const tfa = require('node-2fa')
  5. const jwt = require('jsonwebtoken')
  6. const Model = require('objection').Model
  7. const validate = require('validate.js')
  8. const bcryptRegexp = /^\$2[ayb]\$[0-9]{2}\$[A-Za-z0-9./]{53}$/
  9. /**
  10. * Users model
  11. */
  12. module.exports = class User extends Model {
  13. static get tableName() { return 'users' }
  14. static get jsonSchema () {
  15. return {
  16. type: 'object',
  17. required: ['email'],
  18. properties: {
  19. id: {type: 'integer'},
  20. email: {type: 'string', format: 'email'},
  21. name: {type: 'string', minLength: 1, maxLength: 255},
  22. providerId: {type: 'string'},
  23. password: {type: 'string'},
  24. tfaIsActive: {type: 'boolean', default: false},
  25. tfaSecret: {type: 'string'},
  26. jobTitle: {type: 'string'},
  27. location: {type: 'string'},
  28. pictureUrl: {type: 'string'},
  29. isSystem: {type: 'boolean'},
  30. isActive: {type: 'boolean'},
  31. isVerified: {type: 'boolean'},
  32. createdAt: {type: 'string'},
  33. updatedAt: {type: 'string'}
  34. }
  35. }
  36. }
  37. static get relationMappings() {
  38. return {
  39. groups: {
  40. relation: Model.ManyToManyRelation,
  41. modelClass: require('./groups'),
  42. join: {
  43. from: 'users.id',
  44. through: {
  45. from: 'userGroups.userId',
  46. to: 'userGroups.groupId'
  47. },
  48. to: 'groups.id'
  49. }
  50. },
  51. provider: {
  52. relation: Model.BelongsToOneRelation,
  53. modelClass: require('./authentication'),
  54. join: {
  55. from: 'users.providerKey',
  56. to: 'authentication.key'
  57. }
  58. },
  59. defaultEditor: {
  60. relation: Model.BelongsToOneRelation,
  61. modelClass: require('./editors'),
  62. join: {
  63. from: 'users.editorKey',
  64. to: 'editors.key'
  65. }
  66. },
  67. locale: {
  68. relation: Model.BelongsToOneRelation,
  69. modelClass: require('./locales'),
  70. join: {
  71. from: 'users.localeCode',
  72. to: 'locales.code'
  73. }
  74. }
  75. }
  76. }
  77. async $beforeUpdate(opt, context) {
  78. await super.$beforeUpdate(opt, context)
  79. this.updatedAt = new Date().toISOString()
  80. if (!(opt.patch && this.password === undefined)) {
  81. await this.generateHash()
  82. }
  83. }
  84. async $beforeInsert(context) {
  85. await super.$beforeInsert(context)
  86. this.createdAt = new Date().toISOString()
  87. this.updatedAt = new Date().toISOString()
  88. await this.generateHash()
  89. }
  90. // ------------------------------------------------
  91. // Instance Methods
  92. // ------------------------------------------------
  93. async generateHash() {
  94. if (this.password) {
  95. if (bcryptRegexp.test(this.password)) { return }
  96. this.password = await bcrypt.hash(this.password, 12)
  97. }
  98. }
  99. async verifyPassword(pwd) {
  100. if (await bcrypt.compare(pwd, this.password) === true) {
  101. return true
  102. } else {
  103. throw new WIKI.Error.AuthLoginFailed()
  104. }
  105. }
  106. async enableTFA() {
  107. let tfaInfo = tfa.generateSecret({
  108. name: WIKI.config.site.title
  109. })
  110. return this.$query.patch({
  111. tfaIsActive: true,
  112. tfaSecret: tfaInfo.secret
  113. })
  114. }
  115. async disableTFA() {
  116. return this.$query.patch({
  117. tfaIsActive: false,
  118. tfaSecret: ''
  119. })
  120. }
  121. async verifyTFA(code) {
  122. let result = tfa.verifyToken(this.tfaSecret, code)
  123. return (result && _.has(result, 'delta') && result.delta === 0)
  124. }
  125. getGlobalPermissions() {
  126. return _.uniq(_.flatten(_.map(this.groups, 'permissions')))
  127. }
  128. getGroups() {
  129. return _.uniq(_.map(this.groups, 'id'))
  130. }
  131. // ------------------------------------------------
  132. // Model Methods
  133. // ------------------------------------------------
  134. static async processProfile({ profile, providerKey }) {
  135. const provider = _.get(WIKI.auth.strategies, providerKey, {})
  136. provider.info = _.find(WIKI.data.authentication, ['key', providerKey])
  137. // Find existing user
  138. let user = await WIKI.models.users.query().findOne({
  139. providerId: _.toString(profile.id),
  140. providerKey
  141. })
  142. // Parse email
  143. let primaryEmail = ''
  144. if (_.isArray(profile.emails)) {
  145. const e = _.find(profile.emails, ['primary', true])
  146. primaryEmail = (e) ? e.value : _.first(profile.emails).value
  147. } else if (_.isString(profile.email) && profile.email.length > 5) {
  148. primaryEmail = profile.email
  149. } else if (_.isString(profile.mail) && profile.mail.length > 5) {
  150. primaryEmail = profile.mail
  151. } else if (profile.user && profile.user.email && profile.user.email.length > 5) {
  152. primaryEmail = profile.user.email
  153. } else {
  154. throw new Error('Missing or invalid email address from profile.')
  155. }
  156. primaryEmail = _.toLower(primaryEmail)
  157. // Find pending social user
  158. if (!user) {
  159. user = await WIKI.models.users.query().findOne({
  160. email: primaryEmail,
  161. providerId: null,
  162. providerKey
  163. })
  164. if (user) {
  165. user = await user.$query().patchAndFetch({
  166. providerId: _.toString(profile.id)
  167. })
  168. }
  169. }
  170. // Parse display name
  171. let displayName = ''
  172. if (_.isString(profile.displayName) && profile.displayName.length > 0) {
  173. displayName = profile.displayName
  174. } else if (_.isString(profile.name) && profile.name.length > 0) {
  175. displayName = profile.name
  176. } else {
  177. displayName = primaryEmail.split('@')[0]
  178. }
  179. // Parse picture URL
  180. let pictureUrl = _.truncate(_.get(profile, 'picture', _.get(user, 'pictureUrl', null)), {
  181. length: 255,
  182. omission: ''
  183. })
  184. // Update existing user
  185. if (user) {
  186. if (!user.isActive) {
  187. throw new WIKI.Error.AuthAccountBanned()
  188. }
  189. if (user.isSystem) {
  190. throw new Error('This is a system reserved account and cannot be used.')
  191. }
  192. user = await user.$query().patchAndFetch({
  193. email: primaryEmail,
  194. name: displayName,
  195. pictureUrl: pictureUrl
  196. })
  197. return user
  198. }
  199. // Self-registration
  200. if (provider.selfRegistration) {
  201. // Check if email domain is whitelisted
  202. if (_.get(provider, 'domainWhitelist', []).length > 0) {
  203. const emailDomain = _.last(primaryEmail.split('@'))
  204. if (!_.includes(provider.domainWhitelist, emailDomain)) {
  205. throw new WIKI.Error.AuthRegistrationDomainUnauthorized()
  206. }
  207. }
  208. // Create account
  209. user = await WIKI.models.users.query().insertAndFetch({
  210. providerKey: providerKey,
  211. providerId: _.toString(profile.id),
  212. email: primaryEmail,
  213. name: displayName,
  214. pictureUrl: pictureUrl,
  215. localeCode: WIKI.config.lang.code,
  216. defaultEditor: 'markdown',
  217. tfaIsActive: false,
  218. isSystem: false,
  219. isActive: true,
  220. isVerified: true
  221. })
  222. // Assign to group(s)
  223. if (provider.autoEnrollGroups.length > 0) {
  224. await user.$relatedQuery('groups').relate(provider.autoEnrollGroups)
  225. }
  226. return user
  227. }
  228. throw new Error('You are not authorized to login.')
  229. }
  230. static async login (opts, context) {
  231. if (_.has(WIKI.auth.strategies, opts.strategy)) {
  232. const strInfo = _.find(WIKI.data.authentication, ['key', opts.strategy])
  233. // Inject form user/pass
  234. if (strInfo.useForm) {
  235. _.set(context.req, 'body.email', opts.username)
  236. _.set(context.req, 'body.password', opts.password)
  237. }
  238. // Authenticate
  239. return new Promise((resolve, reject) => {
  240. WIKI.auth.passport.authenticate(opts.strategy, {
  241. session: !strInfo.useForm,
  242. scope: strInfo.scopes ? strInfo.scopes : null
  243. }, async (err, user, info) => {
  244. if (err) { return reject(err) }
  245. if (!user) { return reject(new WIKI.Error.AuthLoginFailed()) }
  246. // Get redirect target
  247. user.groups = await user.$relatedQuery('groups').select('groups.id', 'permissions', 'redirectOnLogin')
  248. let redirect = '/'
  249. if (user.groups && user.groups.length > 0) {
  250. redirect = user.groups[0].redirectOnLogin
  251. }
  252. // Must Change Password?
  253. if (user.mustChangePwd) {
  254. try {
  255. const pwdChangeToken = await WIKI.models.userKeys.generateToken({
  256. kind: 'changePwd',
  257. userId: user.id
  258. })
  259. return resolve({
  260. mustChangePwd: true,
  261. continuationToken: pwdChangeToken,
  262. redirect
  263. })
  264. } catch (errc) {
  265. WIKI.logger.warn(errc)
  266. return reject(new WIKI.Error.AuthGenericError())
  267. }
  268. }
  269. // Is 2FA required?
  270. if (user.tfaIsActive) {
  271. try {
  272. const tfaToken = await WIKI.models.userKeys.generateToken({
  273. kind: 'tfa',
  274. userId: user.id
  275. })
  276. return resolve({
  277. tfaRequired: true,
  278. continuationToken: tfaToken,
  279. redirect
  280. })
  281. } catch (errc) {
  282. WIKI.logger.warn(errc)
  283. return reject(new WIKI.Error.AuthGenericError())
  284. }
  285. }
  286. context.req.logIn(user, { session: !strInfo.useForm }, async errc => {
  287. if (errc) { return reject(errc) }
  288. const jwtToken = await WIKI.models.users.refreshToken(user)
  289. resolve({ jwt: jwtToken.token, redirect })
  290. })
  291. })(context.req, context.res, () => {})
  292. })
  293. } else {
  294. throw new WIKI.Error.AuthProviderInvalid()
  295. }
  296. }
  297. static async refreshToken(user) {
  298. if (_.isSafeInteger(user)) {
  299. user = await WIKI.models.users.query().findById(user).withGraphFetched('groups').modifyGraph('groups', builder => {
  300. builder.select('groups.id', 'permissions')
  301. })
  302. if (!user) {
  303. WIKI.logger.warn(`Failed to refresh token for user ${user}: Not found.`)
  304. throw new WIKI.Error.AuthGenericError()
  305. }
  306. if (!user.isActive) {
  307. WIKI.logger.warn(`Failed to refresh token for user ${user}: Inactive.`)
  308. throw new WIKI.Error.AuthAccountBanned()
  309. }
  310. } else if (_.isNil(user.groups)) {
  311. user.groups = await user.$relatedQuery('groups').select('groups.id', 'permissions')
  312. }
  313. // Update Last Login Date
  314. // -> Bypass Objection.js to avoid updating the updatedAt field
  315. await WIKI.models.knex('users').where('id', user.id).update({ lastLoginAt: new Date().toISOString() })
  316. return {
  317. token: jwt.sign({
  318. id: user.id,
  319. email: user.email,
  320. name: user.name,
  321. av: user.pictureUrl,
  322. tz: user.timezone,
  323. lc: user.localeCode,
  324. df: user.dateFormat,
  325. ap: user.appearance,
  326. // defaultEditor: user.defaultEditor,
  327. permissions: user.getGlobalPermissions(),
  328. groups: user.getGroups()
  329. }, {
  330. key: WIKI.config.certs.private,
  331. passphrase: WIKI.config.sessionSecret
  332. }, {
  333. algorithm: 'RS256',
  334. expiresIn: WIKI.config.auth.tokenExpiration,
  335. audience: WIKI.config.auth.audience,
  336. issuer: 'urn:wiki.js'
  337. }),
  338. user
  339. }
  340. }
  341. static async loginTFA (opts, context) {
  342. if (opts.securityCode.length === 6 && opts.loginToken.length === 64) {
  343. let result = await WIKI.redis.get(`tfa:${opts.loginToken}`)
  344. if (result) {
  345. let userId = _.toSafeInteger(result)
  346. if (userId && userId > 0) {
  347. let user = await WIKI.models.users.query().findById(userId)
  348. if (user && user.verifyTFA(opts.securityCode)) {
  349. return Promise.fromCallback(clb => {
  350. context.req.logIn(user, clb)
  351. }).return({
  352. succeeded: true,
  353. message: 'Login Successful'
  354. }).catch(err => {
  355. WIKI.logger.warn(err)
  356. throw new WIKI.Error.AuthGenericError()
  357. })
  358. } else {
  359. throw new WIKI.Error.AuthTFAFailed()
  360. }
  361. }
  362. }
  363. }
  364. throw new WIKI.Error.AuthTFAInvalid()
  365. }
  366. /**
  367. * Change Password from a Mandatory Password Change after Login
  368. */
  369. static async loginChangePassword ({ continuationToken, newPassword }, context) {
  370. if (!newPassword || newPassword.length < 6) {
  371. throw new WIKI.Error.InputInvalid('Password must be at least 6 characters!')
  372. }
  373. const usr = await WIKI.models.userKeys.validateToken({
  374. kind: 'changePwd',
  375. token: continuationToken
  376. })
  377. if (usr) {
  378. await WIKI.models.users.query().patch({
  379. password: newPassword,
  380. mustChangePwd: false
  381. }).findById(usr.id)
  382. return new Promise((resolve, reject) => {
  383. context.req.logIn(usr, { session: false }, async err => {
  384. if (err) { return reject(err) }
  385. const jwtToken = await WIKI.models.users.refreshToken(usr)
  386. resolve({ jwt: jwtToken.token })
  387. })
  388. })
  389. } else {
  390. throw new WIKI.Error.UserNotFound()
  391. }
  392. }
  393. /**
  394. * Create a new user
  395. *
  396. * @param {Object} param0 User Fields
  397. */
  398. static async createNewUser ({ providerKey, email, passwordRaw, name, groups, mustChangePassword, sendWelcomeEmail }) {
  399. // Input sanitization
  400. email = _.toLower(email)
  401. // Input validation
  402. let validation = null
  403. if (providerKey === 'local') {
  404. validation = validate({
  405. email,
  406. passwordRaw,
  407. name
  408. }, {
  409. email: {
  410. email: true,
  411. length: {
  412. maximum: 255
  413. }
  414. },
  415. passwordRaw: {
  416. presence: {
  417. allowEmpty: false
  418. },
  419. length: {
  420. minimum: 6
  421. }
  422. },
  423. name: {
  424. presence: {
  425. allowEmpty: false
  426. },
  427. length: {
  428. minimum: 2,
  429. maximum: 255
  430. }
  431. }
  432. }, { format: 'flat' })
  433. } else {
  434. validation = validate({
  435. email,
  436. name
  437. }, {
  438. email: {
  439. email: true,
  440. length: {
  441. maximum: 255
  442. }
  443. },
  444. name: {
  445. presence: {
  446. allowEmpty: false
  447. },
  448. length: {
  449. minimum: 2,
  450. maximum: 255
  451. }
  452. }
  453. }, { format: 'flat' })
  454. }
  455. if (validation && validation.length > 0) {
  456. throw new WIKI.Error.InputInvalid(validation[0])
  457. }
  458. // Check if email already exists
  459. const usr = await WIKI.models.users.query().findOne({ email, providerKey })
  460. if (!usr) {
  461. // Create the account
  462. let newUsrData = {
  463. providerKey,
  464. email,
  465. name,
  466. locale: 'en',
  467. defaultEditor: 'markdown',
  468. tfaIsActive: false,
  469. isSystem: false,
  470. isActive: true,
  471. isVerified: true,
  472. mustChangePwd: false
  473. }
  474. if (providerKey === `local`) {
  475. newUsrData.password = passwordRaw
  476. newUsrData.mustChangePwd = (mustChangePassword === true)
  477. }
  478. const newUsr = await WIKI.models.users.query().insert(newUsrData)
  479. // Assign to group(s)
  480. if (groups.length > 0) {
  481. await newUsr.$relatedQuery('groups').relate(groups)
  482. }
  483. if (sendWelcomeEmail) {
  484. // Send welcome email
  485. await WIKI.mail.send({
  486. template: 'accountWelcome',
  487. to: email,
  488. subject: `Welcome to the wiki ${WIKI.config.title}`,
  489. data: {
  490. preheadertext: `You've been invited to the wiki ${WIKI.config.title}`,
  491. title: `You've been invited to the wiki ${WIKI.config.title}`,
  492. content: `Click the button below to access the wiki.`,
  493. buttonLink: `${WIKI.config.host}/login`,
  494. buttonText: 'Login'
  495. },
  496. text: `You've been invited to the wiki ${WIKI.config.title}: ${WIKI.config.host}/login`
  497. })
  498. }
  499. } else {
  500. throw new WIKI.Error.AuthAccountAlreadyExists()
  501. }
  502. }
  503. /**
  504. * Update an existing user
  505. *
  506. * @param {Object} param0 User ID and fields to update
  507. */
  508. static async updateUser ({ id, email, name, newPassword, groups, location, jobTitle, timezone, dateFormat, appearance }) {
  509. const usr = await WIKI.models.users.query().findById(id)
  510. if (usr) {
  511. let usrData = {}
  512. if (!_.isEmpty(email) && email !== usr.email) {
  513. const dupUsr = await WIKI.models.users.query().select('id').where({
  514. email,
  515. providerKey: usr.providerKey
  516. }).first()
  517. if (dupUsr) {
  518. throw new WIKI.Error.AuthAccountAlreadyExists()
  519. }
  520. usrData.email = email
  521. }
  522. if (!_.isEmpty(name) && name !== usr.name) {
  523. usrData.name = _.trim(name)
  524. }
  525. if (!_.isEmpty(newPassword)) {
  526. if (newPassword.length < 6) {
  527. throw new WIKI.Error.InputInvalid('Password must be at least 6 characters!')
  528. }
  529. usrData.password = newPassword
  530. }
  531. if (_.isArray(groups)) {
  532. const usrGroupsRaw = await usr.$relatedQuery('groups')
  533. const usrGroups = _.map(usrGroupsRaw, 'id')
  534. // Relate added groups
  535. const addUsrGroups = _.difference(groups, usrGroups)
  536. for (const grp of addUsrGroups) {
  537. await usr.$relatedQuery('groups').relate(grp)
  538. }
  539. // Unrelate removed groups
  540. const remUsrGroups = _.difference(usrGroups, groups)
  541. for (const grp of remUsrGroups) {
  542. await usr.$relatedQuery('groups').unrelate().where('groupId', grp)
  543. }
  544. }
  545. if (!_.isEmpty(location) && location !== usr.location) {
  546. usrData.location = _.trim(location)
  547. }
  548. if (!_.isEmpty(jobTitle) && jobTitle !== usr.jobTitle) {
  549. usrData.jobTitle = _.trim(jobTitle)
  550. }
  551. if (!_.isEmpty(timezone) && timezone !== usr.timezone) {
  552. usrData.timezone = timezone
  553. }
  554. if (!_.isNil(dateFormat) && dateFormat !== usr.dateFormat) {
  555. usrData.dateFormat = dateFormat
  556. }
  557. if (!_.isNil(appearance) && appearance !== usr.appearance) {
  558. usrData.appearance = appearance
  559. }
  560. await WIKI.models.users.query().patch(usrData).findById(id)
  561. } else {
  562. throw new WIKI.Error.UserNotFound()
  563. }
  564. }
  565. /**
  566. * Delete a User
  567. *
  568. * @param {*} id User ID
  569. */
  570. static async deleteUser (id, replaceId) {
  571. const usr = await WIKI.models.users.query().findById(id)
  572. if (usr) {
  573. await WIKI.models.assets.query().patch({ authorId: replaceId }).where('authorId', id)
  574. await WIKI.models.comments.query().patch({ authorId: replaceId }).where('authorId', id)
  575. await WIKI.models.pageHistory.query().patch({ authorId: replaceId }).where('authorId', id)
  576. await WIKI.models.pages.query().patch({ authorId: replaceId }).where('authorId', id)
  577. await WIKI.models.pages.query().patch({ creatorId: replaceId }).where('creatorId', id)
  578. await WIKI.models.userKeys.query().delete().where('userId', id)
  579. await WIKI.models.users.query().deleteById(id)
  580. } else {
  581. throw new WIKI.Error.UserNotFound()
  582. }
  583. }
  584. /**
  585. * Register a new user (client-side registration)
  586. *
  587. * @param {Object} param0 User fields
  588. * @param {Object} context GraphQL Context
  589. */
  590. static async register ({ email, password, name, verify = false, bypassChecks = false }, context) {
  591. const localStrg = await WIKI.models.authentication.getStrategy('local')
  592. // Check if self-registration is enabled
  593. if (localStrg.selfRegistration || bypassChecks) {
  594. // Input sanitization
  595. email = _.toLower(email)
  596. // Input validation
  597. const validation = validate({
  598. email,
  599. password,
  600. name
  601. }, {
  602. email: {
  603. email: true,
  604. length: {
  605. maximum: 255
  606. }
  607. },
  608. password: {
  609. presence: {
  610. allowEmpty: false
  611. },
  612. length: {
  613. minimum: 6
  614. }
  615. },
  616. name: {
  617. presence: {
  618. allowEmpty: false
  619. },
  620. length: {
  621. minimum: 2,
  622. maximum: 255
  623. }
  624. }
  625. }, { format: 'flat' })
  626. if (validation && validation.length > 0) {
  627. throw new WIKI.Error.InputInvalid(validation[0])
  628. }
  629. // Check if email domain is whitelisted
  630. if (_.get(localStrg, 'domainWhitelist.v', []).length > 0 && !bypassChecks) {
  631. const emailDomain = _.last(email.split('@'))
  632. if (!_.includes(localStrg.domainWhitelist.v, emailDomain)) {
  633. throw new WIKI.Error.AuthRegistrationDomainUnauthorized()
  634. }
  635. }
  636. // Check if email already exists
  637. const usr = await WIKI.models.users.query().findOne({ email, providerKey: 'local' })
  638. if (!usr) {
  639. // Create the account
  640. const newUsr = await WIKI.models.users.query().insert({
  641. provider: 'local',
  642. email,
  643. name,
  644. password,
  645. locale: 'en',
  646. defaultEditor: 'markdown',
  647. tfaIsActive: false,
  648. isSystem: false,
  649. isActive: true,
  650. isVerified: false
  651. })
  652. // Assign to group(s)
  653. if (_.get(localStrg, 'autoEnrollGroups.v', []).length > 0) {
  654. await newUsr.$relatedQuery('groups').relate(localStrg.autoEnrollGroups.v)
  655. }
  656. if (verify) {
  657. // Create verification token
  658. const verificationToken = await WIKI.models.userKeys.generateToken({
  659. kind: 'verify',
  660. userId: newUsr.id
  661. })
  662. // Send verification email
  663. await WIKI.mail.send({
  664. template: 'accountVerify',
  665. to: email,
  666. subject: 'Verify your account',
  667. data: {
  668. preheadertext: 'Verify your account in order to gain access to the wiki.',
  669. title: 'Verify your account',
  670. content: 'Click the button below in order to verify your account and gain access to the wiki.',
  671. buttonLink: `${WIKI.config.host}/verify/${verificationToken}`,
  672. buttonText: 'Verify'
  673. },
  674. 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}`
  675. })
  676. }
  677. return true
  678. } else {
  679. throw new WIKI.Error.AuthAccountAlreadyExists()
  680. }
  681. } else {
  682. throw new WIKI.Error.AuthRegistrationDisabled()
  683. }
  684. }
  685. static async getGuestUser () {
  686. const user = await WIKI.models.users.query().findById(2).withGraphJoined('groups').modifyGraph('groups', builder => {
  687. builder.select('groups.id', 'permissions')
  688. })
  689. if (!user) {
  690. WIKI.logger.error('CRITICAL ERROR: Guest user is missing!')
  691. process.exit(1)
  692. }
  693. user.permissions = user.getGlobalPermissions()
  694. return user
  695. }
  696. static async getRootUser () {
  697. let user = await WIKI.models.users.query().findById(1)
  698. if (!user) {
  699. WIKI.logger.error('CRITICAL ERROR: Root Administrator user is missing!')
  700. process.exit(1)
  701. }
  702. user.permissions = ['manage:system']
  703. return user
  704. }
  705. }