OidcSecuritySettingContents.tsx 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781
  1. import React, { useCallback, useEffect } from 'react';
  2. import { pathUtils } from '@growi/core/dist/utils';
  3. import { useTranslation } from 'next-i18next';
  4. import { useForm } from 'react-hook-form';
  5. import urljoin from 'url-join';
  6. import AdminGeneralSecurityContainer from '~/client/services/AdminGeneralSecurityContainer';
  7. import AdminOidcSecurityContainer from '~/client/services/AdminOidcSecurityContainer';
  8. import { toastError, toastSuccess } from '~/client/util/toastr';
  9. import { useSiteUrlWithEmptyValueWarn } from '~/states/global';
  10. import { withUnstatedContainers } from '../../UnstatedUtils';
  11. type Props = {
  12. adminGeneralSecurityContainer: AdminGeneralSecurityContainer;
  13. adminOidcSecurityContainer: AdminOidcSecurityContainer;
  14. };
  15. const OidcSecurityManagementContents = (props: Props) => {
  16. const { t } = useTranslation('admin');
  17. const siteUrl = useSiteUrlWithEmptyValueWarn();
  18. const { adminGeneralSecurityContainer, adminOidcSecurityContainer } = props;
  19. const { isOidcEnabled } = adminGeneralSecurityContainer.state;
  20. const {
  21. oidcProviderName,
  22. oidcIssuerHost,
  23. oidcClientId,
  24. oidcClientSecret,
  25. oidcAuthorizationEndpoint,
  26. oidcTokenEndpoint,
  27. oidcRevocationEndpoint,
  28. oidcIntrospectionEndpoint,
  29. oidcUserInfoEndpoint,
  30. oidcEndSessionEndpoint,
  31. oidcRegistrationEndpoint,
  32. oidcJWKSUri,
  33. oidcAttrMapId,
  34. oidcAttrMapUserName,
  35. oidcAttrMapName,
  36. oidcAttrMapEmail,
  37. } = adminOidcSecurityContainer.state;
  38. const oidcCallbackUrl = urljoin(
  39. pathUtils.removeTrailingSlash(siteUrl),
  40. '/passport/oidc/callback',
  41. );
  42. const { register, handleSubmit, reset } = useForm();
  43. useEffect(() => {
  44. reset({
  45. oidcProviderName,
  46. oidcIssuerHost,
  47. oidcClientId,
  48. oidcClientSecret,
  49. oidcAuthorizationEndpoint,
  50. oidcTokenEndpoint,
  51. oidcRevocationEndpoint,
  52. oidcIntrospectionEndpoint,
  53. oidcUserInfoEndpoint,
  54. oidcEndSessionEndpoint,
  55. oidcRegistrationEndpoint,
  56. oidcJWKSUri,
  57. oidcAttrMapId,
  58. oidcAttrMapUserName,
  59. oidcAttrMapName,
  60. oidcAttrMapEmail,
  61. });
  62. }, [
  63. reset,
  64. oidcProviderName,
  65. oidcIssuerHost,
  66. oidcClientId,
  67. oidcClientSecret,
  68. oidcAuthorizationEndpoint,
  69. oidcTokenEndpoint,
  70. oidcRevocationEndpoint,
  71. oidcIntrospectionEndpoint,
  72. oidcUserInfoEndpoint,
  73. oidcEndSessionEndpoint,
  74. oidcRegistrationEndpoint,
  75. oidcJWKSUri,
  76. oidcAttrMapId,
  77. oidcAttrMapUserName,
  78. oidcAttrMapName,
  79. oidcAttrMapEmail,
  80. ]);
  81. const onSubmit = useCallback(
  82. async (data) => {
  83. try {
  84. await adminOidcSecurityContainer.updateOidcSetting({
  85. oidcProviderName: data.oidcProviderName,
  86. oidcIssuerHost: data.oidcIssuerHost,
  87. oidcClientId: data.oidcClientId,
  88. oidcClientSecret: data.oidcClientSecret,
  89. oidcAuthorizationEndpoint: data.oidcAuthorizationEndpoint,
  90. oidcTokenEndpoint: data.oidcTokenEndpoint,
  91. oidcRevocationEndpoint: data.oidcRevocationEndpoint,
  92. oidcIntrospectionEndpoint: data.oidcIntrospectionEndpoint,
  93. oidcUserInfoEndpoint: data.oidcUserInfoEndpoint,
  94. oidcEndSessionEndpoint: data.oidcEndSessionEndpoint,
  95. oidcRegistrationEndpoint: data.oidcRegistrationEndpoint,
  96. oidcJWKSUri: data.oidcJWKSUri,
  97. oidcAttrMapId: data.oidcAttrMapId,
  98. oidcAttrMapUserName: data.oidcAttrMapUserName,
  99. oidcAttrMapName: data.oidcAttrMapName,
  100. oidcAttrMapEmail: data.oidcAttrMapEmail,
  101. isSameUsernameTreatedAsIdenticalUser:
  102. adminOidcSecurityContainer.state
  103. .isSameUsernameTreatedAsIdenticalUser,
  104. isSameEmailTreatedAsIdenticalUser:
  105. adminOidcSecurityContainer.state.isSameEmailTreatedAsIdenticalUser,
  106. });
  107. await adminGeneralSecurityContainer.retrieveSetupStratedies();
  108. toastSuccess(t('security_settings.OAuth.OIDC.updated_oidc'));
  109. } catch (err) {
  110. toastError(err);
  111. }
  112. },
  113. [t, adminOidcSecurityContainer, adminGeneralSecurityContainer],
  114. );
  115. return (
  116. <>
  117. <h2 className="alert-anchor border-bottom">
  118. {t('security_settings.OAuth.OIDC.name')}
  119. </h2>
  120. <div className="row my-4">
  121. <div className="offset-3 col-6">
  122. <div className="form-check form-switch form-check-success">
  123. <input
  124. id="isOidcEnabled"
  125. className="form-check-input"
  126. type="checkbox"
  127. checked={adminGeneralSecurityContainer.state.isOidcEnabled}
  128. onChange={() => {
  129. adminGeneralSecurityContainer.switchIsOidcEnabled();
  130. }}
  131. />
  132. <label
  133. className="form-label form-check-label"
  134. htmlFor="isOidcEnabled"
  135. >
  136. {t('security_settings.OAuth.enable_oidc')}
  137. </label>
  138. </div>
  139. {!adminGeneralSecurityContainer.state.setupStrategies.includes(
  140. 'oidc',
  141. ) &&
  142. isOidcEnabled && (
  143. <div className="badge text-bg-warning">
  144. {t('security_settings.setup_is_not_yet_complete')}
  145. </div>
  146. )}
  147. </div>
  148. </div>
  149. <div className="row mb-5">
  150. <label
  151. className="text-start text-md-end col-md-3 col-form-label"
  152. htmlFor="oidcCallbackUrl"
  153. >
  154. {t('security_settings.callback_URL')}
  155. </label>
  156. <div className="col-md-6">
  157. <input
  158. id="oidcCallbackUrl"
  159. className="form-control"
  160. type="text"
  161. value={oidcCallbackUrl}
  162. readOnly
  163. />
  164. <p className="form-text text-muted small">
  165. {t('security_settings.desc_of_callback_URL', { AuthName: 'OAuth' })}
  166. </p>
  167. {(siteUrl == null || siteUrl === '') && (
  168. <div className="alert alert-danger">
  169. <span className="material-symbols-outlined">error</span>
  170. <span
  171. // eslint-disable-next-line max-len
  172. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  173. dangerouslySetInnerHTML={{
  174. __html: t('alert.siteUrl_is_not_set', {
  175. link: `<a href="/admin/app">${t('headers.app_settings', { ns: 'commons' })}<span class="material-symbols-outlined">login</span></a>`,
  176. ns: 'commons',
  177. }),
  178. }}
  179. />
  180. </div>
  181. )}
  182. </div>
  183. </div>
  184. {isOidcEnabled && (
  185. <form onSubmit={handleSubmit(onSubmit)}>
  186. <h3 className="border-bottom mb-4">
  187. {t('security_settings.configuration')}
  188. </h3>
  189. <div className="row mb-4">
  190. <label
  191. htmlFor="oidcProviderName"
  192. className="text-start text-md-end col-md-3 col-form-label"
  193. >
  194. {t('security_settings.providerName')}
  195. </label>
  196. <div className="col-md-6">
  197. <input
  198. className="form-control"
  199. type="text"
  200. {...register('oidcProviderName')}
  201. />
  202. </div>
  203. </div>
  204. <div className="row mb-4">
  205. <label
  206. htmlFor="oidcIssuerHost"
  207. className="text-start text-md-end col-md-3 col-form-label"
  208. >
  209. {t('security_settings.issuerHost')}
  210. </label>
  211. <div className="col-md-6">
  212. <input
  213. className="form-control"
  214. type="text"
  215. {...register('oidcIssuerHost')}
  216. />
  217. <p className="form-text text-muted">
  218. <small
  219. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  220. dangerouslySetInnerHTML={{
  221. __html: t('security_settings.Use env var if empty', {
  222. env: 'OAUTH_OIDC_ISSUER_HOST',
  223. }),
  224. }}
  225. />
  226. </p>
  227. </div>
  228. </div>
  229. <div className="row mb-4">
  230. <label
  231. htmlFor="oidcClientId"
  232. className="text-start text-md-end col-md-3 col-form-label"
  233. >
  234. {t('security_settings.clientID')}
  235. </label>
  236. <div className="col-md-6">
  237. <input
  238. className="form-control"
  239. type="text"
  240. {...register('oidcClientId')}
  241. />
  242. <p className="form-text text-muted">
  243. <small
  244. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  245. dangerouslySetInnerHTML={{
  246. __html: t('security_settings.Use env var if empty', {
  247. env: 'OAUTH_OIDC_CLIENT_ID',
  248. }),
  249. }}
  250. />
  251. </p>
  252. </div>
  253. </div>
  254. <div className="row mb-4">
  255. <label
  256. htmlFor="oidcClientSecret"
  257. className="text-start text-md-end col-md-3 col-form-label"
  258. >
  259. {t('security_settings.client_secret')}
  260. </label>
  261. <div className="col-md-6">
  262. <input
  263. className="form-control"
  264. type="text"
  265. {...register('oidcClientSecret')}
  266. />
  267. <p className="form-text text-muted">
  268. <small
  269. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  270. dangerouslySetInnerHTML={{
  271. __html: t('security_settings.Use env var if empty', {
  272. env: 'OAUTH_OIDC_CLIENT_SECRET',
  273. }),
  274. }}
  275. />
  276. </p>
  277. </div>
  278. </div>
  279. <div className="row mb-4">
  280. <label
  281. htmlFor="oidcAuthorizationEndpoint"
  282. className="text-start text-md-end col-md-3 col-form-label"
  283. >
  284. {t('security_settings.authorization_endpoint')}
  285. </label>
  286. <div className="col-md-6">
  287. <input
  288. className="form-control"
  289. type="text"
  290. {...register('oidcAuthorizationEndpoint')}
  291. />
  292. <p className="form-text text-muted">
  293. <small
  294. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  295. dangerouslySetInnerHTML={{
  296. __html: t(
  297. 'security_settings.OAuth.OIDC.Use discovered URL if empty',
  298. ),
  299. }}
  300. />
  301. </p>
  302. </div>
  303. </div>
  304. <div className="row mb-4">
  305. <label
  306. htmlFor="oidcTokenEndpoint"
  307. className="text-start text-md-end col-md-3 col-form-label"
  308. >
  309. {t('security_settings.token_endpoint')}
  310. </label>
  311. <div className="col-md-6">
  312. <input
  313. className="form-control"
  314. type="text"
  315. {...register('oidcTokenEndpoint')}
  316. />
  317. <p className="form-text text-muted">
  318. <small
  319. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  320. dangerouslySetInnerHTML={{
  321. __html: t(
  322. 'security_settings.OAuth.OIDC.Use discovered URL if empty',
  323. ),
  324. }}
  325. />
  326. </p>
  327. </div>
  328. </div>
  329. <div className="row mb-4">
  330. <label
  331. htmlFor="oidcRevocationEndpoint"
  332. className="text-start text-md-end col-md-3 col-form-label"
  333. >
  334. {t('security_settings.revocation_endpoint')}
  335. </label>
  336. <div className="col-md-6">
  337. <input
  338. className="form-control"
  339. type="text"
  340. {...register('oidcRevocationEndpoint')}
  341. />
  342. <p className="form-text text-muted">
  343. <small
  344. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  345. dangerouslySetInnerHTML={{
  346. __html: t(
  347. 'security_settings.OAuth.OIDC.Use discovered URL if empty',
  348. ),
  349. }}
  350. />
  351. </p>
  352. </div>
  353. </div>
  354. <div className="row mb-4">
  355. <label
  356. htmlFor="oidcIntrospectionEndpoint"
  357. className="text-start text-md-end col-md-3 col-form-label"
  358. >
  359. {t('security_settings.introspection_endpoint')}
  360. </label>
  361. <div className="col-md-6">
  362. <input
  363. className="form-control"
  364. type="text"
  365. {...register('oidcIntrospectionEndpoint')}
  366. />
  367. <p className="form-text text-muted">
  368. <small
  369. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  370. dangerouslySetInnerHTML={{
  371. __html: t(
  372. 'security_settings.OAuth.OIDC.Use discovered URL if empty',
  373. ),
  374. }}
  375. />
  376. </p>
  377. </div>
  378. </div>
  379. <div className="row mb-4">
  380. <label
  381. htmlFor="oidcUserInfoEndpoint"
  382. className="text-start text-md-end col-md-3 col-form-label"
  383. >
  384. {t('security_settings.userinfo_endpoint')}
  385. </label>
  386. <div className="col-md-6">
  387. <input
  388. className="form-control"
  389. type="text"
  390. {...register('oidcUserInfoEndpoint')}
  391. />
  392. <p className="form-text text-muted">
  393. <small
  394. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  395. dangerouslySetInnerHTML={{
  396. __html: t(
  397. 'security_settings.OAuth.OIDC.Use discovered URL if empty',
  398. ),
  399. }}
  400. />
  401. </p>
  402. </div>
  403. </div>
  404. <div className="row mb-4">
  405. <label
  406. htmlFor="oidcEndSessionEndpoint"
  407. className="text-start text-md-end col-md-3 col-form-label"
  408. >
  409. {t('security_settings.end_session_endpoint')}
  410. </label>
  411. <div className="col-md-6">
  412. <input
  413. className="form-control"
  414. type="text"
  415. {...register('oidcEndSessionEndpoint')}
  416. />
  417. <p className="form-text text-muted">
  418. <small
  419. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  420. dangerouslySetInnerHTML={{
  421. __html: t(
  422. 'security_settings.OAuth.OIDC.Use discovered URL if empty',
  423. ),
  424. }}
  425. />
  426. </p>
  427. </div>
  428. </div>
  429. <div className="row mb-4">
  430. <label
  431. htmlFor="oidcRegistrationEndpoint"
  432. className="text-start text-md-end col-md-3 col-form-label"
  433. >
  434. {t('security_settings.registration_endpoint')}
  435. </label>
  436. <div className="col-md-6">
  437. <input
  438. className="form-control"
  439. type="text"
  440. {...register('oidcRegistrationEndpoint')}
  441. />
  442. <p className="form-text text-muted">
  443. <small
  444. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  445. dangerouslySetInnerHTML={{
  446. __html: t(
  447. 'security_settings.OAuth.OIDC.Use discovered URL if empty',
  448. ),
  449. }}
  450. />
  451. </p>
  452. </div>
  453. </div>
  454. <div className="row mb-4">
  455. <label
  456. htmlFor="oidcJWKSUri"
  457. className="text-start text-md-end col-md-3 col-form-label"
  458. >
  459. {t('security_settings.jwks_uri')}
  460. </label>
  461. <div className="col-md-6">
  462. <input
  463. className="form-control"
  464. type="text"
  465. {...register('oidcJWKSUri')}
  466. />
  467. <p className="form-text text-muted">
  468. <small
  469. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  470. dangerouslySetInnerHTML={{
  471. __html: t(
  472. 'security_settings.OAuth.OIDC.Use discovered URL if empty',
  473. ),
  474. }}
  475. />
  476. </p>
  477. </div>
  478. </div>
  479. <h3 className="alert-anchor border-bottom mb-4">
  480. Attribute Mapping ({t('optional')})
  481. </h3>
  482. <div className="row mb-4">
  483. <label
  484. htmlFor="oidcAttrMapId"
  485. className="text-start text-md-end col-md-3 col-form-label"
  486. >
  487. Identifier
  488. </label>
  489. <div className="col-md-6">
  490. <input
  491. className="form-control"
  492. type="text"
  493. {...register('oidcAttrMapId')}
  494. />
  495. <p className="form-text text-muted">
  496. <small
  497. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  498. dangerouslySetInnerHTML={{
  499. __html: t('security_settings.OAuth.OIDC.id_detail'),
  500. }}
  501. />
  502. </p>
  503. </div>
  504. </div>
  505. <div className="row mb-4">
  506. <label
  507. htmlFor="oidcAttrMapUserName"
  508. className="text-start text-md-end col-md-3 col-form-label"
  509. >
  510. {t('username')}
  511. </label>
  512. <div className="col-md-6">
  513. <input
  514. className="form-control"
  515. type="text"
  516. {...register('oidcAttrMapUserName')}
  517. />
  518. <p className="form-text text-muted">
  519. <small
  520. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  521. dangerouslySetInnerHTML={{
  522. __html: t('security_settings.OAuth.OIDC.username_detail'),
  523. }}
  524. />
  525. </p>
  526. </div>
  527. </div>
  528. <div className="row mb-4">
  529. <label
  530. htmlFor="oidcAttrMapName"
  531. className="text-start text-md-end col-md-3 col-form-label"
  532. >
  533. {t('Name')}
  534. </label>
  535. <div className="col-md-6">
  536. <input
  537. className="form-control"
  538. type="text"
  539. {...register('oidcAttrMapName')}
  540. />
  541. <p className="form-text text-muted">
  542. <small
  543. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  544. dangerouslySetInnerHTML={{
  545. __html: t('security_settings.OAuth.OIDC.name_detail'),
  546. }}
  547. />
  548. </p>
  549. </div>
  550. </div>
  551. <div className="row mb-4">
  552. <label
  553. htmlFor="oidcAttrMapEmail"
  554. className="text-start text-md-end col-md-3 col-form-label"
  555. >
  556. {t('Email')}
  557. </label>
  558. <div className="col-md-6">
  559. <input
  560. className="form-control"
  561. type="text"
  562. {...register('oidcAttrMapEmail')}
  563. />
  564. <p className="form-text text-muted">
  565. <small
  566. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  567. dangerouslySetInnerHTML={{
  568. __html: t('security_settings.OAuth.OIDC.mapping_detail', {
  569. target: t('Email'),
  570. }),
  571. }}
  572. />
  573. </p>
  574. </div>
  575. </div>
  576. <div className="row mb-4">
  577. <label
  578. className="form-label text-start text-md-end col-md-3 col-form-label"
  579. htmlFor="oidcCallbackUrlPreview"
  580. >
  581. {t('security_settings.callback_URL')}
  582. </label>
  583. <div className="col-md-6">
  584. <input
  585. id="oidcCallbackUrlPreview"
  586. className="form-control"
  587. type="text"
  588. defaultValue={oidcCallbackUrl}
  589. readOnly
  590. />
  591. <p className="form-text text-muted small">
  592. {t('security_settings.desc_of_callback_URL', {
  593. AuthName: 'OAuth',
  594. })}
  595. </p>
  596. {(siteUrl == null || siteUrl === '') && (
  597. <div className="alert alert-danger">
  598. <span className="material-symbols-outlined">error</span>
  599. <span
  600. // eslint-disable-next-line max-len
  601. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  602. dangerouslySetInnerHTML={{
  603. __html: t('alert.siteUrl_is_not_set', {
  604. link: `<a href="/admin/app">${t('headers.app_settings', { ns: 'commons' })}<span class="material-symbols-outlined">login</span></a>`,
  605. ns: 'commons',
  606. }),
  607. }}
  608. />
  609. </div>
  610. )}
  611. </div>
  612. </div>
  613. <div className="row mb-4">
  614. <div className="offset-md-3 col-md-6">
  615. <div className="form-check form-check-success">
  616. <input
  617. id="bindByUserName-oidc"
  618. className="form-check-input"
  619. type="checkbox"
  620. checked={
  621. adminOidcSecurityContainer.state
  622. .isSameUsernameTreatedAsIdenticalUser
  623. }
  624. onChange={() => {
  625. adminOidcSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser();
  626. }}
  627. />
  628. <label
  629. className="form-label form-check-label"
  630. htmlFor="bindByUserName-oidc"
  631. >
  632. <span
  633. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  634. dangerouslySetInnerHTML={{
  635. __html: t(
  636. 'security_settings.Treat username matching as identical',
  637. ),
  638. }}
  639. />
  640. </label>
  641. </div>
  642. <p className="form-text text-muted">
  643. <small
  644. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  645. dangerouslySetInnerHTML={{
  646. __html: t(
  647. 'security_settings.Treat username matching as identical_warn',
  648. ),
  649. }}
  650. />
  651. </p>
  652. </div>
  653. </div>
  654. <div className="row mb-4">
  655. <div className="offset-md-3 col-md-6">
  656. <div className="form-check form-check-success">
  657. <input
  658. id="bindByEmail-oidc"
  659. className="form-check-input"
  660. type="checkbox"
  661. checked={
  662. adminOidcSecurityContainer.state
  663. .isSameEmailTreatedAsIdenticalUser || false
  664. }
  665. onChange={() => {
  666. adminOidcSecurityContainer.switchIsSameEmailTreatedAsIdenticalUser();
  667. }}
  668. />
  669. <label
  670. className="form-label form-check-label"
  671. htmlFor="bindByEmail-oidc"
  672. >
  673. <span
  674. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  675. dangerouslySetInnerHTML={{
  676. __html: t(
  677. 'security_settings.Treat email matching as identical',
  678. ),
  679. }}
  680. />
  681. </label>
  682. </div>
  683. <p className="form-text text-muted">
  684. <small
  685. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  686. dangerouslySetInnerHTML={{
  687. __html: t(
  688. 'security_settings.Treat email matching as identical_warn',
  689. ),
  690. }}
  691. />
  692. </p>
  693. </div>
  694. </div>
  695. <div className="row my-3">
  696. <div className="offset-3 col-5">
  697. <button
  698. type="submit"
  699. className="btn btn-primary"
  700. disabled={
  701. adminOidcSecurityContainer.state.retrieveError != null
  702. }
  703. >
  704. {t('Update')}
  705. </button>
  706. </div>
  707. </div>
  708. </form>
  709. )}
  710. <hr />
  711. <div style={{ minHeight: '300px' }}>
  712. <h4>
  713. <span className="material-symbols-outlined" aria-hidden="true">
  714. help
  715. </span>
  716. <a href="#collapseHelpForOidcOauth" data-bs-toggle="collapse">
  717. {' '}
  718. {t('security_settings.OAuth.how_to.oidc')}
  719. </a>
  720. </h4>
  721. <div className=" card custom-card bg-body-tertiary">
  722. <ol id="collapseHelpForOidcOauth" className="collapse mb-0">
  723. <li>{t('security_settings.OAuth.OIDC.register_1')}</li>
  724. <li
  725. // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
  726. dangerouslySetInnerHTML={{
  727. __html: t('security_settings.OAuth.OIDC.register_2', {
  728. url: oidcCallbackUrl,
  729. }),
  730. }}
  731. />
  732. <li>{t('security_settings.OAuth.OIDC.register_3')}</li>
  733. </ol>
  734. </div>
  735. </div>
  736. </>
  737. );
  738. };
  739. const OidcSecurityManagementContentsWrapper = withUnstatedContainers(
  740. OidcSecurityManagementContents,
  741. [AdminGeneralSecurityContainer, AdminOidcSecurityContainer],
  742. );
  743. export default OidcSecurityManagementContentsWrapper;