LocalSecuritySettingContents.tsx 11 KB

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