فهرست منبع

Merge branch 'master' into support/migrate-to-biome-from-eslint-rest-files

Yuki Takei 2 ماه پیش
والد
کامیت
42808812b2

+ 7 - 4
apps/app/playwright/utils/Login.ts

@@ -7,12 +7,15 @@ export const login = async (page: Page): Promise<void> => {
   // Perform authentication steps. Replace these actions with your own.
   await page.goto('/admin');
 
-  const loginForm = await page.getByRole('form');
+  const loginForm = await page.getByTestId('login-form');
 
   if (loginForm != null) {
-    await page.getByLabel('Username or E-mail').fill('admin');
-    await page.getByLabel('Password').fill('adminadmin');
-    await page.locator('[type=submit]').filter({ hasText: 'Login' }).click();
+    await loginForm.getByPlaceholder('Username or E-mail').fill('admin');
+    await loginForm.getByPlaceholder('Password').fill('adminadmin');
+    await loginForm
+      .locator('[type=submit]')
+      .filter({ hasText: 'Login' })
+      .click();
   }
 
   await page.waitForURL('/admin');

+ 1 - 1
apps/app/src/client/components/Admin/Customize/ThemeColorBox.tsx

@@ -29,7 +29,7 @@ export const ThemeColorBox = (props: Props): JSX.Element => {
     <button
       type="button"
       id={`theme-option-${name}`}
-      className={`${themeOptionClass} d-flex flex-column align-items-center ${isSelected ? 'active' : ''}`}
+      className={`${themeOptionClass} d-flex flex-column align-items-center ${isSelected ? 'active' : ''} border-0 bg-transparent`}
       onClick={onSelected}
       aria-pressed={isSelected}
     >

+ 112 - 117
apps/app/src/client/components/CompleteUserRegistrationForm.tsx

@@ -93,132 +93,127 @@ export const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
   }
 
   return (
-    <>
-      <div
-        className={`${moduleClass} nologin-dialog mx-auto rounded-4 rounded-top-0`}
-        id="nologin-dialog"
-      >
-        <div className="row mx-0">
-          <div className="col-12 px-4">
-            {errorCode != null &&
-              errorCode === UserActivationErrorCode.TOKEN_NOT_FOUND && (
-                <p className="alert alert-danger">
-                  <span>Token not found</span>
-                </p>
-              )}
-
-            {errorCode != null &&
-              errorCode ===
-                UserActivationErrorCode.USER_REGISTRATION_ORDER_IS_NOT_APPROPRIATE && (
-                <p className="alert alert-danger">
-                  <span>{t('message.incorrect_token_or_expired_url')}</span>
-                </p>
-              )}
-
-            {!isEmailAuthenticationEnabled && (
+    <div
+      className={`${moduleClass} nologin-dialog mx-auto rounded-4 rounded-top-0`}
+    >
+      <div className="row mx-0">
+        <div className="col-12 px-4">
+          {errorCode != null &&
+            errorCode === UserActivationErrorCode.TOKEN_NOT_FOUND && (
               <p className="alert alert-danger">
-                <span>{t('message.email_authentication_is_not_enabled')}</span>
+                <span>Token not found</span>
               </p>
             )}
 
-            <form onSubmit={handleSubmitRegistration} id="registration-form">
-              <input type="hidden" name="token" value={token} />
+          {errorCode != null &&
+            errorCode ===
+              UserActivationErrorCode.USER_REGISTRATION_ORDER_IS_NOT_APPROPRIATE && (
+              <p className="alert alert-danger">
+                <span>{t('message.incorrect_token_or_expired_url')}</span>
+              </p>
+            )}
 
-              <div className="input-group">
-                <span className="p-2 text-white opacity-75">
-                  <span className="material-symbols-outlined">mail</span>
-                </span>
-                <input
-                  type="text"
-                  className="form-control rounded"
-                  placeholder={t('Email')}
-                  disabled
-                  value={email}
-                />
-              </div>
-
-              <div className="input-group" id="input-group-username">
-                <span className="p-2 text-white opacity-75">
-                  <span className="material-symbols-outlined">person</span>
-                </span>
-                <input
-                  type="text"
-                  className="form-control rounded"
-                  placeholder={t('User ID')}
-                  name="username"
-                  onChange={(e) => setUsername(e.target.value)}
-                  required
-                  disabled={forceDisableForm || disableForm}
-                />
-              </div>
-              {!usernameAvailable && (
-                <p className="form-text text-red">
-                  <span id="help-block-username">
-                    <span className="p-2 text-white opacity-75">
-                      <span className="material-symbols-outlined">block</span>
-                    </span>
-                    {t('installer.unavaliable_user_id')}
+          {!isEmailAuthenticationEnabled && (
+            <p className="alert alert-danger">
+              <span>{t('message.email_authentication_is_not_enabled')}</span>
+            </p>
+          )}
+
+          <form onSubmit={handleSubmitRegistration} id="registration-form">
+            <input type="hidden" name="token" value={token} />
+
+            <div className="input-group">
+              <span className="p-2 text-white opacity-75">
+                <span className="material-symbols-outlined">mail</span>
+              </span>
+              <input
+                type="text"
+                className="form-control rounded"
+                placeholder={t('Email')}
+                disabled
+                value={email}
+              />
+            </div>
+
+            <div className="input-group">
+              <span className="p-2 text-white opacity-75">
+                <span className="material-symbols-outlined">person</span>
+              </span>
+              <input
+                type="text"
+                className="form-control rounded"
+                placeholder={t('User ID')}
+                name="username"
+                onChange={(e) => setUsername(e.target.value)}
+                required
+                disabled={forceDisableForm || disableForm}
+              />
+            </div>
+            {!usernameAvailable && (
+              <p className="form-text text-red">
+                <span>
+                  <span className="p-2 text-white opacity-75">
+                    <span className="material-symbols-outlined">block</span>
                   </span>
-                </p>
-              )}
-
-              <div className="input-group">
-                <span className="p-2 text-white opacity-75">
-                  <span className="material-symbols-outlined">sell</span>
+                  {t('installer.unavaliable_user_id')}
                 </span>
-                <input
-                  type="text"
-                  className="form-control rounded"
-                  placeholder={t('Name')}
-                  name="name"
-                  value={name}
-                  onChange={(e) => setName(e.target.value)}
-                  required
-                  disabled={forceDisableForm || disableForm}
-                />
-              </div>
-
-              <div className="input-group">
-                <span className="p-2 text-white opacity-75">
-                  <span className="material-symbols-outlined">lock</span>
+              </p>
+            )}
+
+            <div className="input-group">
+              <span className="p-2 text-white opacity-75">
+                <span className="material-symbols-outlined">sell</span>
+              </span>
+              <input
+                type="text"
+                className="form-control rounded"
+                placeholder={t('Name')}
+                name="name"
+                value={name}
+                onChange={(e) => setName(e.target.value)}
+                required
+                disabled={forceDisableForm || disableForm}
+              />
+            </div>
+
+            <div className="input-group">
+              <span className="p-2 text-white opacity-75">
+                <span className="material-symbols-outlined">lock</span>
+              </span>
+              <input
+                type="password"
+                className="form-control rounded"
+                placeholder={t('Password')}
+                name="password"
+                value={password}
+                onChange={(e) => setPassword(e.target.value)}
+                required
+                disabled={forceDisableForm || disableForm}
+              />
+            </div>
+
+            <div className="input-group justify-content-center mt-4">
+              <button
+                type="submit"
+                disabled={forceDisableForm || disableForm}
+                className="btn btn-secondary btn-register col-6 mx-auto d-flex"
+              >
+                <span>
+                  <span className="material-symbols-outlined">person_add</span>
                 </span>
-                <input
-                  type="password"
-                  className="form-control rounded"
-                  placeholder={t('Password')}
-                  name="password"
-                  value={password}
-                  onChange={(e) => setPassword(e.target.value)}
-                  required
-                  disabled={forceDisableForm || disableForm}
-                />
-              </div>
-
-              <div className="input-group justify-content-center mt-4">
-                <button
-                  type="submit"
-                  disabled={forceDisableForm || disableForm}
-                  className="btn btn-secondary btn-register col-6 mx-auto d-flex"
-                >
-                  <span>
-                    <span className="material-symbols-outlined">
-                      person_add
-                    </span>
-                  </span>
-                  <span className="flex-grow-1">{t('Create')}</span>
-                </button>
-              </div>
-
-              <div className="input-group mt-5 d-flex">
-                <a href="https://growi.org" className="link-growi-org">
-                  <span className="growi">GROWI</span>
-                  <span className="org">.org</span>
-                </a>
-              </div>
-            </form>
-          </div>
+                <span className="flex-grow-1">{t('Create')}</span>
+              </button>
+            </div>
+
+            <div className="input-group mt-5 d-flex">
+              <a href="https://growi.org" className="link-growi-org">
+                <span className="growi">GROWI</span>
+                <span className="org">.org</span>
+              </a>
+            </div>
+          </form>
         </div>
       </div>
-    </>
+    </div>
   );
 };

+ 1 - 1
apps/app/src/client/components/InstallerForm.tsx

@@ -126,7 +126,7 @@ const InstallerForm = memo((props: Props): JSX.Element => {
           </div>
         )}
 
-        <form id="register-form" className="ps-1" onSubmit={submitHandler}>
+        <form className="ps-1" onSubmit={submitHandler}>
           <div className="dropdown mb-3">
             <div className="input-group dropdown-with-icon">
               <span className="p-2 text-white opacity-75">

+ 3 - 3
apps/app/src/client/components/InvitedForm.tsx

@@ -90,9 +90,9 @@ export const InvitedForm = (props: InvitedFormProps): JSX.Element => {
   }
 
   return (
-    <div className="nologin-dialog px-3 pb-3 mx-auto" id="nologin-dialog">
+    <div className="nologin-dialog px-3 pb-3 mx-auto">
       {formNotification()}
-      <form onSubmit={handleSubmit(submitHandler)} id="invited-form">
+      <form onSubmit={handleSubmit(submitHandler)}>
         {/* Email Form */}
         <div className="input-group">
           <span className="input-group-text">
@@ -109,7 +109,7 @@ export const InvitedForm = (props: InvitedFormProps): JSX.Element => {
           />
         </div>
         {/* UserID Form */}
-        <div className="input-group" id="input-group-username">
+        <div className="input-group">
           <span className="input-group-text">
             <span className="material-symbols-outlined">person</span>
           </span>

+ 5 - 12
apps/app/src/client/components/LoginForm/LoginForm.tsx

@@ -211,7 +211,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
           </div>
         )}
 
-        <form onSubmit={handleLoginWithLocalSubmit} id="login-form">
+        <form onSubmit={handleLoginWithLocalSubmit} data-testid="login-form">
           <div className="input-group">
             <label
               className="text-white opacity-75 d-flex align-items-center"
@@ -413,13 +413,10 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
           </p>
         )}
 
