system_setting.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. package v1
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "strings"
  7. "github.com/labstack/echo/v4"
  8. "github.com/usememos/memos/api/auth"
  9. "github.com/usememos/memos/store"
  10. )
  11. type SystemSettingName string
  12. const (
  13. // SystemSettingServerIDName is the name of server id.
  14. SystemSettingServerIDName SystemSettingName = "server-id"
  15. // SystemSettingSecretSessionName is the name of secret session.
  16. SystemSettingSecretSessionName SystemSettingName = "secret-session"
  17. // SystemSettingAllowSignUpName is the name of allow signup setting.
  18. SystemSettingAllowSignUpName SystemSettingName = "allow-signup"
  19. // SystemSettingDisablePasswordLoginName is the name of disable password login setting.
  20. SystemSettingDisablePasswordLoginName SystemSettingName = "disable-password-login"
  21. // SystemSettingDisablePublicMemosName is the name of disable public memos setting.
  22. SystemSettingDisablePublicMemosName SystemSettingName = "disable-public-memos"
  23. // SystemSettingMaxUploadSizeMiBName is the name of max upload size setting.
  24. SystemSettingMaxUploadSizeMiBName SystemSettingName = "max-upload-size-mib"
  25. // SystemSettingAdditionalStyleName is the name of additional style.
  26. SystemSettingAdditionalStyleName SystemSettingName = "additional-style"
  27. // SystemSettingAdditionalScriptName is the name of additional script.
  28. SystemSettingAdditionalScriptName SystemSettingName = "additional-script"
  29. // SystemSettingCustomizedProfileName is the name of customized server profile.
  30. SystemSettingCustomizedProfileName SystemSettingName = "customized-profile"
  31. // SystemSettingStorageServiceIDName is the name of storage service ID.
  32. SystemSettingStorageServiceIDName SystemSettingName = "storage-service-id"
  33. // SystemSettingLocalStoragePathName is the name of local storage path.
  34. SystemSettingLocalStoragePathName SystemSettingName = "local-storage-path"
  35. // SystemSettingTelegramBotToken is the name of Telegram Bot Token.
  36. SystemSettingTelegramBotTokenName SystemSettingName = "telegram-bot-token"
  37. // SystemSettingMemoDisplayWithUpdatedTsName is the name of memo display with updated ts.
  38. SystemSettingMemoDisplayWithUpdatedTsName SystemSettingName = "memo-display-with-updated-ts"
  39. // SystemSettingAutoBackupIntervalName is the name of auto backup interval as seconds.
  40. SystemSettingAutoBackupIntervalName SystemSettingName = "auto-backup-interval"
  41. )
  42. // CustomizedProfile is the struct definition for SystemSettingCustomizedProfileName system setting item.
  43. type CustomizedProfile struct {
  44. // Name is the server name, default is `memos`
  45. Name string `json:"name"`
  46. // LogoURL is the url of logo image.
  47. LogoURL string `json:"logoUrl"`
  48. // Description is the server description.
  49. Description string `json:"description"`
  50. // Locale is the server default locale.
  51. Locale string `json:"locale"`
  52. // Appearance is the server default appearance.
  53. Appearance string `json:"appearance"`
  54. // ExternalURL is the external url of server. e.g. https://usermemos.com
  55. ExternalURL string `json:"externalUrl"`
  56. }
  57. func (key SystemSettingName) String() string {
  58. return string(key)
  59. }
  60. type SystemSetting struct {
  61. Name SystemSettingName `json:"name"`
  62. // Value is a JSON string with basic value.
  63. Value string `json:"value"`
  64. Description string `json:"description"`
  65. }
  66. type UpsertSystemSettingRequest struct {
  67. Name SystemSettingName `json:"name"`
  68. Value string `json:"value"`
  69. Description string `json:"description"`
  70. }
  71. const systemSettingUnmarshalError = `failed to unmarshal value from system setting "%v"`
  72. func (upsert UpsertSystemSettingRequest) Validate() error {
  73. switch settingName := upsert.Name; settingName {
  74. case SystemSettingServerIDName:
  75. return fmt.Errorf("updating %v is not allowed", settingName)
  76. case SystemSettingAllowSignUpName:
  77. var value bool
  78. if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
  79. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  80. }
  81. case SystemSettingDisablePasswordLoginName:
  82. var value bool
  83. if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
  84. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  85. }
  86. case SystemSettingDisablePublicMemosName:
  87. var value bool
  88. if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
  89. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  90. }
  91. case SystemSettingMaxUploadSizeMiBName:
  92. var value int
  93. if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
  94. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  95. }
  96. case SystemSettingAdditionalStyleName:
  97. var value string
  98. if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
  99. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  100. }
  101. case SystemSettingAdditionalScriptName:
  102. var value string
  103. if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
  104. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  105. }
  106. case SystemSettingCustomizedProfileName:
  107. customizedProfile := CustomizedProfile{
  108. Name: "memos",
  109. LogoURL: "",
  110. Description: "",
  111. Locale: "en",
  112. Appearance: "system",
  113. ExternalURL: "",
  114. }
  115. if err := json.Unmarshal([]byte(upsert.Value), &customizedProfile); err != nil {
  116. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  117. }
  118. case SystemSettingStorageServiceIDName:
  119. // Note: 0 is the default value(database) for storage service ID.
  120. value := 0
  121. if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
  122. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  123. }
  124. return nil
  125. case SystemSettingLocalStoragePathName:
  126. value := ""
  127. if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
  128. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  129. }
  130. case SystemSettingAutoBackupIntervalName:
  131. var value int
  132. if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
  133. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  134. }
  135. if value < 0 {
  136. return fmt.Errorf("must be positive")
  137. }
  138. case SystemSettingTelegramBotTokenName:
  139. if upsert.Value == "" {
  140. return nil
  141. }
  142. // Bot Token with Reverse Proxy shoule like `http.../bot<token>`
  143. if strings.HasPrefix(upsert.Value, "http") {
  144. slashIndex := strings.LastIndexAny(upsert.Value, "/")
  145. if strings.HasPrefix(upsert.Value[slashIndex:], "/bot") {
  146. return nil
  147. }
  148. return fmt.Errorf("token start with `http` must end with `/bot<token>`")
  149. }
  150. fragments := strings.Split(upsert.Value, ":")
  151. if len(fragments) != 2 {
  152. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  153. }
  154. case SystemSettingMemoDisplayWithUpdatedTsName:
  155. var value bool
  156. if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
  157. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  158. }
  159. default:
  160. return fmt.Errorf("invalid system setting name")
  161. }
  162. return nil
  163. }
  164. func (s *APIV1Service) registerSystemSettingRoutes(g *echo.Group) {
  165. g.POST("/system/setting", func(c echo.Context) error {
  166. ctx := c.Request().Context()
  167. userID, ok := c.Get(auth.UserIDContextKey).(int32)
  168. if !ok {
  169. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  170. }
  171. user, err := s.Store.GetUser(ctx, &store.FindUser{
  172. ID: &userID,
  173. })
  174. if err != nil {
  175. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  176. }
  177. if user == nil || user.Role != store.RoleHost {
  178. return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
  179. }
  180. systemSettingUpsert := &UpsertSystemSettingRequest{}
  181. if err := json.NewDecoder(c.Request().Body).Decode(systemSettingUpsert); err != nil {
  182. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post system setting request").SetInternal(err)
  183. }
  184. if err := systemSettingUpsert.Validate(); err != nil {
  185. return echo.NewHTTPError(http.StatusBadRequest, "invalid system setting").SetInternal(err)
  186. }
  187. if systemSettingUpsert.Name == SystemSettingDisablePasswordLoginName {
  188. var disablePasswordLogin bool
  189. if err := json.Unmarshal([]byte(systemSettingUpsert.Value), &disablePasswordLogin); err != nil {
  190. return echo.NewHTTPError(http.StatusBadRequest, "invalid system setting").SetInternal(err)
  191. }
  192. identityProviderList, err := s.Store.ListIdentityProviders(ctx, &store.FindIdentityProvider{})
  193. if err != nil {
  194. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert system setting").SetInternal(err)
  195. }
  196. if disablePasswordLogin && len(identityProviderList) == 0 {
  197. return echo.NewHTTPError(http.StatusForbidden, "Cannot disable passwords if no SSO identity provider is configured.")
  198. }
  199. }
  200. systemSetting, err := s.Store.UpsertSystemSetting(ctx, &store.SystemSetting{
  201. Name: systemSettingUpsert.Name.String(),
  202. Value: systemSettingUpsert.Value,
  203. Description: systemSettingUpsert.Description,
  204. })
  205. if err != nil {
  206. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert system setting").SetInternal(err)
  207. }
  208. return c.JSON(http.StatusOK, convertSystemSettingFromStore(systemSetting))
  209. })
  210. g.GET("/system/setting", func(c echo.Context) error {
  211. ctx := c.Request().Context()
  212. userID, ok := c.Get(auth.UserIDContextKey).(int32)
  213. if !ok {
  214. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  215. }
  216. user, err := s.Store.GetUser(ctx, &store.FindUser{
  217. ID: &userID,
  218. })
  219. if err != nil {
  220. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  221. }
  222. if user == nil || user.Role != store.RoleHost {
  223. return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
  224. }
  225. list, err := s.Store.ListSystemSettings(ctx, &store.FindSystemSetting{})
  226. if err != nil {
  227. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find system setting list").SetInternal(err)
  228. }
  229. systemSettingList := make([]*SystemSetting, 0, len(list))
  230. for _, systemSetting := range list {
  231. systemSettingList = append(systemSettingList, convertSystemSettingFromStore(systemSetting))
  232. }
  233. return c.JSON(http.StatusOK, systemSettingList)
  234. })
  235. }
  236. func convertSystemSettingFromStore(systemSetting *store.SystemSetting) *SystemSetting {
  237. return &SystemSetting{
  238. Name: SystemSettingName(systemSetting.Name),
  239. Value: systemSetting.Value,
  240. Description: systemSetting.Description,
  241. }
  242. }