Просмотр исходного кода

Add OAuth 2.0 frontend components and translations

Co-authored-by: yuki-takei <1638767+yuki-takei@users.noreply.github.com>
copilot-swe-agent[bot] 3 месяцев назад
Родитель
Сommit
11d5f20351

+ 8 - 0
apps/app/public/static/locales/en_US/admin.json

@@ -371,6 +371,14 @@
     "transmission_method": "Transmission Method",
     "smtp_label": "SMTP",
     "ses_label": "SES(AWS)",
+    "oauth2_label": "OAuth 2.0 (Google Workspace)",
+    "oauth2_description": "Configure OAuth 2.0 authentication for sending emails using Google Workspace. You need to create OAuth 2.0 credentials in Google Cloud Console and obtain a refresh token.",
+    "oauth2_user": "Email Address",
+    "oauth2_user_help": "The email address of the authorized Google account",
+    "oauth2_client_id": "Client ID",
+    "oauth2_client_secret": "Client Secret",
+    "oauth2_refresh_token": "Refresh Token",
+    "oauth2_refresh_token_help": "The refresh token obtained from OAuth 2.0 authorization flow",
     "send_test_email": "Send a test-email",
     "success_to_send_test_email": "Success to send a test-email",
     "smtp_settings": "SMTP settings",

+ 8 - 0
apps/app/public/static/locales/ja_JP/admin.json

@@ -380,6 +380,14 @@
     "transmission_method": "送信方法",
     "smtp_label": "SMTP",
     "ses_label": "SES(AWS)",
+    "oauth2_label": "OAuth 2.0 (Google Workspace)",
+    "oauth2_description": "Google Workspaceを使用してメールを送信するためのOAuth 2.0認証を設定します。Google Cloud ConsoleでOAuth 2.0認証情報を作成し、リフレッシュトークンを取得する必要があります。",
+    "oauth2_user": "メールアドレス",
+    "oauth2_user_help": "認証されたGoogleアカウントのメールアドレス",
+    "oauth2_client_id": "クライアントID",
+    "oauth2_client_secret": "クライアントシークレット",
+    "oauth2_refresh_token": "リフレッシュトークン",
+    "oauth2_refresh_token_help": "OAuth 2.0認証フローから取得したリフレッシュトークン",
     "send_test_email": "テストメールを送信",
     "success_to_send_test_email": "テストメールを送信しました。",
     "smtp_settings": "SMTP設定",

+ 17 - 1
apps/app/src/client/components/Admin/App/MailSetting.tsx

@@ -6,6 +6,7 @@ import AdminAppContainer from '~/client/services/AdminAppContainer';
 import { toastError, toastSuccess } from '~/client/util/toastr';
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
+import { OAuth2Setting } from './OAuth2Setting';
 import { SesSetting } from './SesSetting';
 import { SmtpSetting } from './SmtpSetting';
 
@@ -17,7 +18,7 @@ const MailSetting = (props: Props) => {
   const { t } = useTranslation(['admin', 'commons']);
   const { adminAppContainer } = props;
 
-  const transmissionMethods = ['smtp', 'ses'];
+  const transmissionMethods = ['smtp', 'ses', 'oauth2'];
 
   const { register, handleSubmit, reset, watch } = useForm();
 
@@ -38,6 +39,10 @@ const MailSetting = (props: Props) => {
       smtpPassword: adminAppContainer.state.smtpPassword || '',
       sesAccessKeyId: adminAppContainer.state.sesAccessKeyId || '',
       sesSecretAccessKey: adminAppContainer.state.sesSecretAccessKey || '',
+      oauth2ClientId: adminAppContainer.state.oauth2ClientId || '',
+      oauth2ClientSecret: adminAppContainer.state.oauth2ClientSecret || '',
+      oauth2RefreshToken: adminAppContainer.state.oauth2RefreshToken || '',
+      oauth2User: adminAppContainer.state.oauth2User || '',
     });
   }, [
     adminAppContainer.state.fromAddress,
@@ -48,6 +53,10 @@ const MailSetting = (props: Props) => {
     adminAppContainer.state.smtpPassword,
     adminAppContainer.state.sesAccessKeyId,
     adminAppContainer.state.sesSecretAccessKey,
+    adminAppContainer.state.oauth2ClientId,
+    adminAppContainer.state.oauth2ClientSecret,
+    adminAppContainer.state.oauth2RefreshToken,
+    adminAppContainer.state.oauth2User,
     reset,
   ]);
 
