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

Merge pull request #4103 from weseek/imprv/gw6778-hit-an-api-to-change-password

Imprv/gw6778 hit an api to change password
cao 4 лет назад
Родитель
Сommit
9e098fc70d

+ 17 - 13
src/client/js/components/PasswordResetExecutionForm.jsx

@@ -1,19 +1,24 @@
 import React, { useState } from 'react';
 import React, { useState } from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 import { withTranslation } from 'react-i18next';
+import loggerFactory from '@alias/logger';
+import { withUnstatedContainers } from './UnstatedUtils';
+import AppContainer from '../services/AppContainer';
 import { toastSuccess, toastError } from '../util/apiNotification';
 import { toastSuccess, toastError } from '../util/apiNotification';
 
 
+const logger = loggerFactory('growi:passwordReset');
+
 
 
 const PasswordResetExecutionForm = (props) => {
 const PasswordResetExecutionForm = (props) => {
-  const { t /* appContainer, personalContainer */ } = props;
+  const { t, appContainer } = props;
 
 
   const [newPassword, setNewPassword] = useState('');
   const [newPassword, setNewPassword] = useState('');
   const [newPasswordConfirm, setNewPasswordConfirm] = useState('');
   const [newPasswordConfirm, setNewPasswordConfirm] = useState('');
   const [validationErrorI18n, setValidationErrorI18n] = useState('');
   const [validationErrorI18n, setValidationErrorI18n] = useState('');
 
 
-  // TODO: delete the following comments by GW-6778
-  // console.log(newPassword);
-  // console.log(newPasswordConfirm);
+  // get token from URL
+  const pathname = window.location.pathname.split('/');
+  const token = pathname[2];
 
 
   const changePassword = async(e) => {
   const changePassword = async(e) => {
     e.preventDefault();
     e.preventDefault();
@@ -29,14 +34,9 @@ const PasswordResetExecutionForm = (props) => {
     }
     }
 
 
     try {
     try {
-      /*
-      * TODO: hit an api to change password by GW-6778
-      * the following code is just a reference
-      */
-
-      // await appContainer.apiv3Put('/personal-setting/password', {
-      //   oldPassword, newPassword, newPasswordConfirm,
-      // });
+      await appContainer.apiv3Put('/forgot-password', {
+        token, newPassword, newPasswordConfirm,
+      });
 
 
       setNewPassword('');
       setNewPassword('');
       setNewPasswordConfirm('');
       setNewPasswordConfirm('');
@@ -46,6 +46,7 @@ const PasswordResetExecutionForm = (props) => {
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
+      logger.error(err);
     }
     }
 
 
   };
   };
@@ -87,8 +88,11 @@ const PasswordResetExecutionForm = (props) => {
   );
   );
 };
 };
 
 
+const PasswordResetExecutionFormWrapper = withUnstatedContainers(PasswordResetExecutionForm, [AppContainer]);
+
 PasswordResetExecutionForm.propTypes = {
 PasswordResetExecutionForm.propTypes = {
   t: PropTypes.func.isRequired, //  i18next
   t: PropTypes.func.isRequired, //  i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
 };
 };
 
 
-export default withTranslation()(PasswordResetExecutionForm);
+export default withTranslation()(PasswordResetExecutionFormWrapper);

+ 1 - 1
src/client/js/components/PasswordResetRequestForm.jsx

