Просмотр исходного кода

Merge branch 'feat/password-rsettings-by-users' into imprv/7091-refactoring-middleware

# Conflicts:
#	packages/app/src/server/routes/apiv3/forgot-password.js
kaori 4 лет назад
Родитель
Сommit
dc54260178

+ 2 - 1
packages/app/package.json

@@ -56,8 +56,8 @@
     "@browser-bunyan/console-formatted-stream": "^1.6.2",
     "@google-cloud/storage": "^5.8.5",
     "@growi/plugin-attachment-refs": "^4.3.3-RC",
-    "@growi/plugin-pukiwiki-like-linker": "^4.3.3-RC",
     "@growi/plugin-lsx": "^4.3.3-RC",
+    "@growi/plugin-pukiwiki-like-linker": "^4.3.3-RC",
     "@growi/slack": "^4.3.3-RC",
     "@kobalab/socket.io-session": "^1.0.3",
     "@promster/express": "^5.0.1",
@@ -91,6 +91,7 @@
     "express-bunyan-logger": "^1.3.3",
     "express-form": "~0.12.0",
     "express-mongo-sanitize": "^2.1.0",
+    "express-rate-limit": "^5.3.0",
     "express-session": "^1.16.1",
     "express-validator": "^6.1.1",
     "express-webpack-assets": "^0.1.0",

+ 8 - 0
packages/app/resource/locales/en_US/notifications/PasswordResetSuccessful.txt

@@ -0,0 +1,8 @@
+Password Reset Successful
+
+Hi {{ email }}
+
+Your password has been successfully reset.
+Please log in with your new password.
+
+Thank you,

+ 6 - 0
packages/app/resource/locales/ja_JP/notifications/passwordResetSuccessful.txt

@@ -0,0 +1,6 @@
+パスワードリセットに成功
+
+こんにちは、 {{ email }}
+
+あなたのパスワードは正常にリセットされました。
+新しいパスワードでログインしてください。

+ 6 - 0
packages/app/resource/locales/zh_CN/notifications/PasswordResetSuccessful.txt

@@ -0,0 +1,6 @@
+密码重置成功
+
+嗨, {{email}}
+
+您的密码已成功重置。
+请使用您的新密码登录。

+ 1 - 1
packages/app/src/components/PasswordResetExecutionForm.jsx

