| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- import React, { useCallback, useEffect } from 'react';
- import { pathUtils } from '@growi/core/dist/utils';
- import { useTranslation } from 'next-i18next';
- import { useForm } from 'react-hook-form';
- import urljoin from 'url-join';
- import AdminGeneralSecurityContainer from '~/client/services/AdminGeneralSecurityContainer';
- import AdminGoogleSecurityContainer from '~/client/services/AdminGoogleSecurityContainer';
- import { toastError, toastSuccess } from '~/client/util/toastr';
- import { useSiteUrlWithEmptyValueWarn } from '~/states/global';
- import { withUnstatedContainers } from '../../UnstatedUtils';
- type Props = {
- adminGeneralSecurityContainer: AdminGeneralSecurityContainer;
- adminGoogleSecurityContainer: AdminGoogleSecurityContainer;
- };
- const GoogleSecurityManagementContents = (props: Props) => {
- const { adminGeneralSecurityContainer, adminGoogleSecurityContainer } = props;
- const { t } = useTranslation('admin');
- const siteUrl = useSiteUrlWithEmptyValueWarn();
- const { isGoogleEnabled } = adminGeneralSecurityContainer.state;
- const { googleClientId, googleClientSecret, retrieveError } =
- adminGoogleSecurityContainer.state;
- const googleCallbackUrl = urljoin(
- pathUtils.removeTrailingSlash(siteUrl),
- '/passport/google/callback',
- );
- const { register, handleSubmit, reset } = useForm();
- // Sync form with container state
- useEffect(() => {
- reset({
- googleClientId,
- googleClientSecret,
- });
- }, [reset, googleClientId, googleClientSecret]);
- const onClickSubmit = useCallback(
- async (data) => {
- try {
- await adminGoogleSecurityContainer.updateGoogleSetting({
- googleClientId: data.googleClientId ?? '',
- googleClientSecret: data.googleClientSecret ?? '',
- isSameEmailTreatedAsIdenticalUser:
- adminGoogleSecurityContainer.state
- .isSameEmailTreatedAsIdenticalUser,
- });
- await adminGeneralSecurityContainer.retrieveSetupStratedies();
- toastSuccess(t('security_settings.OAuth.Google.updated_google'));
- } catch (err) {
- toastError(err);
- }
- },
- [adminGoogleSecurityContainer, adminGeneralSecurityContainer, t],
- );
- return (
- <form onSubmit={handleSubmit(onClickSubmit)}>
- <h2 className="alert-anchor border-bottom">
- {t('security_settings.OAuth.Google.name')}
- </h2>
- {retrieveError != null && (
- <div className="alert alert-danger">
- <p>
- {t('Error occurred')} : {retrieveError}
- </p>
- </div>
- )}
- <div className="row my-4">
- <div className="col-6 offset-3">
- <div className="form-check form-switch form-check-success">
- <input
- id="isGoogleEnabled"
- className="form-check-input"
- type="checkbox"
- checked={
- adminGeneralSecurityContainer.state.isGoogleEnabled || false
- }
- onChange={() => {
- adminGeneralSecurityContainer.switchIsGoogleOAuthEnabled();
- }}
- />
- <label
- className="form-label form-check-label"
- htmlFor="isGoogleEnabled"
- >
- {t('security_settings.OAuth.Google.enable_google')}
- </label>
- </div>
- {!adminGeneralSecurityContainer.state.setupStrategies.includes(
- 'google',
- ) &&
- isGoogleEnabled && (
- <div className="badge text-bg-warning">
- {t('security_settings.setup_is_not_yet_complete')}
- </div>
- )}
- </div>
- </div>
- <div className="row mb-5">
- <label
- className="form-label col-12 col-md-3 text-start text-md-end py-2"
- htmlFor="googleCallbackUrl"
- >
- {t('security_settings.callback_URL')}
- </label>
- <div className="col-12 col-md-6">
- <input
- id="googleCallbackUrl"
- className="form-control"
- type="text"
- value={googleCallbackUrl}
- readOnly
- />
- <p className="form-text text-muted small">
- {t('security_settings.desc_of_callback_URL', {
- AuthName: 'OAuth',
- })}
- </p>
- {(siteUrl == null || siteUrl === '') && (
- <div className="alert alert-danger">
- <span className="material-symbols-outlined">error</span>
- <span
- // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
- dangerouslySetInnerHTML={{
- __html: t('alert.siteUrl_is_not_set', {
- link: `<a href="/admin/app">${t('headers.app_settings', { ns: 'commons' })}<span class="material-symbols-outlined">login</span></a>`,
- ns: 'commons',
- }),
- }}
- />
- </div>
- )}
- </div>
- </div>
- {isGoogleEnabled && (
- <React.Fragment>
- <h3 className="border-bottom mb-4">
- {t('security_settings.configuration')}
- </h3>
- <div className="row mb-4">
- <label
- htmlFor="googleClientId"
- className="col-3 text-end py-2 form-label"
- >
- {t('security_settings.clientID')}
- </label>
- <div className="col-6">
- <input
- className="form-control"
- type="text"
- {...register('googleClientId')}
- />
- <p className="form-text text-muted">
- <small
- // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
- dangerouslySetInnerHTML={{
- __html: t('security_settings.Use env var if empty', {
- env: 'OAUTH_GOOGLE_CLIENT_ID',
- }),
- }}
- />
- </p>
- </div>
- </div>
- <div className="row mb-4">
- <label
- htmlFor="googleClientSecret"
- className="col-3 text-end py-2 form-label"
- >
- {t('security_settings.client_secret')}
- </label>
- <div className="col-6">
- <input
- className="form-control"
- type="password"
- {...register('googleClientSecret')}
- />
- <p className="form-text text-muted">
- <small
- // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
- dangerouslySetInnerHTML={{
- __html: t('security_settings.Use env var if empty', {
- env: 'OAUTH_GOOGLE_CLIENT_SECRET',
- }),
- }}
- />
- </p>
- </div>
- </div>
- <div className="row mb-3">
- <div className="offset-3 col-6">
- <div className="form-check form-check-success">
- <input
- id="bindByUserNameGoogle"
- className="form-check-input"
- type="checkbox"
- checked={
- adminGoogleSecurityContainer.state
- .isSameEmailTreatedAsIdenticalUser || false
- }
- onChange={() => {
- adminGoogleSecurityContainer.switchIsSameEmailTreatedAsIdenticalUser();
- }}
- />
- <label
- className="form-check-label"
- htmlFor="bindByUserNameGoogle"
- >
- <span
- // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
- dangerouslySetInnerHTML={{
- __html: t(
- 'security_settings.Treat email matching as identical',
- ),
- }}
- />
- </label>
- </div>
- <p className="form-text text-muted">
- <small
- // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
- dangerouslySetInnerHTML={{
- __html: t(
- 'security_settings.Treat email matching as identical_warn',
- ),
- }}
- />
- </p>
- </div>
- </div>
- <div className="row mb-4">
- <div className="offset-3 col-5">
- <button
- type="submit"
- className="btn btn-primary"
- disabled={retrieveError != null}
- >
- {t('Update')}
- </button>
- </div>
- </div>
- </React.Fragment>
- )}
- <hr />
- <div style={{ minHeight: '300px' }}>
- <h4>
- <span className="material-symbols-outlined" aria-hidden="true">
- help
- </span>
- <a href="#collapseHelpForGoogleOauth" data-bs-toggle="collapse">
- {' '}
- {t('security_settings.OAuth.how_to.google')}
- </a>
- </h4>
- <div className="card custom-card bg-body-tertiary">
- <ol id="collapseHelpForGoogleOauth" className="collapse mb-0">
- <li
- // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
- dangerouslySetInnerHTML={{
- __html: t('security_settings.OAuth.Google.register_1', {
- link: '<a href="https://console.cloud.google.com/apis/credentials" target=_blank>Google Cloud Platform API Manager</a>',
- }),
- }}
- />
- <li
- // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
- dangerouslySetInnerHTML={{
- __html: t('security_settings.OAuth.Google.register_2'),
- }}
- />
- <li
- // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
- dangerouslySetInnerHTML={{
- __html: t('security_settings.OAuth.Google.register_3'),
- }}
- />
- <li
- // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
- dangerouslySetInnerHTML={{
- __html: t('security_settings.OAuth.Google.register_4', {
- url: googleCallbackUrl,
- }),
- }}
- />
- <li
- // biome-ignore lint/security/noDangerouslySetInnerHtml: trusted translation markup
- dangerouslySetInnerHTML={{
- __html: t('security_settings.OAuth.Google.register_5'),
- }}
- />
- </ol>
- </div>
- </div>
- </form>
- );
- };
- const GoogleSecurityManagementContentsWrapper = withUnstatedContainers(
- GoogleSecurityManagementContents,
- [AdminGeneralSecurityContainer, AdminGoogleSecurityContainer],
- );
- export default GoogleSecurityManagementContentsWrapper;
|