@@ -23,7 +23,7 @@ const PasswordResetRequestForm = (props) => {
     }
     }
 
 
     try {
     try {
-      await appContainer.apiPost('/forgot-password', { email });
+      await appContainer.apiv3Post('/forgot-password', { email });
       toastSuccess(t('forgot_password.success_to_send_email'));
       toastSuccess(t('forgot_password.success_to_send_email'));
     }
     }
     catch (err) {
     catch (err) {

+ 3 - 1
src/client/js/nologin.jsx

@@ -104,7 +104,9 @@ if (passwordResetExecutionFormElem) {
 
 
   ReactDOM.render(
   ReactDOM.render(
     <I18nextProvider i18n={i18n}>
     <I18nextProvider i18n={i18n}>
-      <PasswordResetExecutionForm />
+      <Provider inject={[appContainer]}>
+        <PasswordResetExecutionForm />
+      </Provider>
     </I18nextProvider>,
     </I18nextProvider>,
     passwordResetExecutionFormElem,
     passwordResetExecutionFormElem,
   );
   );

+ 85 - 0
src/server/routes/apiv3/forgot-password.js

@@ -0,0 +1,85 @@
+const loggerFactory = require('@alias/logger');
+
+const logger = loggerFactory('growi:routes:apiv3:forgotPassword'); // eslint-disable-line no-unused-vars
+
+const express = require('express');
+const { body } = require('express-validator');
+const { serializeUserSecurely } = require('../../models/serializers/user-serializer');
+
+const router = express.Router();
+
+module.exports = (crowi) => {
+  const { appService, mailService, configManager } = crowi;
+  const PasswordResetOrder = crowi.model('PasswordResetOrder');
+  const User = crowi.model('User');
+  const path = require('path');
+  const csrf = require('../../middlewares/csrf')(crowi);
+  const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
+
+  const validator = {
+    password: [
+      body('newPassword').isString().not().isEmpty()
+        .isLength({ min: 6 })
+        .withMessage('password must be at least 6 characters long'),
+      // checking if password confirmation matches password
+      body('newPasswordConfirm').isString().not().isEmpty()
+        .custom((value, { req }) => {
+          return (value === req.body.newPassword);
+        }),
+    ],
+  };
+
+  async function sendPasswordResetEmail(email, url, i18n) {
+    return mailService.send({
+      to: email,
+      subject: 'Password Reset',
+      template: path.join(crowi.localeDir, `${i18n}/notifications/passwordReset.txt`),
+      vars: {
+        appTitle: appService.getAppTitle(),
+        email,
+        url,
+      },
+    });
+  }
+
+  router.post('/', async(req, res) => {
+    const { email } = req.body;
+    const grobalLang = configManager.getConfig('crowi', 'app:globalLang');
+    const i18n = req.language || grobalLang;
+    const appUrl = appService.getSiteUrl();
+
+    try {
+      const passwordResetOrderData = await PasswordResetOrder.createPasswordResetOrder(email);
+      const url = new URL(`/forgot-password/${passwordResetOrderData.token}`, appUrl);
+      const oneTimeUrl = url.href;
+      await sendPasswordResetEmail(email, oneTimeUrl, i18n);
+      return res.apiv3();
+    }
+    catch (err) {
+      const msg = 'Error occurred during password reset request procedure';
+      logger.error(err);
+      return res.apiv3Err(msg);
+    }
+  });
+
+  router.put('/', csrf, validator.password, apiV3FormValidator, async(req, res) => {
+    const { token, newPassword } = req.body;
+
+    const passwordResetOrder = await PasswordResetOrder.findOne({ token });
+    const { email } = passwordResetOrder;
+
+    const user = await User.findOne({ email });
+
+    try {
+      const userData = await user.updatePassword(newPassword);
+      const serializedUserData = serializeUserSecurely(userData);
+      return res.apiv3({ userData: serializedUserData });
+    }
+    catch (err) {
+      logger.error(err);
+      return res.apiv3Err('update-password-failed');
+    }
+  });
+
+  return router;
+};

+ 2 - 0
src/server/routes/apiv3/index.js

@@ -50,5 +50,7 @@ module.exports = (crowi) => {
   router.use('/slack-integration-settings', require('./slack-integration-settings')(crowi));
   router.use('/slack-integration-settings', require('./slack-integration-settings')(crowi));
   router.use('/staffs', require('./staffs')(crowi));
   router.use('/staffs', require('./staffs')(crowi));
 
 
+  router.use('/forgot-password', require('./forgot-password')(crowi));
+
   return router;
   return router;
 };
 };

+ 0 - 39
src/server/routes/forgot-password.js

@@ -1,10 +1,4 @@
-const logger = require('@alias/logger')('growi:routes:forgot-password');
-const ApiResponse = require('../util/apiResponse');
-
 module.exports = function(crowi, app) {
 module.exports = function(crowi, app) {
-  const PasswordResetOrder = crowi.model('PasswordResetOrder');
-  const { appService, mailService, configManager } = crowi;
-  const path = require('path');
   const actions = {};
   const actions = {};
   const api = {};
   const api = {};
   actions.api = api;
   actions.api = api;
@@ -19,39 +13,6 @@ module.exports = function(crowi, app) {
     return res.render('reset-password', { email });
     return res.render('reset-password', { email });
   };
   };
 
 
-  async function sendPasswordResetEmail(email, url, i18n) {
-    return mailService.send({
-      to: email,
-      subject: 'Password Reset',
-      template: path.join(crowi.localeDir, `${i18n}/notifications/passwordReset.txt`),
-      vars: {
-        appTitle: appService.getAppTitle(),
-        email,
-        url,
-      },
-    });
-  }
-
-  api.post = async function(req, res) {
-    const { email } = req.body;
-    const grobalLang = configManager.getConfig('crowi', 'app:globalLang');
-    const i18n = req.language || grobalLang;
-    const appUrl = appService.getSiteUrl();
-
-    try {
-      const passwordResetOrderData = await PasswordResetOrder.createPasswordResetOrder(email);
-      const url = new URL(`/forgot-password/${passwordResetOrderData.token}`, appUrl);
-      const oneTimeUrl = url.href;
-      await sendPasswordResetEmail(email, oneTimeUrl, i18n);
-      return res.json(ApiResponse.success());
-    }
-    catch (err) {
-      const msg = 'Error occurred during password reset request procedure';
-      logger.error(err);
-      return res.json(ApiResponse.error(msg));
-    }
-  };
-
   actions.error = function(req, res) {
   actions.error = function(req, res) {
     const { reason } = req.params;
     const { reason } = req.params;
 
 

+ 0 - 1
src/server/routes/index.js

@@ -178,7 +178,6 @@ module.exports = function(crowi, app) {
   app.post('/_api/hackmd.saveOnHackmd'   , accessTokenParser , loginRequiredStrictly , csrf, hackmd.validateForApi, hackmd.saveOnHackmd);
   app.post('/_api/hackmd.saveOnHackmd'   , accessTokenParser , loginRequiredStrictly , csrf, hackmd.validateForApi, hackmd.saveOnHackmd);
 
 
   app.get('/forgot-password', forgotPassword.forgotPassword);
   app.get('/forgot-password', forgotPassword.forgotPassword);
-  app.post('/_api/forgot-password', forgotPassword.api.post);
   app.get('/forgot-password/:token'      , passwordReset, forgotPassword.resetPassword);
   app.get('/forgot-password/:token'      , passwordReset, forgotPassword.resetPassword);
   app.get('/forgot-password/error/:reason'      , applicationInstalled, forgotPassword.error);
   app.get('/forgot-password/error/:reason'      , applicationInstalled, forgotPassword.error);