@@ -64,6 +73,10 @@ const MailSetting = (props: Props) => {
           adminAppContainer.changeSmtpPassword(data.smtpPassword),
           adminAppContainer.changeSesAccessKeyId(data.sesAccessKeyId),
           adminAppContainer.changeSesSecretAccessKey(data.sesSecretAccessKey),
+          adminAppContainer.changeOAuth2ClientId(data.oauth2ClientId),
+          adminAppContainer.changeOAuth2ClientSecret(data.oauth2ClientSecret),
+          adminAppContainer.changeOAuth2RefreshToken(data.oauth2RefreshToken),
+          adminAppContainer.changeOAuth2User(data.oauth2User),
         ]);
 
         await adminAppContainer.updateMailSettingHandler();
@@ -149,6 +162,9 @@ const MailSetting = (props: Props) => {
       {currentTransmissionMethod === 'ses' && (
         <SesSetting register={register} />
       )}
+      {currentTransmissionMethod === 'oauth2' && (
+        <OAuth2Setting register={register} />
+      )}
 
       <div className="row my-3">
         <div className="col-md-3"></div>

+ 119 - 0
apps/app/src/client/components/Admin/App/OAuth2Setting.tsx

@@ -0,0 +1,119 @@
+import React from 'react';
+import { useTranslation } from 'next-i18next';
+import type { UseFormRegister } from 'react-hook-form';
+
+import AdminAppContainer from '~/client/services/AdminAppContainer';
+
+import { withUnstatedContainers } from '../../UnstatedUtils';
+
+type Props = {
+  adminAppContainer?: AdminAppContainer;
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  register: UseFormRegister<any>;
+};
+
+const OAuth2Setting = (props: Props): JSX.Element => {
+  const { t } = useTranslation();
+  const { register } = props;
+
+  return (
+    <React.Fragment>
+      <div id="mail-oauth2" className="tab-pane active">
+        <div className="row mb-3">
+          <div className="col-md-12">
+            <div className="alert alert-info">
+              <span className="material-symbols-outlined">info</span>{' '}
+              {t('admin:app_setting.oauth2_description')}
+            </div>
+          </div>
+        </div>
+
+        <div className="row">
+          <label
+            className="text-start text-md-end col-md-3 col-form-label"
+            htmlFor="admin-oauth2-user"
+          >
+            {t('admin:app_setting.oauth2_user')}
+          </label>
+          <div className="col-md-6">
+            <input
+              className="form-control"
+              type="email"
+              id="admin-oauth2-user"
+              placeholder="user@example.com"
+              {...register('oauth2User')}
+            />
+            <small className="form-text text-muted">
+              {t('admin:app_setting.oauth2_user_help')}
+            </small>
+          </div>
+        </div>
+
+        <div className="row">
+          <label
+            className="text-start text-md-end col-md-3 col-form-label"
+            htmlFor="admin-oauth2-client-id"
+          >
+            {t('admin:app_setting.oauth2_client_id')}
+          </label>
+          <div className="col-md-6">
+            <input
+              className="form-control"
+              type="text"
+              id="admin-oauth2-client-id"
+              {...register('oauth2ClientId')}
+            />
+          </div>
+        </div>
+
+        <div className="row">
+          <label
+            className="text-start text-md-end col-md-3 col-form-label"
+            htmlFor="admin-oauth2-client-secret"
+          >
+            {t('admin:app_setting.oauth2_client_secret')}
+          </label>
+          <div className="col-md-6">
+            <input
+              className="form-control"
+              type="password"
+              id="admin-oauth2-client-secret"
+              {...register('oauth2ClientSecret')}
+            />
+          </div>
+        </div>
+
+        <div className="row">
+          <label
+            className="text-start text-md-end col-md-3 col-form-label"
+            htmlFor="admin-oauth2-refresh-token"
+          >
+            {t('admin:app_setting.oauth2_refresh_token')}
+          </label>
+          <div className="col-md-6">
+            <input
+              className="form-control"
+              type="password"
+              id="admin-oauth2-refresh-token"
+              {...register('oauth2RefreshToken')}
+            />
+            <small className="form-text text-muted">
+              {t('admin:app_setting.oauth2_refresh_token_help')}
+            </small>
+          </div>
+        </div>
+      </div>
+    </React.Fragment>
+  );
+};
+
+export { OAuth2Setting };
+
+/**
+ * Wrapper component for using unstated
+ */
+const OAuth2SettingWrapper = withUnstatedContainers(OAuth2Setting, [
+  AdminAppContainer,
+]);
+
+export default OAuth2SettingWrapper;

+ 60 - 0
apps/app/src/client/services/AdminAppContainer.js

@@ -39,6 +39,11 @@ export default class AdminAppContainer extends Container {
       sesAccessKeyId: '',
       sesSecretAccessKey: '',
 
+      oauth2ClientId: '',
+      oauth2ClientSecret: '',
+      oauth2RefreshToken: '',
+      oauth2User: '',
+
       isMaintenanceMode: false,
     };
   }
@@ -78,6 +83,11 @@ export default class AdminAppContainer extends Container {
       sesAccessKeyId: appSettingsParams.sesAccessKeyId,
       sesSecretAccessKey: appSettingsParams.sesSecretAccessKey,
 
+      oauth2ClientId: appSettingsParams.oauth2ClientId,
+      oauth2ClientSecret: appSettingsParams.oauth2ClientSecret,
+      oauth2RefreshToken: appSettingsParams.oauth2RefreshToken,
+      oauth2User: appSettingsParams.oauth2User,
+
       isMaintenanceMode: appSettingsParams.isMaintenanceMode,
     });
   }
@@ -187,6 +197,34 @@ export default class AdminAppContainer extends Container {
     this.setState({ sesSecretAccessKey });
   }
 
+  /**
+   * Change oauth2ClientId
+   */
+  changeOAuth2ClientId(oauth2ClientId) {
+    this.setState({ oauth2ClientId });
+  }
+
+  /**
+   * Change oauth2ClientSecret
+   */
+  changeOAuth2ClientSecret(oauth2ClientSecret) {
+    this.setState({ oauth2ClientSecret });
+  }
+
+  /**
+   * Change oauth2RefreshToken
+   */
+  changeOAuth2RefreshToken(oauth2RefreshToken) {
+    this.setState({ oauth2RefreshToken });
+  }
+
+  /**
+   * Change oauth2User
+   */
+  changeOAuth2User(oauth2User) {
+    this.setState({ oauth2User });
+  }
+
   /**
    * Update app setting
    * @memberOf AdminAppContainer
@@ -226,6 +264,9 @@ export default class AdminAppContainer extends Container {
     if (this.state.transmissionMethod === 'smtp') {
       return this.updateSmtpSetting();
     }
+    if (this.state.transmissionMethod === 'oauth2') {
+      return this.updateOAuth2Setting();
+    }
     return this.updateSesSetting();
   }
 
@@ -265,6 +306,25 @@ export default class AdminAppContainer extends Container {
     return mailSettingParams;
   }
 
+  /**
+   * Update OAuth 2.0 setting
+   * @memberOf AdminAppContainer
+   * @return {Array} Appearance
+   */
+  async updateOAuth2Setting() {
+    const response = await apiv3Put('/app-settings/oauth2-setting', {
+      fromAddress: this.state.fromAddress,
+      transmissionMethod: this.state.transmissionMethod,
+      oauth2ClientId: this.state.oauth2ClientId,
+      oauth2ClientSecret: this.state.oauth2ClientSecret,
+      oauth2RefreshToken: this.state.oauth2RefreshToken,
+      oauth2User: this.state.oauth2User,
+    });
+    const { mailSettingParams } = response.data;
+    this.setState({ isMailerSetup: mailSettingParams.isMailerSetup });
+    return mailSettingParams;
+  }
+
   /**
    * send test e-mail
    * @memberOf AdminAppContainer