CVE-2022-4683 (published 2022-12-23T12:15Z)

Sensitive Cookie in HTTPS Session Without 'Secure' Attribute in GitHub repository usememos/memos prior to 0.9.0.

CVSS v3 base score: 6.5, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N)

Weakness information:
cwe_id: CWE-311
cwe_name: Missing Encryption of Sensitive Data
description: The product does not encrypt sensitive or critical information before storage or transmission.
extended_description: The lack of proper data encryption passes up the guarantees of confidentiality, integrity, and accountability that properly implemented encryption conveys.
         

Fix information:
repo_url: https://github.com/usememos/memos
commit_details: repo_url: https://github.com/usememos/memos
author: boojack
author_date: 2022-12-22 19:48:44+08:00
msg: feat: customize system profile (#828)
repository: repo_url: https://github.com/usememos/memos
repo_name: usememos/memos
description: An open source, lightweight note-taking service. Easily capture and share your great thoughts.
repo_language: Go
owner: usememos
file_changes: filename: system_setting.go
change_type: ModificationType.MODIFY
diff: @@ -3,6 +3,8 @@ package api
 import (
 	"encoding/json"
 	"fmt"
+
+	"golang.org/x/exp/slices"
 )
 
 type SystemSettingName string
@@ -24,6 +26,12 @@ type CustomizedProfile struct {
 	Name string `json:"name"`
 	// IconURL is the url of icon image.
 	IconURL string `json:"iconUrl"`
+	// Description is the server description.
+	Description string `json:"description"`
+	// Locale is the server default locale.
+	Locale string `json:"locale"`
+	// Appearance is the server default appearance.
+	Appearance string `json:"appearance"`
 	// ExternalURL is the external url of server. e.g. https://usermemos.com
 	ExternalURL string `json:"externalUrl"`
 }
@@ -90,15 +98,24 @@ func (upsert SystemSettingUpsert) Validate() error {
 			return fmt.Errorf("failed to unmarshal system setting additional script value")
 		}
 	} else if upsert.Name == SystemSettingCustomizedProfileName {
-		value := CustomizedProfile{
+		customizedProfile := CustomizedProfile{
 			Name:        "memos",
 			IconURL:     "",
+			Description: "",
+			Locale:      "en",
+			Appearance:  "system",
 			ExternalURL: "",
 		}
-		err := json.Unmarshal([]byte(upsert.Value), &value)
+		err := json.Unmarshal([]byte(upsert.Value), &customizedProfile)
 		if err != nil {
 			return fmt.Errorf("failed to unmarshal system setting customized profile value")
 		}
+		if !slices.Contains(UserSettingLocaleValue, customizedProfile.Locale) {
+			return fmt.Errorf("invalid locale value")
+		}
+		if !slices.Contains(UserSettingAppearanceValue, customizedProfile.Appearance) {
+			return fmt.Errorf("invalid appearance value")
+		}
 	} else {
 		return fmt.Errorf("invalid system setting name")
 	}

programming_language: Go
filename: user_setting.go
change_type: ModificationType.MODIFY
diff: @@ -3,6 +3,8 @@ package api
 import (
 	"encoding/json"
 	"fmt"
+
+	"golang.org/x/exp/slices"
 )
 
 type UserSettingKey string
@@ -60,32 +62,16 @@ func (upsert UserSettingUpsert) Validate() error {
 		if err != nil {
 			return fmt.Errorf("failed to unmarshal user setting locale value")
 		}
-
-		invalid := true
-		for _, value := range UserSettingLocaleValue {
-			if localeValue == value {
-				invalid = false
-				break
-			}
-		}
-		if invalid {
+		if !slices.Contains(UserSettingLocaleValue, localeValue) {
 			return fmt.Errorf("invalid user setting locale value")
 		}
 	} else if upsert.Key == UserSettingAppearanceKey {
-		appearanceValue := "light"
+		appearanceValue := "system"
 		err := json.Unmarshal([]byte(upsert.Value), &appearanceValue)
 		if err != nil {
 			return fmt.Errorf("failed to unmarshal user setting appearance value")
 		}
-
-		invalid := true
-		for _, value := range UserSettingAppearanceValue {
-			if appearanceValue == value {
-				invalid = false
-				break
-			}
-		}
-		if invalid {
+		if !slices.Contains(UserSettingAppearanceValue, appearanceValue) {
 			return fmt.Errorf("invalid user setting appearance value")
 		}
 	} else if upsert.Key == UserSettingMemoVisibilityKey {
@@ -94,15 +80,7 @@ func (upsert UserSettingUpsert) Validate() error {
 		if err != nil {
 			return fmt.Errorf("failed to unmarshal user setting memo visibility value")
 		}
-
-		invalid := true
-		for _, value := range UserSettingMemoVisibilityValue {
-			if memoVisibilityValue == value {
-				invalid = false
-				break
-			}
-		}
-		if invalid {
+		if !slices.Contains(UserSettingMemoVisibilityValue, memoVisibilityValue) {
 			return fmt.Errorf("invalid user setting memo visibility value")
 		}
 	} else if upsert.Key == UserSettingMemoDisplayTsOptionKey {
@@ -111,15 +89,7 @@ func (upsert UserSettingUpsert) Validate() error {
 		if err != nil {
 			return fmt.Errorf("failed to unmarshal user setting memo display ts option")
 		}
-
-		invalid := true
-		for _, value := range UserSettingMemoDisplayTsOptionKeyValue {
-			if memoDisplayTsOption == value {
-				invalid = false
-				break
-			}
-		}
-		if invalid {
+		if !slices.Contains(UserSettingMemoDisplayTsOptionKeyValue, memoDisplayTsOption) {
 			return fmt.Errorf("invalid user setting memo display ts option value")
 		}
 	} else {

programming_language: Go
filename: go.mod
change_type: ModificationType.MODIFY
diff: @@ -38,11 +38,14 @@ require (
 	github.com/valyala/bytebufferpool v1.0.0 // indirect
 	github.com/valyala/fasttemplate v1.2.1 // indirect
 	github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
-	golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
+	golang.org/x/sys v0.1.0 // indirect
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
 	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
-require github.com/segmentio/analytics-go v3.1.0+incompatible
+require (
+	github.com/segmentio/analytics-go v3.1.0+incompatible
+	golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15
+)

programming_language: Go
filename: go.sum
change_type: ModificationType.MODIFY
diff: @@ -70,14 +70,16 @@ github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEAB
 github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 h1:5oN1Pz/eDhCpbMbLstvIPa0b/BEQo6g6nwV3pLjfM6w=
+golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
 golang.org/x/net v0.0.0-20220728030405-41545e8bf201 h1:bvOltf3SADAfG05iRml8lAB3qjoEX5RCyN4K6G5v3N0=
 golang.org/x/net v0.0.0-20220728030405-41545e8bf201/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=

programming_language: Go
filename: acl.go
change_type: ModificationType.MODIFY
diff: @@ -27,6 +27,7 @@ func setUserSession(ctx echo.Context, user *api.User) error {
 		Path:     "/",
 		MaxAge:   3600 * 24 * 30,
 		HttpOnly: true,
+		Secure:   true,
 	}
 	sess.Values[userIDContextKey] = user.ID
 	err := sess.Save(ctx.Request(), ctx.Response())

programming_language: Go
filename: system.go
change_type: ModificationType.MODIFY
diff: @@ -47,7 +47,12 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
 			AdditionalStyle:  "",
 			AdditionalScript: "",
 			CustomizedProfile: api.CustomizedProfile{
-				Name: "memos",
+				Name:        "memos",
+				IconURL:     "",
+				Description: "",
+				Locale:      "en",
+				Appearance:  "system",
+				ExternalURL: "",
 			},
 		}
 
@@ -73,6 +78,9 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
 				systemStatus.CustomizedProfile = api.CustomizedProfile{
 					Name:        valueMap["name"].(string),
 					IconURL:     valueMap["iconUrl"].(string),
+					Description: valueMap["description"].(string),
+					Locale:      valueMap["locale"].(string),
+					Appearance:  valueMap["appearance"].(string),
 					ExternalURL: valueMap["externalUrl"].(string),
 				}
 			}

programming_language: Go
filename: AppearanceSelect.tsx
change_type: ModificationType.MODIFY
diff: @@ -1,16 +1,19 @@
 import { Option, Select } from "@mui/joy";
+import { FC } from "react";
 import { useTranslation } from "react-i18next";
-import { useGlobalStore, useUserStore } from "../store/module";
 import Icon from "./Icon";
 
+interface Props {
+  value: Appearance;
+  onChange: (appearance: Appearance) => void;
+  className?: string;
+}
+
 const appearanceList = ["system", "light", "dark"];
 
-const AppearanceSelect = () => {
+const AppearanceSelect: FC<Props> = (props: Props) => {
+  const { onChange, value, className } = props;
   const { t } = useTranslation();
-  const globalStore = useGlobalStore();
-  const userStore = useUserStore();
-  const { appearance } = globalStore.state;
-  const user = userStore.state.user;
 
   const getPrefixIcon = (apperance: Appearance) => {
     const className = "w-4 h-auto";
@@ -24,22 +27,19 @@ const AppearanceSelect = () => {
   };
 
   const handleSelectChange = async (appearance: Appearance) => {
-    if (user) {
-      await userStore.upsertUserSetting("appearance", appearance);
-    }
-    globalStore.setAppearance(appearance);
+    onChange(appearance);
   };
 
   return (
     <Select
-      className="!min-w-[10rem] w-auto text-sm"
-      value={appearance}
+      className={`!min-w-[10rem] w-auto whitespace-nowrap ${className ?? ""}`}
+      value={value}
       onChange={(_, appearance) => {
         if (appearance) {
           handleSelectChange(appearance);
         }
       }}
-      startDecorator={getPrefixIcon(appearance)}
+      startDecorator={getPrefixIcon(value)}
     >
       {appearanceList.map((item) => (
         <Option key={item} value={item} className="whitespace-nowrap">

programming_language: TypeScript
filename: LocaleSelect.tsx
change_type: ModificationType.ADD
diff: @@ -0,0 +1,37 @@
+import { Option, Select } from "@mui/joy";
+import { FC } from "react";
+import Icon from "./Icon";
+
+interface Props {
+  value: Locale;
+  onChange: (locale: Locale) => void;
+  className?: string;
+}
+
+const LocaleSelect: FC<Props> = (props: Props) => {
+  const { onChange, value, className } = props;
+
+  const handleSelectChange = async (locale: Locale) => {
+    onChange(locale);
+  };
+
+  return (
+    <Select
+      className={`!min-w-[10rem] w-auto whitespace-nowrap ${className ?? ""}`}
+      startDecorator={<Icon.Globe className="w-4 h-auto" />}
+      value={value}
+      onChange={(_, value) => handleSelectChange(value as Locale)}
+    >
+      <Option value="en">English</Option>
+      <Option value="zh">中文</Option>
+      <Option value="vi">Tiếng Việt</Option>
+      <Option value="fr">French</Option>
+      <Option value="nl">Nederlands</Option>
+      <Option value="sv">Svenska</Option>
+      <Option value="de">German</Option>
+      <Option value="es">Español</Option>
+    </Select>
+  );
+};
+
+export default LocaleSelect;

programming_language: TypeScript
filename: PreferencesSection.tsx
change_type: ModificationType.MODIFY
diff: @@ -2,49 +2,15 @@ import { Select, Switch, Option } from "@mui/joy";
 import { useTranslation } from "react-i18next";
 import { useGlobalStore, useUserStore } from "../../store/module";
 import { VISIBILITY_SELECTOR_ITEMS, MEMO_DISPLAY_TS_OPTION_SELECTOR_ITEMS } from "../../helpers/consts";
-import Icon from "../Icon";
 import AppearanceSelect from "../AppearanceSelect";
+import LocaleSelect from "../LocaleSelect";
 import "../../less/settings/preferences-section.less";
 
-const localeSelectorItems = [
-  {
-    text: "English",
-    value: "en",
-  },
-  {
-    text: "中文",
-    value: "zh",
-  },
-  {
-    text: "Tiếng Việt",
-    value: "vi",
-  },
-  {
-    text: "French",
-    value: "fr",
-  },
-  {
-    text: "Nederlands",
-    value: "nl",
-  },
-  {
-    text: "Svenska",
-    value: "sv",
-  },
-  {
-    text: "German",
-    value: "de",
-  },
-  {
-    text: "Español",
-    value: "es",
-  },
-];
-
 const PreferencesSection = () => {
   const { t } = useTranslation();
   const globalStore = useGlobalStore();
   const userStore = useUserStore();
+  const { appearance, locale } = globalStore.state;
   const { setting, localSetting } = userStore.state.user as User;
   const visibilitySelectorItems = VISIBILITY_SELECTOR_ITEMS.map((item) => {
     return {
@@ -60,9 +26,14 @@ const PreferencesSection = () => {
     };
   });
 
-  const handleLocaleChanged = async (value: string) => {
-    await userStore.upsertUserSetting("locale", value);
-    globalStore.setLocale(value as Locale);
+  const handleLocaleSelectChange = async (locale: Locale) => {
+    await userStore.upsertUserSetting("locale", locale);
+    globalStore.setLocale(locale);
+  };
+
+  const handleAppearanceSelectChange = async (appearance: Appearance) => {
+    await userStore.upsertUserSetting("appearance", appearance);
+    globalStore.setAppearance(appearance);
   };
 
   const handleDefaultMemoVisibilityChanged = async (value: string) => {
@@ -82,26 +53,11 @@ const PreferencesSection = () => {
       <p className="title-text">{t("common.basic")}</p>
       <div className="form-label selector">
         <span className="normal-text">{t("common.language")}</span>
-        <Select
-          className="!min-w-[10rem] w-auto text-sm"
-          value={setting.locale}
-          onChange={(_, locale) => {
-            if (locale) {
-              handleLocaleChanged(locale);
-            }
-          }}
-          startDecorator={<Icon.Globe className="w-4 h-auto" />}
-        >
-          {localeSelectorItems.map((item) => (
-            <Option key={item.value} value={item.value} className="whitespace-nowrap">
-              {item.text}
-            </Option>
-          ))}
-        </Select>
+        <LocaleSelect value={locale} onChange={handleLocaleSelectChange} />
       </div>
       <div className="form-label selector">
         <span className="normal-text">{t("setting.preference-section.theme")}</span>
-        <AppearanceSelect />
+        <AppearanceSelect value={appearance} onChange={handleAppearanceSelectChange} />
       </div>
       <p className="title-text">{t("setting.preference")}</p>
       <div className="form-label selector">

programming_language: TypeScript
filename: UpdateCustomizedProfileDialog.tsx
change_type: ModificationType.MODIFY
diff: @@ -5,6 +5,8 @@ import * as api from "../helpers/api";
 import Icon from "./Icon";
 import { generateDialog } from "./Dialog";
 import toastHelper from "./Toast";
+import LocaleSelect from "./LocaleSelect";
+import AppearanceSelect from "./AppearanceSelect";
 
 type Props = DialogProps;
 
@@ -39,6 +41,33 @@ const UpdateCustomizedProfileDialog: React.FC<Props> = ({ destroy }: Props) => {
     });
   };
 
+  const handleDescriptionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
+    setState((state) => {
+      return {
+        ...state,
+        description: e.target.value as string,
+      };
+    });
+  };
+
+  const handleLocaleSelectChange = (locale: Locale) => {
+    setState((state) => {
+      return {
+        ...state,
+        locale: locale,
+      };
+    });
+  };
+
+  const handleAppearanceSelectChange = (appearance: Appearance) => {
+    setState((state) => {
+      return {
+        ...state,
+        appearance: appearance,
+      };
+    });
+  };
+
   const handleSaveBtnClick = async () => {
     if (state.name === "" || state.iconUrl === "") {
       toastHelper.error(t("message.fill-all"));
@@ -61,13 +90,13 @@ const UpdateCustomizedProfileDialog: React.FC<Props> = ({ destroy }: Props) => {
 
   return (
     <>
-      <div className="dialog-header-container !w-64">
+      <div className="dialog-header-container">
         <p className="title-text">{t("setting.system-section.customize-server.title")}</p>
         <button className="btn close-btn" onClick={handleCloseBtnClick}>
           <Icon.X />
         </button>
       </div>
-      <div className="dialog-content-container">
+      <div className="dialog-content-container !w-80">
         <p className="text-sm mb-1">
           {t("setting.system-section.server-name")}
           <span className="text-sm text-gray-400 ml-1">({t("setting.system-section.customize-server.default")})</span>
@@ -75,6 +104,12 @@ const UpdateCustomizedProfileDialog: React.FC<Props> = ({ destroy }: Props) => {
         <input type="text" className="input-text" value={state.name} onChange={handleNameChanged} />
         <p className="text-sm mb-1 mt-2">{t("setting.system-section.customize-server.icon-url")}</p>
         <input type="text" className="input-text" value={state.iconUrl} onChange={handleIconUrlChanged} />
+        <p className="text-sm mb-1 mt-2">Description</p>
+        <input type="text" className="input-text" value={state.description} onChange={handleDescriptionChanged} />
+        <p className="text-sm mb-1 mt-2">Server locale</p>
+        <LocaleSelect className="w-full" value={state.locale} onChange={handleLocaleSelectChange} />
+        <p className="text-sm mb-1 mt-2">Server appearance</p>
+        <AppearanceSelect className="w-full" value={state.appearance} onChange={handleAppearanceSelectChange} />
         <div className="mt-4 w-full flex flex-row justify-end items-center space-x-2">
           <span className="btn-text" onClick={handleCloseBtnClick}>
             {t("common.cancel")}

programming_language: TypeScript
filename: Auth.tsx
change_type: ModificationType.MODIFY
diff: @@ -1,4 +1,3 @@
-import { Option, Select } from "@mui/joy";
 import { useEffect, useState } from "react";
 import { useTranslation } from "react-i18next";
 import { useNavigate } from "react-router-dom";
@@ -9,6 +8,7 @@ import useLoading from "../hooks/useLoading";
 import Icon from "../components/Icon";
 import toastHelper from "../components/Toast";
 import AppearanceSelect from "../components/AppearanceSelect";
+import LocaleSelect from "../components/LocaleSelect";
 import "../less/auth.less";
 
 const validateConfig: ValidatorConfig = {
@@ -19,12 +19,12 @@ const validateConfig: ValidatorConfig = {
 };
 
 const Auth = () => {
-  const { t, i18n } = useTranslation();
+  const { t } = useTranslation();
   const navigate = useNavigate();
   const globalStore = useGlobalStore();
   const userStore = useUserStore();
   const actionBtnLoadingState = useLoading(false);
-  const systemStatus = globalStore.state.systemStatus;
+  const { appearance, locale, systemStatus } = globalStore.state;
   const mode = systemStatus.profile.mode;
   const [username, setUsername] = useState(mode === "dev" ? "demohero" : "");
   const [password, setPassword] = useState(mode === "dev" ? "secret" : "");
@@ -43,6 +43,14 @@ const Auth = () => {
     setPassword(text);
   };
 
+  const handleLocaleSelectChange = (locale: Locale) => {
+    globalStore.setLocale(locale);
+  };
+
+  const handleAppearanceSelectChange = (appearance: Appearance) => {
+    globalStore.setAppearance(appearance);
+  };
+
   const handleSigninBtnsClick = async () => {
     if (actionBtnLoadingState.isLoading) {
       return;
@@ -109,10 +117,6 @@ const Auth = () => {
     actionBtnLoadingState.setFinish();
   };
 
-  const handleLocaleItemClick = (locale: Locale) => {
-    globalStore.setLocale(locale);
-  };
-
   return (
     <div className="page-wrapper auth">
       <div className="page-container">
@@ -122,7 +126,7 @@ const Auth = () => {
               <img className="logo-img" src={systemStatus.customizedProfile.iconUrl} alt="" />
               <p className="logo-text">{systemStatus.customizedProfile.name}</p>
             </div>
-            <p className="slogan-text">{t("slogan")}</p>
+            <p className="slogan-text">{systemStatus.customizedProfile.description || t("slogan")}</p>
           </div>
           <div className={`page-content-container ${actionBtnLoadingState.isLoading ? "requesting" : ""}`}>
             <div className="form-item-container input-form-container">
@@ -167,22 +171,8 @@ const Auth = () => {
           {!systemStatus?.host && <p className="tip-text">{t("auth.host-tip")}</p>}
         </div>
         <div className="flex flex-row items-center justify-center w-full gap-2">
-          <Select
-            className="!min-w-[9rem] w-auto whitespace-nowrap"
-            startDecorator={<Icon.Globe className="w-4 h-auto" />}
-            value={i18n.language}
-            onChange={(_, value) => handleLocaleItemClick(value as Locale)}
-          >
-            <Option value="en">English</Option>
-            <Option value="zh">中文</Option>
-            <Option value="vi">Tiếng Việt</Option>
-            <Option value="fr">French</Option>
-            <Option value="nl">Nederlands</Option>
-            <Option value="sv">Svenska</Option>
-            <Option value="de">German</Option>
-            <Option value="es">Español</Option>
-          </Select>
-          <AppearanceSelect />
+          <LocaleSelect value={locale} onChange={handleLocaleSelectChange} />
+          <AppearanceSelect value={appearance} onChange={handleAppearanceSelectChange} />
         </div>
       </div>
     </div>

programming_language: TypeScript
filename: global.ts
change_type: ModificationType.MODIFY
diff: @@ -13,7 +13,10 @@ export const initialGlobalState = async () => {
       additionalScript: "",
       customizedProfile: {
         name: "memos",
-        iconUrl: "/logo.webp",
+        iconUrl: "https://usememos.com/logo.webp",
+        description: "",
+        locale: "en",
+        appearance: "system",
         externalUrl: "",
       },
     } as SystemStatus,
@@ -31,6 +34,8 @@ export const initialGlobalState = async () => {
     const { data } = (await api.getSystemStatus()).data;
     if (data) {
       defaultGlobalState.systemStatus = data;
+      defaultGlobalState.locale = data.customizedProfile.locale;
+      defaultGlobalState.appearance = data.customizedProfile.appearance;
     }
   } catch (error) {
     // do nth

programming_language: TypeScript
filename: global.ts
change_type: ModificationType.MODIFY
diff: @@ -23,7 +23,10 @@ const globalSlice = createSlice({
       additionalScript: "",
       customizedProfile: {
         name: "memos",
-        iconUrl: "/logo.webp",
+        iconUrl: "https://usememos.com/logo.webp",
+        description: "",
+        locale: "en",
+        appearance: "system",
         externalUrl: "",
       },
     },

programming_language: TypeScript
filename: system.d.ts
change_type: ModificationType.MODIFY
diff: @@ -6,6 +6,9 @@ interface Profile {
 interface CustomizedProfile {
   name: string;
   iconUrl: string;
+  description: string;
+  locale: Locale;
+  appearance: Appearance;
   externalUrl: string;
 }
 

programming_language: TypeScript