Parcourir la source

Merge pull request #7145 from weseek/fix/111880-internal-server-err-when-pending-user-login

fix: Internal Server Err when approval pending  and suspended user login
Kaori Tokashiki il y a 3 ans
Parent
commit
ed92474714

+ 2 - 2
packages/app/public/static/locales/en_US/translation.json

@@ -605,8 +605,8 @@
     }
   },
   "login": {
-    "Sign in error": "Login error",
-    "Registration successful": "Registration successful. Please wait for administrator approval.",
+    "sign_in_error": "Login error",
+    "registration_successful": "registration_successful. Please wait for administrator approval.",
     "Setup": "Setup",
     "enabled_ldap_has_configuration_problem":"LDAP is enabled but the configuration has something wrong.",
     "set_env_var_for_logs": "(Please set the environment variables <code>DEBUG=crowi:service:PassportService</code> to get the logs)"

+ 2 - 2
packages/app/public/static/locales/ja_JP/translation.json

@@ -604,8 +604,8 @@
     }
   },
   "login": {
-    "Sign in error": "ログインエラー",
-    "Registration successful": "登録が完了しました。管理者の承認をお待ちください。",
+    "sign_in_error": "ログインエラー",
+    "registration_successful": "登録が完了しました。管理者の承認をお待ちください。",
     "Setup": "セットアップ",
     "enabled_ldap_has_configuration_problem":"LDAPは有効ですが、設定に問題があります。",
     "set_env_var_for_logs": "(ログを取得するためには、環境変数 <code>DEBUG=crowi:service:PassportService</code> を設定してください。)"

+ 2 - 2
packages/app/public/static/locales/zh_CN/translation.json

@@ -609,8 +609,8 @@
     }
   },
 	"login": {
-		"Sign in error": "登录错误",
-		"Registration successful": "注册成功。请等待管理员批准",
+		"sign_in_error": "登录错误",
+		"registration_successful": "注册成功。请等待管理员批准",
 		"Setup": "安装程序",
     "enabled_ldap_has_configuration_problem":"启用了LDAP,但配置有问题。",
     "set_env_var_for_logs": "(请设置环境变量 <code>DEBUG=crowi:service:PassportService</code> 以获得日志。)"

+ 1 - 1
packages/app/src/components/CompleteUserRegistration.tsx

@@ -11,7 +11,7 @@ export const CompleteUserRegistration: FC = () => {
       <div className="row mx-0">
         <div className="col-12 mb-3 text-center">
           <p className="alert alert-success">
-            <span>{t('login.Registration successful')}</span>
+            <span>{t('login.registration_successful')}</span>
           </p>
           {/* If the transition source is "/login", use <a /> tag since the transition will not occur if next/link is used. */}
           <a href='/login'>

+ 12 - 2
packages/app/src/components/LoginForm.tsx

@@ -2,6 +2,7 @@ import React, {
   useState, useEffect, useCallback,
 } from 'react';
 
+import { USER_STATUS } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useRouter } from 'next/router';
 import ReactCardFlip from 'react-card-flip';
@@ -90,8 +91,17 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
 
     try {
       const res = await apiv3Post('/login', { loginForm });
-      const { redirectTo } = res.data;
-      router.push(redirectTo ?? '/');
+      const { redirectTo, userStatus } = res.data;
+
+      if (redirectTo != null) {
+        return router.push(redirectTo);
+      }
+
+      if (userStatus !== USER_STATUS.ACTIVE) {
+        window.location.href = '/';
+      }
+
+      return router.push('/');
     }
     catch (err) {
       const errs = toArrayIfNot(err);

+ 133 - 0
packages/app/src/pages/login/error/[message].page.tsx

@@ -0,0 +1,133 @@
+import React from 'react';
+
+import {
+  NextPage, GetServerSideProps, GetServerSidePropsContext,
+} from 'next';
+import { useTranslation } from 'next-i18next';
+import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
+import { useRouter } from 'next/router';
+
+import { NoLoginLayout } from '~/components/Layout/NoLoginLayout';
+import {
+  CommonProps, getServerSideCommonProps, getNextI18NextConfig,
+} from '~/pages/utils/commons';
+
+
+type Props = CommonProps;
+const classNames: string[] = ['login-page'];
+
+const LoginPage: NextPage<CommonProps> = () => {
+
+  const { t } = useTranslation();
+  const router = useRouter();
+  const { message } = router.query;
+
+
+  let loginErrorElm;
+
+  const ApprovalPendingUserError = () => {
+    return (
+      <>
+        <div className="alert alert-warning">
+          <h2>{ t('login.sign_in_error') }</h2>
+        </div>
+        <p>Wait for approved by administrators.</p>
+      </>
+    );
+  };
+
+  const SuspendedUserError = () => {
+    return (
+      <>
+        <div className="alert alert-warning">
+          <h2>{ t('login.sign_in_error') }</h2>
+        </div>
+        <p>This account is suspended.</p>
+      </>
+    );
+  };
+
+  const PasswordResetOrderError = () => {
+    return (
+      <>
+        <div className="alert alert-warning mb-3">
+          <h2>{ t('forgot_password.incorrect_token_or_expired_url') }</h2>
+        </div>
+        <a href="/forgot-password" className="link-switch">
+          <i className="icon-key"></i> { t('forgot_password.forgot_password') }
+        </a>
+      </>
+    );
+  };
+
+  const DefaultLoginError = () => {
+    return (
+      <div className="alert alert-warning">
+        <h2>{ t('login.sign_in_error') }</h2>
+      </div>
+    );
+  };
+
+  switch (message) {
+    case 'registered':
+      loginErrorElm = <ApprovalPendingUserError />;
+      break;
+    case 'suspended':
+      loginErrorElm = <SuspendedUserError />;
+      break;
+    case 'password-reset-order':
+      loginErrorElm = <PasswordResetOrderError />;
+      break;
+    default:
+      loginErrorElm = <DefaultLoginError />;
+  }
+
+
+  return (
+    <NoLoginLayout className={classNames.join(' ')}>
+      <div className="mb-4 login-form-errors text-center">
+        <div className='noLogin-dialog pb-4 mx-auto'>
+          <div className="col-12">
+            {loginErrorElm}
+          </div>
+          {/* If the transition source is "/login", use <a /> tag since the transition will not occur if next/link is used. */}
+          <a href='/login'>
+            <i className="icon-login mr-1" />{t('Sign in is here')}
+          </a>
+        </div>
+      </div>
+    </NoLoginLayout>
+  );
+};
+
+/**
+ * for Server Side Translations
+ * @param context
+ * @param props
+ * @param namespacesRequired
+ */
+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;
+
+  await injectNextI18NextConfigurations(context, props, ['translation']);
+
+  return {
+    props,
+  };
+};
+
+export default LoginPage;

+ 4 - 5
packages/app/src/pages/login.page.tsx → packages/app/src/pages/login/index.page.tsx

@@ -11,15 +11,14 @@ import { LoginForm } from '~/components/LoginForm';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import { IExternalAccountLoginError, isExternalAccountLoginError } from '~/interfaces/errors/external-account-login-error';
 import type { RegistrationMode } from '~/interfaces/registration-mode';
-
+import {
+  CommonProps, getServerSideCommonProps, generateCustomTitle, getNextI18NextConfig,
+} from '~/pages/utils/commons';
 import {
   useCsrfToken,
   useCurrentPathname,
-} from '../stores/context';
+} from '~/stores/context';
 
