You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

user.js 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. 'use strict'
  2. require('dotenv').config()
  3. const Util = require('util')
  4. const JWT = require('jsonwebtoken')
  5. const Schmervice = require('@hapipal/schmervice')
  6. const SecurePassword = require('secure-password')
  7. /** Class for methods used in the User plugin */
  8. module.exports = class UserService extends Schmervice.Service {
  9. /**
  10. * Unsure of what our constructor does
  11. * @param {...any} args
  12. */
  13. constructor(...args) {
  14. super(...args)
  15. const pwd = new SecurePassword()
  16. this.pwd = {
  17. hash: Util.promisify(pwd.hash.bind(pwd)),
  18. verify: Util.promisify(pwd.verify.bind(pwd)),
  19. }
  20. }
  21. /**
  22. * Use knex to find users with email column
  23. * @param {string} email
  24. * @param {*} txn
  25. * @returns
  26. */
  27. async findByEmail(email) {
  28. const { User } = this.server.models()
  29. return await User.query()
  30. .throwIfNotFound()
  31. .first()
  32. .where({ user_email: email })
  33. }
  34. /**
  35. * Use knex to find users with id column
  36. * @param {number} id
  37. * @param {*} txn
  38. * @returns
  39. */
  40. async findById(id, txn) {
  41. const { User } = this.server.models()
  42. return await User.query(txn)
  43. .throwIfNotFound()
  44. .first()
  45. .where({ user_id: id })
  46. }
  47. /**
  48. * Use knew to find first user with username
  49. * @param {*} username
  50. * @param {*} txn
  51. * @returns
  52. */
  53. async findByUsername(username, txn) {
  54. const { User } = this.server.models()
  55. return await User.query(txn)
  56. .throwIfNotFound()
  57. .first()
  58. .where({ user_name: username })
  59. }
  60. /**
  61. * Signup function
  62. * @param {*} param0
  63. * @param {*} txn
  64. * @returns
  65. */
  66. async signup({ password, userInfo, created_at }, txn) {
  67. const { User, Auth } = this.server.models()
  68. const matchingEmails = await User.query().where(
  69. 'user_email',
  70. userInfo.user_email,
  71. )
  72. if (matchingEmails.length > 0) {
  73. throw `User ${userInfo.user_email} already exists: Cannot create a user without a unique email`
  74. }
  75. // Insert User Info to User table
  76. const insertUser = await User.query().insert(userInfo)
  77. // insert a row with blank password to be updated by changePassword()
  78. await Auth.query().insert({
  79. user_email: insertUser.user_email,
  80. created_at: created_at,
  81. token: null,
  82. })
  83. // update null token with hashed password
  84. await this.changePassword(insertUser.user_email, password, txn)
  85. return {
  86. user_id: insertUser.id,
  87. user_name: insertUser.user_name,
  88. user_email: insertUser.user_email,
  89. is_poster: insertUser.is_poster,
  90. is_admin: insertUser.is_admin,
  91. is_verified: insertUser.is_verified,
  92. }
  93. }
  94. /**
  95. * Updates user's info
  96. * @param {number} id
  97. * @param {*} param1
  98. * @param {*} txn
  99. * @returns
  100. */
  101. async update(id, { password, ...userInfo }, txn) {
  102. const { User } = this.server.models()
  103. if (Object.keys(userInfo).length > 0) {
  104. await User.query(txn)
  105. .throwIfNotFound()
  106. .where({ id })
  107. .patch(userInfo)
  108. }
  109. if (password) {
  110. await this.changePassword(id, password, txn)
  111. }
  112. return id
  113. }
  114. /**
  115. * Self explanatory
  116. * @param {*} param0
  117. * @param {*} txn
  118. * @returns
  119. */
  120. async login({ email, password }, txn) {
  121. const { User, Auth } = this.server.models()
  122. const user = await Auth.query(txn)
  123. .throwIfNotFound()
  124. .first()
  125. .where({ user_email: email })
  126. const bufferPepper = Buffer.from(process.env.PEPPER + password)
  127. /** Uncomment to run password check using SecurePassword */
  128. const passwordCheck = await this.pwd.verify(bufferPepper, user.token)
  129. if (passwordCheck === SecurePassword.VALID_NEEDS_REHASH) {
  130. await this.changePassword(user.user_email, password, txn)
  131. } else if (passwordCheck !== SecurePassword.VALID) {
  132. throw User.createNotFoundError()
  133. }
  134. return user
  135. }
  136. /**
  137. * Create a token to be sent in request headers
  138. * @param {User} user
  139. * @returns {Token}
  140. */
  141. createToken(user) {
  142. const key = this.server.registrations['main-app-plugin'].options.jwtKey
  143. const obj = {}
  144. Object.assign(obj, { ...user })
  145. return JWT.sign(obj, key)
  146. }
  147. /**
  148. * Use knex to try to change password entry
  149. * @param {number} id
  150. * @param {string} password
  151. * @param {*} txn
  152. * @returns {number}
  153. */
  154. async changePassword(email, password, txn) {
  155. const { Auth } = this.server.models()
  156. const hashed = await this.pwd.hash(
  157. Buffer.from(process.env.PEPPER + password),
  158. )
  159. await Auth.query(txn)
  160. .throwIfNotFound()
  161. .where({ user_email: email })
  162. .patch({
  163. // user_email: email,
  164. token: hashed,
  165. })
  166. return email
  167. }
  168. async getPassword(email, txn) {
  169. const { Auth } = this.server.models()
  170. const passwordRow = await Auth.query(txn)
  171. .where('user_email', email)
  172. .first()
  173. return passwordRow ? passwordRow.token : null
  174. }
  175. }