send-pass-recovery-email.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. /*
  2. * Copyright (C) 2024 Puter Technologies Inc.
  3. *
  4. * This file is part of Puter.
  5. *
  6. * Puter is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License as published
  8. * by the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. "use strict"
  20. const express = require('express');
  21. const router = new express.Router();
  22. const { body_parser_error_handler, get_user, invalidate_cached_user } = require('../helpers');
  23. const config = require('../config');
  24. const { DB_WRITE } = require('../services/database/consts');
  25. // -----------------------------------------------------------------------//
  26. // POST /send-pass-recovery-email
  27. // -----------------------------------------------------------------------//
  28. router.post('/send-pass-recovery-email', express.json(), body_parser_error_handler, async (req, res, next)=>{
  29. // either api. subdomain or no subdomain
  30. if(require('../helpers').subdomain(req) !== 'api' && require('../helpers').subdomain(req) !== '')
  31. next();
  32. // modules
  33. const db = req.services.get('database').get(DB_WRITE, 'auth');
  34. const validator = require('validator')
  35. // validation
  36. if(!req.body.username && !req.body.email)
  37. return res.status(400).send('Username or email is required.');
  38. // username, if provided, must be a string
  39. else if (req.body.username && typeof req.body.username !== 'string')
  40. return res.status(400).send('username must be a string.')
  41. // if username doesn't pass regex test it's invalid anyway, no need to do DB lookup
  42. else if(req.body.username && !req.body.username.match(config.username_regex))
  43. return res.status(400).send('Invalid username.')
  44. // email, if provided, must be a string
  45. else if (req.body.email && typeof req.body.email !== 'string')
  46. return res.status(400).send('email must be a string.')
  47. // if email is invalid, no need to do DB lookup anyway
  48. else if(req.body.email && !validator.isEmail(req.body.email))
  49. return res.status(400).send('Invalid email.')
  50. const svc_edgeRateLimit = req.services.get('edge-rate-limit');
  51. if ( ! svc_edgeRateLimit.check('send-pass-recovery-email') ) {
  52. return res.status(429).send('Too many requests.');
  53. }
  54. try{
  55. let user;
  56. // see if username exists
  57. if(req.body.username){
  58. user = await get_user({username: req.body.username});
  59. if(!user)
  60. return res.status(400).send('Username not found.')
  61. }
  62. // see if email exists
  63. else if(req.body.email){
  64. user = await get_user({email: req.body.email});
  65. if(!user)
  66. return res.status(400).send('Email not found.')
  67. }
  68. // check if user is suspended
  69. if(user.suspended){
  70. return res.status(401).send('Account suspended');
  71. }
  72. // set pass_recovery_token
  73. const { v4: uuidv4 } = require('uuid');
  74. const nodemailer = require("nodemailer");
  75. const token = uuidv4();
  76. await db.write(
  77. 'UPDATE user SET pass_recovery_token=? WHERE `id` = ?',
  78. [token, user.id]
  79. );
  80. invalidate_cached_user(user);
  81. // prepare email
  82. let transporter = nodemailer.createTransport({
  83. host: config.smtp_server,
  84. port: config.smpt_port,
  85. secure: true, // STARTTLS
  86. auth: {
  87. user: config.smtp_username,
  88. pass: config.smtp_password,
  89. },
  90. });
  91. // create link
  92. const rec_link = config.origin + '/action/set-new-password?user=' + user.uuid + '&token=' + token;
  93. // send email
  94. transporter.sendMail({
  95. from: 'no-reply@puter.com', // sender address
  96. to: user.email, // list of receivers
  97. subject: "Password Recovery", // Subject line
  98. html: `
  99. <p>Hi there,</p>
  100. <p>A password recovery request was issued for your account, please follow the link below to reset your password:</p>
  101. <p><a href="${rec_link}">${rec_link}</a></p>
  102. <p>Sincerely,</p>
  103. <p>Puter</p>`,
  104. });
  105. // Send response
  106. if(req.body.username)
  107. return res.send({message: `Password recovery sent to the email associated with <strong>${user.username}</strong>. Please check your email for instructions on how to reset your password.`});
  108. else
  109. return res.send({message: `Password recovery email sent to <strong>${user.email}</strong>. Please check your email for instructions on how to reset your password.`});
  110. }catch(e){
  111. console.log(e)
  112. return res.status(400).send(e);
  113. }
  114. })
  115. module.exports = router