@@ -36,7 +36,7 @@ const PasswordResetExecutionForm = (props) => {
     try {
       // get endpoint by /forgot-password/${token} or window.location.pathname
       await appContainer.apiv3Put(`/forgot-password/${token}`, {
-        newPassword, newPasswordConfirm,
+        token, newPassword, newPasswordConfirm,
       });
 
       setNewPassword('');

+ 19 - 6
packages/app/src/server/routes/apiv3/forgot-password.js

@@ -1,3 +1,4 @@
+import rateLimit from 'express-rate-limit';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:routes:apiv3:forgotPassword'); // eslint-disable-line no-unused-vars
@@ -30,11 +31,18 @@ module.exports = (crowi) => {
     ],
   };
 
-  async function sendPasswordResetEmail(email, url, i18n) {
+  const apiLimiter = rateLimit({
+    windowMs: 15 * 60 * 1000, // 15 minutes
+    max: 5, // limit each IP to 5 requests per windowMs
+    message:
+      'Too many requests were sent from this IP. Please try a password reset request again on the password reset request form',
+  });
+
+  async function sendPasswordResetEmail(txtFileName, i18n, email, url) {
     return mailService.send({
       to: email,
-      subject: 'Password Reset',
-      template: path.join(crowi.localeDir, `${i18n}/notifications/passwordReset.txt`),
+      subject: txtFileName,
+      template: path.join(crowi.localeDir, `${i18n}/notifications/${txtFileName}.txt`),
       vars: {
         appTitle: appService.getAppTitle(),
         email,
@@ -60,7 +68,8 @@ module.exports = (crowi) => {
       const passwordResetOrderData = await PasswordResetOrder.createPasswordResetOrder(email);
       const url = new URL(`/forgot-password/${passwordResetOrderData.token}`, appUrl);
       const oneTimeUrl = url.href;
-      await sendPasswordResetEmail(email, oneTimeUrl, i18n);
+      console.log('oneTimeUrl', oneTimeUrl);
+      await sendPasswordResetEmail('passwordReset', i18n, email, oneTimeUrl);
       return res.apiv3();
     }
     catch (err) {
@@ -70,11 +79,14 @@ module.exports = (crowi) => {
     }
   });
 
-  router.put('/:token', csrf, passwordReset, validator.password, apiV3FormValidator, async(req, res) => {
+  router.put('/:token', apiLimiter, csrf, passwordReset, validator.password, apiV3FormValidator, async(req, res) => {
     const passwordResetOrder = req.DataFromPasswordResetOrderMiddleware;
+    const { email } = passwordResetOrder;
+    const grobalLang = configManager.getConfig('crowi', 'app:globalLang');
+    const i18n = req.language || grobalLang;
     const { newPassword } = req.body;
 
-    const user = await User.findOne({ email: passwordResetOrder.email });
+    const user = await User.findOne({ email });
 
     // when the user is not found or active
     if (user == null || user.status !== 2) {
@@ -85,6 +97,7 @@ module.exports = (crowi) => {
       const userData = await user.updatePassword(newPassword);
       const serializedUserData = serializeUserSecurely(userData);
       passwordResetOrder.revokeOneTimeToken();
+      await sendPasswordResetEmail('passwordResetSuccessful', i18n, email);
       return res.apiv3({ userData: serializedUserData });
     }
     catch (err) {

+ 9 - 1
packages/app/src/server/routes/index.js

@@ -1,5 +1,13 @@
 const multer = require('multer');
 const autoReap = require('multer-autoreap');
+const rateLimit = require('express-rate-limit');
+
+const apiLimiter = rateLimit({
+  windowMs: 15 * 60 * 1000, // 15 minutes
+  max: 5, // limit each IP to 5 requests per windowMs
+  message:
+    'Too many requests sent from this IP, please try again after 15 minutes',
+});
 
 autoReap.options.reapOnError = true; // continue reaping the file even if an error occurs
 
@@ -178,7 +186,7 @@ module.exports = function(crowi, app) {
   app.post('/_api/hackmd.saveOnHackmd'   , accessTokenParser , loginRequiredStrictly , csrf, hackmd.validateForApi, hackmd.saveOnHackmd);
 
   app.get('/forgot-password', forgotPassword.forgotPassword);
-  app.get('/forgot-password/:token'      , passwordReset, forgotPassword.resetPassword);
+  app.get('/forgot-password/:token'      ,apiLimiter, passwordReset, forgotPassword.resetPassword);
   app.get('/forgot-password/error/:reason'      , applicationInstalled, forgotPassword.error);
 
   app.get('/share/:linkId', page.showSharedPage);

+ 5 - 0
yarn.lock

@@ -7991,6 +7991,11 @@ express-mongo-sanitize@^2.1.0:
   resolved "https://registry.yarnpkg.com/express-mongo-sanitize/-/express-mongo-sanitize-2.1.0.tgz#a8c647787c25ded6e97b5e864d113e7687c5d471"
   integrity sha512-ELGeH/Tx+kJGn3klCzSmOewfN1ezJQrkqzq83dl/K3xhd5PUbvLtiD5CiuYRmQfoZPL4rUEVjANf/YjE2BpTWQ==
 
+express-rate-limit@^5.3.0:
+  version "5.3.0"
+  resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-5.3.0.tgz#e7b9d3c2e09ece6e0406a869b2ce00d03fe48aea"
+  integrity sha512-qJhfEgCnmteSeZAeuOKQ2WEIFTX5ajrzE0xS6gCOBCoRQcU+xEzQmgYQQTpzCcqUAAzTEtu4YEih4pnLfvNtew==
+
 express-session@^1.16.1:
   version "1.16.1"
   resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.16.1.tgz#251ff9776c59382301de6c8c33411af357ed439c"