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

Merge pull request #6681 from weseek/feat/104986/render-reset-password-page

feat: Render reset password page
Shun Miyazawa 3 лет назад
Родитель
Сommit
170e285f4c

+ 8 - 7
packages/app/src/components/PasswordResetExecutionForm.jsx → packages/app/src/components/PasswordResetExecutionForm.tsx

@@ -1,6 +1,7 @@
-import React, { useState } from 'react';
+import React, { FC, useState } from 'react';
 
 import { useTranslation } from 'next-i18next';
+import Link from 'next/link';
 
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiv3Put } from '~/client/util/apiv3-client';
@@ -9,7 +10,7 @@ import loggerFactory from '~/utils/logger';
 const logger = loggerFactory('growi:passwordReset');
 
 
-const PasswordResetExecutionForm = (props) => {
+const PasswordResetExecutionForm: FC = () => {
   const { t } = useTranslation();
 
   const [newPassword, setNewPassword] = useState('');
@@ -79,14 +80,14 @@ const PasswordResetExecutionForm = (props) => {
       <div className="form-group">
         <input name="reset-password-btn" className="btn btn-lg btn-primary btn-block" value={t('forgot_password.reset_password')} type="submit" />
       </div>
-      <a href="/login">
-        <i className="icon-login mr-1"></i>{t('forgot_password.sign_in_instead')}
-      </a>
+      <Link href="/login" prefetch={false}>
+        <a>
+          <i className="icon-login mr-1"></i>{t('forgot_password.sign_in_instead')}
+        </a>
+      </Link>
     </form>
   );
 };
 
-PasswordResetExecutionForm.propTypes = {
-};
 
 export default PasswordResetExecutionForm;

+ 1 - 1
packages/app/src/pages/forgot-password-errors.page.tsx

@@ -37,7 +37,7 @@ const ForgotPasswordErrorsPage: NextPage<Props> = (props: Props) => {
                   <h3 className="text-muted">{ t('forgot_password.feature_is_unavailable') }</h3>
                 )}
 
