Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

user.js 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. 'use strict'
  2. require('dotenv').config()
  3. const crypto = require('crypto')
  4. const Util = require('util')
  5. const Jwt = require('@hapi/jwt')
  6. const Schmervice = require('@hapipal/schmervice')
  7. const SecurePassword = require('secure-password')
  8. // Configuration for Brevo
  9. const SibApiV3Sdk = require('sib-api-v3-sdk')
  10. const defaultClient = SibApiV3Sdk.ApiClient.instance
  11. const apiKey = defaultClient.authentications['api-key']
  12. apiKey.apiKey = process.env.BREVO_KEY
  13. const apiInstance = new SibApiV3Sdk.TransactionalEmailsApi()
  14. const hashEmail = async email => {
  15. try {
  16. return crypto.createHmac('sha256', '').update(email).digest('hex')
  17. } catch (err) {
  18. console.error('ERROR :=>', err)
  19. return undefined
  20. }
  21. }
  22. const hasher = async (pwd, steak) => {
  23. const hash = await pwd.hash(steak)
  24. const result = await pwd.verify(steak, hash)
  25. let squirtle = null
  26. switch (result) {
  27. case SecurePassword.INVALID_UNRECOGNIZED_HASH:
  28. return console.error(
  29. 'This hash was not made with secure-password. Attempt legacy algorithm',
  30. )
  31. case SecurePassword.INVALID:
  32. return console.log('Invalid password')
  33. case SecurePassword.VALID:
  34. return result
  35. case SecurePassword.VALID_NEEDS_REHASH:
  36. console.log('Yay you made it, wait for us to improve your safety')
  37. try {
  38. squirtle = await pwd.hash(steak)
  39. // console.log('improvedHash', squirtle)
  40. // const saveHash = Auth.insert({user_email: matchingEmails}, ).into('token')
  41. return squirtle
  42. } catch (err) {
  43. console.error(
  44. 'You are authenticated, but we could not improve your safety this time around',
  45. )
  46. }
  47. break
  48. }
  49. }
  50. /** Class for methods used in the User plugin */
  51. module.exports = class UserService extends Schmervice.Service {
  52. /**
  53. * Unsure of what our constructor does
  54. * @param {...any} args
  55. */
  56. constructor(...args) {
  57. super(...args)
  58. const pwd = new SecurePassword()
  59. this.hashedEmail = ''
  60. // this.hashedEmails = []
  61. this.pwd = {
  62. hash: Util.promisify(pwd.hash.bind(pwd)),
  63. verify: Util.promisify(pwd.verify.bind(pwd)),
  64. }
  65. }
  66. /**
  67. * Use knex to find users with id column
  68. * @param {number} id
  69. * @param {*} txn
  70. * @returns
  71. */
  72. async findById(id, txn) {
  73. const { User } = this.server.models()
  74. return await User.query(txn)
  75. .throwIfNotFound()
  76. .first()
  77. .where({ user_id: id })
  78. }
  79. /**
  80. * Use knew to find first user with username
  81. * @param {*} username
  82. * @param {*} txn
  83. * @returns
  84. */
  85. async findByUsername(username, txn) {
  86. const { User } = this.server.models()
  87. return await User.query(txn)
  88. .throwIfNotFound()
  89. .first()
  90. .where({ user_name: username })
  91. }
  92. /**
  93. * Signup function
  94. * @param {*} param0
  95. * @param {*} txn
  96. * @returns
  97. */
  98. async signup({ password, userInfo, created_at }, txn) {
  99. const { User, Auth } = this.server.models()
  100. const matchingEmails = await User.query().where(
  101. 'user_email',
  102. userInfo.user_email,
  103. )
  104. if (matchingEmails.length > 0) {
  105. throw `User ${userInfo.user_email} already exists: Cannot create a user without a unique email`
  106. }
  107. // Insert User Info to User table
  108. const insertUser = await User.query().insert(userInfo)
  109. // insert a row with blank password to be updated by changePassword()
  110. await Auth.query().insert({
  111. user_email: insertUser.user_email,
  112. created_at: created_at,
  113. token: null,
  114. })
  115. // update null token with hashed password
  116. await this.changePassword(insertUser.user_email, password, txn)
  117. return {
  118. user_id: insertUser.id,
  119. user_name: insertUser.user_name,
  120. user_email: insertUser.user_email,
  121. is_poster: insertUser.is_poster,
  122. is_admin: insertUser.is_admin,
  123. is_verified: insertUser.is_verified,
  124. }
  125. }
  126. /**
  127. * Updates user's info
  128. * @param {number} id
  129. * @param {*} param1
  130. * @param {*} txn
  131. * @returns
  132. */
  133. async update(id, { password, ...userInfo }, txn) {
  134. const { User } = this.server.models()
  135. if (Object.keys(userInfo).length > 0) {
  136. await User.query(txn)
  137. .throwIfNotFound()
  138. .where({ id })
  139. .patch(userInfo)
  140. }
  141. if (password) {
  142. await this.changePassword(id, password, txn)
  143. }
  144. return id
  145. }
  146. /**
  147. * Self explanatory
  148. * @param {*} param0
  149. * @param {*} txn
  150. * @returns
  151. */
  152. async login({ email, password }, txn) {
  153. const { User, Auth } = this.server.models()
  154. const user = await Auth.query(txn)
  155. .throwIfNotFound()
  156. .first()
  157. .where({ user_email: email })
  158. const bufferPepper = Buffer.from(process.env.PEPPER + password)
  159. /** Uncomment to run password check using SecurePassword */
  160. const passwordCheck = await this.pwd.verify(bufferPepper, user.token)
  161. if (passwordCheck === SecurePassword.VALID_NEEDS_REHASH) {
  162. await this.changePassword(user.user_email, password, txn)
  163. } else if (passwordCheck !== SecurePassword.VALID) {
  164. throw User.createNotFoundError()
  165. }
  166. return user
  167. }
  168. /**
  169. * Create a token to be sent in request headers
  170. * @param {User} user
  171. * @returns {Token}
  172. */
  173. createToken(user) {
  174. const key = this.server.registrations['main-app-plugin'].options.jwtKey
  175. return Jwt.token.generate(
  176. {
  177. aud: 'urn:audience:test',
  178. iss: 'urn:issuer:test',
  179. email: user.email,
  180. name: user.name,
  181. seeking: user.seeking,
  182. },
  183. {
  184. key: key,
  185. algorithm: 'HS256',
  186. },
  187. {
  188. // ttlSec: 4 * 60 * 60, // 7 days
  189. ttlSec: 60 * 3, // 3 minutes
  190. },
  191. )
  192. }
  193. /**
  194. * Validates whether a token has expired or not
  195. * @param {User} user
  196. * @returns {Token}
  197. */
  198. validateToken(token) {
  199. const key = this.server.registrations['main-app-plugin'].options.jwtKey
  200. const decodedToken = Jwt.token.decode(token)
  201. console.log('decodedToken :=>', decodedToken)
  202. // NOTE: reveals email...perhaps unhashed email belongs here instead...
  203. try {
  204. Jwt.token.verify(decodedToken, key)
  205. return { isValid: true, payload: decodedToken.decoded.payload }
  206. } catch (err) {
  207. return { isValid: false, error: err.message }
  208. }
  209. }
  210. /**
  211. * Use knex to try to change password entry
  212. * @param {number} id
  213. * @param {string} password
  214. * @param {*} txn
  215. * @returns {number}
  216. */
  217. async changePassword(email, password, txn) {
  218. const { Auth } = this.server.models()
  219. const hashed = await this.pwd.hash(
  220. Buffer.from(process.env.PEPPER + password),
  221. )
  222. await Auth.query(txn)
  223. .throwIfNotFound()
  224. .where({ user_email: email })
  225. .patch({
  226. // user_email: email,
  227. token: hashed,
  228. })
  229. return email
  230. }
  231. async getPassword(email, txn) {
  232. const { Auth } = this.server.models()
  233. const passwordRow = await Auth.query(txn)
  234. .where('user_email', email)
  235. .first()
  236. return passwordRow ? passwordRow.token : null
  237. }
  238. /**
  239. * Sends a Transactional Email via Brevo
  240. * @ returns {Object}
  241. */
  242. async emailSent(userEmail) {
  243. const sendSmtpEmail = {
  244. to: [
  245. {
  246. email: userEmail,
  247. },
  248. ],
  249. templateId: 1,
  250. params: {
  251. // TODO: Change this in production...
  252. link: `localhost:3000/verify/${await hashEmail(userEmail)}`,
  253. },
  254. }
  255. await apiInstance.sendTransacEmail(sendSmtpEmail).then(
  256. data => {
  257. return {
  258. wasSuccessfull: true,
  259. data: data,
  260. }
  261. },
  262. error => {
  263. return {
  264. wasSuccessfull: false,
  265. error: error,
  266. }
  267. },
  268. )
  269. this.hashedEmail = hashEmail(userEmail)
  270. // this.hashedEmails.push(hashEmail(userEmail))
  271. }
  272. }