LocalSecuritySettingContents.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. import React, { useCallback, useEffect } from 'react';
  2. import { useTranslation } from 'next-i18next';
  3. import Link from 'next/link';
  4. import { useForm } from 'react-hook-form';
  5. import AdminGeneralSecurityContainer from '~/client/services/AdminGeneralSecurityContainer';
  6. import AdminLocalSecurityContainer from '~/client/services/AdminLocalSecurityContainer';
  7. import { toastSuccess, toastError } from '~/client/util/toastr';
  8. import { useIsMailerSetup } from '~/stores-universal/context';
  9. import { withUnstatedContainers } from '../../UnstatedUtils';
  10. type Props = {
  11. adminGeneralSecurityContainer: AdminGeneralSecurityContainer;
  12. adminLocalSecurityContainer: AdminLocalSecurityContainer;
  13. };
  14. const LocalSecuritySettingContents = (props: Props): JSX.Element => {
  15. const {
  16. adminGeneralSecurityContainer,
  17. adminLocalSecurityContainer,
  18. } = props;
  19. const { t } = useTranslation('admin');
  20. const { data: isMailerSetup = false } = useIsMailerSetup();
  21. const { register, handleSubmit, reset } = useForm();
  22. const { registrationMode, isPasswordResetEnabled, isEmailAuthenticationEnabled } = adminLocalSecurityContainer.state;
  23. const { isLocalEnabled } = adminGeneralSecurityContainer.state;
  24. useEffect(() => {
  25. reset({
  26. registrationWhitelist: adminLocalSecurityContainer.state.registrationWhitelist.join('\n'),
  27. });
  28. }, [reset, adminLocalSecurityContainer.state.registrationWhitelist]);
  29. const onSubmit = useCallback(async(data) => {
  30. try {
  31. await adminLocalSecurityContainer.updateLocalSecuritySetting({
  32. registrationMode: adminLocalSecurityContainer.state.registrationMode,
  33. registrationWhitelist: data.registrationWhitelist.split('\n'),
  34. isPasswordResetEnabled: adminLocalSecurityContainer.state.isPasswordResetEnabled,
  35. isEmailAuthenticationEnabled: adminLocalSecurityContainer.state.isEmailAuthenticationEnabled,
  36. });
  37. await adminGeneralSecurityContainer.retrieveSetupStratedies();
  38. toastSuccess(t('security_settings.updated_general_security_setting'));
  39. }
  40. catch (err) {
  41. toastError(err);
  42. }
  43. }, [t, adminGeneralSecurityContainer, adminLocalSecurityContainer]);
  44. return (
  45. <>
  46. {adminLocalSecurityContainer.state.retrieveError != null && (
  47. <div className="alert alert-danger">
  48. <p>
  49. {t('Error occurred')} : {adminLocalSecurityContainer.state.retrieveError}
  50. </p>
  51. </div>
  52. )}
  53. <h2 className="alert-anchor border-bottom">{t('security_settings.Local.name')}</h2>
  54. {adminLocalSecurityContainer.state.useOnlyEnvVars && (
  55. <p
  56. className="alert alert-info"
  57. // eslint-disable-next-line max-len
  58. dangerouslySetInnerHTML={{
  59. __html: t('security_settings.Local.note for the only env option', { env: 'LOCAL_STRATEGY_USES_ONLY_ENV_VARS_FOR_SOME_OPTIONS' }),
  60. }}
  61. />
  62. )}
  63. <div className="row mt-4 mb-5">
  64. <div className="col-6 offset-3">
  65. <div className="form-check form-switch form-check-success">
  66. <input
  67. type="checkbox"
  68. className="form-check-input"
  69. id="isLocalEnabled"
  70. checked={isLocalEnabled}
  71. onChange={() => adminGeneralSecurityContainer.switchIsLocalEnabled()}
  72. disabled={adminLocalSecurityContainer.state.useOnlyEnvVars}
  73. />
  74. <label className="form-label form-check-label" htmlFor="isLocalEnabled">
  75. {t('security_settings.Local.enable_local')}
  76. </label>
  77. </div>
  78. {!adminGeneralSecurityContainer.state.setupStrategies.includes('local') && isLocalEnabled && (
  79. <div className="badge bg-warning text-dark">{t('security_settings.setup_is_not_yet_complete')}</div>
  80. )}
  81. </div>
  82. </div>
  83. {isLocalEnabled && (
  84. <form onSubmit={handleSubmit(onSubmit)}>
  85. <h3 className="border-bottom">{t('security_settings.configuration')}</h3>
  86. <div className="row">
  87. <div className="col-12 col-md-4 text-start text-md-end py-2">
  88. <strong>{t('security_settings.register_limitation')}</strong>
  89. </div>
  90. <div className="col-12 col-md-8">
  91. <div className="dropdown">
  92. <button
  93. className="btn btn-outline-secondary dropdown-toggle"
  94. type="button"
  95. id="dropdownMenuButton"
  96. data-bs-toggle="dropdown"
  97. aria-haspopup="true"
  98. aria-expanded="true"
  99. >
  100. {registrationMode === 'Open' && t('security_settings.registration_mode.open')}
  101. {registrationMode === 'Restricted' && t('security_settings.registration_mode.restricted')}
  102. {registrationMode === 'Closed' && t('security_settings.registration_mode.closed')}
  103. </button>
  104. <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
  105. <button
  106. className="dropdown-item"
  107. type="button"
  108. onClick={() => {
  109. adminLocalSecurityContainer.changeRegistrationMode('Open');
  110. }}
  111. >
  112. {t('security_settings.registration_mode.open')}
  113. </button>
  114. <button
  115. className="dropdown-item"
  116. type="button"
  117. onClick={() => {
  118. adminLocalSecurityContainer.changeRegistrationMode('Restricted');
  119. }}
  120. >
  121. {t('security_settings.registration_mode.restricted')}
  122. </button>
  123. <button
  124. className="dropdown-item"
  125. type="button"
  126. onClick={() => {
  127. adminLocalSecurityContainer.changeRegistrationMode('Closed');
  128. }}
  129. >
  130. {t('security_settings.registration_mode.closed')}
  131. </button>
  132. </div>
  133. </div>
  134. <p className="form-text text-muted small">{t('security_settings.register_limitation_desc')}</p>
  135. </div>
  136. </div>
  137. <div className="row">
  138. <div className="col-12 col-md-4 text-start text-md-end">
  139. <strong dangerouslySetInnerHTML={{ __html: t('security_settings.The whitelist of registration permission E-mail address') }} />
  140. </div>
  141. <div className="col-12 col-md-8">
  142. <textarea
  143. className="form-control"
  144. {...register('registrationWhitelist')}
  145. />
  146. <p className="form-text text-muted small">
  147. {t('security_settings.restrict_emails')}
  148. <br />
  149. {t('security_settings.for_example')}
  150. <code>@growi.org</code>
  151. {t('security_settings.in_this_case')}
  152. <br />
  153. {t('security_settings.insert_single')}
  154. </p>
  155. </div>
  156. </div>
  157. <div className="row">
  158. <label className="col-12 col-md-4 text-start text-md-end col-form-label">{t('security_settings.Local.password_reset_by_users')}</label>
  159. <div className="col-12 col-md-8">
  160. <div className="form-check form-switch form-check-success">
  161. <input
  162. type="checkbox"
  163. className="form-check-input"
  164. id="isPasswordResetEnabled"
  165. checked={isPasswordResetEnabled}
  166. onChange={() => adminLocalSecurityContainer.switchIsPasswordResetEnabled()}
  167. />
  168. <label className="form-label form-check-label" htmlFor="isPasswordResetEnabled">
  169. {t('security_settings.Local.enable_password_reset_by_users')}
  170. </label>
  171. </div>
  172. {!isMailerSetup && (
  173. <div className="alert alert-warning p-2 my-1 small d-inline-block">
  174. <span>{t('commons:alert.password_reset_please_enable_mailer')}</span>
  175. <Link href="/admin/app#mail-settings">
  176. <span className="material-symbols-outlined">link</span> {t('app_setting.mail_settings')}
  177. </Link>
  178. </div>
  179. )}
  180. <p className="form-text text-muted small">
  181. {t('security_settings.Local.password_reset_desc')}
  182. </p>
  183. </div>
  184. </div>
  185. <div className="row">
  186. <label className="col-12 col-md-4 text-start text-md-end col-form-label">{t('security_settings.Local.email_authentication')}</label>
  187. <div className="col-12 col-md-8">
  188. <div className="form-check form-switch form-check-success">
  189. <input
  190. type="checkbox"
  191. className="form-check-input"
  192. id="isEmailAuthenticationEnabled"
  193. checked={isEmailAuthenticationEnabled}
  194. onChange={() => adminLocalSecurityContainer.switchIsEmailAuthenticationEnabled()}
  195. />
  196. <label className="form-label form-check-label" htmlFor="isEmailAuthenticationEnabled">
  197. {t('security_settings.Local.enable_email_authentication')}
  198. </label>
  199. </div>
  200. {!isMailerSetup && (
  201. <div className="alert alert-warning p-2 my-1 small d-inline-block">
  202. <span>{t('commons:alert.please_enable_mailer')}</span>
  203. <Link href="/admin/app#mail-settings">
  204. <span className="material-symbols-outlined">link</span> {t('app_setting.mail_settings')}
  205. </Link>
  206. </div>
  207. )}
  208. <p className="form-text text-muted small">
  209. {t('security_settings.Local.enable_email_authentication_desc')}
  210. </p>
  211. </div>
  212. </div>
  213. <div className="row my-3">
  214. <div className="offset-3 col-6">
  215. <button
  216. type="submit"
  217. className="btn btn-primary"
  218. disabled={adminLocalSecurityContainer.state.retrieveError != null}
  219. >
  220. {t('Update')}
  221. </button>
  222. </div>
  223. </div>
  224. </form>
  225. )}
  226. </>
  227. );
  228. };
  229. const LocalSecuritySettingContentsWrapper = withUnstatedContainers(LocalSecuritySettingContents, [
  230. AdminGeneralSecurityContainer,
  231. AdminLocalSecurityContainer,
  232. ]);
  233. export default LocalSecuritySettingContentsWrapper;