user.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. /* global wiki */
  2. const Promise = require('bluebird')
  3. const bcrypt = require('bcryptjs-then')
  4. const _ = require('lodash')
  5. const tfa = require('node-2fa')
  6. const securityHelper = require('../helpers/security')
  7. /**
  8. * Users schema
  9. */
  10. module.exports = (sequelize, DataTypes) => {
  11. let userSchema = sequelize.define('user', {
  12. email: {
  13. type: DataTypes.STRING,
  14. allowNull: false,
  15. validate: {
  16. isEmail: true
  17. }
  18. },
  19. provider: {
  20. type: DataTypes.STRING,
  21. allowNull: false
  22. },
  23. providerId: {
  24. type: DataTypes.STRING,
  25. allowNull: true
  26. },
  27. password: {
  28. type: DataTypes.STRING,
  29. allowNull: true
  30. },
  31. name: {
  32. type: DataTypes.STRING,
  33. allowNull: true
  34. },
  35. role: {
  36. type: DataTypes.ENUM('admin', 'user', 'guest'),
  37. allowNull: false
  38. },
  39. tfaIsActive: {
  40. type: DataTypes.BOOLEAN,
  41. allowNull: false,
  42. defaultValue: false
  43. },
  44. tfaSecret: {
  45. type: DataTypes.STRING,
  46. allowNull: true
  47. }
  48. }, {
  49. timestamps: true,
  50. version: true,
  51. indexes: [
  52. {
  53. unique: true,
  54. fields: ['provider', 'email']
  55. }
  56. ]
  57. })
  58. userSchema.prototype.validatePassword = async function (rawPwd) {
  59. if (await bcrypt.compare(rawPwd, this.password) === true) {
  60. return true
  61. } else {
  62. throw new wiki.Error.AuthLoginFailed()
  63. }
  64. }
  65. userSchema.prototype.enableTFA = async function () {
  66. let tfaInfo = tfa.generateSecret({
  67. name: wiki.config.site.title
  68. })
  69. this.tfaIsActive = true
  70. this.tfaSecret = tfaInfo.secret
  71. return this.save()
  72. }
  73. userSchema.prototype.disableTFA = async function () {
  74. this.tfaIsActive = false
  75. this.tfaSecret = ''
  76. return this.save()
  77. }
  78. userSchema.prototype.verifyTFA = function (code) {
  79. let result = tfa.verifyToken(this.tfaSecret, code)
  80. console.info(result)
  81. return (result && _.has(result, 'delta') && result.delta === 0)
  82. }
  83. userSchema.login = async (opts, context) => {
  84. if (_.has(wiki.config.auth.strategies, opts.provider)) {
  85. _.set(context.req, 'body.email', opts.username)
  86. _.set(context.req, 'body.password', opts.password)
  87. // Authenticate
  88. return new Promise((resolve, reject) => {
  89. wiki.auth.passport.authenticate(opts.provider, async (err, user, info) => {
  90. if (err) { return reject(err) }
  91. if (!user) { return reject(new wiki.Error.AuthLoginFailed()) }
  92. // Is 2FA required?
  93. if (user.tfaIsActive) {
  94. try {
  95. let loginToken = await securityHelper.generateToken(32)
  96. await wiki.redis.set(`tfa:${loginToken}`, user.id, 'EX', 600)
  97. return resolve({
  98. succeeded: true,
  99. message: 'Login Successful. Awaiting 2FA security code.',
  100. tfaRequired: true,
  101. tfaLoginToken: loginToken
  102. })
  103. } catch (err) {
  104. wiki.logger.warn(err)
  105. return reject(new wiki.Error.AuthGenericError())
  106. }
  107. } else {
  108. // No 2FA, log in user
  109. return context.req.logIn(user, err => {
  110. if (err) { return reject(err) }
  111. resolve({
  112. succeeded: true,
  113. message: 'Login Successful',
  114. tfaRequired: false
  115. })
  116. })
  117. }
  118. })(context.req, context.res, () => {})
  119. })
  120. } else {
  121. throw new wiki.Error.AuthProviderInvalid()
  122. }
  123. }
  124. userSchema.loginTFA = async (opts, context) => {
  125. if (opts.securityCode.length === 6 && opts.loginToken.length === 64) {
  126. console.info(opts.loginToken)
  127. let result = await wiki.redis.get(`tfa:${opts.loginToken}`)
  128. console.info(result)
  129. if (result) {
  130. console.info('DUDE2')
  131. let userId = _.toSafeInteger(result)
  132. if (userId && userId > 0) {
  133. console.info('DUDE3')
  134. let user = await wiki.db.User.findById(userId)
  135. if (user && user.verifyTFA(opts.securityCode)) {
  136. console.info('DUDE4')
  137. return Promise.fromCallback(clb => {
  138. context.req.logIn(user, clb)
  139. }).return({
  140. succeeded: true,
  141. message: 'Login Successful'
  142. }).catch(err => {
  143. wiki.logger.warn(err)
  144. throw new wiki.Error.AuthGenericError()
  145. })
  146. } else {
  147. throw new wiki.Error.AuthTFAFailed()
  148. }
  149. }
  150. }
  151. }
  152. throw new wiki.Error.AuthTFAInvalid()
  153. }
  154. userSchema.processProfile = (profile) => {
  155. let primaryEmail = ''
  156. if (_.isArray(profile.emails)) {
  157. let e = _.find(profile.emails, ['primary', true])
  158. primaryEmail = (e) ? e.value : _.first(profile.emails).value
  159. } else if (_.isString(profile.email) && profile.email.length > 5) {
  160. primaryEmail = profile.email
  161. } else if (_.isString(profile.mail) && profile.mail.length > 5) {
  162. primaryEmail = profile.mail
  163. } else if (profile.user && profile.user.email && profile.user.email.length > 5) {
  164. primaryEmail = profile.user.email
  165. } else {
  166. return Promise.reject(new Error(wiki.lang.t('auth:errors.invaliduseremail')))
  167. }
  168. profile.provider = _.lowerCase(profile.provider)
  169. primaryEmail = _.toLower(primaryEmail)
  170. return wiki.db.User.findOneAndUpdate({
  171. email: primaryEmail,
  172. provider: profile.provider
  173. }, {
  174. email: primaryEmail,
  175. provider: profile.provider,
  176. providerId: profile.id,
  177. name: profile.displayName || _.split(primaryEmail, '@')[0]
  178. }, {
  179. new: true
  180. }).then((user) => {
  181. // Handle unregistered accounts
  182. if (!user && profile.provider !== 'local' && (wiki.config.auth.defaultReadAccess || profile.provider === 'ldap' || profile.provider === 'azure')) {
  183. let nUsr = {
  184. email: primaryEmail,
  185. provider: profile.provider,
  186. providerId: profile.id,
  187. password: '',
  188. name: profile.displayName || profile.name || profile.cn,
  189. rights: [{
  190. role: 'read',
  191. path: '/',
  192. exact: false,
  193. deny: false
  194. }]
  195. }
  196. return wiki.db.User.create(nUsr)
  197. }
  198. return user || Promise.reject(new Error(wiki.lang.t('auth:errors:notyetauthorized')))
  199. })
  200. }
  201. userSchema.hashPassword = (rawPwd) => {
  202. return bcrypt.hash(rawPwd)
  203. }
  204. return userSchema
  205. }