-                { errorCode === (forgotPasswordErrorCode.PASSWORD_RESET_ORDER_IS_NOT_APPROPRIATE || forgotPasswordErrorCode.TOKEN_NOT_FOUND) && (
+                { (errorCode === forgotPasswordErrorCode.PASSWORD_RESET_ORDER_IS_NOT_APPROPRIATE || errorCode === forgotPasswordErrorCode.TOKEN_NOT_FOUND) && (
                   <div>
                     <div className="alert alert-warning mb-3">
                       <h2>{ t('forgot_password.incorrect_token_or_expired_url') }</h2>

+ 72 - 0
packages/app/src/pages/reset-password.page.tsx

@@ -0,0 +1,72 @@
+import React from 'react';
+
+import { NextPage, GetServerSideProps, GetServerSidePropsContext } from 'next';
+import { useTranslation } from 'next-i18next';
+import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
+import dynamic from 'next/dynamic';
+
+import {
+  CommonProps, getNextI18NextConfig, getServerSideCommonProps,
+} from './utils/commons';
+
+
+type Props = CommonProps & {
+  email: string
+};
+
+const PasswordResetExecutionForm = dynamic(() => import('~/components/PasswordResetExecutionForm'), { ssr: false });
+
+const ForgotPasswordPage: NextPage<Props> = (props: Props) => {
+  const { t } = useTranslation();
+
+  return (
+    <div id="main" className="main">
+      <div id="content-main" className="content-main container-lg">
+        <div className="container">
+          <div className="row justify-content-md-center">
+            <div className="col-md-6 mt-5">
+              <div className="text-center">
+                <h1><i className="icon-lock-open large"></i></h1>
+                <h2 className="text-center">{ t('forgot_password.reset_password') }</h2>
+                <h5>{ props.email }</h5>
+                <p className="mt-4">{ t('forgot_password.password_reset_excecution_desc') }</p>
+                <PasswordResetExecutionForm />
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+// eslint-disable-next-line max-len
+async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
+  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
+  props._nextI18Next = nextI18NextConfig._nextI18Next;
+}
+
+export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+  const result = await getServerSideCommonProps(context);
+
+  // check for presence
+  // see: https://github.com/vercel/next.js/issues/19271#issuecomment-730006862
+  if (!('props' in result)) {
+    throw new Error('invalid getSSP result');
+  }
+
+  const props: Props = result.props as Props;
+
+  const email = context.query.email;
+  if (typeof email === 'string') {
+    props.email = email;
+  }
+
+  await injectNextI18NextConfigurations(context, props, ['translation']);
+
+  return {
+    props,
+  };
+};
+
+export default ForgotPasswordPage;

+ 6 - 0
packages/app/src/server/middlewares/inject-reset-order-by-token-middleware.ts

@@ -2,9 +2,12 @@ import { NextFunction, Request, Response } from 'express';
 import createError from 'http-errors';
 
 import { forgotPasswordErrorCode } from '~/interfaces/errors/forgot-password';
+import loggerFactory from '~/utils/logger';
 
 import PasswordResetOrder, { IPasswordResetOrder } from '../models/password-reset-order';
 
+const logger = loggerFactory('growi:routes:forgot-password');
+
 export type ReqWithPasswordResetOrder = Request & {
   passwordResetOrder: IPasswordResetOrder,
 };
@@ -14,6 +17,7 @@ export default async(req: ReqWithPasswordResetOrder, res: Response, next: NextFu
   const token = req.params.token || req.body.token;
 
   if (token == null) {
+    logger.error('Token not found');
     return next(createError(400, 'Token not found', { code: forgotPasswordErrorCode.TOKEN_NOT_FOUND }));
   }
 
@@ -21,6 +25,8 @@ export default async(req: ReqWithPasswordResetOrder, res: Response, next: NextFu
 
   // check if the token is valid
   if (passwordResetOrder == null || passwordResetOrder.isExpired() || passwordResetOrder.isRevoked) {
+    const message = 'passwordResetOrder is null or expired or revoked';
+    logger.error(message);
     return next(createError(
       400,
       'passwordResetOrder is null or expired or revoked',

+ 19 - 10
packages/app/src/server/routes/forgot-password.ts

@@ -6,7 +6,7 @@ import createError from 'http-errors';
 import { forgotPasswordErrorCode } from '~/interfaces/errors/forgot-password';
 import loggerFactory from '~/utils/logger';
 
-import { ReqWithPasswordResetOrder } from '../middlewares/inject-reset-order-by-token-middleware';
+import { IPasswordResetOrder } from '../models/password-reset-order';
 
 const logger = loggerFactory('growi:routes:forgot-password');
 
@@ -33,15 +33,6 @@ export const checkForgotPasswordEnabledMiddlewareFactory = (crowi: any, forApi =
 
 };
 
-export const forgotPassword = (req: Request, res: Response): void => {
-  return res.render('forgot-password');
-};
-
-export const resetPassword = (req: ReqWithPasswordResetOrder, res: Response): void => {
-  const { passwordResetOrder } = req;
-  return res.render('reset-password', { email: passwordResetOrder.email });
-};
-
 type Crowi = {
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   nextApp: any,
@@ -51,6 +42,24 @@ type CrowiReq = Request & {
   crowi: Crowi,
 }
 
+export const renderForgotPassword = (crowi: Crowi) => {
+  return (req: CrowiReq, res: Response, next: NextFunction): void => {
+    const { nextApp } = crowi;
+    req.crowi = crowi;
+    nextApp.render(req, res, '/forgot-password');
+    return;
+  };
+};
+
+export const renderResetPassword = (crowi: Crowi) => {
+  return (req: CrowiReq & { passwordResetOrder: IPasswordResetOrder }, res: Response, next: NextFunction): void => {
+    const { nextApp } = crowi;
+    req.crowi = crowi;
+    nextApp.render(req, res, '/reset-password', { email: req.passwordResetOrder.email });
+    return;
+  };
+};
+
 // middleware to handle error
 export const handleErrorsMiddleware = (crowi: Crowi) => {
   return (error: Error & { code: string, statusCode: number }, req: CrowiReq, res: Response, next: NextFunction): void => {

+ 2 - 2
packages/app/src/server/routes/index.js

@@ -232,8 +232,8 @@ module.exports = function(crowi, app) {
 
   app.use('/forgot-password', express.Router()
     .use(forgotPassword.checkForgotPasswordEnabledMiddlewareFactory(crowi))
-    .get('/forgot-password', next.delegateToNext)
-    .get('/:token', injectResetOrderByTokenMiddleware, forgotPassword.resetPassword, next.delegateToNext) // TODO: 104986
+    .get('/', forgotPassword.renderForgotPassword(crowi))
+    .get('/:token', injectResetOrderByTokenMiddleware, forgotPassword.renderResetPassword(crowi))
     .use(forgotPassword.handleErrorsMiddleware(crowi)));
 
   app.get('/_private-legacy-pages', next.delegateToNext);