Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

user.js 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. 'use strict'
  2. require('dotenv').config()
  3. const crypto = require('crypto')
  4. const Util = require('util')
  5. const JWT = require('jsonwebtoken')
  6. const Schmervice = require('@hapipal/schmervice')
  7. const SecurePassword = require('secure-password')
  8. // Configuration for Brevo
  9. const SibApiV3Sdk = require('@getbrevo/brevo')
  10. let apiInstance = new SibApiV3Sdk.AccountApi()
  11. apiInstance.setApiKey(
  12. SibApiV3Sdk.AccountApiApiKeys.apiKey,
  13. process.env.BREVO_KEY,
  14. )
  15. const sendSmtpEmail = new SibApiV3Sdk.SendSmtpEmail()
  16. // TODO: Consider implementing, nice use of SecurePassword,
  17. // but currently not used anywhere...
  18. const hasher = async (pwd, steak) => {
  19. const hash = await pwd.hash(steak)
  20. const result = await pwd.verify(steak, hash)
  21. let squirtle = null
  22. switch (result) {
  23. case SecurePassword.INVALID_UNRECOGNIZED_HASH:
  24. return console.error(
  25. 'This hash was not made with secure-password. Attempt legacy algorithm',
  26. )
  27. case SecurePassword.INVALID:
  28. return console.log('Invalid password')
  29. case SecurePassword.VALID:
  30. return result
  31. case SecurePassword.VALID_NEEDS_REHASH:
  32. console.log('Yay you made it, wait for us to improve your safety')
  33. try {
  34. squirtle = await pwd.hash(steak)
  35. // console.log('improvedHash', squirtle)
  36. // const saveHash = Auth.insert({user_email:
  37. // matchingEmails}).into('token')
  38. return squirtle
  39. } catch (err) {
  40. console.error(
  41. 'You are authenticated, but we could not improve your safety this time around',
  42. )
  43. }
  44. break
  45. }
  46. }
  47. /** Class for methods used in the User plugin */
  48. module.exports = class UserService extends Schmervice.Service {
  49. /**
  50. * Unsure of what our constructor does
  51. * @param {...any} args
  52. */
  53. constructor(...args) {
  54. super(...args)
  55. const pwd = new SecurePassword()
  56. // TODO: Invalidate this application state somehow after a
  57. // certain time period has passed
  58. this.activeSessions = {}
  59. this.pwd = {
  60. hash: Util.promisify(pwd.hash.bind(pwd)),
  61. verify: Util.promisify(pwd.verify.bind(pwd)),
  62. }
  63. }
  64. /**
  65. * Use knex to find users with id column
  66. * @param {number} id
  67. * @param {*} txn
  68. * @returns
  69. */
  70. async findById(id, txn) {
  71. const { User } = this.server.models()
  72. return await User.query(txn)
  73. .throwIfNotFound()
  74. .first()
  75. .where({ user_id: id })
  76. }
  77. /**
  78. * Use knew to find first user with username
  79. * @param {*} username
  80. * @param {*} txn
  81. * @returns
  82. */
  83. async findByUsername(username, txn) {
  84. const { User } = this.server.models()
  85. return await User.query(txn)
  86. .throwIfNotFound()
  87. .first()
  88. .where({ user_name: username })
  89. }
  90. /**
  91. * Use to find first user with useremail
  92. * @param {*} username
  93. * @param {*} txn
  94. * @returns
  95. */
  96. async findByUserEmail(userEmail, txn) {
  97. const { User } = this.server.models()
  98. const user = await User.query(txn)
  99. .throwIfNotFound()
  100. .first()
  101. .where({ user_email: userEmail })
  102. return user
  103. }
  104. hashToken(token) {
  105. const salt = process.env.APP_SESSION_SALT
  106. try {
  107. return crypto.createHmac('sha256', salt).update(token).digest('hex')
  108. } catch (err) {
  109. throw new Error(err.message)
  110. }
  111. }
  112. /**
  113. * Signup function
  114. * @param {*} param0
  115. * @param {*} txn
  116. * @returns
  117. */
  118. async signup({ password, userInfo, created_at }, txn) {
  119. const { User, Auth } = this.server.models()
  120. const matchingEmails = await User.query().where(
  121. 'user_email',
  122. userInfo.user_email,
  123. )
  124. if (matchingEmails.length > 0) {
  125. throw `User ${userInfo.user_email} already exists: Cannot create a user without a unique email`
  126. }
  127. // Insert User Info to User table
  128. const insertUser = await User.query().insert(userInfo)
  129. // insert a row with blank password to be updated by changePassword()
  130. await Auth.query().insert({
  131. user_email: insertUser.user_email,
  132. created_at: created_at,
  133. token: null,
  134. })
  135. // update null token with hashed password
  136. await this.changePassword(insertUser.user_email, password, txn)
  137. return {
  138. user_id: insertUser.id,
  139. user_name: insertUser.user_name,
  140. user_email: insertUser.user_email,
  141. is_poster: insertUser.is_poster,
  142. is_admin: insertUser.is_admin,
  143. is_verified: insertUser.is_verified,
  144. }
  145. }
  146. /**
  147. * Updates user's info
  148. * @param {number} id
  149. * @param {*} param1
  150. * @param {*} txn
  151. * @returns
  152. */
  153. async update(id, { password, ...userInfo }, txn) {
  154. const { User } = this.server.models()
  155. if (Object.keys(userInfo).length > 0) {
  156. await User.query(txn)
  157. .throwIfNotFound()
  158. .where({ id })
  159. .patch(userInfo)
  160. }
  161. if (password) {
  162. await this.changePassword(id, password, txn)
  163. }
  164. return id
  165. }
  166. /**
  167. * Self explanatory
  168. * @param {*} param0
  169. * @param {*} txn
  170. * @returns
  171. */
  172. async login({ email, password }, txn) {
  173. const { User, Auth } = this.server.models()
  174. const user = await Auth.query(txn)
  175. .throwIfNotFound()
  176. .first()
  177. .where({ user_email: email })
  178. const bufferPepper = Buffer.from(process.env.PEPPER + password)
  179. /** Uncomment to run password check using SecurePassword */
  180. const passwordCheck = await this.pwd.verify(bufferPepper, user.token)
  181. if (passwordCheck === SecurePassword.VALID_NEEDS_REHASH) {
  182. await this.changePassword(user.user_email, password, txn)
  183. } else if (passwordCheck !== SecurePassword.VALID) {
  184. throw User.createNotFoundError()
  185. }
  186. return user
  187. }
  188. /**
  189. * Create a token to be sent in request headers
  190. * @param {data, expiration}
  191. * @returns {Token}
  192. */
  193. createToken(data, expiration = 600) {
  194. const key = process.env.APP_SECRET
  195. const obj = {}
  196. Object.assign(obj, { ...data })
  197. return JWT.sign(obj, key, { expiresIn: expiration })
  198. }
  199. async makeUserCredentials(email) {
  200. const user = await this.findByUserEmail(email)
  201. const userCredentials = {
  202. email: user.user_email,
  203. name: user.user_name,
  204. seeking: user.is_poster === 1 ? 'poster' : 'seeker',
  205. }
  206. const token = this.createToken({
  207. payload: userCredentials,
  208. })
  209. return {
  210. userCredentials,
  211. token,
  212. }
  213. }
  214. /**
  215. * Validates whether a token has expired or not
  216. * @param {User} user
  217. * @returns {Token}
  218. */
  219. validateToken(token) {
  220. const key = process.env.APP_SECRET
  221. return JWT.verify(token, key)
  222. }
  223. removeSession(hashedSessionToken) {
  224. const userSession = this.activeSessions[hashedSessionToken]
  225. if (!userSession) {
  226. throw new Error(
  227. 'hashedSessionToken not in activeSessions registry!',
  228. )
  229. } else {
  230. delete this.activeSessions[hashedSessionToken]
  231. }
  232. }
  233. /**
  234. * Use knex to try to change password entry
  235. * @param {number} id
  236. * @param {string} password
  237. * @param {*} txn
  238. * @returns {number}
  239. */
  240. async changePassword(email, password, txn) {
  241. const { Auth } = this.server.models()
  242. const hashed = await this.pwd.hash(
  243. Buffer.from(process.env.PEPPER + password),
  244. )
  245. await Auth.query(txn)
  246. .throwIfNotFound()
  247. .where({ user_email: email })
  248. .patch({
  249. // user_email: email,
  250. token: hashed,
  251. })
  252. return email
  253. }
  254. async getPassword(email, txn) {
  255. const { Auth } = this.server.models()
  256. const passwordRow = await Auth.query(txn)
  257. .where('user_email', email)
  258. .first()
  259. return passwordRow ? passwordRow.token : null
  260. }
  261. /**
  262. * Sends a Transactional Email via Brevo
  263. * @ returns {Object}
  264. */
  265. async emailSent(userCredentials) {
  266. const hashedSessionToken = this.hashToken(userCredentials.sessionToken)
  267. if (Object.keys(this.activeSessions).includes(hashedSessionToken)) {
  268. return new Error('session already in cache!!')
  269. }
  270. // Set expiration time for ten minutes from now
  271. const duration = 600000
  272. this.activeSessions[hashedSessionToken] = {
  273. email: userCredentials.email,
  274. name: userCredentials.name,
  275. seeking: userCredentials.seeking,
  276. sessionToken: userCredentials.sessionToken,
  277. expiration: Date.now() + duration,
  278. emailWasRespondedTo: false,
  279. accessToken: null,
  280. }
  281. // NOTE: Although this looks messy, Brevo requires these
  282. // parameters be defined individually like this, attempts
  283. // to configure this in a single object cause errors on their API
  284. sendSmtpEmail.sender = {
  285. name: '[siimee]',
  286. email: 'mytestemail@email.com',
  287. }
  288. sendSmtpEmail.subject = '[siimee] New Email from Siimee!'
  289. sendSmtpEmail.to = [
  290. {
  291. email: userCredentials.email,
  292. },
  293. ]
  294. sendSmtpEmail.templateId = Number(process.env.BREVO_TEMPLATE_ID)
  295. sendSmtpEmail.params = {
  296. link: `${process.env.BREVO_LINK}/verify/${hashedSessionToken}`,
  297. }
  298. return await apiInstance.sendTransacEmail(sendSmtpEmail).then(
  299. data => {
  300. return { wasSuccessfull: true, data: data }
  301. },
  302. error => {
  303. return { wasSuccessfull: false, error: error }
  304. },
  305. )
  306. }
  307. }