-import {
-  CommonProps, getServerSideCommonProps, generateCustomTitle, getNextI18NextConfig,
-} from './utils/commons';
 
 type Props = CommonProps & {
   registrationMode: RegistrationMode,

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

@@ -76,7 +76,7 @@ module.exports = function(crowi, app) {
 
   app.get('/'                         , applicationInstalled, unavailableWhenMaintenanceMode, loginRequired, autoReconnectToSearch, next.delegateToNext);
 
-  app.get('/login/error/:reason'      , applicationInstalled, login.error);
+  app.get('/login/error/:reason'      , applicationInstalled, next.delegateToNext);
   app.get('/login'                    , applicationInstalled, login.preLogin, next.delegateToNext);
   app.get('/invited'                  , applicationInstalled, next.delegateToNext);
   // app.post('/login'                   , applicationInstalled, loginFormValidator.loginRules(), loginFormValidator.loginValidation, csrfProtection,  addActivity, loginPassport.loginWithLocal, loginPassport.loginWithLdap, loginPassport.loginFailure);

+ 1 - 1
packages/app/src/server/routes/login-passport.js

@@ -130,7 +130,7 @@ module.exports = function(crowi, app) {
     // remove session.redirectTo
     delete req.session.redirectTo;
 
-    return res.apiv3({ redirectTo });
+    return res.apiv3({ redirectTo, userStatus: req.user.status });
   };
 
   const cannotLoginErrorHadnler = (req, res, next) => {

+ 0 - 18
packages/app/src/server/routes/login.js

@@ -82,24 +82,6 @@ module.exports = function(crowi, app) {
     });
   };
 
-  actions.error = function(req, res) {
-    const reason = req.params.reason;
-
-
-    let reasonMessage = '';
-    if (reason === 'suspended') {
-      reasonMessage = 'This account is suspended.';
-    }
-    else if (reason === 'registered') {
-      reasonMessage = 'Wait for approved by administrators.';
-    }
-
-    return res.render('login/error', {
-      reason,
-      reasonMessage,
-    });
-  };
-
   actions.preLogin = function(req, res, next) {
     // user has already logged in
     const { user } = req;

+ 0 - 62
packages/app/src/server/views/login/error.html

@@ -1,62 +0,0 @@
-{% extends '../layout/layout.html' %}
-
-{% block html_base_css %}error nologin{% endblock %}
-
-{% block html_title %}{{ customizeService.generateCustomTitleForFixedPageName(t('login.Setup')) }}{% endblock %}
-
-
-
-{#
- # Remove default contents
- #}
-{% block html_head_loading_legacy %}
-{% endblock %}
-{% block html_head_loading_app %}
-{% endblock %}
-{% block layout_head_nav %}
-{% endblock %}
-{% block sidebar %}
-{% endblock %}
-{% block head_warn_alert_siteurl_undefined %}
-{% endblock %}
-
-
-
-{% block layout_main %}
-
-<div class="main container-fluid">
-
-  <div class="row">
-
-    <div class="login-header offset-sm-4 col-sm-4">
-      <div class="logo">{% include '../widget/logo.html' %}</div>
-      <h1>GROWI</h1>
-
-      <div class="mb-4 login-form-errors text-center">
-        {% if reason === 'registered'%}
-        <div class="alert alert-success">
-          <h2>{{ t('login.Registration successful') }}</h2>
-        </div>
-        {% elseif reason === 'password-reset-order' %}
-        <div class="alert alert-warning mb-3">
-          <h2>{{ t('forgot_password.incorrect_token_or_expired_url') }}</h2>
-        </div>
-          <a href="/forgot-password" class="link-switch">
-            <i class="icon-key"></i> {{ t('forgot_password.forgot_password') }}
-          </a>
-        {% else %}
-        <div class="alert alert-warning">
-            <h2>{{ t('login.Sign in error') }}</h2>
-        </div>
-        {% endif %}
-      </div>
-
-      <p>{{ reasonMessage }}</p>
-    </div>
-
-
-  </div>{# /.row #}
-
-</div>{# /.main #}
-
-{% endblock %}