-        <form
-          onSubmit={(e) => handleRegisterFormSubmit(e, registerAction)}
-          id="register-form"
-        >
+        <form onSubmit={(e) => handleRegisterFormSubmit(e, registerAction)}>
           {!isEmailAuthenticationEnabled && (
             <div>
-              <div className="input-group" id="input-group-username">
+              <div className="input-group">
                 <span className="text-white opacity-75 d-flex align-items-center">
                   <span className="material-symbols-outlined">person</span>
                 </span>
@@ -437,7 +434,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
                 />
               </div>
               <p className="form-text text-danger">
-                <span id="help-block-username"></span>
+                <span></span>
               </p>
               <div className="input-group">
                 <span className="text-white opacity-75 d-flex align-items-center">
@@ -580,11 +577,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
 
   return (
     <div className={moduleClass}>
-      <div
-        className="nologin-dialog mx-auto rounded-4 rounded-top-0"
-        id="nologin-dialog"
-        data-testid="login-form"
-      >
+      <div className="nologin-dialog mx-auto rounded-4 rounded-top-0">
         <div className="row mx-0">
           <div className="col-12 px-md-4 pb-5">
             <ReactCardFlip

+ 3 - 229
apps/app/src/server/routes/apiv3/security-settings/index.js

@@ -13,7 +13,6 @@ import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity
 import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
 import ShareLink from '~/server/models/share-link';
 import { configManager } from '~/server/service/config-manager';
-import { getTranslation } from '~/server/service/i18next';
 import loggerFactory from '~/utils/logger';
 import {
   prepareDeleteConfigValuesForCalc,
@@ -21,6 +20,7 @@ import {
 } from '~/utils/page-delete-config';
 
 import { checkSetupStrategiesHasAdmin } from './checkSetupStrategiesHasAdmin';
+import { handleSamlUpdate, samlAuthValidator } from './saml';
 
 const logger = loggerFactory('growi:routes:apiv3:security-setting');
 
@@ -114,41 +114,6 @@ const validator = {
       .if((value) => value != null)
       .isString(),
   ],
-  samlAuth: [
-    body('entryPoint')
-      .if((value) => value != null)
-      .isString(),
-    body('issuer')
-      .if((value) => value != null)
-      .isString(),
-    body('cert')
-      .if((value) => value != null)
-      .isString(),
-    body('attrMapId')
-      .if((value) => value != null)
-      .isString(),
-    body('attrMapUsername')
-      .if((value) => value != null)
-      .isString(),
-    body('attrMapMail')
-      .if((value) => value != null)
-      .isString(),
-    body('attrMapFirstName')
-      .if((value) => value != null)
-      .isString(),
-    body('attrMapLastName')
-      .if((value) => value != null)
-      .isString(),
-    body('isSameUsernameTreatedAsIdenticalUser')
-      .if((value) => value != null)
-      .isBoolean(),
-    body('isSameEmailTreatedAsIdenticalUser')
-      .if((value) => value != null)
-      .isBoolean(),
-    body('ABLCRule')
-      .if((value) => value != null)
-      .isString(),
-  ],
   oidcAuth: [
     body('oidcProviderName')
       .if((value) => value != null)
@@ -363,75 +328,6 @@ const validator = {
  *          ldapGroupDnProperty:
  *            type: string
  *            description: The property of user object to use in dn interpolation of Group Search Filter
- *      SamlAuthSetting:
- *        type: object
- *        properties:
- *          missingMandatoryConfigKeys:
- *            type: array
- *            description: array of missing mandatory config keys
- *            items:
- *              type: string
- *              description: missing mandatory config key
- *          useOnlyEnvVarsForSomeOptions:
- *            type: boolean
- *            description: use only env vars for some options
- *          samlEntryPoint:
- *            type: string
- *            description: entry point for saml
- *          samlIssuer:
- *            type: string
- *            description: issuer for saml
- *          samlEnvVarIssuer:
- *            type: string
- *            description: issuer for saml
- *          samlCert:
- *            type: string
- *            description: certificate for saml
- *          samlEnvVarCert:
- *            type: string
- *            description: certificate for saml
- *          samlAttrMapId:
- *            type: string
- *            description: attribute mapping id for saml
- *          samlAttrMapUserName:
- *            type: string
- *            description: attribute mapping user name for saml
- *          samlAttrMapMail:
- *            type: string
- *            description: attribute mapping mail for saml
- *          samlEnvVarAttrMapId:
- *            type: string
- *            description: attribute mapping id for saml
- *          samlEnvVarAttrMapUserName:
- *            type: string
- *            description: attribute mapping user name for saml
- *          samlEnvVarAttrMapMail:
- *            type: string
- *            description: attribute mapping mail for saml
- *          samlAttrMapFirstName:
- *            type: string
- *            description: attribute mapping first name for saml
- *          samlAttrMapLastName:
- *            type: string
- *            description: attribute mapping last name for saml
- *          samlEnvVarAttrMapFirstName:
- *            type: string
- *            description: attribute mapping first name for saml
- *          samlEnvVarAttrMapLastName:
- *            type: string
- *            description: attribute mapping last name for saml
- *          isSameUsernameTreatedAsIdenticalUser:
- *            type: boolean
- *            description: local account automatically linked the user name matched
- *          isSameEmailTreatedAsIdenticalUser:
- *            type: boolean
- *            description: local account automatically linked the email matched
- *          samlABLCRule:
- *            type: string
- *            description: ABLCRule for saml
- *          samlEnvVarABLCRule:
- *            type: string
- *            description: ABLCRule for saml
  *      OidcAuthSetting:
  *        type: object
  *        properties:
@@ -1563,131 +1459,9 @@ module.exports = (crowi) => {
     loginRequiredStrictly,
     adminRequired,
     addActivity,
-    validator.samlAuth,
+    samlAuthValidator,
     apiV3FormValidator,
-    async (req, res) => {
-      const { t } = await getTranslation({
-        lang: req.user.lang,
-        ns: ['translation', 'admin'],
-      });
-
-      //  For the value of each mandatory items,
-      //  check whether it from the environment variables is empty and form value to update it is empty
-      //  validate the syntax of a attribute - based login control rule
-      const invalidValues = [];
-      for (const configKey of crowi.passportService
-        .mandatoryConfigKeysForSaml) {
-        const key = configKey.replace('security:passport-saml:', '');
-        const formValue = req.body[key];
-        if (
-          configManager.getConfig(configKey, ConfigSource.env) == null &&
-          formValue == null
-        ) {
-          const formItemName = t(`security_settings.form_item_name.${key}`);
-          invalidValues.push(
-            t('input_validation.message.required', { param: formItemName }),
-          );
-        }
-      }
-      if (invalidValues.length !== 0) {
-        return res.apiv3Err(
-          t('input_validation.message.error_message'),
-          400,
-          invalidValues,
-        );
-      }
-
-      const rule = req.body.ABLCRule;
-      // Empty string disables attribute-based login control.
-      // So, when rule is empty string, validation is passed.
-      if (rule != null) {
-        try {
-          crowi.passportService.parseABLCRule(rule);
-        } catch (err) {
-          return res.apiv3Err(
-            t('input_validation.message.invalid_syntax', {
-              syntax: t('security_settings.form_item_name.ABLCRule'),
-            }),
-            400,
-          );
-        }
-      }
-
-      const requestParams = {
-        'security:passport-saml:entryPoint': req.body.entryPoint,
-        'security:passport-saml:issuer': req.body.issuer,
-        'security:passport-saml:cert': req.body.cert,
-        'security:passport-saml:attrMapId': req.body.attrMapId,
-        'security:passport-saml:attrMapUsername': req.body.attrMapUsername,
-        'security:passport-saml:attrMapMail': req.body.attrMapMail,
-        'security:passport-saml:attrMapFirstName': req.body.attrMapFirstName,
-        'security:passport-saml:attrMapLastName': req.body.attrMapLastName,
-        'security:passport-saml:isSameUsernameTreatedAsIdenticalUser':
-          req.body.isSameUsernameTreatedAsIdenticalUser,
-        'security:passport-saml:isSameEmailTreatedAsIdenticalUser':
-          req.body.isSameEmailTreatedAsIdenticalUser,
-        'security:passport-saml:ABLCRule': req.body.ABLCRule,
-      };
-
-      try {
-        await updateAndReloadStrategySettings('saml', requestParams);
-
-        const securitySettingParams = {
-          missingMandatoryConfigKeys:
-            await crowi.passportService.getSamlMissingMandatoryConfigKeys(),
-          samlEntryPoint: await configManager.getConfig(
-            'security:passport-saml:entryPoint',
-            ConfigSource.db,
-          ),
-          samlIssuer: await configManager.getConfig(
-            'security:passport-saml:issuer',
-            ConfigSource.db,
-          ),
-          samlCert: await configManager.getConfig(
-            'security:passport-saml:cert',
-            ConfigSource.db,
-          ),
-          samlAttrMapId: await configManager.getConfig(
-            'security:passport-saml:attrMapId',
-            ConfigSource.db,
-          ),
-          samlAttrMapUsername: await configManager.getConfig(
-            'security:passport-saml:attrMapUsername',
-            ConfigSource.db,
-          ),
-          samlAttrMapMail: await configManager.getConfig(
-            'security:passport-saml:attrMapMail',
-            ConfigSource.db,
-          ),
-          samlAttrMapFirstName: await configManager.getConfig(
-            'security:passport-saml:attrMapFirstName',
-            ConfigSource.db,
-          ),
-          samlAttrMapLastName: await configManager.getConfig(
-            'security:passport-saml:attrMapLastName',
-            ConfigSource.db,
-          ),
-          isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig(
-            'security:passport-saml:isSameUsernameTreatedAsIdenticalUser',
-          ),
-          isSameEmailTreatedAsIdenticalUser: await configManager.getConfig(
-            'security:passport-saml:isSameEmailTreatedAsIdenticalUser',
-          ),
-          samlABLCRule: await configManager.getConfig(
-            'security:passport-saml:ABLCRule',
-          ),
-        };
-        const parameters = {
-          action: SupportedAction.ACTION_ADMIN_AUTH_SAML_UPDATE,
-        };
-        activityEvent.emit('update', res.locals.activity._id, parameters);
-        return res.apiv3({ securitySettingParams });
-      } catch (err) {
-        const msg = 'Error occurred in updating SAML setting';
-        logger.error('Error', err);
-        return res.apiv3Err(new ErrorV3(msg, 'update-SAML-failed'));
-      }
-    },
+    handleSamlUpdate(crowi, activityEvent, updateAndReloadStrategySettings),
   );
 
   /**

+ 320 - 0
apps/app/src/server/routes/apiv3/security-settings/saml.ts

@@ -0,0 +1,320 @@
+import type { EventEmitter } from 'node:events';
+import type { NonBlankString } from '@growi/core';
+import {
+  ConfigSource,
+  toNonBlankStringOrUndefined,
+} from '@growi/core/dist/interfaces';
+import { ErrorV3 } from '@growi/core/dist/models';
+import { body } from 'express-validator';
+
+import { SupportedAction } from '~/interfaces/activity';
+import type { CrowiRequest } from '~/interfaces/crowi-request';
+import type Crowi from '~/server/crowi';
+import { configManager } from '~/server/service/config-manager';
+import { getTranslation } from '~/server/service/i18next';
+import loggerFactory from '~/utils/logger';
+
+import type { ApiV3Response } from '../interfaces/apiv3-response';
+
+const logger = loggerFactory('growi:routes:apiv3:security-setting:saml');
+
+// Type definitions
+interface SamlRequestBody {
+  entryPoint?: string | null;
+  issuer?: string | null;
+  cert?: string | null;
+  attrMapId?: string | null;
+  attrMapUsername?: string | null;
+  attrMapMail?: string | null;
+  attrMapFirstName?: string | null;
+  attrMapLastName?: string | null;
+  isSameUsernameTreatedAsIdenticalUser?: boolean;
+  isSameEmailTreatedAsIdenticalUser?: boolean;
+  ABLCRule?: string | null;
+}
+
+interface SamlSecuritySettingParams {
+  missingMandatoryConfigKeys: string[];
+  samlEntryPoint: NonBlankString | undefined;
+  samlIssuer: NonBlankString | undefined;
+  samlCert: NonBlankString | undefined;
+  samlAttrMapId: NonBlankString | undefined;
+  samlAttrMapUsername: NonBlankString | undefined;
+  samlAttrMapMail: NonBlankString | undefined;
+  samlAttrMapFirstName: NonBlankString | undefined;
+  samlAttrMapLastName: NonBlankString | undefined;
+  isSameUsernameTreatedAsIdenticalUser: boolean;
+  isSameEmailTreatedAsIdenticalUser: boolean;
+  samlABLCRule: NonBlankString | undefined;
+}
+
+type UpdateAndReloadStrategySettings = (
+  authId: string,
+  params: Record<string, unknown>,
+  opts?: { removeIfUndefined?: boolean },
+) => Promise<void>;
+
+// Validator
+export const samlAuthValidator = [
+  body('entryPoint')
+    .if((value: unknown) => value != null)
+    .isString(),
+  body('issuer')
+    .if((value: unknown) => value != null)
+    .isString(),
+  body('cert')
+    .if((value: unknown) => value != null)
+    .isString(),
+  body('attrMapId')
+    .if((value: unknown) => value != null)
+    .isString(),
+  body('attrMapUsername')
+    .if((value: unknown) => value != null)
+    .isString(),
+  body('attrMapMail')
+    .if((value: unknown) => value != null)
+    .isString(),
+  body('attrMapFirstName')
+    .if((value: unknown) => value != null)
+    .isString(),
+  body('attrMapLastName')
+    .if((value: unknown) => value != null)
+    .isString(),
+  body('isSameUsernameTreatedAsIdenticalUser')
+    .if((value: unknown) => value != null)
+    .isBoolean(),
+  body('isSameEmailTreatedAsIdenticalUser')
+    .if((value: unknown) => value != null)
+    .isBoolean(),
+  body('ABLCRule')
+    .if((value: unknown) => value != null)
+    .isString(),
+];
+
+/**
+ * @swagger
+ *
+ * components:
+ *   schemas:
+ *     SamlAuthSetting:
+ *       type: object
+ *       properties:
+ *         missingMandatoryConfigKeys:
+ *           type: array
+ *           description: array of missing mandatory config keys
+ *           items:
+ *             type: string
+ *             description: missing mandatory config key
+ *         useOnlyEnvVarsForSomeOptions:
+ *           type: boolean
+ *           description: use only env vars for some options
+ *         samlEntryPoint:
+ *           type: string
+ *           description: entry point for saml
+ *         samlIssuer:
+ *           type: string
+ *           description: issuer for saml
+ *         samlEnvVarIssuer:
+ *           type: string
+ *           description: issuer for saml
+ *         samlCert:
+ *           type: string
+ *           description: certificate for saml
+ *         samlEnvVarCert:
+ *           type: string
+ *           description: certificate for saml
+ *         samlAttrMapId:
+ *           type: string
+ *           description: attribute mapping id for saml
+ *         samlAttrMapUserName:
+ *           type: string
+ *           description: attribute mapping user name for saml
+ *         samlAttrMapMail:
+ *           type: string
+ *           description: attribute mapping mail for saml
+ *         samlEnvVarAttrMapId:
+ *           type: string
+ *           description: attribute mapping id for saml
+ *         samlEnvVarAttrMapUserName:
+ *           type: string
+ *           description: attribute mapping user name for saml
+ *         samlEnvVarAttrMapMail:
+ *           type: string
+ *           description: attribute mapping mail for saml
+ *         samlAttrMapFirstName:
+ *           type: string
+ *           description: attribute mapping first name for saml
+ *         samlAttrMapLastName:
+ *           type: string
+ *           description: attribute mapping last name for saml
+ *         samlEnvVarAttrMapFirstName:
+ *           type: string
+ *           description: attribute mapping first name for saml
+ *         samlEnvVarAttrMapLastName:
+ *           type: string
+ *           description: attribute mapping last name for saml
+ *         isSameUsernameTreatedAsIdenticalUser:
+ *           type: boolean
+ *           description: local account automatically linked the user name matched
+ *         isSameEmailTreatedAsIdenticalUser:
+ *           type: boolean
+ *           description: local account automatically linked the email matched
+ *         samlABLCRule:
+ *           type: string
+ *           description: ABLCRule for saml
+ *         samlEnvVarABLCRule:
+ *           type: string
+ *           description: ABLCRule for saml
+ */
+
+/**
+ * SAML authentication route handler
+ */
+export const handleSamlUpdate = (
+  crowi: Crowi,
+  activityEvent: EventEmitter,
+  updateAndReloadStrategySettings: UpdateAndReloadStrategySettings,
+) => {
+  return async (req: CrowiRequest, res: ApiV3Response) => {
+    const { t } = await getTranslation({
+      lang: req.user?.lang,
+      ns: ['translation', 'admin'],
+    });
+
+    const reqBody = req.body as SamlRequestBody;
+
+    //  For the value of each mandatory items,
+    //  check whether it from the environment variables is empty and form value to update it is empty
+    //  validate the syntax of a attribute - based login control rule
+    const invalidValues: string[] = [];
+    for (const configKey of crowi.passportService.mandatoryConfigKeysForSaml) {
+      const key = configKey.replace('security:passport-saml:', '');
+      const formValue = reqBody[key as keyof SamlRequestBody];
+      if (
+        configManager.getConfig(configKey, ConfigSource.env) == null &&
+        formValue == null
+      ) {
+        const formItemName = t(`security_settings.form_item_name.${key}`);
+        invalidValues.push(
+          t('input_validation.message.required', { param: formItemName }),
+        );
+      }
+    }
+    if (invalidValues.length !== 0) {
+      return res.apiv3Err(
+        t('input_validation.message.error_message'),
+        400,
+        invalidValues,
+      );
+    }
+
+    const rule = reqBody.ABLCRule;
+    // Empty string disables attribute-based login control.
+    // So, when rule is empty string, validation is passed.
+    if (rule != null) {
+      try {
+        crowi.passportService.parseABLCRule(rule);
+      } catch (_err) {
+        return res.apiv3Err(
+          t('input_validation.message.invalid_syntax', {
+            syntax: t('security_settings.form_item_name.ABLCRule'),
+          }),
+          400,
+        );
+      }
+    }
+
+    const requestParams: Record<string, unknown> = {
+      'security:passport-saml:entryPoint': toNonBlankStringOrUndefined(
+        reqBody.entryPoint,
+      ),
+      'security:passport-saml:issuer': toNonBlankStringOrUndefined(
+        reqBody.issuer,
+      ),
+      'security:passport-saml:cert': toNonBlankStringOrUndefined(reqBody.cert),
+      'security:passport-saml:attrMapId': toNonBlankStringOrUndefined(
+        reqBody.attrMapId,
+      ),
+      'security:passport-saml:attrMapUsername': toNonBlankStringOrUndefined(
+        reqBody.attrMapUsername,
+      ),
+      'security:passport-saml:attrMapMail': toNonBlankStringOrUndefined(
+        reqBody.attrMapMail,
+      ),
+      'security:passport-saml:attrMapFirstName': toNonBlankStringOrUndefined(
+        reqBody.attrMapFirstName,
+      ),
+      'security:passport-saml:attrMapLastName': toNonBlankStringOrUndefined(
+        reqBody.attrMapLastName,
+      ),
+      'security:passport-saml:isSameUsernameTreatedAsIdenticalUser':
+        reqBody.isSameUsernameTreatedAsIdenticalUser,
+      'security:passport-saml:isSameEmailTreatedAsIdenticalUser':
+        reqBody.isSameEmailTreatedAsIdenticalUser,
+      'security:passport-saml:ABLCRule': toNonBlankStringOrUndefined(
+        reqBody.ABLCRule,
+      ),
+    };
+
+    try {
+      await updateAndReloadStrategySettings('saml', requestParams, {
+        removeIfUndefined: true,
+      });
+
+      const securitySettingParams: SamlSecuritySettingParams = {
+        missingMandatoryConfigKeys:
+          await crowi.passportService.getSamlMissingMandatoryConfigKeys(),
+        samlEntryPoint: await configManager.getConfig(
+          'security:passport-saml:entryPoint',
+          ConfigSource.db,
+        ),
+        samlIssuer: await configManager.getConfig(
+          'security:passport-saml:issuer',
+          ConfigSource.db,
+        ),
+        samlCert: await configManager.getConfig(
+          'security:passport-saml:cert',
+          ConfigSource.db,
+        ),
+        samlAttrMapId: await configManager.getConfig(
+          'security:passport-saml:attrMapId',
+          ConfigSource.db,
+        ),
+        samlAttrMapUsername: await configManager.getConfig(
+          'security:passport-saml:attrMapUsername',
+          ConfigSource.db,
+        ),
+        samlAttrMapMail: await configManager.getConfig(
+          'security:passport-saml:attrMapMail',
+          ConfigSource.db,
+        ),
+        samlAttrMapFirstName: await configManager.getConfig(
+          'security:passport-saml:attrMapFirstName',
+          ConfigSource.db,
+        ),
+        samlAttrMapLastName: await configManager.getConfig(
+          'security:passport-saml:attrMapLastName',
+          ConfigSource.db,
+        ),
+        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig(
+          'security:passport-saml:isSameUsernameTreatedAsIdenticalUser',
+        ),
+        isSameEmailTreatedAsIdenticalUser: await configManager.getConfig(
+          'security:passport-saml:isSameEmailTreatedAsIdenticalUser',
+        ),
+        samlABLCRule: await configManager.getConfig(
+          'security:passport-saml:ABLCRule',
+        ),
+      };
+      const parameters = {
+        action: SupportedAction.ACTION_ADMIN_AUTH_SAML_UPDATE,
+      };
+      activityEvent.emit('update', res.locals.activity._id, parameters);
+      return res.apiv3({ securitySettingParams });
+    } catch (err) {
+      const msg = 'Error occurred in updating SAML setting';
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(msg, 'update-SAML-failed'));
+    }
+  };
+};

+ 25 - 13
apps/app/src/server/service/config-manager/config-definition.ts

@@ -568,43 +568,55 @@ export const CONFIG_DEFINITIONS = {
     envVarName: 'SAML_ENABLED',
     defaultValue: false,
   }),
-  'security:passport-saml:callbackUrl': defineConfig<string | undefined>({
+  'security:passport-saml:callbackUrl': defineConfig<
+    NonBlankString | undefined
+  >({
     envVarName: 'SAML_CALLBACK_URI',
     defaultValue: undefined,
   }),
-  'security:passport-saml:attrMapId': defineConfig<string | undefined>({
+  'security:passport-saml:attrMapId': defineConfig<NonBlankString | undefined>({
     envVarName: 'SAML_ATTR_MAPPING_ID',
     defaultValue: undefined,
   }),
-  'security:passport-saml:attrMapUsername': defineConfig<string | undefined>({
+  'security:passport-saml:attrMapUsername': defineConfig<
+    NonBlankString | undefined
+  >({
     envVarName: 'SAML_ATTR_MAPPING_USERNAME',
     defaultValue: undefined,
   }),
-  'security:passport-saml:attrMapMail': defineConfig<string | undefined>({
+  'security:passport-saml:attrMapMail': defineConfig<
+    NonBlankString | undefined
+  >({
     envVarName: 'SAML_ATTR_MAPPING_MAIL',
     defaultValue: undefined,
   }),
-  'security:passport-saml:attrMapFirstName': defineConfig<string | undefined>({
+  'security:passport-saml:attrMapFirstName': defineConfig<
+    NonBlankString | undefined
+  >({
     envVarName: 'SAML_ATTR_MAPPING_FIRST_NAME',
     defaultValue: undefined,
   }),
-  'security:passport-saml:attrMapLastName': defineConfig<string | undefined>({
+  'security:passport-saml:attrMapLastName': defineConfig<
+    NonBlankString | undefined
+  >({
     envVarName: 'SAML_ATTR_MAPPING_LAST_NAME',
     defaultValue: undefined,
   }),
-  'security:passport-saml:ABLCRule': defineConfig<string | undefined>({
+  'security:passport-saml:ABLCRule': defineConfig<NonBlankString | undefined>({
     envVarName: 'SAML_ABLC_RULE',
     defaultValue: undefined,
   }),
-  'security:passport-saml:entryPoint': defineConfig<string | undefined>({
-    envVarName: 'SAML_ENTRY_POINT',
-    defaultValue: undefined,
-  }),
-  'security:passport-saml:issuer': defineConfig<string | undefined>({
+  'security:passport-saml:entryPoint': defineConfig<NonBlankString | undefined>(
+    {
+      envVarName: 'SAML_ENTRY_POINT',
+      defaultValue: undefined,
+    },
+  ),
+  'security:passport-saml:issuer': defineConfig<NonBlankString | undefined>({
     envVarName: 'SAML_ISSUER',
     defaultValue: undefined,
   }),
-  'security:passport-saml:cert': defineConfig<string | undefined>({
+  'security:passport-saml:cert': defineConfig<NonBlankString | undefined>({
     envVarName: 'SAML_CERT',
     defaultValue: undefined,
   }),

+ 1 - 1
package.json

@@ -49,7 +49,7 @@
     "@playwright/test": "^1.49.1",
     "@swc-node/register": "^1.10.9",
     "@swc/core": "^1.5.25",
-    "@swc/helpers": "^0.5.11",
+    "@swc/helpers": "^0.5.18",
     "@testing-library/dom": "^10.4.0",
     "@testing-library/react": "^16.0.1",
     "@testing-library/react-hooks": "^8.0.1",

+ 1 - 1
packages/editor/package.json

@@ -35,7 +35,7 @@
     "@codemirror/language-data": "^6.5.1",
     "@codemirror/merge": "^6.8.0",
     "@codemirror/state": "^6.5.2",
-    "@codemirror/view": "^6.36.2",
+    "@codemirror/view": "^6.39.9",
     "@emoji-mart/data": "^1.2.1",
     "@emoji-mart/react": "^1.1.1",
     "@growi/core": "workspace:^",

+ 95 - 134
pnpm-lock.yaml

@@ -30,13 +30,13 @@ importers:
         version: 1.49.1
       '@swc-node/register':
         specifier: ^1.10.9
-        version: 1.10.9(@swc/core@1.10.7(@swc/helpers@0.5.15))(@swc/types@0.1.17)(typescript@5.0.4)
+        version: 1.10.9(@swc/core@1.10.7(@swc/helpers@0.5.18))(@swc/types@0.1.17)(typescript@5.0.4)
       '@swc/core':
         specifier: ^1.5.25
-        version: 1.10.7(@swc/helpers@0.5.15)
+        version: 1.10.7(@swc/helpers@0.5.18)
       '@swc/helpers':
-        specifier: ^0.5.11
-        version: 0.5.15
+        specifier: ^0.5.18
+        version: 0.5.18
       '@testing-library/dom':
         specifier: ^10.4.0
         version: 10.4.0
@@ -120,7 +120,7 @@ importers:
         version: 6.2.0
       ts-node:
         specifier: ^10.9.2
-        version: 10.9.2(@swc/core@1.10.7(@swc/helpers@0.5.15))(@types/node@20.19.17)(typescript@5.0.4)
+        version: 10.9.2(@swc/core@1.10.7(@swc/helpers@0.5.18))(@types/node@20.19.17)(typescript@5.0.4)
       ts-patch:
         specifier: ^3.2.0
         version: 3.2.0
@@ -1283,8 +1283,8 @@ importers:
         specifier: ^6.5.2
         version: 6.5.3
       '@codemirror/view':
-        specifier: ^6.36.2
-        version: 6.39.7
+        specifier: ^6.39.9
+        version: 6.39.9
       '@emoji-mart/data':
         specifier: ^1.2.1
         version: 1.2.1
@@ -1305,13 +1305,13 @@ importers:
         version: 2.11.8
       '@replit/codemirror-emacs':
         specifier: ^6.1.0
-        version: 6.1.0(@codemirror/autocomplete@6.18.4)(@codemirror/commands@6.8.0)(@codemirror/search@6.5.6)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)
+        version: 6.1.0(@codemirror/autocomplete@6.18.4)(@codemirror/commands@6.8.0)(@codemirror/search@6.5.6)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)
       '@replit/codemirror-vim':
         specifier: ^6.2.1
-        version: 6.2.1(@codemirror/commands@6.8.0)(@codemirror/language@6.12.1)(@codemirror/search@6.5.6)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)
+        version: 6.2.1(@codemirror/commands@6.8.0)(@codemirror/language@6.12.1)(@codemirror/search@6.5.6)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)
       '@replit/codemirror-vscode-keymap':
         specifier: ^6.0.2
-        version: 6.0.2(@codemirror/autocomplete@6.18.4)(@codemirror/commands@6.8.0)(@codemirror/language@6.12.1)(@codemirror/lint@6.8.1)(@codemirror/search@6.5.6)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)
+        version: 6.0.2(@codemirror/autocomplete@6.18.4)(@codemirror/commands@6.8.0)(@codemirror/language@6.12.1)(@codemirror/lint@6.8.1)(@codemirror/search@6.5.6)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)
       '@types/react':
         specifier: ^18.2.14
         version: 18.3.3
@@ -1320,28 +1320,28 @@ importers:
         version: 18.3.0
       '@uiw/codemirror-theme-eclipse':
         specifier: ^4.23.8
-        version: 4.23.8(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)
+        version: 4.23.8(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)
       '@uiw/codemirror-theme-kimbie':
         specifier: ^4.23.8
-        version: 4.23.8(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)
+        version: 4.23.8(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)
       '@uiw/codemirror-themes':
         specifier: ^4.23.8
-        version: 4.23.8(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)
+        version: 4.23.8(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)
       '@uiw/react-codemirror':
         specifier: ^4.23.8
-        version: 4.23.8(@babel/runtime@7.25.4)(@codemirror/autocomplete@6.18.4)(@codemirror/language@6.12.1)(@codemirror/lint@6.8.1)(@codemirror/search@6.5.6)(@codemirror/state@6.5.3)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.39.7)(codemirror@6.0.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+        version: 4.23.8(@babel/runtime@7.25.4)(@codemirror/autocomplete@6.18.4)(@codemirror/language@6.12.1)(@codemirror/lint@6.8.1)(@codemirror/search@6.5.6)(@codemirror/state@6.5.3)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.39.9)(codemirror@6.0.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
       bootstrap:
         specifier: '=5.3.2'
         version: 5.3.2(@popperjs/core@2.11.8)
       cm6-theme-basic-light:
         specifier: ^0.2.0
-        version: 0.2.0(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.3)
+        version: 0.2.0(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)(@lezer/highlight@1.2.3)
       cm6-theme-material-dark:
         specifier: ^0.2.0
-        version: 0.2.0(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.3)
+        version: 0.2.0(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)(@lezer/highlight@1.2.3)
       cm6-theme-nord:
         specifier: ^0.2.0
-        version: 0.2.0(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.3)
+        version: 0.2.0(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)(@lezer/highlight@1.2.3)
       codemirror:
         specifier: ^6.0.1
         version: 6.0.1
@@ -1389,7 +1389,7 @@ importers:
         version: 6.2.0
       y-codemirror.next:
         specifier: ^0.3.5
-        version: 0.3.5(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)(yjs@13.6.19)
+        version: 0.3.5(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)(yjs@13.6.19)
       y-socket.io:
         specifier: ^1.1.3
         version: 1.1.3(yjs@13.6.19)
@@ -2693,11 +2693,8 @@ packages:
   '@codemirror/theme-one-dark@6.1.2':
     resolution: {integrity: sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==}
 
-  '@codemirror/view@6.39.7':
-    resolution: {integrity: sha512-3Vif9hnNHJnl2YgOtkR/wzGzhYcQ8gy3LGdUhkLUU8xSBbgsTxrE8he/CMTpeINm5TgxLe2FmzvF6IYQL/BSAg==}
-
-  '@codemirror/view@6.39.8':
-    resolution: {integrity: sha512-1rASYd9Z/mE3tkbC9wInRlCNyCkSn+nLsiQKZhEDUUJiUfs/5FHDpCUDaQpoTIaNGeDc6/bhaEAyLmeEucEFPw==}
+  '@codemirror/view@6.39.9':
+    resolution: {integrity: sha512-miGSIfBOKC1s2oHoa80dp+BjtsL8sXsrgGlQnQuOcfvaedcQUtqddTmKbJSDkLl4mkgPvZyXuKic2HDNYcJLYA==}
 
   '@colors/colors@1.5.0':
     resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
@@ -5212,9 +5209,6 @@ packages:
   '@swc/counter@0.1.3':
     resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
 
-  '@swc/helpers@0.5.15':
-    resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
-
   '@swc/helpers@0.5.18':
     resolution: {integrity: sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==}
 
@@ -16251,7 +16245,7 @@ snapshots:
       '@azure/core-util': 1.10.0
       '@azure/logger': 1.1.2
       http-proxy-agent: 7.0.2
-      https-proxy-agent: 7.0.6(supports-color@10.0.0)
+      https-proxy-agent: 7.0.6
       tslib: 2.8.1
     transitivePeerDependencies:
       - supports-color
@@ -16353,7 +16347,7 @@ snapshots:
       '@babel/traverse': 7.24.6
       '@babel/types': 7.25.6
       convert-source-map: 2.0.0
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
       gensync: 1.0.0-beta.2
       json5: 2.2.3
       semver: 6.3.1
@@ -16524,7 +16518,7 @@ snapshots:
       '@babel/helper-split-export-declaration': 7.24.6
       '@babel/parser': 7.25.6
       '@babel/types': 7.25.6
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
@@ -16772,14 +16766,14 @@ snapshots:
     dependencies:
       '@codemirror/language': 6.12.1
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.7
+      '@codemirror/view': 6.39.9
       '@lezer/common': 1.5.0
 
   '@codemirror/commands@6.8.0':
     dependencies:
       '@codemirror/language': 6.12.1
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.7
+      '@codemirror/view': 6.39.9
       '@lezer/common': 1.5.0
 
   '@codemirror/lang-angular@0.1.2':
@@ -16819,7 +16813,7 @@ snapshots:
       '@codemirror/lang-javascript': 6.1.9
       '@codemirror/language': 6.12.1
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.7
+      '@codemirror/view': 6.39.9
       '@lezer/common': 1.5.0
       '@lezer/css': 1.1.3
       '@lezer/html': 1.3.6
@@ -16835,7 +16829,7 @@ snapshots:
       '@codemirror/language': 6.12.1
       '@codemirror/lint': 6.8.1
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.7
+      '@codemirror/view': 6.39.9
       '@lezer/common': 1.5.0
       '@lezer/javascript': 1.4.5
 
@@ -16857,7 +16851,7 @@ snapshots:
       '@codemirror/lang-html': 6.4.5
       '@codemirror/language': 6.12.1
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.7
+      '@codemirror/view': 6.39.9
       '@lezer/common': 1.5.0
       '@lezer/highlight': 1.2.3
       '@lezer/lr': 1.4.5
@@ -16868,7 +16862,7 @@ snapshots:
       '@codemirror/lang-html': 6.4.5
       '@codemirror/language': 6.12.1
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.7
+      '@codemirror/view': 6.39.9
       '@lezer/common': 1.5.0
       '@lezer/markdown': 1.0.5
 
@@ -16967,7 +16961,7 @@ snapshots:
   '@codemirror/language@6.12.1':
     dependencies:
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.7
+      '@codemirror/view': 6.39.9
       '@lezer/common': 1.5.0
       '@lezer/highlight': 1.2.3
       '@lezer/lr': 1.4.5
@@ -16980,21 +16974,21 @@ snapshots:
   '@codemirror/lint@6.8.1':
     dependencies:
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.7
+      '@codemirror/view': 6.39.9
       crelt: 1.0.6
 
   '@codemirror/merge@6.8.0':
     dependencies:
       '@codemirror/language': 6.12.1
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.7
+      '@codemirror/view': 6.39.9
       '@lezer/highlight': 1.2.3
       style-mod: 4.1.3
 
   '@codemirror/search@6.5.6':
     dependencies:
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.7
+      '@codemirror/view': 6.39.9
       crelt: 1.0.6
 
   '@codemirror/state@6.5.3':
@@ -17005,17 +16999,10 @@ snapshots:
     dependencies:
       '@codemirror/language': 6.12.1
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.8
+      '@codemirror/view': 6.39.9
       '@lezer/highlight': 1.2.3
 
-  '@codemirror/view@6.39.7':
-    dependencies:
-      '@codemirror/state': 6.5.3
-      crelt: 1.0.6
-      style-mod: 4.1.3
-      w3c-keyname: 2.2.8
-
-  '@codemirror/view@6.39.8':
+  '@codemirror/view@6.39.9':
     dependencies:
       '@codemirror/state': 6.5.3
       crelt: 1.0.6
@@ -17074,7 +17061,7 @@ snapshots:
 
   '@elastic/elasticsearch@7.17.13':
     dependencies:
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
       hpagent: 0.1.2
       ms: 2.1.3
       secure-json-parse: 2.7.0
@@ -17103,7 +17090,7 @@ snapshots:
     dependencies:
       '@opentelemetry/api': 1.9.0
       '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0)
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
       hpagent: 1.2.0
       ms: 2.1.3
       secure-json-parse: 3.0.2
@@ -17116,7 +17103,7 @@ snapshots:
     dependencies:
       '@opentelemetry/api': 1.9.0
       '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0)
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
       hpagent: 1.2.0
       ms: 2.1.3
       secure-json-parse: 4.0.0
@@ -17445,7 +17432,7 @@ snapshots:
       '@antfu/install-pkg': 1.1.0
       '@antfu/utils': 8.1.1
       '@iconify/types': 2.0.0
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
       globals: 15.15.0
       kolorist: 1.8.0
       local-pkg: 1.1.1
@@ -19128,23 +19115,23 @@ snapshots:
       immutable: 4.3.6
       redux: 4.2.1
 
-  '@replit/codemirror-emacs@6.1.0(@codemirror/autocomplete@6.18.4)(@codemirror/commands@6.8.0)(@codemirror/search@6.5.6)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)':
+  '@replit/codemirror-emacs@6.1.0(@codemirror/autocomplete@6.18.4)(@codemirror/commands@6.8.0)(@codemirror/search@6.5.6)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)':
     dependencies:
       '@codemirror/autocomplete': 6.18.4
       '@codemirror/commands': 6.8.0
       '@codemirror/search': 6.5.6
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.7
+      '@codemirror/view': 6.39.9
 
-  '@replit/codemirror-vim@6.2.1(@codemirror/commands@6.8.0)(@codemirror/language@6.12.1)(@codemirror/search@6.5.6)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)':
+  '@replit/codemirror-vim@6.2.1(@codemirror/commands@6.8.0)(@codemirror/language@6.12.1)(@codemirror/search@6.5.6)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)':
     dependencies:
       '@codemirror/commands': 6.8.0
       '@codemirror/language': 6.12.1
       '@codemirror/search': 6.5.6
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.7
+      '@codemirror/view': 6.39.9
 
-  '@replit/codemirror-vscode-keymap@6.0.2(@codemirror/autocomplete@6.18.4)(@codemirror/commands@6.8.0)(@codemirror/language@6.12.1)(@codemirror/lint@6.8.1)(@codemirror/search@6.5.6)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)':
+  '@replit/codemirror-vscode-keymap@6.0.2(@codemirror/autocomplete@6.18.4)(@codemirror/commands@6.8.0)(@codemirror/language@6.12.1)(@codemirror/lint@6.8.1)(@codemirror/search@6.5.6)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)':
     dependencies:
       '@codemirror/autocomplete': 6.18.4
       '@codemirror/commands': 6.8.0
@@ -19152,7 +19139,7 @@ snapshots:
       '@codemirror/lint': 6.8.1
       '@codemirror/search': 6.5.6
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.7
+      '@codemirror/view': 6.39.9
 
   '@restart/hooks@0.4.16(react@18.2.0)':
     dependencies:
@@ -20100,11 +20087,6 @@ snapshots:
       '@stoplight/yaml-ast-parser': 0.0.50
       tslib: 2.8.1
 
-  '@swc-node/core@1.13.3(@swc/core@1.10.7(@swc/helpers@0.5.15))(@swc/types@0.1.17)':
-    dependencies:
-      '@swc/core': 1.10.7(@swc/helpers@0.5.15)
-      '@swc/types': 0.1.17
-
   '@swc-node/core@1.13.3(@swc/core@1.10.7(@swc/helpers@0.5.18))(@swc/types@0.1.17)':
     dependencies:
       '@swc/core': 1.10.7(@swc/helpers@0.5.18)
@@ -20121,11 +20103,11 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@swc-node/register@1.10.9(@swc/core@1.10.7(@swc/helpers@0.5.15))(@swc/types@0.1.17)(typescript@5.0.4)':
+  '@swc-node/register@1.10.9(@swc/core@1.10.7(@swc/helpers@0.5.18))(@swc/types@0.1.17)(typescript@5.0.4)':
     dependencies:
-      '@swc-node/core': 1.13.3(@swc/core@1.10.7(@swc/helpers@0.5.15))(@swc/types@0.1.17)
+      '@swc-node/core': 1.13.3(@swc/core@1.10.7(@swc/helpers@0.5.18))(@swc/types@0.1.17)
       '@swc-node/sourcemap-support': 0.5.1
-      '@swc/core': 1.10.7(@swc/helpers@0.5.15)
+      '@swc/core': 1.10.7(@swc/helpers@0.5.18)
       colorette: 2.0.20
       debug: 4.4.3(supports-color@5.5.0)
       oxc-resolver: 1.12.0
@@ -20142,7 +20124,7 @@ snapshots:
       '@swc-node/sourcemap-support': 0.5.1
       '@swc/core': 1.10.7(@swc/helpers@0.5.18)
       colorette: 2.0.20
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
       oxc-resolver: 1.12.0
       pirates: 4.0.6
       tslib: 2.8.1
@@ -20216,23 +20198,6 @@ snapshots:
   '@swc/core-win32-x64-msvc@1.4.17':
     optional: true
 
-  '@swc/core@1.10.7(@swc/helpers@0.5.15)':
-    dependencies:
-      '@swc/counter': 0.1.3
-      '@swc/types': 0.1.17
-    optionalDependencies:
-      '@swc/core-darwin-arm64': 1.10.7
-      '@swc/core-darwin-x64': 1.10.7
-      '@swc/core-linux-arm-gnueabihf': 1.10.7
-      '@swc/core-linux-arm64-gnu': 1.10.7
-      '@swc/core-linux-arm64-musl': 1.10.7
-      '@swc/core-linux-x64-gnu': 1.10.7
-      '@swc/core-linux-x64-musl': 1.10.7
-      '@swc/core-win32-arm64-msvc': 1.10.7
-      '@swc/core-win32-ia32-msvc': 1.10.7
-      '@swc/core-win32-x64-msvc': 1.10.7
-      '@swc/helpers': 0.5.15
-
   '@swc/core@1.10.7(@swc/helpers@0.5.18)':
     dependencies:
       '@swc/counter': 0.1.3
@@ -20269,10 +20234,6 @@ snapshots:
 
   '@swc/counter@0.1.3': {}
 
-  '@swc/helpers@0.5.15':
-    dependencies:
-      tslib: 2.8.1
-
   '@swc/helpers@0.5.18':
     dependencies:
       tslib: 2.8.1
@@ -21331,7 +21292,7 @@ snapshots:
 
   '@types/zen-observable@0.8.3': {}
 
-  '@uiw/codemirror-extensions-basic-setup@4.23.8(@codemirror/autocomplete@6.18.4)(@codemirror/commands@6.8.0)(@codemirror/language@6.12.1)(@codemirror/lint@6.8.1)(@codemirror/search@6.5.6)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)':
+  '@uiw/codemirror-extensions-basic-setup@4.23.8(@codemirror/autocomplete@6.18.4)(@codemirror/commands@6.8.0)(@codemirror/language@6.12.1)(@codemirror/lint@6.8.1)(@codemirror/search@6.5.6)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)':
     dependencies:
       '@codemirror/autocomplete': 6.18.4
       '@codemirror/commands': 6.8.0
@@ -21339,38 +21300,38 @@ snapshots:
       '@codemirror/lint': 6.8.1
       '@codemirror/search': 6.5.6
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.7
+      '@codemirror/view': 6.39.9
 
-  '@uiw/codemirror-theme-eclipse@4.23.8(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)':
+  '@uiw/codemirror-theme-eclipse@4.23.8(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)':
     dependencies:
-      '@uiw/codemirror-themes': 4.23.8(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)
+      '@uiw/codemirror-themes': 4.23.8(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)
     transitivePeerDependencies:
       - '@codemirror/language'
       - '@codemirror/state'
       - '@codemirror/view'
 
-  '@uiw/codemirror-theme-kimbie@4.23.8(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)':
+  '@uiw/codemirror-theme-kimbie@4.23.8(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)':
     dependencies:
-      '@uiw/codemirror-themes': 4.23.8(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)
+      '@uiw/codemirror-themes': 4.23.8(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)
     transitivePeerDependencies:
       - '@codemirror/language'
       - '@codemirror/state'
       - '@codemirror/view'
 
-  '@uiw/codemirror-themes@4.23.8(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)':
+  '@uiw/codemirror-themes@4.23.8(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)':
     dependencies:
       '@codemirror/language': 6.12.1
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.7
+      '@codemirror/view': 6.39.9
 
-  '@uiw/react-codemirror@4.23.8(@babel/runtime@7.25.4)(@codemirror/autocomplete@6.18.4)(@codemirror/language@6.12.1)(@codemirror/lint@6.8.1)(@codemirror/search@6.5.6)(@codemirror/state@6.5.3)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.39.7)(codemirror@6.0.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+  '@uiw/react-codemirror@4.23.8(@babel/runtime@7.25.4)(@codemirror/autocomplete@6.18.4)(@codemirror/language@6.12.1)(@codemirror/lint@6.8.1)(@codemirror/search@6.5.6)(@codemirror/state@6.5.3)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.39.9)(codemirror@6.0.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
     dependencies:
       '@babel/runtime': 7.25.4
       '@codemirror/commands': 6.8.0
       '@codemirror/state': 6.5.3
       '@codemirror/theme-one-dark': 6.1.2
-      '@codemirror/view': 6.39.7
-      '@uiw/codemirror-extensions-basic-setup': 4.23.8(@codemirror/autocomplete@6.18.4)(@codemirror/commands@6.8.0)(@codemirror/language@6.12.1)(@codemirror/lint@6.8.1)(@codemirror/search@6.5.6)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)
+      '@codemirror/view': 6.39.9
+      '@uiw/codemirror-extensions-basic-setup': 4.23.8(@codemirror/autocomplete@6.18.4)(@codemirror/commands@6.8.0)(@codemirror/language@6.12.1)(@codemirror/lint@6.8.1)(@codemirror/search@6.5.6)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)
       codemirror: 6.0.1
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
@@ -21666,7 +21627,7 @@ snapshots:
 
   agent-base@6.0.2:
     dependencies:
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -22719,25 +22680,25 @@ snapshots:
 
   clsx@2.1.1: {}
 
-  cm6-theme-basic-light@0.2.0(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.3):
+  cm6-theme-basic-light@0.2.0(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)(@lezer/highlight@1.2.3):
     dependencies:
       '@codemirror/language': 6.12.1
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.7
+      '@codemirror/view': 6.39.9
       '@lezer/highlight': 1.2.3
 
-  cm6-theme-material-dark@0.2.0(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.3):
+  cm6-theme-material-dark@0.2.0(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)(@lezer/highlight@1.2.3):
     dependencies:
       '@codemirror/language': 6.12.1
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.7
+      '@codemirror/view': 6.39.9
       '@lezer/highlight': 1.2.3
 
-  cm6-theme-nord@0.2.0(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.3):
+  cm6-theme-nord@0.2.0(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)(@lezer/highlight@1.2.3):
     dependencies:
       '@codemirror/language': 6.12.1
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.7
+      '@codemirror/view': 6.39.9
       '@lezer/highlight': 1.2.3
 
   co@4.6.0: {}
@@ -22750,7 +22711,7 @@ snapshots:
       '@codemirror/lint': 6.8.1
       '@codemirror/search': 6.5.6
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.7
+      '@codemirror/view': 6.39.9
 
   collect-v8-coverage@1.0.2: {}
 
@@ -22920,7 +22881,7 @@ snapshots:
 
   connect-mongo@4.6.0(express-session@1.18.0)(mongodb@4.17.2(@aws-sdk/client-sso-oidc@3.600.0)):
     dependencies:
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
       express-session: 1.18.0
       kruptein: 3.0.6
       mongodb: 4.17.2(@aws-sdk/client-sso-oidc@3.600.0)
@@ -23768,7 +23729,7 @@ snapshots:
   engine.io-client@6.6.4:
     dependencies:
       '@socket.io/component-emitter': 3.1.2
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
       engine.io-parser: 5.2.3
       ws: 8.18.3
       xmlhttprequest-ssl: 2.1.2
@@ -23787,7 +23748,7 @@ snapshots:
       base64id: 2.0.0
       cookie: 0.7.2
       cors: 2.8.5
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
       engine.io-parser: 5.2.3
       ws: 8.18.3
     transitivePeerDependencies:
@@ -24408,7 +24369,7 @@ snapshots:
 
   follow-redirects@1.15.11(debug@4.4.3):
     optionalDependencies:
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
 
   for-each@0.3.3:
     dependencies:
@@ -24561,7 +24522,7 @@ snapshots:
   gaxios@6.7.1(encoding@0.1.13):
     dependencies:
       extend: 3.0.2
-      https-proxy-agent: 7.0.6(supports-color@10.0.0)
+      https-proxy-agent: 7.0.6
       is-stream: 2.0.0
       node-fetch: 2.7.0(encoding@0.1.13)
       uuid: 9.0.1
@@ -25169,14 +25130,14 @@ snapshots:
     dependencies:
       '@tootallnate/once': 2.0.0
       agent-base: 6.0.2
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
 
   http-proxy-agent@7.0.2:
     dependencies:
       agent-base: 7.1.4
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -25199,14 +25160,14 @@ snapshots:
   https-proxy-agent@5.0.1:
     dependencies:
       agent-base: 6.0.2
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
 
   https-proxy-agent@7.0.6:
     dependencies:
       agent-base: 7.1.4
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -25655,7 +25616,7 @@ snapshots:
 
   istanbul-lib-source-maps@4.0.1:
     dependencies:
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
       istanbul-lib-coverage: 3.2.2
       source-map: 0.6.1
     transitivePeerDependencies:
@@ -27193,7 +27154,7 @@ snapshots:
   micromark@4.0.0:
     dependencies:
       '@types/debug': 4.1.7
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
       decode-named-character-reference: 1.0.2
       devlop: 1.1.0
       micromark-core-commonmark: 2.0.1
@@ -27382,10 +27343,10 @@ snapshots:
     dependencies:
       async-mutex: 0.4.1
       camelcase: 6.3.0
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
       find-cache-dir: 3.3.2
       follow-redirects: 1.15.11(debug@4.4.3)
-      https-proxy-agent: 7.0.6(supports-color@10.0.0)
+      https-proxy-agent: 7.0.6
       mongodb: 5.9.2(@aws-sdk/credential-providers@3.600.0(@aws-sdk/client-sso-oidc@3.600.0))
       new-find-package-json: 2.0.0
       semver: 7.6.3
@@ -27489,7 +27450,7 @@ snapshots:
 
   mquery@4.0.3:
     dependencies:
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -27581,7 +27542,7 @@ snapshots:
 
   new-find-package-json@2.0.0:
     dependencies:
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -28272,7 +28233,7 @@ snapshots:
   passport-saml@3.2.4:
     dependencies:
       '@xmldom/xmldom': 0.7.13
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
       passport-strategy: 1.0.0
       xml-crypto: 2.1.5
       xml-encryption: 2.0.0
@@ -29461,7 +29422,7 @@ snapshots:
 
   require-in-the-middle@7.4.0:
     dependencies:
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
       module-details-from-path: 1.0.3
       resolve: 1.22.8
     transitivePeerDependencies:
@@ -29511,7 +29472,7 @@ snapshots:
 
   retry-request@4.2.2:
     dependencies:
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
       extend: 3.0.2
     transitivePeerDependencies:
       - supports-color
@@ -29967,7 +29928,7 @@ snapshots:
 
   socket.io-adapter@2.5.6:
     dependencies:
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
       ws: 8.18.3
     transitivePeerDependencies:
       - bufferutil
@@ -29977,7 +29938,7 @@ snapshots:
   socket.io-client@4.8.3:
     dependencies:
       '@socket.io/component-emitter': 3.1.2
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
       engine.io-client: 6.6.4
       socket.io-parser: 4.2.5
     transitivePeerDependencies:
@@ -29988,7 +29949,7 @@ snapshots:
   socket.io-parser@4.2.5:
     dependencies:
       '@socket.io/component-emitter': 3.1.2
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -29997,7 +29958,7 @@ snapshots:
       accepts: 1.3.8
       base64id: 2.0.0
       cors: 2.8.5
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
       engine.io: 6.6.5
       socket.io-adapter: 2.5.6
       socket.io-parser: 4.2.5
@@ -30426,7 +30387,7 @@ snapshots:
     dependencies:
       component-emitter: 1.3.1
       cookiejar: 2.1.4
-      debug: 4.4.3(supports-color@10.0.0)
+      debug: 4.4.3(supports-color@5.5.0)
       fast-safe-stringify: 2.1.1
       form-data: 4.0.4
       formidable: 3.5.4
@@ -30798,7 +30759,7 @@ snapshots:
 
   ts-interface-checker@0.1.13: {}
 
-  ts-node@10.9.2(@swc/core@1.10.7(@swc/helpers@0.5.15))(@types/node@20.19.17)(typescript@5.0.4):
+  ts-node@10.9.2(@swc/core@1.10.7(@swc/helpers@0.5.18))(@types/node@20.19.17)(typescript@5.0.4):
     dependencies:
       '@cspotcode/source-map-support': 0.8.1
       '@tsconfig/node10': 1.0.9
@@ -30816,7 +30777,7 @@ snapshots:
       v8-compile-cache-lib: 3.0.1
       yn: 3.1.1
     optionalDependencies:
-      '@swc/core': 1.10.7(@swc/helpers@0.5.15)
+      '@swc/core': 1.10.7(@swc/helpers@0.5.18)
 
   ts-node@10.9.2(@swc/core@1.10.7(@swc/helpers@0.5.18))(@types/node@20.19.17)(typescript@5.4.2):
     dependencies:
@@ -31855,10 +31816,10 @@ snapshots:
 
   xtend@4.0.2: {}
 
-  y-codemirror.next@0.3.5(@codemirror/state@6.5.3)(@codemirror/view@6.39.7)(yjs@13.6.19):
+  y-codemirror.next@0.3.5(@codemirror/state@6.5.3)(@codemirror/view@6.39.9)(yjs@13.6.19):
     dependencies:
       '@codemirror/state': 6.5.3
-      '@codemirror/view': 6.39.7
+      '@codemirror/view': 6.39.9
       lib0: 0.2.94
       yjs: